Migration to TypeScript - Stage 3
Improved safe typing
This commit is contained in:
parent
ac322e08e0
commit
2ede410bec
64 changed files with 462 additions and 433 deletions
|
|
@ -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
10
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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"]}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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}/>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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..."
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(() => {});
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue