@open-pioneer/selection 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/DragController.d.ts +45 -0
- package/DragController.js +175 -0
- package/DragController.js.map +1 -0
- package/LICENSE +201 -0
- package/README.md +117 -0
- package/Selection.d.ts +48 -0
- package/Selection.js +274 -0
- package/Selection.js.map +1 -0
- package/SelectionController.d.ts +20 -0
- package/SelectionController.js +57 -0
- package/SelectionController.js.map +1 -0
- package/_virtual/_virtual-pioneer-module_react-hooks.js +8 -0
- package/_virtual/_virtual-pioneer-module_react-hooks.js.map +1 -0
- package/api.d.ts +134 -0
- package/i18n/de.yaml +13 -0
- package/i18n/en.yaml +13 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/index.js.map +1 -0
- package/package.json +63 -0
- package/selection.css +40 -0
- package/selection.css.map +1 -0
- package/selectionSources.d.ts +37 -0
- package/selectionSources.js +64 -0
- package/selectionSources.js.map +1 -0
- package/services.d.ts +7 -0
- package/services.js +18 -0
- package/services.js.map +1 -0
package/Selection.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { VStack, FormControl, FormLabel, Flex, Box, Tooltip, chakra, Icon, useToken } from '@open-pioneer/chakra-integration';
|
|
3
|
+
import { useMapModel } from '@open-pioneer/map';
|
|
4
|
+
import { useCommonComponentProps, useEvent } from '@open-pioneer/react-utils';
|
|
5
|
+
import { Select, chakraComponents } from 'chakra-react-select';
|
|
6
|
+
import { useIntl, useService } from './_virtual/_virtual-pioneer-module_react-hooks.js';
|
|
7
|
+
import { useState, useCallback, useMemo, useEffect } from 'react';
|
|
8
|
+
import { FiAlertTriangle } from 'react-icons/fi';
|
|
9
|
+
import { DragController } from './DragController.js';
|
|
10
|
+
import { SelectionController } from './SelectionController.js';
|
|
11
|
+
|
|
12
|
+
var SelectionMethods = /* @__PURE__ */ ((SelectionMethods2) => {
|
|
13
|
+
SelectionMethods2["extent"] = "EXTENT";
|
|
14
|
+
SelectionMethods2["polygon"] = "POLYGON";
|
|
15
|
+
SelectionMethods2["free"] = "FREEPOLYGON";
|
|
16
|
+
SelectionMethods2["circle"] = "CIRCLE";
|
|
17
|
+
return SelectionMethods2;
|
|
18
|
+
})(SelectionMethods || {});
|
|
19
|
+
const COMMON_SELECT_PROPS = {
|
|
20
|
+
classNamePrefix: "react-select",
|
|
21
|
+
menuPosition: "fixed",
|
|
22
|
+
isSearchable: false,
|
|
23
|
+
isClearable: false
|
|
24
|
+
};
|
|
25
|
+
const Selection = (props) => {
|
|
26
|
+
const intl = useIntl();
|
|
27
|
+
const { mapId, sources, onSelectionComplete, onSelectionSourceChanged } = props;
|
|
28
|
+
const { containerProps } = useCommonComponentProps("selection", props);
|
|
29
|
+
const [currentSource, setCurrentSource] = useState(
|
|
30
|
+
() => sources.find((s) => (s.status ?? "available") === "available")
|
|
31
|
+
);
|
|
32
|
+
const mapState = useMapModel(mapId);
|
|
33
|
+
const { onExtentSelected } = useSelectionController(
|
|
34
|
+
mapState.map,
|
|
35
|
+
sources,
|
|
36
|
+
currentSource,
|
|
37
|
+
onSelectionComplete
|
|
38
|
+
);
|
|
39
|
+
const chakraStyles = useChakraStyles();
|
|
40
|
+
const buildMethodOptions = useCallback(
|
|
41
|
+
(methods) => {
|
|
42
|
+
const objects = [];
|
|
43
|
+
if (!methods)
|
|
44
|
+
methods = ["EXTENT" /* extent */];
|
|
45
|
+
methods.forEach((item) => {
|
|
46
|
+
if (Object.values(SelectionMethods).includes(item))
|
|
47
|
+
objects.push({ label: intl.formatMessage({ id: item }), value: item });
|
|
48
|
+
});
|
|
49
|
+
if (objects.length === 0)
|
|
50
|
+
throw new Error("methods does not contain valid values");
|
|
51
|
+
return objects;
|
|
52
|
+
},
|
|
53
|
+
[intl]
|
|
54
|
+
);
|
|
55
|
+
const methodOptions = buildMethodOptions(void 0);
|
|
56
|
+
const [selectedMethod, setSelectedMethod] = useState(methodOptions[0]);
|
|
57
|
+
const onMethodeOptionChance = useEvent((newValue) => {
|
|
58
|
+
setSelectedMethod(newValue);
|
|
59
|
+
});
|
|
60
|
+
const [dragControllerActive, setDragControllerActive] = useState(true);
|
|
61
|
+
useDragSelection(mapState.map, selectedMethod, intl, onExtentSelected, dragControllerActive);
|
|
62
|
+
const sourceOptions = useMemo(
|
|
63
|
+
() => sources.map((source) => {
|
|
64
|
+
return { label: source.label, value: source };
|
|
65
|
+
}),
|
|
66
|
+
[sources]
|
|
67
|
+
);
|
|
68
|
+
const currentSourceOption = useMemo(
|
|
69
|
+
() => sourceOptions.find((option) => option.value === currentSource),
|
|
70
|
+
[sourceOptions, currentSource]
|
|
71
|
+
);
|
|
72
|
+
const onSourceOptionChanged = useEvent((newValue) => {
|
|
73
|
+
setCurrentSource(newValue?.value);
|
|
74
|
+
onSelectionSourceChanged && onSelectionSourceChanged({ source: newValue?.value });
|
|
75
|
+
});
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!currentSource) {
|
|
78
|
+
setDragControllerActive(false);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const sourceNotAvailableReason = intl.formatMessage({ id: "sourceNotAvailable" });
|
|
82
|
+
const isCurrentSourceAvailable = () => {
|
|
83
|
+
return currentSource && getSourceStatus(currentSource, sourceNotAvailableReason).kind === "available";
|
|
84
|
+
};
|
|
85
|
+
setDragControllerActive(isCurrentSourceAvailable());
|
|
86
|
+
const handle = currentSource.on("changed:status", () => {
|
|
87
|
+
setDragControllerActive(isCurrentSourceAvailable());
|
|
88
|
+
});
|
|
89
|
+
return () => handle.destroy();
|
|
90
|
+
}, [currentSource, setDragControllerActive, intl]);
|
|
91
|
+
return /* @__PURE__ */ jsxs(VStack, { ...containerProps, spacing: 2, children: [
|
|
92
|
+
methodOptions.length > 1 && /* @__PURE__ */ jsxs(FormControl, { children: [
|
|
93
|
+
/* @__PURE__ */ jsx(FormLabel, { children: intl.formatMessage({ id: "selectMethod" }) }),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
Select,
|
|
96
|
+
{
|
|
97
|
+
className: "selection-method react-select",
|
|
98
|
+
...COMMON_SELECT_PROPS,
|
|
99
|
+
options: methodOptions,
|
|
100
|
+
onChange: onMethodeOptionChance,
|
|
101
|
+
value: selectedMethod,
|
|
102
|
+
chakraStyles
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
] }),
|
|
106
|
+
/* @__PURE__ */ jsxs(FormControl, { children: [
|
|
107
|
+
/* @__PURE__ */ jsx(FormLabel, { children: intl.formatMessage({ id: "selectSource" }) }),
|
|
108
|
+
/* @__PURE__ */ jsx(
|
|
109
|
+
Select,
|
|
110
|
+
{
|
|
111
|
+
className: "selection-source react-select",
|
|
112
|
+
...COMMON_SELECT_PROPS,
|
|
113
|
+
options: sourceOptions,
|
|
114
|
+
placeholder: intl.formatMessage({ id: "selectionPlaceholder" }),
|
|
115
|
+
value: currentSourceOption,
|
|
116
|
+
onChange: onSourceOptionChanged,
|
|
117
|
+
components: {
|
|
118
|
+
Option: SourceSelectOption,
|
|
119
|
+
SingleValue: SourceSelectValue
|
|
120
|
+
},
|
|
121
|
+
isOptionDisabled: (option) => option.value === void 0 || option.value.status === "unavailable",
|
|
122
|
+
getOptionLabel: (option) => option.label + (option.value === void 0 || option.value.status === "unavailable" ? " " + intl.formatMessage({ id: "sourceNotAvailable" }) : ""),
|
|
123
|
+
chakraStyles
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
] })
|
|
127
|
+
] });
|
|
128
|
+
};
|
|
129
|
+
function SourceSelectOption(props) {
|
|
130
|
+
const { value } = props.data;
|
|
131
|
+
const { isAvailable, content } = useSourceItem(value, false);
|
|
132
|
+
return /* @__PURE__ */ jsx(
|
|
133
|
+
chakraComponents.Option,
|
|
134
|
+
{
|
|
135
|
+
...props,
|
|
136
|
+
isDisabled: !isAvailable,
|
|
137
|
+
className: "selection-source-option",
|
|
138
|
+
children: content
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function SourceSelectValue(props) {
|
|
143
|
+
const { value } = props.data;
|
|
144
|
+
const { isAvailable, content } = useSourceItem(value, true);
|
|
145
|
+
const clazz = isAvailable ? "selection-source-value" : "selection-source-value selection-source-value--disabled";
|
|
146
|
+
return /* @__PURE__ */ jsx(chakraComponents.SingleValue, { ...props, isDisabled: !isAvailable, className: clazz, children: content });
|
|
147
|
+
}
|
|
148
|
+
function useSourceItem(source, isSelected) {
|
|
149
|
+
const label = source?.label;
|
|
150
|
+
const status = useSourceStatus(source);
|
|
151
|
+
return {
|
|
152
|
+
isAvailable: status.kind === "available",
|
|
153
|
+
content: /* @__PURE__ */ jsxs(Flex, { direction: "row", alignItems: "center", grow: 1, children: [
|
|
154
|
+
!isSelected && /* @__PURE__ */ jsx(Flex, { grow: 1, children: label }),
|
|
155
|
+
status.kind === "unavailable" && /* @__PURE__ */ jsx(Box, { ml: 2, children: /* @__PURE__ */ jsx(Tooltip, { label: status.reason, placement: "right", openDelay: 500, children: /* @__PURE__ */ jsx(chakra.span, { children: /* @__PURE__ */ jsx(
|
|
156
|
+
Icon,
|
|
157
|
+
{
|
|
158
|
+
as: FiAlertTriangle,
|
|
159
|
+
color: "red",
|
|
160
|
+
className: "warning-icon",
|
|
161
|
+
"aria-label": status.reason
|
|
162
|
+
}
|
|
163
|
+
) }) }) }),
|
|
164
|
+
isSelected && label
|
|
165
|
+
] })
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function useSelectionController(mapModel, sources, currentSource, onSelectionComplete) {
|
|
169
|
+
const notifier = useService("notifier.NotificationService");
|
|
170
|
+
const intl = useIntl();
|
|
171
|
+
const [controller, setController] = useState(void 0);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (!mapModel) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const controller2 = new SelectionController({
|
|
177
|
+
mapModel,
|
|
178
|
+
onError() {
|
|
179
|
+
notifier.notify({
|
|
180
|
+
level: "error",
|
|
181
|
+
message: intl.formatMessage({ id: "selectionFailed" })
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
setController(controller2);
|
|
186
|
+
return () => {
|
|
187
|
+
controller2.destroy();
|
|
188
|
+
};
|
|
189
|
+
}, [mapModel, notifier, sources, intl]);
|
|
190
|
+
const onExtentSelected = useEvent(async (geometry) => {
|
|
191
|
+
if (!controller || !currentSource) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const selectionResult = await controller.select(currentSource, geometry.getExtent());
|
|
195
|
+
if (!selectionResult) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
onSelectionComplete?.(selectionResult);
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
controller,
|
|
202
|
+
onExtentSelected
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function getSourceStatus(source, sourceNotAvailableReason) {
|
|
206
|
+
const rawCurrent = source.status ?? "available";
|
|
207
|
+
const current = typeof rawCurrent === "string" ? { kind: rawCurrent } : rawCurrent;
|
|
208
|
+
if (current.kind === "available") {
|
|
209
|
+
return current;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
kind: "unavailable",
|
|
213
|
+
reason: current.reason ?? sourceNotAvailableReason
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function useSourceStatus(source) {
|
|
217
|
+
const intl = useIntl();
|
|
218
|
+
const [status, setStatus] = useState(() => ({ kind: "available" }));
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (!source) {
|
|
221
|
+
setStatus({ kind: "available" });
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const sourceNotAvailableReason = intl.formatMessage({ id: "sourceNotAvailable" });
|
|
225
|
+
setStatus(getSourceStatus(source, sourceNotAvailableReason));
|
|
226
|
+
const resource = source.on?.("changed:status", () => {
|
|
227
|
+
setStatus(getSourceStatus(source, sourceNotAvailableReason));
|
|
228
|
+
});
|
|
229
|
+
return () => resource?.destroy();
|
|
230
|
+
}, [source, intl]);
|
|
231
|
+
return status;
|
|
232
|
+
}
|
|
233
|
+
function useDragSelection(map, selectMethode, intl, onExtentSelected, isActive) {
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!map) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const dragController = new DragController(
|
|
239
|
+
map.olMap,
|
|
240
|
+
selectMethode.value,
|
|
241
|
+
intl.formatMessage({ id: "tooltip" }),
|
|
242
|
+
intl.formatMessage({ id: "disabledTooltip" }),
|
|
243
|
+
onExtentSelected
|
|
244
|
+
);
|
|
245
|
+
dragController.setActive(isActive);
|
|
246
|
+
return () => {
|
|
247
|
+
dragController?.destroy();
|
|
248
|
+
};
|
|
249
|
+
}, [map, selectMethode, intl, onExtentSelected, isActive]);
|
|
250
|
+
}
|
|
251
|
+
function useChakraStyles() {
|
|
252
|
+
const [dropDownBackground, borderColor] = useToken(
|
|
253
|
+
"colors",
|
|
254
|
+
["background_body", "border"],
|
|
255
|
+
["#ffffff", "#ffffff"]
|
|
256
|
+
);
|
|
257
|
+
return useMemo(() => {
|
|
258
|
+
const chakraStyles = {
|
|
259
|
+
control: (styles) => ({ ...styles, cursor: "pointer" }),
|
|
260
|
+
indicatorSeparator: (styles) => ({
|
|
261
|
+
...styles,
|
|
262
|
+
borderColor
|
|
263
|
+
}),
|
|
264
|
+
dropdownIndicator: (provided) => ({
|
|
265
|
+
...provided,
|
|
266
|
+
backgroundColor: dropDownBackground
|
|
267
|
+
})
|
|
268
|
+
};
|
|
269
|
+
return chakraStyles;
|
|
270
|
+
}, [dropDownBackground, borderColor]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { Selection, SelectionMethods };
|
|
274
|
+
//# sourceMappingURL=Selection.js.map
|
package/Selection.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Selection.js","sources":["Selection.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport {\n Box,\n Flex,\n FormControl,\n FormLabel,\n Icon,\n Tooltip,\n VStack,\n chakra,\n useToken\n} from \"@open-pioneer/chakra-integration\";\nimport { MapModel, useMapModel } from \"@open-pioneer/map\";\nimport { NotificationService } from \"@open-pioneer/notifier\";\nimport { CommonComponentProps, useCommonComponentProps, useEvent } from \"@open-pioneer/react-utils\";\nimport { PackageIntl } from \"@open-pioneer/runtime/i18n\";\nimport {\n OptionProps,\n Select,\n Props as SelectProps,\n SingleValueProps,\n chakraComponents,\n type SingleValue,\n ChakraStylesConfig,\n GroupBase\n} from \"chakra-react-select\";\nimport { Geometry } from \"ol/geom\";\nimport { useIntl, useService } from \"open-pioneer:react-hooks\";\nimport { FC, useCallback, useEffect, useMemo, useState } from \"react\";\nimport { FiAlertTriangle } from \"react-icons/fi\";\nimport { DragController } from \"./DragController\";\nimport { SelectionController } from \"./SelectionController\";\nimport { SelectionResult, SelectionSource, SelectionSourceStatusObject } from \"./api\";\n\n/**\n * Properties supported by the {@link Selection} component.\n */\nexport interface SelectionProps extends CommonComponentProps {\n /**\n * The id of the map.\n */\n mapId: string;\n\n /**\n * Array of selection sources available for spatial selection.\n */\n sources: SelectionSource[];\n\n /**\n * This handler is called whenever the user has successfully selected\n * some items.\n */\n onSelectionComplete?(event: SelectionCompleteEvent): void;\n\n /**\n * This handler is called whenever the user has changed the selected source\n */\n onSelectionSourceChanged?(event: SelectionSourceChangedEvent): void;\n}\n\nexport interface SelectionCompleteEvent {\n /** The source that returned the {@link results}. */\n source: SelectionSource;\n\n /** Results selected by the user. */\n results: SelectionResult[];\n}\n\nexport interface SelectionSourceChangedEvent {\n /** The new selected source */\n source: SelectionSource | undefined;\n}\n\n/**\n * Properties for single select options.\n */\ninterface SelectionOption {\n /**\n * The label of the selection source option.\n */\n label: string;\n\n /**\n * The value (SelectionSource) of the selection source option.\n */\n value: SelectionSource | undefined;\n}\n\n/**\n * Properties for single selection method options.\n */\ninterface MethodOption {\n /**\n * The label of the select method option.\n */\n label: string;\n\n /**\n * The value of the select method option.\n */\n value: string;\n}\n\n/**\n * Supported selection methods\n */\nexport enum SelectionMethods {\n extent = \"EXTENT\",\n polygon = \"POLYGON\",\n free = \"FREEPOLYGON\",\n circle = \"CIRCLE\"\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst COMMON_SELECT_PROPS: SelectProps<any, any, any> = {\n classNamePrefix: \"react-select\",\n menuPosition: \"fixed\",\n isSearchable: false,\n isClearable: false\n};\n\n/**\n * A component that allows the user to perform a spatial selection on a given set of {@link SelectionSource}.\n */\nexport const Selection: FC<SelectionProps> = (props) => {\n const intl = useIntl();\n const { mapId, sources, onSelectionComplete, onSelectionSourceChanged } = props;\n const { containerProps } = useCommonComponentProps(\"selection\", props);\n const [currentSource, setCurrentSource] = useState<SelectionSource | undefined>(() =>\n sources.find((s) => (s.status ?? \"available\") === \"available\")\n );\n const mapState = useMapModel(mapId);\n const { onExtentSelected } = useSelectionController(\n mapState.map,\n sources,\n currentSource,\n onSelectionComplete\n );\n const chakraStyles = useChakraStyles();\n\n /**\n * Method to build Option-Array from the supported selection methods for the selection-method react-select\n * If there is no configuration => Default selection method: EXTENT\n */\n const buildMethodOptions = useCallback(\n (methods: string[] | undefined) => {\n const objects: MethodOption[] = [];\n if (!methods) methods = [SelectionMethods.extent];\n methods.forEach((item) => {\n if (Object.values(SelectionMethods as unknown as string[]).includes(item))\n objects.push({ label: intl.formatMessage({ id: item }), value: item });\n });\n if (objects.length === 0) throw new Error(\"methods does not contain valid values\");\n return objects;\n },\n [intl]\n );\n\n const methodOptions: MethodOption[] = buildMethodOptions(undefined);\n const [selectedMethod, setSelectedMethod] = useState(methodOptions[0] as MethodOption);\n\n /**\n * Method to change used selectmethod\n */\n const onMethodeOptionChance = useEvent((newValue: MethodOption) => {\n setSelectedMethod(newValue);\n });\n\n const [dragControllerActive, setDragControllerActive] = useState<boolean>(true);\n useDragSelection(mapState.map, selectedMethod, intl, onExtentSelected, dragControllerActive);\n\n /**\n * Method to build Option-Array from sources for the selection-source react-select\n */\n const sourceOptions = useMemo(\n () =>\n sources.map<SelectionOption>((source) => {\n return { label: source.label, value: source };\n }),\n [sources]\n );\n const currentSourceOption = useMemo(\n () => sourceOptions.find((option) => option.value === currentSource),\n [sourceOptions, currentSource]\n );\n\n /**\n * Method to change used source\n */\n const onSourceOptionChanged = useEvent((newValue: SingleValue<SelectionOption>) => {\n setCurrentSource(newValue?.value);\n onSelectionSourceChanged && onSelectionSourceChanged({ source: newValue?.value });\n });\n\n useEffect(() => {\n if (!currentSource) {\n setDragControllerActive(false);\n return;\n }\n\n const sourceNotAvailableReason = intl.formatMessage({ id: \"sourceNotAvailable\" });\n const isCurrentSourceAvailable = () => {\n return (\n currentSource &&\n getSourceStatus(currentSource, sourceNotAvailableReason).kind === \"available\"\n );\n };\n\n setDragControllerActive(isCurrentSourceAvailable());\n // Why can this be undefined after test above?!\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const handle = currentSource.on(\"changed:status\", () => {\n setDragControllerActive(isCurrentSourceAvailable());\n });\n return () => handle.destroy();\n }, [currentSource, setDragControllerActive, intl]);\n\n return (\n <VStack {...containerProps} spacing={2}>\n {methodOptions.length > 1 && (\n <FormControl>\n <FormLabel>{intl.formatMessage({ id: \"selectMethod\" })}</FormLabel>\n <Select\n className=\"selection-method react-select\"\n {...COMMON_SELECT_PROPS}\n options={methodOptions}\n onChange={onMethodeOptionChance}\n value={selectedMethod}\n chakraStyles={chakraStyles}\n />\n </FormControl>\n )}\n <FormControl>\n <FormLabel>{intl.formatMessage({ id: \"selectSource\" })}</FormLabel>\n <Select<SelectionOption>\n className=\"selection-source react-select\"\n {...COMMON_SELECT_PROPS}\n options={sourceOptions}\n placeholder={intl.formatMessage({ id: \"selectionPlaceholder\" })}\n value={currentSourceOption}\n onChange={onSourceOptionChanged}\n components={{\n Option: SourceSelectOption,\n SingleValue: SourceSelectValue\n }}\n isOptionDisabled={(option) =>\n option.value === undefined || option.value.status === \"unavailable\"\n }\n // optionLabel is used by screenreaders\n getOptionLabel={(option) =>\n option.label +\n (option.value === undefined || option.value.status === \"unavailable\"\n ? \" \" + intl.formatMessage({ id: \"sourceNotAvailable\" })\n : \"\")\n }\n chakraStyles={chakraStyles}\n />\n </FormControl>\n </VStack>\n );\n};\n\nfunction SourceSelectOption(props: OptionProps<SelectionOption>): JSX.Element {\n const { value } = props.data;\n const { isAvailable, content } = useSourceItem(value, false);\n\n return (\n <chakraComponents.Option\n {...props}\n isDisabled={!isAvailable}\n className=\"selection-source-option\"\n >\n {content}\n </chakraComponents.Option>\n );\n}\n\nfunction SourceSelectValue(props: SingleValueProps<SelectionOption>): JSX.Element {\n const { value } = props.data;\n const { isAvailable, content } = useSourceItem(value, true);\n const clazz = isAvailable\n ? \"selection-source-value\"\n : \"selection-source-value selection-source-value--disabled\";\n\n return (\n <chakraComponents.SingleValue {...props} isDisabled={!isAvailable} className={clazz}>\n {content}\n </chakraComponents.SingleValue>\n );\n}\n\n/**\n * Hook to manage source option in selection-source react-select\n */\nfunction useSourceItem(source: SelectionSource | undefined, isSelected: boolean) {\n const label: string | undefined = source?.label;\n const status = useSourceStatus(source);\n\n return {\n isAvailable: status.kind === \"available\",\n content: (\n <Flex direction=\"row\" alignItems=\"center\" grow={1}>\n {!isSelected && <Flex grow={1}>{label}</Flex>}\n {status.kind === \"unavailable\" && (\n <Box ml={2}>\n <Tooltip label={status.reason} placement=\"right\" openDelay={500}>\n <chakra.span>\n <Icon\n as={FiAlertTriangle}\n color=\"red\"\n className=\"warning-icon\"\n aria-label={status.reason}\n />\n </chakra.span>\n </Tooltip>\n </Box>\n )}\n {isSelected && label}\n </Flex>\n )\n };\n}\n\n/**\n * Hook to manage selection sources\n */\nfunction useSelectionController(\n mapModel: MapModel | undefined,\n sources: SelectionSource[],\n currentSource: SelectionSource | undefined,\n onSelectionComplete: ((event: SelectionCompleteEvent) => void) | undefined\n) {\n const notifier = useService<NotificationService>(\"notifier.NotificationService\");\n const intl = useIntl();\n const [controller, setController] = useState<SelectionController | undefined>(undefined);\n useEffect(() => {\n if (!mapModel) {\n return;\n }\n const controller = new SelectionController({\n mapModel,\n onError() {\n notifier.notify({\n level: \"error\",\n message: intl.formatMessage({ id: \"selectionFailed\" })\n });\n }\n });\n setController(controller);\n return () => {\n controller.destroy();\n };\n }, [mapModel, notifier, sources, intl]);\n\n const onExtentSelected = useEvent(async (geometry: Geometry) => {\n if (!controller || !currentSource) {\n return;\n }\n\n const selectionResult = await controller.select(currentSource, geometry.getExtent());\n if (!selectionResult) {\n return;\n }\n\n onSelectionComplete?.(selectionResult);\n });\n return {\n controller,\n onExtentSelected\n };\n}\n\ntype SimpleStatus =\n | {\n kind: \"available\";\n }\n | {\n kind: \"unavailable\";\n reason: string;\n };\n\nfunction getSourceStatus(source: SelectionSource, sourceNotAvailableReason: string): SimpleStatus {\n const rawCurrent = source.status ?? \"available\";\n const current: SelectionSourceStatusObject =\n typeof rawCurrent === \"string\" ? { kind: rawCurrent } : rawCurrent;\n if (current.kind === \"available\") {\n return current;\n }\n\n return {\n kind: \"unavailable\",\n reason: current.reason ?? sourceNotAvailableReason\n };\n}\n\n/**\n * Hook to manage source status\n */\nfunction useSourceStatus(source: SelectionSource | undefined): SimpleStatus {\n const intl = useIntl();\n const [status, setStatus] = useState<SimpleStatus>(() => ({ kind: \"available\" }));\n useEffect(() => {\n if (!source) {\n setStatus({ kind: \"available\" });\n return;\n }\n const sourceNotAvailableReason = intl.formatMessage({ id: \"sourceNotAvailable\" });\n setStatus(getSourceStatus(source, sourceNotAvailableReason));\n const resource = source.on?.(\"changed:status\", () => {\n setStatus(getSourceStatus(source, sourceNotAvailableReason));\n });\n return () => resource?.destroy();\n }, [source, intl]);\n return status;\n}\n\n/**\n * Hook to manage map controls and tooltip\n */\nfunction useDragSelection(\n map: MapModel | undefined,\n selectMethode: MethodOption,\n intl: PackageIntl,\n onExtentSelected: (geometry: Geometry) => void,\n isActive: boolean\n) {\n useEffect(() => {\n if (!map) {\n return;\n }\n\n const dragController = new DragController(\n map.olMap,\n selectMethode.value,\n intl.formatMessage({ id: \"tooltip\" }),\n intl.formatMessage({ id: \"disabledTooltip\" }),\n onExtentSelected\n );\n dragController.setActive(isActive);\n\n return () => {\n dragController?.destroy();\n };\n }, [map, selectMethode, intl, onExtentSelected, isActive]);\n}\n\n/**\n * Customizes components styles within the select component.\n */\nfunction useChakraStyles() {\n const [dropDownBackground, borderColor] = useToken(\n \"colors\",\n [\"background_body\", \"border\"],\n [\"#ffffff\", \"#ffffff\"]\n );\n return useMemo(() => {\n const chakraStyles: ChakraStylesConfig<\n SelectionOption,\n false,\n GroupBase<SelectionOption>\n > = {\n control: (styles) => ({ ...styles, cursor: \"pointer\" }),\n indicatorSeparator: (styles) => ({\n ...styles,\n borderColor: borderColor\n }),\n dropdownIndicator: (provided) => ({\n ...provided,\n backgroundColor: dropDownBackground\n })\n };\n return chakraStyles;\n }, [dropDownBackground, borderColor]);\n}\n"],"names":["SelectionMethods","controller"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SelectionSource, SelectionResult } from "./api";
|
|
2
|
+
import { MapModel } from "@open-pioneer/map";
|
|
3
|
+
import { Extent } from "ol/extent";
|
|
4
|
+
/**
|
|
5
|
+
* All results returned from one source.
|
|
6
|
+
*/
|
|
7
|
+
export interface SelectionSourceResults {
|
|
8
|
+
source: SelectionSource;
|
|
9
|
+
results: SelectionResult[];
|
|
10
|
+
}
|
|
11
|
+
export declare class SelectionController {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(options: {
|
|
14
|
+
mapModel: MapModel;
|
|
15
|
+
onError: () => void;
|
|
16
|
+
maxResults?: number;
|
|
17
|
+
});
|
|
18
|
+
destroy(): void;
|
|
19
|
+
select(source: SelectionSource, extent: Extent): Promise<SelectionSourceResults | undefined>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createLogger } from '@open-pioneer/core';
|
|
2
|
+
|
|
3
|
+
const LOG = createLogger("selection:SelectionController");
|
|
4
|
+
const DEFAULT_MAX_RESULTS = 1e4;
|
|
5
|
+
class SelectionController {
|
|
6
|
+
#mapModel;
|
|
7
|
+
/**
|
|
8
|
+
* Limits the number of results.
|
|
9
|
+
*/
|
|
10
|
+
#maxResults;
|
|
11
|
+
/**
|
|
12
|
+
* Called whenever an error happens.
|
|
13
|
+
*/
|
|
14
|
+
#onError;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
const { mapModel, onError, maxResults = DEFAULT_MAX_RESULTS } = options;
|
|
17
|
+
this.#mapModel = mapModel;
|
|
18
|
+
this.#maxResults = maxResults;
|
|
19
|
+
this.#onError = onError;
|
|
20
|
+
}
|
|
21
|
+
destroy() {
|
|
22
|
+
}
|
|
23
|
+
async select(source, extent) {
|
|
24
|
+
if (!extent) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
return await this.#selectFromSource(source, extent);
|
|
28
|
+
}
|
|
29
|
+
async #selectFromSource(source, extent) {
|
|
30
|
+
const projection = this.#mapModel.olMap.getView().getProjection();
|
|
31
|
+
try {
|
|
32
|
+
LOG.debug(`Starting selection on source '${source.label}'`);
|
|
33
|
+
const maxResults = this.#maxResults;
|
|
34
|
+
let results = await source.select(
|
|
35
|
+
{ type: "extent", extent },
|
|
36
|
+
{
|
|
37
|
+
maxResults,
|
|
38
|
+
mapProjection: projection,
|
|
39
|
+
signal: new AbortController().signal
|
|
40
|
+
// currently not used
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
if (results.length > maxResults) {
|
|
44
|
+
results = results.slice(0, maxResults);
|
|
45
|
+
}
|
|
46
|
+
LOG.debug(`Found ${results.length} results on source '${source.label}'`);
|
|
47
|
+
return { source, results };
|
|
48
|
+
} catch (e) {
|
|
49
|
+
LOG.error(`selection from source ${source.label} failed`, e);
|
|
50
|
+
this.#onError();
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { SelectionController };
|
|
57
|
+
//# sourceMappingURL=SelectionController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectionController.js","sources":["SelectionController.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger } from \"@open-pioneer/core\";\nimport type { SelectionSource, SelectionResult } from \"./api\";\nimport { MapModel } from \"@open-pioneer/map\";\nimport { Extent } from \"ol/extent\";\n\nconst LOG = createLogger(\"selection:SelectionController\");\n\n/**\n * All results returned from one source.\n */\nexport interface SelectionSourceResults {\n source: SelectionSource;\n results: SelectionResult[];\n}\n\nconst DEFAULT_MAX_RESULTS = 10000;\n\nexport class SelectionController {\n #mapModel: MapModel;\n\n /**\n * Limits the number of results.\n */\n readonly #maxResults: number;\n\n /**\n * Called whenever an error happens.\n */\n readonly #onError: () => void;\n\n constructor(options: { mapModel: MapModel; onError: () => void; maxResults?: number }) {\n const { mapModel, onError, maxResults = DEFAULT_MAX_RESULTS } = options;\n this.#mapModel = mapModel;\n this.#maxResults = maxResults;\n this.#onError = onError;\n }\n\n destroy() {}\n\n async select(\n source: SelectionSource,\n extent: Extent\n ): Promise<SelectionSourceResults | undefined> {\n if (!extent) {\n return undefined;\n }\n\n return await this.#selectFromSource(source, extent);\n }\n\n async #selectFromSource(\n source: SelectionSource,\n extent: Extent\n ): Promise<SelectionSourceResults | undefined> {\n const projection = this.#mapModel.olMap.getView().getProjection();\n try {\n LOG.debug(`Starting selection on source '${source.label}'`);\n\n const maxResults = this.#maxResults;\n let results = await source.select(\n { type: \"extent\", extent },\n {\n maxResults,\n mapProjection: projection,\n signal: new AbortController().signal // currently not used\n }\n );\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n\n LOG.debug(`Found ${results.length} results on source '${source.label}'`);\n return { source: source, results: results };\n } catch (e) {\n LOG.error(`selection from source ${source.label} failed`, e);\n this.#onError();\n return undefined;\n }\n }\n}\n"],"names":[],"mappings":";;AAOA,MAAM,GAAA,GAAM,aAAa,+BAA+B,CAAA,CAAA;AAUxD,MAAM,mBAAsB,GAAA,GAAA,CAAA;AAErB,MAAM,mBAAoB,CAAA;AAAA,EAC7B,SAAA,CAAA;AAAA;AAAA;AAAA;AAAA,EAKS,WAAA,CAAA;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,CAAA;AAAA,EAET,YAAY,OAA2E,EAAA;AACnF,IAAA,MAAM,EAAE,QAAA,EAAU,OAAS,EAAA,UAAA,GAAa,qBAAwB,GAAA,OAAA,CAAA;AAChE,IAAA,IAAA,CAAK,SAAY,GAAA,QAAA,CAAA;AACjB,IAAA,IAAA,CAAK,WAAc,GAAA,UAAA,CAAA;AACnB,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA,CAAA;AAAA,GACpB;AAAA,EAEA,OAAU,GAAA;AAAA,GAAC;AAAA,EAEX,MAAM,MACF,CAAA,MAAA,EACA,MAC2C,EAAA;AAC3C,IAAA,IAAI,CAAC,MAAQ,EAAA;AACT,MAAO,OAAA,KAAA,CAAA,CAAA;AAAA,KACX;AAEA,IAAA,OAAO,MAAM,IAAA,CAAK,iBAAkB,CAAA,MAAA,EAAQ,MAAM,CAAA,CAAA;AAAA,GACtD;AAAA,EAEA,MAAM,iBACF,CAAA,MAAA,EACA,MAC2C,EAAA;AAC3C,IAAA,MAAM,aAAa,IAAK,CAAA,SAAA,CAAU,KAAM,CAAA,OAAA,GAAU,aAAc,EAAA,CAAA;AAChE,IAAI,IAAA;AACA,MAAA,GAAA,CAAI,KAAM,CAAA,CAAA,8BAAA,EAAiC,MAAO,CAAA,KAAK,CAAG,CAAA,CAAA,CAAA,CAAA;AAE1D,MAAA,MAAM,aAAa,IAAK,CAAA,WAAA,CAAA;AACxB,MAAI,IAAA,OAAA,GAAU,MAAM,MAAO,CAAA,MAAA;AAAA,QACvB,EAAE,IAAM,EAAA,QAAA,EAAU,MAAO,EAAA;AAAA,QACzB;AAAA,UACI,UAAA;AAAA,UACA,aAAe,EAAA,UAAA;AAAA,UACf,MAAA,EAAQ,IAAI,eAAA,EAAkB,CAAA,MAAA;AAAA;AAAA,SAClC;AAAA,OACJ,CAAA;AACA,MAAI,IAAA,OAAA,CAAQ,SAAS,UAAY,EAAA;AAC7B,QAAU,OAAA,GAAA,OAAA,CAAQ,KAAM,CAAA,CAAA,EAAG,UAAU,CAAA,CAAA;AAAA,OACzC;AAEA,MAAA,GAAA,CAAI,MAAM,CAAS,MAAA,EAAA,OAAA,CAAQ,MAAM,CAAuB,oBAAA,EAAA,MAAA,CAAO,KAAK,CAAG,CAAA,CAAA,CAAA,CAAA;AACvE,MAAO,OAAA,EAAE,QAAgB,OAAiB,EAAA,CAAA;AAAA,aACrC,CAAG,EAAA;AACR,MAAA,GAAA,CAAI,KAAM,CAAA,CAAA,sBAAA,EAAyB,MAAO,CAAA,KAAK,WAAW,CAAC,CAAA,CAAA;AAC3D,MAAA,IAAA,CAAK,QAAS,EAAA,CAAA;AACd,MAAO,OAAA,KAAA,CAAA,CAAA;AAAA,KACX;AAAA,GACJ;AACJ;;;;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useServiceInternal, useIntlInternal } from '@open-pioneer/runtime/react-integration';
|
|
2
|
+
|
|
3
|
+
const PACKAGE_NAME = "@open-pioneer/selection";
|
|
4
|
+
const useService = /*@__PURE__*/ useServiceInternal.bind(undefined, PACKAGE_NAME);
|
|
5
|
+
const useIntl = /*@__PURE__*/ useIntlInternal.bind(undefined, PACKAGE_NAME);
|
|
6
|
+
|
|
7
|
+
export { useIntl, useService };
|
|
8
|
+
//# sourceMappingURL=_virtual-pioneer-module_react-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_virtual-pioneer-module_react-hooks.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/api.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Geometry } from "ol/geom";
|
|
2
|
+
import type { Projection } from "ol/proj";
|
|
3
|
+
import type { Extent } from "ol/extent";
|
|
4
|
+
import type { EventSource, Resource } from "@open-pioneer/core";
|
|
5
|
+
import { BaseFeature } from "@open-pioneer/map/api/BaseFeature";
|
|
6
|
+
import { DeclaredService } from "@open-pioneer/runtime";
|
|
7
|
+
import VectorLayer from "ol/layer/Vector";
|
|
8
|
+
import VectorSource from "ol/source/Vector";
|
|
9
|
+
/**
|
|
10
|
+
* The status of a selection source.
|
|
11
|
+
*
|
|
12
|
+
* This is used to indicate whether the source is ready for selection.
|
|
13
|
+
*/
|
|
14
|
+
export type SelectionSourceStatus = "available" | "unavailable" | SelectionSourceStatusObject;
|
|
15
|
+
export type SelectionSourceStatusObject = {
|
|
16
|
+
kind: "available";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "unavailable";
|
|
19
|
+
/**
|
|
20
|
+
* If the status of this source is unavailable, the reason for this can be stored here.
|
|
21
|
+
*
|
|
22
|
+
* This will be displayed by the user interface.
|
|
23
|
+
*
|
|
24
|
+
* If it is not defined, a default message will be displayed instead.
|
|
25
|
+
*/
|
|
26
|
+
reason?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Represents a result returned by a spatial selection.
|
|
30
|
+
*/
|
|
31
|
+
export interface SelectionResult extends BaseFeature {
|
|
32
|
+
/**
|
|
33
|
+
* Geometry of the selection result.
|
|
34
|
+
* One should also specify the {@link projection}.
|
|
35
|
+
*/
|
|
36
|
+
geometry: Geometry;
|
|
37
|
+
}
|
|
38
|
+
/** Options passed to a {@link SelectionSource} when triggering a select. */
|
|
39
|
+
export interface SelectionOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The maximum number of selection results to request.
|
|
42
|
+
* The selection component currently only supports a certain amount of results (indicated by this value).
|
|
43
|
+
* If a source results more than `maxResults` results, additional results will be ignored.
|
|
44
|
+
*/
|
|
45
|
+
maxResults: number;
|
|
46
|
+
/**
|
|
47
|
+
* The current projection of the map.
|
|
48
|
+
* Useful to return the selection result's geometry in the suitable projection, should they differ.
|
|
49
|
+
*/
|
|
50
|
+
mapProjection: Projection;
|
|
51
|
+
/**
|
|
52
|
+
* The signal can be used to detect cancellation.
|
|
53
|
+
*
|
|
54
|
+
* You can pass this signal to builtin functions like `fetch` that automatically
|
|
55
|
+
* support cancellation.
|
|
56
|
+
*/
|
|
57
|
+
signal: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
/** Events emitted by the {@link SelectionSource}. */
|
|
60
|
+
export interface SelectionSourceEvents {
|
|
61
|
+
"changed:status": void;
|
|
62
|
+
}
|
|
63
|
+
/** Optional base type for selection source: the event emitter interface is not required. */
|
|
64
|
+
export type SelectionSourceEventBase = EventSource<SelectionSourceEvents>;
|
|
65
|
+
/**
|
|
66
|
+
* The user has selected an extent.
|
|
67
|
+
*/
|
|
68
|
+
export interface ExtentSelection {
|
|
69
|
+
type: "extent";
|
|
70
|
+
extent: Extent;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The selection made by the user.
|
|
74
|
+
*
|
|
75
|
+
* This is currently always `type: "extent"`, but additional selection kinds
|
|
76
|
+
* may be added in the future.
|
|
77
|
+
*
|
|
78
|
+
* Selection sources should check the `type` and throw an error for unsupported
|
|
79
|
+
* selection kinds in order to remain forwards compatible.
|
|
80
|
+
*/
|
|
81
|
+
export type SelectionKind = ExtentSelection;
|
|
82
|
+
/**
|
|
83
|
+
* An object that allows spatial selection.
|
|
84
|
+
*
|
|
85
|
+
* Developers can create classes that implement this interface for different selection sources.
|
|
86
|
+
*
|
|
87
|
+
* The implementation of `SelectionSourceEventBase` is optional: it is only necessary if the status changes
|
|
88
|
+
* during the lifetime of the selection source.
|
|
89
|
+
* To implement events, you can write `class MySelectionSource extends EventEmitter<SelectionSourceEvents>`.
|
|
90
|
+
*
|
|
91
|
+
*/
|
|
92
|
+
export interface SelectionSource extends Partial<SelectionSourceEventBase> {
|
|
93
|
+
/**
|
|
94
|
+
* The label of this source.
|
|
95
|
+
*
|
|
96
|
+
* This will be displayed by the user interface during selection source selection.
|
|
97
|
+
*/
|
|
98
|
+
readonly label: string;
|
|
99
|
+
/**
|
|
100
|
+
* The optional status of this source. If there is no status defined, it is assumed that the
|
|
101
|
+
* source is always available.
|
|
102
|
+
*
|
|
103
|
+
* This will be displayed by the user interface.
|
|
104
|
+
*/
|
|
105
|
+
readonly status?: SelectionSourceStatus;
|
|
106
|
+
/**
|
|
107
|
+
* Performs a selection and returns a list of selection results.
|
|
108
|
+
*
|
|
109
|
+
* Implementations should return the results ordered by priority (best match first), if possible.
|
|
110
|
+
*
|
|
111
|
+
* @param selectionKind The geometry with which to perform the spatial selection. Currently only
|
|
112
|
+
* an extent is supported.
|
|
113
|
+
* @param options see interface documentation {@link SelectionOptions}
|
|
114
|
+
*/
|
|
115
|
+
select(selectionKind: SelectionKind, options: SelectionOptions): Promise<SelectionResult[]>;
|
|
116
|
+
}
|
|
117
|
+
export interface VectorLayerSelectionSourceOptions {
|
|
118
|
+
vectorLayer: VectorLayer<VectorSource>;
|
|
119
|
+
label: string;
|
|
120
|
+
}
|
|
121
|
+
export interface VectorLayerSelectionSource extends Required<SelectionSource>, Resource {
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A factory that creates {@link VectorLayerSelectionSource | selection sources} to be used on an
|
|
125
|
+
* OpenLayers VectorLayer with an OpenLayers VectorSource (e.g. layer of the map).
|
|
126
|
+
*
|
|
127
|
+
* Use the interface name `"selection.VectorSelectionSourceFactory"` to obtain an instance of this factory.
|
|
128
|
+
*/
|
|
129
|
+
export interface VectorLayerSelectionSourceFactory extends DeclaredService<"selection.VectorSelectionSourceFactory"> {
|
|
130
|
+
/**
|
|
131
|
+
* Returns a new {@link VectorLayerSelectionSourceImpl} that operates on the given OpenLayers VectorLayer.
|
|
132
|
+
*/
|
|
133
|
+
createSelectionSource(options: VectorLayerSelectionSourceOptions): VectorLayerSelectionSource;
|
|
134
|
+
}
|
package/i18n/de.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
messages:
|
|
2
|
+
selectMethod: "Methode auswählen"
|
|
3
|
+
EXTENT: "Rechteck"
|
|
4
|
+
POLYGON: "Polygon"
|
|
5
|
+
FREEPOLYGON: "Freies Zeichnen"
|
|
6
|
+
CIRCLE: "Kreis"
|
|
7
|
+
selectSource: "Quelle auswählen"
|
|
8
|
+
tooltip: "Klicken Sie in die Karte, halten Sie die Maustaste gedrückt und ziehen Sie ein Rechteck auf"
|
|
9
|
+
disabledTooltip: "Die aktuelle Suchquelle ist nicht verfügbar."
|
|
10
|
+
sourceNotAvailable: "Quelle nicht verfügbar"
|
|
11
|
+
selectionFailed: "Die räumliche Auswahl ist fehlgeschlagen"
|
|
12
|
+
selectionPlaceholder: "Keine Quelle ausgewählt"
|
|
13
|
+
layerNotVisibleReason: "Zugehöriger Layer ist nicht sichtbar."
|
package/i18n/en.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
messages:
|
|
2
|
+
selectMethod: Select method
|
|
3
|
+
EXTENT: "Rechteck"
|
|
4
|
+
POLYGON: "Polygon"
|
|
5
|
+
FREEPOLYGON: "Freies Zeichnen"
|
|
6
|
+
CIRCLE: "Kreis"
|
|
7
|
+
selectSource: "Select source"
|
|
8
|
+
tooltip: "Click on the map, hold down the mouse button and draw a rectangle"
|
|
9
|
+
disabledTooltip: "The current search source is not available"
|
|
10
|
+
sourceNotAvailable: "Source not available"
|
|
11
|
+
selectionFailed: "Spatial selection failed"
|
|
12
|
+
selectionPlaceholder: "No source selected"
|
|
13
|
+
layerNotVisibleReason: "Matching layer is not visible."
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|