UNPKG

@novely/renderer-toolkit

Version:
1,563 lines (1,535 loc) 42.8 kB
"use strict"; var Novely = (() => { var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { STORE_UNMOUNT_DELAY: () => STORE_UNMOUNT_DELAY, allTasks: () => allTasks, atom: () => atom, batched: () => batched, clean: () => clean, cleanStores: () => cleanStores, cleanTasks: () => cleanTasks, computed: () => computed, createAudio: () => createAudio2, createAudioMisc: () => createAudioMisc, createContextStateRoot: () => createContextStateRoot, createGetContext: () => createGetContext, createRendererState: () => createRendererState, createRootSetter: () => createRootSetter, createShared: () => createShared, createStartFunction: () => createStartFunction, deepAtom: () => deepAtom, deepMap: () => deepMap, getPath: () => getPath, handleBackgroundAction: () => handleBackgroundAction, handleChoiceAction: () => handleChoiceAction, handleClearAction: () => handleClearAction, handleClearBlockingActions: () => handleClearBlockingActions, handleCustomAction: () => handleCustomAction, handleDialogAction: () => handleDialogAction, handleInputAction: () => handleInputAction, handleTextAction: () => handleTextAction, handleVibrateAction: () => handleVibrateAction, keepMount: () => keepMount, listenKeys: () => listenKeys, map: () => map, mapCreator: () => mapCreator, noop: () => noop, onMount: () => onMount, onNotify: () => onNotify, onSet: () => onSet, onStart: () => onStart, onStop: () => onStop, setByKey: () => setByKey, setPath: () => setPath, startTask: () => startTask, subscribeKeys: () => subscribeKeys, task: () => task }); // ../../node_modules/.pnpm/nanostores@0.11.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.11.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.11.3/node_modules/nanostores/atom/index.js var listenerQueue = []; var lqIndex = 0; var QUEUE_ITEMS_PER_LISTENER = 4; var epoch = 0; var atom = (initialValue) => { let listeners = []; let $atom = { get() { if (!$atom.lc) { $atom.listen(() => { })(); } return $atom.value; }, lc: 0, listen(listener) { $atom.lc = listeners.push(listener); return () => { for (let i = lqIndex + QUEUE_ITEMS_PER_LISTENER; i < listenerQueue.length; ) { if (listenerQueue[i] === listener) { listenerQueue.splice(i, QUEUE_ITEMS_PER_LISTENER); } else { i += QUEUE_ITEMS_PER_LISTENER; } } let index = listeners.indexOf(listener); if (~index) { listeners.splice(index, 1); if (!--$atom.lc) $atom.off(); } }; }, notify(oldValue, changedKey) { epoch++; let runListenerQueue = !listenerQueue.length; for (let listener of listeners) { listenerQueue.push( listener, $atom.value, oldValue, changedKey ); } if (runListenerQueue) { for (lqIndex = 0; lqIndex < listenerQueue.length; lqIndex += QUEUE_ITEMS_PER_LISTENER) { listenerQueue[lqIndex]( listenerQueue[lqIndex + 1], listenerQueue[lqIndex + 2], listenerQueue[lqIndex + 3] ); } 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) { let unbind = $atom.listen(listener); 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.11.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.11.3/node_modules/nanostores/computed/index.js var computedStore = (stores, cb, batched2) => { if (!Array.isArray(stores)) stores = [stores]; let previousArgs; let currentEpoch; let set = () => { if (currentEpoch === epoch) return; currentEpoch = epoch; let args = stores.map(($store) => $store.get()); if (!previousArgs || args.some((arg, i) => arg !== previousArgs[i])) { previousArgs = args; let value = cb(...args); if (value && value.then && value.t) { value.then((asyncValue) => { if (previousArgs === args) { $computed.set(asyncValue); } }); } else { $computed.set(value); currentEpoch = epoch; } } }; let $computed = atom(void 0); let get = $computed.get; $computed.get = () => { set(); return get(); }; let timer; let run = batched2 ? () => { clearTimeout(timer); timer = setTimeout(set); } : set; onMount($computed, () => { let unbinds = stores.map(($store) => $store.listen(run)); 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.11.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]; let copy = Array.isArray(obj) ? [...obj] : { ...obj }; if (splittedKeys.length === 1) { if (value === void 0) { if (Array.isArray(copy)) { copy.splice(key, 1); } else { delete copy[key]; } } else { copy[key] = value; } return copy; } ensureKey(copy, key, splittedKeys[1]); copy[key] = setByKey(copy[key], splittedKeys.slice(1), value); return copy; } 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); } else { obj[key] = {}; } } // ../../node_modules/.pnpm/nanostores@0.11.3/node_modules/nanostores/deep-map/index.js function deepMap(initial = {}) { let $deepMap = atom(initial); $deepMap.setKey = (key, value) => { if (getPath($deepMap.value, key) !== value) { let oldValue = $deepMap.value; $deepMap.value = setPath($deepMap.value, key, value); $deepMap.notify(oldValue, key); } }; return $deepMap; } // ../../node_modules/.pnpm/nanostores@0.11.3/node_modules/nanostores/keep-mount/index.js var keepMount = ($store) => { $store.listen(() => { }); }; // ../../node_modules/.pnpm/nanostores@0.11.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); } }); } function subscribeKeys($store, keys, listener) { let unbind = listenKeys($store, keys, listener); listener($store.value); return unbind; } // ../../node_modules/.pnpm/nanostores@0.11.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.11.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; } // 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; $atom.value = setByKey($atom.value, path, newValue); $atom.notify(oldValue, path.join(".")); return newValue; }; return $atom; }; // src/state/context-state.ts var getDefaultContextState = () => { return { background: { background: "#0000" }, 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 }, loading: 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/escape-html.ts var escaped = { '"': "&quot;", "'": "&#39;", "&": "&amp;", "<": "&lt;", ">": "&gt;" }; 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.2.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.9.0_postcss@8.5.1/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 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 onEndedOptions = { once: true }; 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 ]); 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; 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(); }, onEnded: (callback) => { if (state.destroyed || !bufferSource) return; bufferSource.addEventListener("ended", callback, onEndedOptions); }, 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) => { let started = false; 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) }); store[kind][src] = audio; return audio; }; const cleanup = /* @__PURE__ */ new Set(); let voiceCleanup = noop; const context = { music(src, paused, method) { const resource = getAudio(method, src); this.start(); const unsubscribe = paused.subscribe((paused2) => { if (paused2) { resource.pause(); } else { resource.play(); } }); cleanup.add(unsubscribe); return { pause() { resource.pause(); }, play(loop) { if (resource.playing) { resource.volume = getVolume(method); resource.loop = loop; return; } resource.reset().then(() => { resource.volume = getVolume(method); resource.loop = loop; resource.play(); }); }, stop() { resource.stop(); } }; }, voice(source, paused) { this.start(); this.voiceStop(); const resource = store.voice = getAudio("voice", source); resource.volume = getVolume("voice"); resource.play(); voiceCleanup = paused.subscribe((paused2) => { if (paused2) { resource.pause(); } else { resource.play(); } }); }, voiceStop() { if (!store.voice) return; store.voice.stop(); voiceCleanup(); store.voice = void 0; voiceCleanup = noop; }, start() { if (started) return; started = true; const 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; audio.volume = volume; } } if (type === "voice" && store.voice) { store.voice.volume = volume; } } }); cleanup.add(unsubscribe); }, clear() { const musics = Object.values(store.music); const sounds = Object.values(store.sound); for (const music of [...musics, ...sounds]) { if (!music) continue; music.stop(); } this.voiceStop(); }, destroy() { cleanup.forEach((fn) => fn()); this.clear(); started = false; } }; const clear = (keepAudio) => { context.voiceStop(); const entries = [ [store.music, keepAudio.music], [store.sound, keepAudio.sounds] ]; const clearEntries = entries.flatMap(([incoming, keep]) => { return Object.entries(incoming).filter(([name]) => !keep.has(name)).map(([_, a]) => a); }); for (const music of clearEntries) { 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/background.ts var useBackground = (backgrounds, set) => { const mediaQueries = Object.keys(backgrounds).map((media) => matchMedia(media)); const allMedia = mediaQueries.find(({ media }) => media === "all"); const handle = () => { const last = mediaQueries.findLast(({ 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/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/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) => { $contextState.get().background.clear?.(); const { dispose } = useBackground(background, (value) => { $contextState.mutate((s) => s.background.background, value); }); $contextState.mutate( (s) => s.background.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("id", "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; return __toCommonJS(index_exports); })(); //# sourceMappingURL=index.global.js.map