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(
|
export default tseslint.config(
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
// ...tseslint.configs.recommendedTypeChecked,
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
|
@ -53,7 +53,13 @@ export default tseslint.config(
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"comma-spacing": "off",
|
"comma-spacing": "off",
|
||||||
"@typescript-eslint/comma-spacing": "error"
|
"@typescript-eslint/comma-spacing": "error",
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -36,6 +36,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@eslint/js": "^9.2.0",
|
"@eslint/js": "^9.2.0",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@types/webpack-env": "^1.18.4",
|
"@types/webpack-env": "^1.18.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||||
"@typescript-eslint/parser": "^7.8.0",
|
"@typescript-eslint/parser": "^7.8.0",
|
||||||
|
|
@ -4623,6 +4624,15 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-syntax-highlighter": {
|
||||||
|
"version": "15.5.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
|
||||||
|
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@eslint/js": "^9.2.0",
|
"@eslint/js": "^9.2.0",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@types/webpack-env": "^1.18.4",
|
"@types/webpack-env": "^1.18.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||||
"@typescript-eslint/parser": "^7.8.0",
|
"@typescript-eslint/parser": "^7.8.0",
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,8 @@ import { SettingsManagerProvider } from "./hooks/settings/settingsManagerContext
|
||||||
import { ModalsView } from "./components/modals/ModalsView";
|
import { ModalsView } from "./components/modals/ModalsView";
|
||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext";
|
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext";
|
||||||
import { TrackingManager } from "./features/tracking/trackingManager";
|
|
||||||
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext";
|
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext";
|
||||||
|
|
||||||
TrackingManager.initialize();
|
|
||||||
|
|
||||||
const App: FC = () => {
|
const App: FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onContextMenu = (event: Event) => {
|
const onContextMenu = (event: Event) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Children, cloneElement, isValidElement, ReactElement, ReactNode } from "react";
|
import { Children, cloneElement, isValidElement, ReactElement, ReactNode, Ref } from "react";
|
||||||
import { useShortcuts } from "../../hooks/_utils/keyboard";
|
import { useShortcuts } from "../../hooks/_utils/keyboard";
|
||||||
import styles from "./Actions.module.css";
|
import styles from "./Actions.module.css";
|
||||||
import { useScreenBounds } from "../../hooks/_utils/screen";
|
import { useScreenBounds } from "../../hooks/_utils/screen";
|
||||||
|
|
@ -11,17 +11,17 @@ export const STYLES = {
|
||||||
export interface ActionProps {
|
export interface ActionProps {
|
||||||
actionId?: string;
|
actionId?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
icon?: string|object;
|
icon?: string | object;
|
||||||
shortcut?: string[];
|
shortcut?: string[];
|
||||||
onTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void;
|
onTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionsProps {
|
export interface ActionsProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
onAnyTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void;
|
onAnyTrigger?: (event: Event, triggerParams: unknown, ...args: unknown[]) => void;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
triggerParams?: any;
|
triggerParams?: unknown;
|
||||||
avoidTaskbar?: boolean;
|
avoidTaskbar?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
const options = {};
|
const options = {};
|
||||||
const shortcuts = {};
|
const shortcuts = {};
|
||||||
|
|
||||||
const iterateOverChildren = (children: ReactNode) => {
|
const iterateOverChildren = (children: ReactNode): ReactNode => {
|
||||||
let actionId = 0;
|
let actionId = 0;
|
||||||
const newChildren = Children.map(children, (child) => {
|
const newChildren = Children.map(children, (child) => {
|
||||||
if (!isValidElement(child))
|
if (!isValidElement(child))
|
||||||
|
|
@ -52,7 +52,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
|
|
||||||
actionId++;
|
actionId++;
|
||||||
|
|
||||||
const { label, shortcut, onTrigger } = child.props;
|
const { label, shortcut, onTrigger } = child.props as ActionProps;
|
||||||
if (label != null && onTrigger != null) {
|
if (label != null && onTrigger != null) {
|
||||||
options[actionId] = onTrigger;
|
options[actionId] = onTrigger;
|
||||||
|
|
||||||
|
|
@ -61,19 +61,18 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isListener) {
|
if (isListener) {
|
||||||
iterateOverChildren(child.props.children);
|
return iterateOverChildren((child.props as ActionProps).children);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cloneElement(child, {
|
return cloneElement(child, {
|
||||||
...child.props,
|
...child.props,
|
||||||
actionId,
|
actionId,
|
||||||
children: iterateOverChildren(child.props.children),
|
children: iterateOverChildren((child.props as ActionProps).children),
|
||||||
onTrigger: (event, ...args) => {
|
onTrigger: (event, ...args) => {
|
||||||
onAnyTrigger?.(event, triggerParams, ...args);
|
onAnyTrigger?.(event, triggerParams, ...args);
|
||||||
onTrigger?.(event, triggerParams, ...args);
|
onTrigger?.(event, triggerParams, ...args);
|
||||||
}
|
}
|
||||||
});
|
} as ActionProps);
|
||||||
});
|
});
|
||||||
|
|
||||||
return newChildren;
|
return newChildren;
|
||||||
|
|
@ -82,7 +81,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
useShortcuts({ options, shortcuts, useCategories: false });
|
useShortcuts({ options, shortcuts, useCategories: false });
|
||||||
|
|
||||||
if (isListener)
|
if (isListener)
|
||||||
return iterateOverChildren(children);
|
return iterateOverChildren(children) as ReactElement;
|
||||||
|
|
||||||
const classNames = [styles.Container];
|
const classNames = [styles.Container];
|
||||||
if (className != null)
|
if (className != null)
|
||||||
|
|
@ -94,7 +93,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
||||||
if (!initiated)
|
if (!initiated)
|
||||||
classNames.push(styles.Uninitiated);
|
classNames.push(styles.Uninitiated);
|
||||||
|
|
||||||
return <div ref={ref} className={classNames.join(" ")}>
|
return <div ref={ref as Ref<HTMLDivElement>} className={classNames.join(" ")}>
|
||||||
{iterateOverChildren(children)}
|
{iterateOverChildren(children)}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import { WindowProps } from "../../../windows/WindowView";
|
||||||
|
|
||||||
interface WebViewProps extends WindowProps {
|
interface WebViewProps extends WindowProps {
|
||||||
source: string;
|
source: string;
|
||||||
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WebView: FC<WebViewProps> = forwardRef<HTMLIFrameElement>(({ source, focus, ...props }: WebViewProps, ref) => {
|
export const WebView: FC<WebViewProps> = forwardRef<HTMLIFrameElement>(({ source, focus, ...props }: WebViewProps, ref) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { ChangeEventHandler, KeyboardEventHandler, useEffect, useRef, useState } from "react";
|
||||||
import styles from "./Browser.module.css";
|
import styles from "./Browser.module.css";
|
||||||
import { WebView } from "../_utils/web-view/WebView";
|
import { WebView } from "../_utils/web-view/WebView";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
@ -15,10 +15,10 @@ interface BrowserProps extends WindowProps {
|
||||||
export function Browser({ startUrl, focus }: BrowserProps) {
|
export function Browser({ startUrl, focus }: BrowserProps) {
|
||||||
const initialUrl = startUrl ?? HOME_URL;
|
const initialUrl = startUrl ?? HOME_URL;
|
||||||
|
|
||||||
const [url, setUrl] = useState(initialUrl);
|
const [url, setUrl] = useState<string>(initialUrl);
|
||||||
const [input, setInput] = useState(initialUrl);
|
const [input, setInput] = useState(initialUrl);
|
||||||
const { history, pushState, stateIndex, undo, redo, undoAvailable, redoAvailable } = useHistory(initialUrl);
|
const { history, pushState, stateIndex, undo, redo, undoAvailable, redoAvailable } = useHistory(initialUrl);
|
||||||
const ref = useRef(null);
|
const ref = useRef<HTMLIFrameElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (history.length === 0)
|
if (history.length === 0)
|
||||||
|
|
@ -34,7 +34,7 @@ export function Browser({ startUrl, focus }: BrowserProps) {
|
||||||
ref.current.contentWindow.location.href = url;
|
ref.current.contentWindow.location.href = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUrl = (newUrl) => {
|
const updateUrl = (newUrl: string) => {
|
||||||
if (url === newUrl) {
|
if (url === newUrl) {
|
||||||
return reload();
|
return reload();
|
||||||
}
|
}
|
||||||
|
|
@ -44,12 +44,12 @@ export function Browser({ startUrl, focus }: BrowserProps) {
|
||||||
pushState(newUrl);
|
pushState(newUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onInputChange = (event) => {
|
const onInputChange = (event: Event) => {
|
||||||
setInput(event.target.value);
|
setInput((event.target as HTMLInputElement).value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
const value = event.target.value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
|
||||||
if (event.key === "Enter" && value !== "") {
|
if (event.key === "Enter" && value !== "") {
|
||||||
if (isValidUrl(value)) {
|
if (isValidUrl(value)) {
|
||||||
|
|
@ -67,7 +67,7 @@ export function Browser({ startUrl, focus }: BrowserProps) {
|
||||||
title="Back"
|
title="Back"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={styles["Icon-button"]}
|
className={styles["Icon-button"]}
|
||||||
onClick={undo}
|
onClick={() => { undo(); }}
|
||||||
disabled={!undoAvailable}
|
disabled={!undoAvailable}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCaretLeft}/>
|
<FontAwesomeIcon icon={faCaretLeft}/>
|
||||||
|
|
@ -76,7 +76,7 @@ export function Browser({ startUrl, focus }: BrowserProps) {
|
||||||
title="Forward"
|
title="Forward"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={styles["Icon-button"]}
|
className={styles["Icon-button"]}
|
||||||
onClick={redo}
|
onClick={() => { redo(); }}
|
||||||
disabled={!redoAvailable}
|
disabled={!redoAvailable}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCaretRight}/>
|
<FontAwesomeIcon icon={faCaretRight}/>
|
||||||
|
|
@ -103,8 +103,8 @@ export function Browser({ startUrl, focus }: BrowserProps) {
|
||||||
aria-label="Search bar"
|
aria-label="Search bar"
|
||||||
className={styles["Search-bar"]}
|
className={styles["Search-bar"]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange as unknown as ChangeEventHandler}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["Bookmarks"]}>
|
<div className={styles["Bookmarks"]}>
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import { WindowProps } from "../../windows/WindowView";
|
||||||
|
|
||||||
export function Calculator({ active }: WindowProps) {
|
export function Calculator({ active }: WindowProps) {
|
||||||
const [input, setInput] = useState("0");
|
const [input, setInput] = useState("0");
|
||||||
const [firstNumber, setFirstNumber] = useState(null);
|
const [firstNumber, setFirstNumber] = useState<number>(null);
|
||||||
const [secondNumber, setSecondNumber] = useState(null);
|
const [secondNumber, setSecondNumber] = useState<number>(null);
|
||||||
const [operation, setOperation] = useState(null);
|
const [operation, setOperation] = useState<string>(null);
|
||||||
const [isIntermediate, setIsIntermediate] = useState(false);
|
const [isIntermediate, setIsIntermediate] = useState(false);
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
|
|
@ -17,11 +17,11 @@ export function Calculator({ active }: WindowProps) {
|
||||||
setOperation(null);
|
setOperation(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addInput = useCallback((string) => {
|
const addInput = useCallback((string: string) => {
|
||||||
let hasReset = false;
|
let hasReset = false;
|
||||||
if (secondNumber != null) {
|
if (secondNumber != null) {
|
||||||
if (isIntermediate) {
|
if (isIntermediate) {
|
||||||
setFirstNumber(input);
|
setFirstNumber(parseFloat(input));
|
||||||
setSecondNumber(null);
|
setSecondNumber(null);
|
||||||
setInput(null);
|
setInput(null);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54,12 +54,10 @@ export function Calculator({ active }: WindowProps) {
|
||||||
}, [input, isIntermediate, reset, secondNumber]);
|
}, [input, isIntermediate, reset, secondNumber]);
|
||||||
|
|
||||||
const calculate = useCallback((intermediate = false) => {
|
const calculate = useCallback((intermediate = false) => {
|
||||||
if (firstNumber == null) {
|
if (firstNumber != null) {
|
||||||
|
setSecondNumber(parseFloat(input));
|
||||||
|
|
||||||
} else {
|
const a = firstNumber;
|
||||||
setSecondNumber(input);
|
|
||||||
|
|
||||||
const a = parseFloat(firstNumber);
|
|
||||||
const b = parseFloat(input);
|
const b = parseFloat(input);
|
||||||
|
|
||||||
let result = 0;
|
let result = 0;
|
||||||
|
|
@ -84,11 +82,11 @@ export function Calculator({ active }: WindowProps) {
|
||||||
setIsIntermediate(intermediate);
|
setIsIntermediate(intermediate);
|
||||||
}, [firstNumber, input, operation]);
|
}, [firstNumber, input, operation]);
|
||||||
|
|
||||||
const changeOperation = useCallback((operation) => {
|
const changeOperation = useCallback((operation: string) => {
|
||||||
if (firstNumber != null && secondNumber == null) {
|
if (firstNumber != null && secondNumber == null) {
|
||||||
calculate(true);
|
calculate(true);
|
||||||
} else {
|
} else {
|
||||||
setFirstNumber(input);
|
setFirstNumber(parseFloat(input));
|
||||||
setSecondNumber(null);
|
setSecondNumber(null);
|
||||||
setInput(null);
|
setInput(null);
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +95,7 @@ export function Calculator({ active }: WindowProps) {
|
||||||
}, [calculate, firstNumber, input, secondNumber]);
|
}, [calculate, firstNumber, input, secondNumber]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (!active)
|
if (!active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { FC, useCallback, useEffect, useState } from "react";
|
import { ChangeEventHandler, FC, KeyboardEventHandler, useCallback, useEffect, useState } from "react";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
|
||||||
import styles from "./FileExplorer.module.css";
|
import styles from "./FileExplorer.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
@ -40,10 +40,10 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
|
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
const [currentDirectory, setCurrentDirectory] = useState<VirtualFolder>(virtualRoot.navigate(startPath ?? "~") as VirtualFolder);
|
const [currentDirectory, setCurrentDirectory] = useState<VirtualFolder>(virtualRoot.navigate(startPath ?? "~") as VirtualFolder);
|
||||||
const [path, setPath] = useState(currentDirectory?.path ?? "");
|
const [path, setPath] = useState<string>(currentDirectory?.path ?? "");
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const [showHidden] = useState(true);
|
const [showHidden] = useState(true);
|
||||||
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory(currentDirectory.path);
|
const { history, stateIndex, pushState, undo, redo, undoAvailable, redoAvailable } = useHistory<string>(currentDirectory.path);
|
||||||
|
|
||||||
const { openWindowedModal } = useWindowedModal();
|
const { openWindowedModal } = useWindowedModal();
|
||||||
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
||||||
|
|
@ -56,29 +56,29 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
}
|
}
|
||||||
file.open(windowsManager);
|
file.open(windowsManager);
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file: VirtualFile) => {
|
||||||
file.delete();
|
file.delete();
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file) => {
|
<ClickAction label="Properties" icon={faCircleInfo} onTrigger={(event, file: VirtualFile) => {
|
||||||
openWindowedModal({
|
openWindowedModal({
|
||||||
title: `${file.id} ${TITLE_SEPARATOR} Properties`,
|
title: `${file.id} ${TITLE_SEPARATOR} Properties`,
|
||||||
iconUrl: file.getIconUrl(),
|
iconUrl: file.getIconUrl(),
|
||||||
size: new Vector2(400, 500),
|
size: new Vector2(400, 500),
|
||||||
Modal: (props) => <FileProperties file={file} {...props}/>
|
Modal: (props: object) => <FileProperties file={file} {...props}/>
|
||||||
});
|
});
|
||||||
}}/>
|
}}/>
|
||||||
</Actions>
|
</Actions>
|
||||||
});
|
});
|
||||||
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
|
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
|
||||||
<Actions {...props}>
|
<Actions {...props}>
|
||||||
<ClickAction label="Open" onTrigger={(event, folder) => {
|
<ClickAction label="Open" onTrigger={(event, folder: VirtualFolder & VirtualFolderLink) => {
|
||||||
changeDirectory(folder.linkedPath ?? folder.name);
|
changeDirectory(folder.linkedPath ?? folder.name);
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={APP_ICONS.TERMINAL} onTrigger={(event, folder) => {
|
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={APP_ICONS.TERMINAL} onTrigger={(event, folder: VirtualFolder) => {
|
||||||
windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
|
windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
|
||||||
}}/>
|
}}/>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => {
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder: VirtualFolder) => {
|
||||||
folder.delete();
|
folder.delete();
|
||||||
}}/>
|
}}/>
|
||||||
</Actions>
|
</Actions>
|
||||||
|
|
@ -91,7 +91,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const changeDirectory = useCallback((path, absolute = false) => {
|
const changeDirectory = useCallback((path: string, absolute = false) => {
|
||||||
if (path == null)
|
if (path == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -119,12 +119,12 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
}
|
}
|
||||||
}, [history, stateIndex, virtualRoot]);
|
}, [history, stateIndex, virtualRoot]);
|
||||||
|
|
||||||
const onPathChange = (event) => {
|
const onPathChange = (event: Event) => {
|
||||||
setPath(event.target.value);
|
setPath((event.target as HTMLInputElement).value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
let value = event.target.value;
|
let value = (event.target as HTMLInputElement).value;
|
||||||
|
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
if (value === "")
|
if (value === "")
|
||||||
|
|
@ -160,7 +160,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
title="Back"
|
title="Back"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={styles["Icon-button"]}
|
className={styles["Icon-button"]}
|
||||||
onClick={undo}
|
onClick={() => { undo(); }}
|
||||||
disabled={!undoAvailable}
|
disabled={!undoAvailable}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCaretLeft}/>
|
<FontAwesomeIcon icon={faCaretLeft}/>
|
||||||
|
|
@ -169,7 +169,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
title="Forward"
|
title="Forward"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={styles["Icon-button"]}
|
className={styles["Icon-button"]}
|
||||||
onClick={redo}
|
onClick={() => { redo(); }}
|
||||||
disabled={!redoAvailable}
|
disabled={!redoAvailable}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCaretRight}/>
|
<FontAwesomeIcon icon={faCaretRight}/>
|
||||||
|
|
@ -215,8 +215,8 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
aria-label="Path"
|
aria-label="Path"
|
||||||
className={styles["Path-input"]}
|
className={styles["Path-input"]}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={onPathChange}
|
onChange={onPathChange as unknown as ChangeEventHandler}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown as unknown as KeyboardEventHandler}
|
||||||
placeholder="Enter a path..."
|
placeholder="Enter a path..."
|
||||||
/>
|
/>
|
||||||
<button title="Search" tabIndex={0} className={styles["Icon-button"]}>
|
<button title="Search" tabIndex={0} className={styles["Icon-button"]}>
|
||||||
|
|
@ -241,7 +241,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
|
||||||
onOpenFile={(event, file) => {
|
onOpenFile={(event, file) => {
|
||||||
(event as Event).preventDefault();
|
(event as Event).preventDefault();
|
||||||
if (isSelector)
|
if (isSelector)
|
||||||
return onSelectionFinish?.();
|
return void onSelectionFinish?.();
|
||||||
const options: Record<string, string> = {};
|
const options: Record<string, string> = {};
|
||||||
if (file.extension === "md" || CODE_FORMATS.includes(file.extension))
|
if (file.extension === "md" || CODE_FORMATS.includes(file.extension))
|
||||||
options.mode = "view";
|
options.mode = "view";
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { ReactElement, useEffect, useRef, useState } from "react";
|
import { MouseEventHandler, ReactElement, useEffect, useRef, useState } from "react";
|
||||||
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
|
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
|
||||||
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
|
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
|
||||||
import { Interactable } from "../../../_utils/interactable/Interactable";
|
import { Interactable } from "../../../_utils/interactable/Interactable";
|
||||||
import styles from "./DirectoryList.module.css";
|
import styles from "./DirectoryList.module.css";
|
||||||
import { ImagePreview } from "./ImagePreview";
|
import { ImagePreview } from "./ImagePreview";
|
||||||
|
import Vector2 from "../../../../features/math/vector2";
|
||||||
|
|
||||||
export interface OnSelectionChangeParams {
|
export interface OnSelectionChangeParams {
|
||||||
files?: string[];
|
files?: string[];
|
||||||
|
|
@ -26,17 +27,17 @@ interface DirectoryListProps {
|
||||||
onOpenFolder?: FolderEventHandler;
|
onOpenFolder?: FolderEventHandler;
|
||||||
allowMultiSelect?: boolean;
|
allowMultiSelect?: boolean;
|
||||||
onSelectionChange?: (params: OnSelectionChangeParams) => void;
|
onSelectionChange?: (params: OnSelectionChangeParams) => void;
|
||||||
[key: string]: any;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
|
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
|
||||||
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactElement {
|
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactElement {
|
||||||
const [selectedFolders, setSelectedFolders] = useState([]);
|
const [selectedFolders, setSelectedFolders] = useState<string[]>([]);
|
||||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||||
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const [rectSelectStart, setRectSelectStart] = useState(null);
|
const [rectSelectStart, setRectSelectStart] = useState<Vector2>(null);
|
||||||
const [rectSelectEnd, setRectSelectEnd] = useState(null);
|
const [rectSelectEnd, setRectSelectEnd] = useState<Vector2>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onSelectionChange?.({ files: selectedFiles, folders: selectedFolders, directory });
|
onSelectionChange?.({ files: selectedFiles, folders: selectedFolders, directory });
|
||||||
|
|
@ -47,14 +48,14 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
}, [directory]);
|
}, [directory]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onMoveRectSelect = (event) => {
|
const onMoveRectSelect = (event: MouseEvent) => {
|
||||||
if (rectSelectStart == null)
|
if (rectSelectStart == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setRectSelectEnd({ x: event.clientX, y: event.clientY });
|
setRectSelectEnd({ x: event.clientX, y: event.clientY } as Vector2);
|
||||||
};
|
};
|
||||||
const onStopRectSelect = (event) => {
|
const onStopRectSelect = (event: MouseEvent) => {
|
||||||
if (rectSelectStart == null || rectSelectEnd == null) {
|
if (rectSelectStart == null || rectSelectEnd == null) {
|
||||||
setRectSelectStart(null);
|
setRectSelectStart(null);
|
||||||
setRectSelectEnd(null);
|
setRectSelectEnd(null);
|
||||||
|
|
@ -82,14 +83,14 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
setSelectedFolders([]);
|
setSelectedFolders([]);
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
};
|
};
|
||||||
const selectFolder = (folder, exclusive = false) => {
|
const selectFolder = (folder: VirtualFolder, exclusive = false) => {
|
||||||
if (!allowMultiSelect)
|
if (!allowMultiSelect)
|
||||||
exclusive = true;
|
exclusive = true;
|
||||||
setSelectedFolders(exclusive ? [folder.id] : [...selectedFolders, folder.id]);
|
setSelectedFolders(exclusive ? [folder.id] : [...selectedFolders, folder.id]);
|
||||||
if (exclusive)
|
if (exclusive)
|
||||||
setSelectedFiles([]);
|
setSelectedFiles([]);
|
||||||
};
|
};
|
||||||
const selectFile = (file, exclusive = false) => {
|
const selectFile = (file: VirtualFile, exclusive = false) => {
|
||||||
if (!allowMultiSelect)
|
if (!allowMultiSelect)
|
||||||
exclusive = true;
|
exclusive = true;
|
||||||
setSelectedFiles(exclusive ? [file.id] : [...selectedFiles, file.id]);
|
setSelectedFiles(exclusive ? [file.id] : [...selectedFiles, file.id]);
|
||||||
|
|
@ -97,12 +98,12 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
setSelectedFolders([]);
|
setSelectedFolders([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onStartRectSelect = (event) => {
|
const onStartRectSelect = (event: MouseEvent) => {
|
||||||
setRectSelectStart({ x: event.clientX, y: event.clientY });
|
setRectSelectStart({ x: event.clientX, y: event.clientY } as Vector2);
|
||||||
};
|
};
|
||||||
const getRectSelectStyle = () => {
|
const getRectSelectStyle = () => {
|
||||||
let x, y, width, height = null;
|
let x: number, y: number, width: number, height: number = null;
|
||||||
const containerRect = ref.current?.getBoundingClientRect();
|
const containerRect = (ref.current as HTMLElement)?.getBoundingClientRect();
|
||||||
|
|
||||||
if (rectSelectStart.x < rectSelectEnd.x) {
|
if (rectSelectStart.x < rectSelectEnd.x) {
|
||||||
x = rectSelectStart.x;
|
x = rectSelectStart.x;
|
||||||
|
|
@ -143,7 +144,7 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classNames.join(" ")}
|
className={classNames.join(" ")}
|
||||||
onClick={clearSelection}
|
onClick={clearSelection}
|
||||||
onMouseDown={onStartRectSelect}
|
onMouseDown={onStartRectSelect as unknown as MouseEventHandler}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{rectSelectStart != null && rectSelectEnd != null
|
{rectSelectStart != null && rectSelectEnd != null
|
||||||
|
|
@ -156,13 +157,13 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={folderClassNames.join(" ")}
|
className={folderClassNames.join(" ")}
|
||||||
data-selected={selectedFolders.includes(folder.id)}
|
data-selected={selectedFolders.includes(folder.id)}
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event: MouseEvent) => {
|
||||||
onContextMenuFolder?.(event, folder);
|
onContextMenuFolder?.(event, folder);
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event: MouseEvent) => {
|
||||||
selectFolder(folder, !event.ctrlKey);
|
selectFolder(folder, !event.ctrlKey);
|
||||||
}}
|
}}
|
||||||
onDoubleClick={(event) => {
|
onDoubleClick={(event: MouseEvent) => {
|
||||||
onOpenFolder?.(event, folder);
|
onOpenFolder?.(event, folder);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -178,13 +179,13 @@ export function DirectoryList({ directory, showHidden = false, folderClassName,
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={fileClassNames.join(" ")}
|
className={fileClassNames.join(" ")}
|
||||||
data-selected={selectedFiles.includes(file.id)}
|
data-selected={selectedFiles.includes(file.id)}
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event: MouseEvent) => {
|
||||||
onContextMenuFile?.(event, file);
|
onContextMenuFile?.(event, file);
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event: MouseEvent) => {
|
||||||
selectFile(file, !event.ctrlKey);
|
selectFile(file, !event.ctrlKey);
|
||||||
}}
|
}}
|
||||||
onDoubleClick={(event) => {
|
onDoubleClick={(event: MouseEvent) => {
|
||||||
onOpenFile?.(event, file);
|
onOpenFile?.(event, file);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function AboutSettings() {
|
||||||
<div className={styles["Button-group"]}>
|
<div className={styles["Button-group"]}>
|
||||||
<Button
|
<Button
|
||||||
className={`${styles.Button} ${utilStyles["Text-bold"]}`}
|
className={`${styles.Button} ${utilStyles["Text-bold"]}`}
|
||||||
onClick={(event) => {
|
onClick={(event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
windowsManager.open("text-editor", {
|
windowsManager.open("text-editor", {
|
||||||
mode: "view",
|
mode: "view",
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,15 @@ import { ClickAction } from "../../../actions/actions/ClickAction";
|
||||||
import { removeFromArray } from "../../../../features/_utils/array.utils";
|
import { removeFromArray } from "../../../../features/_utils/array.utils";
|
||||||
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
|
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
|
||||||
import { SettingsManager } from "../../../../features/settings/settingsManager";
|
import { SettingsManager } from "../../../../features/settings/settingsManager";
|
||||||
|
import App from "../../../../features/apps/app";
|
||||||
|
|
||||||
export function AppOption({ app, pins, setPins }) {
|
interface AppOptionProps {
|
||||||
|
app: App;
|
||||||
|
pins: string[];
|
||||||
|
setPins: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppOption({ app, pins, setPins: _setPins }: AppOptionProps) {
|
||||||
const isPinned = pins.includes(app.id);
|
const isPinned = pins.includes(app.id);
|
||||||
|
|
||||||
const settingsManager = useSettingsManager();
|
const settingsManager = useSettingsManager();
|
||||||
|
|
@ -29,7 +36,7 @@ export function AppOption({ app, pins, setPins }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
||||||
settings.set("pins", newPins.join(","));
|
void settings.set("pins", newPins.join(","));
|
||||||
}}/>
|
}}/>
|
||||||
</Actions>
|
</Actions>
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from "react";
|
import { ChangeEventHandler, useEffect, useState } from "react";
|
||||||
import { SettingsManager } from "../../../../features/settings/settingsManager";
|
import { SettingsManager } from "../../../../features/settings/settingsManager";
|
||||||
import styles from "../Settings.module.css";
|
import styles from "../Settings.module.css";
|
||||||
import utilStyles from "../../../../styles/utils.module.css";
|
import utilStyles from "../../../../styles/utils.module.css";
|
||||||
|
|
@ -17,17 +17,17 @@ import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtual
|
||||||
export function AppearanceSettings() {
|
export function AppearanceSettings() {
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
const settingsManager = useSettingsManager();
|
const settingsManager = useSettingsManager();
|
||||||
const [wallpaper, setWallpaper] = useState(null);
|
const [wallpaper, setWallpaper] = useState<string>(null);
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
const { openWindowedModal } = useWindowedModal();
|
const { openWindowedModal } = useWindowedModal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
settings.get("wallpaper", setWallpaper);
|
void settings.get("wallpaper", setWallpaper);
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event: Event) => {
|
||||||
const value = event.target.value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
settings.set("wallpaper", value);
|
void settings.set("wallpaper", value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
|
|
@ -42,7 +42,7 @@ export function AppearanceSettings() {
|
||||||
type={SELECTOR_MODE.SINGLE}
|
type={SELECTOR_MODE.SINGLE}
|
||||||
allowedFormats={IMAGE_FORMATS}
|
allowedFormats={IMAGE_FORMATS}
|
||||||
onFinish={(file: VirtualFile) => {
|
onFinish={(file: VirtualFile) => {
|
||||||
settings.set("wallpaper", file.source);
|
void settings.set("wallpaper", file.source);
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
@ -59,7 +59,7 @@ export function AppearanceSettings() {
|
||||||
value={source}
|
value={source}
|
||||||
aria-label="Wallpaper image"
|
aria-label="Wallpaper image"
|
||||||
checked={source === wallpaper}
|
checked={source === wallpaper}
|
||||||
onChange={onChange}
|
onChange={onChange as unknown as ChangeEventHandler}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
<img src={source} alt={id} draggable="false"/>
|
<img src={source} alt={id} draggable="false"/>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function AppsSettings() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
||||||
settings.get("pins", (pins) => {
|
void settings.get("pins", (pins) => {
|
||||||
setPins(pins.split(","));
|
setPins(pins.split(","));
|
||||||
});
|
});
|
||||||
}, [settingsManager]);
|
}, [settingsManager]);
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,18 @@ import * as React from "react";
|
||||||
import styles from "./Terminal.module.css";
|
import styles from "./Terminal.module.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This was copied from
|
* Source:
|
||||||
* https://github.com/nteract/ansi-to-react/blob/master/src/index.ts
|
* https://github.com/nteract/ansi-to-react/blob/master/src/index.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts ANSI strings into JSON output.
|
* Converts ANSI strings into JSON output.
|
||||||
* @name ansiToJSON
|
* @param input - The input string.
|
||||||
* @function
|
* @param use_classes - If `true`, HTML classes will be appended
|
||||||
* @param {string} input - The input string.
|
|
||||||
* @param {boolean=} use_classes - If `true`, HTML classes will be appended
|
|
||||||
* to the HTML output.
|
* to the HTML output.
|
||||||
* @returns {AnserJsonEntry[]} The parsed input.
|
* @returns The parsed input.
|
||||||
*/
|
*/
|
||||||
function ansiToJSON(input, use_classes) {
|
function ansiToJSON(input: string, use_classes: boolean | undefined): AnserJsonEntry[] {
|
||||||
input = escapeCarriageReturn(fixBackspace(input));
|
input = escapeCarriageReturn(fixBackspace(input));
|
||||||
return Anser.ansiToJson(input, {
|
return Anser.ansiToJson(input, {
|
||||||
json: true,
|
json: true,
|
||||||
|
|
@ -28,10 +26,9 @@ function ansiToJSON(input, use_classes) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a class string.
|
* Create a class string.
|
||||||
* @param {AnserJsonEntry} bundle
|
* @returns class name(s)
|
||||||
* @returns {string} class name(s)
|
|
||||||
*/
|
*/
|
||||||
function createClass(bundle) {
|
function createClass(bundle: AnserJsonEntry): string {
|
||||||
const classNames = [];
|
const classNames = [];
|
||||||
|
|
||||||
if (bundle.bg) {
|
if (bundle.bg) {
|
||||||
|
|
@ -53,10 +50,10 @@ function createClass(bundle) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the style attribute.
|
* Create the style attribute.
|
||||||
* @param {AnserJsonEntry} bundle
|
* @param bundle
|
||||||
* @returns {object} returns the style object
|
* @returns returns the style object
|
||||||
*/
|
*/
|
||||||
function createStyle(bundle) {
|
function createStyle(bundle: AnserJsonEntry): object {
|
||||||
const style: Record<string, string> = {};
|
const style: Record<string, string> = {};
|
||||||
if (bundle.bg) {
|
if (bundle.bg) {
|
||||||
style.backgroundColor = `rgb(${bundle.bg})`;
|
style.backgroundColor = `rgb(${bundle.bg})`;
|
||||||
|
|
@ -94,12 +91,11 @@ function createStyle(bundle) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an Anser bundle into a React Node.
|
* Converts an Anser bundle into a React Node.
|
||||||
* @param {boolean} linkify - whether links should be converting into clickable anchor tags.
|
* @param linkify - whether links should be converting into clickable anchor tags.
|
||||||
* @param {boolean} useClasses - should render the span with a class instead of style.
|
* @param useClasses - should render the span with a class instead of style.
|
||||||
* @param {AnserJsonEntry} bundle - Anser output.
|
* @param bundle - Anser output.
|
||||||
* @param {number} key
|
|
||||||
*/
|
*/
|
||||||
function convertBundleIntoReact(linkify, useClasses, bundle, key) {
|
function convertBundleIntoReact(linkify: boolean, useClasses: boolean, bundle: AnserJsonEntry, key: number) {
|
||||||
const style = useClasses ? null : createStyle(bundle);
|
const style = useClasses ? null : createStyle(bundle);
|
||||||
const className = useClasses ? createClass(bundle) : null;
|
const className = useClasses ? createClass(bundle) : null;
|
||||||
|
|
||||||
|
|
@ -115,7 +111,7 @@ function convertBundleIntoReact(linkify, useClasses, bundle, key) {
|
||||||
const linkRegex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/g;
|
const linkRegex = /(\s|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/g;
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let match;
|
let match: RegExpExecArray;
|
||||||
while ((match = linkRegex.exec(bundle.content)) !== null) {
|
while ((match = linkRegex.exec(bundle.content)) !== null) {
|
||||||
const [, pre, url] = match;
|
const [, pre, url] = match;
|
||||||
|
|
||||||
|
|
@ -149,14 +145,7 @@ function convertBundleIntoReact(linkify, useClasses, bundle, key) {
|
||||||
return React.createElement("span", { style, key, className }, content);
|
return React.createElement("span", { style, key, className }, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export default function Ansi(props: { children?: string | undefined; linkify?: boolean | undefined; className?: string | undefined; useClasses?: boolean | undefined; }) {
|
||||||
* @param {object} props
|
|
||||||
* @param {string=} props.children
|
|
||||||
* @param {boolean=} props.linkify
|
|
||||||
* @param {string=} props.className
|
|
||||||
* @param {boolean=} props.useClasses
|
|
||||||
*/
|
|
||||||
export default function Ansi(props) {
|
|
||||||
const { className, useClasses, children, linkify } = props;
|
const { className, useClasses, children, linkify } = props;
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
"code",
|
"code",
|
||||||
|
|
@ -172,7 +161,7 @@ export default function Ansi(props) {
|
||||||
// that is **compatible with Jupyter classic**. One can
|
// that is **compatible with Jupyter classic**. One can
|
||||||
// argue that this behavior is questionable:
|
// argue that this behavior is questionable:
|
||||||
// https://stackoverflow.com/questions/55440152/multiple-b-doesnt-work-as-expected-in-jupyter#
|
// https://stackoverflow.com/questions/55440152/multiple-b-doesnt-work-as-expected-in-jupyter#
|
||||||
function fixBackspace(txt) {
|
function fixBackspace(txt: string) {
|
||||||
let tmp = txt;
|
let tmp = txt;
|
||||||
do {
|
do {
|
||||||
txt = tmp;
|
txt = tmp;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.Input}>
|
<span className={styles.Input}>
|
||||||
{prefix && <Ansi className={[styles.Prefix]} useClasses>{prefix}</Ansi>}
|
{prefix && <Ansi className={styles.Prefix} useClasses>{prefix}</Ansi>}
|
||||||
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition } as CSSProperties}>
|
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition } as CSSProperties}>
|
||||||
<span aria-hidden="true">{value}</span>
|
<span aria-hidden="true">{value}</span>
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
|
||||||
const { openWindowedModal } = useWindowedModal();
|
const { openWindowedModal } = useWindowedModal();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
void (async () => {
|
||||||
let newContent = "";
|
let newContent = "";
|
||||||
|
|
||||||
// Load file
|
// Load file
|
||||||
|
|
@ -97,8 +97,8 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
|
||||||
onChange({ target: { value: content } });
|
onChange({ target: { value: content } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event: Event | { target: { value: string } }) => {
|
||||||
const value = event.target.value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
|
||||||
if (currentFile != null) {
|
if (currentFile != null) {
|
||||||
setUnsavedChanges(currentFile.content !== value);
|
setUnsavedChanges(currentFile.content !== value);
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,11 @@ import { Share } from "../modals/share/Share";
|
||||||
import ModalsManager from "../../features/modals/modalsManager";
|
import ModalsManager from "../../features/modals/modalsManager";
|
||||||
import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder";
|
import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder";
|
||||||
import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink";
|
import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink";
|
||||||
|
import { VirtualFile } from "../../features/virtual-drive/file/virtualFile";
|
||||||
|
|
||||||
export const Desktop = memo(() => {
|
export const Desktop = memo(() => {
|
||||||
const settingsManager = useSettingsManager();
|
const settingsManager = useSettingsManager();
|
||||||
const [wallpaper, setWallpaper] = useState(null);
|
const [wallpaper, setWallpaper] = useState<string>(null);
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
const [showIcons, setShowIcons] = useState(false);
|
const [showIcons, setShowIcons] = useState(false);
|
||||||
|
|
@ -41,16 +42,16 @@ export const Desktop = memo(() => {
|
||||||
<DropdownAction label="View" icon={faEye}>
|
<DropdownAction label="View" icon={faEye}>
|
||||||
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value: string) => {
|
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value: string) => {
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
settings.set("icon-size", value);
|
void settings.set("icon-size", value);
|
||||||
}} options={[
|
}} options={[
|
||||||
{ label: "Small icons" },
|
{ label: "Small icons" },
|
||||||
{ label: "Medium icons" },
|
{ label: "Medium icons" },
|
||||||
{ label: "Large icons" }
|
{ label: "Large icons" }
|
||||||
]}/>
|
]}/>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<ToggleAction label="Show dekstop icons" initialValue={showIcons} onTrigger={(event, params, value) => {
|
<ToggleAction label="Show dekstop icons" initialValue={showIcons} onTrigger={() => {
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
settings.set("show-icons", (!showIcons).toString());
|
void settings.set("show-icons", (!showIcons).toString());
|
||||||
}}/>
|
}}/>
|
||||||
</DropdownAction>
|
</DropdownAction>
|
||||||
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
|
<ClickAction label="Reload" shortcut={["Control", "r"]} icon={faArrowsRotate} onTrigger={() => {
|
||||||
|
|
@ -77,56 +78,54 @@ export const Desktop = memo(() => {
|
||||||
});
|
});
|
||||||
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
|
||||||
<Actions {...props}>
|
<Actions {...props}>
|
||||||
<ClickAction label="Open" onTrigger={(event, file) => {
|
<ClickAction label="Open" onTrigger={(event, file: VirtualFile) => {
|
||||||
file.open(windowsManager);
|
file.open(windowsManager);
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, file) => {
|
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, file: VirtualFile) => {
|
||||||
file.parent.open(windowsManager);
|
file.parent.open(windowsManager);
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file) => {
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, file: VirtualFile) => {
|
||||||
file.delete();
|
file.delete();
|
||||||
}}/>
|
}}/>
|
||||||
</Actions>
|
</Actions>
|
||||||
});
|
});
|
||||||
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
|
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
|
||||||
<Actions {...props}>
|
<Actions {...props}>
|
||||||
<ClickAction label="Open" onTrigger={(event, folder) => {
|
<ClickAction label="Open" onTrigger={(event, folder: VirtualFolder) => {
|
||||||
folder.open(windowsManager);
|
folder.open(windowsManager);
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={faTerminal} onTrigger={(event, folder) => {
|
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={faTerminal} onTrigger={(event, folder: VirtualFolder) => {
|
||||||
windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
|
windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
|
||||||
}}/>
|
}}/>
|
||||||
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, folder) => {
|
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, folder: VirtualFolder) => {
|
||||||
folder.parent.open(windowsManager);
|
folder.parent.open(windowsManager);
|
||||||
}}/>
|
}}/>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder) => {
|
<ClickAction label="Delete" icon={faTrash} onTrigger={(event, folder: VirtualFolder) => {
|
||||||
folder.delete();
|
folder.delete();
|
||||||
}}/>
|
}}/>
|
||||||
</Actions>
|
</Actions>
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
void settings.get("wallpaper", setWallpaper);
|
||||||
settings.get("wallpaper", setWallpaper);
|
void settings.get("show-icons", (value) => {
|
||||||
settings.get("show-icons", (value) => {
|
if (value != null) {
|
||||||
if (value != null) {
|
setShowIcons(value === "true");
|
||||||
setShowIcons(value === "true");
|
} else {
|
||||||
} else {
|
setShowIcons(true);
|
||||||
setShowIcons(true);
|
}
|
||||||
}
|
});
|
||||||
});
|
void settings.get("icon-size", (value) => {
|
||||||
settings.get("icon-size", (value) => {
|
if (isValidInteger(value))
|
||||||
if (isValidInteger(value))
|
setIconSize(parseInt(value));
|
||||||
setIconSize(parseInt(value));
|
});
|
||||||
});
|
|
||||||
})();
|
|
||||||
}, [settingsManager]);
|
}, [settingsManager]);
|
||||||
|
|
||||||
const onError = () => {
|
const onError = () => {
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
|
||||||
settings.set("wallpaper", FALLBACK_WALLPAPER);
|
void settings.set("wallpaper", FALLBACK_WALLPAPER);
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 4;
|
const iconScale = 1 + ((isValidInteger(iconSize) ? iconSize : FALLBACK_ICON_SIZE) - 1) / 4;
|
||||||
|
|
@ -148,7 +147,7 @@ export const Desktop = memo(() => {
|
||||||
onOpenFile={(event, file) => {
|
onOpenFile={(event, file) => {
|
||||||
(event as Event).preventDefault();
|
(event as Event).preventDefault();
|
||||||
|
|
||||||
const options: Record<string, any> = {};
|
const options: Record<string, unknown> = {};
|
||||||
if (file.name === "info.md")
|
if (file.name === "info.md")
|
||||||
options.size = new Vector2(575, 675);
|
options.size = new Vector2(575, 675);
|
||||||
if (file.extension === "md")
|
if (file.extension === "md")
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,27 @@
|
||||||
import { CSSProperties, FC, memo, ReactNode } from "react";
|
import { CSSProperties, FC, KeyboardEvent, memo, ReactNode } from "react";
|
||||||
import Modal from "../../features/modals/modal";
|
import Modal from "../../features/modals/modal";
|
||||||
import OutsideClickListener from "../../hooks/_utils/outsideClick";
|
import OutsideClickListener from "../../hooks/_utils/outsideClick";
|
||||||
import styles from "./ModalView.module.css";
|
import styles from "./ModalView.module.css";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
modal: Modal;
|
modal?: Modal;
|
||||||
params?: Record<string, any>;
|
params?: {
|
||||||
|
appId?: string;
|
||||||
|
fullscreen?: boolean;
|
||||||
|
iconUrl?: string;
|
||||||
|
title?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
onFinish?: Function;
|
onFinish?: Function;
|
||||||
[key: string]: any;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModalView: FC<ModalProps> = memo(({ modal }) => {
|
export const ModalView: FC<ModalProps> = memo(({ modal }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onDismiss = (event) => {
|
const onDismiss = (event: Event) => {
|
||||||
if (event.key === "Escape")
|
if ((event as unknown as KeyboardEvent).key === "Escape")
|
||||||
modal.close();
|
modal.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ import { ModalView } from "./ModalView";
|
||||||
import styles from "./ModalsView.module.css";
|
import styles from "./ModalsView.module.css";
|
||||||
import { useModals } from "../../hooks/modals/modalsContext";
|
import { useModals } from "../../hooks/modals/modalsContext";
|
||||||
import { useModalsManager } from "../../hooks/modals/modalsManagerContext";
|
import { useModalsManager } from "../../hooks/modals/modalsManagerContext";
|
||||||
|
import Modal from "../../features/modals/modal";
|
||||||
|
|
||||||
export const ModalsView = memo(() => {
|
export const ModalsView = memo(() => {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
const modalsManager = useModalsManager();
|
const modalsManager = useModalsManager();
|
||||||
const [sortedModals, setSortedModals] = useState([]);
|
const [sortedModals, setSortedModals] = useState<Modal[]>([]);
|
||||||
|
|
||||||
// Sort modals
|
// Sort modals
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,8 @@ import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
import { ReactSVG } from "react-svg";
|
import { ReactSVG } from "react-svg";
|
||||||
import utilStyles from "../../../styles/utils.module.css";
|
import utilStyles from "../../../styles/utils.module.css";
|
||||||
import Modal from "../../../features/modals/modal";
|
|
||||||
import { ModalProps } from "../ModalView";
|
import { ModalProps } from "../ModalView";
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} props
|
|
||||||
* @param {Modal} props.modal
|
|
||||||
* @param {*} props.params
|
|
||||||
* @param {*} props.children
|
|
||||||
*/
|
|
||||||
export function WindowedModal({ modal, params, children, ...props }: ModalProps) {
|
export function WindowedModal({ modal, params, children, ...props }: ModalProps) {
|
||||||
const { iconUrl, title } = params;
|
const { iconUrl, title } = params;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { WindowedModal } from "../_utils/WindowedModal";
|
||||||
import styles from "./FileSelector.module.css";
|
import styles from "./FileSelector.module.css";
|
||||||
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
|
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
|
||||||
import { ModalProps } from "../ModalView";
|
import { ModalProps } from "../ModalView";
|
||||||
|
import { VirtualFolder } from "../../../features/virtual-drive/folder";
|
||||||
|
|
||||||
interface FileSelectorProps extends ModalProps {
|
interface FileSelectorProps extends ModalProps {
|
||||||
type: number;
|
type: number;
|
||||||
|
|
@ -17,10 +18,10 @@ interface FileSelectorProps extends ModalProps {
|
||||||
export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }: FileSelectorProps) {
|
export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }: FileSelectorProps) {
|
||||||
const multi = (type === SELECTOR_MODE.MULTIPLE);
|
const multi = (type === SELECTOR_MODE.MULTIPLE);
|
||||||
|
|
||||||
const [selection, setSelection] = useState(multi ? [] : null);
|
const [selection, setSelection] = useState<string[]>(multi ? [] : null);
|
||||||
const [directory, setDirectory] = useState(null);
|
const [directory, setDirectory] = useState<VirtualFolder>(null);
|
||||||
|
|
||||||
const finish = (event) => {
|
const finish = (event: Event) => {
|
||||||
event?.preventDefault();
|
event?.preventDefault();
|
||||||
|
|
||||||
if (directory == null || selection == null)
|
if (directory == null || selection == null)
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
import { useState } from "react";
|
import { ChangeEventHandler, useState } from "react";
|
||||||
import styles from "./Share.module.css";
|
import styles from "./Share.module.css";
|
||||||
|
|
||||||
export default function Option({ name, label, setOption }) {
|
interface OptionProps {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
setOption: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Option({ name, label, setOption }: OptionProps) {
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event: Event) => {
|
||||||
const newValue = event.target.value;
|
const newValue = (event.target as HTMLInputElement).value;
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
setOption(name, newValue);
|
setOption(name, newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <label className={styles.Label}>
|
return <label className={styles.Label}>
|
||||||
<p>{label}:</p>
|
<p>{label}:</p>
|
||||||
<input className={styles.Input} name={name} type="text" value={value} onChange={onChange}/>
|
<input className={styles.Input} name={name} type="text" value={value} onChange={onChange as unknown as ChangeEventHandler}/>
|
||||||
</label>;
|
</label>;
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from "react";
|
import { ChangeEventHandler, useEffect, useState } from "react";
|
||||||
import ModalsManager from "../../../features/modals/modalsManager";
|
import ModalsManager from "../../../features/modals/modalsManager";
|
||||||
import { WindowedModal } from "../_utils/WindowedModal";
|
import { WindowedModal } from "../_utils/WindowedModal";
|
||||||
import styles from "./Share.module.css";
|
import styles from "./Share.module.css";
|
||||||
|
|
@ -13,7 +13,7 @@ import { faSquareCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useAlert } from "../../../hooks/modals/alert";
|
import { useAlert } from "../../../hooks/modals/alert";
|
||||||
import { ModalProps } from "../ModalView";
|
import { ModalProps } from "../ModalView";
|
||||||
|
|
||||||
const APP_OPTIONS = {
|
const APP_OPTIONS: Record<string, Record<string, string>[]> = {
|
||||||
"terminal": [
|
"terminal": [
|
||||||
{
|
{
|
||||||
label: "Command",
|
label: "Command",
|
||||||
|
|
@ -35,10 +35,10 @@ const APP_OPTIONS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Share({ modal, params, ...props }: ModalProps) {
|
export function Share({ modal, params, ...props }: ModalProps) {
|
||||||
const [appId, setAppId] = useState(params.appId ?? "");
|
const [appId, setAppId] = useState<string>(params.appId ?? "");
|
||||||
const [fullscreen, setFullscreen] = useState(params.fullscreen ?? false);
|
const [fullscreen, setFullscreen] = useState<boolean>(params.fullscreen ?? false);
|
||||||
const [options, setOptions] = useState({});
|
const [options, setOptions] = useState({});
|
||||||
const [url, setUrl] = useState(null);
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
const { alert } = useAlert();
|
const { alert } = useAlert();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -49,8 +49,8 @@ export function Share({ modal, params, ...props }: ModalProps) {
|
||||||
}));
|
}));
|
||||||
}, [appId, fullscreen, options]);
|
}, [appId, fullscreen, options]);
|
||||||
|
|
||||||
const onAppIdChange = (event) => {
|
const onAppIdChange = (event: Event) => {
|
||||||
const newAppId = event.target.value;
|
const newAppId = (event.target as HTMLInputElement).value;
|
||||||
|
|
||||||
if (newAppId === appId)
|
if (newAppId === appId)
|
||||||
return;
|
return;
|
||||||
|
|
@ -69,12 +69,12 @@ export function Share({ modal, params, ...props }: ModalProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFullscreenChange = (event) => {
|
const onFullscreenChange = (event: Event) => {
|
||||||
const newFullscreen = event.target.checked;
|
const newFullscreen = (event.target as HTMLInputElement).checked;
|
||||||
setFullscreen(newFullscreen);
|
setFullscreen(newFullscreen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setOption = (name, value) => {
|
const setOption = (name: string, value: string) => {
|
||||||
setOptions((options = {}) => {
|
setOptions((options = {}) => {
|
||||||
options = { ...options };
|
options = { ...options };
|
||||||
options[name] = value;
|
options[name] = value;
|
||||||
|
|
@ -92,7 +92,7 @@ export function Share({ modal, params, ...props }: ModalProps) {
|
||||||
<form className={styles.Form}>
|
<form className={styles.Form}>
|
||||||
<label className={styles.Label}>
|
<label className={styles.Label}>
|
||||||
<p>App:</p>
|
<p>App:</p>
|
||||||
<select className={styles.Input} name="app" value={appId} onChange={onAppIdChange}>
|
<select className={styles.Input} name="app" value={appId} onChange={onAppIdChange as unknown as ChangeEventHandler}>
|
||||||
<option value={""}>(None)</option>
|
<option value={""}>(None)</option>
|
||||||
{AppsManager.APPS.map(({ name, id }) =>
|
{AppsManager.APPS.map(({ name, id }) =>
|
||||||
<option key={id} value={id}>{name}</option>
|
<option key={id} value={id}>{name}</option>
|
||||||
|
|
@ -101,7 +101,14 @@ export function Share({ modal, params, ...props }: ModalProps) {
|
||||||
</label>
|
</label>
|
||||||
{appId !== "" ? <label className={styles.Label}>
|
{appId !== "" ? <label className={styles.Label}>
|
||||||
<p>Fullscreen:</p>
|
<p>Fullscreen:</p>
|
||||||
<input className={styles.Input} name="fullscreen" type="checkbox" checked={fullscreen} value={fullscreen} onChange={onFullscreenChange}/>
|
<input
|
||||||
|
className={styles.Input}
|
||||||
|
name="fullscreen"
|
||||||
|
type="checkbox"
|
||||||
|
checked={fullscreen}
|
||||||
|
value={fullscreen.toString()}
|
||||||
|
onChange={onFullscreenChange as unknown as ChangeEventHandler}
|
||||||
|
/>
|
||||||
<div className={styles.Checkbox}>
|
<div className={styles.Checkbox}>
|
||||||
{fullscreen
|
{fullscreen
|
||||||
? <FontAwesomeIcon icon={faSquareCheck}/>
|
? <FontAwesomeIcon icon={faSquareCheck}/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { CSSProperties, memo, useEffect, useMemo, useRef, useState } from "react";
|
import { CSSProperties, memo, ReactEventHandler, UIEventHandler, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import styles from "./Taskbar.module.css";
|
import styles from "./Taskbar.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
@ -78,12 +78,12 @@ export const Taskbar = memo(() => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
||||||
settings.get("pins", (pins) => {
|
void settings.get("pins", (pins) => {
|
||||||
setPins(pins.split(","));
|
setPins(pins.split(","));
|
||||||
});
|
});
|
||||||
}, [settingsManager]);
|
}, [settingsManager]);
|
||||||
|
|
||||||
const updateShowHome = (show) => {
|
const updateShowHome = (show: boolean) => {
|
||||||
setShowHome(show);
|
setShowHome(show);
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
|
|
@ -92,7 +92,7 @@ export const Taskbar = memo(() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateShowSearch = (show) => {
|
const updateShowSearch = (show: boolean) => {
|
||||||
setShowSearch(show);
|
setShowSearch(show);
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
|
|
@ -104,7 +104,7 @@ export const Taskbar = memo(() => {
|
||||||
setHideUtilMenus(true);
|
setHideUtilMenus(true);
|
||||||
|
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus();
|
(inputRef.current as HTMLElement).focus();
|
||||||
window.scrollTo(0, document.body.scrollHeight);
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -122,7 +122,7 @@ export const Taskbar = memo(() => {
|
||||||
setHideUtilMenus(false);
|
setHideUtilMenus(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const search = (query) => {
|
const search = (_query: string) => {
|
||||||
updateShowSearch(true);
|
updateShowSearch(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -174,8 +174,8 @@ export const Taskbar = memo(() => {
|
||||||
<div
|
<div
|
||||||
className={styles["App-icons-inner"]}
|
className={styles["App-icons-inner"]}
|
||||||
data-allow-context-menu={true}
|
data-allow-context-menu={true}
|
||||||
onScroll={onUpdate}
|
onScroll={onUpdate as unknown as UIEventHandler}
|
||||||
onResize={onUpdate}
|
onResize={onUpdate as unknown as ReactEventHandler}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{apps}
|
{apps}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export const AppButton: FC<AppButtonProps> = memo(({ app, windowsManager, pins,
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
||||||
settings.set("pins", newPins.join(","));
|
void settings.set("pins", newPins.join(","));
|
||||||
}}/>
|
}}/>
|
||||||
{active && <ClickAction label="Close window" icon={faTimes} onTrigger={() => {
|
{active && <ClickAction label="Close window" icon={faTimes} onTrigger={() => {
|
||||||
windowsManager.close(windowsManager.getAppWindowId(app.id));
|
windowsManager.close(windowsManager.getAppWindowId(app.id));
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import styles from "./Calendar.module.css";
|
||||||
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
|
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
|
||||||
import { UtilMenu } from "../menus/UtilMenu";
|
import { UtilMenu } from "../menus/UtilMenu";
|
||||||
|
|
||||||
/**
|
interface CalendarProps {
|
||||||
* @param {object} props
|
hideUtilMenus: boolean;
|
||||||
* @param {boolean} props.hideUtilMenus
|
showUtilMenu: Function;
|
||||||
* @param {Function} props.showUtilMenu
|
}
|
||||||
*/
|
|
||||||
export function Calendar({ hideUtilMenus, showUtilMenu }) {
|
export function Calendar({ hideUtilMenus, showUtilMenu }: CalendarProps) {
|
||||||
const [date, setDate] = useState(new Date());
|
const [date, setDate] = useState(new Date());
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ export function Calendar({ hideUtilMenus, showUtilMenu }) {
|
||||||
}
|
}
|
||||||
}, [hideUtilMenus, showMenu]);
|
}, [hideUtilMenus, showMenu]);
|
||||||
|
|
||||||
const updateShowMenu = (show) => {
|
const updateShowMenu = (show: boolean) => {
|
||||||
if (show)
|
if (show)
|
||||||
showUtilMenu();
|
showUtilMenu();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ import OutsideClickListener from "../../../hooks/_utils/outsideClick";
|
||||||
import { UtilMenu } from "../menus/UtilMenu";
|
import { UtilMenu } from "../menus/UtilMenu";
|
||||||
import styles from "./Network.module.css";
|
import styles from "./Network.module.css";
|
||||||
|
|
||||||
/**
|
interface NetworkProps {
|
||||||
* @param {object} props
|
hideUtilMenus: boolean;
|
||||||
* @param {boolean} props.hideUtilMenus
|
showUtilMenu: Function;
|
||||||
* @param {Function} props.showUtilMenu
|
}
|
||||||
*/
|
|
||||||
export function Network({ hideUtilMenus, showUtilMenu }) {
|
export function Network({ hideUtilMenus, showUtilMenu }: NetworkProps) {
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -19,7 +19,7 @@ export function Network({ hideUtilMenus, showUtilMenu }) {
|
||||||
}
|
}
|
||||||
}, [hideUtilMenus, showMenu]);
|
}, [hideUtilMenus, showMenu]);
|
||||||
|
|
||||||
const updateShowMenu = (show) => {
|
const updateShowMenu = (show: boolean) => {
|
||||||
if (show)
|
if (show)
|
||||||
showUtilMenu();
|
showUtilMenu();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ import OutsideClickListener from "../../../hooks/_utils/outsideClick";
|
||||||
import { UtilMenu } from "../menus/UtilMenu";
|
import { UtilMenu } from "../menus/UtilMenu";
|
||||||
import styles from "./Volume.module.css";
|
import styles from "./Volume.module.css";
|
||||||
|
|
||||||
/**
|
interface VolumeProps {
|
||||||
* @param {object} props
|
hideUtilMenus: boolean;
|
||||||
* @param {boolean} props.hideUtilMenus
|
showUtilMenu: Function;
|
||||||
* @param {Function} props.showUtilMenu
|
}
|
||||||
*/
|
|
||||||
export function Volume({ hideUtilMenus, showUtilMenu }) {
|
export function Volume({ hideUtilMenus, showUtilMenu }: VolumeProps) {
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -19,7 +19,7 @@ export function Volume({ hideUtilMenus, showUtilMenu }) {
|
||||||
}
|
}
|
||||||
}, [hideUtilMenus, showMenu]);
|
}, [hideUtilMenus, showMenu]);
|
||||||
|
|
||||||
const updateShowMenu = (show) => {
|
const updateShowMenu = (show: boolean) => {
|
||||||
if (show)
|
if (show)
|
||||||
showUtilMenu();
|
showUtilMenu();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ import utilStyles from "../../../styles/utils.module.css";
|
||||||
import { APPS } from "../../../config/apps.config";
|
import { APPS } from "../../../config/apps.config";
|
||||||
import { NAME } from "../../../config/branding.config";
|
import { NAME } from "../../../config/branding.config";
|
||||||
|
|
||||||
/**
|
interface HomeMenuProps {
|
||||||
* @param {object} props
|
active: boolean;
|
||||||
* @param {boolean} props.active
|
setActive: Function;
|
||||||
* @param {Function} props.setActive
|
search: Function;
|
||||||
* @param {Function} props.search
|
}
|
||||||
*/
|
|
||||||
export function HomeMenu({ active, setActive, search }) {
|
export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
const [tabIndex, setTabIndex] = useState(active ? 0 : -1);
|
const [tabIndex, setTabIndex] = useState(active ? 0 : -1);
|
||||||
|
|
@ -34,7 +34,7 @@ export function HomeMenu({ active, setActive, search }) {
|
||||||
classNames.push(styles.Active);
|
classNames.push(styles.Active);
|
||||||
|
|
||||||
let onlyAltKey = false;
|
let onlyAltKey = false;
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Alt") {
|
if (event.key === "Alt") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onlyAltKey = true;
|
onlyAltKey = true;
|
||||||
|
|
@ -47,7 +47,7 @@ export function HomeMenu({ active, setActive, search }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyUp = (event) => {
|
const onKeyUp = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Alt" && onlyAltKey) {
|
if (event.key === "Alt" && onlyAltKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setActive(!active);
|
setActive(!active);
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,21 @@ import appStyles from "./AppList.module.css";
|
||||||
import AppsManager from "../../../features/apps/appsManager";
|
import AppsManager from "../../../features/apps/appsManager";
|
||||||
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
|
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
|
||||||
import { ReactSVG } from "react-svg";
|
import { ReactSVG } from "react-svg";
|
||||||
import { useEffect, useState } from "react";
|
import { ChangeEventHandler, useEffect, useState } from "react";
|
||||||
import { useKeyboardListener } from "../../../hooks/_utils/keyboard";
|
import { useKeyboardListener } from "../../../hooks/_utils/keyboard";
|
||||||
|
import App from "../../../features/apps/app";
|
||||||
|
|
||||||
/**
|
interface SearchMenuProps {
|
||||||
* @param {object} props
|
active: boolean;
|
||||||
* @param {boolean} props.active
|
setActive: Function;
|
||||||
* @param {Function} props.setActive
|
searchQuery: string;
|
||||||
* @param {string} props.searchQuery
|
setSearchQuery: Function;
|
||||||
* @param {Function} props.setSearchQuery
|
inputRef: { current: HTMLInputElement | undefined };
|
||||||
* @param {import("react").ElementRef} props.inputRef
|
}
|
||||||
*/
|
|
||||||
export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inputRef }) {
|
export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inputRef }: SearchMenuProps) {
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const [apps, setApps] = useState(null);
|
const [apps, setApps] = useState<App[]>(null);
|
||||||
const [tabIndex, setTabIndex] = useState(active ? 0 : -1);
|
const [tabIndex, setTabIndex] = useState(active ? 0 : -1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -38,8 +39,8 @@ export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inp
|
||||||
));
|
));
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event: Event) => {
|
||||||
const value = event.target.value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
setSearchQuery(value);
|
setSearchQuery(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -47,7 +48,7 @@ export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inp
|
||||||
if (active && apps)
|
if (active && apps)
|
||||||
classNames.push(styles.Active);
|
classNames.push(styles.Active);
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if ((event.key === "f" || event.key === "g") && event.ctrlKey && !active) {
|
if ((event.key === "f" || event.key === "g") && event.ctrlKey && !active) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setActive(true);
|
setActive(true);
|
||||||
|
|
@ -72,7 +73,7 @@ export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inp
|
||||||
aria-label="Search query"
|
aria-label="Search query"
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={onChange}
|
onChange={onChange as unknown as ChangeEventHandler}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
import styles from "./UtilMenu.module.css";
|
import styles from "./UtilMenu.module.css";
|
||||||
|
|
||||||
/**
|
interface UtilMenuProps {
|
||||||
* @param {object} props
|
active: boolean;
|
||||||
* @param {boolean} props.active
|
setActive: Function;
|
||||||
* @param {Function} props.setActive
|
className: string;
|
||||||
* @param {*} props.className
|
children: ReactNode;
|
||||||
* @param {*} props.children
|
}
|
||||||
*/
|
|
||||||
export function UtilMenu({ active, setActive, className, children }) {
|
export function UtilMenu({ active, setActive: _setActive, className, children }: UtilMenuProps) {
|
||||||
const classNames = [styles["Container-outer"]];
|
const classNames = [styles["Container-outer"]];
|
||||||
if (active)
|
if (active)
|
||||||
classNames.push(styles.Active);
|
classNames.push(styles.Active);
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import App from "../../features/apps/app";
|
||||||
|
|
||||||
export interface WindowFallbackViewProps {
|
export interface WindowFallbackViewProps {
|
||||||
error?: Error;
|
error?: Error;
|
||||||
resetErrorBoundary?: any;
|
resetErrorBoundary?: unknown;
|
||||||
app?: App;
|
app?: App;
|
||||||
closeWindow?: Function
|
closeWindow?: Function
|
||||||
}
|
}
|
||||||
|
|
||||||
// I don't know why this component's type needs to be ReactNode instead of FC, it has something to do with the way it's implemented
|
// I don't know why this component's type needs to be ReactNode instead of FC, it has something to do with the way it's implemented
|
||||||
export default function WindowFallbackView({ error, resetErrorBoundary, app, closeWindow }: WindowFallbackViewProps): ReactElement {
|
export default function WindowFallbackView({ error, resetErrorBoundary: _resetErrorBoundary, app, closeWindow }: WindowFallbackViewProps): ReactElement {
|
||||||
const { alert } = useAlert();
|
const { alert } = useAlert();
|
||||||
const [alerted, setAlerted] = useState(false);
|
const [alerted, setAlerted] = useState(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { faExpand, faMinus, faWindowMaximize as fasWindowMaximize, faTimes, faXm
|
||||||
import { ReactSVG } from "react-svg";
|
import { ReactSVG } from "react-svg";
|
||||||
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
|
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
import { FC, memo, MouseEventHandler, useEffect, useRef, useState } from "react";
|
import { CSSProperties, FC, memo, MouseEventHandler, useEffect, useRef, useState } from "react";
|
||||||
import Vector2 from "../../features/math/vector2";
|
import Vector2 from "../../features/math/vector2";
|
||||||
import { faWindowMaximize } from "@fortawesome/free-regular-svg-icons";
|
import { faWindowMaximize } from "@fortawesome/free-regular-svg-icons";
|
||||||
import utilStyles from "../../styles/utils.module.css";
|
import utilStyles from "../../styles/utils.module.css";
|
||||||
|
|
@ -111,14 +111,17 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
||||||
windowsManager.close(id);
|
windowsManager.close(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const focus = (event, force = false) => {
|
const focus = (event: Event, force = false): void => {
|
||||||
if (force)
|
if (force) {
|
||||||
return onInteract();
|
onInteract();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event?.defaultPrevented)
|
if (event?.defaultPrevented)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (event == null || event.target?.closest?.(".Handle") == null || event.target?.closest?.("button") == null)
|
const target = event?.target as HTMLElement;
|
||||||
|
if (event == null || target?.closest?.(".Handle") == null || target?.closest?.("button") == null)
|
||||||
onInteract();
|
onInteract();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -128,7 +131,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
||||||
if (minimized)
|
if (minimized)
|
||||||
classNames.push(styles.Minimized);
|
classNames.push(styles.Minimized);
|
||||||
|
|
||||||
return (<div style={{ zIndex, position: !maximized ? "relative" : null }}>
|
return (<div style={{ zIndex, position: !maximized ? "relative" : null } as CSSProperties}>
|
||||||
<ShortcutsListener/>
|
<ShortcutsListener/>
|
||||||
<Draggable
|
<Draggable
|
||||||
key={id}
|
key={id}
|
||||||
|
|
@ -146,13 +149,13 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
||||||
cancel="button"
|
cancel="button"
|
||||||
nodeRef={nodeRef}
|
nodeRef={nodeRef}
|
||||||
disabled={maximized}
|
disabled={maximized}
|
||||||
onStart={(event) => { focus(event); }}
|
onStart={(event) => { focus(event as Event); }}
|
||||||
grid={[1, 1]}
|
grid={[1, 1]}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames.join(" ")}
|
className={classNames.join(" ")}
|
||||||
ref={nodeRef}
|
ref={nodeRef}
|
||||||
onClick={focus}
|
onClick={focus as unknown as MouseEventHandler}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={styles["Window-inner"]}
|
className={styles["Window-inner"]}
|
||||||
|
|
@ -163,7 +166,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
||||||
>
|
>
|
||||||
<div className={`${styles.Header} Window-handle`} onContextMenu={onContextMenu} onDoubleClick={(event) => {
|
<div className={`${styles.Header} Window-handle`} onContextMenu={onContextMenu} onDoubleClick={(event) => {
|
||||||
setMaximized(!maximized);
|
setMaximized(!maximized);
|
||||||
focus(event, true);
|
focus(event as unknown as Event, true);
|
||||||
}}>
|
}}>
|
||||||
<ReactSVG
|
<ReactSVG
|
||||||
className={styles["Window-icon"]}
|
className={styles["Window-icon"]}
|
||||||
|
|
@ -179,20 +182,20 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setMaximized(!maximized);
|
setMaximized(!maximized);
|
||||||
focus(event, true);
|
focus(event as unknown as Event, true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
|
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
|
||||||
</button>
|
</button>
|
||||||
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-window"
|
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-window"
|
||||||
onClick={(event) => { close(event as unknown as Event); }}>
|
onClick={close as unknown as MouseEventHandler}>
|
||||||
<FontAwesomeIcon icon={faXmark}/>
|
<FontAwesomeIcon icon={faXmark}/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["Window-content"]}>
|
<div className={styles["Window-content"]}>
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
FallbackComponent={(props) => <WindowFallbackView app={app} closeWindow={close} {...props}/>}
|
FallbackComponent={(props) => <WindowFallbackView app={app} closeWindow={close} {...props}/>}
|
||||||
onReset={(details) => {
|
onReset={() => {
|
||||||
// Reset the state of your app so the error doesn't happen again
|
// Reset the state of your app so the error doesn't happen again
|
||||||
}}
|
}}
|
||||||
onError={(error) => {
|
onError={(error) => {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export const WindowsView: FC = memo(() => {
|
||||||
if (windowsManager.startupComplete)
|
if (windowsManager.startupComplete)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let startupAppNames = [];
|
let startupAppNames: string[] = [];
|
||||||
|
|
||||||
// Get app name and params from URL query
|
// Get app name and params from URL query
|
||||||
const params = getViewportParams();
|
const params = getViewportParams();
|
||||||
|
|
@ -54,7 +54,7 @@ export const WindowsView: FC = memo(() => {
|
||||||
|
|
||||||
// Get list of app names from settings file
|
// Get list of app names from settings file
|
||||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.apps);
|
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.apps);
|
||||||
settings.get("startup", (value) => {
|
void settings.get("startup", (value) => {
|
||||||
if (value !== "") {
|
if (value !== "") {
|
||||||
startupAppNames = value?.split(",").concat(startupAppNames);
|
startupAppNames = value?.split(",").concat(startupAppNames);
|
||||||
startupAppNames = removeDuplicatesFromArray(startupAppNames);
|
startupAppNames = removeDuplicatesFromArray(startupAppNames);
|
||||||
|
|
@ -79,7 +79,7 @@ export const WindowsView: FC = memo(() => {
|
||||||
position={position}
|
position={position}
|
||||||
options={options}
|
options={options}
|
||||||
minimized={minimized}
|
minimized={minimized}
|
||||||
toggleMinimized={(event) => {
|
toggleMinimized={(event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
windowsManager.setMinimized(id, !minimized);
|
windowsManager.setMinimized(id, !minimized);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export const CODE_FORMATS = [
|
||||||
"yml"
|
"yml"
|
||||||
];
|
];
|
||||||
|
|
||||||
export const EXTENSION_TO_LANGUAGE = {
|
export const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
||||||
"js": "javascript",
|
"js": "javascript",
|
||||||
"jsx": "javascript",
|
"jsx": "javascript",
|
||||||
"ts": "typescript",
|
"ts": "typescript",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
export function removeFromArray(item: any, array: any[]) {
|
export function removeFromArray<Type>(item: Type, array: Type[]) {
|
||||||
const index = array.indexOf(item);
|
const index = array.indexOf(item);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
array.splice(index, 1);
|
array.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function randomFromArray(array: any[]): any {
|
export function randomFromArray<Type>(array: Type[]): Type {
|
||||||
return array[Math.floor(Math.random() * array.length)];
|
return array[Math.floor(Math.random() * array.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDuplicatesFromArray(array: any[]): any[] {
|
export function removeDuplicatesFromArray<Type>(array: Type[]): Type[] {
|
||||||
return array.filter((item, index) => array.indexOf(item) === index);
|
return array.filter((item, index) => array.indexOf(item) === index);
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +15,9 @@ const TIME_INDICATORS = {
|
||||||
* @param maxLength - The maximum amount of units, e.g.: 3 => years, months, days
|
* @param maxLength - The maximum amount of units, e.g.: 3 => years, months, days
|
||||||
*/
|
*/
|
||||||
export function formatTime(time: number, maxLength: number = 3, allowAffixes: boolean): string {
|
export function formatTime(time: number, maxLength: number = 3, allowAffixes: boolean): string {
|
||||||
const result = [];
|
const result: string[] = [];
|
||||||
|
|
||||||
const formatResult = (result, inPast) => {
|
const formatResult = (result: string[], inPast: boolean): string => {
|
||||||
if (!allowAffixes)
|
if (!allowAffixes)
|
||||||
return result.join(", ");
|
return result.join(", ");
|
||||||
|
|
||||||
|
|
@ -44,8 +44,8 @@ export function formatTime(time: number, maxLength: number = 3, allowAffixes: bo
|
||||||
return formatResult(["less than a second"], inPast);
|
return formatResult(["less than a second"], inPast);
|
||||||
}
|
}
|
||||||
|
|
||||||
const units = [];
|
const units: { amount: number, label: string }[] = [];
|
||||||
const unitLabels = {
|
const unitLabels: Record<string, string> = {
|
||||||
"s": "seconds",
|
"s": "seconds",
|
||||||
"m": "minutes",
|
"m": "minutes",
|
||||||
"h": "hours",
|
"h": "hours",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
|
||||||
/**
|
/**
|
||||||
* Add event listener for an event
|
* Add event listener for an event
|
||||||
*/
|
*/
|
||||||
on<Key extends keyof EventMap>(eventName: Key, callback: (data: any) => void) {
|
on<Key extends keyof EventMap>(eventName: Key, callback: (data: unknown) => void) {
|
||||||
if (!this.#events[eventName as string]) {
|
if (!this.#events[eventName as string]) {
|
||||||
this.#events[eventName as string] = [];
|
this.#events[eventName as string] = [];
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
|
||||||
/**
|
/**
|
||||||
* Remove event listener for an event
|
* Remove event listener for an event
|
||||||
*/
|
*/
|
||||||
off<Key extends keyof EventMap>(eventName: Key, callback: (data: any) => void) {
|
off<Key extends keyof EventMap>(eventName: Key, callback: (data: unknown) => void) {
|
||||||
if (this.#events[eventName as string]) {
|
if (this.#events[eventName as string]) {
|
||||||
this.#events[eventName as string] = this.#events[eventName as string].filter(
|
this.#events[eventName as string] = this.#events[eventName as string].filter(
|
||||||
(listener) => listener !== callback
|
(listener) => listener !== callback
|
||||||
|
|
@ -29,7 +29,7 @@ export class EventEmitter<EventMap extends EventNamesMap> {
|
||||||
/**
|
/**
|
||||||
* Dispatch event
|
* Dispatch event
|
||||||
*/
|
*/
|
||||||
emit<Key extends keyof EventMap>(eventName: Key, data?: any) {
|
emit<Key extends keyof EventMap>(eventName: Key, data?: unknown) {
|
||||||
if (this.#events[eventName as string]) {
|
if (this.#events[eventName as string]) {
|
||||||
this.#events[eventName as string].forEach((listener) => {
|
this.#events[eventName as string].forEach((listener) => {
|
||||||
listener(data);
|
listener(data);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export function isValidInteger(number: number | string): number| boolean {
|
export function isValidInteger(number: number | string): number | boolean {
|
||||||
return (typeof number === "number" || parseInt(number) || parseInt(number) === 0);
|
return (typeof number === "number" || parseInt(number) || parseInt(number) === 0);
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ export default class AppsManager {
|
||||||
];
|
];
|
||||||
|
|
||||||
static getAppById(id: string): App | null {
|
static getAppById(id: string): App | null {
|
||||||
let application = null;
|
let application: App | null = null;
|
||||||
|
|
||||||
this.APPS.forEach((app) => {
|
this.APPS.forEach((app) => {
|
||||||
if (app.id === id) {
|
if (app.id === id) {
|
||||||
|
|
@ -54,7 +54,7 @@ export default class AppsManager {
|
||||||
* Get the app associated with a file extension
|
* Get the app associated with a file extension
|
||||||
*/
|
*/
|
||||||
static getAppByFileExtension(fileExtension: string): App {
|
static getAppByFileExtension(fileExtension: string): App {
|
||||||
let app = null;
|
let app: App = null;
|
||||||
|
|
||||||
if (IMAGE_FORMATS.includes(fileExtension))
|
if (IMAGE_FORMATS.includes(fileExtension))
|
||||||
return this.getAppById(APPS.MEDIA_VIEWER);
|
return this.getAppById(APPS.MEDIA_VIEWER);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Command from "./command";
|
import Command from "./command";
|
||||||
|
|
||||||
let commands = [];
|
let commands: Command[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically import commands
|
* Dynamically import commands
|
||||||
|
|
@ -9,7 +9,7 @@ const loadCommands = () => {
|
||||||
commands = [];
|
commands = [];
|
||||||
const context = require.context("./commands", false, /\.ts$/);
|
const context = require.context("./commands", false, /\.ts$/);
|
||||||
context.keys().forEach((key) => {
|
context.keys().forEach((key) => {
|
||||||
const commandModule = context(key);
|
const commandModule = context(key) as Record<string, Command>;
|
||||||
const commandName = Object.keys(commandModule)[0];
|
const commandName = Object.keys(commandModule)[0];
|
||||||
|
|
||||||
const command = commandModule[commandName];
|
const command = commandModule[commandName];
|
||||||
|
|
@ -25,7 +25,7 @@ export default class CommandsManager {
|
||||||
static COMMANDS = commands;
|
static COMMANDS = commands;
|
||||||
|
|
||||||
static find(name: string): Command {
|
static find(name: string): Command {
|
||||||
let matchCommand = null;
|
let matchCommand: Command = null;
|
||||||
|
|
||||||
this.COMMANDS.forEach((command) => {
|
this.COMMANDS.forEach((command) => {
|
||||||
if (command.name === name) {
|
if (command.name === name) {
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default class Modal {
|
||||||
this.lastInteraction = Date.now();
|
this.lastInteraction = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
finish(...args: any[]) {
|
finish(...args: unknown[]) {
|
||||||
if (this.modalsManager == null || this.id == null)
|
if (this.modalsManager == null || this.id == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export class Settings {
|
||||||
this.xmlDoc = xmlDoc;
|
this.xmlDoc = xmlDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
async write() {
|
write() {
|
||||||
if (!this.file)
|
if (!this.file)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -76,14 +76,16 @@ export class Settings {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(value);
|
callback(value);
|
||||||
|
|
||||||
this.file.on(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, async () => {
|
this.file.on(VirtualFile.EVENT_NAMES.CONTENT_CHANGE, () => {
|
||||||
await this.read();
|
void (async () => {
|
||||||
const newValue = await this.get(key);
|
await this.read();
|
||||||
|
const newValue = await this.get(key);
|
||||||
|
|
||||||
if (newValue !== value) {
|
if (newValue !== value) {
|
||||||
callback(newValue);
|
callback(newValue);
|
||||||
value = newValue;
|
value = newValue;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +104,6 @@ export class Settings {
|
||||||
this.xmlDoc.getElementsByTagName(PARENT_NODE)[0].appendChild(newOption);
|
this.xmlDoc.getElementsByTagName(PARENT_NODE)[0].appendChild(newOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.write();
|
this.write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import ReactGA from "react-ga4";
|
import ReactGA from "react-ga4";
|
||||||
import { GA_MEASUREMENT_ID } from "../../config/tracking.config";
|
import { GA_MEASUREMENT_ID } from "../../config/tracking.config";
|
||||||
|
import { UaEventOptions } from "react-ga4/types/ga4";
|
||||||
|
|
||||||
export class TrackingManager {
|
export class TrackingManager {
|
||||||
static initialize() {
|
static initialize() {
|
||||||
|
|
@ -7,8 +8,7 @@ export class TrackingManager {
|
||||||
ReactGA.initialize(GA_MEASUREMENT_ID);
|
ReactGA.initialize(GA_MEASUREMENT_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {ReactGA["event"]} */
|
static event(options: UaEventOptions | string) {
|
||||||
static event(options) {
|
|
||||||
console.info(options);
|
console.info(options);
|
||||||
|
|
||||||
if (GA_MEASUREMENT_ID == null)
|
if (GA_MEASUREMENT_ID == null)
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export class VirtualFile extends VirtualBase {
|
||||||
/**
|
/**
|
||||||
* Sets the content of this file and removes the source
|
* Sets the content of this file and removes the source
|
||||||
*/
|
*/
|
||||||
setContent(content: string | any) {
|
setContent(content: string) {
|
||||||
if (this.content === content || !this.canBeEdited)
|
if (this.content === content || !this.canBeEdited)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ export class VirtualFile extends VirtualBase {
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
console.error(`Error while reading file with id "${this.id}":`, error);
|
console.error(`Error while reading file with id "${this.id}":`, error);
|
||||||
return null;
|
return null;
|
||||||
});
|
}) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
isFile(): boolean {
|
isFile(): boolean {
|
||||||
|
|
@ -143,7 +143,7 @@ export class VirtualFile extends VirtualBase {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return iconUrl;
|
return iconUrl as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
getType(): string {
|
getType(): string {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ export class VirtualFolder extends VirtualBase {
|
||||||
* Finds and returns a file inside this folder matching a name and extension
|
* Finds and returns a file inside this folder matching a name and extension
|
||||||
*/
|
*/
|
||||||
findFile(name: string, extension?: string): VirtualFile | VirtualFileLink {
|
findFile(name: string, extension?: string): VirtualFile | VirtualFileLink {
|
||||||
let resultFile = null;
|
let resultFile: VirtualFile | VirtualFileLink = null;
|
||||||
|
|
||||||
this.files.forEach((file) => {
|
this.files.forEach((file) => {
|
||||||
const matchingName = (file.name === name || (file.alias && file.alias === name));
|
const matchingName = (file.name === name || (file.alias && file.alias === name));
|
||||||
|
|
@ -71,7 +71,7 @@ export class VirtualFolder extends VirtualBase {
|
||||||
* Finds and returns a folder inside this folder matching a name
|
* Finds and returns a folder inside this folder matching a name
|
||||||
*/
|
*/
|
||||||
findSubFolder(name: string): VirtualFolder | VirtualFolderLink {
|
findSubFolder(name: string): VirtualFolder | VirtualFolderLink {
|
||||||
let resultFolder = null;
|
let resultFolder: VirtualFolder | VirtualFolderLink = null;
|
||||||
|
|
||||||
this.subFolders.forEach((folder) => {
|
this.subFolders.forEach((folder) => {
|
||||||
if (folder.name === name || (folder.alias && folder.alias === name)) {
|
if (folder.name === name || (folder.alias && folder.alias === name)) {
|
||||||
|
|
@ -221,7 +221,7 @@ export class VirtualFolder extends VirtualBase {
|
||||||
/**
|
/**
|
||||||
* Removes a file or folder from this folder
|
* Removes a file or folder from this folder
|
||||||
*/
|
*/
|
||||||
remove(child: VirtualFile | VirtualFolder | VirtualFolderLink) {
|
remove(child: VirtualFile | VirtualFileLink | VirtualFolder | VirtualFolderLink) {
|
||||||
if (!this.canBeEdited)
|
if (!this.canBeEdited)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -241,9 +241,9 @@ export class VirtualFolder extends VirtualBase {
|
||||||
*/
|
*/
|
||||||
navigate(relativePath: string): VirtualFile | VirtualFolder | null {
|
navigate(relativePath: string): VirtualFile | VirtualFolder | null {
|
||||||
const segments = relativePath.split("/");
|
const segments = relativePath.split("/");
|
||||||
let currentDirectory: VirtualFile | VirtualFolder = this;
|
let currentDirectory: VirtualFile | VirtualFolder = this as VirtualFolder;
|
||||||
|
|
||||||
const getDirectory = (path, isStart) => {
|
const getDirectory = (path: string, isStart: boolean) => {
|
||||||
if (isStart && path === "") {
|
if (isStart && path === "") {
|
||||||
return this.getRoot();
|
return this.getRoot();
|
||||||
} else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) {
|
} else if (isStart && Object.keys(this.getRoot().shortcuts).includes(path)) {
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).createFolder("fonts", (folder) => {
|
}).createFolder("fonts", (folder) => {
|
||||||
folder.createFolders(["poppins", "roboto-mono"]);
|
folder.createFolders(["outfit", "roboto-mono"]);
|
||||||
}).createFolder("screenshots", (folder) => {
|
}).createFolder("screenshots", (folder) => {
|
||||||
folder.createFile("screenshot", "png", (file) => {
|
folder.createFile("screenshot", "png", (file) => {
|
||||||
file.setSource(`${process.env.PUBLIC_URL}/assets/screenshots/screenshot-files-settings-taskbar-desktop.png`);
|
file.setSource(`${process.env.PUBLIC_URL}/assets/screenshots/screenshot-files-settings-taskbar-desktop.png`);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { StorageManager } from "../../storage/storageManager";
|
import { StorageManager } from "../../storage/storageManager";
|
||||||
import { VirtualFolderLink } from "../folder/virtualFolderLink";
|
import { VirtualFolderLink, VirtualFolderLinkJson } from "../folder/virtualFolderLink";
|
||||||
import { loadDefaultData } from "./defaultData";
|
import { loadDefaultData } from "./defaultData";
|
||||||
import { VirtualFile, VirtualFileJson } from "../file/virtualFile";
|
import { VirtualFile, VirtualFileJson } from "../file/virtualFile";
|
||||||
import { VirtualFolder } from "../folder";
|
import { VirtualFolder } from "../folder";
|
||||||
import { VirtualFileLink } from "../file/virtualFileLink";
|
import { VirtualFileLink, VirtualFileLinkJson } from "../file/virtualFileLink";
|
||||||
import { VirtualFolderJson } from "../folder/virtualFolder";
|
import { VirtualFolderJson } from "../folder/virtualFolder";
|
||||||
|
|
||||||
export interface VirtualRootJson extends VirtualFolderJson {
|
export interface VirtualRootJson extends VirtualFolderJson {
|
||||||
|
|
@ -34,16 +34,16 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
if (data == null)
|
if (data == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let object;
|
let object: VirtualRootJson | null = null;
|
||||||
try {
|
try {
|
||||||
object = JSON.parse(data);
|
object = JSON.parse(data) as VirtualRootJson;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
if (object == null)
|
if (object == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const shortcuts = { ...object.scs };
|
const shortcuts = { ...object.scs } as Record<string, string>;
|
||||||
|
|
||||||
const addFile = ({
|
const addFile = ({
|
||||||
nam: name,
|
nam: name,
|
||||||
|
|
@ -52,7 +52,7 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
cnt: content,
|
cnt: content,
|
||||||
lnk: link,
|
lnk: link,
|
||||||
ico: iconUrl,
|
ico: iconUrl,
|
||||||
}: any, parent: VirtualFolder = this) => {
|
}: VirtualFileJson & VirtualFileLinkJson, parent: VirtualFolder = this) => {
|
||||||
if (link) {
|
if (link) {
|
||||||
parent.createFileLink(name, (fileLink: VirtualFileLink) => {
|
parent.createFileLink(name, (fileLink: VirtualFileLink) => {
|
||||||
fileLink.setLinkedPath(link);
|
fileLink.setLinkedPath(link);
|
||||||
|
|
@ -81,7 +81,7 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
fls: files,
|
fls: files,
|
||||||
lnk: link,
|
lnk: link,
|
||||||
ico: iconUrl,
|
ico: iconUrl,
|
||||||
}: any, parent: VirtualFolder = this) => {
|
}: VirtualFolderJson & VirtualFolderLinkJson, parent: VirtualFolder = this) => {
|
||||||
if (link) {
|
if (link) {
|
||||||
parent.createFolderLink(name, (folderLink: VirtualFolderLink) => {
|
parent.createFolderLink(name, (folderLink: VirtualFolderLink) => {
|
||||||
folderLink.setLinkedPath(link);
|
folderLink.setLinkedPath(link);
|
||||||
|
|
@ -94,7 +94,7 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
|
|
||||||
parent.createFolder(name, (folder: VirtualFolder) => {
|
parent.createFolder(name, (folder: VirtualFolder) => {
|
||||||
if (Object.values(shortcuts).includes(folder.displayPath)) {
|
if (Object.values(shortcuts).includes(folder.displayPath)) {
|
||||||
let alias;
|
let alias: string;
|
||||||
for (const [key, value] of Object.entries(shortcuts)) {
|
for (const [key, value] of Object.entries(shortcuts)) {
|
||||||
if (value === folder.displayPath)
|
if (value === folder.displayPath)
|
||||||
alias = key;
|
alias = key;
|
||||||
|
|
@ -102,12 +102,12 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
folder.setAlias(alias);
|
folder.setAlias(alias);
|
||||||
}
|
}
|
||||||
if (folders != null) {
|
if (folders != null) {
|
||||||
folders.forEach((subFolder: VirtualFolderJson) => {
|
folders.forEach((subFolder: VirtualFolderJson & VirtualFolderLinkJson) => {
|
||||||
addFolder(subFolder, folder);
|
addFolder(subFolder, folder);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
files.forEach((file: VirtualFileJson) => {
|
files.forEach((file: VirtualFileJson & VirtualFileLinkJson) => {
|
||||||
addFile(file, folder);
|
addFile(file, folder);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -118,12 +118,12 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (object.fds != null) {
|
if (object.fds != null) {
|
||||||
object.fds.forEach((subFolder: VirtualFolderJson) => {
|
object.fds.forEach((subFolder: VirtualFolderJson & VirtualFolderLinkJson) => {
|
||||||
addFolder(subFolder);
|
addFolder(subFolder);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (object.fls != null) {
|
if (object.fls != null) {
|
||||||
object.fls.forEach((file: VirtualFileJson) => {
|
object.fls.forEach((file: VirtualFileJson & VirtualFileLinkJson) => {
|
||||||
addFile(file);
|
addFile(file);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -181,15 +181,15 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static isValidName(name) {
|
static isValidName(_name: string) {
|
||||||
// TO DO
|
// TO DO
|
||||||
}
|
}
|
||||||
|
|
||||||
static isValidFileName(name) {
|
static isValidFileName(_name: string) {
|
||||||
// TO DO
|
// TO DO
|
||||||
}
|
}
|
||||||
|
|
||||||
static isValidFolderName(name) {
|
static isValidFolderName(_name: string) {
|
||||||
// TO DO
|
// TO DO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,9 +219,6 @@ export class VirtualRoot extends VirtualFolder {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string | null}
|
|
||||||
*/
|
|
||||||
toString(): string | null {
|
toString(): string | null {
|
||||||
const json = this.toJSON();
|
const json = this.toJSON();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
|
||||||
return this;;
|
return this;;
|
||||||
|
|
||||||
this.alias = alias;
|
this.alias = alias;
|
||||||
this.getRoot().addShortcut(alias, this as any);
|
this.getRoot().addShortcut(alias, this as never);
|
||||||
|
|
||||||
this.confirmChanges();
|
this.confirmChanges();
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -100,7 +100,7 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
|
||||||
if (parent == null)
|
if (parent == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
parent.remove?.(this as any);
|
parent.remove?.(this as never);
|
||||||
this.confirmChanges(parent.getRoot());
|
this.confirmChanges(parent.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,9 +114,11 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
|
||||||
root?.saveData();
|
root?.saveData();
|
||||||
}
|
}
|
||||||
|
|
||||||
open(..._args: any[]): any {}
|
open(..._args: unknown[]): unknown {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
get path() {
|
get path(): string {
|
||||||
return this.alias ?? this.displayPath;
|
return this.alias ?? this.displayPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,4 +179,13 @@ export class VirtualBase extends EventEmitter<EventNamesMap> {
|
||||||
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString(): string | null {
|
||||||
|
const json = this.toJSON();
|
||||||
|
|
||||||
|
if (json == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return JSON.stringify(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ export interface WindowOptions {
|
||||||
isFocused?: boolean;
|
isFocused?: boolean;
|
||||||
lastInteraction?: number;
|
lastInteraction?: number;
|
||||||
minimized?: boolean;
|
minimized?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WindowsManager {
|
export default class WindowsManager {
|
||||||
|
|
@ -174,8 +174,8 @@ export default class WindowsManager {
|
||||||
return active;
|
return active;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppWindowId(appId: string): string {
|
getAppWindowId(appId: string): string | null {
|
||||||
let windowId = null;
|
let windowId: string | null = null;
|
||||||
|
|
||||||
Object.values(this.windows).forEach((window) => {
|
Object.values(this.windows).forEach((window) => {
|
||||||
if (window.app.id === appId) {
|
if (window.app.id === appId) {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@ export class ZIndexManager extends EventEmitter<typeof ZIndexManagerEvents> {
|
||||||
|
|
||||||
static EVENT_NAMES = ZIndexManagerEvents;
|
static EVENT_NAMES = ZIndexManagerEvents;
|
||||||
|
|
||||||
/** @type {ZIndexGroup[]} */
|
groups: ZIndexGroup[] = [];
|
||||||
groups = [];
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -41,7 +40,7 @@ export class ZIndexManager extends EventEmitter<typeof ZIndexManagerEvents> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndex(groupIndex, index) {
|
getIndex(groupIndex: number, index: number) {
|
||||||
return this.groups[groupIndex].getIndex(index);
|
return this.groups[groupIndex].getIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,23 +1,19 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { clamp } from "../../features/math/clamp";
|
import { clamp } from "../../features/math/clamp";
|
||||||
|
|
||||||
/**
|
export function useHistory<Type>(initialState: Type): {
|
||||||
* @param {*} initialState
|
history: Type[];
|
||||||
* @returns {{
|
stateIndex: number;
|
||||||
* history: *[],
|
pushState: Function;
|
||||||
* stateIndex: number,
|
undo: Function;
|
||||||
* pushState: Function,
|
redo: Function;
|
||||||
* undo: Function,
|
undoAvailable: boolean;
|
||||||
* redo: Function,
|
redoAvailable: boolean;
|
||||||
* undoAvailable: boolean,
|
} {
|
||||||
* redoAvailable: boolean
|
const [history, setHistory] = useState<Type[]>(initialState ? [initialState] : []);
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function useHistory(initialState) {
|
|
||||||
const [history, setHistory] = useState(initialState ? [initialState] : []);
|
|
||||||
const [stateIndex, setStateIndex] = useState(0);
|
const [stateIndex, setStateIndex] = useState(0);
|
||||||
|
|
||||||
const pushState = (state) => {
|
const pushState = (state: Type) => {
|
||||||
if (state === history[0])
|
if (state === history[0])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -35,7 +31,7 @@ export function useHistory(initialState) {
|
||||||
setStateIndex(0);
|
setStateIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStateIndex = (delta) => {
|
const updateStateIndex = (delta: number) => {
|
||||||
const index = clamp(stateIndex + delta, 0, history.length - 1);
|
const index = clamp(stateIndex + delta, 0, history.length - 1);
|
||||||
|
|
||||||
if (index === stateIndex)
|
if (index === stateIndex)
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ interface UseShortcutsParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useShortcuts({ options, shortcuts, useCategories = true }: UseShortcutsParams) {
|
export function useShortcuts({ options, shortcuts, useCategories = true }: UseShortcutsParams) {
|
||||||
const [activeKeys, setActiveKeys] = useState([]);
|
const [activeKeys, setActiveKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
const checkShortcuts = useCallback((event, allowExecution = true) => {
|
const checkShortcuts = useCallback((event: KeyboardEvent, allowExecution = true) => {
|
||||||
if (!shortcuts)
|
if (!shortcuts)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
|
||||||
if (keys.includes("Alt") && !event.altKey)
|
if (keys.includes("Alt") && !event.altKey)
|
||||||
removeFromArray("Alt", keys);
|
removeFromArray("Alt", keys);
|
||||||
|
|
||||||
const checkGroup = (group: Record<string, string[]>, category?) => {
|
const checkGroup = (group: Record<string, string[]>, category?: string) => {
|
||||||
for (const [name, shortcut] of Object.entries(group)) {
|
for (const [name, shortcut] of Object.entries(group)) {
|
||||||
let active = true;
|
let active = true;
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (category != null) {
|
if (category != null) {
|
||||||
options?.[category]?.[name]?.();
|
(options?.[category]?.[name] as Function)?.();
|
||||||
} else {
|
} else {
|
||||||
(options?.[name] as Function)?.();
|
(options?.[name] as Function)?.();
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +69,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
|
||||||
|
|
||||||
if (useCategories) {
|
if (useCategories) {
|
||||||
for (const [category, group] of Object.entries(shortcuts)) {
|
for (const [category, group] of Object.entries(shortcuts)) {
|
||||||
checkGroup(group, category);
|
checkGroup(group as Record<string, string[]>, category);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
checkGroup(shortcuts as Record<string, string[]>);
|
checkGroup(shortcuts as Record<string, string[]>);
|
||||||
|
|
@ -78,7 +78,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
|
||||||
setActiveKeys(keys);
|
setActiveKeys(keys);
|
||||||
}, [activeKeys, options, shortcuts, useCategories]);
|
}, [activeKeys, options, shortcuts, useCategories]);
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
const isRepeated = activeKeys.includes(event.key);
|
const isRepeated = activeKeys.includes(event.key);
|
||||||
checkShortcuts(event, isRepeated);
|
checkShortcuts(event, isRepeated);
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ export function useShortcuts({ options, shortcuts, useCategories = true }: UseSh
|
||||||
setActiveKeys(activeKeys.concat([event.key]));
|
setActiveKeys(activeKeys.concat([event.key]));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyUp = (event) => {
|
const onKeyUp = (event: KeyboardEvent) => {
|
||||||
checkShortcuts(event);
|
checkShortcuts(event);
|
||||||
|
|
||||||
if (activeKeys.includes(event.key)) {
|
if (activeKeys.includes(event.key)) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
/**
|
interface UseMouseListenerParams {
|
||||||
* @param {object} params
|
onMouseDown: EventListener;
|
||||||
* @param {Function} params.onMouseDown
|
onMouseUp: EventListener;
|
||||||
* @param {Function} params.onMouseUp
|
onClick: EventListener;
|
||||||
* @param {Function} params.onClick
|
onContextMenu: EventListener;
|
||||||
* @param {Function} params.onContextMenu
|
}
|
||||||
*/
|
|
||||||
export function useMouseListener({ onMouseDown, onMouseUp, onClick, onContextMenu }) {
|
export function useMouseListener({ onMouseDown, onMouseUp, onClick, onContextMenu }: UseMouseListenerParams) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onMouseDown)
|
if (onMouseDown)
|
||||||
document.addEventListener("mousedown", onMouseDown);
|
document.addEventListener("mousedown", onMouseDown);
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,6 @@ function useOutsideClickListener(ref: { current: HTMLElement | null }, callback:
|
||||||
}, [ref, callback]);
|
}, [ref, callback]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} props
|
|
||||||
* @param {Function} props.onOutsideClick
|
|
||||||
* @param {import("react").ElementType} props.children
|
|
||||||
*/
|
|
||||||
|
|
||||||
interface OutsideClickListenerProps {
|
interface OutsideClickListenerProps {
|
||||||
onOutsideClick: (event: Event) => void;
|
onOutsideClick: (event: Event) => void;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { Ref, useEffect, useRef, useState } from "react";
|
||||||
import { TASKBAR_HEIGHT } from "../../config/taskbar.config";
|
import { TASKBAR_HEIGHT } from "../../config/taskbar.config";
|
||||||
|
|
||||||
/**
|
export function useScreenDimensions(): [screenWidth: number, screenHeight: number] {
|
||||||
* @returns {[number, number]}
|
const [screenWidth, setScreenWidth] = useState<number>(null);
|
||||||
*/
|
const [screenHeight, setScreenHeight] = useState<number>(null);
|
||||||
export function useScreenDimensions() {
|
|
||||||
const [screenWidth, setScreenWidth] = useState(null);
|
|
||||||
const [screenHeight, setScreenHeight] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeObserver = new ResizeObserver((event) => {
|
const resizeObserver = new ResizeObserver((event) => {
|
||||||
|
|
@ -20,17 +17,12 @@ export function useScreenDimensions() {
|
||||||
return [screenWidth, screenHeight];
|
return [screenWidth, screenHeight];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function useScreenBounds({ avoidTaskbar = true }: { avoidTaskbar: boolean; }): {
|
||||||
* @param {object} props
|
ref: Ref<HTMLElement>;
|
||||||
* @param {boolean} props.avoidTaskbar
|
initiated: boolean;
|
||||||
* @returns {{
|
alignLeft: boolean;
|
||||||
* ref,
|
alignTop: boolean;
|
||||||
* initiated: boolean,
|
} {
|
||||||
* alignLeft: boolean,
|
|
||||||
* alignTop: boolean
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function useScreenBounds({ avoidTaskbar = true }) {
|
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const [initiated, setInitiated] = useState(false);
|
const [initiated, setInitiated] = useState(false);
|
||||||
const [alignLeft, setAlignLeft] = useState(false);
|
const [alignLeft, setAlignLeft] = useState(false);
|
||||||
|
|
@ -41,7 +33,7 @@ export function useScreenBounds({ avoidTaskbar = true }) {
|
||||||
if (ref.current == null || screenWidth == null || screenHeight == null)
|
if (ref.current == null || screenWidth == null || screenHeight == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const rect = ref.current.getBoundingClientRect();
|
const rect = (ref.current as HTMLElement).getBoundingClientRect();
|
||||||
const maxX = screenWidth;
|
const maxX = screenWidth;
|
||||||
let maxY = screenHeight;
|
let maxY = screenHeight;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,34 +6,39 @@
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
/**
|
interface UseScrollWithShadowParams {
|
||||||
* @param {object} options
|
ref?: { current: HTMLElement | null };
|
||||||
* @param {React.ElementRef} options.ref
|
horizontal?: boolean;
|
||||||
* @param {Boolean=true} options.horizontal
|
dynamicOffset?: boolean;
|
||||||
* @param {Boolean=true} options.dynamicOffset
|
dynamicOffsetFactor?: number;
|
||||||
* @param {Number=1} options.dynamicOffsetFactor
|
shadow?: {
|
||||||
* @param {object} options.shadow
|
offset?: number;
|
||||||
* @param {Number=8} options.shadow.offset
|
blurRadius?: number;
|
||||||
* @param {Number=5} options.shadow.blurRadius
|
spreadRadius?: number;
|
||||||
* @param {Number=-5} options.shadow.spreadRadius
|
color?: {
|
||||||
* @param {object} options.shadow.color
|
r?: number;
|
||||||
* @param {Number=0} options.shadow.color.r
|
g?: number;
|
||||||
* @param {Number=0} options.shadow.color.g
|
b?: number;
|
||||||
* @param {Number=0} options.shadow.color.b
|
a?: number;
|
||||||
* @param {Number=50} options.shadow.color.a
|
}
|
||||||
*/
|
}
|
||||||
export function useScrollWithShadow(options) {
|
}
|
||||||
|
|
||||||
|
export function useScrollWithShadow(params: UseScrollWithShadowParams): {
|
||||||
|
boxShadow: string;
|
||||||
|
onUpdate: (event: Event) => void;
|
||||||
|
} {
|
||||||
const [initiated, setInitiated] = useState(false);
|
const [initiated, setInitiated] = useState(false);
|
||||||
const [scrollStart, setScrollStart] = useState(0);
|
const [scrollStart, setScrollStart] = useState(0);
|
||||||
const [scrollLength, setScrollLength] = useState(0);
|
const [scrollLength, setScrollLength] = useState(0);
|
||||||
const [clientLength, setClientLength] = useState(0);
|
const [clientLength, setClientLength] = useState(0);
|
||||||
|
|
||||||
if (options == null)
|
if (params == null)
|
||||||
options = {};
|
params = {};
|
||||||
if (options.shadow == null)
|
if (params.shadow == null)
|
||||||
options.shadow = {};
|
params.shadow = {};
|
||||||
if (options.shadow.color == null)
|
if (params.shadow.color == null)
|
||||||
options.shadow.color = {};
|
params.shadow.color = {};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ref,
|
ref,
|
||||||
|
|
@ -51,9 +56,9 @@ export function useScrollWithShadow(options) {
|
||||||
a = 50
|
a = 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} = options;
|
} = params;
|
||||||
|
|
||||||
const updateValues = useCallback((element) => {
|
const updateValues = useCallback((element: HTMLElement) => {
|
||||||
if (!element)
|
if (!element)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -62,8 +67,8 @@ export function useScrollWithShadow(options) {
|
||||||
setClientLength(horizontal ? element.clientWidth : element.clientHeight);
|
setClientLength(horizontal ? element.clientWidth : element.clientHeight);
|
||||||
}, [horizontal]);
|
}, [horizontal]);
|
||||||
|
|
||||||
const onUpdate = (event) => {
|
const onUpdate = (event: Event) => {
|
||||||
updateValues(event.target);
|
updateValues(event.target as HTMLElement);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { FC, useCallback } from "react";
|
import { FC, MouseEvent, useCallback } from "react";
|
||||||
import Vector2 from "../../features/math/vector2";
|
import Vector2 from "../../features/math/vector2";
|
||||||
import Modal from "../../features/modals/modal";
|
import Modal from "../../features/modals/modal";
|
||||||
import { ActionsProps, STYLES } from "../../components/actions/Actions";
|
import { ActionsProps, STYLES } from "../../components/actions/Actions";
|
||||||
|
|
@ -13,12 +13,12 @@ export function useContextMenu({ Actions }: UseContextMenuParams) {
|
||||||
const modalsManager = useModalsManager();
|
const modalsManager = useModalsManager();
|
||||||
|
|
||||||
// Open a new modal when context menu is triggered
|
// Open a new modal when context menu is triggered
|
||||||
const onContextMenu = useCallback((event, params = {}) => {
|
const onContextMenu = useCallback((event: MouseEvent<HTMLElement>, params: object = {}) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
let positionX = (event?.clientX ?? 0);
|
let positionX = event?.clientX ?? 0;
|
||||||
let positionY = (event?.clientY ?? 0);
|
let positionY = event?.clientY ?? 0;
|
||||||
|
|
||||||
if (modalsManager.containerRef?.current) {
|
if (modalsManager.containerRef?.current) {
|
||||||
const containerRect = modalsManager.containerRef.current.getBoundingClientRect();
|
const containerRect = modalsManager.containerRef.current.getBoundingClientRect();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { createContext, FC, ReactNode, useContext } from "react";
|
import { createContext, FC, ReactNode, useContext } from "react";
|
||||||
import { SettingsManager } from "../../features/settings/settingsManager";
|
import { SettingsManager } from "../../features/settings/settingsManager";
|
||||||
import { useVirtualRoot } from "../virtual-drive/virtualRootContext";
|
import { useVirtualRoot } from "../virtual-drive/virtualRootContext";
|
||||||
import { VirtualRoot } from "../../features/virtual-drive/root/virtualRoot";
|
|
||||||
|
|
||||||
type SettingsManagerState = SettingsManager | undefined;
|
type SettingsManagerState = SettingsManager | undefined;
|
||||||
|
|
||||||
|
|
@ -12,7 +11,7 @@ const SettingsManagerContext = createContext<SettingsManagerState>(undefined);
|
||||||
*/
|
*/
|
||||||
export const SettingsManagerProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
export const SettingsManagerProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
const settingsManager = new SettingsManager(virtualRoot as VirtualRoot);
|
const settingsManager = new SettingsManager(virtualRoot);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsManagerContext.Provider value={settingsManager}>
|
<SettingsManagerContext.Provider value={settingsManager}>
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,6 @@ export const VirtualRootProvider: FC<{ children: ReactNode }> = ({ children }) =
|
||||||
</VirtualRootContext.Provider>;
|
</VirtualRootContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export function useVirtualRoot(): VirtualRoot {
|
||||||
* @returns {VirtualRoot}
|
|
||||||
*/
|
|
||||||
export function useVirtualRoot() {
|
|
||||||
return useContext(VirtualRootContext);
|
return useContext(VirtualRootContext);
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,12 @@ import { useEffect, useState } from "react";
|
||||||
import { useZIndexManager } from "./zIndexManagerContext";
|
import { useZIndexManager } from "./zIndexManagerContext";
|
||||||
import { ZIndexManager } from "../../features/z-index/zIndexManager";
|
import { ZIndexManager } from "../../features/z-index/zIndexManager";
|
||||||
|
|
||||||
export function useZIndex({ groupIndex, index }) {
|
interface UseZIndexParams {
|
||||||
|
groupIndex: number;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useZIndex({ groupIndex, index }: UseZIndexParams) {
|
||||||
const initialIndex = (groupIndex * 10) + index;
|
const initialIndex = (groupIndex * 10) + index;
|
||||||
const [zIndex, setZIndex] = useState(initialIndex);
|
const [zIndex, setZIndex] = useState(initialIndex);
|
||||||
const zIndexManager = useZIndexManager();
|
const zIndexManager = useZIndexManager();
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,14 @@ import "./styles/global.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import reportWebVitals from "./reportWebVitals";
|
import reportWebVitals from "./reportWebVitals";
|
||||||
import { ASCII_LOGO, NAME } from "./config/branding.config";
|
import { ASCII_LOGO, NAME } from "./config/branding.config";
|
||||||
|
import { TrackingManager } from "./features/tracking/trackingManager";
|
||||||
|
|
||||||
export const START_DATE = new Date();
|
export const START_DATE = new Date();
|
||||||
|
|
||||||
|
TrackingManager.initialize();
|
||||||
|
|
||||||
// Render app
|
// Render app
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
root.render(<React.StrictMode>
|
root.render(<React.StrictMode>
|
||||||
<App/>
|
<App/>
|
||||||
</React.StrictMode>);
|
</React.StrictMode>);
|
||||||
|
|
@ -22,4 +25,4 @@ console.info(ASCII_LOGO + space + welcomeMessage);
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
// reportWebVitals(console.log);
|
reportWebVitals(() => {});
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
const reportWebVitals = (onPerfEntry) => {
|
import { ReportHandler } from "web-vitals";
|
||||||
|
|
||||||
|
const reportWebVitals = (onPerfEntry: ReportHandler) => {
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
void import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
getCLS(onPerfEntry);
|
getCLS(onPerfEntry);
|
||||||
getFID(onPerfEntry);
|
getFID(onPerfEntry);
|
||||||
getFCP(onPerfEntry);
|
getFCP(onPerfEntry);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue