@vcmap/ui 5.0.0-rc.8 → 5.0.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -22
- package/build/build.js +0 -3
- package/build/buildHelpers.js +0 -1
- package/build/commonViteConfig.js +1 -1
- package/config/dev.config.json +4 -4
- package/dist/assets/{cesium.6b5bb6.js → cesium.4e40f4.js} +0 -0
- package/dist/assets/cesium.js +1 -1
- package/dist/assets/core.edcf5e.js +4 -0
- package/dist/assets/core.js +1 -1
- package/dist/assets/{index.0be2842f.js → index.889d0f3a.js} +1 -1
- package/dist/assets/{ol.0561aa.js → ol.246fd4.js} +0 -0
- package/dist/assets/ol.js +1 -1
- package/dist/assets/ui.df4f6d.css +1 -0
- package/dist/assets/ui.df4f6d.js +43 -0
- package/dist/assets/ui.js +1 -1
- package/dist/assets/{vue-composition-api.f926fa.js → vue-composition-api.a520f3.js} +1 -1
- package/dist/assets/vue-composition-api.js +2 -2
- package/dist/assets/{vue.ddcb6b.js → vue.2cee44.js} +0 -0
- package/dist/assets/vue.js +1 -1
- package/dist/assets/{vuetify.d21163.css → vuetify.cc817b.css} +0 -0
- package/dist/assets/{vuetify.d21163.js → vuetify.cc817b.js} +1 -1
- package/dist/assets/vuetify.js +2 -2
- package/dist/index.html +1 -1
- package/index.js +39 -1
- package/package.json +2 -3
- package/plugins/@vcmap/pluginExample/index.js +5 -5
- package/plugins/@vcmap/pluginExample/pluginExampleComponent.vue +1 -1
- package/plugins/@vcmap/project-selector/ContextsListComponent.vue +1 -1
- package/plugins/@vcmap/project-selector/index.js +5 -5
- package/plugins/@vcmap/project-selector/package.json +1 -2
- package/plugins/@vcmap/theme-changer/index.js +6 -6
- package/plugins/buttonExamples/ButtonExamples.vue +1 -1
- package/plugins/buttonExamples/index.js +5 -4
- package/plugins/categoryTest/Categories.vue +1 -1
- package/plugins/categoryTest/Category.vue +1 -1
- package/plugins/categoryTest/index.js +5 -5
- package/plugins/example/index.js +33 -14
- package/plugins/test/allIconsComponent.vue +34 -0
- package/plugins/test/editor.vue +1 -1
- package/plugins/test/index.js +40 -17
- package/plugins/test/toolbox-data.js +106 -26
- package/plugins/test/windowManagerExample.vue +1 -2
- package/src/actions/actionHelper.js +2 -1
- package/src/actions/styleSelector.vue +1 -1
- package/src/application/Navbar.vue +18 -6
- package/src/application/VcsApp.vue +34 -28
- package/src/assets/logo-mobile.svg +9 -0
- package/src/assets/logo.svg +23 -23
- package/src/components/buttons/VcsActionButtonList.vue +99 -0
- package/src/components/buttons/VcsButton.vue +201 -0
- package/src/components/form-inputs-controls/VcsCheckbox.vue +73 -0
- package/src/components/form-inputs-controls/VcsColorPicker.vue +81 -0
- package/src/components/form-inputs-controls/VcsFormSection.vue +46 -0
- package/src/components/form-inputs-controls/VcsLabel.vue +38 -0
- package/src/components/form-inputs-controls/VcsSelect.vue +97 -0
- package/src/components/form-inputs-controls/VcsTextArea.vue +130 -0
- package/src/components/form-inputs-controls/VcsTextField.vue +129 -0
- package/src/components/form-output/VcsFormattedNumber.vue +103 -0
- package/src/components/lists/VcsActionList.vue +100 -0
- package/src/components/lists/VcsTreeview.vue +109 -0
- package/src/components/lists/VcsTreeviewLeaf.vue +105 -0
- package/src/components/lists/VcsTreeviewSearchbar.vue +156 -0
- package/src/components/notification/VcsBadge.vue +27 -0
- package/src/components/notification/VcsTooltip.vue +154 -0
- package/src/components/notification/validation.js +19 -0
- package/src/contentTree/LayerTree.vue +1 -1
- package/src/contentTree/contentTreeCollection.js +6 -2
- package/src/icons/+all.js +359 -0
- package/src/icons/2DAreaIcon.vue +21 -0
- package/src/icons/2DDistanceIcon.vue +18 -0
- package/src/icons/3DAreaIcon.vue +21 -0
- package/src/icons/3DDistanceIcon.vue +18 -0
- package/src/icons/3DHeightIcon.vue +18 -0
- package/src/icons/AngleIcon.vue +8 -0
- package/src/icons/AssociationsIcon.vue +34 -0
- package/src/icons/AxisIcon.vue +10 -0
- package/src/icons/BoundingBoxIcon.vue +15 -0
- package/src/icons/CheckboxCheckedIcon.vue +16 -0
- package/src/icons/CheckboxIcon.vue +23 -0
- package/src/icons/CheckboxIndeterminateIcon.vue +24 -0
- package/src/icons/CircleIcon.vue +10 -0
- package/src/icons/ColorSwatchIcon.vue +17 -0
- package/src/icons/CommentIcon.vue +19 -0
- package/src/icons/CompassIcon.vue +8 -0
- package/src/icons/ComponentsIcon.vue +7 -0
- package/src/icons/ConeIcon.vue +11 -0
- package/src/icons/DimensionsHouseIcon.vue +14 -0
- package/src/icons/ElevationProfileIcon.vue +111 -0
- package/src/icons/ExportAreaIcon.vue +7 -0
- package/src/icons/ExportFlightIcon.vue +7 -0
- package/src/icons/ExportIcon.vue +8 -0
- package/src/icons/ExternalLinkIcon.vue +10 -0
- package/src/icons/EyeIcon.vue +7 -0
- package/src/icons/FastForwardIcon.vue +7 -0
- package/src/icons/FilterIcon.vue +8 -0
- package/src/icons/GlobeNatureIcon.vue +14 -0
- package/src/icons/HealthCareIndustriesIcon.vue +118 -0
- package/src/icons/HelpIcon.vue +7 -0
- package/src/icons/HomePointIcon.vue +8 -0
- package/src/icons/HospitalsIcon.vue +237 -0
- package/src/icons/HouseIcon.vue +25 -0
- package/src/icons/ImportIcon.vue +8 -0
- package/src/icons/InfoIcon.vue +10 -0
- package/src/icons/KebabIcon.vue +36 -0
- package/src/icons/LabelIcon.vue +7 -0
- package/src/icons/LayersIcon.vue +26 -0
- package/src/icons/LegendIcon.vue +65 -0
- package/src/icons/LineIcon.vue +7 -0
- package/src/icons/LinkIcon.vue +7 -0
- package/src/icons/MapIcon.vue +8 -0
- package/src/icons/MenuIcon.vue +34 -0
- package/src/icons/MinusIcon.vue +8 -0
- package/src/icons/ObjectAttributeIcon.vue +18 -0
- package/src/icons/ObjectSelectIcon.vue +8 -0
- package/src/icons/ObliqueViewIcon.vue +13 -0
- package/src/icons/PdfIcon.vue +10 -0
- package/src/icons/PedestrianIcon.vue +8 -0
- package/src/icons/PenIcon.vue +14 -0
- package/src/icons/PlayCircleIcon.vue +10 -0
- package/src/icons/PlusIcon.vue +9 -0
- package/src/icons/PoiIcon.vue +7 -0
- package/src/icons/PointSelectIcon.vue +7 -0
- package/src/icons/PolygonIcon.vue +38 -0
- package/src/icons/PresentationModeIcon.vue +7 -0
- package/src/icons/ProgressIcon.vue +24 -0
- package/src/icons/QueryIcon.vue +15 -0
- package/src/icons/RectangleIcon.vue +9 -0
- package/src/icons/ReturnIcon.vue +7 -0
- package/src/icons/RewindIcon.vue +6 -0
- package/src/icons/SearchIcon.vue +8 -0
- package/src/icons/ShadowIcon.vue +9 -0
- package/src/icons/ShapesIcon.vue +28 -0
- package/src/icons/ShareIcon.vue +22 -0
- package/src/icons/SimpleCircleFilledIcon.vue +15 -0
- package/src/icons/SimpleCircleHalfFilledIcon.vue +12 -0
- package/src/icons/SimpleCircleOutlinedIcon.vue +15 -0
- package/src/icons/SkipNextIcon.vue +7 -0
- package/src/icons/SkipPreviousIcon.vue +9 -0
- package/src/icons/SplitViewIcon.vue +19 -0
- package/src/icons/TextStyleIcon.vue +14 -0
- package/src/icons/ThreeDimensionsIcon.vue +7 -0
- package/src/icons/ToolsIcon.vue +35 -0
- package/src/icons/TouchIcon.vue +8 -0
- package/src/icons/TrashCanIcon.vue +7 -0
- package/src/icons/TriangleIcon.vue +15 -0
- package/src/icons/TwoDimensionsIcon.vue +8 -0
- package/src/icons/UploadIcon.vue +14 -0
- package/src/icons/VideoRecorderIcon.vue +14 -0
- package/src/icons/WalkingIcon.vue +7 -0
- package/src/icons/WallIcon.vue +14 -0
- package/src/manager/buttonManager.js +5 -53
- package/src/manager/navbarManager.js +81 -0
- package/src/manager/toolbox/ToolboxGroupComponent.vue +128 -0
- package/src/manager/toolbox/ToolboxManager.vue +119 -76
- package/src/manager/toolbox/toolboxManager.js +204 -0
- package/src/manager/window/WindowComponentHeader.vue +1 -1
- package/src/manager/window/WindowManager.vue +18 -1
- package/src/manager/window/windowManager.js +3 -5
- package/src/navigation/mapNavigation.vue +9 -5
- package/src/navigation/orientationToolsButton.vue +1 -1
- package/src/navigation/tiltSlider.vue +1 -1
- package/src/styles/_theming.scss +10 -0
- package/src/styles/main.scss +3 -0
- package/src/styles/variables.scss +70 -0
- package/src/styles/vcsFont.scss +5 -0
- package/src/styles/vcsGrid.scss +4 -0
- package/src/vcsUiApp.js +4 -3
- package/src/vuePlugins/vuetify.js +1 -1
- package/dist/assets/core.98f9bb.js +0 -4
- package/dist/assets/ui.b7c1e3.css +0 -1
- package/dist/assets/ui.b7c1e3.js +0 -39
- package/dist/assets/uicomponents.682c5f.css +0 -1
- package/dist/assets/uicomponents.682c5f.js +0 -32
- package/dist/assets/uicomponents.js +0 -1
- package/lib/uicomponents.js +0 -1
- package/src/manager/toolbox/ToolboxMultiSelectButton.vue +0 -96
- package/src/manager/toolbox/ToolboxSingleSelectButton.vue +0 -98
- package/src/manager/toolbox/toolbox-manager.js +0 -203
@@ -0,0 +1,129 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
@mouseover="hover = true"
|
4
|
+
@mouseleave="hover = false"
|
5
|
+
>
|
6
|
+
<VcsTooltip
|
7
|
+
:tooltip-position="tooltipPosition"
|
8
|
+
:tooltip="errorMessage"
|
9
|
+
:value="(hover || focus) && isError"
|
10
|
+
color="error"
|
11
|
+
:max-width="200"
|
12
|
+
>
|
13
|
+
<template #activator="{ attrs }">
|
14
|
+
<v-text-field
|
15
|
+
ref="textFieldRef"
|
16
|
+
hide-details
|
17
|
+
:dense="isDense"
|
18
|
+
:clearable="isClearable"
|
19
|
+
@focus="focus = true"
|
20
|
+
@blur="focus = neverBlurred = false"
|
21
|
+
@input="firstInput = true"
|
22
|
+
:outlined="isOutlined"
|
23
|
+
v-bind="{...$attrs, ...attrs}"
|
24
|
+
v-on="{...$listeners}"
|
25
|
+
:height="isDense ? 24 : 32"
|
26
|
+
class="ma-0 pb-1 pt-1 primary--placeholder"
|
27
|
+
:class="$attrs.color === 'primary' ? 'primary--textfield' : ''"
|
28
|
+
/>
|
29
|
+
</template>
|
30
|
+
</VcsTooltip>
|
31
|
+
</div>
|
32
|
+
</template>
|
33
|
+
|
34
|
+
<style lang="scss" scoped>
|
35
|
+
.primary--placeholder {
|
36
|
+
::v-deep {
|
37
|
+
input::placeholder {
|
38
|
+
color: var(--v-primary-base);
|
39
|
+
font-style: italic;
|
40
|
+
opacity: 1;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
.primary--textfield {
|
45
|
+
::v-deep {
|
46
|
+
input {
|
47
|
+
color: var(--v-primary-base);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
</style>
|
52
|
+
|
53
|
+
<script>
|
54
|
+
import VcsTooltip from '../notification/VcsTooltip.vue';
|
55
|
+
|
56
|
+
/**
|
57
|
+
* @description extends API of {@link https://vuetifyjs.com/en/api/v-text-field/|vuetify v-text-field}.
|
58
|
+
* Provides two height options depending on "dense" property:
|
59
|
+
* - if dense is set true (default), height is 24 px
|
60
|
+
* - if dense is set false, height is 32 px
|
61
|
+
* Provides VcsTooltip to
|
62
|
+
* - show error messages on focus
|
63
|
+
* - show tooltips, if supplied, when hovered over append-icon
|
64
|
+
* @vue-prop {('bottom' | 'left' | 'top' | 'right')} [tooltipPosition='right'] - Position of the error tooltip.
|
65
|
+
* @vue-computed {boolean} isClearable - Whether textfield is isClearable. Makes sure icon is only shown on focus, hover or error.
|
66
|
+
* @vue-computed {boolean} isDense - Whether size of textfield is dense.
|
67
|
+
* @vue-computed {boolean} isError - Whether errorBucket is not empty and textfield was focused at least once.
|
68
|
+
* @vue-computed {boolean} isOutlined - Textfield is outlined on either hover, focus or error, if not disabled.
|
69
|
+
* @vue-computed {Array<string>} joinedErrorBucket - errorBucket + errorMessages of child v-text-field.
|
70
|
+
*/
|
71
|
+
export default {
|
72
|
+
name: 'VcsTextField',
|
73
|
+
components: {
|
74
|
+
VcsTooltip,
|
75
|
+
},
|
76
|
+
props: {
|
77
|
+
tooltipPosition: {
|
78
|
+
type: String,
|
79
|
+
default: 'right',
|
80
|
+
},
|
81
|
+
},
|
82
|
+
data() {
|
83
|
+
return {
|
84
|
+
hover: false,
|
85
|
+
focus: false,
|
86
|
+
firstInput: false,
|
87
|
+
neverBlurred: true,
|
88
|
+
isMounted: false,
|
89
|
+
errorMessage: '',
|
90
|
+
};
|
91
|
+
},
|
92
|
+
computed: {
|
93
|
+
isClearable() {
|
94
|
+
return (this.$attrs.clearable !== undefined && this.$attrs.clearable !== false) &&
|
95
|
+
(this.hover || this.focus || this.isError);
|
96
|
+
},
|
97
|
+
isDense() {
|
98
|
+
return this.$attrs.dense !== undefined && this.$attrs.dense !== false;
|
99
|
+
},
|
100
|
+
isError() {
|
101
|
+
return this.joinedErrorBucket.length > 0 && (this.firstInput || !this.neverBlurred);
|
102
|
+
},
|
103
|
+
isOutlined() {
|
104
|
+
return (this.hover || this.focus || this.isError) && !(this.$attrs.disabled || this.$attrs.disabled === '');
|
105
|
+
},
|
106
|
+
joinedErrorBucket() {
|
107
|
+
if (!this.isMounted) {
|
108
|
+
return false;
|
109
|
+
} else {
|
110
|
+
return this.$refs.textFieldRef.errorBucket.concat(this.$refs.textFieldRef.errorMessages).join('\n');
|
111
|
+
}
|
112
|
+
},
|
113
|
+
},
|
114
|
+
watch: {
|
115
|
+
joinedErrorBucket(newValue, oldValue) {
|
116
|
+
if (oldValue && !newValue) {
|
117
|
+
setTimeout(() => {
|
118
|
+
this.errorMessage = newValue;
|
119
|
+
}, 200);
|
120
|
+
} else {
|
121
|
+
this.errorMessage = newValue;
|
122
|
+
}
|
123
|
+
},
|
124
|
+
},
|
125
|
+
mounted() {
|
126
|
+
this.isMounted = true;
|
127
|
+
},
|
128
|
+
};
|
129
|
+
</script>
|
@@ -0,0 +1,103 @@
|
|
1
|
+
<template>
|
2
|
+
<span class="vcs-formatted-number" :class="{'vcs-formatted-number-dense': dense}">
|
3
|
+
{{ formatted }}
|
4
|
+
<span v-if="unit === SpecialUnits.SQM">
|
5
|
+
m<sup>2</sup>
|
6
|
+
</span>
|
7
|
+
<span v-else-if="unit === SpecialUnits.CBM">
|
8
|
+
m<sup>3</sup>
|
9
|
+
</span>
|
10
|
+
<span v-else-if="unit === SpecialUnits.DEG">
|
11
|
+
°
|
12
|
+
</span>
|
13
|
+
<span v-else>
|
14
|
+
{{ unit }}
|
15
|
+
</span>
|
16
|
+
</span>
|
17
|
+
</template>
|
18
|
+
<style lang="scss" scoped>
|
19
|
+
@import "../../styles/vcsGrid.scss";
|
20
|
+
@import "../../styles/vcsFont";
|
21
|
+
.vcs-formatted-number {
|
22
|
+
font-size: $base-font-size;
|
23
|
+
line-height: $line-height-base;
|
24
|
+
}
|
25
|
+
.vcs-formatted-number-dense {
|
26
|
+
line-height: $line-height-dense;
|
27
|
+
}
|
28
|
+
</style>
|
29
|
+
<script>
|
30
|
+
import { computed } from '@vue/composition-api';
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Converts a number (e.g. 12345678,9) to a locale-aware and
|
34
|
+
* dot-seperated string with given amount of fractional digits (e.g. 12.345.678,90)
|
35
|
+
* @param {string|number} value
|
36
|
+
* @param {number} fractionDigits
|
37
|
+
* @returns {string|number}
|
38
|
+
*/
|
39
|
+
export function numberToLocaleString(value, fractionDigits) { // XXX todo maybe merge with dateTime helpers in core?
|
40
|
+
const number = parseInt(value, 10);
|
41
|
+
if (Number.isNaN(number)) {
|
42
|
+
return number;
|
43
|
+
}
|
44
|
+
|
45
|
+
return (/** @type {number} */ value)
|
46
|
+
.toLocaleString(navigator.language, {
|
47
|
+
minimumFractionDigits: fractionDigits,
|
48
|
+
maximumFractionDigits: fractionDigits,
|
49
|
+
});
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* Enumeration of units displayed with special symbol
|
54
|
+
* @typedef {Object} VcsFormattedNumber.SpecialUnits
|
55
|
+
* @enum {string}
|
56
|
+
* @property {string} SQM square meters
|
57
|
+
* @property {string} CBM cubic meters
|
58
|
+
* @property {string} DEG degrees
|
59
|
+
* @module VcsFormattedNumber
|
60
|
+
*/
|
61
|
+
export const SpecialUnits = {
|
62
|
+
SQM: 'sqm',
|
63
|
+
CBM: 'cbm',
|
64
|
+
DEG: 'deg',
|
65
|
+
};
|
66
|
+
|
67
|
+
/**
|
68
|
+
* @description Formatted number display, optionally with unit
|
69
|
+
* @vue-prop {string|SpecialUnits} [unit=undefined]
|
70
|
+
* @vue-prop {number} [fractionDigits=undefined]
|
71
|
+
* @vue-prop {number} value
|
72
|
+
* @vue-prop {boolean} [dense=true] - default line height is 32px (dense). If set false, height is 40px.
|
73
|
+
* @vue-computed {string} formatted - value formatted to locale string
|
74
|
+
*/
|
75
|
+
export default {
|
76
|
+
name: 'VcsFormattedNumber',
|
77
|
+
props: {
|
78
|
+
unit: {
|
79
|
+
type: [String || SpecialUnits],
|
80
|
+
default: undefined,
|
81
|
+
},
|
82
|
+
fractionDigits: {
|
83
|
+
type: Number,
|
84
|
+
default: 2,
|
85
|
+
},
|
86
|
+
value: {
|
87
|
+
type: Number,
|
88
|
+
default: 0,
|
89
|
+
},
|
90
|
+
dense: {
|
91
|
+
type: Boolean,
|
92
|
+
default: true,
|
93
|
+
},
|
94
|
+
},
|
95
|
+
setup(props) {
|
96
|
+
const formatted = computed(() => numberToLocaleString(props.value, props.fractionDigits));
|
97
|
+
return {
|
98
|
+
SpecialUnits,
|
99
|
+
formatted,
|
100
|
+
};
|
101
|
+
},
|
102
|
+
};
|
103
|
+
</script>
|
@@ -0,0 +1,100 @@
|
|
1
|
+
<template>
|
2
|
+
<v-list
|
3
|
+
v-if="actions.length > 0"
|
4
|
+
>
|
5
|
+
<VcsTooltip
|
6
|
+
v-for="(action, index) in actions"
|
7
|
+
:key="`${action.name}-${index}`"
|
8
|
+
:tooltip="action.title"
|
9
|
+
:tooltip-position="tooltipPosition"
|
10
|
+
v-bind="{...tooltipProps}"
|
11
|
+
>
|
12
|
+
<template #activator="{ on, attrs }">
|
13
|
+
<v-list-item
|
14
|
+
:class="action.active ? 'primary--text' : ''"
|
15
|
+
color="primary"
|
16
|
+
@click="action.callback($event)"
|
17
|
+
v-bind="{...$attrs, ...attrs}"
|
18
|
+
v-on="{...$listeners, ...on}"
|
19
|
+
>
|
20
|
+
<v-list-item-icon v-if="showIcon && action.icon">
|
21
|
+
<v-icon v-text="action.icon" small />
|
22
|
+
</v-list-item-icon>
|
23
|
+
<v-list-item-content
|
24
|
+
class="vcs-action-list"
|
25
|
+
>
|
26
|
+
<v-list-item-title v-text="action.name" />
|
27
|
+
</v-list-item-content>
|
28
|
+
</v-list-item>
|
29
|
+
</template>
|
30
|
+
</VcsTooltip>
|
31
|
+
</v-list>
|
32
|
+
</template>
|
33
|
+
<style lang="scss">
|
34
|
+
.vcs-action-list {
|
35
|
+
width: 100%;
|
36
|
+
&:hover{
|
37
|
+
color: var(--v-primary-base);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
</style>
|
41
|
+
<script>
|
42
|
+
import { is } from '@vcsuite/check';
|
43
|
+
import VcsTooltip from '../notification/VcsTooltip.vue';
|
44
|
+
|
45
|
+
/**
|
46
|
+
* @interface VcsAction
|
47
|
+
* @property {string} name - reactive and translatable name rendered in overflow
|
48
|
+
* @property {string} [title] - reactive and translatable title rendered as tooltip
|
49
|
+
* @property {string} [icon] - icon rendered on the button. If no icon provided, item is rendered in overflow
|
50
|
+
* @property {Function} callback - callback function is triggered when the button is clicked
|
51
|
+
* @property {boolean} [active=false] - optional state of button. If active, button is rendered in primary color
|
52
|
+
*/
|
53
|
+
|
54
|
+
/**
|
55
|
+
* @param {Array<VcsAction>} actions
|
56
|
+
* @returns {boolean}
|
57
|
+
*/
|
58
|
+
export function validateActions(actions) {
|
59
|
+
return actions.every(item => is(item, {
|
60
|
+
name: String,
|
61
|
+
title: [undefined, String],
|
62
|
+
icon: [undefined, String],
|
63
|
+
callback: Function,
|
64
|
+
active: [undefined, Boolean],
|
65
|
+
}));
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* @description
|
70
|
+
* A component rendering a list of actions with overflow mechanic using
|
71
|
+
* {@link https://vuetifyjs.com/en/api/v-list-item-group/|vuetify v-list-item-group} and {@link VcsTooltip}.
|
72
|
+
* @vue-prop {Array<VcsAction>} actions - Array of actions
|
73
|
+
* @vue-prop {('bottom' | 'left' | 'top' | 'right')} tooltipPosition - Position of the tooltip.
|
74
|
+
* @vue-prop {Object<string, any>} tooltipProps - Properties to be passed to VcsTooltip {@link https://vuetifyjs.com/en/api/v-tooltip/#props|vuetify v-tooltip}
|
75
|
+
* @vue-prop {boolean} [showIcon=false] - Whether list item icons should be displayed.
|
76
|
+
*/
|
77
|
+
export default {
|
78
|
+
name: 'VcsActionList',
|
79
|
+
components: { VcsTooltip },
|
80
|
+
props: {
|
81
|
+
actions: {
|
82
|
+
type: Array,
|
83
|
+
required: true,
|
84
|
+
validator: validateActions,
|
85
|
+
},
|
86
|
+
tooltipPosition: {
|
87
|
+
type: String,
|
88
|
+
default: 'right',
|
89
|
+
},
|
90
|
+
tooltipProps: {
|
91
|
+
type: Object,
|
92
|
+
default: () => ({}),
|
93
|
+
},
|
94
|
+
showIcon: {
|
95
|
+
type: Boolean,
|
96
|
+
default: false,
|
97
|
+
},
|
98
|
+
},
|
99
|
+
};
|
100
|
+
</script>
|
@@ -0,0 +1,109 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="d-contents">
|
3
|
+
<VcsTreeviewSearchbar
|
4
|
+
v-if="hasSearchbar"
|
5
|
+
:placeholder="searchbarPlaceholder"
|
6
|
+
v-model="search"
|
7
|
+
/>
|
8
|
+
<v-treeview
|
9
|
+
class="vcs-treeview"
|
10
|
+
v-bind="{...$props, ...$attrs}"
|
11
|
+
v-on="$listeners"
|
12
|
+
expand-icon="mdi-chevron-down"
|
13
|
+
item-key="name"
|
14
|
+
:search="search"
|
15
|
+
:filter="handleFilter"
|
16
|
+
:activatable="false"
|
17
|
+
>
|
18
|
+
<template #label="{ item }">
|
19
|
+
<VcsTreeviewLeaf
|
20
|
+
:item="item"
|
21
|
+
:class="[item.clickable ? 'cursor-pointer' : '']"
|
22
|
+
@click.native="item.clickable && item.clicked()"
|
23
|
+
/>
|
24
|
+
</template>
|
25
|
+
</v-treeview>
|
26
|
+
</div>
|
27
|
+
</template>
|
28
|
+
<style lang="scss" scoped>
|
29
|
+
.vcs-treeview {
|
30
|
+
::v-deep {
|
31
|
+
// Root Level Entries should be 40px high
|
32
|
+
> .v-treeview-node > .v-treeview-node__root {
|
33
|
+
min-height: 40px;
|
34
|
+
}
|
35
|
+
// Border around root nodes with children included
|
36
|
+
> .v-treeview-node {
|
37
|
+
border-bottom: 1px solid var(--v-gray-200-base);
|
38
|
+
}
|
39
|
+
// Only Root Entries have a bold font
|
40
|
+
> .v-treeview-node > .v-treeview-node__root > .v-treeview-node__content > .v-treeview-node__label {
|
41
|
+
font-weight: 700;
|
42
|
+
}
|
43
|
+
// remove ripple effect from expand icon
|
44
|
+
.v-icon.v-icon {
|
45
|
+
&::after{
|
46
|
+
background-color: transparent;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
// Toggle Item Chevron with should be 16px
|
50
|
+
.v-treeview-node__toggle {
|
51
|
+
width: 16px;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
</style>
|
56
|
+
|
57
|
+
<script>
|
58
|
+
import { inject, ref } from '@vue/composition-api';
|
59
|
+
import VcsTreeviewLeaf from './VcsTreeviewLeaf.vue';
|
60
|
+
import VcsTreeviewSearchbar from './VcsTreeviewSearchbar.vue';
|
61
|
+
|
62
|
+
/**
|
63
|
+
* @description extends API of https://vuetifyjs.com/en/api/v-treeview/
|
64
|
+
* Can render dynamic components as leaf items.
|
65
|
+
* In order to display an item needs to be registered and added to `availableComponents`.
|
66
|
+
* @vue-prop {boolean} [hasSearchbar=false] - Whether there is a searchbar for this treeview
|
67
|
+
* @vue-prop {string} [searchbarPlaceholder] - Placeholder text for the searchbar
|
68
|
+
*/
|
69
|
+
export default {
|
70
|
+
name: 'VcsTreeview',
|
71
|
+
components: { VcsTreeviewSearchbar, VcsTreeviewLeaf },
|
72
|
+
props: {
|
73
|
+
hasSearchbar: {
|
74
|
+
type: Boolean,
|
75
|
+
default: false,
|
76
|
+
},
|
77
|
+
searchbarPlaceholder: {
|
78
|
+
type: String,
|
79
|
+
default: undefined,
|
80
|
+
},
|
81
|
+
},
|
82
|
+
setup() {
|
83
|
+
const search = ref('');
|
84
|
+
const language = inject('language');
|
85
|
+
// TODO properly type the tree view item interface & export in index.d.ts
|
86
|
+
/**
|
87
|
+
* @param {{ title: string }} treeNode
|
88
|
+
* @param {string} q
|
89
|
+
* @returns {boolean}
|
90
|
+
*/
|
91
|
+
const handleFilter = (treeNode, q = '') => {
|
92
|
+
if (typeof treeNode.title === 'string') {
|
93
|
+
return treeNode.title.toLocaleLowerCase().includes(q.toLocaleLowerCase());
|
94
|
+
}
|
95
|
+
if (typeof treeNode.title === 'object') {
|
96
|
+
const title = (treeNode.title[language] || treeNode.title.en || treeNode.title.de || 'NO LABEL FOUND');
|
97
|
+
return title.toLocaleLowerCase().includes(q.toLocaleLowerCase());
|
98
|
+
}
|
99
|
+
return false;
|
100
|
+
};
|
101
|
+
|
102
|
+
return {
|
103
|
+
search,
|
104
|
+
handleFilter,
|
105
|
+
};
|
106
|
+
},
|
107
|
+
};
|
108
|
+
</script>
|
109
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
class="d-flex flex-row align-center"
|
4
|
+
v-if="item"
|
5
|
+
>
|
6
|
+
<span v-if="item.icon" class="d-flex align-center">
|
7
|
+
<v-icon
|
8
|
+
v-if="iconType === iconTypes.string"
|
9
|
+
v-text="item.icon"
|
10
|
+
:size="16"
|
11
|
+
class="mr-1"
|
12
|
+
/>
|
13
|
+
<span ref="imgContainer" />
|
14
|
+
</span>
|
15
|
+
|
16
|
+
<div class="position-relative col-8 pa-0 d-flex align-center">
|
17
|
+
<span>{{ label }}</span>
|
18
|
+
</div>
|
19
|
+
<VcsActionButtonList
|
20
|
+
v-if="item.actions.length > 0"
|
21
|
+
:actions="item.actions"
|
22
|
+
:block-overflow="true"
|
23
|
+
:overflow-count="3"
|
24
|
+
small
|
25
|
+
right
|
26
|
+
class="col-4 pa-0 d-flex align-center"
|
27
|
+
/>
|
28
|
+
</div>
|
29
|
+
</template>
|
30
|
+
|
31
|
+
<script>
|
32
|
+
import
|
33
|
+
{
|
34
|
+
computed,
|
35
|
+
inject,
|
36
|
+
onMounted,
|
37
|
+
ref,
|
38
|
+
} from '@vue/composition-api';
|
39
|
+
|
40
|
+
import VcsActionButtonList from '../buttons/VcsActionButtonList.vue';
|
41
|
+
|
42
|
+
|
43
|
+
const iconTypes = {
|
44
|
+
image: 'HTMLImageElement',
|
45
|
+
canvas: 'HTMLCanvasElement',
|
46
|
+
string: 'StringIcon',
|
47
|
+
};
|
48
|
+
|
49
|
+
/**
|
50
|
+
* @description
|
51
|
+
* Template for a treeview leaf, see: https://vuetifyjs.com/en/api/v-treeview/
|
52
|
+
*/
|
53
|
+
export default {
|
54
|
+
components: { VcsActionButtonList },
|
55
|
+
props: {
|
56
|
+
item: {
|
57
|
+
type: Object,
|
58
|
+
default: undefined,
|
59
|
+
},
|
60
|
+
},
|
61
|
+
setup(props) {
|
62
|
+
const iconType = ref();
|
63
|
+
const imgContainer = ref();
|
64
|
+
const language = inject('language');
|
65
|
+
|
66
|
+
const leaf = computed(() => props.item.children.length === 0);
|
67
|
+
const label = computed(() => {
|
68
|
+
const titleObj = props.item.title;
|
69
|
+
if (titleObj) {
|
70
|
+
if (typeof titleObj === 'string') {
|
71
|
+
return titleObj; // TODO translateable text
|
72
|
+
} else {
|
73
|
+
return titleObj[language]; // TODO translateable text
|
74
|
+
}
|
75
|
+
}
|
76
|
+
return props.item.name;
|
77
|
+
});
|
78
|
+
|
79
|
+
onMounted(() => { // TODO make icon reactive
|
80
|
+
const { icon } = props.item;
|
81
|
+
if (icon) {
|
82
|
+
if (icon instanceof HTMLImageElement) {
|
83
|
+
imgContainer.value.appendChild(icon);
|
84
|
+
iconType.value = iconTypes.image;
|
85
|
+
}
|
86
|
+
if (icon instanceof HTMLCanvasElement) {
|
87
|
+
imgContainer.value.appendChild(icon);
|
88
|
+
iconType.value = iconTypes.canvas;
|
89
|
+
}
|
90
|
+
if (typeof icon === 'string') {
|
91
|
+
iconType.value = iconTypes.string;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
});
|
95
|
+
|
96
|
+
return {
|
97
|
+
iconTypes,
|
98
|
+
iconType,
|
99
|
+
label,
|
100
|
+
imgContainer,
|
101
|
+
leaf,
|
102
|
+
};
|
103
|
+
},
|
104
|
+
};
|
105
|
+
</script>
|
@@ -0,0 +1,156 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="pa-2 accent position-relative d-flex flex-row justify-space-between align-center">
|
3
|
+
<slot name="prepend">
|
4
|
+
<v-icon
|
5
|
+
class="search-icon my-0 ml-1"
|
6
|
+
v-text="'$vcsSearch'"
|
7
|
+
slot="prepend"
|
8
|
+
size="12"
|
9
|
+
/>
|
10
|
+
</slot>
|
11
|
+
|
12
|
+
<slot>
|
13
|
+
<v-text-field
|
14
|
+
solo
|
15
|
+
dense
|
16
|
+
hide-details
|
17
|
+
class="searchbar outlined rounded-xl align-center d-flex justify-center white pa-1 pl-6"
|
18
|
+
:placeholder="(placeholder || 'search.title') | translate"
|
19
|
+
:value="value"
|
20
|
+
@input="$event => handleInput($event)"
|
21
|
+
clearable
|
22
|
+
/>
|
23
|
+
</slot>
|
24
|
+
|
25
|
+
<slot name="append">
|
26
|
+
<v-icon v-if="hasFilter" v-text="'$vcsFilter'" class="ml-2" size="16" />
|
27
|
+
</slot>
|
28
|
+
</div>
|
29
|
+
</template>
|
30
|
+
|
31
|
+
<style lang="scss" scoped>
|
32
|
+
.input-container {
|
33
|
+
position: relative;
|
34
|
+
}
|
35
|
+
|
36
|
+
.search-icon {
|
37
|
+
position: absolute;
|
38
|
+
top: 50%;
|
39
|
+
transform: translateY(-50%);
|
40
|
+
left: 12px;
|
41
|
+
}
|
42
|
+
|
43
|
+
input {
|
44
|
+
border-radius: 9999rem;
|
45
|
+
outline-style: none;
|
46
|
+
font-size: 12px;
|
47
|
+
box-shadow: 0 0 0 1px #dedede;
|
48
|
+
|
49
|
+
&:focus {
|
50
|
+
box-shadow: 0 0 0 1px var(--v-primary-base);
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
::v-deep {
|
55
|
+
.v-input__slot {
|
56
|
+
background-color: transparent !important;
|
57
|
+
}
|
58
|
+
|
59
|
+
.v-input__append-inner {
|
60
|
+
height: 20px;
|
61
|
+
display: flex;
|
62
|
+
justify-content: center;
|
63
|
+
align-items: center;
|
64
|
+
|
65
|
+
.v-icon {
|
66
|
+
width: 20px;
|
67
|
+
height: 20px;
|
68
|
+
font-size: 16px;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
.v-text-field.v-text-field--solo.v-input--dense > .v-input__control {
|
73
|
+
min-height: unset;
|
74
|
+
}
|
75
|
+
|
76
|
+
.v-text-field.v-text-field--enclosed:not(.v-text-field--rounded) > .v-input__control > .v-input__slot,
|
77
|
+
.v-text-field.v-text-field--enclosed .v-text-field__details {
|
78
|
+
padding-right: 0;
|
79
|
+
}
|
80
|
+
|
81
|
+
.v-input.outlined {
|
82
|
+
outline-style: none;
|
83
|
+
font-size: 12px;
|
84
|
+
box-shadow: 0 0 0 1px #dedede;
|
85
|
+
|
86
|
+
&:focus,
|
87
|
+
&.v-input--is-focused {
|
88
|
+
box-shadow: 0 0 0 1px var(--v-primary-base);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
.v-text-field.v-input--dense:not(.v-text-field--outlined) .v-text-field__prefix,
|
93
|
+
.v-text-field.v-input--dense:not(.v-text-field--outlined) .v-text-field__suffix,
|
94
|
+
.v-text-field.v-input--dense:not(.v-text-field--outlined) input {
|
95
|
+
padding: 0;
|
96
|
+
}
|
97
|
+
|
98
|
+
.v-text-field.v-text-field--solo:not(.v-text-field--solo-flat) > .v-input__control > .v-input__slot {
|
99
|
+
box-shadow: none;
|
100
|
+
border-radius: 0;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
</style>
|
104
|
+
|
105
|
+
|
106
|
+
<script>
|
107
|
+
import { onMounted, onUnmounted } from '@vue/composition-api';
|
108
|
+
|
109
|
+
import { Subject } from 'rxjs';
|
110
|
+
import { debounceTime } from 'rxjs/operators';
|
111
|
+
|
112
|
+
|
113
|
+
/**
|
114
|
+
* @description Stylized wrapper around vuetify divider
|
115
|
+
* @vue-prop {number} height - Height of the component.
|
116
|
+
*/
|
117
|
+
export default {
|
118
|
+
name: 'VcsTreeviewSearchbar',
|
119
|
+
props: {
|
120
|
+
placeholder: {
|
121
|
+
type: String,
|
122
|
+
default: '',
|
123
|
+
},
|
124
|
+
customClasses: {
|
125
|
+
type: Array,
|
126
|
+
default: () => ([]),
|
127
|
+
},
|
128
|
+
value: {
|
129
|
+
type: String,
|
130
|
+
default: '',
|
131
|
+
},
|
132
|
+
hasFilter: {
|
133
|
+
type: Boolean,
|
134
|
+
default: false,
|
135
|
+
},
|
136
|
+
},
|
137
|
+
setup(props, context) {
|
138
|
+
const sub = new Subject();
|
139
|
+
onMounted(() => {
|
140
|
+
sub.pipe(debounceTime(330)).subscribe(
|
141
|
+
(value) => {
|
142
|
+
context.emit('input', value);
|
143
|
+
},
|
144
|
+
);
|
145
|
+
});
|
146
|
+
onUnmounted(() => sub.unsubscribe());
|
147
|
+
|
148
|
+
return {
|
149
|
+
sub,
|
150
|
+
handleInput: (val) => {
|
151
|
+
sub.next(val);
|
152
|
+
},
|
153
|
+
};
|
154
|
+
},
|
155
|
+
};
|
156
|
+
</script>
|