alembic 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +36 -22
- data/app/assets/builds/alembic/page_builder.css +1 -1
- data/app/assets/builds/alembic/page_builder.js +3 -3
- data/app/controllers/alembic/application_controller.rb +0 -16
- data/app/controllers/alembic/flows_controller.rb +6 -90
- data/app/controllers/alembic/manage/base_controller.rb +1 -0
- data/app/controllers/alembic/manage/flows_controller.rb +7 -44
- data/app/controllers/alembic/manage/pages_controller.rb +8 -1
- data/app/controllers/alembic/manage/previews_controller.rb +4 -19
- data/app/controllers/alembic/pages_controller.rb +19 -0
- data/app/controllers/concerns/alembic/summarizes.rb +38 -0
- data/app/helpers/alembic/pages_helper.rb +39 -0
- data/app/models/alembic/flow/definition_summary.rb +9 -0
- data/app/models/alembic/flow/run_summary.rb +10 -0
- data/app/models/alembic/flow/summaries.rb +36 -5
- data/app/models/alembic/flow/summary_version.rb +1 -1
- data/app/models/alembic/outputs/grouped.rb +1 -1
- data/app/models/alembic/outputs/percentage.rb +1 -1
- data/app/models/alembic/outputs/weighted_sum.rb +1 -1
- data/app/models/alembic/page/drawing.rb +17 -0
- data/app/models/alembic/page/options.rb +46 -0
- data/app/models/alembic/page/version.rb +13 -0
- data/app/models/alembic/page.rb +32 -0
- data/app/views/alembic/flows/complete.html.erb +3 -3
- data/app/views/alembic/flows/show.html.erb +6 -6
- data/app/views/alembic/manage/flows/edit.html.erb +10 -10
- data/app/views/alembic/manage/pages/_block_content.html.erb +1 -1
- data/app/views/alembic/manage/pages/index.html.erb +4 -1
- data/app/views/alembic/manage/pages/show.html.erb +7 -1
- data/app/views/alembic/pages/show.html.erb +5 -0
- data/config/routes.rb +8 -19
- data/db/migrate/20260825224538_remove_published_version_from_alembic_diagnostics.rb +21 -1
- data/db/migrate/20260922120000_add_slug_to_alembic_pages.rb +6 -0
- data/db/migrate/20260922130000_create_alembic_page_versions.rb +16 -0
- data/db/migrate/20260924220000_move_flows_onto_easy_flow.rb +166 -0
- data/lib/alembic/engine.rb +2 -11
- data/lib/alembic/version.rb +1 -1
- data/lib/alembic.rb +30 -6
- metadata +35 -71
- data/app/assets/builds/alembic/canvas.js +0 -1
- data/app/controllers/alembic/manage/canvas_controller.rb +0 -183
- data/app/controllers/alembic/manage/definitions_controller.rb +0 -15
- data/app/controllers/alembic/manage/versions_controller.rb +0 -22
- data/app/controllers/concerns/alembic/manage/draws_canvas.rb +0 -35
- data/app/javascript/alembic/canvas/Canvas.jsx +0 -197
- data/app/javascript/alembic/canvas/Connector.jsx +0 -30
- data/app/javascript/alembic/canvas/ConnectorLayer.jsx +0 -31
- data/app/javascript/alembic/canvas/Control.jsx +0 -42
- data/app/javascript/alembic/canvas/Inspector.jsx +0 -44
- data/app/javascript/alembic/canvas/Panel.jsx +0 -67
- data/app/javascript/alembic/canvas/Placeholder.jsx +0 -24
- data/app/javascript/alembic/canvas/Port.jsx +0 -16
- data/app/javascript/alembic/canvas/Records.jsx +0 -34
- data/app/javascript/alembic/canvas/StepCard.jsx +0 -57
- data/app/javascript/alembic/canvas/Toolbar.jsx +0 -19
- data/app/javascript/alembic/canvas/TypePicker.jsx +0 -21
- data/app/javascript/alembic/canvas/changes.js +0 -5
- data/app/javascript/alembic/canvas/choices.js +0 -4
- data/app/javascript/alembic/canvas/flow.js +0 -7
- data/app/javascript/alembic/canvas/flowSender.js +0 -22
- data/app/javascript/alembic/canvas/ids.js +0 -7
- data/app/javascript/alembic/canvas/rows.js +0 -2
- data/app/javascript/alembic/canvas/styles.js +0 -3
- data/app/javascript/alembic/canvas/useConnectors.js +0 -75
- data/app/javascript/alembic/canvas/useFlow.js +0 -17
- data/app/javascript/alembic/canvas.jsx +0 -13
- data/app/models/alembic/asked.rb +0 -3
- data/app/models/alembic/choice.rb +0 -7
- data/app/models/alembic/flow/admission.rb +0 -19
- data/app/models/alembic/flow/canvas.rb +0 -167
- data/app/models/alembic/flow/change.rb +0 -18
- data/app/models/alembic/flow/condition.rb +0 -24
- data/app/models/alembic/flow/definition.rb +0 -132
- data/app/models/alembic/flow/digest.rb +0 -116
- data/app/models/alembic/flow/document.rb +0 -131
- data/app/models/alembic/flow/drawing.rb +0 -15
- data/app/models/alembic/flow/edge.rb +0 -9
- data/app/models/alembic/flow/edit_history.rb +0 -75
- data/app/models/alembic/flow/layout.rb +0 -88
- data/app/models/alembic/flow/name.rb +0 -15
- data/app/models/alembic/flow/node.rb +0 -5
- data/app/models/alembic/flow/output.rb +0 -23
- data/app/models/alembic/flow/progress/kept.rb +0 -38
- data/app/models/alembic/flow/progress/loose.rb +0 -43
- data/app/models/alembic/flow/progress.rb +0 -11
- data/app/models/alembic/flow/registry.rb +0 -25
- data/app/models/alembic/flow/run.rb +0 -54
- data/app/models/alembic/flow/runner.rb +0 -65
- data/app/models/alembic/flow/settings.rb +0 -80
- data/app/models/alembic/flow/start.rb +0 -13
- data/app/models/alembic/flow/step.rb +0 -42
- data/app/models/alembic/flow/step_type/declaration.rb +0 -118
- data/app/models/alembic/flow/step_type.rb +0 -69
- data/app/models/alembic/flow/switch.rb +0 -17
- data/app/models/alembic/flow/terminal.rb +0 -13
- data/app/models/alembic/flow/validator.rb +0 -171
- data/app/models/alembic/flow/version.rb +0 -26
- data/app/models/alembic/flow/violation.rb +0 -9
- data/app/models/alembic/flow.rb +0 -44
- data/app/models/alembic/runner.rb +0 -13
- data/app/models/alembic/steps/question.rb +0 -52
- data/app/views/alembic/flow/steps/_choosing.html.erb +0 -9
- data/app/views/alembic/flows/step.html.erb +0 -27
- data/app/views/alembic/manage/definitions/edit.html.erb +0 -13
- data/app/views/alembic/manage/flows/index.html.erb +0 -32
- data/app/views/alembic/manage/flows/show.html.erb +0 -26
- data/app/views/alembic/manage/versions/index.html.erb +0 -41
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import KeystonePanel from "keystone_ui-react/src/Panel.jsx"
|
|
3
|
-
import Button from "keystone_ui-react/src/Button.jsx"
|
|
4
|
-
import Input from "keystone_ui-react/src/Input.jsx"
|
|
5
|
-
import Textarea from "keystone_ui-react/src/Textarea.jsx"
|
|
6
|
-
import { Label } from "keystone_ui-react/src/FieldText.jsx"
|
|
7
|
-
import { worded } from "./changes"
|
|
8
|
-
import { standing } from "./flow"
|
|
9
|
-
|
|
10
|
-
const sheet = { width: 280, padding: 20, overflowY: "auto" }
|
|
11
|
-
const heading = { fontWeight: 600, marginBottom: 6, marginTop: 14 }
|
|
12
|
-
const item = { fontSize: 12, marginBottom: 4, lineHeight: 1.4 }
|
|
13
|
-
const caption = { display: "block", marginBottom: 3 }
|
|
14
|
-
|
|
15
|
-
const settling = (flow, name, onSaveDetails) => (event) => {
|
|
16
|
-
if (event.target.value !== (flow[name] ?? "")) onSaveDetails({ [name]: event.target.value })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const Panel = ({ flow, changes, problems, refusal, notice, onCreate, onPublish, onSaveDetails, onClose }) => (
|
|
20
|
-
<KeystonePanel as="aside" style={sheet} data-builder-panel>
|
|
21
|
-
<div style={{ display: "flex", alignItems: "start", justifyContent: "space-between", gap: 8 }}>
|
|
22
|
-
<h2 style={{ fontWeight: 600 }} data-flow-name>{flow.title || flow.slug}</h2>
|
|
23
|
-
<button title="Close" onClick={onClose} className="text-gray-500 dark:text-gray-400"
|
|
24
|
-
style={{ border: "none", background: "none", cursor: "pointer", fontSize: 18, lineHeight: 1 }}>×</button>
|
|
25
|
-
</div>
|
|
26
|
-
<a className="text-accent-600 dark:text-accent-400" style={{ fontSize: 12, display: "block" }} href={flow.history_url} data-history>{standing(flow)}</a>
|
|
27
|
-
|
|
28
|
-
<h2 style={heading}>Problems</h2>
|
|
29
|
-
{refusal && <p className="text-red-800 dark:text-red-400" style={{ ...item, fontWeight: 600 }} data-refusal>{refusal}</p>}
|
|
30
|
-
{notice && !refusal && <p className="text-emerald-800 dark:text-emerald-400" style={{ ...item, fontWeight: 600 }} data-notice>{notice}</p>}
|
|
31
|
-
{problems.length === 0 && !refusal
|
|
32
|
-
? <p className="text-gray-500 dark:text-gray-400" style={{ fontSize: 12 }}>Nothing wrong with this flow.</p>
|
|
33
|
-
: problems.map((problem) => (
|
|
34
|
-
<p key={`${problem.node}-${problem.problem}`} className="text-amber-700 dark:text-amber-400" style={item} data-problem>⚠ {worded(problem)}</p>
|
|
35
|
-
))}
|
|
36
|
-
|
|
37
|
-
<h2 style={heading}>Changes since the last version</h2>
|
|
38
|
-
{changes.length === 0
|
|
39
|
-
? <p className="text-gray-500 dark:text-gray-400" style={{ fontSize: 12 }}>Nothing has changed.</p>
|
|
40
|
-
: changes.map((change, at) => <p key={at} style={item} data-change>{change}</p>)}
|
|
41
|
-
|
|
42
|
-
<h2 style={heading}>Details</h2>
|
|
43
|
-
<Label>
|
|
44
|
-
<span style={caption}>Title</span>
|
|
45
|
-
<Input className="mb-3" defaultValue={flow.title ?? ""} onBlur={settling(flow, "title", onSaveDetails)} data-flow-title />
|
|
46
|
-
</Label>
|
|
47
|
-
|
|
48
|
-
<Label>
|
|
49
|
-
<span style={caption}>Summary</span>
|
|
50
|
-
<Textarea className="mb-3" rows={4} defaultValue={flow.summary ?? ""} onBlur={settling(flow, "summary", onSaveDetails)} data-flow-summary />
|
|
51
|
-
</Label>
|
|
52
|
-
|
|
53
|
-
<Label>
|
|
54
|
-
<span style={caption}>Start label</span>
|
|
55
|
-
<Input className="mb-3" defaultValue={flow.start_label ?? ""} onBlur={settling(flow, "start_label", onSaveDetails)} data-flow-start-label />
|
|
56
|
-
</Label>
|
|
57
|
-
|
|
58
|
-
<div style={{ marginTop: 16 }}>
|
|
59
|
-
<Button variant="secondary" size="sm" className="w-full mb-1.5" onClick={onCreate} data-create-version>Create version</Button>
|
|
60
|
-
<Button size="sm" className="w-full mb-1.5" onClick={onPublish} data-publish>Publish</Button>
|
|
61
|
-
<Button variant="secondary" size="sm" className="w-full mb-1.5" href={flow.definition_url} data-definition>Definition</Button>
|
|
62
|
-
<Button variant="secondary" size="sm" className="w-full mb-1.5" href={flow.details_url} data-details>Edit details</Button>
|
|
63
|
-
</div>
|
|
64
|
-
</KeystonePanel>
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
export default Panel
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { CARD } from "./styles"
|
|
3
|
-
|
|
4
|
-
const waiting = {
|
|
5
|
-
width: CARD, boxSizing: "border-box", padding: "10px 14px", borderRadius: 8, textAlign: "center", fontSize: 12, cursor: "pointer"
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const open = "border-2 border-dashed border-amber-500 bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300"
|
|
9
|
-
const receiving = "border-2 border-dashed border-accent-600 bg-accent-50 text-accent-800 dark:bg-accent-950 dark:text-accent-200"
|
|
10
|
-
|
|
11
|
-
const Placeholder = ({ node, dragging, onFill, onDrop }) => (
|
|
12
|
-
<div ref={node.ref} data-placeholder={node.id}
|
|
13
|
-
title={`Choose the step “${node.label}” should lead to`}
|
|
14
|
-
onClick={onFill}
|
|
15
|
-
onDragOver={(event) => { if (dragging) { event.preventDefault(); event.dataTransfer.dropEffect = "move" } }}
|
|
16
|
-
onDrop={(event) => { event.preventDefault(); onDrop() }}
|
|
17
|
-
className={dragging ? receiving : open}
|
|
18
|
-
style={waiting}>
|
|
19
|
-
<div style={{ fontWeight: 600 }}>{node.label}</div>
|
|
20
|
-
<div style={{ fontSize: 11 }}>{dragging ? "drop a step here" : "leads nowhere yet — click to choose"}</div>
|
|
21
|
-
</div>
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
export default Placeholder
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
|
|
3
|
-
const open = "border border-amber-500 bg-white text-amber-700 dark:bg-zinc-900 dark:text-amber-400"
|
|
4
|
-
const joined = "border border-gray-300 bg-white text-gray-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-gray-400"
|
|
5
|
-
const choosing = "border border-accent-600 bg-accent-600 text-white"
|
|
6
|
-
|
|
7
|
-
const Port = ({ name, connected, armed, onArm, connecting }) => (
|
|
8
|
-
<button onClick={(event) => { if (connecting) return; event.stopPropagation(); onArm(event) }}
|
|
9
|
-
title={armed ? "Now choose a step to connect to" : "Connect this branch"}
|
|
10
|
-
className={armed ? choosing : connected ? joined : open}
|
|
11
|
-
style={{
|
|
12
|
-
padding: "1px 8px", marginRight: 4, borderRadius: 999, fontSize: 11, cursor: "pointer"
|
|
13
|
-
}}>{name || "next"}</button>
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
export default Port
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import Control from "./Control"
|
|
3
|
-
import { amended } from "./rows"
|
|
4
|
-
import Button from "keystone_ui-react/src/Button.jsx"
|
|
5
|
-
|
|
6
|
-
const Records = ({ holds, labels, rows, onChange, onSettle }) => {
|
|
7
|
-
const kept = Array.isArray(rows) ? rows : []
|
|
8
|
-
const amend = (index, name, next, settle) => {
|
|
9
|
-
const updated = amended(kept, index, name, next)
|
|
10
|
-
settle ? onSettle(updated) : onChange(updated)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<div style={{ marginBottom: 12 }}>
|
|
15
|
-
{kept.map((row, index) => (
|
|
16
|
-
<div key={index} className="rounded-md border border-gray-200 dark:border-zinc-700" style={{ padding: "8px 8px 2px", marginBottom: 6 }}>
|
|
17
|
-
{Object.entries(holds).map(([ name, type ]) => (
|
|
18
|
-
<label key={name} style={{ display: "block" }}>
|
|
19
|
-
<span className="text-gray-500 dark:text-gray-400" style={{ display: "block", marginBottom: 2, fontSize: 11 }}>{(labels || {})[name] || name}</span>
|
|
20
|
-
<Control type={type} value={row[name]}
|
|
21
|
-
onChange={(next) => amend(index, name, next, false)}
|
|
22
|
-
onSettle={(next) => amend(index, name, next, true)} />
|
|
23
|
-
</label>
|
|
24
|
-
))}
|
|
25
|
-
<Button variant="secondary" size="sm" className="mb-1.5"
|
|
26
|
-
onClick={() => onSettle(kept.filter((_, at) => at !== index))}>Remove</Button>
|
|
27
|
-
</div>
|
|
28
|
-
))}
|
|
29
|
-
<Button variant="secondary" size="sm" className="w-full" onClick={() => onSettle([ ...kept, {} ])}>Add</Button>
|
|
30
|
-
</div>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default Records
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import Port from "./Port"
|
|
3
|
-
import { CARD } from "./styles"
|
|
4
|
-
|
|
5
|
-
const card = { width: CARD, boxSizing: "border-box", borderRadius: 8 }
|
|
6
|
-
|
|
7
|
-
const bookendCard = { padding: "8px 14px", textAlign: "center", fontWeight: 600 }
|
|
8
|
-
|
|
9
|
-
const named = { fontWeight: 600, lineHeight: 1.3 }
|
|
10
|
-
|
|
11
|
-
const edge = (target, troubled, selected) => {
|
|
12
|
-
const lift = selected ? "ring-4 ring-accent-600/15" : "shadow-sm"
|
|
13
|
-
if (target || (selected && !troubled)) return `border-accent-600 ${lift}`
|
|
14
|
-
if (troubled) return `border-red-600 ${lift}`
|
|
15
|
-
return "border-gray-300 shadow-sm dark:border-zinc-700"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const StepCard = ({ node, selected, armed, connecting, onSelect, onArm, onDragEnd, onDragStart }) => {
|
|
19
|
-
const bookend = node.begins_here || node.ends_here
|
|
20
|
-
const pinned = node.begins_here
|
|
21
|
-
const ports = node.ends_here ? [] : node.ports
|
|
22
|
-
const target = connecting && !pinned
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div ref={node.ref}
|
|
26
|
-
data-step={node.id}
|
|
27
|
-
draggable={!pinned}
|
|
28
|
-
onDragStart={(event) => { event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", node.id); onDragStart() }}
|
|
29
|
-
onDragEnd={onDragEnd}
|
|
30
|
-
onClick={onSelect}
|
|
31
|
-
className={`bg-white border-2 dark:bg-zinc-900 ${edge(target, node.violations.length > 0, selected)}`}
|
|
32
|
-
style={{
|
|
33
|
-
...card,
|
|
34
|
-
padding: bookend ? 0 : "12px 14px",
|
|
35
|
-
cursor: pinned ? "default" : connecting ? "crosshair" : "grab"
|
|
36
|
-
}}>
|
|
37
|
-
{bookend && <div className="text-gray-500 dark:text-gray-400" style={bookendCard}>{node.label}</div>}
|
|
38
|
-
{!bookend && <div style={named}>{node.label}</div>}
|
|
39
|
-
{!bookend && <div className="text-gray-500 dark:text-gray-400" style={{ fontSize: 11, marginTop: 2 }}>{node.type}</div>}
|
|
40
|
-
{node.violations.map((violation) => (
|
|
41
|
-
<div key={violation.problem + violation.detail} className="text-red-600 dark:text-red-400" style={{ fontSize: 11, padding: "0 14px 6px" }}>
|
|
42
|
-
{violation.problem.replace(/_/g, " ")}{violation.detail ? `: ${violation.detail}` : ""}
|
|
43
|
-
</div>
|
|
44
|
-
))}
|
|
45
|
-
{ports.length > 0 && (
|
|
46
|
-
<div style={{ padding: node.begins_here ? "0 14px 8px" : "10px 0 0" }}>
|
|
47
|
-
{ports.map((port) => (
|
|
48
|
-
<Port key={port} name={port} connected={node.connected.includes(port)}
|
|
49
|
-
connecting={connecting} armed={armed === port} onArm={(event) => onArm(port, event)} />
|
|
50
|
-
))}
|
|
51
|
-
</div>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export default StepCard
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import Button from "keystone_ui-react/src/Button.jsx"
|
|
3
|
-
|
|
4
|
-
const round = { width: 24, height: 24, padding: 0, fontSize: 15, lineHeight: "15px" }
|
|
5
|
-
|
|
6
|
-
const Stepper = ({ label, title, idle, enabled, onUse }) => (
|
|
7
|
-
<Button variant="secondary" size="sm" title={enabled ? title : idle} disabled={!enabled} onClick={onUse}
|
|
8
|
-
style={{ opacity: enabled ? 1 : 0.4, cursor: enabled ? "pointer" : "default" }}>{label}</Button>
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
const Toolbar = ({ undoable, redoable, onAdd, onUndo, onRedo }) => (
|
|
12
|
-
<div style={{ position: "absolute", top: 16, left: 16, zIndex: 5, display: "flex", gap: 8 }}>
|
|
13
|
-
<Button variant="secondary" className="rounded-full" style={round} title="Add a step" onClick={onAdd}>+</Button>
|
|
14
|
-
<Stepper label="↶ Undo" title="Undo the last change" idle="Nothing to undo" enabled={undoable} onUse={onUndo} />
|
|
15
|
-
<Stepper label="↷ Redo" title="Redo the change you undid" idle="Nothing to redo" enabled={redoable} onUse={onRedo} />
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
export default Toolbar
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import Panel from "keystone_ui-react/src/Panel.jsx"
|
|
3
|
-
import Button from "keystone_ui-react/src/Button.jsx"
|
|
4
|
-
|
|
5
|
-
const TypePicker = ({ entries, at, onPick, onConnect, onDismiss }) => (
|
|
6
|
-
<Panel className="shadow-lg" style={{ position: "absolute", zIndex: 9, top: at.y, left: at.x, width: 210, padding: 8, borderRadius: 8 }}>
|
|
7
|
-
<p className="text-gray-500 dark:text-gray-400" style={{ margin: "0 0 6px", fontSize: 11 }}>Add a step</p>
|
|
8
|
-
{entries.map((entry) => (
|
|
9
|
-
<Button key={entry.type} variant="secondary" size="sm" className="w-full mb-1.5 flex-col" onClick={() => onPick(entry)}>
|
|
10
|
-
{entry.label}
|
|
11
|
-
<span className="opacity-75" style={{ display: "block", fontSize: 11 }}>{entry.type}</span>
|
|
12
|
-
</Button>
|
|
13
|
-
))}
|
|
14
|
-
{onConnect && (
|
|
15
|
-
<Button variant="secondary" size="sm" className="w-full mb-1.5" onClick={onConnect}>Connect to a step already here</Button>
|
|
16
|
-
)}
|
|
17
|
-
<Button variant="secondary" size="sm" className="w-full" onClick={onDismiss}>Cancel</Button>
|
|
18
|
-
</Panel>
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
export default TypePicker
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export const standing = ({ version, published }) => {
|
|
2
|
-
if (!version) return "No version yet"
|
|
3
|
-
if (!published) return `Version ${version} · never published`
|
|
4
|
-
if (published === version) return `Version ${version} · live`
|
|
5
|
-
|
|
6
|
-
return `Version ${version} · visitors still on ${published}`
|
|
7
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
const UNREACHABLE = "Your change was not saved because the server could not be reached."
|
|
2
|
-
|
|
3
|
-
export const createFlowSender = ({ base, token, fetch, onFlow, onError, onNotice }) => async (path, method, body) => {
|
|
4
|
-
const response = await fetch(base + path, {
|
|
5
|
-
method, headers: { "Content-Type": "application/json", "X-CSRF-Token": token }, body: body && JSON.stringify(body)
|
|
6
|
-
}).catch(() => null)
|
|
7
|
-
if (!response) {
|
|
8
|
-
onNotice(null)
|
|
9
|
-
return onError(UNREACHABLE)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const answered = await response.json().catch(() => ({}))
|
|
13
|
-
|
|
14
|
-
if (!response.ok) {
|
|
15
|
-
onNotice(null)
|
|
16
|
-
return onError(answered.error || "That change was refused")
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
onError(null)
|
|
20
|
-
onNotice(answered.notice || null)
|
|
21
|
-
onFlow(await (await fetch(base + ".json", { headers: { Accept: "application/json" } })).json())
|
|
22
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { useLayoutEffect, useState } from "react"
|
|
2
|
-
|
|
3
|
-
export const anchor = (box, side, frame, surface) => {
|
|
4
|
-
const left = box.left - frame.left + surface.scrollLeft
|
|
5
|
-
const top = box.top - frame.top + surface.scrollTop
|
|
6
|
-
|
|
7
|
-
if (side === "left") return { x: left, y: top + box.height / 2 }
|
|
8
|
-
if (side === "right") return { x: left + box.width, y: top + box.height / 2 }
|
|
9
|
-
if (side === "top") return { x: left + box.width / 2, y: top }
|
|
10
|
-
return { x: left + box.width / 2, y: top + box.height }
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const shy = (point, side) => {
|
|
14
|
-
if (side === "top") return { x: point.x, y: point.y - 3 }
|
|
15
|
-
if (side === "left") return { x: point.x - 3, y: point.y }
|
|
16
|
-
if (side === "right") return { x: point.x + 3, y: point.y }
|
|
17
|
-
return { x: point.x, y: point.y + 3 }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const drawn = (edge, a, b, lane, detour) => {
|
|
21
|
-
const path = {
|
|
22
|
-
straight: `M ${a.x} ${a.y} L ${b.x} ${b.y}`,
|
|
23
|
-
turn: `M ${a.x} ${a.y} H ${b.x} V ${b.y}`,
|
|
24
|
-
lane: `M ${a.x} ${a.y} V ${lane} H ${b.x} V ${b.y}`,
|
|
25
|
-
detour: `M ${a.x} ${a.y} H ${detour} V ${b.y} H ${b.x}`
|
|
26
|
-
}[edge.route]
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
...edge, path,
|
|
30
|
-
midX: edge.route === "detour" ? detour : (a.x + b.x) / 2,
|
|
31
|
-
midY: edge.route === "lane" ? lane : (a.y + b.y) / 2
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const useConnectors = (flow, surface, cards, watching) => {
|
|
36
|
-
const [ links, setLinks ] = useState([])
|
|
37
|
-
const [ extent, setExtent ] = useState({ width: "100%", height: "100%" })
|
|
38
|
-
|
|
39
|
-
useLayoutEffect(() => {
|
|
40
|
-
const measure = () => {
|
|
41
|
-
const frame = surface.current?.getBoundingClientRect()
|
|
42
|
-
if (!frame) return
|
|
43
|
-
|
|
44
|
-
setExtent({ width: surface.current.scrollWidth, height: surface.current.scrollHeight })
|
|
45
|
-
|
|
46
|
-
setLinks(flow.edges.flatMap((edge) => {
|
|
47
|
-
const fromBox = cards.current[edge.source]?.getBoundingClientRect()
|
|
48
|
-
const toBox = cards.current[edge.target]?.getBoundingClientRect()
|
|
49
|
-
if (!fromBox || !toBox) return []
|
|
50
|
-
|
|
51
|
-
const a = anchor(fromBox, edge.leaves, frame, surface.current)
|
|
52
|
-
const b = shy(anchor(toBox, edge.enters, frame, surface.current), edge.enters)
|
|
53
|
-
const lane = (anchor(fromBox, "bottom", frame, surface.current).y + anchor(toBox, "top", frame, surface.current).y) / 2
|
|
54
|
-
const detour = edge.leaves === "left" ? Math.min(a.x, b.x) - 28 : Math.max(a.x, b.x) + 28
|
|
55
|
-
|
|
56
|
-
return [ drawn(edge, a, b, lane, detour) ]
|
|
57
|
-
}))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
measure()
|
|
61
|
-
window.addEventListener("resize", measure)
|
|
62
|
-
|
|
63
|
-
const watcher = new ResizeObserver(measure)
|
|
64
|
-
if (surface.current) watcher.observe(surface.current)
|
|
65
|
-
|
|
66
|
-
return () => {
|
|
67
|
-
window.removeEventListener("resize", measure)
|
|
68
|
-
watcher.disconnect()
|
|
69
|
-
}
|
|
70
|
-
}, watching)
|
|
71
|
-
|
|
72
|
-
return { links, extent }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export default useConnectors
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { useMemo, useState } from "react"
|
|
2
|
-
import { createFlowSender } from "./flowSender"
|
|
3
|
-
|
|
4
|
-
const useFlow = (base, token, initial) => {
|
|
5
|
-
const [ flow, setFlow ] = useState(initial)
|
|
6
|
-
const [ error, setError ] = useState(null)
|
|
7
|
-
const [ notice, setNotice ] = useState(null)
|
|
8
|
-
|
|
9
|
-
const send = useMemo(
|
|
10
|
-
() => createFlowSender({ base, token, fetch: (...request) => window.fetch(...request), onFlow: setFlow, onError: setError, onNotice: setNotice }),
|
|
11
|
-
[ base, token ]
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
return { flow, error, notice, send }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default useFlow
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createRoot } from "react-dom/client"
|
|
2
|
-
import { register } from "keystone_ui-react/src/registry.js"
|
|
3
|
-
import { startMounting } from "keystone_ui-react/src/mounting.js"
|
|
4
|
-
import Canvas from "./canvas/Canvas"
|
|
5
|
-
|
|
6
|
-
register("alembic/flow-editor", Canvas)
|
|
7
|
-
|
|
8
|
-
document.addEventListener("alembic:flow-named", (event) => {
|
|
9
|
-
const heading = document.querySelector("[data-flow-heading]")
|
|
10
|
-
if (heading) heading.textContent = event.detail
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
startMounting(document, createRoot)
|
data/app/models/alembic/asked.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
module Admission
|
|
4
|
-
def self.of(diagnostic, permitted:)
|
|
5
|
-
raise NotPublished if diagnostic.nil? || diagnostic.live_definition.blank?
|
|
6
|
-
raise NotPermitted unless permitted && diagnostic.available?
|
|
7
|
-
|
|
8
|
-
diagnostic
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def self.of_run(run, permitted: true)
|
|
12
|
-
raise Withdrawn if run.definition_version.withdrawn?
|
|
13
|
-
raise NotPermitted unless permitted && run.flow.available?
|
|
14
|
-
|
|
15
|
-
run
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class Canvas
|
|
4
|
-
def initialize(document, registry: Flow.registry)
|
|
5
|
-
@document = document
|
|
6
|
-
@registry = registry
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def to_h
|
|
10
|
-
{ "nodes" => nodes, "edges" => edges, "palette" => palette, "violations" => violations }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def nodes
|
|
16
|
-
@document.nodes.map { |node| drawn_step(node) } + waiting.map { |gap| drawn_placeholder(gap) }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def drawn_step(node)
|
|
20
|
-
{ "id" => node.id, "type" => node.type, "label" => label_for(node),
|
|
21
|
-
"config" => node.config, "ports" => [], "ends_here" => ends_here?(node), "begins_here" => begins_here?(node),
|
|
22
|
-
"loose" => loose?(node), "choices" => choices_for(node), "placeholder" => false, **placed[node.id] }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def drawn_placeholder(gap)
|
|
26
|
-
{ "id" => gap[:id], "type" => "placeholder", "label" => gap[:on], "config" => {}, "ports" => [],
|
|
27
|
-
"ends_here" => false, "begins_here" => false, "loose" => false, "choices" => {},
|
|
28
|
-
"placeholder" => true, "from" => gap[:from], "on" => gap[:on], **placed[gap[:id]] }
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def edges
|
|
32
|
-
drawn.edges.each_with_index.map do |edge, index|
|
|
33
|
-
{ "id" => "#{edge.from}-#{edge.to}-#{index}", "source" => edge.from, "target" => edge.to, "label" => edge.on&.to_s,
|
|
34
|
-
"placeholder" => waiting.any? { |gap| gap[:id] == edge.to } }.merge(routing(edge, placed))
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def placed
|
|
39
|
-
@placed ||= Layout.new(drawn).positions
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def drawn
|
|
43
|
-
@drawn ||= Document.new(@document.to_h.merge(
|
|
44
|
-
"nodes" => Array(@document.to_h["nodes"]) + waiting.map { |gap| { "id" => gap[:id], "type" => "placeholder" } },
|
|
45
|
-
"edges" => Array(@document.to_h["edges"]) + waiting.map { |gap| { "from" => gap[:from], "to" => gap[:id], "on" => gap[:on] } }
|
|
46
|
-
), registry: @registry)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def waiting
|
|
50
|
-
@waiting ||= @document.nodes.flat_map { |node| gaps_after(node) }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def gaps_after(node)
|
|
54
|
-
wired = @document.edges_from(node.id).map { |edge| edge.on.to_s }
|
|
55
|
-
results = digest.routing_values(node.id)
|
|
56
|
-
return [ { id: "#{node.id}--", from: node.id, on: nil } ] if results.empty? && leads_nowhere?(node)
|
|
57
|
-
|
|
58
|
-
results.reject { |value| wired.include?(value) }
|
|
59
|
-
.map { |value| { id: "#{node.id}--#{value}", from: node.id, on: value } }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def leads_nowhere?(node)
|
|
63
|
-
!ends_here?(node) && !loose?(node) && @document.edges_from(node.id).none?
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def routing(edge, placed)
|
|
67
|
-
from = placed[edge.from]
|
|
68
|
-
to = placed[edge.to]
|
|
69
|
-
return { "leaves" => "bottom", "enters" => "top", "route" => "straight" } unless from && to
|
|
70
|
-
|
|
71
|
-
return alongside_routing(from, to) unless to["row"] > from["row"]
|
|
72
|
-
|
|
73
|
-
{ "leaves" => "bottom", "enters" => "top",
|
|
74
|
-
"route" => to["column"] == from["column"] ? "straight" : "lane" }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def alongside_routing(from, to)
|
|
78
|
-
leftward = to["column"] < from["column"]
|
|
79
|
-
|
|
80
|
-
{ "leaves" => leftward ? "left" : "right", "enters" => leftward ? "right" : "left",
|
|
81
|
-
"route" => to["row"] == from["row"] ? "straight" : "detour" }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def palette
|
|
85
|
-
@registry.step_types.reject(&:begins_here?).map do |step_type|
|
|
86
|
-
{ "type" => step_type.id.to_s, "label" => step_type.step_name,
|
|
87
|
-
"fields" => step_type.settings.fields.transform_keys(&:to_s).transform_values(&:to_s),
|
|
88
|
-
"labels" => step_type.settings.labels.transform_keys(&:to_s),
|
|
89
|
-
"choices" => step_type.settings.choices.transform_keys(&:to_s),
|
|
90
|
-
"records" => holdings_of(step_type),
|
|
91
|
-
"record_labels" => step_type.settings.record_labels.to_h { |name, held| [ name.to_s, held.transform_keys(&:to_s) ] },
|
|
92
|
-
"awaits_input" => step_type.awaits_input? }
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def holdings_of(step_type)
|
|
97
|
-
step_type.settings.record_fields.to_h do |name, holds|
|
|
98
|
-
[ name.to_s, holds.transform_keys(&:to_s).transform_values(&:to_s) ]
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def violations
|
|
103
|
-
Validator.new(@document, registry: @registry).violations.map do |violation|
|
|
104
|
-
{ "node" => violation.node, "problem" => violation.problem.to_s, "detail" => violation.detail }
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def label_for(node)
|
|
109
|
-
Name.of(node, @registry)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def choices_for(node)
|
|
113
|
-
naming_steps(node).index_with { earlier_than(node) }.merge(drawn_by(node)).merge(named_outputs_by(node))
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def named_outputs_by(node)
|
|
117
|
-
step_type_for(node)&.settings&.outputs_of.to_h.to_h do |name, source|
|
|
118
|
-
[ name.to_s, digest.outputs_of(node.config[source.to_s]) ]
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def drawn_by(node)
|
|
123
|
-
step_type_for(node)&.settings&.drawn_from.to_h.to_h do |name, source|
|
|
124
|
-
[ name.to_s, digest.values_of(step_named_by(node, source), node.config[source.to_s]) ]
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def step_named_by(node, source)
|
|
129
|
-
node.config[step_type_for(node).settings.outputs_of[source].to_s]
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def digest
|
|
133
|
-
Digest.new(@document, registry: @registry)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def naming_steps(node)
|
|
137
|
-
step_type_for(node)&.settings&.naming_steps.to_a.map(&:to_s)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def earlier_than(node)
|
|
141
|
-
digest.preceding(node.id).map do |id|
|
|
142
|
-
{ "value" => id, "label" => label_for(@document.node(id)) }
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def ends_here?(node)
|
|
147
|
-
step_type_for(node)&.ends_here? || false
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def loose?(node)
|
|
151
|
-
@document.entry.present? && @document.reachable.exclude?(node.id)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def begins_here?(node)
|
|
155
|
-
step_type_for(node)&.begins_here? || false
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def ports_for(node)
|
|
159
|
-
digest.routing_values(node.id)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def step_type_for(node)
|
|
163
|
-
@registry.fetch(node.type) if @registry.registered?(node.type)
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
module Change
|
|
4
|
-
PHRASING = { "added" => "Added", "updated" => "Updated", "removed" => "Removed",
|
|
5
|
-
"moved" => "Moved", "connected" => "Connected", "disconnected" => "Disconnected", "returned" => "Returned to" }.freeze
|
|
6
|
-
|
|
7
|
-
def self.phrase(change)
|
|
8
|
-
named = Array(change["named"]).compact_blank.map { |name| "“#{name}”" }
|
|
9
|
-
verb = PHRASING.fetch(change["action"], change["action"])
|
|
10
|
-
|
|
11
|
-
return "#{verb} #{named.join(' → ')}" if named.any?
|
|
12
|
-
return "#{verb} #{change['detail']}" if change["detail"].present?
|
|
13
|
-
|
|
14
|
-
verb
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|