Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 27 additions & 45 deletions calcit.cirru
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
{} (:description |Browser-app) (:init-fn 'text-diff.main/main!) (:mode :js) (:reload-fn 'text-diff.main/reload!) (:target :browser)
:feature-policy $ {}
:modules $ [] |respo.calcit/ |respo-ui.calcit/ |respo-markdown.calcit/ |reel.calcit/ |js-ffi/
:type-slots $ {} $ :dispatch-op 'text-diff.schema/DispatchOp
:type-slots $ {} $ :dispatch-op 'text-diff.schema/Op
:files $ {}
'text-diff.comp.container $ %{} 'FileEntry
:defs $ {} $ 'comp-container
%{} 'CodeEntry (:doc |)
:code $ quote $ defcomp comp-container (reel)
:code $ quote $ defcomp comp-container (reel on-control)
let
store $ :store reel
states $ :states store
Expand Down Expand Up @@ -43,10 +43,12 @@
-> Unit
println $ :content store
if dev?
Option :some $ comp-typed-reel (>> states :reel) reel $ {}
Option :some $ comp-typed-reel (>> states :reel) reel ({}) on-control
Option :none
:examples $ []
:schema $ quote $ Fn (reel.typed/State text-diff.schema/Op text-diff.schema/Store) (-> respo.schema/Component)
:schema $ quote $ Fn (reel.typed/State text-diff.schema/Op text-diff.schema/Store)
Fn reel.typed/Control $ -> Unit
-> respo.schema/Component
:ns $ %{} 'NsEntry (:doc |)
:code $ quote $ ns text-diff.comp.container
:require (respo-ui.core :as ui)
Expand Down Expand Up @@ -98,17 +100,13 @@
'dispatch! $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn dispatch! (op)
when config/dev? $ println |Dispatch: op
match (schema/decode-op op)
(:some resolved)
match resolved
(reel.typed/Control control)
reset! *reel $ typed/apply-control updater @*reel control
(text-diff.schema/Op action)
reset! *reel $ typed/record-op updater @*reel action (generate-id!) (shared/now-ms)
(:none) &unit
reset! *reel $ match op
(reel.typed/Control control) (typed/apply-control updater @*reel control)
(text-diff.schema/Op action)
typed/record-op updater @*reel action (generate-id!) (shared/now-ms)
, &unit
:examples $ []
:schema $ quote $ Fn AnyEnum (-> Unit) (:features :js-ffi)
:schema $ quote $ Fn text-diff.schema/ResolvedOp (-> Unit) (:features :js-ffi)
'main! $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn main! ()
println "|Running mode:" $ if config/dev? |dev |release
Expand All @@ -117,11 +115,11 @@
hint-fn $ Fn (reel.typed/State text-diff.schema/Op text-diff.schema/Store) (reel.typed/State text-diff.schema/Op text-diff.schema/Store) (-> Unit)
render-app!
, &unit
listen-devtools! |a dispatch!
listen-devtools! |a on-control!
match
browser/storage-get $ .unwrap $ get config/site :storage-key
(:some raw)
dispatch! $ :: :hydrate-storage $ parse-cirru-edn raw
dispatch! $ schema/Op :hydrate-storage $ schema/decode-store (parse-cirru-edn raw)
(:none) &unit
browser/add-event-listener! |beforeunload $ fn (event) (persist-storage!)
println "|App started."
Expand All @@ -132,6 +130,10 @@
.unwrap $ respo-browser/query-selector |.app
:examples $ []
:schema $ quote $ Fn (-> respo.dom/DomElement) (:features :js-ffi)
'on-control! $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn on-control! (control) (dispatch! control)
:examples $ []
:schema $ quote $ Fn reel.typed/Control (-> Unit)
'persist-storage! $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn persist-storage! ()
browser/storage-set!
Expand All @@ -157,8 +159,10 @@
'render-app! $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn render-app! ()
render-with! (mount-target)
fn () $ comp-container @*reel
, dispatch!
fn () $ comp-container @*reel on-control!
fn (op)
hint-fn $ Fn text-diff.schema/Op $ -> Unit
dispatch! op
:examples $ []
:schema $ quote $ Fn (-> Unit) (:features :js-ffi)
'snippets $ %{} 'CodeEntry (:doc |)
Expand Down Expand Up @@ -187,11 +191,6 @@
:code $ quote $ defunion CursorKey Tag String Number
:examples $ []
:schema $ quote UnionDef
'DispatchOp $ %{} 'CodeEntry
:doc "|Respo dispatch slot accepts application operations and Reel controls."
:code $ quote $ def DispatchOp (:: :unhandled)
:examples $ []
:schema $ quote AnyEnum
'Op $ %{} 'CodeEntry (:doc |)
:code $ quote $ defenum Op
:states (List text-diff.schema/CursorKey) Dynamic
Expand All @@ -209,30 +208,13 @@
:content String
:examples $ []
:schema $ quote StructDef
'decode-op $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn decode-op (op)
let
some-action $ fn (action)
hint-fn $ Fn text-diff.schema/ResolvedOp $ -> (Option text-diff.schema/ResolvedOp)
Option :some action
match (reel.typed/decode-control op)
(:some control) (some-action control)
(:none)
match op
(:states cursor data)
some-action $ Op :states
decode-map-as cursor $ List text-diff.schema/CursorKey
, data
(:content content)
some-action $ Op :content $ decode-map-as content String
(:hydrate-storage data)
some-action $ Op :hydrate-storage $ decode-map-as
if (struct? data) (&struct:to-map data) data
, text-diff.schema/Store
_ $ do (eprintln "|Unknown op:" op) (Option :none)
'decode-store $ %{} 'CodeEntry (:doc |)
:code $ quote $ defn decode-store (data)
decode-map-as
if (struct? data) (&struct:to-map data) data
, text-diff.schema/Store
:examples $ []
:schema $ quote $ Fn AnyEnum
-> $ Option text-diff.schema/ResolvedOp
:schema $ quote $ Fn Input (-> text-diff.schema/Store) (:generics Input)
'store $ %{} 'CodeEntry (:doc |)
:code $ quote $ def store
Store :states ({}) :content |
Expand Down
2 changes: 1 addition & 1 deletion deps.cirru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

{} (:calcit-version |0.38.0)
:version |0.1.4
:dependencies $ {} (|Respo/reel.calcit |0.10.0)
:dependencies $ {} (|Respo/reel.calcit |0.11.0)
|Respo/respo-markdown.calcit |0.8.2
|Respo/respo-ui.calcit |0.15.2
|Respo/respo.calcit |0.19.5
Expand Down
40 changes: 22 additions & 18 deletions scripts/upgrade.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,47 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import * as c from '../js-out/calcit.core.mjs';
import { updater } from '../js-out/text-diff.updater.mjs';
import { decode_op, store } from '../js-out/text-diff.schema.mjs';
import { Control } from '../js-out/reel.typed.mjs';
import { decode_store, Op, store } from '../js-out/text-diff.schema.mjs';
import { Control, apply_control, new_reel, record_op } from '../js-out/reel.typed.mjs';

const tags = c.init_tags(['content', 'states']);
const tags = c.init_tags(['content', 'states', 'hydrate-storage', 'editor', 'data', 'draft', 'toggle', 'display?', 'records']);
const read = (value, key) => c.option_$o_unwrap(c.get(value, key));
const apply = (source, op) => updater(source, c.option_$o_unwrap(decode_op(c.parse_cirru_edn(op))), 'test-op', 0);
const op = (tag, ...values) => c._PCT__$o__$o_(Op, tag, ...values);
const apply = (source, tag, ...values) => updater(source, op(tag, ...values), 'test-op', 0);

test('content update preserves cursor state and leaves original store unchanged', () => {
const updated = apply(store, ':: :content |hello');
const updated = apply(store, tags.content, 'hello');
assert.equal(read(updated, tags.content), 'hello');
assert.equal(read(store, tags.content), '');
assert.equal(read(updated, tags.states), read(store, tags.states));
});

test('hydration preserves the persisted map format', () => {
const updated = apply(store, ':: :hydrate-storage $ {} (:content |restored) (:states $ {})');
const restored = decode_store(c.parse_cirru_edn('{} (:content |restored) (:states $ {})'));
const updated = apply(store, tags['hydrate-storage'], restored);
assert.equal(read(updated, tags.content), 'restored');
const roundTrip = c.parse_cirru_edn(c.format_cirru_edn(updated));
assert.equal(read(roundTrip, tags.content), 'restored');
});

test('cursor updates preserve content', () => {
const populated = apply(store, ':: :content |keep');
const updated = apply(populated, ':: :states ([] :editor) $ {} (:draft |typing)');
const populated = apply(store, tags.content, 'keep');
const updated = apply(populated, tags.states, c._$L_(tags.editor), c._$n__$M_(tags.draft, 'typing'));
assert.equal(read(updated, tags.content), 'keep');
const editor = read(read(updated, tags.states), c.init_tags(['editor']).editor);
assert.equal(read(read(editor, c.init_tags(['data']).data), c.init_tags(['draft']).draft), 'typing');
for (const [key, value] of [['|中文', '中文'], ['0', 0]]) {
const keyed = apply(populated, `:: :states ([] :editor ${key}) false`);
const nested = read(read(read(keyed, tags.states), c.init_tags(['editor']).editor), value);
assert.equal(read(nested, c.init_tags(['data']).data), false);
const editor = read(read(updated, tags.states), tags.editor);
assert.equal(read(read(editor, tags.data), tags.draft), 'typing');
for (const key of ['中文', 0]) {
const keyed = apply(populated, tags.states, c._$L_(tags.editor, key), false);
const nested = read(read(read(keyed, tags.states), tags.editor), key);
assert.equal(read(nested, tags.data), false);
assert.equal(read(keyed, tags.content), 'keep');
}
assert.throws(() => apply(populated, ':: :states ([] :editor false) $ {}'), /CursorKey/);
assert.throws(() => apply(populated, tags.states, c._$L_(tags.editor, false), c._$n__$M_()), /CursorKey|declared type/);
});

test('legacy Reel controls decode to the nominal control member', () => {
const control = c.option_$o_unwrap(decode_op(c.parse_cirru_edn(':: :reel/toggle')));
assert.equal(c.option_$o_unwrap(c._$n_enum_$o_definition(control)), Control);
test('typed Reel control toggles the panel without adding an edit', () => {
const before = record_op(updater, new_reel(store), op(tags.content, 'hello'), 'test-op', 0);
const after = apply_control(updater, before, c._PCT__$o__$o_(Control, tags.toggle));
assert.equal(read(after, tags['display?']), !read(before, tags['display?']));
assert.equal(read(after, tags.records), read(before, tags.records));
});