code wiki / _hdl_build / nx_editor_loop.nx
nx_editor_loop.nx source
↩ module page · 80 lines · 4804 B
1// nx_editor_loop.nx -- the NO-JS edit LOOP: the Nishi ecosystem MANAGING a layout with zero JavaScript. The editor
2// surface is rendered (ec_render) with a NATIVE HTML <form> of op buttons; submitting POSTs an op; the server APPLIES
3// the engine op (el_apply -> nx_editor_canvas) and RE-RENDERS. Every edit is a server round-trip -- no client JS,
4// and more governed than drag-drop (each op passes through our code). The daemon route (/editor/apply) is the
5// deployment wiring; this is the sovereign engine of it. REUSE: nx_editor_canvas. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_editor_canvas.nx"
8
9// null-terminated string equality.
10func el_streq(a: *u8, b: *u8) -> i64 {
11 var i: i64 = 0
12 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
13 if b[i] != (0 as u8) { return 0 }
14 return 1
15}
16// The snap grid the posted `snap` op uses. It was a bare 8 inside el_apply; naming it is what lets the
17// stack-backed path below use THE SAME grid instead of a second hand-typed copy that can drift from it.
18const EL_GRID: i64 = 8
19
20// apply a posted op to the canvas via the engine. Returns 1 if applied, 0 if the op is unknown (rejected, no-op).
21// KEPT, UNCHANGED, for every caller that holds a bare canvas array. el_apply_st below is the additive
22// stack-backed sibling; neither signature moved.
23func el_apply(c: *i64, n: i64, op: *u8) -> i64 {
24 if el_streq(op, "align-left" as *u8) == 1 { ec_align(c, n, 0); return 1 }
25 if el_streq(op, "align-right" as *u8) == 1 { ec_align(c, n, 1); return 1 }
26 if el_streq(op, "align-top" as *u8) == 1 { ec_align(c, n, 2); return 1 }
27 if el_streq(op, "snap" as *u8) == 1 { ec_snap_all(c, n, EL_GRID); return 1 }
28 if el_streq(op, "distribute" as *u8) == 1 { ec_distribute_h(c, n); return 1 }
29 return 0
30}
31
32// THE SAME POSTED VOCABULARY, RECORDED ON THE OPERATION LOG. This is what makes the no-JS loop undoable:
33// every posted op becomes one entry on the stack, so `undo` and `redo` are ops the form can post like any
34// other, and they go back as many steps as the user took rather than one. Returns 1 if applied, 0 if the op
35// is unknown OR the engine refused it -- and a refusal is NOT silently reported as applied, so a POST that
36// could not be honoured re-renders unchanged instead of lying about it.
37func el_apply_st(st: *i64, op: *u8) -> i64 {
38 if el_streq(op, "align-left" as *u8) == 1 { if ecs_align(st, 0) == ECS_OK { return 1 } return 0 }
39 if el_streq(op, "align-right" as *u8) == 1 { if ecs_align(st, 1) == ECS_OK { return 1 } return 0 }
40 if el_streq(op, "align-top" as *u8) == 1 { if ecs_align(st, 2) == ECS_OK { return 1 } return 0 }
41 if el_streq(op, "snap" as *u8) == 1 { if ecs_snap(st, EL_GRID) == ECS_OK { return 1 } return 0 }
42 if el_streq(op, "distribute" as *u8) == 1 { if ecs_distribute_h(st) == ECS_OK { return 1 } return 0 }
43 if el_streq(op, "undo" as *u8) == 1 { if ecs_undo(st) == ECS_OK { return 1 } return 0 }
44 if el_streq(op, "redo" as *u8) == 1 { if ecs_redo(st) == ECS_OK { return 1 } return 0 }
45 return 0
46}
47
48// The op buttons, EXTRACTED so the snapshot form and the stack-backed form cannot drift apart in what they
49// offer. Adding an op here reaches both surfaces; typing it twice is how one surface silently loses a verb.
50func el_emit_ops(fd: i64) -> i64 {
51 ec_w(fd, "<button name=\"op\" value=\"align-left\">Align left</button>\n" as *u8)
52 ec_w(fd, "<button name=\"op\" value=\"align-right\">Align right</button>\n" as *u8)
53 ec_w(fd, "<button name=\"op\" value=\"align-top\">Align top</button>\n" as *u8)
54 ec_w(fd, "<button name=\"op\" value=\"snap\">Snap to grid</button>\n" as *u8)
55 ec_w(fd, "<button name=\"op\" value=\"distribute\">Distribute</button>\n" as *u8)
56 return 0
57}
58
59// render the canvas + a NATIVE HTML ops form (no JS). Submitting an op is a server round-trip.
60// BYTE-FOR-BYTE what it emitted before: the buttons moved into el_emit_ops and nothing else changed.
61func el_emit_form(c: *i64, n: i64, fd: i64) -> i64 {
62 ec_render(c, n, fd)
63 ec_w(fd, "<form method=\"post\" action=\"/editor/apply\">\n" as *u8)
64 el_emit_ops(fd)
65 ec_w(fd, "</form>\n" as *u8)
66 return 0
67}
68
69// The stack-backed surface: the same form plus Undo and Redo, which the snapshot form cannot offer because
70// a one-deep snapshot has nothing to redo. STILL ZERO JAVASCRIPT -- undo is a POST like every other op,
71// which is the whole claim this loop exists to make.
72func el_emit_form_st(st: *i64, fd: i64) -> i64 {
73 ecs_render(st, fd)
74 ec_w(fd, "<form method=\"post\" action=\"/editor/apply\">\n" as *u8)
75 el_emit_ops(fd)
76 ec_w(fd, "<button name=\"op\" value=\"undo\">Undo</button>\n" as *u8)
77 ec_w(fd, "<button name=\"op\" value=\"redo\">Redo</button>\n" as *u8)
78 ec_w(fd, "</form>\n" as *u8)
79 return 0
80}