300 lines
9.2 KiB
TypeScript
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;
|