Make mystery importer seed notes and tolerate sparse manifests
Add a notes[] array to the manifest (note exhibits with presentation), so the Scene 6 phone-note board is reproducible from source instead of hand-authored. Also default documents/folders/brief/subtitle so narrative-only mysteries (e.g. barricelli-files) import cleanly. Captures mysteries/barricelli-phone-note/mystery.json from the authored template (note: "PHONE FOR GLITCH HUNTER / Call: 5550100"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"slug": "barricelli-phone-note",
|
||||||
|
"name": "Phone note",
|
||||||
|
"title": "Phone note",
|
||||||
|
"subtitle": "",
|
||||||
|
"brief": { "body": "", "concepts": [] },
|
||||||
|
"documents": [],
|
||||||
|
"folders": [],
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"title": "Note",
|
||||||
|
"content": "PHONE FOR GLITCH HUNTER\n\nCall: 5550100",
|
||||||
|
"presentation": "luggage",
|
||||||
|
"x": 420,
|
||||||
|
"y": 260,
|
||||||
|
"width": 230,
|
||||||
|
"height": 180
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto'
|
|||||||
import { readFile } from 'node:fs/promises'
|
import { readFile } from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import type { CaseDocument, CaseState, ClaimExhibit, DocumentCaptureKind, PartyKind, SourceFileType } from '../src/types.js'
|
import type { CaseDocument, CaseState, ClaimExhibit, DocumentCaptureKind, NoteExhibit, NotePresentation, PartyKind, SourceFileType } from '../src/types.js'
|
||||||
|
|
||||||
type MysteryDocument = {
|
type MysteryDocument = {
|
||||||
key: string
|
key: string
|
||||||
@@ -41,12 +41,13 @@ type MysteryManifest = {
|
|||||||
slug: string
|
slug: string
|
||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle?: string
|
||||||
timelineRange?: { start: string; end: string }
|
timelineRange?: { start: string; end: string }
|
||||||
brief: { body: string; concepts: { label: string; context: string; expectedPartyKind: PartyKind }[] }
|
brief?: { body: string; concepts: { label: string; context: string; expectedPartyKind: PartyKind }[] }
|
||||||
documents: MysteryDocument[]
|
documents?: MysteryDocument[]
|
||||||
folders: { key: string; title: string; content: string; x: number; y: number; width: number; members: string[] }[]
|
folders?: { key: string; title: string; content: string; x: number; y: number; width: number; members: string[] }[]
|
||||||
claims?: { key:string;statement:string;x:number;y:number;width?:number;height?:number }[]
|
claims?: { key:string;statement:string;x:number;y:number;width?:number;height?:number }[]
|
||||||
|
notes?: { title?:string;content:string;presentation?:NotePresentation;x:number;y:number;width?:number;height?:number }[]
|
||||||
report?: { title?:string;requiredForCompletion?:boolean }
|
report?: { title?:string;requiredForCompletion?:boolean }
|
||||||
goals?: MysteryGoal[]
|
goals?: MysteryGoal[]
|
||||||
evidenceMatchRules?: MysteryEvidenceMatchRule[]
|
evidenceMatchRules?: MysteryEvidenceMatchRule[]
|
||||||
@@ -84,14 +85,15 @@ export async function importMysteryTemplate(manifestPath: string, baseUrl = 'htt
|
|||||||
const headers = { 'content-type': 'application/json', ...(authorization ? { authorization } : {}) }
|
const headers = { 'content-type': 'application/json', ...(authorization ? { authorization } : {}) }
|
||||||
const createdResponse = await requireOk(await fetch(`${baseUrl}/api/levels`, {
|
const createdResponse = await requireOk(await fetch(`${baseUrl}/api/levels`, {
|
||||||
method: 'POST', headers,
|
method: 'POST', headers,
|
||||||
body: JSON.stringify({ id: authoringId, title: manifest.title, subtitle: manifest.subtitle }),
|
body: JSON.stringify({ id: authoringId, title: manifest.title, subtitle: manifest.subtitle || '' }),
|
||||||
}), 'Create authoring level')
|
}), 'Create authoring level')
|
||||||
const state = await createdResponse.json() as CaseState
|
const state = await createdResponse.json() as CaseState
|
||||||
|
|
||||||
const documentPositions = new Map<string, { x: number; y: number }>()
|
const documentPositions = new Map<string, { x: number; y: number }>()
|
||||||
manifest.folders.forEach((folder, folderIndex) => folder.members.forEach((key, memberIndex) => documentPositions.set(key, { x: folder.x + 70 + memberIndex * 205, y: folder.y + 230 + folderIndex * 35 })))
|
const manifestFolders = manifest.folders || []
|
||||||
|
manifestFolders.forEach((folder, folderIndex) => folder.members.forEach((key, memberIndex) => documentPositions.set(key, { x: folder.x + 70 + memberIndex * 205, y: folder.y + 230 + folderIndex * 35 })))
|
||||||
const documents = new Map<string, CaseDocument>()
|
const documents = new Map<string, CaseDocument>()
|
||||||
for (const source of manifest.documents) {
|
for (const source of manifest.documents || []) {
|
||||||
const uploaded = await uploadAsset(baseUrl, state.id, manifestDir, source, authorization)
|
const uploaded = await uploadAsset(baseUrl, state.id, manifestDir, source, authorization)
|
||||||
documents.set(source.key, {
|
documents.set(source.key, {
|
||||||
id: uploaded?.id || randomUUID(), type: 'document', title: source.title, publishedAt: source.publishedAt,
|
id: uploaded?.id || randomUUID(), type: 'document', title: source.title, publishedAt: source.publishedAt,
|
||||||
@@ -103,17 +105,19 @@ export async function importMysteryTemplate(manifestPath: string, baseUrl = 'htt
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderIds = new Map(manifest.folders.map(folder => [folder.key, randomUUID()]))
|
const folderIds = new Map(manifestFolders.map(folder => [folder.key, randomUUID()]))
|
||||||
state.brief = { body: manifest.brief.body, concepts: manifest.brief.concepts.map(concept => ({ id: randomUUID(), ...concept })) }
|
if (manifest.brief) state.brief = { body: manifest.brief.body, concepts: manifest.brief.concepts.map(concept => ({ id: randomUUID(), ...concept })) }
|
||||||
state.views = state.views.map(view => view.type === 'timeline' ? { ...view, rangeMode: manifest.timelineRange ? 'fixed' : 'auto', range: manifest.timelineRange } : view)
|
state.views = state.views.map(view => view.type === 'timeline' ? { ...view, rangeMode: manifest.timelineRange ? 'fixed' : 'auto', range: manifest.timelineRange } : view)
|
||||||
const folders = manifest.folders.map(folder => ({
|
const folders = manifestFolders.map(folder => ({
|
||||||
id: folderIds.get(folder.key)!, type: 'folder', title: folder.title, content: folder.content,
|
id: folderIds.get(folder.key)!, type: 'folder', title: folder.title, content: folder.content,
|
||||||
x: folder.x, y: folder.y, width: folder.width, height: 166, rotation: 0, zIndex: 1, hidden: false, isOpen: false,
|
x: folder.x, y: folder.y, width: folder.width, height: 166, rotation: 0, zIndex: 1, hidden: false, isOpen: false,
|
||||||
} as const))
|
} as const))
|
||||||
const claims:ClaimExhibit[]=(manifest.claims || []).map(claim => ({ id:randomUUID(),type:'claim',title:claim.statement,statement:claim.statement,
|
const claims:ClaimExhibit[]=(manifest.claims || []).map(claim => ({ id:randomUUID(),type:'claim',title:claim.statement,statement:claim.statement,
|
||||||
x:claim.x,y:claim.y,width:claim.width || 310,height:claim.height || 180,rotation:0,zIndex:2,hidden:false }))
|
x:claim.x,y:claim.y,width:claim.width || 310,height:claim.height || 180,rotation:0,zIndex:2,hidden:false }))
|
||||||
state.exhibits = [...documents.values(), ...folders,...claims]
|
const notes:NoteExhibit[]=(manifest.notes || []).map(note => ({ id:randomUUID(),type:'note',title:note.title || 'NOTE',content:note.content,
|
||||||
state.relations = manifest.folders.flatMap(folder => folder.members.map((key, memberIndex) => {
|
presentation:note.presentation || 'luggage',x:note.x,y:note.y,width:note.width || 230,height:note.height || 180,rotation:0,zIndex:2,hidden:false }))
|
||||||
|
state.exhibits = [...documents.values(), ...folders,...claims,...notes]
|
||||||
|
state.relations = manifestFolders.flatMap(folder => folder.members.map((key, memberIndex) => {
|
||||||
const document = documents.get(key)
|
const document = documents.get(key)
|
||||||
if (!document) throw new Error(`Folder ${folder.key} refers to unknown document ${key}`)
|
if (!document) throw new Error(`Folder ${folder.key} refers to unknown document ${key}`)
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user