@dfosco/storyboard-core 3.6.0 → 3.7.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/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +12274 -11387
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +1 -1
- package/src/CanvasZoomControl.svelte +8 -8
- package/src/CommentsMenuButton.svelte +7 -21
- package/src/CoreUIBar.svelte +19 -3
- package/src/CreateMenuButton.svelte +8 -12
- package/src/InspectorPanel.svelte +12 -15
- package/src/SidePanel.svelte +14 -14
- package/src/assets/fonts/IoskeleyMono-Bold.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-Italic.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-Medium.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-Regular.woff2 +0 -0
- package/src/assets/fonts/IoskeleyMono-SemiBold.woff2 +0 -0
- package/src/comments/ui/AuthModal.svelte +45 -12
- package/src/comments/ui/authModal.js +6 -1
- package/src/comments/ui/comment-layout.css +15 -15
- package/src/comments/ui/commentWindow.js +6 -1
- package/src/comments/ui/comments.css +57 -57
- package/src/comments/ui/commentsDrawer.js +2 -0
- package/src/comments/ui/composer.js +7 -2
- package/src/comments/ui/mount.js +252 -33
- package/src/comments/ui/mount.test.js +138 -0
- package/src/core-ui-colors.css +28 -28
- package/src/inspector/mouseMode.js +2 -2
- package/src/lib/components/ui/button/button.svelte +9 -9
- package/src/lib/components/ui/panel/panel-content.svelte +2 -2
- package/src/lib/components/ui/select/select-trigger.svelte +1 -1
- package/src/lib/components/ui/toggle/toggle.svelte +1 -1
- package/src/lib/components/ui/toggle-group/toggle-group.svelte +2 -2
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +13 -13
- package/src/modes.css +21 -21
- package/src/mountStoryboardCore.js +4 -4
- package/src/sidepanel.css +11 -11
- package/src/styles/tailwind.css +89 -1
- package/src/svelte-plugin-ui/components/ModeSwitch.svelte +3 -3
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +31 -11
- package/src/svelte-plugin-ui/styles/base.css +41 -41
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +187 -25
- package/src/workshop/features/createFlow/server.js +437 -40
- package/src/workshop/features/createPage/CreatePageForm.svelte +249 -0
- package/src/workshop/features/createPage/index.js +11 -0
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +77 -24
- package/src/workshop/features/createPrototype/server.js +14 -16
- package/src/workshop/features/registry-server.js +1 -0
- package/src/workshop/features/registry.js +2 -0
- package/src/workshop/features/templateIndex.js +155 -0
- package/toolbar.config.json +2 -1
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
-->
|
|
10
10
|
|
|
11
11
|
<script lang="ts">
|
|
12
|
+
import { onMount, onDestroy } from 'svelte'
|
|
12
13
|
import { buildPrototypeIndex } from '../../viewfinder.js'
|
|
13
14
|
import { getLocal, setLocal } from '../../localStorage.js'
|
|
15
|
+
import { getParam, setParam, removeParam } from '../../session.js'
|
|
14
16
|
import Icon from './Icon.svelte'
|
|
15
17
|
|
|
16
18
|
interface Props {
|
|
@@ -86,6 +88,24 @@
|
|
|
86
88
|
type ViewMode = 'prototypes' | 'canvases'
|
|
87
89
|
let viewMode: ViewMode = $state('prototypes')
|
|
88
90
|
|
|
91
|
+
function syncViewModeFromHash() {
|
|
92
|
+
viewMode = getParam('canvas') != null ? 'canvases' : 'prototypes'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
onMount(() => {
|
|
96
|
+
syncViewModeFromHash()
|
|
97
|
+
window.addEventListener('hashchange', syncViewModeFromHash)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
onDestroy(() => {
|
|
101
|
+
window.removeEventListener('hashchange', syncViewModeFromHash)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
$effect(() => {
|
|
105
|
+
if (viewMode === 'canvases') setParam('canvas', '1')
|
|
106
|
+
else removeParam('canvas')
|
|
107
|
+
})
|
|
108
|
+
|
|
89
109
|
// Canvas folder data: extract folders that contain canvases for canvas view
|
|
90
110
|
const canvasFolders = $derived.by(() => {
|
|
91
111
|
const src = prototypeIndex.sorted?.[sortBy]?.folders ?? folders
|
|
@@ -152,7 +172,7 @@
|
|
|
152
172
|
const w = 20 + (s * (i + 3)) % 80
|
|
153
173
|
const ht = 8 + (s * (i + 7)) % 40
|
|
154
174
|
const opacity = 0.06 + ((s * (i + 2)) % 20) / 100
|
|
155
|
-
const fill = i % 3 === 0 ? 'var(--placeholder-accent)' : i % 3 === 1 ? 'var(--placeholder-fg)' : 'var(--placeholder-muted)'
|
|
175
|
+
const fill = i % 3 === 0 ? 'var(--sb--placeholder-accent)' : i % 3 === 1 ? 'var(--sb--placeholder-fg)' : 'var(--sb--placeholder-muted)'
|
|
156
176
|
rects += `<rect x="${x}" y="${y}" width="${w}" height="${ht}" rx="2" fill="${fill}" opacity="${opacity}" />`
|
|
157
177
|
}
|
|
158
178
|
|
|
@@ -160,15 +180,15 @@
|
|
|
160
180
|
for (let i = 0; i < 6; i++) {
|
|
161
181
|
const s = h * (i + 5)
|
|
162
182
|
const y = 10 + (s % 180)
|
|
163
|
-
lines += `<line x1="0" y1="${y}" x2="320" y2="${y}" stroke="var(--placeholder-grid)" stroke-width="0.5" opacity="0.4" />`
|
|
183
|
+
lines += `<line x1="0" y1="${y}" x2="320" y2="${y}" stroke="var(--sb--placeholder-grid)" stroke-width="0.5" opacity="0.4" />`
|
|
164
184
|
}
|
|
165
185
|
for (let i = 0; i < 8; i++) {
|
|
166
186
|
const s = h * (i + 9)
|
|
167
187
|
const x = 10 + (s % 300)
|
|
168
|
-
lines += `<line x1="${x}" y1="0" x2="${x}" y2="200" stroke="var(--placeholder-grid)" stroke-width="0.5" opacity="0.3" />`
|
|
188
|
+
lines += `<line x1="${x}" y1="0" x2="${x}" y2="200" stroke="var(--sb--placeholder-grid)" stroke-width="0.5" opacity="0.3" />`
|
|
169
189
|
}
|
|
170
190
|
|
|
171
|
-
return `<svg viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="320" height="200" fill="var(--placeholder-bg)" />${lines}${rects}</svg>`
|
|
191
|
+
return `<svg viewBox="0 0 320 200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><rect width="320" height="200" fill="var(--sb--placeholder-bg)" />${lines}${rects}</svg>`
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
// Branch switching
|
|
@@ -450,7 +470,7 @@
|
|
|
450
470
|
<a class="listItem" href={canvas.route}>
|
|
451
471
|
<div class="cardBody">
|
|
452
472
|
<p class="protoName">
|
|
453
|
-
<span class="protoIcon">{canvas.icon || '
|
|
473
|
+
<span class="protoIcon">{canvas.icon || ''}</span>
|
|
454
474
|
{canvas.name}
|
|
455
475
|
</p>
|
|
456
476
|
{#if canvas.description}
|
|
@@ -947,11 +967,11 @@
|
|
|
947
967
|
overflow: hidden;
|
|
948
968
|
background: var(--bgColor-inset, #010409);
|
|
949
969
|
|
|
950
|
-
--placeholder-bg: var(--bgColor-inset, #010409);
|
|
951
|
-
--placeholder-grid: var(--borderColor-default, #30363d);
|
|
952
|
-
--placeholder-accent: var(--fgColor-accent, #58a6ff);
|
|
953
|
-
--placeholder-fg: var(--fgColor-default, #c9d1d9);
|
|
954
|
-
--placeholder-muted: var(--fgColor-muted, #484f58);
|
|
970
|
+
--sb--placeholder-bg: var(--bgColor-inset, #010409);
|
|
971
|
+
--sb--placeholder-grid: var(--borderColor-default, #30363d);
|
|
972
|
+
--sb--placeholder-accent: var(--fgColor-accent, #58a6ff);
|
|
973
|
+
--sb--placeholder-fg: var(--fgColor-default, #c9d1d9);
|
|
974
|
+
--sb--placeholder-muted: var(--fgColor-muted, #484f58);
|
|
955
975
|
}
|
|
956
976
|
|
|
957
977
|
.thumbnail :global(svg) {
|
|
@@ -975,7 +995,7 @@
|
|
|
975
995
|
padding: 10px 14px;
|
|
976
996
|
margin-bottom: 16px;
|
|
977
997
|
border-radius: 8px;
|
|
978
|
-
border: 1px solid var(--borderColor-default, var(--color-border, #d0d7de));
|
|
998
|
+
border: 1px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
|
|
979
999
|
background: var(--bgColor-attention-muted, #3d2e00);
|
|
980
1000
|
color: var(--fgColor-attention, #9a6700);
|
|
981
1001
|
font-size: 13px;
|
|
@@ -9,61 +9,61 @@
|
|
|
9
9
|
|
|
10
10
|
/* --- Light theme (default) --- */
|
|
11
11
|
:root {
|
|
12
|
-
--sb
|
|
13
|
-
--sb
|
|
14
|
-
--sb
|
|
15
|
-
--sb
|
|
16
|
-
--sb
|
|
17
|
-
--sb
|
|
18
|
-
--sb
|
|
19
|
-
--sb
|
|
20
|
-
--sb
|
|
21
|
-
--sb
|
|
22
|
-
--sb
|
|
12
|
+
--sb--bg: #ffffff;
|
|
13
|
+
--sb--bg-inset: #f6f8fa;
|
|
14
|
+
--sb--bg-muted: #f3f4f6;
|
|
15
|
+
--sb--border: #d1d5db;
|
|
16
|
+
--sb--border-muted: #e5e7eb;
|
|
17
|
+
--sb--fg: #1f2328;
|
|
18
|
+
--sb--fg-muted: #656d76;
|
|
19
|
+
--sb--fg-accent: #0969da;
|
|
20
|
+
--sb--fg-success: #1a7f37;
|
|
21
|
+
--sb--fg-danger: #d1242f;
|
|
22
|
+
--sb--btn-success: #1a7f37;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/* --- Dark theme (any dark-* variant) --- */
|
|
26
26
|
[data-sb-theme^="dark"] {
|
|
27
|
-
--sb
|
|
28
|
-
--sb
|
|
29
|
-
--sb
|
|
30
|
-
--sb
|
|
31
|
-
--sb
|
|
32
|
-
--sb
|
|
33
|
-
--sb
|
|
34
|
-
--sb
|
|
35
|
-
--sb
|
|
36
|
-
--sb
|
|
37
|
-
--sb
|
|
27
|
+
--sb--bg: #161b22;
|
|
28
|
+
--sb--bg-inset: #0d1117;
|
|
29
|
+
--sb--bg-muted: #21262d;
|
|
30
|
+
--sb--border: #30363d;
|
|
31
|
+
--sb--border-muted: #21262d;
|
|
32
|
+
--sb--fg: #e6edf3;
|
|
33
|
+
--sb--fg-muted: #8b949e;
|
|
34
|
+
--sb--fg-accent: #58a6ff;
|
|
35
|
+
--sb--fg-success: #3fb950;
|
|
36
|
+
--sb--fg-danger: #f85149;
|
|
37
|
+
--sb--btn-success: #238636;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/* --- Semantic utility classes (supplement Tachyons) --- */
|
|
41
|
-
.sb-bg { background-color: var(--sb
|
|
42
|
-
.sb-bg-inset { background-color: var(--sb
|
|
43
|
-
.sb-bg-muted { background-color: var(--sb
|
|
44
|
-
.sb-b-default { border-color: var(--sb
|
|
45
|
-
.sb-b-muted { border-color: var(--sb
|
|
46
|
-
.sb-fg { color: var(--sb
|
|
47
|
-
.sb-fg-muted { color: var(--sb
|
|
48
|
-
.sb-fg-accent { color: var(--sb
|
|
49
|
-
.sb-fg-success { color: var(--sb
|
|
50
|
-
.sb-fg-danger { color: var(--sb
|
|
51
|
-
.sb-btn-success { background-color: var(--sb
|
|
41
|
+
.sb-bg { background-color: var(--sb--bg); }
|
|
42
|
+
.sb-bg-inset { background-color: var(--sb--bg-inset); }
|
|
43
|
+
.sb-bg-muted { background-color: var(--sb--bg-muted); }
|
|
44
|
+
.sb-b-default { border-color: var(--sb--border); }
|
|
45
|
+
.sb-b-muted { border-color: var(--sb--border-muted); }
|
|
46
|
+
.sb-fg { color: var(--sb--fg); }
|
|
47
|
+
.sb-fg-muted { color: var(--sb--fg-muted); }
|
|
48
|
+
.sb-fg-accent { color: var(--sb--fg-accent); }
|
|
49
|
+
.sb-fg-success { color: var(--sb--fg-success); }
|
|
50
|
+
.sb-fg-danger { color: var(--sb--fg-danger); }
|
|
51
|
+
.sb-btn-success { background-color: var(--sb--btn-success); color: #fff; }
|
|
52
52
|
.sb-btn-success:hover { filter: brightness(1.15); }
|
|
53
|
-
.sb-btn-cancel { background-color: var(--sb
|
|
54
|
-
.sb-btn-cancel:hover { background-color: var(--sb
|
|
53
|
+
.sb-btn-cancel { background-color: var(--sb--bg-muted); border: 1px solid var(--sb--border); color: var(--sb--fg); }
|
|
54
|
+
.sb-btn-cancel:hover { background-color: var(--sb--border-muted); }
|
|
55
55
|
|
|
56
56
|
.sb-input {
|
|
57
|
-
background: var(--sb
|
|
58
|
-
border: 1px solid var(--sb
|
|
59
|
-
color: var(--sb
|
|
57
|
+
background: var(--sb--bg-inset);
|
|
58
|
+
border: 1px solid var(--sb--border);
|
|
59
|
+
color: var(--sb--fg);
|
|
60
60
|
outline: none;
|
|
61
61
|
}
|
|
62
62
|
.sb-input:focus {
|
|
63
|
-
border-color: var(--sb
|
|
64
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sb
|
|
63
|
+
border-color: var(--sb--fg-accent);
|
|
64
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sb--fg-accent) 15%, transparent);
|
|
65
65
|
}
|
|
66
|
-
.sb-input::placeholder { color: var(--sb
|
|
66
|
+
.sb-input::placeholder { color: var(--sb--fg-muted); }
|
|
67
67
|
|
|
68
68
|
.sb-shadow { box-shadow: 0 8px 24px rgba(0,0,0,0.15), 0 2px 6px rgba(0,0,0,0.1); }
|
|
69
69
|
[data-sb-theme^="dark"] .sb-shadow { box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.3); }
|
|
@@ -11,22 +11,36 @@
|
|
|
11
11
|
import * as Panel from '../../../lib/components/ui/panel/index.js'
|
|
12
12
|
import * as Alert from '../../../lib/components/ui/alert/index.js'
|
|
13
13
|
|
|
14
|
-
interface Props {
|
|
14
|
+
interface Props {
|
|
15
|
+
onClose?: () => void
|
|
16
|
+
}
|
|
15
17
|
let { onClose }: Props = $props()
|
|
16
18
|
|
|
17
19
|
let name = $state('')
|
|
18
20
|
let title = $state('')
|
|
19
21
|
let titleTouched = $state(false)
|
|
20
22
|
let selectedPrototype = $state('')
|
|
21
|
-
let author = $state('')
|
|
22
23
|
let description = $state('')
|
|
23
24
|
let copyFrom = $state('')
|
|
25
|
+
let startingPage = $state('')
|
|
26
|
+
let newPagePath = $state('')
|
|
27
|
+
let newPageTemplate = $state('')
|
|
28
|
+
const CREATE_NEW_PAGE_VALUE = '__create_new_page__'
|
|
24
29
|
|
|
25
|
-
interface PrototypeEntry { name: string; folder?: string }
|
|
26
|
-
interface FlowEntry { name: string; title: string; path: string }
|
|
30
|
+
interface PrototypeEntry { name: string; folder?: string; routes?: string[] }
|
|
31
|
+
interface FlowEntry { name: string; title: string; path: string; prototype?: string; folder?: string; route?: string }
|
|
32
|
+
interface PartialEntry {
|
|
33
|
+
id: string
|
|
34
|
+
name: string
|
|
35
|
+
kind: 'template' | 'recipe'
|
|
36
|
+
scope: 'global' | 'prototype'
|
|
37
|
+
prototype?: string
|
|
38
|
+
folder?: string
|
|
39
|
+
}
|
|
27
40
|
|
|
28
41
|
let prototypes: PrototypeEntry[] = $state([])
|
|
29
42
|
let flows: FlowEntry[] = $state([])
|
|
43
|
+
let partials: PartialEntry[] = $state([])
|
|
30
44
|
let loading = $state(true)
|
|
31
45
|
let submitting = $state(false)
|
|
32
46
|
let error: string | null = $state(null)
|
|
@@ -45,11 +59,49 @@
|
|
|
45
59
|
: name.trim() && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(kebabName) ? 'Name must be kebab-case'
|
|
46
60
|
: null
|
|
47
61
|
)
|
|
48
|
-
const canSubmit = $derived(!!kebabName && !nameError && !submitting)
|
|
49
|
-
|
|
50
62
|
const selectedProtoEntry = $derived(
|
|
51
63
|
selectedPrototype ? prototypes.find(p => p.name === selectedPrototype) : null
|
|
52
64
|
)
|
|
65
|
+
const copyFromFlows = $derived(
|
|
66
|
+
selectedPrototype
|
|
67
|
+
? flows.filter((f) => {
|
|
68
|
+
if (f.prototype !== selectedPrototype) return false
|
|
69
|
+
const flowFolder = f.folder || ''
|
|
70
|
+
const selectedFolder = selectedProtoEntry?.folder || ''
|
|
71
|
+
return flowFolder === selectedFolder
|
|
72
|
+
})
|
|
73
|
+
: []
|
|
74
|
+
)
|
|
75
|
+
const prototypeRoutes = $derived(selectedProtoEntry?.routes || [])
|
|
76
|
+
const showNewPageFields = $derived(startingPage === CREATE_NEW_PAGE_VALUE)
|
|
77
|
+
const newPagePrefix = $derived(selectedPrototype ? `/${selectedPrototype}/` : '/prototype-name/')
|
|
78
|
+
const templateChoices = $derived(
|
|
79
|
+
selectedPrototype
|
|
80
|
+
? partials.filter((partial) => {
|
|
81
|
+
if (partial.scope === 'global') return true
|
|
82
|
+
return partial.prototype === selectedPrototype && (partial.folder || '') === (selectedProtoEntry?.folder || '')
|
|
83
|
+
})
|
|
84
|
+
: partials.filter((partial) => partial.scope === 'global')
|
|
85
|
+
)
|
|
86
|
+
const globalTemplateChoices = $derived(templateChoices.filter((partial) => partial.scope === 'global'))
|
|
87
|
+
const localTemplateChoices = $derived(
|
|
88
|
+
templateChoices.filter((partial) => partial.scope === 'prototype')
|
|
89
|
+
)
|
|
90
|
+
const localTemplateHeading = $derived(
|
|
91
|
+
selectedPrototype || ''
|
|
92
|
+
)
|
|
93
|
+
const startingPageError = $derived(
|
|
94
|
+
showNewPageFields && !newPagePath.trim()
|
|
95
|
+
? 'Please provide a new page path'
|
|
96
|
+
: null
|
|
97
|
+
)
|
|
98
|
+
const canSubmit = $derived(
|
|
99
|
+
!!kebabName &&
|
|
100
|
+
!!selectedPrototype &&
|
|
101
|
+
!nameError &&
|
|
102
|
+
!startingPageError &&
|
|
103
|
+
!submitting
|
|
104
|
+
)
|
|
53
105
|
|
|
54
106
|
function getApiUrl() {
|
|
55
107
|
const basePath = document.querySelector('base')?.getAttribute('href') || '/'
|
|
@@ -63,10 +115,48 @@
|
|
|
63
115
|
const data = await res.json()
|
|
64
116
|
prototypes = data.prototypes || []
|
|
65
117
|
flows = data.flows || []
|
|
118
|
+
partials = data.partials || []
|
|
66
119
|
}
|
|
67
120
|
} catch { /* defaults */ } finally { loading = false }
|
|
68
121
|
})
|
|
69
122
|
|
|
123
|
+
$effect(() => {
|
|
124
|
+
if (!selectedPrototype) {
|
|
125
|
+
copyFrom = ''
|
|
126
|
+
startingPage = ''
|
|
127
|
+
newPagePath = ''
|
|
128
|
+
newPageTemplate = ''
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const routes = selectedProtoEntry?.routes || []
|
|
133
|
+
if (!startingPage || (startingPage !== CREATE_NEW_PAGE_VALUE && !routes.includes(startingPage))) {
|
|
134
|
+
startingPage = routes[0] || ''
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (showNewPageFields) {
|
|
138
|
+
if (!newPagePath.startsWith(newPagePrefix)) {
|
|
139
|
+
newPagePath = `${newPagePrefix}new-page`
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const activeSelectionStillValid = copyFrom && copyFromFlows.some((f) => f.path === copyFrom)
|
|
144
|
+
if (activeSelectionStillValid) return
|
|
145
|
+
|
|
146
|
+
const matchStartingPage = startingPage && startingPage !== CREATE_NEW_PAGE_VALUE
|
|
147
|
+
? copyFromFlows.find((f) => f.route === startingPage)
|
|
148
|
+
: null
|
|
149
|
+
copyFrom = matchStartingPage?.path || ''
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
$effect(() => {
|
|
153
|
+
if (!newPageTemplate) return
|
|
154
|
+
const stillAvailable = templateChoices.some((choice) => choice.id === newPageTemplate)
|
|
155
|
+
if (!stillAvailable) {
|
|
156
|
+
newPageTemplate = ''
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
70
160
|
function handleTitleInput(e: Event) { title = (e.target as HTMLInputElement).value; titleTouched = true }
|
|
71
161
|
function handleTitleBlur() { if (!title.trim()) titleTouched = false }
|
|
72
162
|
|
|
@@ -80,11 +170,15 @@
|
|
|
80
170
|
body: JSON.stringify({
|
|
81
171
|
name: kebabName,
|
|
82
172
|
title: displayTitle,
|
|
83
|
-
prototype: selectedPrototype
|
|
173
|
+
prototype: selectedPrototype,
|
|
84
174
|
folder: selectedProtoEntry?.folder || undefined,
|
|
85
|
-
author: author.trim() || undefined,
|
|
86
175
|
description: description.trim() || undefined,
|
|
87
176
|
copyFrom: copyFrom || undefined,
|
|
177
|
+
startingPage: showNewPageFields ? newPagePath.trim() : (startingPage || undefined),
|
|
178
|
+
createPage: showNewPageFields ? {
|
|
179
|
+
path: newPagePath.trim(),
|
|
180
|
+
template: newPageTemplate || undefined,
|
|
181
|
+
} : undefined,
|
|
88
182
|
}),
|
|
89
183
|
})
|
|
90
184
|
const data = await res.json()
|
|
@@ -102,7 +196,7 @@
|
|
|
102
196
|
</Panel.Header>
|
|
103
197
|
|
|
104
198
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
105
|
-
<div class="p-4 pt-2 space-y-
|
|
199
|
+
<div class="p-4 pt-2 space-y-5" onkeydown={handleKeydown}>
|
|
106
200
|
<div class="space-y-1">
|
|
107
201
|
<Label for="sb-flow-name">Name</Label>
|
|
108
202
|
<Input id="sb-flow-name" placeholder="e.g. empty-state" autocomplete="off" spellcheck="false" bind:value={name} />
|
|
@@ -115,27 +209,95 @@
|
|
|
115
209
|
<Input id="sb-flow-title" placeholder={autoTitle || 'Auto-derived from name'} value={displayTitle} oninput={handleTitleInput} onblur={handleTitleBlur} />
|
|
116
210
|
</div>
|
|
117
211
|
|
|
118
|
-
<div class="
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
212
|
+
<div class="space-y-1">
|
|
213
|
+
<Label for="sb-flow-prototype">Prototype</Label>
|
|
214
|
+
<select class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" id="sb-flow-prototype" bind:value={selectedPrototype} disabled={loading}>
|
|
215
|
+
<option value="">Select prototype</option>
|
|
216
|
+
{#each prototypes as p}<option value={p.name}>{p.folder ? `${p.folder} / ${p.name}` : p.name}</option>{/each}
|
|
217
|
+
</select>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div class="space-y-1">
|
|
221
|
+
<Label for="sb-flow-copy">Copy from existing flow <span class="text-muted-foreground">(optional)</span></Label>
|
|
222
|
+
<select
|
|
223
|
+
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
224
|
+
id="sb-flow-copy"
|
|
225
|
+
bind:value={copyFrom}
|
|
226
|
+
disabled={loading || !selectedPrototype}
|
|
227
|
+
>
|
|
228
|
+
{#if !selectedPrototype}
|
|
229
|
+
<option value="">Select a prototype first</option>
|
|
230
|
+
{:else}
|
|
129
231
|
<option value="">None</option>
|
|
130
|
-
{#each
|
|
131
|
-
|
|
132
|
-
</
|
|
232
|
+
{#each copyFromFlows as f}<option value={f.path}>{f.title} ({f.name})</option>{/each}
|
|
233
|
+
{/if}
|
|
234
|
+
</select>
|
|
133
235
|
</div>
|
|
134
236
|
|
|
135
237
|
<div class="space-y-1">
|
|
136
|
-
<Label for="sb-flow-
|
|
137
|
-
<
|
|
238
|
+
<Label for="sb-flow-starting-page">Starting page <span class="text-muted-foreground">(optional)</span></Label>
|
|
239
|
+
<select
|
|
240
|
+
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
241
|
+
id="sb-flow-starting-page"
|
|
242
|
+
bind:value={startingPage}
|
|
243
|
+
disabled={loading || !selectedPrototype}
|
|
244
|
+
>
|
|
245
|
+
{#if !selectedPrototype}
|
|
246
|
+
<option value="">Select a prototype first</option>
|
|
247
|
+
{:else}
|
|
248
|
+
<option value="">None</option>
|
|
249
|
+
{#each prototypeRoutes as route}
|
|
250
|
+
<option value={route}>{route}</option>
|
|
251
|
+
{/each}
|
|
252
|
+
<option value={CREATE_NEW_PAGE_VALUE}>Create new page</option>
|
|
253
|
+
{/if}
|
|
254
|
+
</select>
|
|
255
|
+
<p class="text-xs text-muted-foreground">Users will be redirected to this page</p>
|
|
138
256
|
</div>
|
|
257
|
+
|
|
258
|
+
{#if showNewPageFields}
|
|
259
|
+
<div class="space-y-1">
|
|
260
|
+
<Label for="sb-flow-new-page-path">New page path</Label>
|
|
261
|
+
<div class="flex items-center gap-2">
|
|
262
|
+
<span class="text-xs text-muted-foreground font-mono">{newPagePrefix}</span>
|
|
263
|
+
<Input
|
|
264
|
+
id="sb-flow-new-page-path"
|
|
265
|
+
placeholder="new-page"
|
|
266
|
+
value={newPagePath.startsWith(newPagePrefix) ? newPagePath.slice(newPagePrefix.length) : ''}
|
|
267
|
+
oninput={(e: Event) => {
|
|
268
|
+
const suffix = (e.target as HTMLInputElement).value.replace(/^\/+/, '')
|
|
269
|
+
newPagePath = `${newPagePrefix}${suffix}`
|
|
270
|
+
}}
|
|
271
|
+
/>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="space-y-1">
|
|
275
|
+
<Label for="sb-flow-new-page-template">Template / recipe</Label>
|
|
276
|
+
<select
|
|
277
|
+
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
|
278
|
+
id="sb-flow-new-page-template"
|
|
279
|
+
bind:value={newPageTemplate}
|
|
280
|
+
disabled={!selectedPrototype}
|
|
281
|
+
>
|
|
282
|
+
<option value="">Blank page</option>
|
|
283
|
+
{#if globalTemplateChoices.length > 0}
|
|
284
|
+
<optgroup label="Global">
|
|
285
|
+
{#each globalTemplateChoices as partial}
|
|
286
|
+
<option value={partial.id}>{partial.name}</option>
|
|
287
|
+
{/each}
|
|
288
|
+
</optgroup>
|
|
289
|
+
{/if}
|
|
290
|
+
{#if localTemplateChoices.length > 0}
|
|
291
|
+
<optgroup label={localTemplateHeading}>
|
|
292
|
+
{#each localTemplateChoices as partial}
|
|
293
|
+
<option value={partial.id}>{partial.name}</option>
|
|
294
|
+
{/each}
|
|
295
|
+
</optgroup>
|
|
296
|
+
{/if}
|
|
297
|
+
</select>
|
|
298
|
+
</div>
|
|
299
|
+
{#if startingPageError}<p class="text-sm text-destructive">{startingPageError}</p>{/if}
|
|
300
|
+
{/if}
|
|
139
301
|
|
|
140
302
|
<div class="space-y-1">
|
|
141
303
|
<Label for="sb-flow-desc">Description</Label>
|