@metastringfoundation/map-list 0.1.0 → 0.1.2
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/components/Handler/LayerCompareControl.d.ts +7 -0
- package/dist/components/Handler/LayerCompareControl.d.ts.map +1 -0
- package/dist/components/Handler/LayerCompareControl.js +18 -0
- package/dist/components/Handler/MapComparisonControl.d.ts +7 -0
- package/dist/components/Handler/MapComparisonControl.d.ts.map +1 -0
- package/dist/components/Handler/MapComparisonControl.js +8 -0
- package/dist/components/core/button.js +1 -1
- package/dist/components/core/input.d.ts.map +1 -1
- package/dist/components/core/input.js +1 -1
- package/dist/components/dualMap/MapLayerCard.d.ts +8 -0
- package/dist/components/dualMap/MapLayerCard.d.ts.map +1 -0
- package/dist/components/dualMap/MapLayerCard.js +68 -0
- package/dist/components/dualMap/index.d.ts +3 -0
- package/dist/components/dualMap/index.d.ts.map +1 -0
- package/dist/components/dualMap/index.js +277 -0
- package/dist/components/map/compare-sidebar.d.ts +2 -0
- package/dist/components/map/compare-sidebar.d.ts.map +1 -0
- package/dist/components/map/compare-sidebar.js +34 -0
- package/dist/components/map/compare.d.ts +2 -0
- package/dist/components/map/compare.d.ts.map +1 -0
- package/dist/components/map/compare.js +42 -0
- package/dist/components/map/index.d.ts.map +1 -1
- package/dist/components/map/index.js +14 -1
- package/dist/components/map/layers/grid/index.d.ts.map +1 -1
- package/dist/components/map/layers/grid/index.js +11 -1
- package/dist/components/map/layers/index.d.ts.map +1 -1
- package/dist/components/map/layers/index.js +17 -14
- package/dist/components/map/layers/raster/index.d.ts.map +1 -1
- package/dist/components/map/layers/raster/index.js +8 -8
- package/dist/components/map/layers/vector/index.d.ts.map +1 -1
- package/dist/components/map/layers/vector/index.js +25 -1
- package/dist/components/map/search.d.ts +2 -0
- package/dist/components/map/search.d.ts.map +1 -0
- package/dist/components/map/search.js +122 -0
- package/dist/components/map/split-map/SplitMapComparision.d.ts +2 -0
- package/dist/components/map/split-map/SplitMapComparision.d.ts.map +1 -0
- package/dist/components/map/split-map/SplitMapComparision.js +78 -0
- package/dist/components/map-comparison/attribute-selector.d.ts +11 -0
- package/dist/components/map-comparison/attribute-selector.d.ts.map +1 -0
- package/dist/components/map-comparison/attribute-selector.js +10 -0
- package/dist/components/map-comparison/comparison-panel.d.ts +2 -0
- package/dist/components/map-comparison/comparison-panel.d.ts.map +1 -0
- package/dist/components/map-comparison/comparison-panel.js +45 -0
- package/dist/components/map-comparison/comparison-stats.d.ts +2 -0
- package/dist/components/map-comparison/comparison-stats.d.ts.map +1 -0
- package/dist/components/map-comparison/comparison-stats.js +35 -0
- package/dist/components/map-comparison/comparison-view.d.ts +2 -0
- package/dist/components/map-comparison/comparison-view.d.ts.map +1 -0
- package/dist/components/map-comparison/comparison-view.js +152 -0
- package/dist/components/map-comparison/comparison-wrapper.d.ts +8 -0
- package/dist/components/map-comparison/comparison-wrapper.d.ts.map +1 -0
- package/dist/components/map-comparison/comparison-wrapper.js +26 -0
- package/dist/components/map-comparison/index.d.ts +3 -0
- package/dist/components/map-comparison/index.d.ts.map +1 -0
- package/dist/components/map-comparison/index.js +165 -0
- package/dist/components/map-comparison/types.d.ts +30 -0
- package/dist/components/map-comparison/types.d.ts.map +1 -0
- package/dist/components/map-comparison/types.js +2 -0
- package/dist/components/map-comparison/use-map-comparison.d.ts +18 -0
- package/dist/components/map-comparison/use-map-comparison.d.ts.map +1 -0
- package/dist/components/map-comparison/use-map-comparison.js +139 -0
- package/dist/components/sidebar/common/attribute-compare-addon.d.ts +7 -0
- package/dist/components/sidebar/common/attribute-compare-addon.d.ts.map +1 -0
- package/dist/components/sidebar/common/attribute-compare-addon.js +36 -0
- package/dist/components/sidebar/common/layer-compare-addon.d.ts +4 -0
- package/dist/components/sidebar/common/layer-compare-addon.d.ts.map +1 -0
- package/dist/components/sidebar/common/layer-compare-addon.js +17 -0
- package/dist/components/sidebar/common/layer-item-style.d.ts.map +1 -1
- package/dist/components/sidebar/common/layer-item-style.js +46 -1
- package/dist/components/sidebar/common/layer-item.d.ts.map +1 -1
- package/dist/components/sidebar/common/layer-item.js +1 -1
- package/dist/components/sidebar/common/opacity-handler-addon.d.ts +5 -2
- package/dist/components/sidebar/common/opacity-handler-addon.d.ts.map +1 -1
- package/dist/components/sidebar/common/opacity-handler-addon.js +101 -4
- package/dist/components/sidebar/common/style-legend.d.ts.map +1 -1
- package/dist/components/sidebar/common/style-legend.js +1 -2
- package/dist/components/sidebar/comparison/index.d.ts +4 -0
- package/dist/components/sidebar/comparison/index.d.ts.map +1 -0
- package/dist/components/sidebar/comparison/index.js +34 -0
- package/dist/components/sidebar/index.d.ts.map +1 -1
- package/dist/components/sidebar/search/attribute-comparison.d.ts +10 -0
- package/dist/components/sidebar/search/attribute-comparison.d.ts.map +1 -0
- package/dist/components/sidebar/search/attribute-comparison.js +137 -0
- package/dist/components/sidebar/search/index.d.ts +2 -0
- package/dist/components/sidebar/search/index.d.ts.map +1 -0
- package/dist/components/sidebar/search/index.js +29 -0
- package/dist/components/sidebar/selected/index.d.ts.map +1 -1
- package/dist/components/sidebar/selected/index.js +7 -4
- package/dist/components/sidebar/tabs.js +2 -2
- package/dist/hooks/use-layers.d.ts +10 -0
- package/dist/hooks/use-layers.d.ts.map +1 -1
- package/dist/hooks/use-layers.js +115 -5
- package/dist/hooks/use-polygon-data.d.ts +34 -0
- package/dist/hooks/use-polygon-data.d.ts.map +1 -0
- package/dist/hooks/use-polygon-data.js +74 -0
- package/dist/index.css +708 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/services/naksha.d.ts.map +1 -1
- package/dist/services/naksha.js +22 -158
- package/dist/services/polygon.d.ts +36 -0
- package/dist/services/polygon.d.ts.map +1 -0
- package/dist/services/polygon.js +67 -0
- package/dist/utils/naksha.d.ts.map +1 -1
- package/dist/utils/naksha.js +15 -6
- package/package.json +16 -15
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.default = ComparisonView;
|
|
41
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
42
|
+
const react_1 = require("react");
|
|
43
|
+
const maplibre_1 = __importStar(require("react-map-gl/maplibre"));
|
|
44
|
+
const map_common_1 = require("@metastringfoundation/map-common");
|
|
45
|
+
const use_map_comparison_1 = require("./use-map-comparison");
|
|
46
|
+
const layers_1 = require("../map/layers");
|
|
47
|
+
const popup_1 = __importDefault(require("../map/popup"));
|
|
48
|
+
const use_layers_1 = __importDefault(require("../../hooks/use-layers"));
|
|
49
|
+
const comparison_stats_1 = __importDefault(require("./comparison-stats"));
|
|
50
|
+
const NavControl = maplibre_1.NavigationControl;
|
|
51
|
+
function ComparisonView() {
|
|
52
|
+
const { config } = (0, use_map_comparison_1.useMapComparison)();
|
|
53
|
+
const { mp, layer } = (0, use_layers_1.default)();
|
|
54
|
+
const [coordsByPane, setCoordsByPane] = (0, react_1.useState)({});
|
|
55
|
+
const [viewStates, setViewStates] = (0, react_1.useState)({});
|
|
56
|
+
const initialViewState = (0, react_1.useMemo)(() => mp.defaultViewState ?? map_common_1.defaultViewState, [mp.defaultViewState]);
|
|
57
|
+
// Initialize view states
|
|
58
|
+
(0, react_1.useEffect)(() => {
|
|
59
|
+
if (config.layers.length > 0) {
|
|
60
|
+
const newViewStates = {};
|
|
61
|
+
config.layers.forEach((compLayer) => {
|
|
62
|
+
if (!viewStates[compLayer.layerId]) {
|
|
63
|
+
newViewStates[compLayer.layerId] = initialViewState;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (Object.keys(newViewStates).length > 0) {
|
|
67
|
+
setViewStates((prev) => ({ ...prev, ...newViewStates }));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, [config.layers, initialViewState]);
|
|
71
|
+
// Sync view states if enabled
|
|
72
|
+
const handleViewStateChange = (layerId, viewState) => {
|
|
73
|
+
if (config.syncView && config.layers.length > 1) {
|
|
74
|
+
// Sync all maps to the same view state
|
|
75
|
+
const syncedViewStates = {};
|
|
76
|
+
config.layers.forEach((l) => {
|
|
77
|
+
syncedViewStates[l.layerId] = viewState;
|
|
78
|
+
});
|
|
79
|
+
setViewStates((prev) => ({ ...prev, ...syncedViewStates }));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Update only the specific map
|
|
83
|
+
setViewStates((prev) => ({ ...prev, [layerId]: viewState }));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const onMouseMove = (layerId) => (e) => {
|
|
87
|
+
if (!e.lngLat)
|
|
88
|
+
return;
|
|
89
|
+
setCoordsByPane((prev) => ({
|
|
90
|
+
...prev,
|
|
91
|
+
[layerId]: e.lngLat,
|
|
92
|
+
}));
|
|
93
|
+
};
|
|
94
|
+
if (config.layers.length === 0) {
|
|
95
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "w-full h-screen flex items-center justify-center bg-gray-100", children: (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-gray-500 text-lg mb-2", children: "No layers selected for comparison" }), (0, jsx_runtime_1.jsx)("p", { className: "text-gray-400 text-sm", children: "Add layers from the comparison panel to get started" })] }) }));
|
|
96
|
+
}
|
|
97
|
+
// Prepare layers with attribute/style modifications
|
|
98
|
+
const preparedLayers = (0, react_1.useMemo)(() => {
|
|
99
|
+
return config.layers.map((compLayer) => {
|
|
100
|
+
const baseLayer = { ...compLayer.layer };
|
|
101
|
+
// If attribute-based comparison, modify the layer to show specific attribute
|
|
102
|
+
if (compLayer.attributeKey && compLayer.attributeKey.startsWith("style_")) {
|
|
103
|
+
const styleIndex = parseInt(compLayer.attributeKey.replace("style_", ""));
|
|
104
|
+
if (baseLayer.data) {
|
|
105
|
+
baseLayer.data = {
|
|
106
|
+
...baseLayer.data,
|
|
107
|
+
styleIndex: styleIndex,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...baseLayer,
|
|
113
|
+
id: `compare-${compLayer.layerId}`,
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}, [config.layers]);
|
|
117
|
+
// Render based on mode
|
|
118
|
+
if (config.mode === "overlay") {
|
|
119
|
+
// Overlay mode: stack all layers on a single map
|
|
120
|
+
const overlayViewState = viewStates[config.layers[0]?.layerId] || initialViewState;
|
|
121
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full h-screen relative", children: [config.showStats && (0, jsx_runtime_1.jsx)(comparison_stats_1.default, {}), (0, jsx_runtime_1.jsxs)(maplibre_1.default, { id: "comparison-overlay", initialViewState: overlayViewState, style: { width: "100%", height: "100%" }, mapStyle: layer.mapStyle, onMove: (e) => {
|
|
122
|
+
if (config.layers[0]) {
|
|
123
|
+
handleViewStateChange(config.layers[0].layerId, e.viewState);
|
|
124
|
+
}
|
|
125
|
+
}, children: [(0, jsx_runtime_1.jsx)(NavControl, { position: "top-right", showZoom: true, showCompass: true }), preparedLayers.map((l, index) => {
|
|
126
|
+
const beforeId = index > 0 ? preparedLayers[index - 1].id : undefined;
|
|
127
|
+
return (0, jsx_runtime_1.jsx)(layers_1.MapLayer, { layer: l, beforeId: beforeId }, l.id);
|
|
128
|
+
}), (0, jsx_runtime_1.jsx)(popup_1.default, { coordinates: coordsByPane["overlay"] })] })] }));
|
|
129
|
+
}
|
|
130
|
+
// Side-by-side or attribute mode: show multiple maps
|
|
131
|
+
const layout = (0, react_1.useMemo)(() => {
|
|
132
|
+
const count = config.layers.length;
|
|
133
|
+
if (count === 1)
|
|
134
|
+
return { cols: 1, rows: 1 };
|
|
135
|
+
if (count === 2)
|
|
136
|
+
return { cols: 2, rows: 1 };
|
|
137
|
+
if (count === 3)
|
|
138
|
+
return { cols: 2, rows: 2 };
|
|
139
|
+
return { cols: 2, rows: Math.ceil(count / 2) };
|
|
140
|
+
}, [config.layers.length]);
|
|
141
|
+
const renderMap = (compLayer, index) => {
|
|
142
|
+
const preparedLayer = preparedLayers[index];
|
|
143
|
+
const viewState = viewStates[compLayer.layerId] || initialViewState;
|
|
144
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "relative w-full h-full", children: [(0, jsx_runtime_1.jsxs)("div", { className: "absolute top-2 left-2 z-10 bg-black/70 text-white px-3 py-1 rounded-md text-sm font-medium", children: [compLayer.layer.title, compLayer.attributeLabel && ((0, jsx_runtime_1.jsxs)("span", { className: "ml-2 text-xs text-gray-300", children: ["(", compLayer.attributeLabel, ")"] }))] }), (0, jsx_runtime_1.jsxs)(maplibre_1.default, { id: `compare-${compLayer.layerId}`, initialViewState: viewState, style: { position: "absolute", inset: 0 }, mapStyle: layer.mapStyle, onMouseMove: onMouseMove(compLayer.layerId), onMove: (e) => handleViewStateChange(compLayer.layerId, e.viewState), children: [(0, jsx_runtime_1.jsx)(NavControl, { position: "top-right", showZoom: true, showCompass: true }), (0, jsx_runtime_1.jsx)(layers_1.MapLayer, { layer: preparedLayer }), (0, jsx_runtime_1.jsx)(popup_1.default, { coordinates: coordsByPane[compLayer.layerId] })] }, config.syncView ? `sync-${JSON.stringify(viewState)}` : compLayer.layerId)] }, compLayer.layerId));
|
|
145
|
+
};
|
|
146
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full h-screen relative", children: [config.showStats && (0, jsx_runtime_1.jsx)(comparison_stats_1.default, {}), (0, jsx_runtime_1.jsx)("div", { style: {
|
|
147
|
+
display: "grid",
|
|
148
|
+
gridTemplateColumns: `repeat(${layout.cols}, 1fr)`,
|
|
149
|
+
gridTemplateRows: `repeat(${layout.rows}, ${100 / layout.rows}vh)`,
|
|
150
|
+
height: "100vh",
|
|
151
|
+
}, children: config.layers.map((compLayer, index) => renderMap(compLayer, index)) })] }));
|
|
152
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map Comparison Wrapper Component
|
|
3
|
+
*
|
|
4
|
+
* This component works within the existing LayersProvider context
|
|
5
|
+
* Use this when you already have MapProvider and LayersProvider set up
|
|
6
|
+
*/
|
|
7
|
+
export default function ComparisonWrapper(): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=comparison-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison-wrapper.d.ts","sourceRoot":"","sources":["../../../src/components/map-comparison/comparison-wrapper.tsx"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,4CAcxC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.default = ComparisonWrapper;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const comparison_panel_1 = __importDefault(require("./comparison-panel"));
|
|
11
|
+
const comparison_view_1 = __importDefault(require("./comparison-view"));
|
|
12
|
+
const use_map_comparison_1 = require("./use-map-comparison");
|
|
13
|
+
/**
|
|
14
|
+
* Map Comparison Wrapper Component
|
|
15
|
+
*
|
|
16
|
+
* This component works within the existing LayersProvider context
|
|
17
|
+
* Use this when you already have MapProvider and LayersProvider set up
|
|
18
|
+
*/
|
|
19
|
+
function ComparisonWrapper() {
|
|
20
|
+
const { setIsOpen } = (0, use_map_comparison_1.useMapComparison)();
|
|
21
|
+
// Auto-open the comparison panel when comparison mode is activated
|
|
22
|
+
(0, react_1.useEffect)(() => {
|
|
23
|
+
setIsOpen(true);
|
|
24
|
+
}, [setIsOpen]);
|
|
25
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "w-full h-screen relative", children: [(0, jsx_runtime_1.jsx)(comparison_panel_1.default, {}), (0, jsx_runtime_1.jsx)(comparison_view_1.default, {})] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/map-comparison/index.tsx"],"names":[],"mappings":"AAsJA,QAAA,MAAM,aAAa,+CAoQlB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
// @ts-nocheck
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const use_layers_1 = __importDefault(require("../../hooks/use-layers"));
|
|
10
|
+
const core_1 = require("../core");
|
|
11
|
+
const constants_1 = require("../../static/constants");
|
|
12
|
+
const naksha_1 = require("../../utils/naksha");
|
|
13
|
+
const LayerCard = ({ layer, selectedAttributes, isLoadingAttributes, onDropdownToggle, onAttributeToggle }) => {
|
|
14
|
+
const [isDropdownOpen, setIsDropdownOpen] = (0, react_1.useState)(false);
|
|
15
|
+
const attributes = (0, react_1.useMemo)(() => {
|
|
16
|
+
const propertyMap = layer.data?.propertyMap || {};
|
|
17
|
+
return Object.entries(propertyMap).map(([key, displayName]) => ({
|
|
18
|
+
key,
|
|
19
|
+
displayName: displayName || key,
|
|
20
|
+
}));
|
|
21
|
+
}, [layer.data?.propertyMap]);
|
|
22
|
+
const selectedCount = selectedAttributes.size;
|
|
23
|
+
const totalCount = attributes.length;
|
|
24
|
+
const handleDropdownClick = () => {
|
|
25
|
+
const wasOpen = isDropdownOpen;
|
|
26
|
+
const newState = !isDropdownOpen;
|
|
27
|
+
setIsDropdownOpen(newState);
|
|
28
|
+
// Fetch data when opening the dropdown (was closed, now opening)
|
|
29
|
+
if (newState && !wasOpen) {
|
|
30
|
+
onDropdownToggle();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const formatDate = (dateString) => {
|
|
34
|
+
if (!dateString)
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
return new Date(dateString).toLocaleDateString();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "p-3 border-b border-gray-200 bg-white hover:bg-gray-50 transition-colors", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex gap-3 mb-3", children: [(0, jsx_runtime_1.jsx)("img", { className: "flex shrink-0 overflow-hidden h-16 w-16 object-cover border border-gray-300 rounded shadow-sm", src: layer.thumbnail || constants_1.FALLBACK_THUMB, alt: layer.title }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("h3", { className: "font-semibold text-gray-900 mb-1 leading-tight truncate", title: layer.title, children: layer.title }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-600 line-clamp-2 mb-2", title: layer.description, children: layer.description || "No description available" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-sm text-gray-500", children: [layer.createdBy && ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: "By:" }), (0, jsx_runtime_1.jsx)("span", { children: layer.createdBy })] })), formatDate(layer.createdDate) && ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: "Date:" }), (0, jsx_runtime_1.jsx)("span", { children: formatDate(layer.createdDate) })] })), layer.license && ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: "License:" }), (0, jsx_runtime_1.jsx)("span", { children: layer.license })] })), layer.tags && layer.tags.length > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("span", { className: "font-medium", children: "Tags:" }), (0, jsx_runtime_1.jsxs)("span", { children: [layer.tags.slice(0, 2).join(", "), layer.tags.length > 2 ? "..." : ""] })] }))] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: handleDropdownClick, className: "w-full flex items-center justify-between px-3 py-2 text-sm font-medium text-gray-700 bg-gray-50 hover:bg-gray-100 border border-gray-300 rounded transition-colors", children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { children: "Attributes" }), selectedCount > 0 && ((0, jsx_runtime_1.jsxs)("span", { className: "px-1.5 py-0.5 bg-green-100 text-green-700 rounded text-sm font-semibold", children: [selectedCount, "/", totalCount] }))] }), isDropdownOpen ? (0, jsx_runtime_1.jsx)(core_1.UpIcon, {}) : (0, jsx_runtime_1.jsx)(core_1.DownIcon, {})] }), isDropdownOpen && ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 border border-gray-300 rounded bg-white max-h-64 overflow-y-auto shadow-sm scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100", children: isLoadingAttributes ? ((0, jsx_runtime_1.jsx)("div", { className: "px-3 py-4 text-sm text-gray-500 italic text-center", children: "Loading attributes..." })) : attributes.length > 0 ? (attributes.map((attr) => ((0, jsx_runtime_1.jsxs)("label", { className: "flex items-center gap-2 px-3 py-2 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0 transition-colors", children: [(0, jsx_runtime_1.jsx)(core_1.CheckboxInput, { name: `${layer.id}-${attr.key}`, checked: selectedAttributes.has(attr.key), onChange: () => {
|
|
44
|
+
onAttributeToggle(attr.key);
|
|
45
|
+
}, style: { accentColor: '#16a34a' } }), (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-gray-700 flex-1", children: attr.displayName })] }, attr.key)))) : ((0, jsx_runtime_1.jsx)("div", { className: "px-3 py-4 text-sm text-gray-500 italic text-center", children: "No attributes available for this layer" })) }))] })] }));
|
|
46
|
+
};
|
|
47
|
+
const MapComparison = () => {
|
|
48
|
+
const { layer, mp } = (0, use_layers_1.default)();
|
|
49
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
50
|
+
const [searchTerm, setSearchTerm] = (0, react_1.useState)("");
|
|
51
|
+
const [compareSelectedLayers, setCompareSelectedLayers] = (0, react_1.useState)(new Set());
|
|
52
|
+
const [selectedAttributes, setSelectedAttributes] = (0, react_1.useState)({});
|
|
53
|
+
const [loadingAttributes, setLoadingAttributes] = (0, react_1.useState)({});
|
|
54
|
+
const [layersWithData, setLayersWithData] = (0, react_1.useState)({});
|
|
55
|
+
const fetchingRef = (0, react_1.useRef)(new Set());
|
|
56
|
+
const toggleOpen = () => setIsOpen(!isOpen);
|
|
57
|
+
// Filter layers based on search term
|
|
58
|
+
const filteredLayers = (0, react_1.useMemo)(() => {
|
|
59
|
+
if (!searchTerm.trim()) {
|
|
60
|
+
return layer.all;
|
|
61
|
+
}
|
|
62
|
+
const term = searchTerm.toLowerCase();
|
|
63
|
+
return layer.all.filter((l) => {
|
|
64
|
+
return (l.title?.toLowerCase().includes(term) ||
|
|
65
|
+
l.description?.toLowerCase().includes(term) ||
|
|
66
|
+
l.createdBy?.toLowerCase().includes(term) ||
|
|
67
|
+
l.tags?.some((tag) => tag.toLowerCase().includes(term)));
|
|
68
|
+
});
|
|
69
|
+
}, [layer.all, searchTerm]);
|
|
70
|
+
const handleCompareToggle = (layerId) => {
|
|
71
|
+
setCompareSelectedLayers((prev) => {
|
|
72
|
+
const newSet = new Set(prev);
|
|
73
|
+
if (newSet.has(layerId)) {
|
|
74
|
+
newSet.delete(layerId);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
newSet.add(layerId);
|
|
78
|
+
}
|
|
79
|
+
return newSet;
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
const fetchLayerAttributes = async (layerId) => {
|
|
83
|
+
const layerItem = layer.all.find((l) => l.id === layerId);
|
|
84
|
+
if (!layerItem)
|
|
85
|
+
return;
|
|
86
|
+
// Skip if already fetched or currently loading
|
|
87
|
+
if (layersWithData[layerId] || fetchingRef.current.has(layerId))
|
|
88
|
+
return;
|
|
89
|
+
// If layer already has propertyMap, use it directly
|
|
90
|
+
if (layerItem.data?.propertyMap && Object.keys(layerItem.data.propertyMap).length > 0) {
|
|
91
|
+
setLayersWithData((prev) => ({
|
|
92
|
+
...prev,
|
|
93
|
+
[layerId]: layerItem,
|
|
94
|
+
}));
|
|
95
|
+
setSelectedAttributes((prev) => ({
|
|
96
|
+
...prev,
|
|
97
|
+
[layerId]: new Set(),
|
|
98
|
+
}));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
fetchingRef.current.add(layerId);
|
|
102
|
+
setLoadingAttributes((prev) => ({ ...prev, [layerId]: true }));
|
|
103
|
+
try {
|
|
104
|
+
const layerData = await (0, naksha_1.getLayerStyle)(layerItem, 0, // styleIndex
|
|
105
|
+
mp.nakshaApiEndpoint, mp.geoserver);
|
|
106
|
+
const updatedLayer = {
|
|
107
|
+
...layerItem,
|
|
108
|
+
data: layerData,
|
|
109
|
+
};
|
|
110
|
+
setLayersWithData((prev) => ({
|
|
111
|
+
...prev,
|
|
112
|
+
[layerId]: updatedLayer,
|
|
113
|
+
}));
|
|
114
|
+
setSelectedAttributes((prev) => ({
|
|
115
|
+
...prev,
|
|
116
|
+
[layerId]: new Set(),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error("Error fetching layer attributes:", error);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
fetchingRef.current.delete(layerId);
|
|
124
|
+
setLoadingAttributes((prev) => ({ ...prev, [layerId]: false }));
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const handleAttributeToggle = (layerId, attributeKey) => {
|
|
128
|
+
setSelectedAttributes((prev) => {
|
|
129
|
+
const layerAttributes = prev[layerId] || new Set();
|
|
130
|
+
const newSet = new Set(layerAttributes);
|
|
131
|
+
if (newSet.has(attributeKey)) {
|
|
132
|
+
newSet.delete(attributeKey);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
newSet.add(attributeKey);
|
|
136
|
+
// Auto-check the main compare checkbox when any attribute is selected
|
|
137
|
+
if (!compareSelectedLayers.has(layerId)) {
|
|
138
|
+
setCompareSelectedLayers((prevCompare) => {
|
|
139
|
+
const newCompareSet = new Set(prevCompare);
|
|
140
|
+
newCompareSet.add(layerId);
|
|
141
|
+
return newCompareSet;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...prev,
|
|
147
|
+
[layerId]: newSet,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
const handleCompare = () => {
|
|
152
|
+
// TODO: Implement comparison logic
|
|
153
|
+
console.log("Comparing layers:", Array.from(compareSelectedLayers));
|
|
154
|
+
console.log("Selected attributes:", selectedAttributes);
|
|
155
|
+
// This will trigger the actual comparison functionality
|
|
156
|
+
};
|
|
157
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { onClick: toggleOpen, type: "button", className: "absolute left-4 top-[60px] z-10 bg-white rounded-lg shadow-lg flex items-center gap-2 font-medium hover:bg-gray-50 transition-colors border border-gray-200 px-4 py-3", children: [(0, jsx_runtime_1.jsxs)("svg", { width: "18", height: "18", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", className: "text-green-600", children: [(0, jsx_runtime_1.jsx)("path", { d: "M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" }), (0, jsx_runtime_1.jsx)("line", { x1: "12", y1: "2", x2: "12", y2: "22" }), (0, jsx_runtime_1.jsx)("line", { x1: "2", y1: "12", x2: "22", y2: "12" })] }), (0, jsx_runtime_1.jsx)("span", { children: "Compare" })] }), isOpen && ((0, jsx_runtime_1.jsx)("div", { className: "fixed inset-0 flex items-center justify-center z-50 p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-white rounded-lg shadow-2xl w-full max-w-3xl h-[85vh] flex flex-col overflow-hidden border border-gray-200", children: [(0, jsx_runtime_1.jsx)("div", { className: "px-6 py-4 border-b border-gray-200 bg-gray-50", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 shrink-0", children: [(0, jsx_runtime_1.jsxs)("svg", { width: "20", height: "20", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", className: "text-green-600", children: [(0, jsx_runtime_1.jsx)("path", { d: "M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" }), (0, jsx_runtime_1.jsx)("line", { x1: "12", y1: "2", x2: "12", y2: "22" }), (0, jsx_runtime_1.jsx)("line", { x1: "2", y1: "12", x2: "22", y2: "12" })] }), (0, jsx_runtime_1.jsx)("h2", { className: "text-xl font-semibold text-gray-800 whitespace-nowrap", children: "Map Comparison" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 flex-1 min-w-0 max-w-md", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex-1 min-w-0", children: (0, jsx_runtime_1.jsx)(core_1.SearchInput, { placeholder: "Search layers...", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), (0, jsx_runtime_1.jsx)("button", { onClick: toggleOpen, type: "button", className: "flex items-center justify-center w-8 h-8 bg-red-100 hover:bg-red-200 text-red-800 rounded transition-colors shrink-0", title: "Close", children: (0, jsx_runtime_1.jsxs)("svg", { width: "18", height: "18", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", viewBox: "0 0 24 24", children: [(0, jsx_runtime_1.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), (0, jsx_runtime_1.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })] })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 min-h-0 overflow-y-auto bg-white", children: filteredLayers.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-center h-full min-h-[400px] p-8 text-center text-gray-500", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "mb-2 text-base", children: searchTerm ? "No layers found" : "No layers available" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm", children: searchTerm
|
|
158
|
+
? "Try adjusting your search terms"
|
|
159
|
+
: "Layers will appear here when available" })] }) })) : (filteredLayers.map((layerItem) => {
|
|
160
|
+
// Use fetched layer data if available, otherwise use original
|
|
161
|
+
const layerData = layersWithData[layerItem.id] || layerItem;
|
|
162
|
+
return ((0, jsx_runtime_1.jsx)(LayerCard, { layer: layerData, isLoadingAttributes: loadingAttributes[layerItem.id] || false, selectedAttributes: selectedAttributes[layerItem.id] || new Set(), onDropdownToggle: () => fetchLayerAttributes(layerItem.id), onAttributeToggle: (attributeKey) => handleAttributeToggle(layerItem.id, attributeKey) }, layerItem.id));
|
|
163
|
+
})) }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-200 bg-gray-50", children: (0, jsx_runtime_1.jsx)("button", { onClick: handleCompare, type: "button", className: "px-5 py-2.5 text-white bg-green-600 hover:bg-green-700 rounded-md transition-colors font-medium text-sm shadow-sm", children: "Compare" }) })] }) }))] }));
|
|
164
|
+
};
|
|
165
|
+
exports.default = MapComparison;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GeoserverLayer } from "../../interfaces";
|
|
2
|
+
export type ComparisonMode = "side-by-side" | "overlay" | "attribute";
|
|
3
|
+
export interface ComparisonLayer {
|
|
4
|
+
layerId: string;
|
|
5
|
+
layer: GeoserverLayer;
|
|
6
|
+
attributeKey?: string;
|
|
7
|
+
attributeLabel?: string;
|
|
8
|
+
styleIndex?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ComparisonConfig {
|
|
11
|
+
mode: ComparisonMode;
|
|
12
|
+
layers: ComparisonLayer[];
|
|
13
|
+
syncView: boolean;
|
|
14
|
+
showStats: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ComparisonStats {
|
|
17
|
+
layerId: string;
|
|
18
|
+
attributeKey?: string;
|
|
19
|
+
totalFeatures?: number;
|
|
20
|
+
minValue?: number;
|
|
21
|
+
maxValue?: number;
|
|
22
|
+
avgValue?: number;
|
|
23
|
+
uniqueValues?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface AttributeOption {
|
|
26
|
+
key: string;
|
|
27
|
+
label: string;
|
|
28
|
+
type: "numeric" | "categorical" | "date";
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/map-comparison/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AAEtE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;CAC1C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { GeoserverLayer } from "../../interfaces";
|
|
2
|
+
import { ComparisonConfig, ComparisonMode, AttributeOption } from "./types";
|
|
3
|
+
export declare function useMapComparison(): {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
setIsOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
6
|
+
config: ComparisonConfig;
|
|
7
|
+
availableLayers: GeoserverLayer[];
|
|
8
|
+
getLayerAttributes: (layerId: string) => AttributeOption[];
|
|
9
|
+
addLayer: (layerId: string, attributeKey?: string, styleIndex?: number) => void;
|
|
10
|
+
removeLayer: (layerId: string) => void;
|
|
11
|
+
updateLayerAttribute: (layerId: string, attributeKey?: string, styleIndex?: number) => void;
|
|
12
|
+
setMode: (mode: ComparisonMode) => void;
|
|
13
|
+
toggleSyncView: () => void;
|
|
14
|
+
toggleStats: () => void;
|
|
15
|
+
clearComparison: () => void;
|
|
16
|
+
isLayerInComparison: (layerId: string) => boolean;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=use-map-comparison.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-map-comparison.d.ts","sourceRoot":"","sources":["../../../src/components/map-comparison/use-map-comparison.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EACL,gBAAgB,EAEhB,cAAc,EACd,eAAe,EAChB,MAAM,SAAS,CAAC;AAGjB,wBAAgB,gBAAgB;;;;;kCAiBlB,MAAM,KAAG,eAAe,EAAE;wBAqCzB,MAAM,iBACA,MAAM,eACR,MAAM;2BA2BmB,MAAM;oCASpC,MAAM,iBAAiB,MAAM,eAAe,MAAM;oBA0B3B,cAAc;;;;mCA0BrC,MAAM;EAqBnB"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.useMapComparison = useMapComparison;
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const use_layers_1 = __importDefault(require("../../hooks/use-layers"));
|
|
9
|
+
function useMapComparison() {
|
|
10
|
+
const { layer } = (0, use_layers_1.default)();
|
|
11
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
12
|
+
const [config, setConfig] = (0, react_1.useState)({
|
|
13
|
+
mode: "side-by-side",
|
|
14
|
+
layers: [],
|
|
15
|
+
syncView: true,
|
|
16
|
+
showStats: false,
|
|
17
|
+
});
|
|
18
|
+
// Get available layers
|
|
19
|
+
const availableLayers = (0, react_1.useMemo)(() => {
|
|
20
|
+
return layer.all || [];
|
|
21
|
+
}, [layer.all]);
|
|
22
|
+
// Get available attributes for a layer
|
|
23
|
+
const getLayerAttributes = (0, react_1.useCallback)((layerId) => {
|
|
24
|
+
const layerData = availableLayers.find((l) => l.id === layerId);
|
|
25
|
+
if (!layerData?.data?.propertyMap)
|
|
26
|
+
return [];
|
|
27
|
+
const attributes = [];
|
|
28
|
+
const propertyMap = layerData.data.propertyMap;
|
|
29
|
+
// Add all properties from propertyMap
|
|
30
|
+
Object.entries(propertyMap).forEach(([key, label]) => {
|
|
31
|
+
attributes.push({
|
|
32
|
+
key,
|
|
33
|
+
label: String(label),
|
|
34
|
+
type: "categorical", // Default, can be enhanced with actual type detection
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// Add style-based attributes if available
|
|
38
|
+
if (layerData.data.styles && layerData.data.styles.length > 0) {
|
|
39
|
+
layerData.data.styles.forEach((style, index) => {
|
|
40
|
+
if (style.styleName) {
|
|
41
|
+
attributes.push({
|
|
42
|
+
key: `style_${index}`,
|
|
43
|
+
label: style.styleTitle || style.styleName,
|
|
44
|
+
type: "categorical",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return attributes;
|
|
50
|
+
}, [availableLayers]);
|
|
51
|
+
// Add layer to comparison
|
|
52
|
+
const addLayer = (0, react_1.useCallback)((layerId, attributeKey, styleIndex) => {
|
|
53
|
+
const layerData = availableLayers.find((l) => l.id === layerId);
|
|
54
|
+
if (!layerData)
|
|
55
|
+
return;
|
|
56
|
+
const attributeLabel = attributeKey
|
|
57
|
+
? getLayerAttributes(layerId).find((attr) => attr.key === attributeKey)
|
|
58
|
+
?.label || attributeKey
|
|
59
|
+
: undefined;
|
|
60
|
+
const newLayer = {
|
|
61
|
+
layerId,
|
|
62
|
+
layer: layerData,
|
|
63
|
+
attributeKey,
|
|
64
|
+
attributeLabel,
|
|
65
|
+
styleIndex,
|
|
66
|
+
};
|
|
67
|
+
setConfig((prev) => ({
|
|
68
|
+
...prev,
|
|
69
|
+
layers: [...prev.layers, newLayer],
|
|
70
|
+
}));
|
|
71
|
+
}, [availableLayers, getLayerAttributes]);
|
|
72
|
+
// Remove layer from comparison
|
|
73
|
+
const removeLayer = (0, react_1.useCallback)((layerId) => {
|
|
74
|
+
setConfig((prev) => ({
|
|
75
|
+
...prev,
|
|
76
|
+
layers: prev.layers.filter((l) => l.layerId !== layerId),
|
|
77
|
+
}));
|
|
78
|
+
}, []);
|
|
79
|
+
// Update layer attribute
|
|
80
|
+
const updateLayerAttribute = (0, react_1.useCallback)((layerId, attributeKey, styleIndex) => {
|
|
81
|
+
setConfig((prev) => ({
|
|
82
|
+
...prev,
|
|
83
|
+
layers: prev.layers.map((l) => {
|
|
84
|
+
if (l.layerId === layerId) {
|
|
85
|
+
const attributeLabel = attributeKey
|
|
86
|
+
? getLayerAttributes(layerId).find((attr) => attr.key === attributeKey)?.label || attributeKey
|
|
87
|
+
: undefined;
|
|
88
|
+
return {
|
|
89
|
+
...l,
|
|
90
|
+
attributeKey,
|
|
91
|
+
attributeLabel,
|
|
92
|
+
styleIndex,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return l;
|
|
96
|
+
}),
|
|
97
|
+
}));
|
|
98
|
+
}, [getLayerAttributes]);
|
|
99
|
+
// Set comparison mode
|
|
100
|
+
const setMode = (0, react_1.useCallback)((mode) => {
|
|
101
|
+
setConfig((prev) => ({ ...prev, mode }));
|
|
102
|
+
}, []);
|
|
103
|
+
// Toggle sync view
|
|
104
|
+
const toggleSyncView = (0, react_1.useCallback)(() => {
|
|
105
|
+
setConfig((prev) => ({ ...prev, syncView: !prev.syncView }));
|
|
106
|
+
}, []);
|
|
107
|
+
// Toggle stats
|
|
108
|
+
const toggleStats = (0, react_1.useCallback)(() => {
|
|
109
|
+
setConfig((prev) => ({ ...prev, showStats: !prev.showStats }));
|
|
110
|
+
}, []);
|
|
111
|
+
// Clear all comparisons
|
|
112
|
+
const clearComparison = (0, react_1.useCallback)(() => {
|
|
113
|
+
setConfig({
|
|
114
|
+
mode: "side-by-side",
|
|
115
|
+
layers: [],
|
|
116
|
+
syncView: true,
|
|
117
|
+
showStats: false,
|
|
118
|
+
});
|
|
119
|
+
}, []);
|
|
120
|
+
// Check if layer is in comparison
|
|
121
|
+
const isLayerInComparison = (0, react_1.useCallback)((layerId) => {
|
|
122
|
+
return config.layers.some((l) => l.layerId === layerId);
|
|
123
|
+
}, [config.layers]);
|
|
124
|
+
return {
|
|
125
|
+
isOpen,
|
|
126
|
+
setIsOpen,
|
|
127
|
+
config,
|
|
128
|
+
availableLayers,
|
|
129
|
+
getLayerAttributes,
|
|
130
|
+
addLayer,
|
|
131
|
+
removeLayer,
|
|
132
|
+
updateLayerAttribute,
|
|
133
|
+
setMode,
|
|
134
|
+
toggleSyncView,
|
|
135
|
+
toggleStats,
|
|
136
|
+
clearComparison,
|
|
137
|
+
isLayerInComparison,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GeoserverLayer } from "../../../interfaces";
|
|
2
|
+
interface AttributeCompareAddonProps {
|
|
3
|
+
layer: GeoserverLayer;
|
|
4
|
+
}
|
|
5
|
+
export default function AttributeCompareAddon({ layer }: AttributeCompareAddonProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=attribute-compare-addon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribute-compare-addon.d.ts","sourceRoot":"","sources":["../../../../src/components/sidebar/common/attribute-compare-addon.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,UAAU,0BAA0B;IAClC,KAAK,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAAC,EAAE,KAAK,EAAE,EAAE,0BAA0B,kDAyElF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = AttributeCompareAddon;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
// AttributeCompareAddon.tsx - Component to compare different attributes of the same layer
|
|
9
|
+
const react_1 = require("react");
|
|
10
|
+
const use_layers_1 = __importDefault(require("../../../hooks/use-layers"));
|
|
11
|
+
function AttributeCompareAddon({ layer }) {
|
|
12
|
+
const { layer: layerContext } = (0, use_layers_1.default)();
|
|
13
|
+
// Get available attributes (styles) for this layer
|
|
14
|
+
const availableAttributes = (0, react_1.useMemo)(() => {
|
|
15
|
+
if (!layer.data?.styles || !Array.isArray(layer.data.styles)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return layer.data.styles.map((style, index) => ({
|
|
19
|
+
styleIndex: index,
|
|
20
|
+
attributeKey: style.styleName || `attribute_${index}`,
|
|
21
|
+
attributeTitle: style.styleTitle || `Attribute ${index + 1}`,
|
|
22
|
+
}));
|
|
23
|
+
}, [layer.data?.styles]);
|
|
24
|
+
// Don't show if layer has less than 2 attributes
|
|
25
|
+
if (availableAttributes.length < 2) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const comparedAttributes = layerContext.getComparedAttributes(layer.id);
|
|
29
|
+
const handleToggleAttribute = async (attributeKey, styleIndex, checked) => {
|
|
30
|
+
await layerContext.setCompareAttribute(layer.id, attributeKey, styleIndex, checked);
|
|
31
|
+
};
|
|
32
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2 pt-2 border-t border-gray-200", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium text-gray-700", children: "Compare Attributes" }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-2", children: availableAttributes.map((attr) => {
|
|
33
|
+
const isChecked = layerContext.isAttributeCompared(layer.id, attr.attributeKey, attr.styleIndex);
|
|
34
|
+
return ((0, jsx_runtime_1.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer select-none text-sm", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", className: "h-4 w-4 accent-blue-600", checked: isChecked, onChange: (e) => handleToggleAttribute(attr.attributeKey, attr.styleIndex, e.target.checked) }), (0, jsx_runtime_1.jsx)("span", { className: "text-gray-700", children: attr.attributeTitle })] }, `${attr.styleIndex}-${attr.attributeKey}`));
|
|
35
|
+
}) }), comparedAttributes.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "text-xs text-gray-500 mt-1", children: [comparedAttributes.length, " attribute", comparedAttributes.length > 1 ? "s" : "", " selected"] }))] }));
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-compare-addon.d.ts","sourceRoot":"","sources":["../../../../src/components/sidebar/common/layer-compare-addon.tsx"],"names":[],"mappings":"AAKA,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,2CAqB/D"}
|