diff --git a/package-lock.json b/package-lock.json index 588b61d..631d232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "libre-study", - "version": "0.1.12", + "version": "0.1.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "libre-study", - "version": "0.1.12", + "version": "0.1.13", "dependencies": { "@tauri-apps/api": "^2.11.1", "react": "^19.2.7", diff --git a/package.json b/package.json index 4613979..9e960ce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "libre-study", "private": true, - "version": "0.1.12", + "version": "0.1.13", "type": "module", "scripts": { "dev": "vite --host 127.0.0.1", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 09607e6..437bd2d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "app" -version = "0.1.12" +version = "0.1.13" dependencies = [ "log", "rusqlite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1fee6d6..b897271 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "0.1.12" +version = "0.1.13" description = "A Tauri App" authors = ["you"] license = "" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c2f8ecd..310346e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Libre Study", - "version": "0.1.12", + "version": "0.1.13", "identifier": "org.librestudy.desktop", "build": { "frontendDist": "../dist", diff --git a/src/App.css b/src/App.css index 3329dfb..06c67b9 100644 --- a/src/App.css +++ b/src/App.css @@ -461,6 +461,44 @@ textarea:focus-visible { background: #fffdf6; } +.margin-section-header { + display: flex; + gap: 8px; + align-items: center; + justify-content: space-between; +} + +.collapse-button, +.mini-settings { + border: 0; + background: transparent; + color: #657061; + font-size: 11px; + font-weight: 800; + letter-spacing: 0; + text-transform: uppercase; +} + +.collapse-button { + padding: 0; + text-align: left; +} + +.collapse-button::before { + margin-right: 5px; + color: #735c0f; + content: 'v'; +} + +.mini-settings { + width: auto; + height: auto; + padding: 3px 6px; + border: 1px solid #d8cfbf; + border-radius: 4px; + background: #fbf8ef; +} + .original-language-verse h3 { margin: 0; color: #657061; @@ -512,33 +550,40 @@ textarea:focus-visible { outline: none; } -.linked-word-list { - display: flex; - flex-wrap: wrap; +.english-strong-line { + display: grid; gap: 8px; margin: 0 0 14px; + padding: 12px; + border: 1px solid #dfd6c2; + border-radius: 6px; + background: #fffdf6; } -.word-link { - display: inline-flex; - align-items: baseline; - gap: 7px; - padding: 7px 9px; - border-color: #cfc8b8; - background: #f3efe4; +.linked-line-text { + margin: 0; color: #202124; - font-size: 13px; + line-height: 1.65; + overflow-wrap: anywhere; } -.word-link small { - color: #657061; - font-size: 11px; - font-weight: 800; +.linked-word { + display: inline; + padding: 0 2px; + border-radius: 3px; + cursor: pointer; + overflow-wrap: anywhere; } -.word-link.active { - border-color: #2f6f5e; - background: #e7f0eb; +.linked-word + .linked-word { + margin-inline-start: 0.35em; +} + +.linked-word:hover, +.linked-word:focus-visible, +.linked-word.active { + background: rgba(214, 168, 79, 0.25); + outline: none; } .strong-focus { @@ -559,6 +604,11 @@ textarea:focus-visible { font-size: 12px; } +.strong-focus > .margin-section-header span { + color: #657061; + font-size: 12px; +} + .strong-focus h4 { color: #1f211e; font-size: 22px; @@ -970,6 +1020,37 @@ textarea:focus-visible { width: min(1080px, 100%); } +.settings-modal { + width: min(780px, 100%); +} + +.settings-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.settings-grid .modal-section:last-child { + grid-column: 1 / -1; +} + +.settings-grid input[type='range'] { + padding: 0; +} + +.checkbox-row { + display: flex !important; + gap: 9px !important; + align-items: center; + color: #252821 !important; + font-size: 14px !important; + text-transform: none !important; +} + +.checkbox-row input { + width: auto; +} + .about-modal p { margin: 0; color: #3f443d; diff --git a/src/App.tsx b/src/App.tsx index 6631c87..e2baab9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ const tagColors = ['#2f6f5e', '#735c0f', '#8a3ffc', '#b42318', '#245ba7'] const pageSize = 34 const sessionStorageKey = 'libre-study.sessions.v1' const activeSessionStorageKey = 'libre-study.active-session.v1' +const studySettingsStorageKey = 'libre-study.study-settings.v1' const timelineLimit = 200 type ViewMode = 'page' | 'study' | 'notes' | 'compare' type StrongDisplayMode = 'off' | 'highlight' | 'numbers' @@ -155,6 +156,7 @@ function App() { const [sessionModalOpen, setSessionModalOpen] = useState(false) const [aboutModalOpen, setAboutModalOpen] = useState(false) const [timelineModalOpen, setTimelineModalOpen] = useState(false) + const [settingsModalOpen, setSettingsModalOpen] = useState(false) const [pageMotion, setPageMotion] = useState('idle') const [viewMode, setViewMode] = useState('notes') const [query, setQuery] = useState('') @@ -172,6 +174,11 @@ function App() { const [newSessionFootnotes, setNewSessionFootnotes] = useState(true) const [newSessionStrongs, setNewSessionStrongs] = useState(true) const [newSessionTimeline, setNewSessionTimeline] = useState(true) + const [lemmaFontSize, setLemmaFontSize] = useState(17) + const [linkedFontSize, setLinkedFontSize] = useState(15) + const [originalCollapsed, setOriginalCollapsed] = useState(false) + const [linkedCollapsed, setLinkedCollapsed] = useState(false) + const [definitionCollapsed, setDefinitionCollapsed] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const restoredSessionId = useRef('') @@ -300,10 +307,30 @@ function App() { } useEffect(() => { + const savedStudySettings = localStorage.getItem(studySettingsStorageKey) const savedSessions = localStorage.getItem(sessionStorageKey) const savedActiveSessionId = localStorage.getItem(activeSessionStorageKey) let nextSessions: StudySession[] = [] + if (savedStudySettings) { + try { + const parsed = JSON.parse(savedStudySettings) as Partial<{ + lemmaFontSize: number + linkedFontSize: number + originalCollapsed: boolean + linkedCollapsed: boolean + definitionCollapsed: boolean + }> + setLemmaFontSize(parsed.lemmaFontSize ?? 17) + setLinkedFontSize(parsed.linkedFontSize ?? 15) + setOriginalCollapsed(parsed.originalCollapsed ?? false) + setLinkedCollapsed(parsed.linkedCollapsed ?? false) + setDefinitionCollapsed(parsed.definitionCollapsed ?? false) + } catch { + localStorage.removeItem(studySettingsStorageKey) + } + } + if (savedSessions) { try { const parsed = JSON.parse(savedSessions) as StudySession[] @@ -350,6 +377,19 @@ function App() { } }, [activeSessionId]) + useEffect(() => { + localStorage.setItem( + studySettingsStorageKey, + JSON.stringify({ + lemmaFontSize, + linkedFontSize, + originalCollapsed, + linkedCollapsed, + definitionCollapsed, + }), + ) + }, [definitionCollapsed, lemmaFontSize, linkedCollapsed, linkedFontSize, originalCollapsed]) + useEffect(() => { if (restoredSessionId.current === activeSessionId) return const activeSession = sessions.find((session) => session.id === activeSessionId) @@ -718,27 +758,83 @@ function App() { return (
-

{isHebrew ? 'Hebrew Lemma Line' : 'Greek Lemma Line'}

-

- {selectedVerse.strongs.map((entry) => ( - selectStrong(selectedVerse, entry.strong)} - onKeyDown={(event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault() - selectStrong(selectedVerse, entry.strong) - } - }} - > - {entry.lemma || entry.surface} - - ))} -

+
+ + +
+ {!originalCollapsed ? ( +

+ {selectedVerse.strongs.map((entry) => ( + selectStrong(selectedVerse, entry.strong)} + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + selectStrong(selectedVerse, entry.strong) + } + }} + > + {entry.lemma || entry.surface} + + ))} +

+ ) : null} +
+ ) + } + + function renderLinkedEnglishLine() { + if (!selectedVerse?.strongs.length) return null + + return ( +
+
+ + +
+ {!linkedCollapsed ? ( +

+ {selectedVerse.strongs.map((entry) => ( + selectStrong(selectedVerse, entry.strong)} + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + selectStrong(selectedVerse, entry.strong) + } + }} + > + {entry.surface} + + ))} +

+ ) : null}
) } @@ -796,32 +892,33 @@ function App() { ) : ( <> {renderOriginalLanguageLine()} -
- {selectedVerse.strongs.map((entry) => ( - - ))} -
+ {renderLinkedEnglishLine()} {selectedStrongEntry ? ( -
-
- {selectedStrongEntry.strong} +
+
+ {selectedStrongEntry.language} -
-

{selectedStrongEntry.lemma}

-

{selectedStrongEntry.transliteration}

-

{selectedStrongEntry.definition}

- + + {!definitionCollapsed ? ( + <> +
+ {selectedStrongEntry.strong} + {selectedStrongEntry.language} +
+

{selectedStrongEntry.lemma}

+

{selectedStrongEntry.transliteration}

+

{selectedStrongEntry.definition}

+ + + ) : null}
) : null} @@ -1144,6 +1241,91 @@ function App() { ) } + function renderSettingsModal() { + if (!settingsModalOpen) return null + + return ( +
setSettingsModalOpen(false)}> +
event.stopPropagation()} + > +
+
+ Settings +

Study Display

+
+ +
+
+
+

Original Language Line

+ + +
+ +
+

English Strong's Line

+ + +
+ +
+

Definition Panel

+ +
+
+
+
+ ) + } + return (
)