diff --git a/_headers b/_headers new file mode 100644 index 0000000000..c2dc767cd2 --- /dev/null +++ b/_headers @@ -0,0 +1,3 @@ +/* + Access-Control-Allow-Origin: * + diff --git a/script/netlify_build.sh b/script/netlify_build.sh index 10ed1c000e..9a7dc68922 100755 --- a/script/netlify_build.sh +++ b/script/netlify_build.sh @@ -8,5 +8,8 @@ yarn run build2 echo "Copy test files into dist" cp -r test/* dist/ +echo "Copy netlify headers config into dist" +cp _headers dist/ + echo "List files after build" find dist/ diff --git a/src/KonvaApp/index.ts b/src/KonvaApp/index.ts index f2fe03cc0b..452cf23ac4 100644 --- a/src/KonvaApp/index.ts +++ b/src/KonvaApp/index.ts @@ -1,7 +1,7 @@ // to do fix https://codesandbox.io/p/sandbox/react-konva-infinite-grid-kkndq?file=%2Fsrc%2Findex.js the dotted background doesn't move when draggable. import { Engine } from "./Engine"; export { Engine as EngineType }; -export const KonvaApp = async (element: HTMLDivElement) => { +export const KonvaApp = (element: HTMLDivElement) => { const engine = new Engine(element); engine.initializeStage(""); return engine; diff --git a/src/KonvaRootComponent/KonvaRootComponent.tsx b/src/KonvaRootComponent/KonvaRootComponent.tsx index 77155e6baf..21cc2f2960 100644 --- a/src/KonvaRootComponent/KonvaRootComponent.tsx +++ b/src/KonvaRootComponent/KonvaRootComponent.tsx @@ -1,26 +1,33 @@ import { useCallback, useRef } from "react"; -import { useRenderCounter } from "~/hooks/useRenderCounter"; +// Components import { KonvaCanvasContainer } from "./KonvaCanvasContainer"; import { ContextualButtonRetry } from "./ContextualButtonRetry"; import { ContextualToolbarImage } from "./ContextualToolbarImage"; import { ContextualLoadingBar } from "./ContextualLoadingBar"; import { SignaledToolbarMain } from "./SignaledToolbarMain"; -import { SignaledDialogError } from "./SignaledComponents"; -import { EngineType } from "~/KonvaApp"; +import { SignaledDialogs } from "./SignaledDialogs"; +import { + SignaledDialogError, + SignaledCanvasDragDropFiles, +} from "./OtherSignaledComponents"; // The KonvaApp is the root of the Konva stage // and only entry point for anything in Konva JS +import { EngineType } from "~/KonvaApp"; import { KonvaApp } from "~/KonvaApp"; -import { Button } from "~/components/ui"; -import { uiAccess } from "~/signals"; +// all the signal-contexts are wrapped in hooks +import { useAppUiContext } from "./contextSignals/appUi"; + +// common hooks +import { useRenderCounter } from "~/hooks/useRenderCounter"; export const KonvaRootComponent = ({ className }: { className: string }) => { // This is a hook that will log the number of times the component has rerendered // Let's make sure we only log once useRenderCounter("KonvaRootComponent"); - + const appUiContext = useAppUiContext(); const engineRef = useRef(null); const konvaContainerCallbackRef = useCallback((node: HTMLDivElement) => { @@ -31,23 +38,25 @@ export const KonvaRootComponent = ({ className }: { className: string }) => { return ( <> - - + + + + diff --git a/src/KonvaRootComponent/OtherSignaledComponents.tsx b/src/KonvaRootComponent/OtherSignaledComponents.tsx new file mode 100644 index 0000000000..6695e18c19 --- /dev/null +++ b/src/KonvaRootComponent/OtherSignaledComponents.tsx @@ -0,0 +1,75 @@ +import { useCallback } from "react"; + +import { DialogError, CanvasDragDropFiles } from "~/components/features"; +import { uiAccess } from "~/signals"; + +// global signals +import { dialogueError } from "~/signals/uiAccess/dialogueError"; +import { getFileExtension } from "~/utilities"; + +// enums +import { IMAGE_FILE_TYPE, VIDEO_FILE_TYPE } from "~/constants/fileTypeEnums"; + +export const SignaledDialogError = () => { + const props = dialogueError.signal.value; + const { isShowing, title, message } = props; + const onClose = useCallback(() => { + dialogueError.hide(); + }, []); + return ( + + ); +}; + +export const SignaledCanvasDragDropFiles = ({ + openAddImage, + openAddVideo, +}: { + openAddImage: (file?: File) => void; + openAddVideo: (file?: File) => void; +}) => { + // const [files, setFiles] = useState([]); + const imageFileTypes = Object.values(IMAGE_FILE_TYPE) as string[]; + const videoFileTypes = Object.values(VIDEO_FILE_TYPE) as string[]; + const fileTypes = [...imageFileTypes, ...videoFileTypes]; + const onSetFiles = useCallback((files: File[]) => { + if (files.length < 0) { + return; + } + if (files.length > 1) { + uiAccess.dialogueError.show({ + title: "Error in Adding files", + message: + "Sorry, we don't support adding multiple files at once, yet! it's coming soon.", + }); + return; + } + const file = files[0]; + const fileExt = getFileExtension(file); + if (imageFileTypes.includes(fileExt)) { + openAddImage(file); + return; + } + if (videoFileTypes.includes(fileExt)) { + openAddVideo(file); + return; + } + uiAccess.dialogueError.show({ + title: "Error in Adding files", + message: `Sorry, we do not support this file type: .${fileExt.toUpperCase()}`, + }); + return; + }, []); + return ( + + ); +}; diff --git a/src/KonvaRootComponent/SignaledComponents.tsx b/src/KonvaRootComponent/SignaledComponents.tsx deleted file mode 100644 index b52db099af..0000000000 --- a/src/KonvaRootComponent/SignaledComponents.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useCallback } from "react"; - -import { dialogueError } from "~/signals/uiAccess/dialogueError"; - -import { DialogError } from "~/components/features"; - -export const SignaledDialogError = () => { - const props = dialogueError.signal.value; - const { isShowing, title, message } = props; - const onClose = useCallback(() => { - dialogueError.hide(); - }, []); - return ( - - ); -}; diff --git a/src/KonvaRootComponent/SignaledDialogs.tsx b/src/KonvaRootComponent/SignaledDialogs.tsx new file mode 100644 index 0000000000..c735384d42 --- /dev/null +++ b/src/KonvaRootComponent/SignaledDialogs.tsx @@ -0,0 +1,51 @@ +import { Signal } from "@preact/signals-react"; +import { dispatchUiEvents } from "~/signals"; +import { + DialogAddImage, + DialogAddVideo, + DialogAiStylize, +} from "~/components/features"; + +import { AppUiSignalType } from "./contextSignals/appUi"; + +export const SignaledDialogs = ({ + appUiSignal, + resetAll, +}: { + appUiSignal: Signal; + resetAll: () => void; +}) => { + return ( + <> + + { + if (!response.data) { + return; + } + dispatchUiEvents.addVideoToEngine({ + url: response.data.public_bucket_url, + }); + }} + /> + { + const { selectedArtStyle: artstyle, ...rest } = data; + dispatchUiEvents.aiStylize.dispatchRequest({ + artstyle, + ...rest, + }); + }} + closeCallback={resetAll} + /> + + ); +}; diff --git a/src/KonvaRootComponent/SignaledToolbarMain.tsx b/src/KonvaRootComponent/SignaledToolbarMain.tsx index 5d1a94e450..8b2866f8e3 100644 --- a/src/KonvaRootComponent/SignaledToolbarMain.tsx +++ b/src/KonvaRootComponent/SignaledToolbarMain.tsx @@ -1,30 +1,21 @@ -import { MouseEventHandler, useCallback, useState } from "react"; - +import { MouseEventHandler } from "react"; import { toolbarMain } from "~/signals/uiAccess/toolbarMain"; import { dispatchers } from "~/signals/uiEvents/toolbarMain"; -import { dispatchUiEvents } from "~/signals"; -import { - ToolbarMain, - DialogAddImage, - DialogAddVideo, - DialogAiStylize, -} from "~/components/features"; +import { ToolbarMain } from "~/components/features"; import { LoadingBar } from "~/components/ui"; import { ToolbarMainButtonNames } from "~/components/features/ToolbarMain/enum"; -const initialState = { - isAddVideoOpen: false, - isAddImageOpen: false, - isAiStylizeOpen: false, -}; - -export const SignaledToolbarMain = () => { - const [state, setState] = useState(initialState); - const closeAll = useCallback(() => { - setState(initialState); - }, []); +export const SignaledToolbarMain = ({ + openAddImage, + openAddVideo, + openAIStylize, +}: { + openAddImage: () => void; + openAddVideo: () => void; + openAIStylize: () => void; +}) => { const loadingBar = toolbarMain.loadingBar.signal.value; const buttonProps = Object.values(ToolbarMainButtonNames).reduce( (acc, buttonName) => { @@ -62,51 +53,9 @@ export const SignaledToolbarMain = () => { { - setState({ - ...initialState, //this closes all other opened things - isAddImageOpen: true, - }); - }} - openAddVideo={() => { - setState({ - ...initialState, //this closes all other opened things - isAddVideoOpen: true, - }); - }} - openAIStylize={() => { - setState({ - ...initialState, //this closes all other opened things - isAiStylizeOpen: true, - }); - }} - /> - - { - if (!response.data) { - return; - } - dispatchUiEvents.addVideoToEngine({ - url: response.data.public_bucket_url, - }); - }} - /> - { - const { selectedArtStyle: artstyle, ...rest } = data; - dispatchUiEvents.aiStylize.dispatchRequest({ - artstyle, - ...rest, - }); - }} - closeCallback={closeAll} + openAddImage={() => openAddImage()} + openAddVideo={() => openAddVideo()} + openAIStylize={() => openAIStylize()} /> ); diff --git a/src/KonvaRootComponent/contextSignals/appUi.ts b/src/KonvaRootComponent/contextSignals/appUi.ts new file mode 100644 index 0000000000..aaaf82e536 --- /dev/null +++ b/src/KonvaRootComponent/contextSignals/appUi.ts @@ -0,0 +1,50 @@ +import { useCallback, useRef } from "react"; +import { signal } from "@preact/signals-react"; +export type AppUiSignalType = { + isAddVideoOpen: boolean; + stagedVideo: File | null; + isAddImageOpen: boolean; + stagedImage: File | null; + isAiStylizeOpen: boolean; +}; +const appUiInitialState = { + isAddVideoOpen: false, + stagedVideo: null, + isAddImageOpen: false, + stagedImage: null, + isAiStylizeOpen: false, +}; +export const useAppUiContext = () => { + const appUiRef = useRef(signal(appUiInitialState)); + const appUiSignal = appUiRef.current; + const resetAll = useCallback(() => { + appUiSignal.value = appUiInitialState; + }, []); + const openAddImage = useCallback((file?: File) => { + appUiSignal.value = { + ...appUiInitialState, + isAddImageOpen: true, + stagedImage: file ? file : null, + }; + }, []); + const openAddVideo = useCallback((file?: File) => { + appUiSignal.value = { + ...appUiInitialState, + isAddVideoOpen: true, + stagedVideo: file ? file : null, + }; + }, []); + const openAIStylize = useCallback(() => { + appUiSignal.value = { + ...appUiInitialState, + isAiStylizeOpen: true, + }; + }, []); + return { + signal: appUiSignal, + resetAll, + openAddImage, + openAddVideo, + openAIStylize, + }; +}; diff --git a/src/app/pages/sandbox/index.tsx b/src/app/pages/sandbox/index.tsx index 6abf0397de..17f43f6da3 100644 --- a/src/app/pages/sandbox/index.tsx +++ b/src/app/pages/sandbox/index.tsx @@ -1,7 +1,7 @@ import { ContextualToolbarImage } from "~/KonvaRootComponent/ContextualToolbarImage"; import { ContextualLoadingBar } from "~/KonvaRootComponent/ContextualLoadingBar"; import { ContextualButtonRetry } from "~/KonvaRootComponent/ContextualButtonRetry"; -import { SignaledDialogError } from "~/KonvaRootComponent/SignaledComponents"; +import { SignaledDialogError } from "~/KonvaRootComponent/OtherSignaledComponents"; import { useRenderCounter } from "~/hooks/useRenderCounter"; diff --git a/src/components/features/CanvasDragDropFiles.tsx b/src/components/features/CanvasDragDropFiles.tsx new file mode 100644 index 0000000000..ad9a465c99 --- /dev/null +++ b/src/components/features/CanvasDragDropFiles.tsx @@ -0,0 +1,127 @@ +import { + DragEventHandler, + HTMLAttributes, + useEffect, + useRef, + useState, +} from "react"; +import { faFilePlus } from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + +interface CanvasDragDropFilesInterface extends HTMLAttributes { + fileTypes: string[]; + onSetFiles: (file: File[]) => void; +} +enum DragStates { + IDLE = "idle", + READY = "ready", + DRAGGING = "dragging", +} +export const CanvasDragDropFiles = ({ + fileTypes, + onSetFiles, + className, + children, +}: CanvasDragDropFilesInterface) => { + const [dragging, setDragging] = useState(DragStates.IDLE); + const lastDragging = useRef(DragStates.IDLE); + + const handleDrop: DragEventHandler = (event) => { + event.preventDefault(); + if (!event.dataTransfer) { + return; + } + const droppedFiles = event.dataTransfer.files; + if (droppedFiles.length > 0) { + const newFiles = Array.from(droppedFiles) as File[]; + onSetFiles(newFiles); + setDragging(DragStates.READY); + } + }; + const handleDragOver: DragEventHandler = (event) => { + event.preventDefault(); + setDragging((curr) => { + if (!event.dataTransfer) { + return curr; + } + if (curr !== DragStates.DRAGGING) { + lastDragging.current = curr; + } + return DragStates.DRAGGING; + }); + }; + + const handleDragExit: DragEventHandler = (event) => { + event.preventDefault(); + setDragging(lastDragging.current); + }; + + useEffect(() => { + const mouseLeaveHandler = (event: MouseEvent) => { + if ( + event.clientY <= 0 || + event.clientX <= 0 || + event.clientX >= window.innerWidth || + event.clientY >= window.innerHeight + ) { + setDragging(DragStates.READY); + } + }; + const mouseEnterHandler = () => { + setDragging((curr) => { + if (curr === DragStates.DRAGGING) { + return curr; + } + return DragStates.IDLE; + }); + }; + + document.addEventListener("mouseleave", mouseLeaveHandler); + document.addEventListener("mouseenter", mouseEnterHandler); + + return () => { + document.removeEventListener("mouseleave", mouseLeaveHandler); + document.removeEventListener("mouseenter", mouseEnterHandler); + }; + }, []); + return ( +
+ {dragging === DragStates.DRAGGING && ( +
+ +

+ Drag and drop files here to add them to the board! +

+

+ Acceptable file types:{" "} + + {fileTypes.reduce((acc, curr, idx) => { + const prefix = curr[0] !== "." ? "." : ""; + const suffix = idx === fileTypes.length - 1 ? "" : ", "; + return acc + prefix + curr.toUpperCase() + suffix; + }, "")} + +

+
+ )} + {children} +
+
+ ); +}; diff --git a/src/components/features/DialogAddImage/index.tsx b/src/components/features/DialogAddImage/index.tsx index 3d9b832fa6..a0cff325cf 100644 --- a/src/components/features/DialogAddImage/index.tsx +++ b/src/components/features/DialogAddImage/index.tsx @@ -1,10 +1,12 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import { twMerge } from "tailwind-merge"; import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; -import { FileUploader, IMAGE_FILE_TYPE } from "../FileUploader"; +import { FileUploader } from "../FileUploader"; import { Button } from "~/components/ui"; +import { IMAGE_FILE_TYPE } from "~/constants/fileTypeEnums"; + import { paperWrapperStyles, dialogBackgroundStyles, @@ -12,13 +14,26 @@ import { import { dispatchUiEvents } from "~/signals/uiEvents/"; export const DialogAddImage = ({ + stagedImage = null, isOpen, closeCallback, }: { + stagedImage?: File | null; isOpen: boolean; closeCallback: () => void; }) => { const [assetFile, setAssetFile] = useState(null); + const previouslyStagedImageRef = useRef(null); + + const currFile = + stagedImage && + stagedImage !== assetFile && + stagedImage !== previouslyStagedImageRef.current + ? stagedImage + : assetFile; + if (previouslyStagedImageRef.current !== stagedImage) { + previouslyStagedImageRef.current = stagedImage; + } function handleClose() { setAssetFile(null); @@ -26,8 +41,8 @@ export const DialogAddImage = ({ } function handleEnter() { - if (assetFile) { - dispatchUiEvents.addImageToEngine(assetFile); + if (currFile) { + dispatchUiEvents.addImageToEngine(currFile); } handleClose(); } @@ -44,18 +59,18 @@ export const DialogAddImage = ({ { setAssetFile(file); }} /> - {assetFile && ( + {currFile && (
@@ -64,7 +79,7 @@ export const DialogAddImage = ({ -
diff --git a/src/components/features/DialogAddVideo/DialogAddVideo.tsx b/src/components/features/DialogAddVideo/DialogAddVideo.tsx index debb1b7f95..60ab11562f 100644 --- a/src/components/features/DialogAddVideo/DialogAddVideo.tsx +++ b/src/components/features/DialogAddVideo/DialogAddVideo.tsx @@ -24,20 +24,32 @@ const initialState = { dialogStatus: DialogAddMediaStatuses.STAGING_FILE, }; export const DialogAddVideo = ({ + stagedVideo = null, isOpen, closeCallback, onUploadedVideo, }: { + stagedVideo?: File | null; isOpen: boolean; closeCallback: () => void; onUploadedVideo: (response: ApiResponse) => void; }) => { - // useRenderCounter("DialogAddVideo"); - const [{ file, dialogStatus }, setStates] = useState<{ file: File | null; dialogStatus: DialogAddMediaStatuses; }>(initialState); + const previouslyStagedVideoRef = useRef(null); + + const currFile = + stagedVideo && + stagedVideo !== file && + stagedVideo !== previouslyStagedVideoRef.current + ? stagedVideo + : file; + if (previouslyStagedVideoRef.current !== stagedVideo) { + previouslyStagedVideoRef.current = stagedVideo; + } + console.log(currFile); const trimDataRef = useRef(signal(undefined)); const trimData = trimDataRef.current; @@ -81,7 +93,7 @@ export const DialogAddVideo = ({ {dialogStatus === DialogAddMediaStatuses.STAGING_FILE && ( <> { setStates((curr) => ({ ...curr, file })); }} diff --git a/src/components/features/DialogAddVideo/QuickTrimVideoUploader.tsx b/src/components/features/DialogAddVideo/QuickTrimVideoUploader.tsx index b7a9bbe172..8e1a06d288 100644 --- a/src/components/features/DialogAddVideo/QuickTrimVideoUploader.tsx +++ b/src/components/features/DialogAddVideo/QuickTrimVideoUploader.tsx @@ -1,10 +1,12 @@ import { useCallback, useState } from "react"; import { Signal } from "@preact/signals-react"; -import { FileUploader, VIDEO_FILE_TYPE } from "../FileUploader"; +import { FileUploader } from "../FileUploader"; import { VideoControls } from "./VideoControls"; import { TrimmerPlaybar, TrimData } from "./TrimmerPlaybar"; +import { VIDEO_FILE_TYPE } from "~/constants/fileTypeEnums"; + export type { TrimData }; export const QuickTrimVideoUploader = ({ diff --git a/src/components/features/FileUploader/DragAndDropZone.tsx b/src/components/features/FileUploader/DragAndDropZone.tsx index a3996118af..5650b2a690 100644 --- a/src/components/features/FileUploader/DragAndDropZone.tsx +++ b/src/components/features/FileUploader/DragAndDropZone.tsx @@ -5,9 +5,9 @@ import { faFileImage, faFileVideo, } from "@fortawesome/pro-solid-svg-icons"; -import { getFileName } from "~/utilities"; -import { VIDEO_FILE_TYPE } from "./enums"; +import { VIDEO_FILE_TYPE } from "~/constants/fileTypeEnums"; +// import { getFileName } from "~/utilities"; interface Props { file: File | null; diff --git a/src/components/features/FileUploader/index.tsx b/src/components/features/FileUploader/index.tsx index aa366859d4..003bcd36d4 100644 --- a/src/components/features/FileUploader/index.tsx +++ b/src/components/features/FileUploader/index.tsx @@ -25,5 +25,3 @@ export const FileUploader = ({ fileTypes, title, file, setFile }: Props) => { ); }; - -export { IMAGE_FILE_TYPE, VIDEO_FILE_TYPE } from "./enums"; diff --git a/src/components/features/index.ts b/src/components/features/index.ts index 532aefbe5c..879d27bc04 100644 --- a/src/components/features/index.ts +++ b/src/components/features/index.ts @@ -1,3 +1,4 @@ +export { CanvasDragDropFiles } from "./CanvasDragDropFiles"; export { DialogAddImage } from "./DialogAddImage"; export { DialogAddVideo } from "./DialogAddVideo"; export { DialogAiStylize } from "./DialogAiStylize"; diff --git a/src/constants/fileTypeEnums.ts b/src/constants/fileTypeEnums.ts new file mode 100644 index 0000000000..5ddeec5d9b --- /dev/null +++ b/src/constants/fileTypeEnums.ts @@ -0,0 +1,10 @@ +export enum IMAGE_FILE_TYPE { + PNG = "png", + JPG = "jpg", + JPEG = "jpeg", + WEBP = "webp", +} + +export enum VIDEO_FILE_TYPE { + MP4 = "mp4", +} diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 517eb3a48c..4f7b846f03 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -8,5 +8,5 @@ export const getFileName = (file: File) => { return file.name.substring(0, file.name.lastIndexOf(".")); }; export const getFileExtension = (file: File) => { - return file.name.substring(file.name.lastIndexOf(".")); + return file.name.substring(file.name.lastIndexOf(".") + 1); };