js-kompo-utils 1.0.14 → 1.0.15
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/components/VlTutorialStepBuilder.vue +137 -586
- package/components/step-builder/SbContextMenu.vue +41 -0
- package/components/step-builder/SbGraphView.vue +93 -0
- package/components/step-builder/SbHeader.vue +16 -0
- package/components/step-builder/SbHistoryDrawer.vue +43 -0
- package/components/step-builder/SbPathPreview.vue +57 -0
- package/components/step-builder/SbStepList.vue +87 -0
- package/components/step-builder/SbTimeline.vue +66 -0
- package/components/step-builder/cards/SbActionsCard.vue +26 -0
- package/components/step-builder/cards/SbAdvanceCard.vue +43 -0
- package/components/step-builder/cards/SbConditionsCard.vue +20 -0
- package/components/step-builder/cards/SbContentCard.vue +45 -0
- package/components/step-builder/cards/SbCursorCard.vue +102 -0
- package/components/step-builder/cards/SbGlobalOptsCard.vue +40 -0
- package/components/step-builder/cards/SbHighlightCard.vue +40 -0
- package/components/step-builder/cards/SbHoverCard.vue +32 -0
- package/components/step-builder/cards/SbOptionsCard.vue +50 -0
- package/components/step-builder/cards/SbOutputCard.vue +17 -0
- package/components/step-builder/cards/SbPositionCard.vue +87 -0
- package/components/step-builder/cards/SbScrollCard.vue +17 -0
- package/docs/extending-tutorial-system.md +376 -0
- package/package.json +10 -9
- package/tutorials/TutorialConfig.js +4 -0
- package/tutorials/TutorialEngine.js +49 -3
- package/tutorials/extensions/HideIfExtension.js +12 -0
- package/tutorials/extensions/LinkNextExtension.js +117 -0
- package/tutorials/modules/Cursor.js +24 -4
- package/tutorials/modules/Highlight.js +7 -5
- package/tutorials/modules/HoverForcer.js +1 -0
- package/tutorials/modules/Overlay.js +2 -0
- package/tutorials/step-builder/StepBuilderState.js +265 -0
- package/tutorials/tutorial.scss +186 -0
- package/tutorials/step-builder/step-builder-bridge.js +0 -436
|
@@ -1,375 +1,94 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-show="visible" class="sb"
|
|
2
|
+
<div v-show="visible" class="sb-wrapper">
|
|
3
|
+
<sb-history-drawer />
|
|
4
|
+
<div class="sb" ref="panel" @keydown.left.prevent="prevStep" @keydown.right.prevent="nextStep" tabindex="0">
|
|
3
5
|
<div class="sb-resize" @mousedown="startResize"></div>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<
|
|
7
|
-
<span class="sb-header-title">Step Builder</span>
|
|
8
|
-
<button class="sb-hbtn" title="Spectator" @click="spectatorMode">▶</button>
|
|
9
|
-
<button class="sb-hbtn" title="Copy All" @click="copyAll">📋</button>
|
|
10
|
-
<button class="sb-hbtn" title="Close" @click="visible = false">×</button>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<!-- Step List -->
|
|
14
|
-
<div class="sb-steps">
|
|
15
|
-
<div class="sb-steps-bar">
|
|
16
|
-
<span class="sb-steps-count">STEPS ({{ steps.length }})</span>
|
|
17
|
-
<button class="sb-btn sb-btn-sm sb-btn-green" @click="addStep">+</button>
|
|
18
|
-
</div>
|
|
19
|
-
<div class="sb-steps-list">
|
|
20
|
-
<div v-for="(s, i) in steps" :key="i"
|
|
21
|
-
class="sb-step" :class="{ 'sb-step--active': i === idx }"
|
|
22
|
-
@click="selectStep(i)">
|
|
23
|
-
<span class="sb-step-num">{{ i }}</span>
|
|
24
|
-
<span class="sb-step-text">{{ stepPreview(s) }}</span>
|
|
25
|
-
<span class="sb-step-icons">
|
|
26
|
-
<span v-if="s.cursor" title="cursor">⌖</span>
|
|
27
|
-
<span v-if="s.highlight" title="highlight">☆</span>
|
|
28
|
-
<span v-if="s.options" title="options">…</span>
|
|
29
|
-
</span>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
<div class="sb-step-actions">
|
|
33
|
-
<button class="sb-hbtn" @click="moveStepUp" title="Move up">↑</button>
|
|
34
|
-
<button class="sb-hbtn" @click="moveStepDown" title="Move down">↓</button>
|
|
35
|
-
<button class="sb-hbtn" @click="duplicateStep" title="Duplicate">⋔</button>
|
|
36
|
-
<button class="sb-hbtn sb-hbtn--danger" @click="deleteStep" title="Delete">✕</button>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<!-- Detail Zone -->
|
|
6
|
+
<sb-header />
|
|
7
|
+
<sb-step-list />
|
|
8
|
+
<sb-context-menu />
|
|
41
9
|
<div class="sb-detail">
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<!-- Position -->
|
|
60
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.position }">
|
|
61
|
-
<div class="sb-card-head" @click="toggle('position')">
|
|
62
|
-
<span>POSITION</span>
|
|
63
|
-
<span class="sb-chevron">{{ cards.position ? '▼' : '▶' }}</span>
|
|
64
|
-
</div>
|
|
65
|
-
<div v-show="cards.position" class="sb-card-body">
|
|
66
|
-
<div class="sb-row">
|
|
67
|
-
<label class="sb-lbl">Position</label>
|
|
68
|
-
<select v-model="step.position" @change="previewNow()" class="sb-input">
|
|
69
|
-
<option value="left">Left of avatar</option>
|
|
70
|
-
<option value="right">Right of avatar</option>
|
|
71
|
-
<option value="top">Above avatar</option>
|
|
72
|
-
<option value="bottom">Below avatar</option>
|
|
73
|
-
<option value="custom">Custom (manual)</option>
|
|
74
|
-
</select>
|
|
75
|
-
</div>
|
|
76
|
-
<!-- Custom position: free left/top values -->
|
|
77
|
-
<template v-if="step.position === 'custom'">
|
|
78
|
-
<div class="sb-row">
|
|
79
|
-
<label class="sb-lbl">Left</label>
|
|
80
|
-
<input :value="step.bubbleLeft || ''" @input="setStepProp('bubbleLeft', $event.target.value)" class="sb-input" placeholder="e.g. 50%, 300px, 20vw">
|
|
81
|
-
</div>
|
|
82
|
-
<div class="sb-row">
|
|
83
|
-
<label class="sb-lbl">Top</label>
|
|
84
|
-
<input :value="step.bubbleTop || ''" @input="setStepProp('bubbleTop', $event.target.value)" class="sb-input" placeholder="e.g. 50%, 200px, 30vh">
|
|
85
|
-
</div>
|
|
86
|
-
</template>
|
|
87
|
-
<div class="sb-row">
|
|
88
|
-
<label class="sb-lbl">Avatar Pos</label>
|
|
89
|
-
<select v-model="step.align" @change="previewNow()" class="sb-input">
|
|
90
|
-
<option value="left">Left</option>
|
|
91
|
-
<option value="center">Center</option>
|
|
92
|
-
<option value="right">Right</option>
|
|
93
|
-
<option value="custom">Custom (manual)</option>
|
|
94
|
-
</select>
|
|
95
|
-
</div>
|
|
96
|
-
<template v-if="step.align === 'custom'">
|
|
97
|
-
<div class="sb-row">
|
|
98
|
-
<label class="sb-lbl">Left</label>
|
|
99
|
-
<input :value="step.avatarLeft || ''" @input="setStepProp('avatarLeft', $event.target.value)" class="sb-input" placeholder="e.g. 30%, 200px">
|
|
100
|
-
</div>
|
|
101
|
-
<div class="sb-row">
|
|
102
|
-
<label class="sb-lbl">Top</label>
|
|
103
|
-
<input :value="step.avatarTop || ''" @input="setStepProp('avatarTop', $event.target.value)" class="sb-input" placeholder="e.g. 60%, 400px">
|
|
104
|
-
</div>
|
|
105
|
-
</template>
|
|
106
|
-
<!-- Avatar size override -->
|
|
107
|
-
<div class="sb-row">
|
|
108
|
-
<label class="sb-lbl">Avatar Size</label>
|
|
109
|
-
<input :value="step.avatarSize || ''" @input="setStepProp('avatarSize', $event.target.value); applyAvatarSize()" class="sb-input" placeholder="e.g. 200px, 30vh (empty = default)">
|
|
110
|
-
</div>
|
|
111
|
-
<label class="sb-check"><input type="checkbox" v-model="chatMode" @change="previewNow()"> Chat Mode</label>
|
|
112
|
-
<div class="sb-row sb-row--pick">
|
|
113
|
-
<label class="sb-lbl">Target</label>
|
|
114
|
-
<input :value="step.positionTarget || ''" @input="setStepProp('positionTarget', $event.target.value)" class="sb-input" placeholder="CSS selector">
|
|
115
|
-
<button class="sb-pick" @click="pick('positionTarget')">Pick</button>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<!-- Cursor -->
|
|
121
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.cursor }">
|
|
122
|
-
<div class="sb-card-head" @click="toggle('cursor')">
|
|
123
|
-
<span>CURSOR</span>
|
|
124
|
-
<span class="sb-chevron">{{ cards.cursor ? '▼' : '▶' }}</span>
|
|
125
|
-
</div>
|
|
126
|
-
<div v-show="cards.cursor" class="sb-card-body">
|
|
127
|
-
<div class="sb-row sb-row--pick">
|
|
128
|
-
<label class="sb-lbl">From</label>
|
|
129
|
-
<input :value="cursorFrom" @input="setCursor('from', $event.target.value)" class="sb-input">
|
|
130
|
-
<button class="sb-pick" @click="pickCursor('from')">Pick</button>
|
|
131
|
-
</div>
|
|
132
|
-
<div class="sb-row sb-row--pick">
|
|
133
|
-
<label class="sb-lbl">To</label>
|
|
134
|
-
<input :value="cursorTo" @input="setCursor('to', $event.target.value)" class="sb-input">
|
|
135
|
-
<button class="sb-pick" @click="pickCursor('to')">Pick</button>
|
|
136
|
-
</div>
|
|
137
|
-
<div class="sb-row">
|
|
138
|
-
<label class="sb-lbl">Duration</label>
|
|
139
|
-
<input type="number" :value="cursorDuration" @input="setCursor('duration', parseFloat($event.target.value) || 1.5)" class="sb-input sb-input--num" step="0.1">
|
|
140
|
-
</div>
|
|
141
|
-
<div class="sb-row">
|
|
142
|
-
<label class="sb-check"><input type="checkbox" :checked="cursorClick" @change="setCursor('click', $event.target.checked)"> Click</label>
|
|
143
|
-
<label class="sb-check"><input type="checkbox" :checked="cursorLoop" @change="setCursor('loop', $event.target.checked)"> Loop</label>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
<!-- Highlight -->
|
|
149
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.highlight }">
|
|
150
|
-
<div class="sb-card-head" @click="toggle('highlight')">
|
|
151
|
-
<span>HIGHLIGHT</span>
|
|
152
|
-
<span class="sb-chevron">{{ cards.highlight ? '▼' : '▶' }}</span>
|
|
153
|
-
</div>
|
|
154
|
-
<div v-show="cards.highlight" class="sb-card-body">
|
|
155
|
-
<div class="sb-row sb-row--pick">
|
|
156
|
-
<label class="sb-lbl">Elements</label>
|
|
157
|
-
<input :value="highlightElements" @input="setHighlightElements($event.target.value)" class="sb-input" placeholder="sel1, sel2">
|
|
158
|
-
<button class="sb-pick" @click="pickHighlight">Pick</button>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<!-- Hover -->
|
|
164
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.hover }">
|
|
165
|
-
<div class="sb-card-head" @click="toggle('hover')">
|
|
166
|
-
<span>HOVER</span>
|
|
167
|
-
<span class="sb-chevron">{{ cards.hover ? '▼' : '▶' }}</span>
|
|
168
|
-
</div>
|
|
169
|
-
<div v-show="cards.hover" class="sb-card-body">
|
|
170
|
-
<div class="sb-row sb-row--pick">
|
|
171
|
-
<input v-model="hoverValue" class="sb-input" placeholder="CSS selector">
|
|
172
|
-
<button class="sb-pick" @click="pickField('hover')">Pick</button>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
<!-- Scroll -->
|
|
178
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.scroll }">
|
|
179
|
-
<div class="sb-card-head" @click="toggle('scroll')">
|
|
180
|
-
<span>SCROLL</span>
|
|
181
|
-
<span class="sb-chevron">{{ cards.scroll ? '▼' : '▶' }}</span>
|
|
182
|
-
</div>
|
|
183
|
-
<div v-show="cards.scroll" class="sb-card-body">
|
|
184
|
-
<div class="sb-row sb-row--pick">
|
|
185
|
-
<label class="sb-lbl">Scroll To</label>
|
|
186
|
-
<input :value="step.scrollTo || ''" @input="setStepProp('scrollTo', $event.target.value)" class="sb-input">
|
|
187
|
-
<button class="sb-pick" @click="pickField('scrollTo')">Pick</button>
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
<!-- Options -->
|
|
193
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.options }">
|
|
194
|
-
<div class="sb-card-head" @click="toggle('options')">
|
|
195
|
-
<span>OPTIONS</span>
|
|
196
|
-
<span class="sb-chevron">{{ cards.options ? '▼' : '▶' }}</span>
|
|
197
|
-
</div>
|
|
198
|
-
<div v-show="cards.options" class="sb-card-body">
|
|
199
|
-
<div v-for="(opt, oi) in (step.options || [])" :key="oi" class="sb-opt-row">
|
|
200
|
-
<input v-model="opt.label" class="sb-input" placeholder="Label">
|
|
201
|
-
<select v-model="opt._action" @change="setOptionAction(oi, opt._action)" class="sb-input sb-input--sm">
|
|
202
|
-
<option value="next">Next</option>
|
|
203
|
-
<option value="done">Done</option>
|
|
204
|
-
<option value="goToStep">Go To</option>
|
|
205
|
-
<option value="redirect">Redirect</option>
|
|
206
|
-
</select>
|
|
207
|
-
<button class="sb-pick sb-pick--danger" @click="removeOption(oi)">×</button>
|
|
208
|
-
</div>
|
|
209
|
-
<button class="sb-btn sb-btn-sm sb-btn-green" @click="addOption">+ Option</button>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
|
|
213
|
-
<!-- Actions -->
|
|
214
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.actions }">
|
|
215
|
-
<div class="sb-card-head" @click="toggle('actions')">
|
|
216
|
-
<span>ACTIONS</span>
|
|
217
|
-
<span class="sb-chevron">{{ cards.actions ? '▼' : '▶' }}</span>
|
|
218
|
-
</div>
|
|
219
|
-
<div v-show="cards.actions" class="sb-card-body">
|
|
220
|
-
<div class="sb-row sb-row--pick">
|
|
221
|
-
<label class="sb-lbl">Silent Click</label>
|
|
222
|
-
<input :value="step.silentClick || ''" @input="setStepProp('silentClick', $event.target.value)" class="sb-input">
|
|
223
|
-
<button class="sb-pick" @click="pickField('silentClick')">Pick</button>
|
|
224
|
-
</div>
|
|
225
|
-
<div class="sb-row">
|
|
226
|
-
<label class="sb-lbl">Redirect</label>
|
|
227
|
-
<input :value="step.redirect || ''" @input="setStepProp('redirect', $event.target.value)" class="sb-input" placeholder="/url">
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
<!-- Advance -->
|
|
233
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.advance }">
|
|
234
|
-
<div class="sb-card-head" @click="toggle('advance')">
|
|
235
|
-
<span>ADVANCE</span>
|
|
236
|
-
<span class="sb-chevron">{{ cards.advance ? '▼' : '▶' }}</span>
|
|
237
|
-
</div>
|
|
238
|
-
<div v-show="cards.advance" class="sb-card-body">
|
|
239
|
-
<select v-model="advanceMode" @change="previewNow()" class="sb-input">
|
|
240
|
-
<option value="click">Click</option>
|
|
241
|
-
<option value="auto">Auto Next</option>
|
|
242
|
-
<option value="afterAnimation">After Animation</option>
|
|
243
|
-
</select>
|
|
244
|
-
<div v-if="advanceMode === 'auto'" class="sb-row" style="margin-top:8px">
|
|
245
|
-
<label class="sb-lbl">Delay (s)</label>
|
|
246
|
-
<input type="number" v-model.number="autoNextDelay" class="sb-input sb-input--num" step="1" min="1">
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
|
|
251
|
-
<!-- Conditions -->
|
|
252
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.conditions }">
|
|
253
|
-
<div class="sb-card-head" @click="toggle('conditions')">
|
|
254
|
-
<span>CONDITIONS</span>
|
|
255
|
-
<span class="sb-chevron">{{ cards.conditions ? '▼' : '▶' }}</span>
|
|
256
|
-
</div>
|
|
257
|
-
<div v-show="cards.conditions" class="sb-card-body">
|
|
258
|
-
<div class="sb-row">
|
|
259
|
-
<label class="sb-lbl">Show If</label>
|
|
260
|
-
<input :value="step.showIf || ''" @input="setStepProp('showIf', $event.target.value)" class="sb-input" placeholder="permission:X, role:X, selector:.css">
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
|
|
265
|
-
<!-- Output -->
|
|
266
|
-
<div class="sb-card" :class="{ 'sb-card--open': cards.output }">
|
|
267
|
-
<div class="sb-card-head" @click="toggle('output')">
|
|
268
|
-
<span>OUTPUT (JSON)</span>
|
|
269
|
-
<span class="sb-chevron">{{ cards.output ? '▼' : '▶' }}</span>
|
|
270
|
-
</div>
|
|
271
|
-
<div v-show="cards.output" class="sb-card-body">
|
|
272
|
-
<pre class="sb-code">{{ outputJson }}</pre>
|
|
273
|
-
<div class="sb-row" style="margin-top:8px;justify-content:flex-end">
|
|
274
|
-
<button class="sb-btn sb-btn-sm sb-btn-ghost" @click="copyStep">Copy Step</button>
|
|
275
|
-
<button class="sb-btn sb-btn-sm sb-btn-ghost" @click="copyAll">Copy All</button>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
10
|
+
<sb-content-card />
|
|
11
|
+
<sb-position-card />
|
|
12
|
+
<sb-cursor-card />
|
|
13
|
+
<sb-highlight-card />
|
|
14
|
+
<sb-hover-card />
|
|
15
|
+
<sb-scroll-card />
|
|
16
|
+
<sb-options-card />
|
|
17
|
+
<sb-actions-card />
|
|
18
|
+
<sb-advance-card />
|
|
19
|
+
<sb-conditions-card />
|
|
20
|
+
<sb-output-card />
|
|
21
|
+
<sb-global-opts-card />
|
|
22
|
+
</div>
|
|
23
|
+
<sb-timeline />
|
|
24
|
+
<sb-path-preview />
|
|
25
|
+
<sb-graph-view />
|
|
279
26
|
</div>
|
|
280
27
|
</div>
|
|
281
28
|
</template>
|
|
282
29
|
|
|
283
30
|
<script>
|
|
284
|
-
import {
|
|
31
|
+
import { StepBuilderState } from '../tutorials/step-builder/StepBuilderState.js';
|
|
285
32
|
import { generateFullJson } from '../tutorials/step-builder/utils/code-generator.js';
|
|
33
|
+
import SbHeader from './step-builder/SbHeader.vue';
|
|
34
|
+
import SbStepList from './step-builder/SbStepList.vue';
|
|
35
|
+
import SbHistoryDrawer from './step-builder/SbHistoryDrawer.vue';
|
|
36
|
+
import SbContextMenu from './step-builder/SbContextMenu.vue';
|
|
37
|
+
import SbContentCard from './step-builder/cards/SbContentCard.vue';
|
|
38
|
+
import SbPositionCard from './step-builder/cards/SbPositionCard.vue';
|
|
39
|
+
import SbCursorCard from './step-builder/cards/SbCursorCard.vue';
|
|
40
|
+
import SbHighlightCard from './step-builder/cards/SbHighlightCard.vue';
|
|
41
|
+
import SbHoverCard from './step-builder/cards/SbHoverCard.vue';
|
|
42
|
+
import SbScrollCard from './step-builder/cards/SbScrollCard.vue';
|
|
43
|
+
import SbOptionsCard from './step-builder/cards/SbOptionsCard.vue';
|
|
44
|
+
import SbActionsCard from './step-builder/cards/SbActionsCard.vue';
|
|
45
|
+
import SbAdvanceCard from './step-builder/cards/SbAdvanceCard.vue';
|
|
46
|
+
import SbConditionsCard from './step-builder/cards/SbConditionsCard.vue';
|
|
47
|
+
import SbOutputCard from './step-builder/cards/SbOutputCard.vue';
|
|
48
|
+
import SbGlobalOptsCard from './step-builder/cards/SbGlobalOptsCard.vue';
|
|
49
|
+
import SbTimeline from './step-builder/SbTimeline.vue';
|
|
50
|
+
import SbPathPreview from './step-builder/SbPathPreview.vue';
|
|
51
|
+
import SbGraphView from './step-builder/SbGraphView.vue';
|
|
52
|
+
|
|
53
|
+
function isInputFocused() {
|
|
54
|
+
const tag = document.activeElement?.tagName || '';
|
|
55
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
56
|
+
}
|
|
286
57
|
|
|
287
58
|
export default {
|
|
288
59
|
name: 'VlTutorialStepBuilder',
|
|
60
|
+
components: {
|
|
61
|
+
SbHeader, SbStepList, SbHistoryDrawer, SbContextMenu,
|
|
62
|
+
SbContentCard, SbPositionCard, SbCursorCard, SbHighlightCard,
|
|
63
|
+
SbHoverCard, SbScrollCard, SbOptionsCard, SbActionsCard,
|
|
64
|
+
SbAdvanceCard, SbConditionsCard, SbOutputCard, SbGlobalOptsCard,
|
|
65
|
+
SbTimeline, SbPathPreview, SbGraphView,
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
provide() { return { sb: this }; },
|
|
289
69
|
|
|
290
70
|
data() {
|
|
291
71
|
return {
|
|
292
72
|
visible: false,
|
|
293
|
-
|
|
294
|
-
idx: 0,
|
|
73
|
+
state: new StepBuilderState(),
|
|
295
74
|
engine: null,
|
|
296
75
|
cards: {
|
|
297
76
|
content: true, position: false, cursor: false, highlight: false,
|
|
298
77
|
hover: false, scroll: false, options: false, actions: false,
|
|
299
|
-
advance: false, conditions: false, output: false,
|
|
78
|
+
advance: false, conditions: false, output: false, globalOpts: false,
|
|
300
79
|
},
|
|
301
|
-
|
|
80
|
+
graphViewOpen: false,
|
|
81
|
+
historyOpen: false,
|
|
82
|
+
contextMenu: null,
|
|
302
83
|
_syncing: false,
|
|
84
|
+
_previewTimer: null,
|
|
303
85
|
};
|
|
304
86
|
},
|
|
305
87
|
|
|
306
88
|
computed: {
|
|
307
|
-
step()
|
|
308
|
-
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
// --- Cursor helpers (nested object needs explicit reactivity) ---
|
|
312
|
-
cursorFrom() { return this.step.cursor?.from || ''; },
|
|
313
|
-
cursorTo() { return this.step.cursor?.to || ''; },
|
|
314
|
-
cursorDuration() { return this.step.cursor?.duration || 1.5; },
|
|
315
|
-
cursorClick() { return !!this.step.cursor?.click; },
|
|
316
|
-
cursorLoop() { return !!this.step.cursor?.loop; },
|
|
317
|
-
|
|
318
|
-
// --- Highlight helper ---
|
|
319
|
-
highlightElements() {
|
|
320
|
-
return this.step.highlight?.groups?.flatMap(g => g.elements || g)?.join(', ') || '';
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
// --- Hover helper ---
|
|
324
|
-
hoverValue: {
|
|
325
|
-
get() {
|
|
326
|
-
const h = this.step.hover;
|
|
327
|
-
return Array.isArray(h) ? h.join(', ') : (h || '');
|
|
328
|
-
},
|
|
329
|
-
set(val) {
|
|
330
|
-
if (!val) { this.$delete(this.step, 'hover'); return; }
|
|
331
|
-
const parts = val.split(',').map(s => s.trim()).filter(Boolean);
|
|
332
|
-
this.$set(this.step, 'hover', parts.length === 1 ? parts[0] : parts);
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
|
|
336
|
-
// --- Chat mode helper ---
|
|
337
|
-
chatMode: {
|
|
338
|
-
get() { return !!this.step.chatMode; },
|
|
339
|
-
set(v) {
|
|
340
|
-
if (v) this.$set(this.step, 'chatMode', true);
|
|
341
|
-
else this.$delete(this.step, 'chatMode');
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
// --- showBack helper ---
|
|
346
|
-
showBack: {
|
|
347
|
-
get() { return !!this.step.showBack; },
|
|
348
|
-
set(v) {
|
|
349
|
-
if (v) this.$set(this.step, 'showBack', true);
|
|
350
|
-
else this.$delete(this.step, 'showBack');
|
|
351
|
-
}
|
|
352
|
-
},
|
|
353
|
-
|
|
354
|
-
// --- Advance helpers ---
|
|
355
|
-
advanceMode: {
|
|
356
|
-
get() {
|
|
357
|
-
if (this.step.autoNext) return 'auto';
|
|
358
|
-
if (this.step.afterAnimation) return 'afterAnimation';
|
|
359
|
-
return 'click';
|
|
360
|
-
},
|
|
361
|
-
set(mode) {
|
|
362
|
-
this.$delete(this.step, 'autoNext');
|
|
363
|
-
this.$delete(this.step, 'afterAnimation');
|
|
364
|
-
if (mode === 'auto') this.$set(this.step, 'autoNext', 3);
|
|
365
|
-
else if (mode === 'afterAnimation') this.$set(this.step, 'afterAnimation', true);
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
autoNextDelay: {
|
|
369
|
-
get() { return typeof this.step.autoNext === 'number' ? this.step.autoNext : 3; },
|
|
370
|
-
set(v) { this.$set(this.step, 'autoNext', v || 3); }
|
|
371
|
-
},
|
|
372
|
-
|
|
89
|
+
step() { return this.state.step; },
|
|
90
|
+
steps() { return this.state.steps; },
|
|
91
|
+
idx() { return this.state.idx; },
|
|
373
92
|
outputJson() {
|
|
374
93
|
try { return generateFullJson(this.steps, this.engine?.config); }
|
|
375
94
|
catch (e) { return JSON.stringify(this.steps, null, 2); }
|
|
@@ -377,159 +96,60 @@ export default {
|
|
|
377
96
|
},
|
|
378
97
|
|
|
379
98
|
watch: {
|
|
380
|
-
|
|
99
|
+
'state._version'() {
|
|
100
|
+
if (!this._syncing) this.schedulePreview();
|
|
101
|
+
},
|
|
102
|
+
'state.steps': {
|
|
381
103
|
deep: true,
|
|
382
|
-
handler() {
|
|
383
|
-
|
|
104
|
+
handler() {
|
|
105
|
+
if (!this._syncing) this.schedulePreview();
|
|
106
|
+
},
|
|
107
|
+
},
|
|
384
108
|
},
|
|
385
109
|
|
|
386
110
|
mounted() {
|
|
387
111
|
this.engine = window.TutorialEngine;
|
|
388
112
|
if (!this.engine) return;
|
|
389
113
|
|
|
390
|
-
// When engine starts and has steps, sync and show
|
|
391
114
|
this.engine.onReady(() => {
|
|
392
115
|
if (this.engine.steps?.length) {
|
|
393
116
|
this._syncing = true;
|
|
394
|
-
this.
|
|
395
|
-
this.
|
|
117
|
+
this.engine.config.set('devMode', true);
|
|
118
|
+
this.state.init(this.engine.steps);
|
|
396
119
|
this.visible = true;
|
|
397
|
-
this.$nextTick(() => {
|
|
398
|
-
this._syncing = false;
|
|
399
|
-
this.$el.focus();
|
|
400
|
-
});
|
|
120
|
+
this.$nextTick(() => { this._syncing = false; if (this.$refs.panel) this.$refs.panel.focus(); });
|
|
401
121
|
}
|
|
402
122
|
});
|
|
403
123
|
|
|
404
|
-
// Listen for step changes from the running tutorial
|
|
405
124
|
document.addEventListener('tutorial-step-change', (e) => {
|
|
406
125
|
const i = e.detail?.index;
|
|
407
|
-
if (typeof i === 'number' && i !== this.idx && i < this.steps.length) {
|
|
408
|
-
this.idx = i;
|
|
126
|
+
if (typeof i === 'number' && i !== this.state.idx && i < this.state.steps.length) {
|
|
127
|
+
this.state.idx = i;
|
|
409
128
|
}
|
|
410
129
|
});
|
|
130
|
+
|
|
131
|
+
this._shortcutHandler = (e) => this._handleShortcut(e);
|
|
132
|
+
document.addEventListener('keydown', this._shortcutHandler);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
beforeDestroy() {
|
|
136
|
+
if (this._shortcutHandler) document.removeEventListener('keydown', this._shortcutHandler);
|
|
137
|
+
if (this.engine) this.engine.config.set('devMode', false);
|
|
411
138
|
},
|
|
412
139
|
|
|
413
140
|
methods: {
|
|
414
|
-
// --- Step
|
|
415
|
-
selectStep(i) {
|
|
416
|
-
this.idx = Math.max(0, Math.min(i, this.steps.length - 1));
|
|
417
|
-
this.previewNow();
|
|
418
|
-
},
|
|
141
|
+
// --- Step navigation ---
|
|
142
|
+
selectStep(i) { this.state.selectStep(i); this.previewNow(); },
|
|
419
143
|
prevStep() { if (this.idx > 0) this.selectStep(this.idx - 1); },
|
|
420
144
|
nextStep() { if (this.idx < this.steps.length - 1) this.selectStep(this.idx + 1); },
|
|
145
|
+
addStep() { this.state.addStep(this.idx); this.previewNow(); },
|
|
146
|
+
deleteStep() { this.state.deleteStep(this.idx); this.previewNow(); },
|
|
147
|
+
duplicateStep() { this.state.duplicateStep(this.idx); this.previewNow(); },
|
|
148
|
+
moveStepUp() { if (this.idx > 0) { this.state.moveStep(this.idx, this.idx - 1); this.previewNow(); } },
|
|
149
|
+
moveStepDown() { if (this.idx < this.steps.length - 1) { this.state.moveStep(this.idx, this.idx + 1); this.previewNow(); } },
|
|
421
150
|
|
|
422
|
-
addStep() {
|
|
423
|
-
this.steps.splice(this.idx + 1, 0, { html: '', overlay: true, position: 'left', align: 'center' });
|
|
424
|
-
this.selectStep(this.idx + 1);
|
|
425
|
-
},
|
|
426
|
-
deleteStep() {
|
|
427
|
-
if (this.steps.length <= 1) return;
|
|
428
|
-
this.steps.splice(this.idx, 1);
|
|
429
|
-
this.selectStep(Math.min(this.idx, this.steps.length - 1));
|
|
430
|
-
},
|
|
431
|
-
duplicateStep() {
|
|
432
|
-
const clone = JSON.parse(JSON.stringify(this.steps[this.idx]));
|
|
433
|
-
this.steps.splice(this.idx + 1, 0, clone);
|
|
434
|
-
this.selectStep(this.idx + 1);
|
|
435
|
-
},
|
|
436
|
-
moveStepUp() {
|
|
437
|
-
if (this.idx <= 0) return;
|
|
438
|
-
const i = this.idx;
|
|
439
|
-
this.steps.splice(i - 1, 2, this.steps[i], this.steps[i - 1]);
|
|
440
|
-
this.selectStep(i - 1);
|
|
441
|
-
},
|
|
442
|
-
moveStepDown() {
|
|
443
|
-
if (this.idx >= this.steps.length - 1) return;
|
|
444
|
-
const i = this.idx;
|
|
445
|
-
this.steps.splice(i, 2, this.steps[i + 1], this.steps[i]);
|
|
446
|
-
this.selectStep(i + 1);
|
|
447
|
-
},
|
|
448
|
-
|
|
449
|
-
stepPreview(s) {
|
|
450
|
-
return (s.html || s.silentClick || '').substring(0, 30) || '(empty)';
|
|
451
|
-
},
|
|
452
|
-
|
|
453
|
-
// --- Card toggles ---
|
|
454
151
|
toggle(card) { this.cards[card] = !this.cards[card]; },
|
|
455
152
|
|
|
456
|
-
// --- Cursor (nested reactivity) ---
|
|
457
|
-
ensureCursor() {
|
|
458
|
-
if (!this.step.cursor) this.$set(this.step, 'cursor', {});
|
|
459
|
-
},
|
|
460
|
-
setCursor(key, val) {
|
|
461
|
-
this.ensureCursor();
|
|
462
|
-
this.$set(this.step.cursor, key, val);
|
|
463
|
-
if (!this.step.cursor.from && !this.step.cursor.to) {
|
|
464
|
-
this.$delete(this.step, 'cursor');
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
async pickCursor(key) {
|
|
468
|
-
const sel = await pickElement();
|
|
469
|
-
if (sel) this.setCursor(key, sel);
|
|
470
|
-
},
|
|
471
|
-
|
|
472
|
-
// --- Highlight ---
|
|
473
|
-
setHighlightElements(val) {
|
|
474
|
-
if (!val) { this.$delete(this.step, 'highlight'); return; }
|
|
475
|
-
const elements = val.split(',').map(s => s.trim()).filter(Boolean);
|
|
476
|
-
this.$set(this.step, 'highlight', { groups: [{ elements }] });
|
|
477
|
-
},
|
|
478
|
-
async pickHighlight() {
|
|
479
|
-
const sel = await pickElement();
|
|
480
|
-
if (sel) {
|
|
481
|
-
const current = this.highlightElements;
|
|
482
|
-
this.setHighlightElements(current ? current + ', ' + sel : sel);
|
|
483
|
-
}
|
|
484
|
-
},
|
|
485
|
-
|
|
486
|
-
// --- Avatar size ---
|
|
487
|
-
applyAvatarSize() {
|
|
488
|
-
const size = this.step.avatarSize;
|
|
489
|
-
if (this.engine?.avatar?.img) {
|
|
490
|
-
this.engine.avatar.img.style.height = size || this.engine.config?.get('avatarHeight') || '';
|
|
491
|
-
this.engine.avatar.img.style.maxHeight = size || this.engine.config?.get('avatarMaxHeight') || '';
|
|
492
|
-
}
|
|
493
|
-
this.previewNow();
|
|
494
|
-
},
|
|
495
|
-
|
|
496
|
-
// --- Reactive step property setter (Vue 2 needs $set for new keys) ---
|
|
497
|
-
setStepProp(key, value) {
|
|
498
|
-
if (value) {
|
|
499
|
-
this.$set(this.step, key, value);
|
|
500
|
-
} else {
|
|
501
|
-
this.$delete(this.step, key);
|
|
502
|
-
}
|
|
503
|
-
this.previewNow();
|
|
504
|
-
},
|
|
505
|
-
|
|
506
|
-
// --- Generic field pick ---
|
|
507
|
-
async pickField(prop) {
|
|
508
|
-
const sel = await pickElement();
|
|
509
|
-
if (sel) this.$set(this.step, prop, sel);
|
|
510
|
-
},
|
|
511
|
-
async pick(prop) {
|
|
512
|
-
const sel = await pickElement();
|
|
513
|
-
if (sel) this.$set(this.step, prop, sel);
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
// --- Options ---
|
|
517
|
-
addOption() {
|
|
518
|
-
if (!this.step.options) this.$set(this.step, 'options', []);
|
|
519
|
-
this.step.options.push({ label: 'New Option', _action: 'next' });
|
|
520
|
-
},
|
|
521
|
-
removeOption(i) {
|
|
522
|
-
this.step.options.splice(i, 1);
|
|
523
|
-
if (!this.step.options.length) this.$delete(this.step, 'options');
|
|
524
|
-
},
|
|
525
|
-
setOptionAction(i, action) {
|
|
526
|
-
const opt = this.step.options[i];
|
|
527
|
-
delete opt.done; delete opt.goToStep; delete opt.redirect;
|
|
528
|
-
if (action === 'done') opt.done = true;
|
|
529
|
-
else if (action === 'goToStep') opt.goToStep = this.idx + 1;
|
|
530
|
-
else if (action === 'redirect') opt.redirect = '/';
|
|
531
|
-
},
|
|
532
|
-
|
|
533
153
|
// --- Preview ---
|
|
534
154
|
schedulePreview() {
|
|
535
155
|
clearTimeout(this._previewTimer);
|
|
@@ -537,8 +157,23 @@ export default {
|
|
|
537
157
|
},
|
|
538
158
|
previewNow() {
|
|
539
159
|
if (!this.engine) return;
|
|
540
|
-
this.engine.steps = this.steps;
|
|
541
|
-
this.engine.previewStep(this.idx);
|
|
160
|
+
this.engine.steps = this.state.steps;
|
|
161
|
+
this.engine.previewStep(this.state.idx);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// --- Step property helpers (used by children via this.sb) ---
|
|
165
|
+
setStepProp(key, value) {
|
|
166
|
+
this.state.beginEdit('Edit step #' + this.idx + ' \u2192 ' + key);
|
|
167
|
+
if (value) this.$set(this.step, key, value);
|
|
168
|
+
else this.$delete(this.step, key);
|
|
169
|
+
},
|
|
170
|
+
async pickField(prop) {
|
|
171
|
+
const { pickElement } = await import('../tutorials/step-builder/utils/element-picker.js');
|
|
172
|
+
const sel = await pickElement();
|
|
173
|
+
if (sel) {
|
|
174
|
+
this.state.beginEdit('Edit step #' + this.idx + ' \u2192 ' + prop);
|
|
175
|
+
this.$set(this.step, prop, sel);
|
|
176
|
+
}
|
|
542
177
|
},
|
|
543
178
|
|
|
544
179
|
// --- Clipboard ---
|
|
@@ -547,20 +182,15 @@ export default {
|
|
|
547
182
|
_copy(text) {
|
|
548
183
|
navigator.clipboard.writeText(text).catch(() => {
|
|
549
184
|
const ta = document.createElement('textarea');
|
|
550
|
-
ta.value = text;
|
|
551
|
-
|
|
552
|
-
document.body.appendChild(ta);
|
|
553
|
-
ta.select();
|
|
554
|
-
document.execCommand('copy');
|
|
555
|
-
ta.remove();
|
|
185
|
+
ta.value = text; ta.style.cssText = 'position:fixed;left:-9999px';
|
|
186
|
+
document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove();
|
|
556
187
|
});
|
|
557
188
|
},
|
|
558
189
|
|
|
559
190
|
// --- Spectator ---
|
|
560
191
|
spectatorMode() {
|
|
561
192
|
this.visible = false;
|
|
562
|
-
if (this.engine) { this.engine.steps = this.steps; this.engine.previewStep(0); }
|
|
563
|
-
|
|
193
|
+
if (this.engine) { this.engine.steps = this.state.steps; this.engine.previewStep(0); }
|
|
564
194
|
const btn = document.createElement('button');
|
|
565
195
|
btn.textContent = 'Exit Spectator (Esc)';
|
|
566
196
|
Object.assign(btn.style, {
|
|
@@ -570,7 +200,6 @@ export default {
|
|
|
570
200
|
cursor: 'pointer', backdropFilter: 'blur(8px)',
|
|
571
201
|
});
|
|
572
202
|
document.body.appendChild(btn);
|
|
573
|
-
|
|
574
203
|
const exit = () => { this.visible = true; btn.remove(); document.removeEventListener('keydown', esc); this.previewNow(); };
|
|
575
204
|
const esc = (e) => { if (e.key === 'Escape') exit(); };
|
|
576
205
|
btn.onclick = exit;
|
|
@@ -580,113 +209,35 @@ export default {
|
|
|
580
209
|
// --- Resize ---
|
|
581
210
|
startResize(e) {
|
|
582
211
|
e.preventDefault();
|
|
212
|
+
const panel = this.$refs.panel;
|
|
583
213
|
const startX = e.clientX;
|
|
584
|
-
const startW =
|
|
585
|
-
const onMove = (ev) => {
|
|
214
|
+
const startW = panel.offsetWidth;
|
|
215
|
+
const onMove = (ev) => { panel.style.width = Math.max(300, startW - (ev.clientX - startX)) + 'px'; };
|
|
586
216
|
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
|
|
587
217
|
document.addEventListener('mousemove', onMove);
|
|
588
218
|
document.addEventListener('mouseup', onUp);
|
|
589
219
|
},
|
|
220
|
+
|
|
221
|
+
// --- Shortcuts ---
|
|
222
|
+
_handleShortcut(e) {
|
|
223
|
+
if (!this.visible) return;
|
|
224
|
+
if (!isInputFocused()) {
|
|
225
|
+
if (e.key === 'Delete') { e.preventDefault(); this.deleteStep(); return; }
|
|
226
|
+
}
|
|
227
|
+
if (e.ctrlKey || e.metaKey) {
|
|
228
|
+
if (e.key === 'z' && !e.shiftKey) { e.preventDefault(); this.state.undo(); this.previewNow(); return; }
|
|
229
|
+
if ((e.key === 'z' || e.key === 'Z') && e.shiftKey) { e.preventDefault(); this.state.redo(); this.previewNow(); return; }
|
|
230
|
+
if (!isInputFocused()) {
|
|
231
|
+
if (e.key === 'c' && e.shiftKey) { e.preventDefault(); this.copyAll(); return; }
|
|
232
|
+
if (e.key === 'c' && !e.shiftKey) { e.preventDefault(); this.copyStep(); return; }
|
|
233
|
+
if (e.key === 'd') { e.preventDefault(); this.duplicateStep(); return; }
|
|
234
|
+
if (e.key === 'n') { e.preventDefault(); this.addStep(); return; }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (e.key === 'Escape') {
|
|
238
|
+
if (this.historyOpen) { this.historyOpen = false; return; }
|
|
239
|
+
}
|
|
240
|
+
},
|
|
590
241
|
},
|
|
591
242
|
};
|
|
592
243
|
</script>
|
|
593
|
-
|
|
594
|
-
<style scoped>
|
|
595
|
-
.sb {
|
|
596
|
-
position: fixed;
|
|
597
|
-
top: 0;
|
|
598
|
-
right: 0;
|
|
599
|
-
width: 420px;
|
|
600
|
-
height: 100vh;
|
|
601
|
-
z-index: 99997;
|
|
602
|
-
background: linear-gradient(170deg, #1e1e2e 0%, #161625 100%);
|
|
603
|
-
color: #e0e4ec;
|
|
604
|
-
font-family: system-ui, -apple-system, sans-serif;
|
|
605
|
-
font-size: 13px;
|
|
606
|
-
line-height: 1.5;
|
|
607
|
-
display: flex;
|
|
608
|
-
flex-direction: column;
|
|
609
|
-
border-left: 1px solid rgba(255,255,255,0.06);
|
|
610
|
-
box-shadow: -4px 0 24px rgba(0,0,0,0.4);
|
|
611
|
-
outline: none;
|
|
612
|
-
}
|
|
613
|
-
.sb * { box-sizing: border-box; }
|
|
614
|
-
|
|
615
|
-
/* Resize handle */
|
|
616
|
-
.sb-resize { position: absolute; width: 6px; top: 0; bottom: 0; left: 0; cursor: ew-resize; z-index: 2; }
|
|
617
|
-
.sb-resize:hover { background: rgba(108,138,255,0.2); }
|
|
618
|
-
|
|
619
|
-
/* Header */
|
|
620
|
-
.sb-header { height: 44px; min-height: 44px; padding: 0 14px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid rgba(255,255,255,0.06); background: rgba(255,255,255,0.02); }
|
|
621
|
-
.sb-header-title { font-size: 14px; font-weight: 700; flex: 1; }
|
|
622
|
-
.sb-hbtn { background: none; border: 1px solid rgba(255,255,255,0.1); color: #aaa; border-radius: 6px; width: 28px; height: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: all 0.15s; }
|
|
623
|
-
.sb-hbtn:hover { background: rgba(255,255,255,0.08); color: #fff; }
|
|
624
|
-
.sb-hbtn--danger:hover { color: #e74c3c; border-color: #e74c3c; }
|
|
625
|
-
|
|
626
|
-
/* Steps */
|
|
627
|
-
.sb-steps { border-bottom: 1px solid rgba(255,255,255,0.06); padding: 8px 14px; flex-shrink: 0; }
|
|
628
|
-
.sb-steps-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
|
|
629
|
-
.sb-steps-count { font-size: 11px; font-weight: 600; color: #7a7f8e; text-transform: uppercase; letter-spacing: 0.3px; }
|
|
630
|
-
.sb-steps-list { max-height: 200px; overflow-y: auto; }
|
|
631
|
-
.sb-step { display: flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 6px; cursor: pointer; transition: background 0.15s; border: 1px solid transparent; }
|
|
632
|
-
.sb-step:hover { background: rgba(108,138,255,0.1); }
|
|
633
|
-
.sb-step--active { background: rgba(108,138,255,0.2); border-color: rgba(108,138,255,0.3); }
|
|
634
|
-
.sb-step-num { width: 22px; height: 22px; border-radius: 50%; background: rgba(255,255,255,0.08); display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
|
|
635
|
-
.sb-step--active .sb-step-num { background: #6c8aff; color: #fff; }
|
|
636
|
-
.sb-step-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #b0b4c0; font-size: 12px; }
|
|
637
|
-
.sb-step-icons { display: flex; gap: 3px; font-size: 11px; color: #7a7f8e; flex-shrink: 0; }
|
|
638
|
-
.sb-step-actions { display: flex; gap: 4px; margin-top: 6px; }
|
|
639
|
-
|
|
640
|
-
/* Detail zone */
|
|
641
|
-
.sb-detail { flex: 1; overflow-y: auto; padding: 8px 14px; }
|
|
642
|
-
.sb-detail::-webkit-scrollbar { width: 5px; }
|
|
643
|
-
.sb-detail::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
|
|
644
|
-
|
|
645
|
-
/* Cards */
|
|
646
|
-
.sb-card { background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.06); border-radius: 8px; margin-bottom: 6px; overflow: hidden; }
|
|
647
|
-
.sb-card--open { background: rgba(255,255,255,0.04); }
|
|
648
|
-
.sb-card-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; cursor: pointer; user-select: none; font-size: 11px; font-weight: 600; color: #6c8aff; text-transform: uppercase; letter-spacing: 0.3px; }
|
|
649
|
-
.sb-card-head:hover { background: rgba(255,255,255,0.03); }
|
|
650
|
-
.sb-chevron { font-size: 10px; color: #555; }
|
|
651
|
-
.sb-card-body { padding: 8px 12px 12px; }
|
|
652
|
-
|
|
653
|
-
/* Inputs */
|
|
654
|
-
.sb-input { background: rgba(255,255,255,0.06); color: #e0e4ec; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 7px 10px; font-size: 13px; font-family: inherit; outline: none; width: 100%; transition: border-color 0.2s; }
|
|
655
|
-
.sb-input:focus { border-color: #6c8aff; box-shadow: 0 0 0 2px rgba(108,138,255,0.15); }
|
|
656
|
-
.sb-input--num { width: 72px; }
|
|
657
|
-
.sb-input--sm { width: auto; min-width: 80px; }
|
|
658
|
-
.sb-textarea { resize: vertical; min-height: 60px; font-family: "Fira Code", monospace; font-size: 12px; }
|
|
659
|
-
select.sb-input { cursor: pointer; appearance: none; padding-right: 24px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 8px center; }
|
|
660
|
-
|
|
661
|
-
/* Labels, rows */
|
|
662
|
-
.sb-lbl { font-size: 11px; color: #7a7f8e; min-width: 50px; flex-shrink: 0; text-transform: uppercase; letter-spacing: 0.3px; margin-bottom: 4px; display: block; }
|
|
663
|
-
.sb-row { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; }
|
|
664
|
-
.sb-row--pick .sb-input { flex: 1; }
|
|
665
|
-
.sb-check { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #b0b4c0; cursor: pointer; margin-right: 12px; }
|
|
666
|
-
.sb-check input[type="checkbox"] { width: 16px; height: 16px; accent-color: #6c8aff; cursor: pointer; }
|
|
667
|
-
|
|
668
|
-
/* Pick button */
|
|
669
|
-
.sb-pick { background: rgba(108,138,255,0.15); color: #6c8aff; border: 1px solid rgba(108,138,255,0.3); border-radius: 6px; padding: 6px 10px; font-size: 11px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: all 0.15s; }
|
|
670
|
-
.sb-pick:hover { background: rgba(108,138,255,0.25); }
|
|
671
|
-
.sb-pick--danger { background: rgba(231,76,60,0.15); color: #e74c3c; border-color: rgba(231,76,60,0.3); }
|
|
672
|
-
.sb-pick--danger:hover { background: rgba(231,76,60,0.25); }
|
|
673
|
-
|
|
674
|
-
/* Buttons */
|
|
675
|
-
.sb-btn { border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; padding: 6px 12px; transition: all 0.15s; font-family: inherit; white-space: nowrap; }
|
|
676
|
-
.sb-btn:hover { filter: brightness(1.15); }
|
|
677
|
-
.sb-btn-sm { padding: 4px 8px; font-size: 11px; }
|
|
678
|
-
.sb-btn-green { background: #2ecc71; color: #fff; }
|
|
679
|
-
.sb-btn-ghost { background: rgba(255,255,255,0.06); color: #ccc; border: 1px solid rgba(255,255,255,0.1); }
|
|
680
|
-
.sb-btn-ghost:hover { background: rgba(255,255,255,0.12); }
|
|
681
|
-
|
|
682
|
-
/* Options row */
|
|
683
|
-
.sb-opt-row { display: flex; gap: 6px; align-items: center; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.04); }
|
|
684
|
-
.sb-opt-row .sb-input { flex: 1; }
|
|
685
|
-
|
|
686
|
-
/* Code output */
|
|
687
|
-
.sb-code { background: rgba(0,0,0,0.3); color: #8bc78b; padding: 10px 12px; border-radius: 6px; font-family: "Fira Code", monospace; font-size: 10px; line-height: 1.6; overflow: auto; max-height: 200px; white-space: pre-wrap; border: 1px solid rgba(255,255,255,0.04); }
|
|
688
|
-
|
|
689
|
-
@media (max-width: 600px) {
|
|
690
|
-
.sb { width: 100vw; }
|
|
691
|
-
}
|
|
692
|
-
</style>
|