securityos/components/apps/Browser/index.tsx

190 lines
6.0 KiB
TypeScript

import { bookmarks, HOME_PAGE } from "components/apps/Browser/config";
import { Arrow, Refresh, Stop } from "components/apps/Browser/NavigationIcons";
import StyledBrowser from "components/apps/Browser/StyledBrowser";
import type { ComponentProcessProps } from "components/system/Apps/RenderComponent";
import useTitle from "components/system/Window/useTitle";
import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process";
import processDirectory from "contexts/process/directory";
import useHistory from "hooks/useHistory";
import { extname } from "path";
import { useCallback, useEffect, useRef, useState } from "react";
import Button from "styles/common/Button";
import Icon from "styles/common/Icon";
import {
FAVICON_BASE_PATH,
IFRAME_CONFIG,
ONE_TIME_PASSIVE_EVENT,
} from "utils/constants";
import { getUrlOrSearch, GOOGLE_SEARCH_QUERY, label } from "utils/functions";
const Browser: FC<ComponentProcessProps> = ({ id }) => {
const {
icon: setIcon,
linkElement,
url: changeUrl,
processes: { [id]: process },
} = useProcesses();
const { prependFileToTitle } = useTitle(id);
const { url = "" } = process || {};
const initialUrl = url || HOME_PAGE;
const { canGoBack, canGoForward, history, moveHistory, position } =
useHistory(initialUrl, id);
const { exists, readFile } = useFileSystem();
const inputRef = useRef<HTMLInputElement | null>(null);
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const [loading, setLoading] = useState(false);
const [srcDoc, setSrcDoc] = useState("");
const changeHistory = (step: number): void => {
moveHistory(step);
if (inputRef.current) inputRef.current.value = history[position + step];
};
const currentUrl = useRef("");
const setUrl = useCallback(
async (addressInput: string): Promise<void> => {
const { contentWindow } = iframeRef.current || {};
if (contentWindow?.location) {
const isHtml =
[".htm", ".html"].includes(extname(addressInput).toLowerCase()) &&
(await exists(addressInput));
setLoading(true);
setSrcDoc("");
if (isHtml) setSrcDoc((await readFile(addressInput)).toString());
setIcon(id, processDirectory.Browser.icon);
if (!isHtml) {
const addressUrl = await getUrlOrSearch(addressInput);
contentWindow.location.replace(addressUrl);
if (addressUrl.startsWith(GOOGLE_SEARCH_QUERY)) {
prependFileToTitle(`${addressInput} - Google Search`);
} else {
const { name = "" } =
bookmarks?.find(
({ url: bookmarkUrl }) => bookmarkUrl === addressInput
) || {};
prependFileToTitle(name);
}
if (addressInput.startsWith("ipfs://")) {
setIcon(id, "/System/Icons/Favicons/ipfs.webp");
} else {
const favicon = new Image();
const faviconUrl = `${
new URL(addressUrl).origin
}${FAVICON_BASE_PATH}`;
favicon.addEventListener(
"error",
() => {
const { icon } =
bookmarks?.find(
({ url: bookmarkUrl }) => bookmarkUrl === addressUrl
) || {};
if (icon) setIcon(id, icon);
},
ONE_TIME_PASSIVE_EVENT
);
favicon.addEventListener(
"load",
() => setIcon(id, faviconUrl),
ONE_TIME_PASSIVE_EVENT
);
favicon.src = faviconUrl;
}
}
}
},
[exists, id, prependFileToTitle, readFile, setIcon]
);
useEffect(() => {
if (process && history[position] !== currentUrl.current) {
currentUrl.current = history[position];
setUrl(history[position]);
}
}, [history, position, process, setUrl]);
useEffect(() => {
if (iframeRef?.current) {
linkElement(id, "peekElement", iframeRef.current);
}
}, [id, linkElement]);
return (
<StyledBrowser $hasSrcDoc={Boolean(srcDoc)}>
<nav>
<div>
<Button
disabled={!canGoBack}
onClick={() => changeHistory(-1)}
{...label("Click to go back")}
>
<Arrow direction="left" />
</Button>
<Button
disabled={!canGoForward}
onClick={() => changeHistory(+1)}
{...label("Click to go forward")}
>
<Arrow direction="right" />
</Button>
<Button
disabled={loading}
onClick={() => setUrl(history[position])}
{...label("Reload this page")}
>
{loading ? <Stop /> : <Refresh />}
</Button>
</div>
<input
ref={inputRef}
defaultValue={initialUrl}
enterKeyHint="go"
onFocusCapture={() => inputRef.current?.select()}
onKeyDown={({ key }) => {
if (inputRef.current && key === "Enter") {
changeUrl(id, inputRef.current.value);
window.getSelection()?.removeAllRanges();
inputRef.current.blur();
}
}}
type="text"
/>
</nav>
<nav>
{bookmarks.map(({ name, icon, url: bookmarkUrl }) => (
<Button
key={name}
onClick={() => {
if (inputRef.current) {
inputRef.current.value = bookmarkUrl;
}
changeUrl(id, bookmarkUrl);
}}
{...label(`${name}\n${bookmarkUrl}`)}
>
<Icon alt={name} imgSize={16} src={icon} />
</Button>
))}
</nav>
<iframe
ref={iframeRef}
onLoad={() => setLoading(false)}
srcDoc={srcDoc || undefined}
title={id}
{...IFRAME_CONFIG}
/>
</StyledBrowser>
);
};
export default Browser;