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(
eslint.configs.recommended,
// ...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
@ -53,7 +53,13 @@ export default tseslint.config(
}
],
"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": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/js": "^9.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
@ -4623,6 +4624,15 @@
"@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": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",

View file

@ -43,6 +43,7 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/js": "^9.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^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 { FC, useEffect } from "react";
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext";
import { TrackingManager } from "./features/tracking/trackingManager";
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext";
TrackingManager.initialize();
const App: FC = () => {
useEffect(() => {
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 styles from "./Actions.module.css";
import { useScreenBounds } from "../../hooks/_utils/screen";
@ -11,17 +11,17 @@ export const STYLES = {
export interface ActionProps {
actionId?: string;
label?: string;
icon?: string|object;
icon?: string | object;
shortcut?: string[];
onTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void;
onTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
children?: ReactNode;
}
export interface ActionsProps {
className?: string;
onAnyTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void;
onAnyTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
children?: ReactNode;
triggerParams?: any;
triggerParams?: unknown;
avoidTaskbar?: boolean;
}
@ -44,7 +44,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
const options = {};
const shortcuts = {};
const iterateOverChildren = (children: ReactNode) => {
const iterateOverChildren = (children: ReactNode): ReactNode => {
let actionId = 0;
const newChildren = Children.map(children, (child) => {
if (!isValidElement(child))
@ -52,7 +52,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
actionId++;
const { label, shortcut, onTrigger } = child.props;
const { label, shortcut, onTrigger } = child.props as ActionProps;
if (label != null && onTrigger != null) {
options[actionId] = onTrigger;
@ -61,19 +61,18 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
}
if (isListener) {
iterateOverChildren(child.props.children);
return;
return iterateOverChildren((child.props as ActionProps).children);
}
return cloneElement(child, {
...child.props,
actionId,
children: iterateOverChildren(child.props.children),
children: iterateOverChildren((child.props as ActionProps).children),
onTrigger: (event, ...args) => {
onAnyTrigger?.(event, triggerParams, ...args);
onTrigger?.(event, triggerParams, ...args);
}
});
} as ActionProps);
});
return newChildren;
@ -82,7 +81,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
useShortcuts({ options, shortcuts, useCategories: false });
if (isListener)
return iterateOverChildren(children);
return iterateOverChildren(children) as ReactElement;
const classNames = [styles.Container];
if (className != null)
@ -94,7 +93,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
if (!initiated)
classNames.push(styles.Uninitiated);
return <div ref={ref} className={classNames.join(" ")}>
return <div ref={ref as Ref<HTMLDivElement>} className={classNames.join(" ")}>
{iterateOverChildren(children)}
</div>;
}

View file

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

View file

@ -5,9 +5,9 @@ import { WindowProps } from "../../windows/WindowView";
export function Calculator({ active }: WindowProps) {
const [input, setInput] = useState("0");
const [firstNumber, setFirstNumber] = useState(null);
const [secondNumber, setSecondNumber] = useState(null);
const [operation, setOperation] = useState(null);
const [firstNumber, setFirstNumber] = useState<number>(null);
const [secondNumber, setSecondNumber] = useState<number>(null);
const [operation, setOperation] = useState<string>(null);
const [isIntermediate, setIsIntermediate] = useState(false);
const reset = useCallback(() => {
@ -17,11 +17,11 @@ export function Calculator({ active }: WindowProps) {
setOperation(null);
}, []);
const addInput = useCallback((string) => {
const addInput = useCallback((string: string) => {
let hasReset = false;
if (secondNumber != null) {
if (isIntermediate) {
setFirstNumber(input);
setFirstNumber(parseFloat(input));
setSecondNumber(null);
setInput(null);
} else {
@ -54,12 +54,10 @@ export function Calculator({ active }: WindowProps) {
}, [input, isIntermediate, reset, secondNumber]);
const calculate = useCallback((intermediate = false) => {
if (firstNumber == null) {
if (firstNumber != null) {
setSecondNumber(parseFloat(input));
} else {
setSecondNumber(input);
const a = parseFloat(firstNumber);
const a = firstNumber;
const b = parseFloat(input);
let result = 0;
@ -84,11 +82,11 @@ export function Calculator({ active }: WindowProps) {
setIsIntermediate(intermediate);
}, [firstNumber, input, operation]);
const changeOperation = useCallback((operation) => {
const changeOperation = useCallback((operation: string) => {
if (firstNumber != null && secondNumber == null) {
calculate(true);
} else {
setFirstNumber(input);
setFirstNumber(parseFloat(input));
setSecondNumber(null);
setInput(null);
}
@ -97,7 +95,7 @@ export function Calculator({ active }: WindowProps) {
}, [calculate, firstNumber, input, secondNumber]);
useEffect(() => {
const onKeyDown = (event) => {
const onKeyDown = (event: KeyboardEvent) => {
if (!active)
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 styles from "./FileExplorer.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -40,10 +40,10 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
const virtualRoot = useVirtualRoot();
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 [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 { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
@ -56,29 +56,29 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
}
file.open(windowsManager);
}}/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file: VirtualFile) => {
file.delete();
}}/>
<ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file) => {
<ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file: VirtualFile) => {
openWindowedModal({
title: `${file.id} ${TITLE_SEPARATOR} Properties`,
iconUrl: file.getIconUrl(),
size: new Vector2(400, 500),
Modal: (props) => <FileProperties file={file} {...props}/>
Modal: (props: object) => <FileProperties file={file} {...props}/>
});
}}/>
</Actions>
});
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, folder) => {
<ClickAction label="Open" onTrigger={(event, folder: VirtualFolder & VirtualFolderLink) => {
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 });
}}/>
<Divider/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => {
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder: VirtualFolder) => {
folder.delete();
}}/>
</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)
return;
@ -119,12 +119,12 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
}
}, [history, stateIndex, virtualRoot]);
const onPathChange = (event) => {
setPath(event.target.value);
const onPathChange = (event: Event) => {
setPath((event.target as HTMLInputElement).value);
};
const onKeyDown = (event) => {
let value = event.target.value;
const onKeyDown = (event: KeyboardEvent) => {
let value = (event.target as HTMLInputElement).value;
if (event.key === "Enter") {
if (value === "")
@ -160,7 +160,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
title="Back"
tabIndex={0}
className={styles["Icon-button"]}
onClick={undo}
onClick={() => { undo(); }}
disabled={!undoAvailable}
>
<FontAwesomeIcon icon={faCaretLeft}/>
@ -169,7 +169,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
title="Forward"
tabIndex={0}
className={styles["Icon-button"]}
onClick={redo}
onClick={() => { redo(); }}
disabled={!redoAvailable}
>
<FontAwesomeIcon icon={faCaretRight}/>
@ -215,8 +215,8 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
aria-label="Path"
className={styles["Path-input"]}
tabIndex={0}
onChange={onPathChange}
onKeyDown={onKeyDown}
onChange={onPathChange as unknown as ChangeEventHandler}
onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
placeholder="Enter a path..."
/>
<button title="Search" tabIndex={0} className={styles["Icon-button"]}>
@ -241,7 +241,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
onOpenFile={(event, file) => {
(event as Event).preventDefault();
if (isSelector)
return onSelectionFinish?.();
return void onSelectionFinish?.();
const options: Record<string, string> = {};
if (file.extension === "md" || CODE_FORMATS.includes(file.extension))
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 { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
import { Interactable } from "../../../_utils/interactable/Interactable";
import styles from "./DirectoryList.module.css";
import { ImagePreview } from "./ImagePreview";
import Vector2 from "../../../../features/math/vector2";
export interface OnSelectionChangeParams {
files?: string[];
@ -26,17 +27,17 @@ interface DirectoryListProps {
onOpenFolder?: FolderEventHandler;
allowMultiSelect?: boolean;
onSelectionChange?: (params: OnSelectionChangeParams) => void;
[key: string]: any;
[key: string]: unknown;
}
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactElement {
const [selectedFolders, setSelectedFolders] = useState([]);
const [selectedFiles, setSelectedFiles] = useState([]);
const [selectedFolders, setSelectedFolders] = useState<string[]>([]);
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
const ref = useRef(null);
const [rectSelectStart, setRectSelectStart] = useState(null);
const [rectSelectEnd, setRectSelectEnd] = useState(null);
const [rectSelectStart, setRectSelectStart] = useState<Vector2>(null);
const [rectSelectEnd, setRectSelectEnd] = useState<Vector2>(null);
useEffect(() => {
onSelectionChange?.({ files: selectedFiles, folders: selectedFolders, directory });
@ -47,14 +48,14 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
}, [directory]);
useEffect(() => {
const onMoveRectSelect = (event) => {
const onMoveRectSelect = (event: MouseEvent) => {
if (rectSelectStart == null)
return;
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) {
setRectSelectStart(null);
setRectSelectEnd(null);
@ -82,14 +83,14 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
setSelectedFolders([]);
setSelectedFiles([]);
};
const selectFolder = (folder, exclusive = false) => {
const selectFolder = (folder: VirtualFolder, exclusive = false) => {
if (!allowMultiSelect)
exclusive = true;
setSelectedFolders(exclusive ? [folder.id] : [...selectedFolders, folder.id]);
if (exclusive)
setSelectedFiles([]);
};
const selectFile = (file, exclusive = false) => {
const selectFile = (file: VirtualFile, exclusive = false) => {
if (!allowMultiSelect)
exclusive = true;
setSelectedFiles(exclusive ? [file.id] : [...selectedFiles, file.id]);
@ -97,12 +98,12 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
setSelectedFolders([]);
};
const onStartRectSelect = (event) => {
setRectSelectStart({ x: event.clientX, y: event.clientY });
const onStartRectSelect = (event: MouseEvent) => {
setRectSelectStart({ x: event.clientX, y: event.clientY } as Vector2);
};
const getRectSelectStyle = () => {
let x, y, width, height = null;
const containerRect = ref.current?.getBoundingClientRect();
let x: number, y: number, width: number, height: number = null;
const containerRect = (ref.current as HTMLElement)?.getBoundingClientRect();
if (rectSelectStart.x < rectSelectEnd.x) {
x = rectSelectStart.x;
@ -143,7 +144,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
ref={ref}
className={classNames.join(" ")}
onClick={clearSelection}
onMouseDown={onStartRectSelect}
onMouseDown={onStartRectSelect as unknown as MouseEventHandler}
{...props}
>
{rectSelectStart != null && rectSelectEnd != null
@ -156,13 +157,13 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
tabIndex={0}
className={folderClassNames.join(" ")}
data-selected={selectedFolders.includes(folder.id)}
onContextMenu={(event) => {
onContextMenu={(event: MouseEvent) => {
onContextMenuFolder?.(event, folder);
}}
onClick={(event) => {
onClick={(event: MouseEvent) => {
selectFolder(folder, !event.ctrlKey);
}}
onDoubleClick={(event) => {
onDoubleClick={(event: MouseEvent) => {
onOpenFolder?.(event, folder);
}}
>
@ -178,13 +179,13 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
tabIndex={0}
className={fileClassNames.join(" ")}
data-selected={selectedFiles.includes(file.id)}
onContextMenu={(event) => {
onContextMenu={(event: MouseEvent) => {
onContextMenuFile?.(event, file);
}}
onClick={(event) => {
onClick={(event: MouseEvent) => {
selectFile(file, !event.ctrlKey);
}}
onDoubleClick={(event) => {
onDoubleClick={(event: MouseEvent) => {
onOpenFile?.(event, file);
}}
>

View file

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

View file

@ -10,8 +10,15 @@ import { ClickAction } from "../../../actions/actions/ClickAction";
import { removeFromArray } from "../../../../features/_utils/array.utils";
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
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 settingsManager = useSettingsManager();
@ -29,7 +36,7 @@ export function AppOption({ app, pins, setPins }) {
}
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
settings.set("pins", newPins.join(","));
void settings.set("pins", newPins.join(","));
}}/>
</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 styles from "../Settings.module.css";
import utilStyles from "../../../../styles/utils.module.css";
@ -17,17 +17,17 @@ import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtual
export function AppearanceSettings() {
const virtualRoot = useVirtualRoot();
const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null);
const [wallpaper, setWallpaper] = useState<string>(null);
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
const { openWindowedModal } = useWindowedModal();
useEffect(() => {
settings.get("wallpaper", setWallpaper);
void settings.get("wallpaper", setWallpaper);
}, [settings]);
const onChange = (event) => {
const value = event.target.value;
settings.set("wallpaper", value);
const onChange = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
void settings.set("wallpaper", value);
};
return (<>
@ -42,7 +42,7 @@ export function AppearanceSettings() {
type={SELECTOR_MODE.SINGLE}
allowedFormats={IMAGE_FORMATS}
onFinish={(file: VirtualFile) => {
settings.set("wallpaper", file.source);
void settings.set("wallpaper", file.source);
}}
{...props}
/>
@ -59,7 +59,7 @@ export function AppearanceSettings() {
value={source}
aria-label="Wallpaper image"
checked={source === wallpaper}
onChange={onChange}
onChange={onChange as unknown as ChangeEventHandler}
tabIndex={0}
/>
<img src={source} alt={id} draggable="false"/>

View file

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

View file

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

View file

@ -21,7 +21,7 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
return (
<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 aria-hidden="true">{value}</span>
<input

View file

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

View file

@ -24,10 +24,11 @@ import { Share } from "../modals/share/Share";
import ModalsManager from "../../features/modals/modalsManager";
import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder";
import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink";
import { VirtualFile } from "../../features/virtual-drive/file/virtualFile";
export const Desktop = memo(() => {
const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null);
const [wallpaper, setWallpaper] = useState<string>(null);
const windowsManager = useWindowsManager();
const virtualRoot = useVirtualRoot();
const [showIcons, setShowIcons] = useState(false);
@ -41,16 +42,16 @@ export const Desktop = memo(() => {
<DropdownAction label="View" icon={faEye}>
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value: string) => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.set("icon-size", value);
void settings.set("icon-size", value);
}} options={[
{ label: "Small icons" },
{ label: "Medium icons" },
{ label: "Large icons" }
]}/>
<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);
settings.set("show-icons", (!showIcons).toString());
void settings.set("show-icons", (!showIcons).toString());
}}/>
</DropdownAction>
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
@ -77,56 +78,54 @@ export const Desktop = memo(() => {
});
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, file) => {
<ClickAction label="Open" onTrigger={(event, file: VirtualFile) => {
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);
}}/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file: VirtualFile) => {
file.delete();
}}/>
</Actions>
});
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, folder) => {
<ClickAction label="Open" onTrigger={(event, folder: VirtualFolder) => {
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 });
}}/>
<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);
}}/>
<Divider/>
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => {
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder: VirtualFolder) => {
folder.delete();
}}/>
</Actions>
});
useEffect(() => {
(async () => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.get("wallpaper", setWallpaper);
settings.get("show-icons", (value) => {
if (value != null) {
setShowIcons(value === "true");
} else {
setShowIcons(true);
}
});
settings.get("icon-size", (value) => {
if (isValidInteger(value))
setIconSize(parseInt(value));
});
})();
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
void settings.get("wallpaper", setWallpaper);
void settings.get("show-icons", (value) => {
if (value != null) {
setShowIcons(value === "true");
} else {
setShowIcons(true);
}
});
void settings.get("icon-size", (value) => {
if (isValidInteger(value))
setIconSize(parseInt(value));
});
}, [settingsManager]);
const onError = () => {
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;
@ -148,7 +147,7 @@ export const Desktop = memo(() => {
onOpenFile={(event, file) => {
(event as Event).preventDefault();
const options: Record<string, any> = {};
const options: Record<string, unknown> = {};
if (file.name === "info.md")
options.size = new Vector2(575, 675);
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 OutsideClickListener from "../../hooks/_utils/outsideClick";
import styles from "./ModalView.module.css";
import { useEffect } from "react";
export interface ModalProps {
modal: Modal;
params?: Record<string, any>;
modal?: Modal;
params?: {
appId?: string;
fullscreen?: boolean;
iconUrl?: string;
title?: string;
[key: string]: unknown;
};
children?: ReactNode;
onFinish?: Function;
[key: string]: any;
[key: string]: unknown;
}
export const ModalView: FC<ModalProps> = memo(({ modal }) => {
useEffect(() => {
const onDismiss = (event) => {
if (event.key === "Escape")
const onDismiss = (event: Event) => {
if ((event as unknown as KeyboardEvent).key === "Escape")
modal.close();
};

View file

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

View file

@ -7,15 +7,8 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
import Draggable from "react-draggable";
import { ReactSVG } from "react-svg";
import utilStyles from "../../../styles/utils.module.css";
import Modal from "../../../features/modals/modal";
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) {
const { iconUrl, title } = params;

View file

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

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { ChangeEventHandler, useEffect, useState } from "react";
import ModalsManager from "../../../features/modals/modalsManager";
import { WindowedModal } from "../_utils/WindowedModal";
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 { ModalProps } from "../ModalView";
const APP_OPTIONS = {
const APP_OPTIONS: Record<string, Record<string, string>[]> = {
"terminal": [
{
label: "Command",
@ -35,10 +35,10 @@ const APP_OPTIONS = {
};
export function Share({ modal, params, ...props }: ModalProps) {
const [appId, setAppId] = useState(params.appId ?? "");
const [fullscreen, setFullscreen] = useState(params.fullscreen ?? false);
const [appId, setAppId] = useState<string>(params.appId ?? "");
const [fullscreen, setFullscreen] = useState<boolean>(params.fullscreen ?? false);
const [options, setOptions] = useState({});
const [url, setUrl] = useState(null);
const [url, setUrl] = useState<string | null>(null);
const { alert } = useAlert();
useEffect(() => {
@ -49,8 +49,8 @@ export function Share({ modal, params, ...props }: ModalProps) {
}));
}, [appId, fullscreen, options]);
const onAppIdChange = (event) => {
const newAppId = event.target.value;
const onAppIdChange = (event: Event) => {
const newAppId = (event.target as HTMLInputElement).value;
if (newAppId === appId)
return;
@ -69,12 +69,12 @@ export function Share({ modal, params, ...props }: ModalProps) {
}
};
const onFullscreenChange = (event) => {
const newFullscreen = event.target.checked;
const onFullscreenChange = (event: Event) => {
const newFullscreen = (event.target as HTMLInputElement).checked;
setFullscreen(newFullscreen);
};
const setOption = (name, value) => {
const setOption = (name: string, value: string) => {
setOptions((options = {}) => {
options = { ...options };
options[name] = value;
@ -92,7 +92,7 @@ export function Share({ modal, params, ...props }: ModalProps) {
<form className={styles.Form}>
<label className={styles.Label}>
<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>
{AppsManager.APPS.map(({ name, id }) =>
<option key={id} value={id}>{name}</option>
@ -101,7 +101,14 @@ export function Share({ modal, params, ...props }: ModalProps) {
</label>
{appId !== "" ? <label className={styles.Label}>
<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}>
{fullscreen
? <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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
@ -78,12 +78,12 @@ export const Taskbar = memo(() => {
useEffect(() => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
settings.get("pins", (pins) => {
void settings.get("pins", (pins) => {
setPins(pins.split(","));
});
}, [settingsManager]);
const updateShowHome = (show) => {
const updateShowHome = (show: boolean) => {
setShowHome(show);
if (show) {
@ -92,7 +92,7 @@ export const Taskbar = memo(() => {
}
};
const updateShowSearch = (show) => {
const updateShowSearch = (show: boolean) => {
setShowSearch(show);
if (show) {
@ -104,7 +104,7 @@ export const Taskbar = memo(() => {
setHideUtilMenus(true);
if (inputRef.current) {
inputRef.current.focus();
(inputRef.current as HTMLElement).focus();
window.scrollTo(0, document.body.scrollHeight);
}
} else {
@ -122,7 +122,7 @@ export const Taskbar = memo(() => {
setHideUtilMenus(false);
};
const search = (query) => {
const search = (_query: string) => {
updateShowSearch(true);
};
@ -174,8 +174,8 @@ export const Taskbar = memo(() => {
<div
className={styles["App-icons-inner"]}
data-allow-context-menu={true}
onScroll={onUpdate}
onResize={onUpdate}
onScroll={onUpdate as unknown as UIEventHandler}
onResize={onUpdate as unknown as ReactEventHandler}
ref={ref}
>
{apps}

View file

@ -38,7 +38,7 @@ export const AppButton: FC<AppButtonProps> = memo(({ app, windowsManager, pins,
}
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={() => {
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 { UtilMenu } from "../menus/UtilMenu";
/**
* @param {object} props
* @param {boolean} props.hideUtilMenus
* @param {Function} props.showUtilMenu
*/
export function Calendar({ hideUtilMenus, showUtilMenu }) {
interface CalendarProps {
hideUtilMenus: boolean;
showUtilMenu: Function;
}
export function Calendar({ hideUtilMenus, showUtilMenu }: CalendarProps) {
const [date, setDate] = useState(new Date());
const [showMenu, setShowMenu] = useState(false);
@ -28,7 +28,7 @@ export function Calendar({ hideUtilMenus, showUtilMenu }) {
}
}, [hideUtilMenus, showMenu]);
const updateShowMenu = (show) => {
const updateShowMenu = (show: boolean) => {
if (show)
showUtilMenu();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,13 +6,13 @@ import App from "../../features/apps/app";
export interface WindowFallbackViewProps {
error?: Error;
resetErrorBoundary?: any;
resetErrorBoundary?: unknown;
app?: App;
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
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 [alerted, setAlerted] = useState(false);

View file

@ -4,7 +4,7 @@ import { faExpand, faMinus, faWindowMaximize as fasWindowMaximize, faTimes, faXm
import { ReactSVG } from "react-svg";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
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 { faWindowMaximize } from "@fortawesome/free-regular-svg-icons";
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);
};
const focus = (event, force = false) => {
if (force)
return onInteract();
const focus = (event: Event, force = false): void => {
if (force) {
onInteract();
return;
}
if (event?.defaultPrevented)
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();
};
@ -128,7 +131,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
if (minimized)
classNames.push(styles.Minimized);
return (<div style={{ zIndex, position: !maximized ? "relative" : null }}>
return (<div style={{ zIndex, position: !maximized ? "relative" : null } as CSSProperties}>
<ShortcutsListener/>
<Draggable
key={id}
@ -146,13 +149,13 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
cancel="button"
nodeRef={nodeRef}
disabled={maximized}
onStart={(event) => { focus(event); }}
onStart={(event) => { focus(event as Event); }}
grid={[1, 1]}
>
<div
className={classNames.join(" ")}
ref={nodeRef}
onClick={focus}
onClick={focus as unknown as MouseEventHandler}
>
<div
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) => {
setMaximized(!maximized);
focus(event, true);
focus(event as unknown as Event, true);
}}>
<ReactSVG
className={styles["Window-icon"]}
@ -179,20 +182,20 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
onClick={(event) => {
event.preventDefault();
setMaximized(!maximized);
focus(event, true);
focus(event as unknown as Event, true);
}}
>
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
</button>
<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}/>
</button>
</div>
<div className={styles["Window-content"]}>
<ErrorBoundary
FallbackComponent={(props) => <WindowFallbackView app={app} closeWindow={close} {...props}/>}
onReset={(details) => {
onReset={() => {
// Reset the state of your app so the error doesn't happen again
}}
onError={(error) => {

View file

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

View file

@ -12,7 +12,7 @@ export const CODE_FORMATS = [
"yml"
];
export const EXTENSION_TO_LANGUAGE = {
export const EXTENSION_TO_LANGUAGE: Record<string, string> = {
"js": "javascript",
"jsx": "javascript",
"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);
if (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)];
}
export function removeDuplicatesFromArray(array: any[]): any[] {
export function removeDuplicatesFromArray<Type>(array: Type[]): Type[] {
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
*/
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)
return result.join(", ");
@ -44,8 +44,8 @@ export function formatTime(time: number, maxLength: number = 3, allowAffixes: bo
return formatResult(["less than a second"], inPast);
}
const units = [];
const unitLabels = {
const units: { amount: number, label: string }[] = [];
const unitLabels: Record<string, string> = {
"s": "seconds",
"m": "minutes",
"h": "hours",

View file

@ -8,7 +8,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
/**
* 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]) {
this.#events[eventName as string] = [];
}
@ -18,7 +18,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
/**
* 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]) {
this.#events[eventName as string] = this.#events[eventName as string].filter(
(listener) => listener !== callback
@ -29,7 +29,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
/**
* 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]) {
this.#events[eventName as string].forEach((listener) => {
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);
}

View file

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

View file

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

View file

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

View file

@ -44,7 +44,7 @@ export class Settings {
this.xmlDoc = xmlDoc;
}
async write() {
write() {
if (!this.file)
return;
@ -76,14 +76,16 @@ export class Settings {
if (callback) {
callback(value);
this.file.on(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, async () => {
await this.read();
const newValue = await this.get(key);
this.file.on(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, () => {
void (async () => {
await this.read();
const newValue = await this.get(key);
if (newValue !== value) {
callback(newValue);
value = newValue;
}
if (newValue !== value) {
callback(newValue);
value = newValue;
}
})();
});
}
@ -102,6 +104,6 @@ export class Settings {
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 { GA_MEASUREMENT_ID } from "../../config/tracking.config";
import { UaEventOptions } from "react-ga4/types/ga4";
export class TrackingManager {
static initialize() {
@ -7,8 +8,7 @@ export class TrackingManager {
ReactGA.initialize(GA_MEASUREMENT_ID);
}
/** @type {ReactGA["event"]} */
static event(options) {
static event(options: UaEventOptions | string) {
console.info(options);
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
*/
setContent(content: string | any) {
setContent(content: string) {
if (this.content === content || !this.canBeEdited)
return;
@ -106,7 +106,7 @@ export class VirtualFile extends VirtualBase {
).catch((error) => {
console.error(`Error while reading file with id "${this.id}":`, error);
return null;
});
}) as string;
}
isFile(): boolean {
@ -143,7 +143,7 @@ export class VirtualFile extends VirtualBase {
break;
}
return iconUrl;
return iconUrl as 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
*/
findFile(name: string, extension?: string): VirtualFile | VirtualFileLink {
let resultFile = null;
let resultFile: VirtualFile | VirtualFileLink = null;
this.files.forEach((file) => {
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
*/
findSubFolder(name: string): VirtualFolder | VirtualFolderLink {
let resultFolder = null;
let resultFolder: VirtualFolder | VirtualFolderLink = null;
this.subFolders.forEach((folder) => {
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
*/
remove(child: VirtualFile | VirtualFolder | VirtualFolderLink) {
remove(child: VirtualFile | VirtualFileLink | VirtualFolder | VirtualFolderLink) {
if (!this.canBeEdited)
return;
@ -241,9 +241,9 @@ export class VirtualFolder extends VirtualBase {
*/
navigate(relativePath: string): VirtualFile | VirtualFolder | null {
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 === "") {
return this.getRoot();
} else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) {

View file

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

View file

@ -46,7 +46,7 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
return this;;
this.alias = alias;
this.getRoot().addShortcut(alias, this as any);
this.getRoot().addShortcut(alias, this as never);
this.confirmChanges();
return this;
@ -100,7 +100,7 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
if (parent == null)
return;
parent.remove?.(this as any);
parent.remove?.(this as never);
this.confirmChanges(parent.getRoot());
}
@ -114,9 +114,11 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
root?.saveData();
}
open(..._args: any[]): any {}
open(..._args: unknown[]): unknown {
return null;
};
get path() {
get path(): string {
return this.alias ?? this.displayPath;
}
@ -177,4 +179,13 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
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;
lastInteraction?: number;
minimized?: boolean;
[key: string]: any;
[key: string]: unknown;
}
export default class WindowsManager {
@ -174,8 +174,8 @@ export default class WindowsManager {
return active;
}
getAppWindowId(appId: string): string {
let windowId = null;
getAppWindowId(appId: string): string | null {
let windowId: string | null = null;
Object.values(this.windows).forEach((window) => {
if (window.app.id === appId) {

View file

@ -14,8 +14,7 @@ export class ZIndexManager extends EventEmitter<typeof ZIndexManagerEvents> {
static EVENT_NAMES = ZIndexManagerEvents;
/** @type {ZIndexGroup[]} */
groups = [];
groups: ZIndexGroup[] = [];
constructor() {
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);
}
}

View file

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

View file

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

View file

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

View file

@ -20,12 +20,6 @@ function useOutsideClickListener(ref: { current: HTMLElement | null }, callback:
}, [ref, callback]);
}
/**
* @param {object} props
* @param {Function} props.onOutsideClick
* @param {import("react").ElementType} props.children
*/
interface OutsideClickListenerProps {
onOutsideClick: (event: Event) => void;
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";
/**
* @returns {[number, number]}
*/
export function useScreenDimensions() {
const [screenWidth, setScreenWidth] = useState(null);
const [screenHeight, setScreenHeight] = useState(null);
export function useScreenDimensions(): [screenWidth: number, screenHeight: number] {
const [screenWidth, setScreenWidth] = useState<number>(null);
const [screenHeight, setScreenHeight] = useState<number>(null);
useEffect(() => {
const resizeObserver = new ResizeObserver((event) => {
@ -20,17 +17,12 @@ export function useScreenDimensions() {
return [screenWidth, screenHeight];
}
/**
* @param {object} props
* @param {boolean} props.avoidTaskbar
* @returns {{
* ref,
* initiated: boolean,
* alignLeft: boolean,
* alignTop: boolean
* }}
*/
export function useScreenBounds({ avoidTaskbar = true }) {
export function useScreenBounds({ avoidTaskbar = true }: { avoidTaskbar: boolean; }): {
ref: Ref<HTMLElement>;
initiated: boolean;
alignLeft: boolean;
alignTop: boolean;
} {
const ref = useRef(null);
const [initiated, setInitiated] = useState(false);
const [alignLeft, setAlignLeft] = useState(false);
@ -41,7 +33,7 @@ export function useScreenBounds({ avoidTaskbar = true }) {
if (ref.current == null || screenWidth == null || screenHeight == null)
return;
const rect = ref.current.getBoundingClientRect();
const rect = (ref.current as HTMLElement).getBoundingClientRect();
const maxX = screenWidth;
let maxY = screenHeight;

View file

@ -6,34 +6,39 @@
import { useCallback, useEffect } from "react";
import { useState } from "react";
/**
* @param {object} options
* @param {React.ElementRef} options.ref
* @param {Boolean=true} options.horizontal
* @param {Boolean=true} options.dynamicOffset
* @param {Number=1} options.dynamicOffsetFactor
* @param {object} options.shadow
* @param {Number=8} options.shadow.offset
* @param {Number=5} options.shadow.blurRadius
* @param {Number=-5} options.shadow.spreadRadius
* @param {object} options.shadow.color
* @param {Number=0} options.shadow.color.r
* @param {Number=0} options.shadow.color.g
* @param {Number=0} options.shadow.color.b
* @param {Number=50} options.shadow.color.a
*/
export function useScrollWithShadow(options) {
interface UseScrollWithShadowParams {
ref?: { current: HTMLElement | null };
horizontal?: boolean;
dynamicOffset?: boolean;
dynamicOffsetFactor?: number;
shadow?: {
offset?: number;
blurRadius?: number;
spreadRadius?: number;
color?: {
r?: number;
g?: number;
b?: number;
a?: number;
}
}
}
export function useScrollWithShadow(params: UseScrollWithShadowParams): {
boxShadow: string;
onUpdate: (event: Event) => void;
} {
const [initiated, setInitiated] = useState(false);
const [scrollStart, setScrollStart] = useState(0);
const [scrollLength, setScrollLength] = useState(0);
const [clientLength, setClientLength] = useState(0);
if (options == null)
options = {};
if (options.shadow == null)
options.shadow = {};
if (options.shadow.color == null)
options.shadow.color = {};
if (params == null)
params = {};
if (params.shadow == null)
params.shadow = {};
if (params.shadow.color == null)
params.shadow.color = {};
const {
ref,
@ -51,9 +56,9 @@ export function useScrollWithShadow(options) {
a = 50
}
}
} = options;
} = params;
const updateValues = useCallback((element) => {
const updateValues = useCallback((element: HTMLElement) => {
if (!element)
return;
@ -62,8 +67,8 @@ export function useScrollWithShadow(options) {
setClientLength(horizontal ? element.clientWidth : element.clientHeight);
}, [horizontal]);
const onUpdate = (event) => {
updateValues(event.target);
const onUpdate = (event: Event) => {
updateValues(event.target as HTMLElement);
};
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 Modal from "../../features/modals/modal";
import { ActionsProps, STYLES } from "../../components/actions/Actions";
@ -13,12 +13,12 @@ export function useContextMenu({ Actions }: UseContextMenuParams) {
const modalsManager = useModalsManager();
// 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.stopPropagation();
let positionX = (event?.clientX ?? 0);
let positionY = (event?.clientY ?? 0);
let positionX = event?.clientX ?? 0;
let positionY = event?.clientY ?? 0;
if (modalsManager.containerRef?.current) {
const containerRect = modalsManager.containerRef.current.getBoundingClientRect();

View file

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

View file

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

View file

@ -2,7 +2,12 @@ import { useEffect, useState } from "react";
import { useZIndexManager } from "./zIndexManagerContext";
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 [zIndex, setZIndex] = useState(initialIndex);
const zIndexManager = useZIndexManager();

View file

@ -4,11 +4,14 @@ import "./styles/global.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { ASCII_LOGO, NAME } from "./config/branding.config";
import { TrackingManager } from "./features/tracking/trackingManager";
export const START_DATE = new Date();
TrackingManager.initialize();
// Render app
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<React.StrictMode>
<App/>
</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
// to log results (for example: reportWebVitals(console.log))
// 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) {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
void import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);