wm 1.0.1, crash handler demo, basic bundling support
This commit is contained in:
parent
349a9ddc0f
commit
ac2b338f13
10 changed files with 244 additions and 191 deletions
23
package.json
23
package.json
|
@ -2,17 +2,30 @@
|
|||
"name": "neofunkin-1",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"build": {
|
||||
"appId": "asper.games.neofunkin",
|
||||
"productName": "NeoFunkin [EARLY ALPHA]",
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": "portable"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && node scripts/finalizeBuild.js",
|
||||
"electron": "electron dist"
|
||||
"electron": "electron dist",
|
||||
"bundle": "electron-builder"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.4.1",
|
||||
"dependencies": {
|
||||
"electron": "^35.0.0",
|
||||
"express": "^4.21.2"
|
||||
},
|
||||
"pnpm": {
|
||||
|
@ -21,8 +34,10 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/electron": "^1.6.12",
|
||||
"electron": "^35.0.0",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.13.9",
|
||||
"electron-builder": "^25.1.8",
|
||||
"fs-extra": "^11.3.0",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
|
|
100
src/index.ts
100
src/index.ts
|
@ -1,60 +1,72 @@
|
|||
import wm from './lib/windowManager';
|
||||
import { easeInOutQuad } from './lib/animations';
|
||||
import { wait } from './lib/utils';
|
||||
import { app } from 'electron';
|
||||
import displayCrashScreen from './lib/crash';
|
||||
import express from 'express';
|
||||
import path from 'path';
|
||||
import { createServer } from 'http';
|
||||
|
||||
app.on('ready', () => {
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Process {
|
||||
nfenv?: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.nfenv = {
|
||||
serverPort: 65034 as number, // todo: pick a random one instead, not for now bcuz easier to debug and use /eval endpoint
|
||||
crash: function (reason?: string) {
|
||||
console.error('[nfcrash] Simulating a crash with reason:', reason || '*no reason*');
|
||||
displayCrashScreen();
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebRoot() {
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'webroot')));
|
||||
|
||||
app.get('/eval', (req: express.Request, res: express.Response): any => {
|
||||
const code = req.headers['x-code'] as string;
|
||||
|
||||
if (!code) return res.status(400).send('No code provided');
|
||||
|
||||
try {
|
||||
const result = (function(require) {
|
||||
return eval(code);
|
||||
})(require);
|
||||
res.status(200).send(result as string);
|
||||
} catch (error: any) {
|
||||
res.status(500).send(error.message as string);
|
||||
}
|
||||
});
|
||||
|
||||
// to-do: add echo webhook endpoint and endpoints to create more webhook endpoints (and also delete those)
|
||||
|
||||
server.listen(process.nfenv.serverPort, 'localhost');
|
||||
};
|
||||
|
||||
startWebRoot().then(() => {
|
||||
app.on('ready', async () => {
|
||||
try {
|
||||
const mw = wm.create({
|
||||
w: 10,
|
||||
h: 10,
|
||||
w: 20,
|
||||
h: 20,
|
||||
whp: true,
|
||||
onCreate: (win: any) => {
|
||||
win.loadURL('https://example.com');
|
||||
win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/mainmenu/test.html`);
|
||||
},
|
||||
});
|
||||
|
||||
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
(async () => {
|
||||
await wait(200);
|
||||
wm.resize({ id: mw, w: 25, h: 25, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
|
||||
await wait(1000);
|
||||
wm.resize({ id: mw, w: 50, h: 50, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
|
||||
await wait(650);
|
||||
wm.move({ id: mw, x: 0, y: 50, p: true, fromCenter: true});
|
||||
wm.resize({ id: mw, w: 33, h: 100, p: true });
|
||||
wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
|
||||
wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true })
|
||||
await wait(500);
|
||||
wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true});
|
||||
wm.resize({ id: mw, w: 33, h: 100, p: true });
|
||||
wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
|
||||
await wait(500);
|
||||
wm.move({ id: mw, x: 100, y: 50, p: true, fromCenter: true});
|
||||
wm.resize({ id: mw, w: 33, h: 100, p: true });
|
||||
wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
|
||||
await wait(500);
|
||||
wm.resize({ id: mw, w: 100, h: 100, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
|
||||
await wait(550);
|
||||
wm.resize({ id: mw, w: 100, h: 10, smooth: true, p: true, ease: easeInOutQuad, anchor: 'bottom'});
|
||||
await wait(550);
|
||||
wm.resize({ id: mw, w: 33, h: 10, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad, anchor: 'bottom'});
|
||||
await wait(500);
|
||||
wm.move({ id: mw, x: 50, y: 50, p: true, fromCenter: true, ease: easeInOutQuad, smooth: true});
|
||||
await wait(500);
|
||||
wm.resize({ id: mw, w: 50, h: 50, smooth: true, p: true, fromCenter: true, ease: easeInOutQuad});
|
||||
await wait(510);
|
||||
wm.eval(mw, `document.querySelector('body').innerHTML = '<h1>NeoFunkin</h1>'`);
|
||||
await wait(500);
|
||||
wm.eval(mw, `document.querySelector('body').innerHTML = '<h1>MrpGimlom</h1>'`);
|
||||
await wait(100);
|
||||
wm.eval(mw, `document.querySelector('body').innerHTML = '<h1>NeoFunkin: Window manager</h1>'`);
|
||||
await wait(500);
|
||||
wm.eval(mw, `document.querySelector('body').innerHTML = '<h1>NeoFunkin: Window manager</h1><br>DEMO BY @TRUE1ANN, NEOFUNKIN IS PROPERTY OF ASPER'`);
|
||||
await wait(1500);
|
||||
wm.eval(mw, `document.querySelector('body').innerHTML = '<h1>NeoFunkin: Window manager</h1><br>DEMO BY @TRUE1ANN, NEOFUNKIN IS PROPERTY OF ASPER<br><small>this took so much to do omfg</small>'`);
|
||||
})();
|
||||
wm.resize({ id: mw, w: 40, h: 40, smooth: true, p: true});
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.nfenv.crash();
|
||||
}
|
||||
});
|
||||
})
|
34
src/lib/crash.ts
Normal file
34
src/lib/crash.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { wm, wdata } from './windowManager';
|
||||
import { easeInOutQuad } from './animations';
|
||||
import { wait, toP } from './utils'
|
||||
import { app, screen } from 'electron';
|
||||
|
||||
export default async function displayCrashScreen() {
|
||||
const bwdata = { ...wdata };
|
||||
const win = wm.create({
|
||||
w: 40,
|
||||
h: 40,
|
||||
whp: true,
|
||||
onCreate: (win: any) => {
|
||||
win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/crash/index.html`);
|
||||
},
|
||||
});
|
||||
const button = wm.create({
|
||||
w: 10,
|
||||
h: 5,
|
||||
whp: true,
|
||||
noBorder: true,
|
||||
noBackground: true,
|
||||
onCreate: (win: any) => {
|
||||
win.loadURL(`http://localhost:${process.nfenv.serverPort}/screens/crash/exit.html`);
|
||||
},
|
||||
});
|
||||
await wait(300);
|
||||
console.log('[nfcrash] Deleted all windows');
|
||||
Object.keys(bwdata).forEach(async (id) => {
|
||||
bwdata[id].win.destroy(); // Note: We dont use wm.destroy(...) so ...onDestroy wont get called and cause trouble
|
||||
});
|
||||
const { width: sw, height: sh } = screen.getPrimaryDisplay().bounds;
|
||||
wm.move({ id: button, y: 75, x: 50, p: true, fromCenter: true });
|
||||
wm.follow.start(button, win);
|
||||
};
|
4
src/lib/utils.ts
Normal file
4
src/lib/utils.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export function toP(value: number, percentage: number) {
|
||||
return value * (percentage / 100) as number;
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
import { BrowserWindow, screen } from 'electron';
|
||||
import { BrowserWindow, Rectangle, screen } from 'electron';
|
||||
import { wm_create_conf, wm_move_conf, wm_resize_conf } from './../types/wm';
|
||||
import { toP } from './utils';
|
||||
|
||||
interface WindowData {
|
||||
win: BrowserWindow;
|
||||
conf: wm_create_conf;
|
||||
follows?: string;
|
||||
followStuff?: any;
|
||||
}
|
||||
|
||||
let wdata: { [key: string]: WindowData } = {};
|
||||
|
||||
function toP(value: number, percentage: number) {
|
||||
return value * (percentage / 100) as number;
|
||||
}
|
||||
|
||||
const wm = {
|
||||
create: function (c: wm_create_conf) {
|
||||
const wid = Array.from({ length: 8 }, () => '0123456789abcdef'[Math.floor(Math.random() * 16)]).join('');
|
||||
|
@ -21,7 +20,7 @@ const wm = {
|
|||
width: c.whp ? toP(sw, c.w!) : c.w,
|
||||
height: c.whp ? toP(sh, c.h!) : c.h,
|
||||
x: c.xyp ? toP(sw, c.x!) : c.x,
|
||||
y: c.xyp ? toP(sh, c.y!) : c.x,
|
||||
y: c.xyp ? toP(sh, c.y!) : c.y,
|
||||
frame: !c.noBorder,
|
||||
transparent: c.noBackground,
|
||||
webPreferences: {
|
||||
|
@ -30,7 +29,7 @@ const wm = {
|
|||
},
|
||||
});
|
||||
|
||||
wdata[wid] = { win: win, conf: c }; // for further pullin
|
||||
wdata[wid] = { win: win, conf: c };
|
||||
win.setMenuBarVisibility(false); // tell me WHY should i NOT make this the default
|
||||
|
||||
if (typeof c.onMinimize === 'function') win.on('minimize', () => c.onMinimize!(win));
|
||||
|
@ -43,7 +42,7 @@ const wm = {
|
|||
if (c.onCreate) c.onCreate(win);
|
||||
return wid // just so you can reference it later on
|
||||
},
|
||||
destroy: async function (id: number) {
|
||||
destroy: async function (id: string) {
|
||||
console.log('[wm] destroy', id);
|
||||
const win = wdata[id];
|
||||
if (typeof win.conf.onDestroy === 'function') await win.conf.onDestroy!(win.win);
|
||||
|
@ -108,86 +107,85 @@ const wm = {
|
|||
resize: function (c: wm_resize_conf) {
|
||||
console.log('[wm] resize', c.id);
|
||||
const win = wdata[c.id];
|
||||
let wb = win.win.getBounds();
|
||||
if (!win || !win.win) {
|
||||
console.error('Window data not found for id:', c.id);
|
||||
return;
|
||||
}
|
||||
const wb = win.win.getBounds();
|
||||
const sW = wb.width, sH = wb.height;
|
||||
// Use the given dimensions if provided; otherwise fall back to the current window size
|
||||
c.w = c.w === undefined ? sW : c.w;
|
||||
c.h = c.h === undefined ? sH : c.h;
|
||||
const duration = c.duration || 500;
|
||||
const tick = c.tick || 16;
|
||||
const totalSteps = Math.floor(duration / tick);
|
||||
// Get primary display bounds – make sure that screen is imported from Electron
|
||||
const { width: sw, height: sh } = screen.getPrimaryDisplay().bounds;
|
||||
let targetW = toP(c.p ? sw : 1, c.w);
|
||||
let targetH = toP(c.p ? sh : 1, c.h);
|
||||
// Calculate target width and height using percentage conversion if needed.
|
||||
// Assuming toP converts a percentage value to pixels
|
||||
const targetW = toP(c.p ? sw : 1, c.w);
|
||||
const targetH = toP(c.p ? sh : 1, c.h);
|
||||
|
||||
// Compute window center for centering if needed
|
||||
const centerX = wb.x + wb.width / 2;
|
||||
const centerY = wb.y + wb.height / 2;
|
||||
|
||||
// Adjust position based on the chosen anchor.
|
||||
const getAnchoredPosition = (curWidth: number, curHeight: number) => {
|
||||
let newX = wb.x;
|
||||
let newY = wb.y;
|
||||
if (!c.fromCenter && c.anchor) {
|
||||
switch (c.anchor) {
|
||||
case 'top':
|
||||
newX = wb.x;
|
||||
// Align to top – x remains same
|
||||
newY = wb.y;
|
||||
break;
|
||||
case 'bottom':
|
||||
newX = wb.x;
|
||||
// Align to bottom
|
||||
newY = wb.y + wb.height - curHeight;
|
||||
break;
|
||||
case 'left':
|
||||
// Align to left – y remains same
|
||||
newX = wb.x;
|
||||
newY = wb.y;
|
||||
break;
|
||||
case 'right':
|
||||
// Align to right
|
||||
newX = wb.x + wb.width - curWidth;
|
||||
newY = wb.y;
|
||||
break;
|
||||
default:
|
||||
newX = wb.x;
|
||||
newY = wb.y;
|
||||
}
|
||||
// Default: center the resized window relative to the original bounds
|
||||
newX = centerX - curWidth / 2;
|
||||
newY = centerY - curHeight / 2;
|
||||
}
|
||||
return { newX, newY };
|
||||
};
|
||||
|
||||
// Animation function to smooth the resize
|
||||
const animate = () => {
|
||||
let currentStep = 0;
|
||||
const startW = wb.width;
|
||||
const startH = wb.height;
|
||||
const step = () => {
|
||||
currentStep++;
|
||||
const t = currentStep / totalSteps;
|
||||
const t = Math.min(currentStep / totalSteps, 1);
|
||||
const eT = c.ease ? c.ease(t) : t;
|
||||
const newW = startW + (targetW - startW) * eT;
|
||||
const newH = startH + (targetH - startH) * eT;
|
||||
let newX: number, newY: number;
|
||||
if (c.fromCenter) {
|
||||
newX = Math.round(centerX - newW / 2);
|
||||
newY = Math.round(centerY - newH / 2);
|
||||
} else {
|
||||
// Get new positions based on the current width and height
|
||||
const pos = getAnchoredPosition(Math.round(newW), Math.round(newH));
|
||||
newX = pos.newX;
|
||||
newY = pos.newY;
|
||||
}
|
||||
win.win.setBounds({
|
||||
x: newX,
|
||||
y: newY,
|
||||
x: pos.newX,
|
||||
y: pos.newY,
|
||||
width: Math.round(newW),
|
||||
height: Math.round(newH)
|
||||
});
|
||||
if (currentStep < totalSteps) {
|
||||
setTimeout(step, tick);
|
||||
} else {
|
||||
let finalX: number, finalY: number;
|
||||
if (c.fromCenter) {
|
||||
finalX = Math.round(centerX - targetW / 2);
|
||||
finalY = Math.round(centerY - targetH / 2);
|
||||
} else {
|
||||
const pos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
|
||||
finalX = pos.newX;
|
||||
finalY = pos.newY;
|
||||
}
|
||||
// Make sure to set final bounds exactly
|
||||
const finalPos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
|
||||
win.win.setBounds({
|
||||
x: finalX,
|
||||
y: finalY,
|
||||
x: finalPos.newX,
|
||||
y: finalPos.newY,
|
||||
width: Math.round(targetW),
|
||||
height: Math.round(targetH)
|
||||
});
|
||||
|
@ -195,21 +193,15 @@ const wm = {
|
|||
};
|
||||
step();
|
||||
};
|
||||
|
||||
// Use animate if smooth animation is requested, otherwise set bounds immediately.
|
||||
if (c.smooth) {
|
||||
animate();
|
||||
} else {
|
||||
let finalX: number, finalY: number;
|
||||
if (c.fromCenter) {
|
||||
finalX = Math.round(centerX - targetW / 2);
|
||||
finalY = Math.round(centerY - targetH / 2);
|
||||
} else {
|
||||
const pos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
|
||||
finalX = pos.newX;
|
||||
finalY = pos.newY;
|
||||
}
|
||||
const finalPos = getAnchoredPosition(Math.round(targetW), Math.round(targetH));
|
||||
win.win.setBounds({
|
||||
x: finalX,
|
||||
y: finalY,
|
||||
x: finalPos.newX,
|
||||
y: finalPos.newY,
|
||||
width: Math.round(targetW),
|
||||
height: Math.round(targetH)
|
||||
});
|
||||
|
@ -222,6 +214,42 @@ const wm = {
|
|||
} catch (error) {
|
||||
console.error('Error executing JavaScript:', error);
|
||||
}
|
||||
},
|
||||
follow: {
|
||||
INTERNAL_follow: function (id: string) {
|
||||
const leadWin = wdata[wdata[id].follows!].win;
|
||||
const followWin = wdata[id].win;
|
||||
const leadBounds = leadWin.getBounds();
|
||||
const followBounds = followWin.getBounds();
|
||||
|
||||
if (wdata[id].followStuff === undefined) {
|
||||
wdata[id].followStuff = {};
|
||||
}
|
||||
|
||||
if (wdata[id].followStuff.offsetX === undefined || wdata[id].followStuff.offsetY === undefined) {
|
||||
wdata[id].followStuff.offsetX = followBounds.x - leadBounds.x;
|
||||
wdata[id].followStuff.offsetY = followBounds.y - leadBounds.y;
|
||||
}
|
||||
|
||||
const newX = leadBounds.x + wdata[id].followStuff.offsetX;
|
||||
const newY = leadBounds.y + wdata[id].followStuff.offsetY;
|
||||
|
||||
followWin.setPosition(newX, newY);
|
||||
followWin.show();
|
||||
},
|
||||
INTERNAL_followfocus: function (id: string) {
|
||||
wdata[id].win.show();
|
||||
},
|
||||
start: function (id: string, follows: string) {
|
||||
console.log('[wm]', id, 'follows', follows);
|
||||
wdata[id].follows = follows;
|
||||
wdata[follows].win.on('move', this.INTERNAL_follow.bind(this, id));
|
||||
wdata[follows].win.on('focus', this.INTERNAL_followfocus.bind(this, id));
|
||||
},
|
||||
stop: function (id: string) {
|
||||
wdata[wdata[id].follows!].win.removeListener('move', this.INTERNAL_follow.bind(this, id));
|
||||
wdata[wdata[id].follows!].win.removeListener('focus', this.INTERNAL_followfocus.bind(this, id));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
60
src/types/wm.d.ts
vendored
60
src/types/wm.d.ts
vendored
|
@ -1,60 +0,0 @@
|
|||
import { BrowserWindow } from 'electron';
|
||||
|
||||
export type wm_create_conf = {
|
||||
w?: number; // Width
|
||||
h?: number; // Height
|
||||
x?: number; // X start pos
|
||||
y?: number; // Y start pos
|
||||
whp?: boolean; // is w and h in percentages
|
||||
xyp?: boolean; // is x and y in percentages
|
||||
noBorder?: boolean; // Hide OS window decorations
|
||||
noBackground?: boolean; // Hide window background
|
||||
onCreate?: Function; // wm.create(...)
|
||||
onMinimize?: Function; // USER pressed '_'
|
||||
onMaximize?: Function; // USER pressed '[]'
|
||||
onRestore?: Function; // USER pressed '[]]'
|
||||
onUnfocus?: Function; // USER unfocused on the window
|
||||
onFocus?: Function; // USER focused on the window
|
||||
onClose?: Function; // USER pressed 'x'
|
||||
onDestroy?: Function; // USER pressed 'x' OR wm.destroy(...)
|
||||
};
|
||||
|
||||
export type wm_move_conf = {
|
||||
id: string; // ID of the window to perform everythin on
|
||||
x?: number; // New X
|
||||
y?: number; // New Y
|
||||
p?: boolean; // Use percentages
|
||||
fromCenter?: boolean; // Should be performed from the center of the window?
|
||||
smooth?: boolean; // Should it be smooth?
|
||||
ease?: Function; // Calculates the easing (lib/animations.ts)
|
||||
duration?: number; // (ms) The total duration of the animation
|
||||
tick?: number; // (ms) Delay between window movenments
|
||||
}
|
||||
|
||||
export type wm_resize_conf = {
|
||||
id: string; // ID of the window to perform everythin on
|
||||
w?: number; // New width
|
||||
h?: number; // New height
|
||||
p?: boolean; // Use percentages
|
||||
fromCenter?: boolean; // Should be performed from the center of the window?
|
||||
smooth?: boolean; // Should it be smooth?
|
||||
ease?: Function; // Calculates the easing (lib/animations.ts)
|
||||
duration?: number; // (ms) The total duration of the animation
|
||||
tick?: number; // (ms) Delay between window resizings
|
||||
}
|
||||
|
||||
interface WindowData {
|
||||
win: BrowserWindow;
|
||||
conf: wm_create_conf;
|
||||
}
|
||||
|
||||
declare const wm: {
|
||||
create: (c: wm_create_conf) => string;
|
||||
destroy: (id: string) => Promise<void>;
|
||||
move: (c: wm_move_conf) => void;
|
||||
resize: (c: wm_resize_conf) => void;
|
||||
eval: (id: string, code: Function) => Promise<any>;
|
||||
};
|
||||
|
||||
export default wm;
|
||||
export { WindowData, wm };
|
|
@ -1,6 +1,6 @@
|
|||
export type wm_create_conf = {
|
||||
w?: number; // Width
|
||||
h?: number; // Height
|
||||
w: number; // Width
|
||||
h: number; // Height
|
||||
x?: number; // X start pos
|
||||
y?: number; // Y start pos
|
||||
whp?: boolean; // is w and h in percentages
|
||||
|
@ -34,10 +34,9 @@ export type wm_resize_conf = {
|
|||
w?: number; // New width
|
||||
h?: number; // New height
|
||||
p?: boolean; // Use percentages
|
||||
fromCenter?: boolean; // Should be performed from the center of the window?
|
||||
smooth?: boolean; // Should it be smooth?
|
||||
ease?: Function; // Calculates the easing (lib/animations.ts)
|
||||
duration?: number; // (ms) The total duration of the animation
|
||||
tick?: number; // (ms) Delay between window resizings
|
||||
anchor?: string; // bottom, top, left, right
|
||||
anchor?: string; // bottom, top, left, right, center
|
||||
}
|
18
src/webroot/screens/crash/exit.html
Normal file
18
src/webroot/screens/crash/exit.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NeoFunkin</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button style="width: 100vw; height: 100vh; margin: 0;" onclick="fetch('/eval', { headers: { 'x-code': 'process.exit(1)' } });">Exit</button>
|
||||
</body>
|
||||
</html>
|
|
@ -5,5 +5,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<h1>Uh oh.</h1>
|
||||
NeoFunkin crashed. Check logs for more details.
|
||||
</body>
|
||||
</html>
|
|
@ -5,5 +5,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
<button onclick="fetch('/eval', { headers: { 'x-code': 'process.nfenv.crash(`funny`);' } });">Crash</button>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue