eventmodeler 0.6.13 → 0.6.15
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.js +489 -178
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2573,6 +2573,15 @@ function registerStatusCommands(program, conn) {
|
|
|
2573
2573
|
}
|
|
2574
2574
|
// ../packages/canvas-model/src/build.ts
|
|
2575
2575
|
import * as Y2 from "yjs";
|
|
2576
|
+
function buildYMap(props) {
|
|
2577
|
+
const m = new Y2.Map;
|
|
2578
|
+
for (const [k, v] of Object.entries(props)) {
|
|
2579
|
+
if (v === undefined)
|
|
2580
|
+
continue;
|
|
2581
|
+
m.set(k, v);
|
|
2582
|
+
}
|
|
2583
|
+
return m;
|
|
2584
|
+
}
|
|
2576
2585
|
function buildYArray(items) {
|
|
2577
2586
|
const arr = new Y2.Array;
|
|
2578
2587
|
if (items.length > 0)
|
|
@@ -2630,6 +2639,7 @@ var ELEMENT_DIMENSIONS = {
|
|
|
2630
2639
|
aggregate: { width: 300, height: 200 },
|
|
2631
2640
|
actor: { width: 300, height: 200 },
|
|
2632
2641
|
chapter: { width: 800, height: 1200 },
|
|
2642
|
+
sheet: { width: 800, height: 1200 },
|
|
2633
2643
|
note: { width: 200, height: 150 },
|
|
2634
2644
|
externalEvent: { width: 160, height: 100 },
|
|
2635
2645
|
context: { width: 300, height: 200 },
|
|
@@ -2834,6 +2844,7 @@ var SliceSchema = baseContainer("sliceId").extend({
|
|
|
2834
2844
|
var AggregateSchema = baseContainer("aggregateId");
|
|
2835
2845
|
var ActorSchema = baseContainer("actorId");
|
|
2836
2846
|
var ChapterSchema = baseContainer("chapterId");
|
|
2847
|
+
var SheetSchema = baseContainer("sheetId");
|
|
2837
2848
|
var ContextSchema = baseContainer("contextId");
|
|
2838
2849
|
var LaneKindSchema = z.enum(["actor", "interaction", "swimlane", "specification"]);
|
|
2839
2850
|
var SwimLaneSchema = baseContainer("swimLaneId").extend({
|
|
@@ -2937,9 +2948,9 @@ var FlowSchema = z.object({
|
|
|
2937
2948
|
var SheetFlowSchema = z.object({
|
|
2938
2949
|
sheetFlowId: z.string().uuid().optional(),
|
|
2939
2950
|
modelId: z.string().uuid().optional(),
|
|
2940
|
-
|
|
2951
|
+
sourceSheetId: z.string().uuid(),
|
|
2941
2952
|
sourceHandle: z.string().min(1),
|
|
2942
|
-
|
|
2953
|
+
targetSheetId: z.string().uuid(),
|
|
2943
2954
|
targetHandle: z.string().min(1)
|
|
2944
2955
|
});
|
|
2945
2956
|
var CellRefSchema = z.object({
|
|
@@ -2958,6 +2969,7 @@ var SheetStructureSchema = z.object({
|
|
|
2958
2969
|
columns: z.array(z.string().uuid()),
|
|
2959
2970
|
rows: z.array(z.string().uuid()),
|
|
2960
2971
|
cells: z.record(z.string(), z.array(CellRefSchema)),
|
|
2972
|
+
columnSlices: z.record(z.string(), z.string().uuid()).optional(),
|
|
2961
2973
|
cellNotes: z.record(z.string(), z.string()).optional()
|
|
2962
2974
|
});
|
|
2963
2975
|
var SCOPE_SCHEMAS = {
|
|
@@ -2971,6 +2983,7 @@ var SCOPE_SCHEMAS = {
|
|
|
2971
2983
|
aggregates: AggregateSchema,
|
|
2972
2984
|
actors: ActorSchema,
|
|
2973
2985
|
chapters: ChapterSchema,
|
|
2986
|
+
sheets: SheetSchema,
|
|
2974
2987
|
contexts: ContextSchema,
|
|
2975
2988
|
swimLanes: SwimLaneSchema,
|
|
2976
2989
|
notes: NoteSchema,
|
|
@@ -3005,36 +3018,69 @@ var GRID_SCOPE = "grid";
|
|
|
3005
3018
|
function getGridMap(doc) {
|
|
3006
3019
|
return doc.getMap(GRID_SCOPE);
|
|
3007
3020
|
}
|
|
3008
|
-
function cellKey(
|
|
3009
|
-
return `${
|
|
3021
|
+
function cellKey(columnId, swimLaneId) {
|
|
3022
|
+
return `${columnId}:${swimLaneId}`;
|
|
3010
3023
|
}
|
|
3011
|
-
function resolveSheet(doc,
|
|
3024
|
+
function resolveSheet(doc, sheetId) {
|
|
3012
3025
|
const grid = getGridMap(doc);
|
|
3013
|
-
let sheet = grid.get(
|
|
3026
|
+
let sheet = grid.get(sheetId);
|
|
3014
3027
|
if (!sheet) {
|
|
3015
3028
|
sheet = new Y6.Map;
|
|
3016
3029
|
sheet.set("columns", new Y6.Array);
|
|
3017
3030
|
sheet.set("rows", new Y6.Array);
|
|
3018
3031
|
sheet.set("cells", new Y6.Map);
|
|
3019
|
-
|
|
3032
|
+
sheet.set("columnSlices", new Y6.Map);
|
|
3033
|
+
grid.set(sheetId, sheet);
|
|
3034
|
+
}
|
|
3035
|
+
let columnSlices = sheet.get("columnSlices");
|
|
3036
|
+
if (!columnSlices) {
|
|
3037
|
+
columnSlices = new Y6.Map;
|
|
3038
|
+
sheet.set("columnSlices", columnSlices);
|
|
3020
3039
|
}
|
|
3021
3040
|
return {
|
|
3022
3041
|
sheet,
|
|
3023
3042
|
columns: sheet.get("columns"),
|
|
3024
3043
|
rows: sheet.get("rows"),
|
|
3025
|
-
cells: sheet.get("cells")
|
|
3044
|
+
cells: sheet.get("cells"),
|
|
3045
|
+
columnSlices
|
|
3026
3046
|
};
|
|
3027
3047
|
}
|
|
3028
|
-
function
|
|
3048
|
+
function sliceOf(columnSlices, columnId) {
|
|
3049
|
+
return columnSlices[columnId] ?? columnId;
|
|
3050
|
+
}
|
|
3051
|
+
function ensureSheet(doc, sheetId) {
|
|
3029
3052
|
doc.transact(() => {
|
|
3030
|
-
resolveSheet(doc,
|
|
3053
|
+
resolveSheet(doc, sheetId);
|
|
3031
3054
|
});
|
|
3032
3055
|
}
|
|
3033
|
-
function readSheet(doc,
|
|
3034
|
-
const sheet = getGridMap(doc).get(
|
|
3056
|
+
function readSheet(doc, sheetId) {
|
|
3057
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3035
3058
|
if (!sheet)
|
|
3036
3059
|
return;
|
|
3037
|
-
|
|
3060
|
+
const json = sheet.toJSON();
|
|
3061
|
+
const columns = json.columns ?? [];
|
|
3062
|
+
const columnSlices = { ...json.columnSlices ?? {} };
|
|
3063
|
+
for (const c of columns)
|
|
3064
|
+
if (!(c in columnSlices))
|
|
3065
|
+
columnSlices[c] = c;
|
|
3066
|
+
return { ...json, columns, columnSlices };
|
|
3067
|
+
}
|
|
3068
|
+
function sliceOrder(structure) {
|
|
3069
|
+
const cs = structure.columnSlices ?? {};
|
|
3070
|
+
const seen = new Set;
|
|
3071
|
+
const out = [];
|
|
3072
|
+
for (const colId of structure.columns) {
|
|
3073
|
+
const sid = sliceOf(cs, colId);
|
|
3074
|
+
if (!seen.has(sid)) {
|
|
3075
|
+
seen.add(sid);
|
|
3076
|
+
out.push(sid);
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
return out;
|
|
3080
|
+
}
|
|
3081
|
+
function sliceColumns(structure, sliceId) {
|
|
3082
|
+
const cs = structure.columnSlices ?? {};
|
|
3083
|
+
return structure.columns.filter((c) => sliceOf(cs, c) === sliceId);
|
|
3038
3084
|
}
|
|
3039
3085
|
function clampIndex(at, length) {
|
|
3040
3086
|
if (at === undefined || at > length)
|
|
@@ -3046,52 +3092,91 @@ function insertUnique(arr, value, at) {
|
|
|
3046
3092
|
return;
|
|
3047
3093
|
arr.insert(clampIndex(at, arr.length), [value]);
|
|
3048
3094
|
}
|
|
3049
|
-
function addColumn(doc,
|
|
3095
|
+
function addColumn(doc, sheetId, sliceId, at) {
|
|
3096
|
+
doc.transact(() => {
|
|
3097
|
+
const { columns, columnSlices } = resolveSheet(doc, sheetId);
|
|
3098
|
+
if (columns.toArray().includes(sliceId))
|
|
3099
|
+
return;
|
|
3100
|
+
columns.insert(clampIndex(at, columns.length), [sliceId]);
|
|
3101
|
+
columnSlices.set(sliceId, sliceId);
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
function addColumnToSlice(doc, sheetId, sliceId, columnId, atWithin) {
|
|
3050
3105
|
doc.transact(() => {
|
|
3051
|
-
const { columns } = resolveSheet(doc,
|
|
3052
|
-
|
|
3106
|
+
const { columns, columnSlices } = resolveSheet(doc, sheetId);
|
|
3107
|
+
const arr = columns.toArray();
|
|
3108
|
+
if (arr.includes(columnId))
|
|
3109
|
+
return;
|
|
3110
|
+
const slices = columnSlices.toJSON();
|
|
3111
|
+
const flatIdxs = arr.map((c, i) => [c, i]).filter(([c]) => sliceOf(slices, c) === sliceId).map(([, i]) => i);
|
|
3112
|
+
let flat;
|
|
3113
|
+
if (flatIdxs.length === 0) {
|
|
3114
|
+
flat = columns.length;
|
|
3115
|
+
} else {
|
|
3116
|
+
const within = atWithin === undefined ? flatIdxs.length : Math.max(0, Math.min(flatIdxs.length, atWithin));
|
|
3117
|
+
flat = within < flatIdxs.length ? flatIdxs[within] : flatIdxs[flatIdxs.length - 1] + 1;
|
|
3118
|
+
}
|
|
3119
|
+
columns.insert(flat, [columnId]);
|
|
3120
|
+
columnSlices.set(columnId, sliceId);
|
|
3053
3121
|
});
|
|
3054
3122
|
}
|
|
3055
|
-
function moveColumn(doc,
|
|
3123
|
+
function moveColumn(doc, sheetId, columnId, toIndex) {
|
|
3056
3124
|
doc.transact(() => {
|
|
3057
|
-
const { columns } = resolveSheet(doc,
|
|
3058
|
-
const from = columns.toArray().indexOf(
|
|
3125
|
+
const { columns } = resolveSheet(doc, sheetId);
|
|
3126
|
+
const from = columns.toArray().indexOf(columnId);
|
|
3059
3127
|
if (from === -1)
|
|
3060
3128
|
return;
|
|
3061
3129
|
columns.delete(from, 1);
|
|
3062
|
-
columns.insert(clampIndex(toIndex, columns.length), [
|
|
3130
|
+
columns.insert(clampIndex(toIndex, columns.length), [columnId]);
|
|
3063
3131
|
});
|
|
3064
3132
|
}
|
|
3065
|
-
function removeColumn(doc,
|
|
3133
|
+
function removeColumn(doc, sheetId, columnId) {
|
|
3066
3134
|
doc.transact(() => {
|
|
3067
|
-
const sheet = getGridMap(doc).get(
|
|
3135
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3068
3136
|
if (!sheet)
|
|
3069
3137
|
return;
|
|
3070
3138
|
const columns = sheet.get("columns");
|
|
3071
3139
|
const cells = sheet.get("cells");
|
|
3072
3140
|
const cellNotes = sheet.get("cellNotes");
|
|
3073
|
-
const
|
|
3141
|
+
const columnSlices = sheet.get("columnSlices");
|
|
3142
|
+
const idx = columns.toArray().indexOf(columnId);
|
|
3074
3143
|
if (idx !== -1)
|
|
3075
3144
|
columns.delete(idx, 1);
|
|
3145
|
+
columnSlices?.delete(columnId);
|
|
3076
3146
|
for (const key of [...cells.keys()]) {
|
|
3077
|
-
if (key.startsWith(`${
|
|
3147
|
+
if (key.startsWith(`${columnId}:`))
|
|
3078
3148
|
cells.delete(key);
|
|
3079
3149
|
}
|
|
3080
3150
|
for (const key of [...cellNotes?.keys() ?? []]) {
|
|
3081
|
-
if (key.startsWith(`${
|
|
3151
|
+
if (key.startsWith(`${columnId}:`))
|
|
3082
3152
|
cellNotes.delete(key);
|
|
3083
3153
|
}
|
|
3084
3154
|
});
|
|
3085
3155
|
}
|
|
3086
|
-
function
|
|
3156
|
+
function setColumnSlice(doc, sheetId, columnId, sliceId) {
|
|
3157
|
+
doc.transact(() => {
|
|
3158
|
+
const { columnSlices } = resolveSheet(doc, sheetId);
|
|
3159
|
+
columnSlices.set(columnId, sliceId);
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
function ensureColumnSlices(doc, sheetId) {
|
|
3163
|
+
doc.transact(() => {
|
|
3164
|
+
const { columns, columnSlices } = resolveSheet(doc, sheetId);
|
|
3165
|
+
for (const columnId of columns.toArray()) {
|
|
3166
|
+
if (!columnSlices.has(columnId))
|
|
3167
|
+
columnSlices.set(columnId, columnId);
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
function addRow(doc, sheetId, swimLaneId, at) {
|
|
3087
3172
|
doc.transact(() => {
|
|
3088
|
-
const { rows } = resolveSheet(doc,
|
|
3173
|
+
const { rows } = resolveSheet(doc, sheetId);
|
|
3089
3174
|
insertUnique(rows, swimLaneId, at);
|
|
3090
3175
|
});
|
|
3091
3176
|
}
|
|
3092
|
-
function moveRow(doc,
|
|
3177
|
+
function moveRow(doc, sheetId, swimLaneId, toIndex) {
|
|
3093
3178
|
doc.transact(() => {
|
|
3094
|
-
const { rows } = resolveSheet(doc,
|
|
3179
|
+
const { rows } = resolveSheet(doc, sheetId);
|
|
3095
3180
|
const from = rows.toArray().indexOf(swimLaneId);
|
|
3096
3181
|
if (from === -1)
|
|
3097
3182
|
return;
|
|
@@ -3099,9 +3184,9 @@ function moveRow(doc, chapterId, swimLaneId, toIndex) {
|
|
|
3099
3184
|
rows.insert(clampIndex(toIndex, rows.length), [swimLaneId]);
|
|
3100
3185
|
});
|
|
3101
3186
|
}
|
|
3102
|
-
function removeRow(doc,
|
|
3187
|
+
function removeRow(doc, sheetId, swimLaneId) {
|
|
3103
3188
|
doc.transact(() => {
|
|
3104
|
-
const sheet = getGridMap(doc).get(
|
|
3189
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3105
3190
|
if (!sheet)
|
|
3106
3191
|
return;
|
|
3107
3192
|
const rows = sheet.get("rows");
|
|
@@ -3137,9 +3222,9 @@ function findStickyCell(cells, stickyId) {
|
|
|
3137
3222
|
}
|
|
3138
3223
|
return;
|
|
3139
3224
|
}
|
|
3140
|
-
function placeInCell(doc,
|
|
3225
|
+
function placeInCell(doc, sheetId, sliceId, swimLaneId, ref, at) {
|
|
3141
3226
|
doc.transact(() => {
|
|
3142
|
-
const { cells } = resolveSheet(doc,
|
|
3227
|
+
const { cells } = resolveSheet(doc, sheetId);
|
|
3143
3228
|
const existing = findStickyCell(cells, ref.stickyId);
|
|
3144
3229
|
if (existing)
|
|
3145
3230
|
cells.get(existing.key).delete(existing.index, 1);
|
|
@@ -3154,9 +3239,9 @@ function placeInCell(doc, chapterId, sliceId, swimLaneId, ref, at) {
|
|
|
3154
3239
|
]);
|
|
3155
3240
|
});
|
|
3156
3241
|
}
|
|
3157
|
-
function removeSticky(doc,
|
|
3242
|
+
function removeSticky(doc, sheetId, stickyId) {
|
|
3158
3243
|
doc.transact(() => {
|
|
3159
|
-
const sheet = getGridMap(doc).get(
|
|
3244
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3160
3245
|
if (!sheet)
|
|
3161
3246
|
return;
|
|
3162
3247
|
const cells = sheet.get("cells");
|
|
@@ -3166,13 +3251,13 @@ function removeSticky(doc, chapterId, stickyId) {
|
|
|
3166
3251
|
cells.get(loc.key).delete(loc.index, 1);
|
|
3167
3252
|
});
|
|
3168
3253
|
}
|
|
3169
|
-
function removeSheet(doc,
|
|
3254
|
+
function removeSheet(doc, sheetId) {
|
|
3170
3255
|
doc.transact(() => {
|
|
3171
|
-
getGridMap(doc).delete(
|
|
3256
|
+
getGridMap(doc).delete(sheetId);
|
|
3172
3257
|
});
|
|
3173
3258
|
}
|
|
3174
|
-
function getCellNotesMap(doc,
|
|
3175
|
-
const sheet = getGridMap(doc).get(
|
|
3259
|
+
function getCellNotesMap(doc, sheetId, create) {
|
|
3260
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3176
3261
|
if (!sheet)
|
|
3177
3262
|
return;
|
|
3178
3263
|
let notes = sheet.get("cellNotes");
|
|
@@ -3182,30 +3267,117 @@ function getCellNotesMap(doc, chapterId, create) {
|
|
|
3182
3267
|
}
|
|
3183
3268
|
return notes;
|
|
3184
3269
|
}
|
|
3185
|
-
function readCellNotes(doc,
|
|
3186
|
-
const notes = getGridMap(doc).get(
|
|
3270
|
+
function readCellNotes(doc, sheetId) {
|
|
3271
|
+
const notes = getGridMap(doc).get(sheetId)?.get("cellNotes");
|
|
3187
3272
|
return notes ? notes.toJSON() : {};
|
|
3188
3273
|
}
|
|
3189
|
-
function setCellNote(doc,
|
|
3274
|
+
function setCellNote(doc, sheetId, sliceId, swimLaneId, text) {
|
|
3190
3275
|
doc.transact(() => {
|
|
3191
3276
|
const key = cellKey(sliceId, swimLaneId);
|
|
3192
3277
|
const trimmed = text.trim();
|
|
3193
3278
|
if (!trimmed) {
|
|
3194
|
-
getCellNotesMap(doc,
|
|
3279
|
+
getCellNotesMap(doc, sheetId, false)?.delete(key);
|
|
3195
3280
|
return;
|
|
3196
3281
|
}
|
|
3197
|
-
getCellNotesMap(doc,
|
|
3282
|
+
getCellNotesMap(doc, sheetId, true).set(key, trimmed);
|
|
3198
3283
|
});
|
|
3199
3284
|
}
|
|
3200
|
-
// ../packages/canvas-model/src/
|
|
3201
|
-
var CHAPTERS_SCOPE = "chapters";
|
|
3285
|
+
// ../packages/canvas-model/src/slice-span.ts
|
|
3202
3286
|
var SLICES_SCOPE2 = "slices";
|
|
3287
|
+
var SHEET_FLOWS_SCOPE = "sheetFlows";
|
|
3288
|
+
function sliceIdFromHandle(handleId) {
|
|
3289
|
+
if (typeof handleId !== "string")
|
|
3290
|
+
return;
|
|
3291
|
+
const parts = handleId.split(":");
|
|
3292
|
+
if (parts[0] === "sf" && parts[1] === "slice" && parts.length >= 5)
|
|
3293
|
+
return parts.slice(4).join(":");
|
|
3294
|
+
return;
|
|
3295
|
+
}
|
|
3296
|
+
function pruneSheetFlowsForSlice(doc, sliceId) {
|
|
3297
|
+
const map = getScopeMap(doc, SHEET_FLOWS_SCOPE);
|
|
3298
|
+
for (const [id, f] of [...map.entries()]) {
|
|
3299
|
+
if (sliceIdFromHandle(f.get("sourceHandle")) === sliceId || sliceIdFromHandle(f.get("targetHandle")) === sliceId) {
|
|
3300
|
+
map.delete(id);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function resizeSliceSpan(doc, sheetId, sliceId, targetLast) {
|
|
3305
|
+
doc.transact(() => {
|
|
3306
|
+
const structure = readSheet(doc, sheetId);
|
|
3307
|
+
if (!structure)
|
|
3308
|
+
return;
|
|
3309
|
+
const cols = structure.columns;
|
|
3310
|
+
const owned = sliceColumns(structure, sliceId);
|
|
3311
|
+
if (owned.length === 0)
|
|
3312
|
+
return;
|
|
3313
|
+
const firstFlat = cols.indexOf(owned[0]);
|
|
3314
|
+
const currentLast = cols.indexOf(owned[owned.length - 1]);
|
|
3315
|
+
const target = Math.max(firstFlat, Math.min(targetLast, cols.length - 1));
|
|
3316
|
+
if (target === currentLast)
|
|
3317
|
+
return;
|
|
3318
|
+
const owner = (c) => structure.columnSlices?.[c] ?? c;
|
|
3319
|
+
const slices = getScopeMap(doc, SLICES_SCOPE2);
|
|
3320
|
+
const modelId = getEntry(doc, SLICES_SCOPE2, sliceId)?.modelId;
|
|
3321
|
+
const d = ELEMENT_DIMENSIONS.slice;
|
|
3322
|
+
const claimed = cols.slice(firstFlat, target + 1);
|
|
3323
|
+
const prevOwners = new Set(claimed.map(owner).filter((s) => s !== sliceId));
|
|
3324
|
+
for (const colId of claimed)
|
|
3325
|
+
setColumnSlice(doc, sheetId, colId, sliceId);
|
|
3326
|
+
for (const colId of cols.slice(target + 1, currentLast + 1)) {
|
|
3327
|
+
const newSliceId = crypto.randomUUID();
|
|
3328
|
+
const base = { sliceId: newSliceId, name: "Slice", x: 0, y: 0, width: d.width, height: d.height };
|
|
3329
|
+
slices.set(newSliceId, buildYMap(modelId ? { ...base, modelId } : base));
|
|
3330
|
+
setColumnSlice(doc, sheetId, colId, newSliceId);
|
|
3331
|
+
}
|
|
3332
|
+
const after = readSheet(doc, sheetId);
|
|
3333
|
+
for (const sid of prevOwners) {
|
|
3334
|
+
if (sliceColumns(after, sid).length === 0) {
|
|
3335
|
+
slices.delete(sid);
|
|
3336
|
+
pruneSheetFlowsForSlice(doc, sid);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
// ../packages/canvas-model/src/migrate.ts
|
|
3342
|
+
function migrateChaptersToSheets(doc) {
|
|
3343
|
+
const chapters = getScopeMap(doc, "chapters");
|
|
3344
|
+
const sheets = getScopeMap(doc, "sheets");
|
|
3345
|
+
const sheetFlows = getScopeMap(doc, "sheetFlows");
|
|
3346
|
+
const needsEntityMove = sheets.size === 0 && chapters.size > 0;
|
|
3347
|
+
const needsFlowRename = [...sheetFlows.values()].some((f) => f.has("sourceChapterId") || f.has("targetChapterId"));
|
|
3348
|
+
if (!needsEntityMove && !needsFlowRename)
|
|
3349
|
+
return;
|
|
3350
|
+
doc.transact(() => {
|
|
3351
|
+
if (needsEntityMove) {
|
|
3352
|
+
for (const [id, ch] of [...chapters.entries()]) {
|
|
3353
|
+
const json = ch.toJSON();
|
|
3354
|
+
delete json.chapterId;
|
|
3355
|
+
json.sheetId = id;
|
|
3356
|
+
sheets.set(id, deepToY(json));
|
|
3357
|
+
chapters.delete(id);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
for (const f of sheetFlows.values()) {
|
|
3361
|
+
if (f.has("sourceChapterId")) {
|
|
3362
|
+
f.set("sourceSheetId", f.get("sourceChapterId"));
|
|
3363
|
+
f.delete("sourceChapterId");
|
|
3364
|
+
}
|
|
3365
|
+
if (f.has("targetChapterId")) {
|
|
3366
|
+
f.set("targetSheetId", f.get("targetChapterId"));
|
|
3367
|
+
f.delete("targetChapterId");
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
});
|
|
3371
|
+
}
|
|
3372
|
+
// ../packages/canvas-model/src/sheet-delete.ts
|
|
3373
|
+
var SHEETS_SCOPE = "sheets";
|
|
3374
|
+
var SLICES_SCOPE3 = "slices";
|
|
3203
3375
|
var SWIM_LANES_SCOPE = "swimLanes";
|
|
3204
3376
|
var SCENARIOS_SCOPE2 = "scenarios";
|
|
3205
|
-
var
|
|
3206
|
-
function deleteSheet(doc,
|
|
3377
|
+
var SHEET_FLOWS_SCOPE2 = "sheetFlows";
|
|
3378
|
+
function deleteSheet(doc, sheetId) {
|
|
3207
3379
|
doc.transact(() => {
|
|
3208
|
-
const sheet = getGridMap(doc).get(
|
|
3380
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3209
3381
|
const removedStickies = new Set;
|
|
3210
3382
|
const sliceIds = new Set;
|
|
3211
3383
|
const laneIds = new Set;
|
|
@@ -3224,7 +3396,7 @@ function deleteSheet(doc, chapterId) {
|
|
|
3224
3396
|
}
|
|
3225
3397
|
}
|
|
3226
3398
|
for (const id of sliceIds)
|
|
3227
|
-
getScopeMap(doc,
|
|
3399
|
+
getScopeMap(doc, SLICES_SCOPE3).delete(id);
|
|
3228
3400
|
for (const id of laneIds)
|
|
3229
3401
|
getScopeMap(doc, SWIM_LANES_SCOPE).delete(id);
|
|
3230
3402
|
const scenarios = getScopeMap(doc, SCENARIOS_SCOPE2);
|
|
@@ -3235,14 +3407,14 @@ function deleteSheet(doc, chapterId) {
|
|
|
3235
3407
|
}
|
|
3236
3408
|
}
|
|
3237
3409
|
cascadeStickyRemoval(doc, removedStickies);
|
|
3238
|
-
const sheetFlows = getScopeMap(doc,
|
|
3410
|
+
const sheetFlows = getScopeMap(doc, SHEET_FLOWS_SCOPE2);
|
|
3239
3411
|
for (const [id, f] of sheetFlows.entries()) {
|
|
3240
|
-
if (f.get("
|
|
3412
|
+
if (f.get("sourceSheetId") === sheetId || f.get("targetSheetId") === sheetId) {
|
|
3241
3413
|
sheetFlows.delete(id);
|
|
3242
3414
|
}
|
|
3243
3415
|
}
|
|
3244
|
-
removeSheet(doc,
|
|
3245
|
-
getScopeMap(doc,
|
|
3416
|
+
removeSheet(doc, sheetId);
|
|
3417
|
+
getScopeMap(doc, SHEETS_SCOPE).delete(sheetId);
|
|
3246
3418
|
});
|
|
3247
3419
|
}
|
|
3248
3420
|
// ../packages/canvas-model/src/sheet-layout.ts
|
|
@@ -3295,8 +3467,8 @@ function cellContentSize(refs) {
|
|
|
3295
3467
|
});
|
|
3296
3468
|
return { width, height };
|
|
3297
3469
|
}
|
|
3298
|
-
function pruneOrphanCellRefs(doc,
|
|
3299
|
-
const sheet = getGridMap(doc).get(
|
|
3470
|
+
function pruneOrphanCellRefs(doc, sheetId) {
|
|
3471
|
+
const sheet = getGridMap(doc).get(sheetId);
|
|
3300
3472
|
if (!sheet)
|
|
3301
3473
|
return 0;
|
|
3302
3474
|
const cells = sheet.get("cells");
|
|
@@ -3315,19 +3487,21 @@ function pruneOrphanCellRefs(doc, chapterId) {
|
|
|
3315
3487
|
}
|
|
3316
3488
|
return pruned;
|
|
3317
3489
|
}
|
|
3318
|
-
function computeSheetLayout(doc,
|
|
3319
|
-
const structure = readSheet(doc,
|
|
3490
|
+
function computeSheetLayout(doc, sheetId, override) {
|
|
3491
|
+
const structure = readSheet(doc, sheetId);
|
|
3320
3492
|
if (!structure)
|
|
3321
3493
|
return;
|
|
3322
|
-
const
|
|
3323
|
-
const originX = snap(
|
|
3324
|
-
const originY = snap(
|
|
3494
|
+
const sheet = getEntry(doc, "sheets", sheetId);
|
|
3495
|
+
const originX = snap(sheet?.x ?? 0);
|
|
3496
|
+
const originY = snap(sheet?.y ?? 0);
|
|
3325
3497
|
const { columns, rows } = structure;
|
|
3498
|
+
const columnSlices = structure.columnSlices ?? {};
|
|
3499
|
+
const ownerOf = (columnId) => columnSlices[columnId] ?? columnId;
|
|
3326
3500
|
const cells = override ? applyLayoutOverride(structure.cells, override) : structure.cells;
|
|
3327
|
-
const colWidths = columns.map((
|
|
3501
|
+
const colWidths = columns.map((columnId) => {
|
|
3328
3502
|
let w = 0;
|
|
3329
3503
|
for (const swimLaneId of rows) {
|
|
3330
|
-
const refs = cells[cellKey(
|
|
3504
|
+
const refs = cells[cellKey(columnId, swimLaneId)] ?? [];
|
|
3331
3505
|
const c = cellContentSize(refs);
|
|
3332
3506
|
if (c.width > 0)
|
|
3333
3507
|
w = Math.max(w, c.width + SHEET_LAYOUT.cellPadX * 2);
|
|
@@ -3336,8 +3510,8 @@ function computeSheetLayout(doc, chapterId, override) {
|
|
|
3336
3510
|
});
|
|
3337
3511
|
const rowHeights = rows.map((swimLaneId) => {
|
|
3338
3512
|
let h = 0;
|
|
3339
|
-
for (const
|
|
3340
|
-
const refs = cells[cellKey(
|
|
3513
|
+
for (const columnId of columns) {
|
|
3514
|
+
const refs = cells[cellKey(columnId, swimLaneId)] ?? [];
|
|
3341
3515
|
const c = cellContentSize(refs);
|
|
3342
3516
|
if (c.height > 0)
|
|
3343
3517
|
h = Math.max(h, c.height + SHEET_LAYOUT.cellPadY * 2);
|
|
@@ -3358,14 +3532,33 @@ function computeSheetLayout(doc, chapterId, override) {
|
|
|
3358
3532
|
});
|
|
3359
3533
|
const gridWidth = SHEET_LAYOUT.headerLeft + colWidths.reduce((a, b) => a + b, 0);
|
|
3360
3534
|
const gridHeight = SHEET_LAYOUT.headerTop + rowHeights.reduce((a, b) => a + b, 0);
|
|
3361
|
-
const columnLayouts = columns.map((
|
|
3362
|
-
|
|
3535
|
+
const columnLayouts = columns.map((columnId, i) => ({
|
|
3536
|
+
columnId,
|
|
3537
|
+
sliceId: ownerOf(columnId),
|
|
3363
3538
|
index: i,
|
|
3364
3539
|
x: colX[i],
|
|
3365
3540
|
y: originY,
|
|
3366
3541
|
width: colWidths[i],
|
|
3367
3542
|
height: gridHeight
|
|
3368
3543
|
}));
|
|
3544
|
+
const sliceLayouts = [];
|
|
3545
|
+
for (const col of columnLayouts) {
|
|
3546
|
+
const last = sliceLayouts[sliceLayouts.length - 1];
|
|
3547
|
+
if (last && last.sliceId === col.sliceId) {
|
|
3548
|
+
last.columnIds.push(col.columnId);
|
|
3549
|
+
last.width += col.width;
|
|
3550
|
+
} else {
|
|
3551
|
+
sliceLayouts.push({
|
|
3552
|
+
sliceId: col.sliceId,
|
|
3553
|
+
columnIds: [col.columnId],
|
|
3554
|
+
index: sliceLayouts.length,
|
|
3555
|
+
x: col.x,
|
|
3556
|
+
y: originY,
|
|
3557
|
+
width: col.width,
|
|
3558
|
+
height: gridHeight
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3369
3562
|
const rowLayouts = rows.map((swimLaneId, i) => ({
|
|
3370
3563
|
swimLaneId,
|
|
3371
3564
|
index: i,
|
|
@@ -3375,9 +3568,10 @@ function computeSheetLayout(doc, chapterId, override) {
|
|
|
3375
3568
|
height: rowHeights[i]
|
|
3376
3569
|
}));
|
|
3377
3570
|
const stickies = [];
|
|
3378
|
-
columns.forEach((
|
|
3571
|
+
columns.forEach((columnId, ci) => {
|
|
3572
|
+
const sliceId = ownerOf(columnId);
|
|
3379
3573
|
rows.forEach((swimLaneId, ri) => {
|
|
3380
|
-
const refs = cells[cellKey(
|
|
3574
|
+
const refs = cells[cellKey(columnId, swimLaneId)] ?? [];
|
|
3381
3575
|
if (refs.length === 0)
|
|
3382
3576
|
return;
|
|
3383
3577
|
const content = cellContentSize(refs);
|
|
@@ -3392,6 +3586,7 @@ function computeSheetLayout(doc, chapterId, override) {
|
|
|
3392
3586
|
stickies.push({
|
|
3393
3587
|
stickyId: ref.stickyId,
|
|
3394
3588
|
scope: ref.scope,
|
|
3589
|
+
columnId,
|
|
3395
3590
|
sliceId,
|
|
3396
3591
|
swimLaneId,
|
|
3397
3592
|
x: Math.round(midX - size.width / 2),
|
|
@@ -3408,6 +3603,7 @@ function computeSheetLayout(doc, chapterId, override) {
|
|
|
3408
3603
|
stickies.push({
|
|
3409
3604
|
stickyId: ref.stickyId,
|
|
3410
3605
|
scope: ref.scope,
|
|
3606
|
+
columnId,
|
|
3411
3607
|
sliceId,
|
|
3412
3608
|
swimLaneId,
|
|
3413
3609
|
x: Math.round(cursorX),
|
|
@@ -3421,17 +3617,18 @@ function computeSheetLayout(doc, chapterId, override) {
|
|
|
3421
3617
|
});
|
|
3422
3618
|
});
|
|
3423
3619
|
return {
|
|
3424
|
-
|
|
3425
|
-
|
|
3620
|
+
sheetId,
|
|
3621
|
+
sheet: { x: originX, y: originY, width: gridWidth, height: gridHeight },
|
|
3426
3622
|
columns: columnLayouts,
|
|
3623
|
+
slices: sliceLayouts,
|
|
3427
3624
|
rows: rowLayouts,
|
|
3428
3625
|
stickies
|
|
3429
3626
|
};
|
|
3430
3627
|
}
|
|
3431
3628
|
// ../packages/canvas-model/src/reflow.ts
|
|
3432
3629
|
var REFLOW_ORIGIN = Symbol("sheet-reflow");
|
|
3433
|
-
var
|
|
3434
|
-
var
|
|
3630
|
+
var SHEETS_SCOPE2 = "sheets";
|
|
3631
|
+
var SLICES_SCOPE4 = "slices";
|
|
3435
3632
|
var SWIM_LANES_SCOPE2 = "swimLanes";
|
|
3436
3633
|
var STICKY_SCOPES = [
|
|
3437
3634
|
"commands",
|
|
@@ -3457,27 +3654,30 @@ function applyBox(m, box, size = true) {
|
|
|
3457
3654
|
}
|
|
3458
3655
|
}
|
|
3459
3656
|
function reflowAllSheets(doc, origin = REFLOW_ORIGIN) {
|
|
3460
|
-
|
|
3461
|
-
|
|
3657
|
+
migrateChaptersToSheets(doc);
|
|
3658
|
+
const sheets = getScopeMap(doc, SHEETS_SCOPE2);
|
|
3659
|
+
if (sheets.size === 0)
|
|
3462
3660
|
return;
|
|
3463
3661
|
doc.transact(() => {
|
|
3464
|
-
for (const
|
|
3465
|
-
ensureSheet(doc,
|
|
3466
|
-
|
|
3662
|
+
for (const sheetId of sheets.keys()) {
|
|
3663
|
+
ensureSheet(doc, sheetId);
|
|
3664
|
+
ensureColumnSlices(doc, sheetId);
|
|
3665
|
+
}
|
|
3666
|
+
const slices = getScopeMap(doc, SLICES_SCOPE4);
|
|
3467
3667
|
const lanes = getScopeMap(doc, SWIM_LANES_SCOPE2);
|
|
3468
3668
|
const stickyMaps = STICKY_SCOPES.map((s) => getScopeMap(doc, s));
|
|
3469
|
-
for (const
|
|
3470
|
-
pruneOrphanCellRefs(doc,
|
|
3471
|
-
const layout = computeSheetLayout(doc,
|
|
3669
|
+
for (const sheetId of getGridMap(doc).keys()) {
|
|
3670
|
+
pruneOrphanCellRefs(doc, sheetId);
|
|
3671
|
+
const layout = computeSheetLayout(doc, sheetId);
|
|
3472
3672
|
if (!layout)
|
|
3473
3673
|
continue;
|
|
3474
|
-
const ch =
|
|
3674
|
+
const ch = sheets.get(sheetId);
|
|
3475
3675
|
if (ch) {
|
|
3476
|
-
setIfChanged(ch, "width", layout.
|
|
3477
|
-
setIfChanged(ch, "height", layout.
|
|
3676
|
+
setIfChanged(ch, "width", layout.sheet.width);
|
|
3677
|
+
setIfChanged(ch, "height", layout.sheet.height);
|
|
3478
3678
|
}
|
|
3479
|
-
for (const
|
|
3480
|
-
applyBox(slices.get(
|
|
3679
|
+
for (const band of layout.slices)
|
|
3680
|
+
applyBox(slices.get(band.sliceId), band);
|
|
3481
3681
|
for (const row of layout.rows)
|
|
3482
3682
|
applyBox(lanes.get(row.swimLaneId), row);
|
|
3483
3683
|
const scenarioOrder = new Map;
|
|
@@ -4937,25 +5137,36 @@ function pad(s, width) {
|
|
|
4937
5137
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
4938
5138
|
}
|
|
4939
5139
|
var NOTE = "†";
|
|
4940
|
-
function resolve4(doc,
|
|
4941
|
-
const structure = readSheet(doc,
|
|
5140
|
+
function resolve4(doc, sheetId) {
|
|
5141
|
+
const structure = readSheet(doc, sheetId);
|
|
4942
5142
|
if (!structure)
|
|
4943
5143
|
return;
|
|
4944
5144
|
const { columns, rows, cells } = structure;
|
|
4945
|
-
const notes = readCellNotes(doc,
|
|
4946
|
-
const cols = columns.map((
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
5145
|
+
const notes = readCellNotes(doc, sheetId);
|
|
5146
|
+
const cols = columns.map((_, i) => ({ label: colLabel(i) }));
|
|
5147
|
+
const slices = sliceOrder(structure).map((sliceId) => {
|
|
5148
|
+
const cids = sliceColumns(structure, sliceId);
|
|
5149
|
+
const firstCol = columns.indexOf(cids[0]);
|
|
5150
|
+
const lastCol = columns.indexOf(cids[cids.length - 1]);
|
|
5151
|
+
const e = getEntry(doc, "slices", sliceId);
|
|
5152
|
+
return {
|
|
5153
|
+
label: firstCol === lastCol ? colLabel(firstCol) : `${colLabel(firstCol)}–${colLabel(lastCol)}`,
|
|
5154
|
+
name: e?.name?.trim() || "(unnamed)",
|
|
5155
|
+
status: e?.status ?? "",
|
|
5156
|
+
note: Boolean(e?.note),
|
|
5157
|
+
firstCol,
|
|
5158
|
+
lastCol
|
|
5159
|
+
};
|
|
5160
|
+
});
|
|
4951
5161
|
const lanes2 = rows.map((swimLaneId, i) => {
|
|
4952
5162
|
const e = getEntry(doc, "swimLanes", swimLaneId);
|
|
4953
5163
|
return { label: String(i + 1), name: e?.name?.trim() || "(unnamed)", kind: e?.laneKind ?? "?", note: Boolean(e?.note) };
|
|
4954
5164
|
});
|
|
4955
|
-
const elems = columns.map((
|
|
4956
|
-
const cellNote = columns.map((
|
|
5165
|
+
const elems = columns.map((columnId) => rows.map((swimLaneId) => (cells[cellKey(columnId, swimLaneId)] ?? []).map((r) => elementText(doc, r))));
|
|
5166
|
+
const cellNote = columns.map((columnId) => rows.map((swimLaneId) => Boolean(notes[cellKey(columnId, swimLaneId)])));
|
|
4957
5167
|
return {
|
|
4958
|
-
title: entityName(doc, "
|
|
5168
|
+
title: entityName(doc, "sheets", sheetId),
|
|
5169
|
+
slices,
|
|
4959
5170
|
columns: cols,
|
|
4960
5171
|
rows: lanes2,
|
|
4961
5172
|
elems,
|
|
@@ -4968,7 +5179,12 @@ function rowLabel(r) {
|
|
|
4968
5179
|
return r.note ? `${base} ${NOTE}` : base;
|
|
4969
5180
|
}
|
|
4970
5181
|
function header(r) {
|
|
4971
|
-
return `Sheet "${r.title}" (${r.
|
|
5182
|
+
return `Sheet "${r.title}" (${r.slices.length} slices · ${r.columns.length} columns × ${r.rows.length} lanes)`;
|
|
5183
|
+
}
|
|
5184
|
+
function bandLabel(s) {
|
|
5185
|
+
const status = s.status && s.status !== "draft" ? ` · ${s.status}` : "";
|
|
5186
|
+
const note = s.note ? ` ${NOTE}` : "";
|
|
5187
|
+
return `⟦ ${s.label} ${s.name}${status}${note} ⟧`;
|
|
4972
5188
|
}
|
|
4973
5189
|
function cellLines(elems, width, hasNote) {
|
|
4974
5190
|
const lines = elems.length === 0 ? [EMPTY] : (() => {
|
|
@@ -4979,22 +5195,27 @@ function cellLines(elems, width, hasNote) {
|
|
|
4979
5195
|
lines[lines.length - 1] = `${lines[lines.length - 1]} ${NOTE}`;
|
|
4980
5196
|
return lines;
|
|
4981
5197
|
}
|
|
4982
|
-
function
|
|
4983
|
-
|
|
5198
|
+
function spanWidth(s, colWidths) {
|
|
5199
|
+
let w = 2 * (s.lastCol - s.firstCol);
|
|
5200
|
+
for (let i = s.firstCol;i <= s.lastCol; i += 1)
|
|
5201
|
+
w += colWidths[i];
|
|
5202
|
+
return w;
|
|
4984
5203
|
}
|
|
4985
5204
|
function renderTable(r, opts) {
|
|
4986
5205
|
const gutter = Math.max(...r.rows.map(rowLabel).map((s) => s.length), 0);
|
|
4987
5206
|
const colWidths = r.columns.map((c, ci) => {
|
|
4988
|
-
const widest = Math.max(
|
|
5207
|
+
const widest = Math.max(c.label.length, ...r.elems[ci].map((cell, ri) => Math.max(0, ...cell.map((e) => e.length)) + (r.cellNote[ci][ri] ? 2 : 0)), EMPTY.length);
|
|
4989
5208
|
return Math.min(widest, opts.maxColWidth + 2);
|
|
4990
5209
|
});
|
|
5210
|
+
for (const s of r.slices) {
|
|
5211
|
+
const deficit = bandLabel(s).length - spanWidth(s, colWidths);
|
|
5212
|
+
if (deficit > 0)
|
|
5213
|
+
colWidths[s.lastCol] += deficit;
|
|
5214
|
+
}
|
|
4991
5215
|
const totalWidth = gutter + colWidths.reduce((a, w) => a + w + 2, 0);
|
|
4992
5216
|
const lines = [header(r), LEGEND, ""];
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
for (let i = 0;i < headHeight; i += 1) {
|
|
4996
|
-
lines.push(pad("", gutter) + " " + r.columns.map((_, ci) => pad(headCells[ci][i] ?? "", colWidths[ci])).join(" "));
|
|
4997
|
-
}
|
|
5217
|
+
lines.push(pad("", gutter) + " " + r.slices.map((s) => pad(bandLabel(s), spanWidth(s, colWidths))).join(" "));
|
|
5218
|
+
lines.push(pad("", gutter) + " " + r.columns.map((c, ci) => pad(c.label, colWidths[ci])).join(" "));
|
|
4998
5219
|
lines.push("─".repeat(totalWidth));
|
|
4999
5220
|
r.rows.forEach((row, ri) => {
|
|
5000
5221
|
const cellsLines = r.columns.map((_, ci) => cellLines(r.elems[ci][ri], colWidths[ci], r.cellNote[ci][ri]));
|
|
@@ -5012,24 +5233,28 @@ function renderTable(r, opts) {
|
|
|
5012
5233
|
}
|
|
5013
5234
|
function renderListing(r) {
|
|
5014
5235
|
const lines = [header(r), LEGEND, ""];
|
|
5015
|
-
r.
|
|
5016
|
-
|
|
5236
|
+
r.slices.forEach((s) => {
|
|
5237
|
+
const status = s.status && s.status !== "draft" ? ` · ${s.status}` : "";
|
|
5238
|
+
lines.push(`⟦ ${s.label} ⟧ "${s.name}"${status}${s.note ? ` ${NOTE}` : ""}`);
|
|
5017
5239
|
let any = false;
|
|
5018
|
-
|
|
5019
|
-
const
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
});
|
|
5240
|
+
for (let ci = s.firstCol;ci <= s.lastCol; ci += 1) {
|
|
5241
|
+
const col = r.columns[ci];
|
|
5242
|
+
r.rows.forEach((row, ri) => {
|
|
5243
|
+
const elems = r.elems[ci][ri];
|
|
5244
|
+
const noted = r.cellNote[ci][ri];
|
|
5245
|
+
if (elems.length === 0 && !noted)
|
|
5246
|
+
return;
|
|
5247
|
+
any = true;
|
|
5248
|
+
const addr = `${col.label}${row.label}`;
|
|
5249
|
+
const mark = noted ? ` ${NOTE}` : "";
|
|
5250
|
+
if (elems.length <= 1) {
|
|
5251
|
+
lines.push(` ${addr} ${elems[0] ?? EMPTY} [${row.kind}]${mark}`);
|
|
5252
|
+
} else {
|
|
5253
|
+
lines.push(` ${addr} [${row.kind}]${mark}`);
|
|
5254
|
+
elems.forEach((e, i) => lines.push(` ${addr}.${i + 1} ${e}`));
|
|
5255
|
+
}
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
5033
5258
|
if (!any)
|
|
5034
5259
|
lines.push(" (empty)");
|
|
5035
5260
|
lines.push("");
|
|
@@ -5037,9 +5262,9 @@ function renderListing(r) {
|
|
|
5037
5262
|
return lines.join(`
|
|
5038
5263
|
`).trimEnd();
|
|
5039
5264
|
}
|
|
5040
|
-
function renderSheetOverview(doc,
|
|
5265
|
+
function renderSheetOverview(doc, sheetId, options = {}) {
|
|
5041
5266
|
const opts = { ...DEFAULTS, ...options };
|
|
5042
|
-
const r = resolve4(doc,
|
|
5267
|
+
const r = resolve4(doc, sheetId);
|
|
5043
5268
|
if (!r)
|
|
5044
5269
|
return;
|
|
5045
5270
|
if (r.empty)
|
|
@@ -5073,23 +5298,23 @@ function resolveSheet2(doc, ref) {
|
|
|
5073
5298
|
const ids = [...grid2.keys()];
|
|
5074
5299
|
if (ids.length === 0)
|
|
5075
5300
|
throw new SheetAddressError("This model has no sheets.");
|
|
5076
|
-
const done = (
|
|
5301
|
+
const done = (sheetId) => ({ sheetId, structure: readSheet(doc, sheetId) });
|
|
5077
5302
|
if (ref && grid2.has(ref))
|
|
5078
5303
|
return done(ref);
|
|
5079
5304
|
if (!ref) {
|
|
5080
5305
|
if (ids.length === 1)
|
|
5081
5306
|
return done(ids[0]);
|
|
5082
|
-
const list = ids.map((id) => `"${name(doc, "
|
|
5307
|
+
const list = ids.map((id) => `"${name(doc, "sheets", id)}"`).join(", ");
|
|
5083
5308
|
throw new SheetAddressError(`Multiple sheets — name one. Available: ${list}`);
|
|
5084
5309
|
}
|
|
5085
5310
|
const lower = ref.toLowerCase();
|
|
5086
|
-
const matches = ids.filter((id) => name(doc, "
|
|
5311
|
+
const matches = ids.filter((id) => name(doc, "sheets", id).toLowerCase() === lower);
|
|
5087
5312
|
if (matches.length === 0) {
|
|
5088
|
-
const list = ids.map((id) => `"${name(doc, "
|
|
5313
|
+
const list = ids.map((id) => `"${name(doc, "sheets", id)}"`).join(", ") || "(none)";
|
|
5089
5314
|
throw new SheetAddressError(`No sheet named "${ref}". Available: ${list}`);
|
|
5090
5315
|
}
|
|
5091
5316
|
if (matches.length > 1)
|
|
5092
|
-
throw new SheetAddressError(`Ambiguous sheet name "${ref}" — pass the
|
|
5317
|
+
throw new SheetAddressError(`Ambiguous sheet name "${ref}" — pass the sheetId instead.`);
|
|
5093
5318
|
return done(matches[0]);
|
|
5094
5319
|
}
|
|
5095
5320
|
function resolveColumn(doc, s, ref) {
|
|
@@ -5127,6 +5352,36 @@ function resolveRow(doc, s, ref) {
|
|
|
5127
5352
|
throw new SheetAddressError(`Ambiguous row name "${ref}" — use its number instead.`);
|
|
5128
5353
|
throw new SheetAddressError(numeric ? `No row "${ref}" — not a lane name, and row ${ref} is out of range (sheet has ${rows.length} rows).` : `No row named "${ref}".`);
|
|
5129
5354
|
}
|
|
5355
|
+
function resolveSlice(doc, s, ref) {
|
|
5356
|
+
const { columns, columnSlices } = s.structure;
|
|
5357
|
+
const order = sliceOrder(s.structure);
|
|
5358
|
+
const make = (sliceId) => {
|
|
5359
|
+
const columnIds = sliceColumns(s.structure, sliceId);
|
|
5360
|
+
const firstIndex = columns.indexOf(columnIds[0]);
|
|
5361
|
+
const lastIndex = columns.indexOf(columnIds[columnIds.length - 1]);
|
|
5362
|
+
return {
|
|
5363
|
+
sliceId,
|
|
5364
|
+
columnIds,
|
|
5365
|
+
firstIndex,
|
|
5366
|
+
lastIndex,
|
|
5367
|
+
label: firstIndex === lastIndex ? labelOf(firstIndex) : `${labelOf(firstIndex)}–${labelOf(lastIndex)}`
|
|
5368
|
+
};
|
|
5369
|
+
};
|
|
5370
|
+
const lower = ref.toLowerCase();
|
|
5371
|
+
const byName = order.filter((sid) => name(doc, "slices", sid).toLowerCase() === lower);
|
|
5372
|
+
if (byName.length === 1)
|
|
5373
|
+
return make(byName[0]);
|
|
5374
|
+
if (byName.length > 1)
|
|
5375
|
+
throw new SheetAddressError(`Ambiguous slice name "${ref}" — use its leading column letter instead.`);
|
|
5376
|
+
const byLabel = colIndexFromLabel(ref);
|
|
5377
|
+
if (byLabel >= 0 && byLabel < columns.length) {
|
|
5378
|
+
const columnId = columns[byLabel];
|
|
5379
|
+
return make((columnSlices ?? {})[columnId] ?? columnId);
|
|
5380
|
+
}
|
|
5381
|
+
if (order.includes(ref))
|
|
5382
|
+
return make(ref);
|
|
5383
|
+
throw new SheetAddressError(`No slice "${ref}" — not a slice name, a leading column letter, or a sliceId.`);
|
|
5384
|
+
}
|
|
5130
5385
|
function labelOf(index) {
|
|
5131
5386
|
let n = index;
|
|
5132
5387
|
let str = "";
|
|
@@ -5285,18 +5540,58 @@ function rejectLane(doc, swimLaneId, rowIndex, kind, type) {
|
|
|
5285
5540
|
const allowed = (LANE_ELEMENT_SCOPES[kind] ?? []).map((sc) => TYPE_LABEL[sc] ?? sc).join(", ");
|
|
5286
5541
|
throw new SheetAddressError(`Lane "${nameOf(doc, "swimLanes", swimLaneId)}" (row ${rowIndex + 1}, kind=${kind}) accepts ${allowed || "(nothing)"}, not ${type}.`);
|
|
5287
5542
|
}
|
|
5288
|
-
function insertColumn(doc, s, modelId, name2, at) {
|
|
5543
|
+
function insertColumn(doc, s, modelId, name2, at, cols = 1) {
|
|
5289
5544
|
const id = crypto.randomUUID();
|
|
5290
5545
|
const d = ELEMENT_DIMENSIONS.slice;
|
|
5291
5546
|
const v = validateEntry("slices", { name: name2, x: 0, y: 0, width: d.width, height: d.height });
|
|
5292
5547
|
if (!v.ok)
|
|
5293
5548
|
throw new SheetAddressError(`Invalid slice: ${v.issues.join("; ")}`);
|
|
5549
|
+
const span = Math.max(1, cols);
|
|
5294
5550
|
doc.transact(() => {
|
|
5295
5551
|
getScopeMap(doc, "slices").set(id, deepToY({ ...v.data, sliceId: id, modelId }));
|
|
5296
|
-
addColumn(doc, s.
|
|
5552
|
+
addColumn(doc, s.sheetId, id, at);
|
|
5553
|
+
for (let i = 1;i < span; i += 1)
|
|
5554
|
+
addColumnToSlice(doc, s.sheetId, id, crypto.randomUUID());
|
|
5555
|
+
});
|
|
5556
|
+
const idx = readSheet(doc, s.sheetId).columns.indexOf(id);
|
|
5557
|
+
const note = span > 1 ? ` (${span} cols)` : "";
|
|
5558
|
+
return `${colLabel2(idx)} "${name2}"${note} ${id}`;
|
|
5559
|
+
}
|
|
5560
|
+
function spanSlice(doc, s, ref, toCol) {
|
|
5561
|
+
const slice = resolveSlice(doc, s, ref);
|
|
5562
|
+
const col = resolveColumn(doc, s, toCol);
|
|
5563
|
+
const sliceName = getEntry(doc, "slices", slice.sliceId)?.name ?? "(unnamed)";
|
|
5564
|
+
doc.transact(() => resizeSliceSpan(doc, s.sheetId, slice.sliceId, col.index));
|
|
5565
|
+
const after = resolveSlice(doc, { sheetId: s.sheetId, structure: readSheet(doc, s.sheetId) }, slice.sliceId);
|
|
5566
|
+
return `slice "${sliceName}" now spans ${after.label}`;
|
|
5567
|
+
}
|
|
5568
|
+
function splitSlice(doc, s, modelId, colRef) {
|
|
5569
|
+
const col = resolveColumn(doc, s, colRef);
|
|
5570
|
+
const columnId = col.sliceId;
|
|
5571
|
+
const owningSlice = (s.structure.columnSlices ?? {})[columnId] ?? columnId;
|
|
5572
|
+
const cols = sliceColumns(s.structure, owningSlice);
|
|
5573
|
+
const pos = cols.indexOf(columnId);
|
|
5574
|
+
if (pos <= 0)
|
|
5575
|
+
throw new SheetAddressError(`Column ${col.label} already begins its slice — nothing to split.`);
|
|
5576
|
+
const newId = crypto.randomUUID();
|
|
5577
|
+
const d = ELEMENT_DIMENSIONS.slice;
|
|
5578
|
+
doc.transact(() => {
|
|
5579
|
+
getScopeMap(doc, "slices").set(newId, deepToY({ sliceId: newId, modelId, name: "Slice", x: 0, y: 0, width: d.width, height: d.height }));
|
|
5580
|
+
for (const c of cols.slice(pos))
|
|
5581
|
+
setColumnSlice(doc, s.sheetId, c, newId);
|
|
5297
5582
|
});
|
|
5298
|
-
|
|
5299
|
-
|
|
5583
|
+
return `split at ${col.label} — a new slice now covers ${col.label} onward ${newId}`;
|
|
5584
|
+
}
|
|
5585
|
+
function renameSlice(doc, s, ref, newName) {
|
|
5586
|
+
const slice = resolveSlice(doc, s, ref);
|
|
5587
|
+
if (!newName.trim())
|
|
5588
|
+
throw new SheetAddressError("A slice name cannot be empty.");
|
|
5589
|
+
doc.transact(() => {
|
|
5590
|
+
const e = getScopeMap(doc, "slices").get(slice.sliceId);
|
|
5591
|
+
if (e)
|
|
5592
|
+
e.set("name", newName.trim());
|
|
5593
|
+
});
|
|
5594
|
+
return `renamed slice ${slice.label} → "${newName.trim()}"`;
|
|
5300
5595
|
}
|
|
5301
5596
|
function insertLane(doc, s, modelId, kind, name2, at) {
|
|
5302
5597
|
const id = crypto.randomUUID();
|
|
@@ -5306,9 +5601,9 @@ function insertLane(doc, s, modelId, kind, name2, at) {
|
|
|
5306
5601
|
throw new SheetAddressError(`Invalid lane: ${v.issues.join("; ")}`);
|
|
5307
5602
|
doc.transact(() => {
|
|
5308
5603
|
getScopeMap(doc, "swimLanes").set(id, deepToY({ ...v.data, swimLaneId: id, modelId }));
|
|
5309
|
-
addRow(doc, s.
|
|
5604
|
+
addRow(doc, s.sheetId, id, at);
|
|
5310
5605
|
});
|
|
5311
|
-
const idx = readSheet(doc, s.
|
|
5606
|
+
const idx = readSheet(doc, s.sheetId).rows.indexOf(id);
|
|
5312
5607
|
return `${idx + 1} "${name2}" (${kind}) ${id}`;
|
|
5313
5608
|
}
|
|
5314
5609
|
function insertElement(doc, s, modelId, type, cellArg, nameOrJson) {
|
|
@@ -5337,7 +5632,7 @@ function insertElement(doc, s, modelId, type, cellArg, nameOrJson) {
|
|
|
5337
5632
|
};
|
|
5338
5633
|
doc.transact(() => {
|
|
5339
5634
|
getScopeMap(doc, "scenarios").set(id, deepToY(entry2));
|
|
5340
|
-
placeInCell(doc, s.
|
|
5635
|
+
placeInCell(doc, s.sheetId, cell.sliceId, cell.swimLaneId, { stickyId: id, scope: "scenarios" });
|
|
5341
5636
|
});
|
|
5342
5637
|
return addressOf(doc, s, cell.sliceId, cell.swimLaneId, id, cell.address);
|
|
5343
5638
|
}
|
|
@@ -5352,12 +5647,12 @@ function insertElement(doc, s, modelId, type, cellArg, nameOrJson) {
|
|
|
5352
5647
|
const final = { ...v.data, [meta.idKey]: id, modelId };
|
|
5353
5648
|
doc.transact(() => {
|
|
5354
5649
|
getScopeMap(doc, meta.scope).set(id, deepToY(final));
|
|
5355
|
-
placeInCell(doc, s.
|
|
5650
|
+
placeInCell(doc, s.sheetId, cell.sliceId, cell.swimLaneId, { stickyId: id, scope: meta.scope });
|
|
5356
5651
|
});
|
|
5357
5652
|
return addressOf(doc, s, cell.sliceId, cell.swimLaneId, id, cell.address);
|
|
5358
5653
|
}
|
|
5359
5654
|
function addressOf(doc, s, sliceId, swimLaneId, id, cellAddr) {
|
|
5360
|
-
const refs = readSheet(doc, s.
|
|
5655
|
+
const refs = readSheet(doc, s.sheetId).cells[cellKey(sliceId, swimLaneId)] ?? [];
|
|
5361
5656
|
const ord = refs.findIndex((r) => r.stickyId === id) + 1;
|
|
5362
5657
|
return `${cellAddr}.${ord} ${id}`;
|
|
5363
5658
|
}
|
|
@@ -5368,7 +5663,7 @@ function moveElement(doc, s, elAddr, toCell) {
|
|
|
5368
5663
|
if (!laneAllows(kind, el.scope))
|
|
5369
5664
|
rejectLane(doc, dest.swimLaneId, dest.rowIndex, kind, TYPE_LABEL[el.scope] ?? el.scope);
|
|
5370
5665
|
doc.transact(() => {
|
|
5371
|
-
placeInCell(doc, s.
|
|
5666
|
+
placeInCell(doc, s.sheetId, dest.sliceId, dest.swimLaneId, { stickyId: el.stickyId, scope: el.scope });
|
|
5372
5667
|
});
|
|
5373
5668
|
return addressOf(doc, s, dest.sliceId, dest.swimLaneId, el.stickyId, dest.address);
|
|
5374
5669
|
}
|
|
@@ -5376,14 +5671,14 @@ function reorderAxis(doc, s, ref, to) {
|
|
|
5376
5671
|
const toIndex = to - 1;
|
|
5377
5672
|
if (/^\d+$/.test(ref)) {
|
|
5378
5673
|
const row = resolveRow(doc, s, ref);
|
|
5379
|
-
doc.transact(() => moveRow(doc, s.
|
|
5380
|
-
const idx = readSheet(doc, s.
|
|
5674
|
+
doc.transact(() => moveRow(doc, s.sheetId, row.swimLaneId, toIndex));
|
|
5675
|
+
const idx = readSheet(doc, s.sheetId).rows.indexOf(row.swimLaneId);
|
|
5381
5676
|
return `row → ${idx + 1}`;
|
|
5382
5677
|
}
|
|
5383
5678
|
if (colIndexFromLabel(ref) >= 0 || s.structure.columns.length > 0) {
|
|
5384
5679
|
const col = resolveColumn(doc, s, ref);
|
|
5385
|
-
doc.transact(() => moveColumn(doc, s.
|
|
5386
|
-
const idx = readSheet(doc, s.
|
|
5680
|
+
doc.transact(() => moveColumn(doc, s.sheetId, col.sliceId, toIndex));
|
|
5681
|
+
const idx = readSheet(doc, s.sheetId).columns.indexOf(col.sliceId);
|
|
5387
5682
|
return `column → ${colLabel2(idx)}`;
|
|
5388
5683
|
}
|
|
5389
5684
|
throw new SheetAddressError(`reorder takes a column letter (A) or row number (1), got "${ref}".`);
|
|
@@ -5392,7 +5687,7 @@ function removeTarget(doc, s, target) {
|
|
|
5392
5687
|
if (/^[A-Za-z]+\d+(\.\d+)?$/.test(target)) {
|
|
5393
5688
|
const el = resolveElement(doc, s, target);
|
|
5394
5689
|
doc.transact(() => {
|
|
5395
|
-
removeSticky(doc, s.
|
|
5690
|
+
removeSticky(doc, s.sheetId, el.stickyId);
|
|
5396
5691
|
getScopeMap(doc, el.scope).delete(el.stickyId);
|
|
5397
5692
|
cascadeStickyRemoval(doc, new Set([el.stickyId]));
|
|
5398
5693
|
});
|
|
@@ -5403,7 +5698,7 @@ function removeTarget(doc, s, target) {
|
|
|
5403
5698
|
const laneName = nameOf(doc, "swimLanes", row.swimLaneId);
|
|
5404
5699
|
const refs2 = s.structure.columns.flatMap((sliceId) => s.structure.cells[cellKey(sliceId, row.swimLaneId)] ?? []);
|
|
5405
5700
|
doc.transact(() => {
|
|
5406
|
-
removeRow(doc, s.
|
|
5701
|
+
removeRow(doc, s.sheetId, row.swimLaneId);
|
|
5407
5702
|
getScopeMap(doc, "swimLanes").delete(row.swimLaneId);
|
|
5408
5703
|
for (const r of refs2)
|
|
5409
5704
|
getScopeMap(doc, r.scope).delete(r.stickyId);
|
|
@@ -5415,7 +5710,7 @@ function removeTarget(doc, s, target) {
|
|
|
5415
5710
|
const sliceName = nameOf(doc, "slices", col.sliceId);
|
|
5416
5711
|
const refs = s.structure.rows.flatMap((swimLaneId) => s.structure.cells[cellKey(col.sliceId, swimLaneId)] ?? []);
|
|
5417
5712
|
doc.transact(() => {
|
|
5418
|
-
removeColumn(doc, s.
|
|
5713
|
+
removeColumn(doc, s.sheetId, col.sliceId);
|
|
5419
5714
|
getScopeMap(doc, "slices").delete(col.sliceId);
|
|
5420
5715
|
for (const r of refs)
|
|
5421
5716
|
getScopeMap(doc, r.scope).delete(r.stickyId);
|
|
@@ -5493,7 +5788,7 @@ function sheetEdgeHandle(side, dir) {
|
|
|
5493
5788
|
}
|
|
5494
5789
|
function resolveAnchor(doc, sheet, dir, col, side) {
|
|
5495
5790
|
const flag = dir === "source" ? "from" : "to";
|
|
5496
|
-
const sheetName = nameOf(doc, "
|
|
5791
|
+
const sheetName = nameOf(doc, "sheets", sheet.sheetId);
|
|
5497
5792
|
if (col !== undefined) {
|
|
5498
5793
|
const c = resolveColumn(doc, sheet, col);
|
|
5499
5794
|
const s2 = side ?? (dir === "source" ? "bottom" : "top");
|
|
@@ -5511,7 +5806,7 @@ function resolveAnchor(doc, sheet, dir, col, side) {
|
|
|
5511
5806
|
function connectSheets(doc, modelId, fromRef, toRef, opts) {
|
|
5512
5807
|
const src = resolveSheet2(doc, fromRef);
|
|
5513
5808
|
const dst = resolveSheet2(doc, toRef);
|
|
5514
|
-
if (src.
|
|
5809
|
+
if (src.sheetId === dst.sheetId) {
|
|
5515
5810
|
throw new SheetAddressError("Connect joins two different sheets — source and target are the same sheet.");
|
|
5516
5811
|
}
|
|
5517
5812
|
const from = resolveAnchor(doc, src, "source", opts.from, opts.fromSide);
|
|
@@ -5519,9 +5814,9 @@ function connectSheets(doc, modelId, fromRef, toRef, opts) {
|
|
|
5519
5814
|
const id = crypto.randomUUID();
|
|
5520
5815
|
const entry = {
|
|
5521
5816
|
sheetFlowId: id,
|
|
5522
|
-
|
|
5817
|
+
sourceSheetId: src.sheetId,
|
|
5523
5818
|
sourceHandle: from.handle,
|
|
5524
|
-
|
|
5819
|
+
targetSheetId: dst.sheetId,
|
|
5525
5820
|
targetHandle: to.handle
|
|
5526
5821
|
};
|
|
5527
5822
|
const v = validateEntry("sheetFlows", entry);
|
|
@@ -5554,7 +5849,7 @@ function copyElement(doc, s, modelId, srcAddr, toCell) {
|
|
|
5554
5849
|
const dim = FIXED_SIZE_DIM(src.scope);
|
|
5555
5850
|
doc.transact(() => {
|
|
5556
5851
|
getScopeMap(doc, src.scope).set(id, deepToY({ [meta.idKey]: id, modelId, name: "", x: 0, y: 0, width: dim.width, height: dim.height, isLinkedCopy: true, [meta.originalIdKey]: originalId }));
|
|
5557
|
-
placeInCell(doc, s.
|
|
5852
|
+
placeInCell(doc, s.sheetId, dest.sliceId, dest.swimLaneId, { stickyId: id, scope: src.scope });
|
|
5558
5853
|
});
|
|
5559
5854
|
return addressOf(doc, s, dest.sliceId, dest.swimLaneId, id, dest.address);
|
|
5560
5855
|
}
|
|
@@ -5584,7 +5879,7 @@ function noteTarget(doc, s, target, text) {
|
|
|
5584
5879
|
}
|
|
5585
5880
|
if (/^[A-Za-z]+\d+$/.test(target)) {
|
|
5586
5881
|
const cell = resolveCell(doc, s, target);
|
|
5587
|
-
setCellNote(doc, s.
|
|
5882
|
+
setCellNote(doc, s.sheetId, cell.sliceId, cell.swimLaneId, t);
|
|
5588
5883
|
return `${verb} cell ${cell.address}`;
|
|
5589
5884
|
}
|
|
5590
5885
|
if (/^\d+$/.test(target)) {
|
|
@@ -5684,12 +5979,12 @@ function buildFromSpec(doc, modelId, nameArg, spec) {
|
|
|
5684
5979
|
if (f.type && f.type !== inferred)
|
|
5685
5980
|
throw new SheetAddressError(`Flow ${f.from}→${f.to} is ${inferred}, not ${f.type}.`);
|
|
5686
5981
|
}
|
|
5687
|
-
const
|
|
5982
|
+
const sheetId = crypto.randomUUID();
|
|
5688
5983
|
const idByAddress = {};
|
|
5689
5984
|
doc.transact(() => {
|
|
5690
|
-
const cd = ELEMENT_DIMENSIONS.
|
|
5691
|
-
getScopeMap(doc, "
|
|
5692
|
-
ensureSheet(doc,
|
|
5985
|
+
const cd = ELEMENT_DIMENSIONS.sheet;
|
|
5986
|
+
getScopeMap(doc, "sheets").set(sheetId, deepToY({ sheetId, modelId, name: sheetName, x: 0, y: 0, width: cd.width, height: cd.height }));
|
|
5987
|
+
ensureSheet(doc, sheetId);
|
|
5693
5988
|
const sd = ELEMENT_DIMENSIONS.slice;
|
|
5694
5989
|
const sliceIds = cols.map((c) => {
|
|
5695
5990
|
const id = crypto.randomUUID();
|
|
@@ -5699,7 +5994,7 @@ function buildFromSpec(doc, modelId, nameArg, spec) {
|
|
|
5699
5994
|
if (c.note)
|
|
5700
5995
|
entry.note = c.note;
|
|
5701
5996
|
getScopeMap(doc, "slices").set(id, deepToY(entry));
|
|
5702
|
-
addColumn(doc,
|
|
5997
|
+
addColumn(doc, sheetId, id);
|
|
5703
5998
|
return id;
|
|
5704
5999
|
});
|
|
5705
6000
|
const ld = ELEMENT_DIMENSIONS.swimLane;
|
|
@@ -5709,7 +6004,7 @@ function buildFromSpec(doc, modelId, nameArg, spec) {
|
|
|
5709
6004
|
if (r.note)
|
|
5710
6005
|
entry.note = r.note;
|
|
5711
6006
|
getScopeMap(doc, "swimLanes").set(id, deepToY(entry));
|
|
5712
|
-
addRow(doc,
|
|
6007
|
+
addRow(doc, sheetId, id);
|
|
5713
6008
|
return id;
|
|
5714
6009
|
});
|
|
5715
6010
|
for (const op of ops) {
|
|
@@ -5731,10 +6026,10 @@ function buildFromSpec(doc, modelId, nameArg, spec) {
|
|
|
5731
6026
|
width: SCENARIO_CARD.width,
|
|
5732
6027
|
height: SCENARIO_CARD.height
|
|
5733
6028
|
}));
|
|
5734
|
-
placeInCell(doc,
|
|
6029
|
+
placeInCell(doc, sheetId, sliceIds[op.ci], laneIds[op.ri], { stickyId: id, scope: "scenarios" });
|
|
5735
6030
|
} else {
|
|
5736
6031
|
getScopeMap(doc, op.scope).set(id, deepToY({ ...op.data, [op.idKey]: id, modelId }));
|
|
5737
|
-
placeInCell(doc,
|
|
6032
|
+
placeInCell(doc, sheetId, sliceIds[op.ci], laneIds[op.ri], { stickyId: id, scope: op.scope });
|
|
5738
6033
|
}
|
|
5739
6034
|
idByAddress[op.address] = id;
|
|
5740
6035
|
}
|
|
@@ -5745,7 +6040,7 @@ function buildFromSpec(doc, modelId, nameArg, spec) {
|
|
|
5745
6040
|
const id = crypto.randomUUID();
|
|
5746
6041
|
const dim = FIXED_SIZE_DIM(op.scope);
|
|
5747
6042
|
getScopeMap(doc, op.scope).set(id, deepToY({ [meta.idKey]: id, modelId, name: "", x: 0, y: 0, width: dim.width, height: dim.height, isLinkedCopy: true, [meta.originalIdKey]: idByAddress[op.copyOf] }));
|
|
5748
|
-
placeInCell(doc,
|
|
6043
|
+
placeInCell(doc, sheetId, sliceIds[op.ci], laneIds[op.ri], { stickyId: id, scope: op.scope });
|
|
5749
6044
|
idByAddress[op.address] = id;
|
|
5750
6045
|
}
|
|
5751
6046
|
for (const f of specFlows) {
|
|
@@ -5760,25 +6055,37 @@ function buildFromSpec(doc, modelId, nameArg, spec) {
|
|
|
5760
6055
|
const ci = colIndexFromLabel(cl);
|
|
5761
6056
|
const ri = rowNum - 1;
|
|
5762
6057
|
if (ci >= 0 && ci < cols.length && ri >= 0 && ri < rows.length && text) {
|
|
5763
|
-
setCellNote(doc,
|
|
6058
|
+
setCellNote(doc, sheetId, sliceIds[ci], laneIds[ri], text);
|
|
5764
6059
|
}
|
|
5765
6060
|
}
|
|
5766
6061
|
});
|
|
5767
6062
|
reflowAllSheets(doc);
|
|
5768
6063
|
const nElems = ops.length;
|
|
5769
|
-
return `built sheet "${sheetName}" — ${cols.length} slices, ${rows.length} lanes, ${nElems} elements, ${specFlows.length} flows ${
|
|
6064
|
+
return `built sheet "${sheetName}" — ${cols.length} slices, ${rows.length} lanes, ${nElems} elements, ${specFlows.length} flows ${sheetId}`;
|
|
5770
6065
|
}
|
|
5771
6066
|
function buildSheetJson(doc, s) {
|
|
5772
6067
|
const { columns, rows, cells } = s.structure;
|
|
5773
|
-
const
|
|
6068
|
+
const ownerOf = (columnId) => (s.structure.columnSlices ?? {})[columnId] ?? columnId;
|
|
6069
|
+
const columnViews = columns.map((columnId, i) => {
|
|
6070
|
+
const e = getEntry(doc, "slices", ownerOf(columnId));
|
|
6071
|
+
return { label: colLabel2(i), columnId, name: e?.name ?? "(unnamed)", status: e?.status ?? null, note: e?.note ?? null };
|
|
6072
|
+
});
|
|
6073
|
+
const slices = sliceOrder(s.structure).map((sliceId) => {
|
|
6074
|
+
const cids = sliceColumns(s.structure, sliceId);
|
|
5774
6075
|
const e = getEntry(doc, "slices", sliceId);
|
|
5775
|
-
return {
|
|
6076
|
+
return {
|
|
6077
|
+
sliceId,
|
|
6078
|
+
name: e?.name ?? "(unnamed)",
|
|
6079
|
+
status: e?.status ?? null,
|
|
6080
|
+
note: e?.note ?? null,
|
|
6081
|
+
cols: cids.map((c) => colLabel2(columns.indexOf(c)))
|
|
6082
|
+
};
|
|
5776
6083
|
});
|
|
5777
6084
|
const rowViews = rows.map((swimLaneId, i) => {
|
|
5778
6085
|
const e = getEntry(doc, "swimLanes", swimLaneId);
|
|
5779
6086
|
return { label: String(i + 1), name: e?.name ?? "(unnamed)", laneKind: e?.laneKind ?? null, swimLaneId, note: e?.note ?? null };
|
|
5780
6087
|
});
|
|
5781
|
-
const cellNotes = readCellNotes(doc, s.
|
|
6088
|
+
const cellNotes = readCellNotes(doc, s.sheetId);
|
|
5782
6089
|
const cellNotesByAddr = {};
|
|
5783
6090
|
for (const [k, text] of Object.entries(cellNotes)) {
|
|
5784
6091
|
const [sliceId, swimLaneId] = k.split(":");
|
|
@@ -5801,7 +6108,7 @@ function buildSheetJson(doc, s) {
|
|
|
5801
6108
|
}));
|
|
5802
6109
|
});
|
|
5803
6110
|
});
|
|
5804
|
-
return { sheet: nameOf(doc, "
|
|
6111
|
+
return { sheet: nameOf(doc, "sheets", s.sheetId), sheetId: s.sheetId, slices, columns: columnViews, rows: rowViews, cells: cellViews, cellNotes: cellNotesByAddr };
|
|
5805
6112
|
}
|
|
5806
6113
|
function buildCellView(doc, s, cellArg) {
|
|
5807
6114
|
const { ordinal } = parseCellAddress(cellArg);
|
|
@@ -5818,7 +6125,7 @@ function buildCellView(doc, s, cellArg) {
|
|
|
5818
6125
|
const flows = getEntries(doc, "flows");
|
|
5819
6126
|
return {
|
|
5820
6127
|
cell: cell.address,
|
|
5821
|
-
note: readCellNotes(doc, s.
|
|
6128
|
+
note: readCellNotes(doc, s.sheetId)[cellKey(cell.sliceId, cell.swimLaneId)] ?? null,
|
|
5822
6129
|
slice: { label: colLabel2(cell.colIndex), name: slice?.name ?? "(unnamed)", sliceId: cell.sliceId, note: slice?.note ?? null },
|
|
5823
6130
|
lane: { label: String(cell.rowIndex + 1), name: lane?.name ?? "(unnamed)", laneKind: lane?.laneKind ?? null, swimLaneId: cell.swimLaneId, note: lane?.note ?? null },
|
|
5824
6131
|
elements: refs.map((r) => ({
|
|
@@ -5864,7 +6171,7 @@ function registerSheetCommands(program) {
|
|
|
5864
6171
|
return JSON.stringify(buildCellView(doc, s, cellArg), null, 2);
|
|
5865
6172
|
if (opts.json)
|
|
5866
6173
|
return JSON.stringify(buildSheetJson(doc, s), null, 2);
|
|
5867
|
-
return renderSheetOverview(doc, s.
|
|
6174
|
+
return renderSheetOverview(doc, s.sheetId) ?? "(empty sheet)";
|
|
5868
6175
|
});
|
|
5869
6176
|
console.log(output);
|
|
5870
6177
|
} catch (err) {
|
|
@@ -5895,13 +6202,13 @@ function registerSheetCommands(program) {
|
|
|
5895
6202
|
if (!name2)
|
|
5896
6203
|
throw new SheetAddressError("Pass a <name> (or --from <file>).");
|
|
5897
6204
|
const id = crypto.randomUUID();
|
|
5898
|
-
const d = ELEMENT_DIMENSIONS.
|
|
5899
|
-
const v = validateEntry("
|
|
6205
|
+
const d = ELEMENT_DIMENSIONS.sheet;
|
|
6206
|
+
const v = validateEntry("sheets", { name: name2, x: 0, y: 0, width: d.width, height: d.height });
|
|
5900
6207
|
if (!v.ok)
|
|
5901
6208
|
throw new SheetAddressError(`Invalid sheet: ${v.issues.join("; ")}`);
|
|
5902
6209
|
const out = await withDoc(modelId, (doc) => {
|
|
5903
6210
|
doc.transact(() => {
|
|
5904
|
-
getScopeMap(doc, "
|
|
6211
|
+
getScopeMap(doc, "sheets").set(id, deepToY({ ...v.data, sheetId: id, modelId }));
|
|
5905
6212
|
ensureSheet(doc, id);
|
|
5906
6213
|
});
|
|
5907
6214
|
reflowAllSheets(doc);
|
|
@@ -5921,14 +6228,14 @@ function registerSheetCommands(program) {
|
|
|
5921
6228
|
try {
|
|
5922
6229
|
const out = await withDoc(modelId, (doc) => {
|
|
5923
6230
|
const s = resolveSheet2(doc, sheetRef);
|
|
5924
|
-
const name2 = getEntry(doc, "
|
|
6231
|
+
const name2 = getEntry(doc, "sheets", s.sheetId)?.name ?? s.sheetId;
|
|
5925
6232
|
const elements = Object.values(s.structure.cells).reduce((n, refs) => n + refs.length, 0);
|
|
5926
6233
|
const summary = `sheet "${name2}" — ${s.structure.columns.length} slice(s), ${s.structure.rows.length} lane(s), ${elements} element(s)`;
|
|
5927
6234
|
if (!opts.yes) {
|
|
5928
6235
|
return `Would delete ${summary}.
|
|
5929
6236
|
This cannot be undone. Re-run with --yes to confirm.`;
|
|
5930
6237
|
}
|
|
5931
|
-
deleteSheet(doc, s.
|
|
6238
|
+
deleteSheet(doc, s.sheetId);
|
|
5932
6239
|
reflowAllSheets(doc);
|
|
5933
6240
|
return `Deleted ${summary}.`;
|
|
5934
6241
|
});
|
|
@@ -5942,15 +6249,19 @@ This cannot be undone. Re-run with --yes to confirm.`;
|
|
|
5942
6249
|
}
|
|
5943
6250
|
});
|
|
5944
6251
|
const insert = sheet.command("insert").description("Insert a slice (column), a lane (row), or an element into a cell");
|
|
5945
|
-
insert.command("slice <name>").description("Insert a new slice
|
|
6252
|
+
insert.command("slice <name>").description("Insert a new slice; --cols N makes it span N contiguous columns").option("--at <n>", "Position (1-based); appends if omitted").option("--cols <n>", "How many columns the slice spans (default 1)").option("--sheet <ref>", "Which sheet (name or sheetId) when the model has several").option("--model <id>", "Target model id").action((name2, opts) => runWrite(opts, (doc, s, modelId) => insertColumn(doc, s, modelId, name2, atIndex(opts.at), opts.cols ? Math.max(1, parseInt(opts.cols, 10) || 1) : 1)));
|
|
5946
6253
|
for (const kind of LANE_KINDS) {
|
|
5947
|
-
insert.command(`${kind} <name>`).description(`Insert a ${kind} lane (row)`).option("--at <n>", "Position (1-based); appends if omitted").option("--sheet <ref>", "Which sheet (name or
|
|
6254
|
+
insert.command(`${kind} <name>`).description(`Insert a ${kind} lane (row)`).option("--at <n>", "Position (1-based); appends if omitted").option("--sheet <ref>", "Which sheet (name or sheetId) when the model has several").option("--model <id>", "Target model id").action((name2, opts) => runWrite(opts, (doc, s, modelId) => insertLane(doc, s, modelId, kind, name2, atIndex(opts.at))));
|
|
5948
6255
|
}
|
|
5949
6256
|
for (const type of ELEMENT_TYPES) {
|
|
5950
|
-
insert.command(`${type} <cell> <element>`).description(`Insert a ${type} into a cell (e.g. B2). <element> is a name or a JSON object.`).option("--sheet <ref>", "Which sheet (name or
|
|
6257
|
+
insert.command(`${type} <cell> <element>`).description(`Insert a ${type} into a cell (e.g. B2). <element> is a name or a JSON object.`).option("--sheet <ref>", "Which sheet (name or sheetId) when the model has several").option("--model <id>", "Target model id").action((cell, element, opts) => runWrite(opts, (doc, s, modelId) => insertElement(doc, s, modelId, type, cell, element)));
|
|
5951
6258
|
}
|
|
5952
6259
|
sheet.command("move <element>").description("Move an element to another cell, e.g. `move B2.1 --to C2` (keeps its id + flows)").requiredOption("--to <cell>", "Destination cell (e.g. C2)").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((element, opts) => runWrite(opts, (doc, s) => moveElement(doc, s, element, opts.to)));
|
|
5953
6260
|
sheet.command("reorder <ref>").description("Reorder a slice (column letter) or lane (row number), e.g. `reorder A --to 3`").requiredOption("--to <n>", "New 1-based position").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((ref, opts) => runWrite(opts, (doc, s) => reorderAxis(doc, s, ref, parseInt(opts.to, 10))));
|
|
6261
|
+
const slice = sheet.command("slice").description("Slice (work-ticket) ops: span across columns, split, rename");
|
|
6262
|
+
slice.command("span <slice>").description("Set a slice to span up to a column, e.g. `slice span A --to C` (grows or shrinks)").requiredOption("--to <col>", "The column the slice should end at (letter or name)").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((sliceRef, opts) => runWrite(opts, (doc, s) => spanSlice(doc, s, sliceRef, opts.to)));
|
|
6263
|
+
slice.command("split <col>").description("Split a slice so <col> begins a new slice (the columns from <col> onward break off)").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((col, opts) => runWrite(opts, (doc, s, modelId) => splitSlice(doc, s, modelId, col)));
|
|
6264
|
+
slice.command("rename <slice> <name>").description("Rename a slice (by name, leading column letter, or id)").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((sliceRef, name2, opts) => runWrite(opts, (doc, s) => renameSlice(doc, s, sliceRef, name2)));
|
|
5954
6265
|
sheet.command("rm <target>").description("Remove an element (B2.1), a column (A), or a lane (2) — cascades to flows/scenarios").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((target, opts) => runWrite(opts, (doc, s) => removeTarget(doc, s, target)));
|
|
5955
6266
|
sheet.command("update <address> <json>").description(`Replace an element's shape in place (e.g. \`update A2.1 '{"name":"PlaceOrderV2","fields":[…]}'\`)`).option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((address, json, opts) => runWrite(opts, (doc, s, modelId) => updateElement(doc, s, modelId, address, json)));
|
|
5956
6267
|
sheet.command("flow <from> <to>").description("Connect two elements, e.g. `flow A2.1 A3.1` (flow type inferred from the element kinds)").option("--type <flowType>", "Force a flow type (otherwise inferred)").option("--sheet <ref>", "Which sheet when the model has several").option("--model <id>", "Target model id").action((from, to, opts) => runWrite(opts, (doc, s, modelId) => flowBetween(doc, s, modelId, from, to, opts.type)));
|