From b742360a534295065f406a27968f955c86d36337 Mon Sep 17 00:00:00 2001 From: Mikkel RINGAUD Date: Mon, 14 Aug 2023 02:13:33 +0200 Subject: [PATCH] refactor: dashboard layout and shared page --- src/components/layouts/dashboard.tsx | 0 src/routes/dashboard.tsx | 409 ++++++++++++- src/routes/dashboard/[workspace_id].tsx | 732 +++++++----------------- src/routes/dashboard/shared.tsx | 489 ++++++---------- src/types/api.ts | 5 + src/utils/users.ts | 4 +- 6 files changed, 771 insertions(+), 868 deletions(-) create mode 100644 src/components/layouts/dashboard.tsx diff --git a/src/components/layouts/dashboard.tsx b/src/components/layouts/dashboard.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/dashboard.tsx b/src/routes/dashboard.tsx index 9a0197d..2d48c51 100644 --- a/src/routes/dashboard.tsx +++ b/src/routes/dashboard.tsx @@ -1,18 +1,407 @@ -import { type Component, Show } from "solid-js"; -import { Outlet, Navigate } from "solid-start"; -import { auth, isAuthenticated } from "@/stores/auth"; +import { type Component, Show, createSignal } from "solid-js"; +import { Outlet, Navigate, useParams, useNavigate, A, useLocation } from "solid-start"; +import { auth, isAuthenticated, logOutUser } from "@/stores/auth"; + +import { Motion } from "@motionone/solid"; + +import { autofocus } from "@solid-primitives/autofocus"; +// prevents from being tree-shaken by TS +autofocus; + +import IconStarOutline from "~icons/mdi/star-outline"; +import IconPlus from "~icons/mdi/plus"; +import IconFolderAccountOutline from "~icons/mdi/folder-account-outline"; +import IconAccountMultipleOutline from "~icons/mdi/account-multiple-outline"; +import IconTrashCanOutline from "~icons/mdi/trash-can-outline"; +import IconAccount from "~icons/mdi/account"; +import IconMenuDown from "~icons/mdi/menu-down"; +import IconClose from "~icons/mdi/close"; +import IconLogout from "~icons/mdi/logout"; +import IconFolderPlusOutline from "~icons/mdi/folder-plus-outline"; +import cattoDriveBox from "@/assets/icon/box.png"; +import cattoDriveCatto from "@/assets/icon/catto.png"; +import SpinnerRingResize from "~icons/svg-spinners/ring-resize"; + import FullscreenLoader from "@/components/FullscreenLoader"; +import { createFileImporter, makeFileUpload } from "@/utils/files"; +import { setWorkspaces, workspaces } from "@/stores/workspaces"; +import { WorkspaceContent } from "@/types/api"; +import { DropdownMenu } from "@kobalte/core"; +import { createModal } from "@/primitives/modal"; +import { createWorkspace } from "@/utils/workspaces"; + +export const [layoutLoading, setLayoutLoading] = createSignal(true); + +export const fileUploadHandler = async (workspace_id: string | undefined, files: FileList) => { + if (!auth.profile) return; + if (!workspace_id) return; + + try { + const uploaded = await makeFileUpload(files, { + workspace_id: workspace_id, + private: true, + }); + + const new_content: WorkspaceContent[] = uploaded.map((file) => ({ + type: "file", + data: file, + })); + + setWorkspaces(workspace_id, "content", (files) => + files ? [...files, ...new_content] : new_content + ); + } + catch (error) { + console.error(error); + } +}; + +export const LoadingSection: Component<{ message: string }> = (props) => ( + + + + + +
+
+ + +

+ Loading... +

+
+

+ {props.message} +

+
+
+); const Layout: Component = () => { + const navigate = useNavigate(); + const location = useLocation(); + const params = useParams() as { workspace_id: string | undefined }; + const [newFolderName, setNewFolderName] = createSignal(""); + + const [openNewFolderModal] = createModal((Modal) => ( + <> +
+

+ Create a new folder +

+ + + +
+ +
{ + event.preventDefault(); + if (!params.workspace_id) return; + + const workspace = await createWorkspace( + params.workspace_id, + newFolderName() + ); + + const item: WorkspaceContent = { + type: "workspace", + data: workspace, + }; + + setWorkspaces(params.workspace_id, "content", (prev) => + prev ? [...prev, item] : [item] + ); + + setNewFolderName(""); + Modal.close(); + }} + > + setNewFolderName(event.target.value)} + required + use:autofocus autofocus + /> + +
+ + Cancel + + +
+
+ + )); + return ( - - } + } > - }> - + } + > +
+ + +
+
+

+ {params.workspace_id === auth.profile!.root_workspace_id ? "My workspace" : ( + params.workspace_id ? workspaces[params.workspace_id] + ? (workspaces[params.workspace_id].meta.name || workspaces[params.workspace_id].meta.id) + : "Loading..." + : "Drive" + )} +

+
+ {/* + + + + + + { + const workspace = await createWorkspace( + params.workspace_id + ); + const item: WorkspaceContent = { + type: "workspace", + data: workspace, + }; + + setWorkspaces(params.workspace_id, "content", (prev) => + prev ? [...prev, item] : [item] + ); + }} + > + New Folder ⌘ N + + + + Sort By... + + + + + + + + + +

Name

+
+ + + + +

Date modified

+
+ + + + +

Kind

+
+ + + + +

Size

+
+
+
+
+
+ + Workspace properties + +
+
+
*/} + + + + + + + + + navigate("/account")} + > + + My Account + + logOutUser() + .then(() => navigate("/")) + } + > + + Sign out + + + + +
+
+ + +
+
); diff --git a/src/routes/dashboard/[workspace_id].tsx b/src/routes/dashboard/[workspace_id].tsx index 352c679..ac22f8e 100644 --- a/src/routes/dashboard/[workspace_id].tsx +++ b/src/routes/dashboard/[workspace_id].tsx @@ -11,8 +11,8 @@ import { Match, } from "solid-js"; -import { A, Title, useNavigate, useParams } from "solid-start"; -import { Motion, Presence } from "@motionone/solid"; +import { A, Title, useParams } from "solid-start"; +import { Presence } from "@motionone/solid"; import { autofocus } from "@solid-primitives/autofocus"; // prevents from being tree-shaken by TS @@ -21,12 +21,9 @@ autofocus; import IconStarOutline from "~icons/mdi/star-outline"; import IconShareVariantOutline from "~icons/mdi/share-variant-outline"; import IconPlus from "~icons/mdi/plus"; -import IconFolderAccountOutline from "~icons/mdi/folder-account-outline"; import IconAccountPlusOutline from "~icons/mdi/account-plus-outline"; import IconAccountMultipleOutline from "~icons/mdi/account-multiple-outline"; -import IconTrashCanOutline from "~icons/mdi/trash-can-outline"; import IconAccount from "~icons/mdi/account"; -import IconMenuDown from "~icons/mdi/menu-down"; import IconDownload from "~icons/mdi/download"; import IconDeleteOutline from "~icons/mdi/delete-outline"; import IconDotsHorizontal from "~icons/mdi/dots-horizontal"; @@ -34,11 +31,6 @@ import IconContentCopy from "~icons/mdi/content-copy"; import IconClose from "~icons/mdi/close"; import IconFolderOutline from "~icons/mdi/folder-outline"; import IconArrowULeftTop from "~icons/mdi/arrow-u-left-top"; -import IconLogout from "~icons/mdi/logout"; -import IconFolderPlusOutline from "~icons/mdi/folder-plus-outline"; -import cattoDriveBox from "@/assets/icon/box.png"; -import cattoDriveCatto from "@/assets/icon/catto.png"; -import SpinnerRingResize from "~icons/svg-spinners/ring-resize"; import { getFileIcon } from "@/utils/getFileIcons"; import { relativeTime } from "@/utils/relativeTime"; @@ -50,50 +42,28 @@ import { createFileImporter, downloadUploadedFile, getUploadedFileURL, - makeFileUpload, removePermanentlyFile, } from "@/utils/files"; -import { getWorkspace, createWorkspace } from "@/utils/workspaces"; +import { getWorkspace } from "@/utils/workspaces"; +import { auth } from "@/stores/auth"; import { workspaces, setWorkspaces } from "@/stores/workspaces"; -import { auth, logOutUser } from "@/stores/auth"; import { getUserFrom, saveUploadSharingPreferences } from "@/utils/users"; -const Page: Component = () => { - const [newFolderName, setNewFolderName] = createSignal(""); +import { setLayoutLoading, fileUploadHandler, layoutLoading, LoadingSection } from "../dashboard"; - const params = useParams(); - const navigate = useNavigate(); +const Page: Component = () => { + const params = useParams() as { workspace_id: string }; createEffect(on(() => params.workspace_id, async (workspaceId: string) => { + setLayoutLoading(true); + const workspace_content = await getWorkspace(workspaceId); setWorkspaces(workspaceId, workspace_content); + setLayoutLoading(false); })); - const fileUploadHandler = async (files: FileList) => { - if (!auth.profile) return; - - try { - const uploaded = await makeFileUpload(files, { - workspace_id: params.workspace_id, - private: true, - }); - - const new_content: WorkspaceContent[] = uploaded.map((file) => ({ - type: "file", - data: file, - })); - - setWorkspaces(params.workspace_id, "content", (files) => - files ? [...files, ...new_content] : new_content - ); - } - catch (error) { - console.error(error); - } - }; - const getWorkspaceName = (name: string) => { if (name === "../") { return "Go back to parent folder"; @@ -106,57 +76,6 @@ const Page: Component = () => { return name; }; - const [openNewFolderModal] = createModal((Modal) => ( - <> -
-

- Create a new folder -

- - - -
-
{ - event.preventDefault(); - - const workspace = await createWorkspace( - params.workspace_id, - newFolderName() - ); - const item: WorkspaceContent = { - type: "workspace", - data: workspace, - }; - - setWorkspaces(params.workspace_id, "content", (prev) => - prev ? [...prev, item] : [item] - ); - - setNewFolderName(""); - Modal.close(); - }} - > - setNewFolderName(event.target.value)} class="mb-2 rounded-md px-1 py-2 text-text" /> -
- - - Cancel - -
-
- - )); - const [openDeleteModal] = createModal((Modal) => ( <>
@@ -256,7 +175,12 @@ const Page: Component = () => { class="mt-2 w-full flex" onSubmit={async (event) => { event.preventDefault(); + if (layoutLoading() || !uidToAdd()) return; + setLayoutLoading(true); + const user_data = await getUserFrom(uidToAdd()); + setLayoutLoading(false); + if (!user_data) { alert("This user doesn't exist!"); return; @@ -277,6 +201,7 @@ const Page: Component = () => { }} > { onInput={(event) => setUidToAdd(event.currentTarget.value)} /> @@ -314,7 +240,6 @@ const Page: Component = () => { if (!new_data) return; setWorkspaces(params.workspace_id, "content", item => item.data.id === Modal.data.data.id, "data", new_data); - Modal.close(); }} > @@ -322,7 +247,7 @@ const Page: Component = () => { @@ -336,455 +261,198 @@ const Page: Component = () => { <> Dashboard - Drive -
- + + } + > +
+ +
+ + {(content) => ( + + + {(file) => ( +
+
+
+ {getFileIcon(file().data)} +
+
+
+

+ {file().data.name} + + + {/* Public */} + +

-
-
-

- {params.workspace_id === auth.profile!.root_workspace_id ? "My workspace" : ( - workspaces[params.workspace_id] - ? (workspaces[params.workspace_id].meta.name || workspaces[params.workspace_id].meta.id) - : "Loading..." - )} -

-
- {/* - - - - - - { - const workspace = await createWorkspace( - params.workspace_id - ); - const item: WorkspaceContent = { - type: "workspace", - data: workspace, - }; - - setWorkspaces(params.workspace_id, "content", (prev) => - prev ? [...prev, item] : [item] - ); - }} - > - New Folder ⌘ N - - - - Sort By... - - - - - - - - - -

Name

-
- - - - -

Date modified

-
- - - - -

Kind

-
- - - - -

Size

-
-
-
-
-
- - Workspace properties - -
-
-
*/} - - - - - - - - - { - navigate("/account"); - }} - class="flex flex-row gap-4 rounded-md px-4 py-1 text-text transition hover:bg-lavender hover:text-[rgb(46,48,66)]" - > - - My Account - - { - await logOutUser(); - navigate("/"); - }} - class="flex flex-row gap-4 rounded-md px-4 py-1 text-text transition hover:bg-lavender hover:text-[rgb(46,48,66)]" - > - - Sign out - - - - -
-
- -
- - - - - - - - -
-
- - -

- Loading... -

-
-

- Our cats are gathering the files for this workspace! -

-
- - } - > -
- -
- - {(content) => ( - - - {(file) => ( -
-
-
- {getFileIcon(file().data)} -
-
-
-

- {file().data.name} - - - {/* Public */} - -

- -
-
-

- {relativeTime(file().data.created_at)} -

-
-
- -
+
+

+ {relativeTime(file().data.created_at)} +

+
+
+
+ +
+ + + - - - - - - - downloadUploadedFile(file().data)} - class="flex flex-row items-center gap-2 rounded-md py-1 pl-2 pr-4 text-text hover:bg-lavender/30 hover:text-[rgb(46,48,66)]" - > - - Download - - - - Favorite - - - { - const url = getUploadedFileURL(file().data); - await navigator.clipboard.writeText(url.href); - }} - > - - Copy public URL - - - openDeleteModal(file().data)} - > - - Delete permanently - - - - -
-
- )} - - - {(workspace) => ( - -
- - } - > - - -

- {getWorkspaceName(workspace().data.name)} -

-
- -
+ + Copy public URL + + + openDeleteModal(file().data)} + > + + Delete permanently + + + + +
+
+ )} + + + {(workspace) => ( + +
+ + } + > + + +

+ {getWorkspaceName(workspace().data.name)} +

+
+ +
+ + + - - - - - - - - - Favorite - - navigator.clipboard.writeText(workspace().data.id)} - > - - Copy workspace ID - - - - Delete permanently - - - - -
-
- )} - - - )} - -
-
-
-
- - - -
-
+ + + + + + Favorite + + navigator.clipboard.writeText(workspace().data.id)} + > + + Copy workspace ID + + + + Delete permanently + + + + + + + )} + + + )} + + + +
+ + + ); }; diff --git a/src/routes/dashboard/shared.tsx b/src/routes/dashboard/shared.tsx index f33c9e6..d2cab7c 100644 --- a/src/routes/dashboard/shared.tsx +++ b/src/routes/dashboard/shared.tsx @@ -1,28 +1,20 @@ import { onMount, type Component, - Switch, createSignal, Show, For, - Match, } from "solid-js"; -import { A, Title, useNavigate } from "solid-start"; +import { A, Title } from "solid-start"; import { Motion, Presence } from "@motionone/solid"; import IconStarOutline from "~icons/mdi/star-outline"; -import IconPlus from "~icons/mdi/plus"; -import IconFolderAccountOutline from "~icons/mdi/folder-account-outline"; import IconAccountMultipleOutline from "~icons/mdi/account-multiple-outline"; -import IconTrashCanOutline from "~icons/mdi/trash-can-outline"; -import IconAccount from "~icons/mdi/account"; -import IconMenuDown from "~icons/mdi/menu-down"; import IconDownload from "~icons/mdi/download"; import IconDotsHorizontal from "~icons/mdi/dots-horizontal"; import IconContentCopy from "~icons/mdi/content-copy"; import IconFolderOutline from "~icons/mdi/folder-outline"; import IconArrowULeftTop from "~icons/mdi/arrow-u-left-top"; -import IconLogout from "~icons/mdi/logout" import cattoDriveBox from "@/assets/icon/box.png"; import cattoDriveCatto from "@/assets/icon/catto.png"; import SpinnerRingResize from "~icons/svg-spinners/ring-resize"; @@ -37,13 +29,13 @@ import { getUploadedFileURL, } from "@/utils/files"; +import { setLayoutLoading } from "../dashboard"; -import { auth, logOutUser } from "@/stores/auth"; +import { auth } from "@/stores/auth"; +import type { SharedData } from "@/types/api"; const SharedPage: Component = () => { - const navigate = useNavigate(); - const [loading, setLoading] = createSignal(true); - const [content, setContent] = createSignal(null); + const [content, setContent] = createSignal(null); const getWorkspaceName = (name: string) => { if (name === "../") { @@ -58,6 +50,8 @@ const SharedPage: Component = () => { }; onMount(async () => { + setLayoutLoading(true); + const response = await fetch("/api/share", { headers: { authorization: auth.profile!.api_token, @@ -65,331 +59,178 @@ const SharedPage: Component = () => { }); const json = await response.json(); - setLoading(false); - setContent(json.data); - }) + setContent(json.data as SharedData); + setLayoutLoading(false); + }); return ( <> Shared - Drive -
- + -
-
-

- Shared -

-
- - - - - - - - { - navigate("/account"); - }} - class="flex flex-row gap-4 px-4 py-1 hover:bg-lavender transition text-text hover:text-[rgb(46,48,66)] rounded-md" - > - - My Account - - { - await logOutUser(); - navigate("/"); - }} - class="flex flex-row gap-4 px-4 py-1 hover:bg-lavender transition text-text hover:text-[rgb(46,48,66)] rounded-md" - > - - Sign out - - - - -
-
+ -
- - - +
+
+ - +

+ Loading... +

+
+

+ Our cats are gathering your shared files! +

+
+ + } + > +
+ +
+ + {(workspace) => ( + +
+ + } + > + + +

+ {getWorkspaceName(workspace.name)} +

+
-
-
- +
+ + + + + + + + + Favorite + + navigator.clipboard.writeText(workspace.id)} + > + + Copy workspace ID + + + + +
+
+ )} + + + {(file) => ( +
+
+
+ {getFileIcon(file)} +
+
+
+

+ {file.name} + + + {/* Public */} + +

+ +
+
+

+ {relativeTime(file.created_at)} +

+
+
+
-

- Loading... -

+
+ + + + + + + downloadUploadedFile(file)} + class="flex flex-row items-center gap-2 rounded-md py-1 pl-2 pr-4 text-text hover:bg-lavender/30 hover:text-[rgb(46,48,66)]" + > + + Download + + + + Favorite + + + { + const url = getUploadedFileURL(file); + await navigator.clipboard.writeText(url.href); + }} + > + + Copy public URL + + + + +
-

- Our cats are gathering your shared files! -

-
- - } - > -
- -
- - {(content) => ( - - - {(file) => ( -
-
-
- {getFileIcon(file().data)} -
-
-
-

- {file().data.name} - - - {/* Public */} - -

- -
-
-

- {relativeTime(file().data.created_at)} -

-
-
-
- -
- - - - - - - downloadUploadedFile(file().data)} - class="flex flex-row items-center gap-2 pl-2 pr-4 py-1 hover:bg-lavender/30 text-text hover:text-[rgb(46,48,66)] rounded-md" - > - - Download - - - - Favorite - - - { - const url = getUploadedFileURL(file().data); - await navigator.clipboard.writeText(url.href); - }} - > - - Copy public URL - - - - - -
-
- )} -
- - {(workspace) => ( - -
- - } - > - - -

- {getWorkspaceName(workspace().data.name)} -

-
- -
- - - - - - - - - Favorite - - navigator.clipboard.writeText(workspace().data.id)} - > - - Copy workspace ID - - - - -
-
- )} -
-
- )} -
-
-
- - -
- - -
-
+ )} + + + +
+ + ); -} +}; export default SharedPage; diff --git a/src/types/api.ts b/src/types/api.ts index 2ea671e..5f26b11 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -35,3 +35,8 @@ export type WorkspaceContent = ( | { type: "file", data: UploadedFile } | { type: "workspace", data: WorkspaceMeta } ); + +export interface SharedData { + shared_files: UploadedFile[] + shared_workspaces: WorkspaceMeta[] +} diff --git a/src/utils/users.ts b/src/utils/users.ts index 5163c83..653ecdc 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -1,5 +1,5 @@ import { auth } from "@/stores/auth"; -import { UploadedFile, UserProfile, Workspace } from "@/types/api"; +import { UploadedFile, UserProfile, WorkspaceMeta } from "@/types/api"; export const getUserFrom = async (uid: string) => { const response = await fetch(`/api/user/${uid}`, { @@ -25,6 +25,6 @@ export const saveUploadSharingPreferences = async (type: "workspace" | "file", c } }); - const json = await response.json() as { success: boolean, data: Workspace | UploadedFile | undefined }; + const json = await response.json() as { success: boolean, data: WorkspaceMeta | UploadedFile | undefined }; return json.data; };