@tomorrowos/sdk 0.8.4 → 0.8.5
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/REPLIT_SETUP.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build-player": "tomorrowos build --platform tizen"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@tomorrowos/sdk": "^0.8.
|
|
16
|
+
"@tomorrowos/sdk": "^0.8.5",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
18
|
"tsx": "^4.19.0"
|
|
19
19
|
},
|
|
@@ -155,6 +155,23 @@
|
|
|
155
155
|
</div>
|
|
156
156
|
</div>
|
|
157
157
|
|
|
158
|
+
<div id="downloadFailedModal" class="modal hidden" role="dialog" aria-modal="true">
|
|
159
|
+
<div class="modal-backdrop" data-close-download-failed-modal="1"></div>
|
|
160
|
+
<div class="modal-card">
|
|
161
|
+
<h2>Download failed</h2>
|
|
162
|
+
<p class="hint" id="downloadFailedModalMessage">Could not download this asset.</p>
|
|
163
|
+
<p class="hint">
|
|
164
|
+
For permanent, reliable storage of media assets, configure
|
|
165
|
+
<strong>Cloudinary</strong> on your CMS server (<code>CLOUDINARY_CLOUD_NAME</code>,
|
|
166
|
+
<code>CLOUDINARY_API_KEY</code>, <code>CLOUDINARY_API_SECRET</code>). Uploads stored on
|
|
167
|
+
Cloudinary use stable public URLs that are easier to download and share.
|
|
168
|
+
</p>
|
|
169
|
+
<div class="modal-actions">
|
|
170
|
+
<button type="button" data-close-download-failed-modal="1">Close</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
158
175
|
<script src="./methods.js" defer></script>
|
|
159
176
|
</body>
|
|
160
177
|
</html>
|
|
@@ -808,6 +808,12 @@ function renderEditorAssets() {
|
|
|
808
808
|
meta.textContent = `${item.type} · ${Math.round(item.durationMs / 1000)}s`;
|
|
809
809
|
});
|
|
810
810
|
|
|
811
|
+
const downloadBtn = document.createElement("button");
|
|
812
|
+
downloadBtn.type = "button";
|
|
813
|
+
downloadBtn.textContent = "Download";
|
|
814
|
+
downloadBtn.title = "Download asset";
|
|
815
|
+
downloadBtn.addEventListener("click", () => void downloadMediaAsset(item));
|
|
816
|
+
|
|
811
817
|
const removeBtn = document.createElement("button");
|
|
812
818
|
removeBtn.type = "button";
|
|
813
819
|
removeBtn.className = "danger";
|
|
@@ -818,6 +824,7 @@ function renderEditorAssets() {
|
|
|
818
824
|
});
|
|
819
825
|
|
|
820
826
|
actions.appendChild(durInput);
|
|
827
|
+
actions.appendChild(downloadBtn);
|
|
821
828
|
actions.appendChild(removeBtn);
|
|
822
829
|
li.appendChild(name);
|
|
823
830
|
li.appendChild(meta);
|
|
@@ -1679,6 +1686,56 @@ function closeScreenshotModal() {
|
|
|
1679
1686
|
modal?.classList.add("hidden");
|
|
1680
1687
|
}
|
|
1681
1688
|
|
|
1689
|
+
function openDownloadFailedModal(detail) {
|
|
1690
|
+
const modal = document.getElementById("downloadFailedModal");
|
|
1691
|
+
const message = document.getElementById("downloadFailedModalMessage");
|
|
1692
|
+
if (!modal) return;
|
|
1693
|
+
if (message) {
|
|
1694
|
+
message.textContent = detail
|
|
1695
|
+
? `Could not download this asset: ${detail}`
|
|
1696
|
+
: "Could not download this asset.";
|
|
1697
|
+
}
|
|
1698
|
+
modal.classList.remove("hidden");
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function closeDownloadFailedModal() {
|
|
1702
|
+
document.getElementById("downloadFailedModal")?.classList.add("hidden");
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function sanitizeDownloadFilename(name) {
|
|
1706
|
+
const base = String(name || "asset").trim() || "asset";
|
|
1707
|
+
return base.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_");
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
async function downloadMediaAsset(item) {
|
|
1711
|
+
let url;
|
|
1712
|
+
try {
|
|
1713
|
+
url = absoluteMediaUrl(item.url);
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
openDownloadFailedModal(err?.message || "Media URL is not available.");
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
const filename = sanitizeDownloadFilename(item.name || item.url.split("/").pop() || "asset");
|
|
1720
|
+
|
|
1721
|
+
try {
|
|
1722
|
+
const res = await fetch(url);
|
|
1723
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1724
|
+
const blob = await res.blob();
|
|
1725
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1726
|
+
const anchor = document.createElement("a");
|
|
1727
|
+
anchor.href = objectUrl;
|
|
1728
|
+
anchor.download = filename;
|
|
1729
|
+
anchor.style.display = "none";
|
|
1730
|
+
document.body.appendChild(anchor);
|
|
1731
|
+
anchor.click();
|
|
1732
|
+
anchor.remove();
|
|
1733
|
+
URL.revokeObjectURL(objectUrl);
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
openDownloadFailedModal(err?.message || "Network or browser error.");
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1682
1739
|
function startDevicePolling() {
|
|
1683
1740
|
if (devicePollTimer) clearInterval(devicePollTimer);
|
|
1684
1741
|
void fetchDevices();
|
|
@@ -1710,6 +1767,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
1710
1767
|
document.querySelectorAll("[data-close-screenshot-modal]").forEach((el) => {
|
|
1711
1768
|
el.addEventListener("click", closeScreenshotModal);
|
|
1712
1769
|
});
|
|
1770
|
+
document.querySelectorAll("[data-close-download-failed-modal]").forEach((el) => {
|
|
1771
|
+
el.addEventListener("click", closeDownloadFailedModal);
|
|
1772
|
+
});
|
|
1713
1773
|
|
|
1714
1774
|
document.getElementById("addAssetBtn")?.addEventListener("click", () => {
|
|
1715
1775
|
if (uploadInProgress) return;
|