vzcode 1.60.0 → 1.61.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/assets/bootstrap-icons-BeopsB42.woff +0 -0
- package/dist/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
- package/dist/assets/{index-DMIy-XE4.css → index-Br76WttW.css} +5 -1
- package/dist/assets/{index-S-R8oY72.js → index-EWyCE9d7.js} +148 -148
- package/dist/index.html +2 -2
- package/package.json +7 -6
- package/src/client/Icons/AdjustmentSVG.tsx +19 -0
- package/src/client/Icons/index.tsx +1 -0
- package/src/client/VZCodeContext/types.ts +12 -1
- package/src/client/VZCodeContext/useVZCodeState.ts +81 -5
- package/src/client/VZRight.tsx +6 -3
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +45 -2
- package/src/client/VZSidebar/AIChat/MessageList.tsx +2 -0
- package/src/client/VZSidebar/AIChat/index.tsx +12 -0
- package/src/client/VZSidebar/EmptyState.tsx +11 -0
- package/src/client/VZSidebar/VisualEditor.tsx +284 -0
- package/src/client/VZSidebar/index.tsx +64 -0
- package/src/client/VZSidebar/styles.scss +213 -1
- package/src/client/bootstrap.ts +3 -0
- package/src/client/useActions.ts +12 -0
- package/src/client/vzReducer/closeTabReducer.test.ts +33 -21
- package/src/client/vzReducer/createInitialState.ts +1 -0
- package/src/client/vzReducer/index.ts +9 -0
- package/src/client/vzReducer/searchReducer.test.ts +1 -1
- package/src/client/vzReducer/visualEditorReducer.ts +9 -0
- package/src/server/aiChatHandler/chatOperations.ts +22 -0
- package/src/server/aiChatHandler/errorHandling.ts +17 -4
- package/src/server/aiChatHandler/index.ts +131 -67
- package/src/server/aiChatHandler/llmStreaming.ts +76 -27
- package/src/types.ts +12 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
useEffect,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { VZCodeContext } from '../VZCodeContext';
|
|
9
|
+
import { VizContent, VizFileId } from '@vizhub/viz-types';
|
|
10
|
+
import { VisualEditorConfigEntry } from '../../types';
|
|
11
|
+
import { EmptyState } from './EmptyState';
|
|
12
|
+
|
|
13
|
+
const CONFIG_FILE_NAME = 'config.json';
|
|
14
|
+
|
|
15
|
+
export const VisualEditor = () => {
|
|
16
|
+
const {
|
|
17
|
+
files,
|
|
18
|
+
submitOperation,
|
|
19
|
+
runPrettierRef,
|
|
20
|
+
iframeRef,
|
|
21
|
+
} = useContext(VZCodeContext);
|
|
22
|
+
|
|
23
|
+
let configFileId: VizFileId | null = null;
|
|
24
|
+
for (const fileId in files) {
|
|
25
|
+
if (files[fileId].name === CONFIG_FILE_NAME) {
|
|
26
|
+
configFileId = fileId;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (configFileId === null) {
|
|
32
|
+
return (
|
|
33
|
+
<EmptyState>
|
|
34
|
+
To begin using the visual editor, create a
|
|
35
|
+
config.json.
|
|
36
|
+
</EmptyState>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let configData;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
configData = JSON.parse(files[configFileId].text);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return (
|
|
46
|
+
<EmptyState>
|
|
47
|
+
Your config.json file is not valid json.
|
|
48
|
+
</EmptyState>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!('visualEditorWidgets' in configData)) {
|
|
53
|
+
return (
|
|
54
|
+
<EmptyState>
|
|
55
|
+
To begin using the visual editor, make sure your
|
|
56
|
+
config.json has a key called "visualEditorWidgets",
|
|
57
|
+
whose value is the config for the visual editor.
|
|
58
|
+
</EmptyState>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const onInputUpdate = useCallback(
|
|
63
|
+
(
|
|
64
|
+
property: string,
|
|
65
|
+
previousValue: any,
|
|
66
|
+
): React.FormEventHandler<HTMLInputElement> =>
|
|
67
|
+
(event) => {
|
|
68
|
+
//TODO: test race condition in which someone is editing the config file as another user uses the visual editor
|
|
69
|
+
|
|
70
|
+
const newValueOfConsistentType =
|
|
71
|
+
typeof previousValue === 'number'
|
|
72
|
+
? parseFloat(event.currentTarget.value)
|
|
73
|
+
: event.currentTarget.value;
|
|
74
|
+
|
|
75
|
+
const newConfigData = {
|
|
76
|
+
...configData,
|
|
77
|
+
[property]: newValueOfConsistentType,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
submitOperation((document: VizContent) => ({
|
|
81
|
+
...document,
|
|
82
|
+
files: {
|
|
83
|
+
...files,
|
|
84
|
+
[configFileId]: {
|
|
85
|
+
name: 'config.json',
|
|
86
|
+
text: JSON.stringify(newConfigData, null, 2),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
iframeRef.current.contentWindow.postMessage({
|
|
92
|
+
[property]: newValueOfConsistentType,
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
[configData, files, configFileId],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const visualEditorWidgets: VisualEditorConfigEntry[] =
|
|
99
|
+
configData.visualEditorWidgets;
|
|
100
|
+
|
|
101
|
+
// State for advanced interactions
|
|
102
|
+
const [activeSlider, setActiveSlider] = useState<
|
|
103
|
+
string | null
|
|
104
|
+
>(null);
|
|
105
|
+
const [isDragging, setIsDragging] = useState<
|
|
106
|
+
string | null
|
|
107
|
+
>(null);
|
|
108
|
+
const [hoverValue, setHoverValue] = useState<
|
|
109
|
+
number | null
|
|
110
|
+
>(null);
|
|
111
|
+
const sliderRefs = useRef<{
|
|
112
|
+
[key: string]: HTMLInputElement | null;
|
|
113
|
+
}>({});
|
|
114
|
+
|
|
115
|
+
// Helper function to format values nicely
|
|
116
|
+
const formatValue = (
|
|
117
|
+
value: number,
|
|
118
|
+
min: number,
|
|
119
|
+
max: number,
|
|
120
|
+
) => {
|
|
121
|
+
// Determine decimal places based on the range
|
|
122
|
+
const range = max - min;
|
|
123
|
+
const decimalPlaces =
|
|
124
|
+
range < 10 ? 2 : range < 100 ? 1 : 0;
|
|
125
|
+
return Number(value).toFixed(decimalPlaces);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Helper function to calculate percentage for progress fill
|
|
129
|
+
const calculatePercentage = (
|
|
130
|
+
value: number,
|
|
131
|
+
min: number,
|
|
132
|
+
max: number,
|
|
133
|
+
) => {
|
|
134
|
+
return ((value - min) / (max - min)) * 100;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Enhanced input handler with animations
|
|
138
|
+
const onInputUpdateEnhanced = useCallback(
|
|
139
|
+
(
|
|
140
|
+
property: string,
|
|
141
|
+
previousValue: any,
|
|
142
|
+
widgetConfig: VisualEditorConfigEntry,
|
|
143
|
+
): React.FormEventHandler<HTMLInputElement> =>
|
|
144
|
+
(event) => {
|
|
145
|
+
const newValueOfConsistentType =
|
|
146
|
+
typeof previousValue === 'number'
|
|
147
|
+
? parseFloat(event.currentTarget.value)
|
|
148
|
+
: event.currentTarget.value;
|
|
149
|
+
|
|
150
|
+
const newConfigData = {
|
|
151
|
+
...configData,
|
|
152
|
+
[property]: newValueOfConsistentType,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
submitOperation((document: VizContent) => ({
|
|
156
|
+
...document,
|
|
157
|
+
files: {
|
|
158
|
+
...files,
|
|
159
|
+
[configFileId]: {
|
|
160
|
+
name: 'config.json',
|
|
161
|
+
text: JSON.stringify(newConfigData, null, 2),
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
|
|
166
|
+
iframeRef.current.contentWindow.postMessage({
|
|
167
|
+
[property]: newValueOfConsistentType,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Trigger value change animation
|
|
171
|
+
const sliderElement = sliderRefs.current[property];
|
|
172
|
+
if (sliderElement) {
|
|
173
|
+
sliderElement.classList.add('value-changed');
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
sliderElement.classList.remove('value-changed');
|
|
176
|
+
}, 300);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
[configData, files, configFileId],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Mouse event handlers for enhanced interactions
|
|
183
|
+
const handleMouseDown = (property: string) => {
|
|
184
|
+
setIsDragging(property);
|
|
185
|
+
setActiveSlider(property);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const handleMouseUp = () => {
|
|
189
|
+
setIsDragging(null);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleMouseEnter = (property: string) => {
|
|
193
|
+
setActiveSlider(property);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const handleMouseLeave = () => {
|
|
197
|
+
setActiveSlider(null);
|
|
198
|
+
setHoverValue(null);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Add global mouse up listener
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
const handleGlobalMouseUp = () => {
|
|
204
|
+
setIsDragging(null);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
document.addEventListener(
|
|
208
|
+
'mouseup',
|
|
209
|
+
handleGlobalMouseUp,
|
|
210
|
+
);
|
|
211
|
+
return () => {
|
|
212
|
+
document.removeEventListener(
|
|
213
|
+
'mouseup',
|
|
214
|
+
handleGlobalMouseUp,
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div className="visual-editor">
|
|
221
|
+
{visualEditorWidgets.map((widgetConfig, index) => {
|
|
222
|
+
if (widgetConfig.type === 'number') {
|
|
223
|
+
const currentValue =
|
|
224
|
+
configData[widgetConfig.property];
|
|
225
|
+
const percentage = calculatePercentage(
|
|
226
|
+
currentValue,
|
|
227
|
+
widgetConfig.min,
|
|
228
|
+
widgetConfig.max,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<div
|
|
233
|
+
key={widgetConfig.property}
|
|
234
|
+
className="visual-editor-slider"
|
|
235
|
+
>
|
|
236
|
+
<div className="slider-header">
|
|
237
|
+
<label
|
|
238
|
+
htmlFor={widgetConfig.property}
|
|
239
|
+
className="slider-label"
|
|
240
|
+
>
|
|
241
|
+
{widgetConfig.label}
|
|
242
|
+
</label>
|
|
243
|
+
<span className="slider-value">
|
|
244
|
+
{formatValue(
|
|
245
|
+
currentValue,
|
|
246
|
+
widgetConfig.min,
|
|
247
|
+
widgetConfig.max,
|
|
248
|
+
)}
|
|
249
|
+
</span>
|
|
250
|
+
</div>
|
|
251
|
+
<div className="slider-container">
|
|
252
|
+
<input
|
|
253
|
+
type="range"
|
|
254
|
+
id={widgetConfig.property}
|
|
255
|
+
className="slider-input"
|
|
256
|
+
min={widgetConfig.min}
|
|
257
|
+
max={widgetConfig.max}
|
|
258
|
+
step="any"
|
|
259
|
+
onInput={onInputUpdate(
|
|
260
|
+
widgetConfig.property,
|
|
261
|
+
configData[widgetConfig.property],
|
|
262
|
+
)}
|
|
263
|
+
defaultValue={currentValue}
|
|
264
|
+
/>
|
|
265
|
+
<div
|
|
266
|
+
className="slider-track-fill"
|
|
267
|
+
style={{ width: `${percentage}%` }}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
<div className="slider-bounds">
|
|
271
|
+
<span className="min-value">
|
|
272
|
+
{widgetConfig.min}
|
|
273
|
+
</span>
|
|
274
|
+
<span className="max-value">
|
|
275
|
+
{widgetConfig.max}
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
})}
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
QuestionMarkSVG,
|
|
27
27
|
SearchSVG,
|
|
28
28
|
SparklesSVG,
|
|
29
|
+
AdjustmentSVG,
|
|
29
30
|
} from '../Icons';
|
|
30
31
|
import { MicSVG } from '../Icons/MicSVG';
|
|
31
32
|
import { sortFileTree } from '../sortFileTree';
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
enableAIChat,
|
|
42
43
|
} from '../featureFlags';
|
|
43
44
|
import './styles.scss';
|
|
45
|
+
import { VisualEditor } from './VisualEditor';
|
|
44
46
|
|
|
45
47
|
const enableConnectionStatus = true;
|
|
46
48
|
|
|
@@ -113,6 +115,12 @@ export const VZSidebar = ({
|
|
|
113
115
|
<strong>Edit with AI</strong>
|
|
114
116
|
</div>
|
|
115
117
|
),
|
|
118
|
+
|
|
119
|
+
visualEditorToolTipText = (
|
|
120
|
+
<div>
|
|
121
|
+
<strong>Visual Editor</strong>
|
|
122
|
+
</div>
|
|
123
|
+
),
|
|
116
124
|
}: {
|
|
117
125
|
createFileTooltipText?: React.ReactNode;
|
|
118
126
|
createDirTooltipText?: React.ReactNode;
|
|
@@ -125,6 +133,7 @@ export const VZSidebar = ({
|
|
|
125
133
|
disableAutoFollowTooltipText?: React.ReactNode;
|
|
126
134
|
voiceChatToolTipText?: React.ReactNode;
|
|
127
135
|
aiChatToolTipText?: React.ReactNode;
|
|
136
|
+
visualEditorToolTipText?: React.ReactNode;
|
|
128
137
|
}) => {
|
|
129
138
|
const {
|
|
130
139
|
files,
|
|
@@ -133,6 +142,8 @@ export const VZSidebar = ({
|
|
|
133
142
|
setIsDocOpen,
|
|
134
143
|
isSearchOpen,
|
|
135
144
|
setIsSearchOpen,
|
|
145
|
+
isVisualEditorOpen,
|
|
146
|
+
setIsVisualEditorOpen,
|
|
136
147
|
isAIChatOpen,
|
|
137
148
|
setIsAIChatOpen,
|
|
138
149
|
handleOpenCreateFileModal,
|
|
@@ -335,6 +346,7 @@ export const VZSidebar = ({
|
|
|
335
346
|
onClick={() => {
|
|
336
347
|
setIsSearchOpen(false);
|
|
337
348
|
setIsAIChatOpen(false);
|
|
349
|
+
setIsVisualEditorOpen(false);
|
|
338
350
|
setSidebarView(false); // Switch to files view
|
|
339
351
|
}}
|
|
340
352
|
>
|
|
@@ -356,6 +368,7 @@ export const VZSidebar = ({
|
|
|
356
368
|
onClick={() => {
|
|
357
369
|
setIsSearchOpen(true);
|
|
358
370
|
setIsAIChatOpen(false);
|
|
371
|
+
setIsVisualEditorOpen(false);
|
|
359
372
|
setSidebarView(false); // Switch to files view (search uses same width as files)
|
|
360
373
|
}}
|
|
361
374
|
>
|
|
@@ -363,6 +376,50 @@ export const VZSidebar = ({
|
|
|
363
376
|
</i>
|
|
364
377
|
</OverlayTrigger>
|
|
365
378
|
|
|
379
|
+
{enableAIChat && (
|
|
380
|
+
<OverlayTrigger
|
|
381
|
+
placement="right"
|
|
382
|
+
overlay={
|
|
383
|
+
<Tooltip id="ai-chat-tooltip">
|
|
384
|
+
{aiChatToolTipText}
|
|
385
|
+
</Tooltip>
|
|
386
|
+
}
|
|
387
|
+
>
|
|
388
|
+
<i
|
|
389
|
+
id="ai-chat-icon"
|
|
390
|
+
className="icon-button icon-button-dark"
|
|
391
|
+
onClick={() => {
|
|
392
|
+
setIsAIChatOpen(true);
|
|
393
|
+
setIsSearchOpen(false);
|
|
394
|
+
setIsVisualEditorOpen(false);
|
|
395
|
+
}}
|
|
396
|
+
>
|
|
397
|
+
<SparklesSVG />
|
|
398
|
+
</i>
|
|
399
|
+
</OverlayTrigger>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
<OverlayTrigger
|
|
403
|
+
placement="right"
|
|
404
|
+
overlay={
|
|
405
|
+
<Tooltip id="visual-editor-tooltip">
|
|
406
|
+
{visualEditorToolTipText}
|
|
407
|
+
</Tooltip>
|
|
408
|
+
}
|
|
409
|
+
>
|
|
410
|
+
<i
|
|
411
|
+
id="visual-editor-icon"
|
|
412
|
+
className="icon-button icon-button-dark"
|
|
413
|
+
onClick={() => {
|
|
414
|
+
setIsVisualEditorOpen(true);
|
|
415
|
+
setIsAIChatOpen(false);
|
|
416
|
+
setIsSearchOpen(false);
|
|
417
|
+
}}
|
|
418
|
+
>
|
|
419
|
+
<AdjustmentSVG />
|
|
420
|
+
</i>
|
|
421
|
+
</OverlayTrigger>
|
|
422
|
+
|
|
366
423
|
<OverlayTrigger
|
|
367
424
|
placement="right"
|
|
368
425
|
overlay={
|
|
@@ -516,6 +573,10 @@ export const VZSidebar = ({
|
|
|
516
573
|
<div className="sidebar-search">
|
|
517
574
|
<Search />
|
|
518
575
|
</div>
|
|
576
|
+
) : isVisualEditorOpen ? (
|
|
577
|
+
<div className="sidebar-visual-editor">
|
|
578
|
+
<VisualEditor />
|
|
579
|
+
</div>
|
|
519
580
|
) : (
|
|
520
581
|
<div className="sidebar-files">
|
|
521
582
|
{isDragOver ? (
|
|
@@ -569,6 +630,7 @@ export const VZSidebar = ({
|
|
|
569
630
|
onClick={handleExportToZip}
|
|
570
631
|
title="Export files to ZIP"
|
|
571
632
|
>
|
|
633
|
+
<i className="bi bi-download"></i>
|
|
572
634
|
{exportButtonText}
|
|
573
635
|
</button>
|
|
574
636
|
)}
|
|
@@ -578,6 +640,7 @@ export const VZSidebar = ({
|
|
|
578
640
|
onClick={handleCopyForAI}
|
|
579
641
|
title="Copy files formatted for AI"
|
|
580
642
|
>
|
|
643
|
+
<i className="bi bi-clipboard"></i>
|
|
581
644
|
{copyButtonText}
|
|
582
645
|
</button>
|
|
583
646
|
)}
|
|
@@ -587,6 +650,7 @@ export const VZSidebar = ({
|
|
|
587
650
|
onClick={handlePasteForAI}
|
|
588
651
|
title="Paste files from AI"
|
|
589
652
|
>
|
|
653
|
+
<i className="bi bi-clipboard-plus"></i>
|
|
590
654
|
{pasteButtonText}
|
|
591
655
|
</button>
|
|
592
656
|
</div>
|
|
@@ -67,7 +67,8 @@
|
|
|
67
67
|
|
|
68
68
|
.sidebar-files,
|
|
69
69
|
.sidebar-search,
|
|
70
|
-
.sidebar-ai-chat
|
|
70
|
+
.sidebar-ai-chat,
|
|
71
|
+
.sidebar-visual-editor {
|
|
71
72
|
width: 100%;
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -317,6 +318,10 @@
|
|
|
317
318
|
cursor: pointer;
|
|
318
319
|
transition: all 0.2s ease;
|
|
319
320
|
|
|
321
|
+
i {
|
|
322
|
+
margin-right: 8px;
|
|
323
|
+
}
|
|
324
|
+
|
|
320
325
|
&:hover {
|
|
321
326
|
background: var(--vh-color-hover-dark);
|
|
322
327
|
border-color: var(--vh-color-neutral-04);
|
|
@@ -382,4 +387,211 @@
|
|
|
382
387
|
color: #000000;
|
|
383
388
|
z-index: 1;
|
|
384
389
|
}
|
|
390
|
+
|
|
391
|
+
// Visual Editor Slider Styles
|
|
392
|
+
.visual-editor {
|
|
393
|
+
display: flex;
|
|
394
|
+
flex-direction: column;
|
|
395
|
+
gap: 16px;
|
|
396
|
+
width: 100%;
|
|
397
|
+
padding: 10px;
|
|
398
|
+
box-sizing: border-box;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.visual-editor-slider {
|
|
402
|
+
display: flex;
|
|
403
|
+
flex-direction: column;
|
|
404
|
+
gap: 8px;
|
|
405
|
+
padding: 12px 10px;
|
|
406
|
+
background: var(--vh-color-neutral-01);
|
|
407
|
+
border: 1px solid var(--vh-color-neutral-02);
|
|
408
|
+
border-radius: 8px;
|
|
409
|
+
transition: all 0.2s ease;
|
|
410
|
+
width: 100%;
|
|
411
|
+
box-sizing: border-box;
|
|
412
|
+
|
|
413
|
+
&:hover {
|
|
414
|
+
border-color: var(--vh-color-neutral-03);
|
|
415
|
+
background: rgba(255, 255, 255, 0.02);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.slider-header {
|
|
420
|
+
display: flex;
|
|
421
|
+
justify-content: space-between;
|
|
422
|
+
align-items: center;
|
|
423
|
+
margin-bottom: 4px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.slider-label {
|
|
427
|
+
font-family: var(--vzcode-font-family);
|
|
428
|
+
font-size: 13px;
|
|
429
|
+
font-weight: 500;
|
|
430
|
+
color: var(--vh-color-neutral-04);
|
|
431
|
+
margin: 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.slider-value {
|
|
435
|
+
font-family: var(--vzcode-font-family);
|
|
436
|
+
font-size: 13px;
|
|
437
|
+
font-weight: 600;
|
|
438
|
+
color: #66ecff; // SKY color from theme
|
|
439
|
+
background: rgba(102, 236, 255, 0.1);
|
|
440
|
+
padding: 2px 6px;
|
|
441
|
+
border-radius: 4px;
|
|
442
|
+
min-width: 40px;
|
|
443
|
+
text-align: center;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.slider-container {
|
|
447
|
+
position: relative;
|
|
448
|
+
height: 20px;
|
|
449
|
+
display: flex;
|
|
450
|
+
align-items: center;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.slider-input {
|
|
454
|
+
width: 100%;
|
|
455
|
+
height: 6px;
|
|
456
|
+
background: transparent;
|
|
457
|
+
outline: none;
|
|
458
|
+
border: none;
|
|
459
|
+
cursor: pointer;
|
|
460
|
+
position: relative;
|
|
461
|
+
z-index: 2;
|
|
462
|
+
|
|
463
|
+
// Remove default styling
|
|
464
|
+
-webkit-appearance: none;
|
|
465
|
+
appearance: none;
|
|
466
|
+
|
|
467
|
+
// Custom track
|
|
468
|
+
&::-webkit-slider-track {
|
|
469
|
+
width: 100%;
|
|
470
|
+
height: 6px;
|
|
471
|
+
background: var(--vh-color-neutral-02);
|
|
472
|
+
border-radius: 3px;
|
|
473
|
+
border: none;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
&::-moz-range-track {
|
|
477
|
+
width: 100%;
|
|
478
|
+
height: 6px;
|
|
479
|
+
background: var(--vh-color-neutral-02);
|
|
480
|
+
border-radius: 3px;
|
|
481
|
+
border: none;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Custom thumb
|
|
485
|
+
&::-webkit-slider-thumb {
|
|
486
|
+
-webkit-appearance: none;
|
|
487
|
+
appearance: none;
|
|
488
|
+
width: 18px;
|
|
489
|
+
height: 18px;
|
|
490
|
+
background: linear-gradient(135deg, #66ecff, #00ffff);
|
|
491
|
+
border-radius: 50%;
|
|
492
|
+
cursor: pointer;
|
|
493
|
+
border: 2px solid var(--vh-color-neutral-01);
|
|
494
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
495
|
+
transition: all 0.2s ease;
|
|
496
|
+
position: relative;
|
|
497
|
+
z-index: 3;
|
|
498
|
+
|
|
499
|
+
&:hover {
|
|
500
|
+
transform: scale(1.1);
|
|
501
|
+
box-shadow: 0 3px 8px rgba(102, 236, 255, 0.4);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
&:active {
|
|
505
|
+
transform: scale(1.05);
|
|
506
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
&::-moz-range-thumb {
|
|
511
|
+
width: 18px;
|
|
512
|
+
height: 18px;
|
|
513
|
+
background: linear-gradient(135deg, #66ecff, #00ffff);
|
|
514
|
+
border-radius: 50%;
|
|
515
|
+
cursor: pointer;
|
|
516
|
+
border: 2px solid var(--vh-color-neutral-01);
|
|
517
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
518
|
+
transition: all 0.2s ease;
|
|
519
|
+
|
|
520
|
+
&:hover {
|
|
521
|
+
transform: scale(1.1);
|
|
522
|
+
box-shadow: 0 3px 8px rgba(102, 236, 255, 0.4);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
&:active {
|
|
526
|
+
transform: scale(1.05);
|
|
527
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Focus styles
|
|
532
|
+
&:focus {
|
|
533
|
+
outline: none;
|
|
534
|
+
|
|
535
|
+
&::-webkit-slider-thumb {
|
|
536
|
+
box-shadow: 0 0 0 3px rgba(102, 236, 255, 0.3);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
&::-moz-range-thumb {
|
|
540
|
+
box-shadow: 0 0 0 3px rgba(102, 236, 255, 0.3);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.slider-track-fill {
|
|
546
|
+
position: absolute;
|
|
547
|
+
top: 50%;
|
|
548
|
+
left: 0;
|
|
549
|
+
height: 6px;
|
|
550
|
+
background: linear-gradient(90deg, #77fd8c, #66ecff);
|
|
551
|
+
border-radius: 3px;
|
|
552
|
+
transform: translateY(-50%);
|
|
553
|
+
transition: width 0.1s ease;
|
|
554
|
+
pointer-events: none;
|
|
555
|
+
z-index: 1;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.slider-bounds {
|
|
559
|
+
display: flex;
|
|
560
|
+
justify-content: space-between;
|
|
561
|
+
align-items: center;
|
|
562
|
+
margin-top: 2px;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.min-value,
|
|
566
|
+
.max-value {
|
|
567
|
+
font-family: var(--vzcode-font-family);
|
|
568
|
+
font-size: 11px;
|
|
569
|
+
color: var(--vh-color-neutral-03);
|
|
570
|
+
font-weight: 400;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Empty State Component Styles
|
|
574
|
+
.empty-state {
|
|
575
|
+
display: flex;
|
|
576
|
+
flex-direction: column;
|
|
577
|
+
align-items: center;
|
|
578
|
+
justify-content: center;
|
|
579
|
+
padding: 24px 16px;
|
|
580
|
+
margin: 16px;
|
|
581
|
+
background: var(--vh-color-neutral-01);
|
|
582
|
+
border: 1px solid var(--vh-color-neutral-02);
|
|
583
|
+
border-radius: 8px;
|
|
584
|
+
text-align: center;
|
|
585
|
+
font-family: var(--vzcode-font-family);
|
|
586
|
+
font-size: 14px;
|
|
587
|
+
font-weight: 400;
|
|
588
|
+
color: var(--vh-color-neutral-04);
|
|
589
|
+
line-height: 1.5;
|
|
590
|
+
min-height: 80px;
|
|
591
|
+
|
|
592
|
+
&:hover {
|
|
593
|
+
border-color: var(--vh-color-neutral-03);
|
|
594
|
+
background: rgba(255, 255, 255, 0.02);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
385
597
|
}
|
package/src/client/bootstrap.ts
CHANGED
|
@@ -12,6 +12,9 @@ import Tooltip from 'react-bootstrap/cjs/Tooltip.js';
|
|
|
12
12
|
// Pull in custom CSS from vizhub-ui.
|
|
13
13
|
import 'vizhub-ui/dist/vizhub-ui.css';
|
|
14
14
|
|
|
15
|
+
// Pull in Bootstrap Icons CSS.
|
|
16
|
+
import 'bootstrap-icons/font/bootstrap-icons.css';
|
|
17
|
+
|
|
15
18
|
export {
|
|
16
19
|
Form,
|
|
17
20
|
Button,
|
package/src/client/useActions.ts
CHANGED
|
@@ -64,6 +64,17 @@ export const useActions = (
|
|
|
64
64
|
[dispatch],
|
|
65
65
|
);
|
|
66
66
|
|
|
67
|
+
// True to show the visual editor
|
|
68
|
+
const setIsVisualEditorOpen = useCallback(
|
|
69
|
+
(value: boolean) => {
|
|
70
|
+
dispatch({
|
|
71
|
+
type: 'set_is_visual_editor_open',
|
|
72
|
+
value: value,
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
[dispatch],
|
|
76
|
+
);
|
|
77
|
+
|
|
67
78
|
// True to show the settings modal.
|
|
68
79
|
const setIsSearchOpen = useCallback(
|
|
69
80
|
(value: boolean) => {
|
|
@@ -253,6 +264,7 @@ export const useActions = (
|
|
|
253
264
|
openTab,
|
|
254
265
|
closeTabs,
|
|
255
266
|
setTheme,
|
|
267
|
+
setIsVisualEditorOpen,
|
|
256
268
|
setIsSearchOpen,
|
|
257
269
|
setSearch,
|
|
258
270
|
setSearchResults,
|