@vcmap/ui 6.0.14 → 6.1.0-rc.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/config/base.config.json +25 -3
- package/config/dev.config.json +17 -3
- package/config/splashscreen.config.json +13 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/{core-882e211a.js → core-fd079400.js} +6861 -5788
- package/dist/assets/core.js +1 -1
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui-5135917c.css +1 -0
- package/dist/assets/{ui-b6bff1d9.js → ui-5135917c.js} +16764 -18447
- package/dist/assets/ui.js +1 -1
- package/dist/assets/vue.js +1 -1
- package/dist/assets/{vuetify-2d64c180.js → vuetify-f02b7bb9.js} +1 -1
- package/dist/assets/vuetify.js +1 -1
- package/index.d.ts +13 -1
- package/index.js +7 -0
- package/package.json +2 -2
- package/plugins/@vcmap-show-case/dev-tools/package.json +5 -0
- package/plugins/@vcmap-show-case/dev-tools/src/eventLogger.js +35 -0
- package/plugins/@vcmap-show-case/dev-tools/src/index.js +59 -0
- package/plugins/@vcmap-show-case/search-example/src/searchImpl.js +10 -0
- package/src/application/VcsApp.vue.d.ts +26 -0
- package/src/application/VcsContainer.vue +5 -3
- package/src/application/VcsContainer.vue.d.ts +21 -0
- package/src/application/VcsNavbar.vue +10 -6
- package/src/application/VcsNavbar.vue.d.ts +2 -0
- package/src/application/VcsSplashScreen.vue +35 -28
- package/src/application/VcsSplashScreen.vue.d.ts +1 -0
- package/src/callback/addModuleCallback.d.ts +29 -0
- package/src/callback/addModuleCallback.js +61 -0
- package/src/callback/removeModuleCallback.d.ts +29 -0
- package/src/callback/removeModuleCallback.js +53 -0
- package/src/callback/startRotationCallback.d.ts +37 -0
- package/src/callback/startRotationCallback.js +67 -0
- package/src/callback/stopRotationCallback.d.ts +8 -0
- package/src/callback/stopRotationCallback.js +37 -0
- package/src/components/buttons/VcsActionButtonList.vue +6 -4
- package/src/components/buttons/VcsToolButton.vue +0 -1
- package/src/components/form-inputs-controls/VcsDatePicker.vue +7 -1
- package/src/components/form-inputs-controls/VcsDatePicker.vue.d.ts +9 -0
- package/src/components/form-inputs-controls/VcsTextArea.vue +1 -1
- package/src/components/icons/+all.js +4 -0
- package/src/components/icons/View360Icon.vue +55 -0
- package/src/components/icons/View360Icon.vue.d.ts +2 -0
- package/src/components/import/VcsImportComponent.vue +2 -0
- package/src/components/lists/VcsList.vue +15 -11
- package/src/components/lists/VcsList.vue.d.ts +9 -0
- package/src/components/lists/VcsTreeNode.vue +244 -0
- package/src/components/lists/VcsTreeNode.vue.d.ts +31 -0
- package/src/components/lists/VcsTreeview.vue +111 -173
- package/src/components/lists/VcsTreeview.vue.d.ts +58 -4
- package/src/components/lists/VcsTreeviewTitle.vue +10 -3
- package/src/components/lists/VcsTreeviewTitle.vue.d.ts +2 -0
- package/src/components/tables/VcsDataTable.vue +14 -3
- package/src/components/tables/VcsDataTable.vue.d.ts +9 -0
- package/src/featureInfo/BalloonComponent.vue +18 -47
- package/src/featureInfo/BalloonComponent.vue.d.ts +0 -1
- package/src/featureInfo/IframeComponent.vue +1 -32
- package/src/featureInfo/IframeComponent.vue.d.ts +1 -4
- package/src/i18n/de.d.ts +1 -0
- package/src/i18n/de.js +1 -0
- package/src/i18n/en.d.ts +1 -0
- package/src/i18n/en.js +1 -0
- package/src/init.d.ts +6 -0
- package/src/init.js +26 -14
- package/src/legend/VcsLegend.vue +1 -1
- package/src/manager/toolbox/ToolboxManagerComponent.vue +4 -4
- package/src/manager/toolbox/ToolboxManagerComponent.vue.d.ts +2 -2
- package/src/manager/toolbox/toolboxManager.d.ts +5 -0
- package/src/manager/toolbox/toolboxManager.js +7 -1
- package/src/manager/window/WindowComponent.vue +11 -1
- package/src/manager/window/WindowComponent.vue.d.ts +1 -0
- package/src/manager/window/WindowManager.vue +14 -4
- package/src/manager/window/WindowManager.vue.d.ts +1 -0
- package/src/navigation/MapNavigation.vue +87 -5
- package/src/navigation/MapNavigation.vue.d.ts +3 -1
- package/src/navigation/overviewMap.d.ts +6 -0
- package/src/navigation/overviewMap.js +14 -1
- package/src/pluginHelper.d.ts +0 -7
- package/src/pluginHelper.js +4 -18
- package/src/search/ResultItem.vue +1 -10
- package/src/search/ResultsComponent.vue +11 -1
- package/src/search/ResultsComponent.vue.d.ts +9 -0
- package/src/search/SearchComponent.vue +88 -11
- package/src/search/SearchComponent.vue.d.ts +7 -0
- package/src/search/markText.d.ts +1 -1
- package/src/search/markText.js +4 -4
- package/src/search/search.d.ts +3 -0
- package/src/search/search.js +3 -2
- package/src/state.d.ts +2 -4
- package/src/state.js +31 -54
- package/src/uiConfig.d.ts +40 -0
- package/src/uiConfig.js +6 -0
- package/src/vcsUiApp.js +11 -7
- package/src/vuePlugins/vuetify.js +2 -0
- package/dist/assets/ui-b6bff1d9.css +0 -1
- /package/dist/assets/{cesium-615823f2.js → cesium-57fbd309.js} +0 -0
- /package/dist/assets/{ol-7fc05707.js → ol-50dfef96.js} +0 -0
- /package/dist/assets/{vue-74e8343e.js → vue-c3c55d88.js} +0 -0
- /package/dist/assets/{vuetify-2d64c180.css → vuetify-f02b7bb9.css} +0 -0
@@ -1,6 +1,8 @@
|
|
1
1
|
<template>
|
2
2
|
<div
|
3
|
-
:class="{
|
3
|
+
:class="{
|
4
|
+
'win-container-mobile': addMobileClass,
|
5
|
+
}"
|
4
6
|
class="window-manager"
|
5
7
|
>
|
6
8
|
<WindowComponent
|
@@ -126,13 +128,14 @@
|
|
126
128
|
// do not clip balloons to target
|
127
129
|
return windowComponent?.position;
|
128
130
|
}
|
129
|
-
|
130
131
|
return getPositionAppliedOnTarget(
|
131
132
|
windowComponent?.position,
|
132
133
|
targetSize.value,
|
133
134
|
getPosition(parentComponent),
|
134
135
|
);
|
135
136
|
};
|
137
|
+
const display = useDisplay();
|
138
|
+
|
136
139
|
/**
|
137
140
|
* @param {string} id
|
138
141
|
* @returns {import("vue").ComputedRef<Object>}
|
@@ -140,12 +143,14 @@
|
|
140
143
|
const getStyles = (id) =>
|
141
144
|
computed(() => {
|
142
145
|
const windowComponent = windowManager.get(id);
|
146
|
+
const zIndexOffset = Number(display.sm.value); // add z-Index Offset for Tablet View to keep the windows above the detached Icon
|
143
147
|
return {
|
144
|
-
zIndex: windowComponent.zIndex.value,
|
148
|
+
zIndex: windowComponent.zIndex.value + zIndexOffset,
|
145
149
|
...getPosition(windowComponent),
|
146
150
|
...(windowComponent?.state?.styles || {}),
|
147
151
|
};
|
148
152
|
});
|
153
|
+
|
149
154
|
/**
|
150
155
|
* @param {string} id
|
151
156
|
*/
|
@@ -163,11 +168,15 @@
|
|
163
168
|
const position = getPosition(windowComponent);
|
164
169
|
moveWindow(id, translation, windowManager, targetSize.value, position);
|
165
170
|
};
|
166
|
-
|
171
|
+
|
167
172
|
const addMobileClass = computed(() => {
|
168
173
|
return display.xs.value && componentIds.length > 0;
|
169
174
|
});
|
170
175
|
|
176
|
+
const addTabletClass = computed(() => {
|
177
|
+
return display.sm.value && componentIds.length > 0;
|
178
|
+
});
|
179
|
+
|
171
180
|
const setTargetSize = () => {
|
172
181
|
targetSize.value = getTargetSize(app.maps.target);
|
173
182
|
};
|
@@ -199,6 +208,7 @@
|
|
199
208
|
bringWindowToTop,
|
200
209
|
move,
|
201
210
|
addMobileClass,
|
211
|
+
addTabletClass,
|
202
212
|
};
|
203
213
|
},
|
204
214
|
};
|
@@ -15,5 +15,6 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
15
15
|
dy: number;
|
16
16
|
}) => void;
|
17
17
|
addMobileClass: import("vue").ComputedRef<boolean>;
|
18
|
+
addTabletClass: import("vue").ComputedRef<boolean>;
|
18
19
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
|
19
20
|
export default _default;
|
@@ -25,7 +25,7 @@
|
|
25
25
|
></OrientationToolsButton>
|
26
26
|
</v-row>
|
27
27
|
</template>
|
28
|
-
<template v-if="
|
28
|
+
<template v-if="smAndUp">
|
29
29
|
<v-row justify="center">
|
30
30
|
<VcsZoomButton
|
31
31
|
@zoom-out="zoomOut()"
|
@@ -33,9 +33,18 @@
|
|
33
33
|
:disabled="movementApiCallsDisabled"
|
34
34
|
/>
|
35
35
|
</v-row>
|
36
|
-
<v-row justify="center" v-if="is3D &&
|
36
|
+
<v-row justify="center" v-if="is3D && smAndUp">
|
37
37
|
<TiltSlider v-model="tilt" :disabled="movementApiCallsDisabled" />
|
38
38
|
</v-row>
|
39
|
+
<v-row v-if="!hideRotationButton && is3D" justify="center">
|
40
|
+
<OrientationToolsButton
|
41
|
+
:icon="rotationAction.icon"
|
42
|
+
:tooltip="rotationAction.title"
|
43
|
+
:color="rotationAction.active ? 'primary' : undefined"
|
44
|
+
@click.stop="rotationAction.callback($event)"
|
45
|
+
:disabled="rotationAction.disabled"
|
46
|
+
/>
|
47
|
+
</v-row>
|
39
48
|
<v-row justify="center">
|
40
49
|
<OrientationToolsButton
|
41
50
|
v-if="homeAction.icon"
|
@@ -60,7 +69,13 @@
|
|
60
69
|
|
61
70
|
<script>
|
62
71
|
import { computed, inject, ref, reactive, onUnmounted } from 'vue';
|
63
|
-
import {
|
72
|
+
import {
|
73
|
+
ObliqueMap,
|
74
|
+
CesiumMap,
|
75
|
+
ObliqueViewDirection,
|
76
|
+
startRotation,
|
77
|
+
rotationMapControlSymbol,
|
78
|
+
} from '@vcmap/core';
|
64
79
|
import { VContainer, VRow } from 'vuetify/components';
|
65
80
|
import { useDisplay } from 'vuetify';
|
66
81
|
import { Math as CesiumMath } from '@vcmap-cesium/engine';
|
@@ -125,6 +140,59 @@
|
|
125
140
|
return { action, destroy: () => listener?.() };
|
126
141
|
}
|
127
142
|
|
143
|
+
/**
|
144
|
+
* @description Creates a rotate-around-center action to continuously rotate the viewpoint around the current map center at a specified speed. The action can be toggled on or off.
|
145
|
+
* @param {import("@src/vcsUiApp.js").default} app - The app instance containing the active map.
|
146
|
+
* @param {import("vue").ComputedRef<number>} defaultTimePerRotation - A computed property representing the time it takes to complete one rotation. The value should be a number representing seconds per rotation. Default is 60 seconds per rotation.
|
147
|
+
* @returns {{ action: import("vue").Reactive<VcsAction>, destroy: function():void }} - Returns the rotation action and a destroy method to stop the rotation listener if active.
|
148
|
+
*/
|
149
|
+
function setupRotationButton(app, defaultTimePerRotation) {
|
150
|
+
let stopRotation;
|
151
|
+
const action = reactive({
|
152
|
+
name: 'rotate-action',
|
153
|
+
title: 'navigation.rotateButton',
|
154
|
+
icon: '$vcsView360',
|
155
|
+
active: false,
|
156
|
+
callback: async () => {
|
157
|
+
if (action.active) {
|
158
|
+
if (stopRotation) {
|
159
|
+
stopRotation();
|
160
|
+
} else {
|
161
|
+
app.maps.resetExclusiveMapControls();
|
162
|
+
}
|
163
|
+
} else {
|
164
|
+
stopRotation = await startRotation(
|
165
|
+
app,
|
166
|
+
undefined,
|
167
|
+
defaultTimePerRotation.value,
|
168
|
+
);
|
169
|
+
}
|
170
|
+
},
|
171
|
+
});
|
172
|
+
|
173
|
+
const rotationListener =
|
174
|
+
app.maps.exclusiveMapControlsChanged.addEventListener((eventData) => {
|
175
|
+
const { options, id } = eventData;
|
176
|
+
action.active =
|
177
|
+
id === rotationMapControlSymbol &&
|
178
|
+
options.keyEvents === true &&
|
179
|
+
options.apiCalls === true &&
|
180
|
+
options.pointerEvents === true;
|
181
|
+
action.disabled =
|
182
|
+
id !== rotationMapControlSymbol &&
|
183
|
+
options.keyEvents === true &&
|
184
|
+
options.apiCalls === true &&
|
185
|
+
options.pointerEvents === true;
|
186
|
+
});
|
187
|
+
return {
|
188
|
+
action,
|
189
|
+
destroy: () => {
|
190
|
+
stopRotation?.();
|
191
|
+
rotationListener();
|
192
|
+
},
|
193
|
+
};
|
194
|
+
}
|
195
|
+
|
128
196
|
/**
|
129
197
|
* @enum {string}
|
130
198
|
*/
|
@@ -306,6 +374,17 @@
|
|
306
374
|
|
307
375
|
const { action: homeAction, destroy: homeDestroy } = setupHomeButton(app);
|
308
376
|
|
377
|
+
const defaultTimePerRotation = computed(() => {
|
378
|
+
return app.uiConfig.config?.timePerRotation;
|
379
|
+
});
|
380
|
+
|
381
|
+
const { action: rotationAction, destroy: rotationDestroy } =
|
382
|
+
setupRotationButton(app, defaultTimePerRotation);
|
383
|
+
|
384
|
+
const hideRotationButton = computed(() => {
|
385
|
+
return app.uiConfig.config?.hideRotationButton;
|
386
|
+
});
|
387
|
+
|
309
388
|
onUnmounted(() => {
|
310
389
|
if (overviewDestroy) {
|
311
390
|
overviewDestroy();
|
@@ -316,16 +395,17 @@
|
|
316
395
|
if (homeDestroy) {
|
317
396
|
homeDestroy();
|
318
397
|
}
|
398
|
+
rotationDestroy();
|
319
399
|
postRenderHandler();
|
320
400
|
overviewMapListeners.forEach((cb) => cb());
|
321
401
|
removeMovementDisabledListener();
|
322
402
|
});
|
323
403
|
|
324
|
-
const { xs,
|
404
|
+
const { xs, mobile, smAndUp } = useDisplay();
|
325
405
|
|
326
406
|
return {
|
327
407
|
xs,
|
328
|
-
|
408
|
+
smAndUp,
|
329
409
|
mobile,
|
330
410
|
viewMode,
|
331
411
|
heading,
|
@@ -346,7 +426,9 @@
|
|
346
426
|
locatorAction: reactive(locatorAction),
|
347
427
|
showOverviewButton,
|
348
428
|
showLocatorButton,
|
429
|
+
hideRotationButton,
|
349
430
|
homeAction,
|
431
|
+
rotationAction,
|
350
432
|
movementApiCallsDisabled,
|
351
433
|
};
|
352
434
|
},
|
@@ -1,6 +1,6 @@
|
|
1
1
|
declare const _default: import("vue").DefineComponent<{}, {
|
2
2
|
xs: import("vue").Ref<boolean>;
|
3
|
-
|
3
|
+
smAndUp: import("vue").Ref<boolean>;
|
4
4
|
mobile: import("vue").ComputedRef<boolean>;
|
5
5
|
viewMode: import("vue").Ref<string>;
|
6
6
|
heading: import("vue").WritableComputedRef<number>;
|
@@ -79,7 +79,9 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
79
79
|
};
|
80
80
|
showOverviewButton: import("vue").Ref<boolean>;
|
81
81
|
showLocatorButton: import("vue").Ref<boolean>;
|
82
|
+
hideRotationButton: import("vue").ComputedRef<boolean | undefined>;
|
82
83
|
homeAction: any;
|
84
|
+
rotationAction: any;
|
83
85
|
movementApiCallsDisabled: import("vue").Ref<boolean>;
|
84
86
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
|
85
87
|
export default _default;
|
@@ -53,6 +53,12 @@ declare class OverviewMap {
|
|
53
53
|
* @type {VectorStyleItem}
|
54
54
|
*/
|
55
55
|
obliqueSelectedStyle: VectorStyleItem;
|
56
|
+
/**
|
57
|
+
* A factor by which to multiply the distance of the viewpoint of the overviewMap.
|
58
|
+
* @type {number}
|
59
|
+
* @private
|
60
|
+
*/
|
61
|
+
private _scaleFactor;
|
56
62
|
/**
|
57
63
|
* A factor by witch to multiply the resolution when zooming to a single oblique image.
|
58
64
|
* @type {number}
|
@@ -157,6 +157,13 @@ class OverviewMap {
|
|
157
157
|
},
|
158
158
|
});
|
159
159
|
|
160
|
+
/**
|
161
|
+
* A factor by which to multiply the distance of the viewpoint of the overviewMap.
|
162
|
+
* @type {number}
|
163
|
+
* @private
|
164
|
+
*/
|
165
|
+
this._scaleFactor = 1;
|
166
|
+
|
160
167
|
/**
|
161
168
|
* A factor by witch to multiply the resolution when zooming to a single oblique image.
|
162
169
|
* @type {number}
|
@@ -262,13 +269,17 @@ class OverviewMap {
|
|
262
269
|
() => [
|
263
270
|
this._app.uiConfig.config.hideMapNavigation,
|
264
271
|
this._app.uiConfig.config.overviewMapActiveOnStartup,
|
272
|
+
this._app.uiConfig.config.overviewMapScaleFactor,
|
265
273
|
],
|
266
|
-
async ([hide, activeOnStartup]) => {
|
274
|
+
async ([hide, activeOnStartup, scaleFactor]) => {
|
267
275
|
if (activeOnStartup && !hide && !this._active) {
|
268
276
|
await this.activate();
|
269
277
|
} else if (hide && this._active) {
|
270
278
|
await this.deactivate();
|
271
279
|
}
|
280
|
+
if (scaleFactor) {
|
281
|
+
this._scaleFactor = scaleFactor;
|
282
|
+
}
|
272
283
|
},
|
273
284
|
);
|
274
285
|
}
|
@@ -482,6 +493,7 @@ class OverviewMap {
|
|
482
493
|
|
483
494
|
const vp = Viewpoint.createViewpointFromExtent(extent);
|
484
495
|
vp.distance /= this._obliqueResolutionFactor;
|
496
|
+
vp.distance *= this._scaleFactor;
|
485
497
|
this._map.gotoViewpoint(vp);
|
486
498
|
}
|
487
499
|
}
|
@@ -632,6 +644,7 @@ class OverviewMap {
|
|
632
644
|
viewpoint.groundPosition = null;
|
633
645
|
viewpoint.distance = distance * 4;
|
634
646
|
}
|
647
|
+
viewpoint.distance *= this._scaleFactor;
|
635
648
|
this._map.gotoViewpoint(viewpoint);
|
636
649
|
}
|
637
650
|
|
package/src/pluginHelper.d.ts
CHANGED
@@ -15,13 +15,6 @@ export function getPluginAssetUrl(app: import("@src/vcsUiApp.js").default, plugi
|
|
15
15
|
* @returns {boolean}
|
16
16
|
*/
|
17
17
|
export function isValidPackageName(name: string): boolean;
|
18
|
-
/**
|
19
|
-
* joins pathname and moduleUrl.
|
20
|
-
* @param {string} pathname will remove filenames with extension or add a trailing slash if missing
|
21
|
-
* @param {string} module relative url to the module
|
22
|
-
* @returns {string}
|
23
|
-
*/
|
24
|
-
export function getModuleUrl(pathname: string, module: string): string;
|
25
18
|
/**
|
26
19
|
* @param {string} name
|
27
20
|
* @param {T} config
|
package/src/pluginHelper.js
CHANGED
@@ -82,23 +82,6 @@ export function isValidPackageName(name) {
|
|
82
82
|
);
|
83
83
|
}
|
84
84
|
|
85
|
-
/**
|
86
|
-
* joins pathname and moduleUrl.
|
87
|
-
* @param {string} pathname will remove filenames with extension or add a trailing slash if missing
|
88
|
-
* @param {string} module relative url to the module
|
89
|
-
* @returns {string}
|
90
|
-
*/
|
91
|
-
export function getModuleUrl(pathname, module) {
|
92
|
-
const pathNameParts = pathname.split('/');
|
93
|
-
if (pathNameParts.at(-1).includes('.')) {
|
94
|
-
pathNameParts.pop();
|
95
|
-
} else if (pathNameParts.at(-1) === '') {
|
96
|
-
pathNameParts.pop();
|
97
|
-
}
|
98
|
-
const pathName = pathNameParts.join('/');
|
99
|
-
return `${pathName}/${module}`;
|
100
|
-
}
|
101
|
-
|
102
85
|
/**
|
103
86
|
* @param {string} name
|
104
87
|
* @param {T} config
|
@@ -110,7 +93,10 @@ export async function loadPlugin(name, config) {
|
|
110
93
|
let module = config.entry;
|
111
94
|
|
112
95
|
if (!/^(https?:\/\/|\/)/.test(module)) {
|
113
|
-
module = `${window.location.origin}${
|
96
|
+
module = `${window.location.origin}${window.location.pathname.replace(
|
97
|
+
/\/?$/,
|
98
|
+
'/',
|
99
|
+
)}${module}`;
|
114
100
|
} else if (module === '_dev') {
|
115
101
|
module = `/${name}.js`;
|
116
102
|
} else if (module === 'http://localhost/_test') {
|
@@ -10,9 +10,6 @@
|
|
10
10
|
<span v-html="marked" />
|
11
11
|
</v-list-item-title>
|
12
12
|
</template>
|
13
|
-
<v-tooltip activator="parent">
|
14
|
-
{{ $st('search.select') }}
|
15
|
-
</v-tooltip>
|
16
13
|
<template #append>
|
17
14
|
<VcsActionButtonList
|
18
15
|
v-if="hasActions"
|
@@ -26,12 +23,7 @@
|
|
26
23
|
|
27
24
|
<script>
|
28
25
|
import { computed } from 'vue';
|
29
|
-
import {
|
30
|
-
VIcon,
|
31
|
-
VListItem,
|
32
|
-
VListItemTitle,
|
33
|
-
VTooltip,
|
34
|
-
} from 'vuetify/components';
|
26
|
+
import { VIcon, VListItem, VListItemTitle } from 'vuetify/components';
|
35
27
|
import DOMPurify from 'dompurify';
|
36
28
|
import VcsActionButtonList from '../components/buttons/VcsActionButtonList.vue';
|
37
29
|
import { markText } from './markText.js';
|
@@ -50,7 +42,6 @@
|
|
50
42
|
VIcon,
|
51
43
|
VListItem,
|
52
44
|
VListItemTitle,
|
53
|
-
VTooltip,
|
54
45
|
},
|
55
46
|
props: {
|
56
47
|
query: {
|
@@ -7,7 +7,10 @@
|
|
7
7
|
:item="item"
|
8
8
|
:query="query"
|
9
9
|
class="cursor-pointer"
|
10
|
-
:class="{
|
10
|
+
:class="{
|
11
|
+
'vcs-search-result-border': index < items.length - 1,
|
12
|
+
selected: index === selectedIndex,
|
13
|
+
}"
|
11
14
|
v-for="(item, index) in items"
|
12
15
|
:key="index"
|
13
16
|
:value="item.value"
|
@@ -41,6 +44,10 @@
|
|
41
44
|
type: Array,
|
42
45
|
required: true,
|
43
46
|
},
|
47
|
+
selectedIndex: {
|
48
|
+
type: Number,
|
49
|
+
default: -1,
|
50
|
+
},
|
44
51
|
},
|
45
52
|
setup(props) {
|
46
53
|
const items = computed(() => {
|
@@ -100,4 +107,7 @@
|
|
100
107
|
border-bottom: thin solid;
|
101
108
|
border-color: rgb(var(--v-theme-base-lighten-2));
|
102
109
|
}
|
110
|
+
.selected {
|
111
|
+
background-color: rgb(var(--v-theme-base-lighten-4));
|
112
|
+
}
|
103
113
|
</style>
|
@@ -7,6 +7,10 @@ declare const _default: import("vue").DefineComponent<{
|
|
7
7
|
type: ArrayConstructor;
|
8
8
|
required: true;
|
9
9
|
};
|
10
|
+
selectedIndex: {
|
11
|
+
type: NumberConstructor;
|
12
|
+
default: number;
|
13
|
+
};
|
10
14
|
}, {
|
11
15
|
items: import("vue").ComputedRef<any[]>;
|
12
16
|
highlighted: import("vue").WritableComputedRef<never[]>;
|
@@ -19,7 +23,12 @@ declare const _default: import("vue").DefineComponent<{
|
|
19
23
|
type: ArrayConstructor;
|
20
24
|
required: true;
|
21
25
|
};
|
26
|
+
selectedIndex: {
|
27
|
+
type: NumberConstructor;
|
28
|
+
default: number;
|
29
|
+
};
|
22
30
|
}>>, {
|
23
31
|
query: string;
|
32
|
+
selectedIndex: number;
|
24
33
|
}, {}>;
|
25
34
|
export default _default;
|
@@ -10,20 +10,33 @@
|
|
10
10
|
:loading="searching"
|
11
11
|
clearable
|
12
12
|
:placeholder="$t('search.placeholder')"
|
13
|
-
v-model
|
13
|
+
v-model="query"
|
14
14
|
@keydown.enter="search"
|
15
|
-
@
|
15
|
+
@keydown.down.stop.prevent="selectSuggestion(1)"
|
16
|
+
@keydown.up.stop.prevent="selectSuggestion(-1)"
|
17
|
+
@input="onInput"
|
16
18
|
@click:clear="reset"
|
17
19
|
/>
|
18
20
|
</span>
|
19
|
-
<
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
<
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
<template v-if="results.length > 0">
|
22
|
+
<v-divider class="mt-1 base-darken-1" />
|
23
|
+
<ResultsComponent :query="query" :results="results" />
|
24
|
+
<v-divider />
|
25
|
+
<div class="d-flex px-2 pt-2 pb-1 justify-end">
|
26
|
+
<VcsFormButton @click="zoomToAll" variant="outlined">
|
27
|
+
{{ $t('search.zoomToAll') }}
|
28
|
+
</VcsFormButton>
|
29
|
+
</div>
|
30
|
+
</template>
|
31
|
+
<template v-else-if="suggestions.length > 0">
|
32
|
+
<v-divider class="mt-1 base-darken-1" />
|
33
|
+
<ResultsComponent
|
34
|
+
class="suggestions"
|
35
|
+
:results="suggestions"
|
36
|
+
:query="query"
|
37
|
+
:selected-index="selectedSuggestion"
|
38
|
+
/>
|
39
|
+
</template>
|
27
40
|
</v-sheet>
|
28
41
|
</template>
|
29
42
|
|
@@ -31,14 +44,20 @@
|
|
31
44
|
:deep(.v-field .v-field__outline *) {
|
32
45
|
border-color: transparent !important;
|
33
46
|
}
|
47
|
+
|
34
48
|
.user-select-none {
|
35
49
|
user-select: none;
|
36
50
|
}
|
51
|
+
|
52
|
+
.suggestions {
|
53
|
+
font-style: italic;
|
54
|
+
}
|
37
55
|
</style>
|
38
56
|
|
39
57
|
<script>
|
40
58
|
import { inject, onUnmounted, ref, computed } from 'vue';
|
41
59
|
import { getLogger } from '@vcsuite/logger';
|
60
|
+
import { v4 as uuid } from 'uuid';
|
42
61
|
import { VSheet, VDivider, VIcon } from 'vuetify/components';
|
43
62
|
import VcsTextField from '../components/form-inputs-controls/VcsTextField.vue';
|
44
63
|
import ResultsComponent from './ResultsComponent.vue';
|
@@ -62,26 +81,63 @@
|
|
62
81
|
/** @type {import("@src/vcsUiApp.js").default} */
|
63
82
|
const app = inject('vcsApp');
|
64
83
|
const searching = ref(false);
|
84
|
+
const suggesting = ref('');
|
65
85
|
const query = ref(null);
|
66
86
|
const suggestions = ref([]);
|
87
|
+
const selectedSuggestion = ref(-1);
|
67
88
|
const results = app.search.currentResults;
|
89
|
+
let queryPreSuggestion = '';
|
90
|
+
|
91
|
+
let suggestionTimeout;
|
92
|
+
|
93
|
+
const onInput = () => {
|
94
|
+
app.search.clearResults();
|
95
|
+
const trimmedInput = query.value?.trim() ?? '';
|
96
|
+
if (trimmedInput.length > 0) {
|
97
|
+
const requestId = uuid();
|
98
|
+
if (suggestionTimeout) {
|
99
|
+
clearTimeout(suggestionTimeout);
|
100
|
+
}
|
101
|
+
suggestionTimeout = setTimeout(() => {
|
102
|
+
suggesting.value = requestId;
|
103
|
+
queryPreSuggestion = trimmedInput;
|
104
|
+
selectedSuggestion.value = -1;
|
105
|
+
app.search.suggest(trimmedInput).then((s) => {
|
106
|
+
if (suggesting.value === requestId) {
|
107
|
+
suggestions.value = s;
|
108
|
+
suggesting.value = '';
|
109
|
+
}
|
110
|
+
});
|
111
|
+
}, 200);
|
112
|
+
} else {
|
113
|
+
selectedSuggestion.value = -1;
|
114
|
+
suggesting.value = '';
|
115
|
+
suggestions.value = [];
|
116
|
+
queryPreSuggestion = '';
|
117
|
+
}
|
118
|
+
};
|
68
119
|
|
69
120
|
const reset = () => {
|
70
121
|
app.search.clearResults();
|
122
|
+
selectedSuggestion.value = -1;
|
123
|
+
suggesting.value = '';
|
71
124
|
suggestions.value = [];
|
125
|
+
queryPreSuggestion = '';
|
72
126
|
};
|
73
127
|
|
74
128
|
const clear = () => {
|
75
129
|
reset();
|
76
130
|
searching.value = false;
|
131
|
+
suggestions.value = [];
|
77
132
|
query.value = null;
|
133
|
+
queryPreSuggestion = '';
|
78
134
|
};
|
79
135
|
|
80
136
|
const search = async () => {
|
81
137
|
reset();
|
82
138
|
searching.value = true;
|
83
139
|
try {
|
84
|
-
await app.search.search(query.value);
|
140
|
+
await app.search.search(query.value.trim());
|
85
141
|
} catch (e) {
|
86
142
|
getLogger('Search').error(e);
|
87
143
|
}
|
@@ -109,6 +165,27 @@
|
|
109
165
|
search,
|
110
166
|
zoomToAll,
|
111
167
|
searchIconSize,
|
168
|
+
suggestions: computed(() =>
|
169
|
+
suggestions.value.map((s) => ({
|
170
|
+
title: s,
|
171
|
+
clicked() {
|
172
|
+
query.value = s;
|
173
|
+
search();
|
174
|
+
},
|
175
|
+
})),
|
176
|
+
),
|
177
|
+
selectedSuggestion,
|
178
|
+
onInput,
|
179
|
+
selectSuggestion(value) {
|
180
|
+
const newSelection = selectedSuggestion.value + value;
|
181
|
+
if (newSelection > -1 && newSelection < suggestions.value?.length) {
|
182
|
+
selectedSuggestion.value = newSelection;
|
183
|
+
query.value = suggestions.value[newSelection];
|
184
|
+
} else {
|
185
|
+
selectedSuggestion.value = -1;
|
186
|
+
query.value = queryPreSuggestion;
|
187
|
+
}
|
188
|
+
},
|
112
189
|
};
|
113
190
|
},
|
114
191
|
};
|
@@ -7,5 +7,12 @@ declare const _default: import("vue").DefineComponent<{}, {
|
|
7
7
|
search: () => Promise<void>;
|
8
8
|
zoomToAll: () => void;
|
9
9
|
searchIconSize: import("vue").ComputedRef<number>;
|
10
|
+
suggestions: import("vue").ComputedRef<{
|
11
|
+
title: never;
|
12
|
+
clicked(): void;
|
13
|
+
}[]>;
|
14
|
+
selectedSuggestion: import("vue").Ref<number>;
|
15
|
+
onInput: () => void;
|
16
|
+
selectSuggestion(value: any): void;
|
10
17
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
|
11
18
|
export default _default;
|
package/src/search/markText.d.ts
CHANGED
package/src/search/markText.js
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
/**
|
2
|
-
* @typedef {Object}
|
2
|
+
* @typedef {Object} BlockIndices
|
3
3
|
* @property {number} start
|
4
4
|
* @property {number} end
|
5
5
|
*/
|
6
6
|
|
7
7
|
/**
|
8
|
-
* @param {
|
9
|
-
* @param {
|
8
|
+
* @param {BlockIndices[]} blocks
|
9
|
+
* @param {BlockIndices} candidate
|
10
10
|
* @returns {boolean}
|
11
11
|
*/
|
12
12
|
function isBlockWithinBlocks(blocks, candidate) {
|
@@ -18,7 +18,7 @@ function isBlockWithinBlocks(blocks, candidate) {
|
|
18
18
|
/**
|
19
19
|
* @param {string} text
|
20
20
|
* @param {RegExp} partial
|
21
|
-
* @param {
|
21
|
+
* @param {BlockIndices[]} blocks
|
22
22
|
*/
|
23
23
|
function addPartialBlocks(text, partial, blocks) {
|
24
24
|
let match;
|
package/src/search/search.d.ts
CHANGED
@@ -25,6 +25,9 @@ export type SearchImpl = {
|
|
25
25
|
*/
|
26
26
|
name: string;
|
27
27
|
search: (arg0: string) => Promise<Array<ResultItem>>;
|
28
|
+
/**
|
29
|
+
* - optional, provides suggestions for autocomplete.
|
30
|
+
*/
|
28
31
|
suggest?: ((arg0: string) => Promise<Array<string>>) | undefined;
|
29
32
|
/**
|
30
33
|
* - should abort any ongoing requests to search or suggest without throwing an error
|
package/src/search/search.js
CHANGED
@@ -35,8 +35,8 @@ import { getViewpointFromFeature } from '../actions/actionHelper.js';
|
|
35
35
|
* @typedef {Object} SearchImpl
|
36
36
|
* @property {string} name Name of the implementation. Must be unique, best practice is to prefix with your plugin name to ensure uniqueness or use a uuid.
|
37
37
|
* @property {function(string):Promise<Array<ResultItem>>} search
|
38
|
-
* @property {function(string):Promise<Array<string>>} [suggest]
|
39
|
-
* @property{function():void} abort - should abort any ongoing requests to search or suggest without throwing an error
|
38
|
+
* @property {function(string):Promise<Array<string>>} [suggest] - optional, provides suggestions for autocomplete.
|
39
|
+
* @property {function():void} abort - should abort any ongoing requests to search or suggest without throwing an error
|
40
40
|
* @property {function():void} destroy
|
41
41
|
*/
|
42
42
|
|
@@ -257,6 +257,7 @@ class Search extends IndexedCollection {
|
|
257
257
|
* @returns {Promise<Array<string>>}
|
258
258
|
*/
|
259
259
|
async suggest(q) {
|
260
|
+
this.clearResults();
|
260
261
|
const promises = await Promise.allSettled(
|
261
262
|
[...this._array].map((impl) => {
|
262
263
|
if (impl.suggest) {
|