mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-13 13:22:57 +00:00
bookmarks working with new batch endpoint on cards
This commit is contained in:
1 parent
eb4240d072
commit
1404340d45
13 files changed
+207
-45
No files matched your search
@@ -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,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user