From 652d9a91546d0a721527766cd06f170f8e4df24c Mon Sep 17 00:00:00 2001 From: ewsgit Date: Tue, 28 Apr 2026 01:15:01 +0100 Subject: [PATCH] gallery view progress --- applications/uk.ewsgit.files/backend/index.ts | 30 +++- applications/uk.ewsgit.files/web/App.tsx | 6 +- .../layout/components/Quota/Quota.module.scss | 4 + .../web/layout/components/Quota/Quota.tsx | 5 +- .../layout/components/StatusBar/StatusBar.tsx | 6 +- .../web/lib/filesystemInterface.ts | 2 +- .../uk.ewsgit.files/web/pages/dir/View.tsx | 7 +- .../DetailsView/DetailsView.module.scss | 4 +- .../components/DetailsView/DetailsView.tsx | 2 +- .../GalleryView/GalleryView.module.scss | 137 +++++++++++++++++- .../components/GalleryView/GalleryView.tsx | 95 ++++++++++-- .../UKLinearProgressIndicator.module.scss | 4 +- 12 files changed, 269 insertions(+), 33 deletions(-) diff --git a/applications/uk.ewsgit.files/backend/index.ts b/applications/uk.ewsgit.files/backend/index.ts index 38e8f9c..b22443e 100644 --- a/applications/uk.ewsgit.files/backend/index.ts +++ b/applications/uk.ewsgit.files/backend/index.ts @@ -4,8 +4,6 @@ import { promises as fs, existsSync as fsExists } from "node:fs"; import * as path from "node:path"; import { createTRPCContext, procedure } from "@onlineworkspace/workspace-instance/src/systems/trpcRouter.js"; import { initTRPC } from "@trpc/server"; -import { octetInputParser } from "@trpc/server/http"; -import { randomUUIDv7 } from "bun"; import z from "zod"; import fastFolderSize from "fast-folder-size/sync.js"; @@ -466,6 +464,34 @@ const router = t.router({ }; } }), + getGalleryItem: procedure.input(z.object({ path: z.string().or(z.undefined()), height: z.number() })).query(async (opt) => { + let dimensions: { width: number; height: number } = { width: 0, height: 0 }; + + if (opt.input.path === undefined) { + return { image: "/assets/generic_background.svg", dimensions }; + } + + const resolvedPath = path.join(instance.sys.filesystem.FS_ROOT, opt.input.path); + + return { + image: + instance.sys.filesystem.getFileType(opt.input.path) === "image" + ? await instance.sys.image.serveImage(opt.ctx.userId, resolvedPath, { + resize: { + fit: "cover", + position: "centre", + dimensions: ({ width: fileWidth, height: fileHeight }) => { + dimensions = { width: fileWidth, height: fileHeight }; + + return { width: Math.floor((fileWidth / fileHeight) * opt.input.height), height: opt.input.height }; + }, + changeFormatTo: "webp", + }, + }) + : undefined, + dimensions, + }; + }), }, quota: procedure.output(z.object({ currentUsage: z.number(), maximum: z.number() })).query(async (opt) => { const quotaMax = (await (await opt.ctx.user()).getQuota()) || 8589934592; diff --git a/applications/uk.ewsgit.files/web/App.tsx b/applications/uk.ewsgit.files/web/App.tsx index 7d52bd9..8f6c162 100644 --- a/applications/uk.ewsgit.files/web/App.tsx +++ b/applications/uk.ewsgit.files/web/App.tsx @@ -18,8 +18,7 @@ interface UserPreferences { pinnedDirectories: string[]; viewType: "grid" | "details" | "gallery"; showPreview: boolean; - detailsViewIconSize: number; - gridViewIconSize: number; + zoomPercentage: number; showHidden: boolean; } @@ -66,8 +65,7 @@ const App: Component = () => { pinnedDirectories: [ "remote:/users/1/fs/" ], viewType: "details", showPreview: false, - detailsViewIconSize: 32, - gridViewIconSize: 128, + zoomPercentage: 1, showHidden: false }); const [ viewState, setViewState ] = createStore({ diff --git a/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.module.scss b/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.module.scss index dcff8c8..0b766a6 100644 --- a/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.module.scss +++ b/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.module.scss @@ -11,3 +11,7 @@ .linearProgressIndicator { min-width: unset !important; } + +.lowOnQuota { + --linear-indicator-background: 200, 55, 55; +} diff --git a/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.tsx b/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.tsx index 35f1bca..a5b8005 100644 --- a/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/Quota/Quota.tsx @@ -1,9 +1,10 @@ import UKLinearProgressIndicator from "@onlineworkspace/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.tsx"; import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.tsx"; -import {type Component, createSignal, onCleanup, onMount} from "solid-js"; +import {type Component, createSignal, onMount} from "solid-js"; import styles from "./Quota.module.scss"; import filesystemInterface from "../../../lib/filesystemInterface"; import humanReadableSize from "../../../lib/humanReadableSize"; +import clsx from "clsx"; const Quota: Component = () => { const [ used, setUsed ] = createSignal(0); @@ -18,7 +19,7 @@ const Quota: Component = () => { return (
- + {humanReadableSize(used())} of {humanReadableSize(maxUsed())} Used 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 2598ea1..71674f8 100644 --- a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx @@ -94,9 +94,9 @@ const StatusBar: Component = () => {
- appContext?.setUserPreferences("gridViewIconSize", appContext.userPreferences.gridViewIconSize + 2)} /> - appContext?.setUserPreferences("gridViewIconSize", appContext.userPreferences.gridViewIconSize + 2)} /> - appContext?.setUserPreferences("gridViewIconSize", appContext.userPreferences.gridViewIconSize + 2)} /> + appContext?.setUserPreferences("zoomPercentage", appContext.userPreferences.zoomPercentage + 0.2)} /> + appContext?.setUserPreferences("zoomPercentage", 1)} /> + appContext?.setUserPreferences("zoomPercentage", Math.max(appContext.userPreferences.zoomPercentage - 0.2, 0.2))} />
); diff --git a/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts b/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts index 3e95cde..f473f74 100644 --- a/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts +++ b/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts @@ -83,7 +83,7 @@ class FilesystemInterface { if (parsedUrl.type === "local") { return 4; } else { - return 8; + return 10; } } diff --git a/applications/uk.ewsgit.files/web/pages/dir/View.tsx b/applications/uk.ewsgit.files/web/pages/dir/View.tsx index fd43f61..1236185 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/View.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/View.tsx @@ -35,6 +35,7 @@ const View: Component = () => { } appContext?.userPreferences.viewType; + appContext?.userPreferences.zoomPercentage; navigationCounter++ const currentNavigationCount = navigationCounter; @@ -83,9 +84,9 @@ const View: Component = () => { filesystemInterface .getViewEntry( itemPath as UniformResourceLocator, - appContext!.userPreferences.viewType === "details" - ? appContext?.userPreferences.detailsViewIconSize - : appContext?.userPreferences.gridViewIconSize, + Math.floor(appContext!.userPreferences.zoomPercentage * (appContext!.userPreferences.viewType === "details" + ? 32 + : 128)), ) .then((viewEntry) => { if (viewEntry.status === "ok") { diff --git a/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.module.scss b/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.module.scss index c58cbcd..be936a5 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.module.scss +++ b/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.module.scss @@ -7,8 +7,8 @@ } .root { - --icon-size: 2rem; - --item-height: calc(var(--icon-size) + 1rem); + --icon-size: calc(2rem * var(--zoom-percentage, 1)); + --item-height: calc(calc(var(--icon-size) + 1rem) * var(--zoom-percentage, 1)); --col-spacing: 0.5rem; // display: grid; 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 d6542e5..a44c62e 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 @@ -17,7 +17,7 @@ const DetailsView: Component = () => { return ( 0}> - +
diff --git a/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.module.scss b/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.module.scss index 0b89fd3..b5dfdae 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.module.scss +++ b/applications/uk.ewsgit.files/web/pages/dir/components/GalleryView/GalleryView.module.scss @@ -1,2 +1,137 @@ -.galleryItems { +.page { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr auto; + height: 100%; + width: 100%; +} + +.galleryHeader { + display: flex; + flex-direction: row; + padding: 1rem; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + overflow-x: hidden; + + .galleryItems { + display: flex; + flex-direction: row; + align-items: end; + flex-shrink: 1; + height: 100%; + flex-grow: 1; + overflow: hidden; + perspective: 1024px; + + .galleryItem { + background: linear-gradient(to bottom right, pink, purple); + flex-grow: 1; + height: 100%; + width: auto; + } + + &:first-child { + .galleryItem { + &:first-child { + height: 80%; + transform: rotateY(-20deg); + transform-origin: right; + } + &:last-child { + height: 90%; + transform: rotateY(-10deg); + transform-origin: right; + } + } + } + + &:last-child { + .galleryItem { + &:first-child { + height: 90%; + transform: rotateY(10deg); + z-index: 3; + transform-origin: left; + } + &:last-child { + height: 80%; + z-index: 2; + transform: rotateY(20deg); + transform-origin: left; + } + } + } + } + + .galleryPreviewMain { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + } + + .galleryPreviewMainImage { + max-width: 100%; + max-height: 100%; + } +} + +.galleryItemsGrid { + display: flex; + gap: 0.5rem; + width: 100%; + overflow-x: auto; + padding: 1rem; + border-top: 1px solid rgb(var(--uk-sys-color-outline-variant)); + + .item { + display: flex; + flex-direction: column; + flex-shrink: 0; + gap: 0.5rem; + align-items: center; + justify-content: center; + animation: forwards fadeItemIn 200ms var(--uk-sys-motion-easing-standard-decelerate); + + .thumbnailIcon { + --icon-size: 6rem; + } + + &.itemHidden { + opacity: 0.6; + animation: forwards fadeHiddenItemIn 200ms var(--uk-sys-motion-easing-standard-decelerate); + } + + .itemThumbnail { + height: 128px; + aspect-ratio: 1 / 1; + object-fit: cover; + object-position: center; + } + } + + @keyframes fadeItemIn { + from { + opacity: 0; + scale: 0.9; + } + to { + opacity: 1; + scale: 1; + } + } + + @keyframes fadeHiddenItemIn { + from { + opacity: 0; + scale: 0.9; + } + to { + opacity: 0.6; + scale: 1; + } + } } 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 8ec48bc..1a07d71 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 @@ -1,16 +1,87 @@ -import type {Component} from "solid-js"; -import styles from "./GalleryView.module.scss" +import UKCard from "@onlineworkspace/uikit-solid/src/components/card/UKCard.jsx"; +import UKIcon from "@onlineworkspace/uikit-solid/src/components/icon/UKIcon.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 iconForItemType from "../../iconForItemType"; +import onItemClick from "../../itemClick"; +import styles from "./GalleryView.module.scss"; +import trpc from "../../../../lib/trpc"; +import filesystemInterface from "../../../../lib/filesystemInterface"; +import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.jsx"; const GalleryView: Component = () => { - return <> -
-
Gallery Items
- Gallery View Header -
-
- Gallery View Grid List + const appContext = useContext(AppContext); + const [ galleryPreviewMainImage, {refetch: refetchGalleryPreviewMainImage} ] = createResource(() => { + // @ts-ignore + const parsedPath = filesystemInterface.urlToPath(appContext?.viewState.selectedItems?.[ 0 ] || "") + + if (parsedPath.type === "remote") { + 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}} + } + ) + + createEffect(() => { + appContext?.viewState.selectedItems; + + refetchGalleryPreviewMainImage(); + }) + + return ( +
+
+
+
+
+
+
+
+ Filename.ext +
+ +
+
+
+
+
+
+
+ + {(item, index) => { + if (!appContext?.userPreferences.showHidden && item.hidden) return null; + + return ( + onItemClick(e, appContext!, index(), item, () => 0)} + onMouseDown={(e) => { + e.preventDefault(); + e.stopPropagation(); + }} + onDblClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + }} + color={appContext?.viewState.selectedItems.includes(item.path) ? "outlined" : "filled"} + > + {item.thumbnail !== undefined ? ( + + ) : ( + {iconForItemType(item.type)} + )} + + ); + }} + +
- -} + ); +}; -export default GalleryView +export default GalleryView; diff --git a/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss b/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss index c3cb3e5..a4c3aa2 100644 --- a/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss +++ b/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss @@ -1,7 +1,7 @@ // track .root { --track-height: var(--thickness, 0.25rem); - + --indicator-background: rgb(var(--linear-indicator-background, var(--uk-sys-color-primary))); display: flex; flex-direction: row; max-width: 100%; @@ -12,7 +12,7 @@ .activeIndicator { border-radius: 100rem; - background-color: rgb(var(--uk-sys-color-primary)); + background-color: var(--indicator-background); transition: width var(--uk-sys-motion-duration-250) var(--uk-sys-motion-easing-standard-decelerate); } -- 2.51.2