Files
anonymous_github/public/script/state.js
T

79 lines
2.9 KiB
JavaScript

import { reactive, watch, nextTick, onScopeDispose } from "vue";
export function getPath(object, path) {
return path?.split(".").reduce((value, key) => value?.[key], object);
}
// Page state inherits the shared session without copying it. All mutations are
// tracked by Vue, including those made by fetch callbacks and browser events.
export function createPageState(parent = null, events = new Map()) {
const own = reactive({});
const cleanups = [];
let disposed = false;
const state = new Proxy(own, {
get(target, key) {
// Forward Vue metadata so writes through this proxy notify render effects.
if ((typeof key === "string" && key.startsWith("__v_")) || Reflect.has(target, key)) return Reflect.get(target, key);
return parent?.[key];
},
});
Object.defineProperty(own, "parent", { value: parent, configurable: true });
Object.defineProperty(own, "viewState", { get: () => state });
state.watch = (source, callback, deep = false) => {
const read = typeof source === "function" ? () => source(state) : () => getPath(state, source);
const stop = watch(read, callback, { deep, flush: "post" });
cleanups.push(stop);
nextTick(() => { if (!disposed) callback(read(), read()); });
return stop;
};
state.watchGroup = (sources, callback) => state.watch(() => sources.map(s => getPath(state, s)), callback, true);
state.on = (name, callback) => {
if (name === "dispose") { cleanups.push(callback); return () => {}; }
if (!events.has(name)) events.set(name, new Set());
events.get(name).add(callback);
const off = () => events.get(name)?.delete(callback);
cleanups.push(off);
return off;
};
state.emit = (name, ...args) => {
for (const callback of [...(events.get(name) || [])]) callback({}, ...args);
};
state.dispose = () => {
disposed = true;
cleanups.splice(0).reverse().forEach(fn => fn());
};
onScopeDispose(state.dispose);
return state;
}
export function createTimers() {
const timeouts = new Set();
const intervals = new Set();
let disposed = false;
const timeout = (callback, delay = 0) => {
if (disposed) return;
const id = setTimeout(() => { timeouts.delete(id); callback(); }, delay);
timeouts.add(id);
return id;
};
timeout.cancel = id => { clearTimeout(id); timeouts.delete(id); };
const interval = (callback, delay) => {
if (disposed) return;
const id = setInterval(callback, delay);
intervals.add(id);
return id;
};
interval.cancel = id => { clearInterval(id); intervals.delete(id); };
onScopeDispose(() => { disposed = true; timeouts.forEach(clearTimeout); intervals.forEach(clearInterval); });
return { timeout, interval };
}
export function createListeners() {
const cleanups = [];
onScopeDispose(() => cleanups.forEach(fn => fn()));
return (target, type, callback, options) => {
target.addEventListener(type, callback, options);
cleanups.push(() => target.removeEventListener(type, callback, options));
};
}