From b7a7fd760d9195a222968568e2e03b7b794cc9fc Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 19 May 2026 14:28:29 -0500 Subject: [PATCH 1/7] Convert components to TS --- src/app.d.ts | 4 +- src/lib/components/AudioBar.svelte | 26 ++-- src/lib/components/AudioPlaybackSpeed.svelte | 12 +- src/lib/components/BookSelector.svelte | 112 ++++++++++------ src/lib/components/BookTabs.svelte | 33 +++-- src/lib/components/BottomNavigationBar.svelte | 37 +++--- src/lib/components/ChapterSelector.svelte | 40 +++--- src/lib/components/CollectionList.svelte | 4 +- src/lib/components/CollectionSelector.svelte | 51 ++++--- src/lib/components/Dropdown.svelte | 12 +- src/lib/components/FontList.svelte | 8 +- src/lib/components/FontSelector.svelte | 16 ++- src/lib/components/HorizontalPanes.svelte | 125 ------------------ src/lib/components/LayoutOptions.svelte | 87 ++++++------ src/lib/components/Modal.svelte | 17 ++- src/lib/components/Navbar.svelte | 10 +- src/lib/components/PlanStopDialog.svelte | 6 +- src/lib/components/Settings.svelte | 33 ++--- src/lib/components/Sidebar.svelte | 63 ++++----- src/lib/components/Slider.svelte | 4 +- src/lib/components/StackView.svelte | 44 +++--- src/lib/components/TabsMenu.svelte | 14 +- .../components/TextAppearanceSelector.svelte | 64 +++++---- src/lib/components/VerticalPanes.svelte | 99 -------------- src/lib/data/stores/collection.ts | 21 ++- src/lib/data/stores/interface.ts | 19 +-- src/lib/data/stores/localization.ts | 4 +- src/lib/data/stores/reference.ts | 9 +- src/lib/data/stores/setting.ts | 61 +++++---- src/lib/data/stores/theme.ts | 8 +- src/lib/data/stores/view.ts | 13 +- src/lib/scripts/numeralSystem.ts | 2 +- src/routes/+layout.svelte | 20 +-- 33 files changed, 446 insertions(+), 632 deletions(-) delete mode 100644 src/lib/components/HorizontalPanes.svelte delete mode 100644 src/lib/components/VerticalPanes.svelte diff --git a/src/app.d.ts b/src/app.d.ts index 4fd9e41a5..dec751c65 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -75,8 +75,8 @@ declare namespace App { interface CollectionGroup { singlePane?: CollectionEntry; - sideBySide?: FixedLengthArray<[CollectionEntry, CollectionEntry]>; - verseByVerse?: FixedLengthArray<[CollectionEntry, CollectionEntry, CollectionEntry]>; + sideBySide?: [CollectionEntry, CollectionEntry]; + verseByVerse?: [CollectionEntry, CollectionEntry, CollectionEntry]; } interface UserPreferenceSetting { diff --git a/src/lib/components/AudioBar.svelte b/src/lib/components/AudioBar.svelte index 84848ff0a..bcecc7c09 100644 --- a/src/lib/components/AudioBar.svelte +++ b/src/lib/components/AudioBar.svelte @@ -25,13 +25,14 @@ TODO: refs, s, t, - userSettings + userSettings, + type PlayModeSettings } from '$lib/data/stores'; import { AudioIcon } from '$lib/icons'; import PlayButton from './PlayButton.svelte'; import RepeatButton from './RepeatButton.svelte'; - function mayResetPlayMode(hasTiming) { + function mayResetPlayMode(hasTiming: boolean) { // If the current mode is repeatSelection and the reference is changed to something without timing // (even chapter without audio), then reset the playMode. This matches how the Android app behaves. if (!hasTiming && $playMode.mode === 'repeatSelection') { @@ -39,18 +40,19 @@ TODO: } } - function seekAudio(event) { + function seekAudio(event: MouseEvent) { if (!$audioPlayer.loaded) { return; } const progressBar = document.getElementById('progress-bar'); - const percent = (event.clientX - progressBar.offsetLeft) / progressBar.offsetWidth; + const percent = + (event.clientX - (progressBar?.offsetLeft ?? 0)) / (progressBar?.offsetWidth ?? 1); // Set the current time of the audio element to the corresponding time based on the percent seek($audioPlayer.duration * percent); } let lastPlayMode = ''; - function playModeChanged(value) { + function playModeChanged(value: PlayModeSettings) { let key = ''; switch (value.mode) { @@ -77,8 +79,8 @@ TODO: let hintText = $state(''); let showHint = $state(false); - let hintTimeoutId = null; - function startShowHint(text) { + let hintTimeoutId: NodeJS.Timeout | null = null; + function startShowHint(text: string) { showHint = true; hintText = text; if (hintTimeoutId) { @@ -92,14 +94,14 @@ TODO: const showSpeed = config.mainFeatures['settings-audio-speed']; const showRepeatMode = config.mainFeatures['audio-repeat-mode-button']; - const hintStyle = convertStyle($s['ui.bar.audio.hint.text']); + const hintStyle = convertStyle($s?.['ui.bar.audio.hint.text']); const playButtonState = $derived($audioPlayer.playing ? 'pause' : 'play'); - const iconColor = $derived($s['ui.bar.audio.icon']['color']); - const iconPlayColor = $derived($s['ui.bar.audio.play.icon']['color']); - const backgroundColor = $derived($s['ui.bar.audio']['background-color']); + const iconColor = $derived($s?.['ui.bar.audio.icon']['color']); + const iconPlayColor = $derived($s?.['ui.bar.audio.play.icon']['color']); + const backgroundColor = $derived($s?.['ui.bar.audio']['background-color']); const audioBarClass = $derived($refs.hasAudio?.timingFile ? 'audio-bar' : 'audio-bar-progress'); $effect(() => mayResetPlayMode($refs.hasAudio?.timing)); - $effect(() => updatePlaybackSpeed($userSettings['audio-speed'])); + $effect(() => updatePlaybackSpeed($userSettings['audio-speed'] as string));
diff --git a/src/lib/components/AudioPlaybackSpeed.svelte b/src/lib/components/AudioPlaybackSpeed.svelte index d6657a8dd..bcb4c116a 100644 --- a/src/lib/components/AudioPlaybackSpeed.svelte +++ b/src/lib/components/AudioPlaybackSpeed.svelte @@ -1,4 +1,4 @@ - - +

{$t['Settings_Audio_Speed']} @@ -41,7 +41,7 @@ type="radio" name="speed" {value} - on:click={setPlaySpeed} + on:click={(e) => setPlaySpeed(e.currentTarget.value)} checked={$userSettings['audio-speed'] === value} /> {label} diff --git a/src/lib/components/BookSelector.svelte b/src/lib/components/BookSelector.svelte index f63fca56f..b379675ca 100644 --- a/src/lib/components/BookSelector.svelte +++ b/src/lib/components/BookSelector.svelte @@ -2,8 +2,9 @@ @component The navbar component. --> - @@ -46,23 +45,23 @@ A component that displays the book tabs and allows the user to switch between th - {#each bookTabs.tabs as bookTab, i} + {#each bookTabs?.tabs as bookTab, i} diff --git a/src/lib/components/HorizontalPanes.svelte b/src/lib/components/HorizontalPanes.svelte deleted file mode 100644 index ba2e64a42..000000000 --- a/src/lib/components/HorizontalPanes.svelte +++ /dev/null @@ -1,125 +0,0 @@ - - - -
- {#each panes as p, i} -
- -
- {#if i !== panes.length - 1} -
onPointerdown(e, i)} - >
- {/if} - {/each} -
- - diff --git a/src/lib/components/LayoutOptions.svelte b/src/lib/components/LayoutOptions.svelte index 94ef569df..d3ea4059d 100644 --- a/src/lib/components/LayoutOptions.svelte +++ b/src/lib/components/LayoutOptions.svelte @@ -3,17 +3,8 @@ Displays the three different layout option menus. -->
- {#if layoutOption === LAYOUT_SINGLE} + {#if layoutOption === Layout.Single}

{$t['Layout_Single_Pane']}

@@ -92,7 +87,7 @@ Displays the three different layout option menus. menuaction={(event) => handleClick(event, 0)} /> - {:else if layoutOption === LAYOUT_TWO} + {:else if layoutOption === Layout.Two}

{$t['Layout_Two_Pane']}

@@ -101,24 +96,24 @@ Displays the three different layout option menus.
{#snippet label()} -
+
{i + 1}.
-
+
{collection.name}
{#if collection.description}
{collection.description}
{/if}
- +
{/snippet} {#snippet content()} @@ -135,7 +130,7 @@ Displays the three different layout option menus. {/each}
- {:else if layoutOption === LAYOUT_VERSE_BY_VERSE} + {:else if layoutOption === Layout.VerseByVerse}

{$t['Layout_Interlinear']}

@@ -143,24 +138,24 @@ Displays the three different layout option menus.
{#snippet label()} -
+
{i + 1}.
-
+
{collection.name}
{#if collection.description}
{collection.description}
{/if}
- +
{/snippet} {#snippet content()} diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte index 281210cf6..3c70c768a 100644 --- a/src/lib/components/Modal.svelte +++ b/src/lib/components/Modal.svelte @@ -8,19 +8,26 @@ See https://daisyui.com/components/modal/#modal-that-closes-when-clicked-outside @prop { String } addCSS - CSS to inject into the model contents div/form. @prop { Function } onclose - Function to run when Modal closes. --> - @@ -33,7 +40,7 @@ See https://daisyui.com/components/modal/#modal-that-closes-when-clicked-outside >
{@render children?.()} diff --git a/src/lib/components/Navbar.svelte b/src/lib/components/Navbar.svelte index 5214837ed..a9a35cedd 100644 --- a/src/lib/components/Navbar.svelte +++ b/src/lib/components/Navbar.svelte @@ -8,7 +8,7 @@ The navbar component. convertStyle, direction, layout, - LAYOUT_TWO, + Layout, NAVBAR_HEIGHT, s, showDesktopSidebar @@ -27,14 +27,14 @@ The navbar component. let { showBackButton = true, start, center, end, backNavigation }: Props = $props(); function handleBackNavigation() { - if (backNavigation) { + if (backNavigation && page.route.id) { backNavigation(page.route.id); } else { gotoRoute(`/`); } } - let actionBarColor = $derived($s['ui.bar.action']['background-color']); + let actionBarColor = $derived($s?.['ui.bar.action']['background-color']); - - + {#each Object.keys(categories) as category}
{$t[category]}
- {#each categories[category] as setting, i} + {#each categories[category as SettingsCategory] as setting, i} {#if setting.type === 'checkbox'}
{#if setting.summary} @@ -64,7 +57,7 @@ bind:value={$userSettings[setting.key]} > {#each setting.entries ?? [] as entry, i} - + {/each} {#if setting.summary} diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index c557efd05..b3df9242f 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -2,12 +2,13 @@ @component The sidebar/drawer. --> - - + closeOnEscape(e.key)} />
@@ -252,18 +255,18 @@ The sidebar/drawer.
  • - {#if item.images.length > 1} + {#if (item.images?.length ?? 0) > 1} {/if} {item.title[$language] - {#if hasTabs} -
    +
    {#each Object.keys(options) as opt} {#if options[opt].visible} @@ -62,7 +68,7 @@ A component to display tabbed menus.
    {/if}
    - - - -
    -
    - -
    -
    -
    - -
    -
    - - diff --git a/src/lib/data/stores/collection.ts b/src/lib/data/stores/collection.ts index 14bd64ce5..50404f8e5 100644 --- a/src/lib/data/stores/collection.ts +++ b/src/lib/data/stores/collection.ts @@ -1,6 +1,6 @@ import { scriptureConfig } from '$assets/config'; import { get, writable } from 'svelte/store'; -import { LAYOUT_SINGLE, LAYOUT_TWO, LAYOUT_VERSE_BY_VERSE } from './view.js'; +import { Layout } from './view'; function findCollection(id: string): App.CollectionEntry | undefined { const ds = scriptureConfig.bookCollections?.find((x) => x.id === id); @@ -63,20 +63,15 @@ function createSelectedLayouts() { return { subscribe: external.subscribe, set: external.set, - collections: (mode) => { - let value; + collections: (mode: Layout) => { switch (mode) { - case LAYOUT_SINGLE: - value = [get(external).singlePane]; - break; - case LAYOUT_TWO: - value = get(external).sideBySide; - break; - case LAYOUT_VERSE_BY_VERSE: - value = get(external).verseByVerse; - break; + case Layout.Single: + return [get(external).singlePane]; + case Layout.Two: + return get(external).sideBySide; + case Layout.VerseByVerse: + return get(external).verseByVerse; } - return value; }, reset: () => { external.set(initCollections); diff --git a/src/lib/data/stores/interface.ts b/src/lib/data/stores/interface.ts index 3febf526d..45199c951 100644 --- a/src/lib/data/stores/interface.ts +++ b/src/lib/data/stores/interface.ts @@ -4,17 +4,20 @@ import { refs } from './scripture'; import { userSettings } from './setting'; export const direction = derived([refs, userSettings], ([$refs, $userSettings]) => { - let direction = config.mainFeatures['settings-app-layout-direction'] - ? $userSettings['app-layout-direction'] - : config.mainFeatures['app-layout-direction']; + let direction = ( + config.mainFeatures['settings-app-layout-direction'] + ? $userSettings['app-layout-direction'] + : config.mainFeatures['app-layout-direction'] + ) as string; if (direction === 'interface-language') { - const code = $userSettings['interface-language']; + const code = $userSettings['interface-language'] as string; if (code) { - direction = config.interfaceLanguages?.writingSystems[code].textDirection; + direction = config.interfaceLanguages?.writingSystems[code].textDirection ?? direction; } } else if (direction === 'text') { - direction = scriptureConfig.bookCollections?.find((x) => x.id === $refs.collection)?.style - ?.textDirection; + direction = + scriptureConfig.bookCollections?.find((x) => x.id === $refs.collection)?.style + ?.textDirection ?? direction; } - return direction.toLowerCase(); + return direction.toLowerCase() as 'ltr' | 'rtl'; }); diff --git a/src/lib/data/stores/localization.ts b/src/lib/data/stores/localization.ts index 737a63ec7..d91b94bb7 100644 --- a/src/lib/data/stores/localization.ts +++ b/src/lib/data/stores/localization.ts @@ -6,12 +6,12 @@ import { userSettings } from './setting'; /** localization */ // If a word can't be translated in the current language, use languageDefault. -export const languageDefault = config.translationMappings?.defaultLang; +export const languageDefault = config.translationMappings?.defaultLang ?? ''; export const languages = getLanguages(); export const language = derived( userSettings, - ($userSettings) => $userSettings['interface-language'] ?? languageDefault + ($userSettings) => ($userSettings['interface-language'] as string) || languageDefault ); export const t = derived( diff --git a/src/lib/data/stores/reference.ts b/src/lib/data/stores/reference.ts index 3dcfc0c53..49a2ba79e 100644 --- a/src/lib/data/stores/reference.ts +++ b/src/lib/data/stores/reference.ts @@ -3,12 +3,15 @@ import { NavigationContext } from '$lib/data/navigation'; import { derived, writable } from 'svelte/store'; import type { CatalogData } from '../catalogData'; -interface ReferenceStore { +export interface Reference { docSet: string; - collection: string; book: string; chapter: string; verse: string; +} + +interface ReferenceStore extends Reference { + collection: string; chapterVerses: string; numVerses: number; hasAudio: any; @@ -49,7 +52,7 @@ export const referenceStore = () => { localStorage.package = config.package; }; - const set = async ({ docSet, book, chapter, verse }) => { + const set = async ({ docSet, book, chapter, verse }: Reference) => { await nav.goto(docSet, book, chapter, verse); update(); }; diff --git a/src/lib/data/stores/setting.ts b/src/lib/data/stores/setting.ts index 2e80a01be..f8a2422a6 100644 --- a/src/lib/data/stores/setting.ts +++ b/src/lib/data/stores/setting.ts @@ -4,11 +4,14 @@ import { getDefaultLanguage } from '$lib/data/language'; import { mergeDefaultStorage, setDefaultStorage } from '$lib/data/stores/storage'; import { derived, readable, writable } from 'svelte/store'; -export const SETTINGS_CATEGORY_INTERFACE = 'Settings_Category_Interface'; -export const SETTINGS_CATEGORY_NAVIGATION = 'Settings_Category_Navigation'; -export const SETTINGS_CATEGORY_NOTIFICATIONS = 'Settings_Category_Notifications'; -export const SETTINGS_CATEGORY_AUDIO = 'Settings_Category_Audio'; -export const SETTINGS_CATEGORY_TEXT_DISPLAY = 'Settings_Category_Text_Display'; +export const SettingsCategory = { + Interface: 'Settings_Category_Interface', + Navigation: 'Settings_Category_Navigation', + Notifications: 'Settings_Category_Notifications', + Audio: 'Settings_Category_Audio', + TextDisplay: 'Settings_Category_Text_Display' +} as const; +export type SettingsCategory = (typeof SettingsCategory)[keyof typeof SettingsCategory]; setDefaultStorage('development', 'false'); export const development = readable(localStorage.development === 'true'); @@ -63,7 +66,7 @@ export const devPreferenceSettings = ((): Array => { if (isSAB(config)) { settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_INTERFACE, + category: SettingsCategory.Interface, title: 'Scripture Logs', key: 'scripture-logs' }); @@ -81,7 +84,7 @@ export const userPreferenceSettings = ((): Array => { // Verse Numbers settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Verse_Numbers', key: 'verse-numbers' }); @@ -91,7 +94,7 @@ export const userPreferenceSettings = ((): Array => { // Verse Layout settings.push({ type: 'list', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Verse_Layout', key: 'verse-layout', entries: ['Settings_Verse_Layout_Paragraphs', 'Settings_Verse_Layout_One_Per_Line'], @@ -103,7 +106,7 @@ export const userPreferenceSettings = ((): Array => { // Show Border settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Show_Border', summary: 'Settings_Show_Border_Summary', key: 'show-border' @@ -114,7 +117,7 @@ export const userPreferenceSettings = ((): Array => { // Red letters settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Red_Letters', summary: 'Settings_Red_Letters_Summary', key: 'red-letters' @@ -124,7 +127,7 @@ export const userPreferenceSettings = ((): Array => { if (config.mainFeatures['settings-glossary-links'] && config.traits['has-glossary']) { settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Glossary_Words', key: 'glossary-words' }); @@ -138,7 +141,7 @@ export const userPreferenceSettings = ((): Array => { // Images in Bible Text settings.push({ type: 'list', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Display_Images_In_Bible_Text', key: 'display-images-in-bible-text', entries: ['Settings_Display_Images_Normal', 'Settings_Display_Images_Hidden'], @@ -153,7 +156,7 @@ export const userPreferenceSettings = ((): Array => { // Videos in Bible Text settings.push({ type: 'list', - category: SETTINGS_CATEGORY_TEXT_DISPLAY, + category: SettingsCategory.TextDisplay, title: 'Settings_Display_Videos_In_Bible_Text', key: 'display-videos-in-bible-text', entries: ['Settings_Display_Videos_Normal', 'Settings_Display_Videos_Hidden'], @@ -169,7 +172,7 @@ export const userPreferenceSettings = ((): Array => { // Synchronised phrase highlighting settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_AUDIO, + category: SettingsCategory.Audio, title: 'Settings_Audio_Highlight_Phrase', summary: 'Settings_Audio_Highlight_Phrase_Summary', key: 'audio-highlight-phrase' @@ -180,7 +183,7 @@ export const userPreferenceSettings = ((): Array => { // Playback speed settings.push({ type: 'list', - category: SETTINGS_CATEGORY_AUDIO, + category: SettingsCategory.Audio, title: 'Settings_Audio_Speed', key: 'audio-speed', entries: [ @@ -205,7 +208,7 @@ export const userPreferenceSettings = ((): Array => { if (config.mainFeatures['settings-audio-access-method'] && hasAudioSourceWithAccessModeChoice) { settings.push({ type: 'list', - category: SETTINGS_CATEGORY_AUDIO, + category: SettingsCategory.Audio, title: 'Settings_Audio_Access_Method', key: 'audio-access-method', entries: [ @@ -223,7 +226,7 @@ export const userPreferenceSettings = ((): Array => { if (config.mainFeatures['settings-audio-download-mode'] && hasAudioSourceWitbDownload) { settings.push({ type: 'list', - category: SETTINGS_CATEGORY_AUDIO, + category: SettingsCategory.Audio, title: 'Settings_Audio_Download_Mode', key: 'audio-auto-download', entries: [ @@ -240,7 +243,7 @@ export const userPreferenceSettings = ((): Array => { if (config.mainFeatures['settings-verse-of-the-day'] && hasVerses) { settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_NOTIFICATIONS, + category: SettingsCategory.Notifications, title: 'Settings_Verse_Of_The_Day', key: 'verse-of-the-day' }); @@ -250,7 +253,7 @@ export const userPreferenceSettings = ((): Array => { // "Time for verse of the day settings.push({ type: 'time', - category: SETTINGS_CATEGORY_NOTIFICATIONS, + category: SettingsCategory.Notifications, title: 'Settings_Verse_Of_The_Day_Time', key: 'verse-of-the-day-time' }); @@ -273,7 +276,7 @@ export const userPreferenceSettings = ((): Array => { settings.push({ type: 'list', - category: SETTINGS_CATEGORY_NOTIFICATIONS, + category: SettingsCategory.Notifications, title: 'Settings_Verse_Of_The_Day_Book_Collection', key: 'verse-of-the-day-book-collection', defaultValue, @@ -287,7 +290,7 @@ export const userPreferenceSettings = ((): Array => { // "Daily reminder" settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_NOTIFICATIONS, + category: SettingsCategory.Notifications, title: 'Settings_Daily_Reminder', key: 'daily-reminder' }); @@ -297,7 +300,7 @@ export const userPreferenceSettings = ((): Array => { // "Time for daily reminder" settings.push({ type: 'time', - category: SETTINGS_CATEGORY_NOTIFICATIONS, + category: SettingsCategory.Notifications, title: 'Settings_Daily_Reminder_Time', key: 'daily-reminder-time' }); @@ -308,7 +311,7 @@ export const userPreferenceSettings = ((): Array => { // Book Selection settings.push({ type: 'list', - category: SETTINGS_CATEGORY_NAVIGATION, + category: SettingsCategory.Navigation, title: 'Settings_Book_Selection', key: 'book-selection', entries: ['Settings_Book_Selection_List', 'Settings_Book_Selection_Grid'], @@ -320,7 +323,7 @@ export const userPreferenceSettings = ((): Array => { // Verse Selector settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_NAVIGATION, + category: SettingsCategory.Navigation, title: 'Settings_Verse_Selection', key: 'verse-selection' }); @@ -333,7 +336,7 @@ export const userPreferenceSettings = ((): Array => { // Keep Screen on settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_INTERFACE, + category: SettingsCategory.Interface, title: 'Settings_Keep_Screen_On', key: 'keep-screen-on' }); @@ -344,7 +347,7 @@ export const userPreferenceSettings = ((): Array => { // Share app usage data settings.push({ type: 'checkbox', - category: SETTINGS_CATEGORY_INTERFACE, + category: SettingsCategory.Interface, title: 'Settings_Share_Usage_Data', key: 'share-usage-data' }); @@ -362,7 +365,7 @@ export const userPreferenceSettings = ((): Array => { settings.push({ type: 'list', - category: SETTINGS_CATEGORY_INTERFACE, + category: SettingsCategory.Interface, title: 'Settings_Interface_Language', key: 'interface-language', defaultValue: getDefaultLanguage(), @@ -375,7 +378,7 @@ export const userPreferenceSettings = ((): Array => { // App Layout Direction settings.push({ type: 'list', - category: SETTINGS_CATEGORY_INTERFACE, + category: SettingsCategory.Interface, title: 'Settings_Layout_Direction', key: 'app-layout-direction', entries: [ @@ -401,7 +404,7 @@ function defaultUserSettings() { ); } mergeDefaultStorage('userSettings', defaultUserSettings()); -export const userSettings = writable(JSON.parse(localStorage.userSettings)); +export const userSettings = writable(JSON.parse(localStorage.userSettings) as FeatureConfig); userSettings.subscribe((value) => (localStorage.userSettings = JSON.stringify(value))); export const userSettingsOrDefault = derived(userSettings, ($userSettings) => { diff --git a/src/lib/data/stores/theme.ts b/src/lib/data/stores/theme.ts index a26c101ee..83164292e 100644 --- a/src/lib/data/stores/theme.ts +++ b/src/lib/data/stores/theme.ts @@ -37,10 +37,12 @@ const resolveColor = (colorValue: string, colors: Record) => { }; // Convert style to string format for inline styling -export const convertStyle = (style: Record) => { +export const convertStyle = (style?: Record) => { let result = ''; - for (const x in style) { - result += `${x}:${style[x]};`; + if (style) { + for (const x in style) { + result += `${x}:${style[x]};`; + } } return result; }; diff --git a/src/lib/data/stores/view.ts b/src/lib/data/stores/view.ts index d6a34c898..7406af70d 100644 --- a/src/lib/data/stores/view.ts +++ b/src/lib/data/stores/view.ts @@ -15,12 +15,15 @@ export const isFirstLaunch = derived( ); /**the current view/layout mode*/ -export const LAYOUT_SINGLE = 'single'; -export const LAYOUT_TWO = 'two'; -export const LAYOUT_VERSE_BY_VERSE = 'verse-by-verse'; +export const Layout = { + Single: 'single', + Two: 'two', + VerseByVerse: 'verse-by-verse' +} as const; +export type Layout = (typeof Layout)[keyof typeof Layout]; -const singleLayout = { mode: LAYOUT_SINGLE, auxDocSets: [] }; -export const layout = writable(singleLayout); +const singleLayout = { mode: Layout.Single, auxDocSets: [] }; +export const layout = writable<{ mode: Layout; auxDocSets?: string[] }>(singleLayout); export const ModalType = { Collection: 'collection', diff --git a/src/lib/scripts/numeralSystem.ts b/src/lib/scripts/numeralSystem.ts index 35e026622..3e9a6bf50 100644 --- a/src/lib/scripts/numeralSystem.ts +++ b/src/lib/scripts/numeralSystem.ts @@ -71,7 +71,7 @@ export function formatNumberRange(system: NumeralSystem, value: string, directio : `${formattedFirst}${separator}${formattedSecond}`; } -export function formatNumber(system: NumeralSystem, value: string): any { +export function formatNumber(system: NumeralSystem, value: string): string { let fmt = ''; for (let i = 0; i < value.length; i++) { const digit = Number(value.charAt(i)); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3bb890f1e..a0e0859c1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -66,7 +66,7 @@ $modal.forEach(({ modalType, data }) => { switch (modalType) { case ModalType.Collection: - collectionSelector.showModal(); + collectionSelector?.showModal(); break; case ModalType.Note: noteDialog?.showModal( @@ -74,18 +74,20 @@ ); break; case ModalType.TextAppearance: - textAppearanceSelector.options = data; - textAppearanceSelector.showModal(); + if (textAppearanceSelector) { + textAppearanceSelector.options = data; + textAppearanceSelector.showModal(); + } break; case ModalType.Font: - fontSelector.showModal(); + fontSelector?.showModal(); break; case ModalType.StopPlan: planStopId = data as string; planStopDialog?.showModal(); break; case ModalType.PlaybackSpeed: - audioPlaybackSpeed.showModal(); + audioPlaybackSpeed?.showModal(); break; } }); @@ -103,13 +105,13 @@ } }); - let textAppearanceSelector: TextAppearanceSelector = $state(); - let collectionSelector: CollectionSelector = $state(); - let fontSelector: FontSelector = $state(); + let textAppearanceSelector: TextAppearanceSelector | undefined = $state(); + let collectionSelector: CollectionSelector | undefined = $state(); + let fontSelector: FontSelector | undefined = $state(); let noteDialog: NoteDialog | undefined = $state(); let planStopDialog: PlanStopDialog | undefined = $state(undefined); let planStopId: string = $state(''); - let audioPlaybackSpeed: AudioPlaybackSpeed = $state(); + let audioPlaybackSpeed: AudioPlaybackSpeed | undefined = $state(); From 3dafcd5fc5138d01bc3570ceb632258e00fc556c Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Tue, 19 May 2026 15:33:15 -0500 Subject: [PATCH 2/7] Fix leftover component issues --- src/app.d.ts | 11 ++-- src/lib/components/BookSelector.svelte | 40 ++++++++++---- src/lib/components/ChapterSelector.svelte | 55 ++++++++++--------- src/lib/components/CollectionSelector.svelte | 31 +++++++---- src/lib/components/SelectGrid.svelte | 23 ++++---- src/lib/components/TabsMenu.svelte | 21 ++++--- .../components/TextAppearanceSelector.svelte | 41 ++++++++------ src/routes/+layout.svelte | 8 +-- 8 files changed, 135 insertions(+), 95 deletions(-) diff --git a/src/app.d.ts b/src/app.d.ts index dec751c65..14a93d478 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,6 +1,7 @@ /// /// /// +/// // See https://kit.svelte.dev/docs/types#app // for information about these interfaces @@ -40,14 +41,16 @@ declare namespace App { penColor: string; } + type MenuActionHandler = (args: { text: string; url: string }) => void; + + type TabMenuActionHandler = (args: { text: string; url: string; tab: string }) => void; + interface TabMenuOptions { [key: string]: { tab?: { - component: any; - props?: any; + icon?: Snippet<[string]>; }; - component: any; - props: any; + snippet?: Snippet<[string, TabMenuActionHandler]>; visible: boolean; }; } diff --git a/src/lib/components/BookSelector.svelte b/src/lib/components/BookSelector.svelte index b379675ca..03332a24e 100644 --- a/src/lib/components/BookSelector.svelte +++ b/src/lib/components/BookSelector.svelte @@ -31,9 +31,9 @@ The navbar component. ?.books.find((x) => book === x.id)?.chaptersLabels ?? {} ); - const showChapterSelector = config.mainFeatures['show-chapter-selector-after-book']; + const showChapterSelector = config.mainFeatures['show-chapter-selector-after-book'] as boolean; const listView = $derived($userSettingsOrDefault['book-selection'] === 'list'); - const showVerseSelector = $derived($userSettingsOrDefault['verse-selection']); + const showVerseSelector = $derived($userSettingsOrDefault['verse-selection'] as boolean); const showSelectors = $derived(config.mainFeatures['book-select'] !== 'none'); // Translated book, chapter, and verse tab labels @@ -289,7 +289,7 @@ The navbar component. }; const options = $derived({ [b]: { - component: listView ? SelectList : SelectGrid, + component: listView ? selectList : selectGrid, props: { options: bookGridGroup({ colId: $refs.collection, @@ -299,22 +299,40 @@ The navbar component. visible: true }, [c]: { - component: SelectGrid, - props: { - options: chapterGridGroup(chapters) - }, + snippet: selectGrid, visible: showChapterSelector }, [v]: { - component: SelectGrid, - props: { - options: verseGridGroup(chapter) - }, + snippet: selectGrid, visible: showChapterSelector && showVerseSelector } }); +{#snippet selectList(bcv: string, menuaction: App.MenuActionHandler)} + +{/snippet} + +{#snippet selectGrid(bcv: string, menuaction: App.MenuActionHandler)} + +{/snippet} + {#if showSelectors} diff --git a/src/lib/components/ChapterSelector.svelte b/src/lib/components/ChapterSelector.svelte index 94e52f0e0..d9b229619 100644 --- a/src/lib/components/ChapterSelector.svelte +++ b/src/lib/components/ChapterSelector.svelte @@ -18,7 +18,7 @@ The navbar component. // Needs testing, does updating the book correctly effect what chapters or verses are availible in the next tab? const book = $derived($nextRef.book === '' ? $refs.book : $nextRef.book); const chapter = $derived($nextRef.chapter === '' ? $refs.chapter : $nextRef.chapter); - const showVerseSelector = $derived($userSettingsOrDefault['verse-selection']); + const showVerseSelector = $derived($userSettingsOrDefault['verse-selection']) as boolean; const verseCount = $derived(getVerseCount(book, chapter)); const numeralSystem = $derived(numerals.systemForBook(config, $refs.collection, book)); const chaptersLabels = $derived( @@ -164,6 +164,31 @@ The navbar component. const canSelect = config.mainFeatures['show-chapter-selector']; +{#snippet selectGrid(cv: string, menuaction: App.MenuActionHandler)} + x.bookCode === book)?.hasIntroduction + ? [ + { + label: $t['Chapter_Introduction_Title'], + id: 'i' + } + ] + : undefined, + cells: Object.keys(chapters).map((x) => ({ + label: getChapterLabel(x), + id: x + })) + } + ] + : verseGridGroup(chapter)} + {menuaction} + /> +{/snippet} + {#if showSelector && ($nextRef.book === '' || $nextRef.chapter !== '')} @@ -182,35 +207,11 @@ The navbar component. bind:this={chapterSelector} options={{ [c]: { - component: SelectGrid, - props: { - cols: 5, - options: [ - { - rows: books.find((x) => x.bookCode === book) - ?.hasIntroduction - ? [ - { - label: $t['Chapter_Introduction_Title'], - id: 'i' - } - ] - : null, - cells: Object.keys(chapters).map((x) => ({ - label: getChapterLabel(x), - id: x - })) - } - ] - }, + snippet: selectGrid, visible: true }, [v]: { - component: SelectGrid, - props: { - cols: 5, - options: verseGridGroup(chapter) - }, + snippet: selectGrid, visible: showVerseSelector } }} diff --git a/src/lib/components/CollectionSelector.svelte b/src/lib/components/CollectionSelector.svelte index 1892e9b5d..7d17cd626 100644 --- a/src/lib/components/CollectionSelector.svelte +++ b/src/lib/components/CollectionSelector.svelte @@ -14,7 +14,6 @@ Book Collection Selector component. const modalId = 'collectionSelector'; let modal: Modal; - let tabMenu; let tabMenuActive = $state(Layout.Single); // values of selectedLayouts before user makes changes @@ -60,27 +59,37 @@ Book Collection Selector component. } +{#snippet layoutOptions(layoutOption: Layout, menuaction: App.MenuActionHandler)} + +{/snippet} + +{#snippet icon(mode: Layout)} + {#if mode === Layout.Single} + + {:else if mode === Layout.Two} + + {:else} + + {/if} +{/snippet} + = rect.left && touch.clientX <= rect.right && touch.clientY >= rect.top && touch.clientY <= rect.bottom ) { - hovered = element.id; + hovered = element?.id || null; } else { hovered = null; } @@ -45,7 +46,7 @@ A component to display menu options in a grid. const cellStyle = $derived( convertStyle( Object.fromEntries( - Object.entries($s['ui.button.book-grid']).filter( + Object.entries($s?.['ui.button.book-grid'] ?? {}).filter( ([key]) => key != 'background-color' ) ) @@ -54,13 +55,13 @@ A component to display menu options in a grid. const rowStyle = $derived( convertStyle( Object.fromEntries( - Object.entries($s['ui.button.chapter-intro']).filter( + Object.entries($s?.['ui.button.chapter-intro'] ?? '').filter( ([key]) => key != 'background-color' ) ) ) ); - const headerStyle = $derived(convertStyle($s['ui.text.book-group-title'])); + const headerStyle = $derived(convertStyle($s?.['ui.text.book-group-title'])); const bookCollectionColor = $derived((id: string, category: string) => { const section = scriptureConfig.bookCollections @@ -69,7 +70,7 @@ A component to display menu options in a grid. let color = section && Object.keys($themeBookColors).includes(section) ? $themeBookColors[section] - : $s[category]['background-color']; + : $s?.[category]['background-color']; return color; }); diff --git a/src/lib/components/TabsMenu.svelte b/src/lib/components/TabsMenu.svelte index 836e1e6a9..5fa255a63 100644 --- a/src/lib/components/TabsMenu.svelte +++ b/src/lib/components/TabsMenu.svelte @@ -8,20 +8,20 @@ A component to display tabbed menus. import { preventDefault } from '$lib/scripts/event-wrappers'; let { - options = { '': { component: '', props: {}, visible: true } }, + options = { '': { visible: true } }, active = $bindable(Object.keys(options).filter((x) => options[x].visible)[0]), scroll = true, height = '50vh', menuaction }: { options: App.TabMenuOptions; - active: string; - scroll: boolean; - height: string; - menuaction; + active?: string; + scroll?: boolean; + height?: string; + menuaction?: App.TabMenuActionHandler; } = $props(); - const hasTabs = Object.keys(options).filter((x) => options[x].visible).length > 1; + const hasTabs = $derived(Object.keys(options).filter((x) => options[x].visible).length > 1); function handleMenuaction({ text, url }: { text: string; url: string }) { menuaction?.({ text: text, @@ -37,7 +37,7 @@ A component to display tabbed menus. } active = tab; }; - const ActiveComponent = $derived(options[active].component); + const ActiveComponent = $derived(options[active].snippet); {#if hasTabs} @@ -56,9 +56,8 @@ A component to display tabbed menus. style:background="none" role="button" > - {#if options[opt].tab} - {@const TabComponent = options[opt].tab?.component} - + {#if options[opt].tab?.icon} + {@render options[opt].tab.icon(opt)} {:else} {opt} {/if} @@ -73,5 +72,5 @@ A component to display tabbed menus. style:overflow-y={scroll ? 'auto' : ''} style:max-height={height} > - + {@render ActiveComponent?.(active, handleMenuaction)}
    diff --git a/src/lib/components/TextAppearanceSelector.svelte b/src/lib/components/TextAppearanceSelector.svelte index 09be253d6..7376dd329 100644 --- a/src/lib/components/TextAppearanceSelector.svelte +++ b/src/lib/components/TextAppearanceSelector.svelte @@ -3,7 +3,6 @@ The navbar component. We have sliders that update reactively to both font size and line height. 3 buttons to change the style from normal, sepia and dark. --> - @@ -32,12 +33,12 @@
    - {#if features['show-titles'] === true} + {#if features?.['show-titles'] === true}
    {item.title?.[$language] ?? item.title?.default ?? ''}
    {/if} - {#if features['show-subtitles'] === true} + {#if features?.['show-subtitles'] === true}
    {item.subtitle?.[$language] ?? item.subtitle?.default ?? ''}
    @@ -49,8 +50,8 @@
  • {/each} diff --git a/src/lib/components/TabsMenu.svelte b/src/lib/components/TabsMenu.svelte index 5fa255a63..ce6538778 100644 --- a/src/lib/components/TabsMenu.svelte +++ b/src/lib/components/TabsMenu.svelte @@ -37,7 +37,7 @@ A component to display tabbed menus. } active = tab; }; - const ActiveComponent = $derived(options[active].snippet); + const ActiveComponent = $derived(options[active]?.snippet); {#if hasTabs} diff --git a/src/routes/notes/edit/[noteid]/+page.svelte b/src/routes/notes/edit/[noteid]/+page.svelte index 174866be9..62f5c58c9 100644 --- a/src/routes/notes/edit/[noteid]/+page.svelte +++ b/src/routes/notes/edit/[noteid]/+page.svelte @@ -15,7 +15,8 @@ let textarea: HTMLTextAreaElement | undefined = $state(); const note = $derived(data.note); const isNew = $derived(note ? false : true); - let text = $derived(note?.text ?? ''); + // svelte-ignore state_referenced_locally + let text = $state(note?.text ?? ''); const reference = $derived(note?.reference ?? $selectedVerses[0]?.reference); const title = $derived(isNew ? 'Annotation_Note_Add' : 'Annotation_Note_Edit'); From c09f865b3882ddb6149e487854dc4fa822386444 Mon Sep 17 00:00:00 2001 From: Aidan Jones Date: Thu, 21 May 2026 16:16:39 -0500 Subject: [PATCH 6/7] Fix default language fallback --- src/lib/components/BottomNavigationBar.svelte | 4 ++-- src/lib/components/Sidebar.svelte | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/components/BottomNavigationBar.svelte b/src/lib/components/BottomNavigationBar.svelte index f16fb451b..e3393d63b 100644 --- a/src/lib/components/BottomNavigationBar.svelte +++ b/src/lib/components/BottomNavigationBar.svelte @@ -4,7 +4,7 @@ {#snippet selectList(bcv: string, menuaction: App.MenuActionHandler)}