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
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, useState, useEffect, useImperativeHandle, createContext, useContext,
|
|
2
|
+
import { forwardRef, useState, useEffect, useImperativeHandle, createContext, useContext, useCallback, useRef, useMemo } from 'react';
|
|
3
3
|
import Swal from 'sweetalert2';
|
|
4
4
|
|
|
5
5
|
// src/components/icons/icons.tsx
|
|
@@ -902,6 +902,53 @@ function ThemeToggle() {
|
|
|
902
902
|
}
|
|
903
903
|
);
|
|
904
904
|
}
|
|
905
|
+
function useLibraryStorage(storageKey) {
|
|
906
|
+
const [libs, setLibs] = useState(() => {
|
|
907
|
+
if (!storageKey) return {};
|
|
908
|
+
try {
|
|
909
|
+
const raw = localStorage.getItem(storageKey);
|
|
910
|
+
return raw ? JSON.parse(raw) : {};
|
|
911
|
+
} catch {
|
|
912
|
+
return {};
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
const setAndPersist = useCallback(
|
|
916
|
+
(newLibs) => {
|
|
917
|
+
setLibs(newLibs);
|
|
918
|
+
if (!storageKey) return;
|
|
919
|
+
try {
|
|
920
|
+
if (Object.keys(newLibs).length === 0) {
|
|
921
|
+
localStorage.removeItem(storageKey);
|
|
922
|
+
} else {
|
|
923
|
+
localStorage.setItem(storageKey, JSON.stringify(newLibs));
|
|
924
|
+
}
|
|
925
|
+
} catch {
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
[storageKey]
|
|
929
|
+
);
|
|
930
|
+
return [libs, setAndPersist];
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/components/VenueMapEditor/utils/svgParser.ts
|
|
934
|
+
var DANGEROUS_TAGS = /\b(script|iframe|object|embed|link|style|meta)\b/gi;
|
|
935
|
+
var DANGEROUS_ATTRS = /\bon\w+\s*=/gi;
|
|
936
|
+
var DANGEROUS_HREF = /\bhref\s*=\s*["']?\s*javascript:/gi;
|
|
937
|
+
var DANGEROUS_XLINK = /\bxlink:href\s*=\s*["']?\s*javascript:/gi;
|
|
938
|
+
function sanitize(html) {
|
|
939
|
+
return html.replace(DANGEROUS_TAGS, "").replace(DANGEROUS_ATTRS, "").replace(DANGEROUS_HREF, "").replace(DANGEROUS_XLINK, "");
|
|
940
|
+
}
|
|
941
|
+
var VIEWBOX_RE = /viewBox\s*=\s*"([^"]+)"/i;
|
|
942
|
+
var SVG_OPEN_END_RE = /<svg[^>]*>/i;
|
|
943
|
+
function parseSvgMarkup(markup) {
|
|
944
|
+
const viewBoxMatch = markup.match(VIEWBOX_RE);
|
|
945
|
+
const viewBox = viewBoxMatch?.[1] ?? "0 0 100 100";
|
|
946
|
+
const svgOpenMatch = markup.match(SVG_OPEN_END_RE);
|
|
947
|
+
const afterOpen = svgOpenMatch ? markup.slice(svgOpenMatch.index + svgOpenMatch[0].length) : markup;
|
|
948
|
+
const closeIdx = afterOpen.lastIndexOf("</svg>");
|
|
949
|
+
const inner = closeIdx >= 0 ? afterOpen.slice(0, closeIdx) : afterOpen;
|
|
950
|
+
return { viewBox, innerHtml: sanitize(inner) };
|
|
951
|
+
}
|
|
905
952
|
function ToolButton({ active, disabled, title, onClick, children }) {
|
|
906
953
|
return /* @__PURE__ */ jsx(
|
|
907
954
|
"button",
|
|
@@ -931,7 +978,15 @@ function TypeChip({ typeDef, active, onClick }) {
|
|
|
931
978
|
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"
|
|
932
979
|
].join(" "),
|
|
933
980
|
children: [
|
|
934
|
-
/* @__PURE__ */ jsx(
|
|
981
|
+
typeDef.shape === "svg" && typeDef.svgMarkup ? /* @__PURE__ */ jsx(
|
|
982
|
+
"svg",
|
|
983
|
+
{
|
|
984
|
+
viewBox: parseSvgMarkup(typeDef.svgMarkup).viewBox,
|
|
985
|
+
className: "w-2.5 h-2.5 shrink-0",
|
|
986
|
+
style: { color: typeDef.strokeColor },
|
|
987
|
+
dangerouslySetInnerHTML: { __html: parseSvgMarkup(typeDef.svgMarkup).innerHtml }
|
|
988
|
+
}
|
|
989
|
+
) : /* @__PURE__ */ jsx(
|
|
935
990
|
"span",
|
|
936
991
|
{
|
|
937
992
|
className: "w-2.5 h-2.5 rounded-sm shrink-0",
|
|
@@ -966,6 +1021,19 @@ function Toolbar({
|
|
|
966
1021
|
onLoadLibrary,
|
|
967
1022
|
onRemoveLibraryGroup
|
|
968
1023
|
}) {
|
|
1024
|
+
const [activeGroupId, setActiveGroupId] = useState(
|
|
1025
|
+
() => paletteGroups[0]?.id ?? null
|
|
1026
|
+
);
|
|
1027
|
+
useEffect(() => {
|
|
1028
|
+
if (paletteGroups.length === 0) {
|
|
1029
|
+
setActiveGroupId(null);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (!paletteGroups.find((g) => g.id === activeGroupId)) {
|
|
1033
|
+
setActiveGroupId(paletteGroups[0].id);
|
|
1034
|
+
}
|
|
1035
|
+
}, [paletteGroups, activeGroupId]);
|
|
1036
|
+
const activeGroup = paletteGroups.find((g) => g.id === activeGroupId) ?? null;
|
|
969
1037
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-white border-b border-slate-200 shadow-sm shrink-0", children: [
|
|
970
1038
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-2 py-1.5", children: [
|
|
971
1039
|
/* @__PURE__ */ jsx(ToolButton, { title: "Seleccionar (V)", active: tool === "SELECT", onClick: () => onToolChange("SELECT"), children: /* @__PURE__ */ jsx(IconCursor, { className: "w-4 h-4" }) }),
|
|
@@ -1003,21 +1071,34 @@ function Toolbar({
|
|
|
1003
1071
|
/* @__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" }) })
|
|
1004
1072
|
] })
|
|
1005
1073
|
] }),
|
|
1006
|
-
tool === "PLACE" && /* @__PURE__ */
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1074
|
+
tool === "PLACE" && paletteGroups.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-slate-100", children: [
|
|
1075
|
+
/* @__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(
|
|
1076
|
+
"button",
|
|
1077
|
+
{
|
|
1078
|
+
onClick: () => setActiveGroupId(group.id),
|
|
1079
|
+
className: [
|
|
1080
|
+
"flex items-center gap-1 px-3 py-1 text-xs font-medium rounded-t border-x border-t transition-colors whitespace-nowrap",
|
|
1081
|
+
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"
|
|
1082
|
+
].join(" "),
|
|
1083
|
+
children: [
|
|
1084
|
+
group.name || "Sin nombre",
|
|
1085
|
+
!group.isBase && onRemoveLibraryGroup && /* @__PURE__ */ jsx(
|
|
1086
|
+
"span",
|
|
1087
|
+
{
|
|
1088
|
+
role: "button",
|
|
1089
|
+
title: `Eliminar "${group.name}"`,
|
|
1090
|
+
onClick: (e) => {
|
|
1091
|
+
e.stopPropagation();
|
|
1092
|
+
onRemoveLibraryGroup(group.id);
|
|
1093
|
+
},
|
|
1094
|
+
className: "ml-0.5 text-slate-300 hover:text-red-400 transition-colors leading-none",
|
|
1095
|
+
children: "\xD7"
|
|
1096
|
+
}
|
|
1097
|
+
)
|
|
1098
|
+
]
|
|
1099
|
+
}
|
|
1100
|
+
) }, group.id)) }),
|
|
1101
|
+
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(
|
|
1021
1102
|
TypeChip,
|
|
1022
1103
|
{
|
|
1023
1104
|
typeDef,
|
|
@@ -1026,7 +1107,7 @@ function Toolbar({
|
|
|
1026
1107
|
},
|
|
1027
1108
|
typeDef.id
|
|
1028
1109
|
)) })
|
|
1029
|
-
] }
|
|
1110
|
+
] })
|
|
1030
1111
|
] });
|
|
1031
1112
|
}
|
|
1032
1113
|
var ZOOM_MIN = 0.1;
|
|
@@ -1966,6 +2047,7 @@ function ElementNode({
|
|
|
1966
2047
|
{
|
|
1967
2048
|
d: typeDef.svgPath,
|
|
1968
2049
|
fill: fillColor,
|
|
2050
|
+
fillRule: typeDef.fillRule ?? "nonzero",
|
|
1969
2051
|
stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
|
|
1970
2052
|
strokeWidth: isSelected ? customPath.strokeWidth * 1.5 : customPath.strokeWidth,
|
|
1971
2053
|
style: { cursor: bodyCursor },
|
|
@@ -1973,6 +2055,28 @@ function ElementNode({
|
|
|
1973
2055
|
onClick: handleBodyClick
|
|
1974
2056
|
}
|
|
1975
2057
|
) }),
|
|
2058
|
+
typeDef.shape === "svg" && typeDef.svgMarkup && (() => {
|
|
2059
|
+
const parsed = parseSvgMarkup(typeDef.svgMarkup);
|
|
2060
|
+
const parts = parsed.viewBox.split(/[\s,]+/).map(Number);
|
|
2061
|
+
const vw = parts[2] ?? 100;
|
|
2062
|
+
const vh = parts[3] ?? 100;
|
|
2063
|
+
const sx = vw > 0 ? w / vw : 1;
|
|
2064
|
+
const sy = vh > 0 ? h / vh : 1;
|
|
2065
|
+
const avgScale = Math.sqrt(Math.abs(sx * sy)) || 1;
|
|
2066
|
+
return /* @__PURE__ */ jsx(
|
|
2067
|
+
"g",
|
|
2068
|
+
{
|
|
2069
|
+
transform: `translate(${x}, ${y}) scale(${sx}, ${sy})`,
|
|
2070
|
+
fill: fillColor,
|
|
2071
|
+
stroke: isSelected ? "#3b82f6" : typeDef.strokeColor,
|
|
2072
|
+
strokeWidth: isSelected ? sw / avgScale * 1.5 : sw / avgScale,
|
|
2073
|
+
style: { cursor: bodyCursor },
|
|
2074
|
+
onMouseDown: tool === "SELECT" && !onViewerClick ? handleBodyDown : void 0,
|
|
2075
|
+
onClick: handleBodyClick,
|
|
2076
|
+
dangerouslySetInnerHTML: { __html: parsed.innerHtml }
|
|
2077
|
+
}
|
|
2078
|
+
);
|
|
2079
|
+
})(),
|
|
1976
2080
|
(element.label ?? typeDef.label) && /* @__PURE__ */ jsx(
|
|
1977
2081
|
"text",
|
|
1978
2082
|
{
|
|
@@ -2471,14 +2575,35 @@ function PropertiesPanel({
|
|
|
2471
2575
|
/* @__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" }),
|
|
2472
2576
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-4 p-3", children: [
|
|
2473
2577
|
typeDef && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2474
|
-
/* @__PURE__ */ jsx(
|
|
2578
|
+
typeDef.shape === "svg" ? /* @__PURE__ */ jsx(
|
|
2579
|
+
"svg",
|
|
2580
|
+
{
|
|
2581
|
+
viewBox: (() => {
|
|
2582
|
+
try {
|
|
2583
|
+
return typeDef.svgMarkup ? parseSvgMarkup(typeDef.svgMarkup).viewBox : "0 0 100 100";
|
|
2584
|
+
} catch {
|
|
2585
|
+
return "0 0 100 100";
|
|
2586
|
+
}
|
|
2587
|
+
})(),
|
|
2588
|
+
className: "w-3.5 h-3.5 shrink-0 border border-slate-300 rounded-sm",
|
|
2589
|
+
style: { color: typeDef.strokeColor },
|
|
2590
|
+
dangerouslySetInnerHTML: { __html: (() => {
|
|
2591
|
+
try {
|
|
2592
|
+
return typeDef.svgMarkup ? parseSvgMarkup(typeDef.svgMarkup).innerHtml : "";
|
|
2593
|
+
} catch {
|
|
2594
|
+
return "";
|
|
2595
|
+
}
|
|
2596
|
+
})() }
|
|
2597
|
+
}
|
|
2598
|
+
) : /* @__PURE__ */ jsx(
|
|
2475
2599
|
"span",
|
|
2476
2600
|
{
|
|
2477
2601
|
className: "w-3.5 h-3.5 rounded-sm shrink-0 border",
|
|
2478
2602
|
style: { background: typeDef.color, borderColor: typeDef.strokeColor }
|
|
2479
2603
|
}
|
|
2480
2604
|
),
|
|
2481
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label })
|
|
2605
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-slate-700 truncate", children: typeDef.label }),
|
|
2606
|
+
typeDef.shape === "svg" && /* @__PURE__ */ jsx("span", { className: "text-[9px] uppercase tracking-wide text-slate-400 font-medium ml-auto", children: "SVG" })
|
|
2482
2607
|
] }),
|
|
2483
2608
|
/* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-0.5", children: [
|
|
2484
2609
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-slate-400 uppercase tracking-wide", children: "Etiqueta" }),
|
|
@@ -2813,7 +2938,23 @@ function createDefaultMap() {
|
|
|
2813
2938
|
]
|
|
2814
2939
|
};
|
|
2815
2940
|
}
|
|
2816
|
-
var
|
|
2941
|
+
var DEFAULT_LIBRARY_KEY = "venueMapEditor:libraries";
|
|
2942
|
+
function mergeLibraries(existing, incoming) {
|
|
2943
|
+
const result = { ...existing };
|
|
2944
|
+
for (const [groupId, incomingGroup] of Object.entries(incoming)) {
|
|
2945
|
+
if (result[groupId]) {
|
|
2946
|
+
const existingIds = new Set(result[groupId].objects.map((o) => o.id));
|
|
2947
|
+
const newObjects = incomingGroup.objects.filter((o) => !existingIds.has(o.id));
|
|
2948
|
+
result[groupId] = {
|
|
2949
|
+
...result[groupId],
|
|
2950
|
+
objects: [...result[groupId].objects, ...newObjects]
|
|
2951
|
+
};
|
|
2952
|
+
} else {
|
|
2953
|
+
result[groupId] = incomingGroup;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
return result;
|
|
2957
|
+
}
|
|
2817
2958
|
function updateFloor(map, updatedFloor) {
|
|
2818
2959
|
return {
|
|
2819
2960
|
...map,
|
|
@@ -2853,7 +2994,9 @@ function polygonToRect(area) {
|
|
|
2853
2994
|
};
|
|
2854
2995
|
}
|
|
2855
2996
|
function VenueMapEditor({
|
|
2856
|
-
|
|
2997
|
+
domainConfigs,
|
|
2998
|
+
domainConfig,
|
|
2999
|
+
libraryStorageKey = DEFAULT_LIBRARY_KEY,
|
|
2857
3000
|
initialMap,
|
|
2858
3001
|
onChange,
|
|
2859
3002
|
width = "100%",
|
|
@@ -2867,7 +3010,13 @@ function VenueMapEditor({
|
|
|
2867
3010
|
onElementClick,
|
|
2868
3011
|
onElementTypeClick
|
|
2869
3012
|
}) {
|
|
3013
|
+
const effectiveConfigs = useMemo(() => {
|
|
3014
|
+
if (domainConfigs && domainConfigs.length > 0) return domainConfigs;
|
|
3015
|
+
if (domainConfig) return [domainConfig];
|
|
3016
|
+
return [];
|
|
3017
|
+
}, [domainConfigs, domainConfig]);
|
|
2870
3018
|
const initialMapRef = useRef(initialMap ?? createDefaultMap());
|
|
3019
|
+
const [persistedLibs, setPersistedLibs] = useLibraryStorage(libraryStorageKey);
|
|
2871
3020
|
const { map, canUndo, canRedo, push, replace, undo, redo } = useHistory(
|
|
2872
3021
|
initialMapRef.current
|
|
2873
3022
|
);
|
|
@@ -2883,31 +3032,40 @@ function VenueMapEditor({
|
|
|
2883
3032
|
const resetViewRef = useRef(() => void 0);
|
|
2884
3033
|
const importInputRef = useRef(null);
|
|
2885
3034
|
const libraryInputRef = useRef(null);
|
|
3035
|
+
const effectiveLibs = useMemo(() => ({
|
|
3036
|
+
...map.libraries ?? {},
|
|
3037
|
+
...persistedLibs
|
|
3038
|
+
}), [map.libraries, persistedLibs]);
|
|
2886
3039
|
const buildTypeDefs = useCallback(() => {
|
|
2887
|
-
const m = new Map(
|
|
2888
|
-
const
|
|
2889
|
-
|
|
3040
|
+
const m = /* @__PURE__ */ new Map();
|
|
3041
|
+
for (const cfg of effectiveConfigs) {
|
|
3042
|
+
for (const t of cfg.elementTypes) {
|
|
3043
|
+
if (!m.has(t.id)) m.set(t.id, t);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
for (const group of Object.values(effectiveLibs)) {
|
|
2890
3047
|
for (const t of group.objects) {
|
|
2891
3048
|
if (!m.has(t.id)) m.set(t.id, t);
|
|
2892
3049
|
}
|
|
2893
3050
|
}
|
|
2894
3051
|
return m;
|
|
2895
|
-
}, [
|
|
3052
|
+
}, [effectiveConfigs, effectiveLibs]);
|
|
2896
3053
|
const elementTypeDefs = useRef(buildTypeDefs());
|
|
2897
3054
|
useEffect(() => {
|
|
2898
3055
|
elementTypeDefs.current = buildTypeDefs();
|
|
2899
3056
|
}, [buildTypeDefs]);
|
|
2900
3057
|
const paletteGroups = useMemo(() => {
|
|
2901
3058
|
const groups = [];
|
|
2902
|
-
|
|
2903
|
-
|
|
3059
|
+
for (const cfg of effectiveConfigs) {
|
|
3060
|
+
if (cfg.elementTypes.length > 0) {
|
|
3061
|
+
groups.push({ id: cfg.id, name: cfg.name, types: cfg.elementTypes, isBase: true });
|
|
3062
|
+
}
|
|
2904
3063
|
}
|
|
2905
|
-
const
|
|
2906
|
-
for (const [gid, group] of Object.entries(libs)) {
|
|
3064
|
+
for (const [gid, group] of Object.entries(effectiveLibs)) {
|
|
2907
3065
|
groups.push({ id: gid, name: group.name, types: group.objects, isBase: false });
|
|
2908
3066
|
}
|
|
2909
3067
|
return groups;
|
|
2910
|
-
}, [
|
|
3068
|
+
}, [effectiveConfigs, effectiveLibs]);
|
|
2911
3069
|
useEffect(() => {
|
|
2912
3070
|
if (activePlaceTypeId) return;
|
|
2913
3071
|
const firstType = paletteGroups[0]?.types[0];
|
|
@@ -3047,22 +3205,27 @@ function VenueMapEditor({
|
|
|
3047
3205
|
reader.onload = (e) => {
|
|
3048
3206
|
try {
|
|
3049
3207
|
const parsed = JSON.parse(e.target?.result);
|
|
3050
|
-
const
|
|
3051
|
-
|
|
3208
|
+
const mergedPersisted = mergeLibraries(persistedLibs, parsed);
|
|
3209
|
+
setPersistedLibs(mergedPersisted);
|
|
3210
|
+
const mergedMap = mergeLibraries(map.libraries ?? {}, parsed);
|
|
3211
|
+
push({ ...map, libraries: mergedMap });
|
|
3052
3212
|
} catch {
|
|
3053
3213
|
}
|
|
3054
3214
|
};
|
|
3055
3215
|
reader.readAsText(file);
|
|
3056
3216
|
},
|
|
3057
|
-
[map, push]
|
|
3217
|
+
[map, push, persistedLibs, setPersistedLibs]
|
|
3058
3218
|
);
|
|
3059
3219
|
const handleRemoveLibraryGroup = useCallback(
|
|
3060
3220
|
(groupId) => {
|
|
3221
|
+
const newPersistedLibs = { ...persistedLibs };
|
|
3222
|
+
delete newPersistedLibs[groupId];
|
|
3223
|
+
setPersistedLibs(newPersistedLibs);
|
|
3061
3224
|
const libs = { ...map.libraries ?? {} };
|
|
3062
3225
|
delete libs[groupId];
|
|
3063
3226
|
push({ ...map, libraries: Object.keys(libs).length > 0 ? libs : void 0 });
|
|
3064
3227
|
},
|
|
3065
|
-
[map, push]
|
|
3228
|
+
[map, push, persistedLibs, setPersistedLibs]
|
|
3066
3229
|
);
|
|
3067
3230
|
const DEFAULT_WALL_THICKNESS = 8;
|
|
3068
3231
|
const handleAddWall = useCallback(
|
|
@@ -3508,7 +3671,399 @@ function VenueMapViewer({ elementStatus, onElementClick, ...rest }) {
|
|
|
3508
3671
|
}
|
|
3509
3672
|
);
|
|
3510
3673
|
}
|
|
3674
|
+
var DEFAULT_ELEMENT = {
|
|
3675
|
+
id: "",
|
|
3676
|
+
label: "",
|
|
3677
|
+
shape: "rect",
|
|
3678
|
+
defaultWidth: 100,
|
|
3679
|
+
defaultHeight: 100,
|
|
3680
|
+
color: "#cccccc",
|
|
3681
|
+
strokeColor: "#000000"
|
|
3682
|
+
};
|
|
3683
|
+
var SHAPE_OPTIONS = [
|
|
3684
|
+
{ value: "rect", label: "Rectangle" },
|
|
3685
|
+
{ value: "circle", label: "Circle" },
|
|
3686
|
+
{ value: "arrow", label: "Arrow" },
|
|
3687
|
+
{ value: "path", label: "Path" },
|
|
3688
|
+
{ value: "svg", label: "SVG Markup" }
|
|
3689
|
+
];
|
|
3690
|
+
var ElementLibraryBuilder = () => {
|
|
3691
|
+
const [groups, setGroups] = useState([
|
|
3692
|
+
{ internalId: crypto.randomUUID(), name: "defaultGroup", objects: [] }
|
|
3693
|
+
]);
|
|
3694
|
+
const [activeGroupId, setActiveGroupId] = useState(groups[0].internalId);
|
|
3695
|
+
const [editingGroupId, setEditingGroupId] = useState(null);
|
|
3696
|
+
const [activeElementIndex, setActiveElementIndex] = useState(null);
|
|
3697
|
+
const [currentElement, setCurrentElement] = useState({ ...DEFAULT_ELEMENT, id: "rect_1", label: "New Rect" });
|
|
3698
|
+
const [downloadFileName, setDownloadFileName] = useState("libraries");
|
|
3699
|
+
const handleAddGroup = () => {
|
|
3700
|
+
const newGroupId = crypto.randomUUID();
|
|
3701
|
+
const newName = `group_${groups.length + 1}`;
|
|
3702
|
+
setGroups([...groups, { internalId: newGroupId, name: newName, objects: [] }]);
|
|
3703
|
+
setActiveGroupId(newGroupId);
|
|
3704
|
+
setActiveElementIndex(null);
|
|
3705
|
+
};
|
|
3706
|
+
const handleRemoveGroup = (id) => {
|
|
3707
|
+
const newGroups = groups.filter((g) => g.internalId !== id);
|
|
3708
|
+
setGroups(newGroups);
|
|
3709
|
+
if (activeGroupId === id) {
|
|
3710
|
+
if (newGroups.length > 0) {
|
|
3711
|
+
setActiveGroupId(newGroups[0].internalId);
|
|
3712
|
+
} else {
|
|
3713
|
+
setActiveGroupId("");
|
|
3714
|
+
}
|
|
3715
|
+
setActiveElementIndex(null);
|
|
3716
|
+
}
|
|
3717
|
+
};
|
|
3718
|
+
const activeGroup = groups.find((g) => g.internalId === activeGroupId);
|
|
3719
|
+
const handleSelectGroup = (gId) => {
|
|
3720
|
+
setActiveGroupId(gId);
|
|
3721
|
+
setActiveElementIndex(null);
|
|
3722
|
+
};
|
|
3723
|
+
const handleAddElement = () => {
|
|
3724
|
+
if (!activeGroup) return;
|
|
3725
|
+
const newEl = { ...DEFAULT_ELEMENT, id: `shape_${activeGroup.objects.length + 1}`, label: `Shape ${activeGroup.objects.length + 1}` };
|
|
3726
|
+
const updatedGroups = groups.map((g) => {
|
|
3727
|
+
if (g.internalId === activeGroupId) {
|
|
3728
|
+
return { ...g, objects: [...g.objects, newEl] };
|
|
3729
|
+
}
|
|
3730
|
+
return g;
|
|
3731
|
+
});
|
|
3732
|
+
setGroups(updatedGroups);
|
|
3733
|
+
setActiveElementIndex(activeGroup.objects.length);
|
|
3734
|
+
setCurrentElement(newEl);
|
|
3735
|
+
};
|
|
3736
|
+
const handleSelectElement = (idx) => {
|
|
3737
|
+
if (!activeGroup) return;
|
|
3738
|
+
setActiveElementIndex(idx);
|
|
3739
|
+
setCurrentElement(activeGroup.objects[idx]);
|
|
3740
|
+
};
|
|
3741
|
+
const handleRemoveElement = (idx) => {
|
|
3742
|
+
if (!activeGroup) return;
|
|
3743
|
+
const updatedGroups = groups.map((g) => {
|
|
3744
|
+
if (g.internalId === activeGroupId) {
|
|
3745
|
+
const newObjs = [...g.objects];
|
|
3746
|
+
newObjs.splice(idx, 1);
|
|
3747
|
+
return { ...g, objects: newObjs };
|
|
3748
|
+
}
|
|
3749
|
+
return g;
|
|
3750
|
+
});
|
|
3751
|
+
setGroups(updatedGroups);
|
|
3752
|
+
if (activeElementIndex === idx) {
|
|
3753
|
+
setActiveElementIndex(null);
|
|
3754
|
+
} else if (activeElementIndex !== null && activeElementIndex > idx) {
|
|
3755
|
+
setActiveElementIndex(activeElementIndex - 1);
|
|
3756
|
+
}
|
|
3757
|
+
};
|
|
3758
|
+
const handleSaveElement = () => {
|
|
3759
|
+
if (!activeGroup || activeElementIndex === null) return;
|
|
3760
|
+
const updatedGroups = groups.map((g) => {
|
|
3761
|
+
if (g.internalId === activeGroupId) {
|
|
3762
|
+
const newObjs = [...g.objects];
|
|
3763
|
+
newObjs[activeElementIndex] = { ...currentElement };
|
|
3764
|
+
return { ...g, objects: newObjs };
|
|
3765
|
+
}
|
|
3766
|
+
return g;
|
|
3767
|
+
});
|
|
3768
|
+
setGroups(updatedGroups);
|
|
3769
|
+
};
|
|
3770
|
+
const handleFieldChange = (field, value) => {
|
|
3771
|
+
setCurrentElement((prev) => ({ ...prev, [field]: value }));
|
|
3772
|
+
};
|
|
3773
|
+
const handleSvgMarkupChange = (value) => {
|
|
3774
|
+
handleFieldChange("svgMarkup", value);
|
|
3775
|
+
};
|
|
3776
|
+
const generatedLib = useMemo(() => {
|
|
3777
|
+
const lib = {};
|
|
3778
|
+
groups.forEach((g) => {
|
|
3779
|
+
lib[g.name] = {
|
|
3780
|
+
name: g.name,
|
|
3781
|
+
objects: g.objects
|
|
3782
|
+
};
|
|
3783
|
+
});
|
|
3784
|
+
return JSON.stringify(lib, null, 2);
|
|
3785
|
+
}, [groups]);
|
|
3786
|
+
const handleDownload = () => {
|
|
3787
|
+
const blob = new Blob([generatedLib], { type: "application/json" });
|
|
3788
|
+
const url = URL.createObjectURL(blob);
|
|
3789
|
+
const a = document.createElement("a");
|
|
3790
|
+
a.href = url;
|
|
3791
|
+
a.download = `${downloadFileName}.json`;
|
|
3792
|
+
document.body.appendChild(a);
|
|
3793
|
+
a.click();
|
|
3794
|
+
document.body.removeChild(a);
|
|
3795
|
+
URL.revokeObjectURL(url);
|
|
3796
|
+
};
|
|
3797
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-4 p-4 h-full min-h-[600px] text-sm text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-900", children: [
|
|
3798
|
+
/* @__PURE__ */ jsxs("div", { className: "w-1/4 flex flex-col gap-4 border-r dark:border-gray-700 pr-4", children: [
|
|
3799
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3800
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3801
|
+
/* @__PURE__ */ jsx("h3", { className: "font-bold", children: "Libraries (Groups)" }),
|
|
3802
|
+
/* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleAddGroup, children: "+ Group" })
|
|
3803
|
+
] }),
|
|
3804
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1 max-h-48 overflow-y-auto pr-1", children: groups.map((group) => /* @__PURE__ */ jsxs(
|
|
3805
|
+
"div",
|
|
3806
|
+
{
|
|
3807
|
+
className: `flex items-center justify-between p-2 rounded cursor-pointer ${activeGroupId === group.internalId ? "bg-indigo-100 text-indigo-900 dark:bg-indigo-900/50 dark:text-indigo-100 font-semibold" : "hover:bg-gray-100 dark:hover:bg-gray-800"}`,
|
|
3808
|
+
onClick: () => handleSelectGroup(group.internalId),
|
|
3809
|
+
children: [
|
|
3810
|
+
editingGroupId === group.internalId ? /* @__PURE__ */ jsx(
|
|
3811
|
+
Input,
|
|
3812
|
+
{
|
|
3813
|
+
autoFocus: true,
|
|
3814
|
+
value: group.name,
|
|
3815
|
+
onChange: (e) => {
|
|
3816
|
+
setGroups(groups.map((g) => g.internalId === group.internalId ? { ...g, name: e.target.value } : g));
|
|
3817
|
+
},
|
|
3818
|
+
onBlur: () => setEditingGroupId(null),
|
|
3819
|
+
onKeyDown: (e) => e.key === "Enter" && setEditingGroupId(null)
|
|
3820
|
+
}
|
|
3821
|
+
) : /* @__PURE__ */ jsx("span", { onDoubleClick: () => setEditingGroupId(group.internalId), children: group.name }),
|
|
3822
|
+
groups.length > 1 && /* @__PURE__ */ jsx("button", { onClick: (e) => {
|
|
3823
|
+
e.stopPropagation();
|
|
3824
|
+
handleRemoveGroup(group.internalId);
|
|
3825
|
+
}, className: "text-red-500 text-xs", children: "x" })
|
|
3826
|
+
]
|
|
3827
|
+
},
|
|
3828
|
+
group.internalId
|
|
3829
|
+
)) })
|
|
3830
|
+
] }),
|
|
3831
|
+
/* @__PURE__ */ jsx("hr", { className: "dark:border-gray-700" }),
|
|
3832
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 flex-grow overflow-hidden", children: [
|
|
3833
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3834
|
+
/* @__PURE__ */ jsxs("h3", { className: "font-bold", children: [
|
|
3835
|
+
"Elements in ",
|
|
3836
|
+
activeGroup?.name || "?"
|
|
3837
|
+
] }),
|
|
3838
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: handleAddElement, disabled: !activeGroup, children: "+ Element" })
|
|
3839
|
+
] }),
|
|
3840
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 overflow-y-auto flex-grow pr-1", children: [
|
|
3841
|
+
activeGroup?.objects.map((el, i) => /* @__PURE__ */ jsxs(
|
|
3842
|
+
"div",
|
|
3843
|
+
{
|
|
3844
|
+
className: `flex items-center justify-between p-2 rounded cursor-pointer ${activeElementIndex === i ? "bg-indigo-100 text-indigo-900 dark:bg-indigo-900/50 dark:text-indigo-100 font-semibold" : "hover:bg-gray-100 dark:hover:bg-gray-800"}`,
|
|
3845
|
+
onClick: () => handleSelectElement(i),
|
|
3846
|
+
children: [
|
|
3847
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
3848
|
+
el.id,
|
|
3849
|
+
" (",
|
|
3850
|
+
el.shape,
|
|
3851
|
+
")"
|
|
3852
|
+
] }),
|
|
3853
|
+
/* @__PURE__ */ jsx("button", { onClick: (e) => {
|
|
3854
|
+
e.stopPropagation();
|
|
3855
|
+
handleRemoveElement(i);
|
|
3856
|
+
}, className: "text-red-500 text-xs", children: "x" })
|
|
3857
|
+
]
|
|
3858
|
+
},
|
|
3859
|
+
i
|
|
3860
|
+
)),
|
|
3861
|
+
(!activeGroup || activeGroup.objects.length === 0) && /* @__PURE__ */ jsx("span", { className: "text-gray-400 dark:text-gray-500 italic text-xs", children: "No elements yet" })
|
|
3862
|
+
] })
|
|
3863
|
+
] })
|
|
3864
|
+
] }),
|
|
3865
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-4 px-2 overflow-y-auto", children: [
|
|
3866
|
+
/* @__PURE__ */ jsx("h3", { className: "font-bold text-lg", children: "Element Editor" }),
|
|
3867
|
+
activeElementIndex !== null ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 w-full max-w-2xl", children: [
|
|
3868
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3869
|
+
/* @__PURE__ */ jsx(
|
|
3870
|
+
Input,
|
|
3871
|
+
{
|
|
3872
|
+
label: "Element ID (unique)",
|
|
3873
|
+
value: currentElement.id,
|
|
3874
|
+
onChange: (e) => handleFieldChange("id", e.target.value)
|
|
3875
|
+
}
|
|
3876
|
+
),
|
|
3877
|
+
/* @__PURE__ */ jsx(
|
|
3878
|
+
Input,
|
|
3879
|
+
{
|
|
3880
|
+
label: "Label (display name)",
|
|
3881
|
+
value: currentElement.label,
|
|
3882
|
+
onChange: (e) => handleFieldChange("label", e.target.value)
|
|
3883
|
+
}
|
|
3884
|
+
)
|
|
3885
|
+
] }),
|
|
3886
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3887
|
+
/* @__PURE__ */ jsx(
|
|
3888
|
+
Select,
|
|
3889
|
+
{
|
|
3890
|
+
label: "Shape",
|
|
3891
|
+
options: SHAPE_OPTIONS,
|
|
3892
|
+
value: currentElement.shape,
|
|
3893
|
+
onChange: (e) => handleFieldChange("shape", e.target.value)
|
|
3894
|
+
}
|
|
3895
|
+
),
|
|
3896
|
+
/* @__PURE__ */ jsx(
|
|
3897
|
+
Input,
|
|
3898
|
+
{
|
|
3899
|
+
label: "Icon (emoji or class)",
|
|
3900
|
+
value: currentElement.icon || "",
|
|
3901
|
+
onChange: (e) => handleFieldChange("icon", e.target.value)
|
|
3902
|
+
}
|
|
3903
|
+
)
|
|
3904
|
+
] }),
|
|
3905
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3906
|
+
/* @__PURE__ */ jsx(
|
|
3907
|
+
Input,
|
|
3908
|
+
{
|
|
3909
|
+
type: "number",
|
|
3910
|
+
label: "Default Width",
|
|
3911
|
+
value: currentElement.defaultWidth,
|
|
3912
|
+
onChange: (e) => handleFieldChange("defaultWidth", parseFloat(e.target.value) || 0)
|
|
3913
|
+
}
|
|
3914
|
+
),
|
|
3915
|
+
/* @__PURE__ */ jsx(
|
|
3916
|
+
Input,
|
|
3917
|
+
{
|
|
3918
|
+
type: "number",
|
|
3919
|
+
label: "Default Height",
|
|
3920
|
+
value: currentElement.defaultHeight,
|
|
3921
|
+
onChange: (e) => handleFieldChange("defaultHeight", parseFloat(e.target.value) || 0)
|
|
3922
|
+
}
|
|
3923
|
+
)
|
|
3924
|
+
] }),
|
|
3925
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3926
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
3927
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-semibold text-gray-700", children: "Fill Color" }),
|
|
3928
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3929
|
+
/* @__PURE__ */ jsx(
|
|
3930
|
+
"input",
|
|
3931
|
+
{
|
|
3932
|
+
type: "color",
|
|
3933
|
+
className: "w-8 h-8 cursor-pointer rounded",
|
|
3934
|
+
value: currentElement.color,
|
|
3935
|
+
onChange: (e) => handleFieldChange("color", e.target.value)
|
|
3936
|
+
}
|
|
3937
|
+
),
|
|
3938
|
+
/* @__PURE__ */ jsx(
|
|
3939
|
+
Input,
|
|
3940
|
+
{
|
|
3941
|
+
value: currentElement.color,
|
|
3942
|
+
onChange: (e) => handleFieldChange("color", e.target.value)
|
|
3943
|
+
}
|
|
3944
|
+
)
|
|
3945
|
+
] })
|
|
3946
|
+
] }),
|
|
3947
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
3948
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-semibold text-gray-700", children: "Stroke Color" }),
|
|
3949
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3950
|
+
/* @__PURE__ */ jsx(
|
|
3951
|
+
"input",
|
|
3952
|
+
{
|
|
3953
|
+
type: "color",
|
|
3954
|
+
className: "w-8 h-8 cursor-pointer rounded",
|
|
3955
|
+
value: currentElement.strokeColor,
|
|
3956
|
+
onChange: (e) => handleFieldChange("strokeColor", e.target.value)
|
|
3957
|
+
}
|
|
3958
|
+
),
|
|
3959
|
+
/* @__PURE__ */ jsx(
|
|
3960
|
+
Input,
|
|
3961
|
+
{
|
|
3962
|
+
value: currentElement.strokeColor,
|
|
3963
|
+
onChange: (e) => handleFieldChange("strokeColor", e.target.value)
|
|
3964
|
+
}
|
|
3965
|
+
)
|
|
3966
|
+
] })
|
|
3967
|
+
] })
|
|
3968
|
+
] }),
|
|
3969
|
+
currentElement.shape === "path" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 border dark:border-gray-700 p-4 rounded bg-gray-50 dark:bg-gray-800/50", children: [
|
|
3970
|
+
/* @__PURE__ */ jsx("h4", { className: "font-semibold text-sm", children: "Path Config" }),
|
|
3971
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3972
|
+
/* @__PURE__ */ jsx(
|
|
3973
|
+
Input,
|
|
3974
|
+
{
|
|
3975
|
+
label: "ViewBox",
|
|
3976
|
+
placeholder: "0 0 100 100",
|
|
3977
|
+
value: currentElement.viewBox || "",
|
|
3978
|
+
onChange: (e) => handleFieldChange("viewBox", e.target.value)
|
|
3979
|
+
}
|
|
3980
|
+
),
|
|
3981
|
+
/* @__PURE__ */ jsx(
|
|
3982
|
+
Select,
|
|
3983
|
+
{
|
|
3984
|
+
label: "Fill Rule",
|
|
3985
|
+
options: [
|
|
3986
|
+
{ value: "nonzero", label: "nonzero" },
|
|
3987
|
+
{ value: "evenodd", label: "evenodd" }
|
|
3988
|
+
],
|
|
3989
|
+
value: currentElement.fillRule || "nonzero",
|
|
3990
|
+
onChange: (e) => handleFieldChange("fillRule", e.target.value)
|
|
3991
|
+
}
|
|
3992
|
+
)
|
|
3993
|
+
] }),
|
|
3994
|
+
/* @__PURE__ */ jsx(
|
|
3995
|
+
TextArea,
|
|
3996
|
+
{
|
|
3997
|
+
label: "SVG Path (d attribute)",
|
|
3998
|
+
placeholder: "M10 10 H 90 V 90 H 10 Z",
|
|
3999
|
+
value: currentElement.svgPath || "",
|
|
4000
|
+
onChange: (e) => handleFieldChange("svgPath", e.target.value),
|
|
4001
|
+
rows: 4
|
|
4002
|
+
}
|
|
4003
|
+
)
|
|
4004
|
+
] }),
|
|
4005
|
+
currentElement.shape === "svg" && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 border dark:border-amber-700/50 p-4 rounded bg-amber-50 dark:bg-amber-900/10", children: [
|
|
4006
|
+
/* @__PURE__ */ jsx("h4", { className: "font-semibold text-sm", children: "SVG Markup (Autosanitized)" }),
|
|
4007
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-amber-800 dark:text-amber-400", children: "Paste your raw SVG here. Double quotes will be converted to single quotes automatically to safely embed the string in JSON." }),
|
|
4008
|
+
/* @__PURE__ */ jsx(
|
|
4009
|
+
TextArea,
|
|
4010
|
+
{
|
|
4011
|
+
label: "raw <svg>...</svg>",
|
|
4012
|
+
value: currentElement.svgMarkup || "",
|
|
4013
|
+
onChange: (e) => handleSvgMarkupChange(e.target.value),
|
|
4014
|
+
rows: 6,
|
|
4015
|
+
placeholder: "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='50'/></svg>"
|
|
4016
|
+
}
|
|
4017
|
+
)
|
|
4018
|
+
] }),
|
|
4019
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end gap-2 mt-4 pt-4 border-t dark:border-gray-700", children: /* @__PURE__ */ jsx(Button, { onClick: handleSaveElement, children: "Save Changes to Element" }) })
|
|
4020
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-gray-400", children: "Select an element to edit or add a new one." })
|
|
4021
|
+
] }),
|
|
4022
|
+
/* @__PURE__ */ jsxs("div", { className: "w-1/3 flex flex-col gap-2 border-l dark:border-gray-700 pl-4 h-full max-h-full", children: [
|
|
4023
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between shrink-0", children: [
|
|
4024
|
+
/* @__PURE__ */ jsx("h3", { className: "font-bold", children: "Output JSON" }),
|
|
4025
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4026
|
+
/* @__PURE__ */ jsx(
|
|
4027
|
+
Input,
|
|
4028
|
+
{
|
|
4029
|
+
value: downloadFileName,
|
|
4030
|
+
onChange: (e) => setDownloadFileName(e.target.value),
|
|
4031
|
+
placeholder: "filename",
|
|
4032
|
+
title: "Filename without extension"
|
|
4033
|
+
}
|
|
4034
|
+
),
|
|
4035
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: ".json" }),
|
|
4036
|
+
/* @__PURE__ */ jsx(
|
|
4037
|
+
Button,
|
|
4038
|
+
{
|
|
4039
|
+
variant: "secondary",
|
|
4040
|
+
onClick: handleDownload,
|
|
4041
|
+
title: "Download JSON file",
|
|
4042
|
+
children: "Descargar"
|
|
4043
|
+
}
|
|
4044
|
+
),
|
|
4045
|
+
/* @__PURE__ */ jsx(
|
|
4046
|
+
Button,
|
|
4047
|
+
{
|
|
4048
|
+
variant: "secondary",
|
|
4049
|
+
onClick: () => navigator.clipboard.writeText(generatedLib),
|
|
4050
|
+
children: "Copy"
|
|
4051
|
+
}
|
|
4052
|
+
)
|
|
4053
|
+
] })
|
|
4054
|
+
] }),
|
|
4055
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden h-full pb-4", children: /* @__PURE__ */ jsx(
|
|
4056
|
+
TextArea,
|
|
4057
|
+
{
|
|
4058
|
+
readOnly: true,
|
|
4059
|
+
className: "h-full resize-none font-mono text-xs text-green-600 dark:text-green-400 bg-gray-50 dark:bg-gray-900 border-gray-200 dark:border-gray-800",
|
|
4060
|
+
value: generatedLib
|
|
4061
|
+
}
|
|
4062
|
+
) })
|
|
4063
|
+
] })
|
|
4064
|
+
] });
|
|
4065
|
+
};
|
|
3511
4066
|
|
|
3512
|
-
export { AddIcon, Alerta, AlertaAdvertencia, AlertaConfirmacion, AlertaError, AlertaExito, AlertaInfo, AlertaToast, AnimateSpin, ArchiveIcon, ArrowIcon, ArrowLeftIcon, ArrowRightIcon, BackIcon, BarsChartsIcon, BoxIcon, BuildingIcon, Button, CajasIcon, CalendarIcon, CamaraIcon, CancelIcon, CashIcon, CategorieIcon, ChartIcon, CheckCircleIcon, CheckIcon, ClockIcon, CloseIcon, CloudIcon, CopyIcon, DeleteIcon, DocumentIcon, EditIcon, FacturacionIcon, FilterIcon, FolderIcon, Form, GearIcon, HomeIcon, IconCursor, IconDownload, IconDuplicate, IconErase, IconGrid, IconHand, IconLayers, IconPlace, IconPolygon, IconRedo, IconReset, IconUndo, IconUpload, IconWall, IconZoomIn, IconZoomOut, InfoAlert, InfoIcon, Input, LifeGuardIcon, LightingIcon, Loading, LocationIcon, LogoutIcon, MenuIcon, MinusIcon, Modal, MoneyIcon, MonitorIcon, MoonIcon, NetworkIcon, NotFoundIcon, PasteIcon, PercentIcon, PrinterIcon, QuestionIcon, RestaurantMenuIcon, SaveIcon, SearchIcon, Select, ShieldIcon, SpinnerIcon, StackIcon, SunIcon, Table, TestIcon, TextArea, ThemeContext, ThemeProvider, ThemeToggle, TrashIcon, TruckIcon, UsersIcon, VenueMapEditor, VenueMapViewer, WhatsAppIcon, findNearestNode, genId, snapPoint, snapToGrid, usePanZoom, useTheme };
|
|
4067
|
+
export { AddIcon, Alerta, AlertaAdvertencia, AlertaConfirmacion, AlertaError, AlertaExito, AlertaInfo, AlertaToast, AnimateSpin, ArchiveIcon, ArrowIcon, ArrowLeftIcon, ArrowRightIcon, BackIcon, BarsChartsIcon, BoxIcon, BuildingIcon, Button, CajasIcon, CalendarIcon, CamaraIcon, CancelIcon, CashIcon, CategorieIcon, ChartIcon, CheckCircleIcon, CheckIcon, ClockIcon, CloseIcon, CloudIcon, CopyIcon, DeleteIcon, DocumentIcon, EditIcon, ElementLibraryBuilder, FacturacionIcon, FilterIcon, FolderIcon, Form, GearIcon, HomeIcon, IconCursor, IconDownload, IconDuplicate, IconErase, IconGrid, IconHand, IconLayers, IconPlace, IconPolygon, IconRedo, IconReset, IconUndo, IconUpload, IconWall, IconZoomIn, IconZoomOut, InfoAlert, InfoIcon, Input, LifeGuardIcon, LightingIcon, Loading, LocationIcon, LogoutIcon, MenuIcon, MinusIcon, Modal, MoneyIcon, MonitorIcon, MoonIcon, NetworkIcon, NotFoundIcon, PasteIcon, PercentIcon, PrinterIcon, QuestionIcon, RestaurantMenuIcon, SaveIcon, SearchIcon, Select, ShieldIcon, SpinnerIcon, StackIcon, SunIcon, Table, TestIcon, TextArea, ThemeContext, ThemeProvider, ThemeToggle, TrashIcon, TruckIcon, UsersIcon, VenueMapEditor, VenueMapViewer, WhatsAppIcon, findNearestNode, genId, parseSvgMarkup, snapPoint, snapToGrid, useLibraryStorage, usePanZoom, useTheme };
|
|
3513
4068
|
//# sourceMappingURL=index.mjs.map
|
|
3514
4069
|
//# sourceMappingURL=index.mjs.map
|