From bb2b457c1c83769e14cf773e7566954f3c0243da Mon Sep 17 00:00:00 2001 From: David Ralph Date: Wed, 4 Feb 2026 13:30:14 +0000 Subject: [PATCH] refactor: remove unnecessary comments --- eslint.config.js | 14 ++---- scripts/findUnusedTranslations.cjs | 8 ---- scripts/updateTranslationPercentages.cjs | 1 - scripts/updatetranslations.cjs | 9 ---- src/components/Elements/AddModal/AddModal.jsx | 9 ---- src/components/Elements/MainModal/Main.jsx | 32 ------------- .../Elements/MainModal/backend/Tab.jsx | 4 -- .../Elements/MainModal/backend/Tabs.jsx | 13 ------ .../MainModal/components/ModalTopBar.jsx | 42 ++--------------- .../MainModal/components/SidebarSkeleton.jsx | 18 ++++---- .../Elements/MainModal/constants/tabConfig.js | 7 --- .../MainModal/scss/marketplace/_main.scss | 4 +- .../MainModal/scss/modules/_buttons.scss | 1 - .../MainModal/scss/modules/_sidebar.scss | 4 -- .../scss/settings/modules/tabs/_order.scss | 6 --- src/components/Elements/Tooltip/Tooltip.jsx | 5 +-- .../Form/Settings/ChipSelect/ChipSelect.jsx | 5 +-- .../Form/Settings/DatePicker/DatePicker.jsx | 2 - .../Form/Settings/Dropdown/Dropdown.jsx | 13 +----- .../Form/Settings/FileUpload/FileUpload.jsx | 3 -- .../LocationSearch/LocationSearch.jsx | 4 -- .../Form/Settings/Slider/Slider.jsx | 2 - .../Form/Settings/Textarea/Textarea.jsx | 5 --- .../Layout/Settings/Header/Header.jsx | 1 - src/config/constants.js | 3 -- src/contexts/TranslationContext.jsx | 13 +----- .../background/api/backgroundFilters.js | 4 +- .../background/api/backgroundLoader.js | 30 ------------- src/features/background/api/blobUrl.js | 1 - src/features/background/api/photoPackAPI.js | 17 ------- .../components/PhotoInformation.jsx | 2 - .../background/hooks/useBackgroundEvents.js | 3 -- .../background/hooks/useBackgroundLoader.js | 9 +--- .../background/hooks/useBackgroundRenderer.js | 19 -------- .../background/options/BackgroundOptions.jsx | 14 ------ src/features/background/options/Custom.jsx | 35 +-------------- .../options/sections/APISettings.jsx | 3 -- .../options/sections/DisplaySettings.jsx | 2 - src/features/greeting/Greeting.jsx | 6 --- .../greeting/options/GreetingOptions.jsx | 12 ----- .../marketplace/components/Items/Items.jsx | 11 +---- .../components/Modals/ItemSettingsModal.jsx | 6 --- .../components/hooks/useMarketplaceInstall.js | 4 -- src/features/marketplace/views/Added.jsx | 7 +-- src/features/misc/CenteredCustomWidgets.jsx | 1 - src/features/misc/CustomWidgets.jsx | 1 - src/features/misc/customwidgets.scss | 3 -- src/features/misc/modals/Modals.jsx | 8 ---- src/features/misc/sections/Advanced.jsx | 2 - src/features/misc/sections/Changelog.jsx | 3 -- src/features/misc/sections/Language.jsx | 13 ------ src/features/misc/views/Discover.jsx | 35 --------------- src/features/misc/views/Settings.jsx | 1 - src/features/misc/views/Widgets.jsx | 2 +- src/features/navbar/components/Apps.jsx | 2 - src/features/quicklinks/QuickLinks.jsx | 1 - .../quicklinks/options/QuickLinksOptions.jsx | 5 --- src/features/quicklinks/quicklinks.scss | 5 --- src/features/quote/Quote.jsx | 3 -- src/features/quote/hooks/useQuoteLoader.js | 45 ++++--------------- src/features/quote/options/QuoteOptions.jsx | 12 ----- src/features/search/Search.jsx | 6 --- src/features/stats/api/stats.js | 1 - src/features/stats/options/StatsOptions.jsx | 1 - src/features/time/Clock.jsx | 3 -- src/features/time/Date.jsx | 4 -- src/features/weather/Weather.jsx | 4 -- src/features/weather/api/getWeather.js | 19 +++----- src/features/welcome/Welcome.jsx | 13 ------ .../components/Sections/ChooseLanguage.jsx | 3 -- src/hooks/useCachedFetch.js | 7 --- src/hooks/useFrequencyInterval.js | 17 ------- src/index.jsx | 2 - src/scss/_mixins.scss | 1 - src/utils/backgroundQueue.js | 7 --- src/utils/customBackgroundDB.js | 19 +------- src/utils/date/index.js | 1 - src/utils/deepLinking.js | 28 ------------ src/utils/formatNumber.js | 17 +++---- src/utils/frequencyManager.js | 21 +++------ src/utils/imageMetadata.js | 15 +------ src/utils/jsonStorage.js | 1 - src/utils/marketplace/install.js | 13 ------ src/utils/marketplace/uninstall.js | 14 ------ src/utils/migrations.js | 14 ------ src/utils/queueOperations.js | 36 +++++++-------- src/utils/settings/default.js | 1 - src/utils/settings/export.js | 1 - src/utils/settings/load.js | 4 -- src/utils/wikidata/wikidataAuthorFetcher.js | 28 +----------- vite.config.mjs | 39 +--------------- 91 files changed, 77 insertions(+), 818 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 8490d2f6..9e396fd3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,6 @@ import jsxA11y from 'eslint-plugin-jsx-a11y'; import prettier from 'eslint-config-prettier'; export default [ - // Ignore patterns { ignores: [ '**/node_modules/**', @@ -18,7 +17,6 @@ export default [ ], }, - // Base config for all JS/JSX files { files: ['**/*.{js,jsx,mjs}'], languageOptions: { @@ -26,7 +24,6 @@ export default [ sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true } }, globals: { - // Browser globals window: 'readonly', document: 'readonly', navigator: 'readonly', @@ -48,7 +45,6 @@ export default [ AbortController: 'readonly', btoa: 'readonly', atob: 'readonly', - // Node globals for scripts process: 'readonly', __dirname: 'readonly', __filename: 'readonly', @@ -64,21 +60,17 @@ export default [ ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, - // React specific rules - 'react/prop-types': 'off', // Using PropTypes is optional - 'react/react-in-jsx-scope': 'off', // Not needed with React 17+ - 'react/jsx-uses-react': 'off', // Not needed with React 17+ + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + 'react/jsx-uses-react': 'off', - // General rules 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], 'no-console': ['warn', { allow: ['warn', 'error'] }], - // Modern JS 'prefer-const': 'warn', 'no-var': 'error', }, }, - // Prettier config (must be last to override other formatting rules) prettier, ]; diff --git a/scripts/findUnusedTranslations.cjs b/scripts/findUnusedTranslations.cjs index ec9ac5ab..28ef59b8 100644 --- a/scripts/findUnusedTranslations.cjs +++ b/scripts/findUnusedTranslations.cjs @@ -65,27 +65,19 @@ function isKeyUsed(key, files, fileContentsCache) { const keySegments = key.split('.'); const patterns = []; - // Pattern 1: Full key match - // Example: 'modals.main.settings.sections.greeting.event_name' const escapedFullKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); patterns.push(new RegExp(`['"\`]${escapedFullKey}['"\`]`, 'g')); - // Pattern 2: Last 2 segments (catches most template literal cases) - // Example: 'greeting.event_name' for dynamic construction like `${PREFIX}.event_name` if (keySegments.length >= 2) { const lastTwo = keySegments.slice(-2).map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\.'); patterns.push(new RegExp(`['"\`]${lastTwo}['"\`]`, 'g')); } - // Pattern 3: Last 3 segments (more specific than 2, less than full) - // Example: 'sections.greeting.event_name' if (keySegments.length >= 3) { const lastThree = keySegments.slice(-3).map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\.'); patterns.push(new RegExp(`['"\`]${lastThree}['"\`]`, 'g')); } - // Pattern 4: Final segment after a dot (for deeply nested template literals) - // Example: .event_name' in template literal like `${SECTION}.${SUBSECTION}.event_name` if (keySegments.length >= 3) { const finalSegment = keySegments[keySegments.length - 1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); patterns.push(new RegExp(`\\.${finalSegment}['"\`]`, 'g')); diff --git a/scripts/updateTranslationPercentages.cjs b/scripts/updateTranslationPercentages.cjs index e2eef21c..9820936f 100644 --- a/scripts/updateTranslationPercentages.cjs +++ b/scripts/updateTranslationPercentages.cjs @@ -2,7 +2,6 @@ const fs = require('fs'); const path = require('path'); const https = require('https'); -// Language code mappings between Weblate and Mue const CODE_MAPPINGS = { de: 'de_DE', id: 'id_ID', diff --git a/scripts/updatetranslations.cjs b/scripts/updatetranslations.cjs index 74354b9d..cb518305 100644 --- a/scripts/updatetranslations.cjs +++ b/scripts/updatetranslations.cjs @@ -18,12 +18,10 @@ const localesDir = path.join(__dirname, '../src/i18n/locales'); const achievementsDir = path.join(localesDir, 'achievements'); const manifestLocalesDir = path.join(__dirname, '../manifest/_locales'); -// Check if the locales directory exists, if not, create it if (!fs.existsSync(localesDir)) { fs.mkdirSync(localesDir, { recursive: true }); } -// Check if the achievements directory exists, if not, create it if (!fs.existsSync(achievementsDir)) { fs.mkdirSync(achievementsDir, { recursive: true }); } @@ -74,7 +72,6 @@ fs.readdirSync(achievementsDir).forEach((file) => { const locales = fs.readdirSync(localesDir); locales.forEach((locale) => { if (!fs.existsSync(path.join(achievementsDir, locale))) { - // ignore directories if (fs.lstatSync(path.join(localesDir, locale)).isDirectory()) { return; } @@ -85,29 +82,23 @@ fs.readdirSync(achievementsDir).forEach((file) => { }); }); -// Sync manifest locales with src/i18n/locales console.log('Syncing manifest/_locales with src/i18n/locales...'); const enManifestPath = path.join(manifestLocalesDir, 'en', 'messages.json'); -// Check if the English manifest exists if (!fs.existsSync(enManifestPath)) { console.error(`English manifest file does not exist at '${enManifestPath}'`); } else { const enManifest = fs.readFileSync(enManifestPath, 'utf8'); - // Get all locales from src/i18n/locales const localeFiles = fs.readdirSync(localesDir).filter((file) => { - // Only include JSON files, not directories return !fs.lstatSync(path.join(localesDir, file)).isDirectory() && file.endsWith('.json'); }); localeFiles.forEach((localeFile) => { - // Extract locale code (e.g., "en_GB.json" -> "en_GB") const localeCode = localeFile.replace('.json', ''); const manifestLocalePath = path.join(manifestLocalesDir, localeCode); const manifestMessagesPath = path.join(manifestLocalePath, 'messages.json'); - // Check if this locale exists in manifest/_locales if (!fs.existsSync(manifestLocalePath)) { console.log(`Creating missing locale: ${localeCode}`); fs.mkdirSync(manifestLocalePath, { recursive: true }); diff --git a/src/components/Elements/AddModal/AddModal.jsx b/src/components/Elements/AddModal/AddModal.jsx index 4626ccc6..01f70f62 100644 --- a/src/components/Elements/AddModal/AddModal.jsx +++ b/src/components/Elements/AddModal/AddModal.jsx @@ -20,17 +20,14 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed const [suggestedName, setSuggestedName] = useState(''); const [resetKey, setResetKey] = useState(Date.now()); - // Reset form when modal opens for adding new link useEffect(() => { if (!edit) { - // Clear localStorage for the form fields localStorage.removeItem('quicklink_modal_name'); localStorage.removeItem('quicklink_modal_url'); localStorage.removeItem('quicklink_modal_iconType'); localStorage.removeItem('quicklink_modal_icon_url'); localStorage.removeItem('quicklink_modal_emoji'); - // Reset all state setName(''); setUrl(''); setIcon(''); @@ -40,12 +37,10 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed setUploadError(''); setSuggestedName(''); - // Change the key to force remount setResetKey(Date.now()); } }, [edit]); - // Extract domain name from URL as suggestion useEffect(() => { if (name || !url) { setSuggestedName(''); @@ -60,10 +55,8 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed const domain = new URL(urlToTest).hostname; if (domain) { - // Extract first part of domain (e.g., "google" from "google.com", "bbc" from "bbc.co.uk") const parts = domain.split('.'); let name = parts[0]; - // Handle cases like "co.uk" where we want "bbc" not "co" if (parts.length > 2 && parts[parts.length - 2] === 'co') { name = parts[parts.length - 3]; } @@ -89,7 +82,6 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed }; const handleSubmit = () => { - // Use suggested name if no name was entered const finalName = name || suggestedName || ''; if (edit) { @@ -151,7 +143,6 @@ function AddModal({ urlError, iconError, addLink, closeModal, edit, editData, ed name="quicklink_modal_url" noSetting={true} onChange={(value) => { - // Auto-add https:// if no protocol specified let finalValue = value; if (value && !value.startsWith('http://') && !value.startsWith('https://')) { finalValue = 'https://' + value; diff --git a/src/components/Elements/MainModal/Main.jsx b/src/components/Elements/MainModal/Main.jsx index 5ce64dfc..f0c3a0a5 100644 --- a/src/components/Elements/MainModal/Main.jsx +++ b/src/components/Elements/MainModal/Main.jsx @@ -11,7 +11,6 @@ const Settings = lazy(() => import('../../../features/misc/views/Settings')); const Library = lazy(() => import('../../../features/misc/views/Library')); const Discover = lazy(() => import('../../../features/misc/views/Discover')); -// Map tab types to their corresponding components const TAB_COMPONENTS = { [TAB_TYPES.SETTINGS]: Settings, [TAB_TYPES.LIBRARY]: Library, @@ -19,7 +18,6 @@ const TAB_COMPONENTS = { }; function MainModal({ modalClose, deepLinkData }) { - // Initialize with deep link tab if provided, otherwise default to settings const initialTab = deepLinkData?.tab || TAB_TYPES.SETTINGS; const [currentTab, setCurrentTab] = useState(initialTab); const [currentSection, setCurrentSection] = useState(''); @@ -30,27 +28,22 @@ function MainModal({ modalClose, deepLinkData }) { const [navigationTrigger, setNavigationTrigger] = useState(null); const [iframeBreadcrumbs, setIframeBreadcrumbs] = useState([]); - // Clear product view when changing tabs useEffect(() => { setProductView(null); }, [currentTab]); - // Handle deep link updates (when modal opens via EventBus with new deep link) useEffect(() => { if (deepLinkData) { - // Update tab if different if (deepLinkData.tab && deepLinkData.tab !== currentTab) { setCurrentTab(deepLinkData.tab); } - // Handle settings section navigation with subsection if (deepLinkData.tab === TAB_TYPES.SETTINGS && deepLinkData.section) { setNavigationTrigger({ type: 'settings-section', data: deepLinkData.section, timestamp: Date.now(), }); - // Set sub-section if present if (deepLinkData.subSection) { setCurrentSubSection(deepLinkData.subSection); } @@ -58,10 +51,8 @@ function MainModal({ modalClose, deepLinkData }) { } }, [deepLinkData]); - // Clear hash when modal closes useEffect(() => { return () => { - // When modal unmounts, clear the hash if (window.location.hash) { window.history.replaceState(null, null, window.location.pathname); } @@ -69,38 +60,30 @@ function MainModal({ modalClose, deepLinkData }) { }, []); useEffect(() => { - // Listen for browser back/forward navigation via popstate const handlePopState = () => { const linkData = window.location.hash ? parseDeepLink(window.location.hash) : null; if (linkData) { - // Update tab if different if (linkData.tab && linkData.tab !== currentTab) { setCurrentTab(linkData.tab); } - // Handle settings section navigation if (linkData.tab === TAB_TYPES.SETTINGS && linkData.section) { setNavigationTrigger({ type: 'settings-section', data: linkData.section, timestamp: Date.now(), }); - // Set sub-section if present in hash setCurrentSubSection(linkData.subSection || null); return; } - // Handle product and collection navigation if (linkData.itemId && linkData.collection && linkData.fromCollection) { - // Product viewed from within a collection - // First set collection state, then navigate to product setNavigationTrigger({ type: 'collection', data: linkData.collection, timestamp: Date.now(), }); - // Small delay to ensure collection state is set before navigating to product setTimeout(() => { setNavigationTrigger({ type: 'product', @@ -112,7 +95,6 @@ function MainModal({ modalClose, deepLinkData }) { }); }, 100); } else if (linkData.itemId) { - // Product navigation (standalone) setNavigationTrigger({ type: 'product', data: { @@ -122,14 +104,12 @@ function MainModal({ modalClose, deepLinkData }) { timestamp: Date.now(), }); } else if (linkData.collection) { - // Collection page navigation setNavigationTrigger({ type: 'collection', data: linkData.collection, timestamp: Date.now(), }); } else { - // Back to main view (clear collection state) setProductView(null); setNavigationTrigger({ type: 'main', @@ -146,7 +126,6 @@ function MainModal({ modalClose, deepLinkData }) { const handleChangeTab = (newTab) => { setCurrentTab(newTab); - // Update URL hash when tab changes if (newTab === TAB_TYPES.DISCOVER) { updateHash(`#${newTab}/all`); } else if (newTab === TAB_TYPES.LIBRARY) { @@ -159,11 +138,8 @@ function MainModal({ modalClose, deepLinkData }) { const handleSectionChange = (section, sectionName) => { setCurrentSection(section); setCurrentSectionName(sectionName); - // Clear sub-section when changing sections setCurrentSubSection(null); - // Update URL hash when section changes if (currentTab === TAB_TYPES.DISCOVER) { - // For Discover tab, update with the section type const sectionMap = { [variables.getMessage('modals.main.marketplace.all')]: 'all', [variables.getMessage('modals.main.marketplace.photo_packs')]: 'photo_packs', @@ -176,19 +152,16 @@ function MainModal({ modalClose, deepLinkData }) { updateHash(`#${currentTab}/${sectionKey}`); } } else if (currentTab === TAB_TYPES.SETTINGS && sectionName) { - // For Settings tab, update with the section name updateHash(`#${currentTab}/${sectionName}`, false); } }; const handleSubSectionChange = (subSection, sectionName) => { setCurrentSubSection(subSection); - // Update URL hash when sub-section changes if (currentTab === TAB_TYPES.SETTINGS && sectionName) { if (subSection) { updateHash(`#${currentTab}/${sectionName}/${subSection}`); } else { - // Going back to section, remove sub-section from hash updateHash(`#${currentTab}/${sectionName}`); } } @@ -196,8 +169,6 @@ function MainModal({ modalClose, deepLinkData }) { const handleProductView = (product) => { setProductView(product); - // URL hash is already updated by child components (Browse.jsx) - // Browser history automatically tracks hash changes }; const handleResetDiscoverToAll = () => { @@ -206,7 +177,6 @@ function MainModal({ modalClose, deepLinkData }) { }; const handleBack = () => { - // Clear iframe breadcrumbs when navigating back setIframeBreadcrumbs([]); window.history.back(); }; @@ -215,8 +185,6 @@ function MainModal({ modalClose, deepLinkData }) { window.history.forward(); }; - // Browser manages history state, so we always show buttons enabled - // Browser will handle whether there's actually history to go back/forward const canGoBack = true; const canGoForward = true; diff --git a/src/components/Elements/MainModal/backend/Tab.jsx b/src/components/Elements/MainModal/backend/Tab.jsx index 838ee962..3534c79b 100644 --- a/src/components/Elements/MainModal/backend/Tab.jsx +++ b/src/components/Elements/MainModal/backend/Tab.jsx @@ -7,18 +7,14 @@ function Tab({ label, currentTab, onClick, navbarTab, isCollapsed }) { const t = useT(); const isExperimental = localStorage.getItem('experimental') !== 'false'; - // Get the icon component for this label (label is already translated) const IconComponent = getIconComponent(label, { getMessage: t }); - // Determine if this label should have a divider after it const hasDivider = DIVIDER_LABELS.some((key) => t(key) === label); - // Build className const baseClass = navbarTab ? 'navbar-item' : 'tab-list-item'; const activeClass = navbarTab ? 'navbar-item-active' : 'tab-list-active'; const className = `${baseClass}${currentTab === label ? ` ${activeClass}` : ''}`; - // Hide experimental tab if experimental mode is disabled const isExperimentalTab = label === t('modals.main.settings.sections.experimental.title'); if (isExperimentalTab && !isExperimental) { return
; diff --git a/src/components/Elements/MainModal/backend/Tabs.jsx b/src/components/Elements/MainModal/backend/Tabs.jsx index b629be18..5fcf65f5 100644 --- a/src/components/Elements/MainModal/backend/Tabs.jsx +++ b/src/components/Elements/MainModal/backend/Tabs.jsx @@ -20,7 +20,6 @@ const Tabs = ({ }) => { const t = useT(); - // Find initial section from deep link if available const getInitialSection = () => { if (deepLinkData?.section && sections) { const section = sections.find((s) => s.name === deepLinkData.section); @@ -46,7 +45,6 @@ const Tabs = ({ const [searchQuery, setSearchQuery] = useState(''); const contentRef = useRef(null); - // Derive currentTab label from currentName - avoids setState in effects const currentTab = (() => { if (sections && currentName) { const section = sections.find((s) => s.name === currentName); @@ -54,7 +52,6 @@ const Tabs = ({ return t(section.label); } } - // Fallback: find label from children const child = children.find((c) => c.props.name === currentName); return child?.props.label || children[0]?.props.label; })(); @@ -66,32 +63,27 @@ const Tabs = ({ setCurrentName(name); - // Scroll content to top when changing tabs if (contentRef.current) { contentRef.current.scrollTop = 0; } - // Notify parent of section change with both label and name if (onSectionChange) { onSectionChange(tab, name); } }; - // Notify parent of initial section on mount useEffect(() => { if (onSectionChange && currentTab) { onSectionChange(currentTab, currentName); } }, []); - // Handle navigation trigger for settings sections (popstate) // useLayoutEffect is appropriate here for synchronous state updates before paint useLayoutEffect(() => { if (navigationTrigger?.type === 'settings-section' && sections) { const section = sections.find((s) => s.name === navigationTrigger.data); if (section) { setCurrentName(section.name); - // Scroll content to top when navigating via browser history if (contentRef.current) { contentRef.current.scrollTop = 0; } @@ -99,12 +91,10 @@ const Tabs = ({ } }, [navigationTrigger, sections]); - // Reset to first tab when requested // useLayoutEffect is appropriate here for synchronous state updates before paint useLayoutEffect(() => { if (resetToFirst) { setCurrentName(children[0]?.props.name); - // Scroll content to top when resetting to first tab if (contentRef.current) { contentRef.current.scrollTop = 0; } @@ -125,16 +115,13 @@ const Tabs = ({ localStorage.setItem('sidebarCollapsed', newState.toString()); }; - // Show sidebar for Settings and Discover tabs const showSidebar = activeTab === TAB_TYPES.SETTINGS || activeTab === TAB_TYPES.DISCOVER; - // Filter tabs based on search query const filteredChildren = children.filter((tab) => { if (!searchQuery.trim()) return true; return tab.props.label.toLowerCase().includes(searchQuery.toLowerCase()); }); - // Keyboard shortcut for sidebar toggle (Ctrl/Cmd + B) useEffect(() => { const handleKeyPress = (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'b') { diff --git a/src/components/Elements/MainModal/components/ModalTopBar.jsx b/src/components/Elements/MainModal/components/ModalTopBar.jsx index ada02d8f..b652d60c 100644 --- a/src/components/Elements/MainModal/components/ModalTopBar.jsx +++ b/src/components/Elements/MainModal/components/ModalTopBar.jsx @@ -6,7 +6,6 @@ import { NAVBAR_BUTTONS, TAB_TYPES } from '../constants/tabConfig'; import { updateHash } from 'utils/deepLinking'; import mueAboutIcon from 'assets/icons/mue_about.png'; -// Map marketplace types to translation keys const MARKETPLACE_TYPE_TO_KEY = { photo_packs: 'modals.main.marketplace.photo_packs', 'photo packs': 'modals.main.marketplace.photo_packs', @@ -21,7 +20,6 @@ const MARKETPLACE_TYPE_TO_KEY = { all: 'modals.main.marketplace.all', }; -// Map breadcrumb labels (from website) to category keys for navigation const BREADCRUMB_LABEL_TO_CATEGORY = { 'photo packs': 'photo_packs', 'quote packs': 'quote_packs', @@ -48,7 +46,6 @@ function ModalTopBar({ }) { const t = useT(); - // Track installed addons count for badge const [installedCount, setInstalledCount] = useState(() => { try { const installed = JSON.parse(localStorage.getItem('installed')) || []; @@ -68,10 +65,8 @@ function ModalTopBar({ } }; - // Listen for storage events (changes from other tabs) window.addEventListener('storage', updateCount); - // Listen for custom event for same-tab updates window.addEventListener('installedAddonsChanged', updateCount); return () => { @@ -80,22 +75,17 @@ function ModalTopBar({ }; }, []); - // Get the current tab label const currentTabButton = NAVBAR_BUTTONS.find(({ tab }) => tab === currentTab); const currentTabLabel = currentTabButton ? t(currentTabButton.messageKey) : ''; - // Utility function to get translated sub-section label const getSubSectionLabel = (subSection, sectionName) => { if (!subSection || !sectionName) { return subSection; } - // Use the same translation pattern as the section components const translationKey = `modals.main.settings.sections.${sectionName}.${subSection}.title`; const translated = t(translationKey); - // If translation key is returned as-is or empty, it means translation doesn't exist - // Fall back to capitalized sub-section name if (!translated || translated === translationKey) { return subSection.charAt(0).toUpperCase() + subSection.slice(1); } @@ -103,13 +93,11 @@ function ModalTopBar({ return translated; }; - // Determine breadcrumb path with click handlers const breadcrumbPath = []; if (currentTabLabel) { breadcrumbPath.push({ label: currentTabLabel, - // Make "Discover" clickable when viewing items/categories to go back to "All" onClick: (iframeBreadcrumbs && iframeBreadcrumbs.length > 0) || productView ? () => { @@ -118,33 +106,25 @@ function ModalTopBar({ : null, }); - // Check if we have iframe breadcrumbs (from Discover iframe) if (iframeBreadcrumbs && iframeBreadcrumbs.length > 0) { - // Use all iframe breadcrumbs except the first one (which is usually "Marketplace" or the category) - // Skip the first breadcrumb as it's redundant with our tab label const relevantCrumbs = iframeBreadcrumbs.slice(1); relevantCrumbs.forEach((crumb, index) => { const isLast = index === relevantCrumbs.length - 1; - // Translate the breadcrumb label if it's a known category const lowerLabel = crumb.label.toLowerCase(); const translationKey = MARKETPLACE_TYPE_TO_KEY[lowerLabel]; const displayLabel = translationKey ? t(translationKey) : crumb.label; - // Get the category key for navigation const categoryKey = BREADCRUMB_LABEL_TO_CATEGORY[lowerLabel]; breadcrumbPath.push({ label: displayLabel, - // Make it clickable if it has an href and it's not the last item onClick: crumb.clickable && !isLast && crumb.href ? () => { - // Convert website href to extension hash format const href = crumb.href; - // Try to extract type from URL parameters first (most reliable) if (href.includes('type=')) { const urlParams = new URLSearchParams(href.split('?')[1]); const typeParam = urlParams.get('type'); @@ -152,7 +132,6 @@ function ModalTopBar({ updateHash(`#discover/${typeParam}`); } } - // Otherwise check specific paths else if (href.includes('/collections')) { updateHash('#discover/collections'); } else if (href.includes('/collection/')) { @@ -161,13 +140,10 @@ function ModalTopBar({ updateHash(`#discover/collection/${collectionId}`); } } else if (categoryKey) { - // If we recognized the category from the label, navigate to it updateHash(`#discover/${categoryKey}`); } else if (href === '/marketplace' || href === '/marketplace/') { updateHash('#discover/all'); } - // If it's a specific item, we'd need more context to determine the category - // In that case, using history.back() might be more reliable else { const stepsBack = relevantCrumbs.length - index - 1; for (let i = 0; i < stepsBack; i++) { @@ -179,24 +155,18 @@ function ModalTopBar({ }); }); } else if (productView) { - // If viewing a collection page itself (not a product within it) if (productView.isCollection) { - // Show: Discover > Collection Name breadcrumbPath.push({ label: productView.collectionTitle || productView.name, - onClick: null, // Current page - not clickable + onClick: null, }); } else { - // Viewing a product - // Show: Discover > Collection/Category > Product if (productView.fromCollection && productView.collectionTitle) { - // If from a collection, show collection name breadcrumbPath.push({ label: productView.collectionTitle, onClick: productView.onBack || null, }); } else { - // Otherwise show category const categoryKey = MARKETPLACE_TYPE_TO_KEY[productView.type]; if (categoryKey) { breadcrumbPath.push({ @@ -205,24 +175,21 @@ function ModalTopBar({ }); } } - // Add product name as final breadcrumb breadcrumbPath.push({ label: productView.name, - onClick: null, // Current item - not clickable + onClick: null, }); } } else if (currentSection) { - // Show: Tab > Section or Tab > Section > Sub-Section breadcrumbPath.push({ label: currentSection, - onClick: currentSubSection ? () => onSubSectionChange(null) : null, // Clickable if sub-section is active + onClick: currentSubSection ? () => onSubSectionChange(null) : null, }); - // Add sub-section if present if (currentSubSection) { breadcrumbPath.push({ label: getSubSectionLabel(currentSubSection, currentSectionName), - onClick: null, // Current sub-section - not clickable + onClick: null, }); } } @@ -298,7 +265,6 @@ function ModalTopBar({
{NAVBAR_BUTTONS.map(({ tab, icon: Icon, messageKey }) => { - // Show badge for Library tab when there are installed addons const badgeValue = tab === TAB_TYPES.LIBRARY && installedCount > 0 ? installedCount : undefined; diff --git a/src/components/Elements/MainModal/components/SidebarSkeleton.jsx b/src/components/Elements/MainModal/components/SidebarSkeleton.jsx index e474a1bc..e7c0ebe8 100644 --- a/src/components/Elements/MainModal/components/SidebarSkeleton.jsx +++ b/src/components/Elements/MainModal/components/SidebarSkeleton.jsx @@ -1,21 +1,20 @@ import { TAB_TYPES } from '../constants/tabConfig'; -// Tab-specific configurations with exact divider positions const TAB_CONFIGS = { [TAB_TYPES.SETTINGS]: { - itemCount: 16, // Excluding experimental - dividerPositions: [10, 12], // After Weather, Language - textWidths: [80, 100, 70, 90, 85, 75, 80, 95, 90, 75, 85, 90, 85, 80, 70, 95], // Fixed widths in pixels - showSearch: true, // Settings has search bar + itemCount: 16, + dividerPositions: [10, 12], + textWidths: [80, 100, 70, 90, 85, 75, 80, 95, 90, 75, 85, 90, 85, 80, 70, 95], + showSearch: true, }, [TAB_TYPES.DISCOVER]: { itemCount: 5, - dividerPositions: [0], // After "All" - textWidths: [60, 95, 95, 110, 90], // Fixed widths - showSearch: false, // Discover doesn't have search + dividerPositions: [0], + textWidths: [60, 95, 95, 110, 90], + showSearch: false, }, [TAB_TYPES.LIBRARY]: { - itemCount: 0, // Library doesn't show sidebar + itemCount: 0, dividerPositions: [], textWidths: [], showSearch: false, @@ -25,7 +24,6 @@ const TAB_CONFIGS = { const SidebarSkeleton = ({ currentTab = TAB_TYPES.SETTINGS }) => { const config = TAB_CONFIGS[currentTab] || TAB_CONFIGS[TAB_TYPES.SETTINGS]; - // Library tab doesn't show sidebar if (config.itemCount === 0) { return null; } diff --git a/src/components/Elements/MainModal/constants/tabConfig.js b/src/components/Elements/MainModal/constants/tabConfig.js index cdf6ff91..3b870d30 100644 --- a/src/components/Elements/MainModal/constants/tabConfig.js +++ b/src/components/Elements/MainModal/constants/tabConfig.js @@ -28,14 +28,12 @@ import { MdCollectionsBookmark, } from 'react-icons/md'; -// Tab type constants export const TAB_TYPES = { SETTINGS: 'settings', LIBRARY: 'library', DISCOVER: 'discover', }; -// Icon component mapping - using component references instead of elements export const ICON_COMPONENTS = { SETTINGS: MdTune, LIBRARY: MdBookmarks, @@ -63,7 +61,6 @@ export const ICON_COMPONENTS = { COLLECTIONS: MdCollectionsBookmark, }; -// Message keys for icon mapping export const MESSAGE_KEYS = { OVERVIEW: 'modals.main.settings.sections.order.title', SETTINGS: 'modals.main.navbar.settings', @@ -95,8 +92,6 @@ export const MESSAGE_KEYS = { COLLECTIONS: 'modals.main.marketplace.collections', }; -// Helper to get icon component by translated label -// This function builds a map at runtime using variables.getMessage export const getIconComponent = (label, variables) => { const iconMap = { [variables.getMessage(MESSAGE_KEYS.OVERVIEW)]: ICON_COMPONENTS.OVERVIEW, @@ -132,7 +127,6 @@ export const getIconComponent = (label, variables) => { return iconMap[label]; }; -// Navbar configuration export const NAVBAR_BUTTONS = [ { tab: TAB_TYPES.SETTINGS, @@ -151,7 +145,6 @@ export const NAVBAR_BUTTONS = [ }, ]; -// Labels that should have dividers after them export const DIVIDER_LABELS = [ 'modals.main.settings.sections.weather.title', 'modals.main.settings.sections.language.title', diff --git a/src/components/Elements/MainModal/scss/marketplace/_main.scss b/src/components/Elements/MainModal/scss/marketplace/_main.scss index 8f778421..decc19a0 100644 --- a/src/components/Elements/MainModal/scss/marketplace/_main.scss +++ b/src/components/Elements/MainModal/scss/marketplace/_main.scss @@ -1,4 +1,3 @@ -// this file is too long @use 'modules/lightbox' as *; @use 'scss/variables' as *; @@ -24,7 +23,7 @@ background-size: cover; padding: 1.5rem; will-change: transform; - transform: translate3d(0, 0, 0); // Force GPU acceleration + transform: translate3d(0, 0, 0); @include themed { background-color: t($modal-secondaryColour); @@ -115,7 +114,6 @@ } } - // Disabled pack styling &.item-disabled { opacity: 0.5; diff --git a/src/components/Elements/MainModal/scss/modules/_buttons.scss b/src/components/Elements/MainModal/scss/modules/_buttons.scss index 094a00a5..c40cf352 100644 --- a/src/components/Elements/MainModal/scss/modules/_buttons.scss +++ b/src/components/Elements/MainModal/scss/modules/_buttons.scss @@ -1,6 +1,5 @@ @use 'scss/variables' as *; -// Default button behavior for all modal buttons .btn-default { @include modal-button(standard); diff --git a/src/components/Elements/MainModal/scss/modules/_sidebar.scss b/src/components/Elements/MainModal/scss/modules/_sidebar.scss index b6fac058..654e9174 100644 --- a/src/components/Elements/MainModal/scss/modules/_sidebar.scss +++ b/src/components/Elements/MainModal/scss/modules/_sidebar.scss @@ -14,7 +14,6 @@ flex-shrink: 0; transition: min-width 0.4s cubic-bezier(0.4, 0, 0.2, 1); - // Container for search bar and toggle button .sidebarHeader { display: flex; flex-direction: row-reverse; @@ -128,7 +127,6 @@ color: t($color); } - // Active indicator line &::before { content: ''; position: absolute; @@ -142,7 +140,6 @@ } } - // Collapsed state &.collapsed { min-width: 64px; padding: 0.75rem 0.25rem; @@ -230,7 +227,6 @@ margin-right: 0 !important; } -// Sidebar skeleton loader .sidebarSkeleton { padding: 0.5rem 0.2rem; diff --git a/src/components/Elements/MainModal/scss/settings/modules/tabs/_order.scss b/src/components/Elements/MainModal/scss/settings/modules/tabs/_order.scss index acaf0e79..696c7636 100644 --- a/src/components/Elements/MainModal/scss/settings/modules/tabs/_order.scss +++ b/src/components/Elements/MainModal/scss/settings/modules/tabs/_order.scss @@ -95,12 +95,10 @@ } } -// Enhanced custom images grid .images-grid { display: grid; padding: 1px; - // Show all checkboxes when in selection mode (any image selected) &.selection-mode { .image-checkbox { opacity: 1; @@ -187,7 +185,6 @@ } } - // Keep checkbox visible when checked &:has(input:checked) { opacity: 1; } @@ -335,7 +332,6 @@ } } - // Show delete button when card is hovered or has checkbox visible &:hover .delete-button, .image-checkbox:has(input:checked) ~ * .delete-button { opacity: 1; @@ -344,7 +340,6 @@ } } -// Storage quota display .storage-quota { padding: 15px 20px; margin-top: 10px; @@ -413,7 +408,6 @@ } } -// Folder tagging modal styles .taggingModalContent { padding: 20px; diff --git a/src/components/Elements/Tooltip/Tooltip.jsx b/src/components/Elements/Tooltip/Tooltip.jsx index 12e8322f..797370de 100644 --- a/src/components/Elements/Tooltip/Tooltip.jsx +++ b/src/components/Elements/Tooltip/Tooltip.jsx @@ -26,7 +26,6 @@ function Tooltip({ children, title, style, placement, subtitle }) { const { setFloating } = refs; const handleMouseEnter = () => { - // Clear any pending close timeout if mouse re-enters during exit if (closeTimeout.current) { clearTimeout(closeTimeout.current); closeTimeout.current = null; @@ -37,11 +36,10 @@ function Tooltip({ children, title, style, placement, subtitle }) { const handleMouseLeave = () => { setIsClosing(true); - // Wait for exit animation to complete before unmounting closeTimeout.current = setTimeout(() => { setShowTooltip(false); setIsClosing(false); - }, 200); // Match exit animation duration + }, 200); }; const handleFocus = () => { @@ -61,7 +59,6 @@ function Tooltip({ children, title, style, placement, subtitle }) { }, 200); }; - // Determine the data-status attribute value const getStatus = () => { if (!showTooltip && !isClosing) { return 'initial'; diff --git a/src/components/Form/Settings/ChipSelect/ChipSelect.jsx b/src/components/Form/Settings/ChipSelect/ChipSelect.jsx index d25ecafa..180c60df 100644 --- a/src/components/Form/Settings/ChipSelect/ChipSelect.jsx +++ b/src/components/Form/Settings/ChipSelect/ChipSelect.jsx @@ -24,7 +24,7 @@ function ChipSelect({ label, options, onChange, name }) { setTimeout(() => { setIsOpen(false); setIsClosing(false); - }, 200); // Match animation duration + }, 200); }, []); useEffect(() => { @@ -49,14 +49,11 @@ function ChipSelect({ label, options, onChange, name }) { const gap = 4; const viewportHeight = window.innerHeight; - // Estimate menu height const estimatedMenuHeight = Math.min(options.length * 44, 250); - // Calculate if dropdown would overflow bottom of viewport const spaceBelow = viewportHeight - rect.bottom - gap; const spaceAbove = rect.top - gap; - // If not enough space below but more space above, flip to top const shouldFlipUp = spaceBelow < estimatedMenuHeight && spaceAbove > spaceBelow; return { diff --git a/src/components/Form/Settings/DatePicker/DatePicker.jsx b/src/components/Form/Settings/DatePicker/DatePicker.jsx index ddaae235..022fca2c 100644 --- a/src/components/Form/Settings/DatePicker/DatePicker.jsx +++ b/src/components/Form/Settings/DatePicker/DatePicker.jsx @@ -118,12 +118,10 @@ const DatePicker = memo((props) => { const today = new Date(); const selectedDate = props.value; - // Empty cells for days before the first day of the month for (let i = 0; i < firstDay; i++) { days.push(
); } - // Days of the month for (let day = 1; day <= daysInMonth; day++) { const date = new Date(viewDate.getFullYear(), viewDate.getMonth(), day); const isToday = date.toDateString() === today.toDateString(); diff --git a/src/components/Form/Settings/Dropdown/Dropdown.jsx b/src/components/Form/Settings/Dropdown/Dropdown.jsx index a698edb8..b193f49b 100644 --- a/src/components/Form/Settings/Dropdown/Dropdown.jsx +++ b/src/components/Form/Settings/Dropdown/Dropdown.jsx @@ -28,7 +28,7 @@ const Dropdown = memo((props) => { setIsClosing(false); setFocusedIndex(-1); setSearchQuery(''); - }, 200); // Match animation duration + }, 200); }, []); useEffect(() => { @@ -47,7 +47,6 @@ const Dropdown = memo((props) => { return () => document.removeEventListener('mousedown', handleClickOutside); }, [closeDropdown]); - // Memoize items count to avoid unnecessary recalculations const itemsCount = useMemo(() => props.items.filter((i) => i !== null).length, [props.items]); const calculatePosition = useCallback(() => { @@ -56,14 +55,11 @@ const Dropdown = memo((props) => { const gap = 4; const viewportHeight = window.innerHeight; - // Estimate menu height (will be more accurate after first render) const estimatedMenuHeight = Math.min(itemsCount * 44, 250); - // Calculate if dropdown would overflow bottom of viewport const spaceBelow = viewportHeight - rect.bottom - gap; const spaceAbove = rect.top - gap; - // If not enough space below but more space above, flip to top const shouldFlipUp = spaceBelow < estimatedMenuHeight && spaceAbove > spaceBelow; return { @@ -83,7 +79,6 @@ const Dropdown = memo((props) => { setIsOpen(true); }, [calculatePosition]); - // Update dropdown position on scroll or resize useEffect(() => { if (!isOpen) return; @@ -98,11 +93,9 @@ const Dropdown = memo((props) => { }); }; - // Listen to window scroll and resize window.addEventListener('scroll', updatePosition, { passive: true }); window.addEventListener('resize', updatePosition, { passive: true }); - // Find and listen to scrollable ancestors let element = controlRef.current?.parentElement; const scrollableElements = []; while (element) { @@ -127,7 +120,6 @@ const Dropdown = memo((props) => { useEffect(() => { if (isOpen && props.searchable && searchInputRef.current) { - // Focus the search input when dropdown opens setTimeout(() => searchInputRef.current?.focus(), 0); } }, [isOpen, props.searchable]); @@ -153,7 +145,6 @@ const Dropdown = memo((props) => { ); const handleInputFocus = useCallback(() => { - // When focusing, if not default value, pre-fill with current value for editing const defaultValue = props.default || props.items[0]?.value; if (value !== defaultValue && !searchQuery) { const currentItem = props.items.find((item) => item?.value === value); @@ -257,7 +248,6 @@ const Dropdown = memo((props) => { e.stopPropagation(); setSearchQuery(''); if (props.searchable) { - // Reset to default value (first item, usually "Automatic") const defaultValue = props.default || props.items[0]?.value; onChange(defaultValue); } @@ -273,7 +263,6 @@ const Dropdown = memo((props) => { const selectedItem = props.items.find((item) => item?.value === value); const defaultValue = props.default || props.items[0]?.value; - // Filter items based on search query const filteredItems = props.searchable && searchQuery ? props.items.filter( diff --git a/src/components/Form/Settings/FileUpload/FileUpload.jsx b/src/components/Form/Settings/FileUpload/FileUpload.jsx index 9451781b..11571285 100644 --- a/src/components/Form/Settings/FileUpload/FileUpload.jsx +++ b/src/components/Form/Settings/FileUpload/FileUpload.jsx @@ -22,11 +22,9 @@ const FileUpload = memo(({ id, type, accept, loadFunction, multiple }) => { return loadFunction(e.target.result); }; } else { - // Pass files directly to loadFunction if it's a newer implementation if (typeof loadFunction === 'function' && loadFunction.length === 1) { loadFunction(files); } else { - // Legacy background upload - handle multiple files const settings = {}; Object.keys(localStorage).forEach((key) => { @@ -35,7 +33,6 @@ const FileUpload = memo(({ id, type, accept, loadFunction, multiple }) => { const settingsSize = new TextEncoder().encode(JSON.stringify(settings)).length; - // Process each file files.forEach((file, index) => { if (videoCheck(file.type) === true) { if (settingsSize + file.size > 4850000) { diff --git a/src/components/Form/Settings/LocationSearch/LocationSearch.jsx b/src/components/Form/Settings/LocationSearch/LocationSearch.jsx index 47a8022e..d0aaa2fc 100644 --- a/src/components/Form/Settings/LocationSearch/LocationSearch.jsx +++ b/src/components/Form/Settings/LocationSearch/LocationSearch.jsx @@ -11,7 +11,6 @@ import './LocationSearch.scss'; const LocationSearch = memo((props) => { const { label, name, category, placeholder, disabled } = props; - // Load location data from localStorage (new JSON format or legacy string) const [locationData, setLocationData] = useState(() => { const stored = localStorage.getItem(name); if (!stored) return null; @@ -22,7 +21,6 @@ const LocationSearch = memo((props) => { return parsed; } } catch { - // Legacy format: plain string city name return { displayName: stored, legacy: true }; } return null; @@ -139,7 +137,6 @@ const LocationSearch = memo((props) => { } }, [isOpen]); - // Debounced search function const debouncedSearch = useDebouncedCallback(async (query) => { if (query.length < 2) { setSuggestions([]); @@ -147,7 +144,6 @@ const LocationSearch = memo((props) => { return; } - // Cancel previous request if (abortControllerRef.current) { abortControllerRef.current.abort(); } diff --git a/src/components/Form/Settings/Slider/Slider.jsx b/src/components/Form/Settings/Slider/Slider.jsx index 825f3421..32a89474 100644 --- a/src/components/Form/Settings/Slider/Slider.jsx +++ b/src/components/Form/Settings/Slider/Slider.jsx @@ -56,7 +56,6 @@ const SliderComponent = memo((props) => { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); - // Easing function for smooth animation const easeOutCubic = 1 - Math.pow(1 - progress, 3); const currentValue = startValue + (endValue - startValue) * easeOutCubic; @@ -69,7 +68,6 @@ const SliderComponent = memo((props) => { if (progress < 1) { animationRef.current = requestAnimationFrame(animate); } else { - // Ensure we end exactly at the target value localStorage.setItem(props.name, endValue); setValue(endValue); EventBus.emit('refresh', props.category); diff --git a/src/components/Form/Settings/Textarea/Textarea.jsx b/src/components/Form/Settings/Textarea/Textarea.jsx index 45521cc2..22577855 100644 --- a/src/components/Form/Settings/Textarea/Textarea.jsx +++ b/src/components/Form/Settings/Textarea/Textarea.jsx @@ -12,20 +12,16 @@ const Textarea = memo( return; } - // Reset height to auto to get the correct scrollHeight textarea.style.height = 'auto'; - // Calculate line height const computedStyle = window.getComputedStyle(textarea); const lineHeight = parseInt(computedStyle.lineHeight) || 24; const paddingTop = parseInt(computedStyle.paddingTop) || 0; const paddingBottom = parseInt(computedStyle.paddingBottom) || 0; - // Calculate min and max heights const minHeight = minRows * lineHeight + paddingTop + paddingBottom; const maxHeight = maxRows ? maxRows * lineHeight + paddingTop + paddingBottom : Infinity; - // Set the height based on content, clamped between min and max const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight); textarea.style.height = `${newHeight}px`; }, [minRows, maxRows]); @@ -34,7 +30,6 @@ const Textarea = memo( adjustHeight(); }, [value, adjustHeight]); - // Adjust on mount and window resize useEffect(() => { adjustHeight(); window.addEventListener('resize', adjustHeight); diff --git a/src/components/Layout/Settings/Header/Header.jsx b/src/components/Layout/Settings/Header/Header.jsx index 42244db1..bad40688 100644 --- a/src/components/Layout/Settings/Header/Header.jsx +++ b/src/components/Layout/Settings/Header/Header.jsx @@ -23,7 +23,6 @@ function Header(props) { const changeSetting = () => { const toggle = localStorage.getItem(props.setting) === 'true'; - // Small delay to let the button click animation complete setTimeout(() => { localStorage.setItem(props.setting, !toggle); setSetting(!toggle); diff --git a/src/config/constants.js b/src/config/constants.js index 8997a3b3..55051da1 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -1,10 +1,8 @@ -// API URLs export const API_URL = 'https://api.muetab.com/v2'; export const SPONSORS_URL = 'https://sponsors.muetab.com'; export const GITHUB_URL = 'https://api.github.com'; export const OPENSTREETMAP_URL = 'https://www.openstreetmap.org'; -// Mue URLs export const WEBSITE_URL = 'https://muetab.com'; export const MARKETPLACE_URL = 'https://muetab.com/marketplace'; export const CHANGELOG_URL = 'https://muetab.com/blog/changelog'; @@ -18,7 +16,6 @@ export const SENTRY_DSN = 'https://430352fd4b174d688ebd82fc85c22c58@o1217438.ingest.sentry.io/6359480'; export const KNOWLEDGEBASE = 'https://support.muetab.com'; -// Mue Info export const ORG_NAME = 'mue'; export const REPO_NAME = 'mue'; export const EMAIL = 'hello@muetab.com'; diff --git a/src/contexts/TranslationContext.jsx b/src/contexts/TranslationContext.jsx index e32cc175..5c9a5673 100644 --- a/src/contexts/TranslationContext.jsx +++ b/src/contexts/TranslationContext.jsx @@ -17,7 +17,6 @@ export function TranslationProvider({ children, initialLanguage }) { const [currentLanguage, setCurrentLanguage] = useState(initialLanguage); const i18nInstance = useRef(initTranslations(initialLanguage)); - // Update i18n instance when language changes useEffect(() => { if (currentLanguage !== initialLanguage) { i18nInstance.current = initTranslations(currentLanguage); @@ -27,16 +26,13 @@ export function TranslationProvider({ children, initialLanguage }) { document.documentElement.lang = currentLanguage.replace('_', '-'); }, [currentLanguage, initialLanguage]); - // Change language function const changeLanguage = useCallback( (newLanguage) => { - // Update the i18n instance i18nInstance.current = initTranslations(newLanguage); variables.language = i18nInstance.current; variables.languagecode = newLanguage; document.documentElement.lang = newLanguage.replace('_', '-'); - // Update tab name if it's still the default const currentTabName = localStorage.getItem('tabName'); const oldDefaultTabName = i18nInstance.current?.getMessage(currentLanguage, 'tabname'); @@ -49,19 +45,15 @@ export function TranslationProvider({ children, initialLanguage }) { document.title = newTabName; } - // Update language in localStorage localStorage.setItem('language', newLanguage); - // Clear weather cache so it refreshes with the new language localStorage.removeItem('currentWeather'); - // Update state to trigger re-render setCurrentLanguage(newLanguage); }, [currentLanguage], ); - // Single translation function - the main API const t = useCallback( (key, optional = {}) => { if (!i18nInstance.current) { @@ -72,7 +64,6 @@ export function TranslationProvider({ children, initialLanguage }) { [currentLanguage], ); - // Listen for EventBus language change events (for backward compatibility) useEffect(() => { const handleLanguageChange = (data) => { if (data?.language) { @@ -87,7 +78,6 @@ export function TranslationProvider({ children, initialLanguage }) { }; }, [changeLanguage]); - // Update variables.getMessage for backward compatibility useEffect(() => { variables.getMessage = (key, optional = {}) => t(key, optional); }, [t]); @@ -95,7 +85,7 @@ export function TranslationProvider({ children, initialLanguage }) { const value = useMemo( () => ({ language: currentLanguage, - languagecode: currentLanguage, // Alias for backward compatibility + languagecode: currentLanguage, changeLanguage, t, }), @@ -113,7 +103,6 @@ export function useTranslation() { return context; } -// Convenience hook - just returns the t function export function useT() { const { t } = useTranslation(); return t; diff --git a/src/features/background/api/backgroundFilters.js b/src/features/background/api/backgroundFilters.js index dcca7b4a..5aaa0983 100644 --- a/src/features/background/api/backgroundFilters.js +++ b/src/features/background/api/backgroundFilters.js @@ -28,14 +28,12 @@ export function getBackgroundOverlayStyle() { const backgroundFilter = localStorage.getItem('backgroundFilter'); const backgroundFilterAmount = localStorage.getItem('backgroundFilterAmount') || '100'; - // Build backdrop-filter string for blur and other effects let backdropFilterString = `blur(${blur}px)`; if (backgroundFilter && backgroundFilter !== 'none') { backdropFilterString += ` ${backgroundFilter}(${backgroundFilterAmount}%)`; } - // Calculate brightness overlay (black overlay for darkening) // brightness 100% = no overlay (opacity 0) // brightness 0% = full black overlay (opacity 1) const brightnessValue = parseInt(brightness, 10); @@ -43,7 +41,7 @@ export function getBackgroundOverlayStyle() { return { backdropFilter: backdropFilterString, - WebkitBackdropFilter: backdropFilterString, // Safari support + WebkitBackdropFilter: backdropFilterString, backgroundColor: `rgba(0, 0, 0, ${overlayOpacity})`, }; } diff --git a/src/features/background/api/backgroundLoader.js b/src/features/background/api/backgroundLoader.js index 523515be..63a7c68c 100644 --- a/src/features/background/api/backgroundLoader.js +++ b/src/features/background/api/backgroundLoader.js @@ -82,7 +82,6 @@ async function getAPIBackground(isOffline) { const queueManager = new BackgroundQueueManager('imageQueue', 3); let data; - // Use cached next image if available const cachedQueue = queueManager.getQueue(); if (cachedQueue.length > 0) { data = queueManager.shift(); @@ -98,7 +97,6 @@ async function getAPIBackground(isOffline) { console.warn('Could not save currentBackground to localStorage:', e); } - // Pre-fetch next images in the background if (queueManager.needsPrefetch()) { prefetchAPIImages(queueManager, data, cachedQueue).catch((error) => { console.error('Failed to prefetch API images:', error); @@ -121,7 +119,6 @@ async function prefetchAPIImages(queueManager, currentImage, currentQueue) { ...currentQueue.map((img) => img.photoInfo?.pun).filter(Boolean), ]; - // Prefetch remaining images asynchronously const newImages = await Promise.all( Array.from({ length: count }, (_, i) => fetchAPIImageData(excludedPuns[i] || currentImage.photoInfo.pun), @@ -138,14 +135,11 @@ async function prefetchAPIImages(queueManager, currentImage, currentQueue) { * Gets custom background with prefetching */ async function getCustomBackground(isOffline) { - // Get full metadata from IndexedDB let backgrounds = await getAllBackgroundsWithMetadata(); - // Fallback to localStorage URLs if IndexedDB is empty if (!backgrounds || backgrounds.length === 0) { const urls = safeParseJSON('customBackground', []); if (urls && urls.length > 0) { - // Convert old URL format to metadata format backgrounds = urls.map((url) => ({ url, photoInfo: { hidden: true } })); } } @@ -157,24 +151,18 @@ async function getCustomBackground(isOffline) { const queueManager = new BackgroundQueueManager('customQueue', 3); let selected; - // Use cached next background ID if available const cachedQueue = queueManager.getQueue(); if (cachedQueue.length > 0) { - // Queue contains IDs only, not full data const queuedId = queueManager.shift(); - // Look up the full background data by ID selected = backgrounds.find((bg) => bg.id === queuedId); - // If not found (maybe deleted), pick random if (!selected) { selected = backgrounds[Math.floor(Math.random() * backgrounds.length)]; } } else { - // Pick random background selected = backgrounds[Math.floor(Math.random() * backgrounds.length)]; } - // Check if selected is valid before using it if (!selected) { return null; } @@ -196,8 +184,6 @@ async function getCustomBackground(isOffline) { return getOfflineImage('custom'); } - // Don't store full image data in localStorage to avoid quota errors - // Just store metadata try { localStorage.setItem( 'currentBackground', @@ -208,11 +194,9 @@ async function getCustomBackground(isOffline) { }), ); } catch (e) { - // Ignore quota errors for currentBackground console.warn('Could not save currentBackground to localStorage:', e); } - // Prefetch next backgrounds if needed if (queueManager.needsPrefetch()) { const count = queueManager.getSpaceNeeded(); const currentIds = [selected.id, ...cachedQueue]; @@ -245,7 +229,6 @@ export async function getBackgroundData() { localStorage.getItem('offlineMode') === 'true' || localStorage.getItem('showWelcome') === 'true'; - // Handle favourited background const fav = safeParseJSON('favourite'); if (fav) { if (fav.type === 'random_colour' || fav.type === 'random_gradient') { @@ -289,21 +272,17 @@ export async function getBackgroundData() { async function prefetchCustomBackgrounds(queueManager, allBackgrounds, currentId, currentQueue) { const count = queueManager.getSpaceNeeded(); - // Get already used IDs (queue now contains IDs only) const usedIds = [currentId, ...currentQueue.filter(Boolean)]; - // Filter available (exclude videos from prefetch and already used) const available = allBackgrounds.filter( (bg) => bg.id && !usedIds.includes(bg.id) && !videoCheck(bg.url || bg), ); if (available.length === 0) return; - // Shuffle and take N const shuffled = available.sort(() => Math.random() - 0.5); const selected = shuffled.slice(0, count); - // Store only IDs to avoid quota issues (custom backgrounds use large data URLs) const ids = selected.map((bg) => bg.id).filter(Boolean); if (ids.length > 0) { @@ -318,7 +297,6 @@ async function prefetchCustomBackgrounds(queueManager, allBackgrounds, currentId function getPhotoPackBackground(isOffline) { if (isOffline) return getOfflineImage('photo_pack'); - // Build combined pool from static and API packs const pool = buildPhotoPool(); if (pool.length === 0) return null; @@ -326,12 +304,10 @@ function getPhotoPackBackground(isOffline) { const queueManager = new BackgroundQueueManager('photoPackQueue', 3); let photoData; - // Use cached next photo if available const cachedQueue = queueManager.getQueue(); if (cachedQueue.length > 0) { photoData = queueManager.shift(); } else { - // Pick random photo from pool const selected = pool[Math.floor(Math.random() * pool.length)]; photoData = { @@ -355,7 +331,6 @@ function getPhotoPackBackground(isOffline) { console.warn('Could not save currentBackground to localStorage:', e); } - // Prefetch more photos in the background if (queueManager.needsPrefetch()) { prefetchPhotoPackImages(queueManager, pool, photoData, cachedQueue).catch((error) => { console.error('Failed to prefetch photo pack images:', error); @@ -376,10 +351,8 @@ function getPhotoPackBackground(isOffline) { async function prefetchPhotoPackImages(queueManager, pool, currentPhoto, currentQueue) { const count = queueManager.getSpaceNeeded(); - // Get already used URLs const usedUrls = [currentPhoto.url, ...currentQueue.map((p) => p.url)]; - // Filter available photos (handle both URL formats) const available = pool.filter((p) => { const url = p.url.default || p.url; return !usedUrls.includes(url) && !usedUrls.includes(getProxiedImageUrl(url)); @@ -387,11 +360,9 @@ async function prefetchPhotoPackImages(queueManager, pool, currentPhoto, current if (available.length === 0) return; - // Shuffle and take N const shuffled = available.sort(() => Math.random() - 0.5); const selected = shuffled.slice(0, count); - // Normalize metadata const normalized = selected.map((photo) => ({ url: getProxiedImageUrl(photo.url.default || photo.url), type: 'photo_pack', @@ -408,6 +379,5 @@ async function prefetchPhotoPackImages(queueManager, pool, currentPhoto, current queueManager.push(normalized); - // Check if any API pack cache needs refresh checkAndRefreshAPIPacks(); } diff --git a/src/features/background/api/blobUrl.js b/src/features/background/api/blobUrl.js index 713ca1b9..6f45be76 100644 --- a/src/features/background/api/blobUrl.js +++ b/src/features/background/api/blobUrl.js @@ -17,7 +17,6 @@ export async function createBlobUrl(url) { const blob = await response.blob(); return URL.createObjectURL(blob); } catch { - // Silently fail - we'll use direct URL as fallback return null; } } diff --git a/src/features/background/api/photoPackAPI.js b/src/features/background/api/photoPackAPI.js index bf5c7a45..2e4640ee 100644 --- a/src/features/background/api/photoPackAPI.js +++ b/src/features/background/api/photoPackAPI.js @@ -1,6 +1,5 @@ import variables from 'config/variables'; -// Provider-specific response parsers const responseParser = { pexels: (data, query) => { const photos = data.photos || []; @@ -37,7 +36,6 @@ const responseParser = { if (photos.length === 0) return null; const photo = photos[Math.floor(Math.random() * photos.length)]; - // Get photo details const infoParams = new URLSearchParams({ method: 'flickr.photos.getInfo', api_key: apiKey, @@ -59,7 +57,6 @@ const responseParser = { }; }, - // Default parser for Mue backend (already returns correct format) default: (data) => ({ photographer: data.photographer, location: data.location?.name || data.location || 'Unknown', @@ -92,9 +89,7 @@ export async function fetchFromProvider(packId, pack, settings) { const headers = {}; let apiKey = null; - // Build query params and headers based on provider if (pack.direct_api) { - // Direct API calls - provider-specific logic switch (pack.api_provider) { case 'pexels': apiKey = settings[`photoPack_${packId}_api_key`]; @@ -144,7 +139,6 @@ export async function fetchFromProvider(packId, pack, settings) { break; } } else { - // Backend proxy - send settings as params (backend handles the rest) pack.settings_schema?.forEach((setting) => { let value = settings[`photoPack_${packId}_${setting.id}`]; if (setting.secure && value) { @@ -168,7 +162,6 @@ export async function fetchFromProvider(packId, pack, settings) { const data = await response.json(); - // Parse response using provider-specific parser const parser = responseParser[pack.api_provider] || responseParser.default; return await parser(data, params, apiKey ? atob(apiKey) : null); @@ -217,7 +210,6 @@ export async function refreshAPIPackCache(packId) { const settings = JSON.parse(localStorage.getItem(`photopack_settings_${packId}`) || '{}'); - // Use generic provider function that works for any API const promises = Array.from({ length: 8 }, () => fetchFromProvider(packId, pack, settings)); const results = await Promise.all(promises); const validPhotos = results.filter(Boolean); @@ -227,7 +219,6 @@ export async function refreshAPIPackCache(packId) { return false; } - // Update cache const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); apiPackCache[packId] = { photos: validPhotos, @@ -240,7 +231,6 @@ export async function refreshAPIPackCache(packId) { return true; } catch (error) { if (error.name === 'QuotaExceededError') { - // Keep only 5 most recent photos apiPackCache[packId].photos = validPhotos.slice(0, 5); localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); } @@ -257,14 +247,12 @@ export async function checkAndRefreshAPIPacks() { const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); const installed = JSON.parse(localStorage.getItem('installed') || '[]'); - // Default 1 hour refresh interval const DEFAULT_CACHE_REFRESH_INTERVAL = 3600 * 1000; // 1 hour in milliseconds for (const packId of apiPacksReady) { const pack = installed.find((p) => p.id === packId); const cached = apiPackCache[packId]; - // Use pack-specific interval or default const refreshInterval = pack?.cache_refresh_interval ? pack.cache_refresh_interval * 1000 : DEFAULT_CACHE_REFRESH_INTERVAL; @@ -275,7 +263,6 @@ export async function checkAndRefreshAPIPacks() { cached.photos.length < 3; if (needsRefresh) { - // Don't block - refresh in background refreshAPIPackCache(packId).catch((error) => { console.error(`Failed to refresh ${packId}:`, error); }); @@ -297,16 +284,13 @@ export function buildPhotoPool() { installed.forEach((pack) => { if (pack.type !== 'photos') return; - // Filter by enabled status - default to enabled if not in enabledPacks object const packId = pack.id || pack.name; if (enabledPacks[packId] === false) return; if (pack.api_enabled) { - // API pack - check if configured and ready if (apiPacksReady.includes(pack.id)) { const cached = apiPackCache[pack.id]; if (cached && cached.photos.length > 0) { - // Add cached photos with source metadata cached.photos.forEach((photo) => { pool.push({ ...photo, @@ -317,7 +301,6 @@ export function buildPhotoPool() { } } } else { - // Static pack - add all photos pack.photos.forEach((photo) => { pool.push({ photographer: photo.photographer, diff --git a/src/features/background/components/PhotoInformation.jsx b/src/features/background/components/PhotoInformation.jsx index 719704fa..5cab43f0 100644 --- a/src/features/background/components/PhotoInformation.jsx +++ b/src/features/background/components/PhotoInformation.jsx @@ -251,11 +251,9 @@ function PhotoInformation({ info, url, api }) { setPhotoMap(true); setMapIcon(false); } catch { - // Ignore errors } }; } catch { - // Element not found } const widgetStyle = localStorage.getItem('widgetStyle'); diff --git a/src/features/background/hooks/useBackgroundEvents.js b/src/features/background/hooks/useBackgroundEvents.js index 3bff4551..00508fd4 100644 --- a/src/features/background/hooks/useBackgroundEvents.js +++ b/src/features/background/hooks/useBackgroundEvents.js @@ -23,7 +23,6 @@ export function useBackgroundEvents(backgroundData, refreshBackground) { element?.style.setProperty('display', 'block'); if (!backgroundData.photoInfo?.hidden) {photoInfo?.style.setProperty('display', 'flex');} - // Check if refresh needed const type = localStorage.getItem('backgroundType'); const needsRefresh = (type !== backgroundData.type && @@ -40,13 +39,11 @@ export function useBackgroundEvents(backgroundData, refreshBackground) { }; const applyFilters = () => { - // For video backgrounds, apply filters directly to the video element if (backgroundData.video) { const filter = getBackgroundFilterStyle(); const element = document.getElementById('backgroundVideo'); if (element) {element.style.webkitFilter = filter;} } else { - // For image backgrounds, apply filters to the overlay element const overlayElement = document.getElementById('backgroundFilterOverlay'); if (overlayElement) { const overlayStyle = getBackgroundOverlayStyle(); diff --git a/src/features/background/hooks/useBackgroundLoader.js b/src/features/background/hooks/useBackgroundLoader.js index 7e23de3d..ea6bf126 100644 --- a/src/features/background/hooks/useBackgroundLoader.js +++ b/src/features/background/hooks/useBackgroundLoader.js @@ -13,7 +13,6 @@ export function useBackgroundLoader(updateBackground, resetBackground) { isLoadingRef.current = true; try { - // Check for welcome tab first const welcomeTab = localStorage.getItem('welcomeTab'); if (welcomeTab) { const welcomeImage = localStorage.getItem('welcomeImage'); @@ -26,7 +25,7 @@ export function useBackgroundLoader(updateBackground, resetBackground) { const data = await getBackgroundData(); if (data) { updateBackground(data); - resetStartTime('background'); // Reset timestamp after successful load + resetStartTime('background'); } } catch (error) { console.error('Failed to load background:', error); @@ -36,24 +35,20 @@ export function useBackgroundLoader(updateBackground, resetBackground) { }, [updateBackground]); const refreshBackground = useCallback(() => { - resetStartTime('background'); // Reset timer on manual refresh + resetStartTime('background'); resetBackground(); loadBackground(); }, [loadBackground, resetBackground]); - // Initial load - check frequency before loading useEffect(() => { - // Check if we should update based on frequency if (shouldUpdateByFrequency('background')) { loadBackground(); } else { - // Load cached background without fetching new one const cached = localStorage.getItem('currentBackground'); if (cached) { try { updateBackground(JSON.parse(cached)); } catch { - // If cache invalid, load new loadBackground(); } } else { diff --git a/src/features/background/hooks/useBackgroundRenderer.js b/src/features/background/hooks/useBackgroundRenderer.js index fc6bbeed..796ab17e 100644 --- a/src/features/background/hooks/useBackgroundRenderer.js +++ b/src/features/background/hooks/useBackgroundRenderer.js @@ -3,7 +3,6 @@ import { createBlobUrl } from '../api/blobUrl'; import { generateBlurHashDataUrl } from '../api/blurHash'; import { getBackgroundFilterStyle, getBackgroundOverlayStyle } from '../api/backgroundFilters'; -// Constants const TRANSITION_DURATION = 1200; // milliseconds /** @@ -19,7 +18,6 @@ export function useBackgroundRenderer(backgroundData) { const element = document.getElementById('backgroundImage'); if (!element) {return;} - // Abort any pending image loads if (abortControllerRef.current) { abortControllerRef.current.abort(); } @@ -36,21 +34,17 @@ export function useBackgroundRenderer(backgroundData) { return; } - // Create or get overlay element for smooth transitions let overlay = document.getElementById('backgroundOverlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'backgroundOverlay'; - // Insert right after the background element element.parentNode.insertBefore(overlay, element.nextSibling); } - // Set background color if (backgroundData.photoInfo?.colour) { element.style.backgroundColor = backgroundData.photoInfo.colour; } - // Generate and show blur hash immediately on main element if (backgroundData.photoInfo?.blur_hash) { const blurHashUrl = generateBlurHashDataUrl(backgroundData.photoInfo.blur_hash, 64, 64); if (blurHashUrl) { @@ -58,7 +52,6 @@ export function useBackgroundRenderer(backgroundData) { } } - // Load the full image const blobUrl = await createBlobUrl(backgroundData.url); const finalUrl = blobUrl || backgroundData.url; @@ -69,7 +62,6 @@ export function useBackgroundRenderer(backgroundData) { blobRef.current = blobUrl; } - // Preload the image const img = new Image(); img.src = finalUrl; @@ -78,30 +70,22 @@ export function useBackgroundRenderer(backgroundData) { img.onerror = resolve; }); - // CRITICAL: Reset overlay to invisible FIRST overlay.style.transition = 'none'; overlay.style.opacity = '0'; overlay.style.backgroundImage = ''; - // Force a reflow to ensure opacity is actually 0 void overlay.offsetHeight; - // Now set the new image overlay.style.backgroundImage = `url(${finalUrl})`; - // Force another reflow void overlay.offsetHeight; - // Re-enable transition overlay.style.transition = `opacity ${TRANSITION_DURATION / 1000}s ease-in-out`; - // Force another reflow before changing opacity void overlay.offsetHeight; - // Now fade it in overlay.style.opacity = '1'; - // After fade completes, swap to main element and reset overlay setTimeout(() => { element.style.backgroundImage = `url(${finalUrl})`; overlay.style.opacity = '0'; @@ -115,17 +99,14 @@ export function useBackgroundRenderer(backgroundData) { applyBackground(); return () => { - // Cleanup blob URL if (blobRef.current) { URL.revokeObjectURL(blobRef.current); } - // Abort pending image loads if (abortControllerRef.current) { abortControllerRef.current.abort(); } - // Remove overlay element to prevent memory leak const overlay = document.getElementById('backgroundOverlay'); if (overlay) { overlay.remove(); diff --git a/src/features/background/options/BackgroundOptions.jsx b/src/features/background/options/BackgroundOptions.jsx index fbe76b02..ec6b2469 100644 --- a/src/features/background/options/BackgroundOptions.jsx +++ b/src/features/background/options/BackgroundOptions.jsx @@ -39,7 +39,6 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section ); const [marketplaceEnabled] = useState(localStorage.getItem('photo_packs')); - // Helper function to get installed photo packs const getInstalledPhotoPacks = () => { try { const installed = JSON.parse(localStorage.getItem('installed')) || []; @@ -51,14 +50,12 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section const [installedPhotoPacks, setInstalledPhotoPacks] = useState(getInstalledPhotoPacks()); - // Auto-show source section for types without effects/display settings const shouldShowSourceByDefault = ['colour', 'random_colour', 'random_gradient'].includes( backgroundType, ); const controllerRef = useRef(null); - // Auto-navigate to source section when switching to colour/random types useEffect(() => { if (shouldShowSourceByDefault && currentSubSection !== 'source') { onSubSectionChange('source', sectionName); @@ -90,7 +87,6 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section const updateAPI = useCallback( (e) => { localStorage.setItem('nextImage', null); - // Clear prefetch queue when API changes to prevent showing cached images from old API clearQueuesOnSettingChange('backgroundAPI'); if (e === 'mue') { setBackgroundCategories(backgroundCategoriesOG); @@ -123,11 +119,9 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section }; }, [getBackgroundCategories]); - // Listen for installed addons changes useEffect(() => { const handleInstalledAddonsChanged = () => { setInstalledPhotoPacks(getInstalledPhotoPacks()); - // Update backgroundType if it changed (e.g., when all packs are uninstalled) const currentType = localStorage.getItem('backgroundType') || 'api'; if (currentType !== backgroundType) { setBackgroundType(currentType); @@ -138,7 +132,6 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section return () => window.removeEventListener('installedAddonsChanged', handleInstalledAddonsChanged); }, [backgroundType]); - // Handle photo pack uninstall const handlePhotoPackUninstall = (type, name) => { uninstall(type, name); toast(variables.getMessage('toasts.uninstalled')); @@ -147,7 +140,6 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section window.dispatchEvent(new window.Event('installedAddonsChanged')); }; - // Navigate to photo packs marketplace const goToPhotoPacks = () => { updateHash('#discover/photo_packs'); const event = new window.Event('popstate'); @@ -155,18 +147,15 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section }; const handleToggle = (pack) => { - // Navigate to discover tab with the item const itemId = pack.name; updateHash(`#discover/all?item=${itemId}`); - // Trigger navigation const event = new window.Event('popstate'); window.dispatchEvent(event); variables.stats.postEvent('marketplace', 'ItemPage viewed'); }; - // Get total count of photos across all installed packs const getTotalPhotoCount = () => { return installedPhotoPacks.reduce((total, pack) => { return total + (pack.photos?.length || 0); @@ -264,10 +253,8 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section label={variables.getMessage('modals.main.settings.sections.background.type.title')} name="backgroundType" onChange={(value) => { - // Clear prefetch queue when changing background type clearQueuesOnSettingChange('backgroundType'); setBackgroundType(value); - // Automatically refresh background when switching to custom images if (value === 'custom') { EventBus.emit('refresh', 'background'); } @@ -301,7 +288,6 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section installedPhotoPacks={installedPhotoPacks} totalPhotoCount={getTotalPhotoCount()} onTypeChange={(value) => { - // Clear prefetch queue when changing background type clearQueuesOnSettingChange('backgroundType'); setBackgroundType(value); }} diff --git a/src/features/background/options/Custom.jsx b/src/features/background/options/Custom.jsx index 1eba8d89..da2cabfd 100644 --- a/src/features/background/options/Custom.jsx +++ b/src/features/background/options/Custom.jsx @@ -62,10 +62,8 @@ const CustomSettings = memo(() => { const customDnd = useRef(null); const dragCounter = useRef(0); - // IndexedDB typically has 50MB+ quota, we'll check dynamically const FALLBACK_STORAGE_LIMIT = 50000000; // 50MB fallback if API unavailable - // Fetch storage quota useEffect(() => { const fetchQuota = async () => { if (navigator.storage && navigator.storage.estimate) { @@ -86,25 +84,20 @@ const CustomSettings = memo(() => { fetchQuota(); }, [customBackground]); - // Load backgrounds from IndexedDB on mount useEffect(() => { const loadBackgrounds = async () => { try { - // Try migration first await migrateFromLocalStorage(); - // Load from IndexedDB const backgrounds = await getAllBackgroundsWithMetadata(); setCustomBackground(backgrounds); - // Backfill missing metadata for existing images backgrounds.forEach(async (bg) => { if (!bg.dimensions && bg.url && !videoCheck(bg.url)) { try { const dimensions = await getImageDimensions(bg.url); const blurHash = await generateBlurHash(bg.url); await updateBackgroundMetadata(bg.id, { dimensions, blurHash }); - // Reload backgrounds to show updated metadata const updatedBackgrounds = await getAllBackgroundsWithMetadata(); setCustomBackground(updatedBackgrounds); } catch (error) { @@ -139,7 +132,6 @@ const CustomSettings = memo(() => { await addBackground(backgroundData); - // Reload from IndexedDB to get the latest state and update React state const backgrounds = await getAllBackgroundsWithMetadata(); setCustomBackground(backgrounds); @@ -150,7 +142,6 @@ const CustomSettings = memo(() => { localStorage.setItem('customBackgroundCount', backgrounds.length.toString()); } - // Only emit refresh if not part of a batch upload if (!skipRefresh) { EventBus.emit('refresh', 'background'); } @@ -163,7 +154,6 @@ const CustomSettings = memo(() => { ); const processImageFile = async (file, folderName = '') => { - // Calculate actual storage from existing backgrounds const storageSize = customBackground.reduce((total, bg) => { if (bg.url && bg.url.startsWith('data:')) { return total + getDataUrlSize(bg.url); @@ -173,7 +163,6 @@ const CustomSettings = memo(() => { const availableQuota = storageQuota.quota || FALLBACK_STORAGE_LIMIT; - // Request persistent storage if approaching limit (90%) if (storageSize / availableQuota > 0.9 && navigator.storage && navigator.storage.persist) { try { const isPersisted = await navigator.storage.persist(); @@ -194,7 +183,6 @@ const CustomSettings = memo(() => { return new Promise((resolve, reject) => { reader.onloadend = async () => { try { - // Extract thumbnail and dimensions from video const { thumbnail, dimensions } = await extractVideoThumbnail(reader.result); resolve({ @@ -210,7 +198,6 @@ const CustomSettings = memo(() => { }); } catch (error) { console.warn('Could not extract video thumbnail:', error); - // Fallback to no thumbnail if extraction fails resolve({ dataUrl: reader.result, metadata: { @@ -228,7 +215,6 @@ const CustomSettings = memo(() => { reader.readAsDataURL(file); }); } else { - // Compress image const compressed = await compressAccurately(file, { size: 450, accuracy: 0.9, @@ -241,10 +227,9 @@ const CustomSettings = memo(() => { const dataUrl = await filetoDataURL(compressed); - // Generate metadata in parallel const [dimensions, blurHash] = await Promise.all([ getImageDimensions(dataUrl), - generateBlurHash(dataUrl).catch(() => null), // Don't fail if blur hash fails + generateBlurHash(dataUrl).catch(() => null), ]); return { @@ -269,7 +254,6 @@ const CustomSettings = memo(() => { for (let i = 0; i < files.length; i++) { try { const result = await processImageFile(files[i], folderName); - // Skip refresh during batch upload to prevent background flashing await handleCustomBackground(files[i], result.dataUrl, result.metadata, true); setUploadProgress({ current: i + 1, total: files.length }); } catch (error) { @@ -285,7 +269,6 @@ const CustomSettings = memo(() => { toast(variables.getMessage('toasts.error') + `: ${errors.join(', ')}`); } - // Emit refresh once after all images are uploaded EventBus.emit('refresh', 'background'); setIsUploading(false); @@ -306,11 +289,9 @@ const CustomSettings = memo(() => { await deleteBackground(index); } - // Reload from IndexedDB to get the latest state const backgrounds = await getAllBackgroundsWithMetadata(); setCustomBackground(backgrounds); - // Store in localStorage with quota handling try { localStorage.setItem('customBackground', JSON.stringify(backgrounds.map((bg) => bg.url))); } catch (_quotaError) { @@ -334,12 +315,10 @@ const CustomSettings = memo(() => { const indices = Array.from(selectedImages).sort((a, b) => b - a); await deleteMultipleBackgrounds(indices); - // Reload from IndexedDB const backgrounds = await getAllBackgroundsWithMetadata(); setCustomBackground(backgrounds); setSelectedImages(new Set()); - // Update localStorage try { localStorage.setItem('customBackground', JSON.stringify(backgrounds.map((bg) => bg.url))); } catch (_quotaError) { @@ -385,11 +364,9 @@ const CustomSettings = memo(() => { setCustomURLModal(false); try { - // Extract filename from URL const urlParts = e.split('/'); const filename = urlParts[urlParts.length - 1].split('?')[0] || 'Remote Image'; - // Try to extract metadata from the remote image let dimensions = null; let blurHash = null; try { @@ -404,7 +381,7 @@ const CustomSettings = memo(() => { name: filename, uploadDate: Date.now(), dimensions, - fileSize: null, // Cannot determine file size for remote URLs without fetching + fileSize: null, folder: '', blurHash, }; @@ -433,16 +410,13 @@ const CustomSettings = memo(() => { const handleFileInputChange = async (files) => { if (files.length > 1) { - // Multiple files - show tagging modal setPendingFiles(files); setFolderTaggingModal(true); } else { - // Single file - upload directly await handleBatchUpload(files, ''); } }; - // Sorted backgrounds const sortedBackgrounds = [...customBackground].sort((a, b) => { switch (sortBy) { case 'date_asc': @@ -462,9 +436,7 @@ const CustomSettings = memo(() => { } }); - // Calculate storage usage from actual background data const storageUsed = customBackground.reduce((total, bg) => { - // Calculate size of the data URL if (bg.url && bg.url.startsWith('data:')) { return total + getDataUrlSize(bg.url); } @@ -517,11 +489,9 @@ const CustomSettings = memo(() => { } if (files.length > 1) { - // Multiple files - show tagging modal setPendingFiles(files); setFolderTaggingModal(true); } else { - // Single file - upload directly await handleBatchUpload(files, ''); } }; @@ -747,7 +717,6 @@ const CustomSettings = memo(() => { key={originalIndex} className="image-card" onClick={(e) => { - // Only select if clicking the card itself, not navigation buttons if (!e.target.closest('.image-nav-buttons')) { toggleImageSelection(originalIndex); } diff --git a/src/features/background/options/sections/APISettings.jsx b/src/features/background/options/sections/APISettings.jsx index 4f1779e7..30045965 100644 --- a/src/features/background/options/sections/APISettings.jsx +++ b/src/features/background/options/sections/APISettings.jsx @@ -36,7 +36,6 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => { options={backgroundCategories} name="apiCategories" onChange={() => { - // Clear prefetch queue when categories change clearQueuesOnSettingChange('apiCategories'); }} /> @@ -49,7 +48,6 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => { element=".other" items={APIQualityOptions} onChange={() => { - // Clear prefetch queue when quality changes clearQueuesOnSettingChange('apiQuality'); }} /> @@ -91,7 +89,6 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => { category="background" element="#backgroundImage" onChange={() => { - // Clear prefetch queue when Unsplash collections change clearQueuesOnSettingChange('unsplashCollections'); }} /> diff --git a/src/features/background/options/sections/DisplaySettings.jsx b/src/features/background/options/sections/DisplaySettings.jsx index 1780d05e..f814489a 100644 --- a/src/features/background/options/sections/DisplaySettings.jsx +++ b/src/features/background/options/sections/DisplaySettings.jsx @@ -20,12 +20,10 @@ const DisplaySettings = ({ usingImage }) => { label={variables.getMessage('modals.main.settings.sections.background.frequency.title')} onChange={(value) => { localStorage.setItem('backgroundStartTime', Date.now()); - // Clear queue if switching from refresh to time-based frequency const oldValue = localStorage.getItem('backgroundFrequency'); if (oldValue === 'refresh' && value !== 'refresh') { clearQueuesOnSettingChange('backgroundFrequency'); } - // Notify the frequency interval hook that the frequency changed window.dispatchEvent( new CustomEvent('frequencyChanged', { detail: { type: 'background' }, diff --git a/src/features/greeting/Greeting.jsx b/src/features/greeting/Greeting.jsx index 11622685..46bbe9a8 100644 --- a/src/features/greeting/Greeting.jsx +++ b/src/features/greeting/Greeting.jsx @@ -18,11 +18,9 @@ const doEvents = (time, message) => { return message; } - // Get current month & day const month = time.getMonth(); const date = time.getDate(); - // Parse the customEvents from localStorage const customEvents = JSON.parse(localStorage.getItem('customEvents') || '[]'); const event = customEvents.find((e) => e.month - 1 === month && e.date === date); @@ -75,7 +73,6 @@ const Greeting = () => { break; } - // Events and custom const custom = localStorage.getItem('defaultGreetingMessage'); if (custom === 'false') { message = ''; @@ -83,7 +80,6 @@ const Greeting = () => { message = doEvents(now, message); } - // Name let name = ''; const data = localStorage.getItem('greetingName'); @@ -99,7 +95,6 @@ const Greeting = () => { name = name.replace(',', ''); } - // Birthday if (birthday === 'true') { const birth = new Date(localStorage.getItem('birthday')); @@ -114,7 +109,6 @@ const Greeting = () => { } } - // Set the state to the greeting string setGreeting(`${message}${name}`); getGreeting(); diff --git a/src/features/greeting/options/GreetingOptions.jsx b/src/features/greeting/options/GreetingOptions.jsx index 6776b3bf..54d729ce 100644 --- a/src/features/greeting/options/GreetingOptions.jsx +++ b/src/features/greeting/options/GreetingOptions.jsx @@ -45,53 +45,41 @@ const GreetingOptions = ({ currentSubSection, onSubSectionChange, sectionName }) const GREETING_SECTION = 'modals.main.settings.sections.greeting'; const addEvent = () => { - // Retrieve the current array of events from localStorage const customEvents = JSON.parse(localStorage.getItem('customEvents')) || []; - // Create a new event const newEvent = { id: 'widgets.greeting.halloween', name: '', month: 1, date: 1 }; - // Add the new event to the array const updatedEvents = [...customEvents, newEvent]; - // Add the new event to the array customEvents.push(newEvent); - // Store the updated array back in localStorage localStorage.setItem('customEvents', JSON.stringify(customEvents)); setCustomEvents(updatedEvents); }; const removeEvent = (index) => { - // Remove the event at the given index const updatedEvents = customEvents.filter((_, i) => i !== index); - // Store the updated array back in localStorage localStorage.setItem('customEvents', JSON.stringify(updatedEvents)); - // Update the state setCustomEvents(updatedEvents); }; const resetEvents = () => { - // Reset the events array in localStorage localStorage.setItem('customEvents', JSON.stringify(defaultEvents)); - // Update the state setCustomEvents(defaultEvents); toast(variables.getMessage('toasts.reset')); }; const updateEvent = (index, updatedEvent) => { - // Update the event in your state setCustomEvents((prevEvents) => { const newEvents = [...prevEvents]; newEvents[index] = updatedEvent; return newEvents; }); - // Update the event in localStorage const customEvents = JSON.parse(localStorage.getItem('customEvents') || '[]'); customEvents[index] = updatedEvent; localStorage.setItem('customEvents', JSON.stringify(customEvents)); diff --git a/src/features/marketplace/components/Items/Items.jsx b/src/features/marketplace/components/Items/Items.jsx index c11797cc..781199ea 100644 --- a/src/features/marketplace/components/Items/Items.jsx +++ b/src/features/marketplace/components/Items/Items.jsx @@ -19,7 +19,6 @@ function filterItems(item, filter, categoryFilter) { item.author?.toLowerCase().includes(lowerCaseFilter) || item.type?.toLowerCase().includes(lowerCaseFilter); - // Apply category filter if (categoryFilter === 'all') { return textMatch; } @@ -74,10 +73,9 @@ function ItemCard({ const isPhotoPack = item.type === 'photos' || item.type === 'photo_packs'; const hasSettings = isPhotoPack && item.settings_schema && item.settings_schema.length > 0; - // Use React state to manage enabled status for immediate UI updates const [isEnabled, setIsEnabled] = useState(() => { const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}'); - return enabledPacks[packId] !== false; // Default to enabled if not set + return enabledPacks[packId] !== false; }); const [showSettingsModal, setShowSettingsModal] = useState(false); @@ -93,10 +91,8 @@ function ItemCard({ e.stopPropagation(); const newState = !isEnabled; - // Update local state immediately for UI responsiveness setIsEnabled(newState); - // Update localStorage const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}'); enabledPacks[packId] = newState; localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks)); @@ -105,17 +101,13 @@ function ItemCard({ onTogglePack(packId, newState); } - // Clear queue when toggling pack state to prevent stale content if (item.type === 'quotes') { - // Clear quote queue localStorage.removeItem('quoteQueue'); localStorage.removeItem('currentQuote'); EventBus.emit('refresh', 'quote'); } else if (item.type === 'photos') { - // Clear photo pack queue localStorage.removeItem('photoPackQueue'); localStorage.removeItem('currentPhoto'); - // Only refresh if background is currently blank/black to avoid jarring changes const backgroundImage = document.getElementById('backgroundImage'); if (!backgroundImage || !backgroundImage.style.backgroundImage) { EventBus.emit('refresh', 'background'); @@ -285,7 +277,6 @@ function Items({ const [selectedCategory, setSelectedCategory] = useState('all'); const [sortType, setSortType] = useState(localStorage.getItem('sortMarketplace') || 'a-z'); - // Cache installed items lookup - only parse localStorage once const installedNames = useMemo(() => { const installed = JSON.parse(localStorage.getItem('installed')) || []; return new Set(installed.map((item) => item.name)); diff --git a/src/features/marketplace/components/Modals/ItemSettingsModal.jsx b/src/features/marketplace/components/Modals/ItemSettingsModal.jsx index 8bfe1e87..31d0fcfb 100644 --- a/src/features/marketplace/components/Modals/ItemSettingsModal.jsx +++ b/src/features/marketplace/components/Modals/ItemSettingsModal.jsx @@ -29,7 +29,6 @@ const ItemSettingsModal = ({ pack, isOpen, onClose, isEnabled }) => { }); setValidationErrors(errors); - // Update api_packs_ready list const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]'); const isReady = errors.length === 0; const isInList = apiPacksReady.includes(pack.id); @@ -58,7 +57,6 @@ const ItemSettingsModal = ({ pack, isOpen, onClose, isEnabled }) => { } }; - // Load dynamic options (e.g., categories from API) useEffect(() => { if (!pack.settings_schema || pack.settings_schema.length === 0) { return; @@ -70,7 +68,6 @@ const ItemSettingsModal = ({ pack, isOpen, onClose, isEnabled }) => { }); }, [pack.id, pack.settings_schema]); - // Validate settings useEffect(() => { if (!pack.settings_schema || pack.settings_schema.length === 0) { return; @@ -84,14 +81,12 @@ const ItemSettingsModal = ({ pack, isOpen, onClose, isEnabled }) => { setSettings(newSettings); localStorage.setItem(`photopack_settings_${pack.id}`, JSON.stringify(newSettings)); - // Clear cache and immediately refresh when settings change const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); if (apiPackCache[pack.id]) { delete apiPackCache[pack.id]; localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); } - // Trigger immediate refresh with new settings setIsRefreshing(true); await refreshAPIPackCache(pack.id); setIsRefreshing(false); @@ -102,7 +97,6 @@ const ItemSettingsModal = ({ pack, isOpen, onClose, isEnabled }) => { setIsRefreshing(true); await refreshAPIPackCache(pack.id); setIsRefreshing(false); - // Trigger background refresh EventBus.emit('refresh', 'background'); }; diff --git a/src/features/marketplace/components/hooks/useMarketplaceInstall.js b/src/features/marketplace/components/hooks/useMarketplaceInstall.js index 1cf3a9bd..2ab2ec3a 100644 --- a/src/features/marketplace/components/hooks/useMarketplaceInstall.js +++ b/src/features/marketplace/components/hooks/useMarketplaceInstall.js @@ -10,7 +10,6 @@ export const useMarketplaceInstall = () => { const controllerRef = useRef(new AbortController()); const installItem = (type, data) => { - // Check if item is already installed before calling install const installed = JSON.parse(localStorage.getItem('installed') || '[]'); const isNewInstall = !installed.some((item) => item.id === data.id || item.name === data.name); @@ -35,12 +34,10 @@ export const useMarketplaceInstall = () => { const installed = JSON.parse(localStorage.getItem('installed')) || []; for (const item of items) { - // Skip if already installed if (installed.some((i) => i.name === item.display_name)) { continue; } - // Fetch full item data const itemEndpoint = item.id ? `${API_V2_BASE}/item/${item.id}` : `${API_V2_BASE}/item/${item.type}/${item.name}`; @@ -50,7 +47,6 @@ export const useMarketplaceInstall = () => { }); const { data } = await response.json(); - // Install item install(data.type, data, false, true); variables.stats.postEvent('marketplace-item', `${item.display_name} installed`); variables.stats.postEvent('marketplace', 'Install'); diff --git a/src/features/marketplace/views/Added.jsx b/src/features/marketplace/views/Added.jsx index b541fc67..157a2f5a 100644 --- a/src/features/marketplace/views/Added.jsx +++ b/src/features/marketplace/views/Added.jsx @@ -79,11 +79,9 @@ const Added = memo(() => { const toggle = useCallback((type, data) => { if (type === 'item') { - // Navigate to discover tab with the item const itemId = data.name; updateHash(`#discover/all?item=${itemId}`); - // Trigger navigation const event = new window.Event('popstate'); window.dispatchEvent(event); @@ -151,9 +149,7 @@ const Added = memo(() => { installed.forEach((item) => { uninstall(item.type, item.name); }); - } catch { - // Ignore errors during bulk uninstall - } + } catch {} localStorage.setItem('installed', JSON.stringify([])); toast(variables.getMessage('toasts.uninstalled_all')); @@ -202,7 +198,6 @@ const Added = memo(() => { const goToDiscover = useCallback(() => { updateHash('#discover/all'); - // Trigger a popstate event to update the UI const event = new window.Event('popstate'); window.dispatchEvent(event); }, []); diff --git a/src/features/misc/CenteredCustomWidgets.jsx b/src/features/misc/CenteredCustomWidgets.jsx index e11c8804..db569af4 100644 --- a/src/features/misc/CenteredCustomWidgets.jsx +++ b/src/features/misc/CenteredCustomWidgets.jsx @@ -26,7 +26,6 @@ const CenteredCustomWidgets = () => { }; }, []); - // Only render center-positioned widgets const centerWidgets = widgets.filter((w) => w.position === 'center'); if (centerWidgets.length === 0) { diff --git a/src/features/misc/CustomWidgets.jsx b/src/features/misc/CustomWidgets.jsx index 6fa974f0..da09f6a6 100644 --- a/src/features/misc/CustomWidgets.jsx +++ b/src/features/misc/CustomWidgets.jsx @@ -26,7 +26,6 @@ const CustomWidgets = () => { }; }, []); - // Only render corner-positioned widgets (not center) const cornerWidgets = widgets.filter((w) => w.position !== 'center'); if (cornerWidgets.length === 0) { diff --git a/src/features/misc/customwidgets.scss b/src/features/misc/customwidgets.scss index 91c4cdde..aa59c667 100644 --- a/src/features/misc/customwidgets.scss +++ b/src/features/misc/customwidgets.scss @@ -1,6 +1,5 @@ @use 'scss/variables' as *; -// Iframe widgets positioned on the page .custom-widget-iframe { position: fixed; z-index: -1; @@ -41,7 +40,6 @@ } } -// Centered widgets (rendered in main content area) .custom-widget-centered { width: 100%; max-width: 800px; @@ -61,7 +59,6 @@ } } -// Overlay mode - renders above other widgets .custom-widget-overlay { z-index: 100 !important; box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5); diff --git a/src/features/misc/modals/Modals.jsx b/src/features/misc/modals/Modals.jsx index 8b746f35..b8b89557 100644 --- a/src/features/misc/modals/Modals.jsx +++ b/src/features/misc/modals/Modals.jsx @@ -25,7 +25,6 @@ const isDefaultPackUninstalled = () => { }; const tryInstallDefaultPack = async () => { - // Don't install if offline mode, already installed, or explicitly uninstalled if ( localStorage.getItem('offlineMode') === 'true' || isDefaultPackInstalled() || @@ -57,7 +56,6 @@ const Modals = () => { const [deepLinkData, setDeepLinkData] = useState(null); useEffect(() => { - // Check for preview mode - block deep links and redirect to / const isPreviewMode = localStorage.getItem('showWelcome') === 'true'; if (isPreviewMode && shouldAutoOpenModal()) { window.history.replaceState(null, null, '/'); @@ -66,7 +64,6 @@ const Modals = () => { return; } - // Check for deep link first (has priority) if (shouldAutoOpenModal()) { const linkData = parseDeepLink(); setMainModal(true); @@ -91,13 +88,10 @@ const Modals = () => { } } - // Only hide refresh reminder if user navigated naturally (not via deep link or forced intro skip) - // This ensures the reminder shows after user refreshes when they've made changes if (!shouldAutoOpenModal() && window.location.search !== '?nointro=true') { localStorage.setItem('showReminder', false); } - // Try to install default pack if it wasn't installed during welcome (e.g., no internet) if (localStorage.getItem('showWelcome') !== 'true') { tryInstallDefaultPack().then((installed) => { if (installed) { @@ -106,7 +100,6 @@ const Modals = () => { }); } - // Listen for EventBus modal open requests const handleModalOpen = (data) => { if (data === 'openMainModal') { const linkData = parseDeepLink(); @@ -155,7 +148,6 @@ const Modals = () => { if (action !== false) { variables.stats.postEvent('modal', `Opened ${type.replace('Modal', '')}`); - // Set initial hash when opening main modal if (type === 'mainModal') { updateHash('#settings'); } diff --git a/src/features/misc/sections/Advanced.jsx b/src/features/misc/sections/Advanced.jsx index ae21f52e..50ea896c 100644 --- a/src/features/misc/sections/Advanced.jsx +++ b/src/features/misc/sections/Advanced.jsx @@ -166,7 +166,6 @@ function AdvancedOptions({ currentSubSection, onSubSectionChange, sectionName }) const widgetName = name || 'Widget'; const data = widgets.map((item) => { if (item.key === og.key) { - // Only regenerate ID if name changed const newId = item.name !== widgetName ? generateUniqueId(widgetName) @@ -399,7 +398,6 @@ function AdvancedOptions({ currentSubSection, onSubSectionChange, sectionName }) name="marketplaceDDGProxy" element=".other" onChange={() => { - // Clear all prefetch queues when proxy setting changes // so new images are fetched with correct proxy state clearBackgroundQueues('all'); }} diff --git a/src/features/misc/sections/Changelog.jsx b/src/features/misc/sections/Changelog.jsx index 78f2def5..a33bcc3f 100644 --- a/src/features/misc/sections/Changelog.jsx +++ b/src/features/misc/sections/Changelog.jsx @@ -9,7 +9,6 @@ const Changelog = () => { const offlineMode = localStorage.getItem('offlineMode') === 'true'; const isOffline = navigator.onLine === false || offlineMode; - // Helper function to resolve auto theme const getResolvedTheme = () => { const theme = localStorage.getItem('theme') || 'auto'; if (theme === 'auto') { @@ -23,7 +22,6 @@ const Changelog = () => { const handleLoad = () => { setIsLoading(false); - // Send theme to iframe after it loads if (iframeRef.current?.contentWindow) { const theme = getResolvedTheme(); const blogOrigin = new URL(variables.constants.CHANGELOG_URL).origin; @@ -37,7 +35,6 @@ const Changelog = () => { } }; - // Show offline error message if offline if (isOffline) { return (
diff --git a/src/features/misc/sections/Language.jsx b/src/features/misc/sections/Language.jsx index feb92760..85fe5bd8 100644 --- a/src/features/misc/sections/Language.jsx +++ b/src/features/misc/sections/Language.jsx @@ -15,35 +15,27 @@ const LanguageOptions = () => { const [searchQuery, setSearchQuery] = useState(''); - // Create language options with both translated and native names const languageOptions = useMemo(() => { - // Convert currentLanguage to ISO format (e.g., "de_DE" -> "de-DE") const currentLanguageISO = currentLanguage.replace('_', '-'); - // Use Intl.DisplayNames to get language names in the current language const displayNames = new Intl.DisplayNames([currentLanguageISO], { type: 'language' }); const mappedLanguages = languages.map((lang) => { const nativeName = lang.name; - // Convert language code to ISO format for Intl.DisplayNames - // e.g., "en_GB" -> "en-GB", "zh_CN" -> "zh-CN" const isoCode = lang.value.replace('_', '-'); const percentage = translationPercentages[lang.value]?.percent || 0; let translatedName; try { translatedName = displayNames.of(isoCode); - // Simplify by removing country suffixes: "German (Germany)" → "German" if (translatedName) { translatedName = translatedName.split(' (')[0]; } } catch { - // Fallback if the code isn't recognized translatedName = nativeName; } - // Show native name first, then translated name in brackets (greyed and smaller) const displayName = !translatedName || translatedName === nativeName ? ( <> @@ -67,11 +59,9 @@ const LanguageOptions = () => { }; }); - // Sort alphabetically by native name return mappedLanguages.sort((a, b) => a.nativeName.localeCompare(b.nativeName)); }, [currentLanguage]); - // Filter languages based on search query const filteredLanguages = useMemo(() => { if (!searchQuery.trim()) { return languageOptions; @@ -80,17 +70,14 @@ const LanguageOptions = () => { return languageOptions.filter((lang) => lang.searchText.includes(query)); }, [languageOptions, searchQuery]); - // Detect system language const systemLanguage = useMemo(() => { const browserLang = navigator.language.replace('-', '_'); - // Check exact match first, then base language return ( languages.find((l) => l.value === browserLang) || languages.find((l) => l.value.startsWith(browserLang.split('_')[0])) ); }, []); - // Find current language option for display const currentLangOption = languageOptions.find((l) => l.value === currentLanguage); return ( diff --git a/src/features/misc/views/Discover.jsx b/src/features/misc/views/Discover.jsx index ff2b179e..97218af3 100644 --- a/src/features/misc/views/Discover.jsx +++ b/src/features/misc/views/Discover.jsx @@ -15,14 +15,11 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { const [lightboxImg, setLightboxImg] = useState(null); const { installItem, uninstallItem } = useMarketplaceInstall(); - // Check for offline mode const offlineMode = localStorage.getItem('offlineMode') === 'true'; - // Check for preview mode const isPreviewMode = localStorage.getItem('showWelcome') === 'true'; const previewParam = isPreviewMode ? '&preview=true' : ''; const isOffline = navigator.onLine === false || offlineMode; - // Clear breadcrumbs when component unmounts (navigating away from discover) useEffect(() => { return () => { if (onBreadcrumbsChange) { @@ -31,7 +28,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { }; }, [onBreadcrumbsChange]); - // Helper function to resolve auto theme const getResolvedTheme = () => { const theme = localStorage.getItem('theme') || 'auto'; if (theme === 'auto') { @@ -43,25 +39,19 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { }; useEffect(() => { - // Skip category navigation if we have a deep link item to navigate to if (deepLinkData?.itemId) { return; } - // Show loader when category changes setIsLoading(true); - // Clear breadcrumbs when navigating to a new category if (onBreadcrumbsChange) { onBreadcrumbsChange([]); } - // Get current theme const theme = getResolvedTheme(); const themeParam = `&theme=${theme}`; - // Update iframe src with category if (iframeRef.current) { - // Collections use path-based routing, others use query params if (category === 'collections') { iframeRef.current.src = `${MARKETPLACE_URL}/collections?embed=true${previewParam}${themeParam}`; } else { @@ -71,7 +61,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { }, [category, onBreadcrumbsChange, previewParam, deepLinkData]); useEffect(() => { - // Check for item parameter in URL and update iframe const checkAndLoadItem = () => { const hash = window.location.hash; const urlParams = new URLSearchParams(hash.split('?')[1]); @@ -80,16 +69,13 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { if (itemId && iframeRef.current) { setIsLoading(true); - // Get current theme const theme = getResolvedTheme(); const themeParam = `&theme=${theme}`; - // Get item from localStorage to determine type const installed = JSON.parse(localStorage.getItem('installed')) || []; const item = installed.find((i) => i.name === itemId); if (item) { - // Map item type to URL path const pathMap = { photo_packs: 'packs', quote_packs: 'packs', @@ -99,19 +85,15 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { const pathSegment = pathMap[item.type] || 'packs'; const itemIdToUse = item.id || itemId; - // Navigate to /packs/{id} or /presets/{id} iframeRef.current.src = `${MARKETPLACE_URL}/${pathSegment}/${itemIdToUse}?embed=true${previewParam}${themeParam}`; } else { - // Fallback if item not found in localStorage iframeRef.current.src = `${MARKETPLACE_URL}/packs/${itemId}?embed=true${previewParam}${themeParam}`; } } }; - // Check on mount and when category changes checkAndLoadItem(); - // Listen for hash changes and popstate (from history.pushState) window.addEventListener('hashchange', checkAndLoadItem); window.addEventListener('popstate', checkAndLoadItem); @@ -121,14 +103,12 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { }; }, [category, previewParam]); - // Handle deep link item navigation on mount useEffect(() => { if (deepLinkData?.itemId && iframeRef.current) { setIsLoading(true); const theme = getResolvedTheme(); const themeParam = `&theme=${theme}`; - // Map category to URL path segment const pathMap = { photo_packs: 'packs', quote_packs: 'packs', @@ -143,9 +123,7 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { }, [deepLinkData, previewParam]); useEffect(() => { - // Listen for postMessage events from the iframe const handleMessage = (event) => { - // Verify the origin if needed const marketplaceOrigin = new URL(MARKETPLACE_URL).origin; if (event.origin !== marketplaceOrigin) { return; @@ -156,23 +134,19 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { switch (type) { case 'marketplace:item:install': if (payload?.item) { - // Fetch fresh data from API to ensure we get latest version with blur_hash const itemId = payload.item.id || payload.item.name; const itemType = payload.item.type; fetch(`${variables.constants.API_URL}/marketplace/item/${itemId}`) .then((res) => res.json()) .then(({ data }) => { - // Install with fresh data from API installItem(data.type, data); }) .catch((error) => { console.error('Failed to fetch item from API, using iframe data:', error); - // Fallback to iframe data if API fetch fails installItem(itemType, payload.item); }) .finally(() => { - // Send confirmation back to iframe if (iframeRef.current?.contentWindow) { iframeRef.current.contentWindow.postMessage( { @@ -189,7 +163,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { case 'marketplace:item:uninstall': if (payload?.item) { uninstallItem(payload.item.type, payload.item.name || payload.item.display_name); - // Send confirmation back to iframe if (iframeRef.current?.contentWindow) { iframeRef.current.contentWindow.postMessage( { @@ -204,11 +177,9 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { case 'marketplace:item:check-installed': if (payload?.id) { - // Check if item is installed const installed = JSON.parse(localStorage.getItem('installed')) || []; const isInstalled = installed.some((item) => item.id === payload.id); - // Send status back to iframe if (iframeRef.current?.contentWindow) { iframeRef.current.contentWindow.postMessage( { @@ -224,7 +195,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { case 'marketplace:breadcrumbs': if (payload?.breadcrumbs && onBreadcrumbsChange) { onBreadcrumbsChange(payload.breadcrumbs); - // Scroll modal content to top when navigating to an item if (payload.breadcrumbs.length > 0) { const modalContent = document.querySelector('.modalTabContent'); if (modalContent) { @@ -242,7 +212,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { break; case 'marketplace:navigate': - // Update parent URL when iframe navigates if (payload?.itemId) { updateHash(`#discover/${payload.itemId}`); } else if (payload?.category) { @@ -265,12 +234,10 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { const handleLoad = () => { setIsLoading(false); - // Clear breadcrumbs when iframe loads - if on an item page, iframe will send new breadcrumbs if (onBreadcrumbsChange) { onBreadcrumbsChange([]); } - // Send theme to iframe after it loads if (iframeRef.current?.contentWindow) { const theme = getResolvedTheme(); iframeRef.current.contentWindow.postMessage( @@ -283,7 +250,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { } }; - // Show offline error message if offline if (isOffline) { return (
@@ -330,7 +296,6 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) { src={(() => { const theme = getResolvedTheme(); const themeParam = `&theme=${theme}`; - // If we have a deep link item ID, navigate directly to the item if (deepLinkData?.itemId) { const pathMap = { photo_packs: 'packs', diff --git a/src/features/misc/views/Settings.jsx b/src/features/misc/views/Settings.jsx index c89210f5..63ceb54e 100644 --- a/src/features/misc/views/Settings.jsx +++ b/src/features/misc/views/Settings.jsx @@ -91,7 +91,6 @@ const sections = [ function Settings(props) { const t = useT(); - // Recalculate section labels when language changes const translatedSections = useMemo( () => sections.map((section) => ({ diff --git a/src/features/misc/views/Widgets.jsx b/src/features/misc/views/Widgets.jsx index 707cbe15..07ba8764 100644 --- a/src/features/misc/views/Widgets.jsx +++ b/src/features/misc/views/Widgets.jsx @@ -37,7 +37,7 @@ const Widgets = () => { quicklinks: enabled('quicklinksenabled') && , message: enabled('message') && , }), - [order], // Re-create widgets when order changes + [order], ); useEffect(() => { diff --git a/src/features/navbar/components/Apps.jsx b/src/features/navbar/components/Apps.jsx index c6a14b5a..7b6b3238 100644 --- a/src/features/navbar/components/Apps.jsx +++ b/src/features/navbar/components/Apps.jsx @@ -1,4 +1,3 @@ -// TODO: make it work with pins or on click or smth import variables from 'config/variables'; import { memo, useState, useEffect } from 'react'; @@ -31,7 +30,6 @@ const Apps = ({ appsRef, floatRef, position, xPosition, yPosition }) => { try { setZoom(); } catch { - // Ignore errors } } }; diff --git a/src/features/quicklinks/QuickLinks.jsx b/src/features/quicklinks/QuickLinks.jsx index 239a0fc2..cbbdde0e 100644 --- a/src/features/quicklinks/QuickLinks.jsx +++ b/src/features/quicklinks/QuickLinks.jsx @@ -136,7 +136,6 @@ const QuickLinks = memo(() => { ); } - // Default icon style const link = ( { const handleLayoutChange = (key, value) => { const config = JSON.parse(localStorage.getItem('quicklinks_config') || '{}'); @@ -290,7 +289,6 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName ); }; - // Subsection: Display const DisplaySection = () => ( ); - // Subsection: Organization const OrganizationSection = () => { const handleGroupingToggle = (value) => { const config = JSON.parse(localStorage.getItem('quicklinks_config') || '{}'); @@ -337,7 +334,6 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName ); }; - // Subsection: Sync const SyncSection = () => { const [syncStatus, setSyncStatus] = useState(''); const [syncing, setSyncing] = useState(false); @@ -352,7 +348,6 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName setSyncing(true); setSyncStatus('Syncing...'); try { - // Enable sync first if not already enabled const config = JSON.parse(localStorage.getItem('quicklinks_config') || '{}'); if (!config.bookmarkSyncEnabled) { config.bookmarkSyncEnabled = true; diff --git a/src/features/quicklinks/quicklinks.scss b/src/features/quicklinks/quicklinks.scss index 0bdea732..9ef67f36 100644 --- a/src/features/quicklinks/quicklinks.scss +++ b/src/features/quicklinks/quicklinks.scss @@ -24,7 +24,6 @@ overflow-x: visible; box-sizing: border-box; - // Grid layout (NEW) &.layout-grid { display: grid; gap: 16px; @@ -68,7 +67,6 @@ } } - // Flex layout (EXISTING - backward compatibility) &.layout-flex { display: flex; place-content: center center; @@ -76,7 +74,6 @@ gap: 12px; } - // Compact layout (NEW) &.layout-compact { display: flex; flex-direction: column; @@ -85,7 +82,6 @@ margin: 0 auto; } - // Default to flex if no layout class specified &:not(.layout-grid):not(.layout-flex):not(.layout-compact) { display: flex; place-content: center center; @@ -111,7 +107,6 @@ } } -// Group headers (NEW) .quicklinks-group { grid-column: 1 / -1; margin-top: 16px; diff --git a/src/features/quote/Quote.jsx b/src/features/quote/Quote.jsx index a0255ce6..fb8e88e1 100644 --- a/src/features/quote/Quote.jsx +++ b/src/features/quote/Quote.jsx @@ -32,7 +32,6 @@ export default function Quote() { localStorage.getItem('widgetStyle') === 'legacy', ); - // Compute if current quote is favorited const isFavourited = useMemo(() => { const favQuote = localStorage.getItem('favouriteQuote'); return !!favQuote && favQuote === `${quoteData.quote} - ${quoteData.author}`; @@ -43,7 +42,6 @@ export default function Quote() { setLocalIsFavourited(!localIsFavourited); }; - // Set up frequency-based interval for automatic quote updates useFrequencyInterval('quote', getQuote); useEffect(() => { @@ -59,7 +57,6 @@ export default function Quote() { setAuthorDetails(authorDetailsSetting === 'true'); setIsLegacyStyle(widgetStyle === 'legacy'); } else if (data === 'marketplacequoteuninstall' || data === 'quoterefresh') { - // Clear queue when quote packs change localStorage.removeItem('quoteQueue'); localStorage.removeItem('currentQuote'); getQuote(); diff --git a/src/features/quote/hooks/useQuoteLoader.js b/src/features/quote/hooks/useQuoteLoader.js index cd250ee7..452edfde 100644 --- a/src/features/quote/hooks/useQuoteLoader.js +++ b/src/features/quote/hooks/useQuoteLoader.js @@ -42,7 +42,7 @@ export function useQuoteLoader(updateQuote) { authorimg: null, authorimglicense: null, authorOccupation: null, - authorlink: getAuthorLink(author), // Fallback to Wikipedia link + authorlink: getAuthorLink(author), }; } @@ -70,7 +70,6 @@ export function useQuoteLoader(updateQuote) { return tmpdoc.body.textContent || ''; }, []); - // Get cached author data or fetch from Wikidata and cache it const getCachedAuthorData = useCallback( async (author) => { if (localStorage.getItem('authorImg') === 'false' || author === 'Unknown') { @@ -82,24 +81,20 @@ export function useQuoteLoader(updateQuote) { }; } - // Wikidata fetcher has its own caching mechanism return await getAuthorDataFromWikidata(author); }, [getAuthorDataFromWikidata], ); - // Select raw quote data without author images (non-blocking) const selectQuoteData = useCallback(() => { const offline = localStorage.getItem('offlineMode') === 'true'; let type = localStorage.getItem('quoteType') || 'quote_pack'; - // Migrate deprecated 'api' type if (type === 'api') { type = 'quote_pack'; localStorage.setItem('quoteType', 'quote_pack'); } - // Custom quotes if (type === 'custom') { let customQuote; try { @@ -113,7 +108,6 @@ export function useQuoteLoader(updateQuote) { ]; } - // Filter out incomplete quotes (empty quote text) const validQuotes = customQuote?.filter((q) => q.quote && q.quote.trim() !== '') || []; if (validQuotes.length === 0) { @@ -130,14 +124,13 @@ export function useQuoteLoader(updateQuote) { }; } - // Quote packs or offline if (offline) { const quote = offline_quotes[Math.floor(Math.random() * offline_quotes.length)]; return { quote: '"' + quote.quote + '"', author: quote.author, authorlink: getAuthorLink(quote.author), - needsAuthorData: false, // Offline quotes don't get author data + needsAuthorData: false, }; } @@ -149,7 +142,6 @@ export function useQuoteLoader(updateQuote) { return false; } const packId = item.id || item.name; - // Default to enabled if not in enabledPacks object return enabledPacks[packId] !== false; }) .flatMap((item) => @@ -178,25 +170,22 @@ export function useQuoteLoader(updateQuote) { return { quote: `"${data.quote}"`, author: displayAuthor, - realAuthor: hasAuthor ? data.author : null, // For Wikidata lookup + realAuthor: hasAuthor ? data.author : null, authorlink: hasAuthor ? getAuthorLink(data.author) : null, fallbackauthorimg: data.fallbackauthorimg, needsAuthorData: hasAuthor && !data.noAuthorImg, }; }, [getAuthorLink]); - // Fetch complete quote data including author data (for prefetching) const fetchCompleteQuote = useCallback( async (quoteData) => { if (!quoteData || quoteData.noQuote) return quoteData; - // If author data is needed, fetch it if (quoteData.needsAuthorData) { const authorToLookup = quoteData.realAuthor || quoteData.author; try { const authorData = await getCachedAuthorData(authorToLookup); - // For quote packs, use fallback if Wikidata fails if (!authorData.authorimg && quoteData.fallbackauthorimg) { return { ...quoteData, @@ -213,7 +202,6 @@ export function useQuoteLoader(updateQuote) { }; } catch (e) { console.error('Failed to fetch author data:', e); - // Return quote data with fallback or no data return { ...quoteData, authorimg: quoteData.fallbackauthorimg || null, @@ -243,7 +231,6 @@ export function useQuoteLoader(updateQuote) { const getQuote = useCallback(async () => { const offline = localStorage.getItem('offlineMode') === 'true'; - // Initialize prefetch storage on first access if (localStorage.getItem('quoteQueue') === null) { localStorage.setItem('quoteQueue', JSON.stringify([])); } @@ -251,9 +238,7 @@ export function useQuoteLoader(updateQuote) { localStorage.setItem('quotePrefetchEnabled', 'true'); } - // Check if we should update based on frequency if (!shouldUpdateByFrequency('quote')) { - // Load cached quote without fetching new one const cached = localStorage.getItem('currentQuote'); if (cached) { try { @@ -261,27 +246,23 @@ export function useQuoteLoader(updateQuote) { updateQuote(cachedQuote); return; } catch { - // If cache invalid, continue to fetch new } } } - // SPECIAL CASE: Favourite quote (highest priority, no queue) const favouriteQuote = localStorage.getItem('favouriteQuote'); if (favouriteQuote) { const [quote, author] = favouriteQuote.split(' - '); - // Display quote immediately updateQuote({ quote, author, authorlink: getAuthorLink(author), - authorimg: null, // Will be updated asynchronously + authorimg: null, authorimglicense: null, authorOccupation: null, }); - // Fetch author data asynchronously (non-blocking) getCachedAuthorData(author).then((authorData) => { updateQuote({ quote, @@ -291,27 +272,22 @@ export function useQuoteLoader(updateQuote) { }); }); - return; // Don't use queue for favourite quotes + return; } - // MAIN FLOW: Use queue system const queueManager = new QueueManager('quoteQueue', 3); const cachedQueue = queueManager.getQueue(); let quoteData; - // Step 1: Try to get from queue if (cachedQueue.length > 0) { quoteData = queueManager.shift(); } else { - // Step 2: No queue, fetch new quote immediately const rawQuote = selectQuoteData(); if (rawQuote.noQuote) { return updateQuote({ noQuote: true }); } - // For non-queue fetch, display immediately then enhance with author data if (rawQuote.needsAuthorData) { - // Display quote text immediately updateQuote({ ...rawQuote, authorimg: rawQuote.fallbackauthorimg || null, @@ -319,7 +295,6 @@ export function useQuoteLoader(updateQuote) { authorOccupation: null, }); - // Fetch author data asynchronously (after a brief delay to ensure snappy initial render) setTimeout(() => { const authorToLookup = rawQuote.realAuthor || rawQuote.author; getCachedAuthorData(authorToLookup).then((authorData) => { @@ -337,31 +312,27 @@ export function useQuoteLoader(updateQuote) { }); }, 0); - quoteData = rawQuote; // Use for prefetch reference + quoteData = rawQuote; } else { quoteData = rawQuote; updateQuote(quoteData); } } - // Step 3: Display current quote (if from queue, it's already complete) if (cachedQueue.length > 0 || !quoteData.needsAuthorData) { updateQuote(quoteData); } - // Step 4: Store current quote and reset timestamp try { localStorage.setItem('currentQuote', JSON.stringify(quoteData)); - resetStartTime('quote'); // Reset timestamp after successfully updating quote + resetStartTime('quote'); } catch (e) { console.warn('Could not save currentQuote to localStorage:', e); } - // Step 5: Prefetch next 3 quotes asynchronously (non-blocking, deferred) if (queueManager.needsPrefetch() && !offline) { const spaceNeeded = queueManager.getSpaceNeeded(); - // Defer prefetch to avoid blocking initial quote display setTimeout(() => { Promise.all( Array.from({ length: spaceNeeded }, async () => { @@ -379,7 +350,7 @@ export function useQuoteLoader(updateQuote) { .catch((error) => { console.error('Failed to prefetch quotes:', error); }); - }, 100); // Small delay to ensure current quote renders first + }, 100); } }, [updateQuote, getAuthorLink, getCachedAuthorData, selectQuoteData, fetchCompleteQuote]); diff --git a/src/features/quote/options/QuoteOptions.jsx b/src/features/quote/options/QuoteOptions.jsx index 230f1ff1..d0c0f1a1 100644 --- a/src/features/quote/options/QuoteOptions.jsx +++ b/src/features/quote/options/QuoteOptions.jsx @@ -30,7 +30,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => const [quoteType, setQuoteType] = useState(() => { let type = localStorage.getItem('quoteType') || 'quote_pack'; - // Migrate deprecated 'api' type to 'quote_pack' if (type === 'api') { type = 'quote_pack'; localStorage.setItem('quoteType', 'quote_pack'); @@ -38,7 +37,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => return type; }); - // Migration: Force authorDetails on for users upgrading from older versions useState(() => { if (localStorage.getItem('authorDetails') === null) { localStorage.setItem('authorDetails', 'true'); @@ -47,7 +45,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => const [customQuote, setCustomQuote] = useState(getCustom()); - // Clear quote queue when quote type changes useEffect(() => { localStorage.removeItem('quoteQueue'); localStorage.removeItem('currentQuote'); @@ -76,7 +73,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => setCustomQuote(updatedCustomQuote); localStorage.setItem('customQuote', JSON.stringify(updatedCustomQuote)); - // Clear quote queue when custom quotes are modified localStorage.removeItem('quoteQueue'); localStorage.removeItem('currentQuote'); }; @@ -142,12 +138,10 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => label={variables.getMessage(`${QUOTE_SECTION}.frequency.title`)} onChange={(value) => { localStorage.setItem('quoteStartTime', Date.now()); - // Clear queue if switching from refresh to time-based frequency const oldValue = localStorage.getItem('quoteFrequency'); if (oldValue === 'refresh' && value !== 'refresh') { localStorage.removeItem('quoteQueue'); } - // Notify the frequency interval hook that the frequency changed window.dispatchEvent( new CustomEvent('frequencyChanged', { detail: { type: 'quote' }, @@ -196,7 +190,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => const isSourceSection = currentSubSection === 'source'; - // Get installed quote packs const getInstalledQuotePacks = () => { try { const installed = JSON.parse(localStorage.getItem('installed')) || []; @@ -208,11 +201,9 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => const [installedQuotePacks, setInstalledQuotePacks] = useState(getInstalledQuotePacks()); - // Listen for changes to installed addons useEffect(() => { const handleInstalledAddonsChanged = () => { setInstalledQuotePacks(getInstalledQuotePacks()); - // Update quoteType if it changed (e.g., when all packs are uninstalled) const currentType = localStorage.getItem('quoteType') || 'api'; if (currentType !== quoteType) { setQuoteType(currentType); @@ -226,7 +217,6 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => }, [quoteType]); const handleUninstall = (type, name) => { - // Prevent removing the default pack if it's the only one remaining const DEFAULT_PACK_ID = '0c8a5bdebd13'; if (installedQuotePacks.length === 1) { const remainingPack = installedQuotePacks[0]; @@ -251,11 +241,9 @@ const QuoteOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => }; const handleToggle = (pack) => { - // Navigate to discover tab with the item const itemId = pack.name; updateHash(`#discover/all?item=${itemId}`); - // Trigger navigation const event = new window.Event('popstate'); window.dispatchEvent(event); diff --git a/src/features/search/Search.jsx b/src/features/search/Search.jsx index 414f65a8..9e6657fe 100644 --- a/src/features/search/Search.jsx +++ b/src/features/search/Search.jsx @@ -38,7 +38,6 @@ function Search() { setTimeout(() => { variables.stats.postEvent('feature', 'Voice search'); - // Use Chrome Search API - respects user's default search engine if (chrome && chrome.search && chrome.search.query) { chrome.search .query({ @@ -47,11 +46,9 @@ function Search() { }) .catch((error) => { console.error('Search API error:', error); - // Fallback to Google search if API fails window.location.href = `https://www.google.com/search?q=${encodeURIComponent(searchText.value)}`; }); } else { - // Fallback for browsers without chrome.search API window.location.href = `https://www.google.com/search?q=${encodeURIComponent(searchText.value)}`; } }, 1000); @@ -96,7 +93,6 @@ function Search() { const value = e.target.value || document.getElementById('searchtext').value || 'mue fast'; variables.stats.postEvent('feature', 'Search'); - // Use Chrome Search API - respects user's default search engine if (chrome && chrome.search && chrome.search.query) { chrome.search .query({ @@ -105,11 +101,9 @@ function Search() { }) .catch((error) => { console.error('Search API error:', error); - // Fallback to Google search if API fails window.location.href = `https://www.google.com/search?q=${encodeURIComponent(value)}`; }); } else { - // Fallback for browsers without chrome.search API window.location.href = `https://www.google.com/search?q=${encodeURIComponent(value)}`; } } diff --git a/src/features/stats/api/stats.js b/src/features/stats/api/stats.js index 0baab910..8e72ce56 100644 --- a/src/features/stats/api/stats.js +++ b/src/features/stats/api/stats.js @@ -4,7 +4,6 @@ import variables from 'config/variables'; export default class Stats { static async achievementTrigger(stats) { - // Disable achievements in preview mode if (localStorage.getItem('showWelcome') === 'true') { return; } diff --git a/src/features/stats/options/StatsOptions.jsx b/src/features/stats/options/StatsOptions.jsx index ae773176..9b93c921 100644 --- a/src/features/stats/options/StatsOptions.jsx +++ b/src/features/stats/options/StatsOptions.jsx @@ -48,7 +48,6 @@ const Stats = () => { localStorage.setItem('achievements', JSON.stringify([])); localStorage.setItem('achievementTimestamps', JSON.stringify({})); setStats(emptyStats); - // Update achievements based on the empty stats setAchievements(checkAchievements(emptyStats)); setClearmodal(false); toast(variables.getMessage('toasts.stats_reset')); diff --git a/src/features/time/Clock.jsx b/src/features/time/Clock.jsx index 8ef8b7d9..eff7c869 100644 --- a/src/features/time/Clock.jsx +++ b/src/features/time/Clock.jsx @@ -8,10 +8,8 @@ import EventBus from 'utils/eventbus'; import './clock.scss'; -// Helper function to format padded time values while preserving padding const formatPaddedDigits = (value) => { const str = String(value); - // Format each digit individually to preserve padding with locale numerals return str .split('') .map((digit) => formatDigits(digit)) @@ -54,7 +52,6 @@ const Clock = () => { setTime(now); break; default: { - // Default clock let time, sec = ''; const zero = localStorage.getItem('zero'); diff --git a/src/features/time/Date.jsx b/src/features/time/Date.jsx index 5cf7cf51..2c2fc436 100644 --- a/src/features/time/Date.jsx +++ b/src/features/time/Date.jsx @@ -31,7 +31,6 @@ const DateWidget = () => { const weekLabel = variables.getMessage('widgets.date.week'); const weekNum = 1 + Math.ceil((firstThursday - dateToday) / 604800000); - // Support {number} placeholder for locales that need different word order (e.g., Turkish: "{number}. Hafta") const weekText = weekLabel.includes('{number}') ? weekLabel.replace('{number}', weekNum) : `${weekLabel} ${weekNum}`; @@ -71,7 +70,6 @@ const DateWidget = () => { day = dateYear; year = dateDay; break; - // DMY default: break; } @@ -96,7 +94,6 @@ const DateWidget = () => { setDate(format); } else { - // Long date const lang = variables.languagecode.split('_')[0]; const datenth = localStorage.getItem('datenth') === 'true' ? nth(date.getDate(), lang) : date.getDate(); @@ -116,7 +113,6 @@ const DateWidget = () => { case 'YMD': formattedDate = `${dateYear} ${dateMonth} ${datenth}${dateDay ? `, ${dateDay}` : ''}`; break; - // DMY default: formattedDate = `${datenth} ${dateMonth} ${dateYear}${dateDay ? `, ${dateDay}` : ''}`; break; diff --git a/src/features/weather/Weather.jsx b/src/features/weather/Weather.jsx index 4e97a685..d016df4a 100644 --- a/src/features/weather/Weather.jsx +++ b/src/features/weather/Weather.jsx @@ -17,14 +17,12 @@ const WeatherWidget = memo(() => { const stored = localStorage.getItem('location'); if (!stored) return 'London'; - // Try parsing as new JSON format try { const parsed = JSON.parse(stored); if (parsed && typeof parsed === 'object') { return parsed; } } catch { - // Legacy string format } return stored; }); @@ -38,7 +36,6 @@ const WeatherWidget = memo(() => { setWeatherData(data); setDone(data.done); } else { - // Fallback if data is undefined setWeatherData({ done: true }); setDone(true); } @@ -71,7 +68,6 @@ const WeatherWidget = memo(() => { return ; } - // Get display name from location (handles both object and string formats) const locationDisplay = typeof location === 'object' ? location.displayName || location.name : location; diff --git a/src/features/weather/api/getWeather.js b/src/features/weather/api/getWeather.js index be2d9c74..c700791d 100644 --- a/src/features/weather/api/getWeather.js +++ b/src/features/weather/api/getWeather.js @@ -13,28 +13,25 @@ const getLocalizedTempSymbol = (format) => { const language = localStorage.getItem('language') || 'en_GB'; const baseLang = language.split('_')[0]; - // Temperature symbols for different languages const localizedSymbols = { ar: { - celsius: '°س', // Arabic: Celsius (سيلزيوس) - fahrenheit: '°ف', // Arabic: Fahrenheit (فهرنهايت) - kelvin: 'ك', // Arabic: Kelvin (كلفن) + celsius: '°س', + fahrenheit: '°ف', + kelvin: 'ك', }, fa: { - celsius: '°س', // Persian: Celsius - fahrenheit: '°ف', // Persian: Fahrenheit - kelvin: 'ک', // Persian: Kelvin + celsius: '°س', + fahrenheit: '°ف', + kelvin: 'ک', }, }; - // Default Western symbols const defaultSymbols = { celsius: '°C', fahrenheit: '°F', kelvin: 'K', }; - // Return localized symbol if available, otherwise default return localizedSymbols[baseLang]?.[format] || defaultSymbols[format] || 'K'; }; @@ -49,13 +46,10 @@ export const getWeather = async (location) => { } try { - // Build URL based on location type let url; if (typeof location === 'object' && location.lat && location.lon) { - // New format: use coordinates (preferred) url = `${variables.constants.API_URL}/weather?lat=${location.lat}&lon=${location.lon}`; } else { - // Legacy format: use city name string const cityName = typeof location === 'object' ? location.displayName || location.name : location; url = `${variables.constants.API_URL}/weather?city=${encodeURIComponent(cityName)}`; @@ -69,7 +63,6 @@ export const getWeather = async (location) => { } const data = await response.json(); - console.log('Weather API response:', data); if (data.status === 404) { return { diff --git a/src/features/welcome/Welcome.jsx b/src/features/welcome/Welcome.jsx index 45653d34..d72c9b6d 100644 --- a/src/features/welcome/Welcome.jsx +++ b/src/features/welcome/Welcome.jsx @@ -1,4 +1,3 @@ -// Importing necessary libraries and components import { useState, useEffect } from 'react'; import variables from 'config/variables'; import { MdArrowBackIosNew, MdArrowForwardIos, MdOutlinePreview } from 'react-icons/md'; @@ -21,17 +20,13 @@ import { Final, } from './components/Sections'; -// WelcomeModal component function WelcomeModal({ modalClose, modalSkip }) { - // State variables const [currentTab, setCurrentTab] = useState(0); const [buttonText, setButtonText] = useState(variables.getMessage('modals.welcome.buttons.next')); const [importedSettings, setImportedSettings] = useState([]); const finalTab = 6; - // useEffect hook to handle tab changes useEffect(() => { - // Get the current welcome tab from local storage const welcomeTab = localStorage.getItem('welcomeTab'); if (welcomeTab) { const tab = Number(welcomeTab); @@ -44,7 +39,6 @@ function WelcomeModal({ modalClose, modalSkip }) { } }, [finalTab]); - // Function to update the current tab and button text const updateTabAndButtonText = (newTab) => { setCurrentTab(newTab); setButtonText( @@ -57,7 +51,6 @@ function WelcomeModal({ modalClose, modalSkip }) { localStorage.removeItem('welcomeTab'); }; - // Functions to navigate to the previous and next tabs const prevTab = () => { updateTabAndButtonText(currentTab - 1); }; @@ -70,12 +63,10 @@ function WelcomeModal({ modalClose, modalSkip }) { updateTabAndButtonText(currentTab + 1); }; - // Function to switch to a specific tab const switchToTab = (tab) => { updateTabAndButtonText(tab); }; - // Navigation component const Navigation = () => { return (
@@ -105,7 +96,6 @@ function WelcomeModal({ modalClose, modalSkip }) { ); }; - // Mapping of tab numbers to components const tabComponents = { 0: , 1: , @@ -118,10 +108,8 @@ function WelcomeModal({ modalClose, modalSkip }) { ), }; - // Current tab component const CurrentTab = tabComponents[currentTab] || ; - // Render the WelcomeModal component return ( @@ -141,5 +129,4 @@ function WelcomeModal({ modalClose, modalSkip }) { ); } -// Export the WelcomeModal component export default WelcomeModal; diff --git a/src/features/welcome/components/Sections/ChooseLanguage.jsx b/src/features/welcome/components/Sections/ChooseLanguage.jsx index 7dbb0780..731f8754 100644 --- a/src/features/welcome/components/Sections/ChooseLanguage.jsx +++ b/src/features/welcome/components/Sections/ChooseLanguage.jsx @@ -58,11 +58,9 @@ function ChooseLanguage() { }; }); - // Sort alphabetically by native name return mappedLanguages.sort((a, b) => a.nativeName.localeCompare(b.nativeName)); }, [currentLanguage]); - // Filter languages based on search query const filteredLanguages = useMemo(() => { if (!searchQuery.trim()) { return languageOptions; @@ -71,7 +69,6 @@ function ChooseLanguage() { return languageOptions.filter((lang) => lang.searchText.includes(query)); }, [languageOptions, searchQuery]); - // Detect system language const systemLanguage = useMemo(() => { const browserLang = navigator.language.replace('-', '_'); return ( diff --git a/src/hooks/useCachedFetch.js b/src/hooks/useCachedFetch.js index 0d65c2ee..fc9f495d 100644 --- a/src/hooks/useCachedFetch.js +++ b/src/hooks/useCachedFetch.js @@ -10,7 +10,6 @@ import { safeParseJSON } from 'utils/jsonStorage'; * @returns {Object} - Cache utilities */ export function useCachedFetch({ cacheKey, timestampKey, expiryDays = 7 }) { - // Check if cache is still valid const isCacheValid = useCallback(() => { const timestamp = localStorage.getItem(timestampKey); if (!timestamp) return false; @@ -18,7 +17,6 @@ export function useCachedFetch({ cacheKey, timestampKey, expiryDays = 7 }) { return Date.now() - Number(timestamp) < expiryMs; }, [timestampKey, expiryDays]); - // Get cached data for a specific key const getCached = useCallback( (key) => { const cache = safeParseJSON(cacheKey, {}); @@ -30,19 +28,15 @@ export function useCachedFetch({ cacheKey, timestampKey, expiryDays = 7 }) { [cacheKey, isCacheValid], ); - // Fetch with caching const fetchWithCache = useCallback( async (key, fetchFn) => { - // Check cache first const cached = getCached(key); if (cached) { return cached; } - // Fetch from source const result = await fetchFn(key); - // Cache the result (even if null/empty, to avoid re-fetching) if (result !== null && result !== undefined) { const cache = safeParseJSON(cacheKey, {}); cache[key] = { @@ -62,7 +56,6 @@ export function useCachedFetch({ cacheKey, timestampKey, expiryDays = 7 }) { [cacheKey, timestampKey, getCached], ); - // Clear cache const clearCache = useCallback(() => { localStorage.removeItem(cacheKey); localStorage.removeItem(timestampKey); diff --git a/src/hooks/useFrequencyInterval.js b/src/hooks/useFrequencyInterval.js index b8b869ea..0b13f131 100644 --- a/src/hooks/useFrequencyInterval.js +++ b/src/hooks/useFrequencyInterval.js @@ -1,23 +1,13 @@ import { useEffect, useState } from 'react'; import { shouldUpdateByFrequency, FREQUENCY_INTERVALS } from 'utils/frequencyManager'; -/** - * Hook to manage active intervals for time-based content updates - * Automatically starts/stops intervals based on tab visibility and frequency setting - * - * @param {string} type - 'background' or 'quote' - * @param {Function} updateCallback - Function to call when update is needed - */ export function useFrequencyInterval(type, updateCallback) { - // Track frequency in state so we can react to changes const [frequency, setFrequency] = useState( () => localStorage.getItem(`${type}Frequency`) || 'refresh', ); - // Listen for frequency changes via custom storage events useEffect(() => { const handleFrequencyChange = (e) => { - // Listen for custom event dispatched when frequency changes if (e.detail && e.detail.type === type) { const newFrequency = localStorage.getItem(`${type}Frequency`) || 'refresh'; setFrequency(newFrequency); @@ -32,7 +22,6 @@ export function useFrequencyInterval(type, updateCallback) { }, [type]); useEffect(() => { - // No interval needed for refresh mode if (frequency === 'refresh') { return; } @@ -43,7 +32,6 @@ export function useFrequencyInterval(type, updateCallback) { const interval = FREQUENCY_INTERVALS[frequency]; if (!interval) return; - // Set up interval to check and update at the specified frequency intervalId = setInterval(() => { if (shouldUpdateByFrequency(type)) { updateCallback(); @@ -53,13 +41,11 @@ export function useFrequencyInterval(type, updateCallback) { const handleVisibilityChange = () => { if (document.hidden) { - // Tab hidden - clear interval to save resources if (intervalId) { clearInterval(intervalId); intervalId = null; } } else { - // Tab visible - check for catch-up and start interval if (shouldUpdateByFrequency(type)) { updateCallback(); } @@ -67,15 +53,12 @@ export function useFrequencyInterval(type, updateCallback) { } }; - // Start interval if tab is currently visible if (!document.hidden) { startInterval(); } - // Listen for visibility changes document.addEventListener('visibilitychange', handleVisibilityChange); - // Cleanup on unmount or frequency change return () => { if (intervalId) { clearInterval(intervalId); diff --git a/src/index.jsx b/src/index.jsx index 11299578..94a1339c 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -8,7 +8,6 @@ import variables from './config/variables'; import { migrateAPIUsersToPhotoPacks } from './utils/migrations'; import './scss/index.scss'; -// the toast css is based on default so we need to import it import 'react-toastify/dist/ReactToastify.css'; import { initTranslations } from 'lib/translations'; @@ -21,7 +20,6 @@ document.documentElement.lang = languagecode.replace('_', '-'); variables.getMessage = (text, optional) => variables.language.getMessage(variables.languagecode, text, optional || {}); -// Migrate existing API users to photo packs (one-time migration) migrateAPIUsersToPhotoPacks(); Sentry.init({ diff --git a/src/scss/_mixins.scss b/src/scss/_mixins.scss index 0e63c2b2..bf0c8f25 100644 --- a/src/scss/_mixins.scss +++ b/src/scss/_mixins.scss @@ -1,6 +1,5 @@ @use 'sass:list'; -// Source https://joshbroton.com/quick-fix-sass-mixins-for-css-keyframe-animations/ @mixin animation($animate...) { $max: list.length($animate); $animations: ''; diff --git a/src/utils/backgroundQueue.js b/src/utils/backgroundQueue.js index 0de8a3b6..652164fa 100644 --- a/src/utils/backgroundQueue.js +++ b/src/utils/backgroundQueue.js @@ -1,10 +1,5 @@ import { safeParseJSON } from './jsonStorage'; -/** - * Queue Manager - * Manages prefetch queues for content across features (backgrounds, quotes, etc.) - */ - /** * Manages a prefetch queue for content items * @@ -49,7 +44,6 @@ export class QueueManager { localStorage.setItem(this.storageKey, JSON.stringify(queue)); } catch (error) { console.error(`Failed to save queue to ${this.storageKey}:`, error); - // If quota exceeded, clear the queue and try again with empty array if (error.name === 'QuotaExceededError') { try { localStorage.removeItem(this.storageKey); @@ -132,7 +126,6 @@ export class QueueManager { } } -// Backwards compatibility export export const BackgroundQueueManager = QueueManager; export default QueueManager; diff --git a/src/utils/customBackgroundDB.js b/src/utils/customBackgroundDB.js index a212b968..33dcdb6d 100644 --- a/src/utils/customBackgroundDB.js +++ b/src/utils/customBackgroundDB.js @@ -21,7 +21,6 @@ function openDB() { request.onupgradeneeded = (event) => { const db = event.target.result; - // Create object store if it doesn't exist if (!db.objectStoreNames.contains(STORE_NAME)) { const objectStore = db.createObjectStore(STORE_NAME, { keyPath: 'id', @@ -47,12 +46,9 @@ export async function getAllBackgroundsWithMetadata() { return new Promise((resolve, reject) => { request.onsuccess = () => { const results = request.result; - // Return array of background objects in order - // For backward compatibility, convert old string URLs to objects resolve( results.map((item) => { if (typeof item.url === 'string' && !item.name) { - // Old format - migrate to new format return { id: item.id, url: item.url, @@ -102,7 +98,6 @@ export async function addBackground(backgroundData) { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); - // Support old string format for backward compatibility const data = typeof backgroundData === 'string' ? { url: backgroundData, name: 'Image', uploadDate: Date.now(), folder: '' } @@ -127,7 +122,6 @@ export async function updateBackground(index, backgroundData) { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); - // Get all items to find the one at the specified index const getAllRequest = store.getAll(); return new Promise((resolve, reject) => { @@ -136,7 +130,6 @@ export async function updateBackground(index, backgroundData) { if (items[index]) { const item = items[index]; - // Support old string format if (typeof backgroundData === 'string') { item.url = backgroundData; } else { @@ -148,7 +141,6 @@ export async function updateBackground(index, backgroundData) { updateRequest.onsuccess = () => resolve(); updateRequest.onerror = () => reject(updateRequest.error); } else { - // If index doesn't exist, add it addBackground(backgroundData).then(resolve).catch(reject); } }; @@ -166,7 +158,6 @@ export async function deleteBackground(index) { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); - // Get all items to find the one at the specified index const getAllRequest = store.getAll(); return new Promise((resolve, reject) => { @@ -177,7 +168,7 @@ export async function deleteBackground(index) { deleteRequest.onsuccess = () => resolve(); deleteRequest.onerror = () => reject(deleteRequest.error); } else { - resolve(); // Index doesn't exist, nothing to delete + resolve(); } }; getAllRequest.onerror = () => reject(getAllRequest.error); @@ -194,7 +185,6 @@ export async function deleteMultipleBackgrounds(indices) { const transaction = db.transaction(STORE_NAME, 'readwrite'); const store = transaction.objectStore(STORE_NAME); - // Get all items first const getAllRequest = store.getAll(); return new Promise((resolve, reject) => { @@ -202,7 +192,6 @@ export async function deleteMultipleBackgrounds(indices) { const items = getAllRequest.result; const idsToDelete = indices.filter((index) => items[index]).map((index) => items[index].id); - // Delete all selected items let completed = 0; const total = idsToDelete.length; @@ -308,27 +297,23 @@ export async function migrateFromLocalStorage() { backgrounds = [stored]; } - // Filter out null/empty values backgrounds = backgrounds.filter((bg) => bg && bg.trim() !== ''); if (backgrounds.length === 0) { return false; } - // Check if we already have data in IndexedDB const count = await getBackgroundCount(); if (count > 0) { - return false; // Already migrated + return false; } - // Migrate each background for (const bg of backgrounds) { await addBackground(bg); } console.log(`Migrated ${backgrounds.length} backgrounds from localStorage to IndexedDB`); - // Keep localStorage as backup for now, but mark as migrated localStorage.setItem('customBackgroundMigrated', 'true'); return true; diff --git a/src/utils/date/index.js b/src/utils/date/index.js index 40a2483d..95efe333 100644 --- a/src/utils/date/index.js +++ b/src/utils/date/index.js @@ -7,7 +7,6 @@ * @returns The number, optionally with an English ordinal suffix. */ export function nth(d, lang) { - // Only apply English ordinal suffixes for English locales if (lang && !lang.startsWith('en')) { return d; } diff --git a/src/utils/deepLinking.js b/src/utils/deepLinking.js index 5e5f553e..cfc85962 100644 --- a/src/utils/deepLinking.js +++ b/src/utils/deepLinking.js @@ -1,10 +1,3 @@ -/** - * Deep linking utilities for opening specific marketplace items or tabs - * from external sources (e.g., website) - * - * Updated for Marketplace API v2 with item IDs - */ - import { TAB_TYPES } from '../components/Elements/MainModal/constants/tabConfig'; /** @@ -27,7 +20,6 @@ export const parseDeepLink = (hash = window.location.hash) => { return null; } - // Remove the # symbol const path = hash.slice(1); const parts = path.split('/'); @@ -38,52 +30,40 @@ export const parseDeepLink = (hash = window.location.hash) => { itemId: parts[3], }; - // Handle marketplace as an alias for discover (for backward compatibility with external URLs) if (result.tab === 'marketplace') { result.tab = 'discover'; } - // Validate tab const validTabs = Object.values(TAB_TYPES); if (!validTabs.includes(result.tab)) { return null; } - // Handle settings-specific parsing for sub-sections if (result.tab === 'settings') { - // If we have a third part, it's a sub-section if (result.subSection && !result.itemId) { - // Keep subSection as is, clear itemId result.itemId = null; } - // Otherwise section only, no sub-section else if (!result.subSection) { result.subSection = null; } } - // Handle discover-specific parsing (marketplace URLs are aliased to discover) if (result.tab === 'discover') { - // Check if it's a collection if (result.section === 'collection') { result.collection = result.subSection; - // Check if there's a 4th part (item ID within collection) if (result.itemId) { result.fromCollection = true; } else { result.itemId = null; } } - // Check if section is a category (preset_settings, photo_packs, quote_packs) else if (['preset_settings', 'photo_packs', 'quote_packs', 'all'].includes(result.section)) { result.category = result.section; - // Third part is the item ID (now in subSection due to earlier parsing) if (result.subSection) { result.itemId = result.subSection; result.subSection = null; } } - // If only one part after tab, assume it's an item ID else if (result.section && !result.subSection) { result.itemId = result.section; result.section = null; @@ -109,29 +89,23 @@ export const createDeepLink = (tab, options = {}) => { let hash = `#${tab}`; if (tab === 'discover') { - // Collection with item (item viewed from within a collection) if (options.collection && options.itemId && options.fromCollection) { hash += `/collection/${options.collection}/${options.itemId}`; } - // Collection link (just the collection page) else if (options.collection) { hash += `/collection/${options.collection}`; } - // Item with category else if (options.itemId && options.category) { hash += `/${options.category}/${options.itemId}`; } - // Item without category (direct ID) else if (options.itemId) { hash += `/${options.itemId}`; } - // Category only else if (options.category) { hash += `/${options.category}`; } } else if (options.section) { hash += `/${options.section}`; - // Add sub-section for settings tab if (options.subSection) { hash += `/${options.subSection}`; } @@ -152,7 +126,6 @@ export const updateHash = (hash, pushToHistory = true) => { } else { window.history.replaceState(null, null, hash); } - // Dispatch a custom event so components can react to programmatic hash changes window.dispatchEvent(new PopStateEvent('popstate')); } else { window.location.hash = hash; @@ -172,7 +145,6 @@ export const onHashChange = (callback) => { window.addEventListener('hashchange', handler); - // Return cleanup function return () => window.removeEventListener('hashchange', handler); }; diff --git a/src/utils/formatNumber.js b/src/utils/formatNumber.js index c9b2f784..a1554b0b 100644 --- a/src/utils/formatNumber.js +++ b/src/utils/formatNumber.js @@ -5,21 +5,18 @@ export function getLocaleCode() { const language = localStorage.getItem('language')?.replace(/_/g, '-') || 'en-GB'; - // Map language codes to their native numbering systems const numberingSystems = { - ar: 'arab', // Arabic - Eastern Arabic numerals (٠-٩) - fa: 'arabext', // Persian - Extended Arabic-Indic numerals (۰-۹) - bn: 'beng', // Bengali - Bengali numerals (০-৯) - hi: 'deva', // Hindi - Devanagari numerals (०-९) - mr: 'deva', // Marathi - Devanagari numerals - ne: 'deva', // Nepali - Devanagari numerals - ta: 'tamldec', // Tamil - Tamil numerals (௦-௯) + ar: 'arab', + fa: 'arabext', + bn: 'beng', + hi: 'deva', + mr: 'deva', + ne: 'deva', + ta: 'tamldec', }; - // Get the base language code (e.g., 'ar' from 'ar-EG') const baseLang = language.split('-')[0]; - // If this language has a native numbering system, append it if (numberingSystems[baseLang]) { return `${language}-u-nu-${numberingSystems[baseLang]}`; } diff --git a/src/utils/frequencyManager.js b/src/utils/frequencyManager.js index b21fe934..c898e520 100644 --- a/src/utils/frequencyManager.js +++ b/src/utils/frequencyManager.js @@ -1,14 +1,9 @@ -/** - * Frequency Manager - * Manages time-based update frequencies for backgrounds and quotes - */ - export const FREQUENCY_INTERVALS = { - refresh: 0, // Every tab refresh (immediate) - minute: 60 * 1000, // 1 minute - thirty: 30 * 60 * 1000, // 30 minutes - hour: 60 * 60 * 1000, // 1 hour - day: 24 * 60 * 60 * 1000, // 24 hours + refresh: 0, + minute: 60 * 1000, + thirty: 30 * 60 * 1000, + hour: 60 * 60 * 1000, + day: 24 * 60 * 60 * 1000, }; export const FREQUENCY_OPTIONS = [ @@ -27,7 +22,6 @@ export const FREQUENCY_OPTIONS = [ export function shouldUpdateByFrequency(type) { const frequency = localStorage.getItem(`${type}Frequency`) || 'refresh'; - // Always update on 'refresh' mode if (frequency === 'refresh') { return true; } @@ -35,7 +29,6 @@ export function shouldUpdateByFrequency(type) { const startTimeKey = `${type}StartTime`; const startTime = localStorage.getItem(startTimeKey); - // If no start time, update and set it if (!startTime) { localStorage.setItem(startTimeKey, Date.now()); return true; @@ -43,14 +36,12 @@ export function shouldUpdateByFrequency(type) { const elapsed = Date.now() - parseInt(startTime, 10); - // Handle clock changes - if time went backwards, reset if (elapsed < 0) { localStorage.setItem(startTimeKey, Date.now()); return true; } - // Handle clock changes - if elapsed is unreasonably large (>7 days), reset - const MAX_ELAPSED = 7 * 24 * 60 * 60 * 1000; // 7 days + const MAX_ELAPSED = 7 * 24 * 60 * 60 * 1000; if (elapsed > MAX_ELAPSED) { localStorage.setItem(startTimeKey, Date.now()); return true; diff --git a/src/utils/imageMetadata.js b/src/utils/imageMetadata.js index e01045f7..88e2ad6f 100644 --- a/src/utils/imageMetadata.js +++ b/src/utils/imageMetadata.js @@ -14,7 +14,6 @@ export async function getImageDimensions(source) { width: img.width, height: img.height, }); - // Clean up object URL if created if (typeof source !== 'string') { URL.revokeObjectURL(img.src); } @@ -43,11 +42,9 @@ export async function generateBlurHash(source, componentX = 4, componentY = 3) { img.onload = () => { try { - // Create canvas to get pixel data const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); - // Use smaller dimensions for faster processing const maxSize = 64; const scale = Math.min(maxSize / img.width, maxSize / img.height); canvas.width = Math.floor(img.width * scale); @@ -64,7 +61,6 @@ export async function generateBlurHash(source, componentX = 4, componentY = 3) { componentY, ); - // Clean up if (typeof source !== 'string') { URL.revokeObjectURL(img.src); } @@ -91,14 +87,11 @@ export async function generateBlurHash(source, componentX = 4, componentY = 3) { * @returns {number} File size in bytes */ export function getDataUrlSize(dataUrl) { - // Remove data URL prefix to get just the base64 string const base64String = dataUrl.split(',')[1]; if (!base64String) { return 0; } - // Base64 encoding adds ~33% overhead - // Actual size = (base64 length * 3) / 4 const padding = (base64String.match(/=/g) || []).length; return Math.floor((base64String.length * 3) / 4 - padding); } @@ -122,9 +115,7 @@ export function getFileName(file, index) { * @returns {number} Storage size in bytes (estimate) */ export function calculateStorageSize() { - // This is now just an estimate - actual storage is in IndexedDB - // We'll calculate it properly from the actual background data - return 0; // Will be calculated from actual backgrounds in the component + return 0; } /** @@ -173,25 +164,21 @@ export async function extractVideoThumbnail(videoDataUrl, maxWidth = 320) { video.playsInline = true; video.onloadedmetadata = () => { - // Seek to 0.1 seconds to get a better frame than the first black frame video.currentTime = 0.1; }; video.onseeked = () => { try { - // Calculate thumbnail dimensions maintaining aspect ratio const scale = maxWidth / video.videoWidth; const thumbnailWidth = Math.floor(video.videoWidth * scale); const thumbnailHeight = Math.floor(video.videoHeight * scale); - // Create canvas and draw the video frame const canvas = document.createElement('canvas'); canvas.width = thumbnailWidth; canvas.height = thumbnailHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(video, 0, 0, thumbnailWidth, thumbnailHeight); - // Convert to data URL (JPEG for better compression) const thumbnail = canvas.toDataURL('image/jpeg', 0.8); resolve({ diff --git a/src/utils/jsonStorage.js b/src/utils/jsonStorage.js index 4f38dca6..e0cb0828 100644 --- a/src/utils/jsonStorage.js +++ b/src/utils/jsonStorage.js @@ -14,7 +14,6 @@ export function safeParseJSON(key, fallback = null, reinitialize = false) { const parsed = JSON.parse(item); return parsed !== null ? parsed : fallback; } catch (error) { - // Corrupt data - optionally reinitialize if (reinitialize && fallback !== null) { localStorage.setItem(key, JSON.stringify(fallback)); } diff --git a/src/utils/marketplace/install.js b/src/utils/marketplace/install.js index 9e86884c..7e519499 100644 --- a/src/utils/marketplace/install.js +++ b/src/utils/marketplace/install.js @@ -28,7 +28,6 @@ async function trackDownload(itemId) { export function install(type, input, sideload, collection) { let refreshEvent = null; - // Check if item is already installed to determine if we should track download const installed = JSON.parse(localStorage.getItem('installed') || '[]'); const isNewInstall = !installed.some((item) => item.id === input.id || item.name === input.name); @@ -53,16 +52,13 @@ export function install(type, input, sideload, collection) { const currentPhotos = JSON.parse(localStorage.getItem('photo_packs')) || []; const hadPhotoPacks = currentPhotos.length > 0; - // Handle API packs differently if (input.api_enabled) { - // Initialize default settings const defaultSettings = {}; input.settings_schema?.forEach((field) => { defaultSettings[field.key] = field.default || ''; }); localStorage.setItem(`photopack_settings_${input.id}`, JSON.stringify(defaultSettings)); - // Initialize empty cache const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); apiPackCache[input.id] = { photos: [], @@ -71,12 +67,10 @@ export function install(type, input, sideload, collection) { }; localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); - // If no API key required, fetch initial photos if (!input.requires_api_key) { refreshAPIPackCache(input.id); } } else { - // Static pack - add photos to pool input.photos.forEach((photo) => { currentPhotos.push(photo); }); @@ -88,10 +82,7 @@ export function install(type, input, sideload, collection) { } localStorage.setItem('backgroundType', 'photo_pack'); localStorage.removeItem('backgroundchange'); - // Clear image queue to ensure fresh background loads clearQueuesOnSettingChange('packInstall'); - // Only refresh background if this is the first photo pack being installed - // Otherwise just update the queue without jarring the user with an immediate refresh if (!hadPhotoPacks) { refreshEvent = 'backgroundrefresh'; } @@ -110,7 +101,6 @@ export function install(type, input, sideload, collection) { } localStorage.setItem('quoteType', 'quote_pack'); localStorage.removeItem('quotechange'); - // Clear quote queue and cache when installing new quote packs localStorage.removeItem('quoteQueue'); localStorage.removeItem('currentQuote'); refreshEvent = 'quote'; @@ -129,7 +119,6 @@ export function install(type, input, sideload, collection) { localStorage.setItem('installed', JSON.stringify(installed)); - // Auto-enable pack on install if (isNewInstall) { const packId = input.id || input.name; const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}'); @@ -137,12 +126,10 @@ export function install(type, input, sideload, collection) { localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks)); } - // Track download for new installs (not re-installs) if (isNewInstall && input.id) { trackDownload(input.id); } - // Emit refresh event after all data is saved if (refreshEvent) { EventBus.emit('refresh', refreshEvent); } diff --git a/src/utils/marketplace/uninstall.js b/src/utils/marketplace/uninstall.js index 55601be5..37dc0c56 100644 --- a/src/utils/marketplace/uninstall.js +++ b/src/utils/marketplace/uninstall.js @@ -41,7 +41,6 @@ export function uninstall(type, name) { localStorage.removeItem('quote_packs'); } localStorage.removeItem('quotechange'); - // Clear quote queue and cache when uninstalling quote packs localStorage.removeItem('quoteQueue'); localStorage.removeItem('currentQuote'); refreshEvent = 'marketplacequoteuninstall'; @@ -55,20 +54,14 @@ export function uninstall(type, name) { if (packContents) { if (packContents.api_enabled) { - // Remove API pack cache const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); delete apiPackCache[packContents.id]; localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); - // Remove from ready list const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]'); const filtered = apiPacksReady.filter((id) => id !== packContents.id); localStorage.setItem('api_packs_ready', JSON.stringify(filtered)); - - // Keep settings for easy reinstall (optional - can remove if desired) - // localStorage.removeItem(`photopack_settings_${packContents.id}`); } else if (packContents.photos) { - // Remove static photos installedContents = installedContents.filter((item) => { return !packContents.photos.some( (content) => content.url?.default === item.url?.default, @@ -78,22 +71,18 @@ export function uninstall(type, name) { } } - // Check if all packs are uninstalled const remainingInstalled = JSON.parse(localStorage.getItem('installed')).filter( (item) => item.type === 'photos' && item.name !== name, ); if (remainingInstalled.length === 0) { - // Switch back to old background type or default to mue api localStorage.setItem('backgroundType', localStorage.getItem('oldBackgroundType') || 'api'); localStorage.removeItem('oldBackgroundType'); localStorage.removeItem('photo_packs'); } localStorage.removeItem('backgroundchange'); - // Clear image queue to ensure fresh background loads clearQueuesOnSettingChange('packUninstall'); - // Set refresh event to emit after installed data is saved refreshEvent = 'marketplacebackgrounduninstall'; break; @@ -104,7 +93,6 @@ export function uninstall(type, name) { const installed = JSON.parse(localStorage.getItem('installed')); for (let i = 0; i < installed.length; i++) { if (installed[i].name === name) { - // Track uninstalled pack IDs to prevent auto-reinstall if (installed[i].id) { const uninstalledPacks = JSON.parse(localStorage.getItem('uninstalledPacks') || '[]'); if (!uninstalledPacks.includes(installed[i].id)) { @@ -119,7 +107,6 @@ export function uninstall(type, name) { localStorage.setItem('installed', JSON.stringify(installed)); - // Cleanup enabledPacks entry const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}'); const installedItems = JSON.parse(localStorage.getItem('installed') || '[]'); const packToRemove = installedItems.find((item) => item.name === name); @@ -129,7 +116,6 @@ export function uninstall(type, name) { localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks)); } - // Emit refresh event after all data is saved if (refreshEvent) { EventBus.emit('refresh', refreshEvent); } diff --git a/src/utils/migrations.js b/src/utils/migrations.js index e27b812c..79dc71d3 100644 --- a/src/utils/migrations.js +++ b/src/utils/migrations.js @@ -3,7 +3,6 @@ * Run once on extension load after update */ export function migrateAPIUsersToPhotoPacks() { - // Check if migration already completed if (localStorage.getItem('api_migration_completed') === 'true') { return; } @@ -11,15 +10,12 @@ export function migrateAPIUsersToPhotoPacks() { const backgroundType = localStorage.getItem('backgroundType'); const backgroundAPI = localStorage.getItem('backgroundAPI'); - // Only migrate if user is currently using API backgrounds if (backgroundType !== 'api') { localStorage.setItem('api_migration_completed', 'true'); return; } let packToInstall = null; - - // Determine which API pack to install if (backgroundAPI === 'mue') { packToInstall = { id: 'mue_photos', @@ -58,7 +54,6 @@ export function migrateAPIUsersToPhotoPacks() { icon_url: 'https://raw.githubusercontent.com/mue/branding/main/logo/logo_square.png', }; - // Port existing settings const existingQuality = localStorage.getItem('apiQuality') || 'high'; const existingCategories = JSON.parse(localStorage.getItem('apiCategories') || '["nature"]'); @@ -93,7 +88,6 @@ export function migrateAPIUsersToPhotoPacks() { icon_url: 'https://raw.githubusercontent.com/mue/branding/main/logo/logo_square.png', }; - // Port existing Unsplash settings (collection IDs) const existingCollections = localStorage.getItem('unsplashCollections') || ''; const migratedSettings = { @@ -106,16 +100,13 @@ export function migrateAPIUsersToPhotoPacks() { } if (packToInstall) { - // Install the pack const installed = JSON.parse(localStorage.getItem('installed') || '[]'); - // Check if not already installed if (!installed.some((item) => item.id === packToInstall.id)) { installed.push(packToInstall); localStorage.setItem('installed', JSON.stringify(installed)); } - // Initialize cache const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); if (!apiPackCache[packToInstall.id]) { apiPackCache[packToInstall.id] = { @@ -126,7 +117,6 @@ export function migrateAPIUsersToPhotoPacks() { localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); } - // Add to ready list if MUE (no API key required) if (packToInstall.api_provider === 'mue') { const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]'); if (!apiPacksReady.includes(packToInstall.id)) { @@ -135,13 +125,10 @@ export function migrateAPIUsersToPhotoPacks() { } } - // Change background type to photo_pack localStorage.setItem('backgroundType', 'photo_pack'); - // Clear old queue localStorage.removeItem('imageQueue'); - // Fetch initial photos for MUE if (packToInstall.api_provider === 'mue') { import('../features/background/api/photoPackAPI').then((module) => { module.refreshAPIPackCache(packToInstall.id); @@ -153,6 +140,5 @@ export function migrateAPIUsersToPhotoPacks() { ); } - // Mark migration as completed localStorage.setItem('api_migration_completed', 'true'); } diff --git a/src/utils/queueOperations.js b/src/utils/queueOperations.js index ea86aea5..1f739be7 100644 --- a/src/utils/queueOperations.js +++ b/src/utils/queueOperations.js @@ -17,21 +17,19 @@ const QUEUE_KEYS = { * @param {string} type - Queue type to clear: 'all', 'api', 'photo_pack', or 'custom' * * @example - * // Clear all queues + * * clearBackgroundQueues('all'); * - * // Clear only API queue + * * clearBackgroundQueues('api'); */ export function clearBackgroundQueues(type = 'all') { try { if (type === 'all') { - // Clear all queue types Object.values(QUEUE_KEYS).forEach((key) => { localStorage.removeItem(key); }); } else if (QUEUE_KEYS[type]) { - // Clear specific queue type localStorage.removeItem(QUEUE_KEYS[type]); } else { console.warn(`Unknown queue type: ${type}`); @@ -48,34 +46,30 @@ export function clearBackgroundQueues(type = 'all') { * @param {string} settingName - The name of the setting that changed * * @example - * // User changed background type + * * clearQueuesOnSettingChange('backgroundType'); * - * // User changed API categories + * * clearQueuesOnSettingChange('apiCategories'); * - * // User installed a photo pack + * * clearQueuesOnSettingChange('packInstall'); */ export function clearQueuesOnSettingChange(settingName) { const clearTriggers = { - // Clear all queues - backgroundType: 'all', // Background type changed + backgroundType: 'all', - // Clear API queue only - backgroundAPI: 'api', // API provider changed (Mue <-> Unsplash) - apiCategories: 'api', // Categories changed - apiQuality: 'api', // Quality changed - unsplashCollections: 'api', // Unsplash collections changed - backgroundExclude: 'api', // Exclude list changed + backgroundAPI: 'api', + apiCategories: 'api', + apiQuality: 'api', + unsplashCollections: 'api', + backgroundExclude: 'api', - // Clear photo pack queue only - packInstall: 'photo_pack', // Photo pack installed - packUninstall: 'photo_pack', // Photo pack uninstalled + packInstall: 'photo_pack', + packUninstall: 'photo_pack', - // Clear custom queue only - customModified: 'custom', // Custom background modified - customDeleted: 'custom', // Custom background deleted + customModified: 'custom', + customDeleted: 'custom', }; const queueType = clearTriggers[settingName]; diff --git a/src/utils/settings/default.js b/src/utils/settings/default.js index 43d8acd0..b394c924 100644 --- a/src/utils/settings/default.js +++ b/src/utils/settings/default.js @@ -10,7 +10,6 @@ export function setDefaultSettings(reset) { localStorage.clear(); defaultSettings.forEach((element) => localStorage.setItem(element.name, element.value)); - // Languages const languageCodes = languages.map(({ value }) => value); const browserLanguage = (navigator.languages && diff --git a/src/utils/settings/export.js b/src/utils/settings/export.js index 87cd800f..f509cde6 100644 --- a/src/utils/settings/export.js +++ b/src/utils/settings/export.js @@ -12,7 +12,6 @@ export function exportSettings() { }); const date = new Date(); - // Format the date as YYYY-MM-DD_HH-MM-SS const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}_${date.getHours().toString().padStart(2, '0')}-${date.getMinutes().toString().padStart(2, '0')}-${date.getSeconds().toString().padStart(2, '0')}`; const filename = `mue_settings_backup_${formattedDate}.json`; saveFile(settings, filename); diff --git a/src/utils/settings/load.js b/src/utils/settings/load.js index b616f153..b40ee2d1 100644 --- a/src/utils/settings/load.js +++ b/src/utils/settings/load.js @@ -33,7 +33,6 @@ export function loadSettings(hotreload) { try { document.head.removeChild(document.getElementById(element)); } catch (e) { - // Disregard exception if custom stuff doesn't exist } }); } @@ -54,7 +53,6 @@ export function loadSettings(hotreload) { try { document.querySelector('.' + element).classList.add('textBorder'); } catch (e) { - // Disregard exception } }); } else { @@ -63,7 +61,6 @@ export function loadSettings(hotreload) { try { document.querySelector('.' + element).classList.remove('textBorder'); } catch (e) { - // Disregard exception } }); } @@ -103,7 +100,6 @@ export function loadSettings(hotreload) { ); } - // If the extension got updated and the new app links default settings // were not set, set them if (localStorage.getItem('applinks') === null) { localStorage.setItem('applinks', JSON.stringify([])); diff --git a/src/utils/wikidata/wikidataAuthorFetcher.js b/src/utils/wikidata/wikidataAuthorFetcher.js index 48cf4328..130066c8 100644 --- a/src/utils/wikidata/wikidataAuthorFetcher.js +++ b/src/utils/wikidata/wikidataAuthorFetcher.js @@ -1,15 +1,10 @@ -/** - * Wikidata API integration for fetching author information - * Provides author images, occupations, and metadata with multilingual support - */ - /* global URLSearchParams */ import { safeParseJSON } from '../jsonStorage'; const WIKIDATA_API = 'https://www.wikidata.org/w/api.php'; const CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days -const MAX_CACHE_ENTRIES = 50; // Maximum number of cached authors +const MAX_CACHE_ENTRIES = 50; /** * Get all Wikidata cache keys from localStorage @@ -26,7 +21,7 @@ function getWikidataCacheEntries() { timestamp: cached?.timestamp || 0, }; }) - .sort((a, b) => a.timestamp - b.timestamp); // Oldest first + .sort((a, b) => a.timestamp - b.timestamp); } /** @@ -57,7 +52,6 @@ function getCachedAuthorData(authorName, language) { return null; } - // Check if cache has expired const now = Date.now(); if (cached.timestamp && now - cached.timestamp > CACHE_EXPIRY) { localStorage.removeItem(cacheKey); @@ -84,7 +78,6 @@ function cacheAuthorData(authorName, language, data) { }), ); - // Prune cache if it exceeds the limit pruneCache(); } @@ -133,7 +126,6 @@ async function extractOccupations(claims, language) { } try { - // Get all occupations with their ranks const occupations = claims.P106 .filter((claim) => claim.mainsnak?.datavalue?.value?.id) .map((claim) => ({ @@ -145,14 +137,11 @@ async function extractOccupations(claims, language) { return null; } - // Sort by rank: preferred > normal > deprecated const rankOrder = { preferred: 0, normal: 1, deprecated: 2 }; occupations.sort((a, b) => rankOrder[a.rank] - rankOrder[b.rank]); - // Take only the top-ranked occupation const topOccupation = occupations[0]; - // Fetch occupation label const params = new URLSearchParams({ action: 'wbgetentities', ids: topOccupation.id, @@ -168,7 +157,6 @@ async function extractOccupations(claims, language) { const entity = data.entities?.[topOccupation.id]; if (!entity?.labels) return null; - // Try preferred language first, then English const label = entity.labels[language]?.value || entity.labels['en']?.value; return label || null; } catch (error) { @@ -221,13 +209,11 @@ function extractWikipediaLink(sitelinks, language) { return null; } - // Try preferred language wiki first const preferredSite = `${language}wiki`; if (sitelinks[preferredSite]) { return sitelinks[preferredSite].url; } - // Fall back to English Wikipedia if (sitelinks.enwiki) { return sitelinks.enwiki.url; } @@ -246,22 +232,18 @@ export async function fetchAuthorFromWikidata(authorName, language = 'en') { return null; } - // Check cache first const cached = getCachedAuthorData(authorName, language); if (cached) { return cached; } try { - // Step 1: Search for author entity const entityId = await searchAuthorEntity(authorName, language); if (!entityId) { - // Cache negative result to avoid repeated lookups cacheAuthorData(authorName, language, null); return null; } - // Step 2: Fetch entity data const params = new URLSearchParams({ action: 'wbgetentities', ids: entityId, @@ -280,14 +262,11 @@ export async function fetchAuthorFromWikidata(authorName, language = 'en') { return null; } - // Step 3: Extract data const claims = entity.claims; const sitelinks = entity.sitelinks; - // Get occupation const occupation = await extractOccupations(claims, language); - // Get image let imageUrl = null; if (claims.P18 && claims.P18.length > 0) { const filename = claims.P18[0].mainsnak?.datavalue?.value; @@ -296,10 +275,8 @@ export async function fetchAuthorFromWikidata(authorName, language = 'en') { } } - // Get Wikipedia link const wikipediaLink = extractWikipediaLink(sitelinks, language); - // Get description const description = entity.descriptions?.[language]?.value || entity.descriptions?.['en']?.value; @@ -312,7 +289,6 @@ export async function fetchAuthorFromWikidata(authorName, language = 'en') { imageLicense: imageUrl ? 'Wikimedia Commons' : null, }; - // Cache result cacheAuthorData(authorName, language, authorData); return authorData; diff --git a/vite.config.mjs b/vite.config.mjs index 1adbacc0..86c85903 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -1,21 +1,3 @@ -/** - * Vite Configuration for Mue Browser Extension - * - * This configuration handles building the extension for multiple browsers: - * - Chrome/Edge (Manifest V3) - * - Firefox (Manifest V3) - * - Safari (via Xcode project) - * - * Build Process: - * 1. Vite bundles the React app into /dist - * 2. Assets are copied to dist/src/assets for proper path resolution - * 3. Browser-specific builds are created in /build/{browser} - * 4. Distribution .zip files are generated for Chrome and Firefox - * - * Build Commands: - * - `bun run build` - Production build for all browsers - * - `bun run dev` - Development server with HMR - */ import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react-swc'; import path from 'path'; @@ -32,21 +14,17 @@ const prepareBuilds = () => ({ if (isProd) { console.log('📦 Building extension packages...'); - // Ensure base directories exist fs.mkdirSync(path.resolve(__dirname, './build'), { recursive: true }); fs.mkdirSync(path.resolve(__dirname, './dist'), { recursive: true }); - // Copy assets to dist for proper bundling const distAssetsPath = path.resolve(__dirname, './dist/src/assets'); fs.mkdirSync(distAssetsPath, { recursive: true }); fs.cpSync(path.resolve(__dirname, './src/assets'), distAssetsPath, { recursive: true }); - // Chrome Build console.log('🔨 Building Chrome extension...'); const chromeBuildPath = path.resolve(__dirname, './build/chrome'); fs.mkdirSync(chromeBuildPath, { recursive: true }); - // Copy manifest and background script fs.copyFileSync( path.resolve(__dirname, './manifest/chrome.json'), path.resolve(chromeBuildPath, 'manifest.json'), @@ -56,29 +34,24 @@ const prepareBuilds = () => ({ path.resolve(chromeBuildPath, 'background.js'), ); - // Copy locales fs.cpSync( path.resolve(__dirname, './manifest/_locales'), path.resolve(chromeBuildPath, '_locales'), { recursive: true }, ); - // Copy dist (bundled app) fs.cpSync(path.resolve(__dirname, './dist'), chromeBuildPath, { recursive: true }); - // Copy extension icons (separate from bundled assets) fs.cpSync( path.resolve(__dirname, './src/assets/icons'), path.resolve(chromeBuildPath, 'icons'), { recursive: true }, ); - // Firefox Build console.log('🦊 Building Firefox extension...'); const firefoxBuildPath = path.resolve(__dirname, './build/firefox'); fs.mkdirSync(firefoxBuildPath, { recursive: true }); - // Copy manifest and background script fs.copyFileSync( path.resolve(__dirname, './manifest/firefox.json'), path.resolve(firefoxBuildPath, 'manifest.json'), @@ -88,48 +61,40 @@ const prepareBuilds = () => ({ path.resolve(firefoxBuildPath, 'background.js'), ); - // Copy dist (bundled app) fs.cpSync(path.resolve(__dirname, './dist'), firefoxBuildPath, { recursive: true }); - // Copy extension icons (separate from bundled assets) fs.cpSync( path.resolve(__dirname, './src/assets/icons'), path.resolve(firefoxBuildPath, 'icons'), { recursive: true }, ); - // Safari Build console.log('🧭 Building Safari extension...'); const safariResourcesPath = path.resolve(__dirname, './safari/Mue Extension/Resources'); fs.mkdirSync(safariResourcesPath, { recursive: true }); - // Copy background script fs.copyFileSync( path.resolve(__dirname, './manifest/background.js'), path.resolve(safariResourcesPath, 'background.js'), ); - // Copy built files from dist (excluding manifest.json which Safari manages separately) fs.cpSync(path.resolve(__dirname, './dist'), safariResourcesPath, { recursive: true, filter: (src) => !src.endsWith('manifest.json'), }); - // Copy extension icons fs.cpSync( path.resolve(__dirname, './src/assets/icons'), path.resolve(safariResourcesPath, 'icons'), { recursive: true }, ); - // Copy locales fs.cpSync( path.resolve(__dirname, './manifest/_locales'), path.resolve(safariResourcesPath, '_locales'), { recursive: true }, ); - // Create distribution zips console.log('📦 Creating distribution packages...'); const chromeZip = new ADMZip(); chromeZip.addLocalFolder(chromeBuildPath); @@ -153,12 +118,12 @@ export default defineConfig(({ command, mode }) => { plugins: [react(), prepareBuilds(), progress()], server: { open: true, hmr: { protocol: 'ws', host: 'localhost' } }, build: { - target: 'esnext', // Use modern JavaScript features + target: 'esnext', minify: isProd ? 'esbuild' : false, sourcemap: !isProd, rollupOptions: { output: { - manualChunks: undefined, // Let Vite handle chunking automatically to avoid circular dependency issues + manualChunks: undefined, }, }, },