securityos/components/system/Dialogs/Run/index.tsx

300 lines
9.2 KiB
TypeScript

import { parseCommand } from "components/apps/Terminal/functions";
import type { ComponentProcessProps } from "components/system/Apps/RenderComponent";
import StyledRun from "components/system/Dialogs/Run/StyledRun";
import StyledButton from "components/system/Dialogs/Transfer/StyledButton";
import {
getProcessByFileExtension,
getShortcutInfo,
} from "components/system/Files/FileEntry/functions";
import useFileDrop from "components/system/Files/FileManager/useFileDrop";
import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process";
import processDirectory from "contexts/process/directory";
import { useSession } from "contexts/session";
import { basename, extname, join } from "path";
import { useCallback, useLayoutEffect, useRef, useState } from "react";
import {
DESKTOP_PATH,
PACKAGE_DATA,
PREVENT_SCROLL,
SHORTCUT_EXTENSION,
} from "utils/constants";
import { haltEvent } from "utils/functions";
import { getIpfsFileName, getIpfsResource } from "utils/ipfs";
import spawnSheep from "utils/spawnSheep";
const OPEN_ID = "open";
const resourceAliasMap: Record<string, string> = {
cmd: "Terminal",
dos: "JSDOS",
explorer: "FileExplorer",
monaco: "MonacoEditor",
mspaint: "Paint",
vlc: "VideoPlayer",
};
const MESSAGE = `Digite o nome de um programa, pasta, documento ou recurso da Internet e ${PACKAGE_DATA.alias} vai abrir para você.`;
const notFound = (resource: string): void =>
// eslint-disable-next-line no-alert
alert(
`Não foi possível encontrar '${resource}'. Certifique-se de ter digitado o nome corretamente e tente novamente.`
);
const utilCommandMap: Record<string, () => void> = {
esheep: spawnSheep,
sheep: spawnSheep,
};
const Run: FC<ComponentProcessProps> = () => {
const {
open,
closeWithTransition,
processes: { Run: runProcess } = {},
} = useProcesses();
const { createPath, exists, readFile, stat, updateFolder } = useFileSystem();
const { foregroundId, runHistory, setRunHistory } = useSession();
const inputRef = useRef<HTMLInputElement | null>(null);
const [isInputFocused, setIsInputFocused] = useState(true);
const [isEmptyInput, setIsEmptyInput] = useState(!runHistory[0]);
const [running, setRunning] = useState(false);
const runResource = useCallback(
async (resource?: string) => {
if (!resource) return;
setRunning(true);
const addRunHistoryEntry = (): void =>
setRunHistory((currentRunHistory) =>
currentRunHistory[0] === resource
? currentRunHistory
: [resource, ...currentRunHistory]
);
const [resourcePid, ...resourceUrl] = parseCommand(resource);
let resourcePath = resource;
let closeOnExecute = true;
const resourceExists = await exists(resourcePath);
if (!resourceExists) {
resourcePath =
resourceUrl.length > 0 ? resourceUrl.join(" ") : resourcePid;
}
const isIpfs = resourcePath.startsWith("ipfs://");
if (resourceExists || isIpfs || (await exists(resourcePath))) {
if (isIpfs) {
try {
const ipfsData = await getIpfsResource(resourcePath);
resourcePath = join(
DESKTOP_PATH,
await createPath(
await getIpfsFileName(resourcePath, ipfsData),
DESKTOP_PATH,
ipfsData
)
);
updateFolder(DESKTOP_PATH, basename(resourcePath));
} catch {
// Ignore failure to get ipfs resource
}
}
const stats = await stat(resourcePath);
if (stats.isDirectory()) {
open("FileExplorer", { url: resourcePath }, "");
addRunHistoryEntry();
} else if (
resourcePid &&
resourceUrl.length > 0 &&
resourcePath !== resource
) {
const pid = Object.keys(processDirectory).find(
(processName) =>
processName.toLowerCase() === resourcePid.toLowerCase()
);
if (pid) {
open(pid, {
url:
pid === "Browser" && isIpfs
? resourceUrl.join(" ")
: resourcePath,
});
addRunHistoryEntry();
} else {
notFound(resourcePid);
closeOnExecute = false;
}
} else {
const extension = extname(resourcePath);
if (extension === SHORTCUT_EXTENSION) {
const { pid, url } = getShortcutInfo(await readFile(resourcePath));
if (pid) open(pid, { url });
} else {
const basePid = getProcessByFileExtension(extension);
if (basePid) {
open(basePid, {
url: basePid === "Browser" && isIpfs ? resource : resourcePath,
});
}
}
addRunHistoryEntry();
}
} else {
const pid = Object.keys(processDirectory).find(
(processName) =>
processName.toLowerCase() ===
(
resourceAliasMap[resourcePath.toLowerCase()] || resourcePath
).toLowerCase()
);
if (pid) {
open(pid);
addRunHistoryEntry();
} else if (utilCommandMap[resource.toLowerCase()]) {
utilCommandMap[resource.toLowerCase()]();
addRunHistoryEntry();
} else {
notFound(resource);
closeOnExecute = false;
}
}
setRunning(false);
if (closeOnExecute) closeWithTransition("Run");
},
[
closeWithTransition,
createPath,
exists,
open,
readFile,
setRunHistory,
stat,
updateFolder,
]
);
useLayoutEffect(() => {
if (foregroundId === "Run") {
inputRef.current?.focus(PREVENT_SCROLL);
if (inputRef.current?.value) inputRef.current?.select();
}
}, [foregroundId]);
useLayoutEffect(() => {
if (runProcess?.url && inputRef.current) {
inputRef.current.value = `${inputRef.current.value.trimEnd()} ${
runProcess.url
}`.trim();
setIsEmptyInput(false);
}
}, [runProcess?.url]);
return (
<StyledRun
{...useFileDrop({ id: "Run" })}
onContextMenu={(event) => {
if (!(event.target instanceof HTMLInputElement)) {
haltEvent(event);
}
}}
>
<figure>
<img alt="Executar" src="/System/Icons/32x32/run.webp" />
<figcaption>{MESSAGE}</figcaption>
</figure>
<div>
<label htmlFor={OPEN_ID}>Abrir:</label>
<div>
<input
ref={inputRef}
autoComplete="off"
defaultValue={runHistory[0]}
disabled={running}
enterKeyHint="go"
id={OPEN_ID}
onBlurCapture={({ relatedTarget }) => {
if (
!runProcess?.componentWindow ||
runProcess.componentWindow.contains(relatedTarget)
) {
inputRef.current?.focus(PREVENT_SCROLL);
} else {
setIsInputFocused(false);
}
}}
onFocusCapture={() => setIsInputFocused(true)}
onKeyDownCapture={(event) => {
const { key } = event;
if (key === "Enter") runResource(inputRef.current?.value.trim());
if (key === "Escape") {
haltEvent(event);
closeWithTransition("Run");
}
}}
onKeyUp={({ target }) =>
setIsEmptyInput(!(target as HTMLInputElement)?.value)
}
spellCheck="false"
type="text"
/>
<select
disabled={runHistory.length === 0}
onChange={({ target }) => {
if (inputRef.current) {
inputRef.current.value = target.value;
setIsEmptyInput(false);
}
}}
onClick={({ target }) => {
if (
target instanceof HTMLSelectElement &&
target.selectedIndex !== -1
) {
// eslint-disable-next-line no-param-reassign
target.selectedIndex = -1;
}
}}
>
{runHistory.map((entry, index) => (
// eslint-disable-next-line react/no-array-index-key
<option key={`${entry}-${index}`} value={entry}>
{entry}
</option>
))}
</select>
</div>
</div>
<nav>
<StyledButton
$active={isInputFocused}
disabled={isEmptyInput || running}
onClick={() => runResource(inputRef.current?.value.trim())}
>
OK
</StyledButton>
<StyledButton
disabled={running}
onClick={() => closeWithTransition("Run")}
>
Cancel
</StyledButton>
</nav>
</StyledRun>
);
};
export default Run;