From 8713426256512d107f688cb99600b2268cc48027 Mon Sep 17 00:00:00 2001 From: Victor O Date: Sat, 25 May 2024 08:00:25 -0700 Subject: [PATCH] Feature/video media page (#570) * video page initial progress * added MediaPageSwitch --------- Co-authored-by: Victor --- .../common/ActionButton/ActionButton.scss | 83 +++++ .../common/ActionButton/ActionButton.tsx | 82 +++++ .../components/common/ActionButton/index.tsx | 2 + .../common/BookmarkButton/BookmarkButton.tsx | 24 +- .../common/LikeButton/LikeButton.tsx | 21 +- .../common/SocialButton/SocialButton.scss | 34 +- .../common/SocialButton/SocialButton.tsx | 21 +- .../components/common/SocialButton/index.ts | 1 + .../src/components/common/index.tsx | 11 +- .../entities/EntityInput/EntityInputEmpty.tsx | 136 ++++--- .../src/components/svg/WorkDots/WorkDots.tsx | 74 ++-- packages/fakeyou.com/src/hooks/index.tsx | 66 ++-- .../src/hooks/useAnimationStatus.tsx | 23 ++ .../hooks/useBatchContent/useBatchContent.tsx | 161 +++++---- .../fakeyou.com/src/hooks/useBookmarks.tsx | 78 ++++ .../src/hooks/useBookmarks/index.ts | 1 - .../src/hooks/useBookmarks/useBookmarks.tsx | 47 --- .../src/hooks/useMedia/useMedia.tsx | 22 +- packages/fakeyou.com/src/hooks/useRatings.tsx | 96 +++++ .../fakeyou.com/src/hooks/useRatings/index.ts | 2 - .../src/hooks/useRatings/useRatings.tsx | 63 ---- .../src/resources/basicTransition.ts | 13 +- .../fakeyou.com/src/scss/breakpoints.scss | 3 +- packages/fakeyou.com/src/scss/colours.scss | 32 ++ .../fakeyou.com/src/scss/site-refresh.scss | 6 + .../fakeyou.com/src/v2/view/PageContainer.tsx | 14 +- .../src/v2/view/pages/media/MediaPage.scss | 217 ++++++++++++ .../src/v2/view/pages/media/MediaPage.tsx | 225 ++---------- .../v2/view/pages/media/MediaPageSwitch.tsx | 151 ++++++++ .../view/pages/media/MediaVideoComponent.tsx | 12 +- .../src/v2/view/pages/media/PromptViewer.tsx | 142 ++++++++ .../v2/view/pages/media/VideoMediaPage.tsx | 335 ++++++++++++++++++ 32 files changed, 1633 insertions(+), 565 deletions(-) create mode 100644 packages/fakeyou.com/src/components/common/ActionButton/ActionButton.scss create mode 100644 packages/fakeyou.com/src/components/common/ActionButton/ActionButton.tsx create mode 100644 packages/fakeyou.com/src/components/common/ActionButton/index.tsx create mode 100644 packages/fakeyou.com/src/hooks/useAnimationStatus.tsx create mode 100644 packages/fakeyou.com/src/hooks/useBookmarks.tsx delete mode 100644 packages/fakeyou.com/src/hooks/useBookmarks/index.ts delete mode 100644 packages/fakeyou.com/src/hooks/useBookmarks/useBookmarks.tsx create mode 100644 packages/fakeyou.com/src/hooks/useRatings.tsx delete mode 100644 packages/fakeyou.com/src/hooks/useRatings/index.ts delete mode 100644 packages/fakeyou.com/src/hooks/useRatings/useRatings.tsx create mode 100644 packages/fakeyou.com/src/v2/view/pages/media/MediaPage.scss create mode 100644 packages/fakeyou.com/src/v2/view/pages/media/MediaPageSwitch.tsx create mode 100644 packages/fakeyou.com/src/v2/view/pages/media/PromptViewer.tsx create mode 100644 packages/fakeyou.com/src/v2/view/pages/media/VideoMediaPage.tsx diff --git a/packages/fakeyou.com/src/components/common/ActionButton/ActionButton.scss b/packages/fakeyou.com/src/components/common/ActionButton/ActionButton.scss new file mode 100644 index 0000000000..797bfbcf95 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/ActionButton/ActionButton.scss @@ -0,0 +1,83 @@ +@import "scss/colours.scss"; + +.fy-action-button { + appearance: none; + background-color: rgba(lighten($secondary, 5%), 0.9); + border: none; + border-radius: 0.5rem; + color: #fff; + display: flex; + gap: 0.125rem; + align-items: center; + // background: red; + line-height: 1; + font-size: 0.875rem; + text-align: right; + min-height: 2.25rem; + min-width: 1rem; + position: relative; + padding: 0 0 0 0.5rem; + // text-align: right; + + &:hover { + background: rgba(lighten($secondary, 10%), 0.9); + } + + svg { + width: 1em; + color: $primary; + margin-right: 1rem; + } + + .fy-action-button-icon-toggled { + animation: pulse 0.3s ease; + } +} +.bookmark-action-button { + svg { + color: map-get($badge-colors, "yellow"); + } + &.action-button-toggled { + background-color: darken(rgba(map-get($badge-colors, "yellow"), 0.9), 32%); + + &:hover { + background-color: darken( + rgba(map-get($badge-colors, "yellow"), 0.9), + 25% + ); + } + } + + .fy-workdots-label { + min-width: 2rem; + } +} + +.feature-action-button { + svg { + color: map-get($badge-colors, "purple"); + } + &.action-button-toggled { + background-color: darken(rgba(map-get($badge-colors, "purple"), 0.9), 32%); + + &:hover { + background-color: darken( + rgba(map-get($badge-colors, "purple"), 0.9), + 25% + ); + } + } +} + +.like-action-button { + svg { + color: $primary; + } + &.action-button-toggled { + background-color: rgba(#593a48, 0.9); + + &:hover { + background-color: lighten(rgba(#593a48, 0.9), 10%); + } + } +} diff --git a/packages/fakeyou.com/src/components/common/ActionButton/ActionButton.tsx b/packages/fakeyou.com/src/components/common/ActionButton/ActionButton.tsx new file mode 100644 index 0000000000..f96388c70d --- /dev/null +++ b/packages/fakeyou.com/src/components/common/ActionButton/ActionButton.tsx @@ -0,0 +1,82 @@ +import React from "react"; +import Tippy from "@tippyjs/react"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome"; +import { AnimationStatus, useAnimationStatus } from "hooks"; +import { WorkDots } from "components/svg"; +import "./ActionButton.scss"; + +export interface ActionButtonProps { + actionType: "bookmark" | "feature" | "like"; + busy?: boolean; + entityToken?: string; + entityType?: string; + iconOff: IconDefinition; + iconOn: IconDefinition; + isToggled: boolean; + labelOff: string | number; + labelOn: string | number; + toggle: (entityToken: string, entityType: string) => any; + toolTipOff?: string; + toolTipOn?: string; +} + +export default function ActionButton({ + actionType = "like", + busy, + entityToken = "", + entityType = "", + iconOff, + iconOn, + isToggled, + labelOff, + labelOn, + toggle, + toolTipOff, + toolTipOn, +}: ActionButtonProps) { + const { events, status } = useAnimationStatus(); + const onClick = () => { + if (status === AnimationStatus.paused) { + toggle(entityToken, entityType); + } + }; + + return ( + + + + ); +} diff --git a/packages/fakeyou.com/src/components/common/ActionButton/index.tsx b/packages/fakeyou.com/src/components/common/ActionButton/index.tsx new file mode 100644 index 0000000000..6a01ce2742 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/ActionButton/index.tsx @@ -0,0 +1,2 @@ +export { default } from "./ActionButton"; +export type { ActionButtonProps } from "./ActionButton"; diff --git a/packages/fakeyou.com/src/components/common/BookmarkButton/BookmarkButton.tsx b/packages/fakeyou.com/src/components/common/BookmarkButton/BookmarkButton.tsx index 090a2e7097..6c3b1efba1 100644 --- a/packages/fakeyou.com/src/components/common/BookmarkButton/BookmarkButton.tsx +++ b/packages/fakeyou.com/src/components/common/BookmarkButton/BookmarkButton.tsx @@ -3,6 +3,7 @@ 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 { WorkDots } from "components/svg"; +import { AnimationStatus, useAnimationStatus } from "hooks"; import Tippy from "@tippyjs/react"; import "tippy.js/dist/tippy.css"; import "./BookmarkButton.scss"; @@ -29,24 +30,19 @@ export default function BookmarkButton({ overlay, toggle, }: BookmarkButtonProps) { + const { events, status } = useAnimationStatus(); const [isLoading, setIsLoading] = useState(false); const handleClick = async (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); // setIsLoading(true); - toggle(entityToken, entityType).then((isToggled: boolean) => { - // setIsToggled(isToggled); - setIsLoading(false); - }); - // try { - // await onToggle(!isToggled); - // setIsToggled(!isToggled); - // } catch (error) { - // console.error("Error calling API", error); - // } finally { - // setIsLoading(false); - // } + if (status === AnimationStatus.paused) { + toggle(entityToken, entityType).then((isToggled: boolean) => { + // setIsToggled(isToggled); + setIsLoading(false); + }); + } }; const buttonClass = isToggled ? "favorite-button toggled" : "favorite-button"; @@ -79,7 +75,9 @@ export default function BookmarkButton({ />
- +
diff --git a/packages/fakeyou.com/src/components/common/LikeButton/LikeButton.tsx b/packages/fakeyou.com/src/components/common/LikeButton/LikeButton.tsx index 1f772bd10c..2ab44e5eb9 100644 --- a/packages/fakeyou.com/src/components/common/LikeButton/LikeButton.tsx +++ b/packages/fakeyou.com/src/components/common/LikeButton/LikeButton.tsx @@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faHeart } from "@fortawesome/pro-solid-svg-icons"; import { faHeart as faHeartOutline } from "@fortawesome/pro-regular-svg-icons"; import { WorkDots } from "components/svg"; +import { AnimationStatus, useAnimationStatus } from "hooks"; import Tippy from "@tippyjs/react"; import "tippy.js/dist/tippy.css"; import "./LikeButton.scss"; @@ -29,18 +30,19 @@ export default function LikeButton({ large, toggle, }: LikeButtonProps) { + const { events, status } = useAnimationStatus(); const [isLoading, setIsLoading] = useState(false); - // console.log("😎",busy); - const handleClick = async (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); // setIsLoading(true); - toggle(entityToken, entityType).then((isToggled: boolean) => { - // setIsToggled(isToggled); - setIsLoading(false); - }); + if (status === AnimationStatus.paused) { + toggle(entityToken, entityType).then((isToggled: boolean) => { + // setIsToggled(isToggled); + setIsLoading(false); + }); + } // try { // await onToggle(!isToggled); // setIsToggled(!isToggled); @@ -80,7 +82,12 @@ export default function LikeButton({
diff --git a/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.scss b/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.scss index f9eecf19eb..d2b9cb4a07 100644 --- a/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.scss +++ b/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.scss @@ -22,39 +22,7 @@ margin-bottom: 10px; transition: 0.2s; - &:hover { - opacity: 0.8; - } - - &.bg-facebook { - color: $white; - background-color: #4267b2; - } - - &.bg-reddit { - color: $white; - background-color: #ff5700; - } - - &.bg-reddit { - color: $white; - background-color: #ff5700; - } - - &.bg-whatsapp { - color: $white; - background-color: #25d366; - } - - &.bg-x { - color: $white; - background-color: #1da1f2; - } - - &.bg-email { - color: $white; - background-color: #858585; - } + @include social-colors; } .social-button-text { diff --git a/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.tsx b/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.tsx index e46d2d33e7..5c88f5dd7a 100644 --- a/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.tsx +++ b/packages/fakeyou.com/src/components/common/SocialButton/SocialButton.tsx @@ -9,13 +9,17 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React from "react"; import "./SocialButton.scss"; +export type Socials = "x" | "whatsapp" | "facebook" | "reddit" | "email"; + interface SocialButtonProps { - social: "x" | "whatsapp" | "facebook" | "reddit" | "email"; + hideLabel?: boolean; + social: Socials; shareUrl: string; shareText: string; } export default function SocialButton({ + hideLabel, social, shareUrl, shareText, @@ -64,13 +68,18 @@ export default function SocialButton({ return ( ); } diff --git a/packages/fakeyou.com/src/components/common/SocialButton/index.ts b/packages/fakeyou.com/src/components/common/SocialButton/index.ts index 2486a0f50d..eaf12940ca 100644 --- a/packages/fakeyou.com/src/components/common/SocialButton/index.ts +++ b/packages/fakeyou.com/src/components/common/SocialButton/index.ts @@ -1 +1,2 @@ export { default } from "./SocialButton"; +export type { Socials } from "./SocialButton"; diff --git a/packages/fakeyou.com/src/components/common/index.tsx b/packages/fakeyou.com/src/components/common/index.tsx index 55ec424de8..3750abde35 100644 --- a/packages/fakeyou.com/src/components/common/index.tsx +++ b/packages/fakeyou.com/src/components/common/index.tsx @@ -1,8 +1,10 @@ import Accordion from "./Accordion"; +import ActionButton, { ActionButtonProps } from "./ActionButton"; import AudioBlobPreview from "./AudioBlobPreview"; import AudioInput from "./AudioInput"; import Badge from "./Badge"; import BasicTabs from "./BasicTabs"; +import BookmarkButton from "./BookmarkButton"; import Button from "./Button"; import ButtonRevertToDefault from "./ButtonRevertToDefault"; import Checkbox from "./Checkbox"; @@ -39,6 +41,7 @@ import SelectModalVideoTabs from "./SelectModalVideoTabs"; import SelectModalWeightsTabs from "./SelectModalWeightsTabs"; import Skeleton from "./Skeleton"; import Slider from "./Slider"; +import SocialButton, { Socials } from "./SocialButton"; import Spinner from "./Spinner"; import SplitPanel from "./SplitPanel"; import Tabs, { NonRouteTabs } from "./Tabs"; @@ -56,10 +59,12 @@ import WeightCoverImage from "./WeightCoverImage"; export { Accordion, + ActionButton, AudioBlobPreview, AudioInput, Badge, BasicTabs, + BookmarkButton, Button, //Cards MediaCards, @@ -101,6 +106,7 @@ export { SelectModalWeightsTabs, Skeleton, Slider, + SocialButton, Spinner, SplitPanel, Tabs, @@ -117,7 +123,4 @@ export { WeightCoverImage, }; -export type { - ModalUtilities, - SelectModalData -}; +export type { ActionButtonProps, ModalUtilities, SelectModalData, Socials }; diff --git a/packages/fakeyou.com/src/components/entities/EntityInput/EntityInputEmpty.tsx b/packages/fakeyou.com/src/components/entities/EntityInput/EntityInputEmpty.tsx index d734438352..e77241cb46 100644 --- a/packages/fakeyou.com/src/components/entities/EntityInput/EntityInputEmpty.tsx +++ b/packages/fakeyou.com/src/components/entities/EntityInput/EntityInputEmpty.tsx @@ -3,9 +3,11 @@ import { SlideProps } from "./EntityInput"; import { // useMediaUploader, useModal, - useSession + useSession, } from "hooks"; -import MediaBrowser, { MediaBrowserProps } from "components/modals/MediaBrowser"; +import MediaBrowser, { + MediaBrowserProps, +} from "components/modals/MediaBrowser"; import { FileWrapper } from "components/common"; import { AcceptTypes, @@ -14,7 +16,7 @@ import { getMediaTypesByCategory, isSelectedType, mediaCategoryfromString, - MediaFilters + MediaFilters, } from "components/entities/EntityTypes"; import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome"; import { @@ -24,66 +26,112 @@ import { faGrid, faImage, faPersonWalking, - faWaveform - } from "@fortawesome/pro-solid-svg-icons"; - + faWaveform, +} from "@fortawesome/pro-solid-svg-icons"; interface EmptySlideProps extends SlideProps { - accept: AcceptTypes | AcceptTypes[], - inputProps?: any, - queryUser?: string, - type: EntityModeProp, -}; + accept: AcceptTypes | AcceptTypes[]; + inputProps?: any; + queryUser?: string; + type: EntityModeProp; +} -export default function EntityInputEmpty({ accept, inputProps, queryUser, type, ...rest }: EmptySlideProps) { +export default function EntityInputEmpty({ + accept, + inputProps, + queryUser, + type, + ...rest +}: EmptySlideProps) { const { open } = useModal(); const { user } = useSession(); + console.log("😎", user); const accepted = Array.isArray(accept) ? accept : [accept]; const inputMode = EntityInputMode[type]; const isMedia = inputMode === EntityInputMode.media; - const fileTypes = isMedia ? accepted.map((mediaCategory,i) => { - return mediaCategory ? getMediaTypesByCategory(mediaCategoryfromString(mediaCategory)) : []; - }).flat() : []; + const fileTypes = isMedia + ? accepted + .map((mediaCategory, i) => { + return mediaCategory + ? getMediaTypesByCategory(mediaCategoryfromString(mediaCategory)) + : []; + }) + .flat() + : []; const props: MediaBrowserProps = { accept: accepted, inputMode, username: queryUser || user?.username || "", - ...rest + ...rest, }; - const browserClick = () => open({ - component: MediaBrowser, - props - }); + const browserClick = () => + open({ + component: MediaBrowser, + props, + }); const mediaIcons = () => { - if (isSelectedType(MediaFilters.audio,accepted[0])) return faWaveform; - if (isSelectedType(MediaFilters.engine_asset,accepted[0])) return faPersonWalking; - if (isSelectedType(MediaFilters.image,accepted[0])) return faImage; - if (isSelectedType(MediaFilters.video,accepted[0])) return faFile; + if (isSelectedType(MediaFilters.audio, accepted[0])) return faWaveform; + if (isSelectedType(MediaFilters.engine_asset, accepted[0])) + return faPersonWalking; + if (isSelectedType(MediaFilters.image, accepted[0])) return faImage; + if (isSelectedType(MediaFilters.video, accepted[0])) return faFile; return faFile; }; - const supported = `${ fileTypes.length ? fileTypes.join(", ") : fileTypes[0] } files supported`; + const supported = `${ + fileTypes.length ? fileTypes.join(", ") : fileTypes[0] + } files supported`; - return <> - -
- { isMedia && - - <> - -
- Upload, click or drag here - { accept && { supported } } -
- -
} -
- - Choose from your { ["bookmarks","media","weights"][inputMode] } + return ( + <> + +
+ {isMedia && ( + + <> + +
+ Upload, click or drag here + {accept && {supported}} +
+ +
+ )} +
+ + Choose from your {["bookmarks", "media", "weights"][inputMode]} +
-
- ; -}; \ No newline at end of file + + ); +} diff --git a/packages/fakeyou.com/src/components/svg/WorkDots/WorkDots.tsx b/packages/fakeyou.com/src/components/svg/WorkDots/WorkDots.tsx index 68248c953d..d3abe72b45 100644 --- a/packages/fakeyou.com/src/components/svg/WorkDots/WorkDots.tsx +++ b/packages/fakeyou.com/src/components/svg/WorkDots/WorkDots.tsx @@ -1,35 +1,59 @@ -import React, { useState } from 'react'; +import React from "react"; import { a, useTransition } from "@react-spring/web"; import { basicTransition } from "resources"; +import { AnimationEvents } from "hooks"; + +type Label = string | number; interface Props { - debug?: boolean, - labels: string[], - index: number + debug?: boolean; + events: AnimationEvents; + labels: Label[]; + noPad?: boolean; + index: number; } -export default function WorkDots({ debug, labels = [], index }: Props) { - const [animating,animatingSet] = useState(false); - const transitions = useTransition(index, basicTransition({ - onRest: () => animatingSet(false), - onStart: () => animatingSet(true) - })); +export default function WorkDots({ + debug, + events, + labels = [], + noPad, + index, +}: Props) { + const transitions = useTransition( + index, + basicTransition({ + ...events, + }) + ); - if (debug) console.log("WorkDots ... Debug",index); + if (debug) console.log("WorkDots ... Debug", index); return transitions((style: any, i: number, state: any) => { - let isLeaving = state.phase === "leave"; - const content = (txt = "") => - { - txt ? txt : - - - - - }; - return [ content(""), ...labels.map((label,i) => content(label))][i]; + // let isLeaving = state.phase === "leave"; + const content = (txt: Label) => { + return ( + + {txt !== undefined ? ( + txt + ) : ( + + + + + + )} + + ); + }; + return [content(""), ...labels.map((label: Label, i) => content(label))][i]; }); -}; \ No newline at end of file +} diff --git a/packages/fakeyou.com/src/hooks/index.tsx b/packages/fakeyou.com/src/hooks/index.tsx index a33be1d36f..c7b481f9d9 100644 --- a/packages/fakeyou.com/src/hooks/index.tsx +++ b/packages/fakeyou.com/src/hooks/index.tsx @@ -1,34 +1,40 @@ -import useBatchContent from './useBatchContent'; -import useBookmarks from './useBookmarks'; -import useChanger from './useChanger'; -import useCoverImgUpload from './useCoverImgUpload'; -import useDebounce from './useDebounce'; -import useFile from './useFile'; -import useHover from './useHover'; -import useId from './useId'; -import useIdempotency from './useIdempotency'; -import useInferenceJobs from './useInferenceJobs'; -import useInterval from './useInterval'; -import useJobStatus from './useJobStatus'; -import useLazyLists from './useLazyLists'; -import useListContent from './useListContent'; -import useLocalize from './useLocalize'; -import useMedia from './useMedia'; -import useMediaUploader from './useMediaUploader'; -import useModal from './useModal'; -import useNotifications from './useNotifications'; +import useAnimationStatus, { + AnimationEvents, + AnimationStatus, +} from "./useAnimationStatus"; +import useBatchContent from "./useBatchContent"; +import useBookmarks, { MakeBookmarksProps } from "./useBookmarks"; +import useChanger from "./useChanger"; +import useCoverImgUpload from "./useCoverImgUpload"; +import useDebounce from "./useDebounce"; +import useFile from "./useFile"; +import useHover from "./useHover"; +import useId from "./useId"; +import useIdempotency from "./useIdempotency"; +import useInferenceJobs from "./useInferenceJobs"; +import useInterval from "./useInterval"; +import useJobStatus from "./useJobStatus"; +import useLazyLists from "./useLazyLists"; +import useListContent from "./useListContent"; +import useLocalize from "./useLocalize"; +import useMedia from "./useMedia"; +import useMediaUploader from "./useMediaUploader"; +import useModal from "./useModal"; +import useNotifications from "./useNotifications"; import useOnScreen from "./useOnScreen"; -import usePrevious from './usePrevious'; -import useQueuePoll from './useQueuePoll'; -import useRatings from './useRatings'; -import useSdUpload from './useSdUpload'; -import useSession from './useSession'; -import useSlides from './useSlides'; -import useStatusPoll from './useStatusPoll'; -import useVideo from './useVideo'; -import useWeightFetch from './useWeightFetch'; +import usePrevious from "./usePrevious"; +import useQueuePoll from "./useQueuePoll"; +import useRatings, { MakeRatingsProps } from "./useRatings"; +import useSdUpload from "./useSdUpload"; +import useSession from "./useSession"; +import useSlides from "./useSlides"; +import useStatusPoll from "./useStatusPoll"; +import useVideo from "./useVideo"; +import useWeightFetch from "./useWeightFetch"; export { + AnimationStatus, + useAnimationStatus, useBatchContent, useBookmarks, useChanger, @@ -57,5 +63,7 @@ export { useSlides, useStatusPoll, useVideo, - useWeightFetch + useWeightFetch, }; + +export type { AnimationEvents, MakeBookmarksProps, MakeRatingsProps }; diff --git a/packages/fakeyou.com/src/hooks/useAnimationStatus.tsx b/packages/fakeyou.com/src/hooks/useAnimationStatus.tsx new file mode 100644 index 0000000000..82b411678e --- /dev/null +++ b/packages/fakeyou.com/src/hooks/useAnimationStatus.tsx @@ -0,0 +1,23 @@ +import { useState } from "react"; + +export enum AnimationStatus { + paused, + animating, +} + +export interface AnimationEvents { + onStart: () => void; + onRest: () => void; +} + +export default function useAnimationStatus() { + const [status, statusSet] = useState(AnimationStatus.paused); + + return { + status, + events: { + onStart: () => statusSet(AnimationStatus.animating), + onRest: () => statusSet(AnimationStatus.paused), + }, + }; +} diff --git a/packages/fakeyou.com/src/hooks/useBatchContent/useBatchContent.tsx b/packages/fakeyou.com/src/hooks/useBatchContent/useBatchContent.tsx index b5ac6d8825..0afe5eb157 100644 --- a/packages/fakeyou.com/src/hooks/useBatchContent/useBatchContent.tsx +++ b/packages/fakeyou.com/src/hooks/useBatchContent/useBatchContent.tsx @@ -3,37 +3,40 @@ import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetch import { useSession } from "hooks"; interface Props { - checker: any, - debug?: string, - fetcher: any, - modLibrary?: any, - onPass?: any, - onFail?: any, - resultsKey: string, - toggleCheck: (x: any) => any + checker: any; + debug?: string; + fetcher: any; + modLibrary?: any; + onPass?: any; + onFail?: any; + resultsKey: string; + toggleCheck: (x: any) => any; } interface Library { - [key: string]: any + [key: string]: any; } interface Gather { - expand?: boolean, - modLibrary?: any, - key: string, - res: any + expand?: boolean; + modLibrary?: any; + key: string; + res: any; } type BatchToggle = (entity_token: string, entity_type: string) => boolean; -export interface MakePropsParams { entityToken: string, entityType: string } +export interface MakePropsParams { + entityToken: string; + entityType: string; +} export interface BatchInputProps { - busy: boolean, - entityToken: string, - entityType: string, - isToggled: boolean, - toggle: BatchToggle + busy: boolean; + entityToken: string; + entityType: string; + isToggled: boolean; + toggle: BatchToggle; } export type MakeBatchProps = (x: MakePropsParams) => BatchInputProps; @@ -42,57 +45,68 @@ export default function useBatchContent({ checker, debug, fetcher, - modLibrary = (current: any, res: any, entity_token: string ) => current, + modLibrary = (current: any, res: any, entity_token: string) => current, onPass, onFail = { fetch: () => new Promise(() => {}) }, resultsKey, - toggleCheck + toggleCheck, }: Props) { const session = useSession(); const [library, librarySet] = useState({}); const [busyList, busyListSet] = useState({}); const [status, statusSet] = useState(FetchStatus.ready); - const [tokenType,tokenTypeSet] = useState(""); + const [tokenType, tokenTypeSet] = useState(""); - const dlog = (...dbg: any) => debug ? console.log(...dbg) : {}; + const dlog = (...dbg: any) => (debug ? console.log(...dbg) : {}); const gather = ({ expand, key, res }: Gather) => { - let tokens = res.results ? res.results.map((item: any) => (item.details || item)[key]) : [res[key]]; - let loadingTokens = tokens.reduce((obj = {},token = "") => ({ ...obj, [token]: true }),{}) - dlog("🪙",fetcher); - tokenTypeSet(key) + let tokens = res.results + ? res.results.map((item: any) => (item.details || item)[key]) + : [res[key]]; + let loadingTokens = tokens.reduce( + (obj = {}, token = "") => ({ ...obj, [token]: true }), + {} + ); + dlog("🪙", fetcher); + tokenTypeSet(key); busyListSet(loadingTokens); // add current batch to busy list - fetcher("",{},{ tokens }).then((batchRes: any) => { - - // console.log("🦄",resultsKey, res, modLibrary); + fetcher("", {}, { tokens }).then((batchRes: any) => { + // console.log("🦄", resultsKey, res, modLibrary); if (batchRes.success && !!batchRes[resultsKey]) { // console.log("🥏",resultsKey); - let newBatch = batchRes[resultsKey].reduce((obj = {}, { entity_token = "", ...current }) => { - // console.log("🧲", resultsKey, {current, res, entity_token, obj}); - let newCurrent = { - ...obj, - [entity_token]: { ...modLibrary(current, res, entity_token, key) } - }; + let newBatch = batchRes[resultsKey].reduce( + (obj = {}, { entity_token = "", ...current }) => { + // console.log("🧲", resultsKey, {current, res, entity_token, obj}); + let newCurrent = { + ...obj, + [entity_token]: { + ...modLibrary(current, res, entity_token, key), + }, + }; - - // console.log("😡", resultsKey, newCurrent); - return newCurrent; - },{}); + // console.log("😡", resultsKey, newCurrent); + return newCurrent; + }, + {} + ); // console.log("🐸",busyList); busyListSet({}); // this should be a for each key in tokens delete from busyList, but this is fine for now - librarySet((library: any) => expand ? { ...library, ...newBatch } : newBatch); + librarySet((library: any) => + expand ? { ...library, ...newBatch } : newBatch + ); } }); }; + const busyAdd = (entity_token: string) => + busyListSet(state => ({ ...state, [entity_token]: true })); - const busyAdd = (entity_token: string) => busyListSet(state => ({ ...state, [entity_token]: true })); - - const busyRemove = (entity_token: string) => busyListSet(state => { - let newState = { ...state }; - delete newState[entity_token]; - return newState; - }); + const busyRemove = (entity_token: string) => + busyListSet(state => { + let newState = { ...state }; + delete newState[entity_token]; + return newState; + }); const toggle: BatchToggle = (entity_token, entity_type) => { if (session.check()) { @@ -100,43 +114,50 @@ export default function useBatchContent({ statusSet(FetchStatus.in_progress); busyAdd(entity_token); - console.log(`⏳ toggling entity ${ entity_token }, in library?: ${ !!inLibrary }`); + console.log( + `⏳ toggling entity ${entity_token}, in library?: ${!!inLibrary}` + ); if (inLibrary && checker(inLibrary)) { // console.log("⭕️"); - return onPass.fetch(entity_token, entity_type, library) - .then((res: any) => { - console.log("⭕️",res); - busyRemove(entity_token); - librarySet(onPass.modLibrary(res, entity_token, entity_type, library)); - statusSet(FetchStatus.ready); - return false; - }); + return onPass + .fetch(entity_token, entity_type, library) + .then((res: any) => { + console.log("⭕️", res); + busyRemove(entity_token); + librarySet( + onPass.modLibrary(res, entity_token, entity_type, library) + ); + statusSet(FetchStatus.ready); + return false; + }); } else { // console.log("❌"); - return onFail.fetch(entity_token, entity_type, library) - .then((res: any) => { - console.log("❌",res); - busyRemove(entity_token); - librarySet(onFail.modLibrary(res, entity_token, entity_type, library)); - statusSet(FetchStatus.ready); - return true; - }); + return onFail + .fetch(entity_token, entity_type, library) + .then((res: any) => { + console.log("❌", res); + busyRemove(entity_token); + librarySet( + onFail.modLibrary(res, entity_token, entity_type, library) + ); + statusSet(FetchStatus.ready); + return true; + }); } } }; - const toggled = ( entity_token = "" ) => toggleCheck(library[entity_token]); + const toggled = (entity_token = "") => toggleCheck(library[entity_token]); const makeProps = ({ entityToken, entityType }: MakePropsParams) => ({ busy: busyList[entityToken], entityToken, entityType, isToggled: toggled(entityToken), - toggle + toggle, }); - return { busyAdd, busyRemove, @@ -150,6 +171,6 @@ export default function useBatchContent({ statusSet, tokenType, toggle, - toggled + toggled, }; -}; \ No newline at end of file +} diff --git a/packages/fakeyou.com/src/hooks/useBookmarks.tsx b/packages/fakeyou.com/src/hooks/useBookmarks.tsx new file mode 100644 index 0000000000..ea7224986a --- /dev/null +++ b/packages/fakeyou.com/src/hooks/useBookmarks.tsx @@ -0,0 +1,78 @@ +import { GetBookmarks } 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 { IconDefinition } from "@fortawesome/fontawesome-svg-core"; +import { faBookmark } from "@fortawesome/pro-solid-svg-icons"; +import { faBookmark as faBookmarkOutline } from "@fortawesome/pro-regular-svg-icons"; + +import useBatchContent, { + BatchInputProps, + MakePropsParams, +} from "hooks/useBatchContent"; + +export interface BookmarksProps extends BatchInputProps { + actionType: "bookmark"; + iconOff: IconDefinition; + iconOn: IconDefinition; + labelOff: string | number; + labelOn: string | number; +} + +export type MakeBookmarksProps = (x: MakePropsParams) => BookmarksProps; + +export default function useBookmarks() { + const toggleList = + (toBookmark: boolean) => + (res: any, entity_token: string, entity_type: string, lib: any) => { + return { + ...lib, + [entity_token]: { + entity_type, + is_bookmarked: toBookmark, + maybe_bookmark_token: toBookmark ? res.user_bookmark_token : null, + }, + }; + }; + + const bookmarks = useBatchContent({ + debug: "bookmarks", + checker: ({ maybe_bookmark_token }: any) => !!maybe_bookmark_token, + fetcher: GetBookmarks, + onPass: { + fetch: (entity_token: string, entity_type: string, lib: any) => { + let bookmarkToken = lib[entity_token].maybe_bookmark_token; + return DeleteBookmark(bookmarkToken, { as_mod: false }); + }, + modLibrary: toggleList(false), + }, + onFail: { + fetch: (entity_token: string, entity_type: string) => + CreateBookmark("", { + entity_token, + entity_type, + }), + modLibrary: toggleList(true), + }, + resultsKey: "bookmarks", + toggleCheck: (entity: any) => !!entity?.maybe_bookmark_token, + }); + + const makeProps: MakeBookmarksProps = ({ + entityToken, + entityType, + }: MakePropsParams) => { + return { + ...bookmarks.makeProps({ entityToken, entityType }), + actionType: "bookmark", + iconOff: faBookmarkOutline, + iconOn: faBookmark, + labelOff: "Save", + labelOn: "Saved", + }; + }; + + return { + ...bookmarks, + makeProps, + }; +} diff --git a/packages/fakeyou.com/src/hooks/useBookmarks/index.ts b/packages/fakeyou.com/src/hooks/useBookmarks/index.ts deleted file mode 100644 index c5b1d5d36a..0000000000 --- a/packages/fakeyou.com/src/hooks/useBookmarks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./useBookmarks"; \ No newline at end of file diff --git a/packages/fakeyou.com/src/hooks/useBookmarks/useBookmarks.tsx b/packages/fakeyou.com/src/hooks/useBookmarks/useBookmarks.tsx deleted file mode 100644 index f61454df57..0000000000 --- a/packages/fakeyou.com/src/hooks/useBookmarks/useBookmarks.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { GetBookmarks } 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 { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes"; -import { useBatchContent } from "hooks"; -// import { useSession } from "hooks"; - -// interface BookmarkLibrary { -// [key: string]: any -// } - -export default function useBookmarks() { - - const toggleList = (toBookmark: boolean) => (res: any, entity_token: string, entity_type: string, lib: any) => { - return { - ...lib, - [entity_token]: { - entity_type, - is_bookmarked: toBookmark, - maybe_bookmark_token: toBookmark ? res.user_bookmark_token : null - } - } - }; - - const bookmarks = useBatchContent({ - checker: ({ maybe_bookmark_token }: any) => !!maybe_bookmark_token, - fetcher: GetBookmarks, - onPass: { - fetch: (entity_token: string, entity_type: string, lib: any) => { - let bookmarkToken = lib[entity_token].maybe_bookmark_token; - return DeleteBookmark(bookmarkToken,{ as_mod: false }); - }, - modLibrary: toggleList(false) - }, - onFail: { - fetch: (entity_token: string, entity_type: string) => CreateBookmark("",{ - entity_token, - entity_type - }), - modLibrary: toggleList(true) - }, - resultsKey: "bookmarks", - toggleCheck: (entity: any) => !!entity?.maybe_bookmark_token - }); - - return bookmarks; -}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/hooks/useMedia/useMedia.tsx b/packages/fakeyou.com/src/hooks/useMedia/useMedia.tsx index 14e5411097..3c011f5985 100644 --- a/packages/fakeyou.com/src/hooks/useMedia/useMedia.tsx +++ b/packages/fakeyou.com/src/hooks/useMedia/useMedia.tsx @@ -33,8 +33,17 @@ export default function useMedia({ const previousToken = usePrevious(mediaToken); + const reload = () => { + statusSet(FetchStatus.ready); + mediaSet(undefined); + }; + useEffect(() => { - if (mediaToken && (!media || previousToken !== mediaToken)) { + if ( + status === FetchStatus.ready && + mediaToken && + (!media || previousToken !== mediaToken) + ) { statusSet(FetchStatus.in_progress); GetMedia(mediaToken, {}) .then(res => { @@ -64,5 +73,14 @@ export default function useMedia({ } }, [media, mediaToken, previousToken, prompt, onSuccess, status, statusSet]); - return { media, mediaSet, prompt, remove, status, writeStatus }; + return { + media, + mediaFile: media, + mediaSet, + prompt, + remove, + reload, + status, + writeStatus, + }; } diff --git a/packages/fakeyou.com/src/hooks/useRatings.tsx b/packages/fakeyou.com/src/hooks/useRatings.tsx new file mode 100644 index 0000000000..4f1ffe28f8 --- /dev/null +++ b/packages/fakeyou.com/src/hooks/useRatings.tsx @@ -0,0 +1,96 @@ +import { GetRatings } from "@storyteller/components/src/api/user_ratings/GetRatings"; +import { SetRating } from "@storyteller/components/src/api/user_ratings/SetRating"; +import { IconDefinition } from "@fortawesome/fontawesome-svg-core"; +import { faHeart } from "@fortawesome/pro-solid-svg-icons"; +import { faHeart as faHeartOutline } from "@fortawesome/pro-regular-svg-icons"; +import useBatchContent, { + BatchInputProps, + MakePropsParams, +} from "hooks/useBatchContent"; + +export interface RatingsProps extends BatchInputProps { + actionType: "like"; + iconOff: IconDefinition; + iconOn: IconDefinition; + likeCount: 0; + labelOff: string | number; + labelOn: string | number; +} + +export type MakeRatingsProps = (x: MakePropsParams) => RatingsProps; + +export default function useRatings() { + const fetch = (entity_token: string, entity_type: string, lib: any) => { + const newRating = { + entity_token, + entity_type, + rating_value: + lib[entity_token]?.rating_value !== "positive" ? "positive" : "neutral", + }; + return SetRating("", newRating); + }; + + const modLibrary = ( + res: any, + entity_token: string, + entity_type: string, + lib: any + ) => { + return { + ...lib, + [entity_token]: { + entity_type, + rating_value: + lib[entity_token].rating_value === "neutral" ? "positive" : "neutral", + positive_rating_count: res.new_positive_rating_count_for_entity, + }, + }; + }; + + const ratings = useBatchContent({ + fetcher: GetRatings, + checker: () => true, + // debug: "useRatings", + modLibrary: ( + current: any, + res: any, + entity_token: string, + tokenType: string + ) => { + let result = res.results + ? res.results.find((item: any, i: number) => { + return (item.details || item)[tokenType] === entity_token; + }) + : res; + + let { positive_rating_count } = (result.details || result).stats; + + return { ...current, positive_rating_count }; + }, + onFail: { fetch, modLibrary }, + onPass: { fetch, modLibrary }, + resultsKey: "ratings", + toggleCheck: (entity: any) => (entity?.rating_value || "") === "positive", + }); + + const makeProps: MakeRatingsProps = ({ + entityToken, + entityType, + }: MakePropsParams) => { + const likeCount = ratings.library[entityToken]?.positive_rating_count || 0; + return { + ...ratings.makeProps({ entityToken, entityType }), + actionType: "like", + iconOn: faHeart, + iconOff: faHeartOutline, + labelOff: likeCount, + labelOn: likeCount, + likeCount, + }; + }; + + return { + ...ratings, + makeProps, + }; +} diff --git a/packages/fakeyou.com/src/hooks/useRatings/index.ts b/packages/fakeyou.com/src/hooks/useRatings/index.ts deleted file mode 100644 index f8c25b7951..0000000000 --- a/packages/fakeyou.com/src/hooks/useRatings/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./useRatings"; -export type { MakeRatingsProps } from "./useRatings"; \ No newline at end of file diff --git a/packages/fakeyou.com/src/hooks/useRatings/useRatings.tsx b/packages/fakeyou.com/src/hooks/useRatings/useRatings.tsx deleted file mode 100644 index 08b0dfc83e..0000000000 --- a/packages/fakeyou.com/src/hooks/useRatings/useRatings.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { GetRatings } from "@storyteller/components/src/api/user_ratings/GetRatings"; -import { SetRating } from "@storyteller/components/src/api/user_ratings/SetRating"; -import useBatchContent, { BatchInputProps, MakePropsParams } from "hooks/useBatchContent"; - -export interface RatingsProps extends BatchInputProps { - likeCount: 0 -} - -export type MakeRatingsProps = (x: MakePropsParams) => RatingsProps; - -export default function useRatings() { - const fetch = (entity_token: string, entity_type: string, lib: any) => { - const newRating = { - entity_token, - entity_type, - rating_value: lib[entity_token]?.rating_value !== "positive" ? "positive" : "neutral" - } - return SetRating("", newRating); - }; - - const modLibrary = (res: any, entity_token: string, entity_type: string, lib: any) => { - return { - ...lib, - [entity_token]: { - entity_type, - rating_value: lib[entity_token].rating_value === "neutral" ? "positive" : "neutral", - positive_rating_count: res.new_positive_rating_count_for_entity - } - }; - }; - - const ratings = useBatchContent({ - fetcher: GetRatings, - checker: () => true, - // debug: "useRatings", - modLibrary: (current: any, res: any, entity_token: string, tokenType: string) => { - - let result = res.results ? res.results.find((item: any, i: number) => - { - return (item.details || item)[tokenType] === entity_token - } - ) : res; - - let { positive_rating_count } = (result.details || result).stats; - - return { ...current, positive_rating_count }; - }, - onFail: { fetch, modLibrary }, - onPass: { fetch, modLibrary }, - resultsKey: "ratings", - toggleCheck: (entity: any) => (entity?.rating_value || "") === "positive" - }); - - const makeProps: MakeRatingsProps = ({ entityToken, entityType }: MakePropsParams) => ({ - ...ratings.makeProps({ entityToken, entityType }), - likeCount: ratings.library[entityToken]?.positive_rating_count || 0, - }); - - return { - ...ratings, - makeProps - } -}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/resources/basicTransition.ts b/packages/fakeyou.com/src/resources/basicTransition.ts index 87c52112a6..02f8998f76 100644 --- a/packages/fakeyou.com/src/resources/basicTransition.ts +++ b/packages/fakeyou.com/src/resources/basicTransition.ts @@ -1,14 +1,15 @@ import { config } from "@react-spring/web"; -const n = (x:any) => {}; +const n = (x: any) => {}; const basicTransition = ({ ...overwrite }, onRest = n, onStart = n) => ({ config: config.gentle, - from: { opacity: 0, }, - enter: { opacity: 1, }, - leave: { opacity: 0, }, + // config: { duration: 3000 }, + from: { opacity: 0 }, + enter: { opacity: 1.0, position: "relative" }, + leave: { opacity: 0, position: "absolute" }, onRest, onStart, - ...overwrite + ...overwrite, }); -export default basicTransition; \ No newline at end of file +export default basicTransition; diff --git a/packages/fakeyou.com/src/scss/breakpoints.scss b/packages/fakeyou.com/src/scss/breakpoints.scss index ed690c8db6..555ac5f722 100644 --- a/packages/fakeyou.com/src/scss/breakpoints.scss +++ b/packages/fakeyou.com/src/scss/breakpoints.scss @@ -1,2 +1,3 @@ +$under1280: max-width 1280px; $under992: max-width 992px; -$over992: min-width 992px; \ No newline at end of file +$over992: min-width 992px; diff --git a/packages/fakeyou.com/src/scss/colours.scss b/packages/fakeyou.com/src/scss/colours.scss index 9aae25c784..0df4d7f042 100644 --- a/packages/fakeyou.com/src/scss/colours.scss +++ b/packages/fakeyou.com/src/scss/colours.scss @@ -81,3 +81,35 @@ $badge-colors: ( "purple": #8f49de, "pink": #cc1d92, ); + +@mixin social-colors { + color: $white; + + &:hover { + opacity: 0.8; + } + + &.bg-facebook { + background-color: #4267b2; + } + + &.bg-reddit { + background-color: #ff5700; + } + + &.bg-reddit { + background-color: #ff5700; + } + + &.bg-whatsapp { + background-color: #25d366; + } + + &.bg-x { + background-color: #1da1f2; + } + + &.bg-email { + background-color: #858585; + } +} diff --git a/packages/fakeyou.com/src/scss/site-refresh.scss b/packages/fakeyou.com/src/scss/site-refresh.scss index b5ec7c0535..648c43dd25 100644 --- a/packages/fakeyou.com/src/scss/site-refresh.scss +++ b/packages/fakeyou.com/src/scss/site-refresh.scss @@ -193,6 +193,12 @@ styles with important tags: color: rgba($white, 0.8); font-weight: 400; border-color: rgba($white, 0.12); + + // this is to remove the lingering border on the comment component when there is only one comment -V + + &:only-of-type { + border: none; + } } .table-mw { diff --git a/packages/fakeyou.com/src/v2/view/PageContainer.tsx b/packages/fakeyou.com/src/v2/view/PageContainer.tsx index 7ef16fff92..a84fa583d0 100644 --- a/packages/fakeyou.com/src/v2/view/PageContainer.tsx +++ b/packages/fakeyou.com/src/v2/view/PageContainer.tsx @@ -98,7 +98,9 @@ import VcModelEditPage from "./pages/vc/vc_model_edit/VcModelEditPage"; import VcModelDeletePage from "./pages/vc/vc_model_delete/VcModelDeletePage"; // import { StorytellerStudioListPage } from "./pages/storyteller_studio/StorytellerStudioPage"; import TopNav from "components/layout/TopNav/TopNav"; -import MediaPage from "./pages/media/MediaPage"; +// import MediaPage from "./pages/media/MediaPage"; +import MediaPageSwitch from "./pages/media/MediaPageSwitch"; +// import DevMediaPage from "./pages/media/DevMediaPage"; import EditCoverImage from "./pages/media/EditCoverImage"; import MediaRenamePage from "./pages/media/MediaRenamePage"; import { VoiceDesignerFormPage } from "./pages/voice_designer/VoiceDesignerFormPage"; @@ -403,9 +405,17 @@ class PageContainer extends React.Component< - + + {/* + + */} + + {/* + + */} + diff --git a/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.scss b/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.scss new file mode 100644 index 0000000000..5b90c66003 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.scss @@ -0,0 +1,217 @@ +@import "scss/_breakpoint.scss"; +@import "scss/breakpoints.scss"; +@import "scss/colours.scss"; + +.media-page-container { + @include breakpoint($under992) { + display: flex; + flex-direction: column; + } + .video-page-main-content { + width: 75%; + + &.video-page-theater-mode { + width: 100%; + } + + header { + display: flex; + margin: 1rem 0; + justify-content: space-between; + + .video-page-header-button { + height: 3rem; + + display: inline; + } + + .video-page-file-tools { + display: flex; + gap: 0.5rem; + } + } + + @include breakpoint($under992) { + width: 100%; + } + + .video-page-title { + line-height: 1; + span { + opacity: 0.66; + font-size: 0.75rem; + } + } + + .fy-title-editor { + display: flex; + align-items: center; + gap: 0.5rem; + + .fy-input { + margin-bottom: 0; + } + + button { + width: 3rem; + display: inline-flex; + justify-content: center; + } + + &.title-editor-spinner { + position: absolute; + height: 3rem; + // justify-content: center; + } + } + + h2 { + // display: inline; + margin: 0 auto; + font-size: 1.75rem; + line-height: 1; + } + + .video-page-media-content { + border-radius: 0.5rem; + flex-grow: 1; + } + + .video-page-media-details { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + + .media-share-label { + font-size: 0.825rem; + line-height: 1.2; + margin-bottom: 0.5rem; + } + // padding: 1rem 0; + + .video-page-creator { + // background-color: seagreen; + display: flex; + align-items: center; + // flex-wrap: wrap; + .gravatar-img { + // height: 56px; + margin-right: 0.5rem; + display: inline; + } + + .video-page-creator-info { + display: flex; + flex-direction: column; + justify-content: center; + } + h3 { + color: $primary; + font-size: 1rem; + margin: 0; + } + span { + font-size: 0.75rem; + line-height: 1; + opacity: 0.6; + } + } + + // @include breakpoint($under1280) { // replace when related videos returns + @include breakpoint($under992) { + .video-page-actions-container { + display: flex; + justify-content: flex-end; + width: 100%; + flex-wrap: wrap; + margin-top: 1rem; + } + .media-share-label { + width: 100%; + text-align: right; + } + } + + .video-page-share-links { + display: flex; + gap: 0.5rem; + + .social-button { + // margin: 0 0.5rem 0 0; + // height: 2.5rem; + // width: 2.5rem; + svg { + height: 1.5rem; + } + } + + .social-button-icon-no-style { + background-color: red; + border-radius: 0.5rem; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + height: 2.25rem; + width: 2.25rem; + + @include social-colors; + } + + .like-button, + .favorite-button { + line-height: 1; + align-items: center; + max-height: 2.25rem; + margin: 0 0.5rem 0 0; + height: 2.25rem; + + .favorite-text { + display: flex; + align-items: center; + } + // width: 2.25rem; + } + } + } + .video-page-comments { + padding: 1rem; + margin-top: 1rem; + } + } + + .video-page-related-videos { + // background-color: green; + // display: grid; + // grid-template-columns: 50% 50%; + padding: 0 1rem; + flex-grow: 1; + gap: 1rem; + + @include breakpoint($under992) { + margin-top: 1rem; + padding: 0; + } + + .video-page-related-item { + display: flex; + gap: 1rem; + margin-bottom: 1rem; + } + + .video-page-related-details { + width: 40%; + + h6 { + font-size: 0.75rem; + } + } + + .video-page-related-cover-image { + background-color: purple; + border-radius: 0.5rem; + width: 60%; + aspect-ratio: 16/9; + } + } +} diff --git a/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx b/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx index ab08f6c6d3..eff690e759 100644 --- a/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useState } from "react"; -import { Link, useParams } from "react-router-dom"; +import { Link } from "react-router-dom"; import MediaAudioPlayer from "./MediaAudioPlayer"; -import MediaVideoComponent from "./MediaVideoComponent"; import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile"; import Container from "components/common/Container"; import Panel from "components/common/Panel"; import PageHeader from "components/layout/PageHeader"; import Skeleton from "components/common/Skeleton"; import Button from "components/common/Button"; +import { MediaSubViewProps } from "./MediaPageSwitch"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCircleExclamation, @@ -17,7 +17,6 @@ import { faLink, faFileCircleXmark, faArrowRightArrowLeft, - faCopy, faFilm, } from "@fortawesome/pro-solid-svg-icons"; import Accordion from "components/common/Accordion"; @@ -29,10 +28,7 @@ import { BucketConfig } from "@storyteller/components/src/api/BucketConfig"; import moment from "moment"; import WeightCoverImage from "components/common/WeightCoverImage"; import SocialButton from "components/common/SocialButton"; -import { Badge, Input, Modal } from "components/common"; -import BookmarkButton from "components/common/BookmarkButton"; -import LikeButton from "components/common/LikeButton"; -import { useBookmarks, useMedia, useRatings, useSession } from "hooks"; +import { ActionButton, Badge, Input } from "components/common"; import { WeightCategory } from "@storyteller/components/src/api/_common/enums/WeightCategory"; import SdBatchMediaPanel from "./components/SdBatchMediaPanel/SdBatchMediaPanel"; import { GetMediaBatchImages } from "@storyteller/components/src/api/media_files/GetMediaBatchImages"; @@ -41,41 +37,28 @@ import { DeleteFeaturedItem } from "@storyteller/components/src/api/featured_ite import { mediaTypeLabels } from "utils/mediaTypeLabels"; import { EngineMediaPanel } from "./components/EngineMediaPanel/EngineMediaPanel"; import { GetMediaFileTitle } from "common/GetMediaFileTitle"; -import { STYLES_BY_KEY } from "common/StyleOptions"; import { faCube, faStarShooting } from "@fortawesome/pro-duotone-svg-icons"; import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle"; import { GetWebsiteLink } from "@storyteller/components/src/env/GetWebsiteLink"; -export default function MediaPage() { - const { canAccessStudio, canEditMediaFile, canBanUsers, user } = useSession(); - const { token } = useParams<{ token: string }>(); - const bookmarks = useBookmarks(); - const ratings = useRatings(); - const { - media: mediaFile, - prompt, - remove, - status, - } = useMedia({ - mediaToken: token, - onSuccess: (res: any) => { - ratings.gather({ res, key: "token" }); - }, - }); +export default function MediaPage({ + bookmarkButtonProps, + canAccessStudio, + canBanUsers, + canEdit, + mediaFile, + openDeleteModal, + ratingButtonProps, + status, + urlToken, +}: MediaSubViewProps) { const batchToken = mediaFile?.maybe_batch_token; const [images, setImages] = useState<{ url: string; token: string }[]>([]); const bucketConfig = new BucketConfig(); const timeCreated = moment(mediaFile?.created_at || "").fromNow(); const dateCreated = moment(mediaFile?.created_at || "").format("LLL"); const [buttonLabel, setButtonLabel] = useState("Copy"); - const [copyPositiveButtonText, setCopyPositiveButtonText] = useState("Copy"); - const [copyNegativeButtonText, setCopyNegativeButtonText] = useState("Copy"); const [activeSlide, setActiveSlide] = useState({ url: "", token: "" }); - const viewerCanEdit = canEditMediaFile( - mediaFile?.maybe_creator_user?.user_token || "" - ); - - const isModerator = canBanUsers() || false; const viewerCanMakeFeatured = canBanUsers() || false; // Inside MediaPage.tsx @@ -99,14 +82,6 @@ export default function MediaPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [batchToken]); - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - - const closeDeleteModal = () => setIsDeleteModalOpen(false); - - const openDeleteModal = () => setIsDeleteModalOpen(true); - - const deleteMedia = () => remove(!!user?.can_ban_users); - const handleFeatureMedia = async () => { setFeatureMedia(!mediaFile?.is_featured); }; @@ -132,19 +107,6 @@ export default function MediaPage() { } }; - const copyToClipboard = async ( - text: string, - setButtonText: React.Dispatch> - ) => { - try { - await navigator.clipboard.writeText(text); - setButtonText("Copied!"); - setTimeout(() => setButtonText("Copy"), 2000); // Change back after 2 seconds - } catch (err) { - console.error("Failed to copy: ", err); - } - }; - const handleActiveSlideChange = (image: any) => { setActiveSlide(image); }; @@ -168,113 +130,6 @@ export default function MediaPage() { usePrefixedDocumentTitle(pageTitle); - const promptSection = ( - <> - {prompt?.maybe_positive_prompt && ( - -
-
Prompt
-
-
-

{prompt.maybe_positive_prompt}

-
- - {prompt?.maybe_negative_prompt && ( - <> -
-
- Negative Prompt -
-
-
-

{prompt.maybe_negative_prompt}

-
- - )} - - {prompt?.maybe_style_name && ( - <> -
-
Style Name
-
-
-

- {STYLES_BY_KEY.get(prompt.maybe_style_name)?.label} -

-
- - )} - {prompt?.used_face_detailer && ( - <> -
-
- Used Face Detailer -
-
-
-

- {prompt?.used_face_detailer ? "Yes" : "No"} -

-
- - )} - {prompt?.used_upscaler && ( - <> -
-
Used Upscaler
-
-
-

{prompt?.used_upscaler ? "Yes" : "No"}

-
- - )} - {isModerator && - mediaFile?.maybe_moderator_fields - ?.maybe_style_transfer_source_media_file_token && ( - <> -
-
Source Video
-
-
-

- - Link to source (staff only) - -

-
- - )} -
- )} - - ); - function renderMediaComponent(mediaFile: MediaFile) { switch (mediaFile.media_type) { case MediaFileType.Audio: @@ -299,17 +154,6 @@ export default function MediaPage() { )}
); - case MediaFileType.Video: - return ( - <> -
-
- -
-
- {promptSection} - - ); case MediaFileType.Image: let sdMediaImage = [ @@ -331,7 +175,6 @@ export default function MediaPage() { images={mediaFile.maybe_batch_token ? images : sdMediaImage} onActiveSlideChange={handleActiveSlideChange} /> - {promptSection} ); case MediaFileType.Pmd: @@ -563,7 +406,7 @@ export default function MediaPage() { }, ]; - if (user?.canBanUsers) { + if (canBanUsers()) { modMediaDetails = ( @@ -614,7 +457,7 @@ export default function MediaPage() { ...(coverMediaPath ? { src: bucketConfig.getGcsUrl(coverMediaPath) } : {}), - ...(viewerCanEdit ? { to: `/edit-cover-image/${token}` } : {}), + ...(canEdit ? { to: `/edit-cover-image/${urlToken}` } : {}), coverIndex: mediaFile.cover_image.default_cover.image_index, }} /> @@ -626,7 +469,7 @@ export default function MediaPage() { }; return ( -
+ <>
@@ -657,24 +500,8 @@ export default function MediaPage() { )}
- - + +
@@ -946,7 +773,7 @@ export default function MediaPage() { - {viewerCanEdit && ( + {canEdit && ( <>
- ( - <>{`Are you sure you want to delete this media file? This action cannot be undone.`} // replace w/ dynamic later -V - // <>{`Are you sure you want to delete "${title}"? This action cannot be undone.`} - )} - onConfirm={deleteMedia} - /> - + ); } diff --git a/packages/fakeyou.com/src/v2/view/pages/media/MediaPageSwitch.tsx b/packages/fakeyou.com/src/v2/view/pages/media/MediaPageSwitch.tsx new file mode 100644 index 0000000000..8a736f616d --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/media/MediaPageSwitch.tsx @@ -0,0 +1,151 @@ +import React, { useState } from "react"; +import { useParams } from "react-router-dom"; +import { useBookmarks, useMedia, useRatings, useSession } from "hooks"; +import { MediaFileType } from "@storyteller/components/src/api/_common/enums/MediaFileType"; +import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia"; +import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts"; +import { CreateFeaturedItem } from "@storyteller/components/src/api/featured_items/CreateFeaturedItem"; +import { DeleteFeaturedItem } from "@storyteller/components/src/api/featured_items/DeleteFeaturedItem"; +import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes"; +import { ActionButtonProps, Container, Modal, Panel } from "components/common"; +import MediaPage from "./MediaPage"; +import VideoMediaPage from "./VideoMediaPage"; + +import { faStarShooting } from "@fortawesome/pro-duotone-svg-icons"; +import { faStarShooting as faStarShootingOutline } from "@fortawesome/pro-regular-svg-icons"; + +import "./MediaPage.scss"; + +export interface MediaSubViewProps { + bookmarkButtonProps: ActionButtonProps; + canAccessStudio: () => boolean; + canBanUsers: () => boolean; + canEdit: boolean; + featureButtonProps: ActionButtonProps; + isFeatured: boolean; + isModerator: boolean; + mediaFile?: MediaFile; + openDeleteModal: () => void; + prompt?: Prompt; + ratingButtonProps: ActionButtonProps; + status: FetchStatus; + title: string; + titleSet: (title: string) => void; + urlToken: string; +} + +export default function MediaPageSwitch() { + const { token: urlToken } = useParams<{ token: string }>(); + const { canAccessStudio, canBanUsers, canEditMediaFile, user } = useSession(); + const bookmarks = useBookmarks(); + const ratings = useRatings(); + + const [isFeatured, isFeaturedSet] = useState(false); + const [title, titleSet] = useState(""); + + const { mediaFile, prompt, remove, status } = useMedia({ + mediaToken: urlToken, + onSuccess: (res: any) => { + isFeaturedSet(res.is_featured); + titleSet(res.maybe_title || "Untitled Video"); + ratings.gather({ res, key: "token" }); + bookmarks.gather({ res, key: "token" }); + }, + }); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + + const canEdit = canEditMediaFile(urlToken); + const isModerator = canBanUsers() || false; + + const closeDeleteModal = () => setIsDeleteModalOpen(false); + const openDeleteModal = () => setIsDeleteModalOpen(true); + const deleteMedia = () => remove(!!user?.can_ban_users); + + const bookmarkButtonProps: ActionButtonProps = { + ...bookmarks.makeProps({ + entityToken: urlToken, + entityType: "media_file", + }), + toolTipOff: "Add to bookmarks", + toolTipOn: "Remove bookmark", + }; + + const ratingButtonProps: ActionButtonProps = { + ...ratings.makeProps({ + entityToken: urlToken, + entityType: "media_file", + }), + toolTipOff: "Like this", + toolTipOn: "Unlike this", + }; + + const featureButtonProps: ActionButtonProps = { + actionType: "feature", + isToggled: isFeatured, + labelOff: "Feature", + labelOn: "Featured", + iconOn: faStarShooting, + iconOff: faStarShootingOutline, + toggle: () => { + (isFeatured ? DeleteFeaturedItem : CreateFeaturedItem)("", { + entity_type: "media_file", + entity_token: mediaFile?.token || "", + }).then(() => { + isFeaturedSet((current: boolean) => { + return !current; + }); + }); + }, + toolTipOff: "Feature this", + toolTipOn: "Remove from featured", + }; + + const subViewProps: MediaSubViewProps = { + bookmarkButtonProps, + canAccessStudio, + canBanUsers, + canEdit, + featureButtonProps, + isFeatured, + isModerator, + mediaFile, + openDeleteModal, + prompt, + ratingButtonProps, + status, + title, + titleSet, + urlToken, + }; + + const MediaSwitch = () => { + switch (mediaFile?.media_type) { + case MediaFileType.Video: + return ; + default: + return ; + } + }; + + return ( + <> + + + {MediaSwitch()} + + + ( + <>{`Are you sure you want to delete this media file? This action cannot be undone.`} // replace w/ dynamic later -V + // <>{`Are you sure you want to delete "${title}"? This action cannot be undone.`} + ), + handleClose: closeDeleteModal, + onConfirm: deleteMedia, + show: isDeleteModalOpen, + title: "Delete Media", + }} + /> + + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/media/MediaVideoComponent.tsx b/packages/fakeyou.com/src/v2/view/pages/media/MediaVideoComponent.tsx index 786de72de2..e7afd9c27f 100644 --- a/packages/fakeyou.com/src/v2/view/pages/media/MediaVideoComponent.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/media/MediaVideoComponent.tsx @@ -3,20 +3,20 @@ import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaF import React from "react"; interface MediaVideoComponentProps { - mediaFile: MediaFile; + mediaFile?: MediaFile; } export default function MediaVideoComponent({ mediaFile, }: MediaVideoComponentProps) { - let mediaLink = new BucketConfig().getGcsUrl( - mediaFile.public_bucket_path - ); + let mediaLink = mediaFile + ? new BucketConfig().getGcsUrl(mediaFile.public_bucket_path) + : ""; - return ( + return mediaFile && mediaFile.public_bucket_path ? ( - ); + ) : null; } diff --git a/packages/fakeyou.com/src/v2/view/pages/media/PromptViewer.tsx b/packages/fakeyou.com/src/v2/view/pages/media/PromptViewer.tsx new file mode 100644 index 0000000000..b90aa20942 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/media/PromptViewer.tsx @@ -0,0 +1,142 @@ +import React, { useState } from "react"; +import { Link } from "react-router-dom"; +import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts"; +import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia"; +import { Button, Panel } from "components/common"; +import { STYLES_BY_KEY } from "common/StyleOptions"; +import { faCopy } from "@fortawesome/pro-solid-svg-icons"; + +interface PromptViewerProps { + isModerator: boolean; + mediaFile?: MediaFile; + prompt: Prompt; +} + +export default function PromptViewer({ + isModerator, + mediaFile, + prompt, +}: PromptViewerProps) { + const [copyPositiveButtonText, setCopyPositiveButtonText] = useState("Copy"); + const [copyNegativeButtonText, setCopyNegativeButtonText] = useState("Copy"); + + const copyToClipboard = async ( + text: string, + copyTextSet: React.Dispatch> + ) => { + try { + await navigator.clipboard.writeText(text); + copyTextSet("Copied!"); + setTimeout(() => copyTextSet("Copy"), 2000); // Change back after 2 seconds + } catch (err) { + console.error("Failed to copy: ", err); + } + }; + + return ( + <> + {prompt.maybe_positive_prompt && ( + +
+
Prompt
+
+
+

{prompt.maybe_positive_prompt}

+
+ + {prompt.maybe_negative_prompt && ( + <> +
+
+ Negative Prompt +
+
+
+

{prompt.maybe_negative_prompt}

+
+ + )} + + {prompt.maybe_style_name && ( + <> +
+
Style Name
+
+
+

+ {STYLES_BY_KEY.get(prompt.maybe_style_name)?.label} +

+
+ + )} + {prompt?.used_face_detailer && ( + <> +
+
+ Used Face Detailer +
+
+
+

+ {prompt?.used_face_detailer ? "Yes" : "No"} +

+
+ + )} + {prompt?.used_upscaler && ( + <> +
+
Used Upscaler
+
+
+

{prompt?.used_upscaler ? "Yes" : "No"}

+
+ + )} + {isModerator && + mediaFile?.maybe_moderator_fields + ?.maybe_style_transfer_source_media_file_token && ( + <> +
+
Source Video
+
+
+

+ + Link to source (staff only) + +

+
+ + )} +
+ )} + + ); +} diff --git a/packages/fakeyou.com/src/v2/view/pages/media/VideoMediaPage.tsx b/packages/fakeyou.com/src/v2/view/pages/media/VideoMediaPage.tsx new file mode 100644 index 0000000000..b2731c4bf2 --- /dev/null +++ b/packages/fakeyou.com/src/v2/view/pages/media/VideoMediaPage.tsx @@ -0,0 +1,335 @@ +import React, { useState } from "react"; +import moment from "moment"; +import { AnimationStatus, useAnimationStatus } from "hooks"; +import { + ActionButton, + Button, + TempInput as Input, + SocialButton, + Socials, + Spinner, +} from "components/common"; +import { Gravatar } from "@storyteller/components/src/elements/Gravatar"; +import { CommentComponent } from "v2/view/_common/comments/CommentComponent"; +import MediaVideoComponent from "./MediaVideoComponent"; +import { + RenameMedia, + RenameMediaResponse, +} from "@storyteller/components/src/api/media_files/RenameMedia"; +import PromptViewer from "./PromptViewer"; +import { MediaSubViewProps } from "./MediaPageSwitch"; +import { a, useTransition } from "@react-spring/web"; +import { basicTransition } from "resources"; + +import { + faArrowRightArrowLeft, + faCheck, + faPen, + faTrash, + faX, +} from "@fortawesome/pro-solid-svg-icons"; + +export enum EditingTitleState { + closed, + editing, + saving, +} + +export default function DevMediaPage({ + bookmarkButtonProps, + canAccessStudio, + canEdit, + featureButtonProps, + isModerator, + mediaFile, + openDeleteModal, + prompt, + ratingButtonProps, + title, + titleSet, + urlToken, +}: MediaSubViewProps) { + const [editingTitle, editingTitleSet] = useState(EditingTitleState.closed); + const { events, status: animationStatus } = useAnimationStatus(); + const titlePaused = animationStatus === AnimationStatus.paused; + + const transitions = useTransition( + editingTitle, + basicTransition({ ...events }) + ); + + const saveTitle = () => { + editingTitleSet(EditingTitleState.saving); + RenameMedia(urlToken, { + name: title, + }).then((res: RenameMediaResponse) => { + if (res.success) { + editingTitleSet(EditingTitleState.closed); + } + }); + }; + + // for later reference and eventual replacement + + // const fakeVideos = [ + // { + // coverImage: "", + // title: "Lorem ipsum", + // creator: "Alfredo Someguy", + // }, + // { + // coverImage: "", + // title: "The Quick Brown Fox", + // creator: "Cheese McGee", + // }, + // { + // coverImage: "", + // title: "An Anime Video", + // creator: "Georgia Jones", + // }, + // { + // coverImage: "", + // title: "Some Other Video", + // creator: "Brain Problems", + // }, + // { + // coverImage: "", + // title: "Kachow", + // creator: "Mr Smith", + // }, + // { + // coverImage: "", + // title: "Lorem ipsum", + // creator: "Alfredo Someguy", + // }, + // { + // coverImage: "", + // title: "Lorem ipsum", + // creator: "Alfredo Someguy", + // }, + // ]; + + const shareLinks: Socials[] = [ + "x", + "whatsapp", + "facebook", + "reddit", + "email", + ]; + + return ( + <> +
+
+ +
+
+ {transitions( + (style: any, yah) => + [ + +
+

{title}

+ {`Created ${moment( + mediaFile?.created_at || "" + ).fromNow()}`} +
+ {canEdit && ( +
+ +
+
+ +
+

{mediaFile?.maybe_creator_user?.username || ""}

+
+
+
+
Share
+
+ {shareLinks.map((social, i) => ( + + ))} +
+
+
+
Actions
+
+ + + {isModerator && } +
+
+
+ {prompt && ( + + )} +
+
Comments
+ +
+ + {/*
+ {fakeVideos.map((relatedVideo, i) => ( +
+
+
+
{relatedVideo.title}
+
+
+ ))} +
*/} + + ); +}