@novely/renderer-toolkit
Version:
Toolkit for creating renderer for novely
1,718 lines (1,689 loc) • 42.3 kB
JavaScript
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/task/index.js
var tasks = 0;
var resolves = [];
function startTask() {
tasks += 1;
return () => {
tasks -= 1;
if (tasks === 0) {
let prevResolves = resolves;
resolves = [];
for (let i of prevResolves) i();
}
};
}
function task(cb) {
let endTask = startTask();
let promise = cb().finally(endTask);
promise.t = true;
return promise;
}
function allTasks() {
if (tasks === 0) {
return Promise.resolve();
} else {
return new Promise((resolve) => {
resolves.push(resolve);
});
}
}
function cleanTasks() {
tasks = 0;
}
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/clean-stores/index.js
var clean = Symbol("clean");
var cleanStores = (...stores) => {
if (process.env.NODE_ENV === "production") {
throw new Error(
"cleanStores() can be used only during development or tests"
);
}
cleanTasks();
for (let $store of stores) {
if ($store) {
if ($store.mocked) delete $store.mocked;
if ($store[clean]) $store[clean]();
}
}
};
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/atom/index.js
var listenerQueue = [];
var atom = (initialValue, level) => {
let listeners = [];
let $atom = {
get() {
if (!$atom.lc) {
$atom.listen(() => {
})();
}
return $atom.value;
},
l: level || 0,
lc: 0,
listen(listener, listenerLevel) {
$atom.lc = listeners.push(listener, listenerLevel || $atom.l) / 2;
return () => {
let index = listeners.indexOf(listener);
if (~index) {
listeners.splice(index, 2);
if (!--$atom.lc) $atom.off();
}
};
},
notify(oldValue, changedKey) {
let runListenerQueue = !listenerQueue.length;
for (let i = 0; i < listeners.length; i += 2) {
listenerQueue.push(
listeners[i],
listeners[i + 1],
$atom.value,
oldValue,
changedKey
);
}
if (runListenerQueue) {
for (let i = 0; i < listenerQueue.length; i += 5) {
let skip;
for (let j = i + 1; !skip && (j += 5) < listenerQueue.length; ) {
if (listenerQueue[j] < listenerQueue[i + 1]) {
skip = listenerQueue.push(
listenerQueue[i],
listenerQueue[i + 1],
listenerQueue[i + 2],
listenerQueue[i + 3],
listenerQueue[i + 4]
);
}
}
if (!skip) {
listenerQueue[i](
listenerQueue[i + 2],
listenerQueue[i + 3],
listenerQueue[i + 4]
);
}
}
listenerQueue.length = 0;
}
},
/* It will be called on last listener unsubscribing.
We will redefine it in onMount and onStop. */
off() {
},
set(newValue) {
let oldValue = $atom.value;
if (oldValue !== newValue) {
$atom.value = newValue;
$atom.notify(oldValue);
}
},
subscribe(listener, listenerLevel) {
let unbind = $atom.listen(listener, listenerLevel);
listener($atom.value);
return unbind;
},
value: initialValue
};
if (process.env.NODE_ENV !== "production") {
$atom[clean] = () => {
listeners = [];
$atom.lc = 0;
$atom.off();
};
}
return $atom;
};
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/lifecycle/index.js
var START = 0;
var STOP = 1;
var SET = 2;
var NOTIFY = 3;
var MOUNT = 5;
var UNMOUNT = 6;
var REVERT_MUTATION = 10;
var on = (object, listener, eventKey, mutateStore) => {
object.events = object.events || {};
if (!object.events[eventKey + REVERT_MUTATION]) {
object.events[eventKey + REVERT_MUTATION] = mutateStore((eventProps) => {
object.events[eventKey].reduceRight((event, l) => (l(event), event), {
shared: {},
...eventProps
});
});
}
object.events[eventKey] = object.events[eventKey] || [];
object.events[eventKey].push(listener);
return () => {
let currentListeners = object.events[eventKey];
let index = currentListeners.indexOf(listener);
currentListeners.splice(index, 1);
if (!currentListeners.length) {
delete object.events[eventKey];
object.events[eventKey + REVERT_MUTATION]();
delete object.events[eventKey + REVERT_MUTATION];
}
};
};
var onStart = ($store, listener) => on($store, listener, START, (runListeners) => {
let originListen = $store.listen;
$store.listen = (arg) => {
if (!$store.lc && !$store.starting) {
$store.starting = true;
runListeners();
delete $store.starting;
}
return originListen(arg);
};
return () => {
$store.listen = originListen;
};
});
var onStop = ($store, listener) => on($store, listener, STOP, (runListeners) => {
let originOff = $store.off;
$store.off = () => {
runListeners();
originOff();
};
return () => {
$store.off = originOff;
};
});
var onSet = ($store, listener) => on($store, listener, SET, (runListeners) => {
let originSet = $store.set;
let originSetKey = $store.setKey;
if ($store.setKey) {
$store.setKey = (changed, changedValue) => {
let isAborted;
let abort = () => {
isAborted = true;
};
runListeners({
abort,
changed,
newValue: { ...$store.value, [changed]: changedValue }
});
if (!isAborted) return originSetKey(changed, changedValue);
};
}
$store.set = (newValue) => {
let isAborted;
let abort = () => {
isAborted = true;
};
runListeners({ abort, newValue });
if (!isAborted) return originSet(newValue);
};
return () => {
$store.set = originSet;
$store.setKey = originSetKey;
};
});
var onNotify = ($store, listener) => on($store, listener, NOTIFY, (runListeners) => {
let originNotify = $store.notify;
$store.notify = (oldValue, changed) => {
let isAborted;
let abort = () => {
isAborted = true;
};
runListeners({ abort, changed, oldValue });
if (!isAborted) return originNotify(oldValue, changed);
};
return () => {
$store.notify = originNotify;
};
});
var STORE_UNMOUNT_DELAY = 1e3;
var onMount = ($store, initialize) => {
let listener = (payload) => {
let destroy = initialize(payload);
if (destroy) $store.events[UNMOUNT].push(destroy);
};
return on($store, listener, MOUNT, (runListeners) => {
let originListen = $store.listen;
$store.listen = (...args) => {
if (!$store.lc && !$store.active) {
$store.active = true;
runListeners();
}
return originListen(...args);
};
let originOff = $store.off;
$store.events[UNMOUNT] = [];
$store.off = () => {
originOff();
setTimeout(() => {
if ($store.active && !$store.lc) {
$store.active = false;
for (let destroy of $store.events[UNMOUNT]) destroy();
$store.events[UNMOUNT] = [];
}
}, STORE_UNMOUNT_DELAY);
};
if (process.env.NODE_ENV !== "production") {
let originClean = $store[clean];
$store[clean] = () => {
for (let destroy of $store.events[UNMOUNT]) destroy();
$store.events[UNMOUNT] = [];
$store.active = false;
originClean();
};
}
return () => {
$store.listen = originListen;
$store.off = originOff;
};
});
};
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/computed/index.js
var computedStore = (stores, cb, batched2) => {
if (!Array.isArray(stores)) stores = [stores];
let previousArgs;
let currentRunId = 0;
let set = () => {
let args = stores.map(($store) => $store.get());
if (previousArgs === void 0 || args.some((arg, i) => arg !== previousArgs[i])) {
let runId = ++currentRunId;
previousArgs = args;
let value = cb(...args);
if (value && value.then && value.t) {
value.then((asyncValue) => {
if (runId === currentRunId) {
$computed.set(asyncValue);
}
});
} else {
$computed.set(value);
}
}
};
let $computed = atom(void 0, Math.max(...stores.map(($s) => $s.l)) + 1);
let timer;
let run = batched2 ? () => {
clearTimeout(timer);
timer = setTimeout(set);
} : set;
onMount($computed, () => {
let unbinds = stores.map(($store) => $store.listen(run, -1 / $computed.l));
set();
return () => {
for (let unbind of unbinds) unbind();
};
});
return $computed;
};
var computed = (stores, fn) => computedStore(stores, fn);
var batched = (stores, fn) => computedStore(stores, fn, true);
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/deep-map/path.js
function getPath(obj, path) {
let allKeys = getAllKeysFromPath(path);
let res = obj;
for (let key of allKeys) {
if (res === void 0) {
break;
}
res = res[key];
}
return res;
}
function setPath(obj, path, value) {
return setByKey(obj != null ? obj : {}, getAllKeysFromPath(path), value);
}
function setByKey(obj, splittedKeys, value) {
let key = splittedKeys[0];
ensureKey(obj, key, splittedKeys[1]);
let copy = Array.isArray(obj) ? [...obj] : { ...obj };
if (splittedKeys.length === 1) {
if (value === void 0) {
if (Array.isArray(obj)) {
copy.splice(key, 1);
} else {
delete copy[key];
}
} else {
copy[key] = value;
}
return copy;
}
let newVal = setByKey(obj[key], splittedKeys.slice(1), value);
obj[key] = newVal;
return obj;
}
var ARRAY_INDEX = /(.*)\[(\d+)\]/;
function getAllKeysFromPath(path) {
return path.split(".").flatMap((key) => getKeyAndIndicesFromKey(key));
}
function getKeyAndIndicesFromKey(key) {
if (ARRAY_INDEX.test(key)) {
let [, keyPart, index] = key.match(ARRAY_INDEX);
return [...getKeyAndIndicesFromKey(keyPart), index];
}
return [key];
}
var IS_NUMBER = /^\d+$/;
function ensureKey(obj, key, nextKey) {
if (key in obj) {
return;
}
let isNum = IS_NUMBER.test(nextKey);
if (isNum) {
obj[key] = Array(parseInt(nextKey, 10) + 1).fill(void 0);
} else {
obj[key] = {};
}
}
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/deep-map/index.js
function deepMap(initial = {}) {
let $deepMap = atom(initial);
$deepMap.setKey = (key, value) => {
let oldValue;
try {
oldValue = structuredClone($deepMap.value);
} catch {
oldValue = { ...$deepMap.value };
}
if (getPath($deepMap.value, key) !== value) {
$deepMap.value = { ...setPath($deepMap.value, key, value) };
$deepMap.notify(oldValue, key);
}
};
return $deepMap;
}
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/keep-mount/index.js
var keepMount = ($store) => {
$store.listen(() => {
});
};
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/listen-keys/index.js
function listenKeys($store, keys, listener) {
let keysSet = /* @__PURE__ */ new Set([...keys, void 0]);
return $store.listen((value, oldValue, changed) => {
if (keysSet.has(changed)) {
listener(value, oldValue, changed);
}
});
}
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/map/index.js
var map = (initial = {}) => {
let $map = atom(initial);
$map.setKey = function(key, value) {
let oldMap = $map.value;
if (typeof value === "undefined" && key in $map.value) {
$map.value = { ...$map.value };
delete $map.value[key];
$map.notify(oldMap, key);
} else if ($map.value[key] !== value) {
$map.value = {
...$map.value,
[key]: value
};
$map.notify(oldMap, key);
}
};
return $map;
};
// ../../node_modules/.pnpm/nanostores@0.10.3/node_modules/nanostores/map-creator/index.js
function mapCreator(init) {
let Creator = (id, ...args) => {
if (!Creator.cache[id]) {
Creator.cache[id] = Creator.build(id, ...args);
}
return Creator.cache[id];
};
Creator.build = (id, ...args) => {
let store = map({ id });
onMount(store, () => {
let destroy;
if (init) destroy = init(store, id, ...args);
return () => {
delete Creator.cache[id];
if (destroy) destroy();
};
});
return store;
};
Creator.cache = {};
if (process.env.NODE_ENV !== "production") {
Creator[clean] = () => {
for (let id in Creator.cache) {
Creator.cache[id][clean]();
}
Creator.cache = {};
};
}
return Creator;
}
// ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/dist/index.mjs
var has = Object.prototype.hasOwnProperty;
function find(iter, tar, key) {
for (key of iter.keys()) {
if (dequal(key, tar)) return key;
}
}
function dequal(foo, bar) {
var ctor, len, tmp;
if (foo === bar) return true;
if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
if (ctor === Date) return foo.getTime() === bar.getTime();
if (ctor === RegExp) return foo.toString() === bar.toString();
if (ctor === Array) {
if ((len = foo.length) === bar.length) {
while (len-- && dequal(foo[len], bar[len])) ;
}
return len === -1;
}
if (ctor === Set) {
if (foo.size !== bar.size) {
return false;
}
for (len of foo) {
tmp = len;
if (tmp && typeof tmp === "object") {
tmp = find(bar, tmp);
if (!tmp) return false;
}
if (!bar.has(tmp)) return false;
}
return true;
}
if (ctor === Map) {
if (foo.size !== bar.size) {
return false;
}
for (len of foo) {
tmp = len[0];
if (tmp && typeof tmp === "object") {
tmp = find(bar, tmp);
if (!tmp) return false;
}
if (!dequal(len[1], bar.get(tmp))) {
return false;
}
}
return true;
}
if (ctor === ArrayBuffer) {
foo = new Uint8Array(foo);
bar = new Uint8Array(bar);
} else if (ctor === DataView) {
if ((len = foo.byteLength) === bar.byteLength) {
while (len-- && foo.getInt8(len) === bar.getInt8(len)) ;
}
return len === -1;
}
if (ArrayBuffer.isView(foo)) {
if ((len = foo.byteLength) === bar.byteLength) {
while (len-- && foo[len] === bar[len]) ;
}
return len === -1;
}
if (!ctor || typeof foo === "object") {
len = 0;
for (ctor in foo) {
if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
}
return Object.keys(bar).length === len;
}
}
return foo !== foo && bar !== bar;
}
// src/atoms/memo.ts
var memo = (input, cb) => {
const $memoized = atom(cb(input.get()));
const unsubscribe = input.subscribe((value) => {
const comparable = cb(value);
if (!dequal($memoized.get(), comparable)) {
$memoized.set(typeof comparable === "object" ? { ...comparable } : Array.isArray(comparable) ? [...comparable] : comparable);
}
});
onMount($memoized, () => {
return unsubscribe;
});
return $memoized;
};
// src/atoms/deep-atom.ts
var usePath = (atomValue, getPath2) => {
const targets = /* @__PURE__ */ new Set();
const path = [];
let current;
const proxyHandler = {
get(target, prop, receiver) {
if (targets.has(target)) {
throw new ReferenceError(`Attempted to access property on the same target multiple times.`);
}
const value = Reflect.get(target, prop, receiver);
targets.add(target);
path.push(prop);
current = value;
if (value === void 0) {
return new Proxy({}, proxyHandler);
}
if (value && typeof value === "object") {
return new Proxy(value, proxyHandler);
}
return value;
}
};
getPath2(new Proxy(atomValue, proxyHandler));
if (path.length === 0) {
throw new Error("No valid path extracted from the provided getPath function.");
}
return {
path,
value: current
};
};
var deepAtom = (init) => {
const $atom = deepMap(init);
$atom.mutate = (getPath2, setter) => {
const { path, value } = usePath($atom.get(), getPath2);
const newValue = typeof setter === "function" ? setter(value) : setter;
if (newValue === value) {
return newValue;
}
const oldValue = $atom.value;
const fakedPath = {
split: () => {
return {
flatMap: () => {
return path;
}
};
}
};
$atom.value = setPath($atom.value, fakedPath, newValue);
$atom.notify(oldValue, path.join("."));
return newValue;
};
return $atom;
};
// src/state/context-state.ts
var getDefaultContextState = () => {
return {
background: {
background: "#000"
},
characters: {},
choice: {
label: "",
visible: false,
choices: []
},
dialog: {
content: "",
name: "",
visible: false,
miniature: {}
},
input: {
element: null,
label: "",
error: "",
visible: false
},
text: {
content: ""
},
custom: {},
meta: {
restoring: false,
goingBack: false,
preview: false
}
};
};
var createContextStateRoot = (getExtension = () => ({})) => {
const CACHE = /* @__PURE__ */ new Map();
const make = () => {
const contextState = deepAtom({
...getDefaultContextState(),
...getExtension()
});
return contextState;
};
const remove = (id) => {
const contextState = CACHE.get(id);
if (contextState) {
cleanStores(contextState);
}
CACHE.delete(id);
};
const use = (id) => {
const cached = CACHE.get(id);
if (cached) {
return cached;
}
const contextState = make();
CACHE.set(id, contextState);
onMount(contextState, () => {
return () => {
CACHE.delete(id);
};
});
return contextState;
};
return {
useContextState: use,
removeContextState: remove
};
};
// src/state/renderer-state.ts
var defaultEmpty = {};
var createRendererState = (extension = defaultEmpty) => {
const rendererState = deepAtom({
screen: "mainmenu",
loadingShown: false,
exitPromptShown: false,
...extension
});
return rendererState;
};
// src/utils/noop.ts
var noop = () => {
};
// src/utils/findLast.ts
var findLastIndex = (array, fn) => {
for (let i = array.length - 1; i >= 0; i--) {
if (fn.call(array, array[i], i, array)) {
return i;
}
}
return -1;
};
var findLast = (array, fn) => {
return array[findLastIndex(array, fn)];
};
// src/utils/escape-html.ts
var escaped = {
'"': """,
"'": "'",
"&": "&",
"<": "<",
">": ">"
};
var escapeHTML = (str) => {
return String(str).replace(/["'&<>]/g, (match) => escaped[match]);
};
// src/renderer/start.ts
var createStartFunction = (fn) => {
let unmount = noop;
return () => {
unmount();
unmount = fn();
return {
unmount: () => {
unmount();
unmount = noop;
}
};
};
};
// ../../node_modules/.pnpm/yocto-queue@1.1.1/node_modules/yocto-queue/index.js
var Node = class {
value;
next;
constructor(value) {
this.value = value;
}
};
var Queue = class {
#head;
#tail;
#size;
constructor() {
this.clear();
}
enqueue(value) {
const node = new Node(value);
if (this.#head) {
this.#tail.next = node;
this.#tail = node;
} else {
this.#head = node;
this.#tail = node;
}
this.#size++;
}
dequeue() {
const current = this.#head;
if (!current) {
return;
}
this.#head = this.#head.next;
this.#size--;
return current.value;
}
peek() {
if (!this.#head) {
return;
}
return this.#head.value;
}
clear() {
this.#head = void 0;
this.#tail = void 0;
this.#size = 0;
}
get size() {
return this.#size;
}
*[Symbol.iterator]() {
let current = this.#head;
while (current) {
yield current.value;
current = current.next;
}
}
};
// ../../node_modules/.pnpm/p-limit@6.1.0/node_modules/p-limit/index.js
function pLimit(concurrency) {
validateConcurrency(concurrency);
const queue = new Queue();
let activeCount = 0;
const resumeNext = () => {
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
activeCount++;
}
};
const next = () => {
activeCount--;
resumeNext();
};
const run = async (function_, resolve, arguments_) => {
const result = (async () => function_(...arguments_))();
resolve(result);
try {
await result;
} catch {
}
next();
};
const enqueue = (function_, resolve, arguments_) => {
new Promise((internalResolve) => {
queue.enqueue(internalResolve);
}).then(
run.bind(void 0, function_, resolve, arguments_)
);
(async () => {
await Promise.resolve();
if (activeCount < concurrency) {
resumeNext();
}
})();
};
const generator = (function_, ...arguments_) => new Promise((resolve) => {
enqueue(function_, resolve, arguments_);
});
Object.defineProperties(generator, {
activeCount: {
get: () => activeCount
},
pendingCount: {
get: () => queue.size
},
clearQueue: {
value() {
queue.clear();
}
},
concurrency: {
get: () => concurrency,
set(newConcurrency) {
validateConcurrency(newConcurrency);
concurrency = newConcurrency;
queueMicrotask(() => {
while (activeCount < concurrency && queue.size > 0) {
resumeNext();
}
});
}
}
});
return generator;
}
function validateConcurrency(concurrency) {
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
}
}
// ../../node_modules/.pnpm/simple-web-audio@0.7.0_postcss@8.4.32/node_modules/simple-web-audio/dist/index.mjs
var waitForInteraction = (() => {
const { promise, resolve } = Promise.withResolvers();
const onUserInteraction = () => {
resolve();
};
document.addEventListener("touchstart", onUserInteraction, { once: true });
document.addEventListener("touchend", onUserInteraction, { once: true });
document.addEventListener("click", onUserInteraction, { once: true });
document.addEventListener("keydown", onUserInteraction, { once: true });
return () => {
return promise;
};
})();
var BLUR_HANDLERS = /* @__PURE__ */ new Set();
var FOCUS_HANDLERS = /* @__PURE__ */ new Set();
var registerEventListeners = (listeners) => {
BLUR_HANDLERS.add(listeners.blur);
FOCUS_HANDLERS.add(listeners.focus);
return () => {
BLUR_HANDLERS.delete(listeners.blur);
FOCUS_HANDLERS.delete(listeners.focus);
};
};
addEventListener("focus", function(event) {
for (const handler of FOCUS_HANDLERS) {
try {
handler.call(this.document, event);
} catch {
}
}
});
addEventListener("blur", function(event) {
for (const handler of BLUR_HANDLERS) {
try {
handler.call(this.document, event);
} catch {
}
}
});
var createQueue = (queue, stopped = false) => {
const limit = pLimit(1);
const run = async () => {
const items = queue.slice();
for await (const item of items) {
if (stopped) break;
try {
await item();
} catch (error) {
console.error(error);
stopped = true;
}
}
queue = queue.filter((item) => !items.includes(item));
stopped = false;
};
return {
get queue() {
return queue;
},
set queue(value) {
queue = value;
},
stop() {
stopped = true;
},
execute: () => {
return limit(run);
}
};
};
var createMemo = () => {
const cache = /* @__PURE__ */ new Map();
return (key, fn) => {
return () => {
const preserved = cache.get(key);
if (preserved) {
return preserved;
}
const promise = fn();
cache.set(key, promise);
return promise;
};
};
};
var fetcherMemo = createMemo();
var decoderMemo = createMemo();
var createAudio = (options) => {
let audioContext;
let gainNode;
let bufferSource;
let arrayBuffer;
let audioBuffer;
let pendingVolume = options.volume || 1;
let pendingLoop = options.loop || false;
const createAudioContext = () => {
audioContext = new AudioContext();
};
const getGainNode = () => {
return gainNode;
};
const createGainNode = () => {
gainNode = audioContext.createGain();
const node = (options.extendAudioGraph || getGainNode)({
context: audioContext,
node: gainNode
});
node.connect(audioContext.destination);
};
const createBufferSource = () => {
bufferSource = audioContext.createBufferSource();
};
const interruptQueueThenDestroy = (cause) => {
queue.stop();
instance.destroy();
return new Error("", { cause });
};
const fetchArrayBuffer = fetcherMemo(options.src, async () => {
try {
return await fetch(options.src).then((response) => response.arrayBuffer());
} catch (error) {
throw interruptQueueThenDestroy(error);
}
});
const setArrayBuffer = async () => {
arrayBuffer = await fetchArrayBuffer();
};
const decodeAudioData = decoderMemo(options.src, async () => {
try {
return await audioContext.decodeAudioData(arrayBuffer);
} catch (error) {
throw interruptQueueThenDestroy(error);
}
});
const setAudioData = async () => {
audioBuffer = await decodeAudioData();
};
const connectSources = () => {
if (bufferSource && bufferSource.buffer === null) {
bufferSource.buffer = audioBuffer;
bufferSource.connect(gainNode);
}
};
const setVolume = () => {
gainNode.gain.value = pendingVolume;
};
const setLoop = () => {
bufferSource.loop = pendingLoop;
};
const queue = createQueue([
waitForInteraction,
createAudioContext,
createGainNode,
setVolume,
createBufferSource,
setLoop,
fetchArrayBuffer,
setArrayBuffer,
decodeAudioData,
setAudioData,
connectSources
]);
let resume = false;
const unregister = registerEventListeners({
focus: () => {
if (!options.pauseOnBlur || !resume || state.destroyed) return;
resume = false;
queue.queue.push(playAudio);
queue.execute();
},
blur: () => {
if (!options.pauseOnBlur || !state.playing || state.destroyed) return;
resume = true;
queue.queue.push(pauseAudio);
queue.execute();
}
});
const state = {
started: false,
playing: false,
destroyed: false
};
const playAudio = async () => {
if (state.destroyed) return;
if (audioContext.state === "suspended") {
await audioContext.resume();
if (state.started) {
state.playing = true;
}
}
if (!state.started) {
bufferSource.start();
state.started = true;
state.playing = true;
}
};
const pauseAudio = async () => {
if (state.destroyed) return;
if (audioContext.state === "suspended" && queue.queue.at(-1) === playAudio) {
queue.queue.pop();
}
if (audioContext.state === "running") {
await audioContext.suspend();
state.playing = false;
}
};
const disconnectAudio = async () => {
bufferSource && bufferSource.disconnect();
state.started = false;
};
const instance = {
async play() {
if (state.destroyed) return;
queue.queue.push(playAudio);
return queue.execute();
},
async pause() {
if (state.destroyed) return;
queue.queue.push(pauseAudio);
return queue.execute();
},
async reset() {
if (state.destroyed) return;
if (state.playing) {
queue.queue.push(pauseAudio);
}
queue.queue.push(
disconnectAudio,
createBufferSource,
setLoop,
connectSources
);
if (state.playing) {
queue.queue.push(playAudio);
}
return queue.execute();
},
async stop() {
if (state.destroyed) return;
queue.queue.push(
pauseAudio,
disconnectAudio,
createBufferSource,
setLoop,
connectSources
);
return queue.execute();
},
async destroy() {
if (state.destroyed) return;
unregister();
queue.queue = [
pauseAudio,
disconnectAudio
];
await queue.execute();
state.destroyed = true;
audioContext = null;
gainNode = null;
bufferSource = null;
arrayBuffer = null;
audioBuffer = null;
},
async fetch() {
if (state.destroyed) return;
await fetchArrayBuffer();
},
get playing() {
return state.playing;
},
get destroyed() {
return state.destroyed;
},
get volume() {
return pendingVolume;
},
set volume(value) {
if (state.destroyed) return;
pendingVolume = value;
queue.queue.push(setVolume);
queue.execute();
},
get loop() {
return pendingLoop;
},
set loop(value) {
if (state.destroyed) return;
pendingLoop = value;
queue.queue.push(setLoop);
queue.execute();
}
};
if (options.autoplay) {
queue.queue.push(playAudio);
queue.execute();
}
return instance;
};
var prefetchAudio = (src) => {
const fetcher = fetcherMemo(src, () => fetch(src).then((res) => res.arrayBuffer()));
return fetcher();
};
// src/audio/audio.ts
var TYPE_META_MAP = {
"music": 2,
"sound": 3,
"voice": 4
};
var createAudio2 = (storageData) => {
const store = {
music: {},
sound: {},
voices: {}
};
const getVolume = (type) => {
return storageData.get().meta[TYPE_META_MAP[type]];
};
const getAudio = (type, src) => {
const kind = type === "voice" ? "voices" : type;
const cached = store[kind][src];
if (cached) return cached;
const audio = createAudio({
src,
volume: getVolume(type),
pauseOnBlur: true
});
store[kind][src] = audio;
return audio;
};
let unsubscribe = noop;
const context = {
music(src, method) {
const resource = getAudio(method, src);
this.start();
return {
pause() {
resource.pause();
},
play(loop) {
try {
resource.loop = loop;
} catch {
}
resource.play();
},
stop() {
resource.stop();
}
};
},
voice(source) {
this.start();
this.voiceStop();
const resource = store.voice = getAudio("voice", source);
resource.play();
},
voiceStop() {
if (!store.voice) return;
store.voice.stop();
store.voice = void 0;
},
start() {
if (unsubscribe !== noop) return;
unsubscribe = storageData.subscribe(() => {
for (const type of ["music", "sound", "voice"]) {
const volume = getVolume(type);
if (type === "music" || type === "sound") {
for (const audio of Object.values(store[type])) {
if (!audio) continue;
try {
audio.volume = volume;
} catch {
}
}
}
if (type === "voice") {
const audio = store.voice;
if (audio) {
try {
audio.volume = volume;
} catch {
}
}
}
}
});
},
clear() {
const musics = Object.values(store.music);
const sounds = Object.values(store.sound);
for (const music of [...musics, ...sounds]) {
music?.stop();
}
this.voiceStop();
},
destroy() {
unsubscribe();
this.clear();
unsubscribe = noop;
}
};
const clear = (keepAudio) => {
context.voiceStop();
const musics = Object.entries(store.music).filter(([name]) => keepAudio.music && !keepAudio.music.has(name)).map(([_, h]) => h);
const sounds = Object.entries(store.sound).filter(([name]) => keepAudio.sounds && !keepAudio.sounds.has(name)).map(([_, h]) => h);
for (const music of [...musics, ...sounds]) {
if (!music) continue;
music.stop();
}
};
return {
context,
clear,
getVolume,
getAudio
};
};
var createAudioMisc = () => {
const misc = {
preloadAudioBlocking: async (src) => {
await prefetchAudio(src);
}
};
return misc;
};
// src/shared/create-shared.ts
var createShared = (get) => {
const CACHE = /* @__PURE__ */ new Map();
const use = (id) => {
const cached = CACHE.get(id);
if (cached) {
return cached;
}
const shared = get();
CACHE.set(id, shared);
return shared;
};
const remove = (id) => {
CACHE.delete(id);
};
return {
useShared: use,
removeShared: remove
};
};
// src/context/create-get-context.ts
var createGetContext = () => {
const CACHE = /* @__PURE__ */ new Map();
const getContextCached = (createContext) => {
return (key) => {
const cached = CACHE.get(key);
if (cached) {
return cached;
}
const context = createContext(key);
CACHE.set(key, context);
return context;
};
};
const removeContext = (key) => {
CACHE.delete(key);
};
return {
getContextCached,
removeContext
};
};
// src/root/root-setter.ts
var createRootSetter = (getContext) => {
let element;
return {
root() {
return element;
},
setRoot(root) {
element = root;
const context = getContext();
if (!context.root) {
context.root = root;
}
}
};
};
// src/context/vibrate.ts
var vibrationPossible = /* @__PURE__ */ (() => {
let possible = false;
const onPointerDown = () => {
possible = true;
};
const isPossible = () => {
return possible;
};
document.addEventListener("pointerdown", onPointerDown, { once: true });
return isPossible;
})();
var vibrate = (pattern) => {
if (vibrationPossible() && "vibrate" in navigator) {
try {
navigator.vibrate(pattern);
} catch {
}
}
};
// src/context/background.ts
var toMedia = (media) => {
if (media === "portrait" || media === "landscape") {
return `(orientation: ${media})`;
}
return media;
};
var useBackground = (obj, set) => {
const backgrounds = Object.fromEntries(Object.entries(obj).map(([key, value]) => [toMedia(key), value]));
const mediaQueries = Object.keys(backgrounds).map((media) => matchMedia(media));
const allMedia = mediaQueries.find(({ media }) => media === "all");
const handle = () => {
const last = findLast(mediaQueries, ({ matches, media }) => matches && media !== "all");
const bg = last ? backgrounds[last.media] : allMedia ? backgrounds["all"] : "";
set(bg);
};
for (const mq of mediaQueries) {
mq.onchange = handle;
}
let disposed = false;
Promise.resolve().then(() => {
if (disposed) return;
handle();
});
return {
/**
* Remove all listeners
*/
dispose() {
for (const mq of mediaQueries) {
mq.onchange = null;
}
disposed = true;
}
};
};
// src/context/actions.ts
var allEmpty = (target) => {
if (typeof target === "string") {
return target == "";
}
if (typeof target === "number") {
return target == 0;
}
if (!target) {
return true;
}
if (Array.isArray(target) && target.length > 0) {
for (const inner of target) {
if (!allEmpty(inner)) {
return false;
}
}
}
for (const value of Object.values(target)) {
if (!allEmpty(value)) {
return false;
}
}
return true;
};
var handleBackgroundAction = ($contextState, background, onChange) => {
const { clear } = $contextState.get().background;
clear && clear();
if (typeof background === "string") {
$contextState.mutate((s) => s.background, (prev) => {
return {
...prev,
background
};
});
onChange?.(background);
return;
}
const { dispose } = useBackground(background, (value) => {
$contextState.mutate((s) => s.background, (prev) => {
return {
...prev,
background: value
};
});
onChange?.(value);
});
$contextState.mutate((s) => s.background, (prev) => {
return {
...prev,
clear: dispose
};
});
};
var handleDialogAction = ($contextState, content, name, character, emotion, resolve) => {
$contextState.mutate(
(s) => s.dialog,
{
content,
name,
miniature: {
character,
emotion
},
visible: true,
resolve
}
);
};
var handleChoiceAction = ($contextState, label, choices, resolve) => {
$contextState.mutate(
(s) => s.choice,
{ choices, label, resolve, visible: true }
);
};
var handleClearAction = ($rendererState, $contextState, options, context, keep, keepCharacters) => {
$rendererState.mutate((s) => s.exitPromptShown, false);
if (!keep.has("showBackground")) {
$contextState.mutate((s) => s.background.background, "#000");
}
if (!keep.has("choice")) {
$contextState.mutate(
(s) => s.choice,
{
choices: [],
visible: false,
label: ""
}
);
}
const inputCleanup = $contextState.get().input.cleanup;
if (inputCleanup) {
inputCleanup();
}
if (!keep.has("input")) {
$contextState.mutate(
(s) => s.input,
{
element: null,
label: "",
visible: false,
error: ""
}
);
}
if (!keep.has("dialog")) {
$contextState.mutate(
(s) => s.dialog,
{
visible: false,
content: "",
name: "",
miniature: {}
}
);
}
if (!keep.has("text")) {
$contextState.mutate((s) => s.text, { content: "" });
}
const { characters, custom } = $contextState.get();
for (const character of Object.keys(characters)) {
if (!keepCharacters.has(character)) {
$contextState.mutate(
(s) => s.characters[character],
{
style: void 0,
visible: false
}
);
}
}
for (const [id, obj] of Object.entries(custom)) {
if (!obj) continue;
if (context.meta.goingBack && obj.fn.skipClearOnGoingBack) continue;
options.clearCustomAction(context, obj.fn);
$contextState.mutate((s) => s.custom[id], void 0);
}
};
var handleCustomAction = ($contextState, fn) => {
if (!$contextState.get().custom[fn.key]) {
$contextState.mutate(
(s) => s.custom[fn.key],
{
fn,
node: null,
clear: noop
}
);
}
return {
setMountElement(node) {
$contextState.mutate(
(s) => s.custom[fn.key],
(state) => {
return {
...state,
node
};
}
);
},
setClear(clear) {
$contextState.mutate(
(s) => s.custom[fn.key],
(state) => {
return {
...state,
clear
};
}
);
},
remove() {
$contextState.mutate((s) => s.custom[fn.key], void 0);
}
};
};
var handleClearBlockingActions = ($contextState, preserve) => {
const current = $contextState.get();
if (preserve !== "choice" && !allEmpty(current.choice)) {
$contextState.mutate(
(s) => s.choice,
{
choices: [],
visible: false,
label: ""
}
);
}
if (preserve !== "input" && !allEmpty(current.input)) {
$contextState.mutate(
(s) => s.input,
{
element: null,
label: "",
visible: false,
error: ""
}
);
}
if (preserve !== "text" && !allEmpty(current.text)) {
$contextState.mutate((s) => s.text, { content: "" });
}
if (preserve !== "dialog" && !allEmpty(current.dialog)) {
$contextState.mutate(
(s) => s.dialog,
{
visible: false,
content: "",
name: "",
miniature: {}
}
);
}
};
var handleTextAction = ($contextState, content, resolve) => {
$contextState.mutate((s) => s.text, { content, resolve });
};
var handleInputAction = ($contextState, options, context, label, onInput, setup, resolve) => {
const error = (value) => {
$contextState.mutate((s) => s.input.error, value);
};
const onInputHandler = (event) => {
let value;
onInput({
lang: options.storageData.get().meta[0],
input,
event,
error,
state: options.getStateFunction(context.id),
get value() {
if (value) return value;
return value = escapeHTML(input.value);
}
});
};
const input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("name", "novely-input");
input.setAttribute("required", "true");
input.setAttribute("autocomplete", "off");
!context.meta.preview && input.addEventListener("input", onInputHandler);
$contextState.mutate(
(s) => s.input,
{
element: input,
label,
error: "",
visible: true,
cleanup: setup(input) || noop,
resolve
}
);
!context.meta.preview && input.dispatchEvent(new InputEvent("input", { bubbles: true }));
};
var handleVibrateAction = vibrate;
export {
STORE_UNMOUNT_DELAY,
allTasks,
atom,
batched,
clean,
cleanStores,
cleanTasks,
computed,
createAudio2 as createAudio,
createAudioMisc,
createContextStateRoot,
createGetContext,
createRendererState,
createRootSetter,
createShared,
createStartFunction,
deepAtom,
deepMap,
getPath,
handleBackgroundAction,
handleChoiceAction,
handleClearAction,
handleClearBlockingActions,
handleCustomAction,
handleDialogAction,
handleInputAction,
handleTextAction,
handleVibrateAction,
keepMount,
listenKeys,
map,
mapCreator,
memo,
noop,
onMount,
onNotify,
onSet,
onStart,
onStop,
setPath,
startTask,
task
};
//# sourceMappingURL=index.mjs.map