@bimdata/bcf-components 6.0.0-rc.9 → 6.0.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.
Files changed (65) hide show
  1. package/README.md +0 -6
  2. package/package.json +15 -20
  3. package/src/components/bcf-filters/BcfFilters.scss +67 -0
  4. package/src/components/bcf-filters/BcfFilters.vue +228 -0
  5. package/src/components/bcf-settings/BcfSettings.scss +28 -0
  6. package/src/components/bcf-settings/BcfSettings.vue +117 -0
  7. package/src/components/bcf-settings/setting-card/SettingCard.scss +59 -0
  8. package/src/components/bcf-settings/setting-card/SettingCard.vue +140 -0
  9. package/src/components/bcf-settings/setting-card/SettingCardItem.scss +48 -0
  10. package/src/components/bcf-settings/setting-card/SettingCardItem.vue +151 -0
  11. package/src/components/bcf-statistics/BcfStatistics.scss +56 -0
  12. package/src/components/bcf-statistics/BcfStatistics.vue +112 -0
  13. package/src/components/bcf-topic-card/BcfTopicCard.scss +96 -0
  14. package/src/components/bcf-topic-card/BcfTopicCard.vue +150 -0
  15. package/src/components/bcf-topic-card/BcfTopicDefaultImage.vue +90 -0
  16. package/src/components/bcf-topic-creation-card/BcfTopicCreationCard.scss +15 -0
  17. package/src/components/bcf-topic-creation-card/BcfTopicCreationCard.vue +37 -0
  18. package/src/components/bcf-topic-creation-card/BcfTopicCreationCardImage.vue +55 -0
  19. package/src/components/bcf-topic-form/BcfTopicForm.scss +124 -0
  20. package/src/components/bcf-topic-form/BcfTopicForm.vue +541 -0
  21. package/src/components/bcf-topic-form/bcf-topic-images/BcfTopicImages.scss +88 -0
  22. package/src/components/bcf-topic-form/bcf-topic-images/BcfTopicImages.vue +101 -0
  23. package/src/components/bcf-topic-form/bcf-topic-snapshots/BcfTopicSnapshots.scss +50 -0
  24. package/src/components/bcf-topic-form/bcf-topic-snapshots/BcfTopicSnapshots.vue +71 -0
  25. package/src/components/bcf-topic-form/bcf-topic-snapshots-actions/BcfTopicSnapshotsActions.scss +9 -0
  26. package/src/components/bcf-topic-form/bcf-topic-snapshots-actions/BcfTopicSnapshotsActions.vue +69 -0
  27. package/src/components/bcf-topic-overview/BcfTopicOverview.scss +94 -0
  28. package/src/components/bcf-topic-overview/BcfTopicOverview.vue +350 -0
  29. package/src/components/bcf-topic-overview/bcf-topic-comments/BcfTopicComments.scss +39 -0
  30. package/src/components/bcf-topic-overview/bcf-topic-comments/BcfTopicComments.vue +268 -0
  31. package/src/components/bcf-topic-overview/bcf-topic-comments/topic-comment/TopicComment.scss +79 -0
  32. package/src/components/bcf-topic-overview/bcf-topic-comments/topic-comment/TopicComment.vue +357 -0
  33. package/src/components/bcf-topic-overview/bcf-topic-viewpoints/BcfTopicViewpoints.scss +73 -0
  34. package/src/components/bcf-topic-overview/bcf-topic-viewpoints/BcfTopicViewpoints.vue +84 -0
  35. package/src/components/bcf-topics-table/BcfTopicsTable.vue +148 -0
  36. package/src/components/bcf-topics-table/bcf-topic-actions-cell/BcfTopicActionsCell.vue +43 -0
  37. package/src/components/bcf-topics-table/bcf-topic-index-cell/BcfTopicIndexCell.vue +55 -0
  38. package/src/components/bcf-topics-table/bcf-topic-priority-cell/BcfTopicPriorityCell.vue +47 -0
  39. package/src/components/bcf-topics-table/bcf-topic-status-cell/BcfTopicStatusCell.vue +52 -0
  40. package/src/components/bcf-topics-table/columns.js +38 -0
  41. package/src/components/user-avatar/UserAvatar.scss +30 -0
  42. package/src/components/user-avatar/UserAvatar.vue +66 -0
  43. package/src/composables/filter.js +58 -0
  44. package/src/composables/search.js +42 -0
  45. package/src/composables/sort.js +64 -0
  46. package/src/config.js +87 -0
  47. package/{dist → src}/i18n/lang/en.json +3 -1
  48. package/{dist → src}/i18n/lang/fr.json +5 -1
  49. package/src/index.js +59 -0
  50. package/src/service.js +206 -0
  51. package/src/utils/browser.js +12 -0
  52. package/src/utils/download.js +15 -0
  53. package/src/utils/extensions.js +14 -0
  54. package/src/utils/topic.js +23 -0
  55. package/src/utils/viewer.js +21 -0
  56. package/src/utils/viewpoints.js +28 -0
  57. package/vue3-plugin.js +4 -5
  58. package/dist/bcf-components.mjs +0 -10311
  59. package/dist/style.css +0 -1
  60. /package/{dist → src}/i18n/index.js +0 -0
  61. /package/{dist → src}/i18n/lang/de.json +0 -0
  62. /package/{dist → src}/i18n/lang/es.json +0 -0
  63. /package/{dist → src}/i18n/lang/it.json +0 -0
  64. /package/{dist → src}/i18n/lang/nl.json +0 -0
  65. /package/{dist → src}/i18n/lang/no.json +0 -0
@@ -0,0 +1,541 @@
1
+ <template>
2
+ <div class="bcf-topic-form">
3
+ <div class="bcf-topic-form__header">
4
+ <BIMDataButton v-if="uiConfig.backButton" ghost rounded icon @click="$emit('back')">
5
+ <BIMDataIconArrow size="xxs" fill color="granite-light" />
6
+ </BIMDataButton>
7
+ <div class="bcf-topic-form__header__title">
8
+ <template v-if="isCreation">
9
+ {{ $t("BcfComponents.BcfTopicForm.createTitle") }}
10
+ </template>
11
+ <template v-else>
12
+ <BIMDataTextbox maxWidth="250px" :text="topic.title" />
13
+ </template>
14
+ </div>
15
+ <BIMDataButton v-if="uiConfig.closeButton" ghost rounded icon @click="$emit('close')">
16
+ <BIMDataIconClose size="xxs" fill color="granite-light" />
17
+ </BIMDataButton>
18
+ </div>
19
+
20
+ <div class="bcf-topic-form__content">
21
+ <div class="bcf-topic-form__content__head">
22
+ <div class="bcf-topic-form__content__head__index">
23
+ {{ isCreation ? nextIndex : topic.index }}
24
+ </div>
25
+ <div class="bcf-topic-form__content__head__date">
26
+ {{ $d(isCreation ? new Date() : topic.creation_date, "short") }}
27
+ </div>
28
+ </div>
29
+
30
+ <template v-if="uiConfig.viewerMode">
31
+ <BcfTopicSnapshots
32
+ :viewpoints="viewpointsToDisplay"
33
+ :getViewers="getViewers"
34
+ @create-viewpoint="createViewpoints"
35
+ @upload-viewpoint="uploadViewpoints"
36
+ @delete-viewpoint="deleteViewpoint"
37
+ />
38
+ <div class="bcf-topic-form__content__actions">
39
+ <BcfTopicSnapshotsActions
40
+ v-if="viewpointsToDisplay.length > 0"
41
+ :viewpoints="viewpointsToDisplay"
42
+ :getViewers="getViewers"
43
+ @create-viewpoint="createViewpoints"
44
+ @upload-viewpoint="uploadViewpoints"
45
+ />
46
+ <BIMDataButton
47
+ fill
48
+ radius
49
+ :disabled="!objectsEditEnabled"
50
+ @click="$emit('edit-topic-objects', topic)"
51
+ >
52
+ <BIMDataIconPlus size="xxxs" margin="0 6px 0 0" />
53
+ <span>
54
+ {{ $t("BcfComponents.BcfTopicForm.addObjectButton") }}
55
+ </span>
56
+ <span v-if="topicObjects" class="count-objects">
57
+ {{ topicObjects.selection.length }}
58
+ </span>
59
+ </BIMDataButton>
60
+ <BIMDataTooltip
61
+ :disabled="viewpointsToDisplay.length > 0"
62
+ :text="$t('BcfComponents.BcfTopicForm.annotationButtonTooltip')"
63
+ color="granite-light"
64
+ >
65
+ <BIMDataButton
66
+ width="100%"
67
+ color="primary"
68
+ fill
69
+ radius
70
+ :disabled="!annotationsEditEnabled || viewpointsToDisplay.length === 0"
71
+ @click="$emit('edit-topic-annotations', topic)"
72
+ >
73
+ <BIMDataIconPlus size="xxxs" margin="0 6px 0 0" />
74
+ <span>
75
+ {{ $t("BcfComponents.BcfTopicForm.addAnnotationButton") }}
76
+ </span>
77
+ <span v-if="topicAnnotations" class="count-annotations">
78
+ {{ topicAnnotations.length }}
79
+ </span>
80
+ </BIMDataButton>
81
+ </BIMDataTooltip>
82
+ </div>
83
+ </template>
84
+ <template v-else>
85
+ <BcfTopicImages
86
+ :viewpoints="viewpointsToDisplay"
87
+ @upload-viewpoint="uploadViewpoints"
88
+ @delete-viewpoint="deleteViewpoint"
89
+ />
90
+ </template>
91
+
92
+ <div class="bcf-topic-form__content__body">
93
+ <BIMDataInput
94
+ :placeholder="$t('BcfComponents.BcfTopicForm.titlePlaceholder')"
95
+ :error="hasErrorTitle"
96
+ :errorMessage="$t('BcfComponents.BcfTopicForm.titleErrorMessage')"
97
+ v-model="topicTitle"
98
+ @keyup.enter.stop="submit"
99
+ />
100
+ <BIMDataSelect
101
+ width="100%"
102
+ :label="$t('BcfComponents.BcfTopicForm.typeLabel')"
103
+ :options="extensions.topic_type"
104
+ :disabled="extensions.topic_type.length === 0"
105
+ v-model="topicType"
106
+ />
107
+ <BIMDataSelect
108
+ width="100%"
109
+ :label="$t('BcfComponents.BcfTopicForm.priorityLabel')"
110
+ :options="extensions.priority"
111
+ :disabled="extensions.priority.length === 0"
112
+ v-model="topicPriority"
113
+ />
114
+ <BIMDataSelect
115
+ width="100%"
116
+ :label="$t('BcfComponents.BcfTopicForm.statusLabel')"
117
+ :options="extensions.topic_status"
118
+ :disabled="extensions.topic_status.length === 0"
119
+ v-model="topicStatus"
120
+ />
121
+ <BIMDataSelect
122
+ width="100%"
123
+ :label="$t('BcfComponents.BcfTopicForm.stageLabel')"
124
+ :options="extensions.stage"
125
+ :disabled="extensions.stage.length === 0"
126
+ v-model="topicStage"
127
+ />
128
+ <BIMDataSelect
129
+ width="100%"
130
+ :search="true"
131
+ :clearSearchOnLeave="true"
132
+ :label="$t('BcfComponents.BcfTopicForm.assignedToLabel')"
133
+ :options="extensions.user_id_type"
134
+ :disabled="extensions.user_id_type.length === 0"
135
+ v-model="topicAssignedTo"
136
+ :searchPlaceholder="$t('BcfComponents.BcfTopicForm.searchPlaceholder')"
137
+ >
138
+ <template #empty> <span class="color-granite p-x-12">{{ $t('BcfComponents.BcfTopicForm.emptySearch') }}</span> </template>
139
+ </BIMDataSelect>
140
+ <div class="m-b-30">
141
+ <BIMDataDatePicker
142
+ width="100%"
143
+ :placeholder="$t('BcfComponents.BcfTopicForm.dueDateLabel')"
144
+ :value="topicDueDate"
145
+ v-model="topicDueDate"
146
+ :clearButton="true"
147
+ />
148
+ </div>
149
+ <BIMDataTextarea
150
+ width="100%"
151
+ name="description"
152
+ :label="$t('BcfComponents.BcfTopicForm.descriptionLabel')"
153
+ v-model="topicDescription"
154
+ fitContent
155
+ />
156
+ <BIMDataSelect
157
+ width="100%"
158
+ :label="$t('BcfComponents.BcfTopicForm.labelsLabel')"
159
+ :options="extensions.topic_label"
160
+ :disabled="extensions.topic_label.length === 0"
161
+ v-model="topicLabels"
162
+ :multi="true"
163
+ />
164
+ </div>
165
+ </div>
166
+
167
+ <div class="bcf-topic-form__footer">
168
+ <BIMDataButton
169
+ width="100%"
170
+ color="primary"
171
+ fill
172
+ radius
173
+ :disabled="!topicTitle"
174
+ @click="submit"
175
+ >
176
+ {{ $t(`BcfComponents.BcfTopicForm.${isCreation ? "createButton" : "updateButton"}`) }}
177
+ </BIMDataButton>
178
+ </div>
179
+
180
+ <div v-if="loading" class="bcf-topic-form__loader">
181
+ <BIMDataLoading />
182
+ </div>
183
+
184
+ <BIMDataSafeZoneModal v-if="isOpenModal">
185
+ <template #text>
186
+ {{ $t("BcfComponents.BcfTopicForm.modalText", { name: topic.title }) }}
187
+ </template>
188
+ <template #actions>
189
+ <BIMDataButton class="m-r-12" color="high" fill radius @click="$emit('close')">
190
+ {{ $t("BcfComponents.BcfTopicForm.cancelButton") }}
191
+ </BIMDataButton>
192
+ <BIMDataButton color="primary" outline radius @click="isOpenModal = false">
193
+ {{ $t("BcfComponents.BcfTopicForm.continueButton") }}
194
+ </BIMDataButton>
195
+ </template>
196
+ </BIMDataSafeZoneModal>
197
+ </div>
198
+ </template>
199
+
200
+ <script>
201
+ import { computed, ref, watch } from "vue";
202
+ import { useService } from "../../service.js";
203
+ import { getViewerList } from "../../utils/viewer.js";
204
+ import { setViewpointDefaults } from "../../utils/viewpoints.js";
205
+ // Components
206
+ import BIMDataSafeZoneModal from "@bimdata/components/src/BIMDataSafeZoneModal/BIMDataSafeZoneModal.vue";
207
+ import BIMDataButton from "@bimdata/design-system/src/BIMDataComponents/BIMDataButton/BIMDataButton.vue";
208
+ import BIMDataDatePicker from "@bimdata/design-system/src/BIMDataComponents/BIMDataDatePicker/BIMDataDatePicker.vue";
209
+ import {
210
+ BIMDataIconArrow,
211
+ BIMDataIconClose,
212
+ BIMDataIconPlus,
213
+ } from "@bimdata/design-system/src/BIMDataComponents/BIMDataIcon/BIMDataIconStandalone/index.js";
214
+ import BIMDataInput from "@bimdata/design-system/src/BIMDataComponents/BIMDataInput/BIMDataInput.vue";
215
+ import BIMDataLoading from "@bimdata/design-system/src/BIMDataComponents/BIMDataLoading/BIMDataLoading.vue";
216
+ import BIMDataSelect from "@bimdata/design-system/src/BIMDataComponents/BIMDataSelect/BIMDataSelect.vue";
217
+ import BIMDataTextarea from "@bimdata/design-system/src/BIMDataComponents/BIMDataTextarea/BIMDataTextarea.vue";
218
+ import BIMDataTextbox from "@bimdata/design-system/src/BIMDataComponents/BIMDataTextbox/BIMDataTextbox.vue";
219
+ import BIMDataTooltip from "@bimdata/design-system/src/BIMDataComponents/BIMDataTooltip/BIMDataTooltip.vue";
220
+ import BcfTopicImages from "./bcf-topic-images/BcfTopicImages.vue";
221
+ import BcfTopicSnapshots from "./bcf-topic-snapshots/BcfTopicSnapshots.vue";
222
+ import BcfTopicSnapshotsActions from "./bcf-topic-snapshots-actions/BcfTopicSnapshotsActions.vue";
223
+
224
+ export default {
225
+ components: {
226
+ BcfTopicImages,
227
+ BcfTopicSnapshots,
228
+ BcfTopicSnapshotsActions,
229
+ BIMDataButton,
230
+ BIMDataDatePicker,
231
+ BIMDataIconArrow,
232
+ BIMDataIconClose,
233
+ BIMDataIconPlus,
234
+ BIMDataInput,
235
+ BIMDataLoading,
236
+ BIMDataSafeZoneModal,
237
+ BIMDataSelect,
238
+ BIMDataTextarea,
239
+ BIMDataTextbox,
240
+ BIMDataTooltip,
241
+ },
242
+ props: {
243
+ uiConfig: {
244
+ type: Object,
245
+ default: () => ({
246
+ viewerMode: false, // set this to true when used in BIMData Viewer
247
+ backButton: false,
248
+ closeButton: false,
249
+ }),
250
+ },
251
+ objectsEditEnabled: {
252
+ /**
253
+ * Whether topic objects edition is enabled or not.
254
+ */
255
+ type: Boolean,
256
+ default: false,
257
+ },
258
+ annotationsEditEnabled: {
259
+ /**
260
+ * Whether annotations edition is enabled or not.
261
+ */
262
+ type: Boolean,
263
+ default: false,
264
+ },
265
+ project: {
266
+ type: Object,
267
+ required: true,
268
+ },
269
+ extensions: {
270
+ type: Object,
271
+ reuiqred: true,
272
+ },
273
+ topics: {
274
+ type: Array,
275
+ required: true,
276
+ },
277
+ topic: {
278
+ type: Object,
279
+ },
280
+ topicObjects: {
281
+ /**
282
+ * Objects selection that will be set on each topic viewpoints
283
+ * (override `components` field).
284
+ */
285
+ type: Object,
286
+ },
287
+ topicAnnotations: {
288
+ /**
289
+ * Annotations that will be set on each topic viewpoints
290
+ * (override `pins` field).
291
+ */
292
+ type: Array,
293
+ },
294
+ getViewers: {
295
+ type: Function,
296
+ default: () => () => ({})
297
+ },
298
+ },
299
+ emits: [
300
+ "back",
301
+ "close",
302
+ "edit-topic-annotations",
303
+ "edit-topic-objects",
304
+ "topic-created",
305
+ "topic-create-error",
306
+ "topic-updated",
307
+ "topic-update-error",
308
+ ],
309
+ setup(props, { emit }) {
310
+ const service = useService();
311
+
312
+ const isCreation = computed(() => !props.topic);
313
+ const nextIndex = computed(() => Math.max(0, ...props.topics.map((t) => t.index)) + 1);
314
+
315
+ const topicTitle = ref("");
316
+ const topicType = ref(null);
317
+ const topicPriority = ref(null);
318
+ const topicStatus = ref(null);
319
+ const topicStage = ref(null);
320
+ const topicAssignedTo = ref(null);
321
+ const topicDueDate = ref(null);
322
+ const topicDescription = ref("");
323
+ const topicLabels = ref([]);
324
+ const viewpoints = ref([]);
325
+
326
+ const viewpointsToCreate = ref([]);
327
+ const viewpointsToUpdate = ref([]);
328
+ const viewpointsToDelete = ref([]);
329
+ const viewpointsToDisplay = computed(() =>
330
+ viewpoints.value
331
+ .concat(viewpointsToCreate.value)
332
+ .filter((v) => !viewpointsToDelete.value.some((x) => x.guid === v.guid))
333
+ .filter((v) => v.snapshot)
334
+ );
335
+
336
+ const hasErrorTitle = ref(false);
337
+ const isOpenModal = ref(false);
338
+ const loading = ref(false);
339
+
340
+ const reset = () => {
341
+ topicTitle.value = "";
342
+ topicType.value = null;
343
+ topicPriority.value = null;
344
+ topicStatus.value = null;
345
+ topicStage.value = null;
346
+ topicAssignedTo.value = null;
347
+ topicDueDate.value = null;
348
+ topicDescription.value = "";
349
+ topicLabels.value = [];
350
+ viewpoints.value = [];
351
+ viewpointsToCreate.value = [];
352
+ viewpointsToUpdate.value = [];
353
+ viewpointsToDelete.value = [];
354
+ hasErrorTitle.value = false;
355
+ isOpenModal.value = false;
356
+ loading.value = false;
357
+ };
358
+
359
+ watch(
360
+ () => props.topic,
361
+ (topic) => {
362
+ if (topic) {
363
+ topicTitle.value = topic.title || "";
364
+ topicType.value = topic.topic_type || null;
365
+ topicPriority.value = topic.priority || null;
366
+ topicStatus.value = topic.topic_status || null;
367
+ topicStage.value = topic.stage || null;
368
+ topicAssignedTo.value = topic.assigned_to || null;
369
+ topicDueDate.value = topic.due_date;
370
+ topicDescription.value = topic.description || "";
371
+ topicLabels.value = topic.labels || [];
372
+ viewpoints.value = topic.viewpoints || [];
373
+ } else {
374
+ reset();
375
+ }
376
+ },
377
+ { immediate: true }
378
+ );
379
+
380
+ const createViewpoints = () => {
381
+ Promise.all(
382
+ getViewerList(props.getViewers()).map(viewer =>
383
+ viewer.getViewpoint().then(viewpoint => viewpointsToCreate.value.push(viewpoint))
384
+ )
385
+ );
386
+ };
387
+
388
+ const uploadViewpoints = (event) => {
389
+ [...event.target.files].forEach((file) => {
390
+ let type;
391
+ if (file.type === "image/png") {
392
+ type = "png";
393
+ } else if (file.type === "image/jpeg") {
394
+ type = "jpg"; // `jpeg` is not a valid value, only `jpg` is
395
+ } else {
396
+ type = file.type;
397
+ }
398
+
399
+ const reader = new FileReader();
400
+ reader.addEventListener("load", () => {
401
+ const viewpoint = {
402
+ isUpload: true,
403
+ snapshot: {
404
+ snapshot_type: type,
405
+ snapshot_data: reader.result,
406
+ },
407
+ };
408
+ viewpointsToCreate.value.push(viewpoint);
409
+ });
410
+ reader.readAsDataURL(file);
411
+ });
412
+ };
413
+
414
+ const deleteViewpoint = (viewpoint) => {
415
+ if (viewpoint.guid) {
416
+ viewpointsToDelete.value.push(viewpoint);
417
+ } else {
418
+ let index = viewpointsToCreate.value.indexOf(viewpoint);
419
+ viewpointsToCreate.value.splice(index, 1);
420
+ }
421
+ };
422
+
423
+ const submit = async () => {
424
+ if (!topicTitle.value) {
425
+ hasErrorTitle.value = true;
426
+ return;
427
+ }
428
+ try {
429
+ loading.value = true;
430
+
431
+ // Avoid updating snapshots as it is not possible
432
+ // (you can only create/delete snapshots).
433
+ viewpointsToUpdate.value = viewpoints.value.map((viewpoint) => ({
434
+ ...viewpoint,
435
+ snapshot: undefined,
436
+ }));
437
+
438
+ if (viewpointsToUpdate.value.length === 0) {
439
+ // If topic has no viewpoints yet make sure 3D viewpoints
440
+ // comes first in the list of viewpoints to create.
441
+ viewpointsToCreate.value.sort(
442
+ (v1, v2) => (v1.order ?? Infinity) - (v2.order ?? Infinity)
443
+ );
444
+ }
445
+
446
+ const allViewpoints = viewpointsToUpdate.value.concat(viewpointsToCreate.value);
447
+
448
+ if (props.topicObjects) {
449
+ if (allViewpoints.length > 0) {
450
+ // Set provided topic objects on all viewpoints.
451
+ allViewpoints.forEach((viewpoint) => {
452
+ Object.assign(viewpoint, { components: props.topicObjects });
453
+ setViewpointDefaults(viewpoint);
454
+ });
455
+ } else {
456
+ // If topic objects are provided and no viewpoints are set
457
+ // then create an 'empty' viewpoint to hold topic objects.
458
+ viewpointsToCreate.value.push({ components: props.topicObjects });
459
+ }
460
+ }
461
+
462
+ if (props.topicAnnotations) {
463
+ // Set provided topic annotations on all viewpoints.
464
+ allViewpoints.forEach((viewpoint) => (viewpoint.pins = props.topicAnnotations));
465
+ }
466
+
467
+ const data = {
468
+ guid: props.topic?.guid,
469
+ title: topicTitle.value,
470
+ topic_type: topicType.value,
471
+ priority: topicPriority.value,
472
+ topic_status: topicStatus.value,
473
+ stage: topicStage.value,
474
+ assigned_to: topicAssignedTo.value,
475
+ due_date: topicDueDate.value,
476
+ description: topicDescription.value,
477
+ labels: topicLabels.value,
478
+ };
479
+
480
+ let newTopic;
481
+ if (isCreation.value) {
482
+ data.models = getViewerList(props.getViewers())
483
+ .flatMap(v => v.getLoadedModels().map(m => m.id));
484
+ newTopic = await service.createTopic(props.project, data);
485
+ } else {
486
+ newTopic = await service.updateTopic(props.project, data);
487
+ }
488
+
489
+ await Promise.all([
490
+ ...viewpointsToCreate.value.map((viewpoint) =>
491
+ service.createViewpoint(props.project, newTopic, viewpoint)
492
+ ),
493
+ ...viewpointsToUpdate.value.map((viewpoint) =>
494
+ service.updateViewpoint(props.project, newTopic, viewpoint)
495
+ ),
496
+ ...viewpointsToDelete.value.map((viewpoint) =>
497
+ service.deleteViewpoint(props.project, newTopic, viewpoint)
498
+ ),
499
+ ]);
500
+
501
+ if (isCreation.value) {
502
+ emit("topic-created", newTopic);
503
+ reset();
504
+ } else {
505
+ emit("topic-updated", newTopic);
506
+ }
507
+ } catch (error) {
508
+ emit(isCreation.value ? "topic-create-error" : "topic-update-error", error);
509
+ } finally {
510
+ loading.value = false;
511
+ }
512
+ };
513
+
514
+ return {
515
+ // References
516
+ hasErrorTitle,
517
+ isCreation,
518
+ isOpenModal,
519
+ loading,
520
+ nextIndex,
521
+ topicAssignedTo,
522
+ topicDescription,
523
+ topicDueDate,
524
+ topicPriority,
525
+ topicStage,
526
+ topicStatus,
527
+ topicLabels,
528
+ topicTitle,
529
+ topicType,
530
+ viewpointsToDisplay,
531
+ // Methods
532
+ createViewpoints,
533
+ uploadViewpoints,
534
+ deleteViewpoint,
535
+ submit,
536
+ };
537
+ },
538
+ };
539
+ </script>
540
+
541
+ <style scoped lang="scss" src="./BcfTopicForm.scss"></style>
@@ -0,0 +1,88 @@
1
+ .bcf-topic-images {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--spacing-unit);
5
+
6
+ &__images {
7
+ width: 100%;
8
+ height: 262px;
9
+ display: grid;
10
+ grid-template-columns: repeat(3, 1fr);
11
+ gap: 10px;
12
+
13
+ .image-preview {
14
+ position: relative;
15
+
16
+ img {
17
+ width: 100%;
18
+ height: 100%;
19
+ object-fit: cover;
20
+ }
21
+
22
+ .btn-delete {
23
+ position: absolute;
24
+ top: calc(50% - (32px / 2));
25
+ left: calc(50% - (32px / 2));
26
+ }
27
+
28
+ &:first-child {
29
+ &.single {
30
+ height: 262px;
31
+ grid-area: 1 / 1 / 2 / 4;
32
+ }
33
+ &:not(.single) {
34
+ height: 180px;
35
+ grid-area: 1 / 1 / 2 / 4;
36
+ }
37
+ }
38
+ &:nth-child(2) {
39
+ height: 72px;
40
+ grid-area: 2 / 1 / 3 / 2;
41
+ }
42
+ &:nth-child(3) {
43
+ height: 72px;
44
+ grid-area: 2 / 2 / 3 / 3;
45
+ }
46
+ &:nth-child(4) {
47
+ height: 72px;
48
+ grid-area: 2 / 3 / 3 / 4;
49
+ }
50
+ }
51
+ }
52
+
53
+ .btn-upload {
54
+ label {
55
+ display: flex;
56
+ justify-content: center;
57
+ align-items: center;
58
+ }
59
+
60
+ &:not(:disabled) label {
61
+ width: 100%;
62
+ height: 100%;
63
+ cursor: pointer;
64
+ }
65
+ }
66
+
67
+ &__upload {
68
+ width: 100%;
69
+ height: 180px;
70
+ min-height: 180px;
71
+ display: flex;
72
+ flex-direction: column;
73
+ justify-content: center;
74
+ align-items: center;
75
+ gap: calc(var(--spacing-unit) * 3 / 2);
76
+ border: 2px dashed var(--color-silver);
77
+
78
+ .icon {
79
+ width: 52px;
80
+ height: 52px;
81
+ display: flex;
82
+ justify-content: center;
83
+ align-items: center;
84
+ background-color: var(--color-silver-light);
85
+ border-radius: 50%;
86
+ }
87
+ }
88
+ }