From 123cb8dd9853dd097af2bc02c8a95371b7d7805d Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 12 Jul 2026 10:44:01 -0500 Subject: [PATCH] Compact reader controls and margin layout --- package-lock.json | 4 +- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 2 +- src/App.css | 214 +++++++++++++++++++-------- src/App.tsx | 299 +++++++++++++++++++++++++++++++++----- 7 files changed, 425 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index 164d097..5c9a1b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "libre-study", - "version": "0.1.16", + "version": "0.1.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "libre-study", - "version": "0.1.16", + "version": "0.1.17", "dependencies": { "@tauri-apps/api": "^2.11.1", "react": "^19.2.7", diff --git a/package.json b/package.json index 45c22ed..f8a4727 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "libre-study", "private": true, - "version": "0.1.16", + "version": "0.1.17", "type": "module", "scripts": { "dev": "vite --host 127.0.0.1", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 87f072d..8851a45 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "app" -version = "0.1.16" +version = "0.1.17" dependencies = [ "log", "rusqlite", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c96cbf4..be6089e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "0.1.16" +version = "0.1.17" description = "A Tauri App" authors = ["you"] license = "" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 1c8cc76..2838a42 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.16", + "version": "0.1.17", "identifier": "org.librestudy.desktop", "build": { "frontendDist": "../dist", diff --git a/src/App.css b/src/App.css index ebae107..b018513 100644 --- a/src/App.css +++ b/src/App.css @@ -11,6 +11,10 @@ filter: blur(1.5px) brightness(0.62) saturate(0.72); } +.app-shell.notes-pinned .reader { + padding-right: min(500px, 30vw); +} + .tool-rail { display: flex; flex-direction: column; @@ -155,14 +159,13 @@ gap: 10px; min-width: 0; height: 100vh; - padding: 18px 42px 18px 18px; + padding: 10px 48px 12px 12px; box-sizing: border-box; transition: filter 160ms ease; } .top-bar { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 6px; align-items: stretch; min-width: 0; @@ -170,15 +173,18 @@ .bible-top-tools { display: grid; - gap: 8px; + grid-template-columns: minmax(0, 1.42fr) minmax(160px, 1fr); + gap: 6px; + align-items: end; min-width: 0; } .study-top-tools { display: flex; - align-items: end; + align-items: center; + justify-content: flex-start; min-width: 0; - padding: 0 12px 3px; + padding: 4px 8px; border: 1px solid #d4cdbd; border-radius: 8px; background: #fbf8ef; @@ -188,6 +194,18 @@ text-transform: uppercase; } +.study-top-tools button { + min-height: 28px; + padding: 5px 9px; + background: #2f6f5e; + color: #fffef8; + font-size: 12px; +} + +.split-spacer { + min-width: 0; +} + .study-margin h2, .notes-workspace h2, .strong-focus h4 { @@ -206,7 +224,7 @@ .search-box { display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: 8px; + gap: 5px; } input, @@ -216,11 +234,11 @@ textarea { min-width: 0; border: 1px solid #bdb8aa; border-radius: 6px; - padding: 10px 12px; + padding: 5px 8px; background: #fffef9; color: #202124; font: inherit; - font-size: 14px; + font-size: 12px; box-sizing: border-box; } @@ -233,11 +251,11 @@ textarea { button { border: 1px solid transparent; border-radius: 6px; - padding: 10px 12px; + padding: 6px 9px; background: #d6a84f; color: #141510; font: inherit; - font-size: 14px; + font-size: 12px; font-weight: 700; cursor: pointer; } @@ -252,17 +270,17 @@ textarea:focus-visible { .page-controls { display: grid; - grid-template-columns: minmax(92px, 1fr) minmax(118px, 1.24fr) minmax(72px, 0.74fr) minmax(68px, 0.66fr) minmax(96px, 0.9fr); - gap: 8px; + grid-template-columns: minmax(62px, 0.8fr) minmax(92px, 1.25fr) minmax(54px, 0.55fr) minmax(50px, 0.5fr) minmax(80px, 0.82fr); + gap: 5px; align-items: end; max-width: none; } .page-controls label { display: grid; - gap: 4px; + gap: 2px; color: #657061; - font-size: 11px; + font-size: 9px; font-weight: 800; letter-spacing: 0; text-transform: uppercase; @@ -270,8 +288,7 @@ textarea:focus-visible { .study-desk { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); - gap: 6px; + gap: 0; min-height: 0; } @@ -282,6 +299,19 @@ textarea:focus-visible { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } +.split-resizer { + width: 6px; + min-width: 6px; + cursor: col-resize; + background: linear-gradient(90deg, transparent, #cfc6b4 45%, #cfc6b4 55%, transparent); +} + +.split-resizer:hover, +.split-resizer:focus-visible { + background: #d6a84f; + outline: none; +} + .page-system, .notes-workspace, .status-panel { @@ -310,7 +340,7 @@ textarea:focus-visible { grid-column: 1; grid-template-rows: auto minmax(0, 1fr); min-height: 0; - padding: 26px 30px; + padding: 22px 26px; } .bible-page header, @@ -327,7 +357,9 @@ textarea:focus-visible { .page-turner { grid-column: 1; - padding: 14px 30px; + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + padding: 8px 26px 6px; border-top: 1px solid #e8e2d4; background: #fffef8; text-transform: none; @@ -335,10 +367,50 @@ textarea:focus-visible { .page-turner span { color: #6b675d; - font-size: 13px; + font-size: 12px; font-weight: 700; } +.page-turner button { + align-self: start; + min-height: 26px; + padding: 4px 9px; +} + +.page-position { + display: grid; + gap: 4px; + justify-items: center; + min-width: 0; +} + +.chapter-tabs { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 3px; + max-height: 38px; + overflow: hidden; +} + +.chapter-tabs button { + min-width: 22px; + min-height: 20px; + padding: 2px 5px; + border-color: #cfc4ad; + background: #f2ead8; + color: #4c422f; + font-size: 10px; + line-height: 1; +} + +.chapter-tabs button.active, +.chapter-tabs button:hover, +.chapter-tabs button:focus-visible { + background: #d6a84f; + color: #151510; +} + .page-columns { column-count: 2; column-gap: 34px; @@ -419,7 +491,7 @@ textarea:focus-visible { .notes-workspace { min-height: 0; overflow: auto; - padding: 16px; + padding: 10px; } .study-margin { @@ -435,13 +507,13 @@ textarea:focus-visible { justify-content: space-between; gap: 12px; align-items: center; - padding-bottom: 12px; + padding-bottom: 8px; border-bottom: 1px solid #e4e0d6; } .panel-header h2, .notes-workspace h2 { - font-size: 18px; + font-size: 16px; } .panel-header span, @@ -453,16 +525,23 @@ textarea:focus-visible { } .margin-reference { - margin: 12px 0; + margin: 8px 0; font-weight: 800; } +.margin-resource-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + align-items: start; +} + .original-language-verse { display: grid; - gap: 8px; + gap: 5px; min-width: 0; - margin: 0 0 14px; - padding: 12px; + margin: 0; + padding: 8px; overflow: hidden; border: 1px solid #dfd6c2; border-radius: 6px; @@ -471,11 +550,11 @@ textarea:focus-visible { .margin-section-header { display: flex; - gap: 8px; + gap: 6px; align-items: center; justify-content: space-between; - margin: -6px -6px 2px; - padding: 7px 8px; + margin: -5px -5px 1px; + padding: 4px 6px; border-radius: 5px; background: #f5efe1; cursor: pointer; @@ -492,7 +571,7 @@ textarea:focus-visible { border: 0; background: transparent; color: #657061; - font-size: 11px; + font-size: 10px; font-weight: 800; letter-spacing: 0; text-transform: uppercase; @@ -505,16 +584,10 @@ textarea:focus-visible { cursor: pointer; } -.collapse-button::before { - margin-right: 5px; - color: #735c0f; - content: 'v'; -} - .mini-settings { width: auto; height: auto; - padding: 3px 6px; + padding: 2px 5px; border: 1px solid #d8cfbf; border-radius: 4px; background: #fbf8ef; @@ -541,7 +614,7 @@ textarea:focus-visible { color: #1f211e; font-family: Georgia, 'Times New Roman', serif; font-size: 20px; - line-height: 1.7; + line-height: 1.45; white-space: normal; word-break: normal; } @@ -573,9 +646,9 @@ textarea:focus-visible { .english-strong-line { display: grid; - gap: 8px; - margin: 0 0 14px; - padding: 12px; + gap: 5px; + margin: 0; + padding: 8px; border: 1px solid #dfd6c2; border-radius: 6px; background: #fffdf6; @@ -584,7 +657,7 @@ textarea:focus-visible { .linked-line-text { margin: 0; color: #202124; - line-height: 1.65; + line-height: 1.45; overflow-wrap: anywhere; } @@ -609,8 +682,8 @@ textarea:focus-visible { .strong-focus { display: grid; - gap: 9px; - padding: 16px; + gap: 6px; + padding: 8px; border: 1px solid #b9cabf; border-radius: 8px; background: #eef6f1; @@ -632,14 +705,14 @@ textarea:focus-visible { .strong-focus h4 { color: #1f211e; - font-size: 22px; + font-size: 18px; } .strong-focus p { margin: 0; color: #3f443d; - font-size: 14px; - line-height: 1.5; + font-size: 12px; + line-height: 1.38; } .strong-focus button { @@ -651,31 +724,36 @@ textarea:focus-visible { .resource-card { display: grid; - gap: 9px; - margin-top: 14px; - padding: 12px; + gap: 6px; + margin-top: 0; + padding: 8px; border: 1px solid #dfd6c2; border-radius: 6px; background: #fffdf6; } +.commentary-card, +.dictionary-card { + grid-column: 1 / -1; +} + .commentary-list, .dictionary-list { display: grid; - gap: 12px; + gap: 9px; } .commentary-list section, .dictionary-list section { display: grid; - gap: 5px; + gap: 4px; } .commentary-list h3, .dictionary-list h3 { margin: 0; color: #252821; - font-size: 14px; + font-size: 13px; letter-spacing: 0; } @@ -683,8 +761,8 @@ textarea:focus-visible { .dictionary-list p { margin: 0; color: #3f443d; - font-size: 13px; - line-height: 1.48; + font-size: 12px; + line-height: 1.38; } .commentary-list small { @@ -713,15 +791,15 @@ textarea:focus-visible { right: 0; z-index: 28; display: grid; - grid-template-columns: 30px minmax(360px, 30vw); - width: min(540px, calc(100vw - 92px)); + grid-template-columns: 42px minmax(380px, 30vw); + width: min(560px, calc(100vw - 92px)); height: 100vh; - transform: translateX(calc(100% - 30px)); + transform: translateX(calc(100% - 42px)); transition: transform 190ms ease, box-shadow 190ms ease; } .notes-drawer.open, -.notes-drawer:hover, +.notes-drawer.pinned, .notes-drawer:focus-within { transform: translateX(0); box-shadow: -18px 0 42px rgba(17, 20, 17, 0.24); @@ -740,6 +818,24 @@ textarea:focus-visible { font-weight: 900; letter-spacing: 0; cursor: pointer; + width: 42px; + height: 100%; + padding: 0; + border-top: 0; + border-bottom: 0; + border-radius: 0; +} + +.notes-workspace > header { + display: flex; + align-items: start; + justify-content: space-between; + gap: 10px; +} + +.pin-notes { + background: #2f6f5e; + color: #fffef8; } .notes-drawer .notes-workspace { diff --git a/src/App.tsx b/src/App.tsx index de2caf1..2037341 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { getLibraryState, searchVerses, } from './studyClient' -import type { DragEvent, ReactNode } from 'react' +import type { DragEvent, MouseEvent as ReactMouseEvent, ReactNode } from 'react' import type { CommentaryEntry, DictionaryEntry, Verse } from './types' import './App.css' @@ -219,6 +219,7 @@ function App() { const [timelineModalOpen, setTimelineModalOpen] = useState(false) const [settingsModalOpen, setSettingsModalOpen] = useState(false) const [notesDrawerOpen, setNotesDrawerOpen] = useState(false) + const [notesPinned, setNotesPinned] = useState(false) const [pageMotion, setPageMotion] = useState('idle') const [viewMode, setViewMode] = useState('notes') const [query, setQuery] = useState('') @@ -245,6 +246,12 @@ function App() { const [definitionCollapsed, setDefinitionCollapsed] = useState(false) const [commentaryCollapsed, setCommentaryCollapsed] = useState(false) const [dictionaryCollapsed, setDictionaryCollapsed] = useState(false) + const [showOriginalLine, setShowOriginalLine] = useState(true) + const [showLinkedLine, setShowLinkedLine] = useState(true) + const [showDefinitionPanel, setShowDefinitionPanel] = useState(true) + const [showCommentaryPanel, setShowCommentaryPanel] = useState(true) + const [showDictionaryPanel, setShowDictionaryPanel] = useState(true) + const [splitPercent, setSplitPercent] = useState(50) const [commentaryEntries, setCommentaryEntries] = useState([]) const [dictionaryEntries, setDictionaryEntries] = useState([]) const [marginResourcesLoading, setMarginResourcesLoading] = useState(false) @@ -252,6 +259,8 @@ function App() { const [error, setError] = useState(null) const restoredSessionId = useRef('') const pageMotionTimeout = useRef(null) + const notesDragOpenTimeout = useRef(null) + const readerRef = useRef(null) async function loadLibrary() { setError(null) @@ -389,12 +398,30 @@ function App() { originalCollapsed: boolean linkedCollapsed: boolean definitionCollapsed: boolean + commentaryCollapsed: boolean + dictionaryCollapsed: boolean + showOriginalLine: boolean + showLinkedLine: boolean + showDefinitionPanel: boolean + showCommentaryPanel: boolean + showDictionaryPanel: boolean + splitPercent: number + notesPinned: boolean }> setLemmaFontSize(parsed.lemmaFontSize ?? 17) setLinkedFontSize(parsed.linkedFontSize ?? 15) setOriginalCollapsed(parsed.originalCollapsed ?? false) setLinkedCollapsed(parsed.linkedCollapsed ?? false) setDefinitionCollapsed(parsed.definitionCollapsed ?? false) + setCommentaryCollapsed(parsed.commentaryCollapsed ?? false) + setDictionaryCollapsed(parsed.dictionaryCollapsed ?? false) + setShowOriginalLine(parsed.showOriginalLine ?? true) + setShowLinkedLine(parsed.showLinkedLine ?? true) + setShowDefinitionPanel(parsed.showDefinitionPanel ?? true) + setShowCommentaryPanel(parsed.showCommentaryPanel ?? true) + setShowDictionaryPanel(parsed.showDictionaryPanel ?? true) + setSplitPercent(parsed.splitPercent ?? 50) + setNotesPinned(parsed.notesPinned ?? false) } catch { localStorage.removeItem(studySettingsStorageKey) } @@ -457,9 +484,33 @@ function App() { originalCollapsed, linkedCollapsed, definitionCollapsed, + commentaryCollapsed, + dictionaryCollapsed, + showOriginalLine, + showLinkedLine, + showDefinitionPanel, + showCommentaryPanel, + showDictionaryPanel, + splitPercent, + notesPinned, }), ) - }, [definitionCollapsed, lemmaFontSize, linkedCollapsed, linkedFontSize, originalCollapsed]) + }, [ + commentaryCollapsed, + definitionCollapsed, + dictionaryCollapsed, + lemmaFontSize, + linkedCollapsed, + linkedFontSize, + notesPinned, + originalCollapsed, + showCommentaryPanel, + showDefinitionPanel, + showDictionaryPanel, + showLinkedLine, + showOriginalLine, + splitPercent, + ]) useEffect(() => { if (restoredSessionId.current === activeSessionId) return @@ -724,6 +775,34 @@ function App() { ) } + function recordDisplaySetting(label: string) { + recordTimeline({ + type: 'setting', + label, + }) + } + + function beginSplitResize(event: ReactMouseEvent) { + event.preventDefault() + const reader = readerRef.current + if (!reader) return + + const resize = (moveEvent: MouseEvent) => { + const rect = reader.getBoundingClientRect() + const next = ((moveEvent.clientX - rect.left) / rect.width) * 100 + setSplitPercent(Math.min(68, Math.max(36, next))) + } + + const stop = () => { + window.removeEventListener('mousemove', resize) + window.removeEventListener('mouseup', stop) + recordDisplaySetting('Adjusted Bible and study margin split') + } + + window.addEventListener('mousemove', resize) + window.addEventListener('mouseup', stop, { once: true }) + } + function updateNoteDraft(value: string) { setNoteDraft(value) patchActiveSession({ noteDraft: value }) @@ -739,7 +818,20 @@ function App() { function startTextDrag(event: DragEvent, value: string) { event.dataTransfer.setData('text/plain', value) event.dataTransfer.effectAllowed = 'copy' - setNotesDrawerOpen(true) + } + + function scheduleNotesDragOpen() { + if (notesPinned || notesDragOpenTimeout.current) return + notesDragOpenTimeout.current = window.setTimeout(() => { + setNotesDrawerOpen(true) + notesDragOpenTimeout.current = null + }, 550) + } + + function clearNotesDragOpen() { + if (!notesDragOpenTimeout.current) return + window.clearTimeout(notesDragOpenTimeout.current) + notesDragOpenTimeout.current = null } function renameActiveSession(title: string) { @@ -881,7 +973,7 @@ function App() { } function renderOriginalLanguageLine() { - if (!selectedVerse?.strongs.length) return null + if (!showOriginalLine || !selectedVerse?.strongs.length) return null const isHebrew = selectedVerse.strongs.some((entry) => entry.strong.startsWith('H')) const direction = isHebrew ? 'rtl' : 'ltr' @@ -891,11 +983,15 @@ function App() { className="margin-section-header" role="button" tabIndex={0} - onClick={() => setOriginalCollapsed((current) => !current)} + onClick={() => { + setOriginalCollapsed((current) => !current) + recordDisplaySetting('Toggled original language accordion') + }} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() setOriginalCollapsed((current) => !current) + recordDisplaySetting('Toggled original language accordion') } }} > @@ -943,7 +1039,7 @@ function App() { } function renderLinkedEnglishLine() { - if (!selectedVerse?.strongs.length) return null + if (!showLinkedLine || !selectedVerse?.strongs.length) return null return (
@@ -951,11 +1047,15 @@ function App() { className="margin-section-header" role="button" tabIndex={0} - onClick={() => setLinkedCollapsed((current) => !current)} + onClick={() => { + setLinkedCollapsed((current) => !current) + recordDisplaySetting('Toggled English linked text accordion') + }} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() setLinkedCollapsed((current) => !current) + recordDisplaySetting('Toggled English linked text accordion') } }} > @@ -1003,6 +1103,8 @@ function App() { } function renderCommentarySection() { + if (!showCommentaryPanel) return null + if (activeSession?.commentaryEnabled === false) { return (
@@ -1021,11 +1123,15 @@ function App() { className="margin-section-header" role="button" tabIndex={0} - onClick={() => setCommentaryCollapsed((current) => !current)} + onClick={() => { + setCommentaryCollapsed((current) => !current) + recordDisplaySetting('Toggled commentary accordion') + }} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() setCommentaryCollapsed((current) => !current) + recordDisplaySetting('Toggled commentary accordion') } }} > @@ -1060,6 +1166,8 @@ function App() { } function renderDictionarySection() { + if (!showDictionaryPanel) return null + if (activeSession?.dictionaryEnabled === false) { return (
@@ -1078,11 +1186,15 @@ function App() { className="margin-section-header" role="button" tabIndex={0} - onClick={() => setDictionaryCollapsed((current) => !current)} + onClick={() => { + setDictionaryCollapsed((current) => !current) + recordDisplaySetting('Toggled dictionary accordion') + }} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() setDictionaryCollapsed((current) => !current) + recordDisplaySetting('Toggled dictionary accordion') } }} > @@ -1158,7 +1270,21 @@ function App() { - King James Version - {pageRange} +
+ {pageRange} +
+ {chapters.map((chapter) => ( + + ))} +
+
@@ -1182,20 +1308,24 @@ function App() { {activeSession?.strongsEnabled === false ? (

Strong's links are disabled for this session.

) : ( - <> +
{renderOriginalLanguageLine()} {renderLinkedEnglishLine()} - {selectedStrongEntry ? ( + {showDefinitionPanel && selectedStrongEntry ? (
setDefinitionCollapsed((current) => !current)} + onClick={() => { + setDefinitionCollapsed((current) => !current) + recordDisplaySetting('Toggled Strong definition accordion') + }} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() setDefinitionCollapsed((current) => !current) + recordDisplaySetting('Toggled Strong definition accordion') } }} > @@ -1223,10 +1353,12 @@ function App() { ) : null}
) : null} - +
)} - {renderCommentarySection()} - {renderDictionarySection()} +
+ {renderCommentarySection()} + {renderDictionarySection()} +
) } @@ -1617,8 +1749,15 @@ function App() { @@ -1640,8 +1779,13 @@ function App() { @@ -1652,12 +1796,47 @@ function App() {
+ +
+

Reference Panels

+ + +
@@ -1669,32 +1848,58 @@ function App() { return (