poe-svelte-ui-lib 1.2.30 → 1.2.31
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/dist/FileAttach/FileAttach.svelte +2 -13
- package/dist/FileAttach/FileAttach.svelte.d.ts +2 -20
- package/dist/FileAttach/FileAttachProps.svelte +71 -4
- package/dist/FileAttach/FileAttachProps.svelte.d.ts +2 -3
- package/dist/Input/InputProps.svelte +4 -4
- package/dist/Joystick/Joystick.svelte +54 -62
- package/dist/Joystick/JoystickProps.svelte +286 -154
- package/dist/ProgressBar/ProgressBar.svelte +31 -6
- package/dist/Slider/Slider.svelte +5 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/locales/translations.js +3 -0
- package/dist/types.d.ts +25 -6
- package/package.json +1 -1
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { IFileAttachProps } from '../types'
|
|
2
3
|
import { twMerge } from 'tailwind-merge'
|
|
3
4
|
|
|
4
|
-
export interface IFileInputProps {
|
|
5
|
-
id?: string
|
|
6
|
-
wrapperClass?: string
|
|
7
|
-
label?: { name?: string; class?: string }
|
|
8
|
-
type?: 'file' | 'image'
|
|
9
|
-
accept?: string
|
|
10
|
-
imageSize?: { height?: string; width?: string; fitMode?: 'cover' | 'contain'; form?: 'square' | 'circle' }
|
|
11
|
-
disabled?: boolean
|
|
12
|
-
currentImage?: string | null
|
|
13
|
-
onChange?: (event: Event, file: File | null) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
5
|
let {
|
|
17
6
|
id = crypto.randomUUID(),
|
|
18
7
|
wrapperClass = '',
|
|
@@ -23,7 +12,7 @@
|
|
|
23
12
|
disabled = false,
|
|
24
13
|
currentImage = $bindable(''),
|
|
25
14
|
onChange = () => {},
|
|
26
|
-
}:
|
|
15
|
+
}: IFileAttachProps = $props()
|
|
27
16
|
|
|
28
17
|
let ID = `${id}-${crypto.randomUUID().slice(0, 6)}`
|
|
29
18
|
let selectedFile = $state<File | null>(null)
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
wrapperClass?: string;
|
|
4
|
-
label?: {
|
|
5
|
-
name?: string;
|
|
6
|
-
class?: string;
|
|
7
|
-
};
|
|
8
|
-
type?: 'file' | 'image';
|
|
9
|
-
accept?: string;
|
|
10
|
-
imageSize?: {
|
|
11
|
-
height?: string;
|
|
12
|
-
width?: string;
|
|
13
|
-
fitMode?: 'cover' | 'contain';
|
|
14
|
-
form?: 'square' | 'circle';
|
|
15
|
-
};
|
|
16
|
-
disabled?: boolean;
|
|
17
|
-
currentImage?: string | null;
|
|
18
|
-
onChange?: (event: Event, file: File | null) => void;
|
|
19
|
-
}
|
|
20
|
-
declare const FileAttach: import("svelte").Component<IFileInputProps, {}, "currentImage">;
|
|
1
|
+
import type { IFileAttachProps } from '../types';
|
|
2
|
+
declare const FileAttach: import("svelte").Component<IFileAttachProps, {}, "currentImage">;
|
|
21
3
|
type FileAttach = ReturnType<typeof FileAttach>;
|
|
22
4
|
export default FileAttach;
|
|
@@ -1,23 +1,76 @@
|
|
|
1
1
|
<!-- $lib/ElementsUI/ButtonProps.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { t } from '../locales/i18n'
|
|
4
|
-
import { type IUIComponentHandler, type UIComponent, updateProperty } from '../types'
|
|
4
|
+
import { type IFileAttachProps, type IUIComponentHandler, type UIComponent, updateProperty } from '../types'
|
|
5
5
|
import * as UI from '..'
|
|
6
6
|
import { optionsStore } from '../options'
|
|
7
|
-
import
|
|
7
|
+
import { getContext } from 'svelte'
|
|
8
|
+
import { twMerge } from 'tailwind-merge'
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
11
|
component,
|
|
11
12
|
onPropertyChange,
|
|
12
13
|
forConstructor = true,
|
|
13
14
|
} = $props<{
|
|
14
|
-
component: UIComponent & { properties: Partial<
|
|
15
|
+
component: UIComponent & { properties: Partial<IFileAttachProps> }
|
|
15
16
|
onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void
|
|
16
17
|
forConstructor?: boolean
|
|
17
18
|
}>()
|
|
19
|
+
|
|
20
|
+
const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>('DeviceVariables')
|
|
21
|
+
let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
|
|
22
|
+
|
|
23
|
+
const initialAlign = $derived(
|
|
24
|
+
$optionsStore.TEXT_ALIGN_OPTIONS.find((a) =>
|
|
25
|
+
(a.value as string).includes(component.properties.label?.class?.split(' ').find((cls: string) => cls.startsWith('text-'))),
|
|
26
|
+
),
|
|
27
|
+
)
|
|
18
28
|
</script>
|
|
19
29
|
|
|
20
|
-
{#if
|
|
30
|
+
{#if forConstructor}
|
|
31
|
+
<div class="relative flex flex-row items-start justify-center">
|
|
32
|
+
<!-- Сообщение для отправки в ws по нажатию кнопки -->
|
|
33
|
+
<div class="flex w-1/3 flex-col items-center px-2">
|
|
34
|
+
<UI.Select
|
|
35
|
+
label={{ name: $t('constructor.props.variable') }}
|
|
36
|
+
options={VARIABLE_OPTIONS}
|
|
37
|
+
value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id)}
|
|
38
|
+
onUpdate={(value) => {
|
|
39
|
+
updateProperty('id', value.value as string, component, onPropertyChange)
|
|
40
|
+
onPropertyChange({ name: value.name?.split('—')[1].trim(), eventHandler: { Variables: value.value as string } })
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
<UI.Select
|
|
44
|
+
label={{ name: $t('constructor.props.access') }}
|
|
45
|
+
type="buttons"
|
|
46
|
+
options={$optionsStore.ACCESS_OPTION}
|
|
47
|
+
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
48
|
+
onUpdate={(option) => onPropertyChange({ access: option.value })}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
52
|
+
<UI.Input
|
|
53
|
+
label={{ name: $t('constructor.props.label') }}
|
|
54
|
+
value={component.properties.label.name}
|
|
55
|
+
onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
|
|
56
|
+
/>
|
|
57
|
+
<UI.Select
|
|
58
|
+
label={{ name: $t('constructor.props.align') }}
|
|
59
|
+
type="buttons"
|
|
60
|
+
value={initialAlign}
|
|
61
|
+
options={$optionsStore.TEXT_ALIGN_OPTIONS}
|
|
62
|
+
onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
66
|
+
<UI.Input
|
|
67
|
+
label={{ name: $t('constructor.props.file.accept') }}
|
|
68
|
+
value={component.properties.accept}
|
|
69
|
+
onUpdate={(value) => updateProperty('accept', value as string, component, onPropertyChange)}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
{:else}
|
|
21
74
|
<div class="relative flex flex-row items-start justify-center">
|
|
22
75
|
<!-- Сообщение для отправки в ws по нажатию кнопки -->
|
|
23
76
|
<div class="flex w-1/3 flex-col items-center px-2">
|
|
@@ -26,6 +79,13 @@
|
|
|
26
79
|
value={component.properties.id}
|
|
27
80
|
onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
|
|
28
81
|
/>
|
|
82
|
+
<UI.Select
|
|
83
|
+
label={{ name: $t('constructor.props.access') }}
|
|
84
|
+
type="buttons"
|
|
85
|
+
options={$optionsStore.ACCESS_OPTION}
|
|
86
|
+
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
87
|
+
onUpdate={(option) => onPropertyChange({ access: option.value })}
|
|
88
|
+
/>
|
|
29
89
|
<UI.Input
|
|
30
90
|
label={{ name: $t('constructor.props.wrapperclass') }}
|
|
31
91
|
value={component.properties.wrapperClass}
|
|
@@ -43,6 +103,13 @@
|
|
|
43
103
|
value={component.properties.label.class}
|
|
44
104
|
onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
|
|
45
105
|
/>
|
|
106
|
+
<UI.Switch
|
|
107
|
+
wrapperClass="bg-blue"
|
|
108
|
+
label={{ name: $t('constructor.props.disabled') }}
|
|
109
|
+
value={component.properties.disabled}
|
|
110
|
+
options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
|
|
111
|
+
onChange={(value) => updateProperty('disabled', value, component, onPropertyChange)}
|
|
112
|
+
/>
|
|
46
113
|
</div>
|
|
47
114
|
<div class="flex w-1/3 flex-col px-2">
|
|
48
115
|
<UI.Input
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type IUIComponentHandler, type UIComponent } from '../types';
|
|
2
|
-
import type { IFileInputProps } from './FileAttach.svelte';
|
|
1
|
+
import { type IFileAttachProps, type IUIComponentHandler, type UIComponent } from '../types';
|
|
3
2
|
type $$ComponentProps = {
|
|
4
3
|
component: UIComponent & {
|
|
5
|
-
properties: Partial<
|
|
4
|
+
properties: Partial<IFileAttachProps>;
|
|
6
5
|
};
|
|
7
6
|
onPropertyChange: (updates: Partial<{
|
|
8
7
|
properties?: string | object;
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
/* Обновление свойства */
|
|
36
|
-
const updateProperty = (path: string, value: string | object | boolean | number | RegExp
|
|
36
|
+
const updateProperty = (path: string, value: string | object | boolean | number | RegExp) => {
|
|
37
37
|
const newProperties = JSON.parse(JSON.stringify(component.properties))
|
|
38
38
|
if (path === 'regExp') {
|
|
39
39
|
try {
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
obj[parts[parts.length - 1]] = value
|
|
71
|
-
onPropertyChange({ properties: newProperties
|
|
71
|
+
onPropertyChange({ properties: newProperties })
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const handleOptionColorChange = (color: string) => {
|
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
options={VARIABLE_OPTIONS}
|
|
97
97
|
value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id)}
|
|
98
98
|
onUpdate={(value) => {
|
|
99
|
-
updateProperty('id', value.value as string
|
|
100
|
-
onPropertyChange({ eventHandler: { Variables: value.value as string } })
|
|
99
|
+
updateProperty('id', value.value as string)
|
|
100
|
+
onPropertyChange({ name: value.name?.split('—')[1].trim(), eventHandler: { Variables: value.value as string } })
|
|
101
101
|
}}
|
|
102
102
|
/>
|
|
103
103
|
<UI.Select
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
id = crypto.randomUUID(),
|
|
7
7
|
wrapperClass = '',
|
|
8
8
|
label = { name: '', class: '' },
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{ minNum: -100, maxNum: 100 },
|
|
13
|
-
{ minNum: -100, maxNum: 100 },
|
|
14
|
-
{ minNum: -100, maxNum: 100 },
|
|
9
|
+
value = $bindable([0, 0, 0, 0]),
|
|
10
|
+
axes = [
|
|
11
|
+
{ name: 'Roll', minNum: -100, maxNum: 100 },
|
|
12
|
+
{ name: 'Pitch', minNum: -100, maxNum: 100 },
|
|
13
|
+
{ name: 'Yaw', minNum: -100, maxNum: 100 },
|
|
15
14
|
],
|
|
15
|
+
buttonIcon,
|
|
16
16
|
onUpdate = () => {},
|
|
17
|
-
onClick = () => {},
|
|
18
17
|
}: IJoystickProps = $props()
|
|
19
18
|
|
|
20
19
|
const directions = [
|
|
@@ -23,8 +22,8 @@
|
|
|
23
22
|
angle: 30.5,
|
|
24
23
|
content: true,
|
|
25
24
|
onClick: () => {
|
|
26
|
-
if (value[2] + sensitivity >=
|
|
27
|
-
value[2] =
|
|
25
|
+
if (value[2] + sensitivity >= axes[2].maxNum) {
|
|
26
|
+
value[2] = axes[2].maxNum
|
|
28
27
|
onUpdate(value)
|
|
29
28
|
return
|
|
30
29
|
}
|
|
@@ -38,15 +37,15 @@
|
|
|
38
37
|
angle: 58,
|
|
39
38
|
content: false,
|
|
40
39
|
onClick: () => {
|
|
41
|
-
if (value[2] + sensitivity >=
|
|
42
|
-
value[2] =
|
|
40
|
+
if (value[2] + sensitivity >= axes[2].maxNum) {
|
|
41
|
+
value[2] = axes[2].maxNum
|
|
43
42
|
onUpdate(value)
|
|
44
43
|
} else {
|
|
45
44
|
value[2] = roundToClean(value[2] + sensitivity)
|
|
46
45
|
onUpdate(value)
|
|
47
46
|
}
|
|
48
|
-
if (value[1] - sensitivity <=
|
|
49
|
-
value[1] =
|
|
47
|
+
if (value[1] - sensitivity <= axes[1].minNum) {
|
|
48
|
+
value[1] = axes[1].minNum
|
|
50
49
|
onUpdate(value)
|
|
51
50
|
} else {
|
|
52
51
|
value[1] = roundToClean(value[1] - sensitivity)
|
|
@@ -59,8 +58,8 @@
|
|
|
59
58
|
angle: 122,
|
|
60
59
|
content: true,
|
|
61
60
|
onClick: () => {
|
|
62
|
-
if (value[1] - sensitivity <=
|
|
63
|
-
value[1] =
|
|
61
|
+
if (value[1] - sensitivity <= axes[1].minNum) {
|
|
62
|
+
value[1] = axes[1].minNum
|
|
64
63
|
onUpdate(value)
|
|
65
64
|
return
|
|
66
65
|
}
|
|
@@ -73,15 +72,15 @@
|
|
|
73
72
|
angle: 149.5,
|
|
74
73
|
content: false,
|
|
75
74
|
onClick: () => {
|
|
76
|
-
if (value[2] - sensitivity <=
|
|
77
|
-
value[2] =
|
|
75
|
+
if (value[2] - sensitivity <= axes[2].minNum) {
|
|
76
|
+
value[2] = axes[2].minNum
|
|
78
77
|
onUpdate(value)
|
|
79
78
|
} else {
|
|
80
79
|
value[2] = roundToClean(value[2] - sensitivity)
|
|
81
80
|
onUpdate(value)
|
|
82
81
|
}
|
|
83
|
-
if (value[1] - sensitivity <=
|
|
84
|
-
value[1] =
|
|
82
|
+
if (value[1] - sensitivity <= axes[1].minNum) {
|
|
83
|
+
value[1] = axes[1].minNum
|
|
85
84
|
onUpdate(value)
|
|
86
85
|
} else {
|
|
87
86
|
value[1] = roundToClean(value[1] - sensitivity)
|
|
@@ -94,8 +93,8 @@
|
|
|
94
93
|
angle: 212,
|
|
95
94
|
content: true,
|
|
96
95
|
onClick: () => {
|
|
97
|
-
if (value[2] - sensitivity <=
|
|
98
|
-
value[2] =
|
|
96
|
+
if (value[2] - sensitivity <= axes[2].minNum) {
|
|
97
|
+
value[2] = axes[2].minNum
|
|
99
98
|
onUpdate(value)
|
|
100
99
|
return
|
|
101
100
|
}
|
|
@@ -108,15 +107,15 @@
|
|
|
108
107
|
angle: 239,
|
|
109
108
|
content: false,
|
|
110
109
|
onClick: () => {
|
|
111
|
-
if (value[1] + sensitivity >=
|
|
112
|
-
value[1] =
|
|
110
|
+
if (value[1] + sensitivity >= axes[1].maxNum) {
|
|
111
|
+
value[1] = axes[1].maxNum
|
|
113
112
|
onUpdate(value)
|
|
114
113
|
} else {
|
|
115
114
|
value[1] = roundToClean(value[1] + sensitivity)
|
|
116
115
|
onUpdate(value)
|
|
117
116
|
}
|
|
118
|
-
if (value[2] - sensitivity <=
|
|
119
|
-
value[2] =
|
|
117
|
+
if (value[2] - sensitivity <= axes[2].minNum) {
|
|
118
|
+
value[2] = axes[2].minNum
|
|
120
119
|
onUpdate(value)
|
|
121
120
|
} else {
|
|
122
121
|
value[2] = roundToClean(value[2] - sensitivity)
|
|
@@ -129,8 +128,8 @@
|
|
|
129
128
|
angle: 301,
|
|
130
129
|
content: true,
|
|
131
130
|
onClick: () => {
|
|
132
|
-
if (value[1] + sensitivity >=
|
|
133
|
-
value[1] =
|
|
131
|
+
if (value[1] + sensitivity >= axes[1].maxNum) {
|
|
132
|
+
value[1] = axes[1].maxNum
|
|
134
133
|
onUpdate(value)
|
|
135
134
|
return
|
|
136
135
|
}
|
|
@@ -143,15 +142,15 @@
|
|
|
143
142
|
angle: 328,
|
|
144
143
|
content: false,
|
|
145
144
|
onClick: () => {
|
|
146
|
-
if (value[1] + sensitivity >=
|
|
147
|
-
value[1] =
|
|
145
|
+
if (value[1] + sensitivity >= axes[1].maxNum) {
|
|
146
|
+
value[1] = axes[1].maxNum
|
|
148
147
|
onUpdate(value)
|
|
149
148
|
} else {
|
|
150
149
|
value[1] = roundToClean(value[1] + sensitivity)
|
|
151
150
|
onUpdate(value)
|
|
152
151
|
}
|
|
153
|
-
if (value[2] + sensitivity >=
|
|
154
|
-
value[2] =
|
|
152
|
+
if (value[2] + sensitivity >= axes[2].maxNum) {
|
|
153
|
+
value[2] = axes[2].maxNum
|
|
155
154
|
onUpdate(value)
|
|
156
155
|
} else {
|
|
157
156
|
value[2] = roundToClean(value[2] + sensitivity)
|
|
@@ -206,16 +205,12 @@
|
|
|
206
205
|
onmouseenter={(e) => (e.currentTarget.style.backgroundColor = 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 20%)')}
|
|
207
206
|
onmouseleave={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-color)')}
|
|
208
207
|
>
|
|
209
|
-
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
>
|
|
216
|
-
{:else}
|
|
217
|
-
•
|
|
218
|
-
{/if}
|
|
208
|
+
<svg xmlns="http://www.w3.org/2000/svg" width={direction.content ? 32 : 16} height={direction.content ? 32 : 16} viewBox="0 0 24 24"
|
|
209
|
+
><path
|
|
210
|
+
fill="currentColor"
|
|
211
|
+
d="M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z"
|
|
212
|
+
/></svg
|
|
213
|
+
>
|
|
219
214
|
</span>
|
|
220
215
|
</button>
|
|
221
216
|
{/each}
|
|
@@ -237,22 +232,20 @@
|
|
|
237
232
|
class="z-20 flex size-20 items-center justify-center rounded-full bg-(--bg-color) shadow-[0_0_15px_rgb(0_0_0_/0.25)] transition hover:scale-103"
|
|
238
233
|
>
|
|
239
234
|
<button
|
|
240
|
-
class="flex size-18 cursor-pointer items-center justify-center rounded-full
|
|
241
|
-
|
|
235
|
+
class="flex size-18 cursor-pointer items-center justify-center rounded-full"
|
|
236
|
+
style="background: {value[3] == 1 ? 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)' : 'var(--bg-color)'}"
|
|
242
237
|
onclick={() => {
|
|
243
|
-
|
|
238
|
+
value[3] = value[3] == 0 ? 1 : 0
|
|
244
239
|
}}
|
|
245
|
-
><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"
|
|
246
|
-
><path
|
|
247
|
-
fill="currentColor"
|
|
248
|
-
d="M6 19h3v-5q0-.425.288-.712T10 13h4q.425 0 .713.288T15 14v5h3v-9l-6-4.5L6 10zm-2 0v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-4q-.425 0-.712-.288T13 20v-5h-2v5q0 .425-.288.713T10 21H6q-.825 0-1.412-.587T4 19m8-6.75"
|
|
249
|
-
/></svg
|
|
250
|
-
></button
|
|
251
240
|
>
|
|
241
|
+
{@html buttonIcon
|
|
242
|
+
? buttonIcon
|
|
243
|
+
: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor"d="M6 19h3v-5q0-.425.288-.712T10 13h4q.425 0 .713.288T15 14v5h3v-9l-6-4.5L6 10zm-2 0v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-4q-.425 0-.712-.288T13 20v-5h-2v5q0 .425-.288.713T10 21H6q-.825 0-1.412-.587T4 19m8-6.75"/></svg>'}
|
|
244
|
+
</button>
|
|
252
245
|
</div>
|
|
253
246
|
</div>
|
|
254
247
|
<!-- Боковые кнопки (ось roll) -->
|
|
255
|
-
{#if
|
|
248
|
+
{#if axes.length == 3}
|
|
256
249
|
<div
|
|
257
250
|
class="absolute flex h-15 w-65 items-center justify-between rounded-full shadow-[0_0_15px_rgb(0_0_0_/0.25)]"
|
|
258
251
|
style="background: color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)"
|
|
@@ -261,12 +254,12 @@
|
|
|
261
254
|
class="h-full cursor-pointer rounded-l-full px-3.5"
|
|
262
255
|
title=""
|
|
263
256
|
onclick={() => {
|
|
264
|
-
if (value[0] - sensitivity <=
|
|
265
|
-
value[0] =
|
|
257
|
+
if (value[0] - sensitivity <= axes[0].minNum) {
|
|
258
|
+
value[0] = axes[0].minNum
|
|
266
259
|
onUpdate(value)
|
|
267
260
|
return
|
|
268
261
|
}
|
|
269
|
-
value[0]
|
|
262
|
+
value[0] = roundToClean(value[0] - sensitivity)
|
|
270
263
|
onUpdate(value)
|
|
271
264
|
}}
|
|
272
265
|
onmouseenter={(e) => (e.currentTarget.style.backgroundColor = 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 30%)')}
|
|
@@ -284,12 +277,12 @@
|
|
|
284
277
|
class="h-full cursor-pointer rounded-r-full px-3.5"
|
|
285
278
|
title=""
|
|
286
279
|
onclick={() => {
|
|
287
|
-
if (value[0] + sensitivity >=
|
|
288
|
-
value[0] =
|
|
280
|
+
if (value[0] + sensitivity >= axes[0].maxNum) {
|
|
281
|
+
value[0] = axes[0].maxNum
|
|
289
282
|
onUpdate(value)
|
|
290
283
|
return
|
|
291
284
|
}
|
|
292
|
-
value[0]
|
|
285
|
+
value[0] = roundToClean(value[0] + sensitivity)
|
|
293
286
|
onUpdate(value)
|
|
294
287
|
}}
|
|
295
288
|
onmouseenter={(e) => (e.currentTarget.style.backgroundColor = 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 30%)')}
|
|
@@ -307,7 +300,7 @@
|
|
|
307
300
|
{/if}
|
|
308
301
|
</div>
|
|
309
302
|
|
|
310
|
-
<div class="
|
|
303
|
+
<div class="flex items-center md:absolute md:left-[calc(50%+120px)]">
|
|
311
304
|
<div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class="flex h-full flex-col justify-center rounded-full p-10">
|
|
312
305
|
{#each sensitivityOptions as option, index}
|
|
313
306
|
<button
|
|
@@ -338,15 +331,15 @@
|
|
|
338
331
|
</div>
|
|
339
332
|
|
|
340
333
|
<div>
|
|
341
|
-
{#each
|
|
342
|
-
<h5 class=
|
|
334
|
+
{#each axes as axe, index}
|
|
335
|
+
<h5 class="w-full px-4 text-center">{axe.name}</h5>
|
|
343
336
|
<input
|
|
344
337
|
class={`w-20 rounded-2xl border border-(--border-color) px-4 py-1 text-center transition-all duration-300 outline-none
|
|
345
338
|
hover:shadow-md
|
|
346
339
|
[&::-webkit-inner-spin-button]:hidden
|
|
347
340
|
[&::-webkit-outer-spin-button]:hidden`}
|
|
348
341
|
style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
|
|
349
|
-
value={value[index]}
|
|
342
|
+
value={value[axes.length == 3 ? index : index + 1]}
|
|
350
343
|
id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
|
|
351
344
|
readonly
|
|
352
345
|
/>
|
|
@@ -354,4 +347,3 @@
|
|
|
354
347
|
</div>
|
|
355
348
|
</div>
|
|
356
349
|
</div>
|
|
357
|
-
<!-- sensitivity == 0.01 ? value[num].toFixed(2) : sensitivity == 0.1 ? value[num].toFixed(1) : value[num].toFixed(0) -->
|
|
@@ -31,185 +31,317 @@
|
|
|
31
31
|
},
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>('DeviceVariables')
|
|
35
|
-
let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
|
|
36
|
-
|
|
37
34
|
const initialAlign = $derived(
|
|
38
35
|
$optionsStore.TEXT_ALIGN_OPTIONS.find((a) =>
|
|
39
36
|
(a.value as string).includes(component.properties.label?.class?.split(' ').find((cls: string) => cls.startsWith('text-'))),
|
|
40
37
|
),
|
|
41
38
|
)
|
|
39
|
+
|
|
40
|
+
const initialColor = $derived(
|
|
41
|
+
$optionsStore.COLOR_OPTIONS.find((c) =>
|
|
42
|
+
(c.value as string).includes(component.properties.wrapperClass?.split(' ').find((cls: string) => cls.startsWith('bg-'))),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
42
45
|
</script>
|
|
43
46
|
|
|
44
47
|
{#if forConstructor}
|
|
45
|
-
<div
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
label={{ name: $t('constructor.props.header') }}
|
|
50
|
-
type="buttons"
|
|
51
|
-
value={Header}
|
|
52
|
-
options={$optionsStore.HEADER_OPTIONS}
|
|
53
|
-
onUpdate={(option) => {
|
|
54
|
-
Header = { ...option }
|
|
55
|
-
onPropertyChange({ eventHandler: { Header: Header.value as string } })
|
|
56
|
-
}}
|
|
57
|
-
/>
|
|
58
|
-
{#if Header.value === 'SET'}
|
|
48
|
+
<div>
|
|
49
|
+
<div class="relative flex flex-row items-start justify-center">
|
|
50
|
+
<!-- Сообщение для отправки в ws по нажатию кнопки -->
|
|
51
|
+
<div class="flex w-1/3 flex-col items-center px-2">
|
|
59
52
|
<UI.Select
|
|
60
|
-
label={{ name: $t('constructor.props.
|
|
53
|
+
label={{ name: $t('constructor.props.header') }}
|
|
61
54
|
type="buttons"
|
|
62
|
-
value={
|
|
63
|
-
|
|
64
|
-
options={$optionsStore.FULL_ARGUMENT_OPTION}
|
|
55
|
+
value={Header}
|
|
56
|
+
options={$optionsStore.HEADER_OPTIONS}
|
|
65
57
|
onUpdate={(option) => {
|
|
66
|
-
|
|
58
|
+
Header = { ...option }
|
|
59
|
+
onPropertyChange({ eventHandler: { Header: Header.value as string } })
|
|
67
60
|
}}
|
|
68
61
|
/>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
{#if Header.value === 'SET'}
|
|
63
|
+
<UI.Select
|
|
64
|
+
label={{ name: $t('constructor.props.argument') }}
|
|
65
|
+
type="buttons"
|
|
66
|
+
value={$optionsStore.FULL_ARGUMENT_OPTION.find((h) => h.value === component.eventHandler.Argument) ??
|
|
67
|
+
$optionsStore.FULL_ARGUMENT_OPTION.find((h) => h.value === '')}
|
|
68
|
+
options={$optionsStore.FULL_ARGUMENT_OPTION}
|
|
69
|
+
onUpdate={(option) => {
|
|
70
|
+
onPropertyChange({ eventHandler: { Argument: option.value as string } })
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
{/if}
|
|
80
74
|
<UI.Input
|
|
81
|
-
label={{ name: $t('constructor.props.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
maxlength={
|
|
85
|
-
|
|
75
|
+
label={{ name: Header.value !== 'SET' ? $t('constructor.props.argument') : '' }}
|
|
76
|
+
wrapperClass="{Header.value === 'SET' ? 'mt-1' : ''} "
|
|
77
|
+
value={component.eventHandler.Argument}
|
|
78
|
+
maxlength={32}
|
|
79
|
+
disabled={Header.value === 'SET' && (component.eventHandler.Argument == 'Save' || component.eventHandler.Argument == 'NoSave')}
|
|
80
|
+
help={{ info: $t('constructor.props.argument.info'), autocomplete: 'on', regExp: /^[a-zA-Z0-9\-_]{0,32}$/ }}
|
|
81
|
+
onUpdate={(value) => onPropertyChange({ eventHandler: { Argument: value as string } })}
|
|
86
82
|
/>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
95
|
-
onUpdate={(option) => onPropertyChange({ access: option.value })}
|
|
96
|
-
/>
|
|
97
|
-
<UI.Input
|
|
98
|
-
label={{ name: $t('constructor.props.label') }}
|
|
99
|
-
value={component.properties.label.name}
|
|
100
|
-
onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
|
|
101
|
-
/>
|
|
102
|
-
<UI.Select
|
|
103
|
-
label={{ name: $t('constructor.props.align') }}
|
|
104
|
-
type="buttons"
|
|
105
|
-
value={initialAlign}
|
|
106
|
-
options={$optionsStore.TEXT_ALIGN_OPTIONS}
|
|
107
|
-
onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
|
|
108
|
-
/>
|
|
109
|
-
</div>
|
|
110
|
-
<div class="flex w-1/3 flex-col px-2">
|
|
111
|
-
<div class="mt-6 flex gap-2">
|
|
112
|
-
<UI.Button content={{ name: $t('constructor.props.markerIcon') }} onClick={() => (showIconLib = true)} />
|
|
113
|
-
{#if showIconLib}
|
|
114
|
-
<Modal bind:isOpen={showIconLib} wrapperClass="w-130">
|
|
115
|
-
{#snippet main()}
|
|
116
|
-
<div class="grid grid-cols-3">
|
|
117
|
-
{#each ICONS as category}
|
|
118
|
-
<div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
|
|
119
|
-
<div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
|
|
120
|
-
<div class="grid grid-cols-3 place-items-center gap-2">
|
|
121
|
-
{#each category[1] as icon}
|
|
122
|
-
<button
|
|
123
|
-
class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
|
|
124
|
-
onclick={() => {
|
|
125
|
-
updateProperty('markerIcon', icon as string, component, onPropertyChange)
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
{@html icon}
|
|
129
|
-
</button>{/each}
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
{/each}
|
|
133
|
-
</div>
|
|
134
|
-
{/snippet}
|
|
135
|
-
</Modal>
|
|
136
|
-
{/if}
|
|
137
|
-
{#if component.properties.markerIcon}
|
|
138
|
-
<Button
|
|
139
|
-
wrapperClass="w-8.5 "
|
|
140
|
-
componentClass="p-0.5 bg-red"
|
|
141
|
-
content={{ icon: CrossIcon }}
|
|
142
|
-
onClick={() => {
|
|
143
|
-
updateProperty('markerIcon', '', component, onPropertyChange)
|
|
144
|
-
}}
|
|
83
|
+
{#if (component.eventHandler.Argument !== 'Save' && component.eventHandler.Argument !== 'NoSave') || Header.value === 'SET'}
|
|
84
|
+
<UI.Input
|
|
85
|
+
label={{ name: $t('constructor.props.value') }}
|
|
86
|
+
value={component.eventHandler.Value}
|
|
87
|
+
help={{ info: $t('constructor.props.value.info') }}
|
|
88
|
+
maxlength={500}
|
|
89
|
+
onUpdate={(value) => onPropertyChange({ eventHandler: { Value: value as string } })}
|
|
145
90
|
/>
|
|
146
91
|
{/if}
|
|
147
92
|
</div>
|
|
93
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
94
|
+
<UI.Select
|
|
95
|
+
label={{ name: $t('constructor.props.access') }}
|
|
96
|
+
type="buttons"
|
|
97
|
+
options={$optionsStore.ACCESS_OPTION}
|
|
98
|
+
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
99
|
+
onUpdate={(option) => onPropertyChange({ access: option.value })}
|
|
100
|
+
/>
|
|
101
|
+
<UI.Input
|
|
102
|
+
label={{ name: $t('constructor.props.label') }}
|
|
103
|
+
value={component.properties.label.name}
|
|
104
|
+
onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
|
|
105
|
+
/>
|
|
106
|
+
<UI.Input
|
|
107
|
+
label={{ name: $t('constructor.props.label.class') }}
|
|
108
|
+
value={component.properties.label.class}
|
|
109
|
+
onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
113
|
+
<div class="mt-6 flex gap-2">
|
|
114
|
+
<UI.Button content={{ name: $t('constructor.props.buttonIcon') }} onClick={() => (showIconLib = true)} />
|
|
115
|
+
{#if showIconLib}
|
|
116
|
+
<Modal bind:isOpen={showIconLib} wrapperClass="w-130">
|
|
117
|
+
{#snippet main()}
|
|
118
|
+
<div class="grid grid-cols-3">
|
|
119
|
+
{#each ICONS as category}
|
|
120
|
+
<div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
|
|
121
|
+
<div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
|
|
122
|
+
<div class="grid grid-cols-3 place-items-center gap-2">
|
|
123
|
+
{#each category[1] as icon}
|
|
124
|
+
<button
|
|
125
|
+
class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
|
|
126
|
+
onclick={() => {
|
|
127
|
+
updateProperty('buttonIcon', icon as string, component, onPropertyChange)
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
{@html icon}
|
|
131
|
+
</button>{/each}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
{/each}
|
|
135
|
+
</div>
|
|
136
|
+
{/snippet}
|
|
137
|
+
</Modal>
|
|
138
|
+
{/if}
|
|
139
|
+
{#if component.properties.buttonIcon}
|
|
140
|
+
<Button
|
|
141
|
+
wrapperClass="w-8.5 "
|
|
142
|
+
componentClass="p-0.5 bg-red"
|
|
143
|
+
content={{ icon: CrossIcon }}
|
|
144
|
+
onClick={() => {
|
|
145
|
+
updateProperty('buttonIcon', '', component, onPropertyChange)
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
{/if}
|
|
149
|
+
</div>
|
|
150
|
+
<UI.Input
|
|
151
|
+
label={{ name: $t('constructor.props.joystick.axes') }}
|
|
152
|
+
value={component.properties.axes.map((axe: any) => axe.name).join(' ')}
|
|
153
|
+
help={{ info: $t('constructor.props.joystick.axes.info'), regExp: /^[\p{L}0-9\-_":{}]+ +[\p{L}0-9\-_":{}]+(?: +[\p{L}0-9\-_":{}]+)?$/u }}
|
|
154
|
+
maxlength={100}
|
|
155
|
+
onUpdate={(value) => {
|
|
156
|
+
const stringValue = value as string
|
|
157
|
+
const spaceCount = (stringValue.match(/\s/g) || []).length
|
|
158
|
+
if (spaceCount > 2) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
const parts = stringValue.trim().split(/\s+/)
|
|
162
|
+
updateProperty(
|
|
163
|
+
'axes',
|
|
164
|
+
parts.map((a: any, index: number) => {
|
|
165
|
+
let axeIndex = parts.length == 2 && component.properties.axes.length === 3 ? index + 1 : index
|
|
166
|
+
return {
|
|
167
|
+
name: a,
|
|
168
|
+
minNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].minNum : -100,
|
|
169
|
+
maxNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].maxNum : 100,
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
component,
|
|
173
|
+
onPropertyChange,
|
|
174
|
+
)
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
<UI.Select
|
|
178
|
+
wrapperClass="!h-14"
|
|
179
|
+
label={{ name: $t('constructor.props.colors') }}
|
|
180
|
+
type="buttons"
|
|
181
|
+
options={$optionsStore.COLOR_OPTIONS}
|
|
182
|
+
value={initialColor}
|
|
183
|
+
onUpdate={(option) => updateProperty('wrapperClass', twMerge(component.properties.wrapperClass, option.value), component, onPropertyChange)}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="mt-2 flex w-full justify-around gap-2">
|
|
188
|
+
{#each component.properties.axes as axe, index}
|
|
189
|
+
<div class="flex items-start gap-2">
|
|
190
|
+
<h5 class="mt-1">{axe.name}</h5>
|
|
191
|
+
<UI.Slider
|
|
192
|
+
type="range"
|
|
193
|
+
number={{ minNum: -360, maxNum: 360, step: 10 }}
|
|
194
|
+
value={[component.properties.axes[index].minNum, component.properties.axes[index].maxNum]}
|
|
195
|
+
onUpdate={(value) => {
|
|
196
|
+
if (Array.isArray(value)) {
|
|
197
|
+
const axes = component.properties.axes
|
|
198
|
+
|
|
199
|
+
updateProperty(
|
|
200
|
+
'axes',
|
|
201
|
+
axes.map((a: any, i: number) => (i === index ? { ...a, minNum: value[0], maxNum: value[1] } : a)),
|
|
202
|
+
component,
|
|
203
|
+
onPropertyChange,
|
|
204
|
+
)
|
|
205
|
+
console.log(component.properties.axes)
|
|
206
|
+
}
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
{/each}
|
|
148
211
|
</div>
|
|
149
212
|
</div>
|
|
150
213
|
{:else}
|
|
151
|
-
<div
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
214
|
+
<div>
|
|
215
|
+
<div class="relative mb-2 flex flex-row items-start justify-center">
|
|
216
|
+
<!-- Сообщение для отправки в ws по нажатию кнопки -->
|
|
217
|
+
<div class="flex w-1/3 flex-col items-center px-2">
|
|
218
|
+
<UI.Input
|
|
219
|
+
label={{ name: $t('constructor.props.id') }}
|
|
220
|
+
value={component.properties.id}
|
|
221
|
+
onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
|
|
222
|
+
/>
|
|
223
|
+
<UI.Select
|
|
224
|
+
label={{ name: $t('constructor.props.access') }}
|
|
225
|
+
type="buttons"
|
|
226
|
+
options={$optionsStore.ACCESS_OPTION}
|
|
227
|
+
value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
|
|
228
|
+
onUpdate={(option) => onPropertyChange({ access: option.value })}
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
232
|
+
<UI.Input
|
|
233
|
+
label={{ name: $t('constructor.props.label') }}
|
|
234
|
+
value={component.properties.label.name}
|
|
235
|
+
onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
|
|
236
|
+
/>
|
|
237
|
+
<UI.Select
|
|
238
|
+
label={{ name: $t('constructor.props.align') }}
|
|
239
|
+
type="buttons"
|
|
240
|
+
value={initialAlign}
|
|
241
|
+
options={$optionsStore.TEXT_ALIGN_OPTIONS}
|
|
242
|
+
onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
246
|
+
<div class="mt-6 flex gap-2">
|
|
247
|
+
<UI.Button content={{ name: $t('constructor.props.buttonIcon') }} onClick={() => (showIconLib = true)} />
|
|
248
|
+
{#if showIconLib}
|
|
249
|
+
<Modal bind:isOpen={showIconLib} wrapperClass="w-130">
|
|
250
|
+
{#snippet main()}
|
|
251
|
+
<div class="grid grid-cols-3">
|
|
252
|
+
{#each ICONS as category}
|
|
253
|
+
<div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
|
|
254
|
+
<div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
|
|
255
|
+
<div class="grid grid-cols-3 place-items-center gap-2">
|
|
256
|
+
{#each category[1] as icon}
|
|
257
|
+
<button
|
|
258
|
+
class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
|
|
259
|
+
onclick={() => {
|
|
260
|
+
updateProperty('buttonIcon', icon as string, component, onPropertyChange)
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{@html icon}
|
|
264
|
+
</button>{/each}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
{/each}
|
|
268
|
+
</div>
|
|
269
|
+
{/snippet}
|
|
270
|
+
</Modal>
|
|
271
|
+
{/if}
|
|
272
|
+
{#if component.properties.buttonIcon}
|
|
273
|
+
<Button
|
|
274
|
+
wrapperClass="w-8.5 "
|
|
275
|
+
componentClass="p-0.5 bg-red"
|
|
276
|
+
content={{ icon: CrossIcon }}
|
|
277
|
+
onClick={() => {
|
|
278
|
+
updateProperty('buttonIcon', '', component, onPropertyChange)
|
|
279
|
+
}}
|
|
280
|
+
/>
|
|
281
|
+
{/if}
|
|
282
|
+
</div>
|
|
283
|
+
<UI.Input
|
|
284
|
+
label={{ name: $t('constructor.props.joystick.axes') }}
|
|
285
|
+
value={component.properties.axes.map((axe: any) => axe.name).join(' ')}
|
|
286
|
+
help={{ info: $t('constructor.props.joystick.axes.info'), regExp: /^[\p{L}0-9\-_":{}]+ +[\p{L}0-9\-_":{}]+(?: +[\p{L}0-9\-_":{}]+)?$/u }}
|
|
287
|
+
maxlength={100}
|
|
288
|
+
onUpdate={(value) => {
|
|
289
|
+
const stringValue = value as string
|
|
290
|
+
const spaceCount = (stringValue.match(/\s/g) || []).length
|
|
291
|
+
if (spaceCount > 2) {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
const parts = stringValue.trim().split(/\s+/)
|
|
295
|
+
updateProperty(
|
|
296
|
+
'axes',
|
|
297
|
+
parts.map((a: any, index: number) => {
|
|
298
|
+
let axeIndex = parts.length == 2 && component.properties.axes.length === 3 ? index + 1 : index
|
|
299
|
+
return {
|
|
300
|
+
name: a,
|
|
301
|
+
minNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].minNum : -100,
|
|
302
|
+
maxNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].maxNum : 100,
|
|
303
|
+
}
|
|
304
|
+
}),
|
|
305
|
+
component,
|
|
306
|
+
onPropertyChange,
|
|
307
|
+
)
|
|
308
|
+
}}
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
<UI.Select
|
|
312
|
+
wrapperClass="!h-14"
|
|
313
|
+
label={{ name: $t('constructor.props.colors') }}
|
|
314
|
+
type="buttons"
|
|
315
|
+
options={$optionsStore.COLOR_OPTIONS}
|
|
316
|
+
value={initialColor}
|
|
317
|
+
onUpdate={(option) => updateProperty('wrapperClass', twMerge(component.properties.wrapperClass, option.value), component, onPropertyChange)}
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
173
320
|
</div>
|
|
321
|
+
<div class="mt-2 flex w-full justify-around gap-2">
|
|
322
|
+
{#each component.properties.axes as axe, index}
|
|
323
|
+
<div class="flex items-start gap-2">
|
|
324
|
+
<h5 class="mt-1">{axe.name}</h5>
|
|
325
|
+
<UI.Slider
|
|
326
|
+
type="range"
|
|
327
|
+
number={{ minNum: -360, maxNum: 360, step: 10 }}
|
|
328
|
+
value={[component.properties.axes[index].minNum, component.properties.axes[index].maxNum]}
|
|
329
|
+
onUpdate={(value) => {
|
|
330
|
+
if (Array.isArray(value)) {
|
|
331
|
+
const axes = component.properties.axes
|
|
174
332
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
|
|
184
|
-
<div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
|
|
185
|
-
<div class="grid grid-cols-3 place-items-center gap-2">
|
|
186
|
-
{#each category[1] as icon}
|
|
187
|
-
<button
|
|
188
|
-
class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
|
|
189
|
-
onclick={() => {
|
|
190
|
-
updateProperty('markerIcon', icon as string, component, onPropertyChange)
|
|
191
|
-
}}
|
|
192
|
-
>
|
|
193
|
-
{@html icon}
|
|
194
|
-
</button>{/each}
|
|
195
|
-
</div>
|
|
196
|
-
</div>
|
|
197
|
-
{/each}
|
|
198
|
-
</div>
|
|
199
|
-
{/snippet}
|
|
200
|
-
</Modal>
|
|
201
|
-
{/if}
|
|
202
|
-
{#if component.properties.markerIcon}
|
|
203
|
-
<Button
|
|
204
|
-
wrapperClass="w-8.5 "
|
|
205
|
-
componentClass="p-0.5 bg-red"
|
|
206
|
-
content={{ icon: CrossIcon }}
|
|
207
|
-
onClick={() => {
|
|
208
|
-
updateProperty('markerIcon', '', component, onPropertyChange)
|
|
333
|
+
updateProperty(
|
|
334
|
+
'axes',
|
|
335
|
+
axes.map((a: any, i: number) => (i === index ? { ...a, minNum: value[0], maxNum: value[1] } : a)),
|
|
336
|
+
component,
|
|
337
|
+
onPropertyChange,
|
|
338
|
+
)
|
|
339
|
+
console.log(component.properties.axes)
|
|
340
|
+
}
|
|
209
341
|
}}
|
|
210
342
|
/>
|
|
211
|
-
|
|
212
|
-
|
|
343
|
+
</div>
|
|
344
|
+
{/each}
|
|
213
345
|
</div>
|
|
214
346
|
</div>
|
|
215
347
|
{/if}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
wrapperClass = '',
|
|
9
9
|
label = { name: '', class: '' },
|
|
10
10
|
value = $bindable(0),
|
|
11
|
+
type = 'vertical',
|
|
11
12
|
number = {
|
|
12
13
|
minNum: 0,
|
|
13
14
|
maxNum: 100,
|
|
@@ -38,17 +39,41 @@
|
|
|
38
39
|
return (((Math.min(Math.max(value, min), max) - min) / (max - min)) * 100) as number
|
|
39
40
|
}
|
|
40
41
|
})
|
|
42
|
+
|
|
43
|
+
const roundToClean = (num: number): number => {
|
|
44
|
+
if (Number.isInteger(num)) return num
|
|
45
|
+
|
|
46
|
+
const rounded1 = Number(num.toFixed(1))
|
|
47
|
+
if (Math.abs(rounded1 - num) < 1e-10) return rounded1
|
|
48
|
+
|
|
49
|
+
const rounded2 = Number(num.toFixed(2))
|
|
50
|
+
if (Math.abs(rounded2 - num) < 1e-10) return rounded2
|
|
51
|
+
|
|
52
|
+
return rounded2
|
|
53
|
+
}
|
|
41
54
|
</script>
|
|
42
55
|
|
|
43
|
-
<div
|
|
56
|
+
<div
|
|
57
|
+
id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
|
|
58
|
+
class={twMerge(`relative flex ${type == 'vertical' ? 'h-full' : ''} w-full flex-col items-center`, wrapperClass)}
|
|
59
|
+
>
|
|
44
60
|
{#if label.name}
|
|
45
61
|
<h5 class={twMerge(` w-full px-4 text-center`, label.class)}>{label.name}</h5>
|
|
46
62
|
{/if}
|
|
47
63
|
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
{#if type == 'vertical'}
|
|
65
|
+
<div class="flex h-full w-fit min-w-16 flex-col items-center gap-2 rounded-full bg-(--bg-color) px-2">
|
|
66
|
+
<div class="relative my-auto h-[80%] w-[70%] rounded-full bg-(--back-color)/40">
|
|
67
|
+
<div class="absolute bottom-0 left-0 flex w-full rounded-full bg-(--field-color)" style="height: {progressPercent()}%;"></div>
|
|
68
|
+
</div>
|
|
69
|
+
<span class="m-auto font-semibold">{roundToClean(Number(numericValue))}{number.units}</span>
|
|
70
|
+
</div>
|
|
71
|
+
{:else}
|
|
72
|
+
<div class="flex h-7 w-full items-center gap-2 rounded-full bg-(--bg-color) px-2">
|
|
73
|
+
<span class="m-auto font-semibold">{roundToClean(Number(numericValue))}{number.units}</span>
|
|
74
|
+
<div class="relative my-auto h-3.5 w-[85%] rounded-full bg-(--back-color)/40">
|
|
75
|
+
<div class="absolute top-0 left-0 flex h-full rounded-full bg-(--field-color)" style="width: {progressPercent()}%;"></div>
|
|
76
|
+
</div>
|
|
52
77
|
</div>
|
|
53
|
-
|
|
78
|
+
{/if}
|
|
54
79
|
</div>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
let activeRound: 'floor' | 'ceil' = $state('floor')
|
|
29
29
|
|
|
30
|
-
let centerNum = $derived(lowerValue + Math[activeRound]((upperValue - lowerValue) / 2 / number.step) * number.step)
|
|
30
|
+
let centerNum = $derived(lowerValue + Math[activeRound](Math.abs(upperValue - lowerValue) / 2 / number.step) * number.step)
|
|
31
31
|
|
|
32
32
|
$effect(() => {
|
|
33
33
|
if (value === undefined || value === null) {
|
|
@@ -105,13 +105,12 @@
|
|
|
105
105
|
onmousedown={() => (activeRound = 'ceil')}
|
|
106
106
|
{disabled}
|
|
107
107
|
class={twMerge(
|
|
108
|
-
`basis-[calc(${(centerNum / number.maxNum) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
|
|
108
|
+
`basis-[calc(${(Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
|
|
109
109
|
accent-(--back-color)
|
|
110
110
|
[&::-webkit-slider-runnable-track]:rounded-l-full
|
|
111
111
|
[&::-webkit-slider-runnable-track]:bg-(--gray-color)
|
|
112
112
|
[&::-webkit-slider-runnable-track]:px-2
|
|
113
113
|
[&::-webkit-slider-thumb]:relative
|
|
114
|
-
[&::-webkit-slider-thumb]:z-100
|
|
115
114
|
[&::-webkit-slider-thumb]:size-4
|
|
116
115
|
[&::-webkit-slider-thumb]:cursor-pointer
|
|
117
116
|
[&::-webkit-slider-thumb]:rounded-full
|
|
@@ -134,7 +133,7 @@
|
|
|
134
133
|
`[&::-moz-range-thumb]:shadow-[calc(100rem+0.5rem)_0_0_100rem]
|
|
135
134
|
[&::-webkit-slider-thumb]:shadow-[calc(100rem+0.5rem)_0_0_100rem]`,
|
|
136
135
|
)}
|
|
137
|
-
style="color: var(--bg-color); flex-basis: {`calc(${(centerNum / number.maxNum) * 100}% + 2rem + 5px)`};"
|
|
136
|
+
style="color: var(--bg-color); flex-basis: {`calc(${(Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}% + 2rem + 5px)`};"
|
|
138
137
|
/>
|
|
139
138
|
<input
|
|
140
139
|
type="range"
|
|
@@ -152,13 +151,12 @@
|
|
|
152
151
|
onmousedown={() => (activeRound = 'floor')}
|
|
153
152
|
{disabled}
|
|
154
153
|
class={twMerge(
|
|
155
|
-
`basis-[calc(${100 - (centerNum / number.maxNum) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
|
|
154
|
+
`basis-[calc(${100 - (Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
|
|
156
155
|
accent-(--back-color)
|
|
157
156
|
[&::-webkit-slider-runnable-track]:rounded-r-full
|
|
158
157
|
[&::-webkit-slider-runnable-track]:bg-(--gray-color)
|
|
159
158
|
[&::-webkit-slider-runnable-track]:px-2
|
|
160
159
|
[&::-webkit-slider-thumb]:relative
|
|
161
|
-
[&::-webkit-slider-thumb]:z-100
|
|
162
160
|
[&::-webkit-slider-thumb]:size-4
|
|
163
161
|
[&::-webkit-slider-thumb]:cursor-pointer
|
|
164
162
|
[&::-webkit-slider-thumb]:rounded-full
|
|
@@ -181,7 +179,7 @@
|
|
|
181
179
|
`[&::-moz-range-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]
|
|
182
180
|
[&::-webkit-slider-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]`,
|
|
183
181
|
)}
|
|
184
|
-
style="color: var(--bg-color); flex-basis: {`calc(${(1 - centerNum / number.maxNum) * 100}% + 2rem + 5px)`};"
|
|
182
|
+
style="color: var(--bg-color); flex-basis: {`calc(${(1 - Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}% + 2rem + 5px)`};"
|
|
185
183
|
/>
|
|
186
184
|
</div>
|
|
187
185
|
{:else}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { default as ButtonProps } from './Button/ButtonProps.svelte';
|
|
|
5
5
|
export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
|
|
6
6
|
export { default as ColorPickerProps } from './ColorPicker/ColorPickerProps.svelte';
|
|
7
7
|
export { default as FileAttach } from './FileAttach/FileAttach.svelte';
|
|
8
|
+
export { default as FileAttachProps } from './FileAttach/FileAttachProps.svelte';
|
|
8
9
|
export { default as Graph } from './Graph/Graph.svelte';
|
|
9
10
|
export { default as GraphProps } from './Graph/GraphProps.svelte';
|
|
10
11
|
export { default as Input } from './Input/Input.svelte';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { default as ButtonProps } from './Button/ButtonProps.svelte';
|
|
|
6
6
|
export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
|
|
7
7
|
export { default as ColorPickerProps } from './ColorPicker/ColorPickerProps.svelte';
|
|
8
8
|
export { default as FileAttach } from './FileAttach/FileAttach.svelte';
|
|
9
|
+
export { default as FileAttachProps } from './FileAttach/FileAttachProps.svelte';
|
|
9
10
|
export { default as Graph } from './Graph/Graph.svelte';
|
|
10
11
|
export { default as GraphProps } from './Graph/GraphProps.svelte';
|
|
11
12
|
export { default as Input } from './Input/Input.svelte';
|
|
@@ -73,6 +73,7 @@ const translations = {
|
|
|
73
73
|
'constructor.props.image': 'Фоновое изображение',
|
|
74
74
|
'constructor.props.labelicon': 'Иконка заголовка',
|
|
75
75
|
'constructor.props.markerIcon': 'Иконка маркера',
|
|
76
|
+
'constructor.props.buttonIcon': 'Иконка кнопки',
|
|
76
77
|
'constructor.props.removeimage': 'Удалить изображение',
|
|
77
78
|
'constructor.props.name': 'Текст',
|
|
78
79
|
'constructor.props.height': 'Высота',
|
|
@@ -127,6 +128,8 @@ const translations = {
|
|
|
127
128
|
'constructor.props.bitmode': 'Битовый режим',
|
|
128
129
|
'constructor.props.access': 'Доступ (не для владельца)',
|
|
129
130
|
'constructor.props.map.timeout': 'Таймаут маркеров:',
|
|
131
|
+
'constructor.props.joystick.axes': 'Названия осей',
|
|
132
|
+
'constructor.props.joystick.axes.info': 'Поле для ввода названий осей, разделенных пробелами (2 или 3 названия)',
|
|
130
133
|
'constructor.props.table.columns': 'Колонки таблицы',
|
|
131
134
|
'constructor.props.table.columns.key': 'Ключ',
|
|
132
135
|
'constructor.props.table.columns.label': 'Название колонки',
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { Writable } from 'svelte/store';
|
|
3
|
-
import type { IFileInputProps } from './FileAttach/FileAttach.svelte';
|
|
4
3
|
export declare const updateProperty: (path: string, value: string | number | boolean | object | string[], component: UIComponent & {
|
|
5
4
|
properties: Partial<UIComponent["properties"]>;
|
|
6
5
|
}, onPropertyChange: (updates: Partial<{
|
|
@@ -17,7 +16,7 @@ export declare const updateComponent: (component: UIComponent, updates: Partial<
|
|
|
17
16
|
}>) => {
|
|
18
17
|
access: "full" | "viewOnly" | "hidden" | undefined;
|
|
19
18
|
name: string | undefined;
|
|
20
|
-
properties:
|
|
19
|
+
properties: IAccordionProps | IButtonProps | IInputProps | ISelectProps<unknown> | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps | IFileAttachProps | IJoystickProps | IMapProps;
|
|
21
20
|
eventHandler: IUIComponentHandler | undefined;
|
|
22
21
|
id: string;
|
|
23
22
|
type: "Button" | "Accordion" | "Input" | "Select" | "Switch" | "ColorPicker" | "Slider" | "TextField" | "Joystick" | "ProgressBar" | "Graph" | "Table" | "Tabs" | "FileAttach" | "Map";
|
|
@@ -29,7 +28,7 @@ export interface UIComponent {
|
|
|
29
28
|
name?: string;
|
|
30
29
|
access?: 'full' | 'viewOnly' | 'hidden';
|
|
31
30
|
type: 'Button' | 'Accordion' | 'Input' | 'Select' | 'Switch' | 'ColorPicker' | 'Slider' | 'TextField' | 'Joystick' | 'ProgressBar' | 'Graph' | 'Table' | 'Tabs' | 'FileAttach' | 'Map';
|
|
32
|
-
properties: IAccordionProps | IButtonProps | IInputProps | ISelectProps | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps |
|
|
31
|
+
properties: IAccordionProps | IButtonProps | IInputProps | ISelectProps | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps | IFileAttachProps | IJoystickProps | IMapProps;
|
|
33
32
|
position: Position;
|
|
34
33
|
parentId: string;
|
|
35
34
|
eventHandler?: IUIComponentHandler;
|
|
@@ -206,6 +205,7 @@ export interface IProgressBarProps {
|
|
|
206
205
|
maxNum?: number;
|
|
207
206
|
units?: string;
|
|
208
207
|
};
|
|
208
|
+
type?: 'horizontal' | 'vertical';
|
|
209
209
|
wrapperClass?: string;
|
|
210
210
|
}
|
|
211
211
|
export interface IGraphDataObject {
|
|
@@ -305,14 +305,14 @@ export interface IJoystickProps {
|
|
|
305
305
|
name?: string;
|
|
306
306
|
class?: string;
|
|
307
307
|
};
|
|
308
|
-
axesName?: [string, string, string?];
|
|
309
308
|
value?: number[];
|
|
310
|
-
|
|
309
|
+
axes?: {
|
|
310
|
+
name: string;
|
|
311
311
|
minNum: number;
|
|
312
312
|
maxNum: number;
|
|
313
313
|
}[];
|
|
314
|
+
buttonIcon?: string;
|
|
314
315
|
onUpdate?: (value: number[]) => void;
|
|
315
|
-
onClick?: (value: number[]) => void;
|
|
316
316
|
}
|
|
317
317
|
export interface IDeviceGNSS {
|
|
318
318
|
NavLat: number;
|
|
@@ -332,3 +332,22 @@ export interface IMapProps {
|
|
|
332
332
|
data: IDeviceGNSS | null;
|
|
333
333
|
markerIcon?: string;
|
|
334
334
|
}
|
|
335
|
+
export interface IFileAttachProps {
|
|
336
|
+
id?: string;
|
|
337
|
+
wrapperClass?: string;
|
|
338
|
+
label?: {
|
|
339
|
+
name?: string;
|
|
340
|
+
class?: string;
|
|
341
|
+
};
|
|
342
|
+
type?: 'file' | 'image';
|
|
343
|
+
accept?: string;
|
|
344
|
+
imageSize?: {
|
|
345
|
+
height?: string;
|
|
346
|
+
width?: string;
|
|
347
|
+
fitMode?: 'cover' | 'contain';
|
|
348
|
+
form?: 'square' | 'circle';
|
|
349
|
+
};
|
|
350
|
+
disabled?: boolean;
|
|
351
|
+
currentImage?: string | null;
|
|
352
|
+
onChange?: (event: Event, file: File | null) => void;
|
|
353
|
+
}
|