Skip to content

Commit 09fe204

Browse files
abelpzcursoragent
andcommitted
chore(tc-study): lazy-load default sections, remove package creator
- Lazy-load default sections per book (dynamic imports) - Remove package-creator components and PackageBuilderService - Update collections, studio, contexts for async sections API - Clean up stores and types Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6f6e32b commit 09fe204

36 files changed

+416
-1603
lines changed

apps/tc-study/src/components/collections/CollectionImportDialog.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -454,15 +454,15 @@ export function CollectionImportDialog({ isOpen, onClose }: CollectionImportDial
454454
{/* Header */}
455455
<div className="flex items-center justify-between p-4 border-b">
456456
<div className="flex items-center gap-3">
457-
<Upload className="w-6 h-6 text-blue-600" title="Load collection" aria-label="Load collection" />
457+
<span title="Load collection"><Upload className="w-6 h-6 text-blue-600" aria-label="Load collection" /></span>
458458
</div>
459459
<button
460460
onClick={onClose}
461461
className="p-1.5 hover:bg-gray-100 rounded transition-colors"
462462
aria-label="Close dialog"
463463
title="Close dialog"
464464
>
465-
<X className="w-4 h-4" title="Close" />
465+
<span title="Close"><X className="w-4 h-4" /></span>
466466
</button>
467467
</div>
468468

@@ -539,7 +539,7 @@ export function CollectionImportDialog({ isOpen, onClose }: CollectionImportDial
539539
title="Drop .btc.zip file here or click to browse"
540540
aria-label="Drop collection file here or click to select file"
541541
>
542-
<FileArchive className={`w-16 h-16 mx-auto mb-3 ${file ? 'text-blue-600' : 'text-gray-400'}`} title="Collection file" />
542+
<span title="Collection file"><FileArchive className={`w-16 h-16 mx-auto mb-3 ${file ? 'text-blue-600' : 'text-gray-400'}`} /></span>
543543
{file ? (
544544
<>
545545
<p className="text-sm font-medium text-gray-700 mb-1 truncate px-2">{file.name}</p>
@@ -548,7 +548,7 @@ export function CollectionImportDialog({ isOpen, onClose }: CollectionImportDial
548548
) : (
549549
<div className="flex flex-col items-center gap-2">
550550
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
551-
<Upload className="w-5 h-5 text-gray-400" title="Upload" />
551+
<span title="Upload"><Upload className="w-5 h-5 text-gray-400" /></span>
552552
</div>
553553
</div>
554554
)}
@@ -587,7 +587,7 @@ export function CollectionImportDialog({ isOpen, onClose }: CollectionImportDial
587587
{/* Success Message */}
588588
{success && (
589589
<div className="bg-green-50 border border-green-200 rounded-lg p-3 flex items-center justify-center gap-2 text-green-800">
590-
<Check className="w-5 h-5" title="Success" />
590+
<span title="Success"><Check className="w-5 h-5" /></span>
591591
</div>
592592
)}
593593

@@ -608,7 +608,7 @@ export function CollectionImportDialog({ isOpen, onClose }: CollectionImportDial
608608
aria-label="Cancel"
609609
title="Cancel"
610610
>
611-
<X className="w-4 h-4 mx-auto" title="Cancel" />
611+
<span title="Cancel"><X className="w-4 h-4 mx-auto" /></span>
612612
</button>
613613
<button
614614
onClick={handleImport}
@@ -620,9 +620,9 @@ export function CollectionImportDialog({ isOpen, onClose }: CollectionImportDial
620620
{importing ? (
621621
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" title="Loading..." />
622622
) : success ? (
623-
<Check className="w-5 h-5" title="Success" />
623+
<span title="Success"><Check className="w-5 h-5" /></span>
624624
) : (
625-
<Upload className="w-5 h-5" title="Load" />
625+
<span title="Load"><Upload className="w-5 h-5" /></span>
626626
)}
627627
</button>
628628
</div>

apps/tc-study/src/components/collections/ManageCollectionDialog.tsx

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,113 @@
44

55
import { useState, useEffect } from 'react'
66
import { X, Plus, Trash2, BookOpen, Check } from 'lucide-react'
7-
import type { ResourcePackage } from '../../lib/storage/types'
7+
import type { ResourcePackage as WorkspacePackage } from '../../lib/storage/types'
8+
import type { ResourceInfo } from '../../contexts/types'
9+
import type { ResourcePackage as StorageResourcePackage, PackageResource } from '@bt-synergy/package-storage'
810
import { usePackageStore } from '../../lib/stores'
911

1012
interface ManageCollectionDialogProps {
11-
collection: ResourcePackage
13+
collection: WorkspacePackage | StorageResourcePackage
1214
isOpen: boolean
1315
onClose: () => void
1416
onOpenWizard?: () => void
1517
}
1618

19+
function getResourcesArray(collection: WorkspacePackage | StorageResourcePackage): ResourceInfo[] | PackageResource[] {
20+
const r = (collection as { resources?: Map<string, ResourceInfo> | PackageResource[] }).resources
21+
if (r instanceof Map) return Array.from(r.values())
22+
return r ?? []
23+
}
24+
25+
function resourcesMapToArray(resources: Map<string, ResourceInfo>): ResourceInfo[] {
26+
return Array.from(resources.values())
27+
}
28+
29+
function arrayToResourcesMap(resources: ResourceInfo[]): Map<string, ResourceInfo> {
30+
const map = new Map<string, ResourceInfo>()
31+
resources.forEach(r => map.set(r.key, r))
32+
return map
33+
}
34+
35+
/** Convert dialog state + collection to package-storage ResourcePackage for save */
36+
function toStoragePackage(
37+
collection: WorkspacePackage | StorageResourcePackage,
38+
name: string,
39+
description: string | undefined,
40+
resources: ResourceInfo[] | PackageResource[]
41+
): StorageResourcePackage {
42+
const base = collection as unknown as Record<string, unknown>
43+
const now = new Date().toISOString()
44+
const parseKey = (key: string) => {
45+
const parts = key.split('/')
46+
return {
47+
server: 'https://git.door43.org',
48+
owner: parts[0] || 'unfoldingword',
49+
language: parts[1] || 'en',
50+
resourceId: parts[parts.length - 1] || '',
51+
}
52+
}
53+
const packageResources: PackageResource[] = resources.map((r: ResourceInfo | PackageResource) => {
54+
if ('key' in r && r.key) {
55+
const { owner, language, resourceId } = parseKey(r.key)
56+
return { server: (r as ResourceInfo).server || 'https://git.door43.org', owner, language, resourceId, displayName: (r as ResourceInfo).title }
57+
}
58+
return r as PackageResource
59+
})
60+
const panels = (base.panels as { id: string; name?: string; resourceKeys: string[]; activeIndex?: number }[] | undefined) ?? (base.panelLayout as { panels?: { id: string; title?: string; resourceIds: string[]; defaultResourceId?: string }[] })?.panels ?? []
61+
const panelLayout = Array.isArray(panels) && panels.length > 0
62+
? {
63+
panels: panels.map((p: { id: string; name?: string; title?: string; resourceKeys?: string[]; resourceIds?: string[]; activeIndex?: number; defaultResourceId?: string }) => ({
64+
id: p.id,
65+
title: p.name ?? p.title,
66+
resourceIds: p.resourceKeys ?? p.resourceIds ?? [],
67+
defaultResourceId: p.resourceKeys?.[p.activeIndex ?? 0] ?? p.defaultResourceId,
68+
})),
69+
orientation: 'horizontal' as const,
70+
}
71+
: undefined
72+
return {
73+
id: (base.id as string) || `collection_${Date.now()}`,
74+
name: name.trim(),
75+
description: description?.trim() || undefined,
76+
version: (base.version as string) || '1.0.0',
77+
createdAt: (base.createdAt as string) || now,
78+
updatedAt: now,
79+
resources: packageResources,
80+
panelLayout,
81+
}
82+
}
83+
1784
export function ManageCollectionDialog({
1885
collection,
1986
isOpen,
2087
onClose,
2188
onOpenWizard,
2289
}: ManageCollectionDialogProps) {
2390
const savePackage = usePackageStore(state => state.savePackage)
24-
const [resources, setResources] = useState(collection.resources || [])
25-
const [name, setName] = useState(collection.title || collection.name || '')
91+
const [resources, setResources] = useState<ResourceInfo[] | PackageResource[]>(() => getResourcesArray(collection) as ResourceInfo[] | PackageResource[])
92+
const [name, setName] = useState(collection.name || (collection as { title?: string }).title || '')
2693
const [description, setDescription] = useState(collection.description || '')
2794

2895
// Reset state when dialog opens or collection changes
2996
useEffect(() => {
3097
if (isOpen) {
31-
setResources(collection.resources || [])
32-
setName(collection.title || collection.name || '')
98+
setResources(getResourcesArray(collection) as ResourceInfo[] | PackageResource[])
99+
setName(collection.name || (collection as { title?: string }).title || '')
33100
setDescription(collection.description || '')
34101
}
35102
}, [isOpen, collection])
36103

37104
if (!isOpen) return null
38105

39106
const handleRemoveResource = (index: number) => {
40-
const updatedResources = resources.filter((_, i) => i !== index)
107+
const updatedResources = resources.filter((_, i: number) => i !== index)
41108
setResources(updatedResources)
42109
}
43110

44111
const handleSave = async () => {
45-
const updatedCollection = {
46-
...collection,
47-
name: name.trim(),
48-
title: name.trim(),
49-
description: description.trim() || undefined,
50-
resources: resources,
51-
}
52-
await savePackage(updatedCollection)
112+
const toSave = toStoragePackage(collection, name, description, resources)
113+
await savePackage(toSave)
53114
onClose()
54115
}
55116

apps/tc-study/src/components/collections/SaveCollectionDialog.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,15 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
165165
{/* Header */}
166166
<div className="flex items-center justify-between p-4 border-b">
167167
<div className="flex items-center gap-3">
168-
<Save className="w-6 h-6 text-blue-600" title="Save or download collection" aria-label="Save or download collection" />
168+
<span title="Save or download collection"><Save className="w-6 h-6 text-blue-600" aria-label="Save or download collection" /></span>
169169
</div>
170170
<button
171171
onClick={onClose}
172172
className="p-1.5 hover:bg-gray-100 rounded transition-colors"
173173
aria-label="Close dialog"
174174
title="Close dialog"
175175
>
176-
<X className="w-4 h-4" title="Close" />
176+
<span title="Close"><X className="w-4 h-4" /></span>
177177
</button>
178178
</div>
179179

@@ -208,7 +208,7 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
208208
aria-label="Save to collections database"
209209
title="Save to collections database"
210210
>
211-
<Save className={`w-6 h-6 ${actionMode === 'save' ? 'text-blue-600' : 'text-gray-400'}`} title="Save to DB" />
211+
<span title="Save to DB"><Save className={`w-6 h-6 ${actionMode === 'save' ? 'text-blue-600' : 'text-gray-400'}`} /></span>
212212
<span className={`text-sm font-medium ${actionMode === 'save' ? 'text-blue-900' : 'text-gray-600'}`}>
213213
Save
214214
</span>
@@ -228,7 +228,7 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
228228
aria-label="Download as .btc.zip file"
229229
title="Download as .btc.zip file"
230230
>
231-
<Download className={`w-6 h-6 ${actionMode === 'download' ? 'text-green-600' : 'text-gray-400'}`} title="Download" />
231+
<span title="Download"><Download className={`w-6 h-6 ${actionMode === 'download' ? 'text-green-600' : 'text-gray-400'}`} /></span>
232232
<span className={`text-sm font-medium ${actionMode === 'download' ? 'text-green-900' : 'text-gray-600'}`}>
233233
Download
234234
</span>
@@ -274,7 +274,7 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
274274
aria-label="Download metadata only (requires internet to load content)"
275275
title="Metadata only - Content downloads on-demand (requires internet)"
276276
>
277-
<Wifi className={`w-6 h-6 ${!includeContent ? 'text-blue-600' : 'text-gray-400'}`} title="Online mode" />
277+
<span title="Online mode"><Wifi className={`w-6 h-6 ${!includeContent ? 'text-blue-600' : 'text-gray-400'}`} /></span>
278278
<span className={`text-sm font-medium ${!includeContent ? 'text-blue-900' : 'text-gray-600'}`}>
279279
Online
280280
</span>
@@ -291,7 +291,7 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
291291
aria-label="Include downloaded content for offline use"
292292
title="Include content - Works offline but larger file size"
293293
>
294-
<Database className={`w-6 h-6 ${includeContent ? 'text-green-600' : 'text-gray-400'}`} title="Offline mode" />
294+
<span title="Offline mode"><Database className={`w-6 h-6 ${includeContent ? 'text-green-600' : 'text-gray-400'}`} /></span>
295295
<span className={`text-sm font-medium ${includeContent ? 'text-green-900' : 'text-gray-600'}`}>
296296
Offline
297297
</span>
@@ -324,7 +324,7 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
324324
aria-label="Cancel"
325325
title="Cancel"
326326
>
327-
<X className="w-4 h-4 mx-auto" title="Cancel" />
327+
<span title="Cancel"><X className="w-4 h-4 mx-auto" /></span>
328328
</button>
329329
<button
330330
onClick={handleAction}
@@ -336,9 +336,9 @@ export function SaveCollectionDialog({ isOpen, onClose, onSaved }: SaveCollectio
336336
{processing ? (
337337
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" title="Processing..." />
338338
) : actionMode === 'save' ? (
339-
<Save className="w-5 h-5" title="Save" />
339+
<span title="Save"><Save className="w-5 h-5" /></span>
340340
) : (
341-
<Download className="w-5 h-5" title="Download" />
341+
<span title="Download"><Download className="w-5 h-5" /></span>
342342
)}
343343
</button>
344344
</div>

apps/tc-study/src/components/collections/SimpleCollectionCreator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export function SimpleCollectionCreator({ onClose, onComplete, onAddResources, i
242242
<div className="flex items-center gap-2">
243243
<h3 className="font-medium text-gray-900 truncate">{resource.title}</h3>
244244
{isDownloaded && (
245-
<Download className="w-4 h-4 text-green-600 flex-shrink-0" title="Downloaded" aria-label="Downloaded" />
245+
<span title="Downloaded"><Download className="w-4 h-4 text-green-600 flex-shrink-0" aria-label="Downloaded" /></span>
246246
)}
247247
</div>
248248
<div className="flex items-center gap-2 mt-1 text-xs text-gray-500">

apps/tc-study/src/components/data/PassageSetForm.tsx

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,41 @@
44

55
import { useState, useEffect } from 'react'
66
import { X, Plus, Trash2, Save } from 'lucide-react'
7-
import type { PassageSet, BCVReference } from '@bt-synergy/passage-sets'
7+
import type { PassageSet, BCVReference, Passage, PassageLeaf, PassageSetNode, RefRange } from '@bt-synergy/passage-sets'
8+
9+
function flattenPassageSetToBCV(root: PassageSetNode[]): BCVReference[] {
10+
const out: BCVReference[] = []
11+
for (const node of root) {
12+
if (node.type === 'passage' && 'passages' in node) {
13+
const leaf = node as PassageLeaf
14+
for (const p of leaf.passages) {
15+
const ref = p.ref as RefRange
16+
out.push({
17+
book: p.bookCode,
18+
chapter: ref.startChapter,
19+
verse: ref.startVerse ?? 1,
20+
})
21+
}
22+
}
23+
if (node.type === 'group' && 'children' in node) {
24+
out.push(...flattenPassageSetToBCV((node as { children: PassageSetNode[] }).children))
25+
}
26+
}
27+
return out
28+
}
29+
30+
function bcvListToPassageLeaf(bcvList: BCVReference[]): PassageLeaf {
31+
const passages: Passage[] = bcvList.map(bcv => ({
32+
bookCode: bcv.book,
33+
ref: { startChapter: bcv.chapter, startVerse: bcv.verse } as RefRange,
34+
}))
35+
return {
36+
id: 'default',
37+
type: 'passage',
38+
label: 'Passages',
39+
passages,
40+
}
41+
}
842

943
interface PassageSetFormProps {
1044
passageSet?: PassageSet | null
@@ -92,7 +126,7 @@ export function PassageSetForm({ passageSet, onSave, onCancel }: PassageSetFormP
92126
if (passageSet) {
93127
setName(passageSet.name)
94128
setDescription(passageSet.description || '')
95-
setPassages(passageSet.passages)
129+
setPassages(flattenPassageSetToBCV(passageSet.root))
96130
}
97131
}, [passageSet])
98132

@@ -120,11 +154,15 @@ export function PassageSetForm({ passageSet, onSave, onCancel }: PassageSetFormP
120154
return
121155
}
122156

157+
const now = new Date().toISOString()
123158
const saved: PassageSet = {
124159
id: passageSet?.id || `ps-${Date.now()}`,
125160
name: name.trim(),
126-
description: description.trim(),
127-
passages,
161+
description: description.trim() || undefined,
162+
version: passageSet?.version ?? '1.0',
163+
createdAt: passageSet?.createdAt ?? now,
164+
updatedAt: now,
165+
root: [bcvListToPassageLeaf(passages)],
128166
}
129167

130168
onSave(saved)

0 commit comments

Comments
 (0)