@tomorrowos/sdk 0.3.2 → 0.3.3
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/playlist-catalog.d.ts +9 -4
- package/dist/playlist-catalog.d.ts.map +1 -1
- package/dist/playlist-catalog.js +54 -14
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +6 -2
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/index.html +2 -1
- package/templates/cms-starter/public/methods.js +73 -12
|
@@ -13,8 +13,15 @@ export interface BuiltDevicePolicy {
|
|
|
13
13
|
};
|
|
14
14
|
revision?: number;
|
|
15
15
|
syncMode?: "latest" | "snapshot";
|
|
16
|
+
/** One-shot: player may start outside schedule right after publish. */
|
|
17
|
+
playNow?: boolean;
|
|
16
18
|
};
|
|
17
19
|
}
|
|
20
|
+
export interface PolicyBuildOptions {
|
|
21
|
+
useLatest?: boolean;
|
|
22
|
+
playNow?: boolean;
|
|
23
|
+
mediaBaseUrl?: string;
|
|
24
|
+
}
|
|
18
25
|
export declare class PlaylistCatalog {
|
|
19
26
|
private readonly store;
|
|
20
27
|
constructor(store: TomorrowOSStore);
|
|
@@ -24,11 +31,9 @@ export declare class PlaylistCatalog {
|
|
|
24
31
|
savePlaylist(input: SavePlaylistInput): Promise<StoredPlaylist>;
|
|
25
32
|
retirePlaylist(id: string): Promise<StoredPlaylist>;
|
|
26
33
|
getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
|
|
27
|
-
publishPlaylistsToDevice(deviceId: string, playlistIds: string[]): Promise<BuiltDevicePolicy>;
|
|
34
|
+
publishPlaylistsToDevice(deviceId: string, playlistIds: string[], options?: PolicyBuildOptions): Promise<BuiltDevicePolicy>;
|
|
28
35
|
removePlaylistFromDevice(deviceId: string, playlistId: string): Promise<BuiltDevicePolicy>;
|
|
29
|
-
buildPolicyForDevice(deviceId: string, options?:
|
|
30
|
-
useLatest?: boolean;
|
|
31
|
-
}): Promise<BuiltDevicePolicy>;
|
|
36
|
+
buildPolicyForDevice(deviceId: string, options?: PolicyBuildOptions): Promise<BuiltDevicePolicy>;
|
|
32
37
|
private buildPolicyFromAssignments;
|
|
33
38
|
canPublishPlaylistToNewDevice(playlistId: string): Promise<boolean>;
|
|
34
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playlist-catalog.d.ts","sourceRoot":"","sources":["../src/playlist-catalog.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QACN,SAAS,EAAE,yBAAyB,EAAE,CAAC;QACvC,QAAQ,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"playlist-catalog.d.ts","sourceRoot":"","sources":["../src/playlist-catalog.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QACN,SAAS,EAAE,yBAAyB,EAAE,CAAC;QACvC,QAAQ,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;QACjC,uEAAuE;QACvE,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA+CD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,eAAe;IAE7C,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAK1C,6BAA6B,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1D,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI5D,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;IAgC/D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAcnD,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAIhC,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;IAkCvB,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAOvB,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAKf,0BAA0B;IAmCxC,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpE"}
|
package/dist/playlist-catalog.js
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
-
function
|
|
2
|
+
function normalizeMediaBaseUrl(raw) {
|
|
3
|
+
let s = String(raw || "").trim();
|
|
4
|
+
if (!s)
|
|
5
|
+
return "";
|
|
6
|
+
if (!/^https?:\/\//i.test(s))
|
|
7
|
+
s = `http://${s}`;
|
|
8
|
+
try {
|
|
9
|
+
return new URL(s).origin;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function absolutizeMediaUrl(url, mediaBaseUrl) {
|
|
16
|
+
const trimmed = String(url || "").trim();
|
|
17
|
+
if (!trimmed)
|
|
18
|
+
return trimmed;
|
|
19
|
+
if (/^https?:\/\//i.test(trimmed))
|
|
20
|
+
return trimmed;
|
|
21
|
+
const base = normalizeMediaBaseUrl(mediaBaseUrl);
|
|
22
|
+
if (!base)
|
|
23
|
+
return trimmed;
|
|
24
|
+
return `${base}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`;
|
|
25
|
+
}
|
|
26
|
+
function absolutizePlaylistItems(items, mediaBaseUrl) {
|
|
27
|
+
const base = normalizeMediaBaseUrl(mediaBaseUrl);
|
|
28
|
+
if (!base)
|
|
29
|
+
return items.map((item) => ({ ...item }));
|
|
30
|
+
return items.map((item) => ({
|
|
31
|
+
...item,
|
|
32
|
+
url: absolutizeMediaUrl(item.url, base)
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function cloneSnapshot(playlist, mediaBaseUrl) {
|
|
3
36
|
return {
|
|
4
37
|
id: playlist.id,
|
|
5
38
|
name: playlist.name,
|
|
6
39
|
version: playlist.version,
|
|
7
40
|
schedule: playlist.schedule ? { ...playlist.schedule } : undefined,
|
|
8
|
-
items: playlist.items
|
|
41
|
+
items: absolutizePlaylistItems(playlist.items, mediaBaseUrl)
|
|
9
42
|
};
|
|
10
43
|
}
|
|
11
44
|
export class PlaylistCatalog {
|
|
@@ -67,7 +100,7 @@ export class PlaylistCatalog {
|
|
|
67
100
|
async getDeviceAssignments(deviceId) {
|
|
68
101
|
return this.store.getDeviceAssignments(deviceId);
|
|
69
102
|
}
|
|
70
|
-
async publishPlaylistsToDevice(deviceId, playlistIds) {
|
|
103
|
+
async publishPlaylistsToDevice(deviceId, playlistIds, options = {}) {
|
|
71
104
|
const ids = [...new Set(playlistIds.map((x) => String(x).trim()).filter(Boolean))];
|
|
72
105
|
if (ids.length === 0) {
|
|
73
106
|
throw Object.assign(new Error("Select at least one playlist"), {
|
|
@@ -86,11 +119,15 @@ export class PlaylistCatalog {
|
|
|
86
119
|
playlistId,
|
|
87
120
|
publishedVersion: playlist.version,
|
|
88
121
|
publishedAt: new Date().toISOString(),
|
|
89
|
-
snapshot: cloneSnapshot(playlist)
|
|
122
|
+
snapshot: cloneSnapshot(playlist, options.mediaBaseUrl)
|
|
90
123
|
});
|
|
91
124
|
}
|
|
92
125
|
await this.store.setDeviceAssignments(deviceId, assignments);
|
|
93
|
-
return this.buildPolicyFromAssignments(assignments, {
|
|
126
|
+
return this.buildPolicyFromAssignments(assignments, {
|
|
127
|
+
useLatest: false,
|
|
128
|
+
playNow: true,
|
|
129
|
+
mediaBaseUrl: options.mediaBaseUrl
|
|
130
|
+
});
|
|
94
131
|
}
|
|
95
132
|
async removePlaylistFromDevice(deviceId, playlistId) {
|
|
96
133
|
const assignments = await this.store.getDeviceAssignments(deviceId);
|
|
@@ -104,28 +141,31 @@ export class PlaylistCatalog {
|
|
|
104
141
|
}
|
|
105
142
|
async buildPolicyFromAssignments(assignments, options) {
|
|
106
143
|
const useLatest = options.useLatest === true;
|
|
144
|
+
const mediaBaseUrl = options.mediaBaseUrl;
|
|
107
145
|
const playlists = [];
|
|
108
146
|
for (const assignment of assignments) {
|
|
109
147
|
if (useLatest) {
|
|
110
148
|
const current = await this.store.getPlaylist(assignment.playlistId);
|
|
111
149
|
if (current && !current.retired) {
|
|
112
|
-
playlists.push(cloneSnapshot(current));
|
|
150
|
+
playlists.push(cloneSnapshot(current, mediaBaseUrl));
|
|
113
151
|
continue;
|
|
114
152
|
}
|
|
115
153
|
}
|
|
116
154
|
playlists.push({
|
|
117
155
|
...assignment.snapshot,
|
|
118
|
-
items: assignment.snapshot.items
|
|
156
|
+
items: absolutizePlaylistItems(assignment.snapshot.items, mediaBaseUrl)
|
|
119
157
|
});
|
|
120
158
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
syncMode: useLatest ? "latest" : "snapshot"
|
|
127
|
-
}
|
|
159
|
+
const policy = {
|
|
160
|
+
playlists,
|
|
161
|
+
fallback: { type: "brand" },
|
|
162
|
+
revision: Date.now(),
|
|
163
|
+
syncMode: useLatest ? "latest" : "snapshot"
|
|
128
164
|
};
|
|
165
|
+
if (options.playNow === true) {
|
|
166
|
+
policy.playNow = true;
|
|
167
|
+
}
|
|
168
|
+
return { policy };
|
|
129
169
|
}
|
|
130
170
|
canPublishPlaylistToNewDevice(playlistId) {
|
|
131
171
|
return this.store.getPlaylist(playlistId).then((p) => !!p && !p?.retired);
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.js
CHANGED
|
@@ -639,14 +639,18 @@ export class TomorrowOS extends EventEmitter {
|
|
|
639
639
|
}
|
|
640
640
|
try {
|
|
641
641
|
let built;
|
|
642
|
+
const mediaBaseUrl = typeof body.mediaBaseUrl === "string" ? body.mediaBaseUrl : undefined;
|
|
642
643
|
if (body.useLatest === true) {
|
|
643
644
|
built = await this.playlists.buildPolicyForDevice(deviceId, {
|
|
644
|
-
useLatest: true
|
|
645
|
+
useLatest: true,
|
|
646
|
+
mediaBaseUrl
|
|
645
647
|
});
|
|
646
648
|
}
|
|
647
649
|
else {
|
|
648
650
|
const ids = Array.isArray(body.playlistIds) ? body.playlistIds : [];
|
|
649
|
-
built = await this.playlists.publishPlaylistsToDevice(deviceId, ids
|
|
651
|
+
built = await this.playlists.publishPlaylistsToDevice(deviceId, ids, {
|
|
652
|
+
mediaBaseUrl
|
|
653
|
+
});
|
|
650
654
|
}
|
|
651
655
|
const result = await this.sendDeviceCommand(deviceId, "device.content.setPolicy", {
|
|
652
656
|
policy: built.policy
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "TomorrowOS CMS server SDK — WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-cms",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"build-player": "tomorrowos build --platform tizen"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@tomorrowos/sdk": "^0.3.
|
|
13
|
+
"@tomorrowos/sdk": "^0.3.3"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/node": "^20.0.0",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
<section class="card" id="cmsUrlSection">
|
|
42
42
|
<h2>CMS URL for screens</h2>
|
|
43
43
|
<p class="hint">
|
|
44
|
-
<strong>Local development only.</strong> LAN
|
|
44
|
+
<strong>Local development only.</strong> Optional LAN override so TVs reach your PC (not localhost).
|
|
45
|
+
Hosted CMS (e.g. Replit) uses its public URL automatically — leave this empty there.
|
|
45
46
|
</p>
|
|
46
47
|
<div class="row">
|
|
47
48
|
<input
|
|
@@ -79,15 +79,44 @@ function normalizeMediaBaseUrl(raw) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** User-saved LAN override (local dev only). Not used on hosted CMS unless explicitly set. */
|
|
83
|
+
function getExplicitLanMediaBase() {
|
|
84
|
+
return normalizeMediaBaseUrl(localStorage.getItem(PANEL_MEDIA_BASE_KEY) || "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function playlistHasRelativeMediaUrls(playlist) {
|
|
88
|
+
return (playlist?.items || []).some((item) => {
|
|
89
|
+
const url = String(item?.url || "").trim();
|
|
90
|
+
return url && !/^https?:\/\//i.test(url);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Base URL sent on publish only when needed.
|
|
96
|
+
* - Hosted (e.g. Replit): use public origin only if items are still relative (/uploads/...).
|
|
97
|
+
* - Local: use saved LAN override only when set; never default to rewriting otherwise.
|
|
98
|
+
* - Returns null to omit mediaBaseUrl (playlist already has absolute https URLs).
|
|
99
|
+
*/
|
|
100
|
+
function getPublishMediaBaseUrl(selectedPlaylists) {
|
|
101
|
+
const explicitLan = getExplicitLanMediaBase();
|
|
102
|
+
if (explicitLan) return explicitLan;
|
|
103
|
+
|
|
104
|
+
const needsRewrite = selectedPlaylists.some(playlistHasRelativeMediaUrls);
|
|
105
|
+
if (!needsRewrite) return null;
|
|
106
|
+
|
|
107
|
+
if (!isLocalPanelHost(window.location.hostname)) {
|
|
108
|
+
return window.location.origin;
|
|
109
|
+
}
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Resolve media URLs in the editor (save / thumbnails). */
|
|
82
114
|
function getMediaBaseOrigin() {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
localStorage.getItem(PANEL_MEDIA_BASE_KEY) ||
|
|
86
|
-
""
|
|
87
|
-
);
|
|
88
|
-
if (fromInput) return fromInput;
|
|
115
|
+
const explicitLan = getExplicitLanMediaBase();
|
|
116
|
+
if (explicitLan) return explicitLan;
|
|
89
117
|
if (!isLocalPanelHost(window.location.hostname)) return window.location.origin;
|
|
90
|
-
|
|
118
|
+
const draft = normalizeMediaBaseUrl(document.getElementById("cmsDeviceBaseUrl")?.value);
|
|
119
|
+
return draft;
|
|
91
120
|
}
|
|
92
121
|
|
|
93
122
|
function saveCmsDeviceBaseUrl() {
|
|
@@ -188,7 +217,7 @@ async function fetchPlaylists() {
|
|
|
188
217
|
showResult({
|
|
189
218
|
status: "failed",
|
|
190
219
|
error:
|
|
191
|
-
"CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.
|
|
220
|
+
"CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.3 or newer."
|
|
192
221
|
});
|
|
193
222
|
}
|
|
194
223
|
return;
|
|
@@ -547,6 +576,13 @@ function renderDeviceCards() {
|
|
|
547
576
|
infoBtn.textContent = "Info";
|
|
548
577
|
infoBtn.addEventListener("click", () => deviceAction(device.deviceId, "get-info"));
|
|
549
578
|
|
|
579
|
+
const capBtn = document.createElement("button");
|
|
580
|
+
capBtn.type = "button";
|
|
581
|
+
capBtn.textContent = "Get capabilities";
|
|
582
|
+
capBtn.addEventListener("click", () =>
|
|
583
|
+
deviceAction(device.deviceId, "get-capabilities")
|
|
584
|
+
);
|
|
585
|
+
|
|
550
586
|
const rebootBtn = document.createElement("button");
|
|
551
587
|
rebootBtn.type = "button";
|
|
552
588
|
rebootBtn.textContent = "Reboot";
|
|
@@ -565,6 +601,7 @@ function renderDeviceCards() {
|
|
|
565
601
|
|
|
566
602
|
actions.appendChild(publishBtn);
|
|
567
603
|
actions.appendChild(infoBtn);
|
|
604
|
+
actions.appendChild(capBtn);
|
|
568
605
|
actions.appendChild(rebootBtn);
|
|
569
606
|
actions.appendChild(clearBtn);
|
|
570
607
|
actions.appendChild(unpairBtn);
|
|
@@ -624,12 +661,34 @@ async function confirmPublishModal() {
|
|
|
624
661
|
return;
|
|
625
662
|
}
|
|
626
663
|
|
|
664
|
+
const selectedPlaylists = ids
|
|
665
|
+
.map((id) => playlistsCatalog.find((p) => p.id === id))
|
|
666
|
+
.filter(Boolean);
|
|
667
|
+
|
|
668
|
+
for (const pl of selectedPlaylists) {
|
|
669
|
+
if (!(pl.items || []).length) {
|
|
670
|
+
alert(`Playlist "${pl.name || pl.id}" has no assets. Save the playlist first.`);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const mediaBaseUrl = getPublishMediaBaseUrl(selectedPlaylists);
|
|
676
|
+
if (mediaBaseUrl === "") {
|
|
677
|
+
alert(
|
|
678
|
+
"Local CMS: save a LAN URL under “CMS URL for screens” (e.g. http://192.168.1.105:3000) so TVs can load /uploads paths. On Replit/hosted CMS this field is not required."
|
|
679
|
+
);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const publishBody = { playlistIds: ids };
|
|
684
|
+
if (mediaBaseUrl) publishBody.mediaBaseUrl = mediaBaseUrl;
|
|
685
|
+
|
|
627
686
|
const res = await fetch(
|
|
628
687
|
`/device/${encodeURIComponent(publishModalDeviceId)}/assignments`,
|
|
629
688
|
{
|
|
630
689
|
method: "POST",
|
|
631
690
|
headers: { "Content-Type": "application/json" },
|
|
632
|
-
body: JSON.stringify(
|
|
691
|
+
body: JSON.stringify(publishBody)
|
|
633
692
|
}
|
|
634
693
|
);
|
|
635
694
|
const data = await res.json();
|
|
@@ -743,12 +802,14 @@ function startDevicePolling() {
|
|
|
743
802
|
}
|
|
744
803
|
|
|
745
804
|
document.addEventListener("DOMContentLoaded", () => {
|
|
805
|
+
const cmsUrlSection = document.getElementById("cmsUrlSection");
|
|
806
|
+
if (cmsUrlSection && !isLocalPanelHost(window.location.hostname)) {
|
|
807
|
+
cmsUrlSection.classList.add("hidden");
|
|
808
|
+
}
|
|
809
|
+
|
|
746
810
|
const savedMediaBase = localStorage.getItem(PANEL_MEDIA_BASE_KEY);
|
|
747
811
|
const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
|
|
748
812
|
if (savedMediaBase && cmsBaseInput) cmsBaseInput.value = savedMediaBase;
|
|
749
|
-
else if (cmsBaseInput && !isLocalPanelHost(window.location.hostname)) {
|
|
750
|
-
cmsBaseInput.value = window.location.origin;
|
|
751
|
-
}
|
|
752
813
|
|
|
753
814
|
void fetchPlaylists();
|
|
754
815
|
startDevicePolling();
|