Files

285 lines
9.8 KiB
JavaScript

(() => {
const { TIMINGS } = window.InstallerStepsContext || {};
const { formState } = window.InstallerStepsState || {};
const clearFieldErrors = (root) => {
if (!root) return;
root.querySelectorAll('.field-error').forEach((node) => {
node.classList.remove('is-visible');
});
root.querySelectorAll('.input-field.is-error, .input-action.is-error').forEach((node) => {
node.classList.remove('is-error');
});
root.querySelectorAll('.field-helper').forEach((helper) => {
helper.style.display = '';
});
};
const setFieldError = (input, message) => {
if (!input) return;
const group = input.closest('.input-group');
if (!group) return;
let error = group.querySelector('.field-error');
let errorText = error?.querySelector('.field-error-text');
const hasSameMessage = Boolean(errorText && errorText.textContent === message);
const alreadyVisible = Boolean(error && error.classList.contains('is-visible'));
if (hasSameMessage && alreadyVisible) {
return;
}
if (!error) {
const template = document.getElementById('field-error-template');
if (template && template.content) {
const fragment = template.content.cloneNode(true);
error = fragment.querySelector('.field-error');
group.appendChild(fragment);
}
errorText = error?.querySelector('.field-error-text');
}
if (errorText) {
errorText.textContent = message;
}
if (!alreadyVisible) {
requestAnimationFrame(() => {
error.classList.add('is-visible');
});
}
input.classList.add('is-error');
const actionWrapper = input.closest('.input-action');
if (actionWrapper) {
actionWrapper.classList.add('is-error');
}
const helper = group.querySelector('.field-helper');
if (helper) {
helper.style.display = 'none';
}
};
const bindErrorClear = (input) => {
if (!input) return;
const handler = () => {
const group = input.closest('.input-group');
const error = group?.querySelector('.field-error');
if (error) {
error.classList.remove('is-visible');
}
input.classList.remove('is-error');
const actionWrapper = input.closest('.input-action');
if (actionWrapper) {
actionWrapper.classList.remove('is-error');
}
const helper = group?.querySelector('.field-helper');
if (helper) {
helper.style.display = '';
}
};
input.addEventListener('input', handler);
input.addEventListener('change', handler);
};
const toDatabaseLabel = (value) => {
if (!value) return '';
const lower = value.toLowerCase();
if (lower === 'mariadb') return 'MariaDB';
if (lower === 'postgresql') return 'PostgreSQL';
return 'MongoDB';
};
const updateDatabaseSelection = (radio, root) => {
if (!radio || !root) return;
const allOptions = root.querySelectorAll('.selector-card');
allOptions.forEach((option) => option.classList.remove('selected'));
const selectedOption = radio.closest('.selector-card');
if (selectedOption) {
selectedOption.classList.add('selected');
}
};
const syncResetButton = (input, button) => {
const defaultValue = input.dataset.default ?? '';
button.disabled = input.value === defaultValue;
};
const setupResetButtons = (root) => {
const inputs = root.querySelectorAll('.input-field[data-default]');
inputs.forEach((input) => {
const button = root.querySelector(`[data-reset-target="${input.id}"]`);
if (!button) return;
syncResetButton(input, button);
input.addEventListener('input', () => syncResetButton(input, button));
button.addEventListener('click', () => {
input.value = input.dataset.default ?? '';
syncResetButton(input, button);
input.dispatchEvent(new Event('input', { bubbles: true }));
});
});
};
const toggleAccordion = (button) => {
const content = button.nextElementSibling;
const icon = button.querySelector('.accordion-chevron');
const isOpen = button.classList.contains('is-open');
button.classList.toggle('is-open', !isOpen);
button.setAttribute('aria-expanded', String(!isOpen));
if (content) {
if (!isOpen) {
content.classList.add('open');
content.style.maxHeight = `${content.scrollHeight}px`;
} else {
content.style.maxHeight = '0px';
content.classList.remove('open');
}
}
if (icon) {
icon.setAttribute('data-open', String(!isOpen));
}
};
const setupAccordion = (root) => {
const buttons = root.querySelectorAll('.accordion-toggle');
buttons.forEach((button) => {
button.addEventListener('click', () => toggleAccordion(button));
});
};
const openAccordion = (root) => {
const toggle = root.querySelector('.accordion-toggle');
const content = root.querySelector('.accordion-content');
if (!toggle || !content) return;
if (!toggle.classList.contains('is-open')) {
toggle.classList.add('is-open');
toggle.setAttribute('aria-expanded', 'true');
content.classList.add('open');
content.style.maxHeight = `${content.scrollHeight}px`;
}
};
const disableControls = (root) => {
const inputs = root.querySelectorAll('input, select, textarea');
inputs.forEach((input) => {
if (input.type === 'radio' || input.type === 'checkbox') {
input.disabled = true;
} else {
input.readOnly = true;
input.setAttribute('aria-disabled', 'true');
}
});
const buttons = root.querySelectorAll('button');
buttons.forEach((button) => {
if (button.matches('[data-copy-target]')) return;
button.disabled = true;
button.setAttribute('aria-disabled', 'true');
});
root.classList.add('is-locked');
};
const generateSecretKey = () => {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
};
const copyToClipboard = (value, input) => {
if (!value) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(value);
return;
}
if (input) {
input.select();
document.execCommand('copy');
input.setSelectionRange(0, 0);
return;
}
const textArea = document.createElement('textarea');
textArea.value = value;
textArea.style.position = 'fixed';
textArea.style.top = '-9999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
} catch (error) {} finally {
document.body.removeChild(textArea);
}
};
const setTooltipText = (wrapper, message) => {
if (!wrapper) return;
const tooltip = wrapper.querySelector('.tooltip');
if (tooltip && message) {
tooltip.textContent = message;
}
};
const resetTooltipText = (wrapper) => {
if (!wrapper) return;
const defaultText = wrapper.dataset.tooltipDefault;
if (!defaultText) return;
setTooltipText(wrapper, defaultText);
};
const updateReviewSummary = (root) => {
if (!root) return;
const valueNodes = root.querySelectorAll('[data-review-value]');
valueNodes.forEach((node) => {
const key = node.dataset.reviewValue;
if (!key) return;
let value = formState?.[key];
if (key === 'database') {
value = toDatabaseLabel(formState?.database);
}
if (key === 'emailCertificates' && !value) {
value = formState?.accountEmail;
}
if (value) {
node.textContent = value;
}
});
const badge = root.querySelector('[data-review-badge]');
if (badge) {
const hasKey = Boolean((formState?.opensslKey || '').trim());
badge.textContent = hasKey ? 'Generated' : 'Missing';
badge.classList.remove('badge-success', 'badge-warning');
badge.classList.add(hasKey ? 'badge-success' : 'badge-warning');
}
const assistantBadge = root.querySelector('[data-review-assistant-badge]');
if (assistantBadge) {
const hasAssistantKey = Boolean((formState?.assistantOpenAIKey || '').trim());
assistantBadge.textContent = hasAssistantKey ? 'Enabled' : 'Disabled';
assistantBadge.classList.remove('badge-success', 'badge-neutral');
assistantBadge.classList.add(hasAssistantKey ? 'badge-success' : 'badge-neutral');
}
};
window.InstallerStepsUI = {
clearFieldErrors,
setFieldError,
bindErrorClear,
toDatabaseLabel,
updateDatabaseSelection,
setupResetButtons,
setupAccordion,
openAccordion,
disableControls,
generateSecretKey,
copyToClipboard,
setTooltipText,
resetTooltipText,
updateReviewSummary
};
})();