diff --git a/package.json b/package.json index c99b6fc..9edd949 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "stratum", "private": true, - "version": "0.1.7", + "version": "0.1.8", "type": "module", "scripts": { "dev": "vite", @@ -21,6 +21,7 @@ "tinykeys": "^3.0.0" }, "devDependencies": { + "@types/node": "^24.3.0", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@types/debug": "^4.1.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92ee99f..e064976 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: '@types/debug': specifier: ^4.1.12 version: 4.1.12 + '@types/node': + specifier: ^24.3.0 + version: 24.11.0 '@types/react': specifier: ^19.1.8 version: 19.2.9 @@ -47,7 +50,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.7.0(vite@7.3.1(sass@1.97.2)) + version: 4.7.0(vite@7.3.1(@types/node@24.11.0)(sass@1.97.2)) sass: specifier: ^1.80.7 version: 1.97.2 @@ -56,7 +59,7 @@ importers: version: 5.8.3 vite: specifier: ^7.0.4 - version: 7.3.1(sass@1.97.2) + version: 7.3.1(@types/node@24.11.0)(sass@1.97.2) packages: @@ -626,6 +629,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@24.11.0': + resolution: {integrity: sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -807,6 +813,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -1311,6 +1320,10 @@ snapshots: '@types/ms@2.1.0': {} + '@types/node@24.11.0': + dependencies: + undici-types: 7.16.0 + '@types/react-dom@19.2.3(@types/react@19.2.9)': dependencies: '@types/react': 19.2.9 @@ -1319,7 +1332,7 @@ snapshots: dependencies: csstype: 3.2.3 - '@vitejs/plugin-react@4.7.0(vite@7.3.1(sass@1.97.2))': + '@vitejs/plugin-react@4.7.0(vite@7.3.1(@types/node@24.11.0)(sass@1.97.2))': dependencies: '@babel/core': 7.28.6 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) @@ -1327,7 +1340,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.3.1(sass@1.97.2) + vite: 7.3.1(@types/node@24.11.0)(sass@1.97.2) transitivePeerDependencies: - supports-color @@ -1504,6 +1517,8 @@ snapshots: typescript@5.8.3: {} + undici-types@7.16.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 @@ -1514,7 +1529,7 @@ snapshots: dependencies: react: 19.2.3 - vite@7.3.1(sass@1.97.2): + vite@7.3.1(@types/node@24.11.0)(sass@1.97.2): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -1523,6 +1538,7 @@ snapshots: rollup: 4.55.2 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 24.11.0 fsevents: 2.3.3 sass: 1.97.2 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1a151b5..182f592 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3632,7 +3632,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stratum" -version = "0.1.7" +version = "0.1.8" dependencies = [ "flate2", "image", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6e76862..f1d9b72 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stratum" -version = "0.1.7" +version = "0.1.8" description = "Stratum File Manager" authors = ["emy"] edition = "2021" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 76e9053..5ae82fb 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -5,8 +5,12 @@ "windows": ["main"], "permissions": [ "core:default", + "core:window:allow-close", + "core:window:allow-minimize", "core:window:allow-set-size", + "core:window:allow-start-dragging", "core:window:allow-set-title", + "core:window:allow-toggle-maximize", "core:window:allow-destroy", "dialog:default", { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index b692c33..f591ba8 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,11 +1,11 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "Stratum", - "version": "0.1.7", - "identifier": "com.emy.stratum", - "build": { - "beforeDevCommand": "pnpm dev", - "devUrl": "http://localhost:1421", +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Stratum", + "version": "0.1.8", + "identifier": "com.emy.stratum", + "build": { + "beforeDevCommand": "pnpm dev", + "devUrl": "http://127.0.0.1:1420", "beforeBuildCommand": "pnpm build", "frontendDist": "../dist" }, @@ -14,12 +14,14 @@ { "title": "Stratum", "width": 1200, - "height": 760, - "minWidth": 900, - "minHeight": 600, - "dragDropEnabled": false, - "backgroundColor": "#0a0b10" - } + "height": 760, + "minWidth": 900, + "minHeight": 600, + "dragDropEnabled": false, + "decorations": false, + "shadow": true, + "backgroundColor": "#0a0b10" + } ], "security": { "csp": null, diff --git a/src/App.tsx b/src/App.tsx index a08925e..e5e9b69 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ // App shell wiring: composes state hooks, layout blocks, and overlays. import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { AppOverlays, AppShellLayout } from "@/components"; +import { AppOverlays, AppShellLayout, AppWindowFrame } from "@/components"; import { useAppCommands, useAppContextMenuSection, @@ -435,13 +435,12 @@ const App = () => { }, settings: { confirmClose: settings.confirmClose, - accentTheme: settings.accentTheme, - ambientBackground: settings.ambientBackground, - blurOverlays: settings.blurOverlays, - gridRounded: settings.gridRounded, - gridCentered: settings.gridCentered, - compactMode: settings.compactMode, - }, + accentTheme: settings.accentTheme, + ambientBackground: settings.ambientBackground, + blurOverlays: settings.blurOverlays, + gridRounded: settings.gridRounded, + gridCentered: settings.gridCentered, + }, view: { activeTabId, activeTabPath, @@ -698,7 +697,7 @@ const App = () => { scrollRestoreTop, scrollRequest, smoothScroll: settings.smoothScroll, - compactMode: settings.compactMode, + pendingDeletePaths: fileManager.pendingDeletePaths, sortState, canGoUp, }, @@ -837,9 +836,9 @@ const App = () => { return (
- { }} /> +
); }; diff --git a/src/components/app/AppOverlays.tsx b/src/components/app/AppOverlays.tsx index 3e82c98..f3f10d5 100644 --- a/src/components/app/AppOverlays.tsx +++ b/src/components/app/AppOverlays.tsx @@ -24,7 +24,7 @@ const LazyQuickPreviewOverlay = lazy(async () => { return { default: module.QuickPreviewOverlay }; }); -const useDeferredMount = (open: boolean) => { +const useDeferredMount = (open: boolean, prewarmOnIdle = false) => { const [mounted, setMounted] = useState(open); useEffect(() => { @@ -33,6 +33,46 @@ const useDeferredMount = (open: boolean) => { } }, [open]); + useEffect(() => { + if (mounted || open || !prewarmOnIdle) return; + if (typeof window === "undefined") return; + + const idleWindow = window as Window & { + requestIdleCallback?: ( + callback: IdleRequestCallback, + options?: IdleRequestOptions, + ) => number; + cancelIdleCallback?: (handle: number) => void; + }; + let timeoutId: number | null = null; + let idleId: number | null = null; + + const prewarm = () => { + setMounted(true); + }; + + // Warm hidden heavyweight overlays after the first paint so the first open feels instant. + if (typeof idleWindow.requestIdleCallback === "function") { + idleId = idleWindow.requestIdleCallback(() => { + prewarm(); + }, { timeout: 1200 }); + } else { + timeoutId = window.setTimeout(prewarm, 280); + } + + return () => { + if (timeoutId != null) { + window.clearTimeout(timeoutId); + } + if ( + idleId != null && + typeof idleWindow.cancelIdleCallback === "function" + ) { + idleWindow.cancelIdleCallback(idleId); + } + }; + }, [mounted, open, prewarmOnIdle]); + return mounted; }; @@ -52,8 +92,8 @@ export const AppOverlays = ({ settings, }: AppOverlaysProps) => { // Load heavyweight overlays on first use, then keep them mounted. - const conversionMounted = useDeferredMount(conversion.open); - const settingsMounted = useDeferredMount(settings.open); + const conversionMounted = useDeferredMount(conversion.open, true); + const settingsMounted = useDeferredMount(settings.open, true); const quickPreviewMounted = useDeferredMount(quickPreview.open); return ( diff --git a/src/components/app/AppWindowFrame.tsx b/src/components/app/AppWindowFrame.tsx new file mode 100644 index 0000000..0d0bc96 --- /dev/null +++ b/src/components/app/AppWindowFrame.tsx @@ -0,0 +1,4 @@ +// Global window chrome that stays visible above fullscreen overlay backdrops. +export const AppWindowFrame = () => { + return