diff --git a/applications/uk.ewsgit.files/backend/index.ts b/applications/uk.ewsgit.files/backend/index.ts --- a/applications/uk.ewsgit.files/backend/index.ts +++ b/applications/uk.ewsgit.files/backend/index.ts @@ -447,7 +447,7 @@ ? await instance.sys.image.serveImage(opt.ctx.userId, resolvedPath, { resize: { fit: "cover", position: "centre", - dimensions: { width: opt.input.thumbnailSize, height: opt.input.thumbnailSize }, + dimensions: { width: opt.input.thumbnailSize, height: Math.floor((opt.input.thumbnailSize / 16) * 9) }, changeFormatTo: "webp", }, }) diff --git a/applications/uk.ewsgit.files/electron/package.json b/applications/uk.ewsgit.files/electron/package.json --- a/applications/uk.ewsgit.files/electron/package.json +++ b/applications/uk.ewsgit.files/electron/package.json @@ -16,9 +16,9 @@ "@types/node": "latest", "electron": "^41.3.0" }, "trustedDependencies": [ + // "node-fuse-bindings" "fast-folder-size", - "electron", - "node-fuse-bindings" + "electron" ], "peerDependencies": { "typescript": "^5" @@ -30,8 +30,8 @@ "make": "tsc && electron-forge make", "publish": "electron-forge publish" }, "dependencies": { - "node-fuse-bindings": "git+https://github.com/asivery/node-fuse-bindings.git", + // "node-fuse-bindings": "git+https://github.com/asivery/node-fuse-bindings.git", "sharp": "^0.34.5", "fast-folder-size": "^2.4.0" } -} +} \ No newline at end of file diff --git a/applications/uk.ewsgit.files/web/App.tsx b/applications/uk.ewsgit.files/web/App.tsx --- a/applications/uk.ewsgit.files/web/App.tsx +++ b/applications/uk.ewsgit.files/web/App.tsx @@ -1,5 +1,5 @@ import {Route} from "@solidjs/router"; -import {type Accessor, type Component, createSignal, lazy, onMount, type Setter} from "solid-js"; +import {type Accessor, type Component, createSignal, lazy, onCleanup, onMount, type Setter} 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"; @@ -28,6 +28,7 @@ selectedItems: string[]; lastSelectionTime: number; viewId: number; isLoading: boolean; + isRenaming: boolean; } interface AppContextType { @@ -62,8 +63,8 @@ const App: Component = () => { const [ hasLoaded, setHasLoaded ] = createSignal(false); const [ userPreferences, setUserPreferences ] = createStore({ showWelcome: true, - homePath: "remote:/users/1/fs/", - pinnedDirectories: [ "remote:/users/1/fs/" ], + homePath: "remote:/users", + pinnedDirectories: [ "remote:/users" ], viewType: "details", showPreview: false, zoomPercentage: 1, @@ -75,6 +76,7 @@ selectedItems: [], lastSelectionTime: -1, viewId: 0, isLoading: true, + isRenaming: false }); const [ taskStatus, setTaskStatus ] = createSignal([]) diff --git a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.module.scss b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.module.scss --- a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.module.scss +++ b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.module.scss @@ -33,8 +33,14 @@ opacity: 1; } } +.divider { + height: 1rem !important; +} + .sizeControls { display: flex; flex-direction: row; + align-items: center; + justify-content: center; gap: 0.25rem; } diff --git a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx --- a/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx +++ b/applications/uk.ewsgit.files/web/layout/components/StatusBar/StatusBar.tsx @@ -9,6 +9,8 @@ import {type Component, createEffect, createSignal, useContext} from "solid-js"; import {AppContext} from "../../../appContext.ts"; import humanReadableSize from "../../../lib/humanReadableSize.ts"; import styles from "./StatusBar.module.scss"; +import {MAX_ZOOM_VALUE, MIN_ZOOM_VALUE} from "../../../lib/constants.ts"; +import UKDivider from "@onlineworkspace/uikit-solid/src/components/divider/UKDivider.jsx"; const StatusBar: Component = () => { const appContext = useContext(AppContext); @@ -94,13 +96,17 @@ {appContext!.tasks()[ 0 ]?.message.replaceAll("%c", appContext!.tasks()[ 0 ].current.toString()).replaceAll("%m", appContext!.tasks()[ 0 ].max.toString())} +
+ + {((appContext?.userPreferences.zoomPercentage || 1) * 100) + "%"} + appContext?.setUserPreferences("zoomPercentage", Math.max(appContext.userPreferences.zoomPercentage - 0.2, 0.2))} + onClick={() => appContext?.setUserPreferences("zoomPercentage", Math.max(appContext.userPreferences.zoomPercentage - 0.2, MIN_ZOOM_VALUE))} /> appContext?.setUserPreferences("zoomPercentage", appContext.userPreferences.zoomPercentage + 0.2)} + onClick={() => appContext?.setUserPreferences("zoomPercentage", Math.min(appContext.userPreferences.zoomPercentage + 0.2, MAX_ZOOM_VALUE))} />
diff --git a/applications/uk.ewsgit.files/web/lib/constants.ts b/applications/uk.ewsgit.files/web/lib/constants.ts new file mode 100644 --- /dev/null +++ b/applications/uk.ewsgit.files/web/lib/constants.ts @@ -0,0 +1,2 @@ +export const MAX_ZOOM_VALUE: number = 1.5; +export const MIN_ZOOM_VALUE: number = 0.75; diff --git a/applications/uk.ewsgit.files/web/pages/dir/View.tsx b/applications/uk.ewsgit.files/web/pages/dir/View.tsx --- a/applications/uk.ewsgit.files/web/pages/dir/View.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/View.tsx @@ -1,7 +1,7 @@ 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 {type Component, createEffect, createSignal, Match, Switch, useContext} from "solid-js"; +import {type Component, createEffect, createSignal, Match, onCleanup, onMount, Switch, useContext} from "solid-js"; import {createStore} from "solid-js/store"; import type {Task} from "../../App.tsx"; import {AppContext} from "../../appContext.ts"; @@ -24,7 +24,6 @@ }>({ origin: undefined, size: undefined, }); - const [ lastClickTime, setLastClickTime ] = createSignal(-1); const [ errorMessage, setErrorMessage ] = createSignal(undefined); let navigationCounter = 0; @@ -36,6 +35,10 @@ } appContext?.userPreferences.viewType; appContext?.userPreferences.zoomPercentage; + + appContext?.setViewState("isRenaming", false) + appContext?.setViewState("selectedItems", []) + navigationCounter++ const currentNavigationCount = navigationCounter; @@ -124,6 +127,27 @@ setErrorMessage(newItems.status); } }); + const onKeyDown = (e: KeyboardEvent) => { + e.preventDefault(); + + if (e.key === "F2") { + appContext?.setViewState("isRenaming", true) + } + + if (e.key === "Escape") { + appContext?.setViewState("isRenaming", false) + appContext?.setViewState("selectedItems", []) + } + } + + onMount(async () => { + window.addEventListener("keydown", onKeyDown) + }); + + onCleanup(() => { + window.removeEventListener("keydown", onKeyDown) + }) + return ( <> {errorMessage() ? ( @@ -155,98 +179,71 @@ class={styles.root} onMouseDown={(downEvent) => { if (appContext?.userPreferences.viewType === "gallery") return; - if (lastClickTime() > Date.now() - 250) { - setLastClickTime(-1); + const target = downEvent.target as HTMLElement; + const itemPath = target.closest("[data-fs-item-path]")?.getAttribute("data-fs-item-path"); + const currentSelected = appContext?.viewState.selectedItems || []; - if (appContext?.viewState.selectedItems.length === 0) { - appContext!.setViewState( - "selectedItems", - appContext.viewState.viewItems.map((i) => i.path), - ); - } else { - appContext!.setViewState("selectedItems", []); - } + if (itemPath) { + const isSelected = currentSelected.includes(itemPath); + const newSelection = isSelected + ? currentSelected.filter(path => path !== itemPath) + : [ ...currentSelected, itemPath ]; + + appContext?.setViewState("selectedItems", newSelection); + } else { + appContext?.setViewState("selectedItems", []); } - setLastClickTime(Date.now()); + const originX = downEvent.clientX; + const originY = downEvent.clientY; + document.body.style.userSelect = "none"; - setDragSelectRegion("origin", {x: downEvent.clientX, y: downEvent.clientY}); - setDragSelectRegion("transOrigin", {x: downEvent.clientX, y: downEvent.clientY}); + setDragSelectRegion("origin", {x: originX, y: originY}); const currentTarget = downEvent.currentTarget as HTMLDivElement; - const currentTargetBounds = currentTarget.getBoundingClientRect(); - const selectableItems = currentTarget.querySelectorAll("[data-fs-item-path]"); + const bounds = currentTarget.getBoundingClientRect(); + const selectableItems = Array.from(currentTarget.querySelectorAll("[data-fs-item-path]")); - function mouseUp() { - document.body.style.userSelect = "unset"; + 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); - setDragSelectRegion("origin", undefined); - setDragSelectRegion("size", undefined); - setDragSelectRegion("transOrigin", undefined); + const left = Math.min(originX, mouseX); + const top = Math.min(originY, mouseY); + const width = Math.abs(mouseX - originX); + const height = Math.abs(mouseY - originY); - document.removeEventListener("mouseup", mouseUp); - document.removeEventListener("mousemove", mouseMove); - } + setDragSelectRegion("transOrigin", {x: left, y: top}); + setDragSelectRegion("size", {x: width, y: height}); - let itemInRegionCalculationTimeout: NodeJS.Timeout | undefined; - - function itemInRegionCalculation() { - appContext?.setViewState("selectedItems", []); + const boxRight = left + width; + const boxBottom = top + height; + const newlySelected: string[] = []; for (const item of selectableItems) { - const boundingRect = item.getBoundingClientRect(); - - 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)}; - - const tl2 = {x: boundingRect.left, y: boundingRect.top}; - const br2 = {x: boundingRect.right, y: boundingRect.bottom}; - - if (tl1.x > br2.x || tl2.x > br1.x) continue; + const itemRect = item.getBoundingClientRect(); + const path = item.getAttribute("data-fs-item-path"); - if (tl1.y > br2.y || tl2.y > br1.y) continue; + const isIntersecting = !( + itemRect.left > boxRight || + itemRect.right < left || + itemRect.top > boxBottom || + itemRect.bottom < top + ); - 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 (isIntersecting && path) newlySelected.push(path); } + + appContext?.setViewState("selectedItems", newlySelected); } - function mouseMove(e: MouseEvent) { - if (!dragSelectRegion.origin) return; - if (!dragSelectRegion.transOrigin) return; - - const mouseX = Math.min(Math.max(e.clientX, currentTargetBounds.left), currentTargetBounds.right); - const mouseY = Math.min(Math.max(e.clientY, currentTargetBounds.top), currentTargetBounds.bottom); - - let sizeX = 0; - let sizeY = 0; - - sizeX = mouseX - dragSelectRegion.origin.x; - sizeY = mouseY - dragSelectRegion.origin.y; - - if (mouseX < dragSelectRegion.origin.x) { - setDragSelectRegion("transOrigin", {x: mouseX, y: dragSelectRegion.transOrigin.y}); - sizeX = dragSelectRegion.origin.x - mouseX; - } - - if (mouseY < dragSelectRegion.origin.y) { - setDragSelectRegion("transOrigin", {x: dragSelectRegion.transOrigin.x, y: mouseY}); - sizeY = dragSelectRegion.origin.y - mouseY; - } - - setDragSelectRegion("size", { - x: sizeX, - y: sizeY, - }); - - if (itemInRegionCalculationTimeout) clearTimeout(itemInRegionCalculationTimeout); - - itemInRegionCalculationTimeout = setTimeout(() => { - itemInRegionCalculation(); - }, 2); + 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); 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 --- a/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/components/DetailsView/DetailsView.tsx @@ -87,9 +87,12 @@ {iconForItemType(item.type)} )} - - {browserPath.basename(item.path)} - + {appContext?.viewState.isRenaming && appContext.viewState.selectedItems[ 0 ] === item.path ? +
Hello Renaming World!
+ : + {browserPath.basename(item.path)} + + } 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 --- 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 @@ -1,23 +1,79 @@ .root { - --grid-size: 12rem; + --grid-size: calc(calc(128px * var(--zoom-percentage, 1)) + 0.5rem); display: grid; grid-template-columns: repeat(auto-fill, var(--grid-size)); - grid-auto-rows: var(--grid-size); - gap: 0.5rem; + gap: 1rem; padding: 1rem; } .item { display: flex; flex-direction: column; - gap: 0.5rem; + gap: 0.25rem; + padding: 0.25rem; + margin: 0; + background: transparent; + border: none; align-items: center; justify-content: center; animation: forwards fadeItemIn 200ms var(--uk-sys-motion-easing-standard-decelerate); + border-radius: 1.25rem; + cursor: pointer; + outline-style: solid; + outline-offset: 2px; + outline-width: 0; + outline-color: transparent; + transition: + outline-color var(--uk-sys-motion-duration-100) var(--uk-sys-motion-easing-standard-decelerate), + outline-width 0ms linear; + + border-width: 1px; + border-style: solid; + border-color: transparent; + color: rgb(var(--uk-sys-color-on-surface)); + + &.selected { + background-color: rgb(var(--uk-sys-color-surface)); + color: rgb(var(--uk-sys-color-primary)); + box-shadow: 0 0 1px rgb(var(--uk-sys-color-shadow)); + border-width: 1px; + border-style: solid; + border-color: rgb(var(--uk-sys-color-outline-variant)); + } + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 1.25rem; + + transition: background-color var(--uk-sys-motion-duration-100) var(--uk-sys-motion-easing-standard-normal); + } + + &:hover { + &::before { + background-color: rgb(var(--uk-sys-color-on-surface), var(--uk-sys-state-hover-state-layer-opacity)); + } + } + + &:active { + &::before { + background-color: rgb(var(--uk-sys-color-on-surface), var(--uk-sys-state-pressed-state-layer-opacity)); + } + } + + &:focus-visible { + outline-width: var(--uk-sys-state-focus-indicator-thickness); + outline-offset: var(--uk-sys-state-focus-indicator-outer-offset); + outline-color: rgb(var(--uk-sys-color-secondary)); + } .thumbnailIcon { - --icon-size: 6rem; + --icon-size: calc(6rem * var(--zoom-percentage, 1)); } &.itemHidden { @@ -26,10 +82,20 @@ animation: forwards fadeHiddenItemIn 200ms var(--uk-sys-motion-easing-standard-decelerate); } .itemThumbnail { - height: 128px; - aspect-ratio: 1 / 1; + width: calc(128px * var(--zoom-percentage, 1)); + aspect-ratio: 16 / 9; object-fit: cover; object-position: center; + border-radius: 1rem; + } + + .itemLabel { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + padding-left: 0.5rem; + padding-right: 0.5rem; } } 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 --- a/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.tsx +++ b/applications/uk.ewsgit.files/web/pages/dir/components/GridView/GridView.tsx @@ -8,20 +8,22 @@ import {AppContext} from "../../../../appContext.ts"; import iconForItemType from "../../iconForItemType.ts"; import onItemClick from "../../itemClick.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); return ( -
+
{(item, index) => { if (!appContext?.userPreferences.showHidden && item.hidden) return null; return ( - onItemClick(e, appContext!, index(), item, setSearchParams)} onMouseDown={(e) => { @@ -32,15 +34,16 @@ onDblClick={(e) => { e.preventDefault(); e.stopPropagation(); }} - color={appContext?.viewState.selectedItems.includes(item.path) ? "outlined" : "filled"} > {item.thumbnail !== undefined ? ( ) : ( {iconForItemType(item.type)} )} - {browserPath.basename(item.path)} - + + {browserPath.basename(item.path)} + + ); }} diff --git a/applications/uk.ewsgit.ghostty/manifest.json b/applications/uk.ewsgit.ghostty/manifest.json --- a/applications/uk.ewsgit.ghostty/manifest.json +++ b/applications/uk.ewsgit.ghostty/manifest.json @@ -6,11 +6,15 @@ "type": "image", "value": "./icon.png" }, "bannerImage": "./banner.svg", - "description": "The Ghostty Terminal for Workspaces", + "description": "The Ghostty Terminal for Workspaces (Mostly a Placeholder)", "authors": [ { "name": "Ewsgit", "link": "" + }, + { + "name": "The Ghostty Developers", + "link": "" } ], "license": "MIT", @@ -23,4 +27,4 @@ "bun": { "path": "./backend/index.ts" } } -} +} \ No newline at end of file diff --git a/applications/uk.ewsgit.settings/manifest.json b/applications/uk.ewsgit.settings/manifest.json --- a/applications/uk.ewsgit.settings/manifest.json +++ b/applications/uk.ewsgit.settings/manifest.json @@ -6,7 +6,7 @@ "type": "icon", "value": "./web/node_modules/@material-symbols/svg-700/outlined/settings.svg" }, "bannerImage": "./settings_banner.svg", - "description": "The Workspaces Settings Manager", + "description": "The Online Workspace Settings Manager", "authors": [ { "name": "Ewsgit", @@ -23,4 +23,4 @@ "bun": { "path": "./backend/index.ts" } } -} +} \ No newline at end of file