Migration to TypeScript - Stage 3

Improved safe typing
This commit is contained in:
Prozilla 2024-05-10 15:43:14 +02:00
parent ac322e08e0
commit 2ede410bec
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
64 changed files with 462 additions and 433 deletions

View file

@ -6,7 +6,7 @@ import react from "eslint-plugin-react";
export default tseslint.config( export default tseslint.config(
eslint.configs.recommended, eslint.configs.recommended,
// ...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.recommendedTypeChecked,
{ {
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
@ -53,7 +53,13 @@ export default tseslint.config(
} }
], ],
"comma-spacing": "off", "comma-spacing": "off",
"@typescript-eslint/comma-spacing": "error" "@typescript-eslint/comma-spacing": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_"
}
]
}, },
} }
); );

10
package-lock.json generated
View file

@ -36,6 +36,7 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/js": "^9.2.0", "@eslint/js": "^9.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/webpack-env": "^1.18.4", "@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0", "@typescript-eslint/parser": "^7.8.0",
@ -4623,6 +4624,15 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",

View file

@ -43,6 +43,7 @@
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/js": "^9.2.0", "@eslint/js": "^9.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/webpack-env": "^1.18.4", "@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0", "@typescript-eslint/parser": "^7.8.0",

View file

@ -8,11 +8,8 @@ import { SettingsManagerProvider } from "./hooks/settings/settingsManagerContext
import { ModalsView } from "./components/modals/ModalsView"; import { ModalsView } from "./components/modals/ModalsView";
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext"; import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext";
import { TrackingManager } from "./features/tracking/trackingManager";
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext"; import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext";
TrackingManager.initialize();
const App: FC = () => { const App: FC = () => {
useEffect(() => { useEffect(() => {
const onContextMenu = (event: Event) => { const onContextMenu = (event: Event) => {

View file

@ -1,4 +1,4 @@
import { Children, cloneElement, isValidElement, ReactElement, ReactNode } from "react"; import { Children, cloneElement, isValidElement, ReactElement, ReactNode, Ref } from "react";
import { useShortcuts } from "../../hooks/_utils/keyboard"; import { useShortcuts } from "../../hooks/_utils/keyboard";
import styles from "./Actions.module.css"; import styles from "./Actions.module.css";
import { useScreenBounds } from "../../hooks/_utils/screen"; import { useScreenBounds } from "../../hooks/_utils/screen";
@ -11,17 +11,17 @@ export const STYLES = {
export interface ActionProps { export interface ActionProps {
actionId?: string; actionId?: string;
label?: string; label?: string;
icon?: string|object; icon?: string | object;
shortcut?: string[]; shortcut?: string[];
onTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void; onTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
children?: ReactNode; children?: ReactNode;
} }
export interface ActionsProps { export interface ActionsProps {
className?: string; className?: string;
onAnyTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void; onAnyTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
children?: ReactNode; children?: ReactNode;
triggerParams?: any; triggerParams?: unknown;
avoidTaskbar?: boolean; avoidTaskbar?: boolean;
} }
@ -44,7 +44,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
const options = {}; const options = {};
const shortcuts = {}; const shortcuts = {};
const iterateOverChildren = (children: ReactNode) => { const iterateOverChildren = (children: ReactNode): ReactNode => {
let actionId = 0; let actionId = 0;
const newChildren = Children.map(children, (child) => { const newChildren = Children.map(children, (child) => {
if (!isValidElement(child)) if (!isValidElement(child))
@ -52,7 +52,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
actionId++; actionId++;
const { label, shortcut, onTrigger } = child.props; const { label, shortcut, onTrigger } = child.props as ActionProps;
if (label != null && onTrigger != null) { if (label != null && onTrigger != null) {
options[actionId] = onTrigger; options[actionId] = onTrigger;
@ -61,19 +61,18 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
} }
if (isListener) { if (isListener) {
iterateOverChildren(child.props.children); return iterateOverChildren((child.props as ActionProps).children);
return;
} }
return cloneElement(child, { return cloneElement(child, {
...child.props, ...child.props,
actionId, actionId,
children: iterateOverChildren(child.props.children), children: iterateOverChildren((child.props as ActionProps).children),
onTrigger: (event, ...args) => { onTrigger: (event, ...args) => {
onAnyTrigger?.(event, triggerParams, ...args); onAnyTrigger?.(event, triggerParams, ...args);
onTrigger?.(event, triggerParams, ...args); onTrigger?.(event, triggerParams, ...args);
} }
}); } as ActionProps);
}); });
return newChildren; return newChildren;
@ -82,7 +81,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
useShortcuts({ options, shortcuts, useCategories: false }); useShortcuts({ options, shortcuts, useCategories: false });
if (isListener) if (isListener)
return iterateOverChildren(children); return iterateOverChildren(children) as ReactElement;
const classNames = [styles.Container]; const classNames = [styles.Container];
if (className != null) if (className != null)
@ -94,7 +93,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
if (!initiated) if (!initiated)
classNames.push(styles.Uninitiated); classNames.push(styles.Uninitiated);
return <div ref={ref} className={classNames.join(" ")}> return <div ref={ref as Ref<HTMLDivElement>} className={classNames.join(" ")}>
{iterateOverChildren(children)} {iterateOverChildren(children)}
</div>; </div>;
} }

View file

@ -4,6 +4,7 @@ import { WindowProps } from "../../../windows/WindowView";
interface WebViewProps extends WindowProps { interface WebViewProps extends WindowProps {
source: string; source: string;
title: string;
} }
export const WebView: FC<WebViewProps> = forwardRef<HTMLIFrameElement>(({ source, focus, ...props }: WebViewProps, ref) => { export const WebView: FC<WebViewProps> = forwardRef<HTMLIFrameElement>(({ source, focus, ...props }: WebViewProps, ref) => {

View file

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react"; import { ChangeEventHandler, KeyboardEventHandler, useEffect, useRef, useState } from "react";
import styles from "./Browser.module.css"; import styles from "./Browser.module.css";
import { WebView } from "../_utils/web-view/WebView"; import { WebView } from "../_utils/web-view/WebView";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -15,10 +15,10 @@ interface BrowserProps extends WindowProps {
export function Browser({ startUrl, focus }: BrowserProps) { export function Browser({ startUrl, focus }: BrowserProps) {
const initialUrl = startUrl ?? HOME_URL; const initialUrl = startUrl ?? HOME_URL;
const [url, setUrl] = useState(initialUrl); const [url, setUrl] = useState<string>(initialUrl);
const [input, setInput] = useState(initialUrl); const [input, setInput] = useState(initialUrl);
const { history, pushState, stateIndex, undo, redo, undoAvailable, redoAvailable } = useHistory(initialUrl); const { history, pushState, stateIndex, undo, redo, undoAvailable, redoAvailable } = useHistory(initialUrl);
const ref = useRef(null); const ref = useRef<HTMLIFrameElement>(null);
useEffect(() => { useEffect(() => {
if (history.length === 0) if (history.length === 0)
@ -34,7 +34,7 @@ export function Browser({ startUrl, focus }: BrowserProps) {
ref.current.contentWindow.location.href = url; ref.current.contentWindow.location.href = url;
}; };
const updateUrl = (newUrl) => { const updateUrl = (newUrl: string) => {
if (url === newUrl) { if (url === newUrl) {
return reload(); return reload();
} }
@ -44,12 +44,12 @@ export function Browser({ startUrl, focus }: BrowserProps) {
pushState(newUrl); pushState(newUrl);
}; };
const onInputChange = (event) => { const onInputChange = (event: Event) => {
setInput(event.target.value); setInput((event.target as HTMLInputElement).value);
}; };
const onKeyDown = (event) => { const onKeyDown = (event: KeyboardEvent) => {
const value = event.target.value; const value = (event.target as HTMLInputElement).value;
if (event.key === "Enter" && value !== "") { if (event.key === "Enter" && value !== "") {
if (isValidUrl(value)) { if (isValidUrl(value)) {
@ -67,7 +67,7 @@ export function Browser({ startUrl, focus }: BrowserProps) {
title="Back" title="Back"
tabIndex={0} tabIndex={0}
className={styles["Icon-button"]} className={styles["Icon-button"]}
onClick={undo} onClick={() => { undo(); }}
disabled={!undoAvailable} disabled={!undoAvailable}
> >
<FontAwesomeIcon icon={faCaretLeft}/> <FontAwesomeIcon icon={faCaretLeft}/>
@ -76,7 +76,7 @@ export function Browser({ startUrl, focus }: BrowserProps) {
title="Forward" title="Forward"
tabIndex={0} tabIndex={0}
className={styles["Icon-button"]} className={styles["Icon-button"]}
onClick={redo} onClick={() => { redo(); }}
disabled={!redoAvailable} disabled={!redoAvailable}
> >
<FontAwesomeIcon icon={faCaretRight}/> <FontAwesomeIcon icon={faCaretRight}/>
@ -103,8 +103,8 @@ export function Browser({ startUrl, focus }: BrowserProps) {
aria-label="Search bar" aria-label="Search bar"
className={styles["Search-bar"]} className={styles["Search-bar"]}
tabIndex={0} tabIndex={0}
onChange={onInputChange} onChange={onInputChange as unknown as ChangeEventHandler}
onKeyDown={onKeyDown} onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
/> />
</div> </div>
<div className={styles["Bookmarks"]}> <div className={styles["Bookmarks"]}>

View file

@ -5,9 +5,9 @@ import { WindowProps } from "../../windows/WindowView";
export function Calculator({ active }: WindowProps) { export function Calculator({ active }: WindowProps) {
const [input, setInput] = useState("0"); const [input, setInput] = useState("0");
const [firstNumber, setFirstNumber] = useState(null); const [firstNumber, setFirstNumber] = useState<number>(null);
const [secondNumber, setSecondNumber] = useState(null); const [secondNumber, setSecondNumber] = useState<number>(null);
const [operation, setOperation] = useState(null); const [operation, setOperation] = useState<string>(null);
const [isIntermediate, setIsIntermediate] = useState(false); const [isIntermediate, setIsIntermediate] = useState(false);
const reset = useCallback(() => { const reset = useCallback(() => {
@ -17,11 +17,11 @@ export function Calculator({ active }: WindowProps) {
setOperation(null); setOperation(null);
}, []); }, []);
const addInput = useCallback((string) => { const addInput = useCallback((string: string) => {
let hasReset = false; let hasReset = false;
if (secondNumber != null) { if (secondNumber != null) {
if (isIntermediate) { if (isIntermediate) {
setFirstNumber(input); setFirstNumber(parseFloat(input));
setSecondNumber(null); setSecondNumber(null);
setInput(null); setInput(null);
} else { } else {
@ -54,12 +54,10 @@ export function Calculator({ active }: WindowProps) {
}, [input, isIntermediate, reset, secondNumber]); }, [input, isIntermediate, reset, secondNumber]);
const calculate = useCallback((intermediate = false) => { const calculate = useCallback((intermediate = false) => {
if (firstNumber == null) { if (firstNumber != null) {
setSecondNumber(parseFloat(input));
} else { const a = firstNumber;
setSecondNumber(input);
const a = parseFloat(firstNumber);
const b = parseFloat(input); const b = parseFloat(input);
let result = 0; let result = 0;
@ -84,11 +82,11 @@ export function Calculator({ active }: WindowProps) {
setIsIntermediate(intermediate); setIsIntermediate(intermediate);
}, [firstNumber, input, operation]); }, [firstNumber, input, operation]);
const changeOperation = useCallback((operation) => { const changeOperation = useCallback((operation: string) => {
if (firstNumber != null && secondNumber == null) { if (firstNumber != null && secondNumber == null) {
calculate(true); calculate(true);
} else { } else {
setFirstNumber(input); setFirstNumber(parseFloat(input));
setSecondNumber(null); setSecondNumber(null);
setInput(null); setInput(null);
} }
@ -97,7 +95,7 @@ export function Calculator({ active }: WindowProps) {
}, [calculate, firstNumber, input, secondNumber]); }, [calculate, firstNumber, input, secondNumber]);
useEffect(() => { useEffect(() => {
const onKeyDown = (event) => { const onKeyDown = (event: KeyboardEvent) => {
if (!active) if (!active)
return; return;

View file

@ -1,4 +1,4 @@
import { FC, useCallback, useEffect, useState } from "react"; import { ChangeEventHandler, FC, KeyboardEventHandler, useCallback, useEffect, useState } from "react";
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext"; import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
import styles from "./FileExplorer.module.css"; import styles from "./FileExplorer.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -40,10 +40,10 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const [currentDirectory, setCurrentDirectory] = useState<VirtualFolder>(virtualRoot.navigate(startPath ?? "~") as VirtualFolder); const [currentDirectory, setCurrentDirectory] = useState<VirtualFolder>(virtualRoot.navigate(startPath ?? "~") as VirtualFolder);
const [path, setPath] = useState(currentDirectory?.path ?? ""); const [path, setPath] = useState<string>(currentDirectory?.path ?? "");
const windowsManager = useWindowsManager(); const windowsManager = useWindowsManager();
const [showHidden] = useState(true); const [showHidden] = useState(true);
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory(currentDirectory.path); const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory<string>(currentDirectory.path);
const { openWindowedModal } = useWindowedModal(); const { openWindowedModal } = useWindowedModal();
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) => const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
@ -56,29 +56,29 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
} }
file.open(windowsManager); file.open(windowsManager);
}}/> }}/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => { <ClickAction label="Delete" icon={faTrash} onTrigger={(event, file: VirtualFile) => {
file.delete(); file.delete();
}}/> }}/>
<ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file) => { <ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file: VirtualFile) => {
openWindowedModal({ openWindowedModal({
title: `${file.id} ${TITLE_SEPARATOR} Properties`, title: `${file.id} ${TITLE_SEPARATOR} Properties`,
iconUrl: file.getIconUrl(), iconUrl: file.getIconUrl(),
size: new Vector2(400, 500), size: new Vector2(400, 500),
Modal: (props) => <FileProperties file={file} {...props}/> Modal: (props: object) => <FileProperties file={file} {...props}/>
}); });
}}/> }}/>
</Actions> </Actions>
}); });
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) => const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
<Actions {...props}> <Actions {...props}>
<ClickAction label="Open" onTrigger={(event, folder) => { <ClickAction label="Open" onTrigger={(event, folder: VirtualFolder & VirtualFolderLink) => {
changeDirectory(folder.linkedPath ?? folder.name); changeDirectory(folder.linkedPath ?? folder.name);
}}/> }}/>
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={APP_ICONS.TERMINAL} onTrigger={(event, folder) => { <ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={APP_ICONS.TERMINAL} onTrigger={(event, folder: VirtualFolder) => {
windowsManager.open(APPS.TERMINAL, { startPath: folder.path }); windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
}}/> }}/>
<Divider/> <Divider/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => { <ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder: VirtualFolder) => {
folder.delete(); folder.delete();
}}/> }}/>
</Actions> </Actions>
@ -91,7 +91,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
// } // }
// }); // });
const changeDirectory = useCallback((path, absolute = false) => { const changeDirectory = useCallback((path: string, absolute = false) => {
if (path == null) if (path == null)
return; return;
@ -119,12 +119,12 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
} }
}, [history, stateIndex, virtualRoot]); }, [history, stateIndex, virtualRoot]);
const onPathChange = (event) => { const onPathChange = (event: Event) => {
setPath(event.target.value); setPath((event.target as HTMLInputElement).value);
}; };
const onKeyDown = (event) => { const onKeyDown = (event: KeyboardEvent) => {
let value = event.target.value; let value = (event.target as HTMLInputElement).value;
if (event.key === "Enter") { if (event.key === "Enter") {
if (value === "") if (value === "")
@ -160,7 +160,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
title="Back" title="Back"
tabIndex={0} tabIndex={0}
className={styles["Icon-button"]} className={styles["Icon-button"]}
onClick={undo} onClick={() => { undo(); }}
disabled={!undoAvailable} disabled={!undoAvailable}
> >
<FontAwesomeIcon icon={faCaretLeft}/> <FontAwesomeIcon icon={faCaretLeft}/>
@ -169,7 +169,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
title="Forward" title="Forward"
tabIndex={0} tabIndex={0}
className={styles["Icon-button"]} className={styles["Icon-button"]}
onClick={redo} onClick={() => { redo(); }}
disabled={!redoAvailable} disabled={!redoAvailable}
> >
<FontAwesomeIcon icon={faCaretRight}/> <FontAwesomeIcon icon={faCaretRight}/>
@ -215,8 +215,8 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
aria-label="Path" aria-label="Path"
className={styles["Path-input"]} className={styles["Path-input"]}
tabIndex={0} tabIndex={0}
onChange={onPathChange} onChange={onPathChange as unknown as ChangeEventHandler}
onKeyDown={onKeyDown} onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
placeholder="Enter a path..." placeholder="Enter a path..."
/> />
<button title="Search" tabIndex={0} className={styles["Icon-button"]}> <button title="Search" tabIndex={0} className={styles["Icon-button"]}>
@ -241,7 +241,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
onOpenFile={(event, file) => { onOpenFile={(event, file) => {
(event as Event).preventDefault(); (event as Event).preventDefault();
if (isSelector) if (isSelector)
return onSelectionFinish?.(); return void onSelectionFinish?.();
const options: Record<string, string> = {}; const options: Record<string, string> = {};
if (file.extension === "md" || CODE_FORMATS.includes(file.extension)) if (file.extension === "md" || CODE_FORMATS.includes(file.extension))
options.mode = "view"; options.mode = "view";

View file

@ -1,9 +1,10 @@
import { ReactElement, useEffect, useRef, useState } from "react"; import { MouseEventHandler, ReactElement, useEffect, useRef, useState } from "react";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile"; import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder"; import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
import { Interactable } from "../../../_utils/interactable/Interactable"; import { Interactable } from "../../../_utils/interactable/Interactable";
import styles from "./DirectoryList.module.css"; import styles from "./DirectoryList.module.css";
import { ImagePreview } from "./ImagePreview"; import { ImagePreview } from "./ImagePreview";
import Vector2 from "../../../../features/math/vector2";
export interface OnSelectionChangeParams { export interface OnSelectionChangeParams {
files?: string[]; files?: string[];
@ -26,17 +27,17 @@ interface DirectoryListProps {
onOpenFolder?: FolderEventHandler; onOpenFolder?: FolderEventHandler;
allowMultiSelect?: boolean; allowMultiSelect?: boolean;
onSelectionChange?: (params: OnSelectionChangeParams) => void; onSelectionChange?: (params: OnSelectionChangeParams) => void;
[key: string]: any; [key: string]: unknown;
} }
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className, export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactElement { onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactElement {
const [selectedFolders, setSelectedFolders] = useState([]); const [selectedFolders, setSelectedFolders] = useState<string[]>([]);
const [selectedFiles, setSelectedFiles] = useState([]); const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
const ref = useRef(null); const ref = useRef(null);
const [rectSelectStart, setRectSelectStart] = useState(null); const [rectSelectStart, setRectSelectStart] = useState<Vector2>(null);
const [rectSelectEnd, setRectSelectEnd] = useState(null); const [rectSelectEnd, setRectSelectEnd] = useState<Vector2>(null);
useEffect(() => { useEffect(() => {
onSelectionChange?.({ files: selectedFiles, folders: selectedFolders, directory }); onSelectionChange?.({ files: selectedFiles, folders: selectedFolders, directory });
@ -47,14 +48,14 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
}, [directory]); }, [directory]);
useEffect(() => { useEffect(() => {
const onMoveRectSelect = (event) => { const onMoveRectSelect = (event: MouseEvent) => {
if (rectSelectStart == null) if (rectSelectStart == null)
return; return;
event.preventDefault(); event.preventDefault();
setRectSelectEnd({ x: event.clientX, y: event.clientY }); setRectSelectEnd({ x: event.clientX, y: event.clientY } as Vector2);
}; };
const onStopRectSelect = (event) => { const onStopRectSelect = (event: MouseEvent) => {
if (rectSelectStart == null || rectSelectEnd == null) { if (rectSelectStart == null || rectSelectEnd == null) {
setRectSelectStart(null); setRectSelectStart(null);
setRectSelectEnd(null); setRectSelectEnd(null);
@ -82,14 +83,14 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
setSelectedFolders([]); setSelectedFolders([]);
setSelectedFiles([]); setSelectedFiles([]);
}; };
const selectFolder = (folder, exclusive = false) => { const selectFolder = (folder: VirtualFolder, exclusive = false) => {
if (!allowMultiSelect) if (!allowMultiSelect)
exclusive = true; exclusive = true;
setSelectedFolders(exclusive ? [folder.id] : [...selectedFolders, folder.id]); setSelectedFolders(exclusive ? [folder.id] : [...selectedFolders, folder.id]);
if (exclusive) if (exclusive)
setSelectedFiles([]); setSelectedFiles([]);
}; };
const selectFile = (file, exclusive = false) => { const selectFile = (file: VirtualFile, exclusive = false) => {
if (!allowMultiSelect) if (!allowMultiSelect)
exclusive = true; exclusive = true;
setSelectedFiles(exclusive ? [file.id] : [...selectedFiles, file.id]); setSelectedFiles(exclusive ? [file.id] : [...selectedFiles, file.id]);
@ -97,12 +98,12 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
setSelectedFolders([]); setSelectedFolders([]);
}; };
const onStartRectSelect = (event) => { const onStartRectSelect = (event: MouseEvent) => {
setRectSelectStart({ x: event.clientX, y: event.clientY }); setRectSelectStart({ x: event.clientX, y: event.clientY } as Vector2);
}; };
const getRectSelectStyle = () => { const getRectSelectStyle = () => {
let x, y, width, height = null; let x: number, y: number, width: number, height: number = null;
const containerRect = ref.current?.getBoundingClientRect(); const containerRect = (ref.current as HTMLElement)?.getBoundingClientRect();
if (rectSelectStart.x < rectSelectEnd.x) { if (rectSelectStart.x < rectSelectEnd.x) {
x = rectSelectStart.x; x = rectSelectStart.x;
@ -143,7 +144,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
ref={ref} ref={ref}
className={classNames.join(" ")} className={classNames.join(" ")}
onClick={clearSelection} onClick={clearSelection}
onMouseDown={onStartRectSelect} onMouseDown={onStartRectSelect as unknown as MouseEventHandler}
{...props} {...props}
> >
{rectSelectStart != null && rectSelectEnd != null {rectSelectStart != null && rectSelectEnd != null
@ -156,13 +157,13 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
tabIndex={0} tabIndex={0}
className={folderClassNames.join(" ")} className={folderClassNames.join(" ")}
data-selected={selectedFolders.includes(folder.id)} data-selected={selectedFolders.includes(folder.id)}
onContextMenu={(event) => { onContextMenu={(event: MouseEvent) => {
onContextMenuFolder?.(event, folder); onContextMenuFolder?.(event, folder);
}} }}
onClick={(event) => { onClick={(event: MouseEvent) => {
selectFolder(folder, !event.ctrlKey); selectFolder(folder, !event.ctrlKey);
}} }}
onDoubleClick={(event) => { onDoubleClick={(event: MouseEvent) => {
onOpenFolder?.(event, folder); onOpenFolder?.(event, folder);
}} }}
> >
@ -178,13 +179,13 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
tabIndex={0} tabIndex={0}
className={fileClassNames.join(" ")} className={fileClassNames.join(" ")}
data-selected={selectedFiles.includes(file.id)} data-selected={selectedFiles.includes(file.id)}
onContextMenu={(event) => { onContextMenu={(event: MouseEvent) => {
onContextMenuFile?.(event, file); onContextMenuFile?.(event, file);
}} }}
onClick={(event) => { onClick={(event: MouseEvent) => {
selectFile(file, !event.ctrlKey); selectFile(file, !event.ctrlKey);
}} }}
onDoubleClick={(event) => { onDoubleClick={(event: MouseEvent) => {
onOpenFile?.(event, file); onOpenFile?.(event, file);
}} }}
> >

View file

@ -17,7 +17,7 @@ export function AboutSettings() {
<div className={styles["Button-group"]}> <div className={styles["Button-group"]}>
<Button <Button
className={`${styles.Button} ${utilStyles["Text-bold"]}`} className={`${styles.Button} ${utilStyles["Text-bold"]}`}
onClick={(event) => { onClick={(event: Event) => {
event.preventDefault(); event.preventDefault();
windowsManager.open("text-editor", { windowsManager.open("text-editor", {
mode: "view", mode: "view",

View file

@ -10,8 +10,15 @@ import { ClickAction } from "../../../actions/actions/ClickAction";
import { removeFromArray } from "../../../../features/_utils/array.utils"; import { removeFromArray } from "../../../../features/_utils/array.utils";
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext"; import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
import { SettingsManager } from "../../../../features/settings/settingsManager"; import { SettingsManager } from "../../../../features/settings/settingsManager";
import App from "../../../../features/apps/app";
export function AppOption({ app, pins, setPins }) { interface AppOptionProps {
app: App;
pins: string[];
setPins: Function;
}
export function AppOption({ app, pins, setPins: _setPins }: AppOptionProps) {
const isPinned = pins.includes(app.id); const isPinned = pins.includes(app.id);
const settingsManager = useSettingsManager(); const settingsManager = useSettingsManager();
@ -29,7 +36,7 @@ export function AppOption({ app, pins, setPins }) {
} }
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
settings.set("pins", newPins.join(",")); void settings.set("pins", newPins.join(","));
}}/> }}/>
</Actions> </Actions>
}); });

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { ChangeEventHandler, useEffect, useState } from "react";
import { SettingsManager } from "../../../../features/settings/settingsManager"; import { SettingsManager } from "../../../../features/settings/settingsManager";
import styles from "../Settings.module.css"; import styles from "../Settings.module.css";
import utilStyles from "../../../../styles/utils.module.css"; import utilStyles from "../../../../styles/utils.module.css";
@ -17,17 +17,17 @@ import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtual
export function AppearanceSettings() { export function AppearanceSettings() {
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const settingsManager = useSettingsManager(); const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null); const [wallpaper, setWallpaper] = useState<string>(null);
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
const { openWindowedModal } = useWindowedModal(); const { openWindowedModal } = useWindowedModal();
useEffect(() => { useEffect(() => {
settings.get("wallpaper", setWallpaper); void settings.get("wallpaper", setWallpaper);
}, [settings]); }, [settings]);
const onChange = (event) => { const onChange = (event: Event) => {
const value = event.target.value; const value = (event.target as HTMLInputElement).value;
settings.set("wallpaper", value); void settings.set("wallpaper", value);
}; };
return (<> return (<>
@ -42,7 +42,7 @@ export function AppearanceSettings() {
type={SELECTOR_MODE.SINGLE} type={SELECTOR_MODE.SINGLE}
allowedFormats={IMAGE_FORMATS} allowedFormats={IMAGE_FORMATS}
onFinish={(file: VirtualFile) => { onFinish={(file: VirtualFile) => {
settings.set("wallpaper", file.source); void settings.set("wallpaper", file.source);
}} }}
{...props} {...props}
/> />
@ -59,7 +59,7 @@ export function AppearanceSettings() {
value={source} value={source}
aria-label="Wallpaper image" aria-label="Wallpaper image"
checked={source === wallpaper} checked={source === wallpaper}
onChange={onChange} onChange={onChange as unknown as ChangeEventHandler}
tabIndex={0} tabIndex={0}
/> />
<img src={source} alt={id} draggable="false"/> <img src={source} alt={id} draggable="false"/>

View file

@ -11,7 +11,7 @@ export function AppsSettings() {
useEffect(() => { useEffect(() => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
settings.get("pins", (pins) => { void settings.get("pins", (pins) => {
setPins(pins.split(",")); setPins(pins.split(","));
}); });
}, [settingsManager]); }, [settingsManager]);

View file

@ -4,20 +4,18 @@ import * as React from "react";
import styles from "./Terminal.module.css"; import styles from "./Terminal.module.css";
/** /**
* This was copied from * Source:
* https://github.com/nteract/ansi-to-react/blob/master/src/index.ts * https://github.com/nteract/ansi-to-react/blob/master/src/index.ts
*/ */
/** /**
* Converts ANSI strings into JSON output. * Converts ANSI strings into JSON output.
* @name ansiToJSON * @param input - The input string.
* @function * @param use_classes - If `true`, HTML classes will be appended
* @param {string} input - The input string.
* @param {boolean=} use_classes - If `true`, HTML classes will be appended
* to the HTML output. * to the HTML output.
* @returns {AnserJsonEntry[]} The parsed input. * @returns The parsed input.
*/ */
function ansiToJSON(input, use_classes) { function ansiToJSON(input: string, use_classes: boolean | undefined): AnserJsonEntry[] {
input = escapeCarriageReturn(fixBackspace(input)); input = escapeCarriageReturn(fixBackspace(input));
return Anser.ansiToJson(input, { return Anser.ansiToJson(input, {
json: true, json: true,
@ -28,10 +26,9 @@ function ansiToJSON(input, use_classes) {
/** /**
* Create a class string. * Create a class string.
* @param {AnserJsonEntry} bundle * @returns class name(s)
* @returns {string} class name(s)
*/ */
function createClass(bundle) { function createClass(bundle: AnserJsonEntry): string {
const classNames = []; const classNames = [];
if (bundle.bg) { if (bundle.bg) {
@ -53,10 +50,10 @@ function createClass(bundle) {
/** /**
* Create the style attribute. * Create the style attribute.
* @param {AnserJsonEntry} bundle * @param bundle
* @returns {object} returns the style object * @returns returns the style object
*/ */
function createStyle(bundle) { function createStyle(bundle: AnserJsonEntry): object {
const style: Record<string, string> = {}; const style: Record<string, string> = {};
if (bundle.bg) { if (bundle.bg) {
style.backgroundColor = `rgb(${bundle.bg})`; style.backgroundColor = `rgb(${bundle.bg})`;
@ -94,12 +91,11 @@ function createStyle(bundle) {
/** /**
* Converts an Anser bundle into a React Node. * Converts an Anser bundle into a React Node.
* @param {boolean} linkify - whether links should be converting into clickable anchor tags. * @param linkify - whether links should be converting into clickable anchor tags.
* @param {boolean} useClasses - should render the span with a class instead of style. * @param useClasses - should render the span with a class instead of style.
* @param {AnserJsonEntry} bundle - Anser output. * @param bundle - Anser output.
* @param {number} key
*/ */
function convertBundleIntoReact(linkify, useClasses, bundle, key) { function convertBundleIntoReact(linkify: boolean, useClasses: boolean, bundle: AnserJsonEntry, key: number) {
const style = useClasses ? null : createStyle(bundle); const style = useClasses ? null : createStyle(bundle);
const className = useClasses ? createClass(bundle) : null; const className = useClasses ? createClass(bundle) : null;
@ -115,7 +111,7 @@ function convertBundleIntoReact(linkify, useClasses, bundle, key) {
const linkRegex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/g; const linkRegex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/g;
let index = 0; let index = 0;
let match; let match: RegExpExecArray;
while ((match = linkRegex.exec(bundle.content)) !== null) { while ((match = linkRegex.exec(bundle.content)) !== null) {
const [, pre, url] = match; const [, pre, url] = match;
@ -149,14 +145,7 @@ function convertBundleIntoReact(linkify, useClasses, bundle, key) {
return React.createElement("span", { style, key, className }, content); return React.createElement("span", { style, key, className }, content);
} }
/** export default function Ansi(props: { children?: string | undefined; linkify?: boolean | undefined; className?: string | undefined; useClasses?: boolean | undefined; }) {
* @param {object} props
* @param {string=} props.children
* @param {boolean=} props.linkify
* @param {string=} props.className
* @param {boolean=} props.useClasses
*/
export default function Ansi(props) {
const { className, useClasses, children, linkify } = props; const { className, useClasses, children, linkify } = props;
return React.createElement( return React.createElement(
"code", "code",
@ -172,7 +161,7 @@ export default function Ansi(props) {
// that is **compatible with Jupyter classic**. One can // that is **compatible with Jupyter classic**. One can
// argue that this behavior is questionable: // argue that this behavior is questionable:
// https://stackoverflow.com/questions/55440152/multiple-b-doesnt-work-as-expected-in-jupyter# // https://stackoverflow.com/questions/55440152/multiple-b-doesnt-work-as-expected-in-jupyter#
function fixBackspace(txt) { function fixBackspace(txt: string) {
let tmp = txt; let tmp = txt;
do { do {
txt = tmp; txt = tmp;

View file

@ -21,7 +21,7 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
return ( return (
<span className={styles.Input}> <span className={styles.Input}>
{prefix && <Ansi className={[styles.Prefix]} useClasses>{prefix}</Ansi>} {prefix && <Ansi className={styles.Prefix} useClasses>{prefix}</Ansi>}
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition } as CSSProperties}> <span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition } as CSSProperties}>
<span aria-hidden="true">{value}</span> <span aria-hidden="true">{value}</span>
<input <input

View file

@ -36,7 +36,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
const { openWindowedModal } = useWindowedModal(); const { openWindowedModal } = useWindowedModal();
useEffect(() => { useEffect(() => {
(async () => { void (async () => {
let newContent = ""; let newContent = "";
// Load file // Load file
@ -97,8 +97,8 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
onChange({ target: { value: content } }); onChange({ target: { value: content } });
}; };
const onChange = (event) => { const onChange = (event: Event | { target: { value: string } }) => {
const value = event.target.value; const value = (event.target as HTMLInputElement).value;
if (currentFile != null) { if (currentFile != null) {
setUnsavedChanges(currentFile.content !== value); setUnsavedChanges(currentFile.content !== value);

View file

@ -24,10 +24,11 @@ import { Share } from "../modals/share/Share";
import ModalsManager from "../../features/modals/modalsManager"; import ModalsManager from "../../features/modals/modalsManager";
import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder"; import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder";
import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink"; import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink";
import { VirtualFile } from "../../features/virtual-drive/file/virtualFile";
export const Desktop = memo(() => { export const Desktop = memo(() => {
const settingsManager = useSettingsManager(); const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null); const [wallpaper, setWallpaper] = useState<string>(null);
const windowsManager = useWindowsManager(); const windowsManager = useWindowsManager();
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const [showIcons, setShowIcons] = useState(false); const [showIcons, setShowIcons] = useState(false);
@ -41,16 +42,16 @@ export const Desktop = memo(() => {
<DropdownAction label="View" icon={faEye}> <DropdownAction label="View" icon={faEye}>
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value: string) => { <RadioAction initialIndex={iconSize} onTrigger={(event, params, value: string) => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.set("icon-size", value); void settings.set("icon-size", value);
}} options={[ }} options={[
{ label: "Small icons" }, { label: "Small icons" },
{ label: "Medium icons" }, { label: "Medium icons" },
{ label: "Large icons" } { label: "Large icons" }
]}/> ]}/>
<Divider/> <Divider/>
<ToggleAction label="Show dekstop icons" initialValue={showIcons} onTrigger={(event, params, value) => { <ToggleAction label="Show dekstop icons" initialValue={showIcons} onTrigger={() => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.set("show-icons", (!showIcons).toString()); void settings.set("show-icons", (!showIcons).toString());
}}/> }}/>
</DropdownAction> </DropdownAction>
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => { <ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
@ -77,56 +78,54 @@ export const Desktop = memo(() => {
}); });
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) => const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
<Actions {...props}> <Actions {...props}>
<ClickAction label="Open" onTrigger={(event, file) => { <ClickAction label="Open" onTrigger={(event, file: VirtualFile) => {
file.open(windowsManager); file.open(windowsManager);
}}/> }}/>
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, file) => { <ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, file: VirtualFile) => {
file.parent.open(windowsManager); file.parent.open(windowsManager);
}}/> }}/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => { <ClickAction label="Delete" icon={faTrash} onTrigger={(event, file: VirtualFile) => {
file.delete(); file.delete();
}}/> }}/>
</Actions> </Actions>
}); });
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) => const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
<Actions {...props}> <Actions {...props}>
<ClickAction label="Open" onTrigger={(event, folder) => { <ClickAction label="Open" onTrigger={(event, folder: VirtualFolder) => {
folder.open(windowsManager); folder.open(windowsManager);
}}/> }}/>
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={faTerminal} onTrigger={(event, folder) => { <ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={faTerminal} onTrigger={(event, folder: VirtualFolder) => {
windowsManager.open(APPS.TERMINAL, { startPath: folder.path }); windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
}}/> }}/>
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, folder) => { <ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, folder: VirtualFolder) => {
folder.parent.open(windowsManager); folder.parent.open(windowsManager);
}}/> }}/>
<Divider/> <Divider/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => { <ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder: VirtualFolder) => {
folder.delete(); folder.delete();
}}/> }}/>
</Actions> </Actions>
}); });
useEffect(() => { useEffect(() => {
(async () => { const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop); void settings.get("wallpaper", setWallpaper);
settings.get("wallpaper", setWallpaper); void settings.get("show-icons", (value) => {
settings.get("show-icons", (value) => { if (value != null) {
if (value != null) { setShowIcons(value === "true");
setShowIcons(value === "true"); } else {
} else { setShowIcons(true);
setShowIcons(true); }
} });
}); void settings.get("icon-size", (value) => {
settings.get("icon-size", (value) => { if (isValidInteger(value))
if (isValidInteger(value)) setIconSize(parseInt(value));
setIconSize(parseInt(value)); });
});
})();
}, [settingsManager]); }, [settingsManager]);
const onError = () => { const onError = () => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.set("wallpaper", FALLBACK_WALLPAPER); 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) / 4;
@ -148,7 +147,7 @@ export const Desktop = memo(() => {
onOpenFile={(event, file) => { onOpenFile={(event, file) => {
(event as Event).preventDefault(); (event as Event).preventDefault();
const options: Record<string, any> = {}; const options: Record<string, unknown> = {};
if (file.name === "info.md") if (file.name === "info.md")
options.size = new Vector2(575, 675); options.size = new Vector2(575, 675);
if (file.extension === "md") if (file.extension === "md")

View file

@ -1,21 +1,27 @@
import { CSSProperties, FC, memo, ReactNode } from "react"; import { CSSProperties, FC, KeyboardEvent, memo, ReactNode } from "react";
import Modal from "../../features/modals/modal"; import Modal from "../../features/modals/modal";
import OutsideClickListener from "../../hooks/_utils/outsideClick"; import OutsideClickListener from "../../hooks/_utils/outsideClick";
import styles from "./ModalView.module.css"; import styles from "./ModalView.module.css";
import { useEffect } from "react"; import { useEffect } from "react";
export interface ModalProps { export interface ModalProps {
modal: Modal; modal?: Modal;
params?: Record<string, any>; params?: {
appId?: string;
fullscreen?: boolean;
iconUrl?: string;
title?: string;
[key: string]: unknown;
};
children?: ReactNode; children?: ReactNode;
onFinish?: Function; onFinish?: Function;
[key: string]: any; [key: string]: unknown;
} }
export const ModalView: FC<ModalProps> = memo(({ modal }) => { export const ModalView: FC<ModalProps> = memo(({ modal }) => {
useEffect(() => { useEffect(() => {
const onDismiss = (event) => { const onDismiss = (event: Event) => {
if (event.key === "Escape") if ((event as unknown as KeyboardEvent).key === "Escape")
modal.close(); modal.close();
}; };

View file

@ -3,12 +3,13 @@ import { ModalView } from "./ModalView";
import styles from "./ModalsView.module.css"; import styles from "./ModalsView.module.css";
import { useModals } from "../../hooks/modals/modalsContext"; import { useModals } from "../../hooks/modals/modalsContext";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext"; import { useModalsManager } from "../../hooks/modals/modalsManagerContext";
import Modal from "../../features/modals/modal";
export const ModalsView = memo(() => { export const ModalsView = memo(() => {
const ref = useRef(null); const ref = useRef(null);
const modals = useModals(); const modals = useModals();
const modalsManager = useModalsManager(); const modalsManager = useModalsManager();
const [sortedModals, setSortedModals] = useState([]); const [sortedModals, setSortedModals] = useState<Modal[]>([]);
// Sort modals // Sort modals
useEffect(() => { useEffect(() => {

View file

@ -7,15 +7,8 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
import { ReactSVG } from "react-svg"; import { ReactSVG } from "react-svg";
import utilStyles from "../../../styles/utils.module.css"; import utilStyles from "../../../styles/utils.module.css";
import Modal from "../../../features/modals/modal";
import { ModalProps } from "../ModalView"; import { ModalProps } from "../ModalView";
/**
* @param {object} props
* @param {Modal} props.modal
* @param {*} props.params
* @param {*} props.children
*/
export function WindowedModal({ modal, params, children, ...props }: ModalProps) { export function WindowedModal({ modal, params, children, ...props }: ModalProps) {
const { iconUrl, title } = params; const { iconUrl, title } = params;

View file

@ -7,6 +7,7 @@ import { WindowedModal } from "../_utils/WindowedModal";
import styles from "./FileSelector.module.css"; import styles from "./FileSelector.module.css";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile"; import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
import { ModalProps } from "../ModalView"; import { ModalProps } from "../ModalView";
import { VirtualFolder } from "../../../features/virtual-drive/folder";
interface FileSelectorProps extends ModalProps { interface FileSelectorProps extends ModalProps {
type: number; type: number;
@ -17,10 +18,10 @@ interface FileSelectorProps extends ModalProps {
export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }: FileSelectorProps) { export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }: FileSelectorProps) {
const multi = (type === SELECTOR_MODE.MULTIPLE); const multi = (type === SELECTOR_MODE.MULTIPLE);
const [selection, setSelection] = useState(multi ? [] : null); const [selection, setSelection] = useState<string[]>(multi ? [] : null);
const [directory, setDirectory] = useState(null); const [directory, setDirectory] = useState<VirtualFolder>(null);
const finish = (event) => { const finish = (event: Event) => {
event?.preventDefault(); event?.preventDefault();
if (directory == null || selection == null) if (directory == null || selection == null)

View file

@ -1,17 +1,23 @@
import { useState } from "react"; import { ChangeEventHandler, useState } from "react";
import styles from "./Share.module.css"; import styles from "./Share.module.css";
export default function Option({ name, label, setOption }) { interface OptionProps {
name: string;
label: string;
setOption: Function;
}
export default function Option({ name, label, setOption }: OptionProps) {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const onChange = (event) => { const onChange = (event: Event) => {
const newValue = event.target.value; const newValue = (event.target as HTMLInputElement).value;
setValue(newValue); setValue(newValue);
setOption(name, newValue); setOption(name, newValue);
}; };
return <label className={styles.Label}> return <label className={styles.Label}>
<p>{label}:</p> <p>{label}:</p>
<input className={styles.Input} name={name} type="text" value={value} onChange={onChange}/> <input className={styles.Input} name={name} type="text" value={value} onChange={onChange as unknown as ChangeEventHandler}/>
</label>; </label>;
} }

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { ChangeEventHandler, useEffect, useState } from "react";
import ModalsManager from "../../../features/modals/modalsManager"; import ModalsManager from "../../../features/modals/modalsManager";
import { WindowedModal } from "../_utils/WindowedModal"; import { WindowedModal } from "../_utils/WindowedModal";
import styles from "./Share.module.css"; import styles from "./Share.module.css";
@ -13,7 +13,7 @@ import { faSquareCheck } from "@fortawesome/free-solid-svg-icons";
import { useAlert } from "../../../hooks/modals/alert"; import { useAlert } from "../../../hooks/modals/alert";
import { ModalProps } from "../ModalView"; import { ModalProps } from "../ModalView";
const APP_OPTIONS = { const APP_OPTIONS: Record<string, Record<string, string>[]> = {
"terminal": [ "terminal": [
{ {
label: "Command", label: "Command",
@ -35,10 +35,10 @@ const APP_OPTIONS = {
}; };
export function Share({ modal, params, ...props }: ModalProps) { export function Share({ modal, params, ...props }: ModalProps) {
const [appId, setAppId] = useState(params.appId ?? ""); const [appId, setAppId] = useState<string>(params.appId ?? "");
const [fullscreen, setFullscreen] = useState(params.fullscreen ?? false); const [fullscreen, setFullscreen] = useState<boolean>(params.fullscreen ?? false);
const [options, setOptions] = useState({}); const [options, setOptions] = useState({});
const [url, setUrl] = useState(null); const [url, setUrl] = useState<string | null>(null);
const { alert } = useAlert(); const { alert } = useAlert();
useEffect(() => { useEffect(() => {
@ -49,8 +49,8 @@ export function Share({ modal, params, ...props }: ModalProps) {
})); }));
}, [appId, fullscreen, options]); }, [appId, fullscreen, options]);
const onAppIdChange = (event) => { const onAppIdChange = (event: Event) => {
const newAppId = event.target.value; const newAppId = (event.target as HTMLInputElement).value;
if (newAppId === appId) if (newAppId === appId)
return; return;
@ -69,12 +69,12 @@ export function Share({ modal, params, ...props }: ModalProps) {
} }
}; };
const onFullscreenChange = (event) => { const onFullscreenChange = (event: Event) => {
const newFullscreen = event.target.checked; const newFullscreen = (event.target as HTMLInputElement).checked;
setFullscreen(newFullscreen); setFullscreen(newFullscreen);
}; };
const setOption = (name, value) => { const setOption = (name: string, value: string) => {
setOptions((options = {}) => { setOptions((options = {}) => {
options = { ...options }; options = { ...options };
options[name] = value; options[name] = value;
@ -92,7 +92,7 @@ export function Share({ modal, params, ...props }: ModalProps) {
<form className={styles.Form}> <form className={styles.Form}>
<label className={styles.Label}> <label className={styles.Label}>
<p>App:</p> <p>App:</p>
<select className={styles.Input} name="app" value={appId} onChange={onAppIdChange}> <select className={styles.Input} name="app" value={appId} onChange={onAppIdChange as unknown as ChangeEventHandler}>
<option value={""}>(None)</option> <option value={""}>(None)</option>
{AppsManager.APPS.map(({ name, id }) => {AppsManager.APPS.map(({ name, id }) =>
<option key={id} value={id}>{name}</option> <option key={id} value={id}>{name}</option>
@ -101,7 +101,14 @@ export function Share({ modal, params, ...props }: ModalProps) {
</label> </label>
{appId !== "" ? <label className={styles.Label}> {appId !== "" ? <label className={styles.Label}>
<p>Fullscreen:</p> <p>Fullscreen:</p>
<input className={styles.Input} name="fullscreen" type="checkbox" checked={fullscreen} value={fullscreen} onChange={onFullscreenChange}/> <input
className={styles.Input}
name="fullscreen"
type="checkbox"
checked={fullscreen}
value={fullscreen.toString()}
onChange={onFullscreenChange as unknown as ChangeEventHandler}
/>
<div className={styles.Checkbox}> <div className={styles.Checkbox}>
{fullscreen {fullscreen
? <FontAwesomeIcon icon={faSquareCheck}/> ? <FontAwesomeIcon icon={faSquareCheck}/>

View file

@ -1,4 +1,4 @@
import { CSSProperties, memo, useEffect, useMemo, useRef, useState } from "react"; import { CSSProperties, memo, ReactEventHandler, UIEventHandler, useEffect, useMemo, useRef, useState } from "react";
import styles from "./Taskbar.module.css"; import styles from "./Taskbar.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons"; import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
@ -78,12 +78,12 @@ export const Taskbar = memo(() => {
useEffect(() => { useEffect(() => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
settings.get("pins", (pins) => { void settings.get("pins", (pins) => {
setPins(pins.split(",")); setPins(pins.split(","));
}); });
}, [settingsManager]); }, [settingsManager]);
const updateShowHome = (show) => { const updateShowHome = (show: boolean) => {
setShowHome(show); setShowHome(show);
if (show) { if (show) {
@ -92,7 +92,7 @@ export const Taskbar = memo(() => {
} }
}; };
const updateShowSearch = (show) => { const updateShowSearch = (show: boolean) => {
setShowSearch(show); setShowSearch(show);
if (show) { if (show) {
@ -104,7 +104,7 @@ export const Taskbar = memo(() => {
setHideUtilMenus(true); setHideUtilMenus(true);
if (inputRef.current) { if (inputRef.current) {
inputRef.current.focus(); (inputRef.current as HTMLElement).focus();
window.scrollTo(0, document.body.scrollHeight); window.scrollTo(0, document.body.scrollHeight);
} }
} else { } else {
@ -122,7 +122,7 @@ export const Taskbar = memo(() => {
setHideUtilMenus(false); setHideUtilMenus(false);
}; };
const search = (query) => { const search = (_query: string) => {
updateShowSearch(true); updateShowSearch(true);
}; };
@ -174,8 +174,8 @@ export const Taskbar = memo(() => {
<div <div
className={styles["App-icons-inner"]} className={styles["App-icons-inner"]}
data-allow-context-menu={true} data-allow-context-menu={true}
onScroll={onUpdate} onScroll={onUpdate as unknown as UIEventHandler}
onResize={onUpdate} onResize={onUpdate as unknown as ReactEventHandler}
ref={ref} ref={ref}
> >
{apps} {apps}

View file

@ -38,7 +38,7 @@ export const AppButton: FC<AppButtonProps> = memo(({ app, windowsManager, pins,
} }
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
settings.set("pins", newPins.join(",")); void settings.set("pins", newPins.join(","));
}}/> }}/>
{active && <ClickAction label="Close window" icon={faTimes} onTrigger={() => { {active && <ClickAction label="Close window" icon={faTimes} onTrigger={() => {
windowsManager.close(windowsManager.getAppWindowId(app.id)); windowsManager.close(windowsManager.getAppWindowId(app.id));

View file

@ -3,12 +3,12 @@ import styles from "./Calendar.module.css";
import OutsideClickListener from "../../../hooks/_utils/outsideClick"; import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { UtilMenu } from "../menus/UtilMenu"; import { UtilMenu } from "../menus/UtilMenu";
/** interface CalendarProps {
* @param {object} props hideUtilMenus: boolean;
* @param {boolean} props.hideUtilMenus showUtilMenu: Function;
* @param {Function} props.showUtilMenu }
*/
export function Calendar({ hideUtilMenus, showUtilMenu }) { export function Calendar({ hideUtilMenus, showUtilMenu }: CalendarProps) {
const [date, setDate] = useState(new Date()); const [date, setDate] = useState(new Date());
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
@ -28,7 +28,7 @@ export function Calendar({ hideUtilMenus, showUtilMenu }) {
} }
}, [hideUtilMenus, showMenu]); }, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => { const updateShowMenu = (show: boolean) => {
if (show) if (show)
showUtilMenu(); showUtilMenu();

View file

@ -5,12 +5,12 @@ import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { UtilMenu } from "../menus/UtilMenu"; import { UtilMenu } from "../menus/UtilMenu";
import styles from "./Network.module.css"; import styles from "./Network.module.css";
/** interface NetworkProps {
* @param {object} props hideUtilMenus: boolean;
* @param {boolean} props.hideUtilMenus showUtilMenu: Function;
* @param {Function} props.showUtilMenu }
*/
export function Network({ hideUtilMenus, showUtilMenu }) { export function Network({ hideUtilMenus, showUtilMenu }: NetworkProps) {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
useEffect(() => { useEffect(() => {
@ -19,7 +19,7 @@ export function Network({ hideUtilMenus, showUtilMenu }) {
} }
}, [hideUtilMenus, showMenu]); }, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => { const updateShowMenu = (show: boolean) => {
if (show) if (show)
showUtilMenu(); showUtilMenu();

View file

@ -5,12 +5,12 @@ import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { UtilMenu } from "../menus/UtilMenu"; import { UtilMenu } from "../menus/UtilMenu";
import styles from "./Volume.module.css"; import styles from "./Volume.module.css";
/** interface VolumeProps {
* @param {object} props hideUtilMenus: boolean;
* @param {boolean} props.hideUtilMenus showUtilMenu: Function;
* @param {Function} props.showUtilMenu }
*/
export function Volume({ hideUtilMenus, showUtilMenu }) { export function Volume({ hideUtilMenus, showUtilMenu }: VolumeProps) {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
useEffect(() => { useEffect(() => {
@ -19,7 +19,7 @@ export function Volume({ hideUtilMenus, showUtilMenu }) {
} }
}, [hideUtilMenus, showMenu]); }, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => { const updateShowMenu = (show: boolean) => {
if (show) if (show)
showUtilMenu(); showUtilMenu();

View file

@ -14,13 +14,13 @@ import utilStyles from "../../../styles/utils.module.css";
import { APPS } from "../../../config/apps.config"; import { APPS } from "../../../config/apps.config";
import { NAME } from "../../../config/branding.config"; import { NAME } from "../../../config/branding.config";
/** interface HomeMenuProps {
* @param {object} props active: boolean;
* @param {boolean} props.active setActive: Function;
* @param {Function} props.setActive search: Function;
* @param {Function} props.search }
*/
export function HomeMenu({ active, setActive, search }) { export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
const windowsManager = useWindowsManager(); const windowsManager = useWindowsManager();
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const [tabIndex, setTabIndex] = useState(active ? 0 : -1); const [tabIndex, setTabIndex] = useState(active ? 0 : -1);
@ -34,7 +34,7 @@ export function HomeMenu({ active, setActive, search }) {
classNames.push(styles.Active); classNames.push(styles.Active);
let onlyAltKey = false; let onlyAltKey = false;
const onKeyDown = (event) => { const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Alt") { if (event.key === "Alt") {
event.preventDefault(); event.preventDefault();
onlyAltKey = true; onlyAltKey = true;
@ -47,7 +47,7 @@ export function HomeMenu({ active, setActive, search }) {
} }
}; };
const onKeyUp = (event) => { const onKeyUp = (event: KeyboardEvent) => {
if (event.key === "Alt" && onlyAltKey) { if (event.key === "Alt" && onlyAltKey) {
event.preventDefault(); event.preventDefault();
setActive(!active); setActive(!active);

View file

@ -3,20 +3,21 @@ import appStyles from "./AppList.module.css";
import AppsManager from "../../../features/apps/appsManager"; import AppsManager from "../../../features/apps/appsManager";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext"; import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
import { ReactSVG } from "react-svg"; import { ReactSVG } from "react-svg";
import { useEffect, useState } from "react"; import { ChangeEventHandler, useEffect, useState } from "react";
import { useKeyboardListener } from "../../../hooks/_utils/keyboard"; import { useKeyboardListener } from "../../../hooks/_utils/keyboard";
import App from "../../../features/apps/app";
/** interface SearchMenuProps {
* @param {object} props active: boolean;
* @param {boolean} props.active setActive: Function;
* @param {Function} props.setActive searchQuery: string;
* @param {string} props.searchQuery setSearchQuery: Function;
* @param {Function} props.setSearchQuery inputRef: { current: HTMLInputElement | undefined };
* @param {import("react").ElementRef} props.inputRef }
*/
export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inputRef }) { export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inputRef }: SearchMenuProps) {
const windowsManager = useWindowsManager(); const windowsManager = useWindowsManager();
const [apps, setApps] = useState(null); const [apps, setApps] = useState<App[]>(null);
const [tabIndex, setTabIndex] = useState(active ? 0 : -1); const [tabIndex, setTabIndex] = useState(active ? 0 : -1);
useEffect(() => { useEffect(() => {
@ -38,8 +39,8 @@ export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inp
)); ));
}, [searchQuery]); }, [searchQuery]);
const onChange = (event) => { const onChange = (event: Event) => {
const value = event.target.value; const value = (event.target as HTMLInputElement).value;
setSearchQuery(value); setSearchQuery(value);
}; };
@ -47,7 +48,7 @@ export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inp
if (active && apps) if (active && apps)
classNames.push(styles.Active); classNames.push(styles.Active);
const onKeyDown = (event) => { const onKeyDown = (event: KeyboardEvent) => {
if ((event.key === "f" || event.key === "g") && event.ctrlKey && !active) { if ((event.key === "f" || event.key === "g") && event.ctrlKey && !active) {
event.preventDefault(); event.preventDefault();
setActive(true); setActive(true);
@ -72,7 +73,7 @@ export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inp
aria-label="Search query" aria-label="Search query"
tabIndex={tabIndex} tabIndex={tabIndex}
value={searchQuery} value={searchQuery}
onChange={onChange} onChange={onChange as unknown as ChangeEventHandler}
spellCheck={false} spellCheck={false}
placeholder="Search..." placeholder="Search..."
/> />

View file

@ -1,13 +1,14 @@
import { ReactNode } from "react";
import styles from "./UtilMenu.module.css"; import styles from "./UtilMenu.module.css";
/** interface UtilMenuProps {
* @param {object} props active: boolean;
* @param {boolean} props.active setActive: Function;
* @param {Function} props.setActive className: string;
* @param {*} props.className children: ReactNode;
* @param {*} props.children }
*/
export function UtilMenu({ active, setActive, className, children }) { export function UtilMenu({ active, setActive: _setActive, className, children }: UtilMenuProps) {
const classNames = [styles["Container-outer"]]; const classNames = [styles["Container-outer"]];
if (active) if (active)
classNames.push(styles.Active); classNames.push(styles.Active);

View file

@ -6,13 +6,13 @@ import App from "../../features/apps/app";
export interface WindowFallbackViewProps { export interface WindowFallbackViewProps {
error?: Error; error?: Error;
resetErrorBoundary?: any; resetErrorBoundary?: unknown;
app?: App; app?: App;
closeWindow?: Function closeWindow?: Function
} }
// I don't know why this component's type needs to be ReactNode instead of FC, it has something to do with the way it's implemented // I don't know why this component's type needs to be ReactNode instead of FC, it has something to do with the way it's implemented
export default function WindowFallbackView({ error, resetErrorBoundary, app, closeWindow }: WindowFallbackViewProps): ReactElement { export default function WindowFallbackView({ error, resetErrorBoundary: _resetErrorBoundary, app, closeWindow }: WindowFallbackViewProps): ReactElement {
const { alert } = useAlert(); const { alert } = useAlert();
const [alerted, setAlerted] = useState(false); const [alerted, setAlerted] = useState(false);

View file

@ -4,7 +4,7 @@ import { faExpand, faMinus, faWindowMaximize as fasWindowMaximize, faTimes, faXm
import { ReactSVG } from "react-svg"; import { ReactSVG } from "react-svg";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext"; import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
import { FC, memo, MouseEventHandler, useEffect, useRef, useState } from "react"; import { CSSProperties, FC, memo, MouseEventHandler, useEffect, useRef, useState } from "react";
import Vector2 from "../../features/math/vector2"; import Vector2 from "../../features/math/vector2";
import { faWindowMaximize } from "@fortawesome/free-regular-svg-icons"; import { faWindowMaximize } from "@fortawesome/free-regular-svg-icons";
import utilStyles from "../../styles/utils.module.css"; import utilStyles from "../../styles/utils.module.css";
@ -111,14 +111,17 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
windowsManager.close(id); windowsManager.close(id);
}; };
const focus = (event, force = false) => { const focus = (event: Event, force = false): void => {
if (force) if (force) {
return onInteract(); onInteract();
return;
}
if (event?.defaultPrevented) if (event?.defaultPrevented)
return; return;
if (event == null || event.target?.closest?.(".Handle") == null || event.target?.closest?.("button") == null) const target = event?.target as HTMLElement;
if (event == null || target?.closest?.(".Handle") == null || target?.closest?.("button") == null)
onInteract(); onInteract();
}; };
@ -128,7 +131,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
if (minimized) if (minimized)
classNames.push(styles.Minimized); classNames.push(styles.Minimized);
return (<div style={{ zIndex, position: !maximized ? "relative" : null }}> return (<div style={{ zIndex, position: !maximized ? "relative" : null } as CSSProperties}>
<ShortcutsListener/> <ShortcutsListener/>
<Draggable <Draggable
key={id} key={id}
@ -146,13 +149,13 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
cancel="button" cancel="button"
nodeRef={nodeRef} nodeRef={nodeRef}
disabled={maximized} disabled={maximized}
onStart={(event) => { focus(event); }} onStart={(event) => { focus(event as Event); }}
grid={[1, 1]} grid={[1, 1]}
> >
<div <div
className={classNames.join(" ")} className={classNames.join(" ")}
ref={nodeRef} ref={nodeRef}
onClick={focus} onClick={focus as unknown as MouseEventHandler}
> >
<div <div
className={styles["Window-inner"]} className={styles["Window-inner"]}
@ -163,7 +166,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
> >
<div className={`${styles.Header} Window-handle`} onContextMenu={onContextMenu} onDoubleClick={(event) => { <div className={`${styles.Header} Window-handle`} onContextMenu={onContextMenu} onDoubleClick={(event) => {
setMaximized(!maximized); setMaximized(!maximized);
focus(event, true); focus(event as unknown as Event, true);
}}> }}>
<ReactSVG <ReactSVG
className={styles["Window-icon"]} className={styles["Window-icon"]}
@ -179,20 +182,20 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
onClick={(event) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
setMaximized(!maximized); setMaximized(!maximized);
focus(event, true); focus(event as unknown as Event, true);
}} }}
> >
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/> <FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
</button> </button>
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-window" <button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-window"
onClick={(event) => { close(event as unknown as Event); }}> onClick={close as unknown as MouseEventHandler}>
<FontAwesomeIcon icon={faXmark}/> <FontAwesomeIcon icon={faXmark}/>
</button> </button>
</div> </div>
<div className={styles["Window-content"]}> <div className={styles["Window-content"]}>
<ErrorBoundary <ErrorBoundary
FallbackComponent={(props) => <WindowFallbackView app={app} closeWindow={close} {...props}/>} FallbackComponent={(props) => <WindowFallbackView app={app} closeWindow={close} {...props}/>}
onReset={(details) => { onReset={() => {
// Reset the state of your app so the error doesn't happen again // Reset the state of your app so the error doesn't happen again
}} }}
onError={(error) => { onError={(error) => {

View file

@ -43,7 +43,7 @@ export const WindowsView: FC = memo(() => {
if (windowsManager.startupComplete) if (windowsManager.startupComplete)
return; return;
let startupAppNames = []; let startupAppNames: string[] = [];
// Get app name and params from URL query // Get app name and params from URL query
const params = getViewportParams(); const params = getViewportParams();
@ -54,7 +54,7 @@ export const WindowsView: FC = memo(() => {
// Get list of app names from settings file // Get list of app names from settings file
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.apps); const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.apps);
settings.get("startup", (value) => { void settings.get("startup", (value) => {
if (value !== "") { if (value !== "") {
startupAppNames = value?.split(",").concat(startupAppNames); startupAppNames = value?.split(",").concat(startupAppNames);
startupAppNames = removeDuplicatesFromArray(startupAppNames); startupAppNames = removeDuplicatesFromArray(startupAppNames);
@ -79,7 +79,7 @@ export const WindowsView: FC = memo(() => {
position={position} position={position}
options={options} options={options}
minimized={minimized} minimized={minimized}
toggleMinimized={(event) => { toggleMinimized={(event: Event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
windowsManager.setMinimized(id, !minimized); windowsManager.setMinimized(id, !minimized);

View file

@ -12,7 +12,7 @@ export const CODE_FORMATS = [
"yml" "yml"
]; ];
export const EXTENSION_TO_LANGUAGE = { export const EXTENSION_TO_LANGUAGE: Record<string, string> = {
"js": "javascript", "js": "javascript",
"jsx": "javascript", "jsx": "javascript",
"ts": "typescript", "ts": "typescript",

View file

@ -1,14 +1,14 @@
export function removeFromArray(item: any, array: any[]) { export function removeFromArray<Type>(item: Type, array: Type[]) {
const index = array.indexOf(item); const index = array.indexOf(item);
if (index !== -1) { if (index !== -1) {
array.splice(index, 1); array.splice(index, 1);
} }
} }
export function randomFromArray(array: any[]): any { export function randomFromArray<Type>(array: Type[]): Type {
return array[Math.floor(Math.random() * array.length)]; return array[Math.floor(Math.random() * array.length)];
} }
export function removeDuplicatesFromArray(array: any[]): any[] { export function removeDuplicatesFromArray<Type>(array: Type[]): Type[] {
return array.filter((item, index) => array.indexOf(item) === index); return array.filter((item, index) => array.indexOf(item) === index);
} }

View file

@ -15,9 +15,9 @@ const TIME_INDICATORS = {
* @param maxLength - The maximum amount of units, e.g.: 3 => years, months, days * @param maxLength - The maximum amount of units, e.g.: 3 => years, months, days
*/ */
export function formatTime(time: number, maxLength: number = 3, allowAffixes: boolean): string { export function formatTime(time: number, maxLength: number = 3, allowAffixes: boolean): string {
const result = []; const result: string[] = [];
const formatResult = (result, inPast) => { const formatResult = (result: string[], inPast: boolean): string => {
if (!allowAffixes) if (!allowAffixes)
return result.join(", "); return result.join(", ");
@ -44,8 +44,8 @@ export function formatTime(time: number, maxLength: number = 3, allowAffixes: bo
return formatResult(["less than a second"], inPast); return formatResult(["less than a second"], inPast);
} }
const units = []; const units: { amount: number, label: string }[] = [];
const unitLabels = { const unitLabels: Record<string, string> = {
"s": "seconds", "s": "seconds",
"m": "minutes", "m": "minutes",
"h": "hours", "h": "hours",

View file

@ -8,7 +8,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
/** /**
* Add event listener for an event * Add event listener for an event
*/ */
on<Key extends keyof EventMap>(eventName: Key, callback: (data: any) => void) { on<Key extends keyof EventMap>(eventName: Key, callback: (data: unknown) => void) {
if (!this.#events[eventName as string]) { if (!this.#events[eventName as string]) {
this.#events[eventName as string] = []; this.#events[eventName as string] = [];
} }
@ -18,7 +18,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
/** /**
* Remove event listener for an event * Remove event listener for an event
*/ */
off<Key extends keyof EventMap>(eventName: Key, callback: (data: any) => void) { off<Key extends keyof EventMap>(eventName: Key, callback: (data: unknown) => void) {
if (this.#events[eventName as string]) { if (this.#events[eventName as string]) {
this.#events[eventName as string] = this.#events[eventName as string].filter( this.#events[eventName as string] = this.#events[eventName as string].filter(
(listener) => listener !== callback (listener) => listener !== callback
@ -29,7 +29,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
/** /**
* Dispatch event * Dispatch event
*/ */
emit<Key extends keyof EventMap>(eventName: Key, data?: any) { emit<Key extends keyof EventMap>(eventName: Key, data?: unknown) {
if (this.#events[eventName as string]) { if (this.#events[eventName as string]) {
this.#events[eventName as string].forEach((listener) => { this.#events[eventName as string].forEach((listener) => {
listener(data); listener(data);

View file

@ -1,3 +1,3 @@
export function isValidInteger(number: number | string): number| boolean { export function isValidInteger(number: number | string): number | boolean {
return (typeof number === "number" || parseInt(number) || parseInt(number) === 0); return (typeof number === "number" || parseInt(number) || parseInt(number) === 0);
} }

View file

@ -38,7 +38,7 @@ export default class AppsManager {
]; ];
static getAppById(id: string): App | null { static getAppById(id: string): App | null {
let application = null; let application: App | null = null;
this.APPS.forEach((app) => { this.APPS.forEach((app) => {
if (app.id === id) { if (app.id === id) {
@ -54,7 +54,7 @@ export default class AppsManager {
* Get the app associated with a file extension * Get the app associated with a file extension
*/ */
static getAppByFileExtension(fileExtension: string): App { static getAppByFileExtension(fileExtension: string): App {
let app = null; let app: App = null;
if (IMAGE_FORMATS.includes(fileExtension)) if (IMAGE_FORMATS.includes(fileExtension))
return this.getAppById(APPS.MEDIA_VIEWER); return this.getAppById(APPS.MEDIA_VIEWER);

View file

@ -1,6 +1,6 @@
import Command from "./command"; import Command from "./command";
let commands = []; let commands: Command[] = [];
/** /**
* Dynamically import commands * Dynamically import commands
@ -9,7 +9,7 @@ const loadCommands = () => {
commands = []; commands = [];
const context = require.context("./commands", false, /\.ts$/); const context = require.context("./commands", false, /\.ts$/);
context.keys().forEach((key) => { context.keys().forEach((key) => {
const commandModule = context(key); const commandModule = context(key) as Record<string, Command>;
const commandName = Object.keys(commandModule)[0]; const commandName = Object.keys(commandModule)[0];
const command = commandModule[commandName]; const command = commandModule[commandName];
@ -25,7 +25,7 @@ export default class CommandsManager {
static COMMANDS = commands; static COMMANDS = commands;
static find(name: string): Command { static find(name: string): Command {
let matchCommand = null; let matchCommand: Command = null;
this.COMMANDS.forEach((command) => { this.COMMANDS.forEach((command) => {
if (command.name === name) { if (command.name === name) {

View file

@ -60,7 +60,7 @@ export default class Modal {
this.lastInteraction = Date.now(); this.lastInteraction = Date.now();
} }
finish(...args: any[]) { finish(...args: unknown[]) {
if (this.modalsManager == null || this.id == null) if (this.modalsManager == null || this.id == null)
return; return;

View file

@ -44,7 +44,7 @@ export class Settings {
this.xmlDoc = xmlDoc; this.xmlDoc = xmlDoc;
} }
async write() { write() {
if (!this.file) if (!this.file)
return; return;
@ -76,14 +76,16 @@ export class Settings {
if (callback) { if (callback) {
callback(value); callback(value);
this.file.on(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, async () => { this.file.on(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, () => {
await this.read(); void (async () => {
const newValue = await this.get(key); await this.read();
const newValue = await this.get(key);
if (newValue !== value) { if (newValue !== value) {
callback(newValue); callback(newValue);
value = newValue; value = newValue;
} }
})();
}); });
} }
@ -102,6 +104,6 @@ export class Settings {
this.xmlDoc.getElementsByTagName(PARENT_NODE)[0].appendChild(newOption); this.xmlDoc.getElementsByTagName(PARENT_NODE)[0].appendChild(newOption);
} }
await this.write(); this.write();
} }
} }

View file

@ -1,5 +1,6 @@
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import { GA_MEASUREMENT_ID } from "../../config/tracking.config"; import { GA_MEASUREMENT_ID } from "../../config/tracking.config";
import { UaEventOptions } from "react-ga4/types/ga4";
export class TrackingManager { export class TrackingManager {
static initialize() { static initialize() {
@ -7,8 +8,7 @@ export class TrackingManager {
ReactGA.initialize(GA_MEASUREMENT_ID); ReactGA.initialize(GA_MEASUREMENT_ID);
} }
/** @type {ReactGA["event"]} */ static event(options: UaEventOptions | string) {
static event(options) {
console.info(options); console.info(options);
if (GA_MEASUREMENT_ID == null) if (GA_MEASUREMENT_ID == null)

View file

@ -54,7 +54,7 @@ export class VirtualFile extends VirtualBase {
/** /**
* Sets the content of this file and removes the source * Sets the content of this file and removes the source
*/ */
setContent(content: string | any) { setContent(content: string) {
if (this.content === content || !this.canBeEdited) if (this.content === content || !this.canBeEdited)
return; return;
@ -106,7 +106,7 @@ export class VirtualFile extends VirtualBase {
).catch((error) => { ).catch((error) => {
console.error(`Error while reading file with id "${this.id}":`, error); console.error(`Error while reading file with id "${this.id}":`, error);
return null; return null;
}); }) as string;
} }
isFile(): boolean { isFile(): boolean {
@ -143,7 +143,7 @@ export class VirtualFile extends VirtualBase {
break; break;
} }
return iconUrl; return iconUrl as string;
} }
getType(): string { getType(): string {

View file

@ -54,7 +54,7 @@ export class VirtualFolder extends VirtualBase {
* Finds and returns a file inside this folder matching a name and extension * Finds and returns a file inside this folder matching a name and extension
*/ */
findFile(name: string, extension?: string): VirtualFile | VirtualFileLink { findFile(name: string, extension?: string): VirtualFile | VirtualFileLink {
let resultFile = null; let resultFile: VirtualFile | VirtualFileLink = null;
this.files.forEach((file) => { this.files.forEach((file) => {
const matchingName = (file.name === name || (file.alias && file.alias === name)); const matchingName = (file.name === name || (file.alias && file.alias === name));
@ -71,7 +71,7 @@ export class VirtualFolder extends VirtualBase {
* Finds and returns a folder inside this folder matching a name * Finds and returns a folder inside this folder matching a name
*/ */
findSubFolder(name: string): VirtualFolder | VirtualFolderLink { findSubFolder(name: string): VirtualFolder | VirtualFolderLink {
let resultFolder = null; let resultFolder: VirtualFolder | VirtualFolderLink = null;
this.subFolders.forEach((folder) => { this.subFolders.forEach((folder) => {
if (folder.name === name || (folder.alias && folder.alias === name)) { if (folder.name === name || (folder.alias && folder.alias === name)) {
@ -221,7 +221,7 @@ export class VirtualFolder extends VirtualBase {
/** /**
* Removes a file or folder from this folder * Removes a file or folder from this folder
*/ */
remove(child: VirtualFile | VirtualFolder | VirtualFolderLink) { remove(child: VirtualFile | VirtualFileLink | VirtualFolder | VirtualFolderLink) {
if (!this.canBeEdited) if (!this.canBeEdited)
return; return;
@ -241,9 +241,9 @@ export class VirtualFolder extends VirtualBase {
*/ */
navigate(relativePath: string): VirtualFile | VirtualFolder | null { navigate(relativePath: string): VirtualFile | VirtualFolder | null {
const segments = relativePath.split("/"); const segments = relativePath.split("/");
let currentDirectory: VirtualFile | VirtualFolder = this; let currentDirectory: VirtualFile | VirtualFolder = this as VirtualFolder;
const getDirectory = (path, isStart) => { const getDirectory = (path: string, isStart: boolean) => {
if (isStart && path === "") { if (isStart && path === "") {
return this.getRoot(); return this.getRoot();
} else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) { } else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) {

View file

@ -105,7 +105,7 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
}); });
}); });
}).createFolder("fonts", (folder) => { }).createFolder("fonts", (folder) => {
folder.createFolders(["poppins", "roboto-mono"]); folder.createFolders(["outfit", "roboto-mono"]);
}).createFolder("screenshots", (folder) => { }).createFolder("screenshots", (folder) => {
folder.createFile("screenshot", "png", (file) => { folder.createFile("screenshot", "png", (file) => {
file.setSource(`${process.env.PUBLIC_URL}/assets/screenshots/screenshot-files-settings-taskbar-desktop.png`); file.setSource(`${process.env.PUBLIC_URL}/assets/screenshots/screenshot-files-settings-taskbar-desktop.png`);

View file

@ -1,9 +1,9 @@
import { StorageManager } from "../../storage/storageManager"; import { StorageManager } from "../../storage/storageManager";
import { VirtualFolderLink } from "../folder/virtualFolderLink"; import { VirtualFolderLink, VirtualFolderLinkJson } from "../folder/virtualFolderLink";
import { loadDefaultData } from "./defaultData"; import { loadDefaultData } from "./defaultData";
import { VirtualFile, VirtualFileJson } from "../file/virtualFile"; import { VirtualFile, VirtualFileJson } from "../file/virtualFile";
import { VirtualFolder } from "../folder"; import { VirtualFolder } from "../folder";
import { VirtualFileLink } from "../file/virtualFileLink"; import { VirtualFileLink, VirtualFileLinkJson } from "../file/virtualFileLink";
import { VirtualFolderJson } from "../folder/virtualFolder"; import { VirtualFolderJson } from "../folder/virtualFolder";
export interface VirtualRootJson extends VirtualFolderJson { export interface VirtualRootJson extends VirtualFolderJson {
@ -34,16 +34,16 @@ export class VirtualRoot extends VirtualFolder {
if (data == null) if (data == null)
return; return;
let object; let object: VirtualRootJson | null = null;
try { try {
object = JSON.parse(data); object = JSON.parse(data) as VirtualRootJson;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
if (object == null) if (object == null)
return; return;
const shortcuts = { ...object.scs }; const shortcuts = { ...object.scs } as Record<string, string>;
const addFile = ({ const addFile = ({
nam: name, nam: name,
@ -52,7 +52,7 @@ export class VirtualRoot extends VirtualFolder {
cnt: content, cnt: content,
lnk: link, lnk: link,
ico: iconUrl, ico: iconUrl,
}: any, parent: VirtualFolder = this) => { }: VirtualFileJson & VirtualFileLinkJson, parent: VirtualFolder = this) => {
if (link) { if (link) {
parent.createFileLink(name, (fileLink: VirtualFileLink) => { parent.createFileLink(name, (fileLink: VirtualFileLink) => {
fileLink.setLinkedPath(link); fileLink.setLinkedPath(link);
@ -81,7 +81,7 @@ export class VirtualRoot extends VirtualFolder {
fls: files, fls: files,
lnk: link, lnk: link,
ico: iconUrl, ico: iconUrl,
}: any, parent: VirtualFolder = this) => { }: VirtualFolderJson & VirtualFolderLinkJson, parent: VirtualFolder = this) => {
if (link) { if (link) {
parent.createFolderLink(name, (folderLink: VirtualFolderLink) => { parent.createFolderLink(name, (folderLink: VirtualFolderLink) => {
folderLink.setLinkedPath(link); folderLink.setLinkedPath(link);
@ -94,7 +94,7 @@ export class VirtualRoot extends VirtualFolder {
parent.createFolder(name, (folder: VirtualFolder) => { parent.createFolder(name, (folder: VirtualFolder) => {
if (Object.values(shortcuts).includes(folder.displayPath)) { if (Object.values(shortcuts).includes(folder.displayPath)) {
let alias; let alias: string;
for (const [key, value] of Object.entries(shortcuts)) { for (const [key, value] of Object.entries(shortcuts)) {
if (value === folder.displayPath) if (value === folder.displayPath)
alias = key; alias = key;
@ -102,12 +102,12 @@ export class VirtualRoot extends VirtualFolder {
folder.setAlias(alias); folder.setAlias(alias);
} }
if (folders != null) { if (folders != null) {
folders.forEach((subFolder: VirtualFolderJson) => { folders.forEach((subFolder: VirtualFolderJson & VirtualFolderLinkJson) => {
addFolder(subFolder, folder); addFolder(subFolder, folder);
}); });
} }
if (files != null) { if (files != null) {
files.forEach((file: VirtualFileJson) => { files.forEach((file: VirtualFileJson & VirtualFileLinkJson) => {
addFile(file, folder); addFile(file, folder);
}); });
} }
@ -118,12 +118,12 @@ export class VirtualRoot extends VirtualFolder {
}; };
if (object.fds != null) { if (object.fds != null) {
object.fds.forEach((subFolder: VirtualFolderJson) => { object.fds.forEach((subFolder: VirtualFolderJson & VirtualFolderLinkJson) => {
addFolder(subFolder); addFolder(subFolder);
}); });
} }
if (object.fls != null) { if (object.fls != null) {
object.fls.forEach((file: VirtualFileJson) => { object.fls.forEach((file: VirtualFileJson & VirtualFileLinkJson) => {
addFile(file); addFile(file);
}); });
} }
@ -181,15 +181,15 @@ export class VirtualRoot extends VirtualFolder {
} }
} }
static isValidName(name) { static isValidName(_name: string) {
// TO DO // TO DO
} }
static isValidFileName(name) { static isValidFileName(_name: string) {
// TO DO // TO DO
} }
static isValidFolderName(name) { static isValidFolderName(_name: string) {
// TO DO // TO DO
} }
@ -219,9 +219,6 @@ export class VirtualRoot extends VirtualFolder {
return object; return object;
} }
/**
* @returns {string | null}
*/
toString(): string | null { toString(): string | null {
const json = this.toJSON(); const json = this.toJSON();

View file

@ -46,7 +46,7 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
return this;; return this;;
this.alias = alias; this.alias = alias;
this.getRoot().addShortcut(alias, this as any); this.getRoot().addShortcut(alias, this as never);
this.confirmChanges(); this.confirmChanges();
return this; return this;
@ -100,7 +100,7 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
if (parent == null) if (parent == null)
return; return;
parent.remove?.(this as any); parent.remove?.(this as never);
this.confirmChanges(parent.getRoot()); this.confirmChanges(parent.getRoot());
} }
@ -114,9 +114,11 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
root?.saveData(); root?.saveData();
} }
open(..._args: any[]): any {} open(..._args: unknown[]): unknown {
return null;
};
get path() { get path(): string {
return this.alias ?? this.displayPath; return this.alias ?? this.displayPath;
} }
@ -177,4 +179,13 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
return object; return object;
} }
toString(): string | null {
const json = this.toJSON();
if (json == null)
return null;
return JSON.stringify(json);
}
} }

View file

@ -17,7 +17,7 @@ export interface WindowOptions {
isFocused?: boolean; isFocused?: boolean;
lastInteraction?: number; lastInteraction?: number;
minimized?: boolean; minimized?: boolean;
[key: string]: any; [key: string]: unknown;
} }
export default class WindowsManager { export default class WindowsManager {
@ -174,8 +174,8 @@ export default class WindowsManager {
return active; return active;
} }
getAppWindowId(appId: string): string { getAppWindowId(appId: string): string | null {
let windowId = null; let windowId: string | null = null;
Object.values(this.windows).forEach((window) => { Object.values(this.windows).forEach((window) => {
if (window.app.id === appId) { if (window.app.id === appId) {

View file

@ -14,8 +14,7 @@ export class ZIndexManager extends EventEmitter<typeof ZIndexManagerEvents> {
static EVENT_NAMES = ZIndexManagerEvents; static EVENT_NAMES = ZIndexManagerEvents;
/** @type {ZIndexGroup[]} */ groups: ZIndexGroup[] = [];
groups = [];
constructor() { constructor() {
super(); super();
@ -41,7 +40,7 @@ export class ZIndexManager extends EventEmitter<typeof ZIndexManagerEvents> {
} }
} }
getIndex(groupIndex, index) { getIndex(groupIndex: number, index: number) {
return this.groups[groupIndex].getIndex(index); return this.groups[groupIndex].getIndex(index);
} }
} }

View file

@ -1,23 +1,19 @@
import { useState } from "react"; import { useState } from "react";
import { clamp } from "../../features/math/clamp"; import { clamp } from "../../features/math/clamp";
/** export function useHistory<Type>(initialState: Type): {
* @param {*} initialState history: Type[];
* @returns {{ stateIndex: number;
* history: *[], pushState: Function;
* stateIndex: number, undo: Function;
* pushState: Function, redo: Function;
* undo: Function, undoAvailable: boolean;
* redo: Function, redoAvailable: boolean;
* undoAvailable: boolean, } {
* redoAvailable: boolean const [history, setHistory] = useState<Type[]>(initialState ? [initialState] : []);
* }}
*/
export function useHistory(initialState) {
const [history, setHistory] = useState(initialState ? [initialState] : []);
const [stateIndex, setStateIndex] = useState(0); const [stateIndex, setStateIndex] = useState(0);
const pushState = (state) => { const pushState = (state: Type) => {
if (state === history[0]) if (state === history[0])
return; return;
@ -35,7 +31,7 @@ export function useHistory(initialState) {
setStateIndex(0); setStateIndex(0);
}; };
const updateStateIndex = (delta) => { const updateStateIndex = (delta: number) => {
const index = clamp(stateIndex + delta, 0, history.length - 1); const index = clamp(stateIndex + delta, 0, history.length - 1);
if (index === stateIndex) if (index === stateIndex)

View file

@ -29,9 +29,9 @@ interface UseShortcutsParams {
} }
export function useShortcuts({ options, shortcuts, useCategories = true }: UseShortcutsParams) { export function useShortcuts({ options, shortcuts, useCategories = true }: UseShortcutsParams) {
const [activeKeys, setActiveKeys] = useState([]); const [activeKeys, setActiveKeys] = useState<string[]>([]);
const checkShortcuts = useCallback((event, allowExecution = true) => { const checkShortcuts = useCallback((event: KeyboardEvent, allowExecution = true) => {
if (!shortcuts) if (!shortcuts)
return; return;
@ -41,7 +41,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
if (keys.includes("Alt") && !event.altKey) if (keys.includes("Alt") && !event.altKey)
removeFromArray("Alt", keys); removeFromArray("Alt", keys);
const checkGroup = (group: Record<string, string[]>, category?) => { const checkGroup = (group: Record<string, string[]>, category?: string) => {
for (const [name, shortcut] of Object.entries(group)) { for (const [name, shortcut] of Object.entries(group)) {
let active = true; let active = true;
@ -60,7 +60,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
continue; continue;
if (category != null) { if (category != null) {
options?.[category]?.[name]?.(); (options?.[category]?.[name] as Function)?.();
} else { } else {
(options?.[name] as Function)?.(); (options?.[name] as Function)?.();
} }
@ -69,7 +69,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
if (useCategories) { if (useCategories) {
for (const [category, group] of Object.entries(shortcuts)) { for (const [category, group] of Object.entries(shortcuts)) {
checkGroup(group, category); checkGroup(group as Record<string, string[]>, category);
} }
} else { } else {
checkGroup(shortcuts as Record<string, string[]>); checkGroup(shortcuts as Record<string, string[]>);
@ -78,7 +78,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
setActiveKeys(keys); setActiveKeys(keys);
}, [activeKeys, options, shortcuts, useCategories]); }, [activeKeys, options, shortcuts, useCategories]);
const onKeyDown = (event) => { const onKeyDown = (event: KeyboardEvent) => {
const isRepeated = activeKeys.includes(event.key); const isRepeated = activeKeys.includes(event.key);
checkShortcuts(event, isRepeated); checkShortcuts(event, isRepeated);
@ -86,7 +86,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
setActiveKeys(activeKeys.concat([event.key])); setActiveKeys(activeKeys.concat([event.key]));
}; };
const onKeyUp = (event) => { const onKeyUp = (event: KeyboardEvent) => {
checkShortcuts(event); checkShortcuts(event);
if (activeKeys.includes(event.key)) { if (activeKeys.includes(event.key)) {

View file

@ -1,13 +1,13 @@
import { useEffect } from "react"; import { useEffect } from "react";
/** interface UseMouseListenerParams {
* @param {object} params onMouseDown: EventListener;
* @param {Function} params.onMouseDown onMouseUp: EventListener;
* @param {Function} params.onMouseUp onClick: EventListener;
* @param {Function} params.onClick onContextMenu: EventListener;
* @param {Function} params.onContextMenu }
*/
export function useMouseListener({ onMouseDown, onMouseUp, onClick, onContextMenu }) { export function useMouseListener({ onMouseDown, onMouseUp, onClick, onContextMenu }: UseMouseListenerParams) {
useEffect(() => { useEffect(() => {
if (onMouseDown) if (onMouseDown)
document.addEventListener("mousedown", onMouseDown); document.addEventListener("mousedown", onMouseDown);

View file

@ -20,12 +20,6 @@ function useOutsideClickListener(ref: { current: HTMLElement | null }, callback:
}, [ref, callback]); }, [ref, callback]);
} }
/**
* @param {object} props
* @param {Function} props.onOutsideClick
* @param {import("react").ElementType} props.children
*/
interface OutsideClickListenerProps { interface OutsideClickListenerProps {
onOutsideClick: (event: Event) => void; onOutsideClick: (event: Event) => void;
children: ReactNode; children: ReactNode;

View file

@ -1,12 +1,9 @@
import { useEffect, useRef, useState } from "react"; import { Ref, useEffect, useRef, useState } from "react";
import { TASKBAR_HEIGHT } from "../../config/taskbar.config"; import { TASKBAR_HEIGHT } from "../../config/taskbar.config";
/** export function useScreenDimensions(): [screenWidth: number, screenHeight: number] {
* @returns {[number, number]} const [screenWidth, setScreenWidth] = useState<number>(null);
*/ const [screenHeight, setScreenHeight] = useState<number>(null);
export function useScreenDimensions() {
const [screenWidth, setScreenWidth] = useState(null);
const [screenHeight, setScreenHeight] = useState(null);
useEffect(() => { useEffect(() => {
const resizeObserver = new ResizeObserver((event) => { const resizeObserver = new ResizeObserver((event) => {
@ -20,17 +17,12 @@ export function useScreenDimensions() {
return [screenWidth, screenHeight]; return [screenWidth, screenHeight];
} }
/** export function useScreenBounds({ avoidTaskbar = true }: { avoidTaskbar: boolean; }): {
* @param {object} props ref: Ref<HTMLElement>;
* @param {boolean} props.avoidTaskbar initiated: boolean;
* @returns {{ alignLeft: boolean;
* ref, alignTop: boolean;
* initiated: boolean, } {
* alignLeft: boolean,
* alignTop: boolean
* }}
*/
export function useScreenBounds({ avoidTaskbar = true }) {
const ref = useRef(null); const ref = useRef(null);
const [initiated, setInitiated] = useState(false); const [initiated, setInitiated] = useState(false);
const [alignLeft, setAlignLeft] = useState(false); const [alignLeft, setAlignLeft] = useState(false);
@ -41,7 +33,7 @@ export function useScreenBounds({ avoidTaskbar = true }) {
if (ref.current == null || screenWidth == null || screenHeight == null) if (ref.current == null || screenWidth == null || screenHeight == null)
return; return;
const rect = ref.current.getBoundingClientRect(); const rect = (ref.current as HTMLElement).getBoundingClientRect();
const maxX = screenWidth; const maxX = screenWidth;
let maxY = screenHeight; let maxY = screenHeight;

View file

@ -6,34 +6,39 @@
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { useState } from "react"; import { useState } from "react";
/** interface UseScrollWithShadowParams {
* @param {object} options ref?: { current: HTMLElement | null };
* @param {React.ElementRef} options.ref horizontal?: boolean;
* @param {Boolean=true} options.horizontal dynamicOffset?: boolean;
* @param {Boolean=true} options.dynamicOffset dynamicOffsetFactor?: number;
* @param {Number=1} options.dynamicOffsetFactor shadow?: {
* @param {object} options.shadow offset?: number;
* @param {Number=8} options.shadow.offset blurRadius?: number;
* @param {Number=5} options.shadow.blurRadius spreadRadius?: number;
* @param {Number=-5} options.shadow.spreadRadius color?: {
* @param {object} options.shadow.color r?: number;
* @param {Number=0} options.shadow.color.r g?: number;
* @param {Number=0} options.shadow.color.g b?: number;
* @param {Number=0} options.shadow.color.b a?: number;
* @param {Number=50} options.shadow.color.a }
*/ }
export function useScrollWithShadow(options) { }
export function useScrollWithShadow(params: UseScrollWithShadowParams): {
boxShadow: string;
onUpdate: (event: Event) => void;
} {
const [initiated, setInitiated] = useState(false); const [initiated, setInitiated] = useState(false);
const [scrollStart, setScrollStart] = useState(0); const [scrollStart, setScrollStart] = useState(0);
const [scrollLength, setScrollLength] = useState(0); const [scrollLength, setScrollLength] = useState(0);
const [clientLength, setClientLength] = useState(0); const [clientLength, setClientLength] = useState(0);
if (options == null) if (params == null)
options = {}; params = {};
if (options.shadow == null) if (params.shadow == null)
options.shadow = {}; params.shadow = {};
if (options.shadow.color == null) if (params.shadow.color == null)
options.shadow.color = {}; params.shadow.color = {};
const { const {
ref, ref,
@ -51,9 +56,9 @@ export function useScrollWithShadow(options) {
a = 50 a = 50
} }
} }
} = options; } = params;
const updateValues = useCallback((element) => { const updateValues = useCallback((element: HTMLElement) => {
if (!element) if (!element)
return; return;
@ -62,8 +67,8 @@ export function useScrollWithShadow(options) {
setClientLength(horizontal ? element.clientWidth : element.clientHeight); setClientLength(horizontal ? element.clientWidth : element.clientHeight);
}, [horizontal]); }, [horizontal]);
const onUpdate = (event) => { const onUpdate = (event: Event) => {
updateValues(event.target); updateValues(event.target as HTMLElement);
}; };
useEffect(() => { useEffect(() => {

View file

@ -1,4 +1,4 @@
import { FC, useCallback } from "react"; import { FC, MouseEvent, useCallback } from "react";
import Vector2 from "../../features/math/vector2"; import Vector2 from "../../features/math/vector2";
import Modal from "../../features/modals/modal"; import Modal from "../../features/modals/modal";
import { ActionsProps, STYLES } from "../../components/actions/Actions"; import { ActionsProps, STYLES } from "../../components/actions/Actions";
@ -13,12 +13,12 @@ export function useContextMenu({ Actions }: UseContextMenuParams) {
const modalsManager = useModalsManager(); const modalsManager = useModalsManager();
// Open a new modal when context menu is triggered // Open a new modal when context menu is triggered
const onContextMenu = useCallback((event, params = {}) => { const onContextMenu = useCallback((event: MouseEvent<HTMLElement>, params: object = {}) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
let positionX = (event?.clientX ?? 0); let positionX = event?.clientX ?? 0;
let positionY = (event?.clientY ?? 0); let positionY = event?.clientY ?? 0;
if (modalsManager.containerRef?.current) { if (modalsManager.containerRef?.current) {
const containerRect = modalsManager.containerRef.current.getBoundingClientRect(); const containerRect = modalsManager.containerRef.current.getBoundingClientRect();

View file

@ -1,7 +1,6 @@
import { createContext, FC, ReactNode, useContext } from "react"; import { createContext, FC, ReactNode, useContext } from "react";
import { SettingsManager } from "../../features/settings/settingsManager"; import { SettingsManager } from "../../features/settings/settingsManager";
import { useVirtualRoot } from "../virtual-drive/virtualRootContext"; import { useVirtualRoot } from "../virtual-drive/virtualRootContext";
import { VirtualRoot } from "../../features/virtual-drive/root/virtualRoot";
type SettingsManagerState = SettingsManager | undefined; type SettingsManagerState = SettingsManager | undefined;
@ -12,7 +11,7 @@ const SettingsManagerContext = createContext<SettingsManagerState>(undefined);
*/ */
export const SettingsManagerProvider: FC<{ children: ReactNode }> = ({ children }) => { export const SettingsManagerProvider: FC<{ children: ReactNode }> = ({ children }) => {
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const settingsManager = new SettingsManager(virtualRoot as VirtualRoot); const settingsManager = new SettingsManager(virtualRoot);
return ( return (
<SettingsManagerContext.Provider value={settingsManager}> <SettingsManagerContext.Provider value={settingsManager}>

View file

@ -11,9 +11,6 @@ export const VirtualRootProvider: FC<{ children: ReactNode }> = ({ children }) =
</VirtualRootContext.Provider>; </VirtualRootContext.Provider>;
}; };
/** export function useVirtualRoot(): VirtualRoot {
* @returns {VirtualRoot}
*/
export function useVirtualRoot() {
return useContext(VirtualRootContext); return useContext(VirtualRootContext);
} }

View file

@ -2,7 +2,12 @@ import { useEffect, useState } from "react";
import { useZIndexManager } from "./zIndexManagerContext"; import { useZIndexManager } from "./zIndexManagerContext";
import { ZIndexManager } from "../../features/z-index/zIndexManager"; import { ZIndexManager } from "../../features/z-index/zIndexManager";
export function useZIndex({ groupIndex, index }) { interface UseZIndexParams {
groupIndex: number;
index: number;
}
export function useZIndex({ groupIndex, index }: UseZIndexParams) {
const initialIndex = (groupIndex * 10) + index; const initialIndex = (groupIndex * 10) + index;
const [zIndex, setZIndex] = useState(initialIndex); const [zIndex, setZIndex] = useState(initialIndex);
const zIndexManager = useZIndexManager(); const zIndexManager = useZIndexManager();

View file

@ -4,11 +4,14 @@ import "./styles/global.css";
import App from "./App"; import App from "./App";
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from "./reportWebVitals";
import { ASCII_LOGO, NAME } from "./config/branding.config"; import { ASCII_LOGO, NAME } from "./config/branding.config";
import { TrackingManager } from "./features/tracking/trackingManager";
export const START_DATE = new Date(); export const START_DATE = new Date();
TrackingManager.initialize();
// Render app // Render app
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<React.StrictMode> root.render(<React.StrictMode>
<App/> <App/>
</React.StrictMode>); </React.StrictMode>);
@ -22,4 +25,4 @@ console.info(ASCII_LOGO + space + welcomeMessage);
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log)) // to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals(console.log); reportWebVitals(() => {});

View file

@ -1,6 +1,8 @@
const reportWebVitals = (onPerfEntry) => { import { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { void import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry); getCLS(onPerfEntry);
getFID(onPerfEntry); getFID(onPerfEntry);
getFCP(onPerfEntry); getFCP(onPerfEntry);