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.
Files changed (33) hide show
  1. package/components/VlTutorialStepBuilder.vue +137 -586
  2. package/components/step-builder/SbContextMenu.vue +41 -0
  3. package/components/step-builder/SbGraphView.vue +93 -0
  4. package/components/step-builder/SbHeader.vue +16 -0
  5. package/components/step-builder/SbHistoryDrawer.vue +43 -0
  6. package/components/step-builder/SbPathPreview.vue +57 -0
  7. package/components/step-builder/SbStepList.vue +87 -0
  8. package/components/step-builder/SbTimeline.vue +66 -0
  9. package/components/step-builder/cards/SbActionsCard.vue +26 -0
  10. package/components/step-builder/cards/SbAdvanceCard.vue +43 -0
  11. package/components/step-builder/cards/SbConditionsCard.vue +20 -0
  12. package/components/step-builder/cards/SbContentCard.vue +45 -0
  13. package/components/step-builder/cards/SbCursorCard.vue +102 -0
  14. package/components/step-builder/cards/SbGlobalOptsCard.vue +40 -0
  15. package/components/step-builder/cards/SbHighlightCard.vue +40 -0
  16. package/components/step-builder/cards/SbHoverCard.vue +32 -0
  17. package/components/step-builder/cards/SbOptionsCard.vue +50 -0
  18. package/components/step-builder/cards/SbOutputCard.vue +17 -0
  19. package/components/step-builder/cards/SbPositionCard.vue +87 -0
  20. package/components/step-builder/cards/SbScrollCard.vue +17 -0
  21. package/docs/extending-tutorial-system.md +376 -0
  22. package/package.json +10 -9
  23. package/tutorials/TutorialConfig.js +4 -0
  24. package/tutorials/TutorialEngine.js +49 -3
  25. package/tutorials/extensions/HideIfExtension.js +12 -0
  26. package/tutorials/extensions/LinkNextExtension.js +117 -0
  27. package/tutorials/modules/Cursor.js +24 -4
  28. package/tutorials/modules/Highlight.js +7 -5
  29. package/tutorials/modules/HoverForcer.js +1 -0
  30. package/tutorials/modules/Overlay.js +2 -0
  31. package/tutorials/step-builder/StepBuilderState.js +265 -0
  32. package/tutorials/tutorial.scss +186 -0
  33. package/tutorials/step-builder/step-builder-bridge.js +0 -436
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div v-if="sb.contextMenu" class="sb-context-menu"
3
+ :style="{ left: sb.contextMenu.x + 'px', top: sb.contextMenu.y + 'px' }">
4
+ <div class="sb-ctx-item sb-ctx-item--green" @click="action('insertAfter')">Insert After</div>
5
+ <div class="sb-ctx-item" @click="action('duplicate')">Duplicate</div>
6
+ <div class="sb-ctx-sep"></div>
7
+ <div class="sb-ctx-item" @click="action('copyStep')">Copy Step Code</div>
8
+ <div class="sb-ctx-sep"></div>
9
+ <div class="sb-ctx-item" :class="{ 'sb-ctx-item--disabled': sb.contextMenu.idx === 0 }" @click="action('moveUp')">Move Up</div>
10
+ <div class="sb-ctx-item" :class="{ 'sb-ctx-item--disabled': sb.contextMenu.idx >= sb.steps.length - 1 }" @click="action('moveDown')">Move Down</div>
11
+ <div class="sb-ctx-sep"></div>
12
+ <div class="sb-ctx-item sb-ctx-item--red" :class="{ 'sb-ctx-item--disabled': sb.steps.length <= 1 }" @click="action('delete')">Delete</div>
13
+ </div>
14
+ </template>
15
+ <script>
16
+ export default {
17
+ inject: ['sb'],
18
+ watch: {
19
+ 'sb.contextMenu'(val) {
20
+ if (val) {
21
+ const close = () => { this.sb.contextMenu = null; document.removeEventListener('click', close); };
22
+ this.$nextTick(() => document.addEventListener('click', close));
23
+ }
24
+ },
25
+ },
26
+ methods: {
27
+ action(type) {
28
+ const i = this.sb.contextMenu.idx;
29
+ this.sb.contextMenu = null;
30
+ switch (type) {
31
+ case 'insertAfter': this.sb.state.addStep(i); this.sb.previewNow(); break;
32
+ case 'duplicate': this.sb.state.duplicateStep(i); this.sb.previewNow(); break;
33
+ case 'copyStep': this.sb._copy(JSON.stringify(this.sb.steps[i], null, 2)); break;
34
+ case 'moveUp': if (i > 0) { this.sb.state.moveStep(i, i - 1); this.sb.previewNow(); } break;
35
+ case 'moveDown': if (i < this.sb.steps.length - 1) { this.sb.state.moveStep(i, i + 1); this.sb.previewNow(); } break;
36
+ case 'delete': this.sb.state.deleteStep(i); this.sb.previewNow(); break;
37
+ }
38
+ },
39
+ },
40
+ };
41
+ </script>
@@ -0,0 +1,93 @@
1
+ <template>
2
+ <div v-if="sb.graphViewOpen" class="sb-graph-modal" @keydown.esc="sb.graphViewOpen = false" tabindex="0" ref="modal">
3
+ <div class="sb-graph-header">
4
+ <span class="sb-graph-title">Step Graph ({{ sb.steps.length }} steps)</span>
5
+ <button class="sb-hbtn" @click="sb.graphViewOpen = false">&times;</button>
6
+ </div>
7
+ <div class="sb-graph-container" ref="container">
8
+ <svg :width="svgWidth" :height="svgHeight" class="sb-graph-arrows">
9
+ <defs>
10
+ <marker id="sb-arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
11
+ <polygon points="0 0, 8 3, 0 6" fill="#555" />
12
+ </marker>
13
+ </defs>
14
+ <path v-for="(arrow, ai) in arrows" :key="'a' + ai" :d="arrow"
15
+ stroke="#555" stroke-width="2" fill="none" marker-end="url(#sb-arrowhead)" />
16
+ </svg>
17
+ <div v-for="(node, ni) in nodes" :key="'n' + ni"
18
+ class="sb-graph-node" :class="{ 'sb-graph-node--active': node.index === sb.idx }"
19
+ :style="{ left: node.x + 'px', top: node.y + 'px' }"
20
+ @click="sb.selectStep(node.index); sb.graphViewOpen = false">
21
+ <div class="sb-graph-node-branch" :style="{ backgroundColor: node.branchColor }"></div>
22
+ <span class="sb-graph-node-num">{{ node.index }}</span>
23
+ <span class="sb-graph-node-text">{{ node.preview }}</span>
24
+ <span class="sb-graph-node-icons">
25
+ <span v-if="sb.steps[node.index].cursor">&#8982;</span>
26
+ <span v-if="sb.steps[node.index].highlight">&#9734;</span>
27
+ <span v-if="sb.steps[node.index].options">&#8230;</span>
28
+ </span>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </template>
33
+ <script>
34
+ const NODE_W = 160, NODE_H = 72, GAP_X = 24, GAP_Y = 20, PAD = 32, COLS = 6;
35
+ const BRANCH_COLORS = ['#6c8aff', '#2ecc71', '#e74c3c', '#f1c40f', '#9b59b6', '#e67e22', '#1abc9c', '#e84393'];
36
+
37
+ export default {
38
+ inject: ['sb'],
39
+ watch: {
40
+ 'sb.graphViewOpen'(val) {
41
+ if (val) this.$nextTick(() => this.$refs.modal?.focus());
42
+ },
43
+ },
44
+ computed: {
45
+ branchColorMap() {
46
+ const map = {}; let ci = 0;
47
+ for (const s of this.sb.steps) {
48
+ const b = s._branch || '_main';
49
+ if (!map[b]) map[b] = BRANCH_COLORS[ci++ % BRANCH_COLORS.length];
50
+ }
51
+ return map;
52
+ },
53
+ nodes() {
54
+ return this.sb.steps.map((s, i) => {
55
+ const row = Math.floor(i / COLS);
56
+ const colInRow = i % COLS;
57
+ const col = row % 2 === 0 ? colInRow : (COLS - 1 - colInRow);
58
+ return {
59
+ index: i,
60
+ x: PAD + col * (NODE_W + GAP_X),
61
+ y: PAD + row * (NODE_H + GAP_Y),
62
+ preview: (s.html || s.silentClick || '').substring(0, 20) || '(empty)',
63
+ branchColor: this.branchColorMap[s._branch || '_main'],
64
+ };
65
+ });
66
+ },
67
+ arrows() {
68
+ const paths = [];
69
+ for (let i = 0; i < this.nodes.length - 1; i++) {
70
+ const a = this.nodes[i], b = this.nodes[i + 1];
71
+ const ax = a.x + NODE_W / 2, ay = a.y + NODE_H / 2;
72
+ const bx = b.x + NODE_W / 2, by = b.y + NODE_H / 2;
73
+ if (Math.abs(ay - by) < 5) {
74
+ // Same row: horizontal
75
+ const fromX = a.x + (bx > ax ? NODE_W : 0);
76
+ const toX = b.x + (bx > ax ? 0 : NODE_W);
77
+ paths.push(`M ${fromX},${ay} L ${toX},${by}`);
78
+ } else {
79
+ // Row change: U-turn
80
+ const midY = ay + (by - ay) / 2;
81
+ paths.push(`M ${ax},${a.y + NODE_H} L ${ax},${midY} L ${bx},${midY} L ${bx},${b.y}`);
82
+ }
83
+ }
84
+ return paths;
85
+ },
86
+ svgWidth() { return PAD * 2 + COLS * (NODE_W + GAP_X); },
87
+ svgHeight() {
88
+ const rows = Math.ceil(this.sb.steps.length / COLS);
89
+ return PAD * 2 + rows * (NODE_H + GAP_Y);
90
+ },
91
+ },
92
+ };
93
+ </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div class="sb-header">
3
+ <span class="sb-header-title">Step Builder</span>
4
+ <button class="sb-hbtn" title="Undo (Ctrl+Z)" :disabled="!sb.state.canUndo"
5
+ @click="sb.state.undo(); sb.previewNow()">&#8630;</button>
6
+ <button class="sb-hbtn" title="Redo (Ctrl+Shift+Z)" :disabled="!sb.state.canRedo"
7
+ @click="sb.state.redo(); sb.previewNow()">&#8631;</button>
8
+ <button class="sb-hbtn" title="History" @click="sb.historyOpen = !sb.historyOpen">&#128337;</button>
9
+ <button class="sb-hbtn" title="Spectator" @click="sb.spectatorMode()">&#9654;</button>
10
+ <button class="sb-hbtn" title="Copy All" @click="sb.copyAll()">&#128203;</button>
11
+ <button class="sb-hbtn" title="Close" @click="sb.visible = false">&times;</button>
12
+ </div>
13
+ </template>
14
+ <script>
15
+ export default { inject: ['sb'] };
16
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div class="sb-history-drawer" :class="{ 'sb-history-drawer--open': sb.historyOpen }">
3
+ <div class="sb-history-header">
4
+ <span>History ({{ sb.state.undoStack.length }})</span>
5
+ <button class="sb-hbtn" @click="sb.historyOpen = false">&times;</button>
6
+ </div>
7
+ <div class="sb-history-list">
8
+ <div class="sb-history-item sb-history-item--current">Current State</div>
9
+ <div v-for="(entry, i) in sb.state.undoLabels.slice().reverse()" :key="'u' + i"
10
+ class="sb-history-item" @click="undoToIndex(sb.state.undoLabels.length - 1 - i)">
11
+ <span class="sb-history-icon" :style="{ color: historyColor(entry.label) }">&#9679;</span>
12
+ <span class="sb-history-label">{{ entry.label }}</span>
13
+ <span class="sb-history-time">{{ timeAgo(entry.time) }}</span>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </template>
18
+ <script>
19
+ export default {
20
+ inject: ['sb'],
21
+ methods: {
22
+ undoToIndex(targetIdx) {
23
+ const count = this.sb.state.undoStack.length - targetIdx;
24
+ for (let i = 0; i < count; i++) this.sb.state.undo();
25
+ this.sb.previewNow();
26
+ },
27
+ timeAgo(ts) {
28
+ const diff = Math.round((Date.now() - ts) / 1000);
29
+ if (diff < 5) return 'just now';
30
+ if (diff < 60) return diff + 's ago';
31
+ if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
32
+ return Math.floor(diff / 3600) + 'h ago';
33
+ },
34
+ historyColor(label) {
35
+ if (label.startsWith('Add')) return '#2ecc71';
36
+ if (label.startsWith('Delete')) return '#e74c3c';
37
+ if (label.startsWith('Duplicate')) return '#6c8aff';
38
+ if (label.startsWith('Move')) return '#f1c40f';
39
+ return '#b0b4c0';
40
+ },
41
+ },
42
+ };
43
+ </script>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <svg v-if="visible" class="sb-path-preview" xmlns="http://www.w3.org/2000/svg">
3
+ <path v-if="pathD" :d="pathD" fill="none" stroke="#f1c40f" stroke-width="2" stroke-dasharray="8,4" />
4
+ <circle v-if="fromPos" :cx="fromPos.x" :cy="fromPos.y" r="8" fill="#2ecc71" opacity="0.8" />
5
+ <text v-if="fromPos" :x="fromPos.x + 12" :y="fromPos.y + 4" fill="#2ecc71" font-size="10" font-family="system-ui">FROM</text>
6
+ <circle v-if="toPos" :cx="toPos.x" :cy="toPos.y" r="8" fill="#e74c3c" opacity="0.8" />
7
+ <text v-if="toPos" :x="toPos.x + 12" :y="toPos.y + 4" fill="#e74c3c" font-size="10" font-family="system-ui">TO</text>
8
+ <template v-for="(wp, wi) in waypointPositions">
9
+ <circle :key="'wc' + wi" :cx="wp.x" :cy="wp.y" r="6" fill="#00bcd4" opacity="0.8" />
10
+ <text :key="'wt' + wi" :x="wp.x + 10" :y="wp.y + 4" fill="#00bcd4" font-size="9" font-family="system-ui">WP{{ wi }}</text>
11
+ </template>
12
+ </svg>
13
+ </template>
14
+ <script>
15
+ export default {
16
+ inject: ['sb'],
17
+ computed: {
18
+ visible() {
19
+ return !!(this.sb.step.cursor && (this.sb.step.cursor.from || this.sb.step.cursor.to));
20
+ },
21
+ fromPos() { return this._resolve(this.sb.step.cursor?.from); },
22
+ toPos() { return this._resolve(this.sb.step.cursor?.to); },
23
+ pathD() {
24
+ if (!this.fromPos || !this.toPos) return null;
25
+ const svgPath = this.sb.step.cursor?.svgPath || 'M 0,0 C 0.3,0.1 0.7,0.9 1,1';
26
+ return this._denormalize(svgPath, this.fromPos, this.toPos);
27
+ },
28
+ waypointPositions() {
29
+ const wps = this.sb.step.cursor?.waypoints || [];
30
+ return wps.map(wp => this._resolve(wp.target)).filter(Boolean);
31
+ },
32
+ },
33
+ methods: {
34
+ _resolve(selector) {
35
+ if (!selector) return null;
36
+ if (typeof selector === 'string' && selector.startsWith('screen:')) {
37
+ const parts = selector.replace('screen:', '').split(',');
38
+ return { x: parseFloat(parts[0]), y: parseFloat(parts[1]) };
39
+ }
40
+ const el = document.querySelector(selector);
41
+ if (!el) return null;
42
+ const rect = el.getBoundingClientRect();
43
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
44
+ },
45
+ _denormalize(pathStr, from, to) {
46
+ const match = pathStr.match(/C\s*([\d.]+),([\d.]+)\s+([\d.]+),([\d.]+)\s+([\d.]+),([\d.]+)/);
47
+ if (!match) return `M ${from.x},${from.y} L ${to.x},${to.y}`;
48
+ const dx = to.x - from.x, dy = to.y - from.y;
49
+ const cp1x = from.x + parseFloat(match[1]) * dx;
50
+ const cp1y = from.y + parseFloat(match[2]) * dy;
51
+ const cp2x = from.x + parseFloat(match[3]) * dx;
52
+ const cp2y = from.y + parseFloat(match[4]) * dy;
53
+ return `M ${from.x},${from.y} C ${cp1x},${cp1y} ${cp2x},${cp2y} ${to.x},${to.y}`;
54
+ },
55
+ },
56
+ };
57
+ </script>
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <div class="sb-steps">
3
+ <div class="sb-steps-bar">
4
+ <span class="sb-steps-count">STEPS ({{ sb.steps.length }})</span>
5
+ <button class="sb-btn sb-btn-sm sb-btn-green" @click="sb.addStep()">+</button>
6
+ </div>
7
+ <div v-if="branchInfo.order.length > 1" class="sb-branch-tabs">
8
+ <span class="sb-branch-tab" :class="{ 'sb-branch-tab--active': !activeBranchTab }"
9
+ @click="activeBranchTab = null">All</span>
10
+ <span v-for="b in branchInfo.order" :key="b"
11
+ class="sb-branch-tab" :class="{ 'sb-branch-tab--active': activeBranchTab === b }"
12
+ :style="{ borderBottomColor: branchInfo.colors[b] }"
13
+ @click="activeBranchTab = b">
14
+ {{ b === '_main' ? 'Main' : b }}
15
+ </span>
16
+ </div>
17
+ <div class="sb-steps-list">
18
+ <div v-for="(s, i) in sb.steps" :key="i"
19
+ v-show="!activeBranchTab || (s._branch || '_main') === activeBranchTab"
20
+ class="sb-step" :class="{ 'sb-step--active': i === sb.idx, 'sb-step--drag-over': dragOverIdx === i }"
21
+ draggable="true"
22
+ @click="sb.selectStep(i)"
23
+ @contextmenu.prevent="sb.contextMenu = { x: $event.clientX, y: $event.clientY, idx: i }"
24
+ @dragstart="onDragStart($event, i)"
25
+ @dragover.prevent="dragOverIdx = i"
26
+ @dragleave="dragOverIdx = null"
27
+ @drop="onDrop($event, i)"
28
+ @dragend="dragFromIdx = null; dragOverIdx = null">
29
+ <span class="sb-step-branch-bar" :style="{ backgroundColor: getBranchColor(s) }"></span>
30
+ <span class="sb-step-num">{{ i }}</span>
31
+ <span class="sb-step-text">{{ (s.html || s.silentClick || '').substring(0, 30) || '(empty)' }}</span>
32
+ <span class="sb-step-icons">
33
+ <span v-if="s.cursor" title="cursor">&#8982;</span>
34
+ <span v-if="s.highlight" title="highlight">&#9734;</span>
35
+ <span v-if="s.options" title="options">&#8230;</span>
36
+ <span v-if="sb.state.hasStepChanged(i)" class="sb-step-modified" title="modified">&#9679;</span>
37
+ </span>
38
+ </div>
39
+ </div>
40
+ <div class="sb-step-actions">
41
+ <button class="sb-hbtn" @click="sb.moveStepUp()" title="Move up">&#8593;</button>
42
+ <button class="sb-hbtn" @click="sb.moveStepDown()" title="Move down">&#8595;</button>
43
+ <button class="sb-hbtn" @click="sb.duplicateStep()" title="Duplicate">&#8916;</button>
44
+ <button class="sb-hbtn sb-hbtn--danger" @click="sb.deleteStep()" title="Delete">&#10005;</button>
45
+ </div>
46
+ </div>
47
+ </template>
48
+ <script>
49
+ const BRANCH_COLORS = ['#6c8aff', '#2ecc71', '#e74c3c', '#f1c40f', '#9b59b6', '#e67e22', '#1abc9c', '#e84393'];
50
+
51
+ export default {
52
+ inject: ['sb'],
53
+ data() {
54
+ return { activeBranchTab: null, dragFromIdx: null, dragOverIdx: null };
55
+ },
56
+ computed: {
57
+ branchInfo() {
58
+ const colors = {}, order = [];
59
+ let ci = 0;
60
+ for (const s of this.sb.steps) {
61
+ const b = s._branch || '_main';
62
+ if (!colors[b]) { colors[b] = BRANCH_COLORS[ci++ % BRANCH_COLORS.length]; order.push(b); }
63
+ }
64
+ return { order, colors };
65
+ },
66
+ },
67
+ methods: {
68
+ getBranchColor(step) {
69
+ return this.branchInfo.colors[step._branch || '_main'] || BRANCH_COLORS[0];
70
+ },
71
+ onDragStart(e, idx) {
72
+ this.dragFromIdx = idx;
73
+ e.dataTransfer.effectAllowed = 'move';
74
+ e.dataTransfer.setData('text/plain', String(idx));
75
+ },
76
+ onDrop(e, toIdx) {
77
+ e.preventDefault();
78
+ this.dragOverIdx = null;
79
+ if (this.dragFromIdx !== null && this.dragFromIdx !== toIdx) {
80
+ this.sb.state.moveStep(this.dragFromIdx, toIdx);
81
+ this.sb.previewNow();
82
+ }
83
+ this.dragFromIdx = null;
84
+ },
85
+ },
86
+ };
87
+ </script>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <div class="sb-timeline">
3
+ <div class="sb-timeline-bar">
4
+ <span class="sb-steps-count">TIMELINE</span>
5
+ <button class="sb-hbtn" title="Graph View" @click="sb.graphViewOpen = true">&#9673;</button>
6
+ </div>
7
+ <div class="sb-timeline-strip">
8
+ <div v-for="(s, i) in sb.steps" :key="'t' + i"
9
+ class="sb-timeline-thumb" :class="{ 'sb-active': i === sb.idx }"
10
+ draggable="true"
11
+ @click="sb.selectStep(i)"
12
+ @dragstart="onDragStart($event, i)"
13
+ @dragover.prevent="dragOverIdx = i"
14
+ @dragleave="dragOverIdx = null"
15
+ @drop="onDrop($event, i)"
16
+ @dragend="dragFromIdx = null; dragOverIdx = null">
17
+ <img v-if="sb.state.getScreenshot(i)" :src="sb.state.getScreenshot(i)" class="sb-timeline-thumb-img">
18
+ <span class="sb-timeline-thumb-num">{{ i }}</span>
19
+ <button class="sb-timeline-capture" @click.stop="capture(i)" title="Capture">&#128247;</button>
20
+ <span class="sb-timeline-thumb-icons">
21
+ <span v-if="s.cursor">&#8982;</span>
22
+ <span v-if="s.highlight">&#9734;</span>
23
+ </span>
24
+ <span v-if="sb.state.hasStepChanged(i)" class="sb-timeline-modified"></span>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </template>
29
+ <script>
30
+ export default {
31
+ inject: ['sb'],
32
+ data() { return { dragFromIdx: null, dragOverIdx: null }; },
33
+ methods: {
34
+ async capture(index) {
35
+ if (!window.html2canvas) {
36
+ try {
37
+ const mod = await import('html2canvas');
38
+ window.html2canvas = mod.default || mod;
39
+ } catch { console.warn('html2canvas not available'); return; }
40
+ }
41
+ const wasVisible = this.sb.visible;
42
+ this.sb.visible = false;
43
+ await this.$nextTick();
44
+ try {
45
+ const canvas = await window.html2canvas(document.body, { scale: 0.3, logging: false });
46
+ this.sb.state.setScreenshot(index, canvas.toDataURL('image/jpeg', 0.5));
47
+ } catch (e) { console.warn('Screenshot failed:', e); }
48
+ this.sb.visible = wasVisible;
49
+ },
50
+ onDragStart(e, idx) {
51
+ this.dragFromIdx = idx;
52
+ e.dataTransfer.effectAllowed = 'move';
53
+ e.dataTransfer.setData('text/plain', String(idx));
54
+ },
55
+ onDrop(e, toIdx) {
56
+ e.preventDefault();
57
+ this.dragOverIdx = null;
58
+ if (this.dragFromIdx !== null && this.dragFromIdx !== toIdx) {
59
+ this.sb.state.moveStep(this.dragFromIdx, toIdx);
60
+ this.sb.previewNow();
61
+ }
62
+ this.dragFromIdx = null;
63
+ },
64
+ },
65
+ };
66
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="sb-card" :class="{ 'sb-card--open': sb.cards.actions }">
3
+ <div class="sb-card-head" @click="sb.toggle('actions')">
4
+ <span>ACTIONS</span><span class="sb-chevron">{{ sb.cards.actions ? '&#9660;' : '&#9654;' }}</span>
5
+ </div>
6
+ <div v-show="sb.cards.actions" class="sb-card-body">
7
+ <div class="sb-row sb-row--pick">
8
+ <label class="sb-lbl">Silent Click</label>
9
+ <input :value="sb.step.silentClick || ''" @input="sb.setStepProp('silentClick', $event.target.value)" class="sb-input">
10
+ <button class="sb-pick" @click="sb.pickField('silentClick')">Pick</button>
11
+ </div>
12
+ <div class="sb-row sb-row--pick">
13
+ <label class="sb-lbl">Link Next</label>
14
+ <input :value="sb.step.linkNext || ''" @input="sb.setStepProp('linkNext', $event.target.value)" class="sb-input">
15
+ <button class="sb-pick" @click="sb.pickField('linkNext')">Pick</button>
16
+ </div>
17
+ <div class="sb-row">
18
+ <label class="sb-lbl">Redirect</label>
19
+ <input :value="sb.step.redirect || ''" @input="sb.setStepProp('redirect', $event.target.value)" class="sb-input" placeholder="/url">
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </template>
24
+ <script>
25
+ export default { inject: ['sb'] };
26
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div class="sb-card" :class="{ 'sb-card--open': sb.cards.advance }">
3
+ <div class="sb-card-head" @click="sb.toggle('advance')">
4
+ <span>ADVANCE</span><span class="sb-chevron">{{ sb.cards.advance ? '&#9660;' : '&#9654;' }}</span>
5
+ </div>
6
+ <div v-show="sb.cards.advance" class="sb-card-body">
7
+ <select :value="advanceMode" @change="setAdvanceMode($event.target.value)" class="sb-input">
8
+ <option value="click">Click</option>
9
+ <option value="auto">Auto Next</option>
10
+ <option value="afterAnimation">After Animation</option>
11
+ </select>
12
+ <div v-if="advanceMode === 'auto'" class="sb-row" style="margin-top:8px">
13
+ <label class="sb-lbl">Delay (s)</label>
14
+ <input type="number" :value="sb.step.autoNext || 3" @input="setDelay(parseInt($event.target.value) || 3)" class="sb-input sb-input--num" step="1" min="1">
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </template>
19
+ <script>
20
+ export default {
21
+ inject: ['sb'],
22
+ computed: {
23
+ advanceMode() {
24
+ if (this.sb.step.autoNext) return 'auto';
25
+ if (this.sb.step.afterAnimation) return 'afterAnimation';
26
+ return 'click';
27
+ },
28
+ },
29
+ methods: {
30
+ setAdvanceMode(mode) {
31
+ this.sb.state.beginEdit('Edit step #' + this.sb.idx + ' \u2192 advance');
32
+ this.$delete(this.sb.step, 'autoNext');
33
+ this.$delete(this.sb.step, 'afterAnimation');
34
+ if (mode === 'auto') this.$set(this.sb.step, 'autoNext', 3);
35
+ else if (mode === 'afterAnimation') this.$set(this.sb.step, 'afterAnimation', true);
36
+ },
37
+ setDelay(val) {
38
+ this.sb.state.beginEdit('Edit step #' + this.sb.idx + ' \u2192 autoNext delay');
39
+ this.$set(this.sb.step, 'autoNext', val);
40
+ },
41
+ },
42
+ };
43
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="sb-card" :class="{ 'sb-card--open': sb.cards.conditions }">
3
+ <div class="sb-card-head" @click="sb.toggle('conditions')">
4
+ <span>CONDITIONS</span><span class="sb-chevron">{{ sb.cards.conditions ? '&#9660;' : '&#9654;' }}</span>
5
+ </div>
6
+ <div v-show="sb.cards.conditions" class="sb-card-body">
7
+ <div class="sb-row">
8
+ <label class="sb-lbl">Show If</label>
9
+ <input :value="sb.step.showIf || ''" @input="sb.setStepProp('showIf', $event.target.value)" class="sb-input" placeholder="permission:X, role:X, selector:.css">
10
+ </div>
11
+ <div class="sb-row">
12
+ <label class="sb-lbl">Hide If</label>
13
+ <input :value="sb.step.hideIf || ''" @input="sb.setStepProp('hideIf', $event.target.value)" class="sb-input" placeholder="selector:.css — skip if found">
14
+ </div>
15
+ </div>
16
+ </div>
17
+ </template>
18
+ <script>
19
+ export default { inject: ['sb'] };
20
+ </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <div class="sb-card" :class="{ 'sb-card--open': sb.cards.content }">
3
+ <div class="sb-card-head" @click="sb.toggle('content')">
4
+ <span>CONTENT</span>
5
+ <span class="sb-chevron">{{ sb.cards.content ? '&#9660;' : '&#9654;' }}</span>
6
+ </div>
7
+ <div v-show="sb.cards.content" class="sb-card-body">
8
+ <label class="sb-lbl">HTML / Key</label>
9
+ <textarea :value="sb.step.html || ''" @input="setHtml($event.target.value)" class="sb-input sb-textarea" rows="3"></textarea>
10
+ <div class="sb-row">
11
+ <label class="sb-check"><input type="checkbox" :checked="!!sb.step.overlay" @change="toggleOverlay($event.target.checked)"> overlay</label>
12
+ <label class="sb-check"><input type="checkbox" :checked="!!sb.step.showBack" @change="toggleShowBack"> showBack</label>
13
+ </div>
14
+ <div class="sb-row">
15
+ <label class="sb-lbl">Branch</label>
16
+ <input :value="sb.step._branch || ''" @input="setBranch($event.target.value)" class="sb-input" placeholder="(main)">
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </template>
21
+ <script>
22
+ export default {
23
+ inject: ['sb'],
24
+ methods: {
25
+ setHtml(val) {
26
+ this.sb.state.beginEdit('Edit step #' + this.sb.idx + ' \u2192 html');
27
+ this.$set(this.sb.step, 'html', val);
28
+ },
29
+ toggleOverlay(checked) {
30
+ this.sb.state.beginEdit('Edit step #' + this.sb.idx + ' \u2192 overlay');
31
+ this.$set(this.sb.step, 'overlay', checked);
32
+ },
33
+ toggleShowBack(e) {
34
+ this.sb.state.beginEdit('Edit step #' + this.sb.idx + ' \u2192 showBack');
35
+ if (e.target.checked) this.$set(this.sb.step, 'showBack', true);
36
+ else this.$delete(this.sb.step, 'showBack');
37
+ },
38
+ setBranch(val) {
39
+ this.sb.state.beginEdit('Edit step #' + this.sb.idx + ' \u2192 branch');
40
+ if (val) this.$set(this.sb.step, '_branch', val);
41
+ else this.$delete(this.sb.step, '_branch');
42
+ },
43
+ },
44
+ };
45
+ </script>