poe-svelte-ui-lib 1.9.23 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Button/ButtonProps.svelte +2 -2
- package/dist/Select/SelectProps.svelte +1 -1
- package/dist/TextField/TextField.svelte +1 -1
- package/dist/VideoViewer/VideoViewer.svelte +84 -75
- package/dist/VideoViewer/VideoViewer.svelte.d.ts +5 -1
- package/dist/VideoViewer/VideoViewerProps.svelte +53 -0
- package/dist/VideoViewer/VideoViewerProps.svelte.d.ts +17 -0
- package/dist/locales/translations.js +3 -1
- package/dist/options.js +0 -2
- package/dist/types.d.ts +6 -3
- package/package.json +13 -13
|
@@ -83,14 +83,14 @@
|
|
|
83
83
|
wrapperClass="{Header.value === 'SET' ? 'mt-1' : ''} "
|
|
84
84
|
value={component.eventHandler.Argument}
|
|
85
85
|
maxlength={32}
|
|
86
|
-
disabled={Header.value === "SET" &&
|
|
86
|
+
disabled={Header.value === "SET" && component.eventHandler.Argument == "Save"}
|
|
87
87
|
help={{ info: $T("constructor.props.argument.info"), autocomplete: "on", regExp: /^[a-zA-Z0-9\-_]{0,32}$/ }}
|
|
88
88
|
onUpdate={(value) => onPropertyChange({ eventHandler: { Argument: value as string } })}
|
|
89
89
|
/>
|
|
90
90
|
{/snippet}
|
|
91
91
|
|
|
92
92
|
{#snippet ButtonVariables()}
|
|
93
|
-
{#if
|
|
93
|
+
{#if component.eventHandler.Argument !== "Save" || Header.value === "SET"}
|
|
94
94
|
<UI.Input
|
|
95
95
|
label={{ name: $T("constructor.props.value") }}
|
|
96
96
|
value={component.eventHandler.Value}
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
wrapperClass="{Header.value === 'SET' ? 'mt-1' : ''} "
|
|
72
72
|
value={component.eventHandler.Argument}
|
|
73
73
|
maxlength={32}
|
|
74
|
-
disabled={component.eventHandler.Argument == "Save"
|
|
74
|
+
disabled={component.eventHandler.Argument == "Save"}
|
|
75
75
|
help={{ info: $T("constructor.props.argument.info"), autocomplete: "on", regExp: /^[a-zA-Z0-9\-_]{0,32}$/ }}
|
|
76
76
|
onUpdate={(value) => onPropertyChange({ eventHandler: { Argument: value as string } })}
|
|
77
77
|
/>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<div
|
|
17
17
|
id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
|
|
18
|
-
class={twMerge(`relative flex w-full flex-col items-center ${background ? "rounded-2xl bg-
|
|
18
|
+
class={twMerge(`relative flex w-full flex-col items-center ${background ? "rounded-2xl bg-(--back-color) px-6 py-2" : ""}`, wrapperClass)}
|
|
19
19
|
>
|
|
20
20
|
<p class={twMerge(`w-full text-center ${textSize[content.size ?? "base"]}`, content.class)}>
|
|
21
21
|
{content.name}
|
|
@@ -2,42 +2,56 @@
|
|
|
2
2
|
import Button from "../Button/Button.svelte"
|
|
3
3
|
import Error from "../libIcons/Error.svelte"
|
|
4
4
|
import { T } from "../locales/i18n"
|
|
5
|
-
import
|
|
5
|
+
import Select from "../Select/Select.svelte"
|
|
6
|
+
import type { IOption, IVideoViewerProps } from "../types"
|
|
6
7
|
import { onMount } from "svelte"
|
|
8
|
+
import { twMerge } from "tailwind-merge"
|
|
7
9
|
|
|
8
|
-
let { id = crypto.randomUUID(),
|
|
10
|
+
let { id = crypto.randomUUID(), wrapperClass, label = { name: "", class: "" }, showSelect = true }: IVideoViewerProps = $props()
|
|
9
11
|
|
|
10
12
|
let videoElement = $state<HTMLVideoElement | null>(null)
|
|
11
|
-
|
|
13
|
+
let stream = $state<MediaStream | null>(null)
|
|
12
14
|
let error = $state<string | null>(null)
|
|
13
15
|
let isStreaming = $state(false)
|
|
14
|
-
|
|
16
|
+
let sources = $state<MediaDeviceInfo[]>([])
|
|
15
17
|
let loading = $state(false)
|
|
18
|
+
let devId: string = $state("")
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const startStream = async () => {
|
|
20
|
+
export const getDevices = async (): Promise<MediaDeviceInfo[]> => {
|
|
21
|
+
try {
|
|
22
|
+
const deviceList = await navigator.mediaDevices.enumerateDevices()
|
|
23
|
+
sources = deviceList.filter((d) => d.kind === "videoinput")
|
|
24
|
+
return sources
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const startStream = async (devId?: string) => {
|
|
30
31
|
loading = true
|
|
31
32
|
error = null
|
|
32
33
|
|
|
33
34
|
try {
|
|
34
35
|
if (stream) stopStream()
|
|
35
36
|
|
|
37
|
+
const deviceId = devId ?? sources[0].deviceId
|
|
38
|
+
|
|
39
|
+
const constraints: MediaStreamConstraints = {
|
|
40
|
+
video: {
|
|
41
|
+
width: { ideal: 640 },
|
|
42
|
+
height: { ideal: 480 },
|
|
43
|
+
facingMode: "user",
|
|
44
|
+
...(deviceId && { deviceId: { exact: deviceId } }),
|
|
45
|
+
},
|
|
46
|
+
audio: false,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
stream = await navigator.mediaDevices.getUserMedia(constraints)
|
|
50
|
+
|
|
36
51
|
if (videoElement) {
|
|
37
52
|
videoElement.srcObject = stream as MediaStream
|
|
38
53
|
await videoElement.play()
|
|
39
54
|
isStreaming = true
|
|
40
|
-
onstream?.(stream as MediaStream)
|
|
41
55
|
}
|
|
42
56
|
} catch (e: unknown) {
|
|
43
57
|
const errorMessage = e instanceof Error ? e.message : $T("constructor.props.video.viewer.no.access")
|
|
@@ -48,7 +62,7 @@
|
|
|
48
62
|
}
|
|
49
63
|
}
|
|
50
64
|
|
|
51
|
-
const stopStream = () => {
|
|
65
|
+
export const stopStream = () => {
|
|
52
66
|
if (stream) {
|
|
53
67
|
stream.getTracks().forEach((track) => track.stop())
|
|
54
68
|
stream = null
|
|
@@ -59,89 +73,84 @@
|
|
|
59
73
|
isStreaming = false
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
// const captureScreenshot = (): string | null => {
|
|
63
|
-
// if (!videoElement || !isStreaming) return null
|
|
64
|
-
|
|
65
|
-
// const canvas = document.createElement("canvas")
|
|
66
|
-
// canvas.width = videoElement.videoWidth
|
|
67
|
-
// canvas.height = videoElement.videoHeight
|
|
68
|
-
|
|
69
|
-
// const context = canvas.getContext("2d")
|
|
70
|
-
// if (!context) return null
|
|
71
|
-
|
|
72
|
-
// context.drawImage(videoElement, 0, 0)
|
|
73
|
-
// return canvas.toDataURL("image/png")
|
|
74
|
-
// }
|
|
75
|
-
|
|
76
|
-
// const downloadScreenshot = (filename: string = "screenshot.png") => {
|
|
77
|
-
// const dataUrl = captureScreenshot()
|
|
78
|
-
// if (!dataUrl) return
|
|
79
|
-
|
|
80
|
-
// const link = document.createElement("a")
|
|
81
|
-
// link.href = dataUrl
|
|
82
|
-
// link.download = filename
|
|
83
|
-
// link.click()
|
|
84
|
-
// }
|
|
85
|
-
|
|
86
76
|
const handlePermission = async () => {
|
|
87
77
|
try {
|
|
88
78
|
const permission = await navigator.permissions.query({ name: "camera" as PermissionName })
|
|
89
79
|
if (permission.state === "granted") {
|
|
90
|
-
|
|
80
|
+
await getDevices()
|
|
91
81
|
|
|
92
82
|
await startStream()
|
|
93
83
|
} else if (permission.state === "prompt") {
|
|
94
|
-
|
|
84
|
+
await getDevices()
|
|
95
85
|
await startStream()
|
|
96
86
|
} else {
|
|
97
87
|
error = $T("constructor.props.video.viewer.no.permission")
|
|
98
88
|
}
|
|
99
89
|
} catch {
|
|
100
|
-
|
|
90
|
+
await getDevices()
|
|
101
91
|
await startStream()
|
|
102
92
|
}
|
|
103
93
|
}
|
|
104
94
|
|
|
105
|
-
|
|
95
|
+
setInterval(async () => {
|
|
96
|
+
sources = await getDevices()
|
|
97
|
+
}, 1000)
|
|
98
|
+
|
|
99
|
+
onMount(() => {
|
|
100
|
+
getDevices()
|
|
106
101
|
if (typeof window !== "undefined" && "mediaDevices" in navigator) {
|
|
107
102
|
handlePermission()
|
|
108
103
|
}
|
|
109
|
-
|
|
110
104
|
return () => {
|
|
111
105
|
stopStream()
|
|
112
106
|
}
|
|
113
107
|
})
|
|
114
|
-
|
|
115
|
-
onMount(() => {
|
|
116
|
-
if (videoElement) {
|
|
117
|
-
videoElement.addEventListener("loadedmetadata", () => {
|
|
118
|
-
if (!videoElement!.paused) {
|
|
119
|
-
videoElement!.play().catch(console.error)
|
|
120
|
-
}
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
108
|
</script>
|
|
125
109
|
|
|
126
|
-
<div
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
>
|
|
130
|
-
{#if loading}
|
|
131
|
-
<div class="h-full w-full flex items-center justify-center rounded-xl bg-(--border-color)/50 z-10">
|
|
132
|
-
<h2>{$T("constructor.props.video.viewer.loading")}</h2>
|
|
133
|
-
</div>
|
|
110
|
+
<div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge("flex flex-col w-full h-full items-center", wrapperClass)}>
|
|
111
|
+
{#if label.name}
|
|
112
|
+
<h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
|
|
134
113
|
{/if}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
114
|
+
<div class="relative flex flex-1 w-full items-center justify-center">
|
|
115
|
+
{#if loading || error}
|
|
116
|
+
<div class="absolute h-full w-full rounded-2xl px-40 py-15 flex flex-col items-center justify-center bg-(--border-color)/50 gap-4 z-10">
|
|
117
|
+
{#if loading}
|
|
118
|
+
<h2>{$T("constructor.props.camera.loading")}</h2>
|
|
119
|
+
{:else if error}
|
|
120
|
+
<svg class="w-12 h-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
121
|
+
<path
|
|
122
|
+
stroke-linecap="round"
|
|
123
|
+
stroke-linejoin="round"
|
|
124
|
+
stroke-width="2"
|
|
125
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
126
|
+
/>
|
|
127
|
+
</svg>
|
|
128
|
+
<h5 class="text-red-600">{error}</h5>
|
|
129
|
+
<Button wrapperClass="w-fit" content={{ name: $T("library.retry") }} componentClass="bg-blue px-5" onClick={handlePermission} />
|
|
130
|
+
{/if}
|
|
141
131
|
</div>
|
|
142
|
-
|
|
132
|
+
{/if}
|
|
133
|
+
<div class="absolute h-full w-full rounded-2xl bg-(--border-color)/50">
|
|
134
|
+
<video bind:this={videoElement} class="h-full w-full rounded-2xl object-contain block {!isStreaming || loading ? 'invisible' : 'visible'}"></video>
|
|
143
135
|
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{#if showSelect}
|
|
139
|
+
{@const deviceOptions = sources.map((dev) => {
|
|
140
|
+
return { id: dev.deviceId, name: dev.label, value: dev.deviceId }
|
|
141
|
+
})}
|
|
142
|
+
<Select
|
|
143
|
+
wrapperClass="w-[50%]"
|
|
144
|
+
disabled={deviceOptions.length == 0}
|
|
145
|
+
type="buttons"
|
|
146
|
+
label={{ name: $T("constructor.props.source") }}
|
|
147
|
+
value={deviceOptions.find((p) => p.value == devId) || deviceOptions[0]}
|
|
148
|
+
options={deviceOptions}
|
|
149
|
+
onUpdate={async (value) => {
|
|
150
|
+
stopStream()
|
|
151
|
+
devId = (value as IOption).value as string
|
|
152
|
+
await startStream(devId)
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
144
155
|
{/if}
|
|
145
|
-
|
|
146
|
-
<video bind:this={videoElement} class="w-full h-full block {!isStreaming || loading ? 'hidden' : 'visible'}"></video>
|
|
147
156
|
</div>
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { IVideoViewerProps } from "../types";
|
|
2
|
-
declare const VideoViewer: import("svelte").Component<IVideoViewerProps, {
|
|
2
|
+
declare const VideoViewer: import("svelte").Component<IVideoViewerProps, {
|
|
3
|
+
getDevices: () => Promise<MediaDeviceInfo[]>;
|
|
4
|
+
startStream: (devId?: string) => Promise<void>;
|
|
5
|
+
stopStream: () => void;
|
|
6
|
+
}, "">;
|
|
3
7
|
type VideoViewer = ReturnType<typeof VideoViewer>;
|
|
4
8
|
export default VideoViewer;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { T } from "../locales/i18n"
|
|
3
|
+
import { updateProperty, type IUIComponentHandler, type UIComponent } from "../types"
|
|
4
|
+
import * as UI from ".."
|
|
5
|
+
import CommonSnippets from "../CommonSnippets.svelte"
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
component,
|
|
9
|
+
onPropertyChange,
|
|
10
|
+
forConstructor = true,
|
|
11
|
+
} = $props<{
|
|
12
|
+
component: UIComponent & { properties: Partial<UI.IVideoViewerProps> }
|
|
13
|
+
onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void
|
|
14
|
+
forConstructor?: boolean
|
|
15
|
+
}>()
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
{#snippet VideoViewershowSelect()}
|
|
19
|
+
<UI.Switch
|
|
20
|
+
label={{ name: $T("constructor.props.showSelect") }}
|
|
21
|
+
value={component.properties?.showSelect ? 1 : 0}
|
|
22
|
+
options={[{ id: crypto.randomUUID(), value: 0, class: "" }]}
|
|
23
|
+
onChange={(value) => updateProperty("showSelect", value, component, onPropertyChange)}
|
|
24
|
+
/>
|
|
25
|
+
{/snippet}
|
|
26
|
+
|
|
27
|
+
{#if forConstructor}
|
|
28
|
+
<div class="flex mb-4 justify-center gap-8">
|
|
29
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
30
|
+
<CommonSnippets snippet="Access" {component} {onPropertyChange} />
|
|
31
|
+
</div>
|
|
32
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
33
|
+
<CommonSnippets snippet="Label" {component} {onPropertyChange} />
|
|
34
|
+
</div>
|
|
35
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
36
|
+
{@render VideoViewershowSelect()}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
{:else}
|
|
40
|
+
<div class="flex mb-4 justify-center gap-8">
|
|
41
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
42
|
+
<CommonSnippets snippet="Identificator" {component} {onPropertyChange} />
|
|
43
|
+
<CommonSnippets snippet="Access" {component} {onPropertyChange} />
|
|
44
|
+
</div>
|
|
45
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
46
|
+
<CommonSnippets snippet="WrapperClass" {component} {onPropertyChange} />
|
|
47
|
+
<CommonSnippets snippet="Label" {component} {onPropertyChange} />
|
|
48
|
+
</div>
|
|
49
|
+
<div class="flex w-1/3 flex-col px-2">
|
|
50
|
+
{@render VideoViewershowSelect()}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type IUIComponentHandler, type UIComponent } from "../types";
|
|
2
|
+
import * as UI from "..";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
component: UIComponent & {
|
|
5
|
+
properties: Partial<UI.IVideoViewerProps>;
|
|
6
|
+
};
|
|
7
|
+
onPropertyChange: (updates: Partial<{
|
|
8
|
+
properties?: string | object;
|
|
9
|
+
name?: string;
|
|
10
|
+
access?: string;
|
|
11
|
+
eventHandler?: IUIComponentHandler;
|
|
12
|
+
}>) => void;
|
|
13
|
+
forConstructor?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const VideoViewerProps: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
16
|
+
type VideoViewerProps = ReturnType<typeof VideoViewerProps>;
|
|
17
|
+
export default VideoViewerProps;
|
|
@@ -176,9 +176,11 @@ const translations = {
|
|
|
176
176
|
"constructor.props.file.notselected": "Файл не выбран",
|
|
177
177
|
"constructor.props.map.loading": "Загрузка карты...",
|
|
178
178
|
"constructor.props.map.noconnection": "Карта не загружена. Нет подключения к интернету",
|
|
179
|
-
"constructor.props.
|
|
179
|
+
"constructor.props.camera.loading": "Загрузка камеры...",
|
|
180
180
|
"constructor.props.video.viewer.no.permission": "Нет разрешения для доступа к камере",
|
|
181
181
|
"constructor.props.video.viewer.no.access": "Камера недоступна",
|
|
182
|
+
"constructor.props.source": "Источник",
|
|
183
|
+
"constructor.props.showSelect": "Выбор источника",
|
|
182
184
|
"constructor.props.widget.mode": "Зацикленный",
|
|
183
185
|
"constructor.props.table.type": "Тип таблицы",
|
|
184
186
|
"constructor.props.table.type.table": "Статическая таблица",
|
package/dist/options.js
CHANGED
|
@@ -13,12 +13,10 @@ export const optionsStore = derived(T, ($T) => {
|
|
|
13
13
|
{ id: id(), name: "ER!", value: "ER!" },
|
|
14
14
|
],
|
|
15
15
|
SHORT_ARGUMENT_OPTION: [
|
|
16
|
-
{ id: id(), value: "NoSave", name: $T("constructor.props.action.update") },
|
|
17
16
|
{ id: id(), value: "Save", name: $T("constructor.props.action.save") },
|
|
18
17
|
{ id: id(), value: "NoSend", name: $T("constructor.props.action.nosend") },
|
|
19
18
|
],
|
|
20
19
|
FULL_ARGUMENT_OPTION: [
|
|
21
|
-
{ id: id(), value: "NoSave", name: $T("constructor.props.action.update") },
|
|
22
20
|
{ id: id(), value: "Save", name: $T("constructor.props.action.save") },
|
|
23
21
|
{ id: id(), value: "", name: $T("constructor.props.action.custom") },
|
|
24
22
|
],
|
package/dist/types.d.ts
CHANGED
|
@@ -413,9 +413,12 @@ export interface ITextFieldProps {
|
|
|
413
413
|
}
|
|
414
414
|
export interface IVideoViewerProps {
|
|
415
415
|
id?: string;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
416
|
+
wrapperClass?: string;
|
|
417
|
+
label?: {
|
|
418
|
+
name?: string;
|
|
419
|
+
class?: string;
|
|
420
|
+
};
|
|
421
|
+
showSelect?: boolean;
|
|
419
422
|
}
|
|
420
423
|
export interface IWidgetProps {
|
|
421
424
|
id?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poe-svelte-ui-lib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -37,23 +37,23 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
40
|
-
"@sveltejs/kit": "^2.
|
|
41
|
-
"@sveltejs/package": "^2.5.
|
|
40
|
+
"@sveltejs/kit": "^2.68.0",
|
|
41
|
+
"@sveltejs/package": "^2.5.8",
|
|
42
42
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
43
|
-
"@tailwindcss/vite": "^4.3.
|
|
43
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
44
44
|
"@types/maplibre-gl": "^1.14.0",
|
|
45
|
-
"@types/node": "^
|
|
46
|
-
"dompurify": "^3.4.
|
|
47
|
-
"marked": "^18.0.
|
|
48
|
-
"prettier": "^3.
|
|
49
|
-
"prettier-plugin-svelte": "^4.
|
|
45
|
+
"@types/node": "^26.0.1",
|
|
46
|
+
"dompurify": "^3.4.11",
|
|
47
|
+
"marked": "^18.0.5",
|
|
48
|
+
"prettier": "^3.9.3",
|
|
49
|
+
"prettier-plugin-svelte": "^4.1.1",
|
|
50
50
|
"prettier-plugin-tailwindcss": "^0.8.0",
|
|
51
51
|
"publint": "^0.3.21",
|
|
52
|
-
"svelte": "^5.
|
|
53
|
-
"tailwindcss": "^4.3.
|
|
54
|
-
"tsx": "^4.22.
|
|
52
|
+
"svelte": "^5.56.4",
|
|
53
|
+
"tailwindcss": "^4.3.1",
|
|
54
|
+
"tsx": "^4.22.4",
|
|
55
55
|
"typescript": "^6.0.3",
|
|
56
|
-
"vite": "^8.0
|
|
56
|
+
"vite": "^8.1.0",
|
|
57
57
|
"vite-plugin-compression": "^0.5.1"
|
|
58
58
|
}
|
|
59
59
|
}
|