@geode/opengeodeweb-front 10.34.0-rc.13 → 10.34.0-rc.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/Viewer/ContextMenu/InfoCard.vue +2 -3
- package/app/components/Viewer/Options/ColorPicker.vue +223 -41
- package/app/composables/copy_to_clipboard.ts +7 -0
- package/app/stores/app.ts +13 -15
- package/app/stores/viewer.ts +15 -9
- package/app/utils/color_picker.js +54 -0
- package/internal/utils/api_fetch.ts +19 -13
- package/package.json +1 -1
- package/shared/utils/fetch_raw.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import GlassCard from "@ogw_front/components/GlassCard.vue";
|
|
3
3
|
import { middleTruncate } from "@ogw_front/utils/string";
|
|
4
|
-
import {
|
|
4
|
+
import { useCopyToClipboard } from "@ogw_front/composables/copy_to_clipboard";
|
|
5
5
|
import { useDataStore } from "@ogw_front/stores/data";
|
|
6
6
|
import { useMenuStore } from "@ogw_front/stores/menu";
|
|
7
7
|
|
|
@@ -17,7 +17,6 @@ const emit = defineEmits<{
|
|
|
17
17
|
"update:show": [value: boolean];
|
|
18
18
|
}>();
|
|
19
19
|
|
|
20
|
-
const COPIED_TIMEOUT = 1500;
|
|
21
20
|
const MAX_SHORT_ID_LENGTH = 15;
|
|
22
21
|
const ID_SLICE_START = 8;
|
|
23
22
|
const ID_SLICE_END_OFFSET = 7;
|
|
@@ -25,7 +24,7 @@ const TRUNCATE_MAX_LENGTH = 22;
|
|
|
25
24
|
const TRUNCATE_START_CHARS = 12;
|
|
26
25
|
const TRUNCATE_END_CHARS = 7;
|
|
27
26
|
|
|
28
|
-
const { copy, copied } =
|
|
27
|
+
const { copy, copied } = useCopyToClipboard();
|
|
29
28
|
const copiedId = ref("");
|
|
30
29
|
|
|
31
30
|
function isCopied(id: string | undefined): boolean {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// oxlint-disable id-length
|
|
2
|
+
// oxlint-disable id-length since vuetify require { r,g,b} format to work
|
|
3
|
+
import { formatColorString, parseColorString } from "@ogw_front/utils/color_picker";
|
|
3
4
|
import type { RGBAColor } from "@ogw_front/utils/default_styles/constants";
|
|
5
|
+
import { useCopyToClipboard } from "@ogw_front/composables/copy_to_clipboard";
|
|
4
6
|
|
|
5
7
|
interface Props {
|
|
6
8
|
disabledAlpha?: boolean;
|
|
@@ -8,12 +10,15 @@ interface Props {
|
|
|
8
10
|
|
|
9
11
|
const { disabledAlpha = false } = defineProps<Props>();
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
+
const colorPickerWrapperRef = useTemplateRef<HTMLElement>("colorPickerWrapperRef");
|
|
14
|
+
const { pressed } = useMousePressed({ target: colorPickerWrapperRef });
|
|
15
|
+
|
|
13
16
|
const model = defineModel<RGBAColor>();
|
|
14
|
-
const {
|
|
17
|
+
const { copy, copied } = useCopyToClipboard();
|
|
18
|
+
|
|
19
|
+
const currentMode = ref(disabledAlpha ? "rgb" : "rgba");
|
|
20
|
+
const colorInputText = ref("");
|
|
15
21
|
|
|
16
|
-
// The model is always bound by every current caller (ColoringTypeSelector.vue, AttributeSelector.vue); defineModel can't express that as a required prop without breaking its optional v-model contract, so this reads it as defined here.
|
|
17
22
|
const initialColor = model.value as RGBAColor;
|
|
18
23
|
const vuetifyColor = ref({
|
|
19
24
|
r: initialColor.red,
|
|
@@ -22,69 +27,246 @@ const vuetifyColor = ref({
|
|
|
22
27
|
a: initialColor.alpha,
|
|
23
28
|
});
|
|
24
29
|
|
|
30
|
+
function updateInputTextFromColor(red: number, green: number, blue: number, alpha: number) {
|
|
31
|
+
colorInputText.value =
|
|
32
|
+
disabledAlpha || currentMode.value === "rgb"
|
|
33
|
+
? `${red}, ${green}, ${blue}`
|
|
34
|
+
: `${red}, ${green}, ${blue}, ${alpha}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface VuetifyColor {
|
|
38
|
+
r: number;
|
|
39
|
+
g: number;
|
|
40
|
+
b: number;
|
|
41
|
+
a: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function commitColorToModel() {
|
|
45
|
+
const { r: red, g: green, b: blue, a: alpha } = vuetifyColor.value;
|
|
46
|
+
model.value = { red, green, blue, alpha };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function onPickerUpdate(color: VuetifyColor) {
|
|
50
|
+
const red = Math.round(color.r);
|
|
51
|
+
const green = Math.round(color.g);
|
|
52
|
+
const blue = Math.round(color.b);
|
|
53
|
+
const alpha = disabledAlpha ? 1 : Number(color.a.toFixed(2));
|
|
54
|
+
|
|
55
|
+
vuetifyColor.value = { r: red, g: green, b: blue, a: alpha };
|
|
56
|
+
updateInputTextFromColor(red, green, blue, alpha);
|
|
57
|
+
if (!pressed.value) {
|
|
58
|
+
commitColorToModel();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function toggleMode() {
|
|
63
|
+
if (disabledAlpha) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
currentMode.value = currentMode.value === "rgba" ? "rgb" : "rgba";
|
|
67
|
+
const { r: red, g: green, b: blue, a: alpha } = vuetifyColor.value;
|
|
68
|
+
updateInputTextFromColor(red, green, blue, alpha);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function copyToClipboard() {
|
|
72
|
+
const { r: red, g: green, b: blue, a: alpha } = vuetifyColor.value;
|
|
73
|
+
await copy(formatColorString({ red, green, blue, alpha }, currentMode.value));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseAndApplyText(text: string) {
|
|
77
|
+
const parsed = parseColorString(text);
|
|
78
|
+
if (!parsed) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const { red, green, blue, alpha: rawAlpha } = parsed;
|
|
83
|
+
const alpha = disabledAlpha ? 1 : rawAlpha;
|
|
84
|
+
|
|
85
|
+
if (!disabledAlpha && alpha < 1) {
|
|
86
|
+
currentMode.value = "rgba";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
vuetifyColor.value = { r: red, g: green, b: blue, a: alpha };
|
|
90
|
+
model.value = { red, green, blue, alpha };
|
|
91
|
+
updateInputTextFromColor(red, green, blue, alpha);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function onInputPaste(event: ClipboardEvent) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
const text = event.clipboardData!.getData("text/plain");
|
|
98
|
+
parseAndApplyText(text);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function onInputCommit() {
|
|
102
|
+
if (!parseAndApplyText(colorInputText.value)) {
|
|
103
|
+
const { r: red, g: green, b: blue, a: alpha } = vuetifyColor.value;
|
|
104
|
+
updateInputTextFromColor(red, green, blue, alpha);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
25
108
|
watch(
|
|
26
109
|
model,
|
|
27
110
|
(newValue) => {
|
|
28
111
|
if (!newValue) {
|
|
29
112
|
return;
|
|
30
113
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
114
|
+
const { red, green, blue, alpha } = newValue;
|
|
115
|
+
if (
|
|
116
|
+
vuetifyColor.value.r !== red ||
|
|
117
|
+
vuetifyColor.value.g !== green ||
|
|
118
|
+
vuetifyColor.value.b !== blue ||
|
|
119
|
+
vuetifyColor.value.a !== alpha
|
|
120
|
+
) {
|
|
121
|
+
vuetifyColor.value = { r: red, g: green, b: blue, a: alpha };
|
|
38
122
|
}
|
|
39
|
-
|
|
40
|
-
r: newValue.red,
|
|
41
|
-
g: newValue.green,
|
|
42
|
-
b: newValue.blue,
|
|
43
|
-
a: newValue.alpha,
|
|
44
|
-
};
|
|
123
|
+
updateInputTextFromColor(red, green, blue, alpha);
|
|
45
124
|
},
|
|
46
|
-
{ deep: true },
|
|
125
|
+
{ deep: true, immediate: true },
|
|
47
126
|
);
|
|
48
127
|
|
|
49
|
-
watch(pressed, (
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
red: vuetifyColor.value.r,
|
|
53
|
-
green: vuetifyColor.value.g,
|
|
54
|
-
blue: vuetifyColor.value.b,
|
|
55
|
-
alpha: vuetifyColor.value.a,
|
|
56
|
-
};
|
|
128
|
+
watch(pressed, (isPressed) => {
|
|
129
|
+
if (!isPressed) {
|
|
130
|
+
commitColorToModel();
|
|
57
131
|
}
|
|
58
132
|
});
|
|
133
|
+
|
|
134
|
+
watch(
|
|
135
|
+
() => disabledAlpha,
|
|
136
|
+
(disabled) => {
|
|
137
|
+
if (disabled) {
|
|
138
|
+
currentMode.value = "rgb";
|
|
139
|
+
if (model.value) {
|
|
140
|
+
model.value = { ...model.value, alpha: 1 };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
);
|
|
59
145
|
</script>
|
|
60
146
|
|
|
61
147
|
<template>
|
|
62
|
-
<
|
|
63
|
-
ref="
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
148
|
+
<div
|
|
149
|
+
ref="colorPickerWrapperRef"
|
|
150
|
+
class="color-picker-wrapper mx-auto rounded-lg overflow-hidden border"
|
|
151
|
+
>
|
|
152
|
+
<v-color-picker
|
|
153
|
+
data-testid="colorPicker"
|
|
154
|
+
:model-value="vuetifyColor"
|
|
155
|
+
@update:model-value="onPickerUpdate"
|
|
156
|
+
mode="rgba"
|
|
157
|
+
flat
|
|
158
|
+
canvas-height="75"
|
|
159
|
+
hide-eye-dropper
|
|
160
|
+
hide-inputs
|
|
161
|
+
:disabled-alpha="disabledAlpha"
|
|
162
|
+
width="220"
|
|
163
|
+
class="mx-auto bg-transparent"
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<div class="color-picker-controls pa-2 pt-0">
|
|
167
|
+
<div class="d-flex align-center justify-space-between px-1 mb-1">
|
|
168
|
+
<div class="d-flex align-center ga-1">
|
|
169
|
+
<span
|
|
170
|
+
class="text-caption font-weight-medium text-uppercase text-medium-emphasis"
|
|
171
|
+
style="font-size: 0.68rem"
|
|
172
|
+
>
|
|
173
|
+
{{ currentMode }}
|
|
174
|
+
</span>
|
|
175
|
+
<v-btn
|
|
176
|
+
v-if="!disabledAlpha"
|
|
177
|
+
icon
|
|
178
|
+
density="compact"
|
|
179
|
+
variant="text"
|
|
180
|
+
size="small"
|
|
181
|
+
@click="toggleMode"
|
|
182
|
+
v-tooltip="'Swap mode (RGB / RGBA)'"
|
|
183
|
+
>
|
|
184
|
+
<v-icon icon="mdi-swap-horizontal" size="16" />
|
|
185
|
+
</v-btn>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<v-btn
|
|
189
|
+
data-testid="copyColorBtn"
|
|
190
|
+
icon
|
|
191
|
+
density="compact"
|
|
192
|
+
variant="text"
|
|
193
|
+
size="small"
|
|
194
|
+
@click="copyToClipboard"
|
|
195
|
+
v-tooltip="'Copy to clipboard'"
|
|
196
|
+
>
|
|
197
|
+
<v-icon
|
|
198
|
+
:icon="copied ? 'mdi-check' : 'mdi-content-copy'"
|
|
199
|
+
size="14"
|
|
200
|
+
:color="copied ? 'success' : undefined"
|
|
201
|
+
/>
|
|
202
|
+
</v-btn>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<input
|
|
206
|
+
data-testid="colorInput"
|
|
207
|
+
v-model="colorInputText"
|
|
208
|
+
class="color-input-field"
|
|
209
|
+
type="text"
|
|
210
|
+
spellcheck="false"
|
|
211
|
+
autocomplete="off"
|
|
212
|
+
@paste="onInputPaste"
|
|
213
|
+
@change="onInputCommit"
|
|
214
|
+
@keydown.enter.prevent="onInputCommit"
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
75
218
|
</template>
|
|
76
219
|
|
|
77
220
|
<style scoped>
|
|
221
|
+
.color-picker-wrapper {
|
|
222
|
+
width: 220px;
|
|
223
|
+
background-color: rgba(33, 33, 33, 0.95);
|
|
224
|
+
border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
|
225
|
+
outline: none;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
:deep(.v-color-picker) {
|
|
229
|
+
background-color: transparent !important;
|
|
230
|
+
box-shadow: none !important;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
:deep(.v-color-picker-canvas),
|
|
234
|
+
:deep(.v-color-picker__preview),
|
|
235
|
+
:deep(.v-color-picker__controls) {
|
|
236
|
+
box-shadow: none !important;
|
|
237
|
+
}
|
|
238
|
+
|
|
78
239
|
:deep(.v-color-picker__controls) {
|
|
79
|
-
padding: 8px !important;
|
|
240
|
+
padding: 4px 8px 0 !important;
|
|
80
241
|
}
|
|
81
242
|
|
|
82
243
|
:deep(.v-color-picker__dot) {
|
|
83
244
|
width: 18px !important;
|
|
84
245
|
height: 18px !important;
|
|
246
|
+
box-shadow: none !important;
|
|
85
247
|
}
|
|
86
248
|
|
|
87
249
|
:deep(.v-color-picker__preview) {
|
|
88
250
|
margin-bottom: 0 !important;
|
|
89
251
|
}
|
|
252
|
+
|
|
253
|
+
.color-input-field {
|
|
254
|
+
width: 100%;
|
|
255
|
+
box-sizing: border-box;
|
|
256
|
+
padding: 6px 10px;
|
|
257
|
+
background-color: rgba(255, 255, 255, 0.08);
|
|
258
|
+
border: 1px solid transparent;
|
|
259
|
+
border-radius: 6px;
|
|
260
|
+
outline: none;
|
|
261
|
+
color: inherit;
|
|
262
|
+
font-size: 0.82rem;
|
|
263
|
+
font-family: monospace;
|
|
264
|
+
letter-spacing: 0.5px;
|
|
265
|
+
text-align: center;
|
|
266
|
+
transition: border-color 0.15s ease;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.color-input-field:focus {
|
|
270
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
271
|
+
}
|
|
90
272
|
</style>
|
package/app/stores/app.ts
CHANGED
|
@@ -140,14 +140,21 @@ export const useAppStore = defineStore("app", () => {
|
|
|
140
140
|
getExtensionEnabled,
|
|
141
141
|
} = useAppExtensions();
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const request_counter = ref(0);
|
|
144
|
+
function start_request(): void {
|
|
145
|
+
request_counter.value += 1;
|
|
146
|
+
}
|
|
147
|
+
function stop_request(): void {
|
|
148
|
+
request_counter.value -= 1;
|
|
149
|
+
}
|
|
150
|
+
const is_busy = computed(() => request_counter.value > 0);
|
|
151
|
+
|
|
152
|
+
async function upload(file: Readonly<File>, callbacks: RequestHandlers = {}): Promise<unknown> {
|
|
146
153
|
const schema = opengeodeweb_front_schemas.api.local.extensions.upload;
|
|
147
154
|
const { PROJECT: projectName } = useRuntimeConfig().public;
|
|
148
155
|
const params = { projectName };
|
|
149
156
|
const result = await upload_file(
|
|
150
|
-
|
|
157
|
+
{ $id: "app", base_url: base_url.value, start_request, stop_request },
|
|
151
158
|
{ schema, file, params },
|
|
152
159
|
{
|
|
153
160
|
...callbacks,
|
|
@@ -163,13 +170,13 @@ export const useAppStore = defineStore("app", () => {
|
|
|
163
170
|
|
|
164
171
|
// `TResult` is asserted, not verified, at the single `return result as TResult` boundary below: the backend response is only checked against `schema` at runtime, so callers' `TResult` is a contract with the schema, not something this function can prove.
|
|
165
172
|
// oxlint-disable-next-line unicorn/consistent-function-scoping
|
|
173
|
+
// oxlint-disable-next-line unicorn/consistent-function-scoping
|
|
166
174
|
async function request<TResult = unknown>(
|
|
167
175
|
{ schema, params }: { schema: JsonRpcSchema; params?: Record<string, unknown> },
|
|
168
176
|
callbacks: RequestHandlers = {},
|
|
169
177
|
): Promise<TResult> {
|
|
170
|
-
const store = useAppStore();
|
|
171
178
|
const result = await api_fetch(
|
|
172
|
-
|
|
179
|
+
{ $id: "app", base_url: base_url.value, start_request, stop_request },
|
|
173
180
|
// The app store is only ever used with HTTP ("front") schemas, which always carry `methods`; the wider JsonRpcSchema param above is kept as-is to match this action's public signature (e.g. relayed from get_version-style callers that only know about the shared, looser schema shape).
|
|
174
181
|
// oxlint-disable-next-line no-unsafe-type-assertion -- narrowing optional `methods` to required is safe here; see comment above.
|
|
175
182
|
{ schema: schema as JsonRpcSchema & { methods: string[] }, params },
|
|
@@ -186,15 +193,6 @@ export const useAppStore = defineStore("app", () => {
|
|
|
186
193
|
return result as TResult;
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
const request_counter = ref(0);
|
|
190
|
-
function start_request(): void {
|
|
191
|
-
request_counter.value += 1;
|
|
192
|
-
}
|
|
193
|
-
function stop_request(): void {
|
|
194
|
-
request_counter.value -= 1;
|
|
195
|
-
}
|
|
196
|
-
const is_busy = computed(() => request_counter.value > 0);
|
|
197
|
-
|
|
198
196
|
const projectFolderPath = ref("");
|
|
199
197
|
|
|
200
198
|
async function createProjectFolder(): Promise<unknown> {
|
package/app/stores/viewer.ts
CHANGED
|
@@ -58,6 +58,12 @@ export const useViewerStore = defineStore(
|
|
|
58
58
|
viewer_url += "/ws";
|
|
59
59
|
return viewer_url;
|
|
60
60
|
});
|
|
61
|
+
function start_request(): void {
|
|
62
|
+
request_counter.value += 1;
|
|
63
|
+
}
|
|
64
|
+
function stop_request(): void {
|
|
65
|
+
request_counter.value -= 1;
|
|
66
|
+
}
|
|
61
67
|
const is_busy = computed(() => request_counter.value > 0);
|
|
62
68
|
function toggle_picking_mode(value: boolean): void {
|
|
63
69
|
picking_mode.value = value;
|
|
@@ -75,9 +81,15 @@ export const useViewerStore = defineStore(
|
|
|
75
81
|
},
|
|
76
82
|
callbacks: RequestHandlers = {},
|
|
77
83
|
): Promise<unknown> {
|
|
78
|
-
const
|
|
84
|
+
const microservice = {
|
|
85
|
+
$id: "viewer",
|
|
86
|
+
client: client.value,
|
|
87
|
+
base_url: base_url.value,
|
|
88
|
+
start_request,
|
|
89
|
+
stop_request,
|
|
90
|
+
};
|
|
79
91
|
const result = await viewer_call(
|
|
80
|
-
|
|
92
|
+
microservice,
|
|
81
93
|
{
|
|
82
94
|
schema,
|
|
83
95
|
params,
|
|
@@ -148,13 +160,7 @@ export const useViewerStore = defineStore(
|
|
|
148
160
|
}
|
|
149
161
|
});
|
|
150
162
|
}
|
|
151
|
-
function
|
|
152
|
-
request_counter.value += 1;
|
|
153
|
-
}
|
|
154
|
-
function stop_request(): void {
|
|
155
|
-
request_counter.value -= 1;
|
|
156
|
-
}
|
|
157
|
-
async function launch(args: { projectFolderPath?: string } = {}): Promise<unknown> {
|
|
163
|
+
async function launch(args: Readonly<{ projectFolderPath?: string }> = {}): Promise<unknown> {
|
|
158
164
|
const appStore = useAppStore();
|
|
159
165
|
const { COMMAND_VIEWER, NUXT_ROOT_PATH } = useRuntimeConfig().public;
|
|
160
166
|
const schema = opengeodeweb_front_schemas.api.local.app.run_viewer;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const RGB_MAX = 255;
|
|
2
|
+
const PERCENT_MAX = 100;
|
|
3
|
+
|
|
4
|
+
function parseComponent(val, max = RGB_MAX) {
|
|
5
|
+
if (val.endsWith("%")) {
|
|
6
|
+
return (Number(val.slice(0, -1)) / PERCENT_MAX) * max;
|
|
7
|
+
}
|
|
8
|
+
return Number(val);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseColorString(colorText) {
|
|
12
|
+
if (!colorText || typeof colorText !== "string") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const numbersMatch = colorText.match(/[\d.]+%?/gu);
|
|
17
|
+
if (!numbersMatch || (numbersMatch.length !== 3 && numbersMatch.length !== 4)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rawRed = parseComponent(numbersMatch[0]);
|
|
22
|
+
const rawGreen = parseComponent(numbersMatch[1]);
|
|
23
|
+
const rawBlue = parseComponent(numbersMatch[2]);
|
|
24
|
+
|
|
25
|
+
const red = Math.min(RGB_MAX, Math.max(0, Math.round(rawRed)));
|
|
26
|
+
const green = Math.min(RGB_MAX, Math.max(0, Math.round(rawGreen)));
|
|
27
|
+
const blue = Math.min(RGB_MAX, Math.max(0, Math.round(rawBlue)));
|
|
28
|
+
|
|
29
|
+
let alpha = 1;
|
|
30
|
+
if (numbersMatch.length === 4) {
|
|
31
|
+
const rawAlpha = parseComponent(numbersMatch[3], 1);
|
|
32
|
+
const calculatedAlpha = rawAlpha > 1 ? rawAlpha / RGB_MAX : rawAlpha;
|
|
33
|
+
const roundedAlpha = Number(calculatedAlpha.toFixed(2));
|
|
34
|
+
alpha = Math.min(1, Math.max(0, roundedAlpha));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue) || Number.isNaN(alpha)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { red, green, blue, alpha };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function formatColorString(color, mode) {
|
|
45
|
+
const { red, green, blue, alpha } = color;
|
|
46
|
+
const roundRed = Math.round(red);
|
|
47
|
+
const roundGreen = Math.round(green);
|
|
48
|
+
const roundBlue = Math.round(blue);
|
|
49
|
+
return mode === "rgb"
|
|
50
|
+
? `rgb(${roundRed}, ${roundGreen}, ${roundBlue})`
|
|
51
|
+
: `rgba(${roundRed}, ${roundGreen}, ${roundBlue}, ${alpha})`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { parseColorString, formatColorString };
|
|
@@ -57,6 +57,7 @@ function toFetchErrorResponseLike(response: unknown): FetchErrorResponseLike {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// oxlint-disable-next-line max-lines-per-function
|
|
60
61
|
async function api_fetch(
|
|
61
62
|
microservice: Microservice,
|
|
62
63
|
{ schema, params = {}, headers = {} }: ApiFetchParams,
|
|
@@ -65,7 +66,8 @@ async function api_fetch(
|
|
|
65
66
|
response_function,
|
|
66
67
|
response_error_function,
|
|
67
68
|
timeout,
|
|
68
|
-
|
|
69
|
+
skip_feedback_error,
|
|
70
|
+
}: RequestHandlersWithValidation & { timeout?: number; skip_feedback_error?: boolean } = {},
|
|
69
71
|
): Promise<unknown> {
|
|
70
72
|
console.log("[API] Fetching", microservice.base_url);
|
|
71
73
|
const feedbackStore = useFeedbackStore();
|
|
@@ -84,12 +86,14 @@ async function api_fetch(
|
|
|
84
86
|
request_error_function(error: unknown) {
|
|
85
87
|
microservice.stop_request();
|
|
86
88
|
const typedError = toFetchErrorLike(error);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
if (!skip_feedback_error) {
|
|
90
|
+
feedbackStore.add_error(
|
|
91
|
+
typedError.code ?? 0,
|
|
92
|
+
schema.$id,
|
|
93
|
+
typedError.message ?? "",
|
|
94
|
+
typedError.stack ?? "",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
93
97
|
if (request_error_function) {
|
|
94
98
|
request_error_function(error);
|
|
95
99
|
}
|
|
@@ -104,12 +108,14 @@ async function api_fetch(
|
|
|
104
108
|
response_error_function(response: unknown) {
|
|
105
109
|
microservice.stop_request();
|
|
106
110
|
const typedResponse = toFetchErrorResponseLike(response);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
if (!skip_feedback_error) {
|
|
112
|
+
feedbackStore.add_error(
|
|
113
|
+
typedResponse.status ?? 0,
|
|
114
|
+
schema.$id,
|
|
115
|
+
typedResponse.name ?? "",
|
|
116
|
+
typedResponse.description ?? "",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
113
119
|
if (response_error_function) {
|
|
114
120
|
response_error_function(response);
|
|
115
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geode/opengeodeweb-front",
|
|
3
|
-
"version": "10.34.0-rc.
|
|
3
|
+
"version": "10.34.0-rc.15",
|
|
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": {
|
|
@@ -67,8 +67,8 @@ async function performFetch({
|
|
|
67
67
|
},
|
|
68
68
|
async onResponse({
|
|
69
69
|
response,
|
|
70
|
-
// oxlint-disable-next-line eslint/id-length -- mirrors the real ofetch/vitest API field name (`ok`/`fn`)
|
|
71
70
|
}: {
|
|
71
|
+
// oxlint-disable-next-line eslint/id-length -- mirrors the real ofetch/vitest API field name (`ok`/`fn`)
|
|
72
72
|
response: { ok: boolean; _data?: unknown };
|
|
73
73
|
}) {
|
|
74
74
|
if (response.ok && response_function) {
|