goodbye doom
This commit is contained in:
parent
2bfb2be8ee
commit
0ae52669b3
11 changed files with 9 additions and 9686 deletions
|
|
@ -13,7 +13,7 @@ import unusedImports from "eslint-plugin-unused-imports";
|
|||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default defineConfig(
|
||||
{ ignores: ["dist", "browser", "packages/vencord-types", "src/equicordplugins/commandPalette/doom/assets"] },
|
||||
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
||||
{
|
||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||
settings: {
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
Chocolate Doom WebAssembly runtime
|
||||
Source: https://github.com/cloudflare/doom-wasm
|
||||
Engine lineage: https://github.com/chocolate-doom/chocolate-doom
|
||||
License: GNU GPL version 2 or later.
|
||||
|
||||
Chocolate Doom source headers state:
|
||||
"This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version."
|
||||
|
||||
Game data
|
||||
Source: https://github.com/freedoom/freedoom/releases/tag/v0.13.0
|
||||
Asset used: freedoom1.wad
|
||||
License: BSD 3-Clause.
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import freedoomWadGzipBase64 from "file://./assets/freedoom1.wad.gz?base64";
|
||||
import doomWasmScriptSource from "file://./assets/websockets-doom.js";
|
||||
import doomWasmBase64 from "file://./assets/websockets-doom.wasm?base64";
|
||||
|
||||
export { doomWasmBase64, doomWasmScriptSource, freedoomWadGzipBase64 };
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function getDoomBootstrapScript() {
|
||||
return `
|
||||
const setStatus = message => {
|
||||
parent.postMessage({ type: "command-palette-doom-status", message: String(message ?? "") }, "*");
|
||||
};
|
||||
|
||||
const defaultArgs = ["-iwad", "doom1.wad", "-window", "-nogui", "-nomusic"];
|
||||
const defaultConfig = [
|
||||
"use_mouse 0",
|
||||
"joyb_speed 29",
|
||||
"mouse_acceleration 2.0",
|
||||
"snd_musicdevice 0"
|
||||
].join("\\n");
|
||||
|
||||
async function gunzip(url) {
|
||||
if (typeof DecompressionStream !== "function") {
|
||||
throw new Error("This browser does not support gzip decompression");
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error("Failed to load Freedoom data");
|
||||
}
|
||||
|
||||
const stream = response.body.pipeThrough(new DecompressionStream("gzip"));
|
||||
return new Uint8Array(await new Response(stream).arrayBuffer());
|
||||
}
|
||||
|
||||
window.addEventListener("error", event => {
|
||||
setStatus(\`error: \${event.message}\`);
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", event => {
|
||||
setStatus(\`promise rejection: \${event.reason}\`);
|
||||
});
|
||||
|
||||
window.addEventListener("message", async event => {
|
||||
if (event.data === "focus-doom") {
|
||||
document.getElementById("canvas")?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data?.type !== "command-palette-doom-init" || window.__doomBooted) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.__doomBooted = true;
|
||||
|
||||
const { doomWasmScriptUrl, doomWasmUrl, wadGzipUrl } = event.data;
|
||||
const canvas = document.getElementById("canvas");
|
||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
||||
setStatus("error: canvas missing");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setStatus("boot: unpacking Freedoom");
|
||||
const wadBytes = await gunzip(wadGzipUrl);
|
||||
|
||||
setStatus("boot: starting Chocolate Doom");
|
||||
window.Module = {
|
||||
noInitialRun: true,
|
||||
canvas,
|
||||
locateFile(path) {
|
||||
return path.endsWith(".wasm") ? doomWasmUrl : path;
|
||||
},
|
||||
preRun: [() => {
|
||||
Module.FS.writeFile("/doom1.wad", wadBytes);
|
||||
Module.FS.writeFile("/default.cfg", defaultConfig);
|
||||
}],
|
||||
onRuntimeInitialized() {
|
||||
setStatus("boot: ready");
|
||||
canvas.focus();
|
||||
callMain([...defaultArgs, "-config", "default.cfg"]);
|
||||
},
|
||||
print(text) {
|
||||
setStatus(text);
|
||||
},
|
||||
printErr(text) {
|
||||
setStatus("stderr: " + text);
|
||||
},
|
||||
setStatus() { }
|
||||
};
|
||||
|
||||
const scriptTag = document.createElement("script");
|
||||
scriptTag.src = doomWasmScriptUrl;
|
||||
scriptTag.onload = () => setStatus("boot: engine loaded");
|
||||
scriptTag.onerror = () => setStatus("error: failed to load engine");
|
||||
document.body.appendChild(scriptTag);
|
||||
} catch (error) {
|
||||
setStatus(\`error: \${error instanceof Error ? error.message : String(error)}\`);
|
||||
}
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
export function getDoomRuntimeHtml(bootstrapScriptUrl: string) {
|
||||
return `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>DOOM</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #0a0a0a;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#doom-root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background:
|
||||
radial-gradient(circle at top, rgb(153 27 27 / 18%), transparent 42%),
|
||||
linear-gradient(180deg, #171717 0%, #090909 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
#canvas {
|
||||
display: block;
|
||||
width: min(960px, 96vw);
|
||||
max-width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
margin: auto;
|
||||
background: #000;
|
||||
image-rendering: pixelated;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="doom-root">
|
||||
<canvas id="canvas" tabindex="-1"></canvas>
|
||||
</div>
|
||||
<script src="${bootstrapScriptUrl}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
|
@ -698,23 +698,6 @@
|
|||
gap: 12px;
|
||||
}
|
||||
|
||||
.vc-command-palette-doom-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vc-command-palette-doom-iframe {
|
||||
width: min(100%, 960px);
|
||||
min-height: 68vh;
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
background: #050505;
|
||||
}
|
||||
|
||||
.vc-command-palette-page-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -905,8 +888,13 @@
|
|||
}
|
||||
|
||||
@keyframes vc-cp-actions-fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.vc-command-palette-actions-dropdown-header {
|
||||
|
|
@ -1073,4 +1061,4 @@ input.vc-command-palette-main-search-input:read-only,
|
|||
input.vc-command-palette-main-search-input:not(:read-only),
|
||||
.vc-command-palette-main-search-input input:not(:read-only) {
|
||||
color: var(--cp-text-primary) !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +71,6 @@ interface PalettePageStackItem {
|
|||
|
||||
const MENTIONS_CATEGORY_ID = "mentions-actions";
|
||||
const RECENTS_CATEGORY_ID = "recent-actions";
|
||||
const DOOM_QUERY_TRIGGERS = new Set(["doom", "run doom", "can it run doom"]);
|
||||
let persistedCategoryId: string | null = null;
|
||||
const SINGLE_SELECT_PROMPT_COMMAND_IDS = new Set([
|
||||
"command-palette-open-dm-query",
|
||||
|
|
@ -97,10 +96,6 @@ function hasPrimaryModifier(event: ReactKeyboardEvent<HTMLElement>): boolean {
|
|||
return IS_MAC ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey;
|
||||
}
|
||||
|
||||
function matchesDoomQuery(query: string): boolean {
|
||||
return DOOM_QUERY_TRIGGERS.has(query.trim().toLowerCase());
|
||||
}
|
||||
|
||||
function getCommandBadge(command: CommandEntry, fallback: "Command" | "Recent" | "Pinned"): string {
|
||||
const defaultBadge = fallback === "Recent" ? "Command" : fallback;
|
||||
if (!command.id.startsWith("custom-")) return defaultBadge;
|
||||
|
|
@ -564,13 +559,6 @@ export function CommandPaletteModal({ modalProps, instanceKey }: { modalProps: M
|
|||
const activePageState = activePage?.state ?? null;
|
||||
const isPageOpen = Boolean(activePageSpec && activePageState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!matchesDoomQuery(trimmedQuery)) return;
|
||||
if (activePromptCommand) return;
|
||||
if (activePage?.ref.id === "doom") return;
|
||||
pushPage({ id: "doom" });
|
||||
}, [activePage?.ref.id, activePromptCommand, trimmedQuery]);
|
||||
|
||||
const canDrillDown = !isPageOpen && isSelectable(selectedItem) && hasChildren(selectedItem.command, allCommands);
|
||||
const canGoBack = isPageOpen || navigationLevel.type !== "root";
|
||||
const selectedCommand = !isPageOpen && isSelectable(selectedItem) ? selectedItem.command : null;
|
||||
|
|
@ -1287,12 +1275,6 @@ export function CommandPaletteModal({ modalProps, instanceKey }: { modalProps: M
|
|||
return;
|
||||
}
|
||||
|
||||
if (isMainInputTarget && event.key === "Enter" && !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey && matchesDoomQuery(trimmedQuery)) {
|
||||
event.preventDefault();
|
||||
pushPage({ id: "doom" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowRight") {
|
||||
event.preventDefault();
|
||||
if (canDrillDown) drillDown();
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import doomPageSpec from "./specs/doomPage";
|
||||
import scheduledCreatePageSpec from "./specs/scheduledCreatePage";
|
||||
import sendDmPageSpec from "./specs/sendDmPage";
|
||||
import statusTimerPageSpec from "./specs/statusTimerPage";
|
||||
import type { PalettePageId, PalettePageSpec } from "./types";
|
||||
|
||||
const pageSpecs = new Map<PalettePageId, PalettePageSpec>([
|
||||
[doomPageSpec.id, doomPageSpec],
|
||||
[sendDmPageSpec.id, sendDmPageSpec],
|
||||
[scheduledCreatePageSpec.id, scheduledCreatePageSpec],
|
||||
[statusTimerPageSpec.id, statusTimerPageSpec]
|
||||
|
|
|
|||
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { useEffect, useRef, useState } from "@webpack/common";
|
||||
|
||||
import { doomWasmBase64, doomWasmScriptSource, freedoomWadGzipBase64 } from "../../../doom/runtimeAssets";
|
||||
import { getDoomBootstrapScript, getDoomRuntimeHtml } from "../../../doom/runtimeHtml";
|
||||
import type { PalettePageSpec } from "../types";
|
||||
|
||||
const cl = classNameFactory("vc-command-palette-");
|
||||
const logger = new Logger("CommandPalette", "DoomPage");
|
||||
|
||||
let focusDoomRuntime: (() => void) | null = null;
|
||||
|
||||
function base64ToBlobUrl(base64: string, type: string): string {
|
||||
const binary = atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
|
||||
for (let index = 0; index < binary.length; index++) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
}
|
||||
|
||||
return URL.createObjectURL(new Blob([bytes], { type }));
|
||||
}
|
||||
|
||||
function DoomRuntime() {
|
||||
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
||||
const doomWasmScriptUrlRef = useRef<string | null>(null);
|
||||
const doomWasmUrlRef = useRef<string | null>(null);
|
||||
const wadGzipUrlRef = useRef<string | null>(null);
|
||||
const bootstrapUrlRef = useRef<string | null>(null);
|
||||
const [iframeSrcDoc, setIframeSrcDoc] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
doomWasmScriptUrlRef.current = URL.createObjectURL(new Blob([
|
||||
doomWasmScriptSource
|
||||
], { type: "text/javascript" }));
|
||||
doomWasmUrlRef.current = base64ToBlobUrl(doomWasmBase64, "application/wasm");
|
||||
wadGzipUrlRef.current = base64ToBlobUrl(freedoomWadGzipBase64, "application/gzip");
|
||||
bootstrapUrlRef.current = URL.createObjectURL(new Blob([
|
||||
getDoomBootstrapScript()
|
||||
], { type: "text/javascript" }));
|
||||
setIframeSrcDoc(getDoomRuntimeHtml(bootstrapUrlRef.current));
|
||||
|
||||
const focus = () => {
|
||||
iframeRef.current?.focus();
|
||||
iframeRef.current?.contentWindow?.postMessage("focus-doom", "*");
|
||||
};
|
||||
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === "command-palette-doom-status" && typeof event.data?.message === "string") {
|
||||
logger.info(event.data.message);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoad = () => {
|
||||
if (!iframeRef.current?.contentWindow || !doomWasmScriptUrlRef.current || !doomWasmUrlRef.current || !wadGzipUrlRef.current) return;
|
||||
iframeRef.current.contentWindow.postMessage({
|
||||
type: "command-palette-doom-init",
|
||||
doomWasmScriptUrl: doomWasmScriptUrlRef.current,
|
||||
doomWasmUrl: doomWasmUrlRef.current,
|
||||
wadGzipUrl: wadGzipUrlRef.current
|
||||
}, "*");
|
||||
};
|
||||
|
||||
focusDoomRuntime = focus;
|
||||
const timer = window.setTimeout(focus, 200);
|
||||
window.addEventListener("message", onMessage);
|
||||
iframeRef.current?.addEventListener("load", onLoad);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timer);
|
||||
window.removeEventListener("message", onMessage);
|
||||
iframeRef.current?.removeEventListener("load", onLoad);
|
||||
if (focusDoomRuntime === focus) {
|
||||
focusDoomRuntime = null;
|
||||
}
|
||||
if (bootstrapUrlRef.current) URL.revokeObjectURL(bootstrapUrlRef.current);
|
||||
if (doomWasmScriptUrlRef.current) URL.revokeObjectURL(doomWasmScriptUrlRef.current);
|
||||
if (doomWasmUrlRef.current) URL.revokeObjectURL(doomWasmUrlRef.current);
|
||||
if (wadGzipUrlRef.current) URL.revokeObjectURL(wadGzipUrlRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cl("doom-page")}>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
className={cl("doom-iframe")}
|
||||
srcDoc={iframeSrcDoc}
|
||||
title="DOOM"
|
||||
allow="autoplay; fullscreen"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const doomPageSpec: PalettePageSpec = {
|
||||
id: "doom",
|
||||
title: "DOOM",
|
||||
submitLabel: "Refocus DOOM",
|
||||
fields: [],
|
||||
async submit(context) {
|
||||
focusDoomRuntime?.();
|
||||
context.showSuccess("Focused DOOM.");
|
||||
},
|
||||
renderPage() {
|
||||
return <DoomRuntime />;
|
||||
}
|
||||
};
|
||||
|
||||
export default doomPageSpec;
|
||||
Loading…
Add table
Reference in a new issue