@open-pioneer/selection 1.4.0 → 1.5.0-dev.20260910103330
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/CHANGELOG.md +47 -29
- package/README.md +28 -9
- package/_virtual/hooks.js +1 -1
- package/_virtual/source-info.js +1 -1
- package/api.d.ts +27 -2
- package/i18n/de.yaml +0 -6
- package/i18n/en.yaml +0 -6
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/interactions/ExtentSelectionInteraction.d.ts +12 -0
- package/interactions/ExtentSelectionInteraction.js +57 -0
- package/interactions/ExtentSelectionInteraction.js.map +1 -0
- package/model/SelectionViewModel.d.ts +30 -0
- package/model/SelectionViewModel.js +270 -0
- package/model/SelectionViewModel.js.map +1 -0
- package/model/SelectionViewModelFactory.d.ts +21 -0
- package/model/SelectionViewModelFactory.js +43 -0
- package/model/SelectionViewModelFactory.js.map +1 -0
- package/model/index.d.ts +1 -0
- package/model/index.js +2 -0
- package/model/index.js.map +1 -0
- package/package.json +34 -15
- package/services.d.ts +2 -7
- package/services.js +2 -17
- package/services.js.map +1 -1
- package/{VectorSelectionSource.d.ts → sources/VectorSelectionSource.d.ts} +3 -7
- package/{VectorSelectionSource.js → sources/VectorSelectionSource.js} +12 -9
- package/sources/VectorSelectionSource.js.map +1 -0
- package/sources/VectorSelectionSourceFactory.d.ts +7 -0
- package/sources/VectorSelectionSourceFactory.js +19 -0
- package/sources/VectorSelectionSourceFactory.js.map +1 -0
- package/{Selection.d.ts → ui/Selection.d.ts} +1 -1
- package/ui/Selection.js +97 -0
- package/ui/Selection.js.map +1 -0
- package/ui/SelectionSourceItem.d.ts +7 -0
- package/ui/SelectionSourceItem.js +38 -0
- package/ui/SelectionSourceItem.js.map +1 -0
- package/ui/useSelectionSourceId.d.ts +9 -0
- package/ui/useSelectionSourceId.js +23 -0
- package/ui/useSelectionSourceId.js.map +1 -0
- package/ui/useSelectionViewModel.d.ts +11 -0
- package/ui/useSelectionViewModel.js +43 -0
- package/ui/useSelectionViewModel.js.map +1 -0
- package/ui/useSourceStatus.d.ts +8 -0
- package/ui/useSourceStatus.js +27 -0
- package/ui/useSourceStatus.js.map +1 -0
- package/DragController.d.ts +0 -33
- package/DragController.js +0 -175
- package/DragController.js.map +0 -1
- package/Selection.js +0 -232
- package/Selection.js.map +0 -1
- package/SelectionController.d.ts +0 -20
- package/SelectionController.js +0 -58
- package/SelectionController.js.map +0 -1
- package/VectorSelectionSource.js.map +0 -1
- /package/{SelectionTooltipContent.d.ts → ui/SelectionTooltipContent.d.ts} +0 -0
- /package/{SelectionTooltipContent.js → ui/SelectionTooltipContent.js} +0 -0
- /package/{SelectionTooltipContent.js.map → ui/SelectionTooltipContent.js.map} +0 -0
- /package/{selection.css → ui/selection.css} +0 -0
- /package/{selection.css.map → ui/selection.css.map} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { reactive, linked, computed, watchValue, effect } from '@conterra/reactivity-core';
|
|
2
|
+
import { createLogger, shallowEqual, destroyResources, throwAbortError, isAbortError } from '@open-pioneer/core';
|
|
3
|
+
import { sourceId } from '../_virtual/source-info.js';
|
|
4
|
+
import { createElement } from 'react';
|
|
5
|
+
import { ExtentSelectionInteraction } from '../interactions/ExtentSelectionInteraction.js';
|
|
6
|
+
import { SelectionTooltipContent } from '../ui/SelectionTooltipContent.js';
|
|
7
|
+
|
|
8
|
+
const LOG = createLogger(sourceId);
|
|
9
|
+
const DEFAULT_MAX_RESULTS = 1e4;
|
|
10
|
+
const ACTIVE_INTERACTION_CLASS = "selection-active";
|
|
11
|
+
const INACTIVE_INTERACTION_CLASS = "selection-inactive";
|
|
12
|
+
const CLEARED = /* @__PURE__ */ Symbol("cleared");
|
|
13
|
+
class SelectionViewModel {
|
|
14
|
+
#map;
|
|
15
|
+
#messages;
|
|
16
|
+
/** Limits the number of results from a source. */
|
|
17
|
+
#maxResults;
|
|
18
|
+
/** Called whenever results were obtained from a source. */
|
|
19
|
+
#onComplete;
|
|
20
|
+
/** Called whenever an error happens, to show a notification. */
|
|
21
|
+
#onError;
|
|
22
|
+
/** The set of available selection sources. */
|
|
23
|
+
#sources = reactive([], { equal: shallowEqual });
|
|
24
|
+
/**
|
|
25
|
+
* The currently selected selection source (free choice within this.#sources).
|
|
26
|
+
*
|
|
27
|
+
* The state is linked to the set of sources:
|
|
28
|
+
*
|
|
29
|
+
* - while nothing has been selected yet, the first source is selected automatically
|
|
30
|
+
* - if the current source is removed from the set of sources, the selection source is cleared
|
|
31
|
+
*
|
|
32
|
+
* Once the selection source has been cleared that way, it remains empty until a source is
|
|
33
|
+
* selected explicitly again: later updates of the set of sources must not silently
|
|
34
|
+
* re-enable the selection source behind the user's back.
|
|
35
|
+
*/
|
|
36
|
+
#currentSource = linked(
|
|
37
|
+
() => this.#sources.value,
|
|
38
|
+
(sources, previousSource) => {
|
|
39
|
+
if (previousSource === CLEARED) {
|
|
40
|
+
return CLEARED;
|
|
41
|
+
}
|
|
42
|
+
if (previousSource) {
|
|
43
|
+
return sources.includes(previousSource) ? previousSource : CLEARED;
|
|
44
|
+
}
|
|
45
|
+
return sources[0];
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
#interactionActive = computed(() => {
|
|
49
|
+
const source = this.currentSource;
|
|
50
|
+
return !!source && getSourceStatus(source).kind === "available";
|
|
51
|
+
});
|
|
52
|
+
#ariaMessage = computed(() => {
|
|
53
|
+
const messages = this.#messages;
|
|
54
|
+
if (!this.currentSource) {
|
|
55
|
+
return messages.noSource;
|
|
56
|
+
}
|
|
57
|
+
if (!this.isInteractionActive) {
|
|
58
|
+
return messages.inactive;
|
|
59
|
+
}
|
|
60
|
+
return messages.active;
|
|
61
|
+
});
|
|
62
|
+
// For debugging
|
|
63
|
+
// oxlint-disable-next-line no-unused-private-class-members
|
|
64
|
+
#currentInteraction;
|
|
65
|
+
// For debugging
|
|
66
|
+
// oxlint-disable-next-line no-unused-private-class-members
|
|
67
|
+
#tooltip;
|
|
68
|
+
/** The abort controller of the selection request that is currently running (if any). */
|
|
69
|
+
#currentAbortController;
|
|
70
|
+
/** Resources owned by this instance. */
|
|
71
|
+
#resources;
|
|
72
|
+
constructor(options) {
|
|
73
|
+
this.#map = options.map;
|
|
74
|
+
this.#messages = options.messages;
|
|
75
|
+
this.#maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
76
|
+
this.#onComplete = options.onSelectionComplete;
|
|
77
|
+
this.#onError = options.onError;
|
|
78
|
+
this.#resources = [
|
|
79
|
+
this.#initSelectionInteraction(),
|
|
80
|
+
...this.#initTooltip(),
|
|
81
|
+
...this.#initViewport()
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
destroy() {
|
|
85
|
+
this.#abortPendingSelection();
|
|
86
|
+
destroyResources(this.#resources);
|
|
87
|
+
}
|
|
88
|
+
get sources() {
|
|
89
|
+
return this.#sources.value;
|
|
90
|
+
}
|
|
91
|
+
set sources(newSources) {
|
|
92
|
+
this.#sources.value = newSources;
|
|
93
|
+
}
|
|
94
|
+
get currentSource() {
|
|
95
|
+
const source = this.#currentSource.value;
|
|
96
|
+
return source === CLEARED ? void 0 : source;
|
|
97
|
+
}
|
|
98
|
+
set currentSource(source) {
|
|
99
|
+
const sources = this.#sources.value;
|
|
100
|
+
if (sources.length > 0) {
|
|
101
|
+
if (!source) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
"Internal error: cannot select 'undefined' if there are sources present."
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (!sources.includes(source)) {
|
|
107
|
+
throw new Error("Internal error: cannot select unknown selection source.");
|
|
108
|
+
}
|
|
109
|
+
this.#currentSource.value = source;
|
|
110
|
+
} else {
|
|
111
|
+
if (source) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"Internal error: can only select 'undefined' if there are no sources present."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Returns true if the selection interaction is currently active. */
|
|
119
|
+
get isInteractionActive() {
|
|
120
|
+
return this.#interactionActive.value;
|
|
121
|
+
}
|
|
122
|
+
/** Aria message to represent the current state. Also serves as the tooltip text. */
|
|
123
|
+
get ariaMessage() {
|
|
124
|
+
return this.#ariaMessage.value;
|
|
125
|
+
}
|
|
126
|
+
/** Runs the selection interaction while active. */
|
|
127
|
+
#initSelectionInteraction() {
|
|
128
|
+
return watchValue(
|
|
129
|
+
() => this.isInteractionActive,
|
|
130
|
+
(isActive) => {
|
|
131
|
+
if (!isActive) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const selection = this.#currentInteraction = new ExtentSelectionInteraction(
|
|
135
|
+
this.#map,
|
|
136
|
+
(geometry) => this.#onGeometrySelected(geometry)
|
|
137
|
+
);
|
|
138
|
+
return () => {
|
|
139
|
+
selection.destroy();
|
|
140
|
+
this.#abortPendingSelection();
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
immediate: true
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Disables the viewport's context menu
|
|
150
|
+
* and marks the viewport with css classes when selection interaction is active or inactive.
|
|
151
|
+
*/
|
|
152
|
+
#initViewport() {
|
|
153
|
+
const viewport = this.#map.olMap.getViewport();
|
|
154
|
+
const disableContextMenu = (e) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
return false;
|
|
157
|
+
};
|
|
158
|
+
viewport.addEventListener("contextmenu", disableContextMenu);
|
|
159
|
+
return [
|
|
160
|
+
{
|
|
161
|
+
destroy() {
|
|
162
|
+
viewport.removeEventListener("contextmenu", disableContextMenu);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
effect(() => {
|
|
166
|
+
const active = this.isInteractionActive;
|
|
167
|
+
const className = active ? ACTIVE_INTERACTION_CLASS : INACTIVE_INTERACTION_CLASS;
|
|
168
|
+
viewport.classList.add(className);
|
|
169
|
+
return () => viewport.classList.remove(className);
|
|
170
|
+
})
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Creates a tooltip that follows the cursor and updates the message depending on the current state.
|
|
175
|
+
*/
|
|
176
|
+
#initTooltip() {
|
|
177
|
+
const tooltip = this.#tooltip = createHelpTooltip(this.#map);
|
|
178
|
+
return [
|
|
179
|
+
tooltip,
|
|
180
|
+
watchValue(
|
|
181
|
+
() => this.ariaMessage,
|
|
182
|
+
(message) => {
|
|
183
|
+
const tooltipContent = createElement(SelectionTooltipContent, {
|
|
184
|
+
content: message
|
|
185
|
+
});
|
|
186
|
+
tooltip.setContent(tooltipContent);
|
|
187
|
+
},
|
|
188
|
+
{ immediate: true }
|
|
189
|
+
)
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Called after a successful selection interaction on the map.
|
|
194
|
+
* Triggers search on the currently selected selection source.
|
|
195
|
+
*/
|
|
196
|
+
async #onGeometrySelected(geometry) {
|
|
197
|
+
const source = this.currentSource;
|
|
198
|
+
if (!source) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.#abortPendingSelection();
|
|
202
|
+
const abortController = this.#currentAbortController = new AbortController();
|
|
203
|
+
try {
|
|
204
|
+
LOG.debug(`Starting selection on source '${source.label}'`);
|
|
205
|
+
const extent = geometry.getExtent();
|
|
206
|
+
const results = await this.#selectFromSource(source, extent, abortController.signal);
|
|
207
|
+
if (abortController.signal.aborted) {
|
|
208
|
+
throwAbortError();
|
|
209
|
+
}
|
|
210
|
+
LOG.debug(`Found ${results.length} results on source '${source.label}'`);
|
|
211
|
+
this.#onComplete(source, results);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
if (!isAbortError(e)) {
|
|
214
|
+
LOG.error(`selection from source ${source.label} failed`, e);
|
|
215
|
+
if (!abortController.signal.aborted) {
|
|
216
|
+
this.#onError();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} finally {
|
|
220
|
+
if (this.#currentAbortController === abortController) {
|
|
221
|
+
this.#currentAbortController = void 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async #selectFromSource(source, extent, signal) {
|
|
226
|
+
const map = this.#map;
|
|
227
|
+
const maxResults = this.#maxResults;
|
|
228
|
+
let results = await source.select(
|
|
229
|
+
{ type: "extent", extent },
|
|
230
|
+
{
|
|
231
|
+
maxResults,
|
|
232
|
+
map,
|
|
233
|
+
mapProjection: map.projection,
|
|
234
|
+
signal
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
if (results.length > maxResults) {
|
|
238
|
+
results = results.slice(0, maxResults);
|
|
239
|
+
}
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
/** Cancels the current selection request (if any); its results will be ignored. */
|
|
243
|
+
#abortPendingSelection() {
|
|
244
|
+
this.#currentAbortController?.abort();
|
|
245
|
+
this.#currentAbortController = void 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function getSourceStatus(source) {
|
|
249
|
+
const status = source.status;
|
|
250
|
+
if (status == null) {
|
|
251
|
+
return { kind: "available" };
|
|
252
|
+
}
|
|
253
|
+
if (typeof status == "string") {
|
|
254
|
+
return { kind: status };
|
|
255
|
+
}
|
|
256
|
+
return status;
|
|
257
|
+
}
|
|
258
|
+
function createHelpTooltip(map) {
|
|
259
|
+
const overlay = map.overlays.add({
|
|
260
|
+
position: "follow-pointer",
|
|
261
|
+
offset: [15, 0],
|
|
262
|
+
positioning: "center-left",
|
|
263
|
+
ariaRole: "tooltip",
|
|
264
|
+
className: "selection-tooltip printing-hide"
|
|
265
|
+
});
|
|
266
|
+
return overlay;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { SelectionViewModel, getSourceStatus };
|
|
270
|
+
//# sourceMappingURL=SelectionViewModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionViewModel.js","sources":["SelectionViewModel.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { computed, effect, linked, reactive, watchValue } from \"@conterra/reactivity-core\";\nimport {\n createLogger,\n destroyResources,\n isAbortError,\n Resource,\n shallowEqual,\n throwAbortError\n} from \"@open-pioneer/core\";\nimport { MapModel, Overlay } from \"@open-pioneer/map\";\nimport { Extent } from \"ol/extent\";\nimport { Geometry } from \"ol/geom\";\nimport { sourceId } from \"open-pioneer:source-info\";\nimport { createElement } from \"react\";\nimport { SelectionResult, SelectionSource, SelectionSourceStatusObject } from \"../api\";\nimport { ExtentSelectionInteraction } from \"../interactions/ExtentSelectionInteraction\";\nimport { SelectionTooltipContent } from \"../ui/SelectionTooltipContent\";\n\nconst LOG = createLogger(sourceId);\n\nconst DEFAULT_MAX_RESULTS = 10000;\n\nconst ACTIVE_INTERACTION_CLASS = \"selection-active\";\nconst INACTIVE_INTERACTION_CLASS = \"selection-inactive\";\n\nexport interface Messages {\n active: string;\n inactive: string;\n noSource: string;\n}\n\n/**\n * Marker for a selection source that has been cleared.\n * The cleared state (when the selected source has been \"lost\") should be sticky, a new source\n * should not be selected automatically.\n */\nconst CLEARED = Symbol(\"cleared\");\n\nexport class SelectionViewModel {\n #map: MapModel;\n #messages: Messages;\n\n /** Limits the number of results from a source. */\n #maxResults: number;\n\n /** Called whenever results were obtained from a source. */\n #onComplete: (source: SelectionSource, results: SelectionResult[]) => void;\n\n /** Called whenever an error happens, to show a notification. */\n #onError: () => void;\n\n /** The set of available selection sources. */\n #sources = reactive<SelectionSource[]>([], { equal: shallowEqual });\n\n /**\n * The currently selected selection source (free choice within this.#sources).\n *\n * The state is linked to the set of sources:\n *\n * - while nothing has been selected yet, the first source is selected automatically\n * - if the current source is removed from the set of sources, the selection source is cleared\n *\n * Once the selection source has been cleared that way, it remains empty until a source is\n * selected explicitly again: later updates of the set of sources must not silently\n * re-enable the selection source behind the user's back.\n */\n #currentSource = linked(\n () => this.#sources.value,\n (\n sources: SelectionSource[],\n previousSource: SelectionSource | typeof CLEARED | undefined\n ): SelectionSource | typeof CLEARED | undefined => {\n if (previousSource === CLEARED) {\n return CLEARED;\n }\n if (previousSource) {\n return sources.includes(previousSource) ? previousSource : CLEARED;\n }\n return sources[0];\n }\n );\n\n #interactionActive = computed(() => {\n const source = this.currentSource;\n return !!source && getSourceStatus(source).kind === \"available\";\n });\n\n #ariaMessage = computed(() => {\n const messages = this.#messages;\n if (!this.currentSource) {\n return messages.noSource;\n }\n if (!this.isInteractionActive) {\n return messages.inactive;\n }\n return messages.active;\n });\n\n // For debugging\n // oxlint-disable-next-line no-unused-private-class-members\n #currentInteraction: ExtentSelectionInteraction | undefined;\n\n // For debugging\n // oxlint-disable-next-line no-unused-private-class-members\n #tooltip: Overlay | undefined;\n\n /** The abort controller of the selection request that is currently running (if any). */\n #currentAbortController: AbortController | undefined;\n\n /** Resources owned by this instance. */\n #resources: Resource[];\n\n constructor(options: {\n map: MapModel;\n messages: Messages;\n onSelectionComplete: (source: SelectionSource, results: SelectionResult[]) => void;\n onError: () => void;\n maxResults?: number;\n }) {\n this.#map = options.map;\n this.#messages = options.messages;\n this.#maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;\n this.#onComplete = options.onSelectionComplete;\n this.#onError = options.onError;\n this.#resources = [\n this.#initSelectionInteraction(),\n ...this.#initTooltip(),\n ...this.#initViewport()\n ];\n }\n\n destroy() {\n this.#abortPendingSelection();\n destroyResources(this.#resources);\n }\n\n get sources(): SelectionSource[] {\n return this.#sources.value;\n }\n\n set sources(newSources: SelectionSource[]) {\n this.#sources.value = newSources;\n }\n\n get currentSource(): SelectionSource | undefined {\n const source = this.#currentSource.value;\n return source === CLEARED ? undefined : source;\n }\n\n set currentSource(source: SelectionSource | undefined) {\n const sources = this.#sources.value;\n if (sources.length > 0) {\n if (!source) {\n throw new Error(\n \"Internal error: cannot select 'undefined' if there are sources present.\"\n );\n }\n if (!sources.includes(source)) {\n throw new Error(\"Internal error: cannot select unknown selection source.\");\n }\n this.#currentSource.value = source;\n } else {\n if (source) {\n throw new Error(\n \"Internal error: can only select 'undefined' if there are no sources present.\"\n );\n }\n // No source can be selected; the current state is already empty.\n }\n }\n\n /** Returns true if the selection interaction is currently active. */\n get isInteractionActive(): boolean {\n return this.#interactionActive.value;\n }\n\n /** Aria message to represent the current state. Also serves as the tooltip text. */\n get ariaMessage(): string {\n return this.#ariaMessage.value;\n }\n\n /** Runs the selection interaction while active. */\n #initSelectionInteraction(): Resource {\n return watchValue(\n () => this.isInteractionActive,\n (isActive) => {\n if (!isActive) {\n return;\n }\n\n const selection = (this.#currentInteraction = new ExtentSelectionInteraction(\n this.#map,\n (geometry) => this.#onGeometrySelected(geometry)\n ));\n return () => {\n selection.destroy();\n this.#abortPendingSelection();\n };\n },\n {\n immediate: true\n }\n );\n }\n\n /**\n * Disables the viewport's context menu\n * and marks the viewport with css classes when selection interaction is active or inactive.\n */\n #initViewport(): Resource[] {\n const viewport = this.#map.olMap.getViewport();\n const disableContextMenu = (e: MouseEvent) => {\n e.preventDefault();\n return false;\n };\n viewport.addEventListener(\"contextmenu\", disableContextMenu);\n\n return [\n {\n destroy() {\n viewport.removeEventListener(\"contextmenu\", disableContextMenu);\n }\n },\n effect(() => {\n const active = this.isInteractionActive;\n const className = active ? ACTIVE_INTERACTION_CLASS : INACTIVE_INTERACTION_CLASS;\n viewport.classList.add(className);\n return () => viewport.classList.remove(className);\n })\n ];\n }\n\n /**\n * Creates a tooltip that follows the cursor and updates the message depending on the current state.\n */\n #initTooltip(): Resource[] {\n const tooltip = (this.#tooltip = createHelpTooltip(this.#map));\n return [\n tooltip,\n watchValue(\n () => this.ariaMessage,\n (message) => {\n // Aria message doubles as tooltip text at this time\n const tooltipContent = createElement(SelectionTooltipContent, {\n content: message\n });\n tooltip.setContent(tooltipContent);\n },\n { immediate: true }\n )\n ];\n }\n\n /**\n * Called after a successful selection interaction on the map.\n * Triggers search on the currently selected selection source.\n */\n async #onGeometrySelected(geometry: Geometry) {\n const source = this.currentSource;\n if (!source) {\n return;\n }\n\n // Only the most recent selection is of interest; cancel the previous one (if any).\n this.#abortPendingSelection();\n\n const abortController = (this.#currentAbortController = new AbortController());\n try {\n LOG.debug(`Starting selection on source '${source.label}'`);\n\n const extent = geometry.getExtent();\n const results = await this.#selectFromSource(source, extent, abortController.signal);\n if (abortController.signal.aborted) {\n // Superseded by another selection, or the widget is gone.\n throwAbortError();\n }\n\n LOG.debug(`Found ${results.length} results on source '${source.label}'`);\n this.#onComplete(source, results);\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(`selection from source ${source.label} failed`, e);\n if (!abortController.signal.aborted) {\n this.#onError();\n }\n }\n } finally {\n if (this.#currentAbortController === abortController) {\n this.#currentAbortController = undefined;\n }\n }\n }\n\n async #selectFromSource(source: SelectionSource, extent: Extent, signal: AbortSignal) {\n const map = this.#map;\n const maxResults = this.#maxResults;\n let results = await source.select(\n { type: \"extent\", extent },\n {\n maxResults,\n map: map,\n mapProjection: map.projection,\n signal\n }\n );\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n return results;\n }\n\n /** Cancels the current selection request (if any); its results will be ignored. */\n #abortPendingSelection(): void {\n this.#currentAbortController?.abort();\n this.#currentAbortController = undefined;\n }\n}\n\n/**\n * Normalizes the source's status into an object.\n */\nexport function getSourceStatus(source: SelectionSource): SelectionSourceStatusObject {\n const status = source.status;\n if (status == null) {\n return { kind: \"available\" };\n }\n if (typeof status == \"string\") {\n return { kind: status };\n }\n return status;\n}\n\nfunction createHelpTooltip(map: MapModel): Overlay {\n const overlay = map.overlays.add({\n position: \"follow-pointer\",\n offset: [15, 0],\n positioning: \"center-left\",\n ariaRole: \"tooltip\",\n className: \"selection-tooltip printing-hide\"\n });\n return overlay;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MapModel } from "@open-pioneer/map";
|
|
2
|
+
import { NotificationService } from "@open-pioneer/notifier";
|
|
3
|
+
import { DECLARE_SERVICE_INTERFACE, ServiceOptions } from "@open-pioneer/runtime";
|
|
4
|
+
import { SelectionResult, SelectionSource } from "../api";
|
|
5
|
+
import { SelectionViewModel } from "./SelectionViewModel";
|
|
6
|
+
interface References {
|
|
7
|
+
notifier: NotificationService;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export declare class SelectionViewModelFactory {
|
|
13
|
+
#private;
|
|
14
|
+
[DECLARE_SERVICE_INTERFACE]: "selection.ViewModelFactory";
|
|
15
|
+
constructor({ references, currentIntl }: ServiceOptions<References>);
|
|
16
|
+
createViewModel(options: {
|
|
17
|
+
map: MapModel;
|
|
18
|
+
onSelectionComplete: (source: SelectionSource, results: SelectionResult[]) => void;
|
|
19
|
+
}): SelectionViewModel;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FormattedMessage } from '@open-pioneer/react-utils';
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { SelectionViewModel } from './SelectionViewModel.js';
|
|
4
|
+
|
|
5
|
+
class SelectionViewModelFactory {
|
|
6
|
+
#notifier;
|
|
7
|
+
#currentIntl;
|
|
8
|
+
#messages;
|
|
9
|
+
constructor({ references, currentIntl }) {
|
|
10
|
+
this.#notifier = references.notifier;
|
|
11
|
+
this.#currentIntl = currentIntl;
|
|
12
|
+
this.#messages = {
|
|
13
|
+
get active() {
|
|
14
|
+
return currentIntl.value.formatMessage({ id: "tooltip" });
|
|
15
|
+
},
|
|
16
|
+
get inactive() {
|
|
17
|
+
return currentIntl.value.formatMessage({ id: "disabledTooltip" });
|
|
18
|
+
},
|
|
19
|
+
get noSource() {
|
|
20
|
+
return currentIntl.value.formatMessage({ id: "noSourceTooltip" });
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
createViewModel(options) {
|
|
25
|
+
return new SelectionViewModel({
|
|
26
|
+
map: options.map,
|
|
27
|
+
messages: this.#messages,
|
|
28
|
+
onSelectionComplete: options.onSelectionComplete,
|
|
29
|
+
onError: () => {
|
|
30
|
+
this.#notifier.notify({
|
|
31
|
+
level: "error",
|
|
32
|
+
message: createElement(FormattedMessage, {
|
|
33
|
+
intl: this.#currentIntl,
|
|
34
|
+
id: "selectionFailed"
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { SelectionViewModelFactory };
|
|
43
|
+
//# sourceMappingURL=SelectionViewModelFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionViewModelFactory.js","sources":["SelectionViewModelFactory.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ReadonlyReactive } from \"@conterra/reactivity-core\";\nimport { MapModel } from \"@open-pioneer/map\";\nimport { NotificationService } from \"@open-pioneer/notifier\";\nimport { FormattedMessage } from \"@open-pioneer/react-utils\";\nimport { DECLARE_SERVICE_INTERFACE, PackageIntl, ServiceOptions } from \"@open-pioneer/runtime\";\nimport { createElement } from \"react\";\nimport { SelectionResult, SelectionSource } from \"../api\";\nimport { Messages, SelectionViewModel } from \"./SelectionViewModel\";\n\ninterface References {\n notifier: NotificationService;\n}\n\n/**\n * @internal\n */\nexport class SelectionViewModelFactory {\n declare [DECLARE_SERVICE_INTERFACE]: \"selection.ViewModelFactory\";\n\n #notifier: NotificationService;\n #currentIntl: ReadonlyReactive<PackageIntl>;\n #messages: Messages;\n\n constructor({ references, currentIntl }: ServiceOptions<References>) {\n this.#notifier = references.notifier;\n this.#currentIntl = currentIntl;\n this.#messages = {\n get active() {\n return currentIntl.value.formatMessage({ id: \"tooltip\" });\n },\n get inactive() {\n return currentIntl.value.formatMessage({ id: \"disabledTooltip\" });\n },\n get noSource() {\n return currentIntl.value.formatMessage({ id: \"noSourceTooltip\" });\n }\n };\n }\n\n createViewModel(options: {\n map: MapModel;\n onSelectionComplete: (source: SelectionSource, results: SelectionResult[]) => void;\n }): SelectionViewModel {\n return new SelectionViewModel({\n map: options.map,\n messages: this.#messages,\n onSelectionComplete: options.onSelectionComplete,\n onError: () => {\n this.#notifier.notify({\n level: \"error\",\n message: createElement(FormattedMessage, {\n intl: this.#currentIntl,\n id: \"selectionFailed\"\n })\n });\n }\n });\n }\n}\n"],"names":[],"mappings":";;;;AAmBO,MAAM,yBAAA,CAA0B;AAAA,EAGnC,SAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EAEA,WAAA,CAAY,EAAE,UAAA,EAAY,WAAA,EAAY,EAA+B;AACjE,IAAA,IAAA,CAAK,YAAY,UAAA,CAAW,QAAA;AAC5B,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,SAAA,GAAY;AAAA,MACb,IAAI,MAAA,GAAS;AACT,QAAA,OAAO,YAAY,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,WAAW,CAAA;AAAA,MAC5D,CAAA;AAAA,MACA,IAAI,QAAA,GAAW;AACX,QAAA,OAAO,YAAY,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,mBAAmB,CAAA;AAAA,MACpE,CAAA;AAAA,MACA,IAAI,QAAA,GAAW;AACX,QAAA,OAAO,YAAY,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,mBAAmB,CAAA;AAAA,MACpE;AAAA,KACJ;AAAA,EACJ;AAAA,EAEA,gBAAgB,OAAA,EAGO;AACnB,IAAA,OAAO,IAAI,kBAAA,CAAmB;AAAA,MAC1B,KAAK,OAAA,CAAQ,GAAA;AAAA,MACb,UAAU,IAAA,CAAK,SAAA;AAAA,MACf,qBAAqB,OAAA,CAAQ,mBAAA;AAAA,MAC7B,SAAS,MAAM;AACX,QAAA,IAAA,CAAK,UAAU,MAAA,CAAO;AAAA,UAClB,KAAA,EAAO,OAAA;AAAA,UACP,OAAA,EAAS,cAAc,gBAAA,EAAkB;AAAA,YACrC,MAAM,IAAA,CAAK,YAAA;AAAA,YACX,EAAA,EAAI;AAAA,WACP;AAAA,SACJ,CAAA;AAAA,MACL;AAAA,KACH,CAAA;AAAA,EACL;AACJ;;;;"}
|
package/model/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SelectionViewModel, getSourceStatus, type Messages } from "./SelectionViewModel";
|
package/model/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@open-pioneer/selection",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.0-dev.20260910103330",
|
|
5
5
|
"description": "This package provides a UI component to perform a selection on given selection sources from the map.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"open-pioneer-trails"
|
|
@@ -14,19 +14,23 @@
|
|
|
14
14
|
"directory": "src/packages/selection"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@chakra-ui/react": "^3.
|
|
18
|
-
"@conterra/reactivity-core": "^0.8.
|
|
19
|
-
"@open-pioneer/chakra-snippets": "
|
|
20
|
-
"@open-pioneer/core": "
|
|
21
|
-
"@open-pioneer/
|
|
22
|
-
"@open-pioneer/
|
|
23
|
-
"@open-pioneer/
|
|
24
|
-
"@open-pioneer/
|
|
25
|
-
"
|
|
17
|
+
"@chakra-ui/react": "^3.37.0",
|
|
18
|
+
"@conterra/reactivity-core": "^0.8.8",
|
|
19
|
+
"@open-pioneer/chakra-snippets": "4.8.0-dev.20260910101405",
|
|
20
|
+
"@open-pioneer/core": "4.8.0-dev.20260910101405",
|
|
21
|
+
"@open-pioneer/map": "1.5.0-dev.20260910103330",
|
|
22
|
+
"@open-pioneer/notifier": "4.8.0-dev.20260910101405",
|
|
23
|
+
"@open-pioneer/react-utils": "4.8.0-dev.20260910101405",
|
|
24
|
+
"@open-pioneer/reactivity": "4.8.0-dev.20260910101405",
|
|
25
|
+
"@open-pioneer/runtime": "4.8.0-dev.20260910101405",
|
|
26
|
+
"ol": "^10.10.0",
|
|
26
27
|
"react": "^19.2.8",
|
|
27
28
|
"react-icons": "^5.7.0",
|
|
28
|
-
"uuid": "^14.0.
|
|
29
|
-
|
|
29
|
+
"uuid": "^14.0.2"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"directory": "dist",
|
|
33
|
+
"linkDirectory": false
|
|
30
34
|
},
|
|
31
35
|
"exports": {
|
|
32
36
|
"./package.json": "./package.json",
|
|
@@ -38,11 +42,26 @@
|
|
|
38
42
|
"import": "./services.js",
|
|
39
43
|
"types": "./services.d.ts"
|
|
40
44
|
},
|
|
41
|
-
"./selection.css": "./selection.css"
|
|
45
|
+
"./ui/selection.css": "./ui/selection.css"
|
|
42
46
|
},
|
|
43
47
|
"openPioneerFramework": {
|
|
44
|
-
"styles": "./selection.css",
|
|
48
|
+
"styles": "./ui/selection.css",
|
|
45
49
|
"services": [
|
|
50
|
+
{
|
|
51
|
+
"serviceName": "SelectionViewModelFactory",
|
|
52
|
+
"provides": [
|
|
53
|
+
{
|
|
54
|
+
"interfaceName": "selection.ViewModelFactory"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"references": [
|
|
58
|
+
{
|
|
59
|
+
"referenceName": "notifier",
|
|
60
|
+
"type": "unique",
|
|
61
|
+
"interfaceName": "notifier.NotificationService"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
46
65
|
{
|
|
47
66
|
"serviceName": "VectorSelectionSourceFactory",
|
|
48
67
|
"provides": [
|
|
@@ -64,7 +83,7 @@
|
|
|
64
83
|
"references": [
|
|
65
84
|
{
|
|
66
85
|
"type": "unique",
|
|
67
|
-
"interfaceName": "
|
|
86
|
+
"interfaceName": "selection.ViewModelFactory"
|
|
68
87
|
}
|
|
69
88
|
]
|
|
70
89
|
},
|
package/services.d.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export declare class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {
|
|
4
|
-
#private;
|
|
5
|
-
constructor({ currentIntl }: ServiceOptions<PackageIntl>);
|
|
6
|
-
createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource;
|
|
7
|
-
}
|
|
1
|
+
export { VectorSelectionSourceFactory } from "./sources/VectorSelectionSourceFactory";
|
|
2
|
+
export { SelectionViewModelFactory } from "./model/SelectionViewModelFactory";
|
package/services.js
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
class VectorSelectionSourceFactory {
|
|
4
|
-
#currentIntl;
|
|
5
|
-
constructor({ currentIntl }) {
|
|
6
|
-
this.#currentIntl = currentIntl;
|
|
7
|
-
}
|
|
8
|
-
createSelectionSource(options) {
|
|
9
|
-
return new VectorLayerSelectionSourceImpl(
|
|
10
|
-
options.vectorLayer,
|
|
11
|
-
options.label,
|
|
12
|
-
this.#currentIntl
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export { VectorSelectionSourceFactory };
|
|
1
|
+
export { VectorSelectionSourceFactory } from './sources/VectorSelectionSourceFactory.js';
|
|
2
|
+
export { SelectionViewModelFactory } from './model/SelectionViewModelFactory.js';
|
|
18
3
|
//# sourceMappingURL=services.js.map
|
package/services.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"services.js","sources":[
|
|
1
|
+
{"version":3,"file":"services.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -3,19 +3,15 @@ import { PackageIntl } from "@open-pioneer/runtime";
|
|
|
3
3
|
import Feature from "ol/Feature";
|
|
4
4
|
import VectorLayer from "ol/layer/Vector";
|
|
5
5
|
import VectorSource from "ol/source/Vector";
|
|
6
|
-
import {
|
|
6
|
+
import { SelectionKind, SelectionOptions, SelectionResult, SelectionSourceStatusObject, VectorLayerSelectionSource } from "../api";
|
|
7
7
|
/**
|
|
8
8
|
* A SelectionSource to use an OpenLayers VectorLayer with an OpenLayers VectorSource (e.g. layer of the map).
|
|
9
|
-
* Features are:
|
|
10
|
-
* - using only the extent as selection kind
|
|
11
|
-
* - listening to layer visibility changes and updating the status of the source
|
|
12
|
-
* - limiting the number of returned selection results to the corresponding selection option
|
|
13
|
-
* - throwing an event `changed:status` when the status updates
|
|
14
9
|
*/
|
|
15
10
|
export declare class VectorLayerSelectionSourceImpl implements VectorLayerSelectionSource {
|
|
16
11
|
#private;
|
|
12
|
+
readonly id: string | undefined;
|
|
17
13
|
readonly label: string;
|
|
18
|
-
constructor(vectorLayer: VectorLayer<VectorSource, Feature>, label: string, currentIntl: ReadonlyReactive<PackageIntl>);
|
|
14
|
+
constructor(id: string | undefined, vectorLayer: VectorLayer<VectorSource, Feature>, label: string, currentIntl: ReadonlyReactive<PackageIntl>);
|
|
19
15
|
destroy(): void;
|
|
20
16
|
get status(): SelectionSourceStatusObject;
|
|
21
17
|
select(selectionKind: SelectionKind, options: SelectionOptions): Promise<SelectionResult[]>;
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { synchronized, computed } from '@conterra/reactivity-core';
|
|
2
2
|
import { unByKey } from 'ol/Observable.js';
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
class VectorLayerSelectionSourceImpl {
|
|
6
|
+
id;
|
|
6
7
|
label;
|
|
7
8
|
#vectorLayer;
|
|
8
|
-
#eventHandler;
|
|
9
9
|
#currentIntl;
|
|
10
|
-
#layerVisible
|
|
10
|
+
#layerVisible;
|
|
11
11
|
#status;
|
|
12
|
-
constructor(vectorLayer, label, currentIntl) {
|
|
12
|
+
constructor(id, vectorLayer, label, currentIntl) {
|
|
13
|
+
this.id = id;
|
|
13
14
|
this.label = label;
|
|
14
15
|
this.#vectorLayer = vectorLayer;
|
|
15
16
|
this.#currentIntl = currentIntl;
|
|
16
|
-
this.#layerVisible
|
|
17
|
+
this.#layerVisible = synchronized(
|
|
18
|
+
() => vectorLayer.getVisible(),
|
|
19
|
+
(cb) => {
|
|
20
|
+
const key = vectorLayer.on("change:visible", cb);
|
|
21
|
+
return () => unByKey(key);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
17
24
|
this.#status = computed(
|
|
18
25
|
() => this.#layerVisible.value ? { kind: "available" } : {
|
|
19
26
|
kind: "unavailable",
|
|
20
27
|
reason: this.#currentIntl.value.formatMessage({ id: "layerNotVisibleReason" })
|
|
21
28
|
}
|
|
22
29
|
);
|
|
23
|
-
this.#eventHandler = this.#vectorLayer.on("change:visible", () => {
|
|
24
|
-
this.#layerVisible.value = this.#vectorLayer.getVisible();
|
|
25
|
-
});
|
|
26
30
|
}
|
|
27
31
|
destroy() {
|
|
28
|
-
unByKey(this.#eventHandler);
|
|
29
32
|
}
|
|
30
33
|
get status() {
|
|
31
34
|
return this.#status.value;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VectorSelectionSource.js","sources":["VectorSelectionSource.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { computed, ReadonlyReactive, synchronized } from \"@conterra/reactivity-core\";\nimport { PackageIntl } from \"@open-pioneer/runtime\";\nimport Feature from \"ol/Feature\";\nimport VectorLayer from \"ol/layer/Vector\";\nimport { unByKey } from \"ol/Observable\";\nimport VectorSource from \"ol/source/Vector\";\nimport { v4 as uuid4v } from \"uuid\";\nimport {\n SelectionKind,\n SelectionOptions,\n SelectionResult,\n SelectionSourceStatusObject,\n VectorLayerSelectionSource\n} from \"../api\";\n\n/**\n * A SelectionSource to use an OpenLayers VectorLayer with an OpenLayers VectorSource (e.g. layer of the map).\n */\nexport class VectorLayerSelectionSourceImpl implements VectorLayerSelectionSource {\n readonly id: string | undefined;\n readonly label: string;\n #vectorLayer: VectorLayer<VectorSource, Feature>;\n #currentIntl: ReadonlyReactive<PackageIntl>;\n #layerVisible: ReadonlyReactive<boolean>;\n #status: ReadonlyReactive<SelectionSourceStatusObject>;\n\n constructor(\n id: string | undefined,\n vectorLayer: VectorLayer<VectorSource, Feature>,\n label: string,\n currentIntl: ReadonlyReactive<PackageIntl>\n ) {\n this.id = id;\n this.label = label;\n this.#vectorLayer = vectorLayer;\n this.#currentIntl = currentIntl;\n this.#layerVisible = synchronized(\n () => vectorLayer.getVisible(),\n (cb) => {\n const key = vectorLayer.on(\"change:visible\", cb);\n return () => unByKey(key);\n }\n );\n this.#status = computed<SelectionSourceStatusObject>(() =>\n this.#layerVisible.value\n ? { kind: \"available\" }\n : {\n kind: \"unavailable\",\n reason: this.#currentIntl.value.formatMessage({ id: \"layerNotVisibleReason\" })\n }\n );\n }\n\n destroy() {}\n\n get status() {\n return this.#status.value;\n }\n\n async select(\n selectionKind: SelectionKind,\n options: SelectionOptions\n ): Promise<SelectionResult[]> {\n if (selectionKind.type !== \"extent\") {\n throw new Error(`Unsupported selection kind: ${selectionKind.type}`);\n }\n\n if (this.#status.value.kind !== \"available\" || this.#vectorLayer.getSource() === null)\n return [];\n\n const allResults: SelectionResult[] = [];\n // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.#vectorLayer\n .getSource()!\n .forEachFeatureIntersectingExtent(selectionKind.extent, (feature) => {\n if (!feature.getGeometry()) return;\n\n // TODO: Think about where to implement Date-Formatting, if the dates are already\n // encoded as Strings...\n\n const filteredProperties = { ...feature.getProperties() };\n delete filteredProperties.geometries;\n\n const result: SelectionResult = {\n id: feature.getId()?.toString() || uuid4v(),\n // oxlint-disable-next-line @typescript-eslint/no-non-null-assertion\n geometry: feature.getGeometry()!,\n properties: filteredProperties\n };\n\n allResults.push(result);\n });\n const selectedFeatures = allResults.filter((s): s is SelectionResult => s != null);\n const limitedFeatures =\n selectedFeatures.length > options.maxResults\n ? selectedFeatures.slice(0, options.maxResults)\n : selectedFeatures;\n return limitedFeatures;\n }\n}\n"],"names":["uuid4v"],"mappings":";;;;AAqBO,MAAM,8BAAA,CAAqE;AAAA,EACrE,EAAA;AAAA,EACA,KAAA;AAAA,EACT,YAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EAEA,WAAA,CACI,EAAA,EACA,WAAA,EACA,KAAA,EACA,WAAA,EACF;AACE,IAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AACV,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,aAAA,GAAgB,YAAA;AAAA,MACjB,MAAM,YAAY,UAAA,EAAW;AAAA,MAC7B,CAAC,EAAA,KAAO;AACJ,QAAA,MAAM,GAAA,GAAM,WAAA,CAAY,EAAA,CAAG,gBAAA,EAAkB,EAAE,CAAA;AAC/C,QAAA,OAAO,MAAM,QAAQ,GAAG,CAAA;AAAA,MAC5B;AAAA,KACJ;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,QAAA;AAAA,MAAsC,MACjD,IAAA,CAAK,aAAA,CAAc,QACb,EAAE,IAAA,EAAM,aAAY,GACpB;AAAA,QACI,IAAA,EAAM,aAAA;AAAA,QACN,MAAA,EAAQ,KAAK,YAAA,CAAa,KAAA,CAAM,cAAc,EAAE,EAAA,EAAI,yBAAyB;AAAA;AACjF,KACV;AAAA,EACJ;AAAA,EAEA,OAAA,GAAU;AAAA,EAAC;AAAA,EAEX,IAAI,MAAA,GAAS;AACT,IAAA,OAAO,KAAK,OAAA,CAAQ,KAAA;AAAA,EACxB;AAAA,EAEA,MAAM,MAAA,CACF,aAAA,EACA,OAAA,EAC0B;AAC1B,IAAA,IAAI,aAAA,CAAc,SAAS,QAAA,EAAU;AACjC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,aAAA,CAAc,IAAI,CAAA,CAAE,CAAA;AAAA,IACvE;AAEA,IAAA,IAAI,IAAA,CAAK,QAAQ,KAAA,CAAM,IAAA,KAAS,eAAe,IAAA,CAAK,YAAA,CAAa,WAAU,KAAM,IAAA;AAC7E,MAAA,OAAO,EAAC;AAEZ,IAAA,MAAM,aAAgC,EAAC;AAEvC,IAAA,IAAA,CAAK,aACA,SAAA,EAAU,CACV,iCAAiC,aAAA,CAAc,MAAA,EAAQ,CAAC,OAAA,KAAY;AACjE,MAAA,IAAI,CAAC,OAAA,CAAQ,WAAA,EAAY,EAAG;AAK5B,MAAA,MAAM,kBAAA,GAAqB,EAAE,GAAG,OAAA,CAAQ,eAAc,EAAE;AACxD,MAAA,OAAO,kBAAA,CAAmB,UAAA;AAE1B,MAAA,MAAM,MAAA,GAA0B;AAAA,QAC5B,IAAI,OAAA,CAAQ,KAAA,EAAM,EAAG,QAAA,MAAcA,EAAA,EAAO;AAAA;AAAA,QAE1C,QAAA,EAAU,QAAQ,WAAA,EAAY;AAAA,QAC9B,UAAA,EAAY;AAAA,OAChB;AAEA,MAAA,UAAA,CAAW,KAAK,MAAM,CAAA;AAAA,IAC1B,CAAC,CAAA;AACL,IAAA,MAAM,mBAAmB,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,KAA4B,KAAK,IAAI,CAAA;AACjF,IAAA,MAAM,eAAA,GACF,gBAAA,CAAiB,MAAA,GAAS,OAAA,CAAQ,UAAA,GAC5B,iBAAiB,KAAA,CAAM,CAAA,EAAG,OAAA,CAAQ,UAAU,CAAA,GAC5C,gBAAA;AACV,IAAA,OAAO,eAAA;AAAA,EACX;AACJ;;;;"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PackageIntl, ServiceOptions } from "@open-pioneer/runtime";
|
|
2
|
+
import { VectorLayerSelectionSource, VectorLayerSelectionSourceFactory, VectorLayerSelectionSourceOptions } from "../api";
|
|
3
|
+
export declare class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {
|
|
4
|
+
#private;
|
|
5
|
+
constructor({ currentIntl }: ServiceOptions<PackageIntl>);
|
|
6
|
+
createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource;
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { VectorLayerSelectionSourceImpl } from './VectorSelectionSource.js';
|
|
2
|
+
|
|
3
|
+
class VectorSelectionSourceFactory {
|
|
4
|
+
#currentIntl;
|
|
5
|
+
constructor({ currentIntl }) {
|
|
6
|
+
this.#currentIntl = currentIntl;
|
|
7
|
+
}
|
|
8
|
+
createSelectionSource(options) {
|
|
9
|
+
return new VectorLayerSelectionSourceImpl(
|
|
10
|
+
options.id,
|
|
11
|
+
options.vectorLayer,
|
|
12
|
+
options.label,
|
|
13
|
+
this.#currentIntl
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { VectorSelectionSourceFactory };
|
|
19
|
+
//# sourceMappingURL=VectorSelectionSourceFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VectorSelectionSourceFactory.js","sources":["VectorSelectionSourceFactory.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { ReadonlyReactive } from \"@conterra/reactivity-core\";\nimport { type PackageIntl, ServiceOptions } from \"@open-pioneer/runtime\";\nimport {\n VectorLayerSelectionSource,\n VectorLayerSelectionSourceFactory,\n VectorLayerSelectionSourceOptions\n} from \"../api\";\nimport { VectorLayerSelectionSourceImpl } from \"./VectorSelectionSource\";\n\nexport class VectorSelectionSourceFactory implements VectorLayerSelectionSourceFactory {\n #currentIntl: ReadonlyReactive<PackageIntl>;\n\n constructor({ currentIntl }: ServiceOptions<PackageIntl>) {\n this.#currentIntl = currentIntl;\n }\n\n createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource {\n return new VectorLayerSelectionSourceImpl(\n options.id,\n options.vectorLayer,\n options.label,\n this.#currentIntl\n );\n }\n}\n"],"names":[],"mappings":";;AAYO,MAAM,4BAAA,CAA0E;AAAA,EACnF,YAAA;AAAA,EAEA,WAAA,CAAY,EAAE,WAAA,EAAY,EAAgC;AACtD,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AAAA,EACxB;AAAA,EAEA,sBAAsB,OAAA,EAAwE;AAC1F,IAAA,OAAO,IAAI,8BAAA;AAAA,MACP,OAAA,CAAQ,EAAA;AAAA,MACR,OAAA,CAAQ,WAAA;AAAA,MACR,OAAA,CAAQ,KAAA;AAAA,MACR,IAAA,CAAK;AAAA,KACT;AAAA,EACJ;AACJ;;;;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MapModelProps } from "@open-pioneer/map";
|
|
2
2
|
import { CommonComponentProps } from "@open-pioneer/react-utils";
|
|
3
3
|
import { FC } from "react";
|
|
4
|
-
import { SelectionResult, SelectionSource } from "
|
|
4
|
+
import { SelectionResult, SelectionSource } from "../api";
|
|
5
5
|
/**
|
|
6
6
|
* Properties supported by the {@link Selection} component.
|
|
7
7
|
*/
|