@tomorrowos/sdk 0.4.2 → 0.4.4

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.
@@ -32,6 +32,14 @@ export declare class PlaylistCatalog {
32
32
  removePlaylistFromDevice(deviceId: string, playlistId: string): Promise<BuiltDevicePolicy>;
33
33
  /** Remove every playlist assignment from a device (CMS catalog + empty policy). */
34
34
  clearAllAssignmentsFromDevice(deviceId: string): Promise<BuiltDevicePolicy>;
35
+ /**
36
+ * If the device has this playlist assigned, refresh its snapshot/version to latest
37
+ * and return a rebuilt snapshot policy for immediate push.
38
+ */
39
+ refreshPlaylistSnapshotOnDevice(deviceId: string, playlistId: string, options?: PolicyBuildOptions): Promise<{
40
+ updated: boolean;
41
+ built?: BuiltDevicePolicy;
42
+ }>;
35
43
  buildPolicyForDevice(deviceId: string, options?: PolicyBuildOptions): Promise<BuiltDevicePolicy>;
36
44
  private buildPolicyFromAssignments;
37
45
  canPublishPlaylistToNewDevice(playlistId: string): Promise<boolean>;
@@ -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;KAClC,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,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;IA2CvB,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAO7B,mFAAmF;IAC7E,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAK3E,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAKf,0BAA0B;IAgCxC,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpE"}
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;KAClC,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,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;IA2CvB,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAO7B,mFAAmF;IAC7E,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAKjF;;;OAGG;IACG,+BAA+B,CACnC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;IA6BrD,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAKf,0BAA0B;IAgCxC,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpE"}
@@ -148,6 +148,34 @@ export class PlaylistCatalog {
148
148
  await this.store.setDeviceAssignments(deviceId, []);
149
149
  return this.buildPolicyFromAssignments([], { useLatest: false });
150
150
  }
151
+ /**
152
+ * If the device has this playlist assigned, refresh its snapshot/version to latest
153
+ * and return a rebuilt snapshot policy for immediate push.
154
+ */
155
+ async refreshPlaylistSnapshotOnDevice(deviceId, playlistId, options = {}) {
156
+ const assignments = await this.store.getDeviceAssignments(deviceId);
157
+ const idx = assignments.findIndex((a) => a.playlistId === playlistId);
158
+ if (idx < 0)
159
+ return { updated: false };
160
+ const playlist = await this.store.getPlaylist(playlistId);
161
+ if (!playlist || playlist.retired) {
162
+ return { updated: false };
163
+ }
164
+ const next = assignments.map((a, i) => i !== idx
165
+ ? a
166
+ : {
167
+ playlistId,
168
+ publishedVersion: playlist.version,
169
+ publishedAt: new Date().toISOString(),
170
+ snapshot: cloneSnapshot(playlist, options.mediaBaseUrl)
171
+ });
172
+ await this.store.setDeviceAssignments(deviceId, next);
173
+ const built = await this.buildPolicyFromAssignments(next, {
174
+ useLatest: false,
175
+ mediaBaseUrl: options.mediaBaseUrl
176
+ });
177
+ return { updated: true, built };
178
+ }
151
179
  async buildPolicyForDevice(deviceId, options = {}) {
152
180
  const assignments = await this.store.getDeviceAssignments(deviceId);
153
181
  return this.buildPolicyFromAssignments(assignments, options);
@@ -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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"}
@@ -604,7 +604,16 @@ export class TomorrowOS extends EventEmitter {
604
604
  async tryServeStatic(pathname, res) {
605
605
  if (!this.staticRoot)
606
606
  return false;
607
- const rel = pathname === "/" || pathname === "" ? this.staticIndexFile : pathname.slice(1);
607
+ let relRaw = pathname === "/" || pathname === "" ? this.staticIndexFile : pathname.slice(1);
608
+ let rel = relRaw;
609
+ try {
610
+ // Browser path segments are percent-encoded for spaces/non-ASCII.
611
+ // Decode before mapping to filesystem path.
612
+ rel = decodeURIComponent(relRaw);
613
+ }
614
+ catch {
615
+ return false;
616
+ }
608
617
  if (!rel || rel.includes(".."))
609
618
  return false;
610
619
  const rootResolved = path.resolve(this.staticRoot);
@@ -703,7 +712,45 @@ export class TomorrowOS extends EventEmitter {
703
712
  : undefined,
704
713
  items: Array.isArray(body.items) ? body.items : []
705
714
  });
706
- sendJson(res, 200, { status: "success", playlist: saved });
715
+ const paired = await this.store.listPairedDevices();
716
+ const deployResults = [];
717
+ for (const entry of paired) {
718
+ const deviceId = entry.deviceId;
719
+ try {
720
+ const refreshed = await this.playlists.refreshPlaylistSnapshotOnDevice(deviceId, saved.id);
721
+ if (!refreshed.updated || !refreshed.built) {
722
+ deployResults.push({ deviceId, assigned: false, pushed: false });
723
+ continue;
724
+ }
725
+ const ws = this.devices.get(deviceId);
726
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
727
+ deployResults.push({ deviceId, assigned: true, pushed: false });
728
+ continue;
729
+ }
730
+ await this.sendDeviceCommand(deviceId, "device.content.setPolicy", {
731
+ policy: refreshed.built.policy
732
+ });
733
+ await this.recordPolicyPush(deviceId);
734
+ deployResults.push({ deviceId, assigned: true, pushed: true });
735
+ }
736
+ catch (err) {
737
+ deployResults.push({
738
+ deviceId,
739
+ assigned: true,
740
+ pushed: false,
741
+ error: err instanceof Error ? err.message : "push failed"
742
+ });
743
+ }
744
+ }
745
+ sendJson(res, 200, {
746
+ status: "success",
747
+ playlist: saved,
748
+ deploy: {
749
+ assignedDevices: deployResults.filter((x) => x.assigned).length,
750
+ pushedDevices: deployResults.filter((x) => x.pushed).length,
751
+ results: deployResults
752
+ }
753
+ });
707
754
  }
708
755
  catch (e) {
709
756
  const msg = e instanceof Error ? e.message : "Save failed";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
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.4.2",
3
+ "version": "0.4.4",
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.4.2"
13
+ "@tomorrowos/sdk": "^0.4.4"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^20.0.0",
@@ -116,7 +116,15 @@
116
116
  id="fileInput"
117
117
  class="hidden"
118
118
  accept="image/*,video/*,.wgt,.zip"
119
+ multiple
119
120
  />
121
+ <div id="uploadStatusShell" class="upload-status hidden" aria-live="polite">
122
+ <div id="uploadStatusText" class="upload-status-text">Uploading...</div>
123
+ <div class="upload-progress-track">
124
+ <div id="uploadProgressBar" class="upload-progress-bar"></div>
125
+ </div>
126
+ <div id="uploadQueueText" class="upload-queue-text"></div>
127
+ </div>
120
128
  <ul id="playlistList" class="playlist-list">
121
129
  <li class="playlist-empty" id="playlistEmpty">Select or create a playlist, then add assets.</li>
122
130
  </ul>
@@ -19,6 +19,11 @@ let editorItems = [];
19
19
  let publishModalDeviceId = null;
20
20
 
21
21
  let devicePollTimer = null;
22
+ let uploadQueue = [];
23
+ let uploadInProgress = false;
24
+
25
+ const UPLOAD_MAX_RETRIES = 3;
26
+ const UPLOAD_TIMEOUT_MS = 120000;
22
27
 
23
28
  function escapeHtml(value) {
24
29
  return String(value ?? "")
@@ -71,6 +76,34 @@ function showResult(data) {
71
76
  document.getElementById("result").textContent = JSON.stringify(data, null, 2);
72
77
  }
73
78
 
79
+ function setAssetUploadBusy(isBusy) {
80
+ const addBtn = document.getElementById("addAssetBtn");
81
+ if (addBtn) {
82
+ addBtn.disabled = isBusy;
83
+ addBtn.textContent = isBusy ? "Uploading..." : "+";
84
+ }
85
+ }
86
+
87
+ function updateUploadStatusUi(status) {
88
+ const shell = document.getElementById("uploadStatusShell");
89
+ const text = document.getElementById("uploadStatusText");
90
+ const bar = document.getElementById("uploadProgressBar");
91
+ const queue = document.getElementById("uploadQueueText");
92
+ if (!shell || !text || !bar || !queue) return;
93
+
94
+ if (!status || status.hidden) {
95
+ shell.classList.add("hidden");
96
+ bar.style.width = "0%";
97
+ return;
98
+ }
99
+
100
+ shell.classList.remove("hidden");
101
+ text.textContent = status.text || "Uploading...";
102
+ queue.textContent = status.queueText || "";
103
+ const percent = Math.max(0, Math.min(100, Number(status.percent) || 0));
104
+ bar.style.width = `${percent}%`;
105
+ }
106
+
74
107
  function isLocalPanelHost(hostname) {
75
108
  const h = String(hostname || "").toLowerCase();
76
109
  return h === "localhost" || h === "127.0.0.1" || h === "[::1]";
@@ -846,26 +879,68 @@ async function removePlaylistFromDevice(deviceId, playlistId) {
846
879
  await fetchDevices();
847
880
  }
848
881
 
849
- async function uploadFile(file) {
850
- const q = new URLSearchParams({ filename: file.name });
851
- const res = await fetch(`/media/upload?${q.toString()}`, {
852
- method: "POST",
853
- headers: { "Content-Type": "application/octet-stream" },
854
- body: file
882
+ function uploadFileWithProgress(file, { onProgress, attempt }) {
883
+ return new Promise((resolve, reject) => {
884
+ const xhr = new XMLHttpRequest();
885
+ const q = new URLSearchParams({ filename: file.name });
886
+ xhr.open("POST", `/media/upload?${q.toString()}`, true);
887
+ xhr.timeout = UPLOAD_TIMEOUT_MS;
888
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
889
+
890
+ xhr.upload.onprogress = (event) => {
891
+ if (!event.lengthComputable) return;
892
+ const percent = Math.round((event.loaded / event.total) * 100);
893
+ if (typeof onProgress === "function") onProgress(percent);
894
+ };
895
+
896
+ xhr.onerror = () => reject(new Error(`Network error on attempt ${attempt}`));
897
+ xhr.ontimeout = () => reject(new Error(`Upload timed out after ${UPLOAD_TIMEOUT_MS / 1000}s`));
898
+
899
+ xhr.onload = () => {
900
+ let payload = {};
901
+ try {
902
+ payload = xhr.responseText ? JSON.parse(xhr.responseText) : {};
903
+ } catch {
904
+ payload = {};
905
+ }
906
+ if (xhr.status >= 200 && xhr.status < 300 && payload.status !== "failed") {
907
+ resolve(payload);
908
+ return;
909
+ }
910
+ reject(new Error(payload.error || `Upload failed (${xhr.status})`));
911
+ };
912
+
913
+ xhr.send(file);
855
914
  });
856
- const data = await res.json();
857
- if (!res.ok || data.status === "failed") {
858
- throw new Error(data.error || `Upload failed (${res.status})`);
915
+ }
916
+
917
+ async function uploadFile(file, onProgress) {
918
+ let lastErr = null;
919
+ for (let attempt = 1; attempt <= UPLOAD_MAX_RETRIES; attempt += 1) {
920
+ try {
921
+ const payload = await uploadFileWithProgress(file, { onProgress, attempt });
922
+ return payload;
923
+ } catch (err) {
924
+ lastErr = err;
925
+ if (attempt >= UPLOAD_MAX_RETRIES) break;
926
+ await new Promise((resolve) => setTimeout(resolve, 500 * attempt));
927
+ }
859
928
  }
860
- return data;
929
+ throw lastErr || new Error("Upload failed");
861
930
  }
862
931
 
863
- async function addAssetFromFile(file) {
932
+ async function addAssetFromFile(file, queueIndex, queueTotal) {
864
933
  if (!isPlaylistEditorOpen()) {
865
934
  newPlaylistDraft();
866
935
  }
867
936
  const type = inferMediaType(file.name, file.type);
868
- const data = await uploadFile(file);
937
+ const data = await uploadFile(file, (percent) => {
938
+ updateUploadStatusUi({
939
+ text: `Uploading ${file.name} (${percent}%)`,
940
+ percent,
941
+ queueText: `File ${queueIndex}/${queueTotal}`
942
+ });
943
+ });
869
944
  const durationMs = await resolveVideoDurationMs(file, type, data);
870
945
  editorItems.push({
871
946
  id: crypto.randomUUID(),
@@ -878,11 +953,51 @@ async function addAssetFromFile(file) {
878
953
  showResult({
879
954
  status: "uploaded",
880
955
  ...data,
956
+ fileName: file.name,
881
957
  detectedDurationMs: durationMs,
882
958
  detectedDurationSec: Math.round(durationMs / 1000)
883
959
  });
884
960
  }
885
961
 
962
+ async function processUploadQueue() {
963
+ if (uploadInProgress) return;
964
+ if (uploadQueue.length === 0) {
965
+ updateUploadStatusUi({ hidden: true });
966
+ setAssetUploadBusy(false);
967
+ return;
968
+ }
969
+
970
+ uploadInProgress = true;
971
+ setAssetUploadBusy(true);
972
+ const total = uploadQueue.length;
973
+ const failures = [];
974
+
975
+ try {
976
+ for (let i = 0; i < total; i += 1) {
977
+ const file = uploadQueue[i];
978
+ try {
979
+ await addAssetFromFile(file, i + 1, total);
980
+ } catch (err) {
981
+ failures.push({ file: file.name, error: err?.message || String(err) });
982
+ }
983
+ }
984
+ } finally {
985
+ uploadQueue = [];
986
+ uploadInProgress = false;
987
+ setAssetUploadBusy(false);
988
+ updateUploadStatusUi({ hidden: true });
989
+ }
990
+
991
+ if (failures.length > 0) {
992
+ showResult({ status: "upload_completed_with_failures", failures });
993
+ alert(
994
+ `Uploaded with ${failures.length} failure(s). Check result panel for details.`
995
+ );
996
+ } else {
997
+ showResult({ status: "upload_completed", total });
998
+ }
999
+ }
1000
+
886
1001
  async function verify() {
887
1002
  const code = String(document.getElementById("code").value || "")
888
1003
  .trim()
@@ -982,6 +1097,7 @@ document.addEventListener("DOMContentLoaded", () => {
982
1097
  });
983
1098
 
984
1099
  document.getElementById("addAssetBtn")?.addEventListener("click", () => {
1100
+ if (uploadInProgress) return;
985
1101
  if (!isPlaylistEditorOpen()) {
986
1102
  newPlaylistDraft();
987
1103
  }
@@ -991,11 +1107,8 @@ document.addEventListener("DOMContentLoaded", () => {
991
1107
  document.getElementById("fileInput")?.addEventListener("change", async (ev) => {
992
1108
  const files = ev.target.files;
993
1109
  if (!files?.length) return;
994
- try {
995
- for (const file of files) await addAssetFromFile(file);
996
- } catch (err) {
997
- alert(err.message);
998
- }
1110
+ uploadQueue.push(...Array.from(files));
1111
+ void processUploadQueue();
999
1112
  ev.target.value = "";
1000
1113
  });
1001
1114
  });
@@ -306,6 +306,42 @@ button.danger {
306
306
  overflow-y: auto;
307
307
  }
308
308
 
309
+ .upload-status {
310
+ margin: 0.25rem 0.75rem 0.5rem;
311
+ padding: 0.55rem 0.6rem;
312
+ border: 1px solid #e4e1dc;
313
+ border-radius: 8px;
314
+ background: #fafaf9;
315
+ }
316
+
317
+ .upload-status-text {
318
+ font-size: 0.78rem;
319
+ font-weight: 600;
320
+ color: #333;
321
+ margin-bottom: 0.35rem;
322
+ }
323
+
324
+ .upload-progress-track {
325
+ width: 100%;
326
+ height: 8px;
327
+ border-radius: 999px;
328
+ overflow: hidden;
329
+ background: #ece8e2;
330
+ }
331
+
332
+ .upload-progress-bar {
333
+ width: 0%;
334
+ height: 100%;
335
+ background: #ff8a3d;
336
+ transition: width 120ms linear;
337
+ }
338
+
339
+ .upload-queue-text {
340
+ margin-top: 0.35rem;
341
+ font-size: 0.72rem;
342
+ color: #666;
343
+ }
344
+
309
345
  .playlist-empty {
310
346
  padding: 1.5rem 1rem;
311
347
  text-align: center;