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 path from 'node:path'
|
||||
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 = {
|
||||
key: string
|
||||
@@ -41,12 +41,13 @@ type MysteryManifest = {
|
||||
slug: string
|
||||
name: string
|
||||
title: string
|
||||
subtitle: string
|
||||
subtitle?: string
|
||||
timelineRange?: { start: string; end: string }
|
||||
brief: { body: string; concepts: { label: string; context: string; expectedPartyKind: PartyKind }[] }
|
||||
documents: MysteryDocument[]
|
||||
folders: { key: string; title: string; content: string; x: number; y: number; width: number; members: string[] }[]
|
||||
brief?: { body: string; concepts: { label: string; context: string; expectedPartyKind: PartyKind }[] }
|
||||
documents?: MysteryDocument[]
|
||||
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 }[]
|
||||
notes?: { title?:string;content:string;presentation?:NotePresentation;x:number;y:number;width?:number;height?:number }[]
|
||||
report?: { title?:string;requiredForCompletion?:boolean }
|
||||
goals?: MysteryGoal[]
|
||||
evidenceMatchRules?: MysteryEvidenceMatchRule[]
|
||||
@@ -84,14 +85,15 @@ export async function importMysteryTemplate(manifestPath: string, baseUrl = 'htt
|
||||
const headers = { 'content-type': 'application/json', ...(authorization ? { authorization } : {}) }
|
||||
const createdResponse = await requireOk(await fetch(`${baseUrl}/api/levels`, {
|
||||
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')
|
||||
const state = await createdResponse.json() as CaseState
|
||||
|
||||
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>()
|
||||
for (const source of manifest.documents) {
|
||||
for (const source of manifest.documents || []) {
|
||||
const uploaded = await uploadAsset(baseUrl, state.id, manifestDir, source, authorization)
|
||||
documents.set(source.key, {
|
||||
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()]))
|
||||
state.brief = { body: manifest.brief.body, concepts: manifest.brief.concepts.map(concept => ({ id: randomUUID(), ...concept })) }
|
||||
const folderIds = new Map(manifestFolders.map(folder => [folder.key, randomUUID()]))
|
||||
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)
|
||||
const folders = manifest.folders.map(folder => ({
|
||||
const folders = manifestFolders.map(folder => ({
|
||||
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,
|
||||
} as const))
|
||||
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 }))
|
||||
state.exhibits = [...documents.values(), ...folders,...claims]
|
||||
state.relations = manifest.folders.flatMap(folder => folder.members.map((key, memberIndex) => {
|
||||
const notes:NoteExhibit[]=(manifest.notes || []).map(note => ({ id:randomUUID(),type:'note',title:note.title || 'NOTE',content:note.content,
|
||||
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)
|
||||
if (!document) throw new Error(`Folder ${folder.key} refers to unknown document ${key}`)
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user