@patterkit/model 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -70,6 +70,8 @@ __export(index_exports, {
70
70
  qualifierStringKey: () => qualifierStringKey,
71
71
  resolveSceneRef: () => resolveSceneRef,
72
72
  sceneAddressIndex: () => sceneAddressIndex,
73
+ sceneFolderLayout: () => sceneFolderLayout,
74
+ sceneFolderPath: () => sceneFolderPath,
73
75
  speakerLabelKey: () => speakerLabelKey,
74
76
  textIsAction: () => textIsAction,
75
77
  usedQualifiers: () => usedQualifiers,
@@ -225,6 +227,60 @@ var DEFAULT_DOCUMENTATION_CLASSES = [
225
227
  { name: "editor", deliver: ["editor"] }
226
228
  // the editable script handed to an outside editor
227
229
  ];
230
+ function sceneFolderLayout(project, sceneIds) {
231
+ const folders = project.sceneFolders ?? [];
232
+ const known = new Set(folders.map((f) => f.id));
233
+ const folderParent = /* @__PURE__ */ new Map();
234
+ for (const f of folders) folderParent.set(f.id, f.parent !== void 0 && known.has(f.parent) && f.parent !== f.id ? f.parent : void 0);
235
+ for (const f of folders) {
236
+ let at = folderParent.get(f.id);
237
+ for (let steps = 0; at !== void 0 && steps <= folders.length; steps++) {
238
+ if (at === f.id) {
239
+ folderParent.set(f.id, void 0);
240
+ break;
241
+ }
242
+ at = folderParent.get(at);
243
+ }
244
+ }
245
+ const sceneFolder = /* @__PURE__ */ new Map();
246
+ for (const id of sceneIds) {
247
+ const f = project.sceneFolder?.[id];
248
+ sceneFolder.set(id, f !== void 0 && known.has(f) ? f : void 0);
249
+ }
250
+ const rank = new Map((project.sceneOrder ?? []).map((id, i) => [id, i]));
251
+ const children = /* @__PURE__ */ new Map();
252
+ const add = (parent, kind, id, tie) => {
253
+ const list = children.get(parent) ?? [];
254
+ list.push({ kind, id, tie });
255
+ children.set(parent, list);
256
+ };
257
+ sceneIds.forEach((id, i) => add(sceneFolder.get(id), "scene", id, i));
258
+ folders.forEach((f, i) => add(folderParent.get(f.id), "folder", f.id, sceneIds.length + i));
259
+ const rows = [];
260
+ const out = [];
261
+ const walk = (parent, depth) => {
262
+ const list = (children.get(parent) ?? []).slice().sort((a, b) => {
263
+ const ra = rank.get(a.id), rb = rank.get(b.id);
264
+ if (ra !== void 0 && rb !== void 0) return ra - rb;
265
+ if (ra !== void 0) return -1;
266
+ if (rb !== void 0) return 1;
267
+ return a.tie - b.tie;
268
+ });
269
+ for (const c of list) {
270
+ rows.push({ kind: c.kind, id: c.id, depth, ...parent !== void 0 ? { folder: parent } : {} });
271
+ if (c.kind === "scene") out.push(c.id);
272
+ else walk(c.id, depth + 1);
273
+ }
274
+ };
275
+ walk(void 0, 0);
276
+ return { rows, sceneIds: out, folderParent, sceneFolder };
277
+ }
278
+ function sceneFolderPath(project, layout, sceneId) {
279
+ const names = new Map((project.sceneFolders ?? []).map((f) => [f.id, f.name]));
280
+ const parts = [];
281
+ for (let at = layout.sceneFolder.get(sceneId), n = 0; at !== void 0 && n <= names.size; at = layout.folderParent.get(at), n++) parts.unshift(names.get(at) ?? "");
282
+ return parts.join(" / ");
283
+ }
228
284
  var PROJECT_LOCALE_SCENE = "@project";
229
285
  function castStringKey(name) {
230
286
  return `cast:${name}`;
@@ -311,6 +367,8 @@ var UNRESOLVED_KEY = "unresolved";
311
367
  qualifierStringKey,
312
368
  resolveSceneRef,
313
369
  sceneAddressIndex,
370
+ sceneFolderLayout,
371
+ sceneFolderPath,
314
372
  speakerLabelKey,
315
373
  textIsAction,
316
374
  usedQualifiers,
package/dist/index.d.cts CHANGED
@@ -599,6 +599,43 @@ interface EstimatingConfig {
599
599
  lines: number;
600
600
  }[];
601
601
  }
602
+ /** A folder in the scene list (ProjectFile.sceneFolders): organisational only, it carries no meaning. */
603
+ interface SceneFolder {
604
+ id: string;
605
+ name: string;
606
+ /** The folder it sits in; omitted = the top level. A parent that doesn't exist, or a loop of parents
607
+ * (only a bad merge makes one), reads as the top level. */
608
+ parent?: string;
609
+ }
610
+ /** One row of the scene list as laid out by `sceneFolderLayout`, top to bottom with every folder open. */
611
+ interface SceneNavRow {
612
+ kind: "folder" | "scene";
613
+ id: string;
614
+ /** 0 = the top level. */
615
+ depth: number;
616
+ /** The folder this row sits in, if any. */
617
+ folder?: string;
618
+ }
619
+ interface SceneFolderLayout {
620
+ /** Every folder and scene, depth first, in authored order. */
621
+ rows: SceneNavRow[];
622
+ /** The scene ids in that order: what the nav, the exports, and the bundle list scenes by. */
623
+ sceneIds: string[];
624
+ /** Each folder's effective parent (undefined = top level), after the forgiving rules. */
625
+ folderParent: Map<string, string | undefined>;
626
+ /** Each scene's effective folder (undefined = top level). */
627
+ sceneFolder: Map<string, string | undefined>;
628
+ }
629
+ /**
630
+ * Lay out the scene list: folders and scenes, nested and ordered. Forgiving, as `sceneOrder` always was:
631
+ * a scene filed in a missing folder, or a folder with a missing parent, sits at the top level; a loop of
632
+ * parents is broken at the first folder of it in `sceneFolders`; ids in `sceneOrder` that are neither
633
+ * are ignored. In each level the rows `sceneOrder` lists come first, in its order, then unlisted scenes in
634
+ * the order given, then unlisted folders in `sceneFolders` order.
635
+ */
636
+ declare function sceneFolderLayout(project: Pick<ProjectFile, "sceneOrder" | "sceneFolders" | "sceneFolder">, sceneIds: readonly string[]): SceneFolderLayout;
637
+ /** A scene's folder path for display ("Act 1 / The docks"), or "" at the top level. */
638
+ declare function sceneFolderPath(project: Pick<ProjectFile, "sceneFolders">, layout: Pick<SceneFolderLayout, "folderParent" | "sceneFolder">, sceneId: string): string;
602
639
  interface ProjectFile {
603
640
  schema: string;
604
641
  root?: boolean;
@@ -619,8 +656,17 @@ interface ProjectFile {
619
656
  };
620
657
  /** The authored scene order (scene ids) for navigation. Scenes not listed follow in file order;
621
658
  * listed ids that no longer exist are ignored. Omitted = plain file order. Presentation only:
622
- * it never affects play, which always starts from `start` / an explicit address. */
659
+ * it never affects play, which always starts from `start` / an explicit address.
660
+ * Holds scene folder ids too (`sceneFolders`): folders and scenes mix in one order, and Patterpad keeps
661
+ * the list equal to the scene list as shown, read top to bottom with every folder open. */
623
662
  sceneOrder?: string[];
663
+ /** Folders for organising the scene list (design/proposals/scene-folders.md). Navigation only, like
664
+ * `sceneOrder`: no file moves, nothing reaches the bundle. The list's own order means nothing; where a
665
+ * folder is shown comes from `sceneOrder`. Omitted = no folders. */
666
+ sceneFolders?: SceneFolder[];
667
+ /** Scene id -> the id of the folder it's filed in. A scene not listed sits at the top level; an entry
668
+ * naming a folder that doesn't exist is ignored. A map, so two people filing different scenes merge. */
669
+ sceneFolder?: Record<string, string>;
624
670
  /** Version-control system (spec §12); omitted = unset / none. */
625
671
  vcs?: VcsKind;
626
672
  /** Project-wide VO mode (spec §16) - one boolean, no per-scene override. */
@@ -1235,6 +1281,8 @@ interface SavedChoice {
1235
1281
  /** The flow's execution position: where it is, and what it is waiting on. */
1236
1282
  interface FlowCursor {
1237
1283
  flowEnded: boolean;
1284
+ /** The choice group whose running dry ended the flow (its `end` step's reason "ran-dry"). Present only then. */
1285
+ ranDry?: string;
1238
1286
  currentSceneId: string | null;
1239
1287
  /** The continuation stack (call frames + the active block run), root first. */
1240
1288
  stack: StackFrame[];
@@ -1412,4 +1460,4 @@ interface ExtensionFieldDecl {
1412
1460
  /** A node's extension values in a bundle: extension id -> field -> value, raw. */
1413
1461
  type NodeExtensions = Record<string, Record<string, unknown>>;
1414
1462
 
1415
- export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, LINE_CHARS_PER_SECOND, LINE_DURATION_MAX, LINE_DURATION_MIN, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV3, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, estimateLineDuration, formatSpeakerLabel, isContentlessBeat, isDialogueScene, projectLayout, projectQualifiers, qualifierStringKey, resolveSceneRef, sceneAddressIndex, speakerLabelKey, textIsAction, usedQualifiers, usedSpeakerPairs, walkNodes };
1463
+ export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, LINE_CHARS_PER_SECOND, LINE_DURATION_MAX, LINE_DURATION_MIN, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV3, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type SceneFolder, type SceneFolderLayout, type SceneNavRow, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, estimateLineDuration, formatSpeakerLabel, isContentlessBeat, isDialogueScene, projectLayout, projectQualifiers, qualifierStringKey, resolveSceneRef, sceneAddressIndex, sceneFolderLayout, sceneFolderPath, speakerLabelKey, textIsAction, usedQualifiers, usedSpeakerPairs, walkNodes };
package/dist/index.d.ts CHANGED
@@ -599,6 +599,43 @@ interface EstimatingConfig {
599
599
  lines: number;
600
600
  }[];
601
601
  }
602
+ /** A folder in the scene list (ProjectFile.sceneFolders): organisational only, it carries no meaning. */
603
+ interface SceneFolder {
604
+ id: string;
605
+ name: string;
606
+ /** The folder it sits in; omitted = the top level. A parent that doesn't exist, or a loop of parents
607
+ * (only a bad merge makes one), reads as the top level. */
608
+ parent?: string;
609
+ }
610
+ /** One row of the scene list as laid out by `sceneFolderLayout`, top to bottom with every folder open. */
611
+ interface SceneNavRow {
612
+ kind: "folder" | "scene";
613
+ id: string;
614
+ /** 0 = the top level. */
615
+ depth: number;
616
+ /** The folder this row sits in, if any. */
617
+ folder?: string;
618
+ }
619
+ interface SceneFolderLayout {
620
+ /** Every folder and scene, depth first, in authored order. */
621
+ rows: SceneNavRow[];
622
+ /** The scene ids in that order: what the nav, the exports, and the bundle list scenes by. */
623
+ sceneIds: string[];
624
+ /** Each folder's effective parent (undefined = top level), after the forgiving rules. */
625
+ folderParent: Map<string, string | undefined>;
626
+ /** Each scene's effective folder (undefined = top level). */
627
+ sceneFolder: Map<string, string | undefined>;
628
+ }
629
+ /**
630
+ * Lay out the scene list: folders and scenes, nested and ordered. Forgiving, as `sceneOrder` always was:
631
+ * a scene filed in a missing folder, or a folder with a missing parent, sits at the top level; a loop of
632
+ * parents is broken at the first folder of it in `sceneFolders`; ids in `sceneOrder` that are neither
633
+ * are ignored. In each level the rows `sceneOrder` lists come first, in its order, then unlisted scenes in
634
+ * the order given, then unlisted folders in `sceneFolders` order.
635
+ */
636
+ declare function sceneFolderLayout(project: Pick<ProjectFile, "sceneOrder" | "sceneFolders" | "sceneFolder">, sceneIds: readonly string[]): SceneFolderLayout;
637
+ /** A scene's folder path for display ("Act 1 / The docks"), or "" at the top level. */
638
+ declare function sceneFolderPath(project: Pick<ProjectFile, "sceneFolders">, layout: Pick<SceneFolderLayout, "folderParent" | "sceneFolder">, sceneId: string): string;
602
639
  interface ProjectFile {
603
640
  schema: string;
604
641
  root?: boolean;
@@ -619,8 +656,17 @@ interface ProjectFile {
619
656
  };
620
657
  /** The authored scene order (scene ids) for navigation. Scenes not listed follow in file order;
621
658
  * listed ids that no longer exist are ignored. Omitted = plain file order. Presentation only:
622
- * it never affects play, which always starts from `start` / an explicit address. */
659
+ * it never affects play, which always starts from `start` / an explicit address.
660
+ * Holds scene folder ids too (`sceneFolders`): folders and scenes mix in one order, and Patterpad keeps
661
+ * the list equal to the scene list as shown, read top to bottom with every folder open. */
623
662
  sceneOrder?: string[];
663
+ /** Folders for organising the scene list (design/proposals/scene-folders.md). Navigation only, like
664
+ * `sceneOrder`: no file moves, nothing reaches the bundle. The list's own order means nothing; where a
665
+ * folder is shown comes from `sceneOrder`. Omitted = no folders. */
666
+ sceneFolders?: SceneFolder[];
667
+ /** Scene id -> the id of the folder it's filed in. A scene not listed sits at the top level; an entry
668
+ * naming a folder that doesn't exist is ignored. A map, so two people filing different scenes merge. */
669
+ sceneFolder?: Record<string, string>;
624
670
  /** Version-control system (spec §12); omitted = unset / none. */
625
671
  vcs?: VcsKind;
626
672
  /** Project-wide VO mode (spec §16) - one boolean, no per-scene override. */
@@ -1235,6 +1281,8 @@ interface SavedChoice {
1235
1281
  /** The flow's execution position: where it is, and what it is waiting on. */
1236
1282
  interface FlowCursor {
1237
1283
  flowEnded: boolean;
1284
+ /** The choice group whose running dry ended the flow (its `end` step's reason "ran-dry"). Present only then. */
1285
+ ranDry?: string;
1238
1286
  currentSceneId: string | null;
1239
1287
  /** The continuation stack (call frames + the active block run), root first. */
1240
1288
  stack: StackFrame[];
@@ -1412,4 +1460,4 @@ interface ExtensionFieldDecl {
1412
1460
  /** A node's extension values in a bundle: extension id -> field -> value, raw. */
1413
1461
  type NodeExtensions = Record<string, Record<string, unknown>>;
1414
1462
 
1415
- export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, LINE_CHARS_PER_SECOND, LINE_DURATION_MAX, LINE_DURATION_MIN, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV3, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, estimateLineDuration, formatSpeakerLabel, isContentlessBeat, isDialogueScene, projectLayout, projectQualifiers, qualifierStringKey, resolveSceneRef, sceneAddressIndex, speakerLabelKey, textIsAction, usedQualifiers, usedSpeakerPairs, walkNodes };
1463
+ export { AUTHORING_SCHEMA, type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, type BundleExtension, type BundleQualifier, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_LAYOUT, DEFAULT_PAD_AFTER, DEFAULT_QUALIFIERS, DEFAULT_RECORDING_STATUSES, DEFAULT_SPEAKER_PATTERN, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, EXTENSIONS_DIR, EXTENSION_SCHEMA, EXTENSION_SHARD, type EditRecord, type Effect, type EngineSave, type EstimatingConfig, type Expression, type ExtensionFieldDecl, type ExtensionShard, FLOW_SCHEMA, type FlowCursor, type FlowFile, type FlowNode, type FlowSnapshot, type FlowState, type FlowValues, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, HANDOFF_SCHEMA, type HandoffFile, type HandoffImport, type HandoffLine, type HandoffRow, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, LINE_CHARS_PER_SECOND, LINE_DURATION_MAX, LINE_DURATION_MIN, type LineBeat, type LocaleFile, MANIFEST_EXTENSION, type NodeExtensions, PAD_AFTER_MAX, PAD_AFTER_MIN, PROJECT_EXTENSION_STEM, PROJECT_LOCALE_SCENE, PROJECT_SCHEMA, type ProjectDictionary, type ProjectExtension, type ProjectFile, type ProjectLayout, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, SAVE_SCHEMA, SAVE_VERSION, SPEAKER_PATTERN_KEY, STRINGS_SCHEMA, type SaveEnvelope, type SaveGame, type SaveGameV3, type SavedChoice, type SavedChoiceOption, type SavedChoicePrompt, type ScalarValue, type Scene, type SceneFolder, type SceneFolderLayout, type SceneNavRow, type Selector, type SelectorExhaust, type SelectorOrder, type SelectorSnapshot, type SequenceOptions, type Snippet, type SpeakerQualifier, type StackFrame, type Suggestion, type SuggestionSource, type TextBeat, UNRESOLVED_KEY, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, estimateLineDuration, formatSpeakerLabel, isContentlessBeat, isDialogueScene, projectLayout, projectQualifiers, qualifierStringKey, resolveSceneRef, sceneAddressIndex, sceneFolderLayout, sceneFolderPath, speakerLabelKey, textIsAction, usedQualifiers, usedSpeakerPairs, walkNodes };
package/dist/index.js CHANGED
@@ -152,6 +152,60 @@ var DEFAULT_DOCUMENTATION_CLASSES = [
152
152
  { name: "editor", deliver: ["editor"] }
153
153
  // the editable script handed to an outside editor
154
154
  ];
155
+ function sceneFolderLayout(project, sceneIds) {
156
+ const folders = project.sceneFolders ?? [];
157
+ const known = new Set(folders.map((f) => f.id));
158
+ const folderParent = /* @__PURE__ */ new Map();
159
+ for (const f of folders) folderParent.set(f.id, f.parent !== void 0 && known.has(f.parent) && f.parent !== f.id ? f.parent : void 0);
160
+ for (const f of folders) {
161
+ let at = folderParent.get(f.id);
162
+ for (let steps = 0; at !== void 0 && steps <= folders.length; steps++) {
163
+ if (at === f.id) {
164
+ folderParent.set(f.id, void 0);
165
+ break;
166
+ }
167
+ at = folderParent.get(at);
168
+ }
169
+ }
170
+ const sceneFolder = /* @__PURE__ */ new Map();
171
+ for (const id of sceneIds) {
172
+ const f = project.sceneFolder?.[id];
173
+ sceneFolder.set(id, f !== void 0 && known.has(f) ? f : void 0);
174
+ }
175
+ const rank = new Map((project.sceneOrder ?? []).map((id, i) => [id, i]));
176
+ const children = /* @__PURE__ */ new Map();
177
+ const add = (parent, kind, id, tie) => {
178
+ const list = children.get(parent) ?? [];
179
+ list.push({ kind, id, tie });
180
+ children.set(parent, list);
181
+ };
182
+ sceneIds.forEach((id, i) => add(sceneFolder.get(id), "scene", id, i));
183
+ folders.forEach((f, i) => add(folderParent.get(f.id), "folder", f.id, sceneIds.length + i));
184
+ const rows = [];
185
+ const out = [];
186
+ const walk = (parent, depth) => {
187
+ const list = (children.get(parent) ?? []).slice().sort((a, b) => {
188
+ const ra = rank.get(a.id), rb = rank.get(b.id);
189
+ if (ra !== void 0 && rb !== void 0) return ra - rb;
190
+ if (ra !== void 0) return -1;
191
+ if (rb !== void 0) return 1;
192
+ return a.tie - b.tie;
193
+ });
194
+ for (const c of list) {
195
+ rows.push({ kind: c.kind, id: c.id, depth, ...parent !== void 0 ? { folder: parent } : {} });
196
+ if (c.kind === "scene") out.push(c.id);
197
+ else walk(c.id, depth + 1);
198
+ }
199
+ };
200
+ walk(void 0, 0);
201
+ return { rows, sceneIds: out, folderParent, sceneFolder };
202
+ }
203
+ function sceneFolderPath(project, layout, sceneId) {
204
+ const names = new Map((project.sceneFolders ?? []).map((f) => [f.id, f.name]));
205
+ const parts = [];
206
+ for (let at = layout.sceneFolder.get(sceneId), n = 0; at !== void 0 && n <= names.size; at = layout.folderParent.get(at), n++) parts.unshift(names.get(at) ?? "");
207
+ return parts.join(" / ");
208
+ }
155
209
  var PROJECT_LOCALE_SCENE = "@project";
156
210
  function castStringKey(name) {
157
211
  return `cast:${name}`;
@@ -237,6 +291,8 @@ export {
237
291
  qualifierStringKey,
238
292
  resolveSceneRef,
239
293
  sceneAddressIndex,
294
+ sceneFolderLayout,
295
+ sceneFolderPath,
240
296
  speakerLabelKey,
241
297
  textIsAction,
242
298
  usedQualifiers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/model",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Patter data-model types: source (scene/block/group/snippet/beat/jump), project, locale, authoring. The shape source-of-truth.",
5
5
  "type": "module",
6
6
  "license": "MIT",