Improved responsiveness + general maintenance
This commit is contained in:
parent
3bcb70ab95
commit
750a758634
17 changed files with 220 additions and 104 deletions
|
|
@ -15,7 +15,7 @@ echo -e "Commit message: \e[0;36m$COMMIT_MESSAGE\e[0m"
|
|||
echo -e "Repository: \e[0;36m$REPO_URL\e[0m"
|
||||
echo ""
|
||||
|
||||
if gh-pages -x -d dist -m "$COMMIT_MESSAGE" -r "$REPO_URL" ; then
|
||||
if gh-pages -d dist -m "$COMMIT_MESSAGE" -r "$REPO_URL" ; then
|
||||
echo -e "\e[0;32m✓ Successfully deployed to \e[0;36mhttps://$domain/\e[0m"
|
||||
else
|
||||
echo -e "\e[0;31mFailed to deploy\e[0m"
|
||||
|
|
|
|||
12
index.html
12
index.html
|
|
@ -12,9 +12,11 @@
|
|||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=4">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=4">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=4">
|
||||
<link rel="icon" href="/favicon.ico?v=5" sizes="any">
|
||||
<link rel="icon" href="/icon.svg?v=4" type="image/svg+xml">
|
||||
<link rel="icon" href="/icon.png" type="image/png">
|
||||
<link rel="manifest" href="/site.webmanifest?v=4">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=4" color="#4cdfff">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://os.prozilla.dev/favicon.ico?">
|
||||
<meta name="apple-mobile-web-app-title" content="ProzillaOS">
|
||||
<meta name="application-name" content="ProzillaOS">
|
||||
<meta name="msapplication-TileColor" content="#0d1114">
|
||||
|
|
@ -44,6 +46,14 @@
|
|||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="translucent-black">
|
||||
<script>
|
||||
// Change theme color to black if app is running as PWA
|
||||
const isPWA = (window.navigator.standalone === true) || (window.matchMedia("(display-mode: standalone)").matches)
|
||||
if (isPWA) {
|
||||
const themeColor = document.querySelector("meta[name='theme-color']");
|
||||
themeColor?.setAttribute("content", "#0D1114");
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- FAQ -->
|
||||
<script type="application/ld+json">
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#0D1114",
|
||||
"background_color": "#0D1114",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,15 @@
|
|||
margin: 0.5rem auto;
|
||||
}
|
||||
|
||||
.ContextMenu .TextDisplay {
|
||||
margin: 0;
|
||||
padding: 0.25rem 0.5rem;
|
||||
color: var(--foreground-color-1);
|
||||
font-size: 0.875rem;
|
||||
text-align: start;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.Header-menu {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
|
|
|
|||
7
src/components/actions/actions/TextDisplay.tsx
Normal file
7
src/components/actions/actions/TextDisplay.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import styles from "../Actions.module.css";
|
||||
|
||||
export function TextDisplay({ children }) {
|
||||
return <p className={styles.TextDisplay}>
|
||||
{children}
|
||||
</p>;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { WindowProps } from "../../windows/WindowView";
|
|||
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
|
||||
import { CommandResponse } from "../../../features/apps/terminal/command";
|
||||
import { APP_NAMES } from "../../../config/apps.config";
|
||||
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext";
|
||||
|
||||
interface TerminalProps extends WindowProps {
|
||||
startPath: string;
|
||||
|
|
@ -26,7 +27,7 @@ interface HistoryEntry {
|
|||
clear?: boolean;
|
||||
}
|
||||
|
||||
export function Terminal({ startPath, input, setTitle, close: exit, active }: TerminalProps) {
|
||||
export function Terminal({ startPath, input, setTitle, close: exit, active, focus }: TerminalProps) {
|
||||
const [inputKey, setInputKey] = useState(0);
|
||||
const [inputValue, setInputValue] = useState(input ?? "");
|
||||
const [history, setHistory] = useState<HistoryEntry[]>([{
|
||||
|
|
@ -39,21 +40,14 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
const [historyIndex, setHistoryIndex] = useState(0);
|
||||
const [stream, setStream] = useState<Stream>(null);
|
||||
const [streamOutput, setStreamOutput] = useState<string>(null);
|
||||
const streamRef = useRef(null);
|
||||
const ref = useRef(null);
|
||||
const [streamFocused, setStreamFocused] = useState(false);
|
||||
const settingsManager = useSettingsManager();
|
||||
|
||||
useEffect(() => {
|
||||
setTitle(`${USERNAME}@${HOSTNAME}: ${currentDirectory.root ? "/" : currentDirectory.path}`);
|
||||
}, [currentDirectory.path, currentDirectory.root, setTitle]);
|
||||
|
||||
useEffect(() => {
|
||||
if (streamFocused || streamRef.current == null || streamOutput == null)
|
||||
return;
|
||||
|
||||
(streamRef.current as unknown as HTMLDivElement).scrollTop = (streamRef.current as unknown as HTMLDivElement).scrollHeight;
|
||||
setStreamFocused(true);
|
||||
}, [streamFocused, streamOutput, streamRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!inputRef.current || !active)
|
||||
return;
|
||||
|
|
@ -61,6 +55,25 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
(inputRef.current as unknown as HTMLInputElement).focus();
|
||||
}, [inputRef, active]);
|
||||
|
||||
const scrollDown = () => {
|
||||
(ref.current as unknown as HTMLDivElement).scrollTop = (ref.current as unknown as HTMLDivElement).scrollHeight;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (streamFocused || ref.current == null || streamOutput == null)
|
||||
return;
|
||||
|
||||
scrollDown();
|
||||
setStreamFocused(true);
|
||||
}, [streamFocused, streamOutput, ref]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current == null || stream != null)
|
||||
return;
|
||||
|
||||
scrollDown();
|
||||
}, [inputValue]);
|
||||
|
||||
const prefix = `${ANSI.fg.cyan + USERNAME}@${HOSTNAME + ANSI.reset}:`
|
||||
+ `${ANSI.fg.blue + (currentDirectory.root ? "/" : currentDirectory.path) + ANSI.reset}$ `;
|
||||
|
||||
|
|
@ -90,23 +103,26 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
let lastOutput: CommandResponse = null;
|
||||
|
||||
stream.on(Stream.EVENT_NAMES.new, (text: string) => {
|
||||
let output: CommandResponse = text;
|
||||
pipes.forEach((pipe) => {
|
||||
if (output instanceof Stream)
|
||||
void (async () => {
|
||||
let output: CommandResponse = text;
|
||||
|
||||
for (const pipe of pipes) {
|
||||
if (output instanceof Stream)
|
||||
continue;
|
||||
|
||||
// Output from the previous command gets added as an argument for the next command
|
||||
output = await handleInput(output ? `${pipe} ${output as string}` : pipe);
|
||||
}
|
||||
|
||||
if ((output as unknown) instanceof Stream) {
|
||||
stream.stop();
|
||||
promptOutput(ANSI.fg.red + "Stream failed");
|
||||
return;
|
||||
|
||||
// Output from the previous command gets added as an argument for the next command
|
||||
output = handleInput(output ? `${pipe} ${output as string}` : pipe);
|
||||
});
|
||||
}
|
||||
|
||||
if ((output as unknown) instanceof Stream) {
|
||||
stream.stop();
|
||||
promptOutput(ANSI.fg.red + "Stream failed");
|
||||
return;
|
||||
}
|
||||
|
||||
lastOutput = output;
|
||||
setStreamOutput(output);
|
||||
lastOutput = output;
|
||||
setStreamOutput(output as string);
|
||||
})();
|
||||
});
|
||||
|
||||
stream.on(Stream.EVENT_NAMES.stop, () => {
|
||||
|
|
@ -121,7 +137,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
document.addEventListener("keydown", onKeyDown);
|
||||
};
|
||||
|
||||
const handleInput = (value: string): CommandResponse => {
|
||||
const handleInput = async (value: string): Promise<CommandResponse> => {
|
||||
const rawInputValueStart = value.indexOf(" ") + 1;
|
||||
const rawInputValue = rawInputValueStart <= 0 ? "" : value.substr(rawInputValueStart);
|
||||
const timestamp = Date.now();
|
||||
|
|
@ -187,7 +203,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
let response: CommandResponse = null;
|
||||
|
||||
try {
|
||||
response = command.execute(args, {
|
||||
response = await command.execute(args, {
|
||||
promptOutput,
|
||||
pushHistory,
|
||||
virtualRoot,
|
||||
|
|
@ -199,7 +215,8 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
options,
|
||||
exit,
|
||||
inputs,
|
||||
timestamp
|
||||
timestamp,
|
||||
settingsManager,
|
||||
});
|
||||
|
||||
if (response == null)
|
||||
|
|
@ -213,29 +230,33 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
}
|
||||
};
|
||||
|
||||
const submitInput = (value: string) => {
|
||||
const resetInput = () => {
|
||||
setInputValue("");
|
||||
setHistoryIndex(0);
|
||||
};
|
||||
|
||||
const submitInput = async (value: string) => {
|
||||
pushHistory({
|
||||
text: prefix + value,
|
||||
isInput: true,
|
||||
value
|
||||
});
|
||||
|
||||
setInputValue("");
|
||||
setHistoryIndex(0);
|
||||
|
||||
// Piping is used to chain commands
|
||||
let pipes = value.split(" | ");
|
||||
const completedPipes = [];
|
||||
|
||||
let output = null;
|
||||
pipes.forEach((pipe) => {
|
||||
for (const pipe of pipes) {
|
||||
if (output instanceof Stream)
|
||||
return;
|
||||
continue;
|
||||
|
||||
// Output from the previous command gets added as an argument for the next command
|
||||
output = handleInput(output ? `${pipe} ${output}` : pipe);
|
||||
output = await handleInput(output ? `${pipe} ${output}` : pipe);
|
||||
completedPipes.push(pipe);
|
||||
});
|
||||
}
|
||||
|
||||
resetInput();
|
||||
|
||||
pipes = pipes.filter((pipe) => !completedPipes.includes(pipe));
|
||||
|
||||
|
|
@ -274,7 +295,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
const { key } = event;
|
||||
|
||||
if (key === "Enter") {
|
||||
submitInput(value);
|
||||
void submitInput(value);
|
||||
setInputKey((previousKey) => previousKey + 1);
|
||||
} else if (key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
|
|
@ -306,7 +327,9 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
});
|
||||
};
|
||||
|
||||
const onMouseDown = (event: { button: number; preventDefault: () => void; }) => {
|
||||
const onMouseDown = (event: MouseEvent) => {
|
||||
focus(event);
|
||||
|
||||
if (event.button === 2) {
|
||||
event.preventDefault();
|
||||
|
||||
|
|
@ -324,9 +347,9 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
|||
|
||||
return (
|
||||
<div
|
||||
ref={streamRef}
|
||||
ref={ref}
|
||||
className={styles.Terminal}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseDown={onMouseDown as unknown as MouseEventHandler}
|
||||
onContextMenu={onContextMenu as unknown as MouseEventHandler}
|
||||
onClick={(event) => {
|
||||
if (window.getSelection().toString() === "") {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,23 @@ import { VirtualFile } from "../../../features/virtual-drive/file";
|
|||
import { WindowProps } from "../../windows/WindowView";
|
||||
import { DropdownAction } from "../../actions/actions/DropdownAction";
|
||||
import { ClickAction } from "../../actions/actions/ClickAction";
|
||||
import ModalsManager from "../../../features/modals/modalsManager";
|
||||
import App from "../../../features/apps/app";
|
||||
import WindowsManager from "../../../features/windows/windowsManager";
|
||||
|
||||
const OVERRIDES = {
|
||||
a: MarkdownLink,
|
||||
img: MarkdownImage,
|
||||
};
|
||||
|
||||
export interface MarkdownProps {
|
||||
modalsManager: ModalsManager;
|
||||
setCurrentFile: Function;
|
||||
currentFile: VirtualFile;
|
||||
app: App;
|
||||
windowsManager: WindowsManager;
|
||||
}
|
||||
|
||||
interface TextEditorProps extends WindowProps {
|
||||
file?: VirtualFile;
|
||||
}
|
||||
|
|
@ -121,7 +132,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
|
|||
currentFile,
|
||||
app,
|
||||
windowsManager
|
||||
}
|
||||
} as MarkdownProps
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useMemo } from "react";
|
||||
import { MarkdownProps } from "../TextEditor";
|
||||
import { sanitizeProps } from "../../../../features/apps/text-editor/_utils/sanitizeProps";
|
||||
|
||||
interface MarkdownImageProps {
|
||||
interface MarkdownImageProps extends MarkdownProps {
|
||||
src: string;
|
||||
alt: string
|
||||
}
|
||||
|
|
@ -14,6 +16,8 @@ export function MarkdownImage({ alt, src, ...props }: MarkdownImageProps) {
|
|||
return src;
|
||||
}, [src]);
|
||||
|
||||
sanitizeProps(props as MarkdownProps);
|
||||
|
||||
return <img
|
||||
alt={alt}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
import { MouseEventHandler, ReactNode, useMemo } from "react";
|
||||
import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faClipboard, faExternalLink } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useContextMenu } from "../../../../hooks/modals/contextMenu";
|
||||
import { Actions } from "../../../actions/Actions";
|
||||
import { ClickAction } from "../../../actions/actions/ClickAction";
|
||||
import { VirtualFile } from "../../../../features/virtual-drive/file";
|
||||
import { useWindowedModal } from "../../../../hooks/modals/windowedModal";
|
||||
import AppsManager from "../../../../features/apps/appsManager";
|
||||
import App from "../../../../features/apps/app";
|
||||
import { DialogBox } from "../../../modals/dialog-box/DialogBox";
|
||||
import { DIALOG_CONTENT_TYPES } from "../../../../config/modals.config";
|
||||
import Vector2 from "../../../../features/math/vector2";
|
||||
import WindowsManager from "../../../../features/windows/windowsManager";
|
||||
import { APPS } from "../../../../config/apps.config";
|
||||
import { MarkdownProps } from "../TextEditor";
|
||||
import { sanitizeProps } from "../../../../features/apps/text-editor/_utils/sanitizeProps";
|
||||
import { copyToClipboard, removeUrlProtocol } from "../../../../features/_utils/browser.utils";
|
||||
import { TextDisplay } from "../../../actions/actions/TextDisplay";
|
||||
|
||||
interface MarkdownLinkProps {
|
||||
interface MarkdownLinkProps extends MarkdownProps {
|
||||
href: string;
|
||||
children: ReactNode;
|
||||
windowsManager: WindowsManager;
|
||||
setCurrentFile: Function;
|
||||
currentFile: VirtualFile;
|
||||
app: App;
|
||||
}
|
||||
|
||||
export function MarkdownLink({ href, children, windowsManager, currentFile, setCurrentFile, app, ...props }: MarkdownLinkProps) {
|
||||
|
|
@ -55,7 +52,11 @@ export function MarkdownLink({ href, children, windowsManager, currentFile, setC
|
|||
|
||||
const { onContextMenu } = useContextMenu({ Actions: (props) =>
|
||||
<Actions {...props}>
|
||||
<TextDisplay>{removeUrlProtocol(href)}</TextDisplay>
|
||||
<ClickAction label="Open link" icon={faExternalLink} onTrigger={onClick}/>
|
||||
<ClickAction label="Copy link" icon={faClipboard} onTrigger={() => {
|
||||
copyToClipboard(href);
|
||||
}}/>
|
||||
</Actions>
|
||||
});
|
||||
|
||||
|
|
@ -69,6 +70,8 @@ export function MarkdownLink({ href, children, windowsManager, currentFile, setC
|
|||
return title;
|
||||
}, [href]);
|
||||
|
||||
sanitizeProps(props as MarkdownProps);
|
||||
|
||||
return <a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
|
|
|||
|
|
@ -24,14 +24,15 @@ import { Share } from "../modals/share/Share";
|
|||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import WindowFallbackView from "./WindowFallbackView";
|
||||
import { WindowOptions } from "../../features/windows/windowsManager";
|
||||
import { MIN_SCREEN_HEIGHT, MIN_SCREEN_WIDTH } from "../../config/windows.config";
|
||||
|
||||
export interface WindowProps extends WindowOptions {
|
||||
fullscreen?: boolean;
|
||||
onInteract?: Function
|
||||
setTitle?: Function;
|
||||
setIconUrl?: Function;
|
||||
close?: Function;
|
||||
focus?: Function;
|
||||
setTitle?: React.Dispatch<React.SetStateAction<string>>;
|
||||
setIconUrl?: React.Dispatch<React.SetStateAction<string>>;
|
||||
close?: (event?: Event) => void;
|
||||
focus?: (event: Event, force?: boolean) => void;
|
||||
active?: boolean;
|
||||
minimized?: boolean;
|
||||
toggleMinimized?: Function;
|
||||
|
|
@ -44,7 +45,6 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
|||
const nodeRef = useRef(null);
|
||||
const { openWindowedModal } = useWindowedModal();
|
||||
|
||||
const [startSize, setStartSize] = useState(size);
|
||||
const [startPosition, setStartPosition] = useState(position);
|
||||
const [maximized, setMaximized] = useState(fullscreen ?? false);
|
||||
const [screenWidth, screenHeight] = useScreenDimensions();
|
||||
|
|
@ -80,9 +80,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
|||
if (screenWidth == null || screenHeight == null)
|
||||
return;
|
||||
|
||||
if (size.x > screenWidth || size.y > screenHeight) {
|
||||
setStartSize(new Vector2(screenWidth - 32, screenHeight - 32));
|
||||
setStartPosition(new Vector2(16, 16));
|
||||
if (screenWidth < MIN_SCREEN_WIDTH || screenHeight < MIN_SCREEN_HEIGHT) {
|
||||
setMaximized(true);
|
||||
} else {
|
||||
if (position.x > screenWidth)
|
||||
|
|
@ -110,12 +108,12 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
|||
};
|
||||
}, [active, minimized, iconUrl, title]);
|
||||
|
||||
const close = (event?: Event) => {
|
||||
const close: WindowProps["close"] = (event) => {
|
||||
event?.preventDefault();
|
||||
windowsManager.close(id);
|
||||
};
|
||||
|
||||
const focus = (event: Event, force = false): void => {
|
||||
const focus: WindowProps["focus"] = (event, force = false) => {
|
||||
if (force) {
|
||||
onInteract();
|
||||
return;
|
||||
|
|
@ -147,7 +145,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
|||
bounds={{
|
||||
top: 0,
|
||||
bottom: screenHeight - 55,
|
||||
left: -startSize.x + 85,
|
||||
left: -size.x + 85,
|
||||
right: screenWidth - 5
|
||||
}}
|
||||
cancel="button"
|
||||
|
|
@ -164,8 +162,8 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
|||
<div
|
||||
className={styles["Window-inner"]}
|
||||
style={{
|
||||
width: maximized ? null : startSize.x,
|
||||
height: maximized ? null : startSize.y,
|
||||
width: maximized ? null : size.x,
|
||||
height: maximized ? null : size.y,
|
||||
}}
|
||||
>
|
||||
<div className={`${styles.Header} Window-handle`} onContextMenu={onContextMenu} onDoubleClick={(event) => {
|
||||
|
|
@ -182,15 +180,18 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
|
|||
>
|
||||
<FontAwesomeIcon icon={faMinus}/>
|
||||
</button>
|
||||
<button aria-label="Maximize" className={styles["Header-button"]} tabIndex={0} id="maximize-window"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setMaximized(!maximized);
|
||||
focus(event as unknown as Event, true);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
|
||||
</button>
|
||||
{screenWidth > MIN_SCREEN_WIDTH && screenHeight > MIN_SCREEN_HEIGHT
|
||||
? <button aria-label="Maximize" className={styles["Header-button"]} tabIndex={0} id="maximize-window"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setMaximized(!maximized);
|
||||
focus(event as unknown as Event, true);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
|
||||
</button>
|
||||
: null
|
||||
}
|
||||
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-window"
|
||||
onClick={close as unknown as MouseEventHandler}>
|
||||
<FontAwesomeIcon icon={faXmark}/>
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
export const SCREEN_MARGIN = 32;
|
||||
export const TITLE_SEPARATOR = "-";
|
||||
export const TITLE_SEPARATOR = "-";
|
||||
|
||||
// If the user's screen is smaller than these values, windows will always be maximized
|
||||
export const MIN_SCREEN_WIDTH = 350;
|
||||
export const MIN_SCREEN_HEIGHT = 350;
|
||||
|
|
@ -102,6 +102,10 @@ export function openUrl(url: string, target?: HTMLAttributeAnchorTarget) {
|
|||
window.open(url, target ?? "_blank");
|
||||
}
|
||||
|
||||
export function copyToClipboard(string: string, onSuccess: (value: void) => void, onFail: (value: void) => void) {
|
||||
export function removeUrlProtocol(url: string) {
|
||||
return url.replace(/^https?:\/\/|\/$/g, "");
|
||||
}
|
||||
|
||||
export function copyToClipboard(string: string, onSuccess?: (value: void) => void, onFail?: (value: void) => void) {
|
||||
navigator.clipboard.writeText(string).then(onSuccess, onFail);
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { SettingsManager } from "../../settings/settingsManager";
|
||||
import { VirtualFolder } from "../../virtual-drive/folder/virtualFolder";
|
||||
import { VirtualRoot } from "../../virtual-drive/root/virtualRoot";
|
||||
import Stream from "./stream";
|
||||
|
|
@ -21,9 +22,10 @@ type Execute = (args?: string[], options?: {
|
|||
rawInputValue?: string,
|
||||
options?: string[],
|
||||
exit?: Function,
|
||||
inputs?: Record<string, string>;
|
||||
inputs?: Record<string, string>,
|
||||
timestamp: number,
|
||||
}) => CommandResponse;
|
||||
settingsManager: SettingsManager,
|
||||
}) => CommandResponse | Promise<CommandResponse>;
|
||||
|
||||
type Manual = {
|
||||
purpose?: string,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ type Particle = {
|
|||
size: number;
|
||||
};
|
||||
|
||||
let particles: Particle[] = [];
|
||||
|
||||
function generateScreen(frame: number): string {
|
||||
function generateScreen(frame: number, screen: string[][], particles: Particle[]): string {
|
||||
// Spawn new particles
|
||||
if (frame % PARTICLES.framesBetweenSpawn) {
|
||||
const newParticle: Particle = {
|
||||
|
|
@ -34,20 +32,19 @@ function generateScreen(frame: number): string {
|
|||
particles.push(newParticle);
|
||||
}
|
||||
|
||||
// Create screen
|
||||
const screen: string[][] = [];
|
||||
for (let y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
const row: string[] = [];
|
||||
for (let x = 0; x < SCREEN_WIDTH; x++) {
|
||||
row.push(" ");
|
||||
}
|
||||
screen.push(row);
|
||||
}
|
||||
|
||||
// Move and render particles
|
||||
particles.forEach((particle) => {
|
||||
particle.position.y -= PARTICLES.fallSpeed;
|
||||
|
||||
// Clean previous render
|
||||
for (let i = 0; i < PARTICLES.fallSpeed; i++) {
|
||||
const positionX = particle.position.x;
|
||||
const positionY = particle.position.y + i + particle.size;
|
||||
|
||||
if (positionY < SCREEN_HEIGHT && positionY >= 0)
|
||||
screen[positionY][positionX] = " ";
|
||||
}
|
||||
|
||||
// Remove offscreen particles
|
||||
if (particle.position.y + particle.size <= 0 || particle.position.x >= SCREEN_WIDTH)
|
||||
return removeFromArray(particle, particles);
|
||||
|
|
@ -62,13 +59,13 @@ function generateScreen(frame: number): string {
|
|||
const positionX = particle.position.x;
|
||||
const positionY = particle.position.y + i;
|
||||
|
||||
if (positionX < SCREEN_WIDTH && positionY < SCREEN_HEIGHT && positionY > 0) {
|
||||
if (positionX < SCREEN_WIDTH && positionY < SCREEN_HEIGHT && positionY >= 0) {
|
||||
screen[positionY][positionX] = color + character + ANSI.reset;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return screen.map((row) => row.join("")).reverse().join("\n");
|
||||
return [...screen.map((row) => row.join(""))].reverse().join("\n");
|
||||
}
|
||||
|
||||
export const cmatrix = new Command()
|
||||
|
|
@ -78,11 +75,21 @@ export const cmatrix = new Command()
|
|||
})
|
||||
.setExecute(function() {
|
||||
const stream = new Stream();
|
||||
particles = [];
|
||||
const particles: Particle[] = [];
|
||||
|
||||
// Create screen
|
||||
const screen: string[][] = [];
|
||||
for (let y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
const row: string[] = [];
|
||||
for (let x = 0; x < SCREEN_WIDTH; x++) {
|
||||
row.push(" ");
|
||||
}
|
||||
screen.push(row);
|
||||
}
|
||||
|
||||
let frame = 0;
|
||||
const interval = setInterval(() => {
|
||||
const text = generateScreen(frame);
|
||||
const text = generateScreen(frame, screen, particles);
|
||||
stream.send(text);
|
||||
frame++;
|
||||
}, 100 / ANIMATION_SPEED);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { APPS } from "../../../../config/apps.config";
|
||||
import { ANSI } from "../../../../config/apps/terminal.config";
|
||||
import { ANSI_ASCII_LOGO, ANSI_LOGO_COLOR, NAME } from "../../../../config/branding.config";
|
||||
import { THEMES } from "../../../../config/themes.config";
|
||||
import { TimeManager } from "../../../_utils/time.utils";
|
||||
import { SettingsManager } from "../../../settings/settingsManager";
|
||||
import AppsManager from "../../appsManager";
|
||||
import Command from "../command";
|
||||
|
||||
|
|
@ -9,14 +11,17 @@ export const neofetch = new Command()
|
|||
.setManual({
|
||||
purpose: "Fetch system information"
|
||||
})
|
||||
.setExecute(function(args, { username, hostname }) {
|
||||
.setExecute(async function(args, { username, hostname, settingsManager }) {
|
||||
const leftColumn = ANSI_ASCII_LOGO.split("\n");
|
||||
const rightColumnWidth = username.length + hostname.length + 1;
|
||||
|
||||
const themeIndex = await settingsManager.get(SettingsManager.VIRTUAL_PATHS.theme).get("theme");
|
||||
const theme = THEMES[themeIndex ?? 0] as string;
|
||||
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
// Check for the browser name using regular expressions
|
||||
let browserName;
|
||||
let browserName: string;
|
||||
if (userAgent.match(/Firefox\//)) {
|
||||
browserName = "Mozilla Firefox";
|
||||
} else if (userAgent.match(/Edg\//)) {
|
||||
|
|
@ -29,7 +34,7 @@ export const neofetch = new Command()
|
|||
browserName = "Unknown";
|
||||
}
|
||||
|
||||
const formatLine = (label, text) => ANSI.fg.cyan + label.toUpperCase() + ANSI.reset + ": " + text;
|
||||
const formatLine = (label: string, text: string) => ANSI.fg.cyan + label.toUpperCase() + ANSI.reset + ": " + text;
|
||||
|
||||
const rightColumn = [
|
||||
`${ANSI.fg.cyan + username + ANSI.reset}@${ANSI.fg.cyan + hostname + ANSI.reset}`,
|
||||
|
|
@ -37,7 +42,7 @@ export const neofetch = new Command()
|
|||
formatLine("os", NAME),
|
||||
formatLine("uptime", TimeManager.getUptime(2)),
|
||||
formatLine("resolution", window.innerWidth + "x" + window.innerHeight),
|
||||
formatLine("theme", "default"),
|
||||
formatLine("theme", theme),
|
||||
formatLine("icons", "Font Awesome"),
|
||||
formatLine("terminal", AppsManager.getAppById(APPS.TERMINAL)?.name ?? "Unknown"),
|
||||
formatLine("browser", browserName),
|
||||
|
|
|
|||
9
src/features/apps/text-editor/_utils/sanitizeProps.ts
Normal file
9
src/features/apps/text-editor/_utils/sanitizeProps.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { MarkdownProps } from "../../../../components/apps/text-editor/TextEditor";
|
||||
|
||||
export function sanitizeProps(props: MarkdownProps) {
|
||||
delete props.modalsManager;
|
||||
delete props.setCurrentFile;
|
||||
delete props.currentFile;
|
||||
delete props.app;
|
||||
delete props.windowsManager;
|
||||
}
|
||||
|
|
@ -40,10 +40,27 @@ export default class WindowsManager {
|
|||
}
|
||||
|
||||
const size = options?.size ?? app.windowOptions?.size ?? new Vector2(700, 400);
|
||||
const position = new Vector2(randomRange(SCREEN_MARGIN, window.innerWidth - size.x - SCREEN_MARGIN),
|
||||
randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASKBAR_HEIGHT));
|
||||
|
||||
const availableScreenSpace = new Vector2(
|
||||
window.innerWidth - SCREEN_MARGIN * 2,
|
||||
window.innerHeight - SCREEN_MARGIN * 2 - TASKBAR_HEIGHT
|
||||
);
|
||||
|
||||
let fullscreen = false;
|
||||
|
||||
if (size.x > availableScreenSpace.x) {
|
||||
size.x = availableScreenSpace.x;
|
||||
fullscreen = true;
|
||||
} else if (size.y > availableScreenSpace.y) {
|
||||
size.y = availableScreenSpace.y;
|
||||
fullscreen = true;
|
||||
}
|
||||
|
||||
const position = new Vector2(
|
||||
SCREEN_MARGIN + randomRange(0, availableScreenSpace.x - size.x),
|
||||
SCREEN_MARGIN + randomRange(0, availableScreenSpace.y - size.y)
|
||||
);
|
||||
|
||||
if (options?.fullscreen) {
|
||||
if (typeof(options.fullscreen) == "string") {
|
||||
fullscreen = options.fullscreen.toLowerCase() === "true";
|
||||
|
|
|
|||
Loading…
Reference in a new issue