undercity 1.0.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/AGENTS.md +26 -0
- package/README.md +58 -0
- package/actions/AGENTS.md +41 -0
- package/actions/_shared/container.js +16 -0
- package/actions/auth/ask-login/action.json +15 -0
- package/actions/auth/ask-signup/action.json +14 -0
- package/actions/display/clear/action.json +18 -0
- package/actions/display/clear/action.test.js +32 -0
- package/actions/display/markdown/action.json +12 -0
- package/actions/display/markdown/action.test.js +32 -0
- package/actions/display/rawHtml/action.json +24 -0
- package/actions/display/rawHtml/action.test.js +32 -0
- package/actions/display/safeHtml/action.json +24 -0
- package/actions/display/safeHtml/action.test.js +32 -0
- package/actions/display/text/action.js +9 -0
- package/actions/display/text/action.json +12 -0
- package/actions/display/text/action.test.js +40 -0
- package/actions/display/value/action.json +24 -0
- package/actions/display/value/action.test.js +32 -0
- package/actions/dom/addClass/action.json +23 -0
- package/actions/dom/addClass/action.test.js +32 -0
- package/actions/dom/focus/action.json +17 -0
- package/actions/dom/focus/action.test.js +32 -0
- package/actions/dom/hide/action.json +18 -0
- package/actions/dom/hide/action.test.js +32 -0
- package/actions/dom/removeClass/action.json +22 -0
- package/actions/dom/removeClass/action.test.js +32 -0
- package/actions/dom/scroll/action.json +29 -0
- package/actions/dom/scroll/action.test.js +32 -0
- package/actions/dom/setAttr/action.json +29 -0
- package/actions/dom/setAttr/action.test.js +32 -0
- package/actions/dom/setHtml/action.json +22 -0
- package/actions/dom/setHtml/action.test.js +32 -0
- package/actions/dom/setStyle/action.json +29 -0
- package/actions/dom/setStyle/action.test.js +32 -0
- package/actions/dom/setText/action.json +24 -0
- package/actions/dom/setText/action.test.js +32 -0
- package/actions/dom/show/action.json +18 -0
- package/actions/dom/show/action.test.js +32 -0
- package/actions/dom/toggle/action.json +17 -0
- package/actions/dom/toggle/action.test.js +32 -0
- package/actions/dom/toggleClass/action.json +22 -0
- package/actions/dom/toggleClass/action.test.js +32 -0
- package/actions/event/emit/action.json +24 -0
- package/actions/event/emit/action.test.js +32 -0
- package/actions/event/on/action.json +23 -0
- package/actions/event/on/action.test.js +32 -0
- package/actions/event/waitFor/action.json +28 -0
- package/actions/event/waitFor/action.test.js +32 -0
- package/actions/forms/bindField/action.js +27 -0
- package/actions/forms/bindField/action.json +24 -0
- package/actions/forms/bindField/action.test.js +20 -0
- package/actions/forms/check/action.json +22 -0
- package/actions/forms/check/action.test.js +32 -0
- package/actions/forms/clearErrors/action.json +11 -0
- package/actions/forms/clearErrors/action.test.js +35 -0
- package/actions/forms/clearField/action.json +17 -0
- package/actions/forms/clearField/action.test.js +32 -0
- package/actions/forms/getField/action.json +24 -0
- package/actions/forms/getField/action.test.js +32 -0
- package/actions/forms/getRange/action.json +22 -0
- package/actions/forms/getRange/action.test.js +32 -0
- package/actions/forms/getSelect/action.json +22 -0
- package/actions/forms/getSelect/action.test.js +32 -0
- package/actions/forms/index.js +140 -0
- package/actions/forms/serialize/action.json +24 -0
- package/actions/forms/serialize/action.test.js +32 -0
- package/actions/forms/setCheck/action.json +23 -0
- package/actions/forms/setCheck/action.test.js +32 -0
- package/actions/forms/setError/action.json +22 -0
- package/actions/forms/setError/action.test.js +32 -0
- package/actions/forms/setField/action.js +14 -0
- package/actions/forms/setField/action.json +24 -0
- package/actions/forms/setField/action.test.js +32 -0
- package/actions/forms/submit/action.json +18 -0
- package/actions/forms/submit/action.test.js +32 -0
- package/actions/forms/validate/action.json +24 -0
- package/actions/forms/validate/action.test.js +32 -0
- package/actions/http/delete/action.json +22 -0
- package/actions/http/delete/action.test.js +32 -0
- package/actions/http/get/action.json +24 -0
- package/actions/http/get/action.test.js +32 -0
- package/actions/http/post/action.json +30 -0
- package/actions/http/post/action.test.js +32 -0
- package/actions/http/put/action.json +27 -0
- package/actions/http/put/action.test.js +32 -0
- package/actions/http/upload/action.json +35 -0
- package/actions/http/upload/action.test.js +32 -0
- package/actions/index.js +306 -0
- package/actions/index.json +5 -0
- package/actions/input/askChoice/action.json +29 -0
- package/actions/input/askChoice/action.test.js +32 -0
- package/actions/input/askConfirm/action.json +24 -0
- package/actions/input/askConfirm/action.test.js +32 -0
- package/actions/input/askDate/action.json +24 -0
- package/actions/input/askDate/action.test.js +32 -0
- package/actions/input/askEmail/action.json +24 -0
- package/actions/input/askEmail/action.test.js +32 -0
- package/actions/input/askNumber/action.json +35 -0
- package/actions/input/askNumber/action.test.js +32 -0
- package/actions/input/askPassword/action.json +24 -0
- package/actions/input/askPassword/action.test.js +32 -0
- package/actions/input/askText/action.json +36 -0
- package/actions/input/askText/action.test.js +32 -0
- package/actions/logic/delay/action.json +18 -0
- package/actions/logic/delay/action.test.js +32 -0
- package/actions/logic/if/action.json +28 -0
- package/actions/logic/if/action.test.js +32 -0
- package/actions/logic/log/action.json +18 -0
- package/actions/logic/log/action.test.js +32 -0
- package/actions/logic/random/action.json +36 -0
- package/actions/logic/random/action.test.js +32 -0
- package/actions/logic/transform/action.json +24 -0
- package/actions/logic/transform/action.test.js +32 -0
- package/actions/media/askAudioUpload/action.json +30 -0
- package/actions/media/askAudioUpload/action.test.js +32 -0
- package/actions/media/askFileUpload/action.json +36 -0
- package/actions/media/askFileUpload/action.test.js +32 -0
- package/actions/media/askImageUpload/action.json +37 -0
- package/actions/media/askImageUpload/action.test.js +32 -0
- package/actions/media/askVideoUpload/action.js +74 -0
- package/actions/media/askVideoUpload/action.json +44 -0
- package/actions/media/askVideoUpload/action.test.js +51 -0
- package/actions/media/captureWebcam/action.json +24 -0
- package/actions/media/captureWebcam/action.test.js +32 -0
- package/actions/nav/back/action.json +11 -0
- package/actions/nav/back/action.test.js +35 -0
- package/actions/nav/goto/action.json +18 -0
- package/actions/nav/goto/action.test.js +32 -0
- package/actions/nav/redirect/action.json +28 -0
- package/actions/nav/redirect/action.test.js +32 -0
- package/actions/nav/reload/action.json +11 -0
- package/actions/nav/reload/action.test.js +35 -0
- package/actions/nav/reset/action.json +11 -0
- package/actions/nav/reset/action.test.js +35 -0
- package/actions/render/alert/action.js +12 -0
- package/actions/render/alert/action.json +36 -0
- package/actions/render/alert/action.test.js +32 -0
- package/actions/render/button/action.js +15 -0
- package/actions/render/button/action.json +44 -0
- package/actions/render/button/action.test.js +37 -0
- package/actions/render/clear/action.js +7 -0
- package/actions/render/clear/action.json +11 -0
- package/actions/render/clear/action.test.js +35 -0
- package/actions/render/divider/action.js +8 -0
- package/actions/render/divider/action.json +11 -0
- package/actions/render/divider/action.test.js +35 -0
- package/actions/render/field/action.js +17 -0
- package/actions/render/field/action.json +59 -0
- package/actions/render/field/action.test.js +57 -0
- package/actions/render/link/action.js +20 -0
- package/actions/render/link/action.json +30 -0
- package/actions/render/link/action.test.js +32 -0
- package/actions/render/markdown/action.json +18 -0
- package/actions/render/markdown/action.test.js +32 -0
- package/actions/render/paragraph/action.js +9 -0
- package/actions/render/paragraph/action.json +32 -0
- package/actions/render/paragraph/action.test.js +32 -0
- package/actions/render/section/action.js +10 -0
- package/actions/render/section/action.json +18 -0
- package/actions/render/section/action.test.js +32 -0
- package/actions/render/subtitle/action.js +9 -0
- package/actions/render/subtitle/action.json +18 -0
- package/actions/render/subtitle/action.test.js +32 -0
- package/actions/render/title/action.js +9 -0
- package/actions/render/title/action.json +30 -0
- package/actions/render/title/action.test.js +44 -0
- package/actions/session/clear/action.json +11 -0
- package/actions/session/clear/action.test.js +35 -0
- package/actions/session/load/action.json +22 -0
- package/actions/session/load/action.test.js +32 -0
- package/actions/session/local/action.json +22 -0
- package/actions/session/local/action.test.js +32 -0
- package/actions/session/save/action.json +22 -0
- package/actions/session/save/action.test.js +32 -0
- package/actions/ui/accordion/action.json +23 -0
- package/actions/ui/accordion/action.test.js +32 -0
- package/actions/ui/badge/action.json +22 -0
- package/actions/ui/badge/action.test.js +32 -0
- package/actions/ui/collapse/action.json +23 -0
- package/actions/ui/collapse/action.test.js +32 -0
- package/actions/ui/hideModal/action.json +17 -0
- package/actions/ui/hideModal/action.test.js +32 -0
- package/actions/ui/loading/action.json +18 -0
- package/actions/ui/loading/action.test.js +32 -0
- package/actions/ui/modal/action.json +18 -0
- package/actions/ui/modal/action.test.js +32 -0
- package/actions/ui/progress/action.json +24 -0
- package/actions/ui/progress/action.test.js +32 -0
- package/actions/ui/toast/action.json +29 -0
- package/actions/ui/toast/action.test.js +32 -0
- package/actions/ui/tooltip/action.json +17 -0
- package/actions/ui/tooltip/action.test.js +32 -0
- package/actions/user/carry/action.json +25 -0
- package/actions/user/carry/action.test.js +32 -0
- package/actions/user/check/action.json +24 -0
- package/actions/user/check/action.test.js +32 -0
- package/actions/user/clear/action.json +11 -0
- package/actions/user/clear/action.test.js +35 -0
- package/actions/user/delete/action.json +17 -0
- package/actions/user/delete/action.test.js +32 -0
- package/actions/user/dump/action.json +11 -0
- package/actions/user/dump/action.test.js +35 -0
- package/actions/user/get/action.json +24 -0
- package/actions/user/get/action.test.js +32 -0
- package/actions/user/merge/action.json +18 -0
- package/actions/user/merge/action.test.js +32 -0
- package/actions/user/set/action.json +24 -0
- package/actions/user/set/action.test.js +32 -0
- package/generator/base/css/bootstrap.min.css +6 -0
- package/generator/base/icons/app-indicator.svg +4 -0
- package/generator/base/icons/backpack.svg +4 -0
- package/generator/base/icons/broadcast.svg +3 -0
- package/generator/base/icons/bullseye.svg +6 -0
- package/generator/base/icons/chat-dots.svg +4 -0
- package/generator/base/icons/check-circle.svg +4 -0
- package/generator/base/icons/clipboard-check.svg +5 -0
- package/generator/base/icons/clipboard.svg +4 -0
- package/generator/base/icons/copy.svg +3 -0
- package/generator/base/icons/cursor.svg +3 -0
- package/generator/base/icons/diamond.svg +3 -0
- package/generator/base/icons/exclamation-triangle.svg +4 -0
- package/generator/base/icons/film.svg +3 -0
- package/generator/base/icons/floppy.svg +4 -0
- package/generator/base/icons/gear-wide-connected.svg +3 -0
- package/generator/base/icons/gear.svg +4 -0
- package/generator/base/icons/globe.svg +3 -0
- package/generator/base/icons/image.svg +4 -0
- package/generator/base/icons/layout-text-window.svg +4 -0
- package/generator/base/icons/lightning-charge.svg +3 -0
- package/generator/base/icons/magic.svg +3 -0
- package/generator/base/icons/pencil-square.svg +4 -0
- package/generator/base/icons/record-circle.svg +4 -0
- package/generator/base/icons/robot.svg +4 -0
- package/generator/base/icons/shield-check.svg +4 -0
- package/generator/base/icons/shield-lock.svg +4 -0
- package/generator/base/icons/signpost.svg +3 -0
- package/generator/base/icons/stars.svg +3 -0
- package/generator/base/icons/type.svg +3 -0
- package/generator/base/js/bootstrap.bundle.min.js +7 -0
- package/package.json +14 -0
- package/packages/undercity-http-server/index.js +249 -0
- package/packages/undercity-http-server/package.json +10 -0
- package/packages/undercity-parser/index.js +323 -0
- package/packages/undercity-parser/lexer.js +128 -0
- package/packages/undercity-parser/package.json +11 -0
- package/plugins/forms.js +397 -0
- package/plugins/index.js +83 -0
- package/plugins/multipage.js +165 -0
- package/plugins/wizard.js +239 -0
- package/projects/asd/project.json +1031 -0
- package/projects/test-1/project.json +335 -0
- package/projects/test-a/project.json +456 -0
- package/public/icons/arrows-angle-expand.svg +3 -0
- package/public/icons/arrows-fullscreen.svg +3 -0
- package/public/icons/bezier2.svg +3 -0
- package/public/icons/bootstrap/app-indicator.svg +4 -0
- package/public/icons/bootstrap/arrow-clockwise.svg +4 -0
- package/public/icons/bootstrap/arrow-counterclockwise.svg +4 -0
- package/public/icons/bootstrap/arrow-left.svg +3 -0
- package/public/icons/bootstrap/arrows-angle-expand.svg +3 -0
- package/public/icons/bootstrap/arrows-fullscreen.svg +3 -0
- package/public/icons/bootstrap/backpack.svg +4 -0
- package/public/icons/bootstrap/bezier2.svg +3 -0
- package/public/icons/bootstrap/bookmark-check.svg +4 -0
- package/public/icons/bootstrap/bookmark-plus.svg +4 -0
- package/public/icons/bootstrap/box-arrow-right.svg +4 -0
- package/public/icons/bootstrap/box-arrow-up.svg +4 -0
- package/public/icons/bootstrap/broadcast.svg +3 -0
- package/public/icons/bootstrap/bullseye.svg +6 -0
- package/public/icons/bootstrap/chat-dots.svg +4 -0
- package/public/icons/bootstrap/check-circle.svg +4 -0
- package/public/icons/bootstrap/check2.svg +3 -0
- package/public/icons/bootstrap/clipboard-check.svg +5 -0
- package/public/icons/bootstrap/clipboard.svg +4 -0
- package/public/icons/bootstrap/clock-history.svg +5 -0
- package/public/icons/bootstrap/command.svg +3 -0
- package/public/icons/bootstrap/copy.svg +3 -0
- package/public/icons/bootstrap/cursor.svg +3 -0
- package/public/icons/bootstrap/diagram-3.svg +3 -0
- package/public/icons/bootstrap/diamond.svg +3 -0
- package/public/icons/bootstrap/exclamation-triangle.svg +4 -0
- package/public/icons/bootstrap/eye.svg +4 -0
- package/public/icons/bootstrap/file-earmark-plus.svg +4 -0
- package/public/icons/bootstrap/film.svg +3 -0
- package/public/icons/bootstrap/floppy.svg +4 -0
- package/public/icons/bootstrap/folder2-open.svg +3 -0
- package/public/icons/bootstrap/gear-wide-connected.svg +3 -0
- package/public/icons/bootstrap/gear.svg +4 -0
- package/public/icons/bootstrap/globe.svg +3 -0
- package/public/icons/bootstrap/grid-3x3-gap.svg +3 -0
- package/public/icons/bootstrap/house-door.svg +3 -0
- package/public/icons/bootstrap/image.svg +4 -0
- package/public/icons/bootstrap/layout-text-window.svg +4 -0
- package/public/icons/bootstrap/lightning-charge.svg +3 -0
- package/public/icons/bootstrap/magic.svg +3 -0
- package/public/icons/bootstrap/pencil-square.svg +4 -0
- package/public/icons/bootstrap/pencil.svg +3 -0
- package/public/icons/bootstrap/play.svg +3 -0
- package/public/icons/bootstrap/plus-circle.svg +4 -0
- package/public/icons/bootstrap/plus-lg.svg +3 -0
- package/public/icons/bootstrap/record-circle.svg +4 -0
- package/public/icons/bootstrap/robot.svg +4 -0
- package/public/icons/bootstrap/save.svg +3 -0
- package/public/icons/bootstrap/scissors.svg +3 -0
- package/public/icons/bootstrap/shield-check.svg +4 -0
- package/public/icons/bootstrap/shield-lock.svg +4 -0
- package/public/icons/bootstrap/signpost.svg +3 -0
- package/public/icons/bootstrap/stars.svg +3 -0
- package/public/icons/bootstrap/stop-circle.svg +4 -0
- package/public/icons/bootstrap/terminal.svg +4 -0
- package/public/icons/bootstrap/trash3.svg +3 -0
- package/public/icons/bootstrap/type.svg +3 -0
- package/public/icons/bootstrap/x-circle.svg +4 -0
- package/public/icons/bootstrap/x-lg.svg +3 -0
- package/public/icons/bootstrap/zoom-in.svg +5 -0
- package/public/icons/bootstrap/zoom-out.svg +5 -0
- package/public/icons/bullseye.svg +6 -0
- package/public/icons/check2.svg +3 -0
- package/public/icons/cursor.svg +3 -0
- package/public/icons/diamond.svg +3 -0
- package/public/icons/eye.svg +4 -0
- package/public/icons/file-earmark-plus.svg +4 -0
- package/public/icons/floppy.svg +4 -0
- package/public/icons/gear.svg +4 -0
- package/public/icons/lightning-charge.svg +3 -0
- package/public/icons/pencil.svg +3 -0
- package/public/icons/play.svg +3 -0
- package/public/icons/plus-circle.svg +4 -0
- package/public/icons/record-circle.svg +4 -0
- package/public/icons/robot.svg +4 -0
- package/public/icons/save.svg +3 -0
- package/public/icons/stop-circle.svg +4 -0
- package/public/icons/terminal.svg +4 -0
- package/public/icons/trash3.svg +3 -0
- package/public/icons/x-circle.svg +4 -0
- package/public/icons/zoom-in.svg +5 -0
- package/public/icons/zoom-out.svg +5 -0
- package/public/index.html +424 -0
- package/public/testbench.html +899 -0
- package/scripts/extract-actions.js +128 -0
- package/server.js +11 -0
- package/src/emitter.js +48 -0
- package/src/generator/css.js +135 -0
- package/src/generator/index.js +122 -0
- package/src/generator/md-renderer-src.js +77 -0
- package/src/generator/page.js +300 -0
- package/src/generator/runtime.js +1632 -0
- package/src/generator/templates.js +508 -0
- package/src/ide/action-library.js +856 -0
- package/src/ide/af-icons.js +127 -0
- package/src/ide/app.js +1375 -0
- package/src/ide/command-line/commands.js +242 -0
- package/src/ide/command-line/index.js +329 -0
- package/src/ide/command-line/parser.js +21 -0
- package/src/ide/css/ide.css +1501 -0
- package/src/ide/graph.js +282 -0
- package/src/ide/history.js +46 -0
- package/src/ide/map-builder.js +583 -0
- package/src/ide/project-api.js +39 -0
- package/src/ide/savant-chat.js +513 -0
- package/src/ide/savant.js +1287 -0
- package/src/ide/thing-library.js +89 -0
- package/src/ide/undercity-map.js +978 -0
- package/src/lib/icons.js +72 -0
- package/src/lib/scope.js +88 -0
- package/src/lib/signal.js +155 -0
- package/src/lib/state-machine.js +113 -0
- package/src/server/index.js +96 -0
- package/src/server/routes/actions.js +144 -0
- package/src/server/routes/ai.js +176 -0
- package/src/server/routes/generate.js +54 -0
- package/src/server/routes/projects.js +106 -0
- package/src/server/routes/reset.js +30 -0
- package/src/server/routes/submit.js +30 -0
- package/src/server/routes/templates.js +139 -0
- package/src/server/routes/things.js +33 -0
- package/templates/auth-flow.json +335 -0
- package/templates/blank.json +39 -0
- package/things/auth-server/thing.json +17 -0
- package/things/persona-live/thing.json +20 -0
- package/things/workflow/thing.json +15 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plugins/multipage.js — Multi-page transition plugin.
|
|
3
|
+
*
|
|
4
|
+
* Wraps all page-to-page navigation in CSS animations so the generated
|
|
5
|
+
* app feels like a native SPA. The transition style is set on the project:
|
|
6
|
+
* proj.meta.transition = 'fade' | 'slide' | 'push' | 'zoom' | 'none'
|
|
7
|
+
*
|
|
8
|
+
* How it works:
|
|
9
|
+
* 1. Replaces Navigator.goto() with a version that applies an exit class,
|
|
10
|
+
* waits for its CSS animation, then navigates.
|
|
11
|
+
* 2. Adds an enter animation that plays automatically when each page loads
|
|
12
|
+
* (via a CSS animation on body.pw-page).
|
|
13
|
+
* 3. Writes a transitions.css alongside flow.css.
|
|
14
|
+
*
|
|
15
|
+
* Usage: set `proj.meta.transition = 'fade'` in your project and enable the plugin.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ── CSS bundles per transition type ──────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const TRANSITIONS = {
|
|
21
|
+
fade: `
|
|
22
|
+
/* ── Fade transition ─────────────────────────────────────────────────────── */
|
|
23
|
+
body.pw-page {
|
|
24
|
+
animation: pw-enter-fade 0.28s ease-out;
|
|
25
|
+
}
|
|
26
|
+
body.pw-exit {
|
|
27
|
+
animation: pw-exit-fade 0.22s ease-in forwards;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
}
|
|
30
|
+
@keyframes pw-enter-fade {
|
|
31
|
+
from { opacity: 0; }
|
|
32
|
+
to { opacity: 1; }
|
|
33
|
+
}
|
|
34
|
+
@keyframes pw-exit-fade {
|
|
35
|
+
from { opacity: 1; }
|
|
36
|
+
to { opacity: 0; }
|
|
37
|
+
}`,
|
|
38
|
+
|
|
39
|
+
slide: `
|
|
40
|
+
/* ── Slide transition (right→left) ────────────────────────────────────────── */
|
|
41
|
+
body.pw-page {
|
|
42
|
+
animation: pw-enter-slide 0.3s cubic-bezier(.25,.46,.45,.94);
|
|
43
|
+
}
|
|
44
|
+
body.pw-exit {
|
|
45
|
+
animation: pw-exit-slide 0.25s cubic-bezier(.55,.06,.68,.19) forwards;
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
}
|
|
48
|
+
@keyframes pw-enter-slide {
|
|
49
|
+
from { opacity: 0; transform: translateX(40px); }
|
|
50
|
+
to { opacity: 1; transform: translateX(0); }
|
|
51
|
+
}
|
|
52
|
+
@keyframes pw-exit-slide {
|
|
53
|
+
from { opacity: 1; transform: translateX(0); }
|
|
54
|
+
to { opacity: 0; transform: translateX(-40px); }
|
|
55
|
+
}`,
|
|
56
|
+
|
|
57
|
+
push: `
|
|
58
|
+
/* ── Push transition (scale + slide) ────────────────────────────────────── */
|
|
59
|
+
body.pw-page {
|
|
60
|
+
animation: pw-enter-push 0.32s cubic-bezier(.22,.61,.36,1);
|
|
61
|
+
}
|
|
62
|
+
body.pw-exit {
|
|
63
|
+
animation: pw-exit-push 0.24s ease-in forwards;
|
|
64
|
+
pointer-events: none;
|
|
65
|
+
}
|
|
66
|
+
@keyframes pw-enter-push {
|
|
67
|
+
from { opacity: 0; transform: scale(.96) translateY(16px); }
|
|
68
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
69
|
+
}
|
|
70
|
+
@keyframes pw-exit-push {
|
|
71
|
+
from { opacity: 1; transform: scale(1) translateY(0); }
|
|
72
|
+
to { opacity: 0; transform: scale(1.03) translateY(-8px); }
|
|
73
|
+
}`,
|
|
74
|
+
|
|
75
|
+
zoom: `
|
|
76
|
+
/* ── Zoom transition ────────────────────────────────────────────────────── */
|
|
77
|
+
body.pw-page {
|
|
78
|
+
animation: pw-enter-zoom 0.28s ease-out;
|
|
79
|
+
}
|
|
80
|
+
body.pw-exit {
|
|
81
|
+
animation: pw-exit-zoom 0.22s ease-in forwards;
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
}
|
|
84
|
+
@keyframes pw-enter-zoom {
|
|
85
|
+
from { opacity: 0; transform: scale(.94); }
|
|
86
|
+
to { opacity: 1; transform: scale(1); }
|
|
87
|
+
}
|
|
88
|
+
@keyframes pw-exit-zoom {
|
|
89
|
+
from { opacity: 1; transform: scale(1); }
|
|
90
|
+
to { opacity: 0; transform: scale(1.05); }
|
|
91
|
+
}`,
|
|
92
|
+
|
|
93
|
+
none: '',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// ── Runtime extension ─────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function buildRuntimeExtension(proj) {
|
|
99
|
+
const type = proj.meta?.transition ?? 'fade';
|
|
100
|
+
const duration = proj.meta?.transitionDuration ?? 240;
|
|
101
|
+
|
|
102
|
+
return `
|
|
103
|
+
// ── Transitions (injected by multipage plugin) ────────────────────────────
|
|
104
|
+
export const Transition = (() => {
|
|
105
|
+
const TYPE = '${type}';
|
|
106
|
+
const DURATION = ${duration};
|
|
107
|
+
|
|
108
|
+
async function go(room) {
|
|
109
|
+
if (TYPE === 'none') { _origGoto(room); return; }
|
|
110
|
+
document.body.classList.add('pw-exit');
|
|
111
|
+
await new Promise(r => setTimeout(r, DURATION));
|
|
112
|
+
_origGoto(room);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Override Navigator — capture original first, then replace
|
|
116
|
+
const _origGoto = Navigator.goto;
|
|
117
|
+
Navigator.goto = go;
|
|
118
|
+
|
|
119
|
+
return { type: TYPE, duration: DURATION, goto: go };
|
|
120
|
+
})();`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── afterGenerate hook: write transitions.css ─────────────────────────────────
|
|
124
|
+
|
|
125
|
+
async function afterGenerate(proj, outDir, _files) {
|
|
126
|
+
const type = proj.meta?.transition ?? 'fade';
|
|
127
|
+
const css = TRANSITIONS[type] ?? TRANSITIONS.fade;
|
|
128
|
+
if (!css.trim()) return; // no transitions for 'none'
|
|
129
|
+
|
|
130
|
+
const { writeFile } = await import('fs/promises');
|
|
131
|
+
const { join } = await import('path');
|
|
132
|
+
await writeFile(join(outDir, 'css', 'transitions.css'), css.trimStart(), 'utf8');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── CSS injection into flow.css ───────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
function buildCSSExtension(proj) {
|
|
138
|
+
const type = proj.meta?.transition ?? 'fade';
|
|
139
|
+
return TRANSITIONS[type] ?? '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Plugin manifest ───────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
const MultipagePlugin = {
|
|
145
|
+
name: 'multipage',
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
description: 'Animated page transitions for multi-page generated apps',
|
|
148
|
+
|
|
149
|
+
install(registry) {
|
|
150
|
+
registry.addRuntimeExtension(buildRuntimeExtension);
|
|
151
|
+
registry.addCSSExtension(buildCSSExtension);
|
|
152
|
+
registry.addAfterGenerate(afterGenerate);
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
commands: [
|
|
156
|
+
{
|
|
157
|
+
name: 'set transition',
|
|
158
|
+
category: 'Project',
|
|
159
|
+
description: 'Configure page transition style for generated apps',
|
|
160
|
+
usage: 'set transition <fade|slide|push|zoom|none>',
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default MultipagePlugin;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plugins/wizard.js — Multi-step wizard generation plugin.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `wizard` template type. A wizard room renders all
|
|
5
|
+
* its steps on a single page and navigates between them client-side,
|
|
6
|
+
* storing progress in Inventory so the user can resume where they left off.
|
|
7
|
+
*
|
|
8
|
+
* Node meta shape:
|
|
9
|
+
* {
|
|
10
|
+
* "wizard": {
|
|
11
|
+
* "title": "Account Setup",
|
|
12
|
+
* "steps": [
|
|
13
|
+
* {
|
|
14
|
+
* "id": "profile",
|
|
15
|
+
* "title": "Your Profile",
|
|
16
|
+
* "fields": [
|
|
17
|
+
* { "name": "displayName", "type": "text", "label": "Display Name", "required": true },
|
|
18
|
+
* { "name": "bio", "type": "textarea", "label": "Short Bio" }
|
|
19
|
+
* ]
|
|
20
|
+
* },
|
|
21
|
+
* {
|
|
22
|
+
* "id": "billing",
|
|
23
|
+
* "title": "Billing",
|
|
24
|
+
* "fields": [
|
|
25
|
+
* { "name": "plan", "type": "select", "label": "Plan",
|
|
26
|
+
* "options": ["Free", "Pro", "Enterprise"] }
|
|
27
|
+
* ]
|
|
28
|
+
* }
|
|
29
|
+
* ],
|
|
30
|
+
* "finishTarget": "dashboard"
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { escHtml, escAttr } from '../src/generator/templates.js';
|
|
36
|
+
|
|
37
|
+
// ── Field renderer (shared with forms plugin logic) ───────────────────────────
|
|
38
|
+
|
|
39
|
+
function renderField(field) {
|
|
40
|
+
const { name, type = 'text', label, placeholder = '', required, rows, options } = field;
|
|
41
|
+
const id = `wz-field-${escAttr(name)}`;
|
|
42
|
+
const base = `name="${escAttr(name)}" id="${id}"${required ? ' required' : ''}${placeholder ? ` placeholder="${escAttr(placeholder)}"` : ''}`;
|
|
43
|
+
|
|
44
|
+
if (type === 'textarea') {
|
|
45
|
+
return `
|
|
46
|
+
<div class="mb-3">
|
|
47
|
+
<label class="form-label" for="${id}">${escHtml(label ?? name)}${required ? ' <span class="text-danger">*</span>' : ''}</label>
|
|
48
|
+
<textarea class="form-control pw-input" ${base} rows="${rows ?? 3}"></textarea>
|
|
49
|
+
<div class="d-none text-danger small mt-1" data-error="${escAttr(name)}"></div>
|
|
50
|
+
</div>`;
|
|
51
|
+
}
|
|
52
|
+
if (type === 'select') {
|
|
53
|
+
const opts = (options ?? []).map(o => {
|
|
54
|
+
const v = typeof o === 'object' ? o.value : o;
|
|
55
|
+
const l = typeof o === 'object' ? o.label : o;
|
|
56
|
+
return `<option value="${escAttr(v)}">${escHtml(l)}</option>`;
|
|
57
|
+
}).join('');
|
|
58
|
+
return `
|
|
59
|
+
<div class="mb-3">
|
|
60
|
+
<label class="form-label" for="${id}">${escHtml(label ?? name)}</label>
|
|
61
|
+
<select class="form-select pw-input" ${base}><option value="">— choose —</option>${opts}</select>
|
|
62
|
+
<div class="d-none text-danger small mt-1" data-error="${escAttr(name)}"></div>
|
|
63
|
+
</div>`;
|
|
64
|
+
}
|
|
65
|
+
return `
|
|
66
|
+
<div class="mb-3">
|
|
67
|
+
<label class="form-label" for="${id}">${escHtml(label ?? name)}${required ? ' <span class="text-danger">*</span>' : ''}</label>
|
|
68
|
+
<input type="${escAttr(type)}" class="form-control pw-input" ${base}>
|
|
69
|
+
<div class="d-none text-danger small mt-1" data-error="${escAttr(name)}"></div>
|
|
70
|
+
</div>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Template builder ──────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
function buildWizardPage(proj, node, outEdges) {
|
|
76
|
+
const wz = node.meta?.wizard ?? {};
|
|
77
|
+
const title = wz.title ?? node.label ?? 'Setup Wizard';
|
|
78
|
+
const steps = wz.steps ?? [];
|
|
79
|
+
const finishTarget = wz.finishTarget ?? outEdges[0]?.toId ?? null;
|
|
80
|
+
const onExitJSON = JSON.stringify(node.payload?.onExit ?? [], null, 6);
|
|
81
|
+
|
|
82
|
+
// Progress indicators
|
|
83
|
+
const progressItems = steps.map((step, i) => `
|
|
84
|
+
<div class="pw-wz-step${i === 0 ? ' active' : ''}" data-wz-step="${i}">
|
|
85
|
+
<span class="pw-wz-num">${i + 1}</span>
|
|
86
|
+
<span class="pw-wz-title">${escHtml(step.title ?? step.id)}</span>
|
|
87
|
+
</div>`).join('');
|
|
88
|
+
|
|
89
|
+
// Step panels
|
|
90
|
+
const panels = steps.map((step, i) => {
|
|
91
|
+
const fieldsHTML = (step.fields ?? []).map(renderField).join('');
|
|
92
|
+
return `
|
|
93
|
+
<div class="pw-wz-panel${i === 0 ? '' : ' d-none'}" data-wz-panel="${i}">
|
|
94
|
+
<h5 class="pw-heading mb-4">${escHtml(step.title ?? step.id)}</h5>
|
|
95
|
+
${fieldsHTML || '<p class="text-muted">No fields defined for this step.</p>'}
|
|
96
|
+
</div>`;
|
|
97
|
+
}).join('');
|
|
98
|
+
|
|
99
|
+
const stepDefs = JSON.stringify(steps.map(s => ({
|
|
100
|
+
id: s.id,
|
|
101
|
+
fields: (s.fields ?? []).map(f => ({ name: f.name, required: !!f.required, type: f.type ?? 'text' })),
|
|
102
|
+
})), null, 6);
|
|
103
|
+
|
|
104
|
+
return `
|
|
105
|
+
<!-- Wizard: ${escHtml(title)} -->
|
|
106
|
+
<div class="pw-card col-md-7 mx-auto">
|
|
107
|
+
<h2 class="pw-heading mb-4">${escHtml(title)}</h2>
|
|
108
|
+
|
|
109
|
+
<!-- Progress bar -->
|
|
110
|
+
<div class="pw-wz-progress mb-4">${progressItems}
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Step content -->
|
|
114
|
+
<form id="pw-wz-form" novalidate>
|
|
115
|
+
${panels}
|
|
116
|
+
<div id="pw-wz-error" class="d-none alert alert-danger py-2 mb-3"></div>
|
|
117
|
+
</form>
|
|
118
|
+
|
|
119
|
+
<!-- Navigation -->
|
|
120
|
+
<div class="d-flex justify-content-between mt-4">
|
|
121
|
+
<button id="pw-wz-back" class="btn btn-outline-secondary d-none">← Back</button>
|
|
122
|
+
<div class="ms-auto d-flex gap-2">
|
|
123
|
+
<button id="pw-wz-next" class="btn btn-primary">Next →</button>
|
|
124
|
+
<button id="pw-wz-finish" class="btn btn-success d-none">Finish ✓</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<style>
|
|
130
|
+
.pw-wz-progress { display:flex; gap:0; }
|
|
131
|
+
.pw-wz-step { display:flex; align-items:center; gap:8px; padding:8px 16px; font-size:12px;
|
|
132
|
+
color:var(--sol-text); border-bottom:2px solid transparent; }
|
|
133
|
+
.pw-wz-step.active { color:var(--sol-cyan); border-color:var(--sol-cyan); }
|
|
134
|
+
.pw-wz-step.done { color:var(--sol-green); border-color:var(--sol-green); }
|
|
135
|
+
.pw-wz-num { display:inline-flex; align-items:center; justify-content:center;
|
|
136
|
+
width:22px; height:22px; border-radius:50%; font-size:11px; font-weight:700;
|
|
137
|
+
background:rgba(38,139,210,.2); color:var(--sol-blue); }
|
|
138
|
+
.pw-wz-step.active .pw-wz-num { background:var(--sol-cyan); color:#002b36; }
|
|
139
|
+
.pw-wz-step.done .pw-wz-num { background:var(--sol-green); color:#002b36; }
|
|
140
|
+
</style>
|
|
141
|
+
|
|
142
|
+
<script type="module">
|
|
143
|
+
import { Inventory, Navigator, runPayload } from './js/runtime.js';
|
|
144
|
+
|
|
145
|
+
const STEPS = ${stepDefs};
|
|
146
|
+
const ON_EXIT = ${onExitJSON};
|
|
147
|
+
const FINISH_TO = ${finishTarget ? `'${finishTarget}'` : 'null'};
|
|
148
|
+
const STEP_KEY = '__wz_step_${node.id}__';
|
|
149
|
+
let current = Inventory.get(STEP_KEY) ?? 0;
|
|
150
|
+
|
|
151
|
+
const panels = document.querySelectorAll('[data-wz-panel]');
|
|
152
|
+
const progSteps= document.querySelectorAll('[data-wz-step]');
|
|
153
|
+
const btnBack = document.getElementById('pw-wz-back');
|
|
154
|
+
const btnNext = document.getElementById('pw-wz-next');
|
|
155
|
+
const btnFin = document.getElementById('pw-wz-finish');
|
|
156
|
+
|
|
157
|
+
function showStep(n) {
|
|
158
|
+
current = Math.max(0, Math.min(n, STEPS.length - 1));
|
|
159
|
+
Inventory.set(STEP_KEY, current);
|
|
160
|
+
panels.forEach((p, i) => p.classList.toggle('d-none', i !== current));
|
|
161
|
+
progSteps.forEach((s, i) => {
|
|
162
|
+
s.classList.toggle('active', i === current);
|
|
163
|
+
s.classList.toggle('done', i < current);
|
|
164
|
+
});
|
|
165
|
+
btnBack.classList.toggle('d-none', current === 0);
|
|
166
|
+
btnNext.classList.toggle('d-none', current === STEPS.length - 1);
|
|
167
|
+
btnFin.classList.toggle('d-none', current !== STEPS.length - 1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function validateStep(idx) {
|
|
171
|
+
const step = STEPS[idx] ?? {};
|
|
172
|
+
const errors = {};
|
|
173
|
+
for (const f of step.fields ?? []) {
|
|
174
|
+
if (!f.required) continue;
|
|
175
|
+
const el = document.querySelector(\`[name="\${f.name}"]\`);
|
|
176
|
+
const val = f.type === 'checkbox' ? el?.checked : el?.value?.trim();
|
|
177
|
+
if (!val) errors[f.name] = \`\${f.name} is required\`;
|
|
178
|
+
}
|
|
179
|
+
return errors;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function saveStep(idx) {
|
|
183
|
+
for (const f of (STEPS[idx]?.fields ?? [])) {
|
|
184
|
+
const el = document.querySelector(\`[name="\${f.name}"]\`);
|
|
185
|
+
if (el) Inventory.set(f.name, f.type === 'checkbox' ? el.checked : el.value);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
btnBack.addEventListener('click', () => showStep(current - 1));
|
|
190
|
+
|
|
191
|
+
btnNext.addEventListener('click', () => {
|
|
192
|
+
const errors = validateStep(current);
|
|
193
|
+
document.querySelectorAll('[data-error]').forEach(e => { e.textContent=''; e.classList.add('d-none'); });
|
|
194
|
+
if (Object.keys(errors).length) {
|
|
195
|
+
for (const [name, msg] of Object.entries(errors)) {
|
|
196
|
+
const el = document.querySelector(\`[data-error="\${name}"]\`);
|
|
197
|
+
if (el) { el.textContent = msg; el.classList.remove('d-none'); }
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
saveStep(current);
|
|
202
|
+
showStep(current + 1);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
btnFin.addEventListener('click', async () => {
|
|
206
|
+
const errors = validateStep(current);
|
|
207
|
+
if (Object.keys(errors).length) { btnNext.click(); return; }
|
|
208
|
+
saveStep(current);
|
|
209
|
+
Inventory.delete(STEP_KEY);
|
|
210
|
+
await runPayload(ON_EXIT);
|
|
211
|
+
if (FINISH_TO) Navigator.goto(FINISH_TO);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
showStep(current);
|
|
215
|
+
</script>`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Plugin manifest ───────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
const WizardPlugin = {
|
|
221
|
+
name: 'wizard',
|
|
222
|
+
version: '1.0.0',
|
|
223
|
+
description: 'Multi-step wizard pages with progress indicator and inventory persistence',
|
|
224
|
+
|
|
225
|
+
install(registry) {
|
|
226
|
+
registry.addTemplate('wizard', buildWizardPage);
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
commands: [
|
|
230
|
+
{
|
|
231
|
+
name: 'scaffold wizard',
|
|
232
|
+
category: 'Generator',
|
|
233
|
+
description: 'Add a wizard room with multiple steps',
|
|
234
|
+
usage: 'scaffold wizard <room-id> [--steps <step1,step2,...>] [--title <title>]',
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export default WizardPlugin;
|