@walkrstudio/studio 0.2.1

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.
@@ -0,0 +1,443 @@
1
+ import type { Step } from "@walkrstudio/core";
2
+ import type React from "react";
3
+
4
+ interface StepPanelProps {
5
+ step: Step | null;
6
+ stepIndex: number | null;
7
+ onUpdate: (stepIndex: number, updates: Partial<Step>) => void;
8
+ }
9
+
10
+ const STEP_COLORS: Record<string, string> = {
11
+ moveTo: "#1d3a5c",
12
+ moveToCoords: "#1a3050",
13
+ click: "#3b1d5c",
14
+ clickCoords: "#301850",
15
+ type: "#1d5c2a",
16
+ scroll: "#5c3b1d",
17
+ wait: "#333",
18
+ zoom: "#5c1d3b",
19
+ pan: "#1d5c5c",
20
+ highlight: "#5c5c1d",
21
+ clearCache: "#5c2a1d",
22
+ };
23
+
24
+ const labelStyle: React.CSSProperties = {
25
+ fontSize: 12,
26
+ color: "#888",
27
+ marginBottom: 4,
28
+ display: "block",
29
+ };
30
+
31
+ const inputStyle: React.CSSProperties = {
32
+ background: "#222",
33
+ border: "1px solid #333",
34
+ color: "#e8e8e8",
35
+ borderRadius: 4,
36
+ padding: "6px 8px",
37
+ width: "100%",
38
+ fontSize: 13,
39
+ };
40
+
41
+ const fieldGroupStyle: React.CSSProperties = {
42
+ marginBottom: 12,
43
+ };
44
+
45
+ const EASING_OPTIONS = ["linear", "ease", "ease-in", "ease-out", "ease-in-out"];
46
+
47
+ export function StepPanel({ step, stepIndex, onUpdate }: StepPanelProps) {
48
+ if (step === null || stepIndex === null) {
49
+ return (
50
+ <div style={panelStyle}>
51
+ <div style={{ color: "#555", fontSize: 13, marginTop: 40, textAlign: "center" }}>
52
+ Select a step to edit its options.
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ const updateOptions = (updates: Record<string, unknown>) => {
59
+ onUpdate(stepIndex, { options: { ...step.options, ...updates } } as Partial<Step>);
60
+ };
61
+
62
+ const updateDuration = (duration: number) => {
63
+ onUpdate(stepIndex, { duration });
64
+ };
65
+
66
+ return (
67
+ <div style={panelStyle}>
68
+ {/* Step type badge */}
69
+ <div style={{ marginBottom: 16 }}>
70
+ <span
71
+ style={{
72
+ display: "inline-block",
73
+ background: STEP_COLORS[step.type] ?? "#333",
74
+ color: "#e8e8e8",
75
+ fontSize: 11,
76
+ fontWeight: 600,
77
+ textTransform: "uppercase",
78
+ padding: "4px 10px",
79
+ borderRadius: 4,
80
+ }}
81
+ >
82
+ {step.type}
83
+ </span>
84
+ </div>
85
+
86
+ <div style={{ fontSize: 13, color: "#ccc", marginBottom: 16 }}>Step {stepIndex + 1}</div>
87
+
88
+ {/* Duration */}
89
+ <div style={fieldGroupStyle}>
90
+ <label style={labelStyle}>Duration (ms)</label>
91
+ <input
92
+ type="number"
93
+ style={inputStyle}
94
+ value={step.duration}
95
+ min={0}
96
+ onChange={(e) => updateDuration(Math.max(0, parseInt(e.target.value, 10) || 0))}
97
+ />
98
+ </div>
99
+
100
+ {/* Step-specific fields */}
101
+ {renderStepFields(step, updateOptions)}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ function renderStepFields(step: Step, updateOptions: (updates: Record<string, unknown>) => void) {
107
+ const opts = step.options as Record<string, unknown>;
108
+
109
+ switch (step.type) {
110
+ case "moveTo":
111
+ return (
112
+ <>
113
+ <TextField
114
+ label="Selector"
115
+ value={(opts.selector as string) ?? ""}
116
+ onChange={(v) => updateOptions({ selector: v })}
117
+ />
118
+ <SelectField
119
+ label="Easing"
120
+ value={(opts.easing as string) ?? "linear"}
121
+ options={EASING_OPTIONS}
122
+ onChange={(v) => updateOptions({ easing: v })}
123
+ />
124
+ </>
125
+ );
126
+
127
+ case "moveToCoords":
128
+ return (
129
+ <>
130
+ <NumberField
131
+ label="X"
132
+ value={opts.x as number}
133
+ onChange={(v) => updateOptions({ x: v })}
134
+ />
135
+ <NumberField
136
+ label="Y"
137
+ value={opts.y as number}
138
+ onChange={(v) => updateOptions({ y: v })}
139
+ />
140
+ <SelectField
141
+ label="Easing"
142
+ value={(opts.easing as string) ?? "linear"}
143
+ options={EASING_OPTIONS}
144
+ onChange={(v) => updateOptions({ easing: v })}
145
+ />
146
+ </>
147
+ );
148
+
149
+ case "click":
150
+ return (
151
+ <>
152
+ <TextField
153
+ label="Selector"
154
+ value={(opts.selector as string) ?? ""}
155
+ onChange={(v) => updateOptions({ selector: v })}
156
+ />
157
+ <SelectField
158
+ label="Button"
159
+ value={(opts.button as string) ?? "left"}
160
+ options={["left", "right", "middle"]}
161
+ onChange={(v) => updateOptions({ button: v })}
162
+ />
163
+ <CheckboxField
164
+ label="Double click"
165
+ checked={!!opts.double}
166
+ onChange={(v) => updateOptions({ double: v })}
167
+ />
168
+ </>
169
+ );
170
+
171
+ case "clickCoords":
172
+ return (
173
+ <>
174
+ <NumberField
175
+ label="X"
176
+ value={opts.x as number}
177
+ onChange={(v) => updateOptions({ x: v })}
178
+ />
179
+ <NumberField
180
+ label="Y"
181
+ value={opts.y as number}
182
+ onChange={(v) => updateOptions({ y: v })}
183
+ />
184
+ <SelectField
185
+ label="Button"
186
+ value={(opts.button as string) ?? "left"}
187
+ options={["left", "right", "middle"]}
188
+ onChange={(v) => updateOptions({ button: v })}
189
+ />
190
+ <CheckboxField
191
+ label="Double click"
192
+ checked={!!opts.double}
193
+ onChange={(v) => updateOptions({ double: v })}
194
+ />
195
+ </>
196
+ );
197
+
198
+ case "type":
199
+ return (
200
+ <>
201
+ <div style={fieldGroupStyle}>
202
+ <label style={labelStyle}>Text</label>
203
+ <textarea
204
+ style={{ ...inputStyle, minHeight: 60, resize: "vertical" }}
205
+ value={(opts.text as string) ?? ""}
206
+ onChange={(e) => updateOptions({ text: e.target.value })}
207
+ />
208
+ </div>
209
+ <NumberField
210
+ label="Delay (ms per char)"
211
+ value={(opts.delay as number) ?? 0}
212
+ onChange={(v) => updateOptions({ delay: v })}
213
+ />
214
+ <TextField
215
+ label="Selector"
216
+ value={(opts.selector as string) ?? ""}
217
+ onChange={(v) => updateOptions({ selector: v })}
218
+ />
219
+ </>
220
+ );
221
+
222
+ case "scroll":
223
+ return (
224
+ <>
225
+ <NumberField
226
+ label="X"
227
+ value={opts.x as number}
228
+ onChange={(v) => updateOptions({ x: v })}
229
+ />
230
+ <NumberField
231
+ label="Y"
232
+ value={opts.y as number}
233
+ onChange={(v) => updateOptions({ y: v })}
234
+ />
235
+ <CheckboxField
236
+ label="Smooth"
237
+ checked={!!opts.smooth}
238
+ onChange={(v) => updateOptions({ smooth: v })}
239
+ />
240
+ </>
241
+ );
242
+
243
+ case "wait":
244
+ return (
245
+ <NumberField
246
+ label="Wait (ms)"
247
+ value={opts.ms as number}
248
+ onChange={(v) => updateOptions({ ms: v })}
249
+ />
250
+ );
251
+
252
+ case "zoom":
253
+ return (
254
+ <>
255
+ <NumberField
256
+ label="Level"
257
+ value={opts.level as number}
258
+ onChange={(v) => updateOptions({ level: v })}
259
+ min={0.5}
260
+ max={4}
261
+ step={0.1}
262
+ />
263
+ <CheckboxField
264
+ label="Follow"
265
+ checked={!!opts.follow}
266
+ onChange={(v) => updateOptions({ follow: v })}
267
+ />
268
+ <SelectField
269
+ label="Easing"
270
+ value={(opts.easing as string) ?? "linear"}
271
+ options={EASING_OPTIONS}
272
+ onChange={(v) => updateOptions({ easing: v })}
273
+ />
274
+ </>
275
+ );
276
+
277
+ case "pan":
278
+ return (
279
+ <>
280
+ <NumberField
281
+ label="X"
282
+ value={opts.x as number}
283
+ onChange={(v) => updateOptions({ x: v })}
284
+ />
285
+ <NumberField
286
+ label="Y"
287
+ value={opts.y as number}
288
+ onChange={(v) => updateOptions({ y: v })}
289
+ />
290
+ <SelectField
291
+ label="Easing"
292
+ value={(opts.easing as string) ?? "linear"}
293
+ options={EASING_OPTIONS}
294
+ onChange={(v) => updateOptions({ easing: v })}
295
+ />
296
+ </>
297
+ );
298
+
299
+ case "highlight":
300
+ return (
301
+ <>
302
+ <TextField
303
+ label="Selector"
304
+ value={(opts.selector as string) ?? ""}
305
+ onChange={(v) => updateOptions({ selector: v })}
306
+ />
307
+ <div style={fieldGroupStyle}>
308
+ <label style={labelStyle}>Color</label>
309
+ <input
310
+ type="color"
311
+ style={{ ...inputStyle, padding: 2, height: 32 }}
312
+ value={(opts.color as string) ?? "#ffff00"}
313
+ onChange={(e) => updateOptions({ color: e.target.value })}
314
+ />
315
+ </div>
316
+ <NumberField
317
+ label="Duration (ms)"
318
+ value={(opts.duration as number) ?? 500}
319
+ onChange={(v) => updateOptions({ duration: v })}
320
+ />
321
+ </>
322
+ );
323
+
324
+ case "clearCache":
325
+ return (
326
+ <div style={{ fontSize: 12, color: "#888" }}>
327
+ Clears cookies, localStorage, and sessionStorage, then reloads the page.
328
+ </div>
329
+ );
330
+
331
+ default:
332
+ return null;
333
+ }
334
+ }
335
+
336
+ function NumberField({
337
+ label,
338
+ value,
339
+ onChange,
340
+ min,
341
+ max,
342
+ step,
343
+ }: {
344
+ label: string;
345
+ value: number;
346
+ onChange: (v: number) => void;
347
+ min?: number;
348
+ max?: number;
349
+ step?: number;
350
+ }) {
351
+ return (
352
+ <div style={fieldGroupStyle}>
353
+ <label style={labelStyle}>{label}</label>
354
+ <input
355
+ type="number"
356
+ style={inputStyle}
357
+ value={value ?? 0}
358
+ min={min}
359
+ max={max}
360
+ step={step}
361
+ onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
362
+ />
363
+ </div>
364
+ );
365
+ }
366
+
367
+ function TextField({
368
+ label,
369
+ value,
370
+ onChange,
371
+ }: {
372
+ label: string;
373
+ value: string;
374
+ onChange: (v: string) => void;
375
+ }) {
376
+ return (
377
+ <div style={fieldGroupStyle}>
378
+ <label style={labelStyle}>{label}</label>
379
+ <input
380
+ type="text"
381
+ style={inputStyle}
382
+ value={value}
383
+ onChange={(e) => onChange(e.target.value)}
384
+ />
385
+ </div>
386
+ );
387
+ }
388
+
389
+ function SelectField({
390
+ label,
391
+ value,
392
+ options,
393
+ onChange,
394
+ }: {
395
+ label: string;
396
+ value: string;
397
+ options: string[];
398
+ onChange: (v: string) => void;
399
+ }) {
400
+ return (
401
+ <div style={fieldGroupStyle}>
402
+ <label style={labelStyle}>{label}</label>
403
+ <select style={inputStyle} value={value} onChange={(e) => onChange(e.target.value)}>
404
+ {options.map((opt) => (
405
+ <option key={opt} value={opt}>
406
+ {opt}
407
+ </option>
408
+ ))}
409
+ </select>
410
+ </div>
411
+ );
412
+ }
413
+
414
+ function CheckboxField({
415
+ label,
416
+ checked,
417
+ onChange,
418
+ }: {
419
+ label: string;
420
+ checked: boolean;
421
+ onChange: (v: boolean) => void;
422
+ }) {
423
+ return (
424
+ <div style={{ ...fieldGroupStyle, display: "flex", alignItems: "center", gap: 8 }}>
425
+ <input
426
+ type="checkbox"
427
+ checked={checked}
428
+ onChange={(e) => onChange(e.target.checked)}
429
+ style={{ accentColor: "#3b82f6" }}
430
+ />
431
+ <label style={{ ...labelStyle, marginBottom: 0 }}>{label}</label>
432
+ </div>
433
+ );
434
+ }
435
+
436
+ const panelStyle: React.CSSProperties = {
437
+ width: 280,
438
+ background: "#161616",
439
+ borderLeft: "1px solid #2a2a2a",
440
+ padding: 16,
441
+ overflowY: "auto",
442
+ flexShrink: 0,
443
+ };