mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
285 lines
9.8 KiB
JavaScript
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
|
|
};
|
|
})();
|