JSTAR-Tab/js/settings.js
2025-04-18 22:19:06 +05:00

1164 lines
47 KiB
JavaScript

const defaultSettings = {
theme: 'light',
userName: '',
anonymousMode: false,
searchEngine: 'Google',
updateAlerts: true,
motionPreference: 'default',
show_greeting: true,
show_search: true,
show_shortcuts: true,
show_addShortcut: true,
fontFamily: 'Inter',
fontSize: '16',
lightModeColors: {
'--primary': '#f5f5f5',
'--primary-hover': '#e0e0e0',
'--background': '#ffffff',
'--surface': '#fafafa',
'--border': '#eaeaea',
'--text': '#1a1a1a',
'--text-secondary': '#666666',
'--shadow': 'hsla(0, 0.00%, 0.00%, 0.08)',
'--modal-backdrop': 'rgba(0, 0, 0, 0.5)',
'--scrollbar-thumb': '#e0e0e0',
'--scrollbar-track': '#f5f5f5',
'--modal-background': '#ffffff',
'--toggle-bg': '#e0e0e0',
'--toggle-bg-active': 'var(--text)',
'--toggle-knob': 'var(--background)'
},
darkModeColors: {
'--primary': '#1a1a1a',
'--primary-hover': '#2a2a2a',
'--background': '#000000',
'--surface': '#111111',
'--border': '#333333',
'--text': '#ffffff',
'--text-secondary': '#999999',
'--shadow': 'rgba(0, 0, 0, 0.3)',
'--modal-backdrop': 'rgba(0, 0, 0, 0.75)',
'--scrollbar-thumb': '#333333',
'--scrollbar-track': '#1a1a1a',
'--modal-background': '#1a1a1a',
'--toggle-bg': '#333333',
'--toggle-bg-active': 'var(--text)',
'--toggle-knob': 'var(--background)'
},
keybinds: {
settings: { keys: 'Shift+S' },
anonymous: { keys: 'Shift+X' },
theme: { keys: 'Shift+T' },
history: { keys: 'Shift+H' },
url: { keys: 'Shift+Q', url: '' }
}
};
const anonymousNames = {
adjectives: ['Hidden', 'Secret', 'Mystery', 'Shadow', 'Unknown', 'Silent', 'Stealth', 'Phantom', 'Ghost', 'Anon'],
nouns: [' User', ' Visitor', ' Guest', ' Agent', ' Entity', ' Person', ' Browser', ' Explorer', ' Wanderer', ' Navigator'],
generate: function() {
const adjective = this.adjectives[Math.floor(Math.random() * this.adjectives.length)];
const noun = this.nouns[Math.floor(Math.random() * this.nouns.length)];
return `${adjective}${noun}`;
}
};
const getTimeBasedGreeting = () => {
const hour = new Date().getHours();
if (hour < 12) return 'Good Morning';
if (hour < 18) return 'Good Afternoon';
if (hour < 22) return 'Good Evening';
return 'Good Night';
};
const updateAlerts = Storage.get('updateAlerts');
document.getElementById('toggle-update-alerts').addEventListener('change', function() {
Storage.set('updateAlerts', this.checked);
notifications.show(
this.checked ? 'Update alerts enabled!' : 'Update alerts disabled!',
this.checked ? 'success' : 'success'
);
});
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('toggle-update-alerts').checked =
updateAlerts !== false;
});
const updateIconStyle = (style) => {
const icons = document.querySelectorAll('svg use');
icons.forEach(icon => {
const iconId = icon.getAttribute('href').substring(1);
let newIconId;
if (style === 'solid') {
if (iconId.endsWith('-solid')) {
newIconId = iconId;
} else {
newIconId = `${iconId}-solid`;
}
} else {
newIconId = iconId.endsWith('-solid') ? iconId.slice(0, -6) : iconId;
}
icon.setAttribute('href', `#${newIconId}`);
});
};
const savedIconStyle = Storage.get('iconStyle') || 'linear';
document.getElementById('icon-style-select').value = savedIconStyle;
updateIconStyle(savedIconStyle);
document.getElementById('icon-style-select').addEventListener('change', (event) => {
const selectedStyle = event.target.value;
Storage.set('iconStyle', selectedStyle);
updateIconStyle(selectedStyle);
});
const savedMotionPreference = Storage.get('motionPreference') || 'default';
document.getElementById('motion-preference-select').value = savedMotionPreference;
updateMotionPreference(savedMotionPreference);
document.getElementById('motion-preference-select').addEventListener('change', (event) => {
const selectedPreference = event.target.value;
Storage.set('motionPreference', selectedPreference);
updateMotionPreference(selectedPreference);
notifications.show(`Motion preference updated to ${selectedPreference}!`, 'success');
});
function updateMotionPreference(preference) {
document.body.classList.remove('subtle-motion', 'reduced-motion', 'minimal-motion', 'no-motion');
switch(preference) {
case 'subtle':
document.body.classList.add('subtle-motion');
break;
case 'reduced':
document.body.classList.add('reduced-motion');
break;
case 'minimal':
document.body.classList.add('minimal-motion');
break;
case 'disabled':
document.body.classList.add('no-motion');
break;
}
}
const settings = {
GREETING_MAX_LENGTH: 60,
toggleTheme: () => {
const currentTheme = document.body.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
const motionPreference = Storage.get('motionPreference') || 'default';
if (motionPreference !== 'default') {
document.body.classList.add('instant-theme-change');
}
document.body.setAttribute('data-theme', newTheme);
Storage.set('theme', newTheme);
const iconStyle = Storage.get('iconStyle') || 'linear';
const themeIcon = document.querySelector('#toggle-theme svg use');
const lightModeIcon = iconStyle === 'solid' ? 'icon-light-mode-solid' : 'icon-light-mode';
const darkModeIcon = iconStyle === 'solid' ? 'icon-dark-mode-solid' : 'icon-dark-mode';
themeIcon.setAttribute('href', `#${newTheme === 'dark' ? lightModeIcon : darkModeIcon}`);
if (motionPreference !== 'default') {
setTimeout(() => {
document.body.classList.remove('instant-theme-change');
}, 50);
}
},
toggleAnonymousMode: () => {
const isAnonymous = Storage.get('anonymousMode') || false;
Storage.set('anonymousMode', !isAnonymous);
if (!isAnonymous) {
const randomName = anonymousNames.generate();
Storage.set('anonymousName', randomName);
notifications.show('Anonymous mode enabled!', 'info');
} else {
Storage.remove('anonymousName');
notifications.show('Anonymous mode disabled!', 'info');
}
shortcuts.render();
updateGreeting();
},
updateSearchEngine: (engine) => {
Storage.set('searchEngine', engine);
notifications.show('Search engine updated successfully!', 'success');
},
updateVisibility: () => {
const elements = {
greeting: {
id: 'greeting',
toggle: 'toggle-greeting',
functions: ['updateGreeting'],
name: 'Greeting'
},
search: {
id: 'search-container',
toggle: 'toggle-search',
functions: ['search.init', 'search.perform'],
name: 'Search bar'
},
shortcuts: {
id: 'shortcuts-grid',
toggle: 'toggle-shortcuts',
functions: ['shortcuts.init', 'shortcuts.render'],
name: 'Shortcuts'
},
addShortcut: {
id: 'add-shortcut',
toggle: 'toggle-add-shortcut',
functions: [],
name: 'Add shortcut button'
}
};
Object.entries(elements).forEach(([key, element]) => {
const isVisible = Storage.get(`show_${key}`);
if (isVisible === null) Storage.set(`show_${key}`, true);
const toggle = document.getElementById(element.toggle);
const elementNode = document.getElementById(element.id);
if (toggle && elementNode) {
toggle.checked = isVisible !== false;
if (isVisible === false) {
elementNode.style.visibility = 'hidden';
elementNode.style.opacity = '0';
elementNode.style.position = 'absolute';
elementNode.style.pointerEvents = 'none';
} else {
elementNode.style.visibility = 'visible';
elementNode.style.opacity = '1';
elementNode.style.position = 'relative';
elementNode.style.pointerEvents = 'auto';
}
if (!toggle.hasAttribute('data-visibility-initialized')) {
toggle.setAttribute('data-visibility-initialized', 'true');
toggle.addEventListener('change', (e) => {
const isChecked = e.target.checked;
Storage.set(`show_${key}`, isChecked);
if (isChecked) {
elementNode.style.visibility = 'visible';
elementNode.style.opacity = '1';
elementNode.style.position = 'relative';
elementNode.style.pointerEvents = 'auto';
} else {
elementNode.style.visibility = 'hidden';
elementNode.style.opacity = '0';
elementNode.style.position = 'absolute';
elementNode.style.pointerEvents = 'none';
}
if (key === 'shortcuts') {
const addShortcutBtn = document.getElementById('add-shortcut');
const addShortcutVisible = Storage.get('show_addShortcut') !== false;
if (addShortcutBtn && !isChecked && !addShortcutVisible) {
addShortcutBtn.style.visibility = 'hidden';
addShortcutBtn.style.opacity = '0';
addShortcutBtn.style.position = 'absolute';
addShortcutBtn.style.pointerEvents = 'none';
} else if (addShortcutBtn && addShortcutVisible) {
addShortcutBtn.style.visibility = 'visible';
addShortcutBtn.style.opacity = '1';
addShortcutBtn.style.position = 'relative';
addShortcutBtn.style.pointerEvents = 'auto';
}
}
notifications.show(
`${element.name} ${isChecked ? 'shown' : 'hidden'}!`,
isChecked ? 'success' : 'info'
);
});
}
}
});
},
init: () => {
const settingsButton = document.getElementById('settings-button');
const settingsPage = document.getElementById('settings-page');
const backToHome = document.getElementById('back-to-home');
settingsButton.addEventListener('click', (e) => {
e.stopPropagation();
settings.updateSettingsUI();
settingsPage.classList.remove('hidden');
setTimeout(() => {
settingsPage.classList.add('active');
}, 10);
});
backToHome.addEventListener('click', () => {
settingsPage.classList.remove('active');
setTimeout(() => {
settingsPage.classList.add('hidden');
}, 300);
});
const navItems = document.querySelectorAll('.settings-nav-item');
navItems.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const section = item.getAttribute('data-section');
navItems.forEach(navItem => navItem.classList.remove('active'));
item.classList.add('active');
document.querySelectorAll('.settings-section').forEach(section => {
section.classList.remove('active');
});
document.getElementById(section).classList.add('active');
});
});
const themeToggle = document.getElementById('toggle-theme');
themeToggle.addEventListener('click', settings.toggleTheme);
const anonymousToggle = document.getElementById('toggle-anonymous');
anonymousToggle.addEventListener('change', settings.toggleAnonymousMode);
const searchEngineSelect = document.getElementById('search-engine-select');
searchEngineSelect.addEventListener('change', (e) => {
settings.updateSearchEngine(e.target.value);
});
const nameInput = document.getElementById('settings-name');
nameInput.addEventListener('change', (e) => {
const newName = e.target.value.trim();
if (newName) {
Storage.set('userName', newName);
updateGreeting();
notifications.show('Name updated successfully!', 'success');
}
});
const fontFamilySelect = document.getElementById('font-family-select');
const fontSizeSlider = document.getElementById('font-size-slider');
const fontSizeNumber = document.getElementById('font-size-number');
const resetFontSize = document.getElementById('reset-font-size');
const resetLightColors = document.getElementById('reset-light-colors');
const resetDarkColors = document.getElementById('reset-dark-colors');
fontFamilySelect.value = Storage.get('fontFamily') || defaultSettings.fontFamily;
const currentFontSize = Storage.get('fontSize') || defaultSettings.fontSize;
fontSizeSlider.value = currentFontSize;
fontSizeNumber.value = currentFontSize;
fontFamilySelect.addEventListener('change', (e) => {
Storage.set('fontFamily', e.target.value);
settings.updateTypography();
notifications.show('Font updated successfully!', 'success');
});
const updateFontSize = (value) => {
if (value >= 8 && value <= 36) {
fontSizeSlider.value = value;
fontSizeNumber.value = value;
Storage.set('fontSize', value);
settings.updateTypography();
}
};
fontSizeSlider.addEventListener('input', (e) => updateFontSize(e.target.value));
fontSizeNumber.addEventListener('change', (e) => updateFontSize(e.target.value));
resetFontSize.addEventListener('click', () => {
shortcuts.showConfirmDialog(
'Reset Typography',
'Are you sure you want to reset font settings to default?',
settings.resetTypography
);
});
resetLightColors.addEventListener('click', () => {
shortcuts.showConfirmDialog(
'Reset Light Colors',
'Are you sure you want to reset light mode colors to default?',
() => settings.resetColors('light')
);
});
resetDarkColors.addEventListener('click', () => {
shortcuts.showConfirmDialog(
'Reset Dark Colors',
'Are you sure you want to reset dark mode colors to default?',
() => settings.resetColors('dark')
);
});
settings.initColorSettings();
settings.updateTypography();
settings.updateColors();
const savedTheme = Storage.get('theme') || 'light';
document.body.setAttribute('data-theme', savedTheme);
const iconStyle = Storage.get('iconStyle') || 'linear';
const themeIcon = document.querySelector('#toggle-theme svg use');
const lightModeIcon = iconStyle === 'solid' ? 'light-mode-solid' : 'light-mode';
const darkModeIcon = iconStyle === 'solid' ? 'dark-mode-solid' : 'dark-mode';
themeIcon.setAttribute('href', `#icon-${savedTheme === 'dark' ? darkModeIcon : lightModeIcon}`);
settings.initDataManagement();
settings.updateVisibility();
const customGreetingInput = document.getElementById('custom-greeting');
customGreetingInput.maxLength = settings.GREETING_MAX_LENGTH;
customGreetingInput.addEventListener('input', (e) => {
const value = e.target.value;
if (value.length > settings.GREETING_MAX_LENGTH) {
e.target.value = value.substring(0, settings.GREETING_MAX_LENGTH);
notifications.show(`Custom greeting must be less than ${settings.GREETING_MAX_LENGTH} characters`, 'error');
}
});
customGreetingInput.addEventListener('change', (e) => {
const value = e.target.value.trim();
if (value.length > settings.GREETING_MAX_LENGTH) {
e.target.value = value.substring(0, settings.GREETING_MAX_LENGTH);
notifications.show(`Custom greeting must be less than ${settings.GREETING_MAX_LENGTH} characters`, 'error');
return;
}
if (value === '') {
Storage.remove('customGreeting');
notifications.show('Using default greeting format', 'info');
} else {
const testFormat = settings.formatGreeting(value);
if (testFormat) {
Storage.set('customGreeting', value);
notifications.show('Custom greeting updated!', 'success');
}
}
updateGreeting();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && settingsPage.classList.contains('active')) {
backToHome.click();
}
});
},
updateSettingsUI: () => {
const userName = Storage.get('userName') || '';
const isAnonymous = Storage.get('anonymousMode') || false;
const currentEngine = Storage.get('searchEngine') || 'google';
const masterPassword = Storage.get('masterPassword') || '';
document.getElementById('settings-name').value = userName;
document.getElementById('toggle-anonymous').checked = isAnonymous;
document.getElementById('search-engine-select').value = currentEngine;
document.getElementById('toggle-update-alerts').checked = updateAlerts;
document.getElementById('master-password').value = masterPassword;
['greeting', 'search', 'shortcuts', 'addShortcut'].forEach(element => {
const isVisible = Storage.get(`show_${element}`);
const toggle = document.getElementById(`toggle-${element}`);
if (toggle) {
toggle.checked = isVisible !== false;
}
});
const customGreeting = Storage.get('customGreeting') || '';
document.getElementById('custom-greeting').value = customGreeting;
const fontFamily = Storage.get('fontFamily') || defaultSettings.fontFamily;
const fontSize = Storage.get('fontSize') || defaultSettings.fontSize;
document.getElementById('font-family-select').value = fontFamily;
document.getElementById('font-size-slider').value = fontSize;
document.getElementById('font-size-number').value = fontSize;
document.getElementById('motion-preference-select').value = Storage.get('motionPreference') || 'default';
},
formatGreeting: (format) => {
try {
if (!format) return null;
const validTokens = ['{name}', '{greeting}', '{time}', '{date}', '{day}', '{month}', '{year}'];
const tokens = format.match(/{[^}]+}/g) || [];
if (!tokens.every(token => validTokens.includes(token))) {
notifications.show('Invalid greeting format: Unknown token used', 'error');
return null;
}
const now = new Date();
const hour = now.getHours();
let timeGreeting = 'good night';
if (hour >= 5 && hour < 12) timeGreeting = 'good morning';
else if (hour >= 12 && hour < 17) timeGreeting = 'good afternoon';
else if (hour >= 17 && hour < 20) timeGreeting = 'good evening';
const userName = Storage.get('anonymousMode') ?
(Storage.get('anonymousName') || anonymousNames.generate()) :
(Storage.get('userName') || 'Friend');
const formats = {
name: userName,
greeting: format.startsWith('{greeting}') ?
timeGreeting.charAt(0).toUpperCase() + timeGreeting.slice(1) :
timeGreeting,
time: now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }),
date: now.getDate().toString(),
day: now.toLocaleDateString([], { weekday: 'long' }),
month: now.toLocaleDateString([], { month: 'long' }),
year: now.getFullYear()
};
let formattedGreeting = format;
Object.entries(formats).forEach(([key, value]) => {
formattedGreeting = formattedGreeting.replace(
new RegExp(`{${key}}`, 'g'),
value
);
});
if (/{[^}]+}/.test(formattedGreeting)) {
throw new Error('Invalid format used');
}
return formattedGreeting;
} catch (e) {
notifications.show('Invalid greeting format. Using default.', 'error');
return null;
}
},
exportData: () => {
try {
let masterPassword = Storage.get('masterPassword') || '';
if (masterPassword) {
masterPassword = btoa(masterPassword);
}
const data = {
settings: {
theme: Storage.get('theme') || defaultSettings.theme,
userName: Storage.get('userName') || '',
anonymousMode: Storage.get('anonymousMode') || false,
anonymousName: Storage.get('anonymousName') || '',
searchEngine: Storage.get('searchEngine') || 'Google',
customGreeting: Storage.get('customGreeting') || '',
updateAlerts: Storage.get('updateAlerts') !== false,
iconStyle: Storage.get('iconStyle') || 'linear',
motionPreference: Storage.get('motionPreference') || 'default',
passwordProtectionEnabled: Storage.get('passwordProtectionEnabled') || false,
masterPassword: masterPassword,
fontFamily: Storage.get('fontFamily') || defaultSettings.fontFamily,
fontSize: Storage.get('fontSize') || defaultSettings.fontSize,
show_greeting: Storage.get('show_greeting') !== false,
show_search: Storage.get('show_search') !== false,
show_shortcuts: Storage.get('show_shortcuts') !== false,
show_addShortcut: Storage.get('show_addShortcut') !== false,
gridLayout: Storage.get('gridLayout') ?
(typeof Storage.get('gridLayout') === 'string' ?
JSON.parse(Storage.get('gridLayout')) :
Storage.get('gridLayout')) :
{
type: 'default',
columns: 6,
gap: 16,
size: 80,
resizable: false
},
backgrounds: typeof Storage.get('backgrounds') === 'string' ?
JSON.parse(Storage.get('backgrounds') || '[]') :
[],
customBackground: Storage.get('customBackground') || null,
lightModeColors: typeof Storage.get('lightModeColors') === 'string' ?
JSON.parse(Storage.get('lightModeColors')) :
defaultSettings.lightModeColors,
darkModeColors: typeof Storage.get('darkModeColors') === 'string' ?
JSON.parse(Storage.get('darkModeColors')) :
defaultSettings.darkModeColors
},
shortcuts: Storage.get('shortcuts') || [],
keybinds: typeof Storage.get('keybinds') === 'string' ?
JSON.parse(Storage.get('keybinds') || '{}') :
(Storage.get('keybinds') || {})
};
if (data.settings.backgrounds && Array.isArray(data.settings.backgrounds)) {
data.settings.backgrounds = data.settings.backgrounds.filter(bg => {
return typeof bg === 'string' &&
(bg.startsWith('data:image/') || bg.startsWith('images/backgrounds/'));
});
}
if (data.settings.customBackground &&
data.settings.backgrounds &&
!data.settings.backgrounds.includes(data.settings.customBackground)) {
data.settings.customBackground = null;
}
if (!data.settings || typeof data.settings !== 'object' ||
!Array.isArray(data.shortcuts) ||
!data.keybinds || typeof data.keybinds !== 'object') {
throw new Error('Invalid data structure');
}
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'jstar-tab-backup.json';
a.click();
URL.revokeObjectURL(url);
notifications.show('Data exported successfully!', 'success');
} catch (error) {
notifications.show(`Failed to export data: ${error.message}`, 'error');
console.error('Export error:', error);
}
},
importData: async (file) => {
try {
const text = await file.text();
const data = JSON.parse(text);
if (!data.settings || typeof data.settings !== 'object' ||
!Array.isArray(data.shortcuts) ||
!data.keybinds || typeof data.keybinds !== 'object') {
throw new Error('Invalid data structure');
}
if (data.settings.backgrounds) {
if (!Array.isArray(data.settings.backgrounds)) {
throw new Error('Invalid backgrounds format');
}
const validBackgrounds = data.settings.backgrounds.filter(bg => {
return typeof bg === 'string' &&
(bg.startsWith('data:image/') || bg.startsWith('images/backgrounds/'));
});
if (validBackgrounds.length !== data.settings.backgrounds.length) {
throw new Error('Invalid background images detected');
}
if (data.settings.customBackground &&
!validBackgrounds.includes(data.settings.customBackground)) {
delete data.settings.customBackground;
}
}
if (data.settings.gridLayout) {
if (typeof data.settings.gridLayout !== 'object') {
data.settings.gridLayout = {
type: 'default',
columns: 6,
gap: 16,
size: 80,
resizable: false
};
} else {
const defaultLayout = {
type: 'default',
columns: 6,
gap: 16,
size: 80,
resizable: false
};
if (!data.settings.gridLayout.type ||
!['default', 'compact', 'comfortable', 'list', 'custom'].includes(data.settings.gridLayout.type)) {
data.settings.gridLayout.type = defaultLayout.type;
}
const columns = parseInt(data.settings.gridLayout.columns);
if (isNaN(columns) || columns < 1 || columns > 12) {
data.settings.gridLayout.columns = defaultLayout.columns;
}
const gap = parseInt(data.settings.gridLayout.gap);
if (isNaN(gap) || gap < 0 || gap > 50) {
data.settings.gridLayout.gap = defaultLayout.gap;
}
const size = parseInt(data.settings.gridLayout.size);
if (isNaN(size) || size < 40 || size > 200) {
data.settings.gridLayout.size = defaultLayout.size;
}
if (typeof data.settings.gridLayout.resizable !== 'boolean') {
data.settings.gridLayout.resizable = defaultLayout.resizable;
}
}
}
const validateThemeColors = (colorObj, defaultColors) => {
if (!colorObj || typeof colorObj !== 'object') {
return defaultColors;
}
const validatedColors = {...defaultColors};
Object.keys(defaultColors).forEach(key => {
if (colorObj[key] && typeof colorObj[key] === 'string') {
validatedColors[key] = colorObj[key];
}
});
return validatedColors;
};
if (data.settings.lightModeColors) {
data.settings.lightModeColors = validateThemeColors(
data.settings.lightModeColors,
defaultSettings.lightModeColors
);
}
if (data.settings.darkModeColors) {
data.settings.darkModeColors = validateThemeColors(
data.settings.darkModeColors,
defaultSettings.darkModeColors
);
}
Object.entries(data.settings).forEach(([key, value]) => {
if (key === 'backgrounds') {
Storage.set(key, JSON.stringify(value));
} else if (key === 'gridLayout') {
Storage.set(key, JSON.stringify(value));
} else if (key === 'lightModeColors' || key === 'darkModeColors') {
Storage.set(key, JSON.stringify(value));
} else {
Storage.set(key, value);
}
});
if (data.settings.passwordProtectionEnabled) {
if (typeof shortcuts.createShortcutProtectionManager === 'function') {
shortcuts.createShortcutProtectionManager();
}
}
Storage.set('shortcuts', data.shortcuts);
const validatedKeybinds = {};
Object.entries(data.keybinds).forEach(([action, binding]) => {
if (binding && typeof binding === 'object' &&
typeof binding.keys === 'string' &&
(!binding.url || typeof binding.url === 'string')) {
validatedKeybinds[action] = binding;
}
});
Storage.set('keybinds', validatedKeybinds);
settings.updateSettingsUI();
settings.updateVisibility();
shortcuts.render();
if (typeof GridLayout !== 'undefined' && GridLayout.init) {
setTimeout(() => {
GridLayout.init();
}, 100);
}
document.body.setAttribute('data-theme', data.settings.theme || 'light');
settings.updateColors();
if (data.settings.lightModeColors || data.settings.darkModeColors) {
settings.initColorSettings();
}
updateIconStyle(data.settings.iconStyle || 'linear');
if (data.settings.fontFamily) {
document.documentElement.style.setProperty('--font-family', data.settings.fontFamily);
}
if (data.settings.fontSize) {
document.documentElement.style.setProperty('--font-size', data.settings.fontSize + 'px');
}
if (Array.isArray(data.settings.backgrounds)) {
data.settings.backgrounds.forEach(bg => {
if (bg.startsWith('data:image/')) {
addBackgroundPreview(bg, false);
}
});
}
if (data.settings.customBackground) {
setCustomBackground(data.settings.customBackground, true);
} else {
document.body.style.backgroundImage = '';
Storage.remove('customBackground');
}
let masterPassword = data.settings.masterPassword || '';
if (masterPassword) {
try {
masterPassword = atob(masterPassword);
Storage.set('masterPassword', masterPassword);
settings.updateSettingsUI();
} catch (e) {
console.error('Error decoding master password:', e);
notifications.show('Failed to decode master password!', 'error');
}
}
notifications.show('Data imported successfully!', 'success');
} catch (error) {
notifications.show('Failed to import data: Invalid file format!', 'error');
console.error('Import error:', error);
}
},
resetData: () => {
shortcuts.showConfirmDialog(
'Reset All Data',
'Are you sure you want to reset all data? This action cannot be undone.',
() => {
try {
Storage.remove('customGreeting');
Storage.clear();
Storage.set('keybinds', defaultSettings.keybinds);
closeModal(document.getElementById('settings-modal'));
notifications.show('All data has been reset!', 'success');
setTimeout(() => {
window.location.reload();
}, 1000);
} catch (error) {
notifications.show('Failed to reset data!', 'error');
console.error('Reset error:', error);
}
}
);
},
updateTypography: () => {
const fontFamily = Storage.get('fontFamily') || defaultSettings.fontFamily;
const fontSize = Storage.get('fontSize') || defaultSettings.fontSize;
document.documentElement.style.setProperty('--font-family', fontFamily);
document.documentElement.style.setProperty('--font-size-base', `${fontSize}px`);
},
updateColors: () => {
const theme = document.body.getAttribute('data-theme');
let colors;
if (theme === 'dark') {
const darkColors = Storage.get('darkModeColors');
colors = typeof darkColors === 'string' ?
JSON.parse(darkColors) :
(darkColors || defaultSettings.darkModeColors);
} else {
const lightColors = Storage.get('lightModeColors');
colors = typeof lightColors === 'string' ?
JSON.parse(lightColors) :
(lightColors || defaultSettings.lightModeColors);
}
Object.entries(colors).forEach(([variable, value]) => {
document.documentElement.style.setProperty(variable, value);
});
},
resetTypography: () => {
Storage.set('fontFamily', defaultSettings.fontFamily);
Storage.set('fontSize', defaultSettings.fontSize);
settings.updateTypography();
settings.updateSettingsUI();
notifications.show('Typography reset to default!', 'success');
},
resetColors: (theme) => {
if (theme === 'light') {
Storage.set('lightModeColors', JSON.stringify(defaultSettings.lightModeColors));
} else {
Storage.set('darkModeColors', JSON.stringify(defaultSettings.darkModeColors));
}
settings.updateColors();
settings.initColorSettings();
notifications.show(`${theme === 'light' ? 'Light' : 'Dark'} mode colors reset to default!`, 'success');
},
initColorSettings: () => {
const colorVariables = {
'Primary Color': '--primary',
'Primary Hover': '--primary-hover',
'Background': '--background',
'Surface': '--surface',
'Border': '--border',
'Text': '--text',
'Secondary Text': '--text-secondary',
'Shadow': '--shadow',
'Modal Backdrop': '--modal-backdrop',
'Scrollbar Thumb': '--scrollbar-thumb',
'Scrollbar Track': '--scrollbar-track',
'Modal Background': '--modal-background',
'Toggle Background': '--toggle-bg',
'Toggle Active': '--toggle-bg-active',
'Toggle Knob': '--toggle-knob'
};
const createColorInputs = (containerId, theme) => {
const container = document.getElementById(containerId);
container.innerHTML = '';
let colors;
if (theme === 'dark') {
const darkColors = Storage.get('darkModeColors');
colors = typeof darkColors === 'string' ?
JSON.parse(darkColors) :
(darkColors || defaultSettings.darkModeColors);
} else {
const lightColors = Storage.get('lightModeColors');
colors = typeof lightColors === 'string' ?
JSON.parse(lightColors) :
(lightColors || defaultSettings.lightModeColors);
}
Object.entries(colorVariables).forEach(([label, variable]) => {
const div = document.createElement('div');
div.className = 'color-setting';
div.innerHTML = `
<div class="color-setting-label">${label}</div>
<input type="color" value="${colors[variable]}" data-variable="${variable}">
`;
const input = div.querySelector('input');
input.addEventListener('change', (e) => {
let updatedColors;
if (theme === 'dark') {
const darkColors = Storage.get('darkModeColors');
updatedColors = typeof darkColors === 'string' ?
JSON.parse(darkColors) :
(darkColors || {...defaultSettings.darkModeColors});
} else {
const lightColors = Storage.get('lightModeColors');
updatedColors = typeof lightColors === 'string' ?
JSON.parse(lightColors) :
(lightColors || {...defaultSettings.lightModeColors});
}
updatedColors[variable] = e.target.value;
Storage.set(theme === 'dark' ? 'darkModeColors' : 'lightModeColors', JSON.stringify(updatedColors));
settings.updateColors();
});
container.appendChild(div);
});
};
createColorInputs('light-mode-colors', 'light');
createColorInputs('dark-mode-colors', 'dark');
}
};
settings.initDataManagement = () => {
const importBtn = document.getElementById('import-data');
const exportBtn = document.getElementById('export-data');
const resetBtn = document.getElementById('reset-data');
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.id = 'import-file';
fileInput.accept = '.json';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
if (!importBtn || !exportBtn || !resetBtn) {
console.warn('Data management buttons not found');
return;
}
exportBtn.replaceWith(exportBtn.cloneNode(true));
const newExportBtn = document.getElementById('export-data');
newExportBtn.addEventListener('click', () => {
settings.exportData();
});
importBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
settings.importData(file);
fileInput.value = '';
});
resetBtn.addEventListener('click', () => {
shortcuts.showConfirmDialog(
'Reset All Data',
'Are you sure you want to reset all data? This action cannot be undone.',
() => {
try {
Storage.remove('customGreeting');
Storage.clear();
Storage.set('keybinds', defaultSettings.keybinds);
closeModal(document.getElementById('settings-modal'));
notifications.show('All data has been reset!', 'success');
setTimeout(() => {
window.location.reload();
}, 1000);
} catch (error) {
notifications.show('Failed to reset data!', 'error');
console.error('Reset error:', error);
}
}
);
});
};
function initCustomSelects() {
const customSelects = document.querySelectorAll(".custom-select");
customSelects.forEach(select => {
const nativeSelect = select.querySelector("select");
if (!nativeSelect) return;
const selectedDiv = document.createElement("div");
selectedDiv.className = "select-selected";
if (nativeSelect.id === 'search-engine-select') {
nativeSelect.value = Storage.get('searchEngine') || 'google';
} else if (nativeSelect.id === 'icon-style-select') {
nativeSelect.value = Storage.get('iconStyle') || 'linear';
} else if (nativeSelect.id === 'font-family-select') {
nativeSelect.value = Storage.get('fontFamily') || 'Inter';
selectedDiv.style.fontFamily = nativeSelect.value;
} else if (nativeSelect.id === 'grid-layout-type') {
const savedGridType = Storage.get('gridLayoutType');
if (savedGridType) {
nativeSelect.value = savedGridType;
}
}
const initialOption = nativeSelect.options[nativeSelect.selectedIndex];
selectedDiv.innerHTML = `
<span class="${initialOption?.className || ''}">
${initialOption.innerHTML}
</span>
`;
const itemsDiv = document.createElement("div");
itemsDiv.className = "select-items select-hide";
for (let i = 0; i < nativeSelect.options.length; i++) {
const optionDiv = document.createElement("div");
const option = nativeSelect.options[i];
optionDiv.innerHTML = option.innerHTML;
optionDiv.className = option.className || '';
optionDiv.setAttribute('data-value', option.value);
if (nativeSelect.id === 'font-family-select') {
optionDiv.style.fontFamily = option.value;
}
if (option.value === nativeSelect.value) {
optionDiv.classList.add('same-as-selected');
}
optionDiv.addEventListener("click", function() {
nativeSelect.selectedIndex = i;
nativeSelect.dispatchEvent(new Event('change'));
selectedDiv.innerHTML = `
<span class="${this.className.replace('same-as-selected', '').trim()}">
${this.innerHTML}
</span>
`;
if (nativeSelect.id === 'font-family-select') {
selectedDiv.style.fontFamily = nativeSelect.options[i].value;
}
if (nativeSelect.id === 'grid-layout-type') {
Storage.set('gridLayoutType', nativeSelect.value);
}
const sameAsSelected = itemsDiv.querySelector('.same-as-selected');
if (sameAsSelected) {
sameAsSelected.classList.remove('same-as-selected');
}
this.classList.add('same-as-selected');
itemsDiv.classList.add('select-hide');
selectedDiv.classList.remove('select-arrow-active');
});
itemsDiv.appendChild(optionDiv);
}
selectedDiv.addEventListener("click", function(e) {
e.stopPropagation();
closeAllSelects(this);
this.nextSibling.classList.toggle("select-hide");
this.classList.toggle("select-arrow-active");
});
select.appendChild(selectedDiv);
select.appendChild(itemsDiv);
});
function closeAllSelects(element) {
const selectItems = document.getElementsByClassName("select-items");
const selectSelected = document.getElementsByClassName("select-selected");
for (let i = 0; i < selectSelected.length; i++) {
if (element !== selectSelected[i]) {
selectSelected[i].classList.remove("select-arrow-active");
}
}
for (let i = 0; i < selectItems.length; i++) {
if (element !== selectItems[i].previousSibling) {
selectItems[i].classList.add("select-hide");
}
}
}
document.addEventListener("click", closeAllSelects);
}
document.addEventListener("DOMContentLoaded", function() {
initCustomSelects();
});