Skip to content

Commit e05e7e8

Browse files
author
FolderView Plus Test
committed
Extract folder editor type layer batch 3
1 parent 9eaee35 commit e05e7e8

14 files changed

Lines changed: 120 additions & 39 deletions

archive/folderview.plus-2026.04.15.18.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+
6bb4e35d97c1da70bc7a3110aa06caf0acb9bae5af7f5a8d23fcda0afc5a8e19 folderview.plus-2026.04.17.01.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.04.16.07">
10-
<!ENTITY md5 "e72c825a64d544dacf3b1a13054881a7">
9+
<!ENTITY version "2026.04.17.01">
10+
<!ENTITY md5 "610fc9335e10973e0401c1bf032b0b02">
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.04.17.01
17+
- UX: Folder editor flows, previews, and bootstrap behavior.
18+
- Refactor: Shared runtime contracts, request plumbing, and cross-page foundations.
19+
20+
1621
###2026.04.16.07
1722
- Refactor: Moved Docker-only folder editor row registration into the Docker type module so VM no longer inherits Docker field grouping from the shared editor chrome.
1823
- Refactor: Moved Docker preview signal assembly and runtime member normalization into type modules, keeping the shared editor preview/runtime type-agnostic.

folderview.plus.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<Description>
1111
FolderView Plus organizes Docker, VM, and Dashboard views into folders in Unraid, with starter setup tools, rules, bulk assignment, recovery, templates, and diagnostics.
1212
</Description>
13-
<Date>2026-04-16</Date>
13+
<Date>2026-04-17</Date>
1414
<MinVer>7.0.0</MinVer>
1515
<ExtraSearchTerms>folder view docker vm dashboard organization groups sorting import export backup recovery rules templates diagnostics</ExtraSearchTerms>
1616
<Support>https://forums.unraid.net/topic/197631-plugin-folderview-plus/</Support>

pkg_build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ sync_ca_template_metadata() {
270270

271271
acquire_build_lock() {
272272
mkdir -p "$(dirname "$lockfile")"
273-
if command -v flock >/dev/null 2>&1; then
273+
if command -v flock >/dev/null 2>&1 && [ "${FVPLUS_PKG_BUILD_DISABLE_FLOCK:-0}" != "1" ]; then
274274
exec 9>"$lockfile"
275275
if ! flock -n 9; then
276276
echo "ERROR: Another pkg_build.sh instance is already running (lock: $lockfile)." >&2

scripts/repro_build_guard.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ build_once() {
6868
TZ=UTC \
6969
LC_ALL=C \
7070
LANG=C \
71+
FVPLUS_PKG_BUILD_DISABLE_FLOCK=1 \
7172
FVPLUS_VERSION_OVERRIDE="${VERSION_OVERRIDE}" \
7273
bash "${PKG_BUILD_SCRIPT}" --output-dir "${output_dir}" --no-validate
7374
)

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folder.editor.rules.js

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,37 @@
3131
? utils.normalizePrefs(prefs)
3232
: (prefs && typeof prefs === 'object' ? prefs : {})
3333
);
34-
const ruleRegexKinds = type === 'docker'
35-
? Object.freeze(['name_regex', 'image_regex', 'compose_project_regex'])
36-
: Object.freeze(['name_regex']);
34+
const defaultRulesConfig = type === 'docker'
35+
? Object.freeze({
36+
regexKinds: Object.freeze(['name_regex', 'image_regex', 'compose_project_regex']),
37+
subjectLabel: 'container',
38+
nameRegexExample: '^media-',
39+
patternPlaceholders: Object.freeze({
40+
image_regex: 'Regex pattern (example: linuxserver/)',
41+
compose_project_regex: 'Regex pattern (example: ^media$)'
42+
})
43+
})
44+
: Object.freeze({
45+
regexKinds: Object.freeze(['name_regex']),
46+
subjectLabel: 'VM',
47+
nameRegexExample: '^Windows-',
48+
patternPlaceholders: Object.freeze({})
49+
});
50+
const rawRulesConfig = deps.ruleConfig && typeof deps.ruleConfig === 'object' ? deps.ruleConfig : defaultRulesConfig;
51+
const ruleRegexKindsSource = Array.isArray(rawRulesConfig.regexKinds) && rawRulesConfig.regexKinds.length > 0
52+
? rawRulesConfig.regexKinds
53+
: defaultRulesConfig.regexKinds;
54+
const normalizedRuleRegexKinds = ruleRegexKindsSource
55+
.map((kind) => String(kind || '').trim().toLowerCase())
56+
.filter((kind) => Object.prototype.hasOwnProperty.call(RULE_KIND_LABELS, kind));
57+
const ruleRegexKinds = Object.freeze(normalizedRuleRegexKinds.length > 0
58+
? normalizedRuleRegexKinds
59+
: defaultRulesConfig.regexKinds);
60+
const ruleSubjectLabel = String(rawRulesConfig.subjectLabel || defaultRulesConfig.subjectLabel || 'item').trim() || 'item';
61+
const nameRegexExample = String(rawRulesConfig.nameRegexExample || defaultRulesConfig.nameRegexExample || '^item-').trim() || '^item-';
62+
const rulePatternPlaceholders = rawRulesConfig.patternPlaceholders && typeof rawRulesConfig.patternPlaceholders === 'object'
63+
? rawRulesConfig.patternPlaceholders
64+
: defaultRulesConfig.patternPlaceholders;
3765

3866
let folderEditorPrefs = normalizePrefs({});
3967
let folderEditorPrefsLoaded = false;
@@ -68,13 +96,11 @@
6896

6997
const getFolderEditorRulePatternPlaceholder = (kind) => {
7098
const normalized = normalizeFolderEditorRuleKind(kind);
71-
if (normalized === 'image_regex') {
72-
return 'Regex pattern (example: linuxserver/)';
73-
}
74-
if (normalized === 'compose_project_regex') {
75-
return 'Regex pattern (example: ^media$)';
99+
const configuredPlaceholder = String(rulePatternPlaceholders?.[normalized] || '').trim();
100+
if (configuredPlaceholder) {
101+
return configuredPlaceholder;
76102
}
77-
return `Regex pattern (example: ${type === 'docker' ? '^media-' : '^Windows-'})`;
103+
return `Regex pattern (example: ${nameRegexExample})`;
78104
};
79105

80106
const buildFolderEditorRuleDescription = (rule) => {
@@ -98,7 +124,7 @@
98124
if (kind === 'label_starts_with') {
99125
return `${effect} when label ${labelKey || '(missing key)'} starts with "${labelValue || ''}".`;
100126
}
101-
return `${effect} when the ${type === 'docker' ? 'container' : 'VM'} name matches "${pattern || '(missing regex)'}".`;
127+
return `${effect} when the ${ruleSubjectLabel} name matches "${pattern || '(missing regex)'}".`;
102128
};
103129

104130
const getFolderEditorRuleIssues = (rule) => {

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folder.editor.type-docker.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@
3030
const isDockerUpdateAvailableInEditor = typeof deps.isDockerUpdateAvailableInEditor === 'function'
3131
? deps.isDockerUpdateAvailableInEditor
3232
: ((member) => member?.UpdateAvailable === true || member?.update === true);
33+
const DOCKER_RULES_CONFIG = Object.freeze({
34+
regexKinds: Object.freeze(['name_regex', 'image_regex', 'compose_project_regex']),
35+
subjectLabel: 'container',
36+
nameRegexExample: '^media-',
37+
patternPlaceholders: Object.freeze({
38+
image_regex: 'Regex pattern (example: linuxserver/)',
39+
compose_project_regex: 'Regex pattern (example: ^media$)'
40+
})
41+
});
3342

3443
const buildComparableFolder = (folderRecord) => {
3544
const normalized = normalizeFolderRecordForEditor(folderRecord || {});
@@ -169,6 +178,38 @@
169178
};
170179
};
171180

181+
const getRulesConfig = () => DOCKER_RULES_CONFIG;
182+
183+
const buildSmartDefaultSuggestions = ({ selectedMembers = [], form } = {}) => {
184+
const members = Array.isArray(selectedMembers) ? selectedMembers : [];
185+
const suggestions = [];
186+
const composeProjects = Array.from(new Set(
187+
members
188+
.map((member) => String(member?.ComposeProject || '').trim())
189+
.filter((value) => value !== '')
190+
));
191+
if (composeProjects.length === 1) {
192+
suggestions.push({
193+
key: 'compose',
194+
label: 'Compose project',
195+
value: composeProjects[0],
196+
apply: () => {}
197+
});
198+
}
199+
const updateCount = members.filter((member) => isDockerUpdateAvailableInEditor(member)).length;
200+
suggestions.push({
201+
key: 'updates',
202+
label: 'Update-aware preview',
203+
value: `${updateCount}/${members.length} with updates`,
204+
apply: () => {
205+
if (form?.preview_update) {
206+
form.preview_update.checked = updateCount > 0;
207+
}
208+
}
209+
});
210+
return suggestions;
211+
};
212+
172213
const applyPreviewConstraints = () => {};
173214

174215
return Object.freeze({
@@ -179,6 +220,8 @@
179220
collectSectionRows,
180221
applySectionTags,
181222
getPreviewSignals,
223+
getRulesConfig,
224+
buildSmartDefaultSuggestions,
182225
applyPreviewConstraints
183226
});
184227
};

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folder.editor.type-vm.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111

1212
const createApi = (deps = {}) => {
1313
const jq = deps.$ || (typeof globalThis !== 'undefined' ? globalThis.jQuery || globalThis.$ : null);
14+
const VM_RULES_CONFIG = Object.freeze({
15+
regexKinds: Object.freeze(['name_regex']),
16+
subjectLabel: 'VM',
17+
nameRegexExample: '^Windows-',
18+
patternPlaceholders: Object.freeze({})
19+
});
1420

1521
const mapRuntimeMember = (entry = {}) => {
1622
const memberName = String(entry?.name || entry?.Name || '').trim();
@@ -24,13 +30,17 @@
2430
};
2531
};
2632

33+
const getRulesConfig = () => VM_RULES_CONFIG;
34+
2735
return Object.freeze({
2836
shouldSyncAfterSave: () => false,
2937
flushPostSaveSync: async () => {},
3038
mapRuntimeMember,
3139
collectSectionRows: () => EMPTY_SECTION_ROWS,
3240
applySectionTags: () => {},
3341
getPreviewSignals: () => null,
42+
getRulesConfig,
43+
buildSmartDefaultSuggestions: () => [],
3444
applyPreviewConstraints: ({ $, form } = {}) => {
3545
const activeJq = $ || jq;
3646
if (!activeJq || !form) {

0 commit comments

Comments
 (0)