From 55c19fe0a30e18ac3aa1c47b7f798c90cb8619b8 Mon Sep 17 00:00:00 2001 From: ewsgit Date: Thu, 23 Apr 2026 21:20:02 +0100 Subject: [PATCH] get directory listings displaying from the remote instance server with working thumbnails (grid & list view) --- applications/uk.ewsgit.files/backend/index.ts | 90 +++++++++- .../uk.ewsgit.files/electron/package.json | 1 + .../uk.ewsgit.files/electron/src/main.ts | 39 ++-- applications/uk.ewsgit.files/web/App.tsx | 22 +-- .../uk.ewsgit.files/web/layout/Layout.tsx | 25 +-- .../ActionBar/ActionBar.module.scss | 6 +- .../layout/components/ActionBar/ActionBar.tsx | 5 +- .../components/PreviewPane/PreviewPane.tsx | 15 +- .../web/lib/filesystemInterface.ts | 109 ++++++++++- .../web/pages/dir/View.module.scss | 4 +- .../uk.ewsgit.files/web/pages/dir/View.tsx | 169 +++++++++++------- .../DetailsView/DetailsView.module.scss | 10 ++ .../components/DetailsView/DetailsView.tsx | 15 +- .../components/GridView/GridView.module.scss | 13 +- .../dir/components/GridView/GridView.tsx | 10 +- .../web/pages/dir/itemSelection.ts | 10 +- bun.lock | 23 +++ instance/src/systems/image.ts | 4 +- .../UKLinearProgressIndicator.module.scss | 8 +- .../UKLinearProgressIndicator.tsx | 16 +- .../components/splitButtons/UKSplitButton.tsx | 57 +++--- 21 files changed, 467 insertions(+), 184 deletions(-) diff --git a/applications/uk.ewsgit.files/backend/index.ts b/applications/uk.ewsgit.files/backend/index.ts index 1975648..06b9ec7 100644 --- a/applications/uk.ewsgit.files/backend/index.ts +++ b/applications/uk.ewsgit.files/backend/index.ts @@ -361,11 +361,97 @@ const router = t.router({ get: procedure.query(async (opt) => { return { showWelcome: false, - homePath: `/Users/${opt.ctx.userId}/fs/`, - pinnedDirectories: ["/home", "/Photos", "/Documents"], + homePath: `remote:/users/${opt.ctx.userId}/fs`, + pinnedDirectories: [`remote:/users/${opt.ctx.userId}/fs/Photos`, `remote:/users/${opt.ctx.userId}/fs/Documents`], }; }), }, + readDirectory: procedure + .input(z.object({ path: z.string() })) + .output( + z + .object({ items: z.undefined(), status: z.enum(["missing_permission", "invalid_path"]) }) + .or(z.object({ items: z.string().array(), status: z.literal("ok") })), + ) + .query(async (opt) => { + const resolvedPath = path.join(instance.sys.filesystem.FS_ROOT, opt.input.path); + + log.info(`Read Directory requested by user ${opt.ctx.userId} ${resolvedPath}`); + const userPermissions = await instance.sys.filesystem.getUserPermissions(opt.ctx.userId, resolvedPath); + + if (!userPermissions.read) { + return { status: "missing_permission" }; + } + + try { + const directoryContents = await fs.readdir(resolvedPath); + + return { items: directoryContents, status: "ok" }; + } catch (_) { + return { status: "invalid_path" }; + } + }), + view: { + getEntry: procedure + .input(z.object({ path: z.string(), thumbnailSize: z.number() })) + .output( + z + .object({ + status: z.literal("ok"), + data: z.object({ + path: z.string(), + type: z.enum(["file", "directory", "link"]), + thumbnail: z.string().optional(), + shared: z.boolean().optional(), + size: z.number().optional(), + }), + }) + .or(z.object({ status: z.enum(["missing_permission", "invalid_path"]) })), + ) + .query(async (opt) => { + const resolvedPath = path.join(instance.sys.filesystem.FS_ROOT, opt.input.path); + + log.info(`View entry requested by user ${opt.ctx.userId} ${resolvedPath}`); + const userPermissions = await instance.sys.filesystem.getUserPermissions(opt.ctx.userId, resolvedPath); + + if (!userPermissions.read) { + return { + status: "missing_permission", + }; + } + + try { + const itemStats = await fs.lstat(resolvedPath); + + return { + status: "ok", + data: { + path: opt.input.path, + type: itemStats.isSymbolicLink() ? "link" : itemStats.isDirectory() ? "directory" : itemStats.isFile() ? "file" : "file", + // TODO: implement sharing first + shared: false, + size: itemStats.size, + thumbnail: itemStats.isFile() + ? instance.sys.filesystem.getFileType(opt.input.path) === "image" + ? await instance.sys.image.serveImage(opt.ctx.userId, resolvedPath, { + resize: { + fit: "cover", + position: "centre", + dimensions: { width: opt.input.thumbnailSize, height: opt.input.thumbnailSize }, + changeFormatTo: "webp", + }, + }) + : undefined + : undefined, + }, + }; + } catch (_) { + return { + status: "invalid_path", + }; + } + }), + }, }); export type TRPCRouter = typeof router; diff --git a/applications/uk.ewsgit.files/electron/package.json b/applications/uk.ewsgit.files/electron/package.json index de9c726..9a91624 100644 --- a/applications/uk.ewsgit.files/electron/package.json +++ b/applications/uk.ewsgit.files/electron/package.json @@ -10,6 +10,7 @@ "@electron-forge/maker-deb": "^7.11.1", "@electron-forge/maker-squirrel": "^7.11.1", "@electron-forge/maker-zip": "^7.11.1", + "@electron-forge/maker-rpm": "^7.11.1", "@electron-forge/shared-types": "^7.11.1", "@types/node": "latest", "electron": "^41.3.0" diff --git a/applications/uk.ewsgit.files/electron/src/main.ts b/applications/uk.ewsgit.files/electron/src/main.ts index 7d88faf..533e367 100644 --- a/applications/uk.ewsgit.files/electron/src/main.ts +++ b/applications/uk.ewsgit.files/electron/src/main.ts @@ -1,7 +1,7 @@ -import {app, BrowserWindow} from "electron" -import path from "node:path" +import path from "node:path"; +import {app, BrowserWindow, session} from "electron"; -function createWindow() { +async function createWindow() { const window = new BrowserWindow({ width: 1000, height: 800, @@ -12,28 +12,31 @@ function createWindow() { titleBarStyle: "hidden", frame: true, webPreferences: { - preload: path.join(process.cwd(), "./src/preload.js") - } - }) + preload: path.join(process.cwd(), "./src/preload.js"), + }, + }); - window.loadURL("https://localhost/app/uk.ewsgit.files") + if ((await session.defaultSession.cookies.get({url: "https://localhost"})).length === 0) { + window.loadURL("https://localhost/?redirect=/app/uk.ewsgit.files/"); + } else { + window.loadURL("https://localhost/app/uk.ewsgit.files"); + } - return window + return window; } app.whenReady().then(() => { - createWindow() + createWindow(); - app.on('activate', () => { + app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { - createWindow() + createWindow(); } - }) -}) + }); +}); -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit() +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); } -}) - +}); diff --git a/applications/uk.ewsgit.files/web/App.tsx b/applications/uk.ewsgit.files/web/App.tsx index 02de6ff..1b1c4d3 100644 --- a/applications/uk.ewsgit.files/web/App.tsx +++ b/applications/uk.ewsgit.files/web/App.tsx @@ -1,12 +1,12 @@ -import { Route } from "@solidjs/router"; -import { type Accessor, type Component, createSignal, lazy, onMount } from "solid-js"; -import { createStore, type SetStoreFunction, type Store } from "solid-js/store"; -import { AppContext } from "./appContext.ts"; +import {Route} from "@solidjs/router"; +import {type Accessor, type Component, createSignal, lazy, onMount} from "solid-js"; +import {createStore, type SetStoreFunction, type Store} from "solid-js/store"; +import {AppContext} from "./appContext.ts"; import ActionBar from "./layout/components/ActionBar/ActionBar.tsx"; import layoutStyles from "./layout/Layout.module.scss"; import trpc from "./lib/trpc.ts"; import View from "./pages/dir/View.tsx"; -import type { ViewItem } from "./pages/dir/viewItem.ts"; +import type {ViewItem} from "./pages/dir/viewItem.ts"; import Redirect from "./Redirect.tsx"; const Layout = lazy(() => import("./layout/Layout.tsx")); @@ -37,18 +37,18 @@ interface AppContextType { isDesktopApp: boolean; } -export type { AppContextType }; +export type {AppContextType}; const App: Component = () => { - const [hasLoaded, setHasLoaded] = createSignal(false); - const [userPreferences, setUserPreferences] = createStore({ + const [ hasLoaded, setHasLoaded ] = createSignal(false); + const [ userPreferences, setUserPreferences ] = createStore({ showWelcome: true, - homePath: "/Users/1/fs/", - pinnedDirectories: ["/home"], + homePath: "remote:/users/1/fs/", + pinnedDirectories: [ "~/home" ], viewType: "details", showPreview: true, }); - const [viewState, setViewState] = createStore({ + const [ viewState, setViewState ] = createStore({ viewItems: [], selectedItems: [], lastSelectionTime: -1, diff --git a/applications/uk.ewsgit.files/web/layout/Layout.tsx b/applications/uk.ewsgit.files/web/layout/Layout.tsx index 50b2417..d9b35b1 100644 --- a/applications/uk.ewsgit.files/web/layout/Layout.tsx +++ b/applications/uk.ewsgit.files/web/layout/Layout.tsx @@ -3,10 +3,10 @@ import FOLDER_ICON from "@material-symbols/svg-700/outlined/folder.svg"; import HOUSE_ICON from "@material-symbols/svg-700/outlined/house.svg"; import PERSON_ICON from "@material-symbols/svg-700/outlined/person.svg"; import UKSideBar from "@onlineworkspace/uikit-solid/src/components/sideBar/UKSideBar.tsx"; -import { useNavigate } from "@solidjs/router"; +import {useNavigate} from "@solidjs/router"; import browserPath from "path-browserify"; -import { type Component, type ParentProps, useContext } from "solid-js"; -import { AppContext } from "../appContext.ts"; +import {type Component, type ParentProps, useContext} from "solid-js"; +import {AppContext} from "../appContext.ts"; import PreviewPane from "./components/PreviewPane/PreviewPane.tsx"; import Quota from "./components/Quota/Quota.tsx"; import StatusBar from "./components/StatusBar/StatusBar.tsx"; @@ -26,7 +26,7 @@ const Layout: Component = (props) => { }, { type: "button", - icon: { type: "icon", value: HOUSE_ICON }, + icon: {type: "icon", value: HOUSE_ICON}, label: "Home", onClick() { navigate(`/app/uk.ewsgit.files/dir?path=${appContext?.userPreferences.homePath}`); @@ -34,7 +34,7 @@ const Layout: Component = (props) => { }, { type: "button", - icon: { type: "icon", value: PERSON_ICON }, + icon: {type: "icon", value: PERSON_ICON}, label: "Shared With Me", onClick() { navigate("/app/uk.ewsgit.files/shared"); @@ -50,22 +50,27 @@ const Layout: Component = (props) => { ...((appContext?.userPreferences.pinnedDirectories || []).map((dir) => { return { type: "button", - icon: { type: "icon", value: FOLDER_ICON }, + icon: {type: "icon", value: FOLDER_ICON}, label: browserPath.basename(dir), onClick() { - navigate(`/app/uk.ewsgit.files/dir?path=${dir}`); + let transformedPath = dir + + if (dir.startsWith("~")) + transformedPath = dir.replace("~", appContext?.userPreferences.homePath || "~") + + navigate(`/app/uk.ewsgit.files/dir?path=${transformedPath}`); }, }; }) as { type: "button"; - icon: { type: "icon"; value: string }; + icon: {type: "icon"; value: string}; label: string; onClick(): void; }[]), - { type: "margin" }, + {type: "margin"}, { type: "button", - icon: { type: "icon", value: DELETE_ICON }, + icon: {type: "icon", value: DELETE_ICON}, label: "Bin", badgeLabel: appContext?.deletedItemCount || undefined, onClick() { diff --git a/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.module.scss b/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.module.scss index 22bb52a..b14e21b 100644 --- a/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.module.scss +++ b/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.module.scss @@ -52,8 +52,12 @@ } } +.windowIcon { +} + .closeWindowIcon { - &:hover { + &:hover, + &:active { background-color: #cc4444; } } 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 8a77885..4c8d328 100644 --- a/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/ActionBar/ActionBar.tsx @@ -21,6 +21,7 @@ import {type Component, createSignal, Show, useContext} from "solid-js"; import {AppContext} from "../../../appContext.ts"; import type {ViewItem} from "../../../pages/dir/viewItem.ts"; import styles from "./ActionBar.module.scss"; +import clsx from "clsx" const ActionBar: Component = () => { const isMobile = useIsMobile(); @@ -157,6 +158,7 @@ const ActionBar: Component = () => { size="s" shape="square" alt={"minimize window"} + class={styles.windowIcon} onClick={() => { window.close() }} @@ -167,6 +169,7 @@ const ActionBar: Component = () => { size="s" shape="square" alt={"maximize window"} + class={styles.windowIcon} onClick={() => { window.close() }} @@ -177,7 +180,7 @@ const ActionBar: Component = () => { size="s" shape="square" alt={"close window"} - class={styles.closeWindowIcon} + class={clsx(styles.closeWindowIcon, styles.windowIcon)} onClick={() => { window.close() }} 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 1958aec..2ec1c56 100644 --- a/applications/uk.ewsgit.files/web/layout/components/PreviewPane/PreviewPane.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/PreviewPane/PreviewPane.tsx @@ -3,10 +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 iconForItemType from "../../../pages/dir/iconForItemType.ts"; import styles from "./PreviewPane.module.scss"; +import humanReadableSize from "../../../lib/humanReadableSize.ts"; const PreviewPane: Component = () => { const isMobile = useIsMobile(); @@ -26,18 +27,24 @@ const PreviewPane: Component = () => { {appContext!.viewState.selectedItems.length} items + + ({humanReadableSize(appContext.viewState.viewItems.filter(i => appContext.viewState.selectedItems.includes(i.path)).map(i => i?.size || 0).reduce((accumulator, currentValue) => accumulator + currentValue, 0))}) + ) : ( <> {iconForItemType( appContext?.viewState.viewItems.find((item) => { - return item.path === appContext!.viewState.selectedItems[0]; + return item.path === appContext!.viewState.selectedItems[ 0 ]; })?.type || "file", )} - {browserPath.basename(appContext!.viewState.selectedItems[0])} + {browserPath.basename(appContext!.viewState.selectedItems[ 0 ])} + + + ({humanReadableSize(appContext.viewState.viewItems.find(i => i.path === appContext.viewState.selectedItems[ 0 ])?.size || 0)}) )} diff --git a/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts b/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts index 47f259a..db3a91f 100644 --- a/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts +++ b/applications/uk.ewsgit.files/web/lib/filesystemInterface.ts @@ -1,9 +1,16 @@ -export type UniformResourceLocator = `local:${string}` | `remote:${string}` +import path from "path-browserify"; +import trpc from "./trpc"; -export default class FilesystemInterface { +export type UniformResourceLocator = `local:${string}` | `remote:${string}`; + +class FilesystemInterface { createFile(url: UniformResourceLocator) { const parsedUrl = this.urlToPath(url); + if (parsedUrl.type === "invalid") { + return false; + } + if (parsedUrl.type === "local") { // Do some electron things return true; @@ -16,6 +23,10 @@ export default class FilesystemInterface { createDirectory(url: UniformResourceLocator) { const parsedUrl = this.urlToPath(url); + if (parsedUrl.type === "invalid") { + return false; + } + if (parsedUrl.type === "local") { // Do some electron things return true; @@ -25,21 +36,101 @@ export default class FilesystemInterface { } } - urlToPath(url: UniformResourceLocator): {type: "remote" | "local", path: string} | {type: "invalid"} { + async readDirectory(url: UniformResourceLocator) { + const parsedUrl = this.urlToPath(url); + + if (parsedUrl.type === "invalid") { + return { status: "invalid_path" as const }; + } + + if (parsedUrl.type === "local") { + // Do some electron things + return { status: "missing_permission" as const }; + } else { + console.log(parsedUrl.path); + const readDirectoryResponse = await trpc.readDirectory.query({ path: parsedUrl.path }); + + if (readDirectoryResponse.status === "ok") { + return { + status: readDirectoryResponse.status, + items: readDirectoryResponse.items.map((newItem) => { + return `${parsedUrl.type}:${path.join(parsedUrl.path, newItem)}` as UniformResourceLocator; + }), + }; + } else { + return { status: readDirectoryResponse.status }; + } + } + } + + async getViewEntry(url: UniformResourceLocator, viewType: "details" | "grid") { + const parsedUrl = this.urlToPath(url); + + if (parsedUrl.type === "invalid") { + return { status: "invalid_path" as const }; + } + + if (parsedUrl.type === "local") { + // Do some electron things + return { status: "missing_permission" as const }; + } else { + const getEntryResponse = await trpc.view.getEntry.query({ path: parsedUrl.path, thumbnailSize: viewType === "details" ? 32 : 128 }); + + if (getEntryResponse.status === "ok") { + return { + status: getEntryResponse.status, + data: { + ...getEntryResponse.data, + path: `${parsedUrl.type}:${getEntryResponse.data.path}`, + }, + }; + } else { + return { status: getEntryResponse.status }; + } + } + } + + joinUrls(...urls: UniformResourceLocator[]) { + const firstUrlType = this.urlToPath(urls[0]).type; + + const paths: string[] = []; + + for (const url of urls) { + const parsedUrl = this.urlToPath(url); + + if (parsedUrl.type === "invalid") { + throw new Error(`Cannot join invalid fs URLs, \n${urls.join("\n")}`); + } + + if (parsedUrl.type !== firstUrlType) { + throw new Error(`Cannot join fs URLs which are of a different type, \n${urls.join("\n")}`); + } + + paths.push(parsedUrl.path); + } + + return `${firstUrlType}:${path.join(...paths)}`; + } + + urlToPath(url: UniformResourceLocator): { type: "remote" | "local"; path: string } | { type: "invalid" } { if (url.startsWith("local:")) { return { type: "local", - path: url.substring(7) - } + path: url.substring(7), + }; } else if (url.startsWith("remote:")) { return { type: "remote", - path: url.substring(7) - } + path: url.substring(7), + }; } return { - type: "invalid" - } + type: "invalid", + }; } } + +const filesystemInterface = new FilesystemInterface(); + +export default filesystemInterface; 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 f6a31c3..3099d4c 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/View.module.scss +++ b/applications/uk.ewsgit.files/web/pages/dir/View.module.scss @@ -5,6 +5,8 @@ } .dragSelectRegion { - background: rgb(var(--uk-sys-color-primary), 0.25); + background: rgb(var(--uk-sys-color-primary), 0.5); + border: 1px solid rgb(var(--uk-sys-color-primary), 0.25); position: fixed; + border-radius: var(--uk-sys-shape-corner-small); } diff --git a/applications/uk.ewsgit.files/web/pages/dir/View.tsx b/applications/uk.ewsgit.files/web/pages/dir/View.tsx index 6e18c80..3e2f52a 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/View.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/View.tsx @@ -1,111 +1,142 @@ -import { useSearchParams } from "@solidjs/router"; -import { type Component, createEffect, Match, Switch, useContext } from "solid-js"; -import { createStore } from "solid-js/store"; -import { AppContext } from "../../appContext.ts"; +import {useSearchParams} from "@solidjs/router"; +import {type Component, createEffect, Match, Switch, useContext} from "solid-js"; +import {createStore} from "solid-js/store"; +import {AppContext} from "../../appContext.ts"; +import trpc from "../../lib/trpc.ts"; import DetailsView from "./components/DetailsView/DetailsView.tsx"; import GridView from "./components/GridView/GridView.tsx"; import styles from "./View.module.scss"; +import filesystemInterface, {type UniformResourceLocator} from "../../lib/filesystemInterface.ts"; const View: Component = () => { - const [searchParams, setSearchParams] = useSearchParams<{ path?: string }>(); + const [ searchParams, setSearchParams ] = useSearchParams<{path?: UniformResourceLocator}>(); const appContext = useContext(AppContext); - const [dragSelectRegion, setDragSelectRegion] = createStore<{ - origin?: { x: number; y: number }; - size?: { x: number; y: number }; - transOrigin?: { x: number; y: number }; + const [ dragSelectRegion, setDragSelectRegion ] = createStore<{ + origin?: {x: number; y: number}; + size?: {x: number; y: number}; + transOrigin?: {x: number; y: number}; }>({ origin: undefined, size: undefined, }); - createEffect(() => { + createEffect(async () => { if (!searchParams.path) { - setSearchParams({ path: appContext?.userPreferences.homePath }); + setSearchParams({path: appContext?.userPreferences.homePath}); + return; } - }, []); + + const newItems = await filesystemInterface.readDirectory(searchParams.path || "remote:/") + + console.log({path: searchParams.path, newItems}) + + if (newItems.status === "ok") { + appContext?.setViewState("selectedItems", []); + appContext?.setViewState("lastSelectionTime", -1); + appContext?.setViewState("viewItems", []); + + for (const itemPath of newItems.items) { + const viewEntry = await filesystemInterface.getViewEntry(itemPath, appContext!.userPreferences.viewType === "details" ? "details" : "grid") + + if (viewEntry.status === "ok") { + appContext?.setViewState("viewItems", [ ...appContext.viewState.viewItems, viewEntry.data ]); + } + } + + } + }); return ( /** biome-ignore lint/a11y/noStaticElementInteractions: button functionality not required */ - /** biome-ignore lint/a11y/useKeyWithClickEvents: button functionality not required */
{ + onDblClick={(e) => { if (e.currentTarget === e.target) appContext!.setViewState("selectedItems", []); }} - onMouseDown={(downEvent) => { - document.body.style.userSelect = "none"; - setDragSelectRegion("origin", { x: downEvent.clientX, y: downEvent.clientY }); - setDragSelectRegion("transOrigin", { x: downEvent.clientX, y: downEvent.clientY }); + // onMouseDown={(downEvent) => { + // document.body.style.userSelect = "none"; + // setDragSelectRegion("origin", {x: downEvent.clientX, y: downEvent.clientY}); + // setDragSelectRegion("transOrigin", {x: downEvent.clientX, y: downEvent.clientY}); - const currentTarget = downEvent.currentTarget as HTMLDivElement; - const currentTargetBounds = currentTarget.getBoundingClientRect(); - const selectableItems = currentTarget.querySelectorAll("[data-fs-item-path]"); + // const currentTarget = downEvent.currentTarget as HTMLDivElement; + // const currentTargetBounds = currentTarget.getBoundingClientRect(); + // const selectableItems = currentTarget.querySelectorAll("[data-fs-item-path]"); - function mouseUp() { - document.body.style.userSelect = "unset"; + // function mouseUp() { + // document.body.style.userSelect = "unset"; - setDragSelectRegion("origin", undefined); - setDragSelectRegion("size", undefined); - setDragSelectRegion("transOrigin", undefined); + // setDragSelectRegion("origin", undefined); + // setDragSelectRegion("size", undefined); + // setDragSelectRegion("transOrigin", undefined); - document.removeEventListener("mouseup", mouseUp); - document.removeEventListener("mousemove", mouseMove); - } + // document.removeEventListener("mouseup", mouseUp); + // document.removeEventListener("mousemove", mouseMove); + // } - function mouseMove(e: MouseEvent) { - if (!dragSelectRegion.origin) return; - if (!dragSelectRegion.transOrigin) return; + // let itemInRegionCalculationTimeout: NodeJS.Timeout | undefined; - const mouseX = Math.min(Math.max(e.clientX, currentTargetBounds.left), currentTargetBounds.right); - const mouseY = Math.min(Math.max(e.clientY, currentTargetBounds.top), currentTargetBounds.bottom); + // function itemInRegionCalculation() { + // appContext?.setViewState("selectedItems", []); - let sizeX = 0; - let sizeY = 0; + // for (const item of selectableItems) { + // const boundingRect = item.getBoundingClientRect(); - sizeX = mouseX - dragSelectRegion.origin.x; - sizeY = mouseY - dragSelectRegion.origin.y; + // const tl1 = {x: dragSelectRegion.transOrigin?.x || 0, y: dragSelectRegion.transOrigin?.y || 0}; + // const br1 = {x: tl1.x + (dragSelectRegion.size?.x || 0), y: tl1.y + (dragSelectRegion.size?.y || 0)}; - if (mouseX < dragSelectRegion.origin.x) { - setDragSelectRegion("transOrigin", { x: mouseX, y: dragSelectRegion.transOrigin.y }); - sizeX = dragSelectRegion.origin.x - mouseX; - } + // const tl2 = {x: boundingRect.left, y: boundingRect.top}; + // const br2 = {x: boundingRect.right, y: boundingRect.bottom}; - if (mouseY < dragSelectRegion.origin.y) { - setDragSelectRegion("transOrigin", { x: dragSelectRegion.transOrigin.x, y: mouseY }); - sizeY = dragSelectRegion.origin.y - mouseY; - } + // if (tl1.x > br2.x || tl2.x > br1.x) continue; - setDragSelectRegion("size", { - x: sizeX, - y: sizeY, - }); + // if (tl1.y > br2.y || tl2.y > br1.y) continue; - appContext?.setViewState("selectedItems", []); + // const itemPath = item.getAttribute("data-fs-item-path"); + // if (!itemPath) return; + // if (!appContext?.viewState.selectedItems.includes(itemPath)) { + // appContext?.setViewState("selectedItems", [ ...appContext.viewState.selectedItems, itemPath ]); + // } + // } + // } - for (const item of selectableItems) { - const boundingRect = item.getBoundingClientRect(); + // function mouseMove(e: MouseEvent) { + // if (!dragSelectRegion.origin) return; + // if (!dragSelectRegion.transOrigin) return; - const tl1 = { x: dragSelectRegion.transOrigin.x, y: dragSelectRegion.transOrigin.y }; - const br1 = { x: tl1.x + dragSelectRegion.size!.x, y: tl1.y + dragSelectRegion.size!.y }; + // const mouseX = Math.min(Math.max(e.clientX, currentTargetBounds.left), currentTargetBounds.right); + // const mouseY = Math.min(Math.max(e.clientY, currentTargetBounds.top), currentTargetBounds.bottom); - const tl2 = { x: boundingRect.left, y: boundingRect.top }; - const br2 = { x: boundingRect.right, y: boundingRect.bottom }; + // let sizeX = 0; + // let sizeY = 0; - if (tl1.x > br2.x || tl2.x > br1.x) continue; + // sizeX = mouseX - dragSelectRegion.origin.x; + // sizeY = mouseY - dragSelectRegion.origin.y; - if (tl1.y > br2.y || tl2.y > br1.y) continue; + // if (mouseX < dragSelectRegion.origin.x) { + // setDragSelectRegion("transOrigin", {x: mouseX, y: dragSelectRegion.transOrigin.y}); + // sizeX = dragSelectRegion.origin.x - mouseX; + // } - const itemPath = item.getAttribute("data-fs-item-path"); - if (!itemPath) return; - if (!appContext?.viewState.selectedItems.includes(itemPath)) { - appContext?.setViewState("selectedItems", [...appContext.viewState.selectedItems, itemPath]); - } - } - } + // if (mouseY < dragSelectRegion.origin.y) { + // setDragSelectRegion("transOrigin", {x: dragSelectRegion.transOrigin.x, y: mouseY}); + // sizeY = dragSelectRegion.origin.y - mouseY; + // } - document.addEventListener("mouseup", mouseUp); - document.addEventListener("mousemove", mouseMove); - }} + // setDragSelectRegion("size", { + // x: sizeX, + // y: sizeY, + // }); + + // if (itemInRegionCalculationTimeout) clearTimeout(itemInRegionCalculationTimeout); + + // itemInRegionCalculationTimeout = setTimeout(() => { + // itemInRegionCalculation(); + // }, 2); + // } + + // document.addEventListener("mouseup", mouseUp); + // document.addEventListener("mousemove", mouseMove); + // }} > 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 bd439cf..111940b 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 @@ -88,4 +88,14 @@ > td:last-child { border-radius: 0 0.5rem 0.5rem 0; } + + .itemThumbnail { + height: var(--item-size); + aspect-ratio: 1 / 1; + object-fit: cover; + object-position: center; + } + + .itemIcon { + } } 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 5b404d1..f98647d 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,14 +1,17 @@ import UKIcon from "@onlineworkspace/uikit-solid/src/components/icon/UKIcon.tsx"; import UKText from "@onlineworkspace/uikit-solid/src/components/text/UKText.tsx"; 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 "../../itemSelection.ts"; import styles from "./DetailsView.module.scss"; -import type { DOMElement } from "solid-js/jsx-runtime"; +import type {DOMElement} from "solid-js/jsx-runtime"; +import {useSearchParams} from "@solidjs/router"; +import humanReadableSize from "../../../../lib/humanReadableSize.ts"; const DetailsView: Component = () => { + const [ _, setSearchParams ] = useSearchParams(); const appContext = useContext(AppContext); return ( @@ -48,11 +51,11 @@ const DetailsView: Component = () => { data-fs-item-path={item.path} class={styles.item} onClick={(e) => - onItemClick(e as unknown as MouseEvent & { currentTarget: HTMLButtonElement; target: DOMElement }, appContext!, index(), item) + onItemClick(e as unknown as MouseEvent & {currentTarget: HTMLButtonElement; target: DOMElement}, appContext!, index(), item, setSearchParams) } data-selected={appContext?.viewState.selectedItems.includes(item.path)} > - {item.thumbnail !== undefined ? <>thumbnail time : {iconForItemType(item.type)}} + {item.thumbnail !== undefined ? : {iconForItemType(item.type)}} {browserPath.basename(item.path)} @@ -70,7 +73,7 @@ const DetailsView: Component = () => { - 100 Bytes + {humanReadableSize(item.size || 0)} diff --git a/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.module.scss b/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.module.scss index b9dfe00..fbf0468 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.module.scss +++ b/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.module.scss @@ -14,8 +14,15 @@ gap: 0.5rem; align-items: center; justify-content: center; -} -.thumbnailIcon { - --icon-size: 6rem; + .thumbnailIcon { + --icon-size: 6rem; + } + + .itemThumbnail { + height: 128px; + aspect-ratio: 1 / 1; + object-fit: cover; + object-position: center; + } } 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 38a5411..1c3f283 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,13 +1,15 @@ import UKCard from "@onlineworkspace/uikit-solid/src/components/card/UKCard.tsx"; import UKIcon from "@onlineworkspace/uikit-solid/src/components/icon/UKIcon.tsx"; 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 "../../itemSelection.ts"; import styles from "./GridView.module.scss"; +import {useSearchParams} from "@solidjs/router"; const GridView: Component = () => { + const [ _, setSearchParams ] = useSearchParams(); const appContext = useContext(AppContext); return ( @@ -18,10 +20,10 @@ const GridView: Component = () => { onItemClick(e, appContext!, index(), item)} + onClick={(e) => onItemClick(e, appContext!, index(), item, setSearchParams)} color={appContext?.viewState.selectedItems.includes(item.path) ? "outlined" : "filled"} > - {item.thumbnail !== undefined ? <>thumbnail time : {iconForItemType(item.type)}} + {item.thumbnail !== undefined ? : {iconForItemType(item.type)}} {browserPath.basename(item.path)} ); diff --git a/applications/uk.ewsgit.files/web/pages/dir/itemSelection.ts b/applications/uk.ewsgit.files/web/pages/dir/itemSelection.ts index 1c183ae..abfe90e 100644 --- a/applications/uk.ewsgit.files/web/pages/dir/itemSelection.ts +++ b/applications/uk.ewsgit.files/web/pages/dir/itemSelection.ts @@ -7,6 +7,7 @@ const onItemClick = ( appContext: AppContextType, itemIndex: number, item: ViewItem, + setSearchParams: (params: { [key: string]: string }) => void, ) => { if (e.shiftKey && (appContext?.viewState.selectedItems.length || 0) > 0) { // get the index of the selected item, select all items from that index to the current index @@ -65,8 +66,13 @@ const onItemClick = ( } else { if (appContext?.viewState.selectedItems.length === 1 && appContext.viewState.selectedItems[0] === item.path) { if (appContext.viewState.lastSelectionTime > Date.now() - 500) { - // Open the item! - alert("Open this item!"); + if (item.type === "file") { + alert("Implement this when we can open items!"); + return; + } + + setSearchParams({ path: item.path }); + return; } diff --git a/bun.lock b/bun.lock index c49f265..6befecd 100644 --- a/bun.lock +++ b/bun.lock @@ -130,6 +130,7 @@ "devDependencies": { "@electron-forge/cli": "^7.11.1", "@electron-forge/maker-deb": "^7.11.1", + "@electron-forge/maker-rpm": "^7.11.1", "@electron-forge/maker-squirrel": "^7.11.1", "@electron-forge/maker-zip": "^7.11.1", "@electron-forge/shared-types": "^7.11.1", @@ -574,6 +575,8 @@ "@electron-forge/maker-deb": ["@electron-forge/maker-deb@7.11.1", "", { "dependencies": { "@electron-forge/maker-base": "7.11.1", "@electron-forge/shared-types": "7.11.1" }, "optionalDependencies": { "electron-installer-debian": "^3.2.0" } }, "sha512-QTYiryQLYPDkq6pIfBmx0GQ6D8QatUkowH7rTlW5MnCUa0uumX0Xu7yGIjesuwW37fxT3Lv4xi+FSXMCm2eC1w=="], + "@electron-forge/maker-rpm": ["@electron-forge/maker-rpm@7.11.1", "", { "dependencies": { "@electron-forge/maker-base": "7.11.1", "@electron-forge/shared-types": "7.11.1" }, "optionalDependencies": { "electron-installer-redhat": "^3.2.0" } }, "sha512-iEfJPRQQyaTqk2EbUfZgulChNWvxGXeYUH0xBX/r5cj1pL4vcJXt3jLMQBVn3mk/0Ytv9UWRs8R/XuNWX6sf2w=="], + "@electron-forge/maker-squirrel": ["@electron-forge/maker-squirrel@7.11.1", "", { "dependencies": { "@electron-forge/maker-base": "7.11.1", "@electron-forge/shared-types": "7.11.1", "fs-extra": "^10.0.0" }, "optionalDependencies": { "electron-winstaller": "^5.3.0" } }, "sha512-oSg7fgad6l+X0DjtRkSpMzB0AjzyDO4mb2gzM4kTodkP1ADeiMi08bxy0ZeCESqLm5+fG72cAPmEr3BAPvI1yw=="], "@electron-forge/maker-zip": ["@electron-forge/maker-zip@7.11.1", "", { "dependencies": { "@electron-forge/maker-base": "7.11.1", "@electron-forge/shared-types": "7.11.1", "cross-zip": "^4.0.0", "fs-extra": "^10.0.0", "got": "^11.8.5" } }, "sha512-30rcp0AbJLfkFBX2hmO14LKXx7z9V61LffTVbTCFMh5vUB2kZvcA5xAhsBk2oUJWfGVxe1DuSEU0rDR9bUMHUg=="], @@ -1464,6 +1467,8 @@ "electron-installer-debian": ["electron-installer-debian@3.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.0.0", "debug": "^4.1.1", "electron-installer-common": "^0.10.2", "fs-extra": "^9.0.0", "get-folder-size": "^2.0.1", "lodash": "^4.17.4", "word-wrap": "^1.2.3", "yargs": "^16.0.2" }, "os": [ "linux", "darwin", ], "bin": { "electron-installer-debian": "src/cli.js" } }, "sha512-58ZrlJ1HQY80VucsEIG9tQ//HrTlG6sfofA3nRGr6TmkX661uJyu4cMPPh6kXW+aHdq/7+q25KyQhDrXvRL7jw=="], + "electron-installer-redhat": ["electron-installer-redhat@3.4.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.0.0", "debug": "^4.1.1", "electron-installer-common": "^0.10.2", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "word-wrap": "^1.2.3", "yargs": "^16.0.2" }, "os": [ "linux", "darwin", ], "bin": { "electron-installer-redhat": "src/cli.js" } }, "sha512-gEISr3U32Sgtj+fjxUAlSDo3wyGGq6OBx7rF5UdpIgbnpUvMN4W5uYb0ThpnAZ42VEJh/3aODQXHbFS4f5J3Iw=="], + "electron-to-chromium": ["electron-to-chromium@1.5.335", "", {}, "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q=="], "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], @@ -2752,6 +2757,12 @@ "electron-installer-debian/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], + "electron-installer-redhat/@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="], + + "electron-installer-redhat/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "electron-installer-redhat/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], + "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -3088,6 +3099,12 @@ "electron-installer-debian/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "electron-installer-redhat/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], + + "electron-installer-redhat/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "electron-installer-redhat/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], @@ -3210,6 +3227,10 @@ "electron-installer-debian/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "electron-installer-redhat/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "electron-installer-redhat/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "electron/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "electron/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], @@ -3254,6 +3275,8 @@ "electron-installer-debian/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "electron-installer-redhat/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@1.3.0", "", { "dependencies": { "p-try": "^1.0.0" } }, "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q=="], "@grpc/grpc-js/@grpc/proto-loader/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/instance/src/systems/image.ts b/instance/src/systems/image.ts index 3df9c1f..a4fd3b3 100644 --- a/instance/src/systems/image.ts +++ b/instance/src/systems/image.ts @@ -15,7 +15,7 @@ export default class ImageSystem extends System { public?: boolean; resize?: { dimensions: { width: number; height: number }; - changeFormatTo?: "avif" | "jpeg" | "png"; + changeFormatTo?: "avif" | "jpeg" | "png" | "webp"; position?: "top" | "right top" | "right" | "right bottom" | "bottom" | "left bottom" | "left" | "left top" | "centre"; fit?: "cover" | "contain" | "fill"; background?: string; @@ -59,7 +59,7 @@ export default class ImageSystem extends System { width: number; height: number; }); - changeFormatTo?: "avif" | "jpeg" | "png"; + changeFormatTo?: "avif" | "jpeg" | "png" | "webp"; position?: "top" | "right top" | "right" | "right bottom" | "bottom" | "left bottom" | "left" | "left top" | "centre"; fit?: "cover" | "contain" | "fill"; background?: string; diff --git a/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss b/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss index 1a8c2f7..c3f3e46 100644 --- a/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss +++ b/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.module.scss @@ -17,7 +17,9 @@ .remainingIndicator { border-radius: 100rem; + box-sizing: border-box; display: flex; + flex-grow: 1; align-items: center; background-color: rgb(var(--uk-sys-color-secondary-container)); position: relative; @@ -26,13 +28,11 @@ .stopIndicator { position: absolute; - right: 0; + right: calc(calc(var(--thickness, 0.25rem) - 0.25rem) / 2); top: 50%; transform: translateY(-50%); height: 0.25rem; width: 0.25rem; border-radius: 100rem; - background-color: rgb(var(--uk-sys-color-primary)); - margin-left: auto; - margin-right: calc(calc(var(--thickness) - 0.5rem) / 2); + background-color: rgb(var(--uk-sys-color-primary), 0.5); } diff --git a/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.tsx b/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.tsx index b6abfe7..4abe123 100644 --- a/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.tsx +++ b/uikit-solid/src/components/linearProgressIndicator/UKLinearProgressIndicator.tsx @@ -1,9 +1,9 @@ import clsx from "clsx"; -import { type Component, createEffect, createSignal } from "solid-js"; +import {type Component, createEffect, createSignal} from "solid-js"; import styles from "./UKLinearProgressIndicator.module.scss"; -const UKLinearProgressIndicator: Component<{ start: number; stop: number; value: number; thickness?: number; class?: string }> = (props) => { - const [percentageCompletion, setPercentageCompletion] = createSignal(45); +const UKLinearProgressIndicator: Component<{start: number; stop: number; value: number; thickness?: number; class?: string, showStopIndicator?: boolean}> = (props) => { + const [ percentageCompletion, setPercentageCompletion ] = createSignal(45); createEffect(() => { const range = props.stop - props.start; @@ -17,16 +17,16 @@ const UKLinearProgressIndicator: Component<{ start: number; stop: number; value: }); return ( -
-
+
+
5 ? "0.25rem" : "0px"})`, - "margin-left": percentageCompletion() <= 5 ? "0px" : "0.25rem", + // width: `calc(${100 - percentageCompletion()}% - ${percentageCompletion() > 5 ? "0.25rem" : "0px"})`, + // "margin-left": percentageCompletion() <= 5 ? "0px" : percentageCompletion() >= 99 ? "0px" : "0.25rem", }} > -
+ {props.showStopIndicator &&
}
); diff --git a/uikit-solid/src/components/splitButtons/UKSplitButton.tsx b/uikit-solid/src/components/splitButtons/UKSplitButton.tsx index bac7682..511c707 100644 --- a/uikit-solid/src/components/splitButtons/UKSplitButton.tsx +++ b/uikit-solid/src/components/splitButtons/UKSplitButton.tsx @@ -1,39 +1,38 @@ -import { createSignal, type Component } from "solid-js"; +import {createSignal, type Component} from "solid-js"; import UKButton from "../button/UKButton"; -import type { SplitButtonColor } from "./lib/color"; -import type { SplitButtonSize } from "./lib/size"; -import type { MenuItems } from "../menu/lib/items"; +import type {SplitButtonColor} from "./lib/color"; +import type {SplitButtonSize} from "./lib/size"; import UKIconButton from "../iconButton/UKIconButton"; import styles from "./UKSplitButton.module.scss" const UKSplitButton: Component<{ - children: string; - class?: string; - color?: SplitButtonColor; - disabled?: boolean; - onClick: (event: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }) => void; - size?: SplitButtonSize; - // TODO: create a type for this - dropDownItems: MenuItems; + children: string; + class?: string; + color?: SplitButtonColor; + disabled?: boolean; + onClick: (event: MouseEvent & {currentTarget: HTMLButtonElement; target: Element}) => void; + size?: SplitButtonSize; + // TODO: create a type for this + dropDownItems: unknown; }> = (props) => { - const [dropdownSelected, setDropdownSelected] = createSignal(false); + const [ dropdownSelected, setDropdownSelected ] = createSignal(false); - return ( -
- - {props.children} - - { - setDropdownSelected(!dropdownSelected()); - }} - /> - {/* TODO: add a menu here! */} -
- ); + return ( +
+ + {props.children} + + { + setDropdownSelected(!dropdownSelected()); + }} + /> + {/* TODO: add a menu here! */} +
+ ); }; export default UKSplitButton; -- 2.51.2