diff --git a/index.html b/index.html index 5267eb2..a44e629 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ - + @@ -70,8 +70,6 @@ ] } - - diff --git a/src/components/actions/Actions.tsx b/src/components/actions/Actions.tsx index 7d49428..a7db7ef 100644 --- a/src/components/actions/Actions.tsx +++ b/src/components/actions/Actions.tsx @@ -9,7 +9,7 @@ export interface ActionProps { label?: string; icon?: string | object; shortcut?: string[]; - onTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void; + onTrigger?: (event?: Event, triggerParams?: unknown, ...args: unknown[]) => void; children?: ReactNode; } @@ -40,8 +40,8 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi const options = {}; const shortcuts = {}; + let actionId = 0; const iterateOverChildren = (children: ReactNode): ReactNode => { - let actionId = 0; const newChildren = Children.map(children, (child) => { if (!isValidElement(child)) return child; @@ -49,25 +49,30 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi actionId++; const { label, shortcut, onTrigger } = child.props as ActionProps; + + const onTriggerOverride = (event: Event, ...args: unknown[]) => { + onAnyTrigger?.(event, triggerParams, ...args); + onTrigger?.(event, triggerParams, ...args); + }; + + // Register shortcut if (label != null && onTrigger != null) { - options[actionId] = onTrigger; + options[actionId] = onTriggerOverride; if (shortcut != null) shortcuts[actionId] = shortcut; } - if (isListener) { + // Prevent listener from rendering + if (isListener) return iterateOverChildren((child.props as ActionProps).children); - } return cloneElement(child, { ...child.props, actionId, children: iterateOverChildren((child.props as ActionProps).children), - onTrigger: (event, ...args) => { - onAnyTrigger?.(event, triggerParams, ...args); - onTrigger?.(event, triggerParams, ...args); - } + onTrigger: onTriggerOverride + } as ActionProps); }); diff --git a/src/components/desktop/Desktop.module.css b/src/components/desktop/Desktop.module.css index b108094..d8da9df 100644 --- a/src/components/desktop/Desktop.module.css +++ b/src/components/desktop/Desktop.module.css @@ -18,9 +18,11 @@ } .Content { + --direction: column; + position: absolute; display: flex; - flex-direction: column; + flex-direction: var(--direction); flex-wrap: wrap; align-content: flex-start; justify-content: flex-start; diff --git a/src/components/desktop/Desktop.tsx b/src/components/desktop/Desktop.tsx index 0f8ced8..cd6e5a0 100644 --- a/src/components/desktop/Desktop.tsx +++ b/src/components/desktop/Desktop.tsx @@ -5,7 +5,7 @@ import styles from "./Desktop.module.css"; import { useEffect } from "react"; import { useWindowsManager } from "../../hooks/windows/windowsManagerContext"; import { useContextMenu } from "../../hooks/modals/contextMenu"; -import { FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config"; +import { FALLBACK_ICON_DIRECTION, FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config"; import { reloadViewport } from "../../features/_utils/browser.utils"; import { useVirtualRoot } from "../../hooks/virtual-drive/virtualRootContext"; import { DirectoryList } from "../apps/file-explorer/directory-list/DirectoryList"; @@ -13,7 +13,7 @@ import { APPS, APP_ICONS, APP_NAMES } from "../../config/apps.config"; import Vector2 from "../../features/math/vector2"; import { Actions } from "../actions/Actions"; import { ClickAction } from "../actions/actions/ClickAction"; -import { faArrowsRotate, faEye, faFolder, faPaintBrush, faTerminal, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faArrowsRotate, faCompress, faExpand, faEye, faFolder, faPaintBrush, faTerminal, faTrash } from "@fortawesome/free-solid-svg-icons"; import { ToggleAction } from "../actions/actions/ToggleAction"; import { DropdownAction } from "../actions/actions/DropdownAction"; import { RadioAction } from "../actions/actions/RadioAction"; @@ -34,6 +34,7 @@ export const Desktop = memo(() => { const virtualRoot = useVirtualRoot(); const [showIcons, setShowIcons] = useState(false); const [iconSize, setIconSize] = useState(FALLBACK_ICON_SIZE); + const [iconDirection, setIconDirection] = useState(FALLBACK_ICON_DIRECTION); const { openWindowedModal } = useWindowedModal(); const directory = virtualRoot.navigate("~/Desktop"); @@ -47,7 +48,15 @@ export const Desktop = memo(() => { }} options={[ { label: "Small icons" }, { label: "Medium icons" }, - { label: "Large icons" } + { label: "Large icons" }, + ]}/> + + { + const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop); + void settings.set("icon-direction", value); + }} options={[ + { label: "Align vertically" }, + { label: "Align horizontally" }, ]}/> { @@ -58,6 +67,25 @@ export const Desktop = memo(() => { { reloadViewport(); }}/> + { + if (windowsManager.isAnyFocused()) + return; + + if (!document.fullscreenElement) { + void document.body.requestFullscreen().catch((error) => { + console.error(error); + }); + } else { + void document.exitFullscreen().catch((error) => { + console.error(error); + }); + } + }} + /> { windowsManager.open("settings", { tab: TABS.APPEARANCE }); }}/> @@ -122,6 +150,10 @@ export const Desktop = memo(() => { if (isValidInteger(value)) setIconSize(parseInt(value)); }); + void settings.get("icon-direction", (value) => { + if (isValidInteger(value)) + setIconDirection(parseInt(value)); + }); }, [settingsManager]); const onError = () => { @@ -129,7 +161,7 @@ export const Desktop = memo(() => { void settings.set("wallpaper", FALLBACK_WALLPAPER); }; - const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 4; + const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 5; return (<> @@ -141,7 +173,8 @@ export const Desktop = memo(() => { directory={directory as VirtualFolder} className={styles.Content} style={{ - "--scale": `${iconScale}rem` + "--scale": `${iconScale}rem`, + "--direction": iconDirection == 1 ? "row" : "column" }} fileClassName={styles["Item"]} folderClassName={styles["Item"]} diff --git a/src/config/desktop.config.ts b/src/config/desktop.config.ts index e1a613d..e56cd52 100644 --- a/src/config/desktop.config.ts +++ b/src/config/desktop.config.ts @@ -11,4 +11,5 @@ export const WALLPAPERS = [ ]; export const FALLBACK_WALLPAPER = WALLPAPERS[6]; -export const FALLBACK_ICON_SIZE = 1; \ No newline at end of file +export const FALLBACK_ICON_SIZE = 1; +export const FALLBACK_ICON_DIRECTION = 0; // 0: vertical, 1: horizontal \ No newline at end of file diff --git a/src/features/virtual-drive/root/defaultData.ts b/src/features/virtual-drive/root/defaultData.ts index 6c23519..a4505c5 100644 --- a/src/features/virtual-drive/root/defaultData.ts +++ b/src/features/virtual-drive/root/defaultData.ts @@ -79,11 +79,18 @@ export function loadDefaultData(virtualRoot: VirtualRoot) { }); }); - // Create files and folders based on repository tree + loadTree(virtualRoot); +} + +// Create files and folders based on repository tree +function loadTree(virtualRoot: VirtualRoot) { + const excludedFiles = [ + "/public/config/tree.json" + ]; + void fetch("/config/tree.json").then((response) => response.json() ).then(({ files, folders }: { files: string[], folders: string[] }) => { - folders.forEach((folderPath) => { const lastSlashIndex = folderPath.lastIndexOf("/"); @@ -100,11 +107,12 @@ export function loadDefaultData(virtualRoot: VirtualRoot) { }); files.forEach((filePath) => { + if (excludedFiles.includes(filePath)) + return; + const lastSlashIndex = filePath.lastIndexOf("/"); const callback = (virtualFile: VirtualFile) => { - console.log(virtualFile.absolutePath); - const virtualPath = virtualFile.absolutePath; if (virtualPath.startsWith("/public/")) { virtualFile.setSource(virtualPath.replace(/^\/public\//, "/")); @@ -126,6 +134,6 @@ export function loadDefaultData(virtualRoot: VirtualRoot) { parentFolder.createFile(name, extension, callback); }); }).catch(() => { - console.warn("Failed to fetch repository tree. Make sure the tree data is valid and up-to-date using 'npm run fetch'."); + console.warn("Failed to load repository tree. Make sure the tree data is valid and up-to-date using 'npm run fetch'."); }); } \ No newline at end of file diff --git a/src/features/windows/windowsManager.ts b/src/features/windows/windowsManager.ts index e948f08..c5e9261 100644 --- a/src/features/windows/windowsManager.ts +++ b/src/features/windows/windowsManager.ts @@ -136,6 +136,17 @@ export default class WindowsManager { return this.windows[windowId].isFocused; } + isAnyFocused() { + let anyFocused = false; + + Object.values(this.windows).forEach((window) => { + if (window.isFocused) + return anyFocused = true; + }); + + return anyFocused; + } + /** * @param minimized - Leave as undefined to toggle the window's minimization state */ diff --git a/src/hooks/_utils/keyboard.ts b/src/hooks/_utils/keyboard.ts index 6e09cc0..411cdbc 100644 --- a/src/hooks/_utils/keyboard.ts +++ b/src/hooks/_utils/keyboard.ts @@ -28,6 +28,9 @@ interface UseShortcutsParams { useCategories?: boolean, } +/** + * TO DO: rewrite to use a global shortcuts manager instead, to allow certain shortcuts to be prioritized and prevent conflicts + */ export function useShortcuts({ options, shortcuts, useCategories = true }: UseShortcutsParams) { const [activeKeys, setActiveKeys] = useState([]); @@ -37,10 +40,6 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh const keys = [...activeKeys]; - // Prevent alt-tabbing from messing with alt key registration - if (keys.includes("Alt") && !event.altKey) - removeFromArray("Alt", keys); - const checkGroup = (group: Record, category?: string) => { for (const [name, shortcut] of Object.entries(group)) { let active = true; @@ -60,9 +59,9 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh continue; if (category != null) { - (options?.[category]?.[name] as Function)?.(); + (options?.[category]?.[name] as Function)?.(event); } else { - (options?.[name] as Function)?.(); + (options?.[name] as Function)?.(event); } } }; @@ -78,6 +77,18 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh setActiveKeys(keys); }, [activeKeys, options, shortcuts, useCategories]); + useEffect(() => { + const onBlur = () => { + setActiveKeys([]); + }; + + document.addEventListener("blur", onBlur); + + return () => { + document.removeEventListener("blur", onBlur); + }; + }, []); + const onKeyDown = (event: KeyboardEvent) => { const isRepeated = activeKeys.includes(event.key); checkShortcuts(event, isRepeated); diff --git a/src/tools/stage.ts b/src/tools/stage.ts index 7b5d03e..b40e015 100644 --- a/src/tools/stage.ts +++ b/src/tools/stage.ts @@ -59,6 +59,23 @@ function generateCname() { return DOMAIN; } +function generateTemplate(html: string) { + const baseUrlRegex = /(?<=")https?:\/\/[a-z0-9-]+(\.)([a-zA-Z0-9-]+(\.))*[a-z]{2,3}\/(?=.*")/gi; + html = html.replaceAll(baseUrlRegex, BASE_URL); + + const commentsRegex = //g; + html = html.replaceAll(commentsRegex, ""); + + const emptyLinesRegex = /^\s*$\n/gm; + html = html.replaceAll(emptyLinesRegex, ""); + + const path = `${BUILD_DIR}/index.html`; + fs.writeFileSync(path, html, { flag: "w+" }); + console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); + + return html; +} + /** * To avoid GitHub pages rendering certain pages that are only defined by React router as a 404 page, * we copy the content of our index file to the 404 page, letting React router properly handle routing on every page @@ -69,6 +86,9 @@ function generate404Page(template: string) { console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); } +/** + * Add an HTML file for every app page so they can be properly crawled and indexed + */ function generateAppPages(template: string) { for (const [key, value] of Object.entries(APPS)) { const appId = value; @@ -120,9 +140,13 @@ try { }); console.log(`\n${ANSI.fg.yellow}Generating pages...${ANSI.reset}`); - const template = fs.readFileSync(PATHS.indexHtml, "utf-8"); + + const html = fs.readFileSync(PATHS.indexHtml, "utf-8"); + const template = generateTemplate(html); + generate404Page(template); generateAppPages(template); + console.log(`\n${ANSI.fg.green}✓ Staging complete${ANSI.reset}`); } catch (error) { console.error(error);