@bimdata/bcf-components 6.0.0-rc.9 → 6.0.1-rc.1

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