@storylet-studio/model 0.2.0 → 0.4.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 +109 -5
- package/dist/index.d.cts +403 -16
- package/dist/index.d.ts +403 -16
- package/dist/index.js +98 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -394,6 +394,41 @@ function effectiveGameId(entity) {
|
|
|
394
394
|
const fromTitle = entity.title ? gameIdify(entity.title) : "";
|
|
395
395
|
return fromTitle || entity.id;
|
|
396
396
|
}
|
|
397
|
+
function valueAddresses(bundle) {
|
|
398
|
+
const tags = [];
|
|
399
|
+
for (const box of bundle.boxes) {
|
|
400
|
+
const boxGameId = effectiveGameId(box);
|
|
401
|
+
for (const group of box.tagGroups) {
|
|
402
|
+
for (const tag of group.tags) {
|
|
403
|
+
const gameId = effectiveGameId(tag);
|
|
404
|
+
tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const forms = /* @__PURE__ */ new Map();
|
|
409
|
+
for (const tag of tags) {
|
|
410
|
+
const list = forms.get(tag.gameId) ?? [];
|
|
411
|
+
if (!list.includes(tag.qualified)) list.push(tag.qualified);
|
|
412
|
+
forms.set(tag.gameId, list);
|
|
413
|
+
}
|
|
414
|
+
const print = /* @__PURE__ */ new Map();
|
|
415
|
+
const accept = /* @__PURE__ */ new Map();
|
|
416
|
+
const repeated = /* @__PURE__ */ new Map();
|
|
417
|
+
for (const tag of tags) {
|
|
418
|
+
const candidates = forms.get(tag.gameId) ?? [tag.qualified];
|
|
419
|
+
const ambiguous = candidates.length > 1;
|
|
420
|
+
print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);
|
|
421
|
+
if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);
|
|
422
|
+
if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);
|
|
423
|
+
if (ambiguous) repeated.set(tag.gameId, candidates);
|
|
424
|
+
}
|
|
425
|
+
return { print, accept, repeated };
|
|
426
|
+
}
|
|
427
|
+
function ambiguousValueAddressMessage(segment, name, candidates) {
|
|
428
|
+
const forms = candidates.map((q) => `"value.${q}.${name}"`);
|
|
429
|
+
const list = forms.length <= 1 ? forms[0] ?? "" : `${forms.slice(0, -1).join(", ")} or ${forms[forms.length - 1]}`;
|
|
430
|
+
return `"value.${segment}.${name}" names a tag in ${candidates.length} boxes; write ${list}`;
|
|
431
|
+
}
|
|
397
432
|
function freeGameId(base, taken) {
|
|
398
433
|
let gameId = base;
|
|
399
434
|
for (let n = 2; taken.has(gameId); n++) gameId = `${base}-${n}`;
|
|
@@ -407,8 +442,32 @@ function freeTitle(base, taken) {
|
|
|
407
442
|
for (let n = 2; taken.has(gameIdify(title)); n++) title = `${base} ${n}`;
|
|
408
443
|
return title;
|
|
409
444
|
}
|
|
445
|
+
function turnSpan(turns, seconds, long = false) {
|
|
446
|
+
const total = Math.max(0, Math.round(turns * seconds));
|
|
447
|
+
const say = (n, short, one, many) => long ? `${n} ${n === 1 ? one : many}` : `${n} ${short}`;
|
|
448
|
+
if (total < 60) return long ? say(total, "s", "second", "seconds") : `${total}s`;
|
|
449
|
+
if (total < 3600) {
|
|
450
|
+
const minutes = total % 60 === 0 ? total / 60 : Math.round(total / 6) / 10;
|
|
451
|
+
return say(minutes, "min", "minute", "minutes");
|
|
452
|
+
}
|
|
453
|
+
let hours = Math.floor(total / 3600);
|
|
454
|
+
let rest = Math.round(total % 3600 / 60);
|
|
455
|
+
if (rest === 60) {
|
|
456
|
+
hours += 1;
|
|
457
|
+
rest = 0;
|
|
458
|
+
}
|
|
459
|
+
const said = say(hours, "hr", "hour", "hours");
|
|
460
|
+
return rest === 0 ? said : `${said} ${say(rest, "min", "minute", "minutes")}`;
|
|
461
|
+
}
|
|
410
462
|
var PLACE_GROUP = "place";
|
|
463
|
+
var HOLE_REF = /^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/;
|
|
464
|
+
var isHoleRef = (value) => value.startsWith("@");
|
|
465
|
+
var parseHoleRef = (value) => {
|
|
466
|
+
const m = HOLE_REF.exec(value);
|
|
467
|
+
return m === null ? void 0 : { scope: m[1], name: m[2] };
|
|
468
|
+
};
|
|
411
469
|
var BUNDLE_SCHEMA = "storylets/bundle@0";
|
|
470
|
+
var DEFAULT_PLAY_RUNG = "solo";
|
|
412
471
|
var SAVE_SCHEMA = "storylets/save@1";
|
|
413
472
|
var SAVEFILE_SCHEMA = "storylets/savefile@1";
|
|
414
473
|
var PROJECT_FOLDER_EXTENSION = ".storylets";
|
|
@@ -420,34 +479,61 @@ var SHARD_EXTENSIONS = {
|
|
|
420
479
|
tags: ".storylettags",
|
|
421
480
|
hands: ".storylethands",
|
|
422
481
|
deck: ".storyletdeck",
|
|
423
|
-
/** The arrangement layer:
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
482
|
+
/** The AUTHOR's arrangement layer: the canvases, where cards sit on a deck's
|
|
483
|
+
* node canvas and the furniture drawn round them. Its own shard because
|
|
484
|
+
* positions churn (an afternoon of tidying a canvas touches every card) and
|
|
485
|
+
* content does not, so a designer arranging and a writer editing never
|
|
486
|
+
* collide on one file (design/graphical-views.md section 1.2). */
|
|
427
487
|
view: ".storyletview",
|
|
488
|
+
/** The DESIGNER's map: where a box's hands stand in space, and the furniture
|
|
489
|
+
* round them. One per box, beside the view shard.
|
|
490
|
+
*
|
|
491
|
+
* Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1
|
|
492
|
+
* point 5) because the two halves stopped having one owner. A hand's
|
|
493
|
+
* position ships in the bundle's `maps` block (4.3) and is where a venue's
|
|
494
|
+
* kiosk stands, so it is SHAPE, which a server's author key may not change;
|
|
495
|
+
* the canvases are the author's own working drawing and never leave the
|
|
496
|
+
* project folder. One file could not be both. */
|
|
497
|
+
map: ".storyletmap",
|
|
428
498
|
/** Threaded comments: content-ADJACENT, so neither in a content shard (a
|
|
429
499
|
* writer's deck edit must not conflict with a reviewer's comment) nor in the
|
|
430
500
|
* arrangement sidecar (this is not where anything sits). One per box,
|
|
431
501
|
* id-keyed (design/annotation.md). Documentation NOTES used to share this
|
|
432
502
|
* file and were retired: `purpose` already says why a thing exists, and
|
|
433
503
|
* Patterpad's typed routing has no destination here. */
|
|
434
|
-
notes: ".storyletnotes"
|
|
504
|
+
notes: ".storyletnotes",
|
|
505
|
+
/** An installation contract: what a VENUE depends on, one file per
|
|
506
|
+
* installation in `contracts/` at the project root
|
|
507
|
+
* (design/engine-server.md 4.11). Its own shard, and its own folder, for the
|
|
508
|
+
* walkthrough's reason (Reboot 7.5, S4): a different owner, a different
|
|
509
|
+
* change rate, and a merge that must never collide with the author's edits,
|
|
510
|
+
* since the server always wins its own file. */
|
|
511
|
+
contract: ".storyletcontract"
|
|
435
512
|
};
|
|
513
|
+
var CONTRACTS_DIR = "contracts";
|
|
436
514
|
var PROJECT_SCHEMA = "storylets/project@0";
|
|
437
515
|
var BOX_SCHEMA = "storylets/box@0";
|
|
438
516
|
var TAGS_SCHEMA = "storylets/tags@0";
|
|
439
517
|
var HANDS_SCHEMA = "storylets/hands@0";
|
|
440
518
|
var DECK_SCHEMA = "storylets/deck@0";
|
|
441
519
|
var VIEW_SCHEMA = "storylets/view@0";
|
|
520
|
+
var MAP_SCHEMA = "storylets/map@0";
|
|
442
521
|
var NOTES_SCHEMA = "storylets/notes@0";
|
|
522
|
+
var CONTRACT_SCHEMA = "storylets/contract@0";
|
|
523
|
+
var contractPropertyPath = (p) => typeof p === "string" ? p : p.path;
|
|
524
|
+
var contractPropertyType = (p) => typeof p === "string" ? void 0 : p.type;
|
|
443
525
|
var FURNITURE_COLOURS = ["paper", "amber", "sage", "sky", "rose", "slate"];
|
|
444
526
|
export {
|
|
445
527
|
BOX_SCHEMA,
|
|
446
528
|
BUNDLE_EXTENSION,
|
|
447
529
|
BUNDLE_SCHEMA,
|
|
530
|
+
CONTRACTS_DIR,
|
|
531
|
+
CONTRACT_SCHEMA,
|
|
448
532
|
DECK_SCHEMA,
|
|
533
|
+
DEFAULT_PLAY_RUNG,
|
|
449
534
|
FURNITURE_COLOURS,
|
|
450
535
|
HANDS_SCHEMA,
|
|
536
|
+
MAP_SCHEMA,
|
|
451
537
|
NOTES_SCHEMA,
|
|
452
538
|
PLACE_GROUP,
|
|
453
539
|
PROJECT_FOLDER_EXTENSION,
|
|
@@ -459,12 +545,15 @@ export {
|
|
|
459
545
|
SPATIAL,
|
|
460
546
|
TAGS_SCHEMA,
|
|
461
547
|
VIEW_SCHEMA,
|
|
548
|
+
ambiguousValueAddressMessage,
|
|
462
549
|
backgroundsOf,
|
|
463
550
|
bindHand,
|
|
464
551
|
bundleAssetPath,
|
|
465
552
|
byDisplayOrder,
|
|
466
553
|
centroid,
|
|
467
554
|
commentsOf,
|
|
555
|
+
contractPropertyPath,
|
|
556
|
+
contractPropertyType,
|
|
468
557
|
droppedRect,
|
|
469
558
|
effectiveGameId,
|
|
470
559
|
framesOf,
|
|
@@ -474,6 +563,7 @@ export {
|
|
|
474
563
|
handBinding,
|
|
475
564
|
inferDeclFromWrite,
|
|
476
565
|
isCaseOnlyPropertyName,
|
|
566
|
+
isHoleRef,
|
|
477
567
|
isSpatial,
|
|
478
568
|
isValidGameId,
|
|
479
569
|
isValidPropertyName,
|
|
@@ -481,6 +571,7 @@ export {
|
|
|
481
571
|
markOf,
|
|
482
572
|
marksOn,
|
|
483
573
|
openThreadCounts,
|
|
574
|
+
parseHoleRef,
|
|
484
575
|
pointInPolygon,
|
|
485
576
|
polygonBounds,
|
|
486
577
|
polygonOf,
|
|
@@ -489,7 +580,9 @@ export {
|
|
|
489
580
|
spatialOf,
|
|
490
581
|
stacked,
|
|
491
582
|
threadsFor,
|
|
583
|
+
turnSpan,
|
|
492
584
|
unbindHand,
|
|
585
|
+
valueAddresses,
|
|
493
586
|
withBackgrounds,
|
|
494
587
|
withPolygon,
|
|
495
588
|
withSpatialGroup,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storylet-studio/model",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Storylets data-model types: source shards (project / box / tags / hands / deck), the compiled bundle, the save envelope. The shape source-of-truth.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|