bookmarks working with new batch endpoint on cards

This commit is contained in:
Victor committed 2024-01-09 19:37:14 -08:00
1 parent eb4240d072
commit 1404340d45
13 files changed
+207 -45

No files matched your search

+5 -6
View File
@@ -25,12 +25,11 @@ const METHOD_OMITS_BODY: { [key: string]: boolean } = {
const MakeRequest = <UrlRouteArgs, Request, Response, UrlParams>(routeSetup: RouteSetup<UrlRouteArgs>) : (urlRouteArgs: UrlRouteArgs, request: Request, queries?: UrlParams) => Promise<Response> => {
return async function(urlRouteArgs: UrlRouteArgs, request: Request, queries?: any) : Promise<Response> {
const prepedQueries = queries ? Object.keys(queries).reduce((obj,key) => {
let current = queries[key]
return { [key]: Array.isArray(current) ? current.join("&tokens=") : current };
},{}) : null;
console.log("❓",prepedQueries);
const endpoint = `${ routeSetup.routingFunction(urlRouteArgs) }${ prepedQueries ? "?" + new URLSearchParams(prepedQueries) : "" }`;
const newQueries = queries ? Object.keys(queries).map((key, i) => {
return `${ i ? "&" : "" }${ key }=${ Array.isArray(queries[key]) ? queries[key].join(`&${ key }=`) : queries[key] }`
}).join("") : null;
const endpoint = `${ routeSetup.routingFunction(urlRouteArgs) }${ newQueries ? "?" + newQueries : "" }`;
const method = routeSetup.method;
const methodOmitsBody = METHOD_OMITS_BODY[method] || false;
@@ -0,0 +1,24 @@
import MakeRequest from "../MakeRequest";
export interface Bookmark {
entity_token: string,
entity_type: string,
is_bookmarked: boolean,
maybe_bookmark_token?: string
}
export interface GetBookmarksRequest {}
export interface GetBookmarksResponse {
success: boolean,
bookmarks?: Bookmark[]
}
export interface GetBookmarksQueries {
tokens: string[],
}
export const GetBookmarks = MakeRequest<string, GetBookmarksRequest, GetBookmarksResponse, GetBookmarksQueries>({
method: "GET",
routingFunction: () => `/v1/user_bookmarks/batch`,
});
@@ -13,10 +13,15 @@
cursor: pointer;
outline: inherit;
font-size: 16px;
transition: 0.2s;
transition: 0.2s all;
padding: 3px 8px;
min-width: 52px;
flex-wrap: none;
overflow: hidden;
width: fit-content;
height: 1.75rem;
border-radius: 0.5rem;
// white-space: nowrap;
position: relative;
&.shadow {
box-shadow: 0 0 12px rgba($bg, 0.4) !important;
@@ -74,8 +79,55 @@
color: $white;
font-size: 14px;
font-weight: 400 !important;
height: 100%;
opacity: 0.9;
transition: 0.2s;
// flex-grow: 1;
position: relative;
// width: 1.5rem;
// display: inline-block;
// width: 100%;
// height: max-content;
.favorite-text-wrapper {
position: relative;
}
span {
display: block;
top: 0;
// height: fit-content;
// width: 2.5rem;
background-color: green;
position: absolute;
}
}
@keyframes workdots {
0% { opacity: .33 }
33% { opacity: .66 }
66% { opacity: .66 }
100% { opacity: .33 }
}
.fy-workdots {
width: 1rem;
height: 1rem;
fill: #fff;
circle {
animation: .66s infinite workdots;
animation-timing-function: ease-in-out;
}
:nth-child(1) {
animation-delay: 0;
}
:nth-child(2) {
animation-delay: .22s;
}
:nth-child(3) {
animation-delay: .44s;
}
}
@keyframes pulse {
@@ -2,12 +2,15 @@ import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBookmark } from "@fortawesome/pro-solid-svg-icons";
import { faBookmark as faBookmarkOutline } from "@fortawesome/pro-regular-svg-icons";
import { a, useTransition } from "@react-spring/web";
import { basicTransition } from "resources";
import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css";
import "./BookmarkButton.scss";
// import useShortenNumber from "hooks/useShortenNumber";
interface BookmarkButtonProps {
busy?: boolean;
entityToken?: string;
entityType?: string;
initialToggled?: boolean;
@@ -18,6 +21,7 @@ interface BookmarkButtonProps {
}
export default function BookmarkButton({
busy,
entityToken = "",
entityType = "",
initialToggled = false,
@@ -29,6 +33,7 @@ export default function BookmarkButton({
const isToggled = initialToggled; // toggled value managed externally via usebookmarks, this may change
// const [isToggled, setIsToggled] = useState(initialToggled);
const [isLoading, setIsLoading] = useState(false);
const [animating,animatingSet] = useState(false);
const handleClick = async (event: React.MouseEvent) => {
event.preventDefault();
@@ -54,6 +59,15 @@ export default function BookmarkButton({
const toolTip = isToggled ? "Unbookmark" : "Bookmark";
// let favoriteCountShort = useShortenNumber(favoriteCount || 0);
const index = busy ? 0 : isToggled ? 1 : 2;
const transitions = useTransition(index, basicTransition({
onRest: () => animatingSet(false),
onStart: () => {
animatingSet(true)
}
}));
return (
<div className="d-flex gap-2">
<Tippy
@@ -74,7 +88,28 @@ export default function BookmarkButton({
icon={isToggled ? faBookmark : faBookmarkOutline}
className={`${iconClass} me-2`}
/>
<p className="favorite-text">{isToggled ? "Saved" : "Save"}</p>
<div className="favorite-text">
<div {...{ className: "favorite-text-wrapper" }}>
{
transitions((style, i, state) => {
let isLeaving = state.phase === "leave";
const content = (txt = "") =>
<a.div {...{ style: {
...style,
position: isLeaving && animating ? "absolute" : "relative"
} }}>{
txt ? txt : <svg {...{ className: "fy-workdots" }}>
<circle cx="2" cy="8" r="2" />
<circle cx="8" cy="8" r="2" />
<circle cx="14" cy="8" r="2" />
</svg>
}</a.div>;
return [ content(""), content("Saved"), content("Save")
][i];
})
}
</div>
</div>
</button>
</Tippy>
</div>
@@ -195,10 +195,11 @@ export default function AudioCard({
</div>
<BookmarkButton
{...{
busy: bookmarks.busyList[data.weight_token],
entityToken: data.weight_token,
entityType: "model_weight",
onToggle: bookmarks?.toggle,
initialToggled: bookmarks.list[data.weight_token]
initialToggled: bookmarks?.list[data.weight_token]?.is
}}
/>
</div>
@@ -199,10 +199,11 @@ export default function ImageCard({
</div>
<BookmarkButton
{...{
busy: bookmarks.busyList[data.weight_token],
entityToken: data.weight_token,
entityType: "model_weight",
onToggle: bookmarks?.toggle,
initialToggled: bookmarks.list[data.weight_token]
initialToggled: bookmarks?.list[data.weight_token]?.is_bookmarked
}}
/>
</div>
@@ -1,59 +1,93 @@
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { GetBookmarks, GetBookmarksResponse } from "@storyteller/components/src/api/bookmarks/GetBookmarks";
import { CreateBookmark } from "@storyteller/components/src/api/bookmarks/CreateBookmark";
import { DeleteBookmark } from "@storyteller/components/src/api/bookmarks/DeleteBookmark";
import { GetBookmarksByUser } from "@storyteller/components/src/api/bookmarks/GetBookmarksByUser";
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { useSession } from "hooks";
// import { useSession } from "hooks";
interface BookmarkLibrary {
[key: string]: any
}
export default function useBookmarks() {
const { user } = useSession();
const [baseList, baseListSet] = useState<any[]>([]);
const [list, listSet] = useState<BookmarkLibrary>({});
const [busyList, busyListSet] = useState<BookmarkLibrary>({});
const [status, statusSet] = useState(FetchStatus.ready);
const list = baseList.reduce((obj: any, current: any) => {
return { ...obj, [current.details.entity_token]: current.token }
},{});
const getBookmarks = (tokens = [], expand = false) => {
busyListSet(tokens.reduce((obj,token) => ({ ...obj, [token]: true }),{}));
GetBookmarks("",{},{ tokens }).then((res: GetBookmarksResponse) => {
if (res.success && res.bookmarks) {
let newBatch = res.bookmarks.reduce((obj, { entity_token, ...bookmark }) => ({
...obj,
[entity_token]: bookmark
}),{});
busyListSet({}); // this should be a for each key in tokens delete from busyList, but this is fine for now
listSet(list => expand ? { ...list, ...newBatch } : newBatch);
}
})
};
const toggle = (entityToken = "", type = "") => {
let bookmarkToken = list[entityToken];
const gather = ({ res, expand }: { res: any, expand?: boolean }) => {
let tokens = res.results.map((item: any) => item.weight_token);
getBookmarks(tokens, expand);
};
const toggleList = (entity_type = "", entityToken = "", maybe_bookmark_token = null) => (list: BookmarkLibrary) => ({
...list,
[entityToken]: {
entity_type,
is_bookmarked: !!maybe_bookmark_token,
maybe_bookmark_token
}
});
// console.log("😎",list);
const toggle = (entityToken: string, type: string) => {
statusSet(FetchStatus.in_progress);
busyListSet(state => ({ ...state, [entityToken]: true }));
let bookmarkToken = list[entityToken].maybe_bookmark_token;
if (bookmarkToken) {
console.log("⏳ deleting bookmark",);
console.log(`⏳ deleting bookmark: ${ entityToken }`,bookmarkToken);
return DeleteBookmark(bookmarkToken,{ as_mod: true })
.then((res: any) => {
console.log("🔥",res);
busyListSet(state => {
let newState = { ...state };
delete newState[entityToken];
return newState;
});
listSet(toggleList(type,entityToken));
statusSet(FetchStatus.ready);
return false;
});
} else {
console.log("⏳ creating bookmark",);
console.log(`⏳ creating bookmark: ${ entityToken }`,bookmarkToken);
return CreateBookmark("",{
entity_token: entityToken,
entity_type: type
})
.then((res: any) => {
console.log("🔖",res);
busyListSet(state => {
let newState = { ...state };
delete newState[entityToken];
return newState;
});
listSet(toggleList(type,entityToken,res.user_bookmark_token));
statusSet(FetchStatus.ready);
return true;
});
}
};
useEffect(() => {
if (user && status === FetchStatus.ready) {
statusSet(FetchStatus.in_progress);
GetBookmarksByUser(user?.username || "",{},{ page_size: 999 }) // high number because we want all bookmarks
.then((res: any) => {
statusSet(FetchStatus.success);
if (res.results) {
baseListSet(res.results);
}
});
}
},[status, user]);
return {
busyList,
gather,
list,
status,
toggle
};
};
@@ -1,10 +1,13 @@
import { config } from "@react-spring/web";
const n = (x:any) => {};
const basicTransition = ({ ...overwrite }) => ({
const basicTransition = ({ ...overwrite }, onRest = n, onStart = n) => ({
config: config.gentle,
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
from: { opacity: 0, },
enter: { opacity: 1, },
leave: { opacity: 0, },
onRest,
onStart,
...overwrite
});
@@ -37,7 +37,10 @@ export default function MediaTab() {
list,
listSet,
onInputChange: () => setShowMasonryGrid(false),
onSuccess: () => setShowMasonryGrid(true),
onSuccess: (res) => {
bookmarks.gather({ res, expand: true }); // expand rather than replace for lazy loading
setShowMasonryGrid(true);
},
requestList: true,
});
@@ -30,11 +30,15 @@ export default function WeightsTab() {
...prepFilter(weightType, "maybe_scoped_weight_type"),
},
addSetters: { weightCategorySet, weightTypeSet },
debug: "explore weights tab",
fetcher: ListWeights,
list,
listSet,
onInputChange: () => setShowMasonryGrid(false),
onSuccess: () => setShowMasonryGrid(true),
onSuccess: (res) => {
bookmarks.gather({ res, expand: true }); // expand rather than replace for lazy loading
setShowMasonryGrid(true);
},
requestList: true,
});
@@ -57,7 +57,10 @@ export default function BookmarksTab({ username }: { username: string }) {
list,
listSet,
onInputChange: () => setShowMasonryGrid(false),
onSuccess: () => setShowMasonryGrid(true),
onSuccess: (res) => {
bookmarks.gather({ res });
setShowMasonryGrid(true);
},
requestList: true,
urlParam: username,
});
@@ -39,7 +39,7 @@ export default function MediaTab({ username }: { username: string }) {
listSet,
onInputChange: () => setShowMasonryGrid(false),
onSuccess: (res) => {
ratings.gather(res);
bookmarks.gather({ res });
setShowMasonryGrid(true);
},
requestList: true,
@@ -48,7 +48,10 @@ export default function WeightsTab({ username }: { username: string }) {
list,
listSet,
onInputChange: () => setShowMasonryGrid(false),
onSuccess: () => setShowMasonryGrid(true),
onSuccess: (res) => {
bookmarks.gather({ res });
setShowMasonryGrid(true);
},
requestList: true,
urlParam: username,
});