@vivix-labs/dashboard-assets 0.0.0-stage → 1.0.0-beta
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/UI-SYSTEM-LICENSE.txt +21 -0
- package/dist/assets/hero-base-5KRPU5WN.png +0 -0
- package/dist/assets/hero-landscape-H35LYO4H.png +0 -0
- package/dist/assets/inria-serif-latin-700-italic-UNRIXGAH.woff2 +0 -0
- package/dist/client-code.css +306 -0
- package/dist/client-code.js +542 -0
- package/dist/client.js +3 -0
- package/package.json +18 -3
- package/studio-plugin.json +8 -0
- package/README.md +0 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VIVIX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/* packages/studio-workbench/src/home.css */
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: "Inria Serif";
|
|
4
|
+
font-style: italic;
|
|
5
|
+
font-weight: 700;
|
|
6
|
+
font-display: swap;
|
|
7
|
+
src: url("./assets/inria-serif-latin-700-italic-UNRIXGAH.woff2") format("woff2");
|
|
8
|
+
}
|
|
9
|
+
.studio-home {
|
|
10
|
+
display: grid;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
inline-size: 100%;
|
|
13
|
+
max-inline-size: 1920px;
|
|
14
|
+
align-self: center;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
.studio-home-toolbar,
|
|
18
|
+
.studio-home-panel > header {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: space-between;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
}
|
|
24
|
+
.studio-home-toolbar {
|
|
25
|
+
flex-wrap: wrap;
|
|
26
|
+
}
|
|
27
|
+
.studio-home-hero {
|
|
28
|
+
position: relative;
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
min-block-size: 123px;
|
|
31
|
+
border-radius: 16px;
|
|
32
|
+
isolation: isolate;
|
|
33
|
+
container: dashboard-hero / inline-size;
|
|
34
|
+
background:
|
|
35
|
+
linear-gradient(
|
|
36
|
+
90deg,
|
|
37
|
+
rgb(255 255 255 / 40%),
|
|
38
|
+
rgb(255 255 255 / 60%) 55.28%),
|
|
39
|
+
url("./assets/hero-base-5KRPU5WN.png") 50% 32.44% / 108.95% 839.02% no-repeat;
|
|
40
|
+
}
|
|
41
|
+
.studio-home-hero::before {
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: 53.31%;
|
|
44
|
+
top: calc(50% - 2.365cqw);
|
|
45
|
+
width: max(85.56cqw, 380px);
|
|
46
|
+
height: max(114.04cqw, 507px);
|
|
47
|
+
transform: translate(-50%, -50%) rotate(90deg);
|
|
48
|
+
z-index: -1;
|
|
49
|
+
content: "";
|
|
50
|
+
--hero-wash: light-dark(rgb(255 255 255 / 40%), rgb(0 0 0 / 70%));
|
|
51
|
+
background: linear-gradient(var(--hero-wash), var(--hero-wash)), url("./assets/hero-landscape-H35LYO4H.png") center / cover;
|
|
52
|
+
}
|
|
53
|
+
.studio-home-hero h1 {
|
|
54
|
+
color: var(--vx-text-primary);
|
|
55
|
+
margin: 0;
|
|
56
|
+
font: italic 700 32px/1.15 "Inria Serif", serif;
|
|
57
|
+
}
|
|
58
|
+
.studio-home-hero p {
|
|
59
|
+
margin: 0;
|
|
60
|
+
color: var(--vx-text-secondary);
|
|
61
|
+
}
|
|
62
|
+
.studio-home-panels {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
}
|
|
67
|
+
.studio-home-panel {
|
|
68
|
+
min-width: 0;
|
|
69
|
+
border-radius: 12px;
|
|
70
|
+
background: var(--vx-background-dialog);
|
|
71
|
+
}
|
|
72
|
+
.studio-home-panel > header {
|
|
73
|
+
min-height: 44px;
|
|
74
|
+
padding: 0 var(--home-panel-inset);
|
|
75
|
+
}
|
|
76
|
+
.studio-home-panel > header h2 {
|
|
77
|
+
margin: 0;
|
|
78
|
+
font-size: 16px;
|
|
79
|
+
}
|
|
80
|
+
.studio-home-grid {
|
|
81
|
+
display: grid;
|
|
82
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 178px), 1fr));
|
|
83
|
+
gap: 8px;
|
|
84
|
+
padding: 12px;
|
|
85
|
+
}
|
|
86
|
+
.studio-home-tile {
|
|
87
|
+
display: grid;
|
|
88
|
+
align-content: start;
|
|
89
|
+
text-align: start;
|
|
90
|
+
gap: 4px;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
width: 100%;
|
|
93
|
+
color: inherit;
|
|
94
|
+
font: inherit;
|
|
95
|
+
background: transparent;
|
|
96
|
+
border: 0;
|
|
97
|
+
padding: 0;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
}
|
|
100
|
+
.studio-home-tile:disabled {
|
|
101
|
+
opacity: 0.5;
|
|
102
|
+
cursor: default;
|
|
103
|
+
}
|
|
104
|
+
.studio-home-tile strong,
|
|
105
|
+
.studio-home-tile small {
|
|
106
|
+
display: block;
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
text-overflow: ellipsis;
|
|
110
|
+
}
|
|
111
|
+
.studio-home-tile small {
|
|
112
|
+
color: var(--vx-text-secondary);
|
|
113
|
+
}
|
|
114
|
+
.studio-home-cover {
|
|
115
|
+
display: grid;
|
|
116
|
+
width: 100%;
|
|
117
|
+
aspect-ratio: 16 / 9;
|
|
118
|
+
place-items: center;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
border-radius: 8px;
|
|
121
|
+
background: var(--vx-surface-input);
|
|
122
|
+
}
|
|
123
|
+
.studio-home-cover {
|
|
124
|
+
position: relative;
|
|
125
|
+
}
|
|
126
|
+
.studio-home-cover img,
|
|
127
|
+
.studio-home-cover video {
|
|
128
|
+
position: absolute;
|
|
129
|
+
inset: 0;
|
|
130
|
+
width: 100%;
|
|
131
|
+
height: 100%;
|
|
132
|
+
object-fit: contain;
|
|
133
|
+
}
|
|
134
|
+
.studio-home-cover:has(img, video) {
|
|
135
|
+
background: #151515;
|
|
136
|
+
}
|
|
137
|
+
.studio-home-template {
|
|
138
|
+
container-type: inline-size;
|
|
139
|
+
position: relative;
|
|
140
|
+
min-width: 0;
|
|
141
|
+
}
|
|
142
|
+
.studio-home-template > button:last-child {
|
|
143
|
+
position: absolute;
|
|
144
|
+
inset: 0 0 auto;
|
|
145
|
+
translate: 0 -50%;
|
|
146
|
+
top: 28.125cqw;
|
|
147
|
+
margin: auto;
|
|
148
|
+
height: max-content;
|
|
149
|
+
width: max-content;
|
|
150
|
+
opacity: 0;
|
|
151
|
+
background: var(--vx-background-page);
|
|
152
|
+
}
|
|
153
|
+
.studio-home-template:hover > button:last-child,
|
|
154
|
+
.studio-home-template:focus-within > button:last-child {
|
|
155
|
+
opacity: 1;
|
|
156
|
+
}
|
|
157
|
+
.studio-home-empty {
|
|
158
|
+
padding: 12px;
|
|
159
|
+
margin: 0;
|
|
160
|
+
color: var(--vx-text-secondary);
|
|
161
|
+
}
|
|
162
|
+
.studio-home-dialog textarea {
|
|
163
|
+
box-sizing: border-box;
|
|
164
|
+
width: 100%;
|
|
165
|
+
resize: vertical;
|
|
166
|
+
padding: 10px;
|
|
167
|
+
border: 1px solid var(--vx-border-secondary);
|
|
168
|
+
border-radius: 8px;
|
|
169
|
+
background: var(--vx-surface-input);
|
|
170
|
+
color: var(--vx-text-primary);
|
|
171
|
+
font: inherit;
|
|
172
|
+
}
|
|
173
|
+
.studio-home-dialog .studio-home-preview {
|
|
174
|
+
margin-bottom: 12px;
|
|
175
|
+
max-height: 240px;
|
|
176
|
+
}
|
|
177
|
+
@media (max-width: 660px) {
|
|
178
|
+
.studio-home-hero h1 {
|
|
179
|
+
font-size: 24px;
|
|
180
|
+
}
|
|
181
|
+
.studio-home-grid {
|
|
182
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 132px), 1fr));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
@media (hover: none) {
|
|
186
|
+
.studio-home-template > button:last-child {
|
|
187
|
+
opacity: 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
.studio-home {
|
|
191
|
+
--home-panel-inset: 16px;
|
|
192
|
+
}
|
|
193
|
+
.studio-home-toolbar > .vx-segment {
|
|
194
|
+
width: max-content;
|
|
195
|
+
max-width: 100%;
|
|
196
|
+
border: 1px solid var(--vx-border-secondary);
|
|
197
|
+
border-radius: 999px;
|
|
198
|
+
}
|
|
199
|
+
.studio-home-hero-copy {
|
|
200
|
+
padding: 24px 343px 32px 24px;
|
|
201
|
+
}
|
|
202
|
+
.studio-home-grid {
|
|
203
|
+
padding: 0 var(--home-panel-inset) var(--home-panel-inset);
|
|
204
|
+
}
|
|
205
|
+
.studio-home-grid:not(.studio-home-gallery) {
|
|
206
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, max(150px, calc((100% - 32px) / 5))), 1fr));
|
|
207
|
+
}
|
|
208
|
+
.studio-home-views {
|
|
209
|
+
margin-block-start: 4px;
|
|
210
|
+
--vx-tabs-primary-height: 55px;
|
|
211
|
+
}
|
|
212
|
+
.studio-home-views .vx-tabs {
|
|
213
|
+
padding-inline: var(--home-panel-inset);
|
|
214
|
+
border-block-end: 1px solid var(--vx-border-secondary);
|
|
215
|
+
}
|
|
216
|
+
.studio-home-views .vx-tab-list {
|
|
217
|
+
gap: var(--vx-space-16);
|
|
218
|
+
}
|
|
219
|
+
.studio-home-views [role=tab] {
|
|
220
|
+
padding-inline: 0;
|
|
221
|
+
}
|
|
222
|
+
.studio-home-views .studio-home-empty {
|
|
223
|
+
padding: var(--home-panel-inset);
|
|
224
|
+
}
|
|
225
|
+
.studio-home-template-summary {
|
|
226
|
+
color: var(--vx-text-secondary-readable);
|
|
227
|
+
font: var(--vx-typography-inter-12-regular);
|
|
228
|
+
}
|
|
229
|
+
.studio-home-gallery {
|
|
230
|
+
padding-block-start: var(--home-panel-inset);
|
|
231
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, max(240px, calc((100% - 24px) / 4))), 1fr));
|
|
232
|
+
}
|
|
233
|
+
.studio-home-template > button:last-child:not(:focus-visible) {
|
|
234
|
+
pointer-events: none;
|
|
235
|
+
}
|
|
236
|
+
.studio-home-template:hover > button:last-child,
|
|
237
|
+
.studio-home-template:focus-within > button:last-child {
|
|
238
|
+
pointer-events: auto;
|
|
239
|
+
}
|
|
240
|
+
@media (hover: none) {
|
|
241
|
+
.studio-home-template > button:last-child:not(:focus-visible) {
|
|
242
|
+
pointer-events: auto;
|
|
243
|
+
opacity: 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
@media (max-width: 660px) {
|
|
247
|
+
.studio-home {
|
|
248
|
+
--home-panel-inset: 12px;
|
|
249
|
+
}
|
|
250
|
+
.studio-home-grid:not(.studio-home-gallery) {
|
|
251
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, 132px), 1fr));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
.studio-home-create .studio-home-cover {
|
|
255
|
+
border: 1px dashed var(--vx-border-secondary);
|
|
256
|
+
background: var(--vx-surface-input);
|
|
257
|
+
}
|
|
258
|
+
.studio-home-create .studio-home-cover > span {
|
|
259
|
+
display: grid;
|
|
260
|
+
place-items: center;
|
|
261
|
+
inline-size: 24px;
|
|
262
|
+
block-size: 24px;
|
|
263
|
+
border-radius: 4px;
|
|
264
|
+
background: var(--vx-background-dialog);
|
|
265
|
+
}
|
|
266
|
+
.studio-home-create-form {
|
|
267
|
+
display: grid;
|
|
268
|
+
gap: var(--vx-space-12);
|
|
269
|
+
}
|
|
270
|
+
.studio-home-create-form p {
|
|
271
|
+
color: var(--vx-text-secondary-readable);
|
|
272
|
+
}
|
|
273
|
+
.studio-home-toolbar .vx-segment-item {
|
|
274
|
+
padding-inline: var(--vx-space-16);
|
|
275
|
+
border-radius: 999px;
|
|
276
|
+
font: var(--vx-typography-inter-16-medium);
|
|
277
|
+
}
|
|
278
|
+
.studio-home-toolbar input:checked + .vx-segment-item {
|
|
279
|
+
color: var(--vx-state-success);
|
|
280
|
+
background: color-mix(in srgb, var(--vx-state-success) 16%, var(--vx-background-page));
|
|
281
|
+
}
|
|
282
|
+
.studio-home-view-all.vx-button {
|
|
283
|
+
background: transparent;
|
|
284
|
+
color: var(--vx-state-success);
|
|
285
|
+
}
|
|
286
|
+
.studio-home-actions {
|
|
287
|
+
display: flex;
|
|
288
|
+
gap: 8px;
|
|
289
|
+
align-items: center;
|
|
290
|
+
margin-inline-start: auto;
|
|
291
|
+
}
|
|
292
|
+
.studio-home-script-preview {
|
|
293
|
+
position: absolute;
|
|
294
|
+
inset: 10% 12% 0;
|
|
295
|
+
padding: 8px 10px;
|
|
296
|
+
white-space: pre-line;
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
overflow-wrap: anywhere;
|
|
299
|
+
background: var(--vx-background-dialog);
|
|
300
|
+
border-radius: 4px 4px 0 0;
|
|
301
|
+
font:
|
|
302
|
+
12px/1.4 "Inria Serif",
|
|
303
|
+
Georgia,
|
|
304
|
+
serif;
|
|
305
|
+
mask-image: linear-gradient(#000 65%, transparent);
|
|
306
|
+
}
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
// packages/dashboard-assets/src/client.tsx
|
|
2
|
+
import "react";
|
|
3
|
+
|
|
4
|
+
// packages/studio-workbench/src/dashboard-card.tsx
|
|
5
|
+
import * as React2 from "react";
|
|
6
|
+
import { z as z2 } from "vivix-studio/sdk/shared";
|
|
7
|
+
|
|
8
|
+
// packages/ui-system/dist/button.js
|
|
9
|
+
import { jsx as _jsx2, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
+
|
|
11
|
+
// packages/ui-system/dist/icon.js
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
function Icon({ name, size }) {
|
|
14
|
+
return _jsx("span", { "aria-hidden": "true", className: "vx-icon", "data-icon": name, style: size === void 0 ? void 0 : { "--vx-icon-size": `${size}px` } });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// packages/ui-system/dist/button-variants.js
|
|
18
|
+
var buttonVariants = {
|
|
19
|
+
"text-filled-sm": { size: 32, appearance: "filled", shape: "text", width: 215 },
|
|
20
|
+
"text-filled-leading-sm": { size: 32, appearance: "filled", shape: "text" },
|
|
21
|
+
"text-ghost-sm": { size: 32, appearance: "ghost", shape: "text" },
|
|
22
|
+
"text-on-content-sm": { size: 32, appearance: "on-content", shape: "text" },
|
|
23
|
+
"text-tonal-sm": { size: 32, appearance: "tonal", shape: "text" },
|
|
24
|
+
// The medium recipes are shared by all dialog footer actions.
|
|
25
|
+
"text-filled-md": { size: 40, appearance: "filled", shape: "text" },
|
|
26
|
+
"text-tonal-md": { size: 40, appearance: "tonal", shape: "text" },
|
|
27
|
+
"text-ghost-md": { size: 40, appearance: "ghost", shape: "text" },
|
|
28
|
+
"icon-ghost-md": { size: 40, appearance: "ghost", shape: "icon" },
|
|
29
|
+
"text-filled-lg": { size: 44, appearance: "filled", shape: "text", width: 412 },
|
|
30
|
+
"text-tonal-lg": { size: 44, appearance: "tonal", shape: "text", width: 412 },
|
|
31
|
+
"text-outline-lg": {
|
|
32
|
+
size: 44,
|
|
33
|
+
appearance: "outline",
|
|
34
|
+
shape: "text",
|
|
35
|
+
width: 412
|
|
36
|
+
},
|
|
37
|
+
"pill-filled": { size: 44, appearance: "filled", shape: "pill" },
|
|
38
|
+
"pill-plain": { size: 48, appearance: "plain", shape: "pill" },
|
|
39
|
+
"pill-small": { size: 32, appearance: "plain", shape: "pill" },
|
|
40
|
+
"icon-outline-sm": { size: 32, appearance: "outline", shape: "icon" },
|
|
41
|
+
"icon-on-content": { size: 24, appearance: "on-content", shape: "icon" },
|
|
42
|
+
"icon-outline-lg": { size: 48, appearance: "outline", shape: "icon" },
|
|
43
|
+
// Compact bare action: reuse the transparent/default and surface-hover ghost treatment.
|
|
44
|
+
"icon-bare-xs": { size: 24, appearance: "ghost", shape: "icon" },
|
|
45
|
+
"icon-ghost-sm": { size: 32, appearance: "ghost", shape: "icon" },
|
|
46
|
+
"icon-ghost-lg": { size: 48, appearance: "ghost", shape: "icon" },
|
|
47
|
+
"icon-circle-sm": { size: 32, appearance: "filled", shape: "circle" },
|
|
48
|
+
"icon-circle-lg": { size: 44, appearance: "filled", shape: "circle" }
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// packages/ui-system/dist/button.js
|
|
52
|
+
function ButtonFrame({ label, variant, loading = false, width, children, disabled, type = "button", style, className, ...props }) {
|
|
53
|
+
const spec = buttonVariants[variant];
|
|
54
|
+
const buttonStyle = "width" in spec ? { ...style, "--vx-button-width": `${spec.width}px` } : style;
|
|
55
|
+
return _jsxs("button", { ...props, type, className: className ? `vx-button ${className}` : "vx-button", "data-variant": variant, "data-appearance": spec.appearance, "data-shape": spec.shape, "data-size": spec.size, "data-width": width, "data-state": loading ? "loading" : disabled ? "disabled" : "default", style: buttonStyle, disabled: disabled || loading, "aria-label": label, "aria-busy": loading || void 0, children: [_jsx2("span", { className: "vx-button-content", children }), loading && _jsx2("span", { className: "vx-button-loading", "aria-hidden": "true" })] });
|
|
56
|
+
}
|
|
57
|
+
function Button({ variant = "text-filled-sm", leadingIcon, trailingIcon, ...props }) {
|
|
58
|
+
return _jsxs(ButtonFrame, { ...props, variant, children: [leadingIcon && _jsx2(Icon, { name: leadingIcon }), _jsx2("span", { className: "vx-button-label", children: props.label }), trailingIcon && _jsx2("span", { className: "vx-button-trailing", children: _jsx2(Icon, { name: trailingIcon }) })] });
|
|
59
|
+
}
|
|
60
|
+
function IconButton({ icon, variant = "icon-ghost-sm", ...props }) {
|
|
61
|
+
return _jsx2(ButtonFrame, { ...props, variant, children: _jsx2(Icon, { name: icon }) });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// packages/ui-system/dist/segmented-control.js
|
|
65
|
+
import { jsx as _jsx3, jsxs as _jsxs2 } from "react/jsx-runtime";
|
|
66
|
+
import { useId } from "react";
|
|
67
|
+
function SegmentedControl({ label, items, value, onValueChange, size = "medium", width = "fixed", iconOnly = false }) {
|
|
68
|
+
const id = useId();
|
|
69
|
+
return _jsx3("div", { role: "radiogroup", "aria-label": label, className: "vx-segment", "data-size": size, "data-width": width, "data-icon-only": iconOnly, children: items.map((item) => _jsxs2("label", { children: [_jsx3("input", { type: "radio", name: id, value: item.value, disabled: item.disabled, checked: item.value === value, "aria-label": item.label, onChange: () => onValueChange(item.value) }), _jsxs2("span", { className: "vx-segment-item", children: [item.icon && _jsx3(Icon, { name: item.icon }), (!iconOnly || !item.icon) && item.label] })] }, item.value)) });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// packages/ui-system/dist/input.js
|
|
73
|
+
import { jsx as _jsx4, jsxs as _jsxs3 } from "react/jsx-runtime";
|
|
74
|
+
function TextInput({ label, value, onValueChange, size = "medium", ...props }) {
|
|
75
|
+
return _jsxs3("label", { className: "vx-field", "data-size": size, children: [_jsx4("span", { children: label }), _jsx4("input", { ...props, type: "text", value, onChange: (event) => onValueChange(event.currentTarget.value) })] });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// packages/ui-system/dist/dialog.js
|
|
79
|
+
import { jsx as _jsx5, jsxs as _jsxs4 } from "react/jsx-runtime";
|
|
80
|
+
import { useEffect, useId as useId2, useLayoutEffect, useRef } from "react";
|
|
81
|
+
function Dialog({ title, open, onOpenChange, children, actions, className }) {
|
|
82
|
+
const ref = useRef(null);
|
|
83
|
+
const heading = useId2();
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const dialog = ref.current;
|
|
86
|
+
if (!dialog || dialog.open === open)
|
|
87
|
+
return;
|
|
88
|
+
if (open)
|
|
89
|
+
dialog.showModal();
|
|
90
|
+
else
|
|
91
|
+
dialog.close();
|
|
92
|
+
}, [open]);
|
|
93
|
+
useLayoutEffect(() => {
|
|
94
|
+
const dialog = ref.current;
|
|
95
|
+
return () => {
|
|
96
|
+
if (dialog?.open)
|
|
97
|
+
dialog.close();
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
return _jsxs4("dialog", { ref, className: className ? `vx-dialog ${className}` : "vx-dialog", "aria-labelledby": heading, onCancel: (event) => {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
onOpenChange(false);
|
|
104
|
+
}, onClose: (event) => {
|
|
105
|
+
if (open && !event.currentTarget.open)
|
|
106
|
+
onOpenChange(false);
|
|
107
|
+
}, children: [_jsxs4("header", { children: [_jsx5("h2", { id: heading, children: title }), _jsx5(IconButton, { icon: "close", label: "Close dialog", onClick: () => onOpenChange(false) })] }), _jsx5("div", { className: "vx-dialog-body", children }), actions && _jsx5("footer", { children: actions })] });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// packages/studio-workbench/src/query-view.ts
|
|
111
|
+
import { z } from "vivix-studio/sdk/shared";
|
|
112
|
+
var pageSources = ["projects", "assets", "tasks"];
|
|
113
|
+
var columnName = z.string().min(1).max(200).refine((value) => !/[\u0000-\u001f]/.test(value));
|
|
114
|
+
var scalar = z.union([z.null(), z.string(), z.number().finite()]);
|
|
115
|
+
var customViewSchema = z.object({
|
|
116
|
+
source: z.enum(pageSources),
|
|
117
|
+
projectId: z.uuid().optional(),
|
|
118
|
+
name: z.string().trim().min(1).max(120),
|
|
119
|
+
sql: z.string().trim().min(1).max(32e3),
|
|
120
|
+
params: z.union([z.array(scalar), z.record(z.string(), scalar)]).default([]),
|
|
121
|
+
columns: z.array(
|
|
122
|
+
z.object({
|
|
123
|
+
path: columnName,
|
|
124
|
+
label: z.string().min(1).max(120).optional(),
|
|
125
|
+
width: z.number().int().min(80).max(600).default(140),
|
|
126
|
+
visible: z.boolean().default(true),
|
|
127
|
+
format: z.enum(["text", "categories", "date", "json"]).default("text"),
|
|
128
|
+
role: z.enum(["name", "preview", "state", "project"]).optional()
|
|
129
|
+
}).strict()
|
|
130
|
+
).min(1).max(60).refine(
|
|
131
|
+
(columns) => new Set(columns.map((c) => c.path)).size === columns.length,
|
|
132
|
+
"Column names must be unique."
|
|
133
|
+
),
|
|
134
|
+
identity: z.object({
|
|
135
|
+
row: columnName.default("rowId"),
|
|
136
|
+
project: columnName.default("projectId"),
|
|
137
|
+
ref: columnName.default("ref"),
|
|
138
|
+
outputs: columnName.default("outputRefs")
|
|
139
|
+
}).strict().default({ row: "rowId", project: "projectId", ref: "ref", outputs: "outputRefs" })
|
|
140
|
+
}).strict();
|
|
141
|
+
var column = (path, label, width = 140, extra = {}) => ({ path, label, width, visible: true, format: "text", ...extra });
|
|
142
|
+
function defaultSql(source, favorites = false) {
|
|
143
|
+
if (source === "projects")
|
|
144
|
+
return "SELECT * FROM studio_projects ORDER BY createdAt DESC, rowId ASC";
|
|
145
|
+
const table = favorites ? "studio_asset_versions" : `studio_${source}`;
|
|
146
|
+
return `SELECT a.*, p.name AS projectName, p.available AS projectAvailable FROM ${table} AS a LEFT JOIN studio_projects AS p ON p.projectId = a.projectId${favorites ? " WHERE a.favorite = 1" : ""} ORDER BY a.createdAt DESC, a.rowId ASC`;
|
|
147
|
+
}
|
|
148
|
+
function defaultView(source) {
|
|
149
|
+
const columns = source === "projects" ? [
|
|
150
|
+
column("previewUrl", "Preview", 120, { role: "preview" }),
|
|
151
|
+
column("name", "Project title", 260, { role: "name" }),
|
|
152
|
+
column("assetCount", "Assets", 120),
|
|
153
|
+
column("available", "Available", 120),
|
|
154
|
+
column("createdAt", "Created Time", 160, { format: "date" })
|
|
155
|
+
] : source === "assets" ? [
|
|
156
|
+
column("previewUrl", "Preview", 120, { role: "preview" }),
|
|
157
|
+
column("name", "Asset", 240, { role: "name" }),
|
|
158
|
+
column("kind", "Format", 100),
|
|
159
|
+
column("prompt", "Prompt", 280),
|
|
160
|
+
column("projectName", "Project", 180, { role: "project" }),
|
|
161
|
+
column("modelId", "Model", 160),
|
|
162
|
+
column("createdAt", "Created Time", 160, { format: "date" }),
|
|
163
|
+
column("purpose", "Purpose", 140, { visible: false })
|
|
164
|
+
] : [
|
|
165
|
+
column("previewUrl", "Preview", 120, { role: "preview" }),
|
|
166
|
+
column("name", "Task", 260, { role: "name" }),
|
|
167
|
+
column("state", "Status", 180, { role: "state" }),
|
|
168
|
+
column("costLabel", "Actual cost", 180),
|
|
169
|
+
column("kind", "Format", 100),
|
|
170
|
+
column("prompt", "Prompt", 280),
|
|
171
|
+
column("projectName", "Project", 200, { role: "project" }),
|
|
172
|
+
column("modelId", "Model", 160),
|
|
173
|
+
column("createdAt", "Created Time", 160, { format: "date" })
|
|
174
|
+
];
|
|
175
|
+
return {
|
|
176
|
+
id: `default:${source}`,
|
|
177
|
+
revision: 0,
|
|
178
|
+
view: customViewSchema.parse({
|
|
179
|
+
source,
|
|
180
|
+
name: "All",
|
|
181
|
+
sql: defaultSql(source),
|
|
182
|
+
columns
|
|
183
|
+
})
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function decodeRef(value) {
|
|
187
|
+
let item = value;
|
|
188
|
+
if (typeof item === "string") {
|
|
189
|
+
try {
|
|
190
|
+
item = JSON.parse(item);
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
196
|
+
const ref = item;
|
|
197
|
+
return ["storeId", "objectId", "versionId"].every(
|
|
198
|
+
(key) => typeof ref[key] === "string" && /^[0-9a-f-]{36}$/i.test(ref[key])
|
|
199
|
+
) ? {
|
|
200
|
+
storeId: ref.storeId,
|
|
201
|
+
objectId: ref.objectId,
|
|
202
|
+
versionId: ref.versionId
|
|
203
|
+
} : null;
|
|
204
|
+
}
|
|
205
|
+
function rowRef(row, view) {
|
|
206
|
+
return decodeRef(row[view.identity.ref]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// packages/studio-workbench/src/text-preview.ts
|
|
210
|
+
async function readTextPreview(response, signal, maxBytes = 128 * 1024) {
|
|
211
|
+
if (!response.ok || !response.body) throw Error("The text preview could not be loaded.");
|
|
212
|
+
const reader = response.body.getReader();
|
|
213
|
+
const decoder = new TextDecoder();
|
|
214
|
+
let text = "", used = 0, truncated = false;
|
|
215
|
+
const cancel = () => {
|
|
216
|
+
void reader.cancel(signal.reason).catch(() => void 0);
|
|
217
|
+
};
|
|
218
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
219
|
+
try {
|
|
220
|
+
while (true) {
|
|
221
|
+
signal.throwIfAborted();
|
|
222
|
+
const chunk = await reader.read();
|
|
223
|
+
signal.throwIfAborted();
|
|
224
|
+
if (chunk.done) break;
|
|
225
|
+
const remaining = maxBytes - used;
|
|
226
|
+
text += decoder.decode(chunk.value.subarray(0, remaining), { stream: true });
|
|
227
|
+
used += Math.min(remaining, chunk.value.byteLength);
|
|
228
|
+
if (chunk.value.byteLength > remaining) {
|
|
229
|
+
truncated = true;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
text += decoder.decode();
|
|
234
|
+
return { text, truncated };
|
|
235
|
+
} finally {
|
|
236
|
+
signal.removeEventListener("abort", cancel);
|
|
237
|
+
await reader.cancel().catch(() => void 0);
|
|
238
|
+
reader.releaseLock();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// packages/studio-workbench/src/studio-glyph.tsx
|
|
243
|
+
import "react";
|
|
244
|
+
import { jsx } from "react/jsx-runtime";
|
|
245
|
+
var paths = {
|
|
246
|
+
projects: [
|
|
247
|
+
"M6.66803 5.83333H10.0023M10.0023 5.83333H13.9617C15.7177 5.83333 16.5958 5.83333 17.2265 6.25466C17.4995 6.43706 17.734 6.67143 17.9164 6.94441C18.2448 7.43568 18.3173 8.07703 18.3333 9.16667M10.0023 5.83333L9.47426 4.7776C9.03683 3.90298 8.63709 3.0226 7.66768 2.65917C7.2431 2.5 6.75807 2.5 5.78802 2.5C4.27388 2.5 3.51681 2.5 2.94874 2.81693C2.54379 3.04285 2.20966 3.37689 1.98368 3.78172C1.66667 4.34964 1.66667 5.1065 1.66667 6.62023V9.16667C1.66667 13.095 1.66667 15.0592 2.88739 16.2796C4.10811 17.5 6.07283 17.5 10.0023 17.5",
|
|
248
|
+
"M15 16.4773C16.4728 16.4773 17.6667 15.2562 17.6667 13.75C17.6667 12.2438 16.4728 11.0227 15 11.0227M15 16.4773C13.5272 16.4773 12.3333 15.2562 12.3333 13.75C12.3333 12.2438 13.5272 11.0227 15 11.0227M15 16.4773V17.5M15 11.0227V10M12.5761 12.2624L11.667 11.7045M18.3333 15.7955L17.4243 15.2376M17.4239 12.2624L18.333 11.7045M11.6667 15.7955L12.5757 15.2376"
|
|
249
|
+
],
|
|
250
|
+
assets: [
|
|
251
|
+
"M2.5 2.5H17.5C17.9602 2.5 18.3333 2.8731 18.3333 3.33333V5.83333C18.3333 6.29357 17.9602 6.66667 17.5 6.66667H2.5C2.03976 6.66667 1.66667 6.29357 1.66667 5.83333V3.33333C1.66667 2.8731 2.03976 2.5 2.5 2.5Z",
|
|
252
|
+
"M3.33333 6.66667V15.8333C3.33333 16.2754 3.50893 16.6993 3.82149 17.0118C4.13405 17.3244 4.55797 17.5 5 17.5H15C15.442 17.5 15.866 17.3244 16.1785 17.0118C16.4911 16.6993 16.6667 16.2754 16.6667 15.8333V6.66667",
|
|
253
|
+
"M8.33333 10H11.6667"
|
|
254
|
+
],
|
|
255
|
+
tasks: [
|
|
256
|
+
"M15.75 7.41667C15.75 8.58344 15.75 9.16683 15.5229 9.61248C15.3232 10.0045 15.0045 10.3232 14.6125 10.5229C14.1668 10.75 13.5834 10.75 12.4167 10.75H4.08333C2.91656 10.75 2.33317 10.75 1.88752 10.5229C1.49552 10.3232 1.17681 10.0045 0.977071 9.61248C0.750001 9.16683 0.750001 8.58344 0.750001 7.41667",
|
|
257
|
+
"M3.25 7.41667H13.25M3.25 4.08333H13.25M3.25 0.75H13.25"
|
|
258
|
+
],
|
|
259
|
+
panel: [
|
|
260
|
+
"M2.08333 10C2.08333 6.26805 2.08333 4.40207 3.2427 3.2427C4.40207 2.08333 6.26805 2.08333 10 2.08333C13.7319 2.08333 15.5979 2.08333 16.7573 3.2427C17.9167 4.40207 17.9167 6.26805 17.9167 10C17.9167 13.7319 17.9167 15.5979 16.7573 16.7573C15.5979 17.9167 13.7319 17.9167 10 17.9167C6.26805 17.9167 4.40207 17.9167 3.2427 16.7573C2.08333 15.5979 2.08333 13.7319 2.08333 10Z",
|
|
261
|
+
"M7.06381 6.69231L7.09951 13.3523"
|
|
262
|
+
],
|
|
263
|
+
favorite: [
|
|
264
|
+
"M0.600003 5.73835V11.2539C0.600003 12.7911 0.600003 13.5597 1.11523 13.8349C2.11298 14.3676 3.98454 12.5901 4.87334 12.0549C5.38881 11.7446 5.64654 11.5894 5.93334 11.5894C6.22013 11.5894 6.47787 11.7446 6.99333 12.0549C7.88214 12.5901 9.75369 14.3676 10.7514 13.8349C11.2667 13.5597 11.2667 12.7911 11.2667 11.2539V5.73835C11.2667 3.31611 11.2667 2.10499 10.4856 1.3525C9.70457 0.600001 8.44749 0.600001 5.93334 0.600001C3.41918 0.600001 2.1621 0.600001 1.38105 1.3525C0.600003 2.10499 0.600003 3.31611 0.600003 5.73835Z"
|
|
265
|
+
]
|
|
266
|
+
};
|
|
267
|
+
function StudioGlyph({ name }) {
|
|
268
|
+
return /* @__PURE__ */ jsx(
|
|
269
|
+
"svg",
|
|
270
|
+
{
|
|
271
|
+
className: "studio-glyph",
|
|
272
|
+
"aria-hidden": "true",
|
|
273
|
+
width: "20",
|
|
274
|
+
height: "20",
|
|
275
|
+
viewBox: name === "tasks" ? "0 0 16.5 11.5" : name === "favorite" ? "0 0 11.8667 14.5333" : "0 0 20 20",
|
|
276
|
+
fill: "none",
|
|
277
|
+
stroke: "currentColor",
|
|
278
|
+
strokeWidth: name === "favorite" ? 1.2 : 1.5,
|
|
279
|
+
strokeLinecap: "round",
|
|
280
|
+
strokeLinejoin: "round",
|
|
281
|
+
children: paths[name].map((d, index) => /* @__PURE__ */ jsx("path", { d }, index))
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// packages/studio-workbench/src/dashboard-card.tsx
|
|
287
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
288
|
+
var Config = z2.object({ studioInstance: z2.string().min(1).nullable().default(null) });
|
|
289
|
+
var dashboardAssetsSql = `SELECT a.* FROM studio_assets AS a
|
|
290
|
+
ORDER BY (json_extract(a.metadata, '$.homeFeatured') IS NOT NULL) DESC,
|
|
291
|
+
ROW_NUMBER() OVER (
|
|
292
|
+
PARTITION BY a.projectId, a.kind, a.purpose ORDER BY a.createdAt DESC, a.rowId ASC
|
|
293
|
+
), a.createdAt DESC, a.rowId ASC`;
|
|
294
|
+
function featuredRole(row) {
|
|
295
|
+
const metadata = row?.metadata;
|
|
296
|
+
return metadata && typeof metadata === "object" && "homeFeatured" in metadata ? metadata.homeFeatured : void 0;
|
|
297
|
+
}
|
|
298
|
+
function sampleAssets(items) {
|
|
299
|
+
const chosen = /* @__PURE__ */ new Set();
|
|
300
|
+
for (const [role, kind, purpose] of [
|
|
301
|
+
["character", "image", "reference"],
|
|
302
|
+
["video", "video", "shot"],
|
|
303
|
+
["scene", "image", "shot"],
|
|
304
|
+
["script", "text", "script"],
|
|
305
|
+
["audio", "audio", "voice"]
|
|
306
|
+
]) {
|
|
307
|
+
const asset = items.find((row) => featuredRole(row) === role && row.kind === kind) ?? items.find((row) => row.kind === kind && row.purpose === purpose && !chosen.has(row));
|
|
308
|
+
if (asset) chosen.add(asset);
|
|
309
|
+
}
|
|
310
|
+
for (const row of items) if (row.kind !== "text" || row.purpose === "script") chosen.add(row);
|
|
311
|
+
return [...chosen].slice(0, 5);
|
|
312
|
+
}
|
|
313
|
+
var projectTypes = [
|
|
314
|
+
{
|
|
315
|
+
value: "short-drama",
|
|
316
|
+
label: "Short Drama",
|
|
317
|
+
preset: "@vivix-labs/short-drama-workbench:short-drama"
|
|
318
|
+
},
|
|
319
|
+
{ value: "ads", label: "Product Ad", preset: "@vivix-labs/studio-workbench:ads" },
|
|
320
|
+
{ value: "table", label: "Other", preset: "@vivix-labs/studio-workbench:table" }
|
|
321
|
+
];
|
|
322
|
+
function NewProjectDialog({ close }) {
|
|
323
|
+
const [type, setType] = React2.useState(projectTypes[0].value);
|
|
324
|
+
const [name, setName] = React2.useState("");
|
|
325
|
+
const [status, setStatus] = React2.useState("");
|
|
326
|
+
const selection = projectTypes.find((item) => item.value === type);
|
|
327
|
+
const placeholder = type === "table" ? "Untitled Project" : "Untitled " + selection.label;
|
|
328
|
+
const prompt = [
|
|
329
|
+
`Create a local Vivix Studio project named ${JSON.stringify(name.trim() || placeholder)} from the installed ${selection.preset} preset.`,
|
|
330
|
+
"Use the profile of my current Studio workbench. Run vivix-studio preset list --json and check vivix-studio project init --help. Create it with project init --preset in a new empty local folder, then open the project in Studio."
|
|
331
|
+
].join("\n");
|
|
332
|
+
return /* @__PURE__ */ jsx2(
|
|
333
|
+
Dialog,
|
|
334
|
+
{
|
|
335
|
+
title: "Create Project",
|
|
336
|
+
open: true,
|
|
337
|
+
className: "studio-home-dialog",
|
|
338
|
+
onOpenChange: (open) => !open && close(),
|
|
339
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
340
|
+
/* @__PURE__ */ jsx2(Button, { label: "Cancel", variant: "text-tonal-sm", onClick: close }),
|
|
341
|
+
/* @__PURE__ */ jsx2(
|
|
342
|
+
Button,
|
|
343
|
+
{
|
|
344
|
+
label: "Copy Prompt",
|
|
345
|
+
onClick: async () => {
|
|
346
|
+
try {
|
|
347
|
+
await navigator.clipboard.writeText(prompt);
|
|
348
|
+
setStatus("Prompt copied. Paste it into your Agent chat.");
|
|
349
|
+
} catch {
|
|
350
|
+
setStatus("Select and copy the prompt below.");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
] }),
|
|
356
|
+
children: /* @__PURE__ */ jsxs("div", { className: "studio-home-create-form", children: [
|
|
357
|
+
/* @__PURE__ */ jsx2("strong", { children: "Project type" }),
|
|
358
|
+
/* @__PURE__ */ jsx2(
|
|
359
|
+
SegmentedControl,
|
|
360
|
+
{
|
|
361
|
+
label: "Project type",
|
|
362
|
+
value: type,
|
|
363
|
+
items: projectTypes,
|
|
364
|
+
onValueChange: setType
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
/* @__PURE__ */ jsx2(
|
|
368
|
+
TextInput,
|
|
369
|
+
{
|
|
370
|
+
label: "Project name",
|
|
371
|
+
value: name,
|
|
372
|
+
onValueChange: setName,
|
|
373
|
+
maxLength: 100,
|
|
374
|
+
placeholder
|
|
375
|
+
}
|
|
376
|
+
),
|
|
377
|
+
/* @__PURE__ */ jsx2("p", { children: "Copy a prompt for your agent to create and open the project." }),
|
|
378
|
+
status && /* @__PURE__ */ jsx2("p", { role: "status", children: status }),
|
|
379
|
+
status.startsWith("Select") && /* @__PURE__ */ jsx2("textarea", { "aria-label": "Project prompt", readOnly: true, value: prompt })
|
|
380
|
+
] })
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
function Cover({ row, kind }) {
|
|
385
|
+
const [failed, setFailed] = React2.useState(false);
|
|
386
|
+
const source = typeof row?.previewUrl === "string" && !failed ? row.previewUrl : void 0;
|
|
387
|
+
const script = kind === "assets" && row?.kind === "text" && row?.purpose === "script";
|
|
388
|
+
const [excerpt, setExcerpt] = React2.useState("");
|
|
389
|
+
React2.useEffect(() => {
|
|
390
|
+
if (!script || !source) return;
|
|
391
|
+
const controller = new AbortController();
|
|
392
|
+
if (new URL(source, window.location.origin).origin !== window.location.origin) return;
|
|
393
|
+
void fetch(source, { signal: controller.signal }).then((response) => readTextPreview(response, controller.signal, 1024)).then(({ text }) => setExcerpt(text.trim())).catch(() => void 0);
|
|
394
|
+
return () => controller.abort();
|
|
395
|
+
}, [script, source]);
|
|
396
|
+
return /* @__PURE__ */ jsx2("span", { className: "studio-home-cover", "aria-hidden": "true", children: source && (kind === "projects" ? row?.previewKind : row?.kind) === "video" ? /* @__PURE__ */ jsx2("video", { src: source, muted: true, playsInline: true, preload: "auto", onError: () => setFailed(true) }) : source && (kind === "projects" || row?.kind === "image") ? /* @__PURE__ */ jsx2("img", { src: source, alt: "", loading: "lazy", onError: () => setFailed(true) }) : excerpt ? /* @__PURE__ */ jsx2("span", { className: "studio-home-script-preview", children: excerpt }) : row?.kind === "audio" ? /* @__PURE__ */ jsx2(Icon, { name: "sound-file", size: 32 }) : /* @__PURE__ */ jsx2(StudioGlyph, { name: kind }) });
|
|
397
|
+
}
|
|
398
|
+
function DataCard({
|
|
399
|
+
client,
|
|
400
|
+
projects,
|
|
401
|
+
ready,
|
|
402
|
+
source,
|
|
403
|
+
title,
|
|
404
|
+
openProject,
|
|
405
|
+
openProjects,
|
|
406
|
+
openAssets
|
|
407
|
+
}) {
|
|
408
|
+
const [state, setState] = React2.useState();
|
|
409
|
+
const [creating, setCreating] = React2.useState(false);
|
|
410
|
+
const catalogKey = JSON.stringify(projects);
|
|
411
|
+
const view = React2.useMemo(() => defaultView(source).view, [source]);
|
|
412
|
+
React2.useEffect(() => {
|
|
413
|
+
if (!ready || !client.config.studioInstance) return;
|
|
414
|
+
let active = true;
|
|
415
|
+
void client.invoke(
|
|
416
|
+
{ instanceId: client.config.studioInstance, operationId: "global.query" },
|
|
417
|
+
{
|
|
418
|
+
source,
|
|
419
|
+
sql: source === "assets" ? dashboardAssetsSql : view.sql,
|
|
420
|
+
params: view.params,
|
|
421
|
+
catalog: JSON.parse(catalogKey),
|
|
422
|
+
limit: source === "assets" ? 24 : 5,
|
|
423
|
+
timeoutMs: 8e3,
|
|
424
|
+
offset: 0
|
|
425
|
+
}
|
|
426
|
+
).then(
|
|
427
|
+
async ({ items }) => {
|
|
428
|
+
const rows2 = source === "projects" ? await Promise.all(
|
|
429
|
+
items.map(async (row) => {
|
|
430
|
+
const ref = decodeRef(row.previewRef ?? row.coverRef);
|
|
431
|
+
if (!ref || !row.available) return row;
|
|
432
|
+
try {
|
|
433
|
+
const { item } = await client.invoke(
|
|
434
|
+
{ instanceId: client.config.studioInstance, operationId: "global.asset" },
|
|
435
|
+
{ ref }
|
|
436
|
+
);
|
|
437
|
+
return {
|
|
438
|
+
...row,
|
|
439
|
+
previewUrl: item.previewUrl ?? null,
|
|
440
|
+
previewKind: item.kind ?? null
|
|
441
|
+
};
|
|
442
|
+
} catch {
|
|
443
|
+
return row;
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
) : sampleAssets(items);
|
|
447
|
+
if (active) setState({ rows: rows2 });
|
|
448
|
+
},
|
|
449
|
+
() => {
|
|
450
|
+
if (active) setState({ rows: [], error: true });
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
return () => {
|
|
454
|
+
active = false;
|
|
455
|
+
};
|
|
456
|
+
}, [client, ready, catalogKey, source, view]);
|
|
457
|
+
const rows = state?.rows ?? [];
|
|
458
|
+
const entries = source === "projects" ? (rows.length ? rows.map((row) => projects.find(({ id }) => id === row.projectId)).filter((entry) => !!entry) : projects.slice(0, 5)).map((project) => {
|
|
459
|
+
const row = rows.find((entry) => entry.projectId === project.id);
|
|
460
|
+
return {
|
|
461
|
+
key: project.id,
|
|
462
|
+
row,
|
|
463
|
+
name: String(row?.name ?? project.name),
|
|
464
|
+
note: row?.assetCount !== void 0 ? String(row.assetCount) + " assets" : "",
|
|
465
|
+
enabled: project.available,
|
|
466
|
+
open: () => openProject(project.id)
|
|
467
|
+
};
|
|
468
|
+
}) : rows.map((row) => {
|
|
469
|
+
const ref = rowRef(row, view);
|
|
470
|
+
return {
|
|
471
|
+
key: String(row.rowId),
|
|
472
|
+
row,
|
|
473
|
+
name: String(row.name ?? "Asset"),
|
|
474
|
+
note: featuredRole(row) === "character" ? "Character \xB7 Turnaround" : row.purpose === "script" ? "Script" : String(row.kind ?? "").replace(/^./, (first) => first.toUpperCase()),
|
|
475
|
+
enabled: !!ref,
|
|
476
|
+
open: () => ref && openAssets(ref)
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
return /* @__PURE__ */ jsxs("section", { className: "studio-home-panel", "aria-label": title, children: [
|
|
480
|
+
/* @__PURE__ */ jsxs("header", { children: [
|
|
481
|
+
/* @__PURE__ */ jsx2("h2", { children: title }),
|
|
482
|
+
/* @__PURE__ */ jsx2(
|
|
483
|
+
Button,
|
|
484
|
+
{
|
|
485
|
+
label: "View all",
|
|
486
|
+
"aria-label": "View all " + title.toLowerCase(),
|
|
487
|
+
className: "studio-home-view-all",
|
|
488
|
+
variant: "text-tonal-sm",
|
|
489
|
+
onClick: source === "projects" ? openProjects : () => openAssets()
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
] }),
|
|
493
|
+
entries.length || source === "projects" ? /* @__PURE__ */ jsxs("div", { className: "studio-home-grid", children: [
|
|
494
|
+
source === "projects" && /* @__PURE__ */ jsxs(
|
|
495
|
+
"button",
|
|
496
|
+
{
|
|
497
|
+
type: "button",
|
|
498
|
+
className: "studio-home-tile studio-home-create",
|
|
499
|
+
onClick: () => setCreating(true),
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ jsx2("span", { className: "studio-home-cover", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("span", { children: /* @__PURE__ */ jsx2(Icon, { name: "add" }) }) }),
|
|
502
|
+
/* @__PURE__ */ jsx2("strong", { children: "Create Project" }),
|
|
503
|
+
/* @__PURE__ */ jsx2("small", { children: "Create a local project" })
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
),
|
|
507
|
+
entries.map(({ key, row, name, note, enabled, open }) => /* @__PURE__ */ jsxs(
|
|
508
|
+
"button",
|
|
509
|
+
{
|
|
510
|
+
type: "button",
|
|
511
|
+
className: "studio-home-tile",
|
|
512
|
+
onClick: open,
|
|
513
|
+
disabled: !enabled,
|
|
514
|
+
children: [
|
|
515
|
+
/* @__PURE__ */ jsx2(Cover, { row, kind: source }, String(row?.previewUrl ?? "")),
|
|
516
|
+
/* @__PURE__ */ jsx2("strong", { title: name, children: name }),
|
|
517
|
+
note && /* @__PURE__ */ jsx2("small", { children: note })
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
key
|
|
521
|
+
))
|
|
522
|
+
] }) : /* @__PURE__ */ jsx2("p", { className: "studio-home-empty", role: "status", children: !client.config.studioInstance || state?.error ? `${title} unavailable.` : !state ? "Loading\u2026" : "No assets yet." }),
|
|
523
|
+
creating && /* @__PURE__ */ jsx2(NewProjectDialog, { close: () => setCreating(false) })
|
|
524
|
+
] });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// packages/dashboard-assets/src/client.tsx
|
|
528
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
529
|
+
var dependencies = (config) => config.studioInstance ? [config.studioInstance] : [];
|
|
530
|
+
function activate(client) {
|
|
531
|
+
client.registerUI({
|
|
532
|
+
id: "assets",
|
|
533
|
+
title: "Assets",
|
|
534
|
+
kind: "component",
|
|
535
|
+
component: (props) => /* @__PURE__ */ jsx3(DataCard, { ...props, client, source: "assets", title: "Assets" })
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
export {
|
|
539
|
+
Config,
|
|
540
|
+
activate as default,
|
|
541
|
+
dependencies
|
|
542
|
+
};
|
package/dist/client.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vivix-labs/dashboard-assets",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0-beta",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"registry": "https://registry.npmjs.org/",
|
|
8
|
+
"tag": "beta"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"studio-plugin.json"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
"./client": "./dist/client.js"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": "^19.0.0",
|
|
19
|
+
"react-dom": "^19.0.0"
|
|
20
|
+
}
|
|
6
21
|
}
|
package/README.md
DELETED