diff --git a/applications/uk.ewsgit.dashboard/backend/index.ts b/applications/uk.ewsgit.dashboard/backend/index.ts index 3ded2a7..028c078 100644 --- a/applications/uk.ewsgit.dashboard/backend/index.ts +++ b/applications/uk.ewsgit.dashboard/backend/index.ts @@ -38,11 +38,11 @@ const router = t.router({ return { displayName: `${forename} ${surname}`, username: username, - avatar: `${opt.ctx.instance.sys.configuration.backendUrl}/api/user/me/avatar/m`, + avatar: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/user/me/avatar/m`, }; }), avatar: procedure.output(z.string()).query(async (opt) => { - return `${opt.ctx.instance.sys.configuration.backendUrl}/api/user/me/avatar/2xl`; + return `${opt.ctx.instance.sys.configuration.proxyUrl}/api/user/me/avatar/2xl`; }), }, }, @@ -201,7 +201,7 @@ const router = t.router({ ); } - return opt.ctx.instance.sys.configuration.backendUrl + (await opt.ctx.instance.sys.image.serveImage(opt.ctx.userId, requiredResizedWallpaperPath)); + return opt.ctx.instance.sys.configuration.proxyUrl + (await opt.ctx.instance.sys.image.serveImage(opt.ctx.userId, requiredResizedWallpaperPath)); }), }, }); diff --git a/applications/uk.ewsgit.files/web/App.module.scss b/applications/uk.ewsgit.files/web/App.module.scss new file mode 100644 index 0000000..772876c --- /dev/null +++ b/applications/uk.ewsgit.files/web/App.module.scss @@ -0,0 +1,21 @@ +.splitView { + display: grid; + grid-template-columns: 1fr 1fr; + width: 100%; + height: 100%; + overflow: auto; + + &>div { + border-color: rgb(var(--uk-sys-color-outline)); + border-style: solid; + border-width: 0; + } + + &> :first-child { + border-right-width: 1px; + } + + &> :last-child { + border-left-width: 1px; + } +} \ No newline at end of file diff --git a/applications/uk.ewsgit.files/web/App.tsx b/applications/uk.ewsgit.files/web/App.tsx index 192f803..4dfb273 100644 --- a/applications/uk.ewsgit.files/web/App.tsx +++ b/applications/uk.ewsgit.files/web/App.tsx @@ -1,5 +1,6 @@ import { Route } from "@solidjs/router"; import { type Component, lazy, useContext } from "solid-js"; +import styles from "./App.module.scss"; import { AppContext } from "./appContext.ts"; import Core from "./Core.tsx"; import ActionBar from "./layout/components/ActionBar/ActionBar.tsx"; @@ -38,7 +39,21 @@ const App: Component = () => {
- + + + )} + /> + ( + <> +
+ +
+
+ + +
)} /> @@ -55,7 +70,7 @@ const App: Component = () => { path={"/bin"} component={() => ( <> - + )} /> diff --git a/applications/uk.ewsgit.files/web/Core.tsx b/applications/uk.ewsgit.files/web/Core.tsx index 91e587d..97b2c00 100644 --- a/applications/uk.ewsgit.files/web/Core.tsx +++ b/applications/uk.ewsgit.files/web/Core.tsx @@ -2,11 +2,13 @@ import { type Component, createSignal, onMount, type ParentProps } from "solid-j import { createStore } from "solid-js/store"; import { AppContext, type AppContextType } from "./appContext"; import type { Task } from "./layout/components/StatusBar/task"; +import type { UniformResourceLocator } from "./lib/filesystemInterface"; import trpc from "./lib/trpc"; +import type { ViewState } from "./pages/dir/View"; export interface Preferences { showWelcome: boolean; - homePath: string; + homePath: UniformResourceLocator; pinnedDirectories: string[]; viewType: "grid" | "details" | "gallery"; showPreview: boolean; @@ -17,6 +19,8 @@ export interface Preferences { export interface GlobalState { showPreview: boolean; disableShortcuts: boolean; + deletedItemCount: number; + activeViewId: number; } const Core: Component = (props) => { @@ -29,9 +33,33 @@ const Core: Component = (props) => { zoomPercentage: 1, showHidden: false, }); + const [viewState, setViewState] = createStore<{ [viewId: number]: ViewState }>({ + 0: { + pathUrl: "remote:/", + viewItems: [], + selectedItems: [], + lastSelectionTime: -1, + lastSelectedItem: undefined, + viewId: 0, + isLoading: true, + isRenaming: undefined, + }, + 1: { + pathUrl: "remote:/", + viewItems: [], + selectedItems: [], + lastSelectionTime: -1, + lastSelectedItem: undefined, + viewId: 0, + isLoading: true, + isRenaming: undefined, + }, + }); const [globalState, setGlobalState] = createStore({ showPreview: false, disableShortcuts: false, + deletedItemCount: 0, + activeViewId: 0, }); const [taskStatus, setTaskStatus] = createSignal([]); @@ -39,7 +67,7 @@ const Core: Component = (props) => { const userServerPreferences = await trpc.userPreferences.get.query(); setUserPreferences("showWelcome", userServerPreferences.showWelcome); - setUserPreferences("homePath", userServerPreferences.homePath); + setUserPreferences("homePath", userServerPreferences.homePath as UniformResourceLocator); setUserPreferences("pinnedDirectories", userServerPreferences.pinnedDirectories); }); @@ -50,9 +78,10 @@ const Core: Component = (props) => { shootYourselfInTheFoot: () => false, userPreferences: userPreferences, setUserPreferences: setUserPreferences, + viewState: viewState, + setViewState: setViewState, globalState: globalState, setGlobalState: setGlobalState, - deletedItemCount: 24, isDesktopApp: localStorage.getItem("onlineworkspace_workspace_desktop_app") === "true", tasks: taskStatus, setTasks: setTaskStatus, diff --git a/applications/uk.ewsgit.files/web/appContext.ts b/applications/uk.ewsgit.files/web/appContext.ts index 13d5f8f..f983663 100644 --- a/applications/uk.ewsgit.files/web/appContext.ts +++ b/applications/uk.ewsgit.files/web/appContext.ts @@ -2,15 +2,17 @@ import { type Accessor, createContext, type Setter } from "solid-js"; import type { SetStoreFunction, Store } from "solid-js/store"; import type { GlobalState, Preferences } from "./Core"; import type { Task } from "./layout/components/StatusBar/task"; +import type { ViewState } from "./pages/dir/View"; export interface AppContextType { isAdministrator: Accessor; shootYourselfInTheFoot: Accessor; userPreferences: Store; setUserPreferences: SetStoreFunction; + viewState: Store<{ [viewId: number]: ViewState }>; + setViewState: SetStoreFunction<{ [viewId: number]: ViewState }>; globalState: Store; setGlobalState: SetStoreFunction; - deletedItemCount: number; isDesktopApp: boolean; tasks: Accessor; setTasks: Setter; diff --git a/applications/uk.ewsgit.files/web/layout/Layout.tsx b/applications/uk.ewsgit.files/web/layout/Layout.tsx index e863007..302c627 100644 --- a/applications/uk.ewsgit.files/web/layout/Layout.tsx +++ b/applications/uk.ewsgit.files/web/layout/Layout.tsx @@ -94,7 +94,7 @@ const Layout: Component = (props) => { type: "button", icon: { type: "icon", value: DELETE_ICON }, label: "Bin", - badgeLabel: appContext?.deletedItemCount || undefined, + badgeLabel: appContext?.globalState.deletedItemCount || undefined, onClick() { navigate("/app/uk.ewsgit.files/bin"); }, @@ -117,11 +117,11 @@ const Layout: Component = (props) => { - {/* + - + - */} + ); }; diff --git a/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.tsx b/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.tsx index 561d522..db0f2a3 100644 --- a/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.tsx @@ -1,21 +1,20 @@ import ADD_ICON from "@material-symbols/svg-700/outlined/add.svg"; import ARROW_UPWARD_ICON from "@material-symbols/svg-700/outlined/arrow_upward.svg"; import ART_TRACK_ICON from "@material-symbols/svg-700/outlined/art_track.svg"; -import CHEVRON_LEFT_ICON from "@material-symbols/svg-700/outlined/chevron_left.svg"; -import CHEVRON_RIGHT_ICON from "@material-symbols/svg-700/outlined/chevron_right.svg"; +// import CHEVRON_LEFT_ICON from "@material-symbols/svg-700/outlined/chevron_left.svg"; +// import CHEVRON_RIGHT_ICON from "@material-symbols/svg-700/outlined/chevron_right.svg"; import CLOSE_ICON from "@material-symbols/svg-700/outlined/close.svg"; import CROP_SQUARE_ICON from "@material-symbols/svg-700/outlined/crop_square.svg"; import LISTS_ICON from "@material-symbols/svg-700/outlined/lists.svg"; import MINIMIZE_ICON from "@material-symbols/svg-700/outlined/minimize.svg"; -import RIGHT_PANEL_CLOSE_ICON from "@material-symbols/svg-700/outlined/right_panel_close.svg"; -import RIGHT_PANEL_OPEN_ICON from "@material-symbols/svg-700/outlined/right_panel_open.svg"; +// import RIGHT_PANEL_CLOSE_ICON from "@material-symbols/svg-700/outlined/right_panel_close.svg"; +// import RIGHT_PANEL_OPEN_ICON from "@material-symbols/svg-700/outlined/right_panel_open.svg"; import UPLOAD_ICON from "@material-symbols/svg-700/outlined/upload.svg"; import VIEW_MODULE_ICON from "@material-symbols/svg-700/outlined/view_module.svg"; import UKCard from "@onlineworkspace/uikit-solid/src/components/card/UKCard.tsx"; import UKIconButton from "@onlineworkspace/uikit-solid/src/components/iconButton/UKIconButton.tsx"; import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.jsx"; import useIsMobile from "@onlineworkspace/uikit-solid/src/core/useIsMobile.ts"; -import { useSearchParams } from "@solidjs/router"; import clsx from "clsx"; import browserPath from "path-browserify"; import { type Component, createEffect, createSignal, Show, useContext } from "solid-js"; @@ -28,34 +27,36 @@ import styles from "./ActionBar.module.scss"; const ActionBar: Component = () => { const isMobile = useIsMobile(); const appContext = useContext(AppContext); - const [searchParams, setSearchParams] = useSearchParams<{ path: string }>(); const [pathQuery, setPathQuery] = createSignal(undefined); const [showTextualPath, setShowTextualPath] = createSignal(false); const [canNavigateUp, setCanNavigateUp] = createSignal(false); createEffect(() => { - setCanNavigateUp(canViewNavigateUp((searchParams.path || "invalid:") as UniformResourceLocator)); + setCanNavigateUp(canViewNavigateUp((appContext?.viewState[appContext.globalState.activeViewId].pathUrl || "invalid:") as UniformResourceLocator)); }); return (
- window.history.back()} /> - window.history.forward()} /> + {/* window.history.back()} /> + window.history.forward()} /> */} { - viewNavigateUp((p) => setSearchParams({ path: p }), (searchParams.path || "invalid:") as UniformResourceLocator); + viewNavigateUp( + (p) => appContext?.setViewState(appContext.globalState.activeViewId, "pathUrl", p as UniformResourceLocator), + (appContext?.viewState[appContext.globalState.activeViewId].pathUrl || "invalid:") as UniformResourceLocator, + ); }} />
- +
- {(searchParams.path || "") + {(appContext?.viewState[appContext.globalState.activeViewId].pathUrl || "") .split(browserPath.sep) .slice(0, 1) .map((segment, index) => { @@ -64,7 +65,7 @@ const ActionBar: Component = () => { {segment.slice(0, -1).toUpperCase()} - {(searchParams.path || "").split(browserPath.sep).length - 1 === index ? ( + {(appContext?.viewState[appContext.globalState.activeViewId].pathUrl || "").split(browserPath.sep).length - 1 === index ? ( "" ) : ( @@ -74,7 +75,7 @@ const ActionBar: Component = () => { ); })} - {(searchParams.path || "") + {(appContext?.viewState[appContext.globalState.activeViewId].pathUrl || "") .split(browserPath.sep) .slice(1) .map((segment, index) => { @@ -83,7 +84,7 @@ const ActionBar: Component = () => { {segment} - {(searchParams.path || "").split(browserPath.sep).length - 1 === index ? ( + {(appContext?.viewState[appContext.globalState.activeViewId].pathUrl || "").split(browserPath.sep).length - 1 === index ? ( "" ) : ( @@ -97,14 +98,15 @@ const ActionBar: Component = () => { { setPathQuery(e.currentTarget.value); }} onBlur={() => setShowTextualPath(false)} onClick={() => setShowTextualPath(true)} data-visible={showTextualPath()} - onChange={(e) => setSearchParams({ path: e.currentTarget.value })} + // TODO: perhaps validate this first and warn the user if it's invalid + onChange={(e) => appContext?.setViewState(appContext.globalState.activeViewId, "pathUrl", e.currentTarget.value as UniformResourceLocator)} /> {pathQuery()} @@ -163,8 +165,8 @@ const ActionBar: Component = () => { alt={"Create File"} onClick={() => { for (let i = 0; i < 10; i++) { - appContext?.setViewState("viewItems", [ - ...appContext.viewState.viewItems, + appContext?.setViewState(0, "viewItems", [ + ...appContext.viewState[0].viewItems, { type: "file", path: `/randomNewItem${Math.round(Math.random() * 100000000)}` }, ] as ViewItem[]); } @@ -172,7 +174,7 @@ const ActionBar: Component = () => { /> )} - {!isMobile() && ( + {/* {!isMobile() && ( { appContext?.setUserPreferences("showPreview", !appContext?.userPreferences.showPreview); }} /> - )} + )} */} {appContext?.isDesktopApp && localStorage.getItem("onlineworkspace_workspace_desktop_platform") !== "darwin" && ( <> = (props) => { +const PreviewDialog: Component<{ pathUrl: UniformResourceLocator }> = (props) => { const appContext = useContext(AppContext); - const [ isExpanded, setIsExpanded ] = createSignal(false); - const [ originalHasLoaded, setOriginalHasLoaded ] = createSignal(false); - const [data] = createResource(() => props.pathUrl, (url) => filesystemInterface.getPreviewDialogMetadata(url as UniformResourceLocator)) + const [isExpanded, setIsExpanded] = createSignal(false); + const [originalHasLoaded, setOriginalHasLoaded] = createSignal(false); + const [data] = createResource( + () => props.pathUrl, + (url) => filesystemInterface.getPreviewDialogMetadata(url as UniformResourceLocator), + ); return ( <> @@ -35,8 +38,8 @@ const PreviewDialog: Component<{pathUrl: UniformResourceLocator}> = (props) => { />
- { - !appContext?.isDesktopApp && <> + {!appContext?.isDesktopApp && ( + <> = (props) => { color={"tonal"} alt={"Expand preview"} onClick={() => { - setIsExpanded((p) => !p) + setIsExpanded((p) => !p); }} icon={isExpanded() ? COLLAPSE_CONTENT_ICON : EXPAND_CONTENT_ICON} /> - } + )} - - Preview for {path.basename(props.pathUrl)} + + Preview for {path.basename(props.pathUrl)} - { - }} - icon={ROTATE_90_DEGREES_CW_ICON} - /> - { - }} - icon={SHARE_ICON} - /> - { - }} - > + {}} icon={ROTATE_90_DEGREES_CW_ICON} /> + {}} icon={SHARE_ICON} /> + {}}> Open in [DEFAULT APPLICATION]
-
, {status: "ok"}>)?.data?.metadata?.pixelate && styles.pixelate)}> - - , { status: "ok" }>).data.assets} fallback={<> +
, { status: "ok" }>)?.data?.metadata?.pixelate && styles.pixelate, + )} + > + + , { status: "ok" }>).data.assets} + fallback={
- { iconForItemType((data() as Extract, { status: "ok" }>).data.metadata.type as unknown as any) } - -
- {path.basename(props.pathUrl)} - Contains { (data() as Extract, { status: "ok" }>).data.metadata.itemCount} items - {humanReadableSize((data() as Extract, { status: "ok" }>).data.metadata.size)} + + {iconForItemType((data() as Extract, { status: "ok" }>).data.metadata.type as unknown as any)} + + +
+ + {path.basename(props.pathUrl)} + + + Contains {(data() as Extract, { status: "ok" }>).data.metadata.itemCount} items + + + {humanReadableSize((data() as Extract, { status: "ok" }>).data.metadata.size)} + +
-
- }> - { - data()!.status === "ok" && <> - , {status: "ok"}>).data.assets?.small || ""} alt="preview"> - {(isExpanded() || originalHasLoaded()) && - setOriginalHasLoaded(true)} src={(data() as Extract, {status: "ok"}>).data.assets?.original || ""} alt="preview"> - } + } + > + {data()!.status === "ok" && ( + <> + , { status: "ok" }>).data.assets?.small || ""} + alt="preview" + > + {(isExpanded() || originalHasLoaded()) && ( + setOriginalHasLoaded(true)} + src={(data() as Extract, { status: "ok" }>).data.assets?.original || ""} + alt="preview" + > + )} - } -
+ )}
-
+
+
); diff --git a/applications/uk.ewsgit.files/web/layout/components/PreviewPane/PreviewPane.tsx b/applications/uk.ewsgit.files/web/layout/components/PreviewPane/PreviewPane.tsx index 2ec1c56..6c01ccc 100644 --- a/applications/uk.ewsgit.files/web/layout/components/PreviewPane/PreviewPane.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/PreviewPane/PreviewPane.tsx @@ -3,11 +3,11 @@ import UKIcon from "@onlineworkspace/uikit-solid/src/components/icon/UKIcon.tsx" import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.tsx"; import useIsMobile from "@onlineworkspace/uikit-solid/src/core/useIsMobile.ts"; import browserPath from "path-browserify"; -import {type Component, useContext} from "solid-js"; -import {AppContext} from "../../../appContext.ts"; +import { type Component, useContext } from "solid-js"; +import { AppContext } from "../../../appContext.ts"; +import humanReadableSize from "../../../lib/humanReadableSize.ts"; import iconForItemType from "../../../pages/dir/iconForItemType.ts"; import styles from "./PreviewPane.module.scss"; -import humanReadableSize from "../../../lib/humanReadableSize.ts"; const PreviewPane: Component = () => { const isMobile = useIsMobile(); @@ -15,7 +15,7 @@ const PreviewPane: Component = () => { return (
- + {/* Preview @@ -56,7 +56,7 @@ const PreviewPane: Component = () => { Dimensions: ...x... - ) : null} + ) : null} */}
); }; diff --git a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx index 810f1a1..caca0ce 100644 --- a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx @@ -6,18 +6,20 @@ import UKIconButton from "@onlineworkspace/uikit-solid/src/components/iconButton import UKLinearProgressIndicator from "@onlineworkspace/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.tsx"; import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.tsx"; import clsx from "clsx"; -import {type Component, createEffect, createSignal, useContext} from "solid-js"; -import {AppContext} from "../../../appContext.ts"; -import {MAX_ZOOM_VALUE, MIN_ZOOM_VALUE} from "../../../lib/constants.ts"; +import { type Component, createEffect, createSignal, useContext } from "solid-js"; +import { AppContext } from "../../../appContext.ts"; +import { MAX_ZOOM_VALUE, MIN_ZOOM_VALUE } from "../../../lib/constants.ts"; import humanReadableSize from "../../../lib/humanReadableSize.ts"; +import { ViewContext } from "../../../pages/dir/viewContext.ts"; import styles from "./StatusBar.module.scss"; const StatusBar: Component = () => { const appContext = useContext(AppContext); - const [ dirContents, setDirContents ] = createSignal(""); + const viewContext = useContext(ViewContext); + const [dirContents, setDirContents] = createSignal(""); createEffect(() => { - const viewItems = appContext?.viewState.viewItems; + const viewItems = appContext?.viewState[viewContext!.viewId].viewItems; let outputString = ""; @@ -46,7 +48,7 @@ const StatusBar: Component = () => { outputString += ` (${humanReadableSize(totalSize)})`; } - const selectedItems = appContext?.viewState.selectedItems || []; + const selectedItems = appContext?.viewState[viewContext!.viewId].selectedItems || []; if (selectedItems?.length > 0) { outputString += ` - Selected ${selectedItems.length} item${selectedItems.length !== 1 ? "s" : ""}`; @@ -90,10 +92,10 @@ const StatusBar: Component = () => { {dirContents()}
-
- +
+ - {appContext!.tasks()[ 0 ]?.message.replaceAll("%c", appContext!.tasks()[ 0 ].current.toString()).replaceAll("%m", appContext!.tasks()[ 0 ].max.toString())} + {appContext!.tasks()[0]?.message.replaceAll("%c", appContext!.tasks()[0].current.toString()).replaceAll("%m", appContext!.tasks()[0].max.toString())}
diff --git a/applications/uk.ewsgit.files/web/pages/dir/View.module.scss b/applications/uk.ewsgit.files/web/pages/dir/View.module.scss index f0f8426..101d2a0 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/View.module.scss +++ b/applications/uk.ewsgit.files/web/pages/dir/View.module.scss @@ -1,6 +1,8 @@ .root { display: flex; flex-direction: column; + height: 100%; + overflow: hidden; } .itemView { @@ -15,6 +17,4 @@ border: 1px solid rgb(var(--uk-sys-color-primary), 0.25); position: fixed; border-radius: var(--uk-sys-shape-corner-small); -} - -.statusBar {} \ No newline at end of file +} \ No newline at end of file diff --git a/applications/uk.ewsgit.files/web/pages/dir/View.tsx b/applications/uk.ewsgit.files/web/pages/dir/View.tsx index ce37266..af4494c 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/View.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/View.tsx @@ -1,6 +1,5 @@ import ERROR_ICON from "@material-symbols/svg-700/outlined/error.svg"; import FOLDER_LIMITED_ICON from "@material-symbols/svg-700/outlined/folder_limited.svg"; -import { useSearchParams } from "@solidjs/router"; import path from "path-browserify"; import { type Component, createEffect, createSignal, Match, onCleanup, onMount, Suspense, Switch, useContext } from "solid-js"; import { createStore } from "solid-js/store"; @@ -19,6 +18,7 @@ import { ViewContext } from "./viewContext.ts"; import type { ViewItem } from "./viewItem.ts"; export interface ViewState { + pathUrl: UniformResourceLocator; viewItems: ViewItem[]; selectedItems: string[]; lastSelectionTime: number; @@ -28,22 +28,8 @@ export interface ViewState { isRenaming: string | undefined; } -const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = (props) => { - const [searchParams, setSearchParams] = useSearchParams<{ path?: UniformResourceLocator }>(); +const View: Component<{ pathOverride?: UniformResourceLocator; disallowCreation?: boolean; viewId: number }> = (props) => { const appContext = useContext(AppContext); - const [viewState, setViewState] = createStore({ - viewItems: [], - selectedItems: [], - lastSelectionTime: -1, - lastSelectedItem: undefined, - viewId: 0, - isLoading: true, - isRenaming: undefined, - }); - const viewContext = { - viewState, - setViewState, - }; const [dragSelectRegion, setDragSelectRegion] = createStore<{ origin?: { x: number; y: number }; size?: { x: number; y: number }; @@ -54,11 +40,13 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( }); const [errorMessage, setErrorMessage] = createSignal(undefined); const [forceViewItemUpdate, setForceViewItemUpdate] = createSignal(0); + const [itemViewRef, setItemViewRef] = createSignal(null); + let selectableItems: Element[] = []; let navigationCounter = 0; createEffect(async () => { - if (!searchParams.path) { - setSearchParams({ path: props.pathOverride || appContext?.userPreferences.homePath }); + if (!appContext?.viewState[props.viewId].pathUrl) { + appContext?.setViewState(props.viewId, "pathUrl", props.pathOverride || appContext?.userPreferences.homePath); return; } @@ -66,24 +54,24 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( appContext?.userPreferences.zoomPercentage; forceViewItemUpdate(); - setViewState("isRenaming", undefined); - setViewState("selectedItems", []); + appContext?.setViewState(props.viewId, "isRenaming", undefined); + appContext?.setViewState(props.viewId, "selectedItems", []); navigationCounter++; const currentNavigationCount = navigationCounter; appContext?.setTasks((tasks) => tasks.filter((t) => t.type !== "view_fetch_items")); - setViewState("isLoading", true); - const newItems = await filesystemInterface.readDirectory(searchParams.path || "remote:/"); + appContext?.setViewState(props.viewId, "isLoading", true); + const newItems = await filesystemInterface.readDirectory(appContext?.viewState[props.viewId].pathUrl || "remote:/"); if (currentNavigationCount !== navigationCounter) return; if (newItems.status === "ok") { setErrorMessage(undefined); - setViewState("selectedItems", []); - setViewState("lastSelectionTime", -1); - setViewState("viewItems", []); + appContext?.setViewState(props.viewId, "selectedItems", []); + appContext?.setViewState(props.viewId, "lastSelectionTime", -1); + appContext?.setViewState(props.viewId, "viewItems", []); const task: Task = { parent: "view0", @@ -96,7 +84,7 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( appContext?.setTasks((tasks) => [...tasks, task]); - const CHUNK_SIZE = filesystemInterface.getViewEntryBatchSize(searchParams.path); + const CHUNK_SIZE = filesystemInterface.getViewEntryBatchSize(appContext?.viewState[props.viewId].pathUrl); for (const itemPathGroup of chunkArray(newItems.items, CHUNK_SIZE)) { if (currentNavigationCount !== navigationCounter) { @@ -124,8 +112,8 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( resolve(); return; } - setViewState("viewItems", [ - ...viewState.viewItems, + appContext?.setViewState(props.viewId, "viewItems", [ + ...appContext.viewState[props.viewId].viewItems, ...itemGroupResponseResolvedPromises.map((ig) => ig?.data || undefined).filter((ig) => ig !== undefined), ]); @@ -149,24 +137,24 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( }); } - setViewState("isLoading", false); + appContext?.setViewState(props.viewId, "isLoading", false); } else { setErrorMessage(newItems.status); } }); const onKeyDown = (e: KeyboardEvent) => { - e.preventDefault(); + if (e.key === "Tab" || e.key === " " || appContext?.globalState.disableShortcuts) return; - if (e.key !== "Escape" && appContext?.globalState.disableShortcuts) return; + e.preventDefault(); switch (e.key) { case "F2": { - setViewState("isRenaming", viewState.lastSelectedItem); + appContext?.setViewState(props.viewId, "isRenaming", appContext.viewState[props.viewId].lastSelectedItem); break; } case " ": { - if (viewState.lastSelectedItem === undefined) return; + if (appContext?.viewState[props.viewId].lastSelectedItem === undefined) return; if (appContext?.globalState.showPreview === false) { appContext?.setGlobalState("showPreview", true); @@ -177,14 +165,14 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( } case "Escape": { appContext?.setGlobalState("showPreview", false); - setViewState("isRenaming", undefined); - setViewState("selectedItems", []); + appContext?.setViewState(props.viewId, "isRenaming", undefined); + appContext?.setViewState(props.viewId, "selectedItems", []); appContext?.setGlobalState("disableShortcuts", false); break; } case "Enter": { - if (viewState.lastSelectedItem) { - const item = viewState.viewItems.find((i) => i.path === viewState.lastSelectedItem); + if (appContext?.viewState[props.viewId].lastSelectedItem) { + const item = appContext?.viewState[props.viewId].viewItems.find((i) => i.path === appContext?.viewState[props.viewId].lastSelectedItem); if (!item) return; @@ -193,9 +181,9 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( return; } - setSearchParams({ path: item.path }); + appContext?.setViewState(props.viewId, "pathUrl", item.path as UniformResourceLocator); } - deselectAllItems(appContext!, viewContext!); + deselectAllItems(appContext!, props.viewId); break; } case "ArrowLeft": { @@ -204,7 +192,7 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( return; } - selectPreviousItem(viewContext!); + selectPreviousItem(appContext!, props.viewId); break; } case "ArrowRight": { @@ -213,15 +201,15 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( return; } - selectNextItem(viewContext!); + selectNextItem(appContext!, props.viewId); break; } case "ArrowUp": { - selectPreviousItem(viewContext!); + selectPreviousItem(appContext!, props.viewId); break; } case "ArrowDown": { - selectNextItem(viewContext!); + selectNextItem(appContext!, props.viewId); break; } case "F5": { @@ -239,19 +227,69 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( onMount(async () => { window.addEventListener("keydown", onKeyDown); - }); - onCleanup(() => { - window.removeEventListener("keydown", onKeyDown); + function mouseMove(e: MouseEvent) { + const bounds = itemViewRef()!.getBoundingClientRect(); + + const mouseX = Math.min(Math.max(e.clientX, bounds.left), bounds.right); + const mouseY = Math.min(Math.max(e.clientY, bounds.top), bounds.bottom); + + const left = Math.min(dragSelectRegion.origin!.x, mouseX); + const top = Math.min(dragSelectRegion.origin!.y, mouseY); + const width = Math.abs(mouseX - dragSelectRegion.origin!.x); + const height = Math.abs(mouseY - dragSelectRegion.origin!.y); + + setDragSelectRegion("transOrigin", { x: left, y: top }); + setDragSelectRegion("size", { x: width, y: height }); + + const boxRight = left + width; + const boxBottom = top + height; + + const newlySelected: string[] = []; + for (const item of selectableItems) { + const itemRect = item.getBoundingClientRect(); + const path = item.getAttribute("data-fs-item-path"); + + const isIntersecting = !(itemRect.left > boxRight || itemRect.right < left || itemRect.top > boxBottom || itemRect.bottom < top); + + if (isIntersecting && path) newlySelected.push(path); + } + + appContext?.setViewState(props.viewId, "selectedItems", newlySelected); + } + + function mouseUp() { + document.body.style.userSelect = "unset"; + setDragSelectRegion("origin", undefined); + setDragSelectRegion("size", undefined); + setDragSelectRegion("transOrigin", undefined); + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("mousemove", mouseMove); + } + + createEffect(() => { + function mouseDown() { + window.addEventListener("mouseup", mouseUp); + window.addEventListener("mousemove", mouseMove); + } + + itemViewRef()?.addEventListener("mousedown", mouseDown); + }); + + onCleanup(() => { + window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("mouseup", mouseUp); + window.removeEventListener("mousemove", mouseMove); + }); }); return ( - -
+ +
appContext?.setGlobalState("activeViewId", props.viewId)}> {errorMessage() ? ( - ) : viewState.viewItems.length === 0 && !viewState.isLoading ? ( + ) : appContext!.viewState[props.viewId].viewItems.length === 0 && !appContext!.viewState[props.viewId].isLoading ? ( = ( color: "filled", label: "Create new Folder", async onClick() { - const resolvedPath = filesystemInterface.urlToPath(searchParams.path || "remote:/"); + const resolvedPath = filesystemInterface.urlToPath(appContext?.viewState[props.viewId].pathUrl || "remote:/"); if (resolvedPath.type === "invalid") throw "Error resolving searchParams path"; @@ -290,20 +328,21 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( {/** biome-ignore lint/a11y/noStaticElementInteractions: button functionality not required */}
{ if (appContext?.userPreferences.viewType === "gallery") return; const target = downEvent.target as HTMLElement; const itemPath = target.closest("[data-fs-item-path]")?.getAttribute("data-fs-item-path"); - const currentSelected = viewState.selectedItems || []; + const currentSelected = appContext?.viewState[props.viewId].selectedItems || []; if (itemPath) { const isSelected = currentSelected.includes(itemPath); const newSelection = isSelected ? currentSelected.filter((path) => path !== itemPath) : [...currentSelected, itemPath]; - setViewState("selectedItems", newSelection); + appContext?.setViewState(props.viewId, "selectedItems", newSelection); } else { - deselectAllItems(appContext!, viewContext!); + deselectAllItems(appContext!, props.viewId); } const originX = downEvent.clientX; @@ -313,48 +352,7 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = ( setDragSelectRegion("origin", { x: originX, y: originY }); const currentTarget = downEvent.currentTarget as HTMLDivElement; - const bounds = currentTarget.getBoundingClientRect(); - const selectableItems = Array.from(currentTarget.querySelectorAll("[data-fs-item-path]")); - - function mouseMove(e: MouseEvent) { - const mouseX = Math.min(Math.max(e.clientX, bounds.left), bounds.right); - const mouseY = Math.min(Math.max(e.clientY, bounds.top), bounds.bottom); - - const left = Math.min(originX, mouseX); - const top = Math.min(originY, mouseY); - const width = Math.abs(mouseX - originX); - const height = Math.abs(mouseY - originY); - - setDragSelectRegion("transOrigin", { x: left, y: top }); - setDragSelectRegion("size", { x: width, y: height }); - - const boxRight = left + width; - const boxBottom = top + height; - - const newlySelected: string[] = []; - for (const item of selectableItems) { - const itemRect = item.getBoundingClientRect(); - const path = item.getAttribute("data-fs-item-path"); - - const isIntersecting = !(itemRect.left > boxRight || itemRect.right < left || itemRect.top > boxBottom || itemRect.bottom < top); - - if (isIntersecting && path) newlySelected.push(path); - } - - setViewState("selectedItems", newlySelected); - } - - function mouseUp() { - document.body.style.userSelect = "unset"; - setDragSelectRegion("origin", undefined); - setDragSelectRegion("size", undefined); - setDragSelectRegion("transOrigin", undefined); - document.removeEventListener("mouseup", mouseUp); - document.removeEventListener("mousemove", mouseMove); - } - - document.addEventListener("mouseup", mouseUp); - document.addEventListener("mousemove", mouseMove); + selectableItems = Array.from(currentTarget.querySelectorAll("[data-fs-item-path]")); }} > @@ -382,9 +380,7 @@ const View: Component<{ pathOverride?: string; disallowCreation?: boolean }> = (
)} -
- -
+
diff --git a/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.tsx b/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.tsx index 5e0f4b3..8e9ab6d 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.tsx @@ -1,6 +1,5 @@ import UKIcon from "@onlineworkspace/uikit-solid/src/components/icon/UKIcon.tsx"; import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.tsx"; -import { useSearchParams } from "@solidjs/router"; import clsx from "clsx"; import browserPath from "path-browserify"; import { type Component, For, Show, useContext } from "solid-js"; @@ -13,12 +12,11 @@ import { ViewContext } from "../../viewContext.ts"; import styles from "./DetailsView.module.scss"; const DetailsView: Component = () => { - const [_, setSearchParams] = useSearchParams(); const appContext = useContext(AppContext); const viewContext = useContext(ViewContext); return ( - 0}> + 0}> @@ -46,7 +44,7 @@ const DetailsView: Component = () => { - + {(item, index) => { if (!appContext?.userPreferences.showHidden && item.hidden) return null; @@ -59,18 +57,12 @@ const DetailsView: Component = () => { return ( { e.stopPropagation(); - onItemClick( - e as unknown as MouseEvent & { currentTarget: HTMLButtonElement; target: DOMElement }, - appContext!, - viewContext!, - index(), - item, - setSearchParams, - ); + onItemClick(e as unknown as MouseEvent & { currentTarget: HTMLButtonElement; target: DOMElement }, appContext!, index(), item); }} onMouseDown={(e) => { e.preventDefault(); @@ -80,7 +72,7 @@ const DetailsView: Component = () => { e.preventDefault(); e.stopPropagation(); }} - data-selected={viewContext?.viewState.selectedItems.includes(item.path)} + data-selected={appContext?.viewState[viewContext!.viewId].selectedItems.includes(item.path)} >
{item.thumbnail !== undefined ? ( @@ -90,7 +82,7 @@ const DetailsView: Component = () => { )} - {viewContext?.viewState.isRenaming === item.path ? ( + {appContext?.viewState[viewContext!.viewId].isRenaming === item.path ? (
Hello Renaming World!
) : ( diff --git a/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.tsx b/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.tsx index 3cc0055..94a180a 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.tsx @@ -6,30 +6,32 @@ import UKIconButton from "@onlineworkspace/uikit-solid/src/components/iconButton import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.jsx"; import clsx from "clsx"; import browserPath from "path-browserify"; -import {type Component, createEffect, createResource, For, useContext} from "solid-js"; -import {AppContext} from "../../../../appContext"; +import { type Component, createEffect, createResource, For, useContext } from "solid-js"; +import { AppContext } from "../../../../appContext"; import filesystemInterface from "../../../../lib/filesystemInterface"; import trpc from "../../../../lib/trpc"; import iconForItemType from "../../iconForItemType"; import onItemClick from "../../itemClick"; +import { ViewContext } from "../../viewContext"; import styles from "./GalleryView.module.scss"; const GalleryView: Component = () => { const appContext = useContext(AppContext); - const [ galleryPreviewMainImage, {refetch: refetchGalleryPreviewMainImage} ] = createResource(() => { + const viewContext = useContext(ViewContext); + const [galleryPreviewMainImage, { refetch: refetchGalleryPreviewMainImage }] = createResource(() => { // @ts-ignore - const parsedPath = filesystemInterface.urlToPath(appContext?.viewState.selectedItems?.[ 0 ] || ""); + const parsedPath = filesystemInterface.urlToPath(appContext?.viewState.selectedItems?.[0] || ""); if (parsedPath.type === "remote") { - return trpc.view.getGalleryItem.query({height: 768, path: parsedPath.path || undefined}); + return trpc.view.getGalleryItem.query({ height: 768, path: parsedPath.path || undefined }); } alert("this view is unsupported on this configuration"); - return {image: "/assets/generic_background.svg", dimensions: {width: 0, height: 0}}; + return { image: "/assets/generic_background.svg", dimensions: { width: 0, height: 0 } }; }); createEffect(() => { - appContext?.viewState.selectedItems; + appContext?.viewState[viewContext!.viewId].selectedItems; refetchGalleryPreviewMainImage(); }); @@ -51,7 +53,7 @@ const GalleryView: Component = () => { @@ -62,7 +64,7 @@ const GalleryView: Component = () => {
- + {(item, index) => { if (!appContext?.userPreferences.showHidden && item.hidden) return null; @@ -70,7 +72,7 @@ const GalleryView: Component = () => { onItemClick(e, appContext!, index(), item, () => 0)} + onClick={(e) => onItemClick(e, appContext!, index(), item)} onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); @@ -79,7 +81,7 @@ const GalleryView: Component = () => { e.preventDefault(); e.stopPropagation(); }} - color={appContext?.viewState.selectedItems.includes(item.path) ? "outlined" : "filled"} + color={appContext?.viewState[viewContext!.viewId].selectedItems.includes(item.path) ? "outlined" : "filled"} > {item.thumbnail !== undefined ? ( diff --git a/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.tsx b/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.tsx index fd112fc..88a3601 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.tsx @@ -1,31 +1,34 @@ -import UKCard from "@onlineworkspace/uikit-solid/src/components/card/UKCard.tsx"; import UKIcon from "@onlineworkspace/uikit-solid/src/components/icon/UKIcon.tsx"; -import {useSearchParams} from "@solidjs/router"; +import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.jsx"; import clsx from "clsx"; import browserPath from "path-browserify"; -import {type Component, For, useContext} from "solid-js"; -import {AppContext} from "../../../../appContext.ts"; +import { type Component, For, useContext } from "solid-js"; +import { AppContext } from "../../../../appContext.ts"; import iconForItemType from "../../iconForItemType.ts"; import onItemClick from "../../itemClick.ts"; +import { ViewContext } from "../../viewContext.ts"; import styles from "./GridView.module.scss"; -import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.jsx"; const GridView: Component = () => { - const [ _, setSearchParams ] = useSearchParams(); const appContext = useContext(AppContext); + const viewContext = useContext(ViewContext); return ( -
- +
+ {(item, index) => { if (!appContext?.userPreferences.showHidden && item.hidden) return null; return (