@tomorrowos/sdk 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/playlist-catalog.d.ts +35 -0
- package/dist/playlist-catalog.d.ts.map +1 -0
- package/dist/playlist-catalog.js +133 -0
- package/dist/store/memory-store.d.ts +9 -1
- package/dist/store/memory-store.d.ts.map +1 -1
- package/dist/store/memory-store.js +35 -0
- package/dist/store/types.d.ts +41 -0
- package/dist/store/types.d.ts.map +1 -1
- package/dist/tomorrowos.d.ts +14 -0
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +137 -1
- package/package.json +1 -1
- package/templates/cms-starter/node_modules/.bin/esbuild +16 -0
- package/templates/cms-starter/node_modules/.bin/esbuild.cmd +17 -0
- package/templates/cms-starter/node_modules/.bin/esbuild.ps1 +28 -0
- package/templates/cms-starter/node_modules/.bin/tomorrowos +16 -0
- package/templates/cms-starter/node_modules/.bin/tomorrowos.cmd +17 -0
- package/templates/cms-starter/node_modules/.bin/tomorrowos.ps1 +28 -0
- package/templates/cms-starter/node_modules/.bin/tsc +16 -0
- package/templates/cms-starter/node_modules/.bin/tsc.cmd +17 -0
- package/templates/cms-starter/node_modules/.bin/tsc.ps1 +28 -0
- package/templates/cms-starter/node_modules/.bin/tsserver +16 -0
- package/templates/cms-starter/node_modules/.bin/tsserver.cmd +17 -0
- package/templates/cms-starter/node_modules/.bin/tsserver.ps1 +28 -0
- package/templates/cms-starter/node_modules/.bin/tsx +16 -0
- package/templates/cms-starter/node_modules/.bin/tsx.cmd +17 -0
- package/templates/cms-starter/node_modules/.bin/tsx.ps1 +28 -0
- package/templates/cms-starter/node_modules/.package-lock.json +140 -0
- package/templates/cms-starter/node_modules/@esbuild/win32-x64/README.md +3 -0
- package/templates/cms-starter/node_modules/@esbuild/win32-x64/esbuild.exe +0 -0
- package/templates/cms-starter/node_modules/@esbuild/win32-x64/package.json +20 -0
- package/templates/cms-starter/node_modules/@types/node/LICENSE +21 -0
- package/templates/cms-starter/node_modules/@types/node/README.md +15 -0
- package/templates/cms-starter/node_modules/@types/node/assert/strict.d.ts +8 -0
- package/templates/cms-starter/node_modules/@types/node/assert.d.ts +1062 -0
- package/templates/cms-starter/node_modules/@types/node/async_hooks.d.ts +605 -0
- package/templates/cms-starter/node_modules/@types/node/buffer.buffer.d.ts +471 -0
- package/templates/cms-starter/node_modules/@types/node/buffer.d.ts +1936 -0
- package/templates/cms-starter/node_modules/@types/node/child_process.d.ts +1475 -0
- package/templates/cms-starter/node_modules/@types/node/cluster.d.ts +577 -0
- package/templates/cms-starter/node_modules/@types/node/compatibility/disposable.d.ts +16 -0
- package/templates/cms-starter/node_modules/@types/node/compatibility/index.d.ts +9 -0
- package/templates/cms-starter/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
- package/templates/cms-starter/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
- package/templates/cms-starter/node_modules/@types/node/console.d.ts +452 -0
- package/templates/cms-starter/node_modules/@types/node/constants.d.ts +21 -0
- package/templates/cms-starter/node_modules/@types/node/crypto.d.ts +4590 -0
- package/templates/cms-starter/node_modules/@types/node/dgram.d.ts +597 -0
- package/templates/cms-starter/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
- package/templates/cms-starter/node_modules/@types/node/dns/promises.d.ts +479 -0
- package/templates/cms-starter/node_modules/@types/node/dns.d.ts +871 -0
- package/templates/cms-starter/node_modules/@types/node/domain.d.ts +170 -0
- package/templates/cms-starter/node_modules/@types/node/events.d.ts +977 -0
- package/templates/cms-starter/node_modules/@types/node/fs/promises.d.ts +1270 -0
- package/templates/cms-starter/node_modules/@types/node/fs.d.ts +4375 -0
- package/templates/cms-starter/node_modules/@types/node/globals.d.ts +172 -0
- package/templates/cms-starter/node_modules/@types/node/globals.typedarray.d.ts +38 -0
- package/templates/cms-starter/node_modules/@types/node/http.d.ts +2049 -0
- package/templates/cms-starter/node_modules/@types/node/http2.d.ts +2708 -0
- package/templates/cms-starter/node_modules/@types/node/https.d.ts +578 -0
- package/templates/cms-starter/node_modules/@types/node/index.d.ts +93 -0
- package/templates/cms-starter/node_modules/@types/node/inspector.generated.d.ts +3966 -0
- package/templates/cms-starter/node_modules/@types/node/module.d.ts +539 -0
- package/templates/cms-starter/node_modules/@types/node/net.d.ts +1031 -0
- package/templates/cms-starter/node_modules/@types/node/os.d.ts +506 -0
- package/templates/cms-starter/node_modules/@types/node/package.json +140 -0
- package/templates/cms-starter/node_modules/@types/node/path.d.ts +200 -0
- package/templates/cms-starter/node_modules/@types/node/perf_hooks.d.ts +961 -0
- package/templates/cms-starter/node_modules/@types/node/process.d.ts +1961 -0
- package/templates/cms-starter/node_modules/@types/node/punycode.d.ts +117 -0
- package/templates/cms-starter/node_modules/@types/node/querystring.d.ts +152 -0
- package/templates/cms-starter/node_modules/@types/node/readline/promises.d.ts +162 -0
- package/templates/cms-starter/node_modules/@types/node/readline.d.ts +589 -0
- package/templates/cms-starter/node_modules/@types/node/repl.d.ts +430 -0
- package/templates/cms-starter/node_modules/@types/node/sea.d.ts +153 -0
- package/templates/cms-starter/node_modules/@types/node/stream/consumers.d.ts +38 -0
- package/templates/cms-starter/node_modules/@types/node/stream/promises.d.ts +90 -0
- package/templates/cms-starter/node_modules/@types/node/stream/web.d.ts +533 -0
- package/templates/cms-starter/node_modules/@types/node/stream.d.ts +1698 -0
- package/templates/cms-starter/node_modules/@types/node/string_decoder.d.ts +67 -0
- package/templates/cms-starter/node_modules/@types/node/test.d.ts +1787 -0
- package/templates/cms-starter/node_modules/@types/node/timers/promises.d.ts +108 -0
- package/templates/cms-starter/node_modules/@types/node/timers.d.ts +286 -0
- package/templates/cms-starter/node_modules/@types/node/tls.d.ts +1259 -0
- package/templates/cms-starter/node_modules/@types/node/trace_events.d.ts +197 -0
- package/templates/cms-starter/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
- package/templates/cms-starter/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
- package/templates/cms-starter/node_modules/@types/node/ts5.6/index.d.ts +93 -0
- package/templates/cms-starter/node_modules/@types/node/tty.d.ts +208 -0
- package/templates/cms-starter/node_modules/@types/node/url.d.ts +964 -0
- package/templates/cms-starter/node_modules/@types/node/util.d.ts +2331 -0
- package/templates/cms-starter/node_modules/@types/node/v8.d.ts +809 -0
- package/templates/cms-starter/node_modules/@types/node/vm.d.ts +1001 -0
- package/templates/cms-starter/node_modules/@types/node/wasi.d.ts +181 -0
- package/templates/cms-starter/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
- package/templates/cms-starter/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
- package/templates/cms-starter/node_modules/@types/node/web-globals/events.d.ts +97 -0
- package/templates/cms-starter/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
- package/templates/cms-starter/node_modules/@types/node/worker_threads.d.ts +715 -0
- package/templates/cms-starter/node_modules/@types/node/zlib.d.ts +598 -0
- package/templates/cms-starter/node_modules/esbuild/LICENSE.md +21 -0
- package/templates/cms-starter/node_modules/esbuild/README.md +3 -0
- package/templates/cms-starter/node_modules/esbuild/bin/esbuild +223 -0
- package/templates/cms-starter/node_modules/esbuild/install.js +300 -0
- package/templates/cms-starter/node_modules/esbuild/lib/main.d.ts +716 -0
- package/templates/cms-starter/node_modules/esbuild/lib/main.js +2532 -0
- package/templates/cms-starter/node_modules/esbuild/package.json +74 -0
- package/templates/cms-starter/node_modules/tsx/LICENSE +21 -0
- package/templates/cms-starter/node_modules/tsx/README.md +32 -0
- package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
- package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
- package/templates/cms-starter/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/cjs/index.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/cjs/index.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/cli.cjs +54 -0
- package/templates/cms-starter/node_modules/tsx/dist/cli.mjs +55 -0
- package/templates/cms-starter/node_modules/tsx/dist/client-D3mGB526.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/client-D_mPDF5S.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
- package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
- package/templates/cms-starter/node_modules/tsx/dist/esm/api/index.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/esm/index.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/esm/index.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/get-pipe-path-D4YM6rQt.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/get-pipe-path-_tAJyU_v.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/index-D9F1FXzN.cjs +14 -0
- package/templates/cms-starter/node_modules/tsx/dist/index-XurvG3JN.mjs +14 -0
- package/templates/cms-starter/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
- package/templates/cms-starter/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
- package/templates/cms-starter/node_modules/tsx/dist/loader.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/loader.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/node-features-B9BBLzwu.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/node-features-CQLdkVE6.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/package-CGdS2_oX.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/package-DyJMwVU5.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/patch-repl.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/patch-repl.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/preflight.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/preflight.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/register-BOkp8V6j.cjs +10 -0
- package/templates/cms-starter/node_modules/tsx/dist/register-BnTWPeIB.mjs +10 -0
- package/templates/cms-starter/node_modules/tsx/dist/register-CHVGxKtC.cjs +2 -0
- package/templates/cms-starter/node_modules/tsx/dist/register-D_B8UL5H.mjs +2 -0
- package/templates/cms-starter/node_modules/tsx/dist/repl.cjs +3 -0
- package/templates/cms-starter/node_modules/tsx/dist/repl.mjs +3 -0
- package/templates/cms-starter/node_modules/tsx/dist/require-CjvaJWEr.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/require-DzmC1hVr.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/temporary-directory-BDDVQOvU.mjs +1 -0
- package/templates/cms-starter/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
- package/templates/cms-starter/node_modules/tsx/package.json +67 -0
- package/templates/cms-starter/node_modules/typescript/LICENSE.txt +55 -0
- package/templates/cms-starter/node_modules/typescript/README.md +50 -0
- package/templates/cms-starter/node_modules/typescript/SECURITY.md +41 -0
- package/templates/cms-starter/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
- package/templates/cms-starter/node_modules/typescript/bin/tsc +2 -0
- package/templates/cms-starter/node_modules/typescript/bin/tsserver +2 -0
- package/templates/cms-starter/node_modules/typescript/lib/_tsc.js +133818 -0
- package/templates/cms-starter/node_modules/typescript/lib/_tsserver.js +659 -0
- package/templates/cms-starter/node_modules/typescript/lib/_typingsInstaller.js +222 -0
- package/templates/cms-starter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.d.ts +22 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.es6.d.ts +23 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
- package/templates/cms-starter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
- package/templates/cms-starter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/tsc.js +8 -0
- package/templates/cms-starter/node_modules/typescript/lib/tsserver.js +8 -0
- package/templates/cms-starter/node_modules/typescript/lib/tsserverlibrary.d.ts +17 -0
- package/templates/cms-starter/node_modules/typescript/lib/tsserverlibrary.js +21 -0
- package/templates/cms-starter/node_modules/typescript/lib/typesMap.json +497 -0
- package/templates/cms-starter/node_modules/typescript/lib/typescript.d.ts +11437 -0
- package/templates/cms-starter/node_modules/typescript/lib/typescript.js +200276 -0
- package/templates/cms-starter/node_modules/typescript/lib/typingsInstaller.js +8 -0
- package/templates/cms-starter/node_modules/typescript/lib/watchGuard.js +53 -0
- package/templates/cms-starter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +2122 -0
- package/templates/cms-starter/node_modules/typescript/package.json +120 -0
- package/templates/cms-starter/node_modules/undici-types/LICENSE +21 -0
- package/templates/cms-starter/node_modules/undici-types/README.md +6 -0
- package/templates/cms-starter/node_modules/undici-types/agent.d.ts +31 -0
- package/templates/cms-starter/node_modules/undici-types/api.d.ts +43 -0
- package/templates/cms-starter/node_modules/undici-types/balanced-pool.d.ts +29 -0
- package/templates/cms-starter/node_modules/undici-types/cache.d.ts +36 -0
- package/templates/cms-starter/node_modules/undici-types/client.d.ts +108 -0
- package/templates/cms-starter/node_modules/undici-types/connector.d.ts +34 -0
- package/templates/cms-starter/node_modules/undici-types/content-type.d.ts +21 -0
- package/templates/cms-starter/node_modules/undici-types/cookies.d.ts +28 -0
- package/templates/cms-starter/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
- package/templates/cms-starter/node_modules/undici-types/dispatcher.d.ts +256 -0
- package/templates/cms-starter/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
- package/templates/cms-starter/node_modules/undici-types/errors.d.ts +149 -0
- package/templates/cms-starter/node_modules/undici-types/eventsource.d.ts +61 -0
- package/templates/cms-starter/node_modules/undici-types/fetch.d.ts +209 -0
- package/templates/cms-starter/node_modules/undici-types/file.d.ts +39 -0
- package/templates/cms-starter/node_modules/undici-types/filereader.d.ts +54 -0
- package/templates/cms-starter/node_modules/undici-types/formdata.d.ts +108 -0
- package/templates/cms-starter/node_modules/undici-types/global-dispatcher.d.ts +9 -0
- package/templates/cms-starter/node_modules/undici-types/global-origin.d.ts +7 -0
- package/templates/cms-starter/node_modules/undici-types/handlers.d.ts +15 -0
- package/templates/cms-starter/node_modules/undici-types/header.d.ts +4 -0
- package/templates/cms-starter/node_modules/undici-types/index.d.ts +71 -0
- package/templates/cms-starter/node_modules/undici-types/interceptors.d.ts +17 -0
- package/templates/cms-starter/node_modules/undici-types/mock-agent.d.ts +50 -0
- package/templates/cms-starter/node_modules/undici-types/mock-client.d.ts +25 -0
- package/templates/cms-starter/node_modules/undici-types/mock-errors.d.ts +12 -0
- package/templates/cms-starter/node_modules/undici-types/mock-interceptor.d.ts +93 -0
- package/templates/cms-starter/node_modules/undici-types/mock-pool.d.ts +25 -0
- package/templates/cms-starter/node_modules/undici-types/package.json +55 -0
- package/templates/cms-starter/node_modules/undici-types/patch.d.ts +33 -0
- package/templates/cms-starter/node_modules/undici-types/pool-stats.d.ts +19 -0
- package/templates/cms-starter/node_modules/undici-types/pool.d.ts +39 -0
- package/templates/cms-starter/node_modules/undici-types/proxy-agent.d.ts +28 -0
- package/templates/cms-starter/node_modules/undici-types/readable.d.ts +65 -0
- package/templates/cms-starter/node_modules/undici-types/retry-agent.d.ts +8 -0
- package/templates/cms-starter/node_modules/undici-types/retry-handler.d.ts +116 -0
- package/templates/cms-starter/node_modules/undici-types/util.d.ts +18 -0
- package/templates/cms-starter/node_modules/undici-types/webidl.d.ts +228 -0
- package/templates/cms-starter/node_modules/undici-types/websocket.d.ts +150 -0
- package/templates/cms-starter/package-lock.json +592 -0
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/index.html +42 -14
- package/templates/cms-starter/public/methods.js +505 -371
- package/templates/cms-starter/public/panel.css +130 -1
- package/templates/cms-starter/server.ts +7 -0
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
const PANEL_PLAYLIST_KEY = "tomorrowos.panel.playlistDraft";
|
|
2
|
-
const PANEL_SCHEDULE_KEY = "tomorrowos.panel.scheduleDraft";
|
|
3
1
|
const PANEL_MEDIA_BASE_KEY = "tomorrowos.panel.mediaBaseUrl";
|
|
4
2
|
|
|
5
|
-
/** @type {
|
|
6
|
-
let
|
|
3
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
4
|
+
let playlistsCatalog = [];
|
|
7
5
|
|
|
8
6
|
/** @type {Array<Record<string, unknown>>} */
|
|
9
7
|
let devicesCache = [];
|
|
10
8
|
|
|
9
|
+
/** @type {string|null} */
|
|
10
|
+
let selectedPlaylistId = null;
|
|
11
|
+
|
|
12
|
+
/** True while creating a new playlist locally (assets allowed before Save). */
|
|
13
|
+
let playlistDraftActive = false;
|
|
14
|
+
|
|
15
|
+
/** @type {{ id: string, url: string, name: string, type: string, durationMs: number }[]} */
|
|
16
|
+
let editorItems = [];
|
|
17
|
+
|
|
18
|
+
/** @type {string|null} */
|
|
19
|
+
let publishModalDeviceId = null;
|
|
20
|
+
|
|
11
21
|
let devicePollTimer = null;
|
|
12
22
|
|
|
13
23
|
function escapeHtml(value) {
|
|
@@ -37,17 +47,9 @@ function formatDateTimeSeconds(iso) {
|
|
|
37
47
|
if (!iso) return "—";
|
|
38
48
|
const d = new Date(iso);
|
|
39
49
|
if (Number.isNaN(d.getTime())) return "—";
|
|
40
|
-
return d.toLocaleString(
|
|
41
|
-
year: "numeric",
|
|
42
|
-
month: "short",
|
|
43
|
-
day: "numeric",
|
|
44
|
-
hour: "2-digit",
|
|
45
|
-
minute: "2-digit",
|
|
46
|
-
second: "2-digit"
|
|
47
|
-
});
|
|
50
|
+
return d.toLocaleString();
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
/** TV uptime: now minus last boot time (device.lastBootAt). Not active when app is offline. */
|
|
51
53
|
function formatDeviceOnlineLabel(device) {
|
|
52
54
|
if (!device.connected) return "Not active";
|
|
53
55
|
const bootIso = device.lastBootAt;
|
|
@@ -57,142 +59,6 @@ function formatDeviceOnlineLabel(device) {
|
|
|
57
59
|
return formatDurationMs(Date.now() - bootMs);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
async function fetchDevices() {
|
|
61
|
-
try {
|
|
62
|
-
const res = await fetch("/devices");
|
|
63
|
-
const data = await res.json();
|
|
64
|
-
if (Array.isArray(data.devices)) {
|
|
65
|
-
devicesCache = data.devices;
|
|
66
|
-
renderDeviceCards();
|
|
67
|
-
}
|
|
68
|
-
} catch (err) {
|
|
69
|
-
showResult({ status: "failed", error: err.message });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function renderDeviceCards() {
|
|
74
|
-
const grid = document.getElementById("devicesGrid");
|
|
75
|
-
if (!grid) return;
|
|
76
|
-
|
|
77
|
-
grid.innerHTML = "";
|
|
78
|
-
|
|
79
|
-
if (devicesCache.length === 0) {
|
|
80
|
-
const empty = document.createElement("p");
|
|
81
|
-
empty.className = "devices-empty";
|
|
82
|
-
empty.textContent = "No paired devices yet. Enter a code above.";
|
|
83
|
-
grid.appendChild(empty);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const device of devicesCache) {
|
|
88
|
-
const card = document.createElement("article");
|
|
89
|
-
card.className = "device-card";
|
|
90
|
-
card.dataset.deviceId = device.deviceId;
|
|
91
|
-
|
|
92
|
-
const header = document.createElement("div");
|
|
93
|
-
header.className = "device-card-header";
|
|
94
|
-
|
|
95
|
-
const led = document.createElement("span");
|
|
96
|
-
led.className = `status-led ${device.connected ? "status-led--online" : "status-led--offline"}`;
|
|
97
|
-
led.title = device.connected ? "Connected" : "Disconnected";
|
|
98
|
-
|
|
99
|
-
const title = document.createElement("h3");
|
|
100
|
-
title.className = "device-card-title";
|
|
101
|
-
title.textContent = device.deviceName || "Screen";
|
|
102
|
-
|
|
103
|
-
header.appendChild(led);
|
|
104
|
-
header.appendChild(title);
|
|
105
|
-
|
|
106
|
-
const meta = document.createElement("dl");
|
|
107
|
-
meta.className = "device-meta";
|
|
108
|
-
|
|
109
|
-
const rows = [
|
|
110
|
-
["Device ID", device.deviceId],
|
|
111
|
-
["System", device.system || device.platform || "—"],
|
|
112
|
-
[
|
|
113
|
-
"Device online",
|
|
114
|
-
formatDeviceOnlineLabel(device)
|
|
115
|
-
],
|
|
116
|
-
["Last boot", formatDateTimeSeconds(device.lastBootAt)],
|
|
117
|
-
["Latest content push", formatDateTimeSeconds(device.lastPolicyPushAt)]
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
for (const [label, value] of rows) {
|
|
121
|
-
const row = document.createElement("div");
|
|
122
|
-
row.className = "device-meta-row";
|
|
123
|
-
const dt = document.createElement("dt");
|
|
124
|
-
dt.textContent = label;
|
|
125
|
-
const dd = document.createElement("dd");
|
|
126
|
-
if (label === "Device online") {
|
|
127
|
-
dd.className = "device-online-time";
|
|
128
|
-
dd.title = "Current time minus this TV last boot time";
|
|
129
|
-
}
|
|
130
|
-
dd.textContent = value;
|
|
131
|
-
row.appendChild(dt);
|
|
132
|
-
row.appendChild(dd);
|
|
133
|
-
meta.appendChild(row);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const actions = document.createElement("div");
|
|
137
|
-
actions.className = "device-card-actions";
|
|
138
|
-
|
|
139
|
-
const publishBtn = document.createElement("button");
|
|
140
|
-
publishBtn.type = "button";
|
|
141
|
-
publishBtn.className = "primary";
|
|
142
|
-
publishBtn.textContent = "Publish";
|
|
143
|
-
publishBtn.addEventListener("click", () => publishToDevice(device.deviceId));
|
|
144
|
-
|
|
145
|
-
const infoBtn = document.createElement("button");
|
|
146
|
-
infoBtn.type = "button";
|
|
147
|
-
infoBtn.textContent = "Info";
|
|
148
|
-
infoBtn.addEventListener("click", () => deviceAction(device.deviceId, "get-info"));
|
|
149
|
-
|
|
150
|
-
const capsBtn = document.createElement("button");
|
|
151
|
-
capsBtn.type = "button";
|
|
152
|
-
capsBtn.textContent = "Capabilities";
|
|
153
|
-
capsBtn.addEventListener("click", () =>
|
|
154
|
-
deviceAction(device.deviceId, "get-capabilities")
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const rebootBtn = document.createElement("button");
|
|
158
|
-
rebootBtn.type = "button";
|
|
159
|
-
rebootBtn.textContent = "Reboot";
|
|
160
|
-
rebootBtn.addEventListener("click", () => deviceAction(device.deviceId, "reboot"));
|
|
161
|
-
|
|
162
|
-
const clearBtn = document.createElement("button");
|
|
163
|
-
clearBtn.type = "button";
|
|
164
|
-
clearBtn.textContent = "Clear";
|
|
165
|
-
clearBtn.addEventListener("click", () => deviceAction(device.deviceId, "content/clear"));
|
|
166
|
-
|
|
167
|
-
const unpairBtn = document.createElement("button");
|
|
168
|
-
unpairBtn.type = "button";
|
|
169
|
-
unpairBtn.className = "danger";
|
|
170
|
-
unpairBtn.textContent = "Unpair";
|
|
171
|
-
unpairBtn.addEventListener("click", () => unpairDevice(device.deviceId));
|
|
172
|
-
|
|
173
|
-
actions.appendChild(publishBtn);
|
|
174
|
-
actions.appendChild(infoBtn);
|
|
175
|
-
actions.appendChild(capsBtn);
|
|
176
|
-
actions.appendChild(rebootBtn);
|
|
177
|
-
actions.appendChild(clearBtn);
|
|
178
|
-
actions.appendChild(unpairBtn);
|
|
179
|
-
|
|
180
|
-
card.appendChild(header);
|
|
181
|
-
card.appendChild(meta);
|
|
182
|
-
card.appendChild(actions);
|
|
183
|
-
grid.appendChild(card);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function startDevicePolling() {
|
|
188
|
-
if (devicePollTimer) clearInterval(devicePollTimer);
|
|
189
|
-
|
|
190
|
-
void fetchDevices();
|
|
191
|
-
devicePollTimer = setInterval(() => {
|
|
192
|
-
void fetchDevices();
|
|
193
|
-
}, 8000);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
62
|
function showResult(data) {
|
|
197
63
|
document.getElementById("result").textContent = JSON.stringify(data, null, 2);
|
|
198
64
|
}
|
|
@@ -205,12 +71,9 @@ function isLocalPanelHost(hostname) {
|
|
|
205
71
|
function normalizeMediaBaseUrl(raw) {
|
|
206
72
|
let s = String(raw || "").trim();
|
|
207
73
|
if (!s) return "";
|
|
208
|
-
if (!/^https?:\/\//i.test(s)) {
|
|
209
|
-
s = `http://${s}`;
|
|
210
|
-
}
|
|
74
|
+
if (!/^https?:\/\//i.test(s)) s = `http://${s}`;
|
|
211
75
|
try {
|
|
212
|
-
|
|
213
|
-
return u.origin;
|
|
76
|
+
return new URL(s).origin;
|
|
214
77
|
} catch {
|
|
215
78
|
return "";
|
|
216
79
|
}
|
|
@@ -223,11 +86,7 @@ function getMediaBaseOrigin() {
|
|
|
223
86
|
""
|
|
224
87
|
);
|
|
225
88
|
if (fromInput) return fromInput;
|
|
226
|
-
|
|
227
|
-
if (!isLocalPanelHost(window.location.hostname)) {
|
|
228
|
-
return window.location.origin;
|
|
229
|
-
}
|
|
230
|
-
|
|
89
|
+
if (!isLocalPanelHost(window.location.hostname)) return window.location.origin;
|
|
231
90
|
return "";
|
|
232
91
|
}
|
|
233
92
|
|
|
@@ -248,12 +107,9 @@ function absoluteMediaUrl(path) {
|
|
|
248
107
|
const p = String(path || "").trim();
|
|
249
108
|
if (!p) return "";
|
|
250
109
|
if (/^https?:\/\//i.test(p)) return p;
|
|
251
|
-
|
|
252
110
|
const base = getMediaBaseOrigin();
|
|
253
111
|
if (!base) {
|
|
254
|
-
throw new Error(
|
|
255
|
-
"Local CMS only: set CMS URL for screens (your PC LAN IP, e.g. http://192.168.1.105:3000 — not localhost)."
|
|
256
|
-
);
|
|
112
|
+
throw new Error("Set CMS URL for screens (LAN IP, not localhost).");
|
|
257
113
|
}
|
|
258
114
|
return `${base}${p.startsWith("/") ? p : `/${p}`}`;
|
|
259
115
|
}
|
|
@@ -276,94 +132,181 @@ function defaultDurationMs(type) {
|
|
|
276
132
|
}
|
|
277
133
|
|
|
278
134
|
function normalizeDurationMs(item) {
|
|
279
|
-
const maxMs = 3600 * 1000;
|
|
280
135
|
const minMs = 1000;
|
|
136
|
+
const maxMs = 3600 * 1000;
|
|
281
137
|
let ms = Number(item?.durationMs);
|
|
282
|
-
if (!Number.isFinite(ms) || ms < minMs)
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
if (ms === 1000000) {
|
|
286
|
-
return defaultDurationMs(item?.type);
|
|
287
|
-
}
|
|
138
|
+
if (!Number.isFinite(ms) || ms < minMs) return defaultDurationMs(item?.type);
|
|
139
|
+
if (ms === 1000000) return defaultDurationMs(item?.type);
|
|
288
140
|
return Math.min(maxMs, ms);
|
|
289
141
|
}
|
|
290
142
|
|
|
291
|
-
function
|
|
292
|
-
|
|
143
|
+
function buildScheduleFromForm() {
|
|
144
|
+
const schedule = {};
|
|
145
|
+
const startDate = document.getElementById("scheduleStartDate")?.value?.trim();
|
|
146
|
+
const endDate = document.getElementById("scheduleEndDate")?.value?.trim();
|
|
147
|
+
const startTime = document.getElementById("scheduleStartTime")?.value?.trim();
|
|
148
|
+
const endTime = document.getElementById("scheduleEndTime")?.value?.trim();
|
|
149
|
+
const days = [...document.querySelectorAll(".day-checkbox:checked")].map((el) =>
|
|
150
|
+
Number(el.value)
|
|
151
|
+
);
|
|
152
|
+
if (startDate) schedule.startDate = startDate;
|
|
153
|
+
if (endDate) schedule.endDate = endDate;
|
|
154
|
+
if (startTime) schedule.start = startTime;
|
|
155
|
+
if (endTime) schedule.end = endTime;
|
|
156
|
+
if (days.length > 0) schedule.daysOfWeek = days;
|
|
157
|
+
return Object.keys(schedule).length > 0 ? schedule : undefined;
|
|
293
158
|
}
|
|
294
159
|
|
|
295
|
-
function
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
160
|
+
function loadScheduleIntoForm(schedule) {
|
|
161
|
+
const s = schedule || {};
|
|
162
|
+
document.getElementById("scheduleStartDate").value = s.startDate || "";
|
|
163
|
+
document.getElementById("scheduleEndDate").value = s.endDate || "";
|
|
164
|
+
document.getElementById("scheduleStartTime").value = s.start || "";
|
|
165
|
+
document.getElementById("scheduleEndTime").value = s.end || "";
|
|
166
|
+
document.querySelectorAll(".day-checkbox").forEach((el) => {
|
|
167
|
+
el.checked =
|
|
168
|
+
Array.isArray(s.daysOfWeek) && s.daysOfWeek.includes(Number(el.value));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getSelectedPlaylist() {
|
|
173
|
+
return playlistsCatalog.find((p) => p.id === selectedPlaylistId) || null;
|
|
304
174
|
}
|
|
305
175
|
|
|
306
|
-
function
|
|
176
|
+
async function fetchPlaylists() {
|
|
307
177
|
try {
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
178
|
+
const res = await fetch("/playlists");
|
|
179
|
+
let data = {};
|
|
180
|
+
try {
|
|
181
|
+
data = await res.json();
|
|
182
|
+
} catch {
|
|
183
|
+
data = {};
|
|
184
|
+
}
|
|
185
|
+
if (!res.ok) {
|
|
186
|
+
console.warn("[CMS] GET /playlists failed", res.status, data);
|
|
187
|
+
if (res.status === 404) {
|
|
188
|
+
showResult({
|
|
189
|
+
status: "failed",
|
|
190
|
+
error:
|
|
191
|
+
"CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.1 or newer."
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (Array.isArray(data.playlists)) {
|
|
197
|
+
playlistsCatalog = data.playlists;
|
|
198
|
+
renderPlaylistCatalog();
|
|
199
|
+
if (selectedPlaylistId && !getSelectedPlaylist()) {
|
|
200
|
+
selectedPlaylistId = playlistsCatalog[0]?.id || null;
|
|
201
|
+
loadEditorFromSelection();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.error("[CMS] fetchPlaylists:", err);
|
|
206
|
+
showResult({ status: "failed", error: err.message });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function renderPlaylistCatalog() {
|
|
211
|
+
const list = document.getElementById("playlistCatalog");
|
|
212
|
+
if (!list) return;
|
|
213
|
+
list.innerHTML = "";
|
|
214
|
+
|
|
215
|
+
if (playlistsCatalog.length === 0) {
|
|
216
|
+
const li = document.createElement("li");
|
|
217
|
+
li.className = "playlist-catalog-item";
|
|
218
|
+
li.textContent = "No playlists yet. Tap +.";
|
|
219
|
+
list.appendChild(li);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const pl of playlistsCatalog) {
|
|
224
|
+
const li = document.createElement("li");
|
|
225
|
+
li.className = "playlist-catalog-item";
|
|
226
|
+
if (pl.id === selectedPlaylistId) li.classList.add("playlist-catalog-item--active");
|
|
227
|
+
li.innerHTML = `<strong>${escapeHtml(pl.name)}</strong><small>v${pl.version} · ${(pl.items || []).length} items</small>`;
|
|
228
|
+
li.addEventListener("click", () => {
|
|
229
|
+
playlistDraftActive = false;
|
|
230
|
+
selectedPlaylistId = pl.id;
|
|
231
|
+
loadEditorFromSelection();
|
|
232
|
+
renderPlaylistCatalog();
|
|
317
233
|
});
|
|
318
|
-
|
|
319
|
-
/* ignore */
|
|
234
|
+
list.appendChild(li);
|
|
320
235
|
}
|
|
321
236
|
}
|
|
322
237
|
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
238
|
+
function isPlaylistEditorOpen() {
|
|
239
|
+
return playlistDraftActive || !!selectedPlaylistId;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function loadEditorFromSelection() {
|
|
243
|
+
const pl = getSelectedPlaylist();
|
|
244
|
+
const nameInput = document.getElementById("playlistName");
|
|
245
|
+
const editorTitle = document.getElementById("editorTitle");
|
|
246
|
+
|
|
247
|
+
if (!pl) {
|
|
248
|
+
if (playlistDraftActive) {
|
|
249
|
+
if (editorTitle) editorTitle.textContent = "New playlist";
|
|
250
|
+
renderEditorAssets();
|
|
251
|
+
return;
|
|
335
252
|
}
|
|
336
|
-
|
|
337
|
-
|
|
253
|
+
playlistDraftActive = false;
|
|
254
|
+
if (editorTitle) editorTitle.textContent = "Playlist editor";
|
|
255
|
+
if (nameInput) nameInput.value = "";
|
|
256
|
+
editorItems = [];
|
|
257
|
+
loadScheduleIntoForm(null);
|
|
258
|
+
renderEditorAssets();
|
|
259
|
+
return;
|
|
338
260
|
}
|
|
261
|
+
|
|
262
|
+
playlistDraftActive = false;
|
|
263
|
+
if (editorTitle) editorTitle.textContent = `Edit: ${pl.name}`;
|
|
264
|
+
if (nameInput) nameInput.value = pl.name || "";
|
|
265
|
+
loadScheduleIntoForm(pl.schedule);
|
|
266
|
+
editorItems = (pl.items || []).map((item) => ({
|
|
267
|
+
id: crypto.randomUUID(),
|
|
268
|
+
url: item.url,
|
|
269
|
+
name: item.url?.split("/").pop() || "asset",
|
|
270
|
+
type: item.type || "image",
|
|
271
|
+
durationMs: normalizeDurationMs(item)
|
|
272
|
+
}));
|
|
273
|
+
renderEditorAssets();
|
|
339
274
|
}
|
|
340
275
|
|
|
341
|
-
function
|
|
276
|
+
function renderEditorAssets() {
|
|
342
277
|
const list = document.getElementById("playlistList");
|
|
343
278
|
const empty = document.getElementById("playlistEmpty");
|
|
344
279
|
list.querySelectorAll(".playlist-item").forEach((el) => el.remove());
|
|
345
280
|
|
|
346
|
-
if (
|
|
281
|
+
if (!isPlaylistEditorOpen()) {
|
|
347
282
|
empty.classList.remove("hidden");
|
|
283
|
+
empty.textContent = "Select or create a playlist (Playlists +).";
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (editorItems.length === 0) {
|
|
288
|
+
empty.classList.remove("hidden");
|
|
289
|
+
empty.textContent = "No assets yet. Tap + to upload.";
|
|
348
290
|
return;
|
|
349
291
|
}
|
|
350
292
|
|
|
351
293
|
empty.classList.add("hidden");
|
|
352
294
|
|
|
353
|
-
for (const item of
|
|
295
|
+
for (const item of editorItems) {
|
|
354
296
|
const li = document.createElement("li");
|
|
355
297
|
li.className = "playlist-item";
|
|
356
|
-
li.dataset.id = item.id;
|
|
357
298
|
|
|
358
299
|
if (item.type === "image" || item.type === "video") {
|
|
359
300
|
const thumb = document.createElement(item.type === "video" ? "video" : "img");
|
|
360
301
|
thumb.className = "playlist-item-thumb";
|
|
361
|
-
|
|
302
|
+
try {
|
|
303
|
+
thumb.src = absoluteMediaUrl(item.url);
|
|
304
|
+
} catch {
|
|
305
|
+
thumb.removeAttribute("src");
|
|
306
|
+
}
|
|
362
307
|
if (item.type === "video") {
|
|
363
308
|
thumb.muted = true;
|
|
364
309
|
thumb.playsInline = true;
|
|
365
|
-
} else {
|
|
366
|
-
thumb.alt = item.name;
|
|
367
310
|
}
|
|
368
311
|
li.appendChild(thumb);
|
|
369
312
|
}
|
|
@@ -378,20 +321,14 @@ function renderPlaylist() {
|
|
|
378
321
|
|
|
379
322
|
const actions = document.createElement("div");
|
|
380
323
|
actions.className = "playlist-item-actions";
|
|
381
|
-
|
|
382
|
-
const durLabel = document.createElement("label");
|
|
383
|
-
durLabel.style.fontSize = "0.75rem";
|
|
384
|
-
durLabel.textContent = "sec ";
|
|
385
324
|
const durInput = document.createElement("input");
|
|
386
325
|
durInput.type = "number";
|
|
387
326
|
durInput.min = "1";
|
|
388
327
|
durInput.max = "3600";
|
|
389
328
|
durInput.value = String(Math.round(item.durationMs / 1000));
|
|
390
329
|
durInput.addEventListener("change", () => {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
meta.textContent = `${item.type} · ${seconds}s`;
|
|
394
|
-
savePlaylistDraft();
|
|
330
|
+
item.durationMs = Math.min(3600, Math.max(1, Number(durInput.value) || 10)) * 1000;
|
|
331
|
+
meta.textContent = `${item.type} · ${Math.round(item.durationMs / 1000)}s`;
|
|
395
332
|
});
|
|
396
333
|
|
|
397
334
|
const removeBtn = document.createElement("button");
|
|
@@ -399,15 +336,12 @@ function renderPlaylist() {
|
|
|
399
336
|
removeBtn.className = "danger";
|
|
400
337
|
removeBtn.textContent = "Remove";
|
|
401
338
|
removeBtn.addEventListener("click", () => {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
renderPlaylist();
|
|
339
|
+
editorItems = editorItems.filter((x) => x.id !== item.id);
|
|
340
|
+
renderEditorAssets();
|
|
405
341
|
});
|
|
406
342
|
|
|
407
|
-
actions.appendChild(durLabel);
|
|
408
343
|
actions.appendChild(durInput);
|
|
409
344
|
actions.appendChild(removeBtn);
|
|
410
|
-
|
|
411
345
|
li.appendChild(name);
|
|
412
346
|
li.appendChild(meta);
|
|
413
347
|
li.appendChild(actions);
|
|
@@ -415,11 +349,321 @@ function renderPlaylist() {
|
|
|
415
349
|
}
|
|
416
350
|
}
|
|
417
351
|
|
|
352
|
+
async function saveCurrentPlaylist() {
|
|
353
|
+
const name = String(document.getElementById("playlistName")?.value || "").trim();
|
|
354
|
+
if (!name) {
|
|
355
|
+
alert("Enter a playlist name.");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (editorItems.length === 0) {
|
|
359
|
+
alert("Add at least one asset before saving.");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let items;
|
|
364
|
+
try {
|
|
365
|
+
items = editorItems.map((item) => ({
|
|
366
|
+
url: absoluteMediaUrl(item.url),
|
|
367
|
+
type: item.type,
|
|
368
|
+
durationMs: item.durationMs
|
|
369
|
+
}));
|
|
370
|
+
} catch (err) {
|
|
371
|
+
alert(err.message);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const body = {
|
|
376
|
+
id: selectedPlaylistId || undefined,
|
|
377
|
+
name,
|
|
378
|
+
schedule: buildScheduleFromForm(),
|
|
379
|
+
items
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const res = await fetch("/playlists", {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: { "Content-Type": "application/json" },
|
|
385
|
+
body: JSON.stringify(body)
|
|
386
|
+
});
|
|
387
|
+
const data = await res.json();
|
|
388
|
+
showResult(data);
|
|
389
|
+
|
|
390
|
+
if (!res.ok) {
|
|
391
|
+
alert(data.error || "Save failed");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
playlistDraftActive = false;
|
|
396
|
+
selectedPlaylistId = data.playlist?.id || selectedPlaylistId;
|
|
397
|
+
await fetchPlaylists();
|
|
398
|
+
loadEditorFromSelection();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function deleteCurrentPlaylist() {
|
|
402
|
+
if (!selectedPlaylistId) {
|
|
403
|
+
alert("Select a playlist to delete.");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const pl = getSelectedPlaylist();
|
|
407
|
+
if (
|
|
408
|
+
!confirm(
|
|
409
|
+
`Delete playlist "${pl?.name}"? Devices already playing it keep their cached copy until Clear or reboot without sync. New devices cannot receive it.`
|
|
410
|
+
)
|
|
411
|
+
) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const res = await fetch(`/playlists/${encodeURIComponent(selectedPlaylistId)}`, {
|
|
416
|
+
method: "DELETE"
|
|
417
|
+
});
|
|
418
|
+
const data = await res.json();
|
|
419
|
+
showResult(data);
|
|
420
|
+
if (!res.ok) {
|
|
421
|
+
alert(data.error || "Delete failed");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
selectedPlaylistId = null;
|
|
426
|
+
playlistDraftActive = false;
|
|
427
|
+
editorItems = [];
|
|
428
|
+
await fetchPlaylists();
|
|
429
|
+
loadEditorFromSelection();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function newPlaylistDraft() {
|
|
433
|
+
selectedPlaylistId = null;
|
|
434
|
+
playlistDraftActive = true;
|
|
435
|
+
const nameInput = document.getElementById("playlistName");
|
|
436
|
+
if (nameInput) {
|
|
437
|
+
nameInput.value = "";
|
|
438
|
+
nameInput.focus();
|
|
439
|
+
}
|
|
440
|
+
loadScheduleIntoForm(null);
|
|
441
|
+
editorItems = [];
|
|
442
|
+
renderPlaylistCatalog();
|
|
443
|
+
renderEditorAssets();
|
|
444
|
+
const editorTitle = document.getElementById("editorTitle");
|
|
445
|
+
if (editorTitle) editorTitle.textContent = "New playlist";
|
|
446
|
+
document.getElementById("playlistEditorSection")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
447
|
+
showResult({
|
|
448
|
+
status: "draft",
|
|
449
|
+
message: "New playlist — enter a name, add assets with + (right), then Save playlist."
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function fetchDevices() {
|
|
454
|
+
try {
|
|
455
|
+
const res = await fetch("/devices");
|
|
456
|
+
const data = await res.json();
|
|
457
|
+
if (Array.isArray(data.devices)) {
|
|
458
|
+
devicesCache = data.devices;
|
|
459
|
+
renderDeviceCards();
|
|
460
|
+
}
|
|
461
|
+
} catch (err) {
|
|
462
|
+
showResult({ status: "failed", error: err.message });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function renderDeviceCards() {
|
|
467
|
+
const grid = document.getElementById("devicesGrid");
|
|
468
|
+
if (!grid) return;
|
|
469
|
+
grid.innerHTML = "";
|
|
470
|
+
|
|
471
|
+
if (devicesCache.length === 0) {
|
|
472
|
+
const empty = document.createElement("p");
|
|
473
|
+
empty.className = "devices-empty";
|
|
474
|
+
empty.textContent = "No paired devices yet.";
|
|
475
|
+
grid.appendChild(empty);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
for (const device of devicesCache) {
|
|
480
|
+
const card = document.createElement("article");
|
|
481
|
+
card.className = "device-card";
|
|
482
|
+
|
|
483
|
+
const header = document.createElement("div");
|
|
484
|
+
header.className = "device-card-header";
|
|
485
|
+
const led = document.createElement("span");
|
|
486
|
+
led.className = `status-led ${device.connected ? "status-led--online" : "status-led--offline"}`;
|
|
487
|
+
const title = document.createElement("h3");
|
|
488
|
+
title.className = "device-card-title";
|
|
489
|
+
title.textContent = device.deviceName || "Screen";
|
|
490
|
+
header.appendChild(led);
|
|
491
|
+
header.appendChild(title);
|
|
492
|
+
|
|
493
|
+
const published = document.createElement("ul");
|
|
494
|
+
published.className = "device-published-list";
|
|
495
|
+
const pubs = Array.isArray(device.publishedPlaylists) ? device.publishedPlaylists : [];
|
|
496
|
+
if (pubs.length === 0) {
|
|
497
|
+
const li = document.createElement("li");
|
|
498
|
+
li.textContent = "No playlists published";
|
|
499
|
+
published.appendChild(li);
|
|
500
|
+
} else {
|
|
501
|
+
for (const p of pubs) {
|
|
502
|
+
const li = document.createElement("li");
|
|
503
|
+
const label = document.createElement("span");
|
|
504
|
+
label.textContent = `${p.name} (v${p.version})`;
|
|
505
|
+
const rm = document.createElement("button");
|
|
506
|
+
rm.type = "button";
|
|
507
|
+
rm.textContent = "Remove";
|
|
508
|
+
rm.addEventListener("click", () => removePlaylistFromDevice(device.deviceId, p.playlistId));
|
|
509
|
+
li.appendChild(label);
|
|
510
|
+
li.appendChild(rm);
|
|
511
|
+
published.appendChild(li);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const meta = document.createElement("dl");
|
|
516
|
+
meta.className = "device-meta";
|
|
517
|
+
const rows = [
|
|
518
|
+
["Device ID", device.deviceId],
|
|
519
|
+
["System", device.system || device.platform || "—"],
|
|
520
|
+
["Device online", formatDeviceOnlineLabel(device)],
|
|
521
|
+
["Last boot", formatDateTimeSeconds(device.lastBootAt)],
|
|
522
|
+
["Latest push", formatDateTimeSeconds(device.lastPolicyPushAt)]
|
|
523
|
+
];
|
|
524
|
+
for (const [label, value] of rows) {
|
|
525
|
+
const row = document.createElement("div");
|
|
526
|
+
row.className = "device-meta-row";
|
|
527
|
+
const dt = document.createElement("dt");
|
|
528
|
+
dt.textContent = label;
|
|
529
|
+
const dd = document.createElement("dd");
|
|
530
|
+
dd.textContent = value;
|
|
531
|
+
row.appendChild(dt);
|
|
532
|
+
row.appendChild(dd);
|
|
533
|
+
meta.appendChild(row);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const actions = document.createElement("div");
|
|
537
|
+
actions.className = "device-card-actions";
|
|
538
|
+
|
|
539
|
+
const publishBtn = document.createElement("button");
|
|
540
|
+
publishBtn.type = "button";
|
|
541
|
+
publishBtn.className = "primary";
|
|
542
|
+
publishBtn.textContent = "Publish";
|
|
543
|
+
publishBtn.addEventListener("click", () => openPublishModal(device.deviceId));
|
|
544
|
+
|
|
545
|
+
const infoBtn = document.createElement("button");
|
|
546
|
+
infoBtn.type = "button";
|
|
547
|
+
infoBtn.textContent = "Info";
|
|
548
|
+
infoBtn.addEventListener("click", () => deviceAction(device.deviceId, "get-info"));
|
|
549
|
+
|
|
550
|
+
const rebootBtn = document.createElement("button");
|
|
551
|
+
rebootBtn.type = "button";
|
|
552
|
+
rebootBtn.textContent = "Reboot";
|
|
553
|
+
rebootBtn.addEventListener("click", () => deviceAction(device.deviceId, "reboot"));
|
|
554
|
+
|
|
555
|
+
const clearBtn = document.createElement("button");
|
|
556
|
+
clearBtn.type = "button";
|
|
557
|
+
clearBtn.textContent = "Clear";
|
|
558
|
+
clearBtn.addEventListener("click", () => deviceAction(device.deviceId, "content/clear"));
|
|
559
|
+
|
|
560
|
+
const unpairBtn = document.createElement("button");
|
|
561
|
+
unpairBtn.type = "button";
|
|
562
|
+
unpairBtn.className = "danger";
|
|
563
|
+
unpairBtn.textContent = "Unpair";
|
|
564
|
+
unpairBtn.addEventListener("click", () => unpairDevice(device.deviceId));
|
|
565
|
+
|
|
566
|
+
actions.appendChild(publishBtn);
|
|
567
|
+
actions.appendChild(infoBtn);
|
|
568
|
+
actions.appendChild(rebootBtn);
|
|
569
|
+
actions.appendChild(clearBtn);
|
|
570
|
+
actions.appendChild(unpairBtn);
|
|
571
|
+
|
|
572
|
+
card.appendChild(header);
|
|
573
|
+
card.appendChild(published);
|
|
574
|
+
card.appendChild(meta);
|
|
575
|
+
card.appendChild(actions);
|
|
576
|
+
grid.appendChild(card);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function openPublishModal(deviceId) {
|
|
581
|
+
publishModalDeviceId = deviceId;
|
|
582
|
+
const modal = document.getElementById("publishModal");
|
|
583
|
+
const checklist = document.getElementById("publishChecklist");
|
|
584
|
+
const hint = document.getElementById("publishModalHint");
|
|
585
|
+
if (!modal || !checklist) return;
|
|
586
|
+
|
|
587
|
+
if (playlistsCatalog.length === 0) {
|
|
588
|
+
alert("Create and save at least one playlist first.");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
hint.textContent = `Device ${deviceId} — select playlists to publish (snapshot at publish time).`;
|
|
593
|
+
checklist.innerHTML = "";
|
|
594
|
+
|
|
595
|
+
for (const pl of playlistsCatalog) {
|
|
596
|
+
const label = document.createElement("label");
|
|
597
|
+
const cb = document.createElement("input");
|
|
598
|
+
cb.type = "checkbox";
|
|
599
|
+
cb.value = pl.id;
|
|
600
|
+
cb.dataset.name = pl.name;
|
|
601
|
+
const pubs = devicesCache.find((d) => d.deviceId === deviceId)?.publishedPlaylists || [];
|
|
602
|
+
if (pubs.some((p) => p.playlistId === pl.id)) cb.checked = true;
|
|
603
|
+
label.appendChild(cb);
|
|
604
|
+
label.appendChild(document.createTextNode(` ${pl.name} (v${pl.version})`));
|
|
605
|
+
checklist.appendChild(label);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
modal.classList.remove("hidden");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function closePublishModal() {
|
|
612
|
+
publishModalDeviceId = null;
|
|
613
|
+
document.getElementById("publishModal")?.classList.add("hidden");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function confirmPublishModal() {
|
|
617
|
+
if (!publishModalDeviceId) return;
|
|
618
|
+
const ids = [
|
|
619
|
+
...document.querySelectorAll("#publishChecklist input[type=checkbox]:checked")
|
|
620
|
+
].map((el) => el.value);
|
|
621
|
+
|
|
622
|
+
if (ids.length === 0) {
|
|
623
|
+
alert("Select at least one playlist.");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const res = await fetch(
|
|
628
|
+
`/device/${encodeURIComponent(publishModalDeviceId)}/assignments`,
|
|
629
|
+
{
|
|
630
|
+
method: "POST",
|
|
631
|
+
headers: { "Content-Type": "application/json" },
|
|
632
|
+
body: JSON.stringify({ playlistIds: ids })
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
const data = await res.json();
|
|
636
|
+
showResult({ deviceId: publishModalDeviceId, publish: data });
|
|
637
|
+
if (!res.ok) {
|
|
638
|
+
alert(data.error || "Publish failed");
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
closePublishModal();
|
|
642
|
+
await fetchDevices();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async function removePlaylistFromDevice(deviceId, playlistId) {
|
|
646
|
+
if (!confirm("Remove this playlist from the device? Currently playing content may continue until Clear or failed reboot sync.")) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const res = await fetch(
|
|
650
|
+
`/device/${encodeURIComponent(deviceId)}/assignments/${encodeURIComponent(playlistId)}`,
|
|
651
|
+
{ method: "DELETE" }
|
|
652
|
+
);
|
|
653
|
+
const data = await res.json();
|
|
654
|
+
showResult({ deviceId, remove: data });
|
|
655
|
+
if (!res.ok) {
|
|
656
|
+
alert(data.error || "Remove failed");
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
await fetchDevices();
|
|
660
|
+
}
|
|
661
|
+
|
|
418
662
|
async function uploadFile(file) {
|
|
419
663
|
const q = new URLSearchParams({ filename: file.name });
|
|
420
664
|
const res = await fetch(`/media/upload?${q.toString()}`, {
|
|
421
665
|
method: "POST",
|
|
422
|
-
headers: { "Content-Type":
|
|
666
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
423
667
|
body: file
|
|
424
668
|
});
|
|
425
669
|
const data = await res.json();
|
|
@@ -430,219 +674,109 @@ async function uploadFile(file) {
|
|
|
430
674
|
}
|
|
431
675
|
|
|
432
676
|
async function addAssetFromFile(file) {
|
|
433
|
-
|
|
677
|
+
if (!isPlaylistEditorOpen()) {
|
|
678
|
+
newPlaylistDraft();
|
|
679
|
+
}
|
|
434
680
|
const data = await uploadFile(file);
|
|
435
681
|
const type = inferMediaType(file.name, file.type);
|
|
436
|
-
|
|
682
|
+
editorItems.push({
|
|
437
683
|
id: crypto.randomUUID(),
|
|
438
684
|
url: data.url,
|
|
439
685
|
name: file.name,
|
|
440
686
|
type,
|
|
441
687
|
durationMs: defaultDurationMs(type)
|
|
442
688
|
});
|
|
443
|
-
|
|
444
|
-
renderPlaylist();
|
|
689
|
+
renderEditorAssets();
|
|
445
690
|
showResult({ status: "uploaded", ...data });
|
|
446
691
|
}
|
|
447
692
|
|
|
448
|
-
function buildSchedule() {
|
|
449
|
-
const schedule = {};
|
|
450
|
-
const startDate = document.getElementById("scheduleStartDate")?.value?.trim();
|
|
451
|
-
const endDate = document.getElementById("scheduleEndDate")?.value?.trim();
|
|
452
|
-
const startTime = document.getElementById("scheduleStartTime")?.value?.trim();
|
|
453
|
-
const endTime = document.getElementById("scheduleEndTime")?.value?.trim();
|
|
454
|
-
const days = [...document.querySelectorAll(".day-checkbox:checked")].map((el) =>
|
|
455
|
-
Number(el.value)
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
if (startDate) schedule.startDate = startDate;
|
|
459
|
-
if (endDate) schedule.endDate = endDate;
|
|
460
|
-
if (startTime) schedule.start = startTime;
|
|
461
|
-
if (endTime) schedule.end = endTime;
|
|
462
|
-
if (days.length > 0) schedule.daysOfWeek = days;
|
|
463
|
-
|
|
464
|
-
return Object.keys(schedule).length > 0 ? schedule : undefined;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function buildPolicyPayload() {
|
|
468
|
-
const schedule = buildSchedule();
|
|
469
|
-
const playlist = {
|
|
470
|
-
id: "main",
|
|
471
|
-
name: "Playlist",
|
|
472
|
-
items: playlistItems.map((item) => ({
|
|
473
|
-
url: absoluteMediaUrl(item.url),
|
|
474
|
-
type: item.type,
|
|
475
|
-
durationMs: item.durationMs
|
|
476
|
-
}))
|
|
477
|
-
};
|
|
478
|
-
if (schedule) playlist.schedule = schedule;
|
|
479
|
-
|
|
480
|
-
return {
|
|
481
|
-
policy: {
|
|
482
|
-
playlists: [playlist],
|
|
483
|
-
fallback: { type: "brand" }
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
|
|
488
693
|
async function verify() {
|
|
489
694
|
const code = String(document.getElementById("code").value || "")
|
|
490
695
|
.trim()
|
|
491
696
|
.toUpperCase()
|
|
492
697
|
.replace(/[^0-9A-Z]/g, "");
|
|
493
|
-
|
|
494
698
|
if (code.length !== 6) {
|
|
495
699
|
showResult({ status: "failed", error: "Enter the 6-character code from the screen." });
|
|
496
700
|
return;
|
|
497
701
|
}
|
|
498
|
-
|
|
499
702
|
const res = await fetch("/pairing/verify", {
|
|
500
703
|
method: "POST",
|
|
501
704
|
headers: { "Content-Type": "application/json" },
|
|
502
705
|
body: JSON.stringify({ code })
|
|
503
706
|
});
|
|
504
|
-
|
|
505
707
|
const data = await res.json();
|
|
506
708
|
showResult(data);
|
|
507
|
-
|
|
508
709
|
if (data.deviceId) {
|
|
509
|
-
|
|
510
|
-
if (codeInput) codeInput.value = "";
|
|
710
|
+
document.getElementById("code").value = "";
|
|
511
711
|
await fetchDevices();
|
|
512
712
|
}
|
|
513
713
|
}
|
|
514
714
|
|
|
515
715
|
async function unpairDevice(deviceId) {
|
|
516
|
-
if (
|
|
517
|
-
!confirm(
|
|
518
|
-
"Unpair this device? Its card will be removed; the screen keeps the same permanent pairing code."
|
|
519
|
-
)
|
|
520
|
-
) {
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
|
|
716
|
+
if (!confirm("Unpair this device?")) return;
|
|
524
717
|
const res = await fetch("/pairing/unpair", {
|
|
525
718
|
method: "POST",
|
|
526
719
|
headers: { "Content-Type": "application/json" },
|
|
527
720
|
body: JSON.stringify({ deviceId })
|
|
528
721
|
});
|
|
529
|
-
|
|
530
722
|
const data = await res.json();
|
|
531
723
|
showResult(data);
|
|
532
|
-
|
|
533
|
-
if (res.ok) {
|
|
534
|
-
await fetchDevices();
|
|
535
|
-
} else {
|
|
536
|
-
alert(data.error || "Unpair failed");
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
async function publishToDevice(deviceId) {
|
|
541
|
-
if (!deviceId) {
|
|
542
|
-
alert("Unknown device.");
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (playlistItems.length === 0) {
|
|
547
|
-
alert("Add at least one asset to the playlist.");
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
const mediaBase = getMediaBaseOrigin();
|
|
552
|
-
if (!mediaBase) {
|
|
553
|
-
alert(
|
|
554
|
-
"Local CMS only: set CMS URL for screens first (e.g. http://192.168.1.105:3000 — your PC LAN IP, not localhost)."
|
|
555
|
-
);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
if (isLocalPanelHost(new URL(mediaBase).hostname)) {
|
|
559
|
-
const ok = confirm(
|
|
560
|
-
"Media URLs use localhost. TVs cannot download from localhost unless the player runs on this PC. Continue anyway?"
|
|
561
|
-
);
|
|
562
|
-
if (!ok) return;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
saveScheduleDraft();
|
|
566
|
-
let payload;
|
|
567
|
-
try {
|
|
568
|
-
payload = buildPolicyPayload();
|
|
569
|
-
} catch (err) {
|
|
570
|
-
alert(err.message);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const res = await fetch(`/device/${deviceId}/content/set-policy`, {
|
|
575
|
-
method: "POST",
|
|
576
|
-
headers: { "Content-Type": "application/json" },
|
|
577
|
-
body: JSON.stringify(payload)
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const data = await res.json();
|
|
581
|
-
showResult({ deviceId, publish: payload, response: data });
|
|
582
|
-
|
|
583
|
-
if (res.ok) {
|
|
584
|
-
await fetchDevices();
|
|
585
|
-
}
|
|
724
|
+
if (res.ok) await fetchDevices();
|
|
586
725
|
}
|
|
587
726
|
|
|
588
727
|
async function deviceAction(deviceId, action) {
|
|
589
728
|
if (!deviceId) return;
|
|
590
|
-
|
|
591
729
|
if (action === "reboot" && !confirm("Reboot this device?")) return;
|
|
592
730
|
if (action === "content/clear" && !confirm("Clear content on this device?")) return;
|
|
593
|
-
|
|
594
731
|
const res = await fetch(`/device/${encodeURIComponent(deviceId)}/${action}`, {
|
|
595
732
|
method: "POST"
|
|
596
733
|
});
|
|
597
734
|
const data = await res.json();
|
|
598
735
|
showResult({ deviceId, action, ...data });
|
|
736
|
+
if (action === "reboot" && res.ok) await fetchDevices();
|
|
737
|
+
}
|
|
599
738
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
739
|
+
function startDevicePolling() {
|
|
740
|
+
if (devicePollTimer) clearInterval(devicePollTimer);
|
|
741
|
+
void fetchDevices();
|
|
742
|
+
devicePollTimer = setInterval(() => void fetchDevices(), 8000);
|
|
603
743
|
}
|
|
604
744
|
|
|
605
745
|
document.addEventListener("DOMContentLoaded", () => {
|
|
606
746
|
const savedMediaBase = localStorage.getItem(PANEL_MEDIA_BASE_KEY);
|
|
607
747
|
const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
|
|
608
|
-
if (savedMediaBase && cmsBaseInput)
|
|
609
|
-
|
|
610
|
-
} else if (cmsBaseInput && !isLocalPanelHost(window.location.hostname)) {
|
|
748
|
+
if (savedMediaBase && cmsBaseInput) cmsBaseInput.value = savedMediaBase;
|
|
749
|
+
else if (cmsBaseInput && !isLocalPanelHost(window.location.hostname)) {
|
|
611
750
|
cmsBaseInput.value = window.location.origin;
|
|
612
751
|
}
|
|
613
752
|
|
|
614
|
-
|
|
615
|
-
loadScheduleDraft();
|
|
753
|
+
void fetchPlaylists();
|
|
616
754
|
startDevicePolling();
|
|
617
755
|
|
|
618
|
-
document.getElementById("
|
|
619
|
-
|
|
756
|
+
document.getElementById("newPlaylistBtn")?.addEventListener("click", newPlaylistDraft);
|
|
757
|
+
document.getElementById("savePlaylistBtn")?.addEventListener("click", () => void saveCurrentPlaylist());
|
|
758
|
+
document.getElementById("deletePlaylistBtn")?.addEventListener("click", () => void deleteCurrentPlaylist());
|
|
759
|
+
document.getElementById("publishConfirmBtn")?.addEventListener("click", () => void confirmPublishModal());
|
|
760
|
+
|
|
761
|
+
document.querySelectorAll("[data-close-modal]").forEach((el) => {
|
|
762
|
+
el.addEventListener("click", closePublishModal);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
document.getElementById("addAssetBtn")?.addEventListener("click", () => {
|
|
766
|
+
if (!isPlaylistEditorOpen()) {
|
|
767
|
+
newPlaylistDraft();
|
|
768
|
+
}
|
|
769
|
+
document.getElementById("fileInput")?.click();
|
|
620
770
|
});
|
|
621
771
|
|
|
622
|
-
document.getElementById("fileInput")
|
|
772
|
+
document.getElementById("fileInput")?.addEventListener("change", async (ev) => {
|
|
623
773
|
const files = ev.target.files;
|
|
624
774
|
if (!files?.length) return;
|
|
625
775
|
try {
|
|
626
|
-
for (const file of files)
|
|
627
|
-
await addAssetFromFile(file);
|
|
628
|
-
}
|
|
776
|
+
for (const file of files) await addAssetFromFile(file);
|
|
629
777
|
} catch (err) {
|
|
630
778
|
alert(err.message);
|
|
631
|
-
showResult({ status: "failed", error: err.message });
|
|
632
779
|
}
|
|
633
780
|
ev.target.value = "";
|
|
634
781
|
});
|
|
635
|
-
|
|
636
|
-
[
|
|
637
|
-
"scheduleStartDate",
|
|
638
|
-
"scheduleEndDate",
|
|
639
|
-
"scheduleStartTime",
|
|
640
|
-
"scheduleEndTime"
|
|
641
|
-
].forEach((id) => {
|
|
642
|
-
document.getElementById(id)?.addEventListener("change", saveScheduleDraft);
|
|
643
|
-
});
|
|
644
|
-
document.querySelectorAll(".day-checkbox").forEach((el) => {
|
|
645
|
-
el.addEventListener("change", saveScheduleDraft);
|
|
646
|
-
});
|
|
647
|
-
|
|
648
782
|
});
|