Skip to content

Commit 045bd3b

Browse files
perf: batch storage writes and smooth folder editor rendering
1 parent e873f13 commit 045bd3b

11 files changed

Lines changed: 669 additions & 72 deletions

File tree

14.1 MB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
e34f03d16fefe833a12b5c1401152b7c3f63473d718ede0e021013a07f358321 folderview.plus-2026.03.21.16.txz

folderview.plus.plg

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/dev/folderview.plus.plg">
9-
<!ENTITY version "2026.03.21.15">
10-
<!ENTITY md5 "36068849707f82654284ad3c715c164d">
9+
<!ENTITY version "2026.03.21.16">
10+
<!ENTITY md5 "8b813c84878baf0ad44ec74895d76d0a">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-icon.png" support="https://forums.unraid.net/topic/197631-plugin-folderview-plus/" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.03.21.16
17+
- UX: Refined settings and on-screen update messaging for clarity and consistency.
18+
- Quality: Strengthened release automation and regression guards to prevent note drift.
19+
20+
1621
###2026.03.21.15
1722
- Fix: Improved new-folder editor performance by deferring heavy recompute paths while typing and making icon-manager hydration non-blocking at load.
1823
- Fix: Hardened Docker runtime name-column recovery with post-render width reflow passes and stronger width fallback to prevent severe name truncation after folder rename/create.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/dashboard.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ const utils = window.FolderViewPlusUtils || {
6060
};
6161
}
6262
};
63+
const dashboardStorageWriter = typeof utils.createBatchedStorageWriter === 'function'
64+
? utils.createBatchedStorageWriter(window.localStorage, {
65+
defaultDelayMs: 84,
66+
idleTimeoutMs: 900
67+
})
68+
: null;
6369
const FOLDER_LABEL_KEYS = ['folderview.plus', 'folder.view3', 'folder.view2', 'folder.view'];
6470
const getFolderLabelValue = (labels) => {
6571
const source = labels && typeof labels === 'object' ? labels : {};
@@ -232,7 +238,11 @@ const writeDashboardHealthEmphasisStateForType = (type, enabled) => {
232238
if (!window.localStorage) {
233239
return;
234240
}
235-
window.localStorage.setItem(storageKey, enabled === true ? '1' : '0');
241+
if (dashboardStorageWriter && typeof dashboardStorageWriter.setItem === 'function') {
242+
dashboardStorageWriter.setItem(storageKey, enabled === true ? '1' : '0', { delayMs: 70, idle: true });
243+
} else {
244+
window.localStorage.setItem(storageKey, enabled === true ? '1' : '0');
245+
}
236246
} catch (_error) {
237247
// Ignore localStorage failures so dashboard rendering remains stable.
238248
}
@@ -260,7 +270,11 @@ const writeDashboardCompactDensityStateForType = (type, enabled) => {
260270
if (!window.localStorage) {
261271
return;
262272
}
263-
window.localStorage.setItem(storageKey, enabled === true ? '1' : '0');
273+
if (dashboardStorageWriter && typeof dashboardStorageWriter.setItem === 'function') {
274+
dashboardStorageWriter.setItem(storageKey, enabled === true ? '1' : '0', { delayMs: 70, idle: true });
275+
} else {
276+
window.localStorage.setItem(storageKey, enabled === true ? '1' : '0');
277+
}
264278
} catch (_error) {
265279
// Ignore localStorage failures so dashboard rendering remains stable.
266280
}
@@ -838,7 +852,12 @@ const writeDashboardExpandedStateMap = (type, map) => {
838852
}
839853
try {
840854
if (window.localStorage) {
841-
window.localStorage.setItem(storageKey, JSON.stringify(normalizeExpandedStateMap(map)));
855+
const payload = JSON.stringify(normalizeExpandedStateMap(map));
856+
if (dashboardStorageWriter && typeof dashboardStorageWriter.setItem === 'function') {
857+
dashboardStorageWriter.setItem(storageKey, payload, { delayMs: 80, idle: true });
858+
} else {
859+
window.localStorage.setItem(storageKey, payload);
860+
}
842861
}
843862
} catch (_error) {
844863
// Ignore localStorage failures so dashboard rendering remains stable.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/docker.js

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ const utils = window.FolderViewPlusUtils || {
110110
}
111111
};
112112
const dockerRuntimeShared = window.FolderViewDockerRuntimeShared || {};
113+
const dockerStorageWriter = typeof utils.createBatchedStorageWriter === 'function'
114+
? utils.createBatchedStorageWriter(window.localStorage, {
115+
defaultDelayMs: 72,
116+
idleTimeoutMs: 900
117+
})
118+
: null;
113119
const createDockerRuntimeStateStore = typeof dockerRuntimeShared.createRuntimeStateStore === 'function'
114120
? dockerRuntimeShared.createRuntimeStateStore
115121
: (initialState = {}) => {
@@ -351,7 +357,15 @@ const isDockerRuntimeWidthDebugEnabled = () => {
351357
const setDockerRuntimeWidthDebugEnabled = (enabled) => {
352358
const next = enabled === true;
353359
try {
354-
localStorage.setItem(DOCKER_RUNTIME_WIDTH_DEBUG_STORAGE_KEY, next ? '1' : '0');
360+
if (dockerStorageWriter && typeof dockerStorageWriter.setItem === 'function') {
361+
dockerStorageWriter.setItem(
362+
DOCKER_RUNTIME_WIDTH_DEBUG_STORAGE_KEY,
363+
next ? '1' : '0',
364+
{ delayMs: 0, idle: false }
365+
);
366+
} else {
367+
localStorage.setItem(DOCKER_RUNTIME_WIDTH_DEBUG_STORAGE_KEY, next ? '1' : '0');
368+
}
355369
} catch (_error) {
356370
// Ignore localStorage limitations.
357371
}
@@ -533,15 +547,40 @@ const persistDockerRuntimeColumnWidths = (widthMap) => {
533547
const normalized = normalizeDockerRuntimeColumnWidthMap(widthMap);
534548
try {
535549
if (Object.keys(normalized).length > 0) {
536-
localStorage.setItem(DOCKER_RUNTIME_COLUMN_WIDTHS_STORAGE_KEY, JSON.stringify(normalized));
550+
if (dockerStorageWriter && typeof dockerStorageWriter.setItem === 'function') {
551+
dockerStorageWriter.setItem(
552+
DOCKER_RUNTIME_COLUMN_WIDTHS_STORAGE_KEY,
553+
JSON.stringify(normalized),
554+
{ delayMs: 96, idle: true }
555+
);
556+
} else {
557+
localStorage.setItem(DOCKER_RUNTIME_COLUMN_WIDTHS_STORAGE_KEY, JSON.stringify(normalized));
558+
}
537559
if (normalized[1]) {
538-
localStorage.setItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY, String(normalized[1]));
560+
if (dockerStorageWriter && typeof dockerStorageWriter.setItem === 'function') {
561+
dockerStorageWriter.setItem(
562+
DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY,
563+
String(normalized[1]),
564+
{ delayMs: 96, idle: true }
565+
);
566+
} else {
567+
localStorage.setItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY, String(normalized[1]));
568+
}
539569
} else {
540-
localStorage.removeItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY);
570+
if (dockerStorageWriter && typeof dockerStorageWriter.removeItem === 'function') {
571+
dockerStorageWriter.removeItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY, { delayMs: 40, idle: true });
572+
} else {
573+
localStorage.removeItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY);
574+
}
541575
}
542576
} else {
543-
localStorage.removeItem(DOCKER_RUNTIME_COLUMN_WIDTHS_STORAGE_KEY);
544-
localStorage.removeItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY);
577+
if (dockerStorageWriter && typeof dockerStorageWriter.removeItem === 'function') {
578+
dockerStorageWriter.removeItem(DOCKER_RUNTIME_COLUMN_WIDTHS_STORAGE_KEY, { delayMs: 40, idle: true });
579+
dockerStorageWriter.removeItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY, { delayMs: 40, idle: true });
580+
} else {
581+
localStorage.removeItem(DOCKER_RUNTIME_COLUMN_WIDTHS_STORAGE_KEY);
582+
localStorage.removeItem(DOCKER_RUNTIME_LEGACY_APP_WIDTH_STORAGE_KEY);
583+
}
545584
}
546585
} catch (_error) {
547586
// Ignore localStorage quota/privacy failures.
@@ -1220,7 +1259,12 @@ const readDockerLockedFolderIds = () => {
12201259
const writeDockerLockedFolderIds = (ids) => {
12211260
try {
12221261
if (window.localStorage) {
1223-
window.localStorage.setItem(DOCKER_LOCKED_STATE_KEY, JSON.stringify(normalizeLockedFolderIdList(ids)));
1262+
const payload = JSON.stringify(normalizeLockedFolderIdList(ids));
1263+
if (dockerStorageWriter && typeof dockerStorageWriter.setItem === 'function') {
1264+
dockerStorageWriter.setItem(DOCKER_LOCKED_STATE_KEY, payload, { delayMs: 70, idle: true });
1265+
} else {
1266+
window.localStorage.setItem(DOCKER_LOCKED_STATE_KEY, payload);
1267+
}
12241268
}
12251269
} catch (_error) {
12261270
// Best effort only.
@@ -1808,7 +1852,12 @@ const writeDockerExpandedStateMap = (map) => {
18081852
try {
18091853
const payload = map && typeof map === 'object' ? map : {};
18101854
if (window.localStorage) {
1811-
window.localStorage.setItem(DOCKER_EXPANDED_STATE_KEY, JSON.stringify(payload));
1855+
const serialized = JSON.stringify(payload);
1856+
if (dockerStorageWriter && typeof dockerStorageWriter.setItem === 'function') {
1857+
dockerStorageWriter.setItem(DOCKER_EXPANDED_STATE_KEY, serialized, { delayMs: 80, idle: true });
1858+
} else {
1859+
window.localStorage.setItem(DOCKER_EXPANDED_STATE_KEY, serialized);
1860+
}
18121861
}
18131862
} catch (_error) {
18141863
// Ignore storage failures so runtime rendering never breaks.

0 commit comments

Comments
 (0)