61 lines
3.3 KiB
JavaScript
61 lines
3.3 KiB
JavaScript
// Shared by GNOME preferences and the daemon; no platform-specific imports.
|
|
export function normalizeSettings(source, fields, { strict = false } = {}) {
|
|
const config = source && typeof source === 'object' && !Array.isArray(source) ? source : {};
|
|
const result = {};
|
|
for (const field of fields) {
|
|
let value = config[field.key];
|
|
if (value == null) {
|
|
for (const alias of field.aliases || []) if (config[alias] != null) { value = config[alias]; break; }
|
|
}
|
|
if (value == null) value = field.default;
|
|
if (field.type === 'boolean' && typeof value === 'string') {
|
|
const flag = value.trim().toLowerCase();
|
|
if (['false', '0', 'off', 'no'].includes(flag)) value = false;
|
|
else if (['true', '1', 'on', 'yes'].includes(flag)) value = true;
|
|
}
|
|
if (field.type === 'number' && typeof value === 'string' && value.trim()) value = Number(value);
|
|
if (field.type === 'list' && typeof value === 'string') value = value.split(',').map(v => v.trim()).filter(Boolean);
|
|
if (field.key === 'asrLanguage' && typeof value === 'string') value = value.toLowerCase().split(/[-_]/)[0];
|
|
if (['string', 'file', 'password'].includes(field.type) && typeof value === 'string') value = value.trim();
|
|
if (field.key === 'assistantName' && typeof value === 'string') {
|
|
value = value.replace(/[\r\n\t]/g, ' ').replace(/\s+/g, ' ').slice(0, field.maxLength || 32);
|
|
if (!value || /[<>&]/.test(value)) value = field.default;
|
|
}
|
|
if (field.type === 'text' && typeof value === 'string') {
|
|
value = value.replace(/\0/g, '').replace(/\s+$/g, '').slice(0, field.maxLength || 4000);
|
|
}
|
|
const valid = field.type === 'boolean' ? typeof value === 'boolean'
|
|
: field.type === 'number' ? Number.isFinite(value) && value >= field.min && value <= field.max && (field.step < 1 || Number.isInteger(value))
|
|
: field.type === 'choice' ? field.options.some(option => option.value === value)
|
|
: field.type === 'list' ? Array.isArray(value) && value.every(item => typeof item === 'string')
|
|
: field.type === 'text' ? typeof value === 'string' && value.length <= (field.maxLength || 4000)
|
|
: field.type === 'password' ? typeof value === 'string' && value.length <= (field.maxLength || 256)
|
|
: typeof value === 'string' && value.length <= (field.maxLength || 4096);
|
|
if (!valid && strict) throw new Error(`Invalid value for ${field.title}`);
|
|
result[field.key] = valid ? value : field.default;
|
|
}
|
|
if (result.vadMinSpeechMs > result.vadMaxSpeechSeconds * 1000) throw new Error('Minimum speech must be shorter than the maximum recording');
|
|
return result;
|
|
}
|
|
|
|
export function mergeSettings(source, changes, fields) {
|
|
const merged = { ...source, ...changes };
|
|
// Remove obsolete aliases only for settings actually changed by this window.
|
|
for (const field of fields) if (Object.hasOwn(changes, field.key)) {
|
|
for (const alias of field.aliases || []) delete merged[alias];
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
export function settingVisible(field, values) {
|
|
return !field.when || Object.entries(field.when).every(([key, choices]) => choices.includes(values[key]));
|
|
}
|
|
|
|
export function publicSettings(values, fields) {
|
|
const out = { ...(values || {}) };
|
|
for (const field of fields || []) {
|
|
if (field.type === 'password') out[field.key] = out[field.key] ? 'set' : '';
|
|
}
|
|
return out;
|
|
}
|