neogestify-ui-components 2.0.1 → 2.2.1
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/README.md +153 -19
- package/dist/components/ElementLibraryBuilder/index.d.mts +5 -0
- package/dist/components/ElementLibraryBuilder/index.d.ts +5 -0
- package/dist/components/ElementLibraryBuilder/index.js +689 -0
- package/dist/components/ElementLibraryBuilder/index.js.map +1 -0
- package/dist/components/ElementLibraryBuilder/index.mjs +687 -0
- package/dist/components/ElementLibraryBuilder/index.mjs.map +1 -0
- package/dist/components/VenueMapEditor/index.d.mts +66 -5
- package/dist/components/VenueMapEditor/index.d.ts +66 -5
- package/dist/components/VenueMapEditor/index.js +199 -34
- package/dist/components/VenueMapEditor/index.js.map +1 -1
- package/dist/components/VenueMapEditor/index.mjs +199 -36
- package/dist/components/VenueMapEditor/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +592 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +591 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ElementLibraryBuilder/builder.tsx +400 -0
- package/src/components/ElementLibraryBuilder/index.ts +1 -0
- package/src/components/VenueMapEditor/VenueMapEditor.tsx +79 -20
- package/src/components/VenueMapEditor/components/ElementNode.tsx +23 -0
- package/src/components/VenueMapEditor/components/PropertiesPanel.tsx +17 -4
- package/src/components/VenueMapEditor/components/Toolbar.tsx +73 -39
- package/src/components/VenueMapEditor/hooks/useLibraryStorage.ts +46 -0
- package/src/components/VenueMapEditor/index.ts +3 -0
- package/src/components/VenueMapEditor/types.ts +45 -3
- package/src/components/VenueMapEditor/utils/svgParser.ts +33 -0
- package/src/index.ts +1 -0
|
@@ -1,7 +1,34 @@
|
|
|
1
|
-
import { useState, useRef,
|
|
1
|
+
import { useState, useCallback, useRef, useMemo, useEffect } from 'react';
|
|
2
2
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/components/VenueMapEditor/VenueMapEditor.tsx
|
|
5
|
+
function useLibraryStorage(storageKey) {
|
|
6
|
+
const [libs, setLibs] = useState(() => {
|
|
7
|
+
if (!storageKey) return {};
|
|
8
|
+
try {
|
|
9
|
+
const raw = localStorage.getItem(storageKey);
|
|
10
|
+
return raw ? JSON.parse(raw) : {};
|
|
11
|
+
} catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const setAndPersist = useCallback(
|
|
16
|
+
(newLibs) => {
|
|
17
|
+
setLibs(newLibs);
|
|
18
|
+
if (!storageKey) return;
|
|
19
|
+
try {
|
|
20
|
+
if (Object.keys(newLibs).length === 0) {
|
|
21
|
+
localStorage.removeItem(storageKey);
|
|
22
|
+
} else {
|
|
23
|
+
localStorage.setItem(storageKey, JSON.stringify(newLibs));
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
[storageKey]
|
|
29
|
+
);
|
|
30
|
+
return [libs, setAndPersist];
|
|
31
|
+
}
|
|
5
32
|
function IconCursor({ className }) {
|
|
6
33
|
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M2 1l12 5.5-5.5 1.5L7 13.5 2 1z" }) });
|
|
7
34
|
}
|
|
@@ -64,6 +91,26 @@ function IconUpload({ className }) {
|
|
|
64
91
|
function IconLayers({ className }) {
|
|
65
92
|
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", className, fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8.235 1.559a.5.5 0 0 0-.47 0l-7.5 4a.5.5 0 0 0 0 .882L3.188 8 .265 9.559a.5.5 0 0 0 0 .882l7.5 4a.5.5 0 0 0 .47 0l7.5-4a.5.5 0 0 0 0-.882L12.813 8l2.922-1.559a.5.5 0 0 0 0-.882l-7.5-4zm3.515 7.008L14.438 10 8 13.433 1.562 10 4.25 8.567l3.515 1.874a.5.5 0 0 0 .47 0l3.515-1.874zM8 9.433 1.562 6 8 2.567 14.438 6 8 9.433z" }) });
|
|
66
93
|
}
|
|
94
|
+
|
|
95
|
+
// src/components/VenueMapEditor/utils/svgParser.ts
|
|
96
|
+
var DANGEROUS_TAGS = /\b(script|iframe|object|embed|link|style|meta)\b/gi;
|
|
97
|
+
var DANGEROUS_ATTRS = /\bon\w+\s*=/gi;
|
|
98
|
+
var DANGEROUS_HREF = /\bhref\s*=\s*["']?\s*javascript:/gi;
|
|
99
|
+
var DANGEROUS_XLINK = /\bxlink:href\s*=\s*["']?\s*javascript:/gi;
|
|
100
|
+
function sanitize(html) {
|
|
101
|
+
return html.replace(DANGEROUS_TAGS, "").replace(DANGEROUS_ATTRS, "").replace(DANGEROUS_HREF, "").replace(DANGEROUS_XLINK, "");
|
|
102
|
+
}
|
|
103
|
+
var VIEWBOX_RE = /viewBox\s*=\s*"([^"]+)"/i;
|
|
104
|
+
var SVG_OPEN_END_RE = /<svg[^>]*>/i;
|
|
105
|
+
function parseSvgMarkup(markup) {
|
|
106
|
+
const viewBoxMatch = markup.match(VIEWBOX_RE);
|
|
107
|
+
const viewBox = viewBoxMatch?.[1] ?? "0 0 100 100";
|
|
108
|
+
const svgOpenMatch = markup.match(SVG_OPEN_END_RE);
|
|
109
|
+
const afterOpen = svgOpenMatch ? markup.slice(svgOpenMatch.index + svgOpenMatch[0].length) : markup;
|
|
110
|
+
const closeIdx = afterOpen.lastIndexOf("</svg>");
|
|
111
|
+
const inner = closeIdx >= 0 ? afterOpen.slice(0, closeIdx) : afterOpen;
|
|
112
|
+
return { viewBox, innerHtml: sanitize(inner) };
|
|
113
|
+
}
|
|
67
114
|
function ToolButton({ active, disabled, title, onClick, children }) {
|
|
68
115
|
return /* @__PURE__ */ jsx(
|
|
69
116
|
"button",
|
|
@@ -93,7 +140,15 @@ function TypeChip({ typeDef, active, onClick }) {
|
|
|
93
140
|
active ? "border-blue-400 bg-blue-50 text-blue-700 font-medium" : "border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:bg-slate-50"
|
|
94
141
|
].join(" "),
|
|
95
142
|
children: [
|
|
96
|
-
/* @__PURE__ */ jsx(
|
|
143
|
+
typeDef.shape === "svg" && typeDef.svgMarkup ? /* @__PURE__ */ jsx(
|
|
144
|
+
"svg",
|
|
145
|
+
{
|
|
146
|
+
viewBox: parseSvgMarkup(typeDef.svgMarkup).viewBox,
|
|
147
|
+
className: "w-2.5 h-2.5 shrink-0",
|
|
148
|
+
style: { color: typeDef.strokeColor },
|
|
149
|
+
dangerouslySetInnerHTML: { __html: parseSvgMarkup(typeDef.svgMarkup).innerHtml }
|
|
150
|
+
}
|
|
151
|
+
) : /* @__PURE__ */ jsx(
|
|
97
152
|
"span",
|
|
98
153
|
{
|
|
99
154
|
className: "w-2.5 h-2.5 rounded-sm shrink-0",
|
|
@@ -128,6 +183,19 @@ function Toolbar({
|
|
|
128
183
|
onLoadLibrary,
|
|
129
184
|
onRemoveLibraryGroup
|
|
130
185
|
}) {
|
|
186
|
+
const [activeGroupId, setActiveGroupId] = useState(
|
|
187
|
+
() => paletteGroups[0]?.id ?? null
|
|
188
|
+
);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (paletteGroups.length === 0) {
|
|
191
|
+
setActiveGroupId(null);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!paletteGroups.find((g) => g.id === activeGroupId)) {
|
|
195
|
+
setActiveGroupId(paletteGroups[0].id);
|
|
196
|
+
}
|
|
197
|
+
}, [paletteGroups, activeGroupId]);
|
|
198
|
+
const activeGroup = paletteGroups.find((g) => g.id === activeGroupId) ?? null;
|
|
131
199
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-white border-b border-slate-200 shadow-sm shrink-0", children: [
|
|
132
200
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1.5", children: [
|
|
133
201
|
/* @__PURE__ */ jsx(ToolButton, { title: "Seleccionar (V)", active: tool === "SELECT", onClick: () => onToolChange("SELECT"), children: /* @__PURE__ */ jsx(IconCursor, { className: "w-4 h-4" }) }),
|
|
@@ -165,21 +233,34 @@ function Toolbar({
|
|
|
165
233
|
/* @__PURE__ */ jsx(ToolButton, { title: areaShape === "polygon" ? "Cambiar a rect\xE1ngulo" : "Cambiar a pol\xEDgono", onClick: () => onToggleAreaShape?.(), children: /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: areaShape === "polygon" ? "Poly" : "Rect" }) })
|
|
166
234
|
] })
|
|
167
235
|
] }),
|
|
168
|
-
tool === "PLACE" && /* @__PURE__ */
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
236
|
+
tool === "PLACE" && paletteGroups.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-slate-100", children: [
|
|
237
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-end gap-0 overflow-x-auto bg-slate-50 border-b border-slate-200 px-2 pt-1", children: paletteGroups.map((group) => /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: /* @__PURE__ */ jsxs(
|
|
238
|
+
"button",
|
|
239
|
+
{
|
|
240
|
+
onClick: () => setActiveGroupId(group.id),
|
|
241
|
+
className: [
|
|
242
|
+
"flex items-center gap-1 px-3 py-1 text-xs font-medium rounded-t border-x border-t transition-colors whitespace-nowrap",
|
|
243
|
+
group.id === activeGroupId ? "bg-white border-slate-200 text-slate-800 -mb-px pb-[5px]" : "bg-slate-50 border-transparent text-slate-400 hover:text-slate-600 hover:bg-slate-100"
|
|
244
|
+
].join(" "),
|
|
245
|
+
children: [
|
|
246
|
+
group.name || "Sin nombre",
|
|
247
|
+
!group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsx(
|
|
248
|
+
"span",
|
|
249
|
+
{
|
|
250
|
+
role: "button",
|
|
251
|
+
title: `Eliminar "${group.name}"`,
|
|
252
|
+
onClick: (e) => {
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
onRemoveLibraryGroup(group.id);
|
|
255
|
+
},
|
|
256
|
+
className: "ml-0.5 text-slate-300 hover:text-red-400 transition-colors leading-none",
|
|
257
|
+
children: "\xD7"
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
) }, group.id)) }),
|
|
263
|
+
activeGroup && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 flex-wrap px-2 py-1.5 bg-white min-h-[36px]", children: activeGroup.types.map((typeDef) => /* @__PURE__ */ jsx(
|
|
183
264
|
TypeChip,
|
|
184
265
|
{
|
|
185
266
|
typeDef,
|
|
@@ -188,7 +269,7 @@ function Toolbar({
|
|
|
188
269
|
},
|
|
189
270
|
typeDef.id
|
|
190
271
|
)) })
|
|
191
|
-
] }
|
|
272
|
+
] })
|
|
192
273
|
] });
|
|
193
274
|
}
|
|
194
275
|
var ZOOM_MIN = 0.1;
|
|
@@ -1128,6 +1209,7 @@ function ElementNode({
|
|
|
1128
1209
|
{
|
|
1129
1210
|
d: typeDef.svgPath,
|
|
1130
1211
|
fill: fillColor,
|
|
1212
|
+
fillRule: typeDef.fillRule ?? "nonzero",
|
|
1131
1213
|
stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
|
|
1132
1214
|
strokeWidth: isSelected ? customPath.strokeWidth * 1.5 : customPath.strokeWidth,
|
|
1133
1215
|
style: { cursor: bodyCursor },
|
|
@@ -1135,6 +1217,28 @@ function ElementNode({
|
|
|
1135
1217
|
onClick: handleBodyClick
|
|
1136
1218
|
}
|
|
1137
1219
|
) }),
|
|
1220
|
+
typeDef.shape === "svg" && typeDef.svgMarkup && (() => {
|
|
1221
|
+
const parsed = parseSvgMarkup(typeDef.svgMarkup);
|
|
1222
|
+
const parts = parsed.viewBox.split(/[\s,]+/).map(Number);
|
|
1223
|
+
const vw = parts[2] ?? 100;
|
|
1224
|
+
const vh = parts[3] ?? 100;
|
|
1225
|
+
const sx = vw > 0 ? w / vw : 1;
|
|
1226
|
+
const sy = vh > 0 ? h / vh : 1;
|
|
1227
|
+
const avgScale = Math.sqrt(Math.abs(sx * sy)) || 1;
|
|
1228
|
+
return /* @__PURE__ */ jsx(
|
|
1229
|
+
"g",
|
|
1230
|
+
{
|
|
1231
|
+
transform: `translate(${x}, ${y}) scale(${sx}, ${sy})`,
|
|
1232
|
+
fill: fillColor,
|
|
1233
|
+
stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
|
|
1234
|
+
strokeWidth: isSelected ? sw / avgScale * 1.5 : sw / avgScale,
|
|
1235
|
+
style: { cursor: bodyCursor },
|
|
1236
|
+
onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
|
|
1237
|
+
onClick: handleBodyClick,
|
|
1238
|
+
dangerouslySetInnerHTML: { __html: parsed.innerHtml }
|
|
1239
|
+
}
|
|
1240
|
+
);
|
|
1241
|
+
})(),
|
|
1138
1242
|
(element.label ?? typeDef.label) && /* @__PURE__ */ jsx(
|
|
1139
1243
|
"text",
|
|
1140
1244
|
{
|
|
@@ -1633,14 +1737,35 @@ function PropertiesPanel({
|
|
|
1633
1737
|
/* @__PURE__ */ jsx("div", { className: "px-3 py-2 border-b border-slate-100 text-xs font-semibold text-slate-500 uppercase tracking-wide", children: "Propiedades" }),
|
|
1634
1738
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-4 p-3", children: [
|
|
1635
1739
|
typeDef && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1636
|
-
/* @__PURE__ */ jsx(
|
|
1740
|
+
typeDef.shape === "svg" ? /* @__PURE__ */ jsx(
|
|
1741
|
+
"svg",
|
|
1742
|
+
{
|
|
1743
|
+
viewBox: (() => {
|
|
1744
|
+
try {
|
|
1745
|
+
return typeDef.svgMarkup ? parseSvgMarkup(typeDef.svgMarkup).viewBox : "0 0 100 100";
|
|
1746
|
+
} catch {
|
|
1747
|
+
return "0 0 100 100";
|
|
1748
|
+
}
|
|
1749
|
+
})(),
|
|
1750
|
+
className: "w-3.5 h-3.5 shrink-0 border border-slate-300 rounded-sm",
|
|
1751
|
+
style: { color: typeDef.strokeColor },
|
|
1752
|
+
dangerouslySetInnerHTML: { __html: (() => {
|
|
1753
|
+
try {
|
|
1754
|
+
return typeDef.svgMarkup ? parseSvgMarkup(typeDef.svgMarkup).innerHtml : "";
|
|
1755
|
+
} catch {
|
|
1756
|
+
return "";
|
|
1757
|
+
}
|
|
1758
|
+
})() }
|
|
1759
|
+
}
|
|
1760
|
+
) : /* @__PURE__ */ jsx(
|
|
1637
1761
|
"span",
|
|
1638
1762
|
{
|
|
1639
1763
|
className: "w-3.5 h-3.5 rounded-sm shrink-0 border",
|
|
1640
1764
|
style: { background: typeDef.color, borderColor: typeDef.strokeColor }
|
|
1641
1765
|
}
|
|
1642
1766
|
),
|
|
1643
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label })
|
|
1767
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label }),
|
|
1768
|
+
typeDef.shape === "svg" && /* @__PURE__ */ jsx("span", { className: "text-[9px] uppercase tracking-wide text-slate-400 font-medium ml-auto", children: "SVG" })
|
|
1644
1769
|
] }),
|
|
1645
1770
|
/* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-0.5", children: [
|
|
1646
1771
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: "Etiqueta" }),
|
|
@@ -1975,7 +2100,23 @@ function createDefaultMap() {
|
|
|
1975
2100
|
]
|
|
1976
2101
|
};
|
|
1977
2102
|
}
|
|
1978
|
-
var
|
|
2103
|
+
var DEFAULT_LIBRARY_KEY = "venueMapEditor:libraries";
|
|
2104
|
+
function mergeLibraries(existing, incoming) {
|
|
2105
|
+
const result = { ...existing };
|
|
2106
|
+
for (const [groupId, incomingGroup] of Object.entries(incoming)) {
|
|
2107
|
+
if (result[groupId]) {
|
|
2108
|
+
const existingIds = new Set(result[groupId].objects.map((o) => o.id));
|
|
2109
|
+
const newObjects = incomingGroup.objects.filter((o) => !existingIds.has(o.id));
|
|
2110
|
+
result[groupId] = {
|
|
2111
|
+
...result[groupId],
|
|
2112
|
+
objects: [...result[groupId].objects, ...newObjects]
|
|
2113
|
+
};
|
|
2114
|
+
} else {
|
|
2115
|
+
result[groupId] = incomingGroup;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return result;
|
|
2119
|
+
}
|
|
1979
2120
|
function updateFloor(map, updatedFloor) {
|
|
1980
2121
|
return {
|
|
1981
2122
|
...map,
|
|
@@ -2015,7 +2156,9 @@ function polygonToRect(area) {
|
|
|
2015
2156
|
};
|
|
2016
2157
|
}
|
|
2017
2158
|
function VenueMapEditor({
|
|
2018
|
-
|
|
2159
|
+
domainConfigs,
|
|
2160
|
+
domainConfig,
|
|
2161
|
+
libraryStorageKey = DEFAULT_LIBRARY_KEY,
|
|
2019
2162
|
initialMap,
|
|
2020
2163
|
onChange,
|
|
2021
2164
|
width = "100%",
|
|
@@ -2029,7 +2172,13 @@ function VenueMapEditor({
|
|
|
2029
2172
|
onElementClick,
|
|
2030
2173
|
onElementTypeClick
|
|
2031
2174
|
}) {
|
|
2175
|
+
const effectiveConfigs = useMemo(() => {
|
|
2176
|
+
if (domainConfigs && domainConfigs.length > 0) return domainConfigs;
|
|
2177
|
+
if (domainConfig) return [domainConfig];
|
|
2178
|
+
return [];
|
|
2179
|
+
}, [domainConfigs, domainConfig]);
|
|
2032
2180
|
const initialMapRef = useRef(initialMap ?? createDefaultMap());
|
|
2181
|
+
const [persistedLibs, setPersistedLibs] = useLibraryStorage(libraryStorageKey);
|
|
2033
2182
|
const { map, canUndo, canRedo, push, replace, undo, redo } = useHistory(
|
|
2034
2183
|
initialMapRef.current
|
|
2035
2184
|
);
|
|
@@ -2045,31 +2194,40 @@ function VenueMapEditor({
|
|
|
2045
2194
|
const resetViewRef = useRef(() => void 0);
|
|
2046
2195
|
const importInputRef = useRef(null);
|
|
2047
2196
|
const libraryInputRef = useRef(null);
|
|
2197
|
+
const effectiveLibs = useMemo(() => ({
|
|
2198
|
+
...map.libraries ?? {},
|
|
2199
|
+
...persistedLibs
|
|
2200
|
+
}), [map.libraries, persistedLibs]);
|
|
2048
2201
|
const buildTypeDefs = useCallback(() => {
|
|
2049
|
-
const m = new Map(
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2202
|
+
const m = /* @__PURE__ */ new Map();
|
|
2203
|
+
for (const cfg of effectiveConfigs) {
|
|
2204
|
+
for (const t of cfg.elementTypes) {
|
|
2205
|
+
if (!m.has(t.id)) m.set(t.id, t);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
for (const group of Object.values(effectiveLibs)) {
|
|
2052
2209
|
for (const t of group.objects) {
|
|
2053
2210
|
if (!m.has(t.id)) m.set(t.id, t);
|
|
2054
2211
|
}
|
|
2055
2212
|
}
|
|
2056
2213
|
return m;
|
|
2057
|
-
}, [
|
|
2214
|
+
}, [effectiveConfigs, effectiveLibs]);
|
|
2058
2215
|
const elementTypeDefs = useRef(buildTypeDefs());
|
|
2059
2216
|
useEffect(() => {
|
|
2060
2217
|
elementTypeDefs.current = buildTypeDefs();
|
|
2061
2218
|
}, [buildTypeDefs]);
|
|
2062
2219
|
const paletteGroups = useMemo(() => {
|
|
2063
2220
|
const groups = [];
|
|
2064
|
-
|
|
2065
|
-
|
|
2221
|
+
for (const cfg of effectiveConfigs) {
|
|
2222
|
+
if (cfg.elementTypes.length > 0) {
|
|
2223
|
+
groups.push({ id: cfg.id, name: cfg.name, types: cfg.elementTypes, isBase: true });
|
|
2224
|
+
}
|
|
2066
2225
|
}
|
|
2067
|
-
const
|
|
2068
|
-
for (const [gid, group] of Object.entries(libs)) {
|
|
2226
|
+
for (const [gid, group] of Object.entries(effectiveLibs)) {
|
|
2069
2227
|
groups.push({ id: gid, name: group.name, types: group.objects, isBase: false });
|
|
2070
2228
|
}
|
|
2071
2229
|
return groups;
|
|
2072
|
-
}, [
|
|
2230
|
+
}, [effectiveConfigs, effectiveLibs]);
|
|
2073
2231
|
useEffect(() => {
|
|
2074
2232
|
if (activePlaceTypeId) return;
|
|
2075
2233
|
const firstType = paletteGroups[0]?.types[0];
|
|
@@ -2209,22 +2367,27 @@ function VenueMapEditor({
|
|
|
2209
2367
|
reader.onload = (e) => {
|
|
2210
2368
|
try {
|
|
2211
2369
|
const parsed = JSON.parse(e.target?.result);
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2370
|
+
const mergedPersisted = mergeLibraries(persistedLibs, parsed);
|
|
2371
|
+
setPersistedLibs(mergedPersisted);
|
|
2372
|
+
const mergedMap = mergeLibraries(map.libraries ?? {}, parsed);
|
|
2373
|
+
push({ ...map, libraries: mergedMap });
|
|
2214
2374
|
} catch {
|
|
2215
2375
|
}
|
|
2216
2376
|
};
|
|
2217
2377
|
reader.readAsText(file);
|
|
2218
2378
|
},
|
|
2219
|
-
[map, push]
|
|
2379
|
+
[map, push, persistedLibs, setPersistedLibs]
|
|
2220
2380
|
);
|
|
2221
2381
|
const handleRemoveLibraryGroup = useCallback(
|
|
2222
2382
|
(groupId) => {
|
|
2383
|
+
const newPersistedLibs = { ...persistedLibs };
|
|
2384
|
+
delete newPersistedLibs[groupId];
|
|
2385
|
+
setPersistedLibs(newPersistedLibs);
|
|
2223
2386
|
const libs = { ...map.libraries ?? {} };
|
|
2224
2387
|
delete libs[groupId];
|
|
2225
2388
|
push({ ...map, libraries: Object.keys(libs).length > 0 ? libs : void 0 });
|
|
2226
2389
|
},
|
|
2227
|
-
[map, push]
|
|
2390
|
+
[map, push, persistedLibs, setPersistedLibs]
|
|
2228
2391
|
);
|
|
2229
2392
|
const DEFAULT_WALL_THICKNESS = 8;
|
|
2230
2393
|
const handleAddWall = useCallback(
|
|
@@ -2671,6 +2834,6 @@ function VenueMapViewer({ elementStatus, onElementClick, ...rest }) {
|
|
|
2671
2834
|
);
|
|
2672
2835
|
}
|
|
2673
2836
|
|
|
2674
|
-
export { VenueMapEditor, VenueMapViewer, findNearestNode, genId, snapPoint, snapToGrid, usePanZoom };
|
|
2837
|
+
export { VenueMapEditor, VenueMapViewer, findNearestNode, genId, parseSvgMarkup, snapPoint, snapToGrid, useLibraryStorage, usePanZoom };
|
|
2675
2838
|
//# sourceMappingURL=index.mjs.map
|
|
2676
2839
|
//# sourceMappingURL=index.mjs.map
|