178 lines
4.6 KiB
TypeScript
178 lines
4.6 KiB
TypeScript
import { TrackSegment } from "../track/system.ts";
|
|
import { Train, TrainCar } from "../train/train.ts";
|
|
import { InputManager } from "./input.ts";
|
|
|
|
type ContextStore = Record<string, any>;
|
|
|
|
const defaultContext: ContextStore = {};
|
|
const contextStack: ContextStore[] = [defaultContext];
|
|
|
|
const debug = JSON.parse(localStorage.getItem("debug") || "false");
|
|
|
|
export function setDefaultContext(context: ContextStore) {
|
|
Object.assign(defaultContext, context);
|
|
}
|
|
|
|
export function withContext<T>(context: ContextStore, fn: () => T): T {
|
|
contextStack.push(context);
|
|
try {
|
|
return fn();
|
|
} finally {
|
|
contextStack.pop();
|
|
}
|
|
}
|
|
|
|
export const ctx = new Proxy(
|
|
{},
|
|
{
|
|
get(_, prop: string) {
|
|
for (let i = contextStack.length - 1; i >= 0; i--) {
|
|
if (prop in contextStack[i]) return contextStack[i][prop];
|
|
}
|
|
if (prop in defaultContext) return defaultContext[prop];
|
|
throw new Error(`Context variable '${prop}' is not defined.`);
|
|
},
|
|
},
|
|
) as Record<string, unknown>;
|
|
|
|
export function getContext() {
|
|
return ctx;
|
|
}
|
|
export function getContextItem<T>(prop: string): T {
|
|
return ctx[prop] as T;
|
|
}
|
|
export function getContextItemOrDefault<T>(prop: string, defaultValue: T): T {
|
|
try {
|
|
return ctx[prop] as T;
|
|
} catch {
|
|
return defaultValue;
|
|
}
|
|
}
|
|
export function setContextItem<T>(prop: string, value: T) {
|
|
Object.assign(contextStack[contextStack.length - 1] ?? defaultContext, {
|
|
[prop]: value,
|
|
});
|
|
}
|
|
|
|
if (debug) {
|
|
setInterval(() => {
|
|
let ctxEl = document.getElementById("context");
|
|
if (!ctxEl) {
|
|
ctxEl = document.createElement("div");
|
|
ctxEl.id = "context";
|
|
document.body.append(ctxEl);
|
|
}
|
|
ctxEl.innerHTML = "";
|
|
const div = document.createElement("div");
|
|
const pre = document.createElement("pre");
|
|
const h3 = document.createElement("h3");
|
|
h3.textContent = "Default";
|
|
div.append(h3);
|
|
pre.textContent = safeStringify(defaultContext);
|
|
div.append(pre);
|
|
ctxEl.append(div);
|
|
for (const [idx, ctx] of contextStack.entries()) {
|
|
const div = document.createElement("div");
|
|
const pre = document.createElement("pre");
|
|
const h3 = document.createElement("h3");
|
|
h3.textContent = "CTX " + idx;
|
|
div.append(h3);
|
|
pre.textContent = safeStringify(ctx);
|
|
div.append(pre);
|
|
ctxEl.append(div);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function safeStringify(obj: any) {
|
|
const seen = new WeakSet();
|
|
return JSON.stringify(obj, (key, value) => {
|
|
if (typeof value === "object" && value !== null) {
|
|
if (seen.has(value)) {
|
|
return "[Circular]"; // Replace circular references
|
|
}
|
|
seen.add(value);
|
|
}
|
|
if (value instanceof Map) {
|
|
const val: Record<string, unknown> = {};
|
|
for (const [k, v] of value) {
|
|
if (typeof k !== "string") continue;
|
|
val[k] = v;
|
|
}
|
|
seen.add(value);
|
|
return val;
|
|
}
|
|
|
|
if (value instanceof Set) {
|
|
seen.add(value);
|
|
value = Array.from(value);
|
|
return value;
|
|
}
|
|
|
|
if (value instanceof TrackSegment) {
|
|
seen.add(value);
|
|
const val = { ...value };
|
|
(val as any).frontNeighbours = value.frontNeighbours.map((n) => n.id);
|
|
(val as any).backNeighbours = value.backNeighbours.map((n) => n.id);
|
|
return val;
|
|
}
|
|
|
|
// if (value instanceof Train) {
|
|
// const val = { ...value };
|
|
// // val.segments = value.segments.map((s) => s.id);
|
|
// delete (val as any).path;
|
|
// delete (val as any).nodes;
|
|
// delete (val as any).cars;
|
|
// delete (val as any).t;
|
|
|
|
// // val.t
|
|
// // val.
|
|
// return null;
|
|
// }
|
|
|
|
// if (value instanceof InputManager) {
|
|
// return {};
|
|
// }
|
|
|
|
// if (value instanceof TrainCar) {
|
|
// const val = { ...value };
|
|
|
|
// return val;
|
|
// }
|
|
|
|
return value;
|
|
}, 2);
|
|
}
|
|
const getContextStack = () => JSON.parse(safeStringify(contextStack));
|
|
const updateContextValue = (
|
|
key: string,
|
|
value: unknown,
|
|
depth = contextStack.length - 1,
|
|
) => {
|
|
const keys = key.split(".");
|
|
let context = contextStack[depth] ?? defaultContext;
|
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
if (context instanceof Map) {
|
|
context = context.get(keys[i]);
|
|
} else {
|
|
context = context[keys[i]];
|
|
}
|
|
}
|
|
if (context instanceof Map) {
|
|
context.set(keys.at(-1)!, value);
|
|
} else {
|
|
context[keys.at(-1)!] = value;
|
|
}
|
|
};
|
|
if (location.hostname === "localhost") {
|
|
globalThis.getContextStack = getContextStack;
|
|
globalThis.updateContextValue = updateContextValue;
|
|
globalThis.contextStack = contextStack;
|
|
}
|
|
|
|
declare global {
|
|
var getContextStack: () => ContextStore[];
|
|
var updateContextValue: (key: string, value: unknown, depth?: number) => void;
|
|
var contextStack: ContextStore[];
|
|
}
|