12
components/apps/Tic80/StyledTic80.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledTic80 = styled.div`
|
||||
iframe {
|
||||
background-color: #1a1c2c;
|
||||
border: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledTic80;
|
||||
10
components/apps/Tic80/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import useTic80 from "components/apps/Tic80/useTic80";
|
||||
import StyledTic80 from "components/apps/Tic80/StyledTic80";
|
||||
import AppContainer from "components/system/Apps/AppContainer";
|
||||
import { type ComponentProcessProps } from "components/system/Apps/RenderComponent";
|
||||
|
||||
const Tic80: FC<ComponentProcessProps> = ({ id }) => (
|
||||
<AppContainer StyledComponent={StyledTic80} id={id} useHook={useTic80} />
|
||||
);
|
||||
|
||||
export default Tic80;
|
||||
143
components/apps/Tic80/useTic80.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { basename } from "path";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { type ContainerHookProps } from "components/system/Apps/AppContainer";
|
||||
import { useProcesses } from "contexts/process";
|
||||
import { bufferToUrl } from "utils/functions";
|
||||
import { useFileSystem } from "contexts/fileSystem";
|
||||
import { useSession } from "contexts/session";
|
||||
import { PREVENT_SCROLL } from "utils/constants";
|
||||
import useFileDrop from "components/system/Files/FileManager/useFileDrop";
|
||||
import useTitle from "components/system/Window/useTitle";
|
||||
import processDirectory from "contexts/process/directory";
|
||||
|
||||
const useTic80 = ({
|
||||
containerRef,
|
||||
id,
|
||||
setLoading,
|
||||
url,
|
||||
}: ContainerHookProps): void => {
|
||||
const { processes: { [id]: { libs = [], maximized, title } = {} } = {} } =
|
||||
useProcesses();
|
||||
const { foregroundId, setForegroundId } = useSession();
|
||||
const { readFile } = useFileSystem();
|
||||
const { onDragOver, onDrop } = useFileDrop({ id });
|
||||
const loadedUrl = useRef<string>(undefined);
|
||||
const initializing = useRef(false);
|
||||
const { appendFileToTitle } = useTitle(id);
|
||||
const createIframe = useCallback((): Window => {
|
||||
containerRef.current?.querySelector("iframe")?.remove();
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
|
||||
iframe.title = title || processDirectory[id].title;
|
||||
|
||||
containerRef.current?.append(iframe);
|
||||
|
||||
const contentWindow = iframe.contentWindow as Window;
|
||||
|
||||
contentWindow.document.body.style.margin = "0";
|
||||
contentWindow.document.body.style.overflow = "hidden";
|
||||
|
||||
return contentWindow;
|
||||
}, [containerRef, id, title]);
|
||||
const createCanvas = useCallback(
|
||||
(baseDocument: Document): HTMLCanvasElement => {
|
||||
const canvas = baseDocument.createElement("canvas");
|
||||
|
||||
canvas.id = "canvas";
|
||||
canvas.style.width = "100%";
|
||||
canvas.style.height = "100%";
|
||||
canvas.style.imageRendering = "pixelated";
|
||||
canvas.tabIndex = -1;
|
||||
|
||||
baseDocument.body.append(canvas);
|
||||
|
||||
return canvas;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const loadComputer = useCallback(
|
||||
(fileUrl?: string) => {
|
||||
initializing.current = true;
|
||||
setLoading(true);
|
||||
|
||||
const iframeWindow = createIframe();
|
||||
const canvas = createCanvas(iframeWindow.document);
|
||||
|
||||
const focusCanvas = (): void => {
|
||||
canvas.focus(PREVENT_SCROLL);
|
||||
setForegroundId(id);
|
||||
};
|
||||
const postRun = (): void => setLoading(false);
|
||||
|
||||
iframeWindow.Module = { canvas, postRun };
|
||||
|
||||
iframeWindow.addEventListener("click", focusCanvas);
|
||||
iframeWindow.addEventListener("focus", focusCanvas);
|
||||
iframeWindow.addEventListener("blur", () => setForegroundId(""));
|
||||
iframeWindow.addEventListener("dragover", onDragOver);
|
||||
iframeWindow.addEventListener("drop", onDrop);
|
||||
|
||||
const loadApp = (blobUrl?: string): void => {
|
||||
if (blobUrl) {
|
||||
iframeWindow.Module.arguments = [blobUrl];
|
||||
appendFileToTitle(basename(url));
|
||||
}
|
||||
|
||||
const [tic80Lib] = libs;
|
||||
const script = iframeWindow.document.createElement("script");
|
||||
|
||||
script.type = "text/javascript";
|
||||
script.src = tic80Lib;
|
||||
|
||||
iframeWindow.document.head.append(script);
|
||||
|
||||
loadedUrl.current = fileUrl || "";
|
||||
initializing.current = false;
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (fileUrl) {
|
||||
readFile(fileUrl).then((file) =>
|
||||
loadApp(`${bufferToUrl(file)}?e=.tic`)
|
||||
);
|
||||
} else {
|
||||
loadApp();
|
||||
}
|
||||
},
|
||||
[
|
||||
appendFileToTitle,
|
||||
createCanvas,
|
||||
createIframe,
|
||||
id,
|
||||
libs,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
readFile,
|
||||
setForegroundId,
|
||||
setLoading,
|
||||
url,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initializing.current && url !== loadedUrl.current) {
|
||||
loadComputer(url);
|
||||
}
|
||||
}, [loadComputer, url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (foregroundId === id) {
|
||||
requestAnimationFrame(() => {
|
||||
containerRef.current
|
||||
?.querySelector("iframe")
|
||||
?.contentWindow?.document.querySelector<HTMLCanvasElement>("#canvas")
|
||||
?.focus(PREVENT_SCROLL);
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks-addons/no-unused-deps
|
||||
}, [containerRef, foregroundId, id, maximized]);
|
||||
};
|
||||
|
||||
export default useTic80;
|
||||
@@ -88,6 +88,10 @@ const types = {
|
||||
process: ["Photos", ...TEXT_EDITORS],
|
||||
type: "Scalable Vector Graphics File",
|
||||
},
|
||||
Tic80: {
|
||||
process: ["Tic80"],
|
||||
type: "TIC-80 Cartridge",
|
||||
},
|
||||
WasmFile: {
|
||||
command: "wapm",
|
||||
icon: "wapm",
|
||||
@@ -133,6 +137,7 @@ const extensions: Record<string, Extension> = {
|
||||
".spl": types.FutureSplash,
|
||||
".svg": types.SvgFile,
|
||||
".swf": types.ShockwaveFlash,
|
||||
".tic": types.Tic80,
|
||||
".ttf": types.Font,
|
||||
".wasm": types.WasmFile,
|
||||
".whtml": types.WysiwygHtmlDocument,
|
||||
|
||||
@@ -305,6 +305,19 @@ const directory: Processes = {
|
||||
preferProcessIcon: true,
|
||||
title: "Terminal",
|
||||
},
|
||||
Tic80: {
|
||||
Component: dynamic(() => import("components/apps/Tic80")),
|
||||
backgroundColor: "#1A1C2C",
|
||||
defaultSize: {
|
||||
height: 346,
|
||||
width: 615,
|
||||
},
|
||||
dependantLibs: ["/Program Files/Tic80/tic80.wasm"],
|
||||
icon: "/System/Icons/tic80.webp",
|
||||
libs: ["/Program Files/Tic80/tic80.js"],
|
||||
lockAspectRatio: true,
|
||||
title: "TIC-80 tiny computer",
|
||||
},
|
||||
TinyMCE: {
|
||||
Component: dynamic(() => import("components/apps/TinyMCE")),
|
||||
backgroundColor: "#202124",
|
||||
|
||||
@@ -90,6 +90,7 @@ This project is greatly augmented by code from the open source community. Thank
|
||||
- [nostr-tools](https://github.com/nbd-wtf/nostr-tools)
|
||||
- [DOMPurify](https://github.com/cure53/DOMPurify)
|
||||
- [t-rex-runner](https://github.com/wayou/t-rex-runner)
|
||||
- [TIC-80](https://tic80.com/)
|
||||
|
||||
## Services
|
||||
|
||||
|
||||
1
public/Program Files/Tic80/tic80.js
Normal file
BIN
public/Program Files/Tic80/tic80.wasm
Normal file
BIN
public/System/Icons/144x144/tic80.png
Normal file
|
After Width: | Height: | Size: 701 B |
BIN
public/System/Icons/144x144/tic80.webp
Normal file
|
After Width: | Height: | Size: 430 B |
BIN
public/System/Icons/16x16/tic80.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
public/System/Icons/16x16/tic80.webp
Normal file
|
After Width: | Height: | Size: 152 B |
BIN
public/System/Icons/32x32/tic80.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
public/System/Icons/32x32/tic80.webp
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
public/System/Icons/48x48/tic80.png
Normal file
|
After Width: | Height: | Size: 461 B |
BIN
public/System/Icons/48x48/tic80.webp
Normal file
|
After Width: | Height: | Size: 304 B |
BIN
public/System/Icons/96x96/tic80.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
public/System/Icons/96x96/tic80.webp
Normal file
|
After Width: | Height: | Size: 434 B |
4
public/Users/Public/Start Menu/Emulators/TIC-80.url
Normal file
@@ -0,0 +1,4 @@
|
||||
[InternetShortcut]
|
||||
BaseURL=Tic80
|
||||
Comment=TIC-80 Tiny Computer
|
||||
IconFile=/System/Icons/tic80.webp
|
||||