@nex125/seatmap-core 0.1.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 +392 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +170 -0
- package/dist/index.d.ts +170 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var RBush = require('rbush');
|
|
4
|
+
var pixi_js = require('pixi.js');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var RBush__default = /*#__PURE__*/_interopDefault(RBush);
|
|
9
|
+
|
|
10
|
+
// src/models/helpers.ts
|
|
11
|
+
function seatWorldPosition(section, seat) {
|
|
12
|
+
const cos = Math.cos(section.rotation);
|
|
13
|
+
const sin = Math.sin(section.rotation);
|
|
14
|
+
return {
|
|
15
|
+
x: section.position.x + seat.position.x * cos - seat.position.y * sin,
|
|
16
|
+
y: section.position.y + seat.position.x * sin + seat.position.y * cos
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function sectionAABB(section) {
|
|
20
|
+
const allPoints = [];
|
|
21
|
+
if (section.outline.length > 0) {
|
|
22
|
+
const cos = Math.cos(section.rotation);
|
|
23
|
+
const sin = Math.sin(section.rotation);
|
|
24
|
+
for (const p of section.outline) {
|
|
25
|
+
allPoints.push({
|
|
26
|
+
x: section.position.x + p.x * cos - p.y * sin,
|
|
27
|
+
y: section.position.y + p.x * sin + p.y * cos
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const allSeats = section.rows.flatMap((r) => r.seats);
|
|
32
|
+
for (const seat of allSeats) {
|
|
33
|
+
allPoints.push(seatWorldPosition(section, seat));
|
|
34
|
+
}
|
|
35
|
+
if (allPoints.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
minX: section.position.x,
|
|
38
|
+
minY: section.position.y,
|
|
39
|
+
maxX: section.position.x,
|
|
40
|
+
maxY: section.position.y
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const pad = 10;
|
|
44
|
+
return {
|
|
45
|
+
minX: Math.min(...allPoints.map((p) => p.x)) - pad,
|
|
46
|
+
minY: Math.min(...allPoints.map((p) => p.y)) - pad,
|
|
47
|
+
maxX: Math.max(...allPoints.map((p) => p.x)) + pad,
|
|
48
|
+
maxY: Math.max(...allPoints.map((p) => p.y)) + pad
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function venueAABB(venue) {
|
|
52
|
+
if (venue.sections.length === 0) {
|
|
53
|
+
return { minX: 0, minY: 0, maxX: venue.bounds.width, maxY: venue.bounds.height };
|
|
54
|
+
}
|
|
55
|
+
const boxes = venue.sections.map(sectionAABB);
|
|
56
|
+
return {
|
|
57
|
+
minX: Math.min(...boxes.map((b) => b.minX)),
|
|
58
|
+
minY: Math.min(...boxes.map((b) => b.minY)),
|
|
59
|
+
maxX: Math.max(...boxes.map((b) => b.maxX)),
|
|
60
|
+
maxY: Math.max(...boxes.map((b) => b.maxY))
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function pointInPolygon(point, polygon) {
|
|
64
|
+
if (polygon.length < 3) return false;
|
|
65
|
+
let inside = false;
|
|
66
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
67
|
+
const xi = polygon[i].x, yi = polygon[i].y;
|
|
68
|
+
const xj = polygon[j].x, yj = polygon[j].y;
|
|
69
|
+
if (yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi) {
|
|
70
|
+
inside = !inside;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return inside;
|
|
74
|
+
}
|
|
75
|
+
function clampToPolygon(point, polygon, margin = 5) {
|
|
76
|
+
if (polygon.length < 3 || pointInPolygon(point, polygon)) return point;
|
|
77
|
+
let bestX = point.x, bestY = point.y, bestDist = Infinity;
|
|
78
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
79
|
+
const ax = polygon[j].x, ay = polygon[j].y;
|
|
80
|
+
const bx = polygon[i].x, by = polygon[i].y;
|
|
81
|
+
const dx = bx - ax, dy = by - ay;
|
|
82
|
+
const len2 = dx * dx + dy * dy;
|
|
83
|
+
if (len2 === 0) continue;
|
|
84
|
+
const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));
|
|
85
|
+
const cx = ax + t * dx - margin * dy / Math.sqrt(len2);
|
|
86
|
+
const cy = ay + t * dy + margin * dx / Math.sqrt(len2);
|
|
87
|
+
const d = Math.hypot(point.x - cx, point.y - cy);
|
|
88
|
+
if (d < bestDist) {
|
|
89
|
+
bestDist = d;
|
|
90
|
+
bestX = cx;
|
|
91
|
+
bestY = cy;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!pointInPolygon({ x: bestX, y: bestY }, polygon)) {
|
|
95
|
+
bestDist = Infinity;
|
|
96
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
97
|
+
const ax = polygon[j].x, ay = polygon[j].y;
|
|
98
|
+
const bx = polygon[i].x, by = polygon[i].y;
|
|
99
|
+
const dx = bx - ax, dy = by - ay;
|
|
100
|
+
const len2 = dx * dx + dy * dy;
|
|
101
|
+
if (len2 === 0) continue;
|
|
102
|
+
const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));
|
|
103
|
+
const cx = ax + t * dx, cy = ay + t * dy;
|
|
104
|
+
const d = Math.hypot(point.x - cx, point.y - cy);
|
|
105
|
+
if (d < bestDist) {
|
|
106
|
+
bestDist = d;
|
|
107
|
+
bestX = cx;
|
|
108
|
+
bestY = cy;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { x: bestX, y: bestY };
|
|
113
|
+
}
|
|
114
|
+
var _nextId = 1;
|
|
115
|
+
function generateId(prefix = "") {
|
|
116
|
+
return `${prefix}${prefix ? "-" : ""}${Date.now().toString(36)}-${(_nextId++).toString(36)}`;
|
|
117
|
+
}
|
|
118
|
+
var SpatialIndex = class {
|
|
119
|
+
tree = new RBush__default.default();
|
|
120
|
+
items = [];
|
|
121
|
+
buildFromSections(sections) {
|
|
122
|
+
this.items = [];
|
|
123
|
+
for (const section of sections) {
|
|
124
|
+
const box = sectionAABB(section);
|
|
125
|
+
this.items.push({
|
|
126
|
+
...box,
|
|
127
|
+
type: "section",
|
|
128
|
+
sectionId: section.id
|
|
129
|
+
});
|
|
130
|
+
for (const row of section.rows) {
|
|
131
|
+
for (const seat of row.seats) {
|
|
132
|
+
const wp = seatWorldPosition(section, seat);
|
|
133
|
+
const r = 8;
|
|
134
|
+
this.items.push({
|
|
135
|
+
minX: wp.x - r,
|
|
136
|
+
minY: wp.y - r,
|
|
137
|
+
maxX: wp.x + r,
|
|
138
|
+
maxY: wp.y + r,
|
|
139
|
+
type: "seat",
|
|
140
|
+
sectionId: section.id,
|
|
141
|
+
seatId: seat.id
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.tree.clear();
|
|
147
|
+
this.tree.load(this.items);
|
|
148
|
+
}
|
|
149
|
+
queryViewport(viewport) {
|
|
150
|
+
return this.tree.search(viewport);
|
|
151
|
+
}
|
|
152
|
+
queryPoint(point, radius = 8) {
|
|
153
|
+
return this.tree.search({
|
|
154
|
+
minX: point.x - radius,
|
|
155
|
+
minY: point.y - radius,
|
|
156
|
+
maxX: point.x + radius,
|
|
157
|
+
maxY: point.y + radius
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
queryRect(rect) {
|
|
161
|
+
return this.tree.search(rect);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/rendering/Viewport.ts
|
|
166
|
+
var MIN_ZOOM = 0.05;
|
|
167
|
+
var MAX_ZOOM = 4;
|
|
168
|
+
var Viewport = class {
|
|
169
|
+
x = 0;
|
|
170
|
+
y = 0;
|
|
171
|
+
zoom = 1;
|
|
172
|
+
screenWidth = 0;
|
|
173
|
+
screenHeight = 0;
|
|
174
|
+
listeners = /* @__PURE__ */ new Set();
|
|
175
|
+
setScreenSize(width, height) {
|
|
176
|
+
this.screenWidth = width;
|
|
177
|
+
this.screenHeight = height;
|
|
178
|
+
this.notify();
|
|
179
|
+
}
|
|
180
|
+
pan(dx, dy) {
|
|
181
|
+
this.x += dx / this.zoom;
|
|
182
|
+
this.y += dy / this.zoom;
|
|
183
|
+
this.notify();
|
|
184
|
+
}
|
|
185
|
+
zoomAt(screenPoint, factor) {
|
|
186
|
+
const newZoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, this.zoom * factor));
|
|
187
|
+
const wx = screenPoint.x / this.zoom - this.x;
|
|
188
|
+
const wy = screenPoint.y / this.zoom - this.y;
|
|
189
|
+
this.x = screenPoint.x / newZoom - wx;
|
|
190
|
+
this.y = screenPoint.y / newZoom - wy;
|
|
191
|
+
this.zoom = newZoom;
|
|
192
|
+
this.notify();
|
|
193
|
+
}
|
|
194
|
+
setZoom(zoom) {
|
|
195
|
+
this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
196
|
+
this.notify();
|
|
197
|
+
}
|
|
198
|
+
fitBounds(aabb, padding = 40) {
|
|
199
|
+
const contentW = aabb.maxX - aabb.minX;
|
|
200
|
+
const contentH = aabb.maxY - aabb.minY;
|
|
201
|
+
if (contentW <= 0 || contentH <= 0) return;
|
|
202
|
+
const scaleX = (this.screenWidth - padding * 2) / contentW;
|
|
203
|
+
const scaleY = (this.screenHeight - padding * 2) / contentH;
|
|
204
|
+
this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.min(scaleX, scaleY)));
|
|
205
|
+
this.x = -(aabb.minX + contentW / 2) + this.screenWidth / (2 * this.zoom);
|
|
206
|
+
this.y = -(aabb.minY + contentH / 2) + this.screenHeight / (2 * this.zoom);
|
|
207
|
+
this.notify();
|
|
208
|
+
}
|
|
209
|
+
screenToWorld(screenX, screenY) {
|
|
210
|
+
return {
|
|
211
|
+
x: screenX / this.zoom - this.x,
|
|
212
|
+
y: screenY / this.zoom - this.y
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
worldToScreen(worldX, worldY) {
|
|
216
|
+
return {
|
|
217
|
+
x: (worldX + this.x) * this.zoom,
|
|
218
|
+
y: (worldY + this.y) * this.zoom
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
getVisibleAABB() {
|
|
222
|
+
const topLeft = this.screenToWorld(0, 0);
|
|
223
|
+
const bottomRight = this.screenToWorld(this.screenWidth, this.screenHeight);
|
|
224
|
+
return {
|
|
225
|
+
minX: topLeft.x,
|
|
226
|
+
minY: topLeft.y,
|
|
227
|
+
maxX: bottomRight.x,
|
|
228
|
+
maxY: bottomRight.y
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
getState() {
|
|
232
|
+
return { x: this.x, y: this.y, zoom: this.zoom };
|
|
233
|
+
}
|
|
234
|
+
subscribe(listener) {
|
|
235
|
+
this.listeners.add(listener);
|
|
236
|
+
return () => this.listeners.delete(listener);
|
|
237
|
+
}
|
|
238
|
+
notify() {
|
|
239
|
+
for (const listener of this.listeners) {
|
|
240
|
+
listener();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/rendering/LODLevel.ts
|
|
246
|
+
var LODLevel = /* @__PURE__ */ ((LODLevel2) => {
|
|
247
|
+
LODLevel2["Overview"] = "overview";
|
|
248
|
+
LODLevel2["Section"] = "section";
|
|
249
|
+
LODLevel2["Detail"] = "detail";
|
|
250
|
+
return LODLevel2;
|
|
251
|
+
})(LODLevel || {});
|
|
252
|
+
var SECTION_THRESHOLD = 0.3;
|
|
253
|
+
var DETAIL_THRESHOLD = 0.7;
|
|
254
|
+
function getLODLevel(zoom) {
|
|
255
|
+
if (zoom < SECTION_THRESHOLD) return "overview" /* Overview */;
|
|
256
|
+
if (zoom < DETAIL_THRESHOLD) return "section" /* Section */;
|
|
257
|
+
return "detail" /* Detail */;
|
|
258
|
+
}
|
|
259
|
+
var STATUS_COLORS = {
|
|
260
|
+
available: 5025616,
|
|
261
|
+
held: 16750592,
|
|
262
|
+
sold: 10395294,
|
|
263
|
+
blocked: 16007990,
|
|
264
|
+
selected: 2201331,
|
|
265
|
+
hovered: 6600182
|
|
266
|
+
};
|
|
267
|
+
function createSeatTextures(renderer, radius = 7, categoryColor, textureResolution) {
|
|
268
|
+
const result = {};
|
|
269
|
+
const diameter = (radius + 4) * 2;
|
|
270
|
+
const resolution = textureResolution ?? 4 * (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
271
|
+
for (const [status, color] of Object.entries(STATUS_COLORS)) {
|
|
272
|
+
const g = new pixi_js.Graphics();
|
|
273
|
+
const fillColor = status === "available" && categoryColor != null ? categoryColor : color;
|
|
274
|
+
g.circle(radius + 4, radius + 4, radius);
|
|
275
|
+
g.fill({ color: fillColor });
|
|
276
|
+
if (status === "selected") {
|
|
277
|
+
g.circle(radius + 4, radius + 4, radius + 2);
|
|
278
|
+
g.stroke({ color: 16777215, width: 2 });
|
|
279
|
+
}
|
|
280
|
+
const texture = pixi_js.RenderTexture.create({ width: diameter, height: diameter, resolution });
|
|
281
|
+
renderer.render({ container: g, target: texture });
|
|
282
|
+
g.destroy();
|
|
283
|
+
result[status] = texture;
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
function destroySeatTextures(textures) {
|
|
288
|
+
for (const tex of Object.values(textures)) {
|
|
289
|
+
tex.destroy(true);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/rendering/CategoryTextureCache.ts
|
|
294
|
+
var CategoryTextureCache = class {
|
|
295
|
+
cache = /* @__PURE__ */ new Map();
|
|
296
|
+
defaultTextures = null;
|
|
297
|
+
create(renderer, categories, seatRadius = 7) {
|
|
298
|
+
this.destroy();
|
|
299
|
+
this.defaultTextures = createSeatTextures(renderer, seatRadius);
|
|
300
|
+
for (const cat of categories) {
|
|
301
|
+
const color = parseInt(cat.color.replace("#", ""), 16);
|
|
302
|
+
this.cache.set(cat.id, createSeatTextures(renderer, seatRadius, color));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
get(categoryId) {
|
|
306
|
+
return this.cache.get(categoryId) ?? this.defaultTextures;
|
|
307
|
+
}
|
|
308
|
+
destroy() {
|
|
309
|
+
for (const textures of this.cache.values()) {
|
|
310
|
+
destroySeatTextures(textures);
|
|
311
|
+
}
|
|
312
|
+
if (this.defaultTextures) {
|
|
313
|
+
destroySeatTextures(this.defaultTextures);
|
|
314
|
+
}
|
|
315
|
+
this.cache.clear();
|
|
316
|
+
this.defaultTextures = null;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/commands/CommandHistory.ts
|
|
321
|
+
var CommandHistory = class {
|
|
322
|
+
undoStack = [];
|
|
323
|
+
redoStack = [];
|
|
324
|
+
listeners = /* @__PURE__ */ new Set();
|
|
325
|
+
execute(command) {
|
|
326
|
+
command.execute();
|
|
327
|
+
this.undoStack.push(command);
|
|
328
|
+
this.redoStack = [];
|
|
329
|
+
this.notify();
|
|
330
|
+
}
|
|
331
|
+
undo() {
|
|
332
|
+
const command = this.undoStack.pop();
|
|
333
|
+
if (!command) return;
|
|
334
|
+
command.undo();
|
|
335
|
+
this.redoStack.push(command);
|
|
336
|
+
this.notify();
|
|
337
|
+
}
|
|
338
|
+
redo() {
|
|
339
|
+
const command = this.redoStack.pop();
|
|
340
|
+
if (!command) return;
|
|
341
|
+
command.execute();
|
|
342
|
+
this.undoStack.push(command);
|
|
343
|
+
this.notify();
|
|
344
|
+
}
|
|
345
|
+
get canUndo() {
|
|
346
|
+
return this.undoStack.length > 0;
|
|
347
|
+
}
|
|
348
|
+
get canRedo() {
|
|
349
|
+
return this.redoStack.length > 0;
|
|
350
|
+
}
|
|
351
|
+
clear() {
|
|
352
|
+
this.undoStack = [];
|
|
353
|
+
this.redoStack = [];
|
|
354
|
+
this.notify();
|
|
355
|
+
}
|
|
356
|
+
subscribe(listener) {
|
|
357
|
+
this.listeners.add(listener);
|
|
358
|
+
return () => this.listeners.delete(listener);
|
|
359
|
+
}
|
|
360
|
+
notify() {
|
|
361
|
+
for (const listener of this.listeners) {
|
|
362
|
+
listener();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/serialization/index.ts
|
|
368
|
+
function serializeVenue(venue) {
|
|
369
|
+
return JSON.stringify(venue, null, 2);
|
|
370
|
+
}
|
|
371
|
+
function deserializeVenue(json) {
|
|
372
|
+
return JSON.parse(json);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
exports.CategoryTextureCache = CategoryTextureCache;
|
|
376
|
+
exports.CommandHistory = CommandHistory;
|
|
377
|
+
exports.LODLevel = LODLevel;
|
|
378
|
+
exports.SpatialIndex = SpatialIndex;
|
|
379
|
+
exports.Viewport = Viewport;
|
|
380
|
+
exports.clampToPolygon = clampToPolygon;
|
|
381
|
+
exports.createSeatTextures = createSeatTextures;
|
|
382
|
+
exports.deserializeVenue = deserializeVenue;
|
|
383
|
+
exports.destroySeatTextures = destroySeatTextures;
|
|
384
|
+
exports.generateId = generateId;
|
|
385
|
+
exports.getLODLevel = getLODLevel;
|
|
386
|
+
exports.pointInPolygon = pointInPolygon;
|
|
387
|
+
exports.seatWorldPosition = seatWorldPosition;
|
|
388
|
+
exports.sectionAABB = sectionAABB;
|
|
389
|
+
exports.serializeVenue = serializeVenue;
|
|
390
|
+
exports.venueAABB = venueAABB;
|
|
391
|
+
//# sourceMappingURL=index.cjs.map
|
|
392
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/models/helpers.ts","../src/spatial/SpatialIndex.ts","../src/rendering/Viewport.ts","../src/rendering/LODLevel.ts","../src/rendering/SpriteAtlas.ts","../src/rendering/CategoryTextureCache.ts","../src/commands/CommandHistory.ts","../src/serialization/index.ts"],"names":["RBush","LODLevel","Graphics","RenderTexture"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { AABB, Section, Seat, Vec2, Venue } from \"./types\";\n\nexport function seatWorldPosition(section: Section, seat: Seat): Vec2 {\n const cos = Math.cos(section.rotation);\n const sin = Math.sin(section.rotation);\n return {\n x: section.position.x + seat.position.x * cos - seat.position.y * sin,\n y: section.position.y + seat.position.x * sin + seat.position.y * cos,\n };\n}\n\nexport function sectionAABB(section: Section): AABB {\n const allPoints: Vec2[] = [];\n\n if (section.outline.length > 0) {\n const cos = Math.cos(section.rotation);\n const sin = Math.sin(section.rotation);\n for (const p of section.outline) {\n allPoints.push({\n x: section.position.x + p.x * cos - p.y * sin,\n y: section.position.y + p.x * sin + p.y * cos,\n });\n }\n }\n\n const allSeats = section.rows.flatMap((r) => r.seats);\n for (const seat of allSeats) {\n allPoints.push(seatWorldPosition(section, seat));\n }\n\n if (allPoints.length === 0) {\n return {\n minX: section.position.x,\n minY: section.position.y,\n maxX: section.position.x,\n maxY: section.position.y,\n };\n }\n\n const pad = 10;\n return {\n minX: Math.min(...allPoints.map((p) => p.x)) - pad,\n minY: Math.min(...allPoints.map((p) => p.y)) - pad,\n maxX: Math.max(...allPoints.map((p) => p.x)) + pad,\n maxY: Math.max(...allPoints.map((p) => p.y)) + pad,\n };\n}\n\nexport function venueAABB(venue: Venue): AABB {\n if (venue.sections.length === 0) {\n return { minX: 0, minY: 0, maxX: venue.bounds.width, maxY: venue.bounds.height };\n }\n const boxes = venue.sections.map(sectionAABB);\n return {\n minX: Math.min(...boxes.map((b) => b.minX)),\n minY: Math.min(...boxes.map((b) => b.minY)),\n maxX: Math.max(...boxes.map((b) => b.maxX)),\n maxY: Math.max(...boxes.map((b) => b.maxY)),\n };\n}\n\n/** Ray-casting point-in-polygon test. Works with any simple polygon. */\nexport function pointInPolygon(point: Vec2, polygon: Vec2[]): boolean {\n if (polygon.length < 3) return false;\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const xi = polygon[i].x, yi = polygon[i].y;\n const xj = polygon[j].x, yj = polygon[j].y;\n if ((yi > point.y) !== (yj > point.y) &&\n point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi) {\n inside = !inside;\n }\n }\n return inside;\n}\n\n/** Clamp a point to the nearest position inside a polygon (with margin). */\nexport function clampToPolygon(point: Vec2, polygon: Vec2[], margin = 5): Vec2 {\n if (polygon.length < 3 || pointInPolygon(point, polygon)) return point;\n\n // Find the closest point on any polygon edge\n let bestX = point.x, bestY = point.y, bestDist = Infinity;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const ax = polygon[j].x, ay = polygon[j].y;\n const bx = polygon[i].x, by = polygon[i].y;\n const dx = bx - ax, dy = by - ay;\n const len2 = dx * dx + dy * dy;\n if (len2 === 0) continue;\n const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));\n const cx = ax + t * dx - margin * dy / Math.sqrt(len2);\n const cy = ay + t * dy + margin * dx / Math.sqrt(len2);\n const d = Math.hypot(point.x - cx, point.y - cy);\n if (d < bestDist) { bestDist = d; bestX = cx; bestY = cy; }\n }\n\n // Verify the clamped point is inside; if not just project onto edge without margin\n if (!pointInPolygon({ x: bestX, y: bestY }, polygon)) {\n bestDist = Infinity;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const ax = polygon[j].x, ay = polygon[j].y;\n const bx = polygon[i].x, by = polygon[i].y;\n const dx = bx - ax, dy = by - ay;\n const len2 = dx * dx + dy * dy;\n if (len2 === 0) continue;\n const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));\n const cx = ax + t * dx, cy = ay + t * dy;\n const d = Math.hypot(point.x - cx, point.y - cy);\n if (d < bestDist) { bestDist = d; bestX = cx; bestY = cy; }\n }\n }\n\n return { x: bestX, y: bestY };\n}\n\nlet _nextId = 1;\nexport function generateId(prefix = \"\"): string {\n return `${prefix}${prefix ? \"-\" : \"\"}${Date.now().toString(36)}-${(_nextId++).toString(36)}`;\n}\n","import RBush from \"rbush\";\nimport type { AABB, Section, Vec2 } from \"../models/types\";\nimport { seatWorldPosition, sectionAABB } from \"../models/helpers\";\n\nexport interface SpatialItem extends AABB {\n type: \"section\" | \"seat\";\n sectionId: string;\n seatId?: string;\n}\n\nexport class SpatialIndex {\n private tree = new RBush<SpatialItem>();\n private items: SpatialItem[] = [];\n\n buildFromSections(sections: Section[]): void {\n this.items = [];\n\n for (const section of sections) {\n const box = sectionAABB(section);\n this.items.push({\n ...box,\n type: \"section\",\n sectionId: section.id,\n });\n\n for (const row of section.rows) {\n for (const seat of row.seats) {\n const wp = seatWorldPosition(section, seat);\n const r = 8;\n this.items.push({\n minX: wp.x - r,\n minY: wp.y - r,\n maxX: wp.x + r,\n maxY: wp.y + r,\n type: \"seat\",\n sectionId: section.id,\n seatId: seat.id,\n });\n }\n }\n }\n\n this.tree.clear();\n this.tree.load(this.items);\n }\n\n queryViewport(viewport: AABB): SpatialItem[] {\n return this.tree.search(viewport);\n }\n\n queryPoint(point: Vec2, radius = 8): SpatialItem[] {\n return this.tree.search({\n minX: point.x - radius,\n minY: point.y - radius,\n maxX: point.x + radius,\n maxY: point.y + radius,\n });\n }\n\n queryRect(rect: AABB): SpatialItem[] {\n return this.tree.search(rect);\n }\n}\n","import type { AABB, Vec2 } from \"../models/types\";\n\nexport interface ViewportState {\n x: number;\n y: number;\n zoom: number;\n}\n\nconst MIN_ZOOM = 0.05;\nconst MAX_ZOOM = 4;\n\nexport class Viewport {\n x = 0;\n y = 0;\n zoom = 1;\n screenWidth = 0;\n screenHeight = 0;\n\n private listeners = new Set<() => void>();\n\n setScreenSize(width: number, height: number): void {\n this.screenWidth = width;\n this.screenHeight = height;\n this.notify();\n }\n\n pan(dx: number, dy: number): void {\n this.x += dx / this.zoom;\n this.y += dy / this.zoom;\n this.notify();\n }\n\n zoomAt(screenPoint: Vec2, factor: number): void {\n const newZoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, this.zoom * factor));\n\n // 1) World coordinate currently under the cursor\n const wx = screenPoint.x / this.zoom - this.x;\n const wy = screenPoint.y / this.zoom - this.y;\n\n // 2) Solve for the offset that keeps that world point at the same screen position\n // screenPoint = (world + offset) * newZoom → offset = screenPoint / newZoom - world\n this.x = screenPoint.x / newZoom - wx;\n this.y = screenPoint.y / newZoom - wy;\n this.zoom = newZoom;\n this.notify();\n }\n\n setZoom(zoom: number): void {\n this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));\n this.notify();\n }\n\n fitBounds(aabb: AABB, padding = 40): void {\n const contentW = aabb.maxX - aabb.minX;\n const contentH = aabb.maxY - aabb.minY;\n if (contentW <= 0 || contentH <= 0) return;\n\n const scaleX = (this.screenWidth - padding * 2) / contentW;\n const scaleY = (this.screenHeight - padding * 2) / contentH;\n this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.min(scaleX, scaleY)));\n\n this.x = -(aabb.minX + contentW / 2) + this.screenWidth / (2 * this.zoom);\n this.y = -(aabb.minY + contentH / 2) + this.screenHeight / (2 * this.zoom);\n this.notify();\n }\n\n screenToWorld(screenX: number, screenY: number): Vec2 {\n return {\n x: screenX / this.zoom - this.x,\n y: screenY / this.zoom - this.y,\n };\n }\n\n worldToScreen(worldX: number, worldY: number): Vec2 {\n return {\n x: (worldX + this.x) * this.zoom,\n y: (worldY + this.y) * this.zoom,\n };\n }\n\n getVisibleAABB(): AABB {\n const topLeft = this.screenToWorld(0, 0);\n const bottomRight = this.screenToWorld(this.screenWidth, this.screenHeight);\n return {\n minX: topLeft.x,\n minY: topLeft.y,\n maxX: bottomRight.x,\n maxY: bottomRight.y,\n };\n }\n\n getState(): ViewportState {\n return { x: this.x, y: this.y, zoom: this.zoom };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n","export enum LODLevel {\n Overview = \"overview\",\n Section = \"section\",\n Detail = \"detail\",\n}\n\nconst SECTION_THRESHOLD = 0.3;\nconst DETAIL_THRESHOLD = 0.7;\n\nexport function getLODLevel(zoom: number): LODLevel {\n if (zoom < SECTION_THRESHOLD) return LODLevel.Overview;\n if (zoom < DETAIL_THRESHOLD) return LODLevel.Section;\n return LODLevel.Detail;\n}\n","import { Graphics, RenderTexture, type Renderer } from \"pixi.js\";\n\nexport interface SeatTextureSet {\n available: RenderTexture;\n held: RenderTexture;\n sold: RenderTexture;\n blocked: RenderTexture;\n selected: RenderTexture;\n hovered: RenderTexture;\n}\n\nconst STATUS_COLORS: Record<string, number> = {\n available: 0x4caf50,\n held: 0xff9800,\n sold: 0x9e9e9e,\n blocked: 0xf44336,\n selected: 0x2196f3,\n hovered: 0x64b5f6,\n};\n\nexport function createSeatTextures(\n renderer: Renderer,\n radius = 7,\n categoryColor?: number,\n textureResolution?: number,\n): SeatTextureSet {\n const result: Partial<SeatTextureSet> = {};\n const diameter = (radius + 4) * 2;\n const resolution = textureResolution ?? (4 * (typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1));\n\n for (const [status, color] of Object.entries(STATUS_COLORS)) {\n const g = new Graphics();\n const fillColor = status === \"available\" && categoryColor != null ? categoryColor : color;\n g.circle(radius + 4, radius + 4, radius);\n g.fill({ color: fillColor });\n\n if (status === \"selected\") {\n g.circle(radius + 4, radius + 4, radius + 2);\n g.stroke({ color: 0xffffff, width: 2 });\n }\n\n const texture = RenderTexture.create({ width: diameter, height: diameter, resolution });\n renderer.render({ container: g, target: texture });\n g.destroy();\n\n result[status as keyof SeatTextureSet] = texture;\n }\n\n return result as SeatTextureSet;\n}\n\nexport function destroySeatTextures(textures: SeatTextureSet): void {\n for (const tex of Object.values(textures)) {\n (tex as RenderTexture).destroy(true);\n }\n}\n","import type { Renderer } from \"pixi.js\";\nimport {\n createSeatTextures,\n destroySeatTextures,\n type SeatTextureSet,\n} from \"./SpriteAtlas\";\n\nexport class CategoryTextureCache {\n private cache = new Map<string, SeatTextureSet>();\n private defaultTextures: SeatTextureSet | null = null;\n\n create(renderer: Renderer, categories: { id: string; color: string }[], seatRadius = 7): void {\n this.destroy();\n this.defaultTextures = createSeatTextures(renderer, seatRadius);\n\n for (const cat of categories) {\n const color = parseInt(cat.color.replace(\"#\", \"\"), 16);\n this.cache.set(cat.id, createSeatTextures(renderer, seatRadius, color));\n }\n }\n\n get(categoryId: string): SeatTextureSet {\n return this.cache.get(categoryId) ?? this.defaultTextures!;\n }\n\n destroy(): void {\n for (const textures of this.cache.values()) {\n destroySeatTextures(textures);\n }\n if (this.defaultTextures) {\n destroySeatTextures(this.defaultTextures);\n }\n this.cache.clear();\n this.defaultTextures = null;\n }\n}\n","export interface Command {\n execute(): void;\n undo(): void;\n description: string;\n}\n\nexport class CommandHistory {\n private undoStack: Command[] = [];\n private redoStack: Command[] = [];\n private listeners = new Set<() => void>();\n\n execute(command: Command): void {\n command.execute();\n this.undoStack.push(command);\n this.redoStack = [];\n this.notify();\n }\n\n undo(): void {\n const command = this.undoStack.pop();\n if (!command) return;\n command.undo();\n this.redoStack.push(command);\n this.notify();\n }\n\n redo(): void {\n const command = this.redoStack.pop();\n if (!command) return;\n command.execute();\n this.undoStack.push(command);\n this.notify();\n }\n\n get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n clear(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.notify();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n","import type { Venue } from \"../models/types\";\n\nexport function serializeVenue(venue: Venue): string {\n return JSON.stringify(venue, null, 2);\n}\n\nexport function deserializeVenue(json: string): Venue {\n return JSON.parse(json) as Venue;\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { RenderTexture, Renderer } from 'pixi.js';
|
|
2
|
+
|
|
3
|
+
interface Vec2 {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
interface Bounds {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
interface AABB {
|
|
12
|
+
minX: number;
|
|
13
|
+
minY: number;
|
|
14
|
+
maxX: number;
|
|
15
|
+
maxY: number;
|
|
16
|
+
}
|
|
17
|
+
type SeatStatus = "available" | "held" | "sold" | "blocked";
|
|
18
|
+
interface PricingCategory {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
color: string;
|
|
22
|
+
}
|
|
23
|
+
interface Seat {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
position: Vec2;
|
|
27
|
+
status: SeatStatus;
|
|
28
|
+
categoryId: string;
|
|
29
|
+
}
|
|
30
|
+
interface Row {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
seats: Seat[];
|
|
34
|
+
}
|
|
35
|
+
interface Section {
|
|
36
|
+
id: string;
|
|
37
|
+
label: string;
|
|
38
|
+
position: Vec2;
|
|
39
|
+
rotation: number;
|
|
40
|
+
categoryId: string;
|
|
41
|
+
rows: Row[];
|
|
42
|
+
outline: Vec2[];
|
|
43
|
+
}
|
|
44
|
+
interface GeneralAdmissionArea {
|
|
45
|
+
id: string;
|
|
46
|
+
label: string;
|
|
47
|
+
shape: Vec2[];
|
|
48
|
+
capacity: number;
|
|
49
|
+
categoryId: string;
|
|
50
|
+
}
|
|
51
|
+
interface Table {
|
|
52
|
+
id: string;
|
|
53
|
+
label: string;
|
|
54
|
+
position: Vec2;
|
|
55
|
+
shape: "round" | "rectangular";
|
|
56
|
+
seats: Seat[];
|
|
57
|
+
categoryId: string;
|
|
58
|
+
}
|
|
59
|
+
interface Venue {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
bounds: Bounds;
|
|
63
|
+
backgroundImage?: string;
|
|
64
|
+
backgroundImageOpacity?: number;
|
|
65
|
+
sections: Section[];
|
|
66
|
+
gaAreas: GeneralAdmissionArea[];
|
|
67
|
+
tables: Table[];
|
|
68
|
+
categories: PricingCategory[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare function seatWorldPosition(section: Section, seat: Seat): Vec2;
|
|
72
|
+
declare function sectionAABB(section: Section): AABB;
|
|
73
|
+
declare function venueAABB(venue: Venue): AABB;
|
|
74
|
+
/** Ray-casting point-in-polygon test. Works with any simple polygon. */
|
|
75
|
+
declare function pointInPolygon(point: Vec2, polygon: Vec2[]): boolean;
|
|
76
|
+
/** Clamp a point to the nearest position inside a polygon (with margin). */
|
|
77
|
+
declare function clampToPolygon(point: Vec2, polygon: Vec2[], margin?: number): Vec2;
|
|
78
|
+
declare function generateId(prefix?: string): string;
|
|
79
|
+
|
|
80
|
+
interface SpatialItem extends AABB {
|
|
81
|
+
type: "section" | "seat";
|
|
82
|
+
sectionId: string;
|
|
83
|
+
seatId?: string;
|
|
84
|
+
}
|
|
85
|
+
declare class SpatialIndex {
|
|
86
|
+
private tree;
|
|
87
|
+
private items;
|
|
88
|
+
buildFromSections(sections: Section[]): void;
|
|
89
|
+
queryViewport(viewport: AABB): SpatialItem[];
|
|
90
|
+
queryPoint(point: Vec2, radius?: number): SpatialItem[];
|
|
91
|
+
queryRect(rect: AABB): SpatialItem[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface ViewportState {
|
|
95
|
+
x: number;
|
|
96
|
+
y: number;
|
|
97
|
+
zoom: number;
|
|
98
|
+
}
|
|
99
|
+
declare class Viewport {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
zoom: number;
|
|
103
|
+
screenWidth: number;
|
|
104
|
+
screenHeight: number;
|
|
105
|
+
private listeners;
|
|
106
|
+
setScreenSize(width: number, height: number): void;
|
|
107
|
+
pan(dx: number, dy: number): void;
|
|
108
|
+
zoomAt(screenPoint: Vec2, factor: number): void;
|
|
109
|
+
setZoom(zoom: number): void;
|
|
110
|
+
fitBounds(aabb: AABB, padding?: number): void;
|
|
111
|
+
screenToWorld(screenX: number, screenY: number): Vec2;
|
|
112
|
+
worldToScreen(worldX: number, worldY: number): Vec2;
|
|
113
|
+
getVisibleAABB(): AABB;
|
|
114
|
+
getState(): ViewportState;
|
|
115
|
+
subscribe(listener: () => void): () => void;
|
|
116
|
+
private notify;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare enum LODLevel {
|
|
120
|
+
Overview = "overview",
|
|
121
|
+
Section = "section",
|
|
122
|
+
Detail = "detail"
|
|
123
|
+
}
|
|
124
|
+
declare function getLODLevel(zoom: number): LODLevel;
|
|
125
|
+
|
|
126
|
+
interface SeatTextureSet {
|
|
127
|
+
available: RenderTexture;
|
|
128
|
+
held: RenderTexture;
|
|
129
|
+
sold: RenderTexture;
|
|
130
|
+
blocked: RenderTexture;
|
|
131
|
+
selected: RenderTexture;
|
|
132
|
+
hovered: RenderTexture;
|
|
133
|
+
}
|
|
134
|
+
declare function createSeatTextures(renderer: Renderer, radius?: number, categoryColor?: number, textureResolution?: number): SeatTextureSet;
|
|
135
|
+
declare function destroySeatTextures(textures: SeatTextureSet): void;
|
|
136
|
+
|
|
137
|
+
declare class CategoryTextureCache {
|
|
138
|
+
private cache;
|
|
139
|
+
private defaultTextures;
|
|
140
|
+
create(renderer: Renderer, categories: {
|
|
141
|
+
id: string;
|
|
142
|
+
color: string;
|
|
143
|
+
}[], seatRadius?: number): void;
|
|
144
|
+
get(categoryId: string): SeatTextureSet;
|
|
145
|
+
destroy(): void;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface Command {
|
|
149
|
+
execute(): void;
|
|
150
|
+
undo(): void;
|
|
151
|
+
description: string;
|
|
152
|
+
}
|
|
153
|
+
declare class CommandHistory {
|
|
154
|
+
private undoStack;
|
|
155
|
+
private redoStack;
|
|
156
|
+
private listeners;
|
|
157
|
+
execute(command: Command): void;
|
|
158
|
+
undo(): void;
|
|
159
|
+
redo(): void;
|
|
160
|
+
get canUndo(): boolean;
|
|
161
|
+
get canRedo(): boolean;
|
|
162
|
+
clear(): void;
|
|
163
|
+
subscribe(listener: () => void): () => void;
|
|
164
|
+
private notify;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
declare function serializeVenue(venue: Venue): string;
|
|
168
|
+
declare function deserializeVenue(json: string): Venue;
|
|
169
|
+
|
|
170
|
+
export { type AABB, type Bounds, CategoryTextureCache, type Command, CommandHistory, type GeneralAdmissionArea, LODLevel, type PricingCategory, type Row, type Seat, type SeatStatus, type SeatTextureSet, type Section, SpatialIndex, type SpatialItem, type Table, type Vec2, type Venue, Viewport, type ViewportState, clampToPolygon, createSeatTextures, deserializeVenue, destroySeatTextures, generateId, getLODLevel, pointInPolygon, seatWorldPosition, sectionAABB, serializeVenue, venueAABB };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { RenderTexture, Renderer } from 'pixi.js';
|
|
2
|
+
|
|
3
|
+
interface Vec2 {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
interface Bounds {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
interface AABB {
|
|
12
|
+
minX: number;
|
|
13
|
+
minY: number;
|
|
14
|
+
maxX: number;
|
|
15
|
+
maxY: number;
|
|
16
|
+
}
|
|
17
|
+
type SeatStatus = "available" | "held" | "sold" | "blocked";
|
|
18
|
+
interface PricingCategory {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
color: string;
|
|
22
|
+
}
|
|
23
|
+
interface Seat {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
position: Vec2;
|
|
27
|
+
status: SeatStatus;
|
|
28
|
+
categoryId: string;
|
|
29
|
+
}
|
|
30
|
+
interface Row {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
seats: Seat[];
|
|
34
|
+
}
|
|
35
|
+
interface Section {
|
|
36
|
+
id: string;
|
|
37
|
+
label: string;
|
|
38
|
+
position: Vec2;
|
|
39
|
+
rotation: number;
|
|
40
|
+
categoryId: string;
|
|
41
|
+
rows: Row[];
|
|
42
|
+
outline: Vec2[];
|
|
43
|
+
}
|
|
44
|
+
interface GeneralAdmissionArea {
|
|
45
|
+
id: string;
|
|
46
|
+
label: string;
|
|
47
|
+
shape: Vec2[];
|
|
48
|
+
capacity: number;
|
|
49
|
+
categoryId: string;
|
|
50
|
+
}
|
|
51
|
+
interface Table {
|
|
52
|
+
id: string;
|
|
53
|
+
label: string;
|
|
54
|
+
position: Vec2;
|
|
55
|
+
shape: "round" | "rectangular";
|
|
56
|
+
seats: Seat[];
|
|
57
|
+
categoryId: string;
|
|
58
|
+
}
|
|
59
|
+
interface Venue {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
bounds: Bounds;
|
|
63
|
+
backgroundImage?: string;
|
|
64
|
+
backgroundImageOpacity?: number;
|
|
65
|
+
sections: Section[];
|
|
66
|
+
gaAreas: GeneralAdmissionArea[];
|
|
67
|
+
tables: Table[];
|
|
68
|
+
categories: PricingCategory[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare function seatWorldPosition(section: Section, seat: Seat): Vec2;
|
|
72
|
+
declare function sectionAABB(section: Section): AABB;
|
|
73
|
+
declare function venueAABB(venue: Venue): AABB;
|
|
74
|
+
/** Ray-casting point-in-polygon test. Works with any simple polygon. */
|
|
75
|
+
declare function pointInPolygon(point: Vec2, polygon: Vec2[]): boolean;
|
|
76
|
+
/** Clamp a point to the nearest position inside a polygon (with margin). */
|
|
77
|
+
declare function clampToPolygon(point: Vec2, polygon: Vec2[], margin?: number): Vec2;
|
|
78
|
+
declare function generateId(prefix?: string): string;
|
|
79
|
+
|
|
80
|
+
interface SpatialItem extends AABB {
|
|
81
|
+
type: "section" | "seat";
|
|
82
|
+
sectionId: string;
|
|
83
|
+
seatId?: string;
|
|
84
|
+
}
|
|
85
|
+
declare class SpatialIndex {
|
|
86
|
+
private tree;
|
|
87
|
+
private items;
|
|
88
|
+
buildFromSections(sections: Section[]): void;
|
|
89
|
+
queryViewport(viewport: AABB): SpatialItem[];
|
|
90
|
+
queryPoint(point: Vec2, radius?: number): SpatialItem[];
|
|
91
|
+
queryRect(rect: AABB): SpatialItem[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface ViewportState {
|
|
95
|
+
x: number;
|
|
96
|
+
y: number;
|
|
97
|
+
zoom: number;
|
|
98
|
+
}
|
|
99
|
+
declare class Viewport {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
zoom: number;
|
|
103
|
+
screenWidth: number;
|
|
104
|
+
screenHeight: number;
|
|
105
|
+
private listeners;
|
|
106
|
+
setScreenSize(width: number, height: number): void;
|
|
107
|
+
pan(dx: number, dy: number): void;
|
|
108
|
+
zoomAt(screenPoint: Vec2, factor: number): void;
|
|
109
|
+
setZoom(zoom: number): void;
|
|
110
|
+
fitBounds(aabb: AABB, padding?: number): void;
|
|
111
|
+
screenToWorld(screenX: number, screenY: number): Vec2;
|
|
112
|
+
worldToScreen(worldX: number, worldY: number): Vec2;
|
|
113
|
+
getVisibleAABB(): AABB;
|
|
114
|
+
getState(): ViewportState;
|
|
115
|
+
subscribe(listener: () => void): () => void;
|
|
116
|
+
private notify;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare enum LODLevel {
|
|
120
|
+
Overview = "overview",
|
|
121
|
+
Section = "section",
|
|
122
|
+
Detail = "detail"
|
|
123
|
+
}
|
|
124
|
+
declare function getLODLevel(zoom: number): LODLevel;
|
|
125
|
+
|
|
126
|
+
interface SeatTextureSet {
|
|
127
|
+
available: RenderTexture;
|
|
128
|
+
held: RenderTexture;
|
|
129
|
+
sold: RenderTexture;
|
|
130
|
+
blocked: RenderTexture;
|
|
131
|
+
selected: RenderTexture;
|
|
132
|
+
hovered: RenderTexture;
|
|
133
|
+
}
|
|
134
|
+
declare function createSeatTextures(renderer: Renderer, radius?: number, categoryColor?: number, textureResolution?: number): SeatTextureSet;
|
|
135
|
+
declare function destroySeatTextures(textures: SeatTextureSet): void;
|
|
136
|
+
|
|
137
|
+
declare class CategoryTextureCache {
|
|
138
|
+
private cache;
|
|
139
|
+
private defaultTextures;
|
|
140
|
+
create(renderer: Renderer, categories: {
|
|
141
|
+
id: string;
|
|
142
|
+
color: string;
|
|
143
|
+
}[], seatRadius?: number): void;
|
|
144
|
+
get(categoryId: string): SeatTextureSet;
|
|
145
|
+
destroy(): void;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface Command {
|
|
149
|
+
execute(): void;
|
|
150
|
+
undo(): void;
|
|
151
|
+
description: string;
|
|
152
|
+
}
|
|
153
|
+
declare class CommandHistory {
|
|
154
|
+
private undoStack;
|
|
155
|
+
private redoStack;
|
|
156
|
+
private listeners;
|
|
157
|
+
execute(command: Command): void;
|
|
158
|
+
undo(): void;
|
|
159
|
+
redo(): void;
|
|
160
|
+
get canUndo(): boolean;
|
|
161
|
+
get canRedo(): boolean;
|
|
162
|
+
clear(): void;
|
|
163
|
+
subscribe(listener: () => void): () => void;
|
|
164
|
+
private notify;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
declare function serializeVenue(venue: Venue): string;
|
|
168
|
+
declare function deserializeVenue(json: string): Venue;
|
|
169
|
+
|
|
170
|
+
export { type AABB, type Bounds, CategoryTextureCache, type Command, CommandHistory, type GeneralAdmissionArea, LODLevel, type PricingCategory, type Row, type Seat, type SeatStatus, type SeatTextureSet, type Section, SpatialIndex, type SpatialItem, type Table, type Vec2, type Venue, Viewport, type ViewportState, clampToPolygon, createSeatTextures, deserializeVenue, destroySeatTextures, generateId, getLODLevel, pointInPolygon, seatWorldPosition, sectionAABB, serializeVenue, venueAABB };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import RBush from 'rbush';
|
|
2
|
+
import { Graphics, RenderTexture } from 'pixi.js';
|
|
3
|
+
|
|
4
|
+
// src/models/helpers.ts
|
|
5
|
+
function seatWorldPosition(section, seat) {
|
|
6
|
+
const cos = Math.cos(section.rotation);
|
|
7
|
+
const sin = Math.sin(section.rotation);
|
|
8
|
+
return {
|
|
9
|
+
x: section.position.x + seat.position.x * cos - seat.position.y * sin,
|
|
10
|
+
y: section.position.y + seat.position.x * sin + seat.position.y * cos
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function sectionAABB(section) {
|
|
14
|
+
const allPoints = [];
|
|
15
|
+
if (section.outline.length > 0) {
|
|
16
|
+
const cos = Math.cos(section.rotation);
|
|
17
|
+
const sin = Math.sin(section.rotation);
|
|
18
|
+
for (const p of section.outline) {
|
|
19
|
+
allPoints.push({
|
|
20
|
+
x: section.position.x + p.x * cos - p.y * sin,
|
|
21
|
+
y: section.position.y + p.x * sin + p.y * cos
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const allSeats = section.rows.flatMap((r) => r.seats);
|
|
26
|
+
for (const seat of allSeats) {
|
|
27
|
+
allPoints.push(seatWorldPosition(section, seat));
|
|
28
|
+
}
|
|
29
|
+
if (allPoints.length === 0) {
|
|
30
|
+
return {
|
|
31
|
+
minX: section.position.x,
|
|
32
|
+
minY: section.position.y,
|
|
33
|
+
maxX: section.position.x,
|
|
34
|
+
maxY: section.position.y
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const pad = 10;
|
|
38
|
+
return {
|
|
39
|
+
minX: Math.min(...allPoints.map((p) => p.x)) - pad,
|
|
40
|
+
minY: Math.min(...allPoints.map((p) => p.y)) - pad,
|
|
41
|
+
maxX: Math.max(...allPoints.map((p) => p.x)) + pad,
|
|
42
|
+
maxY: Math.max(...allPoints.map((p) => p.y)) + pad
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function venueAABB(venue) {
|
|
46
|
+
if (venue.sections.length === 0) {
|
|
47
|
+
return { minX: 0, minY: 0, maxX: venue.bounds.width, maxY: venue.bounds.height };
|
|
48
|
+
}
|
|
49
|
+
const boxes = venue.sections.map(sectionAABB);
|
|
50
|
+
return {
|
|
51
|
+
minX: Math.min(...boxes.map((b) => b.minX)),
|
|
52
|
+
minY: Math.min(...boxes.map((b) => b.minY)),
|
|
53
|
+
maxX: Math.max(...boxes.map((b) => b.maxX)),
|
|
54
|
+
maxY: Math.max(...boxes.map((b) => b.maxY))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function pointInPolygon(point, polygon) {
|
|
58
|
+
if (polygon.length < 3) return false;
|
|
59
|
+
let inside = false;
|
|
60
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
61
|
+
const xi = polygon[i].x, yi = polygon[i].y;
|
|
62
|
+
const xj = polygon[j].x, yj = polygon[j].y;
|
|
63
|
+
if (yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi) {
|
|
64
|
+
inside = !inside;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return inside;
|
|
68
|
+
}
|
|
69
|
+
function clampToPolygon(point, polygon, margin = 5) {
|
|
70
|
+
if (polygon.length < 3 || pointInPolygon(point, polygon)) return point;
|
|
71
|
+
let bestX = point.x, bestY = point.y, bestDist = Infinity;
|
|
72
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
73
|
+
const ax = polygon[j].x, ay = polygon[j].y;
|
|
74
|
+
const bx = polygon[i].x, by = polygon[i].y;
|
|
75
|
+
const dx = bx - ax, dy = by - ay;
|
|
76
|
+
const len2 = dx * dx + dy * dy;
|
|
77
|
+
if (len2 === 0) continue;
|
|
78
|
+
const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));
|
|
79
|
+
const cx = ax + t * dx - margin * dy / Math.sqrt(len2);
|
|
80
|
+
const cy = ay + t * dy + margin * dx / Math.sqrt(len2);
|
|
81
|
+
const d = Math.hypot(point.x - cx, point.y - cy);
|
|
82
|
+
if (d < bestDist) {
|
|
83
|
+
bestDist = d;
|
|
84
|
+
bestX = cx;
|
|
85
|
+
bestY = cy;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!pointInPolygon({ x: bestX, y: bestY }, polygon)) {
|
|
89
|
+
bestDist = Infinity;
|
|
90
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
91
|
+
const ax = polygon[j].x, ay = polygon[j].y;
|
|
92
|
+
const bx = polygon[i].x, by = polygon[i].y;
|
|
93
|
+
const dx = bx - ax, dy = by - ay;
|
|
94
|
+
const len2 = dx * dx + dy * dy;
|
|
95
|
+
if (len2 === 0) continue;
|
|
96
|
+
const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));
|
|
97
|
+
const cx = ax + t * dx, cy = ay + t * dy;
|
|
98
|
+
const d = Math.hypot(point.x - cx, point.y - cy);
|
|
99
|
+
if (d < bestDist) {
|
|
100
|
+
bestDist = d;
|
|
101
|
+
bestX = cx;
|
|
102
|
+
bestY = cy;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { x: bestX, y: bestY };
|
|
107
|
+
}
|
|
108
|
+
var _nextId = 1;
|
|
109
|
+
function generateId(prefix = "") {
|
|
110
|
+
return `${prefix}${prefix ? "-" : ""}${Date.now().toString(36)}-${(_nextId++).toString(36)}`;
|
|
111
|
+
}
|
|
112
|
+
var SpatialIndex = class {
|
|
113
|
+
tree = new RBush();
|
|
114
|
+
items = [];
|
|
115
|
+
buildFromSections(sections) {
|
|
116
|
+
this.items = [];
|
|
117
|
+
for (const section of sections) {
|
|
118
|
+
const box = sectionAABB(section);
|
|
119
|
+
this.items.push({
|
|
120
|
+
...box,
|
|
121
|
+
type: "section",
|
|
122
|
+
sectionId: section.id
|
|
123
|
+
});
|
|
124
|
+
for (const row of section.rows) {
|
|
125
|
+
for (const seat of row.seats) {
|
|
126
|
+
const wp = seatWorldPosition(section, seat);
|
|
127
|
+
const r = 8;
|
|
128
|
+
this.items.push({
|
|
129
|
+
minX: wp.x - r,
|
|
130
|
+
minY: wp.y - r,
|
|
131
|
+
maxX: wp.x + r,
|
|
132
|
+
maxY: wp.y + r,
|
|
133
|
+
type: "seat",
|
|
134
|
+
sectionId: section.id,
|
|
135
|
+
seatId: seat.id
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
this.tree.clear();
|
|
141
|
+
this.tree.load(this.items);
|
|
142
|
+
}
|
|
143
|
+
queryViewport(viewport) {
|
|
144
|
+
return this.tree.search(viewport);
|
|
145
|
+
}
|
|
146
|
+
queryPoint(point, radius = 8) {
|
|
147
|
+
return this.tree.search({
|
|
148
|
+
minX: point.x - radius,
|
|
149
|
+
minY: point.y - radius,
|
|
150
|
+
maxX: point.x + radius,
|
|
151
|
+
maxY: point.y + radius
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
queryRect(rect) {
|
|
155
|
+
return this.tree.search(rect);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/rendering/Viewport.ts
|
|
160
|
+
var MIN_ZOOM = 0.05;
|
|
161
|
+
var MAX_ZOOM = 4;
|
|
162
|
+
var Viewport = class {
|
|
163
|
+
x = 0;
|
|
164
|
+
y = 0;
|
|
165
|
+
zoom = 1;
|
|
166
|
+
screenWidth = 0;
|
|
167
|
+
screenHeight = 0;
|
|
168
|
+
listeners = /* @__PURE__ */ new Set();
|
|
169
|
+
setScreenSize(width, height) {
|
|
170
|
+
this.screenWidth = width;
|
|
171
|
+
this.screenHeight = height;
|
|
172
|
+
this.notify();
|
|
173
|
+
}
|
|
174
|
+
pan(dx, dy) {
|
|
175
|
+
this.x += dx / this.zoom;
|
|
176
|
+
this.y += dy / this.zoom;
|
|
177
|
+
this.notify();
|
|
178
|
+
}
|
|
179
|
+
zoomAt(screenPoint, factor) {
|
|
180
|
+
const newZoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, this.zoom * factor));
|
|
181
|
+
const wx = screenPoint.x / this.zoom - this.x;
|
|
182
|
+
const wy = screenPoint.y / this.zoom - this.y;
|
|
183
|
+
this.x = screenPoint.x / newZoom - wx;
|
|
184
|
+
this.y = screenPoint.y / newZoom - wy;
|
|
185
|
+
this.zoom = newZoom;
|
|
186
|
+
this.notify();
|
|
187
|
+
}
|
|
188
|
+
setZoom(zoom) {
|
|
189
|
+
this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
190
|
+
this.notify();
|
|
191
|
+
}
|
|
192
|
+
fitBounds(aabb, padding = 40) {
|
|
193
|
+
const contentW = aabb.maxX - aabb.minX;
|
|
194
|
+
const contentH = aabb.maxY - aabb.minY;
|
|
195
|
+
if (contentW <= 0 || contentH <= 0) return;
|
|
196
|
+
const scaleX = (this.screenWidth - padding * 2) / contentW;
|
|
197
|
+
const scaleY = (this.screenHeight - padding * 2) / contentH;
|
|
198
|
+
this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.min(scaleX, scaleY)));
|
|
199
|
+
this.x = -(aabb.minX + contentW / 2) + this.screenWidth / (2 * this.zoom);
|
|
200
|
+
this.y = -(aabb.minY + contentH / 2) + this.screenHeight / (2 * this.zoom);
|
|
201
|
+
this.notify();
|
|
202
|
+
}
|
|
203
|
+
screenToWorld(screenX, screenY) {
|
|
204
|
+
return {
|
|
205
|
+
x: screenX / this.zoom - this.x,
|
|
206
|
+
y: screenY / this.zoom - this.y
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
worldToScreen(worldX, worldY) {
|
|
210
|
+
return {
|
|
211
|
+
x: (worldX + this.x) * this.zoom,
|
|
212
|
+
y: (worldY + this.y) * this.zoom
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
getVisibleAABB() {
|
|
216
|
+
const topLeft = this.screenToWorld(0, 0);
|
|
217
|
+
const bottomRight = this.screenToWorld(this.screenWidth, this.screenHeight);
|
|
218
|
+
return {
|
|
219
|
+
minX: topLeft.x,
|
|
220
|
+
minY: topLeft.y,
|
|
221
|
+
maxX: bottomRight.x,
|
|
222
|
+
maxY: bottomRight.y
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
getState() {
|
|
226
|
+
return { x: this.x, y: this.y, zoom: this.zoom };
|
|
227
|
+
}
|
|
228
|
+
subscribe(listener) {
|
|
229
|
+
this.listeners.add(listener);
|
|
230
|
+
return () => this.listeners.delete(listener);
|
|
231
|
+
}
|
|
232
|
+
notify() {
|
|
233
|
+
for (const listener of this.listeners) {
|
|
234
|
+
listener();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/rendering/LODLevel.ts
|
|
240
|
+
var LODLevel = /* @__PURE__ */ ((LODLevel2) => {
|
|
241
|
+
LODLevel2["Overview"] = "overview";
|
|
242
|
+
LODLevel2["Section"] = "section";
|
|
243
|
+
LODLevel2["Detail"] = "detail";
|
|
244
|
+
return LODLevel2;
|
|
245
|
+
})(LODLevel || {});
|
|
246
|
+
var SECTION_THRESHOLD = 0.3;
|
|
247
|
+
var DETAIL_THRESHOLD = 0.7;
|
|
248
|
+
function getLODLevel(zoom) {
|
|
249
|
+
if (zoom < SECTION_THRESHOLD) return "overview" /* Overview */;
|
|
250
|
+
if (zoom < DETAIL_THRESHOLD) return "section" /* Section */;
|
|
251
|
+
return "detail" /* Detail */;
|
|
252
|
+
}
|
|
253
|
+
var STATUS_COLORS = {
|
|
254
|
+
available: 5025616,
|
|
255
|
+
held: 16750592,
|
|
256
|
+
sold: 10395294,
|
|
257
|
+
blocked: 16007990,
|
|
258
|
+
selected: 2201331,
|
|
259
|
+
hovered: 6600182
|
|
260
|
+
};
|
|
261
|
+
function createSeatTextures(renderer, radius = 7, categoryColor, textureResolution) {
|
|
262
|
+
const result = {};
|
|
263
|
+
const diameter = (radius + 4) * 2;
|
|
264
|
+
const resolution = textureResolution ?? 4 * (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
265
|
+
for (const [status, color] of Object.entries(STATUS_COLORS)) {
|
|
266
|
+
const g = new Graphics();
|
|
267
|
+
const fillColor = status === "available" && categoryColor != null ? categoryColor : color;
|
|
268
|
+
g.circle(radius + 4, radius + 4, radius);
|
|
269
|
+
g.fill({ color: fillColor });
|
|
270
|
+
if (status === "selected") {
|
|
271
|
+
g.circle(radius + 4, radius + 4, radius + 2);
|
|
272
|
+
g.stroke({ color: 16777215, width: 2 });
|
|
273
|
+
}
|
|
274
|
+
const texture = RenderTexture.create({ width: diameter, height: diameter, resolution });
|
|
275
|
+
renderer.render({ container: g, target: texture });
|
|
276
|
+
g.destroy();
|
|
277
|
+
result[status] = texture;
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
function destroySeatTextures(textures) {
|
|
282
|
+
for (const tex of Object.values(textures)) {
|
|
283
|
+
tex.destroy(true);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/rendering/CategoryTextureCache.ts
|
|
288
|
+
var CategoryTextureCache = class {
|
|
289
|
+
cache = /* @__PURE__ */ new Map();
|
|
290
|
+
defaultTextures = null;
|
|
291
|
+
create(renderer, categories, seatRadius = 7) {
|
|
292
|
+
this.destroy();
|
|
293
|
+
this.defaultTextures = createSeatTextures(renderer, seatRadius);
|
|
294
|
+
for (const cat of categories) {
|
|
295
|
+
const color = parseInt(cat.color.replace("#", ""), 16);
|
|
296
|
+
this.cache.set(cat.id, createSeatTextures(renderer, seatRadius, color));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
get(categoryId) {
|
|
300
|
+
return this.cache.get(categoryId) ?? this.defaultTextures;
|
|
301
|
+
}
|
|
302
|
+
destroy() {
|
|
303
|
+
for (const textures of this.cache.values()) {
|
|
304
|
+
destroySeatTextures(textures);
|
|
305
|
+
}
|
|
306
|
+
if (this.defaultTextures) {
|
|
307
|
+
destroySeatTextures(this.defaultTextures);
|
|
308
|
+
}
|
|
309
|
+
this.cache.clear();
|
|
310
|
+
this.defaultTextures = null;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/commands/CommandHistory.ts
|
|
315
|
+
var CommandHistory = class {
|
|
316
|
+
undoStack = [];
|
|
317
|
+
redoStack = [];
|
|
318
|
+
listeners = /* @__PURE__ */ new Set();
|
|
319
|
+
execute(command) {
|
|
320
|
+
command.execute();
|
|
321
|
+
this.undoStack.push(command);
|
|
322
|
+
this.redoStack = [];
|
|
323
|
+
this.notify();
|
|
324
|
+
}
|
|
325
|
+
undo() {
|
|
326
|
+
const command = this.undoStack.pop();
|
|
327
|
+
if (!command) return;
|
|
328
|
+
command.undo();
|
|
329
|
+
this.redoStack.push(command);
|
|
330
|
+
this.notify();
|
|
331
|
+
}
|
|
332
|
+
redo() {
|
|
333
|
+
const command = this.redoStack.pop();
|
|
334
|
+
if (!command) return;
|
|
335
|
+
command.execute();
|
|
336
|
+
this.undoStack.push(command);
|
|
337
|
+
this.notify();
|
|
338
|
+
}
|
|
339
|
+
get canUndo() {
|
|
340
|
+
return this.undoStack.length > 0;
|
|
341
|
+
}
|
|
342
|
+
get canRedo() {
|
|
343
|
+
return this.redoStack.length > 0;
|
|
344
|
+
}
|
|
345
|
+
clear() {
|
|
346
|
+
this.undoStack = [];
|
|
347
|
+
this.redoStack = [];
|
|
348
|
+
this.notify();
|
|
349
|
+
}
|
|
350
|
+
subscribe(listener) {
|
|
351
|
+
this.listeners.add(listener);
|
|
352
|
+
return () => this.listeners.delete(listener);
|
|
353
|
+
}
|
|
354
|
+
notify() {
|
|
355
|
+
for (const listener of this.listeners) {
|
|
356
|
+
listener();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/serialization/index.ts
|
|
362
|
+
function serializeVenue(venue) {
|
|
363
|
+
return JSON.stringify(venue, null, 2);
|
|
364
|
+
}
|
|
365
|
+
function deserializeVenue(json) {
|
|
366
|
+
return JSON.parse(json);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export { CategoryTextureCache, CommandHistory, LODLevel, SpatialIndex, Viewport, clampToPolygon, createSeatTextures, deserializeVenue, destroySeatTextures, generateId, getLODLevel, pointInPolygon, seatWorldPosition, sectionAABB, serializeVenue, venueAABB };
|
|
370
|
+
//# sourceMappingURL=index.js.map
|
|
371
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/models/helpers.ts","../src/spatial/SpatialIndex.ts","../src/rendering/Viewport.ts","../src/rendering/LODLevel.ts","../src/rendering/SpriteAtlas.ts","../src/rendering/CategoryTextureCache.ts","../src/commands/CommandHistory.ts","../src/serialization/index.ts"],"names":["LODLevel"],"mappings":";;;;AAEO,SAAS,iBAAA,CAAkB,SAAkB,IAAA,EAAkB;AACpE,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA;AACrC,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA;AACrC,EAAA,OAAO;AAAA,IACL,CAAA,EAAG,OAAA,CAAQ,QAAA,CAAS,CAAA,GAAI,IAAA,CAAK,SAAS,CAAA,GAAI,GAAA,GAAM,IAAA,CAAK,QAAA,CAAS,CAAA,GAAI,GAAA;AAAA,IAClE,CAAA,EAAG,OAAA,CAAQ,QAAA,CAAS,CAAA,GAAI,IAAA,CAAK,SAAS,CAAA,GAAI,GAAA,GAAM,IAAA,CAAK,QAAA,CAAS,CAAA,GAAI;AAAA,GACpE;AACF;AAEO,SAAS,YAAY,OAAA,EAAwB;AAClD,EAAA,MAAM,YAAoB,EAAC;AAE3B,EAAA,IAAI,OAAA,CAAQ,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AAC9B,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA;AACrC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA;AACrC,IAAA,KAAA,MAAW,CAAA,IAAK,QAAQ,OAAA,EAAS;AAC/B,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,CAAA,EAAG,QAAQ,QAAA,CAAS,CAAA,GAAI,EAAE,CAAA,GAAI,GAAA,GAAM,EAAE,CAAA,GAAI,GAAA;AAAA,QAC1C,CAAA,EAAG,QAAQ,QAAA,CAAS,CAAA,GAAI,EAAE,CAAA,GAAI,GAAA,GAAM,EAAE,CAAA,GAAI;AAAA,OAC3C,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,MAAM,WAAW,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAC,CAAA,KAAM,EAAE,KAAK,CAAA;AACpD,EAAA,KAAA,MAAW,QAAQ,QAAA,EAAU;AAC3B,IAAA,SAAA,CAAU,IAAA,CAAK,iBAAA,CAAkB,OAAA,EAAS,IAAI,CAAC,CAAA;AAAA,EACjD;AAEA,EAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,QAAQ,QAAA,CAAS,CAAA;AAAA,MACvB,IAAA,EAAM,QAAQ,QAAA,CAAS,CAAA;AAAA,MACvB,IAAA,EAAM,QAAQ,QAAA,CAAS,CAAA;AAAA,MACvB,IAAA,EAAM,QAAQ,QAAA,CAAS;AAAA,KACzB;AAAA,EACF;AAEA,EAAA,MAAM,GAAA,GAAM,EAAA;AACZ,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,GAAA;AAAA,IAC/C,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,GAAA;AAAA,IAC/C,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,GAAA;AAAA,IAC/C,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,SAAA,CAAU,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI;AAAA,GACjD;AACF;AAEO,SAAS,UAAU,KAAA,EAAoB;AAC5C,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAC/B,IAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,IAAA,EAAM,KAAA,CAAM,MAAA,CAAO,MAAA,EAAO;AAAA,EACjF;AACA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,QAAA,CAAS,GAAA,CAAI,WAAW,CAAA;AAC5C,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,IAC1C,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,IAC1C,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,IAC1C,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC;AAAA,GAC5C;AACF;AAGO,SAAS,cAAA,CAAe,OAAa,OAAA,EAA0B;AACpE,EAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG,OAAO,KAAA;AAC/B,EAAA,IAAI,MAAA,GAAS,KAAA;AACb,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,GAAS,GAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,EAAQ,CAAA,GAAI,CAAA,EAAA,EAAK;AACnE,IAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA,CAAE,GAAG,EAAA,GAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AACzC,IAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA,CAAE,GAAG,EAAA,GAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AACzC,IAAA,IAAK,EAAA,GAAK,KAAA,CAAM,CAAA,KAAQ,EAAA,GAAK,MAAM,CAAA,IAC/B,KAAA,CAAM,CAAA,GAAA,CAAK,EAAA,GAAK,OAAO,KAAA,CAAM,CAAA,GAAI,EAAA,CAAA,IAAO,EAAA,GAAK,MAAM,EAAA,EAAI;AACzD,MAAA,MAAA,GAAS,CAAC,MAAA;AAAA,IACZ;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,cAAA,CAAe,KAAA,EAAa,OAAA,EAAiB,MAAA,GAAS,CAAA,EAAS;AAC7E,EAAA,IAAI,QAAQ,MAAA,GAAS,CAAA,IAAK,eAAe,KAAA,EAAO,OAAO,GAAG,OAAO,KAAA;AAGjE,EAAA,IAAI,QAAQ,KAAA,CAAM,CAAA,EAAG,KAAA,GAAQ,KAAA,CAAM,GAAG,QAAA,GAAW,QAAA;AACjD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,GAAS,GAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,EAAQ,CAAA,GAAI,CAAA,EAAA,EAAK;AACnE,IAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA,CAAE,GAAG,EAAA,GAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AACzC,IAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA,CAAE,GAAG,EAAA,GAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AACzC,IAAA,MAAM,EAAA,GAAK,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,GAAK,EAAA;AAC9B,IAAA,MAAM,IAAA,GAAO,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA;AAC5B,IAAA,IAAI,SAAS,CAAA,EAAG;AAChB,IAAA,MAAM,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAA,CAAA,CAAK,KAAA,CAAM,CAAA,GAAI,EAAA,IAAM,MAAM,KAAA,CAAM,CAAA,GAAI,EAAA,IAAM,EAAA,IAAM,IAAI,CAAC,CAAA;AACrF,IAAA,MAAM,EAAA,GAAK,KAAK,CAAA,GAAI,EAAA,GAAK,SAAS,EAAA,GAAK,IAAA,CAAK,KAAK,IAAI,CAAA;AACrD,IAAA,MAAM,EAAA,GAAK,KAAK,CAAA,GAAI,EAAA,GAAK,SAAS,EAAA,GAAK,IAAA,CAAK,KAAK,IAAI,CAAA;AACrD,IAAA,MAAM,CAAA,GAAI,KAAK,KAAA,CAAM,KAAA,CAAM,IAAI,EAAA,EAAI,KAAA,CAAM,IAAI,EAAE,CAAA;AAC/C,IAAA,IAAI,IAAI,QAAA,EAAU;AAAE,MAAA,QAAA,GAAW,CAAA;AAAG,MAAA,KAAA,GAAQ,EAAA;AAAI,MAAA,KAAA,GAAQ,EAAA;AAAA,IAAI;AAAA,EAC5D;AAGA,EAAA,IAAI,CAAC,eAAe,EAAE,CAAA,EAAG,OAAO,CAAA,EAAG,KAAA,EAAM,EAAG,OAAO,CAAA,EAAG;AACpD,IAAA,QAAA,GAAW,QAAA;AACX,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,GAAS,GAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,EAAQ,CAAA,GAAI,CAAA,EAAA,EAAK;AACnE,MAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA,CAAE,GAAG,EAAA,GAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AACzC,MAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA,CAAE,GAAG,EAAA,GAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AACzC,MAAA,MAAM,EAAA,GAAK,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,GAAK,EAAA;AAC9B,MAAA,MAAM,IAAA,GAAO,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA;AAC5B,MAAA,IAAI,SAAS,CAAA,EAAG;AAChB,MAAA,MAAM,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,EAAA,CAAA,CAAK,KAAA,CAAM,CAAA,GAAI,EAAA,IAAM,MAAM,KAAA,CAAM,CAAA,GAAI,EAAA,IAAM,EAAA,IAAM,IAAI,CAAC,CAAA;AACrF,MAAA,MAAM,KAAK,EAAA,GAAK,CAAA,GAAI,EAAA,EAAI,EAAA,GAAK,KAAK,CAAA,GAAI,EAAA;AACtC,MAAA,MAAM,CAAA,GAAI,KAAK,KAAA,CAAM,KAAA,CAAM,IAAI,EAAA,EAAI,KAAA,CAAM,IAAI,EAAE,CAAA;AAC/C,MAAA,IAAI,IAAI,QAAA,EAAU;AAAE,QAAA,QAAA,GAAW,CAAA;AAAG,QAAA,KAAA,GAAQ,EAAA;AAAI,QAAA,KAAA,GAAQ,EAAA;AAAA,MAAI;AAAA,IAC5D;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,CAAA,EAAG,KAAA,EAAO,CAAA,EAAG,KAAA,EAAM;AAC9B;AAEA,IAAI,OAAA,GAAU,CAAA;AACP,SAAS,UAAA,CAAW,SAAS,EAAA,EAAY;AAC9C,EAAA,OAAO,GAAG,MAAM,CAAA,EAAG,MAAA,GAAS,GAAA,GAAM,EAAE,CAAA,EAAG,IAAA,CAAK,GAAA,EAAI,CAAE,SAAS,EAAE,CAAC,KAAK,OAAA,EAAA,EAAW,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA;AAC5F;AC3GO,IAAM,eAAN,MAAmB;AAAA,EAChB,IAAA,GAAO,IAAI,KAAA,EAAmB;AAAA,EAC9B,QAAuB,EAAC;AAAA,EAEhC,kBAAkB,QAAA,EAA2B;AAC3C,IAAA,IAAA,CAAK,QAAQ,EAAC;AAEd,IAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,MAAA,MAAM,GAAA,GAAM,YAAY,OAAO,CAAA;AAC/B,MAAA,IAAA,CAAK,MAAM,IAAA,CAAK;AAAA,QACd,GAAG,GAAA;AAAA,QACH,IAAA,EAAM,SAAA;AAAA,QACN,WAAW,OAAA,CAAQ;AAAA,OACpB,CAAA;AAED,MAAA,KAAA,MAAW,GAAA,IAAO,QAAQ,IAAA,EAAM;AAC9B,QAAA,KAAA,MAAW,IAAA,IAAQ,IAAI,KAAA,EAAO;AAC5B,UAAA,MAAM,EAAA,GAAK,iBAAA,CAAkB,OAAA,EAAS,IAAI,CAAA;AAC1C,UAAA,MAAM,CAAA,GAAI,CAAA;AACV,UAAA,IAAA,CAAK,MAAM,IAAA,CAAK;AAAA,YACd,IAAA,EAAM,GAAG,CAAA,GAAI,CAAA;AAAA,YACb,IAAA,EAAM,GAAG,CAAA,GAAI,CAAA;AAAA,YACb,IAAA,EAAM,GAAG,CAAA,GAAI,CAAA;AAAA,YACb,IAAA,EAAM,GAAG,CAAA,GAAI,CAAA;AAAA,YACb,IAAA,EAAM,MAAA;AAAA,YACN,WAAW,OAAA,CAAQ,EAAA;AAAA,YACnB,QAAQ,IAAA,CAAK;AAAA,WACd,CAAA;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,KAAK,KAAA,EAAM;AAChB,IAAA,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA;AAAA,EAC3B;AAAA,EAEA,cAAc,QAAA,EAA+B;AAC3C,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,QAAQ,CAAA;AAAA,EAClC;AAAA,EAEA,UAAA,CAAW,KAAA,EAAa,MAAA,GAAS,CAAA,EAAkB;AACjD,IAAA,OAAO,IAAA,CAAK,KAAK,MAAA,CAAO;AAAA,MACtB,IAAA,EAAM,MAAM,CAAA,GAAI,MAAA;AAAA,MAChB,IAAA,EAAM,MAAM,CAAA,GAAI,MAAA;AAAA,MAChB,IAAA,EAAM,MAAM,CAAA,GAAI,MAAA;AAAA,MAChB,IAAA,EAAM,MAAM,CAAA,GAAI;AAAA,KACjB,CAAA;AAAA,EACH;AAAA,EAEA,UAAU,IAAA,EAA2B;AACnC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA;AAAA,EAC9B;AACF;;;ACtDA,IAAM,QAAA,GAAW,IAAA;AACjB,IAAM,QAAA,GAAW,CAAA;AAEV,IAAM,WAAN,MAAe;AAAA,EACpB,CAAA,GAAI,CAAA;AAAA,EACJ,CAAA,GAAI,CAAA;AAAA,EACJ,IAAA,GAAO,CAAA;AAAA,EACP,WAAA,GAAc,CAAA;AAAA,EACd,YAAA,GAAe,CAAA;AAAA,EAEP,SAAA,uBAAgB,GAAA,EAAgB;AAAA,EAExC,aAAA,CAAc,OAAe,MAAA,EAAsB;AACjD,IAAA,IAAA,CAAK,WAAA,GAAc,KAAA;AACnB,IAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AACpB,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,GAAA,CAAI,IAAY,EAAA,EAAkB;AAChC,IAAA,IAAA,CAAK,CAAA,IAAK,KAAK,IAAA,CAAK,IAAA;AACpB,IAAA,IAAA,CAAK,CAAA,IAAK,KAAK,IAAA,CAAK,IAAA;AACpB,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,MAAA,CAAO,aAAmB,MAAA,EAAsB;AAC9C,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK,IAAI,QAAA,EAAU,IAAA,CAAK,IAAA,GAAO,MAAM,CAAC,CAAA;AAGzE,IAAA,MAAM,EAAA,GAAK,WAAA,CAAY,CAAA,GAAI,IAAA,CAAK,OAAO,IAAA,CAAK,CAAA;AAC5C,IAAA,MAAM,EAAA,GAAK,WAAA,CAAY,CAAA,GAAI,IAAA,CAAK,OAAO,IAAA,CAAK,CAAA;AAI5C,IAAA,IAAA,CAAK,CAAA,GAAI,WAAA,CAAY,CAAA,GAAI,OAAA,GAAU,EAAA;AACnC,IAAA,IAAA,CAAK,CAAA,GAAI,WAAA,CAAY,CAAA,GAAI,OAAA,GAAU,EAAA;AACnC,IAAA,IAAA,CAAK,IAAA,GAAO,OAAA;AACZ,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,QAAQ,IAAA,EAAoB;AAC1B,IAAA,IAAA,CAAK,IAAA,GAAO,KAAK,GAAA,CAAI,QAAA,EAAU,KAAK,GAAA,CAAI,QAAA,EAAU,IAAI,CAAC,CAAA;AACvD,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,SAAA,CAAU,IAAA,EAAY,OAAA,GAAU,EAAA,EAAU;AACxC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,IAAA;AAClC,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,IAAA;AAClC,IAAA,IAAI,QAAA,IAAY,CAAA,IAAK,QAAA,IAAY,CAAA,EAAG;AAEpC,IAAA,MAAM,MAAA,GAAA,CAAU,IAAA,CAAK,WAAA,GAAc,OAAA,GAAU,CAAA,IAAK,QAAA;AAClD,IAAA,MAAM,MAAA,GAAA,CAAU,IAAA,CAAK,YAAA,GAAe,OAAA,GAAU,CAAA,IAAK,QAAA;AACnD,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,MAAM,CAAC,CAAC,CAAA;AAE3E,IAAA,IAAA,CAAK,CAAA,GAAI,EAAE,IAAA,CAAK,IAAA,GAAO,WAAW,CAAA,CAAA,GAAK,IAAA,CAAK,WAAA,IAAe,CAAA,GAAI,IAAA,CAAK,IAAA,CAAA;AACpE,IAAA,IAAA,CAAK,CAAA,GAAI,EAAE,IAAA,CAAK,IAAA,GAAO,WAAW,CAAA,CAAA,GAAK,IAAA,CAAK,YAAA,IAAgB,CAAA,GAAI,IAAA,CAAK,IAAA,CAAA;AACrE,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,aAAA,CAAc,SAAiB,OAAA,EAAuB;AACpD,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,OAAA,GAAU,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,CAAA;AAAA,MAC9B,CAAA,EAAG,OAAA,GAAU,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK;AAAA,KAChC;AAAA,EACF;AAAA,EAEA,aAAA,CAAc,QAAgB,MAAA,EAAsB;AAClD,IAAA,OAAO;AAAA,MACL,CAAA,EAAA,CAAI,MAAA,GAAS,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK,IAAA;AAAA,MAC5B,CAAA,EAAA,CAAI,MAAA,GAAS,IAAA,CAAK,CAAA,IAAK,IAAA,CAAK;AAAA,KAC9B;AAAA,EACF;AAAA,EAEA,cAAA,GAAuB;AACrB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,aAAA,CAAc,CAAA,EAAG,CAAC,CAAA;AACvC,IAAA,MAAM,cAAc,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,WAAA,EAAa,KAAK,YAAY,CAAA;AAC1E,IAAA,OAAO;AAAA,MACL,MAAM,OAAA,CAAQ,CAAA;AAAA,MACd,MAAM,OAAA,CAAQ,CAAA;AAAA,MACd,MAAM,WAAA,CAAY,CAAA;AAAA,MAClB,MAAM,WAAA,CAAY;AAAA,KACpB;AAAA,EACF;AAAA,EAEA,QAAA,GAA0B;AACxB,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,CAAA,EAAG,GAAG,IAAA,CAAK,CAAA,EAAG,IAAA,EAAM,IAAA,CAAK,IAAA,EAAK;AAAA,EACjD;AAAA,EAEA,UAAU,QAAA,EAAkC;AAC1C,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,EAC7C;AAAA,EAEQ,MAAA,GAAe;AACrB,IAAA,KAAA,MAAW,QAAA,IAAY,KAAK,SAAA,EAAW;AACrC,MAAA,QAAA,EAAS;AAAA,IACX;AAAA,EACF;AACF;;;ACzGO,IAAK,QAAA,qBAAAA,SAAAA,KAAL;AACL,EAAAA,UAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,UAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,UAAA,QAAA,CAAA,GAAS,QAAA;AAHC,EAAA,OAAAA,SAAAA;AAAA,CAAA,EAAA,QAAA,IAAA,EAAA;AAMZ,IAAM,iBAAA,GAAoB,GAAA;AAC1B,IAAM,gBAAA,GAAmB,GAAA;AAElB,SAAS,YAAY,IAAA,EAAwB;AAClD,EAAA,IAAI,IAAA,GAAO,mBAAmB,OAAO,UAAA;AACrC,EAAA,IAAI,IAAA,GAAO,kBAAkB,OAAO,SAAA;AACpC,EAAA,OAAO,QAAA;AACT;ACFA,IAAM,aAAA,GAAwC;AAAA,EAC5C,SAAA,EAAW,OAAA;AAAA,EACX,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,QAAA;AAAA,EACN,OAAA,EAAS,QAAA;AAAA,EACT,QAAA,EAAU,OAAA;AAAA,EACV,OAAA,EAAS;AACX,CAAA;AAEO,SAAS,kBAAA,CACd,QAAA,EACA,MAAA,GAAS,CAAA,EACT,eACA,iBAAA,EACgB;AAChB,EAAA,MAAM,SAAkC,EAAC;AACzC,EAAA,MAAM,QAAA,GAAA,CAAY,SAAS,CAAA,IAAK,CAAA;AAChC,EAAA,MAAM,UAAA,GAAa,qBAAsB,CAAA,IAAK,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,oBAAoB,CAAA,GAAI,CAAA,CAAA;AAE7G,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA,EAAG;AAC3D,IAAA,MAAM,CAAA,GAAI,IAAI,QAAA,EAAS;AACvB,IAAA,MAAM,SAAA,GAAY,MAAA,KAAW,WAAA,IAAe,aAAA,IAAiB,OAAO,aAAA,GAAgB,KAAA;AACpF,IAAA,CAAA,CAAE,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,MAAA,GAAS,GAAG,MAAM,CAAA;AACvC,IAAA,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,SAAA,EAAW,CAAA;AAE3B,IAAA,IAAI,WAAW,UAAA,EAAY;AACzB,MAAA,CAAA,CAAE,OAAO,MAAA,GAAS,CAAA,EAAG,MAAA,GAAS,CAAA,EAAG,SAAS,CAAC,CAAA;AAC3C,MAAA,CAAA,CAAE,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,GAAG,CAAA;AAAA,IACxC;AAEA,IAAA,MAAM,OAAA,GAAU,cAAc,MAAA,CAAO,EAAE,OAAO,QAAA,EAAU,MAAA,EAAQ,QAAA,EAAU,UAAA,EAAY,CAAA;AACtF,IAAA,QAAA,CAAS,OAAO,EAAE,SAAA,EAAW,CAAA,EAAG,MAAA,EAAQ,SAAS,CAAA;AACjD,IAAA,CAAA,CAAE,OAAA,EAAQ;AAEV,IAAA,MAAA,CAAO,MAA8B,CAAA,GAAI,OAAA;AAAA,EAC3C;AAEA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,oBAAoB,QAAA,EAAgC;AAClE,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA,EAAG;AACzC,IAAC,GAAA,CAAsB,QAAQ,IAAI,CAAA;AAAA,EACrC;AACF;;;AChDO,IAAM,uBAAN,MAA2B;AAAA,EACxB,KAAA,uBAAY,GAAA,EAA4B;AAAA,EACxC,eAAA,GAAyC,IAAA;AAAA,EAEjD,MAAA,CAAO,QAAA,EAAoB,UAAA,EAA6C,UAAA,GAAa,CAAA,EAAS;AAC5F,IAAA,IAAA,CAAK,OAAA,EAAQ;AACb,IAAA,IAAA,CAAK,eAAA,GAAkB,kBAAA,CAAmB,QAAA,EAAU,UAAU,CAAA;AAE9D,IAAA,KAAA,MAAW,OAAO,UAAA,EAAY;AAC5B,MAAA,MAAM,KAAA,GAAQ,SAAS,GAAA,CAAI,KAAA,CAAM,QAAQ,GAAA,EAAK,EAAE,GAAG,EAAE,CAAA;AACrD,MAAA,IAAA,CAAK,KAAA,CAAM,IAAI,GAAA,CAAI,EAAA,EAAI,mBAAmB,QAAA,EAAU,UAAA,EAAY,KAAK,CAAC,CAAA;AAAA,IACxE;AAAA,EACF;AAAA,EAEA,IAAI,UAAA,EAAoC;AACtC,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,UAAU,KAAK,IAAA,CAAK,eAAA;AAAA,EAC5C;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,KAAA,CAAM,MAAA,EAAO,EAAG;AAC1C,MAAA,mBAAA,CAAoB,QAAQ,CAAA;AAAA,IAC9B;AACA,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,mBAAA,CAAoB,KAAK,eAAe,CAAA;AAAA,IAC1C;AACA,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AACjB,IAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AAAA,EACzB;AACF;;;AC7BO,IAAM,iBAAN,MAAqB;AAAA,EAClB,YAAuB,EAAC;AAAA,EACxB,YAAuB,EAAC;AAAA,EACxB,SAAA,uBAAgB,GAAA,EAAgB;AAAA,EAExC,QAAQ,OAAA,EAAwB;AAC9B,IAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,OAAO,CAAA;AAC3B,IAAA,IAAA,CAAK,YAAY,EAAC;AAClB,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,IAAA,GAAa;AACX,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,GAAA,EAAI;AACnC,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,OAAA,CAAQ,IAAA,EAAK;AACb,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,OAAO,CAAA;AAC3B,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,IAAA,GAAa;AACX,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,GAAA,EAAI;AACnC,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,OAAA,CAAQ,OAAA,EAAQ;AAChB,IAAA,IAAA,CAAK,SAAA,CAAU,KAAK,OAAO,CAAA;AAC3B,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,IAAI,OAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,UAAU,MAAA,GAAS,CAAA;AAAA,EACjC;AAAA,EAEA,IAAI,OAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,UAAU,MAAA,GAAS,CAAA;AAAA,EACjC;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,YAAY,EAAC;AAClB,IAAA,IAAA,CAAK,YAAY,EAAC;AAClB,IAAA,IAAA,CAAK,MAAA,EAAO;AAAA,EACd;AAAA,EAEA,UAAU,QAAA,EAAkC;AAC1C,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,EAC7C;AAAA,EAEQ,MAAA,GAAe;AACrB,IAAA,KAAA,MAAW,QAAA,IAAY,KAAK,SAAA,EAAW;AACrC,MAAA,QAAA,EAAS;AAAA,IACX;AAAA,EACF;AACF;;;ACxDO,SAAS,eAAe,KAAA,EAAsB;AACnD,EAAA,OAAO,IAAA,CAAK,SAAA,CAAU,KAAA,EAAO,IAAA,EAAM,CAAC,CAAA;AACtC;AAEO,SAAS,iBAAiB,IAAA,EAAqB;AACpD,EAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB","file":"index.js","sourcesContent":["import type { AABB, Section, Seat, Vec2, Venue } from \"./types\";\n\nexport function seatWorldPosition(section: Section, seat: Seat): Vec2 {\n const cos = Math.cos(section.rotation);\n const sin = Math.sin(section.rotation);\n return {\n x: section.position.x + seat.position.x * cos - seat.position.y * sin,\n y: section.position.y + seat.position.x * sin + seat.position.y * cos,\n };\n}\n\nexport function sectionAABB(section: Section): AABB {\n const allPoints: Vec2[] = [];\n\n if (section.outline.length > 0) {\n const cos = Math.cos(section.rotation);\n const sin = Math.sin(section.rotation);\n for (const p of section.outline) {\n allPoints.push({\n x: section.position.x + p.x * cos - p.y * sin,\n y: section.position.y + p.x * sin + p.y * cos,\n });\n }\n }\n\n const allSeats = section.rows.flatMap((r) => r.seats);\n for (const seat of allSeats) {\n allPoints.push(seatWorldPosition(section, seat));\n }\n\n if (allPoints.length === 0) {\n return {\n minX: section.position.x,\n minY: section.position.y,\n maxX: section.position.x,\n maxY: section.position.y,\n };\n }\n\n const pad = 10;\n return {\n minX: Math.min(...allPoints.map((p) => p.x)) - pad,\n minY: Math.min(...allPoints.map((p) => p.y)) - pad,\n maxX: Math.max(...allPoints.map((p) => p.x)) + pad,\n maxY: Math.max(...allPoints.map((p) => p.y)) + pad,\n };\n}\n\nexport function venueAABB(venue: Venue): AABB {\n if (venue.sections.length === 0) {\n return { minX: 0, minY: 0, maxX: venue.bounds.width, maxY: venue.bounds.height };\n }\n const boxes = venue.sections.map(sectionAABB);\n return {\n minX: Math.min(...boxes.map((b) => b.minX)),\n minY: Math.min(...boxes.map((b) => b.minY)),\n maxX: Math.max(...boxes.map((b) => b.maxX)),\n maxY: Math.max(...boxes.map((b) => b.maxY)),\n };\n}\n\n/** Ray-casting point-in-polygon test. Works with any simple polygon. */\nexport function pointInPolygon(point: Vec2, polygon: Vec2[]): boolean {\n if (polygon.length < 3) return false;\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const xi = polygon[i].x, yi = polygon[i].y;\n const xj = polygon[j].x, yj = polygon[j].y;\n if ((yi > point.y) !== (yj > point.y) &&\n point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi) {\n inside = !inside;\n }\n }\n return inside;\n}\n\n/** Clamp a point to the nearest position inside a polygon (with margin). */\nexport function clampToPolygon(point: Vec2, polygon: Vec2[], margin = 5): Vec2 {\n if (polygon.length < 3 || pointInPolygon(point, polygon)) return point;\n\n // Find the closest point on any polygon edge\n let bestX = point.x, bestY = point.y, bestDist = Infinity;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const ax = polygon[j].x, ay = polygon[j].y;\n const bx = polygon[i].x, by = polygon[i].y;\n const dx = bx - ax, dy = by - ay;\n const len2 = dx * dx + dy * dy;\n if (len2 === 0) continue;\n const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));\n const cx = ax + t * dx - margin * dy / Math.sqrt(len2);\n const cy = ay + t * dy + margin * dx / Math.sqrt(len2);\n const d = Math.hypot(point.x - cx, point.y - cy);\n if (d < bestDist) { bestDist = d; bestX = cx; bestY = cy; }\n }\n\n // Verify the clamped point is inside; if not just project onto edge without margin\n if (!pointInPolygon({ x: bestX, y: bestY }, polygon)) {\n bestDist = Infinity;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const ax = polygon[j].x, ay = polygon[j].y;\n const bx = polygon[i].x, by = polygon[i].y;\n const dx = bx - ax, dy = by - ay;\n const len2 = dx * dx + dy * dy;\n if (len2 === 0) continue;\n const t = Math.max(0, Math.min(1, ((point.x - ax) * dx + (point.y - ay) * dy) / len2));\n const cx = ax + t * dx, cy = ay + t * dy;\n const d = Math.hypot(point.x - cx, point.y - cy);\n if (d < bestDist) { bestDist = d; bestX = cx; bestY = cy; }\n }\n }\n\n return { x: bestX, y: bestY };\n}\n\nlet _nextId = 1;\nexport function generateId(prefix = \"\"): string {\n return `${prefix}${prefix ? \"-\" : \"\"}${Date.now().toString(36)}-${(_nextId++).toString(36)}`;\n}\n","import RBush from \"rbush\";\nimport type { AABB, Section, Vec2 } from \"../models/types\";\nimport { seatWorldPosition, sectionAABB } from \"../models/helpers\";\n\nexport interface SpatialItem extends AABB {\n type: \"section\" | \"seat\";\n sectionId: string;\n seatId?: string;\n}\n\nexport class SpatialIndex {\n private tree = new RBush<SpatialItem>();\n private items: SpatialItem[] = [];\n\n buildFromSections(sections: Section[]): void {\n this.items = [];\n\n for (const section of sections) {\n const box = sectionAABB(section);\n this.items.push({\n ...box,\n type: \"section\",\n sectionId: section.id,\n });\n\n for (const row of section.rows) {\n for (const seat of row.seats) {\n const wp = seatWorldPosition(section, seat);\n const r = 8;\n this.items.push({\n minX: wp.x - r,\n minY: wp.y - r,\n maxX: wp.x + r,\n maxY: wp.y + r,\n type: \"seat\",\n sectionId: section.id,\n seatId: seat.id,\n });\n }\n }\n }\n\n this.tree.clear();\n this.tree.load(this.items);\n }\n\n queryViewport(viewport: AABB): SpatialItem[] {\n return this.tree.search(viewport);\n }\n\n queryPoint(point: Vec2, radius = 8): SpatialItem[] {\n return this.tree.search({\n minX: point.x - radius,\n minY: point.y - radius,\n maxX: point.x + radius,\n maxY: point.y + radius,\n });\n }\n\n queryRect(rect: AABB): SpatialItem[] {\n return this.tree.search(rect);\n }\n}\n","import type { AABB, Vec2 } from \"../models/types\";\n\nexport interface ViewportState {\n x: number;\n y: number;\n zoom: number;\n}\n\nconst MIN_ZOOM = 0.05;\nconst MAX_ZOOM = 4;\n\nexport class Viewport {\n x = 0;\n y = 0;\n zoom = 1;\n screenWidth = 0;\n screenHeight = 0;\n\n private listeners = new Set<() => void>();\n\n setScreenSize(width: number, height: number): void {\n this.screenWidth = width;\n this.screenHeight = height;\n this.notify();\n }\n\n pan(dx: number, dy: number): void {\n this.x += dx / this.zoom;\n this.y += dy / this.zoom;\n this.notify();\n }\n\n zoomAt(screenPoint: Vec2, factor: number): void {\n const newZoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, this.zoom * factor));\n\n // 1) World coordinate currently under the cursor\n const wx = screenPoint.x / this.zoom - this.x;\n const wy = screenPoint.y / this.zoom - this.y;\n\n // 2) Solve for the offset that keeps that world point at the same screen position\n // screenPoint = (world + offset) * newZoom → offset = screenPoint / newZoom - world\n this.x = screenPoint.x / newZoom - wx;\n this.y = screenPoint.y / newZoom - wy;\n this.zoom = newZoom;\n this.notify();\n }\n\n setZoom(zoom: number): void {\n this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));\n this.notify();\n }\n\n fitBounds(aabb: AABB, padding = 40): void {\n const contentW = aabb.maxX - aabb.minX;\n const contentH = aabb.maxY - aabb.minY;\n if (contentW <= 0 || contentH <= 0) return;\n\n const scaleX = (this.screenWidth - padding * 2) / contentW;\n const scaleY = (this.screenHeight - padding * 2) / contentH;\n this.zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.min(scaleX, scaleY)));\n\n this.x = -(aabb.minX + contentW / 2) + this.screenWidth / (2 * this.zoom);\n this.y = -(aabb.minY + contentH / 2) + this.screenHeight / (2 * this.zoom);\n this.notify();\n }\n\n screenToWorld(screenX: number, screenY: number): Vec2 {\n return {\n x: screenX / this.zoom - this.x,\n y: screenY / this.zoom - this.y,\n };\n }\n\n worldToScreen(worldX: number, worldY: number): Vec2 {\n return {\n x: (worldX + this.x) * this.zoom,\n y: (worldY + this.y) * this.zoom,\n };\n }\n\n getVisibleAABB(): AABB {\n const topLeft = this.screenToWorld(0, 0);\n const bottomRight = this.screenToWorld(this.screenWidth, this.screenHeight);\n return {\n minX: topLeft.x,\n minY: topLeft.y,\n maxX: bottomRight.x,\n maxY: bottomRight.y,\n };\n }\n\n getState(): ViewportState {\n return { x: this.x, y: this.y, zoom: this.zoom };\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n","export enum LODLevel {\n Overview = \"overview\",\n Section = \"section\",\n Detail = \"detail\",\n}\n\nconst SECTION_THRESHOLD = 0.3;\nconst DETAIL_THRESHOLD = 0.7;\n\nexport function getLODLevel(zoom: number): LODLevel {\n if (zoom < SECTION_THRESHOLD) return LODLevel.Overview;\n if (zoom < DETAIL_THRESHOLD) return LODLevel.Section;\n return LODLevel.Detail;\n}\n","import { Graphics, RenderTexture, type Renderer } from \"pixi.js\";\n\nexport interface SeatTextureSet {\n available: RenderTexture;\n held: RenderTexture;\n sold: RenderTexture;\n blocked: RenderTexture;\n selected: RenderTexture;\n hovered: RenderTexture;\n}\n\nconst STATUS_COLORS: Record<string, number> = {\n available: 0x4caf50,\n held: 0xff9800,\n sold: 0x9e9e9e,\n blocked: 0xf44336,\n selected: 0x2196f3,\n hovered: 0x64b5f6,\n};\n\nexport function createSeatTextures(\n renderer: Renderer,\n radius = 7,\n categoryColor?: number,\n textureResolution?: number,\n): SeatTextureSet {\n const result: Partial<SeatTextureSet> = {};\n const diameter = (radius + 4) * 2;\n const resolution = textureResolution ?? (4 * (typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1));\n\n for (const [status, color] of Object.entries(STATUS_COLORS)) {\n const g = new Graphics();\n const fillColor = status === \"available\" && categoryColor != null ? categoryColor : color;\n g.circle(radius + 4, radius + 4, radius);\n g.fill({ color: fillColor });\n\n if (status === \"selected\") {\n g.circle(radius + 4, radius + 4, radius + 2);\n g.stroke({ color: 0xffffff, width: 2 });\n }\n\n const texture = RenderTexture.create({ width: diameter, height: diameter, resolution });\n renderer.render({ container: g, target: texture });\n g.destroy();\n\n result[status as keyof SeatTextureSet] = texture;\n }\n\n return result as SeatTextureSet;\n}\n\nexport function destroySeatTextures(textures: SeatTextureSet): void {\n for (const tex of Object.values(textures)) {\n (tex as RenderTexture).destroy(true);\n }\n}\n","import type { Renderer } from \"pixi.js\";\nimport {\n createSeatTextures,\n destroySeatTextures,\n type SeatTextureSet,\n} from \"./SpriteAtlas\";\n\nexport class CategoryTextureCache {\n private cache = new Map<string, SeatTextureSet>();\n private defaultTextures: SeatTextureSet | null = null;\n\n create(renderer: Renderer, categories: { id: string; color: string }[], seatRadius = 7): void {\n this.destroy();\n this.defaultTextures = createSeatTextures(renderer, seatRadius);\n\n for (const cat of categories) {\n const color = parseInt(cat.color.replace(\"#\", \"\"), 16);\n this.cache.set(cat.id, createSeatTextures(renderer, seatRadius, color));\n }\n }\n\n get(categoryId: string): SeatTextureSet {\n return this.cache.get(categoryId) ?? this.defaultTextures!;\n }\n\n destroy(): void {\n for (const textures of this.cache.values()) {\n destroySeatTextures(textures);\n }\n if (this.defaultTextures) {\n destroySeatTextures(this.defaultTextures);\n }\n this.cache.clear();\n this.defaultTextures = null;\n }\n}\n","export interface Command {\n execute(): void;\n undo(): void;\n description: string;\n}\n\nexport class CommandHistory {\n private undoStack: Command[] = [];\n private redoStack: Command[] = [];\n private listeners = new Set<() => void>();\n\n execute(command: Command): void {\n command.execute();\n this.undoStack.push(command);\n this.redoStack = [];\n this.notify();\n }\n\n undo(): void {\n const command = this.undoStack.pop();\n if (!command) return;\n command.undo();\n this.redoStack.push(command);\n this.notify();\n }\n\n redo(): void {\n const command = this.redoStack.pop();\n if (!command) return;\n command.execute();\n this.undoStack.push(command);\n this.notify();\n }\n\n get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n clear(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.notify();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n","import type { Venue } from \"../models/types\";\n\nexport function serializeVenue(venue: Venue): string {\n return JSON.stringify(venue, null, 2);\n}\n\nexport function deserializeVenue(json: string): Venue {\n return JSON.parse(json) as Venue;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nex125/seatmap-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"lint": "tsc --noEmit",
|
|
23
|
+
"clean": "rm -rf dist"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"pixi.js": "^8"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/rbush": "^4.0.0",
|
|
30
|
+
"pixi.js": "^8",
|
|
31
|
+
"tsup": "^8",
|
|
32
|
+
"typescript": "^5.7"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"rbush": "^4.0.1"
|
|
36
|
+
}
|
|
37
|
+
}
|