@vcmap/ui 6.1.0-rc.1 → 6.1.0-rc.3

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.
Files changed (128) hide show
  1. package/config/base.config.json +6 -0
  2. package/config/clipping.config.json +384 -0
  3. package/config/cluster.config.json +106 -0
  4. package/config/concepts-show-case.config.json +4 -0
  5. package/config/projects.config.json +5 -2
  6. package/dist/assets/{cesium-11e5bbc6.js → cesium-87d5e72d.js} +438 -432
  7. package/dist/assets/cesium.js +1 -1
  8. package/dist/assets/{core-9d0cfec3.js → core-72f9f393.js} +4907 -4514
  9. package/dist/assets/core.js +1 -1
  10. package/dist/assets/{ol-0d0ebb27.js → ol-e468ba43.js} +23518 -22404
  11. package/dist/assets/ol.js +1 -1
  12. package/dist/assets/ui-73257b15.css +1 -0
  13. package/dist/assets/{ui-08446666.js → ui-73257b15.js} +13703 -12977
  14. package/dist/assets/ui.js +1 -1
  15. package/dist/assets/vue.js +1 -1
  16. package/dist/assets/{vuetify-67025c41.css → vuetify-2437380c.css} +2 -2
  17. package/dist/assets/{vuetify-67025c41.js → vuetify-2437380c.js} +8024 -7634
  18. package/dist/assets/vuetify.js +1 -1
  19. package/index.d.ts +40 -19
  20. package/index.js +36 -6
  21. package/lib/olLib.js +25 -3
  22. package/package.json +6 -6
  23. package/plugins/@vcmap-show-case/callback-tester/README.md +3 -0
  24. package/plugins/@vcmap-show-case/callback-tester/package.json +5 -0
  25. package/plugins/@vcmap-show-case/callback-tester/src/CallbackTester.vue +62 -0
  26. package/plugins/@vcmap-show-case/callback-tester/src/index.js +48 -0
  27. package/plugins/@vcmap-show-case/form-inputs-example/src/FormInputsExample.vue +1 -0
  28. package/src/actions/actionHelper.d.ts +1 -0
  29. package/src/actions/actionHelper.js +70 -19
  30. package/src/application/VcsApp.vue +83 -50
  31. package/src/application/VcsApp.vue.d.ts +28 -2
  32. package/src/application/VcsContainer.vue +5 -3
  33. package/src/application/VcsContainer.vue.d.ts +14 -0
  34. package/src/application/VcsNavbar.vue +10 -6
  35. package/src/application/VcsNavbar.vue.d.ts +2 -0
  36. package/src/application/VcsObliqueFooter.vue +9 -3
  37. package/src/application/VcsSplashScreen.vue +37 -0
  38. package/src/application/VcsSplashScreen.vue.d.ts +6 -0
  39. package/src/application/positionDisplayInteraction.js +1 -1
  40. package/src/callback/activateClippingPolygonCallback.d.ts +29 -0
  41. package/src/callback/activateClippingPolygonCallback.js +54 -0
  42. package/src/callback/closeSplashScreenCallback.d.ts +8 -0
  43. package/src/callback/closeSplashScreenCallback.js +33 -0
  44. package/src/callback/deactivateClippingPolygonCallback.d.ts +29 -0
  45. package/src/callback/deactivateClippingPolygonCallback.js +54 -0
  46. package/src/callback/openSplashScreenCallback.d.ts +8 -0
  47. package/src/callback/openSplashScreenCallback.js +35 -0
  48. package/src/callback/toggleNavbarButtonCallback.d.ts +36 -0
  49. package/src/callback/toggleNavbarButtonCallback.js +62 -0
  50. package/src/components/buttons/VcsActionButtonList.vue +6 -4
  51. package/src/components/buttons/VcsToolButton.vue +0 -1
  52. package/src/components/form-inputs-controls/VcsDatePicker.vue +7 -1
  53. package/src/components/form-inputs-controls/VcsDatePicker.vue.d.ts +9 -0
  54. package/src/components/form-inputs-controls/VcsSelect.vue +1 -1
  55. package/src/components/form-inputs-controls/VcsTextArea.vue +13 -8
  56. package/src/components/form-output/markdownHelper.d.ts +0 -25
  57. package/src/components/form-output/markdownHelper.js +1 -386
  58. package/src/components/import/VcsImportComponent.vue +2 -0
  59. package/src/components/lists/VcsGroupedList.vue +178 -0
  60. package/src/components/lists/VcsGroupedList.vue.d.ts +17 -0
  61. package/src/components/lists/VcsList.vue +144 -394
  62. package/src/components/lists/VcsList.vue.d.ts +38 -159
  63. package/src/components/lists/VcsTreeNode.vue +18 -11
  64. package/src/components/lists/VcsTreeview.vue +27 -20
  65. package/src/components/lists/VcsTreeview.vue.d.ts +18 -1
  66. package/src/components/lists/listHelper.d.ts +87 -0
  67. package/src/components/lists/listHelper.js +348 -0
  68. package/src/components/section/VcsFormSection.vue +7 -2
  69. package/src/components/section/VcsFormSection.vue.d.ts +9 -0
  70. package/src/components/tables/VcsDataTable.vue +14 -3
  71. package/src/components/tables/VcsDataTable.vue.d.ts +9 -0
  72. package/src/components/vector-properties/VcsVectorPropertiesComponent.vue.d.ts +1 -1
  73. package/src/contentTree/LayerTree.vue +2 -1
  74. package/src/contentTree/LayerTree.vue.d.ts +2 -0
  75. package/src/contentTree/contentTreeCollection.d.ts +1 -0
  76. package/src/contentTree/contentTreeCollection.js +7 -3
  77. package/src/contentTree/contentTreeItem.js +4 -2
  78. package/src/contentTree/groupContentTreeItem.js +5 -3
  79. package/src/featureInfo/ClusterFeatureComponent.vue +58 -0
  80. package/src/featureInfo/ClusterFeatureComponent.vue.d.ts +6 -0
  81. package/src/featureInfo/abstractFeatureInfoView.js +1 -2
  82. package/src/featureInfo/featureInfo.d.ts +87 -1
  83. package/src/featureInfo/featureInfo.js +342 -34
  84. package/src/featureInfo/featureInfoInteraction.js +18 -3
  85. package/src/featureInfo/iframeFeatureInfoView.js +1 -1
  86. package/src/featureInfo/markdownBalloonFeatureInfoView.js +2 -4
  87. package/src/featureInfo/markdownFeatureInfoView.js +1 -1
  88. package/src/i18n/de.d.ts +17 -4
  89. package/src/i18n/de.js +7 -0
  90. package/src/i18n/en.d.ts +17 -4
  91. package/src/i18n/en.js +7 -0
  92. package/src/legend/VcsLegend.vue +1 -1
  93. package/src/legend/legendHelper.d.ts +1 -1
  94. package/src/legend/legendHelper.js +52 -9
  95. package/src/localStorage.d.ts +21 -0
  96. package/src/localStorage.js +51 -0
  97. package/src/manager/collectionManager/CollectionComponent.vue +1 -1
  98. package/src/manager/collectionManager/CollectionComponentContent.vue +2 -3
  99. package/src/manager/collectionManager/CollectionComponentList.vue +2 -3
  100. package/src/manager/collectionManager/CollectionComponentStandalone.vue +1 -1
  101. package/src/manager/navbarManager.js +9 -4
  102. package/src/manager/toolbox/ToolboxManagerComponent.vue +14 -12
  103. package/src/manager/toolbox/ToolboxManagerComponent.vue.d.ts +13 -2
  104. package/src/manager/toolbox/toolboxManager.d.ts +5 -0
  105. package/src/manager/toolbox/toolboxManager.js +7 -1
  106. package/src/manager/window/WindowComponent.vue +10 -0
  107. package/src/manager/window/WindowComponent.vue.d.ts +1 -0
  108. package/src/manager/window/WindowManager.vue +14 -4
  109. package/src/manager/window/WindowManager.vue.d.ts +1 -0
  110. package/src/manager/window/windowHelper.d.ts +7 -3
  111. package/src/manager/window/windowHelper.js +30 -10
  112. package/src/navigation/MapNavigation.vue +5 -5
  113. package/src/navigation/MapNavigation.vue.d.ts +1 -1
  114. package/src/navigation/overviewMap.d.ts +7 -0
  115. package/src/navigation/overviewMap.js +18 -4
  116. package/src/pluginHelper.d.ts +7 -0
  117. package/src/pluginHelper.js +18 -4
  118. package/src/search/ResultItem.vue.d.ts +1 -1
  119. package/src/search/markText.d.ts +1 -1
  120. package/src/search/markText.js +4 -4
  121. package/src/search/search.js +1 -1
  122. package/src/state.d.ts +4 -2
  123. package/src/state.js +54 -31
  124. package/src/uiConfig.d.ts +36 -0
  125. package/src/uiConfig.js +17 -1
  126. package/src/vcsUiApp.js +7 -11
  127. package/dist/assets/ui-08446666.css +0 -1
  128. /package/dist/assets/{vue-2f81c7f8.js → vue-ff37ea23.js} +0 -0
@@ -72,24 +72,27 @@
72
72
  border-color: rgb(var(--v-theme-primary));
73
73
  }
74
74
 
75
- :deep(.v-field--focused .v-field__outline__end) {
76
- border-width: 1px 1px 1px 0 !important;
77
- }
78
75
  :deep(.v-field--focused .v-field__outline__start) {
79
76
  border-width: 1px 0 1px 1px !important;
80
77
  }
78
+ :deep(.v-field--focused .v-field__outline__notch)::after {
79
+ border-width: 0 0 1px 0 !important;
80
+ }
81
+ :deep(.v-field--focused .v-field__outline__end) {
82
+ border-width: 1px 1px 1px 0 !important;
83
+ }
81
84
 
82
85
  // remove outline, if not focused, hovered or an error
83
86
  :deep(.v-field:not(.v-field--focused):not(.v-field--error):not(:hover)) {
84
87
  .v-field__outline * {
85
- border-width: 0 0 0 0;
88
+ border-width: 0 0 1px 0 !important;
86
89
  border-radius: 0;
87
90
  }
91
+ .v-field__outline__start {
92
+ margin-left: 4px;
93
+ }
88
94
  .v-field__outline__end {
89
- border-width: 0 0 1px 0 !important;
90
- border-radius: 0;
91
95
  margin-right: 4px;
92
- margin-left: -8px;
93
96
  }
94
97
  .v-field__loader {
95
98
  padding-left: 3px;
@@ -102,6 +105,8 @@
102
105
  .v-field__outline * label {
103
106
  color: rgb(var(--v-theme-primary));
104
107
  margin-left: -4px;
108
+ border-width: 0 0 0 0;
109
+ border-radius: 0;
105
110
  }
106
111
  }
107
112
  :deep(.v-field .v-field--focused .v-field--error):hover {
@@ -110,7 +115,7 @@
110
115
  // Not color, just used if label is given
111
116
  :deep(.v-field--focused:not(.v-field--error) .v-field__outline *::after) {
112
117
  border-color: rgb(var(--v-theme-primary));
113
- border-width: 1px !important;
118
+ border-width: 0 0 1px !important;
114
119
  }
115
120
 
116
121
  // remove margin from prepended Icon
@@ -1,30 +1,5 @@
1
- /**
2
- * @typedef {Object} Block
3
- * @property {RegExpExecArray} opening
4
- * @property {RegExpExecArray} closing
5
- */
6
- /**
7
- * @typedef {Block} ConditionalBlock
8
- * @property {RegExpExecArray|undefined} elseStatement
9
- * @property {RegExpExecArray[]} elseIfs
10
- */
11
1
  /**
12
2
  * @param {string} content
13
3
  * @returns {string}
14
4
  */
15
5
  export function parseAndSanitizeMarkdown(content: string): string;
16
- /**
17
- * Renders a template in these steps
18
- * 1. expand conditional blocks. this will remove any blocks that do not match their expressions and choose from if / elseif / else block which of them to render
19
- * 2. expand iterations. this will create new templates for each iteration and re-run the rendering for those blocks
20
- * 3. render attributes. this will add the attributes to all the blocks not within each blocks
21
- * @param {string|string[]} template
22
- * @param {Record<string, unknown>} data
23
- * @returns {string}
24
- */
25
- export function renderTemplate(template: string | string[], data: Record<string, unknown>): string;
26
- export type Block = {
27
- opening: RegExpExecArray;
28
- closing: RegExpExecArray;
29
- };
30
- export type ConditionalBlock = Block;
@@ -1,398 +1,13 @@
1
1
  import { marked } from 'marked';
2
2
  import DOMPurify from 'dompurify';
3
- import {
4
- BooleanType,
5
- newParsingContext,
6
- StringType,
7
- NoneType,
8
- } from 'ol/expr/expression';
9
- import { buildExpression, newEvaluationContext } from 'ol/expr/cpu';
10
- import { is } from '@vcsuite/check';
11
-
12
- /**
13
- * @typedef {Object} Block
14
- * @property {RegExpExecArray} opening
15
- * @property {RegExpExecArray} closing
16
- */
17
-
18
- /**
19
- * @typedef {Block} ConditionalBlock
20
- * @property {RegExpExecArray|undefined} elseStatement
21
- * @property {RegExpExecArray[]} elseIfs
22
- */
23
3
 
24
4
  /**
25
5
  * @param {string} content
26
6
  * @returns {string}
27
7
  */
8
+ // eslint-disable-next-line import/prefer-default-export
28
9
  export function parseAndSanitizeMarkdown(content) {
29
10
  const html = marked.parse(content, { breaks: true });
30
-
31
11
  // Then sanitize the HTML using DOMPurify
32
12
  return DOMPurify.sanitize(html, { ADD_ATTR: ['target'] });
33
13
  }
34
-
35
- /**
36
- * @param {string} expressionString
37
- * @param {Record<string, unknown>} data
38
- * @param {number} evaluationType
39
- * @returns {*}
40
- */
41
- function evaluateExpression(expressionString, data, evaluationType) {
42
- const parsed = expressionString.startsWith('[')
43
- ? JSON.parse(expressionString)
44
- : [
45
- 'get',
46
- ...expressionString
47
- .replace(/\[([^\]]+)]/g, '.$1')
48
- .split('.')
49
- .filter((f) => f),
50
- ];
51
-
52
- const compiledExpression = buildExpression(
53
- parsed,
54
- evaluationType,
55
- newParsingContext(),
56
- );
57
- const evaluationContext = newEvaluationContext();
58
- evaluationContext.properties = data;
59
- return compiledExpression(evaluationContext);
60
- }
61
-
62
- /**
63
- * Replaces template strings by provided attributes, e.g. {{myAttribute}}
64
- * @param {string} template
65
- * @param {Record<string, unknown>} data
66
- * @returns {string}
67
- */
68
- function replaceAttributes(template, data) {
69
- const pattern = /\{\{([^}]+)}}/g;
70
- return template.replace(pattern, (p, value) => {
71
- return evaluateExpression(value.trim(), data, StringType) ?? '';
72
- });
73
- }
74
-
75
- /**
76
- * @param {RegExp} regexp
77
- * @param {string} string
78
- * @returns {RegExpExecArray[]}
79
- */
80
- function regexHits(regexp, string) {
81
- const hits = [];
82
- let hit;
83
- // eslint-disable-next-line no-cond-assign
84
- while ((hit = regexp.exec(string))) {
85
- hits.push(hit);
86
- }
87
-
88
- return hits;
89
- }
90
-
91
- /**
92
- * @param {RegExpExecArray[]} openings
93
- * @param {RegExpExecArray[]} closings
94
- * @param {(Block) => void} accepted
95
- * @param {(Block) => void} rejected
96
- * @returns {void}
97
- */
98
- function findTopLevelBlock(openings, closings, accepted, rejected) {
99
- const localOpenings = openings.slice();
100
- const localClosings = closings.slice();
101
- while (localOpenings.length > 0) {
102
- let matchingClosing;
103
- let matchingOpening;
104
- while (!matchingClosing && localClosings.length > 0) {
105
- const currentClosing = localClosings.shift();
106
- const openingDistances = localOpenings.map(
107
- (o) => currentClosing.index - o.index,
108
- );
109
- const minDistance = openingDistances.reduce((min, currentDistance) => {
110
- if (currentDistance > 0 && currentDistance < min) {
111
- return currentDistance;
112
- }
113
- return min;
114
- }, Infinity);
115
- const matchingOpeningIndex = openingDistances.indexOf(minDistance);
116
- matchingOpening = localOpenings[matchingOpeningIndex];
117
-
118
- if (matchingOpeningIndex === 0) {
119
- matchingClosing = currentClosing;
120
- } else {
121
- rejected({ opening: matchingOpening, closing: currentClosing });
122
- }
123
- localOpenings.splice(matchingOpeningIndex, 1);
124
- }
125
-
126
- if (matchingClosing) {
127
- accepted({ opening: matchingOpening, closing: matchingClosing });
128
- }
129
- }
130
- }
131
-
132
- /**
133
- * @param {RegExpExecArray} tag
134
- * @param {Block} block
135
- * @returns {boolean}
136
- */
137
- function tagWithinBlock(tag, block) {
138
- return tag.index > block.opening.index && tag.index < block.closing.index;
139
- }
140
-
141
- /**
142
- * @param {string} template
143
- * @returns {Block[]}
144
- */
145
- function getForEachBlocks(template) {
146
- const forEachBlocks = [];
147
- const forEachOpenings = regexHits(
148
- /\s*{{#each\s\(([^.)]+)\)\sin\s([^}]+)}}\s*/g,
149
- template,
150
- );
151
- const forEachClosings = regexHits(/\s*{{\/each}}\s*/g, template);
152
-
153
- if (forEachClosings.length > forEachOpenings.length) {
154
- throw new Error(
155
- 'Template failed to render, missing opening tag for each statements',
156
- );
157
- } else if (forEachClosings.length < forEachOpenings.length) {
158
- throw new Error(
159
- 'Template failed to render, missing closing tag for each statements',
160
- );
161
- }
162
-
163
- findTopLevelBlock(
164
- forEachOpenings,
165
- forEachClosings,
166
- (block) => {
167
- forEachBlocks.push(block);
168
- },
169
- () => {},
170
- );
171
-
172
- return forEachBlocks;
173
- }
174
-
175
- /**
176
- * @param {string} template
177
- * @param {Block[]} forEachBlocks
178
- * @returns {ConditionalBlock[]}
179
- */
180
- function getConditionalBlocks(template, forEachBlocks) {
181
- const conditionalBlocks = [];
182
- let conditionalOpenings = regexHits(/\s*{{#if\s([^}]*)}}\s*/g, template);
183
- let conditionalClosings = regexHits(/\s*{{\/if}}\s*/g, template);
184
- let elseIfs = regexHits(/\s*{{elseif\s([^}]*)}}\s*/g, template);
185
- let elses = regexHits(/\s*{{else}}\s*/g, template);
186
-
187
- const withinForEachBlock = (tag) =>
188
- forEachBlocks.find((block) => tagWithinBlock(tag, block));
189
-
190
- // conditionals within a for each blocks are rendered with the for each block, ignore
191
- conditionalOpenings = conditionalOpenings.filter(
192
- (t) => !withinForEachBlock(t),
193
- );
194
- conditionalClosings = conditionalClosings.filter(
195
- (t) => !withinForEachBlock(t),
196
- );
197
-
198
- if (conditionalClosings.length > conditionalOpenings.length) {
199
- throw new Error(
200
- 'Template failed to render, missing closing tag for if statements',
201
- );
202
- } else if (conditionalClosings.length < conditionalOpenings.length) {
203
- throw new Error(
204
- 'Template failed to render, missing opening tag for if statements',
205
- );
206
- }
207
-
208
- const filterElseIfElse = (block) => {
209
- elseIfs = elseIfs.filter((tag) => !tagWithinBlock(tag, block));
210
- elses = elses.filter((tag) => !tagWithinBlock(tag, block));
211
- };
212
-
213
- findTopLevelBlock(
214
- conditionalOpenings,
215
- conditionalClosings,
216
- (block) => {
217
- const elsIfs = elseIfs.filter((tag) => tagWithinBlock(tag, block));
218
- const elseStatement = elses.find((tag) => tagWithinBlock(tag, block));
219
- if (
220
- elseStatement &&
221
- elsIfs.length > 0 &&
222
- elseStatement.index < elsIfs.at(-1).index
223
- ) {
224
- throw new Error('{{else}} must be the last entry in a block');
225
- }
226
- conditionalBlocks.push({
227
- ...block,
228
- elseStatement,
229
- elseIfs,
230
- });
231
- },
232
- (block) => {
233
- filterElseIfElse(block);
234
- },
235
- );
236
-
237
- return conditionalBlocks;
238
- }
239
-
240
- /**
241
- * @param {string} openingTag
242
- * @returns {boolean}
243
- */
244
- function shouldRemoveWhiteSpace(openingTag) {
245
- return /\n\s*\{/.test(openingTag) && /}\s*\n/.test(openingTag);
246
- }
247
-
248
- /**
249
- * This will extract the block to render separately. This will depend on the white space handling. If the
250
- * opening is placed on its own line, whitespace after the opening and before the closing blocks will be removed
251
- * from the sub template, up to the first new line feed.
252
- * @param {string} template
253
- * @param {Block} block
254
- * @returns {string}
255
- */
256
- function getSubTemplateForBlock(template, block) {
257
- const removeWhiteSpace = shouldRemoveWhiteSpace(block.opening[0]);
258
- let startIndex = block.opening.index + block.opening[0].indexOf('}') + 2;
259
- let endIndex = block.closing.index + block.closing[0].indexOf('{');
260
- if (removeWhiteSpace) {
261
- startIndex += (/}\s*\n/.exec(block.opening[0])?.[0].length ?? 1) - 1;
262
- endIndex -= (/\n\s*\{/.exec(block.closing[0])?.[0].length ?? 2) - 2;
263
- }
264
-
265
- return template.substring(startIndex, endIndex);
266
- }
267
-
268
- /**
269
- * This will replace a block with a previously extracted blocks rendered template.
270
- * This will depend on the white space handling. If the opening is placed on its own line,
271
- * whitespace before the opening and after the closing blocks will be removed up to the first new line feed,
272
- * from the new template string all together.
273
- * @param {string} template
274
- * @param {Block} block
275
- * @param {string} replacement
276
- * @returns {string}
277
- */
278
- function replaceBlock(template, block, replacement) {
279
- const removeWhiteSpace = shouldRemoveWhiteSpace(block.opening[0]);
280
- let startIndex = block.opening.index + block.opening[0].indexOf('{');
281
- let endIndex = block.closing.index + block.closing[0].indexOf('}') + 2;
282
- if (removeWhiteSpace) {
283
- startIndex -= (/\n\s*\{/.exec(block.opening[0])?.[0].length ?? 2) - 2;
284
- endIndex += (/}\s*\n/.exec(block.closing[0])?.[0].length ?? 1) - 1;
285
- }
286
-
287
- return `${template.substring(0, startIndex)}${replacement}${template.substring(endIndex)}`;
288
- }
289
-
290
- /**
291
- * Replaces {{#if }} blocks
292
- * @param {string} template
293
- * @param {Record<string, unknown>} data
294
- * @returns {string}
295
- */
296
- function expandConditionalsAndLoops(template, data) {
297
- let renderedTemplate = template;
298
- const forEachBlocks = getForEachBlocks(template);
299
-
300
- getConditionalBlocks(template, forEachBlocks)
301
- .reverse()
302
- .forEach(
303
- /** @param {ConditionalBlock} block */ (block) => {
304
- const partialBlocks = [block.opening];
305
- if (block.elseIfs) {
306
- partialBlocks.push(...block.elseIfs);
307
- }
308
- let trueStatementIndex = partialBlocks.findIndex((s) =>
309
- evaluateExpression(s[1], data, BooleanType),
310
- );
311
- if (trueStatementIndex === -1 && block.elseStatement) {
312
- trueStatementIndex = partialBlocks.length;
313
- }
314
-
315
- let renderedBlock = '';
316
- if (trueStatementIndex > -1) {
317
- if (block.elseStatement) {
318
- partialBlocks.push(block.elseStatement);
319
- }
320
- partialBlocks.push(block.closing);
321
-
322
- const blockTemplate = getSubTemplateForBlock(template, {
323
- opening: partialBlocks[trueStatementIndex],
324
- closing: partialBlocks[trueStatementIndex + 1],
325
- });
326
-
327
- renderedBlock = expandConditionalsAndLoops(blockTemplate, data);
328
- }
329
-
330
- renderedTemplate = replaceBlock(renderedTemplate, block, renderedBlock);
331
- },
332
- );
333
-
334
- // only iterate over blocks not removed by conditionals
335
- getForEachBlocks(renderedTemplate)
336
- .reverse()
337
- .forEach((block) => {
338
- const obj = evaluateExpression(block.opening[2], data, NoneType);
339
- let keyValuePairs;
340
- if (is(obj, Object)) {
341
- keyValuePairs = Object.entries(obj);
342
- } else if (Array.isArray(obj)) {
343
- keyValuePairs = obj.entries();
344
- }
345
- const renderedBlocks = [];
346
- if (keyValuePairs) {
347
- let index = 0;
348
- const [valueName, keyName, indexName] = block.opening[1]
349
- .split(',')
350
- .map((e) => e.trim())
351
- .slice(0, 3);
352
-
353
- const blockTemplate = getSubTemplateForBlock(renderedTemplate, block);
354
- // eslint-disable-next-line no-restricted-syntax
355
- for (const args of keyValuePairs) {
356
- const forEachData = structuredClone(data);
357
- forEachData[valueName] = args[1];
358
- if (keyName) {
359
- forEachData[keyName] = args[0];
360
- }
361
- if (indexName) {
362
- forEachData[indexName] = index;
363
- }
364
- const currentBlock = expandConditionalsAndLoops(
365
- blockTemplate,
366
- forEachData,
367
- );
368
- renderedBlocks.push(replaceAttributes(currentBlock, forEachData));
369
- index += 1;
370
- }
371
- }
372
-
373
- renderedTemplate = replaceBlock(
374
- renderedTemplate,
375
- block,
376
- renderedBlocks.join(''),
377
- );
378
- });
379
-
380
- return renderedTemplate;
381
- }
382
-
383
- /**
384
- * Renders a template in these steps
385
- * 1. expand conditional blocks. this will remove any blocks that do not match their expressions and choose from if / elseif / else block which of them to render
386
- * 2. expand iterations. this will create new templates for each iteration and re-run the rendering for those blocks
387
- * 3. render attributes. this will add the attributes to all the blocks not within each blocks
388
- * @param {string|string[]} template
389
- * @param {Record<string, unknown>} data
390
- * @returns {string}
391
- */
392
- export function renderTemplate(template, data) {
393
- const templateString = Array.isArray(template)
394
- ? template.join('\n')
395
- : template;
396
- const conditionalTemplate = expandConditionalsAndLoops(templateString, data);
397
- return replaceAttributes(conditionalTemplate, data);
398
- }
@@ -12,6 +12,7 @@
12
12
  v-bind="noListenerAttrs"
13
13
  v-model="files"
14
14
  />
15
+ <slot />
15
16
  <div class="d-flex justify-end gc-1 mx-3 pt-2 pb-1">
16
17
  <vcs-form-button
17
18
  variant="filled"
@@ -41,6 +42,7 @@
41
42
  * @vue-prop {function(Array<File>):boolean} importFiles - the callback to
42
43
  * @vue-prop {string[]} [fileTypes=[]] - accepted file types, see https://html.spec.whatwg.org/multipage/input.html#attr-input-accept
43
44
  * @vue-prop {boolean} [multiple=true] - allows or disallows importing multiple files at once
45
+ * @vue-data {#default} - Slot to add additional html after file input and before the buttons
44
46
  */
45
47
  export default {
46
48
  name: 'VcsImportComponent',
@@ -0,0 +1,178 @@
1
+ <script setup>
2
+ import { computed, getCurrentInstance, watch } from 'vue';
3
+ import { VExpansionPanels } from 'vuetify/components';
4
+ import {
5
+ useProxiedAtomicModel,
6
+ useProxiedComplexModel,
7
+ } from '../modelHelper.js';
8
+ import VcsTreeviewSearchbar from './VcsTreeviewSearchbar.vue';
9
+ import VcsExpansionPanel from '../section/VcsExpansionPanel.vue';
10
+ import VcsList from './VcsList.vue';
11
+ import { setupSelectableList } from './listHelper.js';
12
+
13
+ /**
14
+ * @typedef {Object} VcsListGroup
15
+ * @property {string} name
16
+ * @property {string?} title
17
+ * @property {boolean?} disabled
18
+ * @property {Array<import("../../actions/actionHelper.js").VcsAction>?} headerActions
19
+ * @property {number?} overflowCount
20
+ */
21
+
22
+ /**
23
+ * @typedef {import("./VcsListItemComponent.vue").VcsListItem & { group: string } VcsGroupedListItem
24
+ * @property {string} group
25
+ */
26
+
27
+ /**
28
+ * @description
29
+ * The VcsGroupedList is intended to render items grouped by key.
30
+ * Items not belonging to one of the provided groups are neglected.
31
+ * Per group a sub list is rendered, which behaves just like a { @link VcsList }.
32
+ * Only differences: The search and selection behaviour are cross group and items are not draggable.
33
+ * @vue-prop {Array<import("./VcsListItemComponent.vue").VcsListItem>} items
34
+ * @vue-prop {boolean} [selectable=false]
35
+ * @vue-prop {boolean} [singleSelect=false]
36
+ * @vue-prop {Array<import("./VcsListItemComponent.vue").VcsListItem>} [modelValue=[]] - the initial items to be selected.
37
+ * @vue-prop {boolean} [searchable=false] - if this list can have its items searched.
38
+ * @vue-prop {function(import("./VcsListItemComponent.vue").VcsListItem, string):boolean} [customFilter] - a function to customize filtering when searching.
39
+ * @vue-prop {string} [searchbarPlaceholder] - placeholder to render inside the search field
40
+ */
41
+
42
+ const props = defineProps({
43
+ /** @type {VcsGroupedListItem} */
44
+ items: {
45
+ type: Array,
46
+ default: () => [],
47
+ },
48
+ groups: {
49
+ type: Array,
50
+ default: () => [],
51
+ },
52
+ opened: {
53
+ type: Array,
54
+ default: () => [],
55
+ },
56
+ selectable: {
57
+ type: Boolean,
58
+ default: false,
59
+ },
60
+ singleSelect: {
61
+ type: Boolean,
62
+ default: false,
63
+ },
64
+ modelValue: {
65
+ type: Array,
66
+ default: () => [],
67
+ },
68
+ search: {
69
+ type: String,
70
+ default: '',
71
+ },
72
+ searchable: {
73
+ type: Boolean,
74
+ default: false,
75
+ },
76
+ searchbarPlaceholder: {
77
+ type: String,
78
+ default: undefined,
79
+ },
80
+ customFilter: {
81
+ type: Function,
82
+ default: undefined,
83
+ },
84
+ openAll: {
85
+ type: Boolean,
86
+ default: false,
87
+ },
88
+ });
89
+
90
+ const emit = defineEmits([
91
+ 'update:modelValue',
92
+ 'update:search',
93
+ 'update:opened',
94
+ ]);
95
+
96
+ const vm = getCurrentInstance().proxy;
97
+ const localSearchValue = useProxiedAtomicModel(props, 'search', emit);
98
+ const localOpenedGroups = useProxiedComplexModel(props, 'opened', emit);
99
+
100
+ const translatedTitle = (item) =>
101
+ item.title ? vm.$t(item.title) : item.name;
102
+
103
+ /** @type {function(import("./VcsListItemComponent.vue").VcsListItem, string):boolean} */
104
+ const filterPredicate = (item, queryString = '') => {
105
+ if (props.customFilter) {
106
+ return props.customFilter(item, queryString);
107
+ }
108
+ return translatedTitle(item)
109
+ .toLocaleLowerCase()
110
+ .includes(queryString.toLocaleLowerCase());
111
+ };
112
+
113
+ function sortByGroupOrder(items, groups) {
114
+ const groupOrder = new Map(groups.map((g, index) => [g.name, index]));
115
+
116
+ return items.sort(
117
+ (a, b) => groupOrder.get(a.group) - groupOrder.get(b.group),
118
+ );
119
+ }
120
+
121
+ const renderingItems = computed(() => {
122
+ let { items } = props;
123
+ if (localSearchValue.value) {
124
+ items = items.filter((i) => filterPredicate(i, localSearchValue.value));
125
+ }
126
+ return sortByGroupOrder(items, props.groups);
127
+ });
128
+
129
+ const { select, selected } = setupSelectableList(props, renderingItems, emit);
130
+
131
+ watch(
132
+ [() => props.groups, () => props.openAll],
133
+ () => {
134
+ if (props.openAll) {
135
+ localOpenedGroups.value = [...props.groups.map((group) => group.name)];
136
+ }
137
+ },
138
+ {
139
+ immediate: true,
140
+ },
141
+ );
142
+ </script>
143
+
144
+ <template>
145
+ <div>
146
+ <VcsTreeviewSearchbar
147
+ v-if="searchable"
148
+ :placeholder="searchbarPlaceholder"
149
+ v-model="localSearchValue"
150
+ />
151
+ <v-expansion-panels
152
+ variant="accordion"
153
+ multiple
154
+ v-model="localOpenedGroups"
155
+ class="rounded-0"
156
+ >
157
+ <vcs-expansion-panel
158
+ v-for="(group, i) in groups"
159
+ :key="i"
160
+ :value="group.name"
161
+ :disabled="group.disabled"
162
+ :heading="group.title ?? group.name"
163
+ :header-actions="group.headerActions"
164
+ :action-button-list-overflow-count="group.overflowCount"
165
+ >
166
+ <vcs-list
167
+ :items="renderingItems.filter((s) => s.group === group.name)"
168
+ :selectable="selectable"
169
+ :single-select="singleSelect"
170
+ :select-function="select"
171
+ :model-value="selected.filter((s) => s.group === group.name)"
172
+ />
173
+ </vcs-expansion-panel>
174
+ </v-expansion-panels>
175
+ </div>
176
+ </template>
177
+
178
+ <style scoped lang="scss"></style>
@@ -0,0 +1,17 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {
2
+ $emit: (event: "update:modelValue" | "update:opened" | "update:search", ...args: any[]) => void;
3
+ $props: {
4
+ readonly [x: string]: any;
5
+ };
6
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
7
+ export default _default;
8
+ export type VcsListGroup = {
9
+ name: string;
10
+ title: string | null;
11
+ disabled: boolean | null;
12
+ headerActions: Array<import("../../actions/actionHelper.js").VcsAction> | null;
13
+ overflowCount: number | null;
14
+ };
15
+ export type VcsGroupedListItem = import("./VcsListItemComponent.vue").VcsListItem & {
16
+ group: string;
17
+ };