@geode/opengeodeweb-front 10.34.0-rc.20 → 10.34.0-rc.22

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.
@@ -1,5 +1,7 @@
1
1
  import _ from "lodash";
2
2
  //#region shared/utils/file.ts
3
+ const BYTES_PER_KIBIBYTE = 1024;
4
+ const CHUNK_SIZE_BYTES = 8192 * BYTES_PER_KIBIBYTE;
3
5
  function assertFile(file) {
4
6
  console.log("[ASSERT_FILE] Asserting file", { file });
5
7
  if (!(file instanceof File)) throw new Error("file must be an instance of File");
@@ -9,4 +11,4 @@ function hasBody(params) {
9
11
  return !_.isEmpty(params);
10
12
  }
11
13
  //#endregion
12
- export { assertFile, hasBody };
14
+ export { BYTES_PER_KIBIBYTE, CHUNK_SIZE_BYTES, assertFile, hasBody };
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import type { CollectionComponent } from "@ogw_front/stores/data_helpers/collections";
2
3
  import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
3
4
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
4
5
  import ViewerOptionsColoringTypeSelector from "@ogw_front/components/Viewer/Options/ColoringTypeSelector.vue";
@@ -11,16 +12,29 @@ interface Props {
11
12
  modelId: string;
12
13
  blockId?: string;
13
14
  targetBlockIds: string[];
15
+ collections?: CollectionComponent[];
14
16
  }
15
17
 
16
- const { modelId, blockId = undefined, targetBlockIds } = defineProps<Props>();
18
+ const {
19
+ modelId,
20
+ blockId = undefined,
21
+ targetBlockIds,
22
+ collections = undefined,
23
+ } = defineProps<Props>();
17
24
 
18
25
  const dataStyleStore = useDataStyleStore();
19
26
  const hybridViewerStore = useHybridViewerStore();
20
27
 
28
+ const referenceBlockId = computed<string | undefined>(() =>
29
+ collections ? targetBlockIds[0] : undefined,
30
+ );
31
+
21
32
  // Visibility
22
33
  const blocksVisibility = computed<boolean>({
23
- get: () => dataStyleStore.modelComponentTypeVisibility(modelId, "Block"),
34
+ get: () =>
35
+ collections
36
+ ? targetBlockIds.every((id) => dataStyleStore.modelBlockVisibility(modelId, id))
37
+ : dataStyleStore.modelComponentTypeVisibility(modelId, "Block"),
24
38
  set: async (newValue) => {
25
39
  await dataStyleStore.setModelBlocksVisibility(modelId, targetBlockIds, newValue);
26
40
  hybridViewerStore.remoteRender();
@@ -40,7 +54,8 @@ const blockVisibility = computed<boolean | undefined>({
40
54
 
41
55
  // Color
42
56
  const blocksColor = computed<RGBAColor | undefined>({
43
- get: () => dataStyleStore.modelComponentTypeColor(modelId, "Block") as RGBAColor | undefined,
57
+ get: () =>
58
+ dataStyleStore.modelBlockColor(modelId, referenceBlockId.value) as RGBAColor | undefined,
44
59
  set: async (color) => {
45
60
  await dataStyleStore.setModelBlocksColor(modelId, targetBlockIds, color);
46
61
  hybridViewerStore.remoteRender();
@@ -60,12 +75,25 @@ const blockColor = computed<RGBAColor | undefined>({
60
75
 
61
76
  const blocksActiveColoring = computed<string | undefined>({
62
77
  get: () =>
63
- dataStyleStore.getModelComponentTypeActiveColoring(modelId, "Block") as string | undefined,
78
+ dataStyleStore.modelBlockActiveColoring(modelId, referenceBlockId.value) as string | undefined,
64
79
  set: async (coloringType) => {
65
80
  if (typeof coloringType !== "string") {
66
81
  return;
67
82
  }
68
- await dataStyleStore.setModelBlocksActiveColoring(modelId, targetBlockIds, coloringType);
83
+ if (collections && coloringType === "random") {
84
+ await Promise.all(
85
+ collections.map((collection) =>
86
+ dataStyleStore.setModelBlocksActiveColoring(
87
+ modelId,
88
+ collection.children.map((child) => child.id),
89
+ coloringType,
90
+ collection.id,
91
+ ),
92
+ ),
93
+ );
94
+ } else {
95
+ await dataStyleStore.setModelBlocksActiveColoring(modelId, targetBlockIds, coloringType);
96
+ }
69
97
  hybridViewerStore.remoteRender();
70
98
  },
71
99
  });
@@ -83,7 +111,7 @@ const blockActiveColoring = computed<string | undefined>({
83
111
 
84
112
  // Group Attributes
85
113
  const blocksVertexAttributeName = computed<string | undefined>({
86
- get: () => dataStyleStore.modelBlocksVertexAttributeName(modelId),
114
+ get: () => dataStyleStore.modelBlocksVertexAttributeName(modelId, referenceBlockId.value),
87
115
  set: async (newValue) => {
88
116
  if (newValue === undefined) {
89
117
  return;
@@ -94,7 +122,7 @@ const blocksVertexAttributeName = computed<string | undefined>({
94
122
  });
95
123
 
96
124
  const blocksVertexAttributeItem = computed<string | undefined>({
97
- get: () => dataStyleStore.modelBlocksVertexAttributeItem(modelId),
125
+ get: () => dataStyleStore.modelBlocksVertexAttributeItem(modelId, referenceBlockId.value),
98
126
  set: async (newValue) => {
99
127
  await dataStyleStore.setModelBlocksVertexAttributeItem(modelId, targetBlockIds, newValue);
100
128
  hybridViewerStore.remoteRender();
@@ -102,7 +130,7 @@ const blocksVertexAttributeItem = computed<string | undefined>({
102
130
  });
103
131
 
104
132
  const blocksVertexAttributeRange = computed<[number, number] | undefined>({
105
- get: () => dataStyleStore.modelBlocksVertexAttributeRange(modelId),
133
+ get: () => dataStyleStore.modelBlocksVertexAttributeRange(modelId, referenceBlockId.value),
106
134
  set: async (newValue) => {
107
135
  const [minimum, maximum] = newValue;
108
136
  if (minimum === undefined || maximum === undefined) {
@@ -119,7 +147,7 @@ const blocksVertexAttributeRange = computed<[number, number] | undefined>({
119
147
  });
120
148
 
121
149
  const blocksVertexAttributeColorMap = computed<Map<string, RGBAColor> | undefined>({
122
- get: () => dataStyleStore.modelBlocksVertexAttributeColorMap(modelId),
150
+ get: () => dataStyleStore.modelBlocksVertexAttributeColorMap(modelId, referenceBlockId.value),
123
151
  set: async (newValue) => {
124
152
  await dataStyleStore.setModelBlocksVertexAttributeColorMap(modelId, targetBlockIds, newValue);
125
153
  hybridViewerStore.remoteRender();
@@ -127,7 +155,10 @@ const blocksVertexAttributeColorMap = computed<Map<string, RGBAColor> | undefine
127
155
  });
128
156
 
129
157
  const blocksVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
130
- get: () => dataStyleStore.modelBlocksVertexAttributeNoDataColor(modelId) as RGBAColor | undefined,
158
+ get: () =>
159
+ dataStyleStore.modelBlocksVertexAttributeNoDataColor(modelId, referenceBlockId.value) as
160
+ | RGBAColor
161
+ | undefined,
131
162
  set: async (newValue) => {
132
163
  await dataStyleStore.setModelBlocksVertexAttributeNoDataColor(
133
164
  modelId,
@@ -139,7 +170,7 @@ const blocksVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
139
170
  });
140
171
 
141
172
  const blocksPolyhedronAttributeName = computed<string | undefined>({
142
- get: () => dataStyleStore.modelBlocksPolyhedronAttributeName(modelId),
173
+ get: () => dataStyleStore.modelBlocksPolyhedronAttributeName(modelId, referenceBlockId.value),
143
174
  set: async (newValue) => {
144
175
  if (newValue === undefined) {
145
176
  return;
@@ -150,7 +181,7 @@ const blocksPolyhedronAttributeName = computed<string | undefined>({
150
181
  });
151
182
 
152
183
  const blocksPolyhedronAttributeItem = computed<string | undefined>({
153
- get: () => dataStyleStore.modelBlocksPolyhedronAttributeItem(modelId),
184
+ get: () => dataStyleStore.modelBlocksPolyhedronAttributeItem(modelId, referenceBlockId.value),
154
185
  set: async (newValue) => {
155
186
  await dataStyleStore.setModelBlocksPolyhedronAttributeItem(modelId, targetBlockIds, newValue);
156
187
  hybridViewerStore.remoteRender();
@@ -158,7 +189,7 @@ const blocksPolyhedronAttributeItem = computed<string | undefined>({
158
189
  });
159
190
 
160
191
  const blocksPolyhedronAttributeRange = computed<[number, number] | undefined>({
161
- get: () => dataStyleStore.modelBlocksPolyhedronAttributeRange(modelId),
192
+ get: () => dataStyleStore.modelBlocksPolyhedronAttributeRange(modelId, referenceBlockId.value),
162
193
  set: async (newValue) => {
163
194
  const [minimum, maximum] = newValue;
164
195
  if (minimum === undefined || maximum === undefined) {
@@ -175,7 +206,7 @@ const blocksPolyhedronAttributeRange = computed<[number, number] | undefined>({
175
206
  });
176
207
 
177
208
  const blocksPolyhedronAttributeColorMap = computed<Map<string, RGBAColor> | undefined>({
178
- get: () => dataStyleStore.modelBlocksPolyhedronAttributeColorMap(modelId),
209
+ get: () => dataStyleStore.modelBlocksPolyhedronAttributeColorMap(modelId, referenceBlockId.value),
179
210
  set: async (newValue) => {
180
211
  await dataStyleStore.setModelBlocksPolyhedronAttributeColorMap(
181
212
  modelId,
@@ -188,7 +219,9 @@ const blocksPolyhedronAttributeColorMap = computed<Map<string, RGBAColor> | unde
188
219
 
189
220
  const blocksPolyhedronAttributeNoDataColor = computed<RGBAColor | undefined>({
190
221
  get: () =>
191
- dataStyleStore.modelBlocksPolyhedronAttributeNoDataColor(modelId) as RGBAColor | undefined,
222
+ dataStyleStore.modelBlocksPolyhedronAttributeNoDataColor(modelId, referenceBlockId.value) as
223
+ | RGBAColor
224
+ | undefined,
192
225
  set: async (newValue) => {
193
226
  await dataStyleStore.setModelBlocksPolyhedronAttributeNoDataColor(
194
227
  modelId,
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import type { CollectionComponent } from "@ogw_front/stores/data_helpers/collections";
2
3
  import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
3
4
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
4
5
  import ViewerOptionsColoringTypeSelector from "@ogw_front/components/Viewer/Options/ColoringTypeSelector.vue";
@@ -11,16 +12,29 @@ interface Props {
11
12
  modelId: string;
12
13
  cornerId?: string;
13
14
  targetCornerIds: string[];
15
+ collections?: CollectionComponent[];
14
16
  }
15
17
 
16
- const { modelId, cornerId = undefined, targetCornerIds } = defineProps<Props>();
18
+ const {
19
+ modelId,
20
+ cornerId = undefined,
21
+ targetCornerIds,
22
+ collections = undefined,
23
+ } = defineProps<Props>();
17
24
 
18
25
  const dataStyleStore = useDataStyleStore();
19
26
  const hybridViewerStore = useHybridViewerStore();
20
27
 
28
+ const referenceCornerId = computed<string | undefined>(() =>
29
+ collections ? targetCornerIds[0] : undefined,
30
+ );
31
+
21
32
  // Visibility
22
33
  const cornersVisibility = computed<boolean>({
23
- get: () => dataStyleStore.modelComponentTypeVisibility(modelId, "Corner"),
34
+ get: () =>
35
+ collections
36
+ ? targetCornerIds.every((id) => dataStyleStore.modelCornerVisibility(modelId, id))
37
+ : dataStyleStore.modelComponentTypeVisibility(modelId, "Corner"),
24
38
  set: async (newValue) => {
25
39
  await dataStyleStore.setModelCornersVisibility(modelId, targetCornerIds, newValue);
26
40
  hybridViewerStore.remoteRender();
@@ -40,7 +54,8 @@ const cornerVisibility = computed<boolean | undefined>({
40
54
 
41
55
  // Color
42
56
  const cornersColor = computed<RGBAColor | undefined>({
43
- get: () => dataStyleStore.modelComponentTypeColor(modelId, "Corner") as RGBAColor | undefined,
57
+ get: () =>
58
+ dataStyleStore.modelCornerColor(modelId, referenceCornerId.value) as RGBAColor | undefined,
44
59
  set: async (color) => {
45
60
  await dataStyleStore.setModelCornersColor(modelId, targetCornerIds, color);
46
61
  hybridViewerStore.remoteRender();
@@ -60,12 +75,27 @@ const cornerColor = computed<RGBAColor | undefined>({
60
75
 
61
76
  const cornersActiveColoring = computed<string | undefined>({
62
77
  get: () =>
63
- dataStyleStore.getModelComponentTypeActiveColoring(modelId, "Corner") as string | undefined,
78
+ dataStyleStore.modelCornerActiveColoring(modelId, referenceCornerId.value) as
79
+ | string
80
+ | undefined,
64
81
  set: async (coloringType) => {
65
82
  if (typeof coloringType !== "string") {
66
83
  return;
67
84
  }
68
- await dataStyleStore.setModelCornersActiveColoring(modelId, targetCornerIds, coloringType);
85
+ if (collections && coloringType === "random") {
86
+ await Promise.all(
87
+ collections.map((collection) =>
88
+ dataStyleStore.setModelCornersActiveColoring(
89
+ modelId,
90
+ collection.children.map((child) => child.id),
91
+ coloringType,
92
+ collection.id,
93
+ ),
94
+ ),
95
+ );
96
+ } else {
97
+ await dataStyleStore.setModelCornersActiveColoring(modelId, targetCornerIds, coloringType);
98
+ }
69
99
  hybridViewerStore.remoteRender();
70
100
  },
71
101
  });
@@ -83,7 +113,7 @@ const cornerActiveColoring = computed<string | undefined>({
83
113
 
84
114
  // Group Attributes
85
115
  const cornersVertexAttributeName = computed<string | undefined>({
86
- get: () => dataStyleStore.modelCornersVertexAttributeName(modelId),
116
+ get: () => dataStyleStore.modelCornersVertexAttributeName(modelId, referenceCornerId.value),
87
117
  set: async (newValue) => {
88
118
  if (newValue === undefined) {
89
119
  return;
@@ -94,7 +124,7 @@ const cornersVertexAttributeName = computed<string | undefined>({
94
124
  });
95
125
 
96
126
  const cornersVertexAttributeItem = computed<string | undefined>({
97
- get: () => dataStyleStore.modelCornersVertexAttributeItem(modelId),
127
+ get: () => dataStyleStore.modelCornersVertexAttributeItem(modelId, referenceCornerId.value),
98
128
  set: async (newValue) => {
99
129
  await dataStyleStore.setModelCornersVertexAttributeItem(modelId, targetCornerIds, newValue);
100
130
  hybridViewerStore.remoteRender();
@@ -102,7 +132,7 @@ const cornersVertexAttributeItem = computed<string | undefined>({
102
132
  });
103
133
 
104
134
  const cornersVertexAttributeRange = computed<[number, number] | undefined>({
105
- get: () => dataStyleStore.modelCornersVertexAttributeRange(modelId),
135
+ get: () => dataStyleStore.modelCornersVertexAttributeRange(modelId, referenceCornerId.value),
106
136
  set: async (newValue) => {
107
137
  const [minimum, maximum] = newValue;
108
138
  if (minimum === undefined || maximum === undefined) {
@@ -119,7 +149,7 @@ const cornersVertexAttributeRange = computed<[number, number] | undefined>({
119
149
  });
120
150
 
121
151
  const cornersVertexAttributeColorMap = computed<ColorMap | undefined>({
122
- get: () => dataStyleStore.modelCornersVertexAttributeColorMap(modelId),
152
+ get: () => dataStyleStore.modelCornersVertexAttributeColorMap(modelId, referenceCornerId.value),
123
153
  set: async (newValue) => {
124
154
  await dataStyleStore.setModelCornersVertexAttributeColorMap(modelId, targetCornerIds, newValue);
125
155
  hybridViewerStore.remoteRender();
@@ -128,7 +158,9 @@ const cornersVertexAttributeColorMap = computed<ColorMap | undefined>({
128
158
 
129
159
  const cornersVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
130
160
  get: () =>
131
- dataStyleStore.modelCornersVertexAttributeNoDataColor(modelId) as RGBAColor | undefined,
161
+ dataStyleStore.modelCornersVertexAttributeNoDataColor(modelId, referenceCornerId.value) as
162
+ | RGBAColor
163
+ | undefined,
132
164
  set: async (newValue) => {
133
165
  await dataStyleStore.setModelCornersVertexAttributeNoDataColor(
134
166
  modelId,
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import type { CollectionComponent } from "@ogw_front/stores/data_helpers/collections";
2
3
  import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
3
4
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
4
5
  import ViewerOptionsColoringTypeSelector from "@ogw_front/components/Viewer/Options/ColoringTypeSelector.vue";
@@ -11,16 +12,29 @@ interface Props {
11
12
  modelId: string;
12
13
  lineId?: string;
13
14
  targetLineIds: string[];
15
+ collections?: CollectionComponent[];
14
16
  }
15
17
 
16
- const { modelId, lineId = undefined, targetLineIds } = defineProps<Props>();
18
+ const {
19
+ modelId,
20
+ lineId = undefined,
21
+ targetLineIds,
22
+ collections = undefined,
23
+ } = defineProps<Props>();
17
24
 
18
25
  const dataStyleStore = useDataStyleStore();
19
26
  const hybridViewerStore = useHybridViewerStore();
20
27
 
28
+ const referenceLineId = computed<string | undefined>(() =>
29
+ collections ? targetLineIds[0] : undefined,
30
+ );
31
+
21
32
  // Visibility
22
33
  const linesVisibility = computed<boolean>({
23
- get: () => dataStyleStore.modelComponentTypeVisibility(modelId, "Line"),
34
+ get: () =>
35
+ collections
36
+ ? targetLineIds.every((id) => dataStyleStore.modelLineVisibility(modelId, id))
37
+ : dataStyleStore.modelComponentTypeVisibility(modelId, "Line"),
24
38
  set: async (newValue) => {
25
39
  await dataStyleStore.setModelLinesVisibility(modelId, targetLineIds, newValue);
26
40
  hybridViewerStore.remoteRender();
@@ -40,7 +54,7 @@ const lineVisibility = computed<boolean | undefined>({
40
54
 
41
55
  // Color
42
56
  const linesColor = computed<RGBAColor | undefined>({
43
- get: () => dataStyleStore.modelComponentTypeColor(modelId, "Line") as RGBAColor | undefined,
57
+ get: () => dataStyleStore.modelLineColor(modelId, referenceLineId.value) as RGBAColor | undefined,
44
58
  set: async (color) => {
45
59
  await dataStyleStore.setModelLinesColor(modelId, targetLineIds, color);
46
60
  hybridViewerStore.remoteRender();
@@ -60,12 +74,25 @@ const lineColor = computed<RGBAColor | undefined>({
60
74
 
61
75
  const linesActiveColoring = computed<string | undefined>({
62
76
  get: () =>
63
- dataStyleStore.getModelComponentTypeActiveColoring(modelId, "Line") as string | undefined,
77
+ dataStyleStore.modelLineActiveColoring(modelId, referenceLineId.value) as string | undefined,
64
78
  set: async (coloringType) => {
65
79
  if (typeof coloringType !== "string") {
66
80
  return;
67
81
  }
68
- await dataStyleStore.setModelLinesActiveColoring(modelId, targetLineIds, coloringType);
82
+ if (collections && coloringType === "random") {
83
+ await Promise.all(
84
+ collections.map((collection) =>
85
+ dataStyleStore.setModelLinesActiveColoring(
86
+ modelId,
87
+ collection.children.map((child) => child.id),
88
+ coloringType,
89
+ collection.id,
90
+ ),
91
+ ),
92
+ );
93
+ } else {
94
+ await dataStyleStore.setModelLinesActiveColoring(modelId, targetLineIds, coloringType);
95
+ }
69
96
  hybridViewerStore.remoteRender();
70
97
  },
71
98
  });
@@ -83,7 +110,7 @@ const lineActiveColoring = computed<string | undefined>({
83
110
 
84
111
  // Group Attributes
85
112
  const linesVertexAttributeName = computed<string | undefined>({
86
- get: () => dataStyleStore.modelLinesVertexAttributeName(modelId),
113
+ get: () => dataStyleStore.modelLinesVertexAttributeName(modelId, referenceLineId.value),
87
114
  set: async (newValue) => {
88
115
  if (newValue === undefined) {
89
116
  return;
@@ -94,7 +121,7 @@ const linesVertexAttributeName = computed<string | undefined>({
94
121
  });
95
122
 
96
123
  const linesVertexAttributeItem = computed<string | undefined>({
97
- get: () => dataStyleStore.modelLinesVertexAttributeItem(modelId),
124
+ get: () => dataStyleStore.modelLinesVertexAttributeItem(modelId, referenceLineId.value),
98
125
  set: async (newValue) => {
99
126
  await dataStyleStore.setModelLinesVertexAttributeItem(modelId, targetLineIds, newValue);
100
127
  hybridViewerStore.remoteRender();
@@ -102,7 +129,7 @@ const linesVertexAttributeItem = computed<string | undefined>({
102
129
  });
103
130
 
104
131
  const linesVertexAttributeRange = computed<[number, number] | undefined>({
105
- get: () => dataStyleStore.modelLinesVertexAttributeRange(modelId),
132
+ get: () => dataStyleStore.modelLinesVertexAttributeRange(modelId, referenceLineId.value),
106
133
  set: async (newValue) => {
107
134
  const [minimum, maximum] = newValue;
108
135
  if (minimum === undefined || maximum === undefined) {
@@ -119,7 +146,7 @@ const linesVertexAttributeRange = computed<[number, number] | undefined>({
119
146
  });
120
147
 
121
148
  const linesVertexAttributeColorMap = computed<RGBAColorMap | undefined>({
122
- get: () => dataStyleStore.modelLinesVertexAttributeColorMap(modelId),
149
+ get: () => dataStyleStore.modelLinesVertexAttributeColorMap(modelId, referenceLineId.value),
123
150
  set: async (newValue) => {
124
151
  await dataStyleStore.setModelLinesVertexAttributeColorMap(modelId, targetLineIds, newValue);
125
152
  hybridViewerStore.remoteRender();
@@ -127,7 +154,10 @@ const linesVertexAttributeColorMap = computed<RGBAColorMap | undefined>({
127
154
  });
128
155
 
129
156
  const linesVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
130
- get: () => dataStyleStore.modelLinesVertexAttributeNoDataColor(modelId) as RGBAColor | undefined,
157
+ get: () =>
158
+ dataStyleStore.modelLinesVertexAttributeNoDataColor(modelId, referenceLineId.value) as
159
+ | RGBAColor
160
+ | undefined,
131
161
  set: async (newValue) => {
132
162
  await dataStyleStore.setModelLinesVertexAttributeNoDataColor(modelId, targetLineIds, newValue);
133
163
  hybridViewerStore.remoteRender();
@@ -135,7 +165,7 @@ const linesVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
135
165
  });
136
166
 
137
167
  const linesEdgeAttributeName = computed<string | undefined>({
138
- get: () => dataStyleStore.modelLinesEdgeAttributeName(modelId),
168
+ get: () => dataStyleStore.modelLinesEdgeAttributeName(modelId, referenceLineId.value),
139
169
  set: async (newValue) => {
140
170
  if (newValue === undefined) {
141
171
  return;
@@ -146,7 +176,7 @@ const linesEdgeAttributeName = computed<string | undefined>({
146
176
  });
147
177
 
148
178
  const linesEdgeAttributeItem = computed<string | undefined>({
149
- get: () => dataStyleStore.modelLinesEdgeAttributeItem(modelId),
179
+ get: () => dataStyleStore.modelLinesEdgeAttributeItem(modelId, referenceLineId.value),
150
180
  set: async (newValue) => {
151
181
  await dataStyleStore.setModelLinesEdgeAttributeItem(modelId, targetLineIds, newValue);
152
182
  hybridViewerStore.remoteRender();
@@ -154,7 +184,7 @@ const linesEdgeAttributeItem = computed<string | undefined>({
154
184
  });
155
185
 
156
186
  const linesEdgeAttributeRange = computed<[number, number] | undefined>({
157
- get: () => dataStyleStore.modelLinesEdgeAttributeRange(modelId),
187
+ get: () => dataStyleStore.modelLinesEdgeAttributeRange(modelId, referenceLineId.value),
158
188
  set: async (newValue) => {
159
189
  const [minimum, maximum] = newValue;
160
190
  if (minimum === undefined || maximum === undefined) {
@@ -166,7 +196,7 @@ const linesEdgeAttributeRange = computed<[number, number] | undefined>({
166
196
  });
167
197
 
168
198
  const linesEdgeAttributeColorMap = computed<Map<string, RGBAColor> | undefined>({
169
- get: () => dataStyleStore.modelLinesEdgeAttributeColorMap(modelId),
199
+ get: () => dataStyleStore.modelLinesEdgeAttributeColorMap(modelId, referenceLineId.value),
170
200
  set: async (newValue) => {
171
201
  await dataStyleStore.setModelLinesEdgeAttributeColorMap(modelId, targetLineIds, newValue);
172
202
  hybridViewerStore.remoteRender();
@@ -174,7 +204,10 @@ const linesEdgeAttributeColorMap = computed<Map<string, RGBAColor> | undefined>(
174
204
  });
175
205
 
176
206
  const linesEdgeAttributeNoDataColor = computed<RGBAColor | undefined>({
177
- get: () => dataStyleStore.modelLinesEdgeAttributeNoDataColor(modelId) as RGBAColor | undefined,
207
+ get: () =>
208
+ dataStyleStore.modelLinesEdgeAttributeNoDataColor(modelId, referenceLineId.value) as
209
+ | RGBAColor
210
+ | undefined,
178
211
  set: async (newValue) => {
179
212
  await dataStyleStore.setModelLinesEdgeAttributeNoDataColor(modelId, targetLineIds, newValue);
180
213
  hybridViewerStore.remoteRender();
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import BlocksOptions from "./BlocksOptions.vue";
3
+ import type { CollectionComponent } from "@ogw_front/stores/data_helpers/collections";
3
4
  import CornersOptions from "./CornersOptions.vue";
4
5
  import LinesOptions from "./LinesOptions.vue";
6
+ import { MESH_COMPONENT_TYPES } from "@ogw_front/utils/default_styles";
5
7
  import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
6
8
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
7
9
  import SurfacesOptions from "./SurfacesOptions.vue";
@@ -37,6 +39,7 @@ const selection = computed<string[]>(
37
39
  () => dataStyleStore.visibleMeshComponents(modelId.value).value || [],
38
40
  );
39
41
  const componentType = ref<string | undefined>(undefined);
42
+ const collections = ref<CollectionComponent[] | undefined>(undefined);
40
43
 
41
44
  watch(
42
45
  () => [
@@ -47,8 +50,18 @@ watch(
47
50
  ],
48
51
  async () => {
49
52
  componentType.value = undefined;
53
+ collections.value = undefined;
50
54
  if (itemProps.meta_data.viewer_type === "model_component_type") {
51
- componentType.value = itemProps.meta_data.modelComponentType;
55
+ const { modelComponentType, targetComponentIds: ids } = itemProps.meta_data;
56
+ if (MESH_COMPONENT_TYPES.includes(modelComponentType)) {
57
+ componentType.value = modelComponentType;
58
+ } else {
59
+ const collectionsByType = await dataStore.fetchAllCollectionComponents(modelId.value);
60
+ collections.value = collectionsByType[modelComponentType].filter((collection) =>
61
+ collection.children.every((child) => ids.includes(child.id)),
62
+ );
63
+ componentType.value = collections.value[0].children[0].category;
64
+ }
52
65
  } else if (componentId.value && modelId.value) {
53
66
  const currentModelId = modelId.value;
54
67
  const currentCompId = componentId.value;
@@ -155,24 +168,28 @@ const modelComponentsActiveColoring = computed<string | undefined>({
155
168
  :modelId="modelId"
156
169
  :blockId="componentId"
157
170
  :targetBlockIds="targetComponentIds"
171
+ :collections="collections"
158
172
  />
159
173
  <SurfacesOptions
160
174
  v-else-if="componentType === 'Surface'"
161
175
  :modelId="modelId"
162
176
  :surfaceId="componentId"
163
177
  :targetSurfaceIds="targetComponentIds"
178
+ :collections="collections"
164
179
  />
165
180
  <LinesOptions
166
181
  v-else-if="componentType === 'Line'"
167
182
  :modelId="modelId"
168
183
  :lineId="componentId"
169
184
  :targetLineIds="targetComponentIds"
185
+ :collections="collections"
170
186
  />
171
187
  <CornersOptions
172
188
  v-else-if="componentType === 'Corner'"
173
189
  :modelId="modelId"
174
190
  :cornerId="componentId"
175
191
  :targetCornerIds="targetComponentIds"
192
+ :collections="collections"
176
193
  />
177
194
  </v-sheet>
178
195
  </template>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import type { CollectionComponent } from "@ogw_front/stores/data_helpers/collections";
2
3
  import OptionsSection from "@ogw_front/components/Viewer/Options/OptionsSection.vue";
3
4
  import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
4
5
  import ViewerOptionsColoringTypeSelector from "@ogw_front/components/Viewer/Options/ColoringTypeSelector.vue";
@@ -11,16 +12,29 @@ interface Props {
11
12
  modelId: string;
12
13
  surfaceId?: string;
13
14
  targetSurfaceIds: string[];
15
+ collections?: CollectionComponent[];
14
16
  }
15
17
 
16
- const { modelId, surfaceId = undefined, targetSurfaceIds } = defineProps<Props>();
18
+ const {
19
+ modelId,
20
+ surfaceId = undefined,
21
+ targetSurfaceIds,
22
+ collections = undefined,
23
+ } = defineProps<Props>();
17
24
 
18
25
  const dataStyleStore = useDataStyleStore();
19
26
  const hybridViewerStore = useHybridViewerStore();
20
27
 
28
+ const referenceSurfaceId = computed<string | undefined>(() =>
29
+ collections ? targetSurfaceIds[0] : undefined,
30
+ );
31
+
21
32
  // Visibility
22
33
  const surfacesVisibility = computed<boolean>({
23
- get: () => dataStyleStore.modelComponentTypeVisibility(modelId, "Surface"),
34
+ get: () =>
35
+ collections
36
+ ? targetSurfaceIds.every((id) => dataStyleStore.modelSurfaceVisibility(modelId, id))
37
+ : dataStyleStore.modelComponentTypeVisibility(modelId, "Surface"),
24
38
  set: async (newValue) => {
25
39
  await dataStyleStore.setModelSurfacesVisibility(modelId, targetSurfaceIds, newValue);
26
40
  hybridViewerStore.remoteRender();
@@ -40,7 +54,8 @@ const surfaceVisibility = computed<boolean>({
40
54
 
41
55
  // Color
42
56
  const surfacesColor = computed<RGBAColor | undefined>({
43
- get: () => dataStyleStore.modelComponentTypeColor(modelId, "Surface") as RGBAColor | undefined,
57
+ get: () =>
58
+ dataStyleStore.modelSurfaceColor(modelId, referenceSurfaceId.value) as RGBAColor | undefined,
44
59
  set: async (color) => {
45
60
  await dataStyleStore.setModelSurfacesColor(modelId, targetSurfaceIds, color);
46
61
  hybridViewerStore.remoteRender();
@@ -60,12 +75,27 @@ const surfaceColor = computed<RGBAColor | undefined>({
60
75
 
61
76
  const surfacesActiveColoring = computed<string | undefined>({
62
77
  get: () =>
63
- dataStyleStore.getModelComponentTypeActiveColoring(modelId, "Surface") as string | undefined,
78
+ dataStyleStore.modelSurfaceActiveColoring(modelId, referenceSurfaceId.value) as
79
+ | string
80
+ | undefined,
64
81
  set: async (coloringType) => {
65
82
  if (typeof coloringType !== "string") {
66
83
  return;
67
84
  }
68
- await dataStyleStore.setModelSurfacesActiveColoring(modelId, targetSurfaceIds, coloringType);
85
+ if (collections && coloringType === "random") {
86
+ await Promise.all(
87
+ collections.map((collection) =>
88
+ dataStyleStore.setModelSurfacesActiveColoring(
89
+ modelId,
90
+ collection.children.map((child) => child.id),
91
+ coloringType,
92
+ collection.id,
93
+ ),
94
+ ),
95
+ );
96
+ } else {
97
+ await dataStyleStore.setModelSurfacesActiveColoring(modelId, targetSurfaceIds, coloringType);
98
+ }
69
99
  hybridViewerStore.remoteRender();
70
100
  },
71
101
  });
@@ -83,7 +113,7 @@ const surfaceActiveColoring = computed<string | undefined>({
83
113
 
84
114
  // Group Attributes
85
115
  const surfacesVertexAttributeName = computed<string | undefined>({
86
- get: () => dataStyleStore.modelSurfacesVertexAttributeName(modelId),
116
+ get: () => dataStyleStore.modelSurfacesVertexAttributeName(modelId, referenceSurfaceId.value),
87
117
  set: async (newValue) => {
88
118
  if (newValue === undefined) {
89
119
  return;
@@ -94,7 +124,7 @@ const surfacesVertexAttributeName = computed<string | undefined>({
94
124
  });
95
125
 
96
126
  const surfacesVertexAttributeItem = computed<string | undefined>({
97
- get: () => dataStyleStore.modelSurfacesVertexAttributeItem(modelId),
127
+ get: () => dataStyleStore.modelSurfacesVertexAttributeItem(modelId, referenceSurfaceId.value),
98
128
  set: async (newValue) => {
99
129
  await dataStyleStore.setModelSurfacesVertexAttributeItem(modelId, targetSurfaceIds, newValue);
100
130
  hybridViewerStore.remoteRender();
@@ -102,7 +132,7 @@ const surfacesVertexAttributeItem = computed<string | undefined>({
102
132
  });
103
133
 
104
134
  const surfacesVertexAttributeRange = computed<[number, number] | undefined>({
105
- get: () => dataStyleStore.modelSurfacesVertexAttributeRange(modelId),
135
+ get: () => dataStyleStore.modelSurfacesVertexAttributeRange(modelId, referenceSurfaceId.value),
106
136
  set: async (newValue) => {
107
137
  const [minimum, maximum] = newValue;
108
138
  if (minimum === undefined || maximum === undefined) {
@@ -119,7 +149,7 @@ const surfacesVertexAttributeRange = computed<[number, number] | undefined>({
119
149
  });
120
150
 
121
151
  const surfacesVertexAttributeColorMap = computed<Map<string, RGBAColor> | undefined>({
122
- get: () => dataStyleStore.modelSurfacesVertexAttributeColorMap(modelId),
152
+ get: () => dataStyleStore.modelSurfacesVertexAttributeColorMap(modelId, referenceSurfaceId.value),
123
153
  set: async (newValue) => {
124
154
  await dataStyleStore.setModelSurfacesVertexAttributeColorMap(
125
155
  modelId,
@@ -132,7 +162,9 @@ const surfacesVertexAttributeColorMap = computed<Map<string, RGBAColor> | undefi
132
162
 
133
163
  const surfacesVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
134
164
  get: () =>
135
- dataStyleStore.modelSurfacesVertexAttributeNoDataColor(modelId) as RGBAColor | undefined,
165
+ dataStyleStore.modelSurfacesVertexAttributeNoDataColor(modelId, referenceSurfaceId.value) as
166
+ | RGBAColor
167
+ | undefined,
136
168
  set: async (newValue) => {
137
169
  await dataStyleStore.setModelSurfacesVertexAttributeNoDataColor(
138
170
  modelId,
@@ -144,7 +176,7 @@ const surfacesVertexAttributeNoDataColor = computed<RGBAColor | undefined>({
144
176
  });
145
177
 
146
178
  const surfacesPolygonAttributeName = computed<string | undefined>({
147
- get: () => dataStyleStore.modelSurfacesPolygonAttributeName(modelId),
179
+ get: () => dataStyleStore.modelSurfacesPolygonAttributeName(modelId, referenceSurfaceId.value),
148
180
  set: async (newValue) => {
149
181
  if (newValue === undefined) {
150
182
  return;
@@ -155,7 +187,7 @@ const surfacesPolygonAttributeName = computed<string | undefined>({
155
187
  });
156
188
 
157
189
  const surfacesPolygonAttributeItem = computed<string | undefined>({
158
- get: () => dataStyleStore.modelSurfacesPolygonAttributeItem(modelId),
190
+ get: () => dataStyleStore.modelSurfacesPolygonAttributeItem(modelId, referenceSurfaceId.value),
159
191
  set: async (newValue) => {
160
192
  await dataStyleStore.setModelSurfacesPolygonAttributeItem(modelId, targetSurfaceIds, newValue);
161
193
  hybridViewerStore.remoteRender();
@@ -163,7 +195,7 @@ const surfacesPolygonAttributeItem = computed<string | undefined>({
163
195
  });
164
196
 
165
197
  const surfacesPolygonAttributeRange = computed<[number, number] | undefined>({
166
- get: () => dataStyleStore.modelSurfacesPolygonAttributeRange(modelId),
198
+ get: () => dataStyleStore.modelSurfacesPolygonAttributeRange(modelId, referenceSurfaceId.value),
167
199
  set: async (newValue) => {
168
200
  const [minimum, maximum] = newValue;
169
201
  if (minimum === undefined || maximum === undefined) {
@@ -180,7 +212,8 @@ const surfacesPolygonAttributeRange = computed<[number, number] | undefined>({
180
212
  });
181
213
 
182
214
  const surfacesPolygonAttributeColorMap = computed<Map<string, RGBAColor> | undefined>({
183
- get: () => dataStyleStore.modelSurfacesPolygonAttributeColorMap(modelId),
215
+ get: () =>
216
+ dataStyleStore.modelSurfacesPolygonAttributeColorMap(modelId, referenceSurfaceId.value),
184
217
  set: async (newValue) => {
185
218
  await dataStyleStore.setModelSurfacesPolygonAttributeColorMap(
186
219
  modelId,
@@ -193,7 +226,9 @@ const surfacesPolygonAttributeColorMap = computed<Map<string, RGBAColor> | undef
193
226
 
194
227
  const surfacesPolygonAttributeNoDataColor = computed<RGBAColor | undefined>({
195
228
  get: () =>
196
- dataStyleStore.modelSurfacesPolygonAttributeNoDataColor(modelId) as RGBAColor | undefined,
229
+ dataStyleStore.modelSurfacesPolygonAttributeNoDataColor(modelId, referenceSurfaceId.value) as
230
+ | RGBAColor
231
+ | undefined,
197
232
  set: async (newValue) => {
198
233
  await dataStyleStore.setModelSurfacesPolygonAttributeNoDataColor(
199
234
  modelId,
@@ -3,6 +3,7 @@ import { sortAndFormatItems, useTreeFilter } from "@ogw_front/composables/tree_f
3
3
  import CommonTreeView from "@ogw_front/components/Viewer/ObjectTree/Base/CommonTreeView.vue";
4
4
  import type { DisplayItem } from "@ogw_front/composables/virtual_tree";
5
5
  import FetchingData from "@ogw_front/components/FetchingData.vue";
6
+ import { MESH_COMPONENT_TYPES } from "@ogw_front/utils/default_styles";
6
7
  import ObjectTreeControls from "@ogw_front/components/Viewer/ObjectTree/Base/Controls.vue";
7
8
  import ObjectTreeItemLabel from "@ogw_front/components/Viewer/ObjectTree/Base/ItemLabel.vue";
8
9
  import { useHoverhighlight } from "@ogw_front/composables/hover_highlight";
@@ -129,8 +130,26 @@ const itemsForTreeView = computed<CollectionTreeItem[]>(() => {
129
130
  return result;
130
131
  });
131
132
 
133
+ function extractComponentIds(node: CollectionTreeItem): string[] {
134
+ if (node.children) {
135
+ return node.children.flatMap((child) => extractComponentIds(child));
136
+ }
137
+ return [node.id];
138
+ }
139
+
132
140
  function showContextMenu(event: unknown, item: CollectionTreeItem): void {
133
141
  const actualItem = item.raw || item;
142
+ if (isCollections.value && !MESH_COMPONENT_TYPES.includes(actualItem.category ?? "")) {
143
+ emit("show-menu", {
144
+ event,
145
+ itemId: id,
146
+ context_type: "model_component_type",
147
+ modelId: id,
148
+ modelComponentType: actualItem.category ?? actualItem.id,
149
+ targetComponentIds: extractComponentIds(actualItem),
150
+ });
151
+ return;
152
+ }
134
153
  const typeId = actualItem.category || actualItem.id;
135
154
  const typeItem = itemsForTreeView.value.find((type) => type.id === typeId);
136
155
  const targetComponentIds = typeItem?.children?.map((child) => child.id);
@@ -133,7 +133,6 @@ export const useBackStore = defineStore("back", {
133
133
  {
134
134
  schema,
135
135
  file,
136
- raw: true,
137
136
  },
138
137
  {
139
138
  ...callbacks,
@@ -19,12 +19,14 @@ export function useModelBlocksColor(): {
19
19
  blocks_ids: string[],
20
20
  color: unknown,
21
21
  activeColoring?: string,
22
+ collectionId?: string,
22
23
  ) => Promise<unknown>;
23
24
  modelBlockActiveColoring: (id: string, block_id?: string) => unknown;
24
25
  setModelBlocksActiveColoring: (
25
26
  modelId: string,
26
27
  blocks_ids: string[],
27
28
  activeColoring: string,
29
+ collectionId?: string,
28
30
  ) => Promise<void>;
29
31
  } {
30
32
  const dataStore = useDataStore();
@@ -47,6 +49,7 @@ export function useModelBlocksColor(): {
47
49
  blocks_ids: string[],
48
50
  color: unknown,
49
51
  activeColoring = "constant",
52
+ collectionId?: string,
50
53
  ): Promise<unknown> {
51
54
  const result = await modelCommonStyle.setModelTypeColor(
52
55
  modelId,
@@ -54,6 +57,7 @@ export function useModelBlocksColor(): {
54
57
  color,
55
58
  schema,
56
59
  activeColoring,
60
+ collectionId,
57
61
  );
58
62
  return result;
59
63
  }
@@ -66,6 +70,7 @@ export function useModelBlocksColor(): {
66
70
  modelId: string,
67
71
  blocks_ids: string[],
68
72
  activeColoring: string,
73
+ collectionId?: string,
69
74
  ): Promise<void> {
70
75
  const totalBlockIds = await dataStore.getBlocksGeodeIds(modelId);
71
76
  if (blocks_ids.length === totalBlockIds.length) {
@@ -78,7 +83,7 @@ export function useModelBlocksColor(): {
78
83
  });
79
84
  if (activeColoring === "constant" || activeColoring === "random") {
80
85
  const color = modelBlockColor(modelId, blocks_ids[0]);
81
- await setModelBlocksColor(modelId, blocks_ids, color, activeColoring);
86
+ await setModelBlocksColor(modelId, blocks_ids, color, activeColoring, collectionId);
82
87
  return;
83
88
  }
84
89
 
@@ -48,6 +48,7 @@ interface UseModelCommonStyleReturn {
48
48
  color: unknown,
49
49
  schema: JsonRpcSchema,
50
50
  activeColoring?: string,
51
+ collectionId?: string,
51
52
  ) => Promise<unknown>;
52
53
  setModelTypeVisibility: (
53
54
  id: string,
@@ -166,6 +167,7 @@ export function useModelCommonStyle(): UseModelCommonStyleReturn {
166
167
  color: unknown,
167
168
  schema: JsonRpcSchema,
168
169
  activeColoring = "constant",
170
+ collectionId?: string,
169
171
  ): Promise<unknown> {
170
172
  if (!component_ids?.length) {
171
173
  return undefined;
@@ -180,6 +182,7 @@ export function useModelCommonStyle(): UseModelCommonStyleReturn {
180
182
  id,
181
183
  block_ids: viewer_ids,
182
184
  color_mode: activeColoring,
185
+ collection_id: collectionId,
183
186
  };
184
187
  if (activeColoring === "constant") {
185
188
  await mutateComponentStyles(id, component_ids, {
@@ -13,6 +13,7 @@ interface ModelCornersColorApi {
13
13
  corners_ids: string[],
14
14
  color: unknown,
15
15
  activeColoring?: string,
16
+ collectionId?: string,
16
17
  ) => Promise<unknown>;
17
18
  modelCornerColoring: (id: string, corner_id?: string) => StyleValues;
18
19
  modelCornerColor: (id: string, corner_id?: string) => unknown;
@@ -21,6 +22,7 @@ interface ModelCornersColorApi {
21
22
  modelId: string,
22
23
  corners_ids: string[],
23
24
  activeColoring: string,
25
+ collectionId?: string,
24
26
  ) => Promise<unknown>;
25
27
  }
26
28
 
@@ -44,6 +46,7 @@ export function useModelCornersColor(): ModelCornersColorApi {
44
46
  corners_ids: string[],
45
47
  color: unknown,
46
48
  activeColoring = "constant",
49
+ collectionId?: string,
47
50
  ): Promise<unknown> {
48
51
  const result = await modelCommonStyle.setModelTypeColor(
49
52
  modelId,
@@ -51,6 +54,7 @@ export function useModelCornersColor(): ModelCornersColorApi {
51
54
  color,
52
55
  schema,
53
56
  activeColoring,
57
+ collectionId,
54
58
  );
55
59
  return result;
56
60
  }
@@ -63,6 +67,7 @@ export function useModelCornersColor(): ModelCornersColorApi {
63
67
  modelId: string,
64
68
  corners_ids: string[],
65
69
  activeColoring: string,
70
+ collectionId?: string,
66
71
  ): Promise<unknown> {
67
72
  const totalCornerIds = await dataStore.getCornersGeodeIds(modelId);
68
73
  if (corners_ids.length === totalCornerIds.length) {
@@ -75,7 +80,7 @@ export function useModelCornersColor(): ModelCornersColorApi {
75
80
  });
76
81
  if (activeColoring === "constant" || activeColoring === "random") {
77
82
  const color = modelCornerColor(modelId, corners_ids[0]);
78
- return setModelCornersColor(modelId, corners_ids, color, activeColoring);
83
+ return setModelCornersColor(modelId, corners_ids, color, activeColoring, collectionId);
79
84
  }
80
85
 
81
86
  if (activeColoring === "vertex") {
@@ -14,6 +14,7 @@ export function useModelLinesColor(): {
14
14
  lines_ids: string[],
15
15
  color: unknown,
16
16
  activeColoring?: string,
17
+ collectionId?: string,
17
18
  ) => Promise<unknown>;
18
19
  modelLineColoring: (id: string, line_id?: string) => StyleValues;
19
20
  modelLineColor: (id: string, line_id?: string) => unknown;
@@ -22,6 +23,7 @@ export function useModelLinesColor(): {
22
23
  modelId: string,
23
24
  lines_ids: string[],
24
25
  activeColoring: string,
26
+ collectionId?: string,
25
27
  ) => Promise<unknown>;
26
28
  } {
27
29
  const dataStore = useDataStore();
@@ -44,6 +46,7 @@ export function useModelLinesColor(): {
44
46
  lines_ids: string[],
45
47
  color: unknown,
46
48
  activeColoring = "constant",
49
+ collectionId?: string,
47
50
  ): Promise<unknown> {
48
51
  const result = await modelCommonStyle.setModelTypeColor(
49
52
  modelId,
@@ -51,6 +54,7 @@ export function useModelLinesColor(): {
51
54
  color,
52
55
  schema,
53
56
  activeColoring,
57
+ collectionId,
54
58
  );
55
59
  return result;
56
60
  }
@@ -63,6 +67,7 @@ export function useModelLinesColor(): {
63
67
  modelId: string,
64
68
  lines_ids: string[],
65
69
  activeColoring: string,
70
+ collectionId?: string,
66
71
  ): Promise<unknown> {
67
72
  const totalLineIds = await dataStore.getLinesGeodeIds(modelId);
68
73
  if (lines_ids.length === totalLineIds.length) {
@@ -75,7 +80,7 @@ export function useModelLinesColor(): {
75
80
  });
76
81
  if (activeColoring === "constant" || activeColoring === "random") {
77
82
  const color = modelLineColor(modelId, lines_ids[0]);
78
- return setModelLinesColor(modelId, lines_ids, color, activeColoring);
83
+ return setModelLinesColor(modelId, lines_ids, color, activeColoring, collectionId);
79
84
  }
80
85
 
81
86
  if (activeColoring === "vertex") {
@@ -14,6 +14,7 @@ export function useModelSurfacesColor(): {
14
14
  surfaces_ids: string[],
15
15
  color: unknown,
16
16
  activeColoring?: string,
17
+ collectionId?: string,
17
18
  ) => Promise<unknown>;
18
19
  modelSurfaceColoring: (id: string, surface_id?: string) => StyleValues;
19
20
  modelSurfaceColor: (id: string, surface_id?: string) => unknown;
@@ -22,6 +23,7 @@ export function useModelSurfacesColor(): {
22
23
  modelId: string,
23
24
  surfaces_ids: string[],
24
25
  activeColoring: string,
26
+ collectionId?: string,
25
27
  ) => Promise<unknown>;
26
28
  } {
27
29
  const dataStore = useDataStore();
@@ -44,6 +46,7 @@ export function useModelSurfacesColor(): {
44
46
  surfaces_ids: string[],
45
47
  color: unknown,
46
48
  activeColoring = "constant",
49
+ collectionId?: string,
47
50
  ): Promise<unknown> {
48
51
  const result = await modelCommonStyle.setModelTypeColor(
49
52
  modelId,
@@ -51,6 +54,7 @@ export function useModelSurfacesColor(): {
51
54
  color,
52
55
  schema,
53
56
  activeColoring,
57
+ collectionId,
54
58
  );
55
59
  return result;
56
60
  }
@@ -63,6 +67,7 @@ export function useModelSurfacesColor(): {
63
67
  modelId: string,
64
68
  surfaces_ids: string[],
65
69
  activeColoring: string,
70
+ collectionId?: string,
66
71
  ): Promise<unknown> {
67
72
  const totalSurfaceIds = await dataStore.getSurfacesGeodeIds(modelId);
68
73
  if (surfaces_ids.length === totalSurfaceIds.length) {
@@ -75,7 +80,7 @@ export function useModelSurfacesColor(): {
75
80
  });
76
81
  if (activeColoring === "constant" || activeColoring === "random") {
77
82
  const color = modelSurfaceColor(modelId, surfaces_ids[0]);
78
- return setModelSurfacesColor(modelId, surfaces_ids, color, activeColoring);
83
+ return setModelSurfacesColor(modelId, surfaces_ids, color, activeColoring, collectionId);
79
84
  }
80
85
 
81
86
  if (activeColoring === "vertex") {
@@ -1,4 +1,5 @@
1
1
  import type { JsonRpcSchema, RequestHandlers } from "@ogw_shared/utils/types.js";
2
+ import { CHUNK_SIZE_BYTES } from "@ogw_shared/utils/file.js";
2
3
  import type { Microservice } from "./api_fetch.js";
3
4
  import { fetchRaw } from "@ogw_shared/utils/fetch_raw.js";
4
5
  import { useFeedbackStore } from "@ogw_front/stores/feedback.js";
@@ -8,11 +9,7 @@ interface UploadFileParams {
8
9
  // HTTP-flavored ("front"/"back") schemas.
9
10
  schema: JsonRpcSchema & { methods: string[] };
10
11
  file: File;
11
- params?: Record<string, string | Blob>;
12
- // When true, the file is sent as the raw request body instead of multipart
13
- // FormData, with `params`/filename moved into the query string. See
14
- // OpenGeodeWeb-Back's upload_file route, which supports both request shapes.
15
- raw?: boolean;
12
+ params?: Record<string, string>;
16
13
  }
17
14
 
18
15
  // Loosely-typed shapes for the dynamic error/response values reported by fetchRaw.
@@ -38,7 +35,7 @@ function isFetchErrorResponseLike(value: unknown): value is FetchErrorResponseLi
38
35
 
39
36
  async function upload_file(
40
37
  microservice: Microservice,
41
- { schema, file, params = {}, raw = false }: UploadFileParams,
38
+ { schema, file, params = {} }: UploadFileParams,
42
39
  { request_error_function, response_function, response_error_function }: RequestHandlers = {},
43
40
  ): Promise<unknown> {
44
41
  console.log("[UPLOAD_FILE] Uploading file", { schema, file });
@@ -48,68 +45,70 @@ async function upload_file(
48
45
  throw new Error("file must be an instance of File");
49
46
  }
50
47
 
51
- let route = schema.$id;
52
- let body: FormData | File = file;
48
+ const route = schema.$id;
49
+ const method = schema.methods.find((candidate) => candidate !== "OPTIONS");
50
+ const totalChunks = Math.max(1, Math.ceil(file.size / CHUNK_SIZE_BYTES));
53
51
 
54
- if (raw) {
55
- const queryEntries = Object.entries(params).filter(
56
- (entry): entry is [string, string] => typeof entry[1] === "string",
52
+ function onRequestError(error: unknown): void {
53
+ microservice.stop_request();
54
+ const typedError = isFetchErrorLike(error) ? error : {};
55
+ feedbackStore.add_error(
56
+ typedError.code ?? 0,
57
+ route,
58
+ typedError.message ?? "",
59
+ typedError.stack ?? "",
57
60
  );
58
- const query = new URLSearchParams([...queryEntries, ["filename", file.name]]);
59
- route = `${route}?${query.toString()}`;
60
- } else {
61
- const formData = new FormData();
62
- for (const [key, value] of Object.entries(params)) {
63
- formData.append(key, value);
61
+ if (request_error_function) {
62
+ request_error_function(error);
63
+ }
64
+ }
65
+
66
+ function onResponseError(response: unknown): void {
67
+ microservice.stop_request();
68
+ const typedResponse = isFetchErrorResponseLike(response) ? response : {};
69
+ feedbackStore.add_error(
70
+ typedResponse.status ?? 0,
71
+ route,
72
+ typedResponse.name ?? "",
73
+ typedResponse.description ?? "",
74
+ );
75
+ if (response_error_function) {
76
+ response_error_function(response);
64
77
  }
65
- formData.append("file", file);
66
- body = formData;
67
78
  }
68
79
 
69
80
  microservice.start_request();
70
81
 
71
- const result = await fetchRaw(
72
- {
73
- route,
74
- method: schema.methods.find((method) => method !== "OPTIONS"),
75
- params: body,
76
- baseURL: microservice.base_url,
77
- },
78
- {
79
- request_error_function(error: unknown) {
80
- microservice.stop_request();
81
- const typedError = isFetchErrorLike(error) ? error : {};
82
- feedbackStore.add_error(
83
- typedError.code ?? 0,
84
- route,
85
- typedError.message ?? "",
86
- typedError.stack ?? "",
87
- );
88
- if (request_error_function) {
89
- request_error_function(error);
90
- }
91
- },
92
- response_function(data: unknown) {
93
- microservice.stop_request();
94
- if (response_function) {
95
- response_function(data);
96
- }
82
+ let result: unknown = undefined;
83
+ for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
84
+ const start = chunkIndex * CHUNK_SIZE_BYTES;
85
+ const chunk = file.slice(start, start + CHUNK_SIZE_BYTES);
86
+ const query = new URLSearchParams({
87
+ ...params,
88
+ filename: file.name,
89
+ chunk_index: String(chunkIndex),
90
+ total_chunks: String(totalChunks),
91
+ });
92
+
93
+ // oxlint-disable-next-line no-await-in-loop -- chunks must be sent in order; the server appends each one to the file as it arrives.
94
+ result = await fetchRaw(
95
+ {
96
+ route: `${route}?${query.toString()}`,
97
+ method,
98
+ params: chunk,
99
+ baseURL: microservice.base_url,
97
100
  },
98
- response_error_function(response: unknown) {
99
- microservice.stop_request();
100
- const typedResponse = isFetchErrorResponseLike(response) ? response : {};
101
- feedbackStore.add_error(
102
- typedResponse.status ?? 0,
103
- route,
104
- typedResponse.name ?? "",
105
- typedResponse.description ?? "",
106
- );
107
- if (response_error_function) {
108
- response_error_function(response);
109
- }
101
+ {
102
+ request_error_function: onRequestError,
103
+ response_error_function: onResponseError,
110
104
  },
111
- },
112
- );
105
+ );
106
+ }
107
+
108
+ microservice.stop_request();
109
+ if (response_function) {
110
+ response_function(result);
111
+ }
113
112
  return result;
114
113
  }
115
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.34.0-rc.20",
3
+ "version": "10.34.0-rc.22",
4
4
  "description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
5
5
  "homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
6
6
  "bugs": {
@@ -1,98 +1,71 @@
1
1
  // Node imports
2
- import { finished, pipeline } from "node:stream/promises";
3
- import type { IncomingHttpHeaders } from "node:http";
4
2
  import { Readable } from "node:stream";
5
3
  import fs from "node:fs";
4
+ import { pipeline } from "node:stream/promises";
6
5
 
7
6
  // Third party imports
8
- import {
9
- type H3Event,
10
- createError,
11
- defineEventHandler,
12
- getRequestHeaders,
13
- getRequestWebStream,
14
- } from "h3";
15
- import busboy from "busboy";
7
+ import { type H3Event, createError, defineEventHandler, getQuery, getRequestWebStream } from "h3";
16
8
 
17
9
  // Local imports
18
10
  import {
19
11
  registerExtensionFile,
20
12
  targetExtensionFilePath,
21
13
  } from "@geode/opengeodeweb-front/server/utils/app_config.ts";
14
+ import { BYTES_PER_KIBIBYTE } from "@ogw_shared/utils/file.js";
22
15
  import { toNodeWebStream } from "@geode/opengeodeweb-front/server/utils/stream.ts";
23
16
 
17
+ const CODE_200 = 200;
24
18
  const CODE_201 = 201;
25
- const BYTES_PER_KIBIBYTE = 1024;
26
19
  const MAX_FILE_MEGABYTES = 500;
27
20
  const FILE_SIZE_LIMIT = MAX_FILE_MEGABYTES * BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE;
28
21
 
29
- export default defineEventHandler(async (event: H3Event) => {
30
- const writePromises: Promise<void>[] = [];
31
- const savedFiles: string[] = [];
32
-
33
- const headers: IncomingHttpHeaders = Object.fromEntries(
34
- Object.entries(getRequestHeaders(event)).filter(
35
- (entry: readonly [string, string | undefined]) => entry[1] !== undefined,
36
- ),
37
- );
38
- const busboyInstance = busboy({
39
- headers,
40
- limits: {
41
- fileSize: FILE_SIZE_LIMIT,
42
- files: 1,
43
- },
44
- });
45
- let projectName = "";
46
- busboyInstance.on("field", (name, value) => {
47
- console.log(`Field ${name}: ${value}`);
48
- if (name === "projectName") {
49
- projectName = value;
50
- }
51
- });
22
+ function requiredQueryString(event: H3Event, key: string): string {
23
+ const value = getQuery(event)[key];
24
+ if (typeof value !== "string" || value.length === 0) {
25
+ throw createError({ statusCode: 400, message: `Missing "${key}" query parameter` });
26
+ }
27
+ return value;
28
+ }
52
29
 
53
- busboyInstance.on("file", (fieldname, fileStream, info) => {
54
- if (fieldname !== "file") {
55
- // Drain & ignore unwanted fields
56
- fileStream.resume();
57
- return;
58
- }
59
- const targetPath = targetExtensionFilePath(projectName, info.filename);
60
- const writePromise = (async (): Promise<void> => {
61
- const writeStream = fs.createWriteStream(targetPath);
62
- await pipeline(fileStream, writeStream);
63
- savedFiles.push(targetPath);
64
- console.log("File written:", targetPath);
65
- })();
66
- writePromises.push(writePromise);
67
- fileStream.on("limit", () => {
68
- busboyInstance.destroy(new Error("File too large"));
69
- });
70
- });
30
+ function requiredQueryInt(event: H3Event, key: string): number {
31
+ const parsed = Math.trunc(Number(requiredQueryString(event, key)));
32
+ if (!Number.isInteger(parsed) || parsed < 0) {
33
+ throw createError({ statusCode: 400, message: `Invalid "${key}" query parameter` });
34
+ }
35
+ return parsed;
36
+ }
71
37
 
72
- busboyInstance.on("filesLimit", () => {
73
- busboyInstance.destroy(new Error("Too many files"));
74
- });
75
- busboyInstance.on("partsLimit", () => {
76
- busboyInstance.destroy(new Error("Too many parts"));
77
- });
38
+ // The file is sent as a raw (non-multipart) body, split into sequential chunks the client uploads in order (see OpenGeodeWeb-Front's upload_file.ts and OpenGeodeWeb-Back's upload_file route, which use the same protocol). Each chunk is appended to a `.part` file; the last one triggers the existing zip-metadata registration.
39
+ export default defineEventHandler(async (event: H3Event) => {
40
+ const projectName = requiredQueryString(event, "projectName");
41
+ const filename = requiredQueryString(event, "filename");
42
+ const totalChunks = requiredQueryInt(event, "total_chunks");
43
+ const chunkIndex = requiredQueryInt(event, "chunk_index");
44
+ if (chunkIndex >= totalChunks) {
45
+ throw createError({ statusCode: 400, message: "chunk_index must be less than total_chunks" });
46
+ }
78
47
 
79
48
  const webStream = getRequestWebStream(event);
80
49
  if (!webStream) {
81
50
  throw createError({ statusCode: 400, message: "No request body received" });
82
51
  }
83
- Readable.fromWeb(toNodeWebStream(webStream)).pipe(busboyInstance);
84
- await finished(busboyInstance);
85
- if (writePromises.length > 0) {
86
- await Promise.all(writePromises);
87
- console.log("All disk writes completed");
52
+
53
+ const targetPath = targetExtensionFilePath(projectName, filename);
54
+ const partPath = `${targetPath}.part`;
55
+ const writeStream = fs.createWriteStream(partPath, { flags: chunkIndex === 0 ? "w" : "a" });
56
+ await pipeline(Readable.fromWeb(toNodeWebStream(webStream)), writeStream);
57
+
58
+ const { size } = await fs.promises.stat(partPath);
59
+ if (size > FILE_SIZE_LIMIT) {
60
+ await fs.promises.unlink(partPath);
61
+ throw createError({ statusCode: 400, message: "File too large" });
88
62
  }
89
- if (savedFiles.length === 0) {
90
- throw createError({ statusCode: 400, message: "No file received" });
63
+
64
+ if (chunkIndex < totalChunks - 1) {
65
+ return { statusCode: CODE_200 };
91
66
  }
92
- await Promise.all(
93
- savedFiles.map(async (file) => {
94
- await registerExtensionFile(projectName, file);
95
- }),
96
- );
67
+
68
+ await fs.promises.rename(partPath, targetPath);
69
+ await registerExtensionFile(projectName, targetPath);
97
70
  return { statusCode: CODE_201 };
98
71
  });
@@ -1,5 +1,11 @@
1
1
  import _ from "lodash";
2
2
 
3
+ const BYTES_PER_KIBIBYTE = 1024;
4
+ const KIBIBYTES_PER_MEBIBYTE = 1024;
5
+ const CHUNK_SIZE_MEBIBYTES = 8;
6
+ // Cloud hosting in front of the upload routes (Cloud Run/Firebase Hosting) enforces a hard ~32MB request body limit that no amount of server-side streaming can bypass, so uploads are always split into chunks comfortably under that cap. The single source of truth here keeps every uploader (OpenGeodeWeb-Front, Vease) and the routes that assemble the chunks in agreement.
7
+ const CHUNK_SIZE_BYTES = CHUNK_SIZE_MEBIBYTES * KIBIBYTES_PER_MEBIBYTE * BYTES_PER_KIBIBYTE;
8
+
3
9
  function assertFile(file: unknown): asserts file is File {
4
10
  console.log("[ASSERT_FILE] Asserting file", { file });
5
11
  if (!(file instanceof File)) {
@@ -13,4 +19,4 @@ function hasBody(params: unknown): params is Blob | FormData | Record<string, un
13
19
  }
14
20
  return !_.isEmpty(params);
15
21
  }
16
- export { assertFile, hasBody };
22
+ export { assertFile, BYTES_PER_KIBIBYTE, CHUNK_SIZE_BYTES, hasBody };