ZDDC/archive/js/presets.js
ZDDC ea385b5366 Initial commit
ZDDC — Zero Day Document Control. A file-naming convention plus five
single-file HTML tools (archive, transmittal, classifier, mdedit,
landing) and an optional Go HTTP server (zddc-server) with ACL and a
virtual archive index. Self-contained, offline-capable, dependency-free.

See README.md for an overview, AGENTS.md and ARCHITECTURE.md for the
build/release/architecture detail, bootstrap/README.md for the
two-level deployment install pattern, and zddc/README.md for the
HTTP server.
2026-04-27 11:05:47 -05:00

437 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function() {
'use strict';
// Party presets for archive browser — IIFE module
// State (module scope, NOT on window.app)
let presets = [];
let activePresetName = null;
let isOpen = false;
let isNamingMode = false;
// Get localStorage key based on source mode and directory
function getStorageKey() {
if (window.app.sourceMode === 'http' && window.app.directories.length > 0) {
var u = window.app.directories[0].url || '';
return 'zddc-presets:http:' + u;
} else if (window.app.sourceMode === 'local' && window.app.directories.length > 0) {
return 'zddc-presets:local:' + window.app.directories[0].name;
}
return 'zddc-presets:default';
}
// Load presets from localStorage
function loadFromStorage() {
try {
var stored = localStorage.getItem(getStorageKey());
if (stored) {
var parsed = JSON.parse(stored);
if (parsed && Array.isArray(parsed.presets)) {
presets = parsed.presets;
} else {
presets = [];
}
} else {
presets = [];
}
} catch (e) {
presets = [];
}
}
// Save presets to localStorage
function saveToStorage() {
try {
localStorage.setItem(getStorageKey(), JSON.stringify({ presets: presets }));
} catch (e) {
// Silently fail on storage errors
}
}
// Load a preset by name
function loadPreset(name) {
var preset = presets.find(p => p.name === name);
if (!preset) return;
// Filter paths to only include folders that exist in groupingFolders
var validPaths = preset.paths.filter(p =>
window.app.groupingFolders.some(f => f.path === p)
);
window.app.selectedGroupingFolders = new Set(validPaths);
window.app.selectAllGroupingFolders = false;
var checkbox = document.getElementById('selectAllGroupingCheckbox');
if (checkbox) checkbox.checked = false;
activePresetName = name;
// Trigger UI updates
window.app.modules.app.updateFolderSelectionState('groupingFoldersList');
window.app.modules.app.renderTransmittalFolders();
window.app.modules.filtering.applyFilters();
renderButton();
renderDropdown();
}
// Save current selection as a preset
function savePreset(name) {
// Build paths array from current selection
var paths = Array.from(window.app.selectedGroupingFolders);
// Upsert preset
var existingIndex = presets.findIndex(p => p.name === name);
if (existingIndex >= 0) {
presets[existingIndex] = { name: name, paths: paths };
} else {
presets.push({ name: name, paths: paths });
}
saveToStorage();
activePresetName = name;
isNamingMode = false;
renderButton();
renderDropdown();
}
// Delete a preset by name
function deletePreset(name) {
presets = presets.filter(p => p.name !== name);
if (activePresetName === name) {
activePresetName = null;
}
saveToStorage();
renderButton();
renderDropdown();
}
// Check if current selection differs from active preset
function checkDirty() {
if (activePresetName === null) return;
var preset = presets.find(p => p.name === activePresetName);
if (!preset) return;
var currentPaths = new Set(window.app.selectedGroupingFolders);
var presetPaths = new Set(preset.paths || []);
// Compare sets
var dirty = currentPaths.size !== presetPaths.size ||
!Array.from(currentPaths).every(p => presetPaths.has(p));
if (dirty) {
renderButton();
}
}
// Get minimum depth of grouping folders (for top-level Only)
function getMinDepth() {
if (window.app.groupingFolders.length === 0) return 1;
return Math.min.apply(null, window.app.groupingFolders.map(f => f.path.split('/').length));
}
// Render the preset button label
function renderButton() {
var btn = document.getElementById('presetBtn');
if (!btn) return;
if (activePresetName !== null) {
// Check if dirty
var preset = presets.find(p => p.name === activePresetName);
var dirty = false;
if (preset) {
var currentPaths = new Set(window.app.selectedGroupingFolders);
var presetPaths = new Set(preset.paths || []);
dirty = currentPaths.size !== presetPaths.size ||
!Array.from(currentPaths).every(p => presetPaths.has(p));
}
btn.textContent = '▾ ' + activePresetName + (dirty ? '*' : '');
} else {
btn.textContent = '▾ Presets';
}
}
// Escape HTML for safe insertion
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Render the dropdown panel
function renderDropdown() {
var dropdown = document.getElementById('presetDropdown');
if (!dropdown) return;
var minDepth = getMinDepth();
// Build presets list HTML
var presetsHtml = '';
if (presets.length === 0) {
presetsHtml = '<div class="preset-no-presets"><i>No saved presets</i></div>';
} else {
presetsHtml = presets.map(preset => {
var escapedName = escapeHtml(preset.name);
return (
'<div class="preset-item" data-name="' + escapedName + '">' +
'<span>' + escapedName + '</span>' +
'<button class="preset-delete" data-name="' + escapedName + '">×</button>' +
'</div>'
);
}).join('');
}
// Build project checkboxes HTML
var projectsHtml = '';
window.app.groupingFolders.forEach(folder => {
// Only include top-level folders (minDepth)
var pathParts = folder.path.split('/');
if (pathParts.length !== minDepth) return;
var isSelected = window.app.selectedGroupingFolders.has(folder.path);
var escapedPath = escapeHtml(folder.path);
var escapedName = escapeHtml(folder.name);
projectsHtml += (
'<div class="preset-project-item">' +
'<label class="preset-project-label">' +
'<input type="checkbox" class="preset-checkbox" data-path="' + escapedPath + '"' +
(isSelected ? ' checked' : '') + '>' +
' ' + escapedName +
'</label>' +
'</div>'
);
});
// Footer HTML
var footerHtml = '';
if (activePresetName !== null) {
// Check if dirty
var preset = presets.find(p => p.name === activePresetName);
var dirty = false;
if (preset) {
var currentPaths = new Set(window.app.selectedGroupingFolders);
var presetPaths = new Set(preset.paths || []);
dirty = currentPaths.size !== presetPaths.size ||
!Array.from(currentPaths).every(p => presetPaths.has(p));
}
if (isNamingMode) {
footerHtml = (
'<div class="preset-footer-naming">' +
'<input type="text" class="preset-name-input" placeholder="Preset name" autoFocus>' +
'<button class="preset-confirm-name btn btn-sm">✓</button>' +
'<button class="preset-cancel-name btn btn-sm">✗</button>' +
'</div>'
);
} else if (dirty) {
footerHtml = (
'<div class="preset-footer-actions">' +
'<button class="preset-update-btn btn btn-primary btn-sm">Update "' + escapeHtml(activePresetName) + '"</button>' +
'<button class="preset-save-new-btn btn btn-secondary btn-sm">Save as New</button>' +
'</div>'
);
} else {
footerHtml = (
'<div class="preset-footer-actions">' +
'<button class="preset-save-new-btn btn btn-primary btn-sm">Save as New</button>' +
'</div>'
);
}
} else {
// No active preset — disabled if nothing selected
var selectedCount = window.app.selectedGroupingFolders.size;
var disabledAttr = selectedCount === 0 ? ' disabled' : '';
footerHtml = (
'<div class="preset-footer-actions">' +
'<button class="preset-save-btn btn btn-primary btn-sm' + (selectedCount === 0 ? ' btn-disabled' : '') + '" ' +
'data-disabled="' + (selectedCount === 0 ? 'true' : 'false') + '">Save as Preset</button>' +
'</div>'
);
}
dropdown.innerHTML = (
'<div class="preset-section-top">' +
'<div class="preset-section-label">Saved Presets:</div>' +
'<div class="preset-list">' + presetsHtml + '</div>' +
'</div>' +
'<div class="preset-divider"></div>' +
'<div class="preset-section-bottom">' +
'<div class="preset-section-label">Projects:</div>' +
'<div class="preset-projects-list">' + projectsHtml + '</div>' +
'</div>' +
footerHtml
);
}
// Toggle dropdown visibility
function toggleDropdown() {
var dropdown = document.getElementById('presetDropdown');
if (isOpen) {
closeDropdown();
} else {
isOpen = true;
if (dropdown) dropdown.classList.remove('hidden');
renderDropdown();
}
}
// Close dropdown
function closeDropdown() {
isOpen = false;
var dropdown = document.getElementById('presetDropdown');
if (dropdown) dropdown.classList.add('hidden');
isNamingMode = false;
}
// Set up event delegation on dropdown
function setupDropdownDelegation() {
var dropdown = document.getElementById('presetDropdown');
if (!dropdown) return;
dropdown.addEventListener('click', function(e) {
// Close on clicks inside dropdown
e.stopPropagation();
// Preset item click — load preset (do NOT close dropdown)
var presetItem = e.target.closest('.preset-item');
if (presetItem && !e.target.classList.contains('preset-delete')) {
var name = presetItem.getAttribute('data-name');
if (name) loadPreset(name);
return;
}
// Delete button
var deleteBtn = e.target.closest('.preset-delete');
if (deleteBtn) {
e.stopPropagation();
var name = deleteBtn.getAttribute('data-name');
if (name) deletePreset(name);
return;
}
// Checkbox click
var checkbox = e.target.closest('.preset-checkbox');
if (checkbox) {
var path = checkbox.getAttribute('data-path');
if (path) {
if (checkbox.checked) {
window.app.selectedGroupingFolders.add(path);
} else {
window.app.selectedGroupingFolders.delete(path);
}
window.app.modules.app.updateFolderSelectionState('groupingFoldersList');
window.app.modules.app.renderTransmittalFolders();
window.app.modules.filtering.applyFilters();
checkDirty();
renderButton();
renderDropdown(); // Re-render to update checkbox states and footer
}
return;
}
// Save button (not in naming mode)
var saveBtn = e.target.closest('.preset-save-btn');
if (saveBtn && !isNamingMode) {
if (saveBtn.getAttribute('data-disabled') !== 'true') {
isNamingMode = true;
renderDropdown();
}
return;
}
// Update button — save current selection as active preset
var updateBtn = e.target.closest('.preset-update-btn');
if (updateBtn) {
if (activePresetName) savePreset(activePresetName);
return;
}
// Save as New button
var saveNewBtn = e.target.closest('.preset-save-new-btn');
if (saveNewBtn) {
isNamingMode = true;
renderDropdown();
return;
}
// Confirm name input
var confirmBtn = e.target.closest('.preset-confirm-name');
if (confirmBtn) {
var input = dropdown.querySelector('.preset-name-input');
if (input && input.value.trim()) {
savePreset(input.value.trim());
}
return;
}
// Cancel name input
var cancelBtn = e.target.closest('.preset-cancel-name');
if (cancelBtn) {
isNamingMode = false;
renderDropdown();
return;
}
});
// Keydown on name input
dropdown.addEventListener('keydown', function(e) {
var input = e.target.closest('.preset-name-input');
if (!input) return;
if (e.key === 'Enter') {
e.stopPropagation();
if (input.value.trim()) {
savePreset(input.value.trim());
}
} else if (e.key === 'Escape') {
e.stopPropagation();
isNamingMode = false;
renderDropdown();
}
});
}
// Handle outside click to close dropdown
function setupOutsideClickHandler() {
document.addEventListener('click', function(e) {
var section = document.getElementById('presetSection');
var dropdown = document.getElementById('presetDropdown');
if (isOpen && section && dropdown && !section.contains(e.target)) {
closeDropdown();
}
});
}
// Initialize presets module — called after first scan completes
function init() {
// Idempotent: skip if button listener already attached
var btn = document.getElementById('presetBtn');
if (!btn || btn.dataset.presetInit) return;
btn.dataset.presetInit = '1';
btn.addEventListener('click', function(e) {
e.stopPropagation();
toggleDropdown();
});
setupDropdownDelegation();
setupOutsideClickHandler();
loadFromStorage();
renderButton();
}
// Register module
window.app.modules.presets = {
init: init,
loadPreset: loadPreset,
savePreset: savePreset,
deletePreset: deletePreset,
checkDirty: checkDirty,
renderButton: renderButton,
toggleDropdown: toggleDropdown,
closeDropdown: closeDropdown
};
})();