Skip to content

Commit f17ba6e

Browse files
Improve folder preview readability and chevron controls
1 parent 155b5da commit f17ba6e

17 files changed

Lines changed: 314 additions & 67 deletions

archive/folderview.plus-2026.03.22.31.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
c2dd8f91fc94ef3aeed1e196ebfcbc005e2878e831edabc25cddb2410cc5b1b6 folderview.plus-2026.03.23.03.txz

folderview.plus.plg

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
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.23.02">
10-
<!ENTITY md5 "bc9ba1871d7fe92a4863c01329d8b29e">
9+
<!ENTITY version "2026.03.23.03">
10+
<!ENTITY md5 "e0d67eec411f704a07295c17d69727fe">
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.23.03
17+
- Feature: Add docker multi-row preview regression guards.
18+
19+
1620
###2026.03.23.02
1721
- UX: Refined settings and on-screen update messaging for clarity and consistency.
1822

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/Folder.page

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ emitRequestTokenMetaTag();
413413
<dt data-i18n="bars-color">Vertical bars color:</dt>
414414
<dd>
415415
<input type="color" name="preview_vertical_bars_color" style="height:28px;vertical-align:middle;">
416+
<input type="number" name="preview_vertical_bars_width" min="1" max="4" step="1" value="1" style="width:64px;height:28px;margin-left:0.5em;vertical-align:middle;">
416417
<button onclick="event.preventDefault(); $('div.canvas > form')[0].preview_vertical_bars_color.value = rgbToHex($('body').css('color'));" style="width:44px;height:28px;min-width:0;padding:0;margin:0;margin-left:0.5em;vertical-align:middle;">
417418
<i class="fa fa-repeat" aria-hidden="true"></i>
418419
</button>
@@ -422,6 +423,8 @@ emitRequestTokenMetaTag();
422423
<p data-i18n="[html]bars-color-tooltip">
423424
Set the color of the vertical bars between containers.
424425
<br>
426+
Use the number field to increase bar thickness from 1px to 4px.
427+
<br>
425428
The <i class="fa fa-repeat" aria-hidden="true"></i> button will reset to the current font color.
426429
</p>
427430
</blockquote>
@@ -450,6 +453,7 @@ emitRequestTokenMetaTag();
450453
<dt data-i18n="border-color">Border color:</dt>
451454
<dd>
452455
<input type="color" name="preview_border_color" style="height:28px;vertical-align:middle;">
456+
<input type="number" name="preview_border_width" min="1" max="4" step="1" value="1" style="width:64px;height:28px;margin-left:0.5em;vertical-align:middle;">
453457
<button onclick="event.preventDefault(); $('div.canvas > form')[0].preview_border_color.value = '#afa89e';" style="width:44px;height:28px;min-width:0;padding:0;margin:0;margin-left:0.5em;vertical-align:middle;">
454458
<i class="fa fa-repeat" aria-hidden="true"></i>
455459
</button>
@@ -459,13 +463,52 @@ emitRequestTokenMetaTag();
459463
<p data-i18n="[html]border-color-tooltip">
460464
Set the color of the preview border.
461465
<br>
466+
Use the number field to increase border thickness from 1px to 4px.
467+
<br>
462468
The <i class="fa fa-repeat" aria-hidden="true"></i> button will reset to the current font color.
463469
</p>
464470
</blockquote>
465471
</div>
466472
</li>
467473
</ul>
468474
</li>
475+
<li constraint="preview-1 preview-2 preview-3 preview-4">
476+
<div class="basic">
477+
<dl>
478+
<dt>Chevron style:</dt>
479+
<dd>
480+
<img class="img" style="visibility: hidden;">
481+
<select name="dropdown_style" onchange="updateForm()">
482+
<option value="boxed" selected>Boxed</option>
483+
<option value="minimal">Minimal</option>
484+
</select>
485+
</dd>
486+
</dl>
487+
<blockquote class="inline_help">
488+
<p>
489+
Choose whether the folder chevron uses a boxed button or the lighter minimal style.
490+
</p>
491+
</blockquote>
492+
</div>
493+
<ul>
494+
<li>
495+
<div class="basic">
496+
<dl>
497+
<dt>Chevron colors:</dt>
498+
<dd>
499+
<input type="color" name="dropdown_color" style="height:28px;vertical-align:middle;">
500+
<input type="color" name="dropdown_hover_color" style="height:28px;vertical-align:middle;margin-left:0.5em;">
501+
</dd>
502+
</dl>
503+
<blockquote class="inline_help">
504+
<p>
505+
First picker sets the normal chevron and box color. Second picker sets the hover color.
506+
</p>
507+
</blockquote>
508+
</div>
509+
</li>
510+
</ul>
511+
</li>
469512
<li constraint="docker preview-1 preview-2 preview-3 preview-4">
470513
<div class="basic">
471514
<dl>

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

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const applyFolderStatusColorOverrides = typeof dockerRuntimeShared.applyFolderSt
1818
const applyPreviewBorderStyle = typeof dockerRuntimeShared.applyPreviewBorderStyle === 'function'
1919
? dockerRuntimeShared.applyPreviewBorderStyle
2020
: (() => {});
21+
const applyFolderDropdownStyle = typeof dockerRuntimeShared.applyFolderDropdownStyle === 'function'
22+
? dockerRuntimeShared.applyFolderDropdownStyle
23+
: (() => {});
2124
const utils = window.FolderViewPlusUtils || {
2225
normalizePrefs: () => ({
2326
sortMode: 'created',
@@ -1059,6 +1062,7 @@ const estimateDockerRuntimeAutoAppWidth = () => {
10591062
rows,
10601063
baseline,
10611064
nameSelector: '.folder-appname',
1065+
auxSelectors: ['.folder-state'],
10621066
indentSelector: '.folder-name-sub',
10631067
hiddenClass: 'fv-nested-hidden',
10641068
chromeWidth: DOCKER_RUNTIME_APP_CHROME_WIDTH,
@@ -1081,25 +1085,30 @@ const adjustDockerRuntimeAppWidthForRenderedOverflow = (baseWidth = null) => {
10811085
if (!row || row.offsetParent === null || row.classList.contains('fv-nested-hidden')) {
10821086
return;
10831087
}
1084-
const label = row.querySelector('.folder-appname');
1085-
if (!label) {
1086-
return;
1087-
}
1088-
const clientWidth = Math.max(0, Math.ceil(label.clientWidth || 0));
1089-
if (clientWidth <= 0) {
1090-
return;
1091-
}
1092-
const rawOverflow = Math.ceil((label.scrollWidth || 0) - clientWidth);
1093-
if (clientWidth < DOCKER_RUNTIME_APP_OVERFLOW_CLIENT_WIDTH_MIN && rawOverflow <= 0) {
1094-
return;
1095-
}
1096-
if (rawOverflow <= 0) {
1088+
const widthNodes = [
1089+
row.querySelector('.folder-appname'),
1090+
row.querySelector('.folder-state')
1091+
].filter(Boolean);
1092+
if (!widthNodes.length) {
10971093
return;
10981094
}
1099-
const overflow = Math.min(rawOverflow, DOCKER_RUNTIME_APP_OVERFLOW_NUDGE_MAX);
1100-
if (overflow > maxOverflow) {
1101-
maxOverflow = overflow;
1102-
}
1095+
widthNodes.forEach((node) => {
1096+
const clientWidth = Math.max(0, Math.ceil(node.clientWidth || 0));
1097+
if (clientWidth <= 0) {
1098+
return;
1099+
}
1100+
const rawOverflow = Math.ceil((node.scrollWidth || 0) - clientWidth);
1101+
if (clientWidth < DOCKER_RUNTIME_APP_OVERFLOW_CLIENT_WIDTH_MIN && rawOverflow <= 0) {
1102+
return;
1103+
}
1104+
if (rawOverflow <= 0) {
1105+
return;
1106+
}
1107+
const overflow = Math.min(rawOverflow, DOCKER_RUNTIME_APP_OVERFLOW_NUDGE_MAX);
1108+
if (overflow > maxOverflow) {
1109+
maxOverflow = overflow;
1110+
}
1111+
});
11031112
});
11041113
if (maxOverflow <= 0) {
11051114
return startingWidth;
@@ -2962,6 +2971,7 @@ const createFolder = (folder, id, positionInMainOrder, liveOrderArray, container
29622971

29632972
const previewNode = $(`tr.folder-id-${id} div.folder-preview`).get(0);
29642973
applyPreviewBorderStyle(previewNode, folder.settings);
2974+
applyFolderDropdownStyle($(`tr.folder-id-${id}`), folder.settings);
29652975
applyFolderPreviewLayout($(`tr.folder-id-${id} div.folder-preview`), folder.settings);
29662976
$(`tr.folder-id-${id} div.folder-preview`).addClass(`folder-preview-${folder.settings.preview}`);
29672977
if (FOLDER_VIEW_DEBUG_MODE) console.log(`[FV3_DEBUG] createFolder (id: ${id}): Added class folder-preview-${folder.settings.preview} to preview div.`);
@@ -3670,6 +3680,7 @@ const createFolder = (folder, id, positionInMainOrder, liveOrderArray, container
36703680
}
36713681
const $folderRow = $(`tr.folder-id-${id}`);
36723682
applyFolderStatusColorOverrides($folderRow, folder.settings);
3683+
applyFolderDropdownStyle($folderRow, folder.settings);
36733684
const $folderIcon = $folderRow.find(`i#load-folder-${id}`);
36743685
const $folderState = $folderRow.find('span.folder-state');
36753686
$folderState.removeClass('fv-folder-state-started fv-folder-state-paused fv-folder-state-stopped');
@@ -3882,6 +3893,7 @@ const updateFolderRowStatusFromContainers = (id, folder, runtimeContainers) => {
38823893
const total = containerEntries.length;
38833894
const $folderRow = $(`tr.folder-id-${id}`);
38843895
applyFolderStatusColorOverrides($folderRow, folder.settings);
3896+
applyFolderDropdownStyle($folderRow, folder.settings);
38853897
const $folderIcon = $folderRow.find(`i#load-folder-${id}`);
38863898
const $folderState = $folderRow.find('span.folder-state');
38873899
$folderState.removeClass('fv-folder-state-started fv-folder-state-paused fv-folder-state-stopped');

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
stopped: '#ff4d4d'
2121
});
2222
const DEFAULT_PREVIEW_BORDER_COLOR = '#afa89e';
23+
const DEFAULT_PREVIEW_BORDER_WIDTH = 1;
24+
const DEFAULT_PREVIEW_VERTICAL_BARS_WIDTH = 1;
25+
const DEFAULT_DROPDOWN_STYLE = 'boxed';
26+
const DEFAULT_DROPDOWN_COLOR = '#ff9a3c';
27+
const DEFAULT_DROPDOWN_HOVER_COLOR = '#111111';
2328
const FOLDER_STATUS_COLOR_STYLE_PROPS = Object.freeze({
2429
started: '--fvplus-folder-status-started',
2530
paused: '--fvplus-folder-status-paused',
@@ -40,6 +45,29 @@
4045
return trimmed.toLowerCase();
4146
};
4247

48+
const normalizePositiveInt = (value, fallback, min = 1, max = 4) => {
49+
const parsed = Number(value);
50+
if (!Number.isFinite(parsed)) {
51+
return fallback;
52+
}
53+
return Math.max(min, Math.min(max, Math.round(parsed)));
54+
};
55+
56+
const normalizeDropdownStyle = (value) => {
57+
const normalized = String(value || '').trim().toLowerCase();
58+
return normalized === 'minimal' ? 'minimal' : DEFAULT_DROPDOWN_STYLE;
59+
};
60+
61+
const hexToRgba = (hex, alpha) => {
62+
const normalized = normalizeStatusHexColor(hex, DEFAULT_DROPDOWN_COLOR);
63+
const safeAlpha = Number.isFinite(Number(alpha)) ? Math.max(0, Math.min(1, Number(alpha))) : 1;
64+
const value = normalized.slice(1);
65+
const r = parseInt(value.slice(0, 2), 16);
66+
const g = parseInt(value.slice(2, 4), 16);
67+
const b = parseInt(value.slice(4, 6), 16);
68+
return `rgba(${r}, ${g}, ${b}, ${safeAlpha})`;
69+
};
70+
4371
const isPreviewBorderEnabled = (settings) => {
4472
const source = settings && typeof settings === 'object' ? settings : {};
4573
if (Object.prototype.hasOwnProperty.call(source, 'preview_border')) {
@@ -98,7 +126,29 @@
98126
previewNode.classList.toggle('fv-preview-border-off', !enabled);
99127
}
100128
const previewColor = normalizeStatusHexColor(source.preview_border_color, DEFAULT_PREVIEW_BORDER_COLOR);
101-
previewNode.style.setProperty('border', enabled ? `1px solid ${previewColor}` : 'none', 'important');
129+
const previewBorderWidth = normalizePositiveInt(source.preview_border_width, DEFAULT_PREVIEW_BORDER_WIDTH, 1, 4);
130+
const previewBarsWidth = normalizePositiveInt(source.preview_vertical_bars_width, DEFAULT_PREVIEW_VERTICAL_BARS_WIDTH, 1, 4);
131+
previewNode.style.setProperty('--fvplus-preview-border-width', `${previewBorderWidth}px`);
132+
previewNode.style.setProperty('--fvplus-preview-divider-width', `${previewBarsWidth}px`);
133+
previewNode.style.setProperty('border', enabled ? `${previewBorderWidth}px solid ${previewColor}` : 'none', 'important');
134+
};
135+
136+
const applyFolderDropdownStyle = ($folderRow, settings) => {
137+
if (!$folderRow || !$folderRow.length || !$folderRow[0] || !$folderRow[0].style) {
138+
return;
139+
}
140+
const source = settings && typeof settings === 'object' ? settings : {};
141+
const rowStyle = $folderRow[0].style;
142+
const dropdownStyle = normalizeDropdownStyle(source.dropdown_style);
143+
const normalColor = normalizeStatusHexColor(source.dropdown_color, DEFAULT_DROPDOWN_COLOR);
144+
const hoverColor = normalizeStatusHexColor(source.dropdown_hover_color, DEFAULT_DROPDOWN_HOVER_COLOR);
145+
rowStyle.setProperty('--fvplus-folder-dropdown-color', normalColor);
146+
rowStyle.setProperty('--fvplus-folder-dropdown-hover-color', hoverColor);
147+
rowStyle.setProperty('--fvplus-folder-dropdown-border-width', dropdownStyle === 'boxed' ? '1px' : '0px');
148+
rowStyle.setProperty('--fvplus-folder-dropdown-border-color', dropdownStyle === 'boxed' ? hexToRgba(normalColor, 0.52) : 'transparent');
149+
rowStyle.setProperty('--fvplus-folder-dropdown-hover-border-color', dropdownStyle === 'boxed' ? hoverColor : 'transparent');
150+
rowStyle.setProperty('--fvplus-folder-dropdown-bg', dropdownStyle === 'boxed' ? hexToRgba(normalColor, 0.10) : 'transparent');
151+
rowStyle.setProperty('--fvplus-folder-dropdown-hover-bg', dropdownStyle === 'boxed' ? hexToRgba(normalColor, 0.82) : 'transparent');
102152
};
103153

104154
/**
@@ -444,13 +494,21 @@
444494
window.FolderViewDockerRuntimeShared = {
445495
DEFAULT_FOLDER_STATUS_COLORS,
446496
DEFAULT_PREVIEW_BORDER_COLOR,
497+
DEFAULT_PREVIEW_BORDER_WIDTH,
498+
DEFAULT_PREVIEW_VERTICAL_BARS_WIDTH,
499+
DEFAULT_DROPDOWN_STYLE,
500+
DEFAULT_DROPDOWN_COLOR,
501+
DEFAULT_DROPDOWN_HOVER_COLOR,
447502
FOLDER_STATUS_COLOR_STYLE_PROPS,
448503
normalizeStatusHexColor,
504+
normalizePositiveInt,
505+
normalizeDropdownStyle,
449506
isPreviewBorderEnabled,
450507
getFolderStatusColors,
451508
getFolderStatusColorOverrides,
452509
applyFolderStatusColorOverrides,
453510
applyPreviewBorderStyle,
511+
applyFolderDropdownStyle,
454512
createRuntimeStateStore,
455513
createAsyncActionBoundary,
456514
createContextMenuQuickStripAdapter,

0 commit comments

Comments
 (0)