diff --git a/src/Action.tsx b/src/Action.tsx index 072fdfe..8781a9d 100644 --- a/src/Action.tsx +++ b/src/Action.tsx @@ -4,6 +4,8 @@ import * as Hover from "./Hover"; import * as Settings from "./Settings"; import * as Transform from "./Transform"; import * as Pat from "./syntax/Pat"; +import * as Projector from "./Projector"; +import * as ID from "./syntax/ID"; export type Inject = (_: Action) => void; @@ -16,8 +18,8 @@ export type Action = | { t: "setSelect"; path: Path.t } | { t: "moveStage"; direction: Direction } | { t: "moveTool"; direction: Direction } - | { t: "wheelTools"; offset: number} - | { t: "wheelNumTools"; offset: number} + | { t: "wheelTools"; offset: number } + | { t: "wheelNumTools"; offset: number } | { t: "unsetSelections" } | { t: "transformNode"; @@ -35,6 +37,7 @@ export type Action = | { t: "applyTransform"; idx: number; direction: "forward" | "reverse" } | { t: "applyTransformSelected" } | { t: "flipTransform"; idx: number } - | { t: "Noop" }; + | { t: "Noop" } + | { t: "Project"; id: ID.t | undefined; action: Projector.Action }; export type t = Action; diff --git a/src/Animate.tsx b/src/Animate.tsx index 52370bc..b60200b 100644 --- a/src/Animate.tsx +++ b/src/Animate.tsx @@ -8,10 +8,9 @@ const blah = (s: string) => ` export const init = ():void => { //TODO: unhardcode id max var style = document.createElement("style"); - for (let id = 0; id < 100; id++) { + for (let id = 0; id < 200; id++) { style.innerHTML += `#node-${id}.animate { view-transition-name: flip-node-${id}; }\n`; - style.innerHTML += `#main.unsetSelections #sym-${id}, #main.setSelect #sym-${id}, #main.moveStage #sym-${id} { view-transition-name: flip-sym-${id}; }\n`; - + style.innerHTML += `#main.unsetSelections #sym-${id} , #main.setSelect #sym-${id}, #main.moveStage #sym-${id} { view-transition-name: flip-sym-${id}; }\n`; //style.innerHTML += `#main.setSelect #pat-${id} { view-transition-name: flip-pat-${id}; }`; //style.innerHTML += blah(`flip-node-${id}`); //style.innerHTML += blah(`flip-pat-${id}`); diff --git a/src/App.tsx b/src/App.tsx index ceb24c5..95a4ba5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,44 +8,25 @@ import { SettingsView } from "./view/SettingsView"; import { Seed } from "./view/SeedView"; import * as ExpToPat from "./syntax/ExpToPat"; import * as Animate from "./Animate"; -//import { Toolbar } from "./view/ToolsView"; +import * as Tone from "tone"; export type SetModel = SetStoreFunction; - const App: Component = () => { const [model, setModel] = createStore({ ...Model.init }); Animate.init(); const inject = (a: Action.t) => { console.log(a); - if (a.t === "setHover" || !document.startViewTransition) { - console.log("sethover dont transition:" + a.t); - go(model, setModel, a); - return; - } - const guy2 = document.getElementById("main"); - guy2 ? guy2.classList.add(a.t) : console.log("no guy r add"); - let v = document.startViewTransition(() => go(model, setModel, a)); - v.finished.then(() => - guy2 ? guy2.classList.remove(a.t) : console.log("no guy 2 rm") - ); + go(model, setModel, a); }; document.addEventListener("keydown", Keyboard.keydown(inject), false); document.addEventListener("keyup", Keyboard.keyup(inject), false); - // document.addEventListener("transitionstart", (e) => { - // in_transition = true; - // }); - // document.addEventListener("transitionend", (e) => { - // in_transition = false; - // }); return (
- {/* diff --git a/src/Keyboard.tsx b/src/Keyboard.tsx index 90c3bf5..09c3e2b 100644 --- a/src/Keyboard.tsx +++ b/src/Keyboard.tsx @@ -30,6 +30,8 @@ const action_of = (key: string): Action.t | "NoBinding" => { return { t: "moveTool", direction: "right" }; case " ": return { t: "applyTransformSelected" }; + case "f": + return { t: "Project", action: "toggleEnfoldCurrent", id: undefined }; default: return "NoBinding"; } diff --git a/src/Projector.tsx b/src/Projector.tsx index a8b9506..97c12be 100644 --- a/src/Projector.tsx +++ b/src/Projector.tsx @@ -1,18 +1,141 @@ import * as ID from "./syntax/ID"; +import * as Exp from "./syntax/Exp"; -type PaintColor = "Cyan" | "Magenta" | "Yellow"; +//type PaintColor = "Cyan" | "Magenta" | "Yellow"; +//type Painter = PaintColor | "Unpainted"; -type Painter = PaintColor | "Unpainted"; +type Folded = "Folded" | "Enfolded" | "NotFolded"; type Projector = { - painter: Painter; + //painter: Painter; + folded: Folded; }; type t = Projector; +export const basic: Projector = { folded: "NotFolded" }; + export type PMap = Map; -export const init: PMap = new Map(); +export const init: PMap = (() => { + const map = new Map(); + map.set(77, { folded: "Folded" }); + map.set(79, { folded: "Folded" }); + map.set(81, { folded: "Folded" }); + map.set(20, { folded: "Enfolded" }); + map.set(82, { folded: "Folded" }); + return map; +})(); + +export type Action = "toggleFoldCurrent" | "toggleEnfoldCurrent"; + +export const get = (projectors: PMap, id: ID.t): t => { + const res = projectors.get(id); + return res == undefined ? basic : res; +}; + +export const update = (id: ID.t, action: Action, projectors: PMap): PMap => { + const map: PMap = new Map(projectors); + const current = get(map, id); + switch (action) { + case "toggleFoldCurrent": + if (current === undefined) { + map.set(id, { folded: "Folded" }); + } else { + map.set(id, { + ...current, + folded: current.folded === "Folded" ? "NotFolded" : "Folded", + }); + } + return map; + case "toggleEnfoldCurrent": + if (current === undefined) { + console.log("Projector.update: id not found, adding new entry:" + id); + map.set(id, { folded: "Enfolded" }); + } else { + console.log("Projector.update: id found, changing folding state:" + id); + map.set(id, { + ...current, + folded: current.folded === "Enfolded" ? "NotFolded" : "Enfolded", + }); + } + return map; + } +}; + +export const is_folded = (id: ID.t, projectors: PMap): boolean => + get(projectors, id).folded === "Folded"; + +export const is_enfolded = (id: ID.t, projectors: PMap): boolean => + get(projectors, id).folded === "Enfolded"; + +const get_enfolded = (projectors: PMap, node: Exp.t): Exp.t[] => { + let p = get(projectors, node.id); + if (p.folded == "Enfolded") { + return [node]; + } else { + switch (node.t) { + case "Atom": + return []; + case "Comp": + if (p.folded == "Folded") { + return []; + } else { + return node.kids + .map((kid) => + get_enfolded(projectors, project_folds(projectors, kid)) + ) + .flat(); + } + } + } +}; + +export const has_enfolded = (projectors: PMap, node: Exp.t): boolean => + get_enfolded(projectors, node).length > 0; + +let get_id = (node: Exp.t) => { + const res = Exp.head_id(node); + return res == undefined ? -1 : res; +}; + +let summary_head = (node: Exp.t): Exp.t => ({ + t: "Atom", + id: get_id(node), + sym: Exp.head(node), +}); + +let blank_head = (node: Exp.t): Exp.t => ({ + t: "Atom", + id: get_id(node), + sym: "", +}); + +/* TODO: maybe dont allow enfolds inside enfolds */ +export const project_folds = (projectors: PMap, node: Exp.t): Exp.t => { + switch (node.t) { + case "Atom": + return node; + case "Comp": + switch (get(projectors, node.id).folded) { + case "Folded": + const enfolded = node.kids + .map((kid) => get_enfolded(projectors, kid)) + .flat(); + const head = + enfolded.length == 0 ? [summary_head(node)] : [blank_head(node)]; + return { + ...node, + kids: head.concat(enfolded), + }; + default: + return { + ...node, + kids: node.kids.map((kid) => project_folds(projectors, kid)), + }; + } + } +}; /* paint plan: diff --git a/src/Stage.tsx b/src/Stage.tsx index 325068e..cba10aa 100644 --- a/src/Stage.tsx +++ b/src/Stage.tsx @@ -1,14 +1,15 @@ -import { Exp } from "./syntax/Exp"; +import * as Exp from "./syntax/Exp"; import * as Statics from "./Statics"; import * as Path from "./syntax/Path"; import * as Projector from "./Projector"; import * as World from "./data/World"; -import { is_path_valid } from "./syntax/Node"; +import { is_path_valid, id_at } from "./syntax/Node"; +import * as ID from "./syntax/ID"; export type selection = "unselected" | Path.t; export type Stage = { - exp: Exp; + exp: Exp.t; selection: selection; info: Statics.InfoMap; //derived from exp projectors: Projector.PMap; //annotations @@ -16,7 +17,7 @@ export type Stage = { export type t = Stage; -const exp: Exp = World.init; +const exp: Exp.t = World.init; export const init: Stage = { exp, @@ -44,7 +45,7 @@ const rev = (path: Path.t): Path.t => { return blah.reverse(); }; -const move_up = (exp: Exp, selection: number[]): Path.t => { +const move_up = (exp: Exp.t, selection: number[]): Path.t => { if (selection.length === 0) return selection; const [last, ...tl] = rev(selection); const new_path = rev([last - 1, ...tl]); @@ -54,7 +55,7 @@ const move_up = (exp: Exp, selection: number[]): Path.t => { } else return selection; }; -const move_down = (exp: Exp, selection: number[]): Path.t => { +const move_down = (exp: Exp.t, selection: number[]): Path.t => { /* first, try to increment last idx of selection. if that gives a valid path, return it. then try to increment second last index, etc. @@ -75,7 +76,7 @@ const move_down = (exp: Exp, selection: number[]): Path.t => { return move_down(exp, Array(selection.length).fill(0)); }; -const move_left = (_exp: Exp, selection: number[]): Path.t => { +const move_left = (_exp: Exp.t, selection: number[]): Path.t => { if (selection.length === 0) { return selection; } else { @@ -83,7 +84,7 @@ const move_left = (_exp: Exp, selection: number[]): Path.t => { } }; -const move_right = (exp: Exp, selection: number[]): Path.t => { +const move_right = (exp: Exp.t, selection: number[]): Path.t => { const new_selection = [...selection, 1]; if (is_path_valid(new_selection, exp)) { return new_selection; @@ -114,3 +115,12 @@ export const move = ( stage: Stage, direction: "up" | "down" | "left" | "right" ) => put_selection(stage, move_(stage, direction)); + +export const indicated_id = (stage: Stage): ID.t | undefined => + stage.selection === "unselected" + ? undefined + : id_at(stage.selection, stage.exp); + +export const projected_width = (stage: t) =>{ + console.log("width: " +Exp.width(Projector.project_folds(stage.projectors, stage.exp))) + return Exp.width(Projector.project_folds(stage.projectors, stage.exp));} diff --git a/src/Statics.tsx b/src/Statics.tsx index 5d2c1eb..f9abcd4 100644 --- a/src/Statics.tsx +++ b/src/Statics.tsx @@ -2,14 +2,106 @@ import * as Exp from "./syntax/Exp"; import * as Path from "./syntax/Path"; import * as ID from "./syntax/ID"; import { size } from "./syntax/Node"; +import * as Symbols from "./data/Symbols"; + +type code_form = "Let" | "Fun" | "Atom" | "App" | "If"; +type math_form = "Const" | "UnOp" | "BinOp" | "Digits" | "Free"; + +export type cls = + | { t: "Delim" } + | { t: "Room" } + | { t: "File" } + | { t: "Math"; form: math_form } + | { t: "Code"; form: code_form } + | { t: "Unknown" }; + +export const string_of_cls = (cls: cls): string => { + switch (cls.t) { + case "Delim": + return "Delim"; + case "Room": + return "Room"; + case "File": + return "File"; + case "Unknown": + return "Unknown"; + case "Math": + return "Math:" + cls.form; + case "Code": + return "Code" + cls.form; + } +}; + +const is_room = (e: Exp.t): boolean => + e.t == "Comp" && Symbols.rooms.has(Exp.head(e)); + +const is_file = (e: Exp.t): boolean => + e.t == "Comp" && e.kids.length == 2 && Symbols.filenames.has(Exp.head(e)); + +export const get_code_form = (e: Exp.t): code_form | "NotCode" => { + if (e.t == "Comp") { + const k = (i: number) => Exp.of_atom(e.kids[i]); + const l = e.kids.length; + if (l == 1) return "Atom"; + if (l == 4 && k(1) == "(" && k(3) == ")") return "App"; + if (l == 6 && k(0) == "let" && k(2) == "=" && k(4) == "in") return "Let"; + if (l == 3 && k(0) == "fun" && k(2) == "->") return "Fun"; + if (l == 6 && k(0) == "if" && k(2) == "then" && k(4) == "else") return "If"; + } + return "NotCode"; +}; + +export const is_delim = (cf: code_form, i: number): boolean => { + switch (cf) { + case "Let": + return i === 0 || i === 2 || i === 4; + case "Fun": + return i === 0 || i === 2; + case "App": + return i === 1 || i === 3; + case "If": + return i === 0 || i === 2 || i === 4; + default: + return i === 0; + } +}; + +const get_math_form = (e: Exp.t): math_form | "NotMath" => { + if (e.t == "Atom" && Symbols.math_const.has(e.sym)) return "Const"; + if (e.t == "Atom") return "Free"; + if (e.t == "Comp") { + const k = (i: number) => Exp.of_atom(e.kids[i]); + const l = e.kids.length; + if (l == 2 && Symbols.math_un_ops.has(k(0))) return "UnOp"; + if (l == 3 && k(0) == Symbols.digit) return "Digits"; + if (l == 3 && Symbols.math_bin_ops.has(k(0))) return "BinOp"; + } + return "NotMath"; +}; + +export const cls_of = (exp: Exp.t): cls => { + switch (exp.t) { + case "Comp": + if (exp.kids.length == 0) return { t: "Unknown" }; + if (is_room(exp)) return { t: "Room" }; + if (is_file(exp)) return { t: "File" }; + const code_form = get_code_form(exp); + if (code_form != "NotCode") return { t: "Code", form: code_form }; + const math_form = get_math_form(exp); + if (math_form != "NotMath") return { t: "Math", form: math_form }; + return { t: "Unknown" }; + case "Atom": + return { t: "Unknown" }; + } +}; export type Info = { path: Path.t; ancestors: ID.t[]; size: number; depth: number; + cls: cls; //id: ID; - //depth: number; // length of path //parent: number; // head of path }; @@ -25,6 +117,7 @@ export const mk = (exp: Exp.t, ancestors: ID.t[]): InfoMap => { depth: path.length, // add id to beginning of ancestors ancestors: [id, ...ancestors], + cls: cls_of(exp), }; map.set(id, info); switch (exp.t) { @@ -45,7 +138,7 @@ export const get = (map: InfoMap, id: ID.t): Info => { if (info == undefined) { console.log(`InfoMap.get: id ${id} not found`); //throw new Error(`InfoMap.get: id ${id} not found`); - return { path: [], depth: 0, size: 0, ancestors: [] }; + return { path: [], depth: 0, size: 0, ancestors: [], cls: {t:"Unknown"} }; } else { return info; } diff --git a/src/Update.tsx b/src/Update.tsx index 1e779ca..10d9124 100644 --- a/src/Update.tsx +++ b/src/Update.tsx @@ -1,5 +1,4 @@ import { at_path } from "./Transform"; -import Flipping from "flipping/lib/adapters/web"; import * as Model from "./Model"; import * as Sound from "./Sound"; import * as Settings from "./Settings"; @@ -13,9 +12,12 @@ import * as Exp from "./syntax/Exp"; import * as Pat from "./syntax/Pat"; import { SetStoreFunction } from "solid-js/store"; import * as Path from "./syntax/Path"; -import * as Animate from "./Animate"; import * as Util from "./Util"; - +import { reconcile } from "solid-js/store"; +import * as Projectors from "./Projector"; +import * as Tone from "tone"; +import * as Statics from "./Statics"; +import { id_at } from "./syntax/Node"; export type result = Model.t | "NoChange"; @@ -28,7 +30,11 @@ export const of_theme = (theme: Settings.theme): [number, number] => { } }; -export const sound = (model: Model.t, action: Action.t): void => { +export const sound = async ( + model: Model.t, + action: Action.t +): Promise => { + await Tone.start(); const [pitch, volume] = of_theme(model.settings.theme); switch (action.t) { case "transformNodeAndFlipTransform": @@ -57,6 +63,9 @@ export const sound = (model: Model.t, action: Action.t): void => { case "Noop": Sound.noop(); break; + case "Project": + Sound.sfx("pew")(); + break; case "setHover": case "flipTransform": case "applyTransform": @@ -206,7 +215,8 @@ export const update = (model: Model.t, action: Action.t): result => { }, }; case "wheelNumTools": - const clamp = (x:number, a:number, b:number) => Math.max( a, Math.min(x, b) ); + const clamp = (x: number, a: number, b: number) => + Math.max(a, Math.min(x, b)); console.log("wheelNumTools:" + action.offset + ":" + model.tools.size); return { ...model, @@ -215,35 +225,81 @@ export const update = (model: Model.t, action: Action.t): result => { size: clamp( model.tools.size + action.offset, 1, - model.tools.transforms.length, - - + model.tools.transforms.length + ), + }, + }; + case "Project": + const indicated_id = Stage.indicated_id(model.stage); + console.log("Project: Indicated ID: " + indicated_id); + const id = + action.id != undefined + ? action.id + : indicated_id != undefined + ? indicated_id + : -666; + return { + ...model, + stage: { + ...model.stage, + projectors: Projectors.update( + id, + action.action, + model.stage.projectors ), }, }; } }; +export const viewTransition = (action: Action.t, f: () => void) => { + const r = document.querySelector(":root"); + if (action.t == "Project") { + if (r != null) r.style.setProperty("--anim-factor", "2"); + } + const guy2 = document.getElementById("main"); + guy2 ? guy2.classList.add(action.t) : console.log("no guy 1"); + let v = document.startViewTransition(f); + v.finished.then(() => { + guy2 ? guy2.classList.remove(action.t) : console.log("no guy 2"); + if (r != null) r.style.setProperty("--anim-factor", "1"); + }); +}; +// document.addEventListener("transitionstart", (e) => { +// in_transition = true; +// }); +// document.addEventListener("transitionend", (e) => { +// in_transition = false; +// }); + export const go = ( model: Model.t, setModel: SetStoreFunction, action: Action.t ): void => { - if (model.settings.sound) sound(model, action); - /* Catching because problem on build server */ - try { - //Animate.read(model, action); - } catch (e) { - console.error(e); - } const result = update(model, action); if (result == "NoChange") { - //Sound.noop(); console.log("Action NoChange:" + action.t); return; } else { console.log("Action Success: " + action.t); - setModel(result); + const doIt = () => setModel(result); + const indicated_id = Stage.indicated_id(result.stage); + if (indicated_id) + console.log( + "ci: " + + Statics.string_of_cls( + Statics.get(result.stage.info, indicated_id).cls + ) + ); + //setModel(reconcile(result, { merge: true, key: "kids" })); + if (action.t === "setHover" || !document.startViewTransition) { + console.log("setHover: Don't transition:" + action.t); + doIt(); + return; + } else { + viewTransition(action, doIt); + } } /* HACK: We want transforms the duplicate subtrees e.g. distributivity to * retain their duplicate ids for animations, but then we need to freshen @@ -257,10 +313,4 @@ export const go = ( stage: Stage.put_exp(model.stage, freshened), }); }, 250);*/ - /* Catching because problem on build server */ - try { - //Animate.flip(model, action); - } catch (e) { - console.error(e); - } }; diff --git a/src/data/Symbols.tsx b/src/data/Symbols.tsx new file mode 100644 index 0000000..f47cd90 --- /dev/null +++ b/src/data/Symbols.tsx @@ -0,0 +1,9 @@ +import * as Exp from "../syntax/Exp"; + +export const digit = "ɖ"; + +export const rooms = new Set(["䷶", "䷀", "䷂"]); +export const filenames = new Set(["ᖛ", "ᙊ", "◵", "ᝏ"]); +export const math_bin_ops = new Set(["➕", "✖️"]); +export const math_un_ops = new Set(["➖"]); +export const math_const = new Set(["🌘", "🌑"]); diff --git a/src/data/Tools.tsx b/src/data/Tools.tsx index 5305cc4..34b0802 100644 --- a/src/data/Tools.tsx +++ b/src/data/Tools.tsx @@ -1,13 +1,13 @@ import * as Pat from "../syntax/Pat"; import * as Sound from "../Sound"; import * as Transform from "../Transform"; +import * as Symbols from "./Symbols"; type Base = { source: Pat.t; result: Pat.t; sound: Sound.Sfxbank }; - - const zero = Pat.p_const("🌑"); const one = Pat.p_const("🌘"); +const one2 = Pat.p_const("🌘"); const hole = Pat.p_const("❓"); const var_a = Pat.p_var("♫"); @@ -31,6 +31,41 @@ const times_x = bin_x("✖️"); const times_y = bin_y("✖️"); const equals_x = bin_x("🟰"); +const Bx = bin_x(Symbols.digit); +const By = bin_y(Symbols.digit); +const two = Bx(zero, one); + +const b_def_0: Base = { + source: plus_x(one, one), + result: two, + sound: "tiup", +}; +const b_def_1: Base = { + source: Bx(var_a, var_b), + result: plus_x(var_a, times_x(var_b, two)), + sound: "tiup", +}; +const b_thm_0: Base = { + source: Bx(one, var_a), + result: plus_x(Bx(zero, var_a), one), + sound: "tiup", +}; +const b_thm_1: Base = { + source: Bx(var_a, zero), + result: times_x(var_a, two), + sound: "tiup", +}; +const b_thm_2: Base = { + source: plus_x(Bx(zero, var_a), By(var_c, var_b)), + result: Bx(var_c, plus_x(var_a, var_b)), + sound: "tiup", +}; +const b_thm_3: Base = { + source: plus_x(Bx(one, var_a), By(one2, var_b)), + result: Bx(zero, plus_x(var_a, plus_x(var_b, one2))), + sound: "tiup", +}; + const commute_plus: Base = { source: plus_x(var_a, var_b), result: plus_x(var_b, var_a), @@ -87,13 +122,13 @@ const distribute_times_plus: Base = { const mk_mk = (result: Pat.t): Base => ({ source: hole, - result:result, - sound: "klohk",//TODO + result: result, + sound: "klohk", //TODO }); const mk_wrap = (result: Pat.t): Base => ({ source: var_a, - result:result, - sound: "klohk",//TODO + result: result, + sound: "klohk", //TODO }); const mk_one = mk_mk(one); const mk_zero = mk_mk(zero); @@ -147,6 +182,12 @@ const mk = ({ source, result, sound }: Base): Transform.t => ({ }); export const init = [ + b_def_0, + b_def_1, + b_thm_0, + b_thm_1, + b_thm_2, + b_thm_3, associate_plus, commute_plus, identity_plus, @@ -158,4 +199,4 @@ export const init = [ double_neg, ].map(mk); -export const _init =makers.map(mk); \ No newline at end of file +export const _init = makers.map(mk); diff --git a/src/data/World.tsx b/src/data/World.tsx index f0217fb..a6617f2 100644 --- a/src/data/World.tsx +++ b/src/data/World.tsx @@ -1,9 +1,12 @@ -import { Exp, atom, comp } from "../syntax/Exp"; +import { Exp, atom, comp, flat } from "../syntax/Exp"; +import * as Symbols from "../data/Symbols"; // ☁️ 🧩 🦷 🦠 🌸 🍄 🎲 🐝 -// ➕ ➖ ✖️ ➗ 🟰 🌕 🌘 0️⃣ 1️⃣ +// ➕ ➖ ✖️ ➗ 🟰 🌕 🌘 🌑 0️⃣ 1️⃣ ❓ -export const init: Exp = comp([ +const B = flat(Symbols.digit); + +export const alg: Exp = comp([ atom("➕"), comp([ atom("➕"), @@ -17,41 +20,43 @@ export const init: Exp = comp([ ]), ]); -export const _init:Exp = atom("❓"); +const lab: Exp = comp([atom("➕"), atom("❓"), atom("❓")]); -export const moons: Exp = comp([ +const moons: Exp = comp([ atom("➕"), + comp([atom("➕"), B(["🌘", "🌘", "🌑", "🌑", "🌘"]), B(["🌘", "🌑", "🌘"])]), comp([ atom("➕"), - comp([atom("➕"), atom("🌘🌕"), comp([atom("➖"), atom("🌘🌕🌕🌕🌘")])]), - atom("🌘🌕 "), - ]), - comp([ - atom("➕"), - comp([atom("✖️"), atom("🌕"), atom("🌘")]), - comp([atom("✖️"), atom("🌘🌕🌘🌕 "), atom("🌘🌘")]), + comp([atom("➕"), atom("🌘"), atom("🌘")]), + comp([atom("➕"), atom("🌘"), comp([atom("➕"), atom("🌘"), atom("🌘")])]), ]), ]); -const pv = (hd: string, tl: Exp) => comp([atom("."), atom(hd), tl]); +const let_exp = (pat: Exp, def: Exp, body: Exp): Exp => + comp([atom("let"), pat, atom("="), def, atom("in"), body]); -export const moons2: Exp = comp([ - atom("➕"), - comp([ - atom("➕"), - comp([ - atom("➕"), - pv("🌘", atom("🌕")), - comp([atom("➖"), pv("🌘", pv("🌕", pv("🌕", pv("🌕", atom("🌘")))))]), - ]), - pv("🌘", atom("🌕")), - ]), - comp([ - atom("➕"), - comp([ - atom("✖️"), - pv("🌘", pv("🌕", pv("🌘", atom("🌕")))), - pv("🌘", atom("🌘")), - ]), - ]), +const fun_exp = (pat: Exp, body: Exp): Exp => + comp([atom("fun"), pat, atom("->"), body]); + +const var_exp = (name: string): Exp => atom(name); + +const app_exp = (fun: Exp, arg: Exp): Exp => + comp([fun, atom("("), arg, atom(")")]); + +const if_exp = (cond: Exp, then: Exp, els: Exp): Exp => + comp([atom("if"), cond, atom("then"), then, atom("else"), els]); + +const code: Exp = let_exp( + var_exp("x"), + atom("1️"), + app_exp(var_exp("f"), var_exp("x")) +); + +// ▨ ䷀ ䷂ ᖛ ᙊ ࢥ Ꭳ ◵ +export const init: Exp = comp([ + atom("䷶"), + comp([atom("ᖛ"), alg]), + comp([atom("ᙊ"), moons]), + comp([atom("◵"), code]), + comp([atom("ᝏ"), lab]), ]); diff --git a/src/index.css b/src/index.css index 5a0453c..0e71466 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,5 @@ :root { - --anim-factor: 2; + --anim-factor: 1.25; } .notransition * { @@ -184,7 +184,37 @@ } } +/* styles for mask */ +@keyframes mask-pulse-✿ { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.03); + } +} +@keyframes mask-pulse-♫ { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.03); + } +} +@keyframes mask-pulse-♥ { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.03); + } +} + body { + user-select: none; height: 100vh; overflow: hidden; margin: 0; @@ -216,7 +246,7 @@ code { transition: border-radius 0.5s ease-in-out; /* padding: 1em; */ /* main scale factor: */ - font-size: 3em; + font-size: 4em; border-radius: 0em; box-shadow: inset 0px 0px 0.6em #ffffff; /* mix-blend-mode: color-dodge; */ @@ -248,17 +278,14 @@ code { } .node-container { - /*width: fit-content;*/ user-select: none; cursor: pointer; } .node.atom { - /*background-color: #ba65ff55;*/ - /*box-shadow: none*/ color: white; width: fit-content; - border-radius: 1em; /*0.5em 1.3em 0.9em 0.9em;*/ + border-radius: 1em; padding: 0 0.25em; justify-content: center; } @@ -269,7 +296,6 @@ code { animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: cubic-bezier(0.35, 0, 0.25, 0.35); - /*filter: brightness(1.1) drop-shadow(2px 4px 6px #f5f6);*/ } .transform .source, @@ -277,42 +303,28 @@ code { transition: filter 0.2s cubic-bezier(0.4, 0, 1, 1); } -/* balances out display of identity transform, mostly so - clicking the arrow to reverse the transform doesn't - end up moving the arrow */ -.transform .source > .node.atom, -.transform .result > .node.atom { - /*margin: 1.1em 0.5em 1.1em 1.8em;*/ -} - .node { transition: all 0.3s ease-in-out; - transition: scale 0.1s linear; + transition: translate 0.1s linear; width: fit-content; display: flex; border-radius: 3em 1.2em 0.6em 32%; - padding: 0.07em; /*prev: both 0.20em*/ + padding: 0.07em; margin: 0.07em; align-items: center; box-shadow: 0.25em 0.25em 0.5em #773fcc40; - /*border-bottom: 0.3px solid rgba(13, 0, 95, 22); - border-top: 0.3px solid rgba(255, 255, 255, 66);*/ border-bottom: 0.3px solid rgb(45 93 111 / 18%); border-top: 0.3px solid rgb(173 207 229 / 55%); } .node:hover { - /*mix-blend-mode: color-dodge;*/ - /*transform: scale(1.03);*/ - filter: brightness(1.05); border-bottom: 0.3px solid rgb(187, 0, 255); border-top: 0.3px solid rgb(255, 100, 212); - /* scale: 1.03; */ } .node:hover:not(:has(.node:hover)) { - scale: 1.02; + translate: 1.5px 1px; } -.node:hover > .node { - /*scale: 0.99;*/ +.node:hover .node:hover:not(:has(.node:hover)) .node { + translate: -1.5px 1px; } .node:has(.node:hover) { scale: 1; @@ -320,37 +332,16 @@ code { .node.depth-0:hover { scale: 1 !important; } -/*.node.depth-0:hover { - transform: scale(1.01); -} -.node.depth-1:hover { - transform: scale(1.02); -} -.node.depth-2:hover { - transform: scale(1.02); -} -.node.depth-3:hover { - transform: scale(1.03); -} -.node.depth-4:hover { - transform: scale(1.03); -} -.node.depth-5:hover { - transform: scale(1.04); + +.node.comp { + text-shadow: 0 0 0 #50b0ff; } -.node.depth-6:hover { - transform: scale(1.04); -}*/ .node.selected, .node.selected:hover { - border: 0; - outline: 0.03em solid #ffffff; box-shadow: 0px 0px 0.6em #ffffff; - /* mix-blend-mode: color-dodge; */ outline: 2px solid white; } - .node.selected > .head, .node.selected > .head { animation-name: pulse-scale; @@ -358,33 +349,54 @@ code { animation-iteration-count: infinite; animation-timing-function: cubic-bezier(0.35, 0, 0.25, 0.35); } +.node:active { + animation-delay: 0.25s; + animation-name: wobble2; + animation-duration: 0.15s; + animation-iteration-count: infinite; +} -.node.floatselect { - position: absolute; - /* left: -3px; */ - outline: 2px solid red; - /* margin: 0 auto; */ - left: 0; - top: 0; - opacity: 20%; +/* FOLDS */ + +.node-container > .node.folded { + background: none !important; + border: none !important; + box-shadow: none; +} +#stage .node.atom.folded, +#stage .node.comp.folded { + background: antiquewhite; +} +.node.folded:has(> .head:only-child) { + border-radius: 2em; +} +.node.folded > .head { + font-size: 0.8em; + color: black; } -.node.floatselect > * { - /*opacity: 0;*/ +.node.folded > .head:after { + content: "\00a0⋯"; } -.node.comp { - /* display: flex; - color: transparent; */ - text-shadow: 0 0 0 #50b0ff; +.node.enfolded { + /* important is hack to deal with digits; can remove after statics is extended */ + border-left: 5px solid #9c7bdf !important; } -.node:active { - /*animation-name: wobble; - animation-duration: 1s;*/ - animation-name: wobble2; - animation-duration: 0.15s; - animation-iteration-count: infinite; +.node.folded > .node.enfolded { + border-left: 2px solid #9c7bdf !important; + background-color: #c7f0e3; + box-shadow: inset 0.5em 0 1em #dacae6aa; +} +/* If we're only showing one punchout, make it clean */ +.node-container > .node.folded > *.head + .node.enfolded:last-child { + border-left: none !important; + background: none; + box-shadow: 0.25em 0.25em 0.5em #773fcc40; /* HACK */ } -#stage .node.comp { + +/* STAGE */ + +.node.comp { /*background-color: #005bff26;*/ background: linear-gradient( 222deg, @@ -399,6 +411,8 @@ code { border-top: 0.3px solid rgb(226 255 244); } .node.comp .head { + display: flex; + align-items: center; background-color: #284a37; /* mix-blend-mode: color-burn; */ color: transparent; @@ -423,9 +437,10 @@ code { grid-column: unset; } .TreeTop .node-container.TreeTop .node.comp { - align-items: end; + grid-row: 2; display: grid; grid-auto-flow: row; + align-items: end; justify-items: center; border-radius: 3em 3em 1em 1em; } @@ -433,152 +448,7 @@ code { grid-column: 1/3; } -/* nool 3d */ -/*#stage .node-container.TreeTop .node.comp { - transform: perspective(10em) rotateX(37deg); - background: linear-gradient( 222deg, #32b7bd, #43dddc, #00ffba, #ffb7ec, #7f91ff ); - border-bottom: 0.3px solid rgb(255 0 220 / 53%); - border-top: 1px solid rgb(255 255 255); - border-left: 0.3px solid white; - border-right: 0.3px solid white; -}*/ - -.node-container.TreeLeft .node.comp { - /* grid-template-columns: fit-content(1.5em) 1fr; */ - /*align-items: center;*/ - display: grid; - grid-auto-flow: column; -} -.node-container.TreeLeft .node.comp .head { - grid-column: 1; - grid-row: 1/3; -} - -#seed:has(.LinearPrefix), -#seed:has(.LinearInfix) { - flex-direction: column-reverse; -} - -.node-container.LinearPrefix:has(.selected) .node.atom, -.node-container.LinearInfix:has(.selected) .node.atom, -.node-container.LinearPrefix:has(.selected) .head, -.node-container.LinearInfix:has(.selected) .head { - opacity: 50%; -} - -.node-container.LinearPrefix .node.selected, -.node-container.LinearInfix .node.selected, -.node-container.LinearPrefix .node.selected .node.atom, -.node-container.LinearInfix .node.selected .node.atom, -.node-container.LinearPrefix .node.selected .head, -.node-container.LinearInfix .node.selected .head { - opacity: 100% !important; -} - -.node-container.LinearPrefix .node.comp, -.node-container.LinearInfix .node.comp { - margin: 0; - padding: 0; - border-radius: 1.5em; - box-shadow: none; -} -.node-container.LinearPrefix .node.atom, -.node-container.LinearInfix .node.atom { - border-radius: 1.5em; - border-color: #0000; - box-shadow: none; -} - -.node-container.LinearInfix .node.atom:not(.selected) { - box-shadow: #0000; -} - -.node-container.LinearInfix .node.selected:has(> *), -.node-container.LinearInfix .node.selected:has(> *):hover { - border: 0; - outline: 0.03em solid #ffffff; - box-shadow: 0px 0px 0.6em #ffffff; - /* mix-blend-mode: color-dodge; */ - /* outline: 1px solid #258b86; */ - background: none; - outline: 0 !important; - box-shadow: none !important; - border-color: #0000 !important; -} - -.node-container.LinearInfix .node.selected > .node { - outline: 2px dotted white; - border-color: #0000 !important; - /*transform: scale(1.03);*/ - box-shadow: 7px 7px 15px #008750aa !important; -} - -.node-container.LinearInfix .node:hover { - transform: scale(1.01); -} - -.node-container.LinearInfix .node.comp *:nth-child(1) { - order: 2; -} -.node-container.LinearInfix .node.comp *:nth-child(2) { - order: 1; -} -.node-container.LinearInfix .node.comp *:nth-child(3) { - order: 3; -} -/* Special case for unary ops (TODO: Betterize) */ -.node-container.LinearInfix - .node.comp:has(> :nth-child(2):last-child) - *:nth-child(1) { - order: 1; -} - -#seed.SingleChar #stage .node.comp, -#seed.SingleChar #noolbox .node.comp { - border-left: 0.3px solid rgb(255 0 220 / 53%); - border-right: 0.3px solid rgb(119 198 168); - border-top: none; - border-bottom: none; - border-radius: 0.8em; -} -/* HACKY */ -#seed.SingleChar #stage .node.comp:not(.mask), -#seed.SingleChar #noolbox .node.comp:not(.mask) { - background: none; -} - -#seed.SingleChar .node.atom, -#seed.SingleChar .head { - font-family: "Roboto"; - font-weight: 700; - font-family: monospace; - - border-top: none; - border-bottom: none; - box-shadow: none; -} -#seed.SingleChar .node.atom { - padding: 0 0.4em 0 0.4em; - color: #474646; -} -#seed.SingleChar .head { - padding: 0 0.35em 0 0.35em; - color: #2d8b7e; -} -#seed.SingleChar .selected > .head { - -webkit-text-stroke-width: 0.02em; - -webkit-text-stroke-color: black; -} - -#seed.SingleChar .NoMatch .node.comp { - box-shadow: none; -} - -/*#seed.LinearPrefix { - flex-direction: column-reverse; - justify-content: center; - align-items: flex-start; -}*/ +/* NOOLBOX */ #noolbox { box-shadow: inset -8px 0px 8px #3ca38211, inset -4px 0px 4px #3ca38211, @@ -588,22 +458,14 @@ code { display: grid; grid-template-columns: 1fr 0.7em 1fr; user-select: none; - /* background-color: #aee7e866; */ border-radius: 0 0.9em 0.9em 0em; - /* border-top: 0.04em solid #5bf296; */ border-bottom: 0.04em solid #53f5ff66; - /* border-top: 0.02em solid #f8a7ff66; */ width: fit-content; padding: 0.3em 0.4em 0.3em 0; } .TreeLeft #noolbox { height: 9.2em; } -/*#noolbox .result .node.comp, -#noolbox .source .node.comp { - border-radius: 0.6em 2em 2.5em 0.5em; - flex-direction: row-reverse; -}*/ .transform-view { position: relative; @@ -616,9 +478,6 @@ code { grid-template-columns: subgrid; grid-column: 1 / span 3; align-items: center; - /*background-color: #f0f0f077; - border-top: 0.4px solid #b0b0b099; - border-bottom: 1.1px solid #f0f0f1;*/ transition: all 0.05s; font-variant: diagonal-fractions; text-shadow: 0 -0.3px 0 #469aa5; @@ -654,10 +513,7 @@ v .transform-view .label { /*animation-name: wobble3; animation-duration: 0.15s;*/ } -.transform-view .result .node, -.transform-view .source .node { - /*transition: all 0.1s ease-in;*/ -} + .transform-view .result.selected > .node, .transform-view .source.selected > .node { outline: 0.14em solid #9e00ff; @@ -670,14 +526,9 @@ v .transform-view .label { } .transform-view .node.comp { - /*transition: all 0.1s ease-in-out; - box-shadow: 0.25em 0.25em 0.5em #55ac8740;*/ box-shadow: 0.1em 0.1em 1.4em #55ac8740; border-bottom: 0.1px solid #8ee347; } -.Light .transform-view .node-container.match > .node:hover { - outline: 2px solid white; -} .transform-view:active { color: black; @@ -688,41 +539,6 @@ v .transform-view .label { box-shadow: none; } -.previews { - display: flex; - flex-direction: column; - gap: 0.2em; - /* font-size: 0.25em; */ - border-radius: 0.8em 0 0 0.8em; - padding: 0.5em 0 0.5em 0.5em; - border-top: 0.04em solid #d4a4d8; - border-bottom: 0.04em solid #7ae0e6; -} -.previews .node-container { - font-size: 0.25em; - mix-blend-mode: color-burn !important; - filter: saturate(1) opacity(0.5) hue-rotate(277deg) blur(0.2em) !important; -} -.previews .node-container:hover { - filter: saturate(1) brightness(0.1) invert(1) opacity(1) hue-rotate(210deg); - mix-blend-mode: exclusion; -} -.previews .node.comp { - background-color: #005bff26; - transition: all 0.05s ease-in-out; -} -.previews .node-container:hover .node.comp { - box-shadow: none; - background-color: #0000; - transform: none; -} -.previews .id-view { - display: none; -} -#main.setSelect.selected #seed .previews { - mix-blend-mode: color-burn !important; -} - #toolbar { grid-row: 2 / span 1; grid-column: 1 / span 1; @@ -735,11 +551,6 @@ v .transform-view .label { opacity: 80%; } -#stage { - /*grid-row: 2 / span 1; - grid-column: 3 / span 1;*/ -} - .transform-arrow { display: flex; justify-content: center; @@ -747,14 +558,6 @@ v .transform-view .label { -moz-background-clip: text; background-clip: text; } -.Light .transform-arrow { - background-color: #009f46; - text-shadow: 0.01em 0.02em 0.02em rgb(216 248 227); -} -.Dark .transform-arrow { - background-color: #5aae3a; - text-shadow: 0.01em 0.02em 0.02em rgb(8 90 111); -} .match:hover + .transform-arrow, .transform-arrow:has(+ .match:hover) { color: white; @@ -776,192 +579,84 @@ v .transform-view .label { color: #00e6f0; box-shadow: none; border-color: #0000; - /*box-shadow: 0 0 1em #0ffa; - mix-blend-mode: lighten;*/ } .pat.node.atom.♥ { background-color: #ffd5fc; color: #f8abf2; box-shadow: none; border-color: #0000; - /*box-shadow: 0 0 1em #f0fa; - mix-blend-mode: lighten;*/ } .pat.node.atom.✿ { background-color: #ffffb2; color: #f7e300; box-shadow: none; border-color: #0000; - /*box-shadow: 0 0 1em #ff0a; - mix-blend-mode: lighten;*/ +} +.pat.node.comp { + position: relative; + display: flex; + flex-direction: row; + background: none; } -/* styles for mask */ -@keyframes mask-pulse-✿ { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.03); - } +.NoMatch .node.pat.atom, +.NoMatch .head { + color: #682828; + opacity: 20%; } -@keyframes mask-pulse-♫ { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.03); - } +.NoMatch .node.pat.atom, +.NoMatch .head .NoMatch .node.comp { + box-shadow: 0.1em 0.1em 1.4em #55ac8740; } -@keyframes mask-pulse-♥ { - 0%, - 100% { - transform: scale(1); - } - 50% { - transform: scale(1.03); - } + +.NoMatch { + opacity: 0.9; } -#stage .node.mask.✿ { +.node.mask.✿ { animation-name: mask-pulse-✿; background-color: yellow; filter: sepia(100%) saturate(240%) brightness(98%) hue-rotate(-10deg); } -#stage .node.mask.♫ { +.node.mask.♫ { animation-name: mask-pulse-♫; background-color: cyan; filter: sepia(100%) saturate(240%) brightness(98%) hue-rotate(140deg); } -#stage .node.mask.♥ { +.node.mask.♥ { animation-name: mask-pulse-♥; background-color: #f772f7; filter: sepia(100%) saturate(240%) brightness(98%) hue-rotate(280deg); } -#stage .node.mask { +.node.mask { animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: cubic-bezier(0.35, 0, 0.25, 0.35); - /*filter: brightness(1.1) drop-shadow(2px 4px 6px #f5f6);*/ } .node.mask .node { - /* border-top: 1px solid #a3a3a3; */ border-bottom: 1px solid #0007; } -.pat.node.comp { - position: relative; - display: flex; - flex-direction: row; -} - -.NoMatch .node.pat.atom, -.NoMatch .head { - color: #682828; - opacity: 20%; -} -.NoMatch .node.pat.atom, -.NoMatch .head .NoMatch .node.comp { - box-shadow: 0.1em 0.1em 1.4em #55ac8740; -} - -.NoMatch { - opacity: 0.9; - /* filter: opacity(0.6) sepia(1) saturate(0) brightness(0.6) contrast(5); */ -} - .id-view { display: contents; font-size: 3pt; - /* position: absolute; - left: 1em; - color: #c8cfff; - text-shadow: 0.3px 0.2px 0.5px #7856ba;*/ } .🌑 { filter: brightness(1.4); } -#settings-panel { - position: absolute; - right: 0; - top: 0; - display: flex; - gap: 0.25em; -} - -#settings-panel .icon:hover { - opacity: 100%; - filter: invert(0); -} - -#settings-panel .icon { - opacity: 90%; - width: 0.5em; - padding: 0.15em; - filter: invert(1); - position: fixed; - scale: 0.8; -} - -#settings-panel .icon:nth-child(1) { - left: 0; - top: 0; -} - -#settings-panel .icon:nth-child(2) { - right: 0; - bottom: 0; -} - -#settings-panel .icon:nth-child(3) { - left: 0; - bottom: 0; -} - -#settings-panel .icon:nth-child(4) { - right: 0; - top: 0; -} - -#settings-panel .icon:nth-child(5) { - right: 0.75em; - top: 0; -} - -#settings-panel .icon:nth-child(6) { - right: 0em; - top: 0.75em; -} - -.toolbar2 { -} -.toolbar2 ul { -} -.toolbar2 li { -} +/* SEED */ #seed { height: 100vh; display: flex; align-items: center; justify-content: center; - /* gap: 0.7em; */ } -/*#seed { - display: grid; - grid-template-columns: fit-content(30%) 2em fit-content(30%); - grid-auto-flow: row; - align-content: center; - align-items: center; -}*/ - #seed .icon { padding: 0.5em 0.5em 0.5em 0.5em; filter: sepia(1) hue-rotate(210deg) saturate(4) brightness(1); @@ -975,11 +670,15 @@ v .transform-view .label { width: calc(1.9766em * 0.5); height: calc(1em * 0.5); position: relative; - margin: 0.5em; + margin: 0.5em 0.05em 0.5em 0.5em; transition: transform 1s cubic-bezier(0.68, -0.6, 0.32, 1.6), margin 0.1s linear; } +#seed .icon2:has(+ #stage .selected) .inner { + filter: invert(1) brightness(10.5) sepia(1) hue-rotate(200deg) saturate(2); +} + /* Actual image container */ #seed .icon2 .inner { filter: invert(1) brightness(0.5) sepia(1) hue-rotate(200deg) saturate(2); @@ -987,7 +686,6 @@ v .transform-view .label { width: 100%; height: 100%; background-image: url("/src/assets/nool-seed-infinity-only.svg"); - /* background-size: cover; */ background-size: 100% 100%; transition: all 0.3s ease; position: absolute; @@ -1001,25 +699,277 @@ v .transform-view .label { width: calc(4.1232em * 0.5); } #seed .icon2:active { - /* animation: spin 0.4s infinite linear; */ margin: 1em; opacity: 1; transform: rotate(-359deg); } -#seed .icon2:active .inner { - /* animation: spin 0.4s infinite linear; */ - /* filter: invert(1) drop-shadow(0px 0px 8px rgb(0, 93, 52)); */ -} -/*#seed .icon2:hover .inner { -mix-blend-mode: plus-lighter; -transition: all; -transition-duration: 2s; -animation: flare; -animation-duration: 2s; -animation-timing-function: linear; + +/* PREVIEWS PANEL */ + +.previews { + display: flex; + flex-direction: column; + gap: 0.2em; + /* font-size: 0.25em; */ + border-radius: 0.8em 0 0 0.8em; + padding: 0.5em 0 0.5em 0.5em; + border-top: 0.04em solid #d4a4d8; + border-bottom: 0.04em solid #7ae0e6; +} +.previews .node-container { + font-size: 0.25em; + mix-blend-mode: color-burn !important; + filter: saturate(1) opacity(0.5) hue-rotate(277deg) blur(0.2em) !important; +} +.previews .node-container:hover { + filter: saturate(1) brightness(0.1) invert(1) opacity(1) hue-rotate(210deg); + mix-blend-mode: exclusion; +} +.previews .node.comp { + background-color: #005bff26; + transition: all 0.05s ease-in-out; +} +.previews .node-container:hover .node.comp { + box-shadow: none; + background-color: #0000; + transform: none; +} +.previews .id-view { + display: none; +} +#main.setSelect.selected #seed .previews { + mix-blend-mode: color-burn !important; +} + +/* ROOM NODES */ + +.node.room > .node.file { + background: none; + box-shadow: none; + border-left: 0.3px solid #6b19da52; + border-top: none; + border-bottom: none; +} + +.node.䷶ > .head, +.node.䷶ > .node.file > .head { + text-shadow: 0.01em 0.02em 0.02em rgb(212 245 228); +} +.node.䷶.selected > .head, +.node.䷶ > .node.file.selected > .head { + animation-name: none; + color: black; + text-shadow: 0.01em 0.02em 0.02em rgb(32 100 65); +} + +.node.comp.䷶ { + border-radius: 2.7em 0.3em 0.3em 2.7em; + /* display: grid; + grid-auto-flow: unset; + grid-template-columns: fit-content(1em) 1fr; */ + background: none; + box-shadow: -1em 0em 1.5em #34b59516; + border: none; + border-left: 0.3px solid #19dac5; +} +.node.䷶.selected, +.node.䷶ > .node.file.selected { + outline: none; + border-left: 0.07em solid white; +} + +/* NODE LAYOUT: DIGITS */ + +.node.digits .node.digits, +.node.digits > .node.atom { + box-shadow: none; + padding: 0; + border: none; +} + +.node.digits, +.node.digits > .node.atom { + border-radius: 1.1em; + padding: 0; + margin: 0; + background: none; + box-shadow: none; + border-color: #0000; +} + +.node.comp.digits .head { + color: black; + text-shadow: none; + padding: 0; +} + +.node.comp.digits.selected > .head { + filter: brightness(2) !important; +} + +.node:not(.digits):has(.digits) > .digits { + padding-left: 0.15em; + padding-right: 0.15em; +} + +/* NODE LAYOUTS */ + +/* nool 3d */ +/*#stage .node-container.TreeTop .node.comp { + transform: perspective(10em) rotateX(37deg); + background: linear-gradient( 222deg, #32b7bd, #43dddc, #00ffba, #ffb7ec, #7f91ff ); + border-bottom: 0.3px solid rgb(255 0 220 / 53%); + border-top: 1px solid rgb(255 255 255); + border-left: 0.3px solid white; + border-right: 0.3px solid white; }*/ -/* dark mode */ +.node-container.TreeLeft .node.comp { + display: grid; + grid-auto-flow: column; +} +.node-container.TreeLeft .node.comp .head { + grid-column: 1; + grid-row: 1/100; +} + +#seed:has(.LinearPrefix), +#seed:has(.LinearInfix) { + flex-direction: column-reverse; +} + +.node-container.LinearPrefix:has(.selected) .node.atom, +.node-container.LinearInfix:has(.selected) .node.atom, +.node-container.LinearPrefix:has(.selected) .head, +.node-container.LinearInfix:has(.selected) .head { + opacity: 50%; +} + +.node-container.LinearPrefix .node.selected, +.node-container.LinearInfix .node.selected, +.node-container.LinearPrefix .node.selected .node.atom, +.node-container.LinearInfix .node.selected .node.atom, +.node-container.LinearPrefix .node.selected .head, +.node-container.LinearInfix .node.selected .head { + opacity: 100% !important; +} + +.node-container.LinearPrefix .node.comp, +.node-container.LinearInfix .node.comp { + margin: 0; + padding: 0; + border-radius: 1.5em; + box-shadow: none; +} +.node-container.LinearPrefix .node.atom, +.node-container.LinearInfix .node.atom { + border-radius: 1.5em; + border-color: #0000; + box-shadow: none; +} + +.node-container.LinearInfix .node.atom:not(.selected) { + box-shadow: #0000; +} + +.node-container.LinearInfix .node.selected:has(> *), +.node-container.LinearInfix .node.selected:has(> *):hover { + border: 0; + outline: 0.03em solid #ffffff; + box-shadow: 0px 0px 0.6em #ffffff; + background: none; + outline: 0 !important; + box-shadow: none !important; + border-color: #0000 !important; +} + +.node-container.LinearInfix .node.selected > .node { + outline: 2px dotted white; + border-color: #0000 !important; + box-shadow: 7px 7px 15px #008750aa !important; +} + +.node-container.LinearInfix .node:hover { + transform: scale(1.01); +} + +/* HACK: Actual infix ops style: applies to all nodes with 3 children */ +.node-container.LinearInfix .node.comp *:nth-child(1):nth-last-child(3) { + order: 2; +} +.node-container.LinearInfix .node.comp *:nth-child(2):nth-last-child(2) { + order: 1; +} +.node-container.LinearInfix .node.comp *:nth-child(3):nth-last-child(1) { + order: 3; +} + +/* STYLE: ALPHANUMERIC NAMES */ + +#seed.SingleChar .node.comp { + border-left: 0.3px solid rgb(255 0 220 / 53%); + border-right: 0.3px solid rgb(119 198 168); + border-top: none; + border-bottom: none; + border-radius: 0.8em; +} + +#seed.SingleChar .node.comp.digits { + border-color: #ffff; +} + +/* HACKY */ +#seed.SingleChar .node.comp:not(.mask) { + background: none; +} + +#seed.SingleChar .node.atom, +#seed.SingleChar .head { + padding: 0 0.25em 0 0.25em; + font-size: 1.3em; + font-family: monospace; + /* font-weight: 700; */ + border-top: none; + border-bottom: none; + box-shadow: none; +} +#seed.SingleChar .node.atom, +#seed.SingleChar .node.digits .head { + color: black; +} +#seed.SingleChar .head { + color: #9c7bdf; +} +#seed.SingleChar .node.atom.selected, +#seed.SingleChar .node.selected > .head { + animation-name: none; + color: white; + -webkit-text-stroke-width: 0.02em; + -webkit-text-stroke-color: black; +} + +#seed.SingleChar .NoMatch .node.comp { + box-shadow: none; +} + +/* LIGHT MODE*/ + +.Light .transform-arrow { + background-color: #009f46; + text-shadow: 0.01em 0.02em 0.02em rgb(216 248 227); +} +.Light .transform-view .node-container.match > .node:hover { + outline: 2px solid white; +} +.Light .transform-view .pat.node { + box-shadow: 0.05em 0.05em 0.1em #90c2b344, 0.1em 0.1em 0.2em #90c2b344, + 0.2em 0.2em 0.4em #90c2b344, 0.4em 0.4em 0.8em #90c2b344, + 0.8em 0.8em 1.6em #90c2b344, -0.05em -0.05em 0.1em #e0ffe544, + -0.1em -0.1em 0.2em #ebfff744, -0.2em -0.2em 0.4em #d2fff044, + -0.4em -0.4em 0.8em #cff2e644, -0.8em -0.8em 1.6em #d2fff044; +} + +/* DARK MODE */ body:has(#main.Dark) { background-image: linear-gradient( @@ -1069,11 +1019,6 @@ body:has(#main.Dark) { animation-duration: 1s; animation-iteration-count: infinite; } -.Dark .node.selected, -.Dark .node.selected:hover { - /*box-shadow: none; - outline: 1.4px solid #00ffb6;*/ -} .Dark .node.selected:has(.mask) { box-shadow: none; outline: none; @@ -1089,6 +1034,10 @@ body:has(#main.Dark) { inset 0px -2px 2px #a000ff, inset 0px -4px 4px #9d80ff33, inset 0px -8px 8px #9d80ff33, inset 0px -16px 16px #9d80ff33; } +.Dark .transform-arrow { + background-color: #5aae3a; + text-shadow: 0.01em 0.02em 0.02em rgb(8 90 111); +} /* HACKY INTERACTIONS */ .Dark .Emoji #stage .node.mask.✿ { @@ -1114,26 +1063,69 @@ body:has(#main.Dark) { color: black !important; } -::view-transition-old(flip-node-selected), -::view-transition-new(flip-node-selected) { - /*transition: none; - transition: transform 250ms cubic-bezier(0.68, -0.6, 0.32, 1.6) !important;*/ - /*animation-duration: 200ms !important; - transition: transform 250ms cubic-bezier(0.68, -0.6, 0.32, 1.6) !important; - height: auto; +/* SETTINGS RING */ + +#settings-panel { + position: absolute; + right: 0; + top: 0; + display: flex; + gap: 0.25em; +} + +#settings-panel .icon:hover { + opacity: 100%; + filter: invert(0); +} + +#settings-panel .icon { + opacity: 90%; + width: 0.5em; + padding: 0.15em; + filter: invert(1); + position: fixed; + scale: 0.8; +} + +#settings-panel .icon:nth-child(1) { + left: 0; + top: 0; +} + +#settings-panel .icon:nth-child(2) { right: 0; - left: auto; - transform-origin: right center;*/ + bottom: 0; +} + +#settings-panel .icon:nth-child(3) { + left: 0; + bottom: 0; +} + +#settings-panel .icon:nth-child(4) { + right: 0; + top: 0; +} + +#settings-panel .icon:nth-child(5) { + right: 0.75em; + top: 0; +} + +#settings-panel .icon:nth-child(6) { + right: 0em; + top: 0.75em; } + +/* VIEW TRANSITIONS */ + .selected { - /* needs some tlc. dont want internals to show during transition - */ + /* needs some tlc. dont want internals to show during transition */ view-transition-name: flip-node-selected; } #main.setSelect .selected, #main.unsetSelections .selected { - /* needs some tlc. dont want internals to show during transition - */ + /* needs some tlc. dont want internals to show during transition */ view-transition-name: flip-node-selected !important; } @@ -1143,22 +1135,14 @@ body:has(#main.Dark) { #seed #noolbox { view-transition-name: flip-noolbox; } -#seed .previews { - /* view-transition-name: flip-previews; */ -} ::view-transition-group(flip-node-selected) { view-transition-name: selected; - animation-timing-function: easeInBack; /*easeInBack; easeOutBounce;*/ + animation-timing-function: easeInBack; animation-duration: calc(var(--anim-factor) * 0.2s); } -::view-transition-old(flip-node-selected) { -} -::view-transition-new(flip-node-selected) { -} ::view-transition-group(*) { - /*pointer-events: none;*/ animation-duration: calc(var(--anim-factor) * 0.25s); animation-fill-mode: both; animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6); @@ -1167,24 +1151,10 @@ body:has(#main.Dark) { ::view-transition-new(*) { height: 100%; width: 100%; - /*animation-name: none !important;*/ - /*animation-name: none !important; - animation-fill-mode: forwards; - opacity: 0;*/ - /*animation-name: none;*/ } ::view-transition-old(*) { height: 100%; width: 100%; - /*animation-name: none !important;*/ - /*animation-name: none !important;*/ - - /*pointer-events: none;*/ - /*animation-name: none;*/ -} - -::view-transition { - /*pointer-events: none;*/ } .selected-transition * { @@ -1195,10 +1165,20 @@ body:has(#main.Dark) { outline: 0.2em solid red; } -.Light .transform-view .pat.node { - box-shadow: 0.05em 0.05em 0.1em #90c2b344, 0.1em 0.1em 0.2em #90c2b344, - 0.2em 0.2em 0.4em #90c2b344, 0.4em 0.4em 0.8em #90c2b344, - 0.8em 0.8em 1.6em #90c2b344, -0.05em -0.05em 0.1em #e0ffe544, - -0.1em -0.1em 0.2em #ebfff744, -0.2em -0.2em 0.4em #d2fff044, - -0.4em -0.4em 0.8em #cff2e644, -0.8em -0.8em 1.6em #d2fff044; +/* CODE STYLE */ + +.node.code .node.atom { + color: #1c4690; + background: none; + box-shadow: none; + border: none; +} +.node.code .node.atom.selected { + color: white; +} +.node-container.TreeLeft .node.comp.code { + display: flex; + border-radius: 0.7em; + padding: 0; + margin: 0; } diff --git a/src/syntax/Exp.tsx b/src/syntax/Exp.tsx index b4c951e..d9c6575 100644 --- a/src/syntax/Exp.tsx +++ b/src/syntax/Exp.tsx @@ -1,4 +1,6 @@ import * as Node from "./Node"; +import * as Symbols from "../data/Symbols"; +import * as Statics from "../Statics"; export type t = Node.t; export type Exp = t; @@ -11,3 +13,49 @@ export const equals = Node.equals; export const equals_id = Node.equals_id; export const erase = Node.erase; export const depth = Node.depth; + +export const of_atom = (e: Exp): string => (e.t == "Atom" ? e.sym : "headless"); + +export const head = (e: Exp): string => + e.t == "Comp" && e.kids.length > 0 && e.kids[0].t == "Atom" + ? e.kids[0].sym + : "headless"; + +export const head_is = (s: string, e: Exp): boolean => head(e) === s; + +export const head_id = (e: Exp): number | undefined => + e.t == "Comp" && e.kids.length > 0 && e.kids[0].t == "Atom" + ? e.kids[0].id + : undefined; + +const cons = + (base: string) => + (hd: string, tl: Exp): Exp => + comp([atom(base), atom(hd), tl]); + +export const flat = + (base: string) => + (contents: string[]): Exp => + contents.length == 0 + ? atom(base) + : contents + .slice(0, -1) + .reduceRight( + (acc, cur) => cons(base)(cur, acc), + atom(contents.slice(-1)[0]) + ); + +export const is_digits = (e: t) => head_is(Symbols.digit, e); + +export const width = (node: t): number => { + //TODO: Currently special casting on treeleft projection + //TODO: Special case for code below + switch (node.t) { + case "Atom": + return 1; + case "Comp": + return is_digits(node) || Statics.get_code_form(node) != "NotCode" + ? 1 + : Math.max(1, -1 + node.kids.reduce((acc, n) => acc + width(n), 0)); + } +}; diff --git a/src/syntax/ExpToPat.tsx b/src/syntax/ExpToPat.tsx index 5905310..749950e 100644 --- a/src/syntax/ExpToPat.tsx +++ b/src/syntax/ExpToPat.tsx @@ -1,4 +1,4 @@ -import { Exp } from "../syntax/Exp"; +import * as Exp from "../syntax/Exp"; import * as Pat from "../syntax/Pat"; import * as Id from "../syntax/ID"; import * as ToolsExp from "../data/ToolsExp"; @@ -44,7 +44,7 @@ type MapIdToSymbol = Map; type res = "NotTool" | { source: Pat.t; result: Pat.t }; -const extract_id_symbol_list = (e: Exp): pair_of_id_and_symbol[] => { +const extract_id_symbol_list = (e: Exp.t): pair_of_id_and_symbol[] => { switch (e.t) { case "Atom": return [{ id: e.id, symbol: e.sym }]; @@ -58,7 +58,7 @@ const extract_id_symbol_list = (e: Exp): pair_of_id_and_symbol[] => { } }; // like above but instead returning MapIdToSymbol -const extract_id_symbol_map = (e: Exp): MapIdToSymbol => { +const extract_id_symbol_map = (e: Exp.t): MapIdToSymbol => { const res = new Map(); switch (e.t) { case "Atom": @@ -91,18 +91,12 @@ const get_or_fresh_id = (map: map_of_ids, id: number): number => { return Id.mk(); }; -const head_id = (e: Exp): number | undefined => { - if (e.t == "Comp" && e.kids.length > 0 && e.kids[0].t == "Atom") - return e.kids[0].id; - return undefined; -}; - //try to get id from head using above; if it's there, comp id becomes that id + 1000 -const get_of_fresh_comp_id = (map: map_of_ids, e: Exp): number => - get_or_fresh_id(map, head_id(e) ?? Id.mk()) + 1000; +const get_of_fresh_comp_id = (map: map_of_ids, e: Exp.t): number => + get_or_fresh_id(map, Exp.head_id(e) ?? Id.mk()) + 1000; const convert_exp_to_pat_getting_ids_from_map = ( - e: Exp, + e: Exp.t, map: map_of_ids ): Pat.t => { switch (e.t) { @@ -225,7 +219,7 @@ const match_up_consts = ( return res; }; -const convert_inner = (s: Exp, r: Exp): res => { +const convert_inner = (s: Exp.t, r: Exp.t): res => { const source_symbols = extract_id_symbol_map(s); const [source_vars, source_consts] = parition_symbol_map_into_vars_and_consts(source_symbols); @@ -243,7 +237,7 @@ const convert_inner = (s: Exp, r: Exp): res => { /* If the expression is rooted in binop =, treat lhs and rhs as source and result of a transformation */ -export const convert = (e: Exp): res => { +export const convert = (e: Exp.t): res => { if ( e.t == "Comp" && e.kids.length == 3 && diff --git a/src/syntax/Node.tsx b/src/syntax/Node.tsx index 2fa8143..2770c77 100644 --- a/src/syntax/Node.tsx +++ b/src/syntax/Node.tsx @@ -37,6 +37,12 @@ export function map_ids(f: (id: ID.t) => ID.t, e: t): t { } } +export function head(e: t): T | undefined { + return e.t == "Comp" && e.kids.length > 0 && e.kids[0].t == "Atom" + ? e.kids[0].sym + : undefined; +} + /* Zero out all ids */ export function erase(x: t): t { return map_ids((_) => 0, x); @@ -57,6 +63,17 @@ export function depth(node: t): number { } } +export function width(node: t): number { + //TODO: Minus max/-1 special casting on treeleft projection + //TODO: This doesn't work with digits... need to unabstract + switch (node.t) { + case "Atom": + return 1; + case "Comp": + return Math.max(1, -1 + node.kids.reduce((acc, n) => acc + width(n), 0)); + } +} + /* Structural equality modulo ids */ export function equals(a: t, b: t): boolean { switch (a.t) { diff --git a/src/syntax/Pat.tsx b/src/syntax/Pat.tsx index f89bfb4..9298c8b 100644 --- a/src/syntax/Pat.tsx +++ b/src/syntax/Pat.tsx @@ -3,6 +3,7 @@ import * as Node from "./Node"; import * as Exp from "./Exp"; import * as ID from "./ID"; import * as Path from "./Path"; +import * as Symbols from "../data/Symbols"; export type Symbol = { t: "Var"; name: string } | { t: "Const"; name: string }; @@ -266,31 +267,9 @@ export const transform_at_path = ( } }; -export const transform_at_path2 = ( - exp: Exp.t, - pat: Pat, - template: Pat, - path: Path.t -): TransformResult => { - if (path.length === 0) { - return transform(exp, pat, template); - } else { - let [hd, ...tl] = path; - switch (exp.t) { - case "Atom": - return "NoMatch"; - case "Comp": - //TODO: cleanup... - const res = transform_at_path(exp.kids[hd], pat, template, tl); - if (res === "NoMatch") return "NoMatch"; - let kids = exp.kids.map((kid, index) => { - if (index === hd) { - const res = transform_at_path(kid, pat, template, tl); - if (res === "NoMatch") return kid; - else return res; - } else return kid; - }); - return { ...exp, kids }; - } - } -}; +export const head_is = + (sym: string) => + (node: t): boolean => + Node.head(node)?.name === sym; + +export const is_digits = head_is(Symbols.digit); \ No newline at end of file diff --git a/src/view/ExpView.tsx b/src/view/ExpView.tsx index 0f01ffd..ebb5d35 100644 --- a/src/view/ExpView.tsx +++ b/src/view/ExpView.tsx @@ -1,17 +1,18 @@ -import { Component } from "solid-js"; +import { Component, Switch, Match } from "solid-js"; import { For, Show, Index } from "solid-js"; //import Rand from "rand-seed"; import * as Pat from "../syntax/Pat"; -import { Exp } from "../syntax/Exp"; +import * as Exp from "../syntax/Exp"; import * as Action from "../Action"; import * as Path from "../syntax/Path"; import * as Statics from "../Statics"; import * as Stage from "../Stage"; import * as Names from "../Names"; import * as Settings from "../Settings"; +import * as Projector from "../Projector"; type expviewprops = { - node: Exp; + node: Exp.t; info: Statics.InfoMap; selection: Stage.selection; animate: boolean; @@ -19,9 +20,10 @@ type expviewprops = { inject: Action.Inject; mask: Pat.Binding[]; symbols: Settings.symbols; + projectors: Projector.PMap; }; -const setSelect = (props: expviewprops) => (e: Event) => { +const setSelect = (props: expviewprops) => (e: MouseEvent) => { e.preventDefault(); //above modulates whether shake occurs for some reason? e.stopPropagation(); @@ -31,6 +33,97 @@ const setSelect = (props: expviewprops) => (e: Event) => { }); }; +const eff = (props: expviewprops): boolean => { + /* search mask for a binding whose first id is this node's id. + then check if it's a val binding. if so return true. else false. */ + const binding = props.mask.find( + ({ ids: [_, id_stage], t }) => id_stage == props.node.id && t == "Val" + ); + // if binding is undefind rerturn false. else return true. + return binding?.t == "Val" ? false : true; +}; + +const enfold = (props: expviewprops) => (e: MouseEvent) => { + if (e.shiftKey) { + return; + } else { + e.preventDefault(); + e.stopPropagation(); + props.inject({ + t: "Project", + id: props.node.id, + action: "toggleEnfoldCurrent", + }); + } +}; + +const enfoldAction = (props: expviewprops) => (e: MouseEvent) => { + const p = Projector.get(props.projectors, props.node.id); + if (e.shiftKey) { + return; + } else if (p.folded == "Folded") { + props.inject({ + t: "Project", + id: props.node.id, + action: "toggleFoldCurrent", + }); + } else if ( + Projector.has_enfolded(props.projectors, props.node) && + p.folded != "Enfolded" + ) + props.inject({ + t: "Project", + id: props.node.id, + action: "toggleFoldCurrent", + }); + else { + switch (p.folded) { + case "Enfolded": + props.inject({ + t: "Project", + id: props.node.id, + action: "toggleEnfoldCurrent", + }); + break; + // case "Folded": + // props.inject({ + // t: "Project", + // id: props.node.id, + // action: "toggleFoldCurrent", + // }); + // break; + default: + props.inject({ + t: "Project", + id: props.node.id, + action: "toggleEnfoldCurrent", + }); + break; + } + } + e.preventDefault(); + e.stopPropagation(); +}; + +const selectOrFold = (props: expviewprops) => (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + console.log("clicks: " + e.detail); + switch (e.detail) { + case 1: + setSelect(props)(e); + break; + case 2: + case 3: + props.inject({ + t: "Project", + id: props.node.id, + action: "toggleFoldCurrent", + }); + break; + } +}; + const common_clss = ({ node, mask, info, selection }: expviewprops): string => { const { path, depth } = Statics.get(info, node.id); const is_selected = @@ -42,66 +135,113 @@ const common_clss = ({ node, mask, info, selection }: expviewprops): string => { return `node ${is_selected ? "selected" : ""} ${mask_cls} depth-${depth}`; }; -const ExpViewGo: Component = (props) => { - const eff = (props: expviewprops): boolean => { - /* search mask for a binding whose first id is this node's id. - then check if it's a val binding. if so return true. else false. */ - const binding = props.mask.find( - ({ ids: [_, id_stage], t }) => id_stage == props.node.id && t == "Val" - ); - // if binding is undefind rerturn false. else return true. - return binding?.t == "Val" ? false : true; +const is_code = (info: Statics.InfoMap, node: Exp.t): boolean => + Statics.get(info, node.id).cls.t == "Code"; + + const is_file = (info: Statics.InfoMap, node: Exp.t): boolean => + Statics.get(info, node.id).cls.t == "File"; + + const is_room = (info: Statics.InfoMap, node: Exp.t): boolean => + Statics.get(info, node.id).cls.t == "Room"; + +const is_head = + (info: Statics.InfoMap, node: Exp.t) => + (i: number): boolean => { + const cls = Statics.get(info, node.id).cls; + if (cls.t == "Code") { + return Statics.is_delim(cls.form, i); + } else { + return i === 0; + } }; + +const ExpViewGo: Component = (props) => { switch (props.node.t) { case "Atom": return ( - -
{Names.get(props.symbols, props.node.sym)}
+
+ {Names.get(props.symbols, props.node.sym)} +
} > -
- {Names.get(props.symbols, props.node.sym)} -
- + +
+ {Names.get(props.symbols, props.node.sym)} +
+
+ ); case "Comp": return ( -
- { - - {(kid, i) => ( - - )} - - } -
+ error}> + +
+ { + + {(kid, i) => ( + + )} + + } +
+
+
); } }; @@ -115,16 +255,17 @@ export const ExpView: Component<{ ExpViewGo({ info: props.stage.info, selection: props.stage.selection, - node: props.stage.exp, + node: Projector.project_folds(props.stage.projectors, props.stage.exp), inject: props.inject, mask: props.mask, is_head: false, animate: true, symbols: props.symbols, + projectors: props.stage.projectors, }); export const ViewOnly: Component<{ - node: Exp; + node: Exp.t; symbols: Settings.symbols; }> = (props) => ExpViewGo({ @@ -136,4 +277,5 @@ export const ViewOnly: Component<{ inject: (_) => {}, mask: [], symbols: props.symbols, + projectors: Projector.init, }); diff --git a/src/view/SeedView.tsx b/src/view/SeedView.tsx index b08c6a2..31fcff9 100644 --- a/src/view/SeedView.tsx +++ b/src/view/SeedView.tsx @@ -30,8 +30,9 @@ export const Seed: Component<{ model: Model.t; inject: Action.Inject }> = ( noanimation: props.model.settings.motion === "Off", notransformation: props.model.settings.motion === "Off", }} - onmousedown={(e) => { + onclick={(e) => { e.preventDefault(); + e.stopPropagation(); props.inject({ t: "unsetSelections" }); }} > diff --git a/src/view/StageView.tsx b/src/view/StageView.tsx index c9ca4c4..4441f39 100644 --- a/src/view/StageView.tsx +++ b/src/view/StageView.tsx @@ -4,9 +4,16 @@ import { Model } from "../Model"; import * as Action from "../Action"; import { ExpView } from "./ExpView"; import * as Hover from "../Hover"; -import { depth } from "../syntax/Node"; +import * as Stage from "../Stage"; -const stage_scale = (d: number) => (d == 0 ? 1 : 4 / (d + 1)); +const stage_scale = (w: number) => { + /*TODO: magic numbers */ + if (w < 12) { + return "0.8em"; + } else { + return `${100 / 1.85 / w}vh`; + } +}; export const StageView: Component<{ model: Model; @@ -14,7 +21,9 @@ export const StageView: Component<{ }> = (props) => (