mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (workflow): ux for workflow creation
This commit is contained in:
parent
3788d6bffe
commit
882b036615
4 changed files with 669 additions and 82 deletions
|
|
@ -1,6 +1,11 @@
|
||||||
.component_page_workflow .pull-right {
|
.component_page_workflow .pull-right {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
.component_page_workflow button {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 3px 5px;
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
.component_page_workflow .box {
|
.component_page_workflow .box {
|
||||||
display: block;
|
display: block;
|
||||||
background: white;
|
background: white;
|
||||||
|
|
@ -11,7 +16,7 @@
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
.component_page_workflow .box.status-unpublished {
|
.component_page_workflow .box.disabled {
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
}
|
}
|
||||||
.component_page_workflow .box h3 {
|
.component_page_workflow .box h3 {
|
||||||
|
|
@ -24,26 +29,14 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--light);
|
color: var(--light);
|
||||||
}
|
}
|
||||||
.component_page_workflow button {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 3px 5px;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
.component_page_workflow .box svg {
|
.component_page_workflow .box svg {
|
||||||
float: left;
|
float: left;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
margin-right: 10px;
|
|
||||||
fill: var(--light);
|
fill: var(--light);
|
||||||
}
|
}
|
||||||
.component_page_workflow button.box {
|
|
||||||
padding: 5px 10px;
|
|
||||||
background: var(--light);
|
|
||||||
color: var(--bg-color);
|
|
||||||
}
|
|
||||||
.component_page_workflow .box .workflow-summary button {
|
.component_page_workflow .box .workflow-summary button {
|
||||||
color: var(--light);
|
color: var(--light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component_page_workflow hr {
|
.component_page_workflow hr {
|
||||||
border: none;
|
border: none;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
@ -53,13 +46,162 @@
|
||||||
.component_page_workflow hr:after {
|
.component_page_workflow hr:after {
|
||||||
content: " ";
|
content: " ";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20px;
|
left: 26px;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border-right: 4px dashed rgba(0, 0, 0, 0.15);
|
border-right: 4px dashed rgba(0, 0, 0, 0.2);
|
||||||
border-right-style: dotted;
|
border-right-style: dotted;
|
||||||
}
|
}
|
||||||
|
.component_page_workflow .box [data-bind="form"] {
|
||||||
.component_page_admin .page_container a:hover {
|
overflow: hidden;
|
||||||
opacity: 1;
|
}
|
||||||
|
.component_page_workflow .box [data-bind="form"] .formbuilder {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
border: 2px solid #ebebec;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.component_page_workflow .box.disabled [data-bind="form"] .formbuilder {
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
.component_page_workflow .box [data-bind="form"] .formbuilder:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component_page_workflow .box h3 button.pull-right {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* details page buttons */
|
||||||
|
.component_page_workflow .box button[alt="delete"] {
|
||||||
|
position: absolute;
|
||||||
|
top: -13px;
|
||||||
|
right: -13px;
|
||||||
|
}
|
||||||
|
.component_page_workflow .box button[alt="delete"] svg {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
background: #ebebec;
|
||||||
|
fill: var(--light);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Workflow creation modal */
|
||||||
|
.component_workflow_create {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.component_workflow_create h2 {
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--light);
|
||||||
|
}
|
||||||
|
.component_workflow_create form {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.component_workflow_create form input {
|
||||||
|
font-size: 1rem;
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: rgba(0,0,0,0.75);
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.component_workflow_create form input::placeholder {
|
||||||
|
color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.component_workflow_create [data-bind="list"] {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.component_workflow_create [data-bind="list"] .item {
|
||||||
|
color: var(--color);
|
||||||
|
background: #f2f2f2;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
padding: 5px 5px 5px 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
margin-top: 2px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.component_workflow_create [data-bind="list"] .item svg {
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
fill: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* save button */
|
||||||
|
.workflow-fab {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: var(--emphasis);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.workflow-fab select {
|
||||||
|
background: var(--emphasis);
|
||||||
|
color: var(--bg-color);
|
||||||
|
font-family: monospace;
|
||||||
|
border: none;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 5px;
|
||||||
|
text-align: right;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-bottom-left-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.workflow-fab svg {
|
||||||
|
height: 30px;
|
||||||
|
fill: var(--bg-color);
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ADD BUTTON */
|
||||||
|
.component_page_workflow [data-bind="add"] {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.component_page_workflow [data-bind="add"] .box {
|
||||||
|
align-items: center;
|
||||||
|
background: var(--emphasis);
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 0px 2px 0px;
|
||||||
|
position: relative;
|
||||||
|
left: 5px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
.component_page_workflow [data-bind="add"] .box button {
|
||||||
|
color: var(--bg-color);
|
||||||
|
font-family: monospace;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.component_page_workflow [data-bind="add"] .box svg {
|
||||||
|
margin-right: 0;
|
||||||
|
fill: var(--bg-color);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.component_page_workflow [data-bind="add"] .box > .item {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: transparent;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
.component_page_workflow [data-bind="add"] .box .sub {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.component_page_workflow [data-bind="add"] .box > .flex {
|
||||||
|
padding: 0 5px 0 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,34 +5,251 @@ import AdminHOC from "./decorator.js";
|
||||||
import ctrlList from "./ctrl_workflow_list.js";
|
import ctrlList from "./ctrl_workflow_list.js";
|
||||||
import ctrlDetails from "./ctrl_workflow_details.js";
|
import ctrlDetails from "./ctrl_workflow_details.js";
|
||||||
|
|
||||||
const mockWorkflows = [
|
const workflows = [
|
||||||
{
|
{
|
||||||
id: "uuid",
|
id: "dummy",
|
||||||
name: "Notify team when file uploaded",
|
name: "My First Workflow",
|
||||||
description: "Send notification to #general when any file is uploaded to /shared",
|
published: false,
|
||||||
status: "published",
|
trigger: { name: "user" },
|
||||||
lastEdited: "2 days ago",
|
actions: [
|
||||||
trigger: "File uploaded",
|
{
|
||||||
action: "Send notification"
|
name: "tools/debug",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notify/email",
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "uuid",
|
id: "uuid0",
|
||||||
name: "Notify team when file uploaded",
|
name: "Detection de fichier d'inbox",
|
||||||
description: "Send notification to #general when any file is uploaded to /shared",
|
published: true,
|
||||||
status: "unpublished",
|
|
||||||
lastEdited: "2 days ago",
|
lastEdited: "2 days ago",
|
||||||
trigger: "File uploaded",
|
trigger: { name: "watch" },
|
||||||
action: "Send notification"
|
actions: [
|
||||||
|
{
|
||||||
|
name: "run/program",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notify/email",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "uuid1",
|
||||||
|
name: "Notify team when file is moved",
|
||||||
|
published: true,
|
||||||
|
lastEdited: "2 days ago",
|
||||||
|
history: [],
|
||||||
|
trigger: { name: "operation" },
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: "notify/email",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "uuid2",
|
||||||
|
name: "Any change to the contract folder",
|
||||||
|
published: true,
|
||||||
|
history: [],
|
||||||
|
trigger: { name: "operation", values: {} },
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: "notify/email",
|
||||||
|
// values: {} // TODO
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default AdminHOC(async function(render) {
|
const triggers = [
|
||||||
const id = new URLSearchParams(location.search).get("id");
|
{
|
||||||
|
name: "schedule",
|
||||||
|
title: "On a Schedule",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M528 320C528 434.9 434.9 528 320 528C205.1 528 112 434.9 112 320C112 205.1 205.1 112 320 112C434.9 112 528 205.1 528 320zM64 320C64 461.4 178.6 576 320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320zM296 184L296 320C296 328 300 335.5 306.7 340L402.7 404C413.7 411.4 428.6 408.4 436 397.3C443.4 386.2 440.4 371.4 429.3 364L344 307.2L344 184C344 170.7 333.3 160 320 160C306.7 160 296 170.7 296 184z"></path></svg>`,
|
||||||
|
specs: {
|
||||||
|
"start": {
|
||||||
|
name: "test",
|
||||||
|
type: "datetime",
|
||||||
|
},
|
||||||
|
"frequency": {
|
||||||
|
type: "select",
|
||||||
|
options: ["hourly", "weekly", "monthly", "yearly"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user",
|
||||||
|
title: "When a User Creates a Request",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M240 192C240 147.8 275.8 112 320 112C364.2 112 400 147.8 400 192C400 236.2 364.2 272 320 272C275.8 272 240 236.2 240 192zM448 192C448 121.3 390.7 64 320 64C249.3 64 192 121.3 192 192C192 262.7 249.3 320 320 320C390.7 320 448 262.7 448 192zM144 544C144 473.3 201.3 416 272 416L368 416C438.7 416 496 473.3 496 544L496 552C496 565.3 506.7 576 520 576C533.3 576 544 565.3 544 552L544 544C544 446.8 465.2 368 368 368L272 368C174.8 368 96 446.8 96 544L96 552C96 565.3 106.7 576 120 576C133.3 576 144 565.3 144 552L144 544z"/></svg>`,
|
||||||
|
specs: {
|
||||||
|
name: { type: "text" },
|
||||||
|
form: { type: "text", placeholder: "Optional form user should submit" },
|
||||||
|
visibility: { type: "text" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "operation",
|
||||||
|
title: "When a File Action Happens",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M192 64C156.7 64 128 92.7 128 128L128 368L310.1 368L279.1 337C269.7 327.6 269.7 312.4 279.1 303.1C288.5 293.8 303.7 293.7 313 303.1L385 375.1C394.4 384.5 394.4 399.7 385 409L313 481C303.6 490.4 288.4 490.4 279.1 481C269.8 471.6 269.7 456.4 279.1 447.1L310.1 416.1L128 416.1L128 512.1C128 547.4 156.7 576.1 192 576.1L448 576.1C483.3 576.1 512 547.4 512 512.1L512 234.6C512 217.6 505.3 201.3 493.3 189.3L386.7 82.7C374.7 70.7 358.5 64 341.5 64L192 64zM453.5 240L360 240C346.7 240 336 229.3 336 216L336 122.5L453.5 240z"/></svg>`,
|
||||||
|
specs: {
|
||||||
|
event: {
|
||||||
|
type: "text",
|
||||||
|
datalist: ["ls", "cat", "mkdir", "mv", "rm", "touch"],
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "watch",
|
||||||
|
title: "When the Filesystem Changes",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M128 64C92.7 64 64 92.7 64 128L64 512C64 547.3 92.7 576 128 576L308 576C285.3 544.5 272 505.8 272 464C272 363.4 349.4 280.8 448 272.7L448 234.6C448 217.6 441.3 201.3 429.3 189.3L322.7 82.7C310.7 70.7 294.5 64 277.5 64L128 64zM389.5 240L296 240C282.7 240 272 229.3 272 216L272 122.5L389.5 240zM464 608C543.5 608 608 543.5 608 464C608 384.5 543.5 320 464 320C384.5 320 320 384.5 320 464C320 543.5 384.5 608 464 608zM480 400L480 448L528 448C536.8 448 544 455.2 544 464C544 472.8 536.8 480 528 480L480 480L480 528C480 536.8 472.8 544 464 544C455.2 544 448 536.8 448 528L448 480L400 480C391.2 480 384 472.8 384 464C384 455.2 391.2 448 400 448L448 448L448 400C448 391.2 455.2 384 464 384C472.8 384 480 391.2 480 400z"/></svg>`,
|
||||||
|
specs: {
|
||||||
|
token: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
path: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "webhook",
|
||||||
|
title: "From a webhook",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M392.8 65.2C375.8 60.3 358.1 70.2 353.2 87.2L225.2 535.2C220.3 552.2 230.2 569.9 247.2 574.8C264.2 579.7 281.9 569.8 286.8 552.8L414.8 104.8C419.7 87.8 409.8 70.1 392.8 65.2zM457.4 201.3C444.9 213.8 444.9 234.1 457.4 246.6L530.8 320L457.4 393.4C444.9 405.9 444.9 426.2 457.4 438.7C469.9 451.2 490.2 451.2 502.7 438.7L598.7 342.7C611.2 330.2 611.2 309.9 598.7 297.4L502.7 201.4C490.2 188.9 469.9 188.9 457.4 201.4zM182.7 201.3C170.2 188.8 149.9 188.8 137.4 201.3L41.4 297.3C28.9 309.8 28.9 330.1 41.4 342.6L137.4 438.6C149.9 451.1 170.2 451.1 182.7 438.6C195.2 426.1 195.2 405.8 182.7 393.3L109.3 320L182.6 246.6C195.1 234.1 195.1 213.8 182.6 201.3z"/></svg>`,
|
||||||
|
specs: {
|
||||||
|
url: {
|
||||||
|
type: "text",
|
||||||
|
readonly: true,
|
||||||
|
value: "http://example.com/workflow/webhook?id=generatedID",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
name: "run/program",
|
||||||
|
title: "Execute Program",
|
||||||
|
subtitle: "name",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M96 160C96 124.7 124.7 96 160 96L480 96C515.3 96 544 124.7 544 160L544 480C544 515.3 515.3 544 480 544L160 544C124.7 544 96 515.3 96 480L96 160zM240 164C215.7 164 196 183.7 196 208L196 256C196 280.3 215.7 300 240 300L272 300C296.3 300 316 280.3 316 256L316 208C316 183.7 296.3 164 272 164L240 164zM236 208C236 205.8 237.8 204 240 204L272 204C274.2 204 276 205.8 276 208L276 256C276 258.2 274.2 260 272 260L240 260C237.8 260 236 258.2 236 256L236 208zM376 164C365 164 356 173 356 184C356 193.7 362.9 201.7 372 203.6L372 280C372 291 381 300 392 300C403 300 412 291 412 280L412 184C412 173 403 164 392 164L376 164zM228 360C228 369.7 234.9 377.7 244 379.6L244 456C244 467 253 476 264 476C275 476 284 467 284 456L284 360C284 349 275 340 264 340L248 340C237 340 228 349 228 360zM324 384L324 432C324 456.3 343.7 476 368 476L400 476C424.3 476 444 456.3 444 432L444 384C444 359.7 424.3 340 400 340L368 340C343.7 340 324 359.7 324 384zM368 380L400 380C402.2 380 404 381.8 404 384L404 432C404 434.2 402.2 436 400 436L368 436C365.8 436 364 434.2 364 432L364 384C364 381.8 365.8 380 368 380z"></path></svg>`,
|
||||||
|
specs: {
|
||||||
|
name: {
|
||||||
|
type: "select",
|
||||||
|
options: ["ocr", "duplicate", "expiry", "sign"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run/api",
|
||||||
|
title: "Make API Call",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M96 160C96 124.7 124.7 96 160 96L480 96C515.3 96 544 124.7 544 160L544 480C544 515.3 515.3 544 480 544L160 544C124.7 544 96 515.3 96 480L96 160zM240 164C215.7 164 196 183.7 196 208L196 256C196 280.3 215.7 300 240 300L272 300C296.3 300 316 280.3 316 256L316 208C316 183.7 296.3 164 272 164L240 164zM236 208C236 205.8 237.8 204 240 204L272 204C274.2 204 276 205.8 276 208L276 256C276 258.2 274.2 260 272 260L240 260C237.8 260 236 258.2 236 256L236 208zM376 164C365 164 356 173 356 184C356 193.7 362.9 201.7 372 203.6L372 280C372 291 381 300 392 300C403 300 412 291 412 280L412 184C412 173 403 164 392 164L376 164zM228 360C228 369.7 234.9 377.7 244 379.6L244 456C244 467 253 476 264 476C275 476 284 467 284 456L284 360C284 349 275 340 264 340L248 340C237 340 228 349 228 360zM324 384L324 432C324 456.3 343.7 476 368 476L400 476C424.3 476 444 456.3 444 432L444 384C444 359.7 424.3 340 400 340L368 340C343.7 340 324 359.7 324 384zM368 380L400 380C402.2 380 404 381.8 404 384L404 432C404 434.2 402.2 436 400 436L368 436C365.8 436 364 434.2 364 432L364 384C364 381.8 365.8 380 368 380z"></path></svg>`,
|
||||||
|
specs: {
|
||||||
|
url: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
method: {
|
||||||
|
options: ["POST", "PUT", "GET"],
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
type: "long_text",
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
type: "long_text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "notify/email",
|
||||||
|
title: "Notify",
|
||||||
|
subtitle: "email",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M112 128C85.5 128 64 149.5 64 176C64 191.1 71.1 205.3 83.2 214.4L291.2 370.4C308.3 383.2 331.7 383.2 348.8 370.4L556.8 214.4C568.9 205.3 576 191.1 576 176C576 149.5 554.5 128 528 128L112 128zM64 260L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 260L377.6 408.8C343.5 434.4 296.5 434.4 262.4 408.8L64 260z"></path></svg>`,
|
||||||
|
specs: {
|
||||||
|
email: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: "long_text",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approval/email",
|
||||||
|
title: "Approval",
|
||||||
|
subtitle: "email",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M112 128C85.5 128 64 149.5 64 176C64 191.1 71.1 205.3 83.2 214.4L291.2 370.4C308.3 383.2 331.7 383.2 348.8 370.4L556.8 214.4C568.9 205.3 576 191.1 576 176C576 149.5 554.5 128 528 128L112 128zM64 260L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 260L377.6 408.8C343.5 434.4 296.5 434.4 262.4 408.8L64 260z"></path></svg>`,
|
||||||
|
specs: {
|
||||||
|
email: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: "long_text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tools/debug",
|
||||||
|
title: "Debug",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M102.8 57.3C108.2 51.9 116.6 51.1 123 55.3L241.9 134.5C250.8 140.4 256.1 150.4 256.1 161.1L256.1 210.7L346.9 301.5C380.2 286.5 420.8 292.6 448.1 320L574.2 446.1C592.9 464.8 592.9 495.2 574.2 514L514.1 574.1C495.4 592.8 465 592.8 446.2 574.1L320.1 448C292.7 420.6 286.6 380.1 301.6 346.8L210.8 256L161.2 256C150.5 256 140.5 250.7 134.6 241.8L55.4 122.9C51.2 116.6 52 108.1 57.4 102.7L102.8 57.3zM247.8 360.8C241.5 397.7 250.1 436.7 274 468L179.1 563C151 591.1 105.4 591.1 77.3 563C49.2 534.9 49.2 489.3 77.3 461.2L212.7 325.7L247.9 360.8zM416.1 64C436.2 64 455.5 67.7 473.2 74.5C483.2 78.3 485 91 477.5 98.6L420.8 155.3C417.8 158.3 416.1 162.4 416.1 166.6L416.1 208C416.1 216.8 423.3 224 432.1 224L473.5 224C477.7 224 481.8 222.3 484.8 219.3L541.5 162.6C549.1 155.1 561.8 156.9 565.6 166.9C572.4 184.6 576.1 203.9 576.1 224C576.1 267.2 558.9 306.3 531.1 335.1L482 286C448.9 253 403.5 240.3 360.9 247.6L304.1 190.8L304.1 161.1L303.9 156.1C303.1 143.7 299.5 131.8 293.4 121.2C322.8 86.2 366.8 64 416.1 63.9z"/></svg>`,
|
||||||
|
specs: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tools/map",
|
||||||
|
title: "Map",
|
||||||
|
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.0.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M102.8 57.3C108.2 51.9 116.6 51.1 123 55.3L241.9 134.5C250.8 140.4 256.1 150.4 256.1 161.1L256.1 210.7L346.9 301.5C380.2 286.5 420.8 292.6 448.1 320L574.2 446.1C592.9 464.8 592.9 495.2 574.2 514L514.1 574.1C495.4 592.8 465 592.8 446.2 574.1L320.1 448C292.7 420.6 286.6 380.1 301.6 346.8L210.8 256L161.2 256C150.5 256 140.5 250.7 134.6 241.8L55.4 122.9C51.2 116.6 52 108.1 57.4 102.7L102.8 57.3zM247.8 360.8C241.5 397.7 250.1 436.7 274 468L179.1 563C151 591.1 105.4 591.1 77.3 563C49.2 534.9 49.2 489.3 77.3 461.2L212.7 325.7L247.9 360.8zM416.1 64C436.2 64 455.5 67.7 473.2 74.5C483.2 78.3 485 91 477.5 98.6L420.8 155.3C417.8 158.3 416.1 162.4 416.1 166.6L416.1 208C416.1 216.8 423.3 224 432.1 224L473.5 224C477.7 224 481.8 222.3 484.8 219.3L541.5 162.6C549.1 155.1 561.8 156.9 565.6 166.9C572.4 184.6 576.1 203.9 576.1 224C576.1 267.2 558.9 306.3 531.1 335.1L482 286C448.9 253 403.5 240.3 360.9 247.6L304.1 190.8L304.1 161.1L303.9 156.1C303.1 143.7 299.5 131.8 293.4 121.2C322.8 86.2 366.8 64 416.1 63.9z"/></svg>`,
|
||||||
|
specs: {
|
||||||
|
transform: {
|
||||||
|
type: "long_text",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// TODO: expand macros
|
||||||
|
];
|
||||||
|
|
||||||
|
const macros = [
|
||||||
|
{
|
||||||
|
name: "files/mv",
|
||||||
|
specs: [
|
||||||
|
{ name: "from", type: "text" },
|
||||||
|
{ name: "to", type: "text" },
|
||||||
|
],
|
||||||
|
run: [
|
||||||
|
{
|
||||||
|
name: "run/api",
|
||||||
|
values: [
|
||||||
|
{ name: "url", value: "{{ .endpoint }}/api/files/mv?from={{ .from }}&to={{ .to }}" },
|
||||||
|
{ name: "method", value: "POST" },
|
||||||
|
{ name: "headers", value: "Authorization: Bearer {{ .authorization }}" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: "metadata/add",
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
|
||||||
|
export default AdminHOC(async function(render) {
|
||||||
await loadCSS(import.meta.url, "./ctrl_workflow.css");
|
await loadCSS(import.meta.url, "./ctrl_workflow.css");
|
||||||
render(createElement("<component-loader inlined></component-loader>"));
|
render(createElement("<component-loader inlined></component-loader>"));
|
||||||
await new Promise((done) => setTimeout(() => done(), 100));
|
|
||||||
|
|
||||||
if (id) ctrlDetails(render, mockWorkflows[0]);
|
const specs = getSpecs();
|
||||||
else ctrlList(render, mockWorkflows);
|
if (specs) ctrlDetails(render, { workflow: specs, triggers, actions });
|
||||||
|
else {
|
||||||
|
await new Promise((done) => setTimeout(() => done(), 100));
|
||||||
|
ctrlList(render, { workflows, triggers, actions });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getSpecs() {
|
||||||
|
const GET = new URLSearchParams(location.search)
|
||||||
|
try {
|
||||||
|
return JSON.parse(atob(GET.get("specs")));
|
||||||
|
} catch (err ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import { createElement } from "../../lib/skeleton/index.js";
|
import { createElement, createFragment } from "../../lib/skeleton/index.js";
|
||||||
|
import { animate, slideXIn, slideXOut } from "../../lib/animate.js";
|
||||||
|
import { qs, qsa } from "../../lib/dom.js";
|
||||||
|
import { createForm, mutateForm } from "../../lib/form.js";
|
||||||
|
import { formTmpl } from "../../components/form.js";
|
||||||
|
|
||||||
|
import { renderLeaf, useForm$, formObjToJSON$ } from "./helper_form.js";
|
||||||
import transition from "./animate.js";
|
import transition from "./animate.js";
|
||||||
|
|
||||||
export default async function(render, { name }) {
|
// TODO: auto id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
||||||
|
|
||||||
|
export default async function(render, { workflow, triggers, actions }) {
|
||||||
const $page = createElement(`
|
const $page = createElement(`
|
||||||
<div class="component_page_workflow">
|
<div class="component_page_workflow">
|
||||||
<h2 class="ellipsis">
|
<h2 class="ellipsis">
|
||||||
|
|
@ -11,42 +18,204 @@ export default async function(render, { name }) {
|
||||||
<path d="M169.4 297.4C156.9 309.9 156.9 330.2 169.4 342.7L361.4 534.7C373.9 547.2 394.2 547.2 406.7 534.7C419.2 522.2 419.2 501.9 406.7 489.4L237.3 320L406.6 150.6C419.1 138.1 419.1 117.8 406.6 105.3C394.1 92.8 373.8 92.8 361.3 105.3L169.3 297.3z"/>
|
<path d="M169.4 297.4C156.9 309.9 156.9 330.2 169.4 342.7L361.4 534.7C373.9 547.2 394.2 547.2 406.7 534.7C419.2 522.2 419.2 501.9 406.7 489.4L237.3 320L406.6 150.6C419.1 138.1 419.1 117.8 406.6 105.3C394.1 92.8 373.8 92.8 361.3 105.3L169.3 297.3z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
${name}
|
${workflow.name}
|
||||||
</h2>
|
</h2>
|
||||||
<div data-bind="workflow">
|
|
||||||
<div class="box status-unpublished">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M528 320C528 434.9 434.9 528 320 528C205.1 528 112 434.9 112 320C112 205.1 205.1 112 320 112C434.9 112 528 205.1 528 320zM64 320C64 461.4 178.6 576 320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320zM296 184L296 320C296 328 300 335.5 306.7 340L402.7 404C413.7 411.4 428.6 408.4 436 397.3C443.4 386.2 440.4 371.4 429.3 364L344 307.2L344 184C344 170.7 333.3 160 320 160C306.7 160 296 170.7 296 184z"/></svg>
|
|
||||||
<h3 class="ellipsis no-select">On a Schedule <span>(every day)</span></h3>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="box status-published">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M96 160C96 124.7 124.7 96 160 96L480 96C515.3 96 544 124.7 544 160L544 480C544 515.3 515.3 544 480 544L160 544C124.7 544 96 515.3 96 480L96 160zM240 164C215.7 164 196 183.7 196 208L196 256C196 280.3 215.7 300 240 300L272 300C296.3 300 316 280.3 316 256L316 208C316 183.7 296.3 164 272 164L240 164zM236 208C236 205.8 237.8 204 240 204L272 204C274.2 204 276 205.8 276 208L276 256C276 258.2 274.2 260 272 260L240 260C237.8 260 236 258.2 236 256L236 208zM376 164C365 164 356 173 356 184C356 193.7 362.9 201.7 372 203.6L372 280C372 291 381 300 392 300C403 300 412 291 412 280L412 184C412 173 403 164 392 164L376 164zM228 360C228 369.7 234.9 377.7 244 379.6L244 456C244 467 253 476 264 476C275 476 284 467 284 456L284 360C284 349 275 340 264 340L248 340C237 340 228 349 228 360zM324 384L324 432C324 456.3 343.7 476 368 476L400 476C424.3 476 444 456.3 444 432L444 384C444 359.7 424.3 340 400 340L368 340C343.7 340 324 359.7 324 384zM368 380L400 380C402.2 380 404 381.8 404 384L404 432C404 434.2 402.2 436 400 436L368 436C365.8 436 364 434.2 364 432L364 384C364 381.8 365.8 380 368 380z"/></svg>
|
|
||||||
<h3 class="ellipsis">Execute Program <span>(duplicate)</span></h3>
|
|
||||||
|
|
||||||
<div style="margin-top: 10px;padding: 10px 0 0 0; border-top: 2px solid rgba(0, 0, 0, 0.1);">
|
<div data-bind="trigger"></div>
|
||||||
<div class="flex">
|
<div data-bind="actions"></div>
|
||||||
<span class="ellipsis">Frequency:</span>
|
<div data-bind="add"></div>
|
||||||
<div style="width:100%;"><select class="component_select" name="params.level" id="log_level"><option name="DEBUG" selected="">DEBUG</option><option name="INFO">INFO</option><option name="WARNING">WARNING</option><option name="ERROR">ERROR</option></select></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex">
|
|
||||||
<span class="ellipsis">Frequency:</span>
|
|
||||||
<div style="width:100%;"><select class="component_select" name="params.level" id="log_level"><option name="DEBUG" selected="">DEBUG</option><option name="INFO">INFO</option><option name="WARNING">WARNING</option><option name="ERROR">ERROR</option></select></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<h2 class="ellipsis hidden">History</h2>
|
||||||
<hr>
|
<style>.component_page_admin .page_container h2:after { display: none; }</style>
|
||||||
<div class="box status-published">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M112 128C85.5 128 64 149.5 64 176C64 191.1 71.1 205.3 83.2 214.4L291.2 370.4C308.3 383.2 331.7 383.2 348.8 370.4L556.8 214.4C568.9 205.3 576 191.1 576 176C576 149.5 554.5 128 528 128L112 128zM64 260L64 448C64 483.3 92.7 512 128 512L512 512C547.3 512 576 483.3 576 448L576 260L377.6 408.8C343.5 434.4 296.5 434.4 262.4 408.8L64 260z"/></svg>
|
|
||||||
<h3 class="ellipsis">Notify <span>(john@example.com)</span></h3>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<button class="box">+ Add step</button>
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
.component_page_admin .page_container h2:after { display: none; }
|
|
||||||
</style>
|
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
render(transition($page));
|
render(transition($page));
|
||||||
|
|
||||||
|
// feature1: setup trigger
|
||||||
|
const $trigger = qs($page, `[data-bind="trigger"]`);
|
||||||
|
$trigger.appendChild(await createTrigger({ workflow, triggers }));
|
||||||
|
|
||||||
|
// feature2: setup actions
|
||||||
|
const $actions = qs($page, `[data-bind="actions"]`);
|
||||||
|
for (let i=0; i<workflow.actions.length; i++) {
|
||||||
|
$actions.appendChild(await createAction({ action: workflow.actions[i], actions }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// feature3: add a step
|
||||||
|
const $add = qs($page, `[data-bind="add"]`);
|
||||||
|
$add.appendChild(await createAdd({
|
||||||
|
workflow,
|
||||||
|
actions,
|
||||||
|
createAction: async ({ action, actions }) => {
|
||||||
|
const $action = await createAction({ action, actions });
|
||||||
|
qs($action, `button[alt="delete"]`).onclick = (e) => removeAction(e.target);
|
||||||
|
$actions.appendChild($action);
|
||||||
|
withToggle(qs($action, `[data-bind="form"]`));
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// feature4: save button
|
||||||
|
$page.parentElement.appendChild(createSave());
|
||||||
|
|
||||||
|
// feature5: toggle form visibility
|
||||||
|
qsa($page, `[data-bind="form"]`).forEach(($form) => {
|
||||||
|
withToggle($form);
|
||||||
|
if (workflow.id) $form.classList.add("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
// feature6: remove button
|
||||||
|
qsa($page, `button[alt="delete"]`).forEach(($delete) => $delete.onclick = (e) => {
|
||||||
|
removeAction(e.target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTrigger({ workflow, triggers }) {
|
||||||
|
const trigger = triggers.find(({ name }) => name === workflow.trigger.name);
|
||||||
|
if (!trigger) return createElement(`<div class="box disabled">Trigger not found "${workflow.trigger.name}"</div>`);
|
||||||
|
const { title, icon } = trigger;
|
||||||
|
const $trigger = createFragment(`
|
||||||
|
<div class="box disabled">
|
||||||
|
${icon}
|
||||||
|
<h3 class="ellipsis no-select">
|
||||||
|
${title}
|
||||||
|
<button alt="configure" class="pull-right"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg></button>
|
||||||
|
</h3>
|
||||||
|
<div data-bind="form"></div>
|
||||||
|
</div><hr>
|
||||||
|
`);
|
||||||
|
const $form = await createForm(trigger.specs, formTmpl());
|
||||||
|
qs($trigger, `[data-bind="form"]`).appendChild($form);
|
||||||
|
return $trigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAction({ action, actions }) {
|
||||||
|
const selected = actions.find((_action) => _action.name === action.name);
|
||||||
|
if (!selected) return createElement(`<div class="box disabled">Action not found "${action.name}"</div>`);
|
||||||
|
const subtitle = selected.subtitle ? `({{ ${action.subtitle} }})` : "";
|
||||||
|
const $action = createElement(`
|
||||||
|
<div>
|
||||||
|
<div class="box">
|
||||||
|
${selected.icon}
|
||||||
|
<h3 class="ellipsis no-select">
|
||||||
|
${selected.title} <span>${subtitle}</span>
|
||||||
|
<button alt="delete" class="pull-right"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M183.1 137.4C170.6 124.9 150.3 124.9 137.8 137.4C125.3 149.9 125.3 170.2 137.8 182.7L275.2 320L137.9 457.4C125.4 469.9 125.4 490.2 137.9 502.7C150.4 515.2 170.7 515.2 183.2 502.7L320.5 365.3L457.9 502.6C470.4 515.1 490.7 515.1 503.2 502.6C515.7 490.1 515.7 469.8 503.2 457.3L365.8 320L503.1 182.6C515.6 170.1 515.6 149.8 503.1 137.3C490.6 124.8 470.3 124.8 457.8 137.3L320.5 274.7L183.1 137.4z"/></svg></button>
|
||||||
|
<button alt="configure" class="pull-right ${Object.keys(selected.specs).length === 0 ? "hidden": ""}"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg></button>
|
||||||
|
</h3>
|
||||||
|
<div data-bind="form"></div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
const $form = await createForm(selected.specs, formTmpl());
|
||||||
|
qs($action, `[data-bind="form"]`).appendChild($form);
|
||||||
|
return $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeAction($target) {
|
||||||
|
const $box = $target.closest(".box");
|
||||||
|
await animate($box, {
|
||||||
|
time: 150,
|
||||||
|
keyframes: slideXOut(10),
|
||||||
|
});
|
||||||
|
$box.parentElement.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAdd({ actions, createAction }) {
|
||||||
|
const $el = createElement(`
|
||||||
|
<div class="box">
|
||||||
|
<button class="item" title="add">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
|
||||||
|
<path d="M352 128C352 110.3 337.7 96 320 96C302.3 96 288 110.3 288 128L288 288L128 288C110.3 288 96 302.3 96 320C96 337.7 110.3 352 128 352L288 352L288 512C288 529.7 302.3 544 320 544C337.7 544 352 529.7 352 512L352 352L512 352C529.7 352 544 337.7 544 320C544 302.3 529.7 288 512 288L352 288L352 128z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
const categories = actions.reduce((acc, { name }) => {
|
||||||
|
const s = name.split("/");
|
||||||
|
if (!acc[s[0]]) acc[s[0]] = [];
|
||||||
|
acc[s[0]].push(name);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const $categories = createElement(`
|
||||||
|
<div class="hidden flex">
|
||||||
|
`+ Object.keys(categories).map((category) => `
|
||||||
|
<button class="item">${category}</button>
|
||||||
|
`+categories[category].map((subcategory) => `
|
||||||
|
<button class="sub" style="background:var(--border)" data-name="${subcategory}">${subcategory.split("/")[1]}</button>
|
||||||
|
`).join("")+`
|
||||||
|
`).join("")+`
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
const $item = qs($el, ".item");
|
||||||
|
const width = 45;
|
||||||
|
let rotate = 0;
|
||||||
|
$el.appendChild($categories);
|
||||||
|
$item.onclick = async (e) => {
|
||||||
|
rotate += 45;
|
||||||
|
$item.firstElementChild.style.transform = `rotate(${rotate}deg)`;
|
||||||
|
if (rotate % 90 === 0) {
|
||||||
|
$categories.classList.add("hidden");
|
||||||
|
await animate($el, {
|
||||||
|
time: 150,
|
||||||
|
keyframes: [
|
||||||
|
{ width: "300px" },
|
||||||
|
{ width: `${width}px` },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await animate($el, {
|
||||||
|
time: 150,
|
||||||
|
keyframes: [
|
||||||
|
{ width: `${width}px` },
|
||||||
|
{ width: "300px" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
$categories.classList.remove("hidden");
|
||||||
|
animate($categories, { time: 80, keyframes: slideXIn(-20) });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
qsa($el, ".sub").forEach(($action) => $action.onclick = () => {
|
||||||
|
const action = actions.find(({ name }) => name === $action.getAttribute("data-name"));
|
||||||
|
if (action) createAction({ action, actions });
|
||||||
|
$item.onclick();
|
||||||
|
});
|
||||||
|
return $el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSave() {
|
||||||
|
const $fab = createElement(`
|
||||||
|
<div class="workflow-fab flex no-select">
|
||||||
|
<select>
|
||||||
|
<option>publish</option>
|
||||||
|
<option>unpublish</option>
|
||||||
|
</select>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
|
||||||
|
<path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
animate($fab, { time: 100, keyframes: slideXIn(5) });
|
||||||
|
return $fab;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withToggle($form) {
|
||||||
|
const height = $form.clientHeight;
|
||||||
|
const $box = $form.closest(".box");
|
||||||
|
qs($box, `h3 button[alt="configure"]`).onclick = () => {
|
||||||
|
const shouldOpen = $form.classList.contains("hidden");
|
||||||
|
if (shouldOpen) {
|
||||||
|
animate($form, {
|
||||||
|
time: 120,
|
||||||
|
keyframes: [{ height: "0px" }, { height: `${height}px` }],
|
||||||
|
});
|
||||||
|
$form.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
animate($form, {
|
||||||
|
time: 80,
|
||||||
|
keyframes: [{ height: `${height}px` }, { height: "0px" }],
|
||||||
|
onExit: () => $form.classList.add("hidden"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,100 @@
|
||||||
import { createElement } from "../../lib/skeleton/index.js";
|
import { createElement } from "../../lib/skeleton/index.js";
|
||||||
import rxjs, { effect, onClick } from "../../lib/rx.js";
|
import rxjs, { effect, onClick } from "../../lib/rx.js";
|
||||||
import { qs } from "../../lib/dom.js";
|
import { qs } from "../../lib/dom.js";
|
||||||
|
import { animate } from "../../lib/animate.js";
|
||||||
import { createModal } from "../../components/modal.js";
|
import { createModal } from "../../components/modal.js";
|
||||||
|
import { generateSkeleton } from "../../components/skeleton.js";
|
||||||
|
import t from "../../locales/index.js";
|
||||||
|
|
||||||
import transition from "./animate.js";
|
import transition from "./animate.js";
|
||||||
|
|
||||||
export default async function(render, workflows) {
|
export default async function(render, { workflows, triggers }) {
|
||||||
const $page = createElement(`
|
const $page = createElement(`
|
||||||
<div class="component_page_workflow">
|
<div class="component_page_workflow">
|
||||||
<h2>
|
<h2>
|
||||||
Workflows
|
Workflows
|
||||||
<a class="pull-right pointer no-select">+</a>
|
<a class="pull-right pointer no-select">+</a>
|
||||||
</h2>
|
</h2>
|
||||||
<div data-bind="workflows"><Loader /></div>
|
<div data-bind="workflows"></div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
render(transition($page));
|
render(transition($page));
|
||||||
|
|
||||||
workflows.forEach((workflow) => qs($page, `[data-bind="workflows"]`).appendChild(createWorkflow(workflow)));
|
workflows.forEach((workflow) => qs($page, `[data-bind="workflows"]`).appendChild(createWorkflow(workflow)));
|
||||||
|
|
||||||
effect(onClick(qs($page, "h2 > a")).pipe(
|
effect(onClick(qs($page, "h2 > a")).pipe(
|
||||||
rxjs.tap((a) => createModal({ withButtonsRight: "Create", withButtonsLeft: "Cancel" })(createElement(`
|
rxjs.tap((a) => ctrlModal(createModal(), { triggers })),
|
||||||
<div>
|
|
||||||
<input type="component_input" placeholder="worklow name" />
|
|
||||||
</div>
|
|
||||||
`))),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWorkflow({ id, name, status }) {
|
function createWorkflow(specs) {
|
||||||
|
const { name, published, actions, trigger } = specs;
|
||||||
|
const summaryHTML = {
|
||||||
|
trigger: `<button class="light">${trigger.name}</button>`,
|
||||||
|
actions: actions.map(({ name }) => `<button class="light">${name.split("/")[0]}</button>`).join(""),
|
||||||
|
};
|
||||||
const $workflow = createElement(`
|
const $workflow = createElement(`
|
||||||
<a href="./admin/workflow?id=${id}" class="box status-${status}" data-link>
|
<a href="./admin/workflow?specs=${btoa(JSON.stringify(specs))}" class="box ${published ? "" : "disabled"}" data-link>
|
||||||
<h3 class="ellipsis">${name} <span>(2 weeks ago)</span></h3>
|
<h3 class="ellipsis">${name} <span>(2 weeks ago)</span></h3>
|
||||||
<div class="workflow-summary">
|
<div class="workflow-summary">
|
||||||
<button class="light">Schedule</button> → <button class="light">execute</button><button class="light">notify</button>
|
${summaryHTML.trigger} → ${summaryHTML.actions}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
`);
|
`);
|
||||||
return $workflow;
|
return $workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ctrlModal(render, { triggers }) {
|
||||||
|
const $page = createElement(`
|
||||||
|
<div class="component_workflow_create">
|
||||||
|
<form>
|
||||||
|
<h2>Step1: Name the Workflow</h2>
|
||||||
|
<input name="tag" type="text" placeholder="${t("Workflow Name")}" value="">
|
||||||
|
<div data-bind="list">
|
||||||
|
${generateSkeleton(1)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
render($page);
|
||||||
|
|
||||||
|
const $list = qs($page, `[data-bind="list"]`);
|
||||||
|
const $input = qs($page, "input");
|
||||||
|
effect(rxjs.of(triggers).pipe(
|
||||||
|
rxjs.map((arr) => arr.map(({ name, title, icon }) => createElement(`
|
||||||
|
<a class="item flex no-select ellipsis" data-name="${name}">${icon} ${title}</a>
|
||||||
|
`))),
|
||||||
|
rxjs.map(($els) => {
|
||||||
|
$list.innerHTML = "";
|
||||||
|
$input.focus();
|
||||||
|
const $fragment = document.createDocumentFragment();
|
||||||
|
$fragment.appendChild(createElement(`<h2>Step2: Select a Trigger</h2>`));
|
||||||
|
$els.forEach(($el) => $fragment.appendChild($el));
|
||||||
|
$list.appendChild($fragment);
|
||||||
|
const height = $list.clientHeight;
|
||||||
|
$list.style.height = "0";
|
||||||
|
return { height, $els };
|
||||||
|
}),
|
||||||
|
rxjs.mergeMap(({ height, $els }) => rxjs.fromEvent($input, "keydown").pipe(
|
||||||
|
rxjs.debounceTime(200),
|
||||||
|
rxjs.tap((e) => {
|
||||||
|
const shouldOpen = e.target.value.length > 0;
|
||||||
|
if ($list.clientHeight === 0 && shouldOpen) animate($list, {
|
||||||
|
time: Math.max(50, Math.min(height, 150)),
|
||||||
|
keyframes: [{ height: "0" }, { height: `${height}px` }],
|
||||||
|
onExit: () => $list.style.height = "",
|
||||||
|
});
|
||||||
|
else if ($list.clientHeight > 0 && !shouldOpen) animate($list, {
|
||||||
|
time: Math.max(50, Math.min(height, 150)),
|
||||||
|
keyframes: [{ height: `${height}px` }, { height: "0" }],
|
||||||
|
onExit: () => $list.style.height = "0",
|
||||||
|
});
|
||||||
|
$els.forEach(($el) => $el.setAttribute("href", "./admin/workflow?specs="+btoa(JSON.stringify({
|
||||||
|
name: e.target.value,
|
||||||
|
published: false,
|
||||||
|
trigger: { name: $el.getAttribute("data-name") },
|
||||||
|
actions: [ { name: "tools/debug" }]
|
||||||
|
}))));
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue