notesy::drawings

Drawings are .excalidraw files: shapes, arrows, lines, strokes of the pen and words, as Excalidraw (https://excalidraw.com) draws and saves them. notesy opens one in a tab to draw on, and shows it in a note that embeds it (![[Sketch.excalidraw]]). A plugin's script can read and change drawings, put buttons in a drawing's tool strip (notesy::zones), and hear when one is opened or saved (notesy::events). Shapes ready made, for the Stamps list, need no script at all: see [[stamps]].

rustuse notesy::{drawings, zones, Zone};

pub fn ready() {
    zones::add(Zone::DrawingTray, "tint", #{ title: "Make what's picked red", icon: "highlight" }, |drawing| {
        for id in drawing.selected {
            drawings::update(drawing.path.clone(), id, #{ backgroundColor: "#ffc9c9", fillStyle: "solid" });
        }
    });
}

#Elements

A drawing is #{ elements, background }: its elements bottom first, as Excalidraw writes them, and its paper's color when it has one of its own. Each element has id, type (rectangle, diamond, ellipse, arrow, line, freedraw, text or image), x, y, width, height, angle (radians), strokeColor, backgroundColor ("transparent" for none), fillStyle (hachure, cross-hatch or solid), strokeWidth, strokeStyle (solid, dashed or dotted), roughness (0 for straight lines, 1, or 2 for the roughest), opacity (0 to 100), groupIds, locked, and by its type: points (a line's or an arrow's, [x, y] pairs from its x and y), startArrowhead and endArrowhead (arrow, bar, dot, triangle), startBinding and endBinding (#{ elementId, focus, gap }: what an arrow is joined to); a text's text, fontSize, fontFamily (5 for Excalidraw's hand, 2 for plain, 3 for code), textAlign and containerId (the shape it's written in); and boundElements, the words and arrows joined to a shape ([#{ id, type }]). Fields another app added are kept as they are, and deleted ones are left out. A picture's data isn't read.

What an element a script adds leaves out is filled in: an id, how it's drawn by hand (its seed), a line's points from its width and height, and a text's size from its words. A field it gives that notesy doesn't know of is kept, for Excalidraw. One it does know, of the wrong kind (an x that's words, text that's a number), is refused, and so are two elements given one id.

Drawings are named by their path in the vault, with or without .excalidraw: "Sketch", "Plans/Flow.excalidraw". A path starting at / starts at the vault's top.

Function Takes What it does
drawings::read(path) notes.read The drawing, #{ elements, background }; None when there's none
drawings::get(path, id) notes.read Element id of the drawing; None when there's no such element
drawings::current() notes.read The drawing in front, #{ path, selected, visible }: the ids of what's picked, and what of it is in view, #{ x, y, width, height } in its own units; None when a drawing isn't in front
drawings::open(path) notes.read Opens the drawing in a tab, in front
drawings::pick(path, ids) notes.read Picks what ids name (an id, or a list) in the drawing's tab, and nothing else; the drawing has to be open
drawings::write(path, drawing) notes.write Puts drawing's elements (#{ elements }, and background when it says) in place of what's there; makes the drawing when there's none
drawings::add(path, elements) notes.write Adds an element, or a list of them (at most 2,000 at once), and returns its id, or the list of them
drawings::update(path, id, fields) notes.write Puts fields over those of element id; () takes a field away. Words in a shape follow it
drawings::remove(path, ids) notes.write Takes an element away, or a list of them by id, and the words in them; arrows and shapes bound to them let go

A change to a drawing that's open shows at once and is one step the user can undo; one that isn't open is written to its file. To change many elements as one step, read the drawing, change what was read, and write it back: words in a shape are put back in its middle, wherever it went.

#A button of its own in a drawing's tool strip

A plugin's buttons go at the end of every drawing's tool strip, after a rule, each an icon with its title as its tip.

rustuse notesy::{drawings, notices, zones, Zone};

pub fn ready() {
    let count = zones::add(Zone::DrawingTray, "count", #{ title: "Count what's picked", icon: "number" }, |drawing| {
        notices::info(`${drawing.selected.len()} picked in ${drawing.path}`);
    });
    // Right-clicking the button opens what's put in its own menu.
    count.menu("all", "Count everything", || {
        if let Some(drawing) = drawings::current() {
            if let Some(all) = drawings::read(drawing.path) {
                notices::info(`${all.elements.len()} in all`);
            }
        }
    });
}
Function What it does
zones::add(Zone::DrawingTray, key, options, run) Adds a button to every drawing's tool strip, and returns it (notesy::zones): options as any place's entry, the title its tip. run(drawing) gets #{ path, selected }: the drawing in the vault, and the ids of what's picked.

button.menu(key, options, run) puts items in the button's own right-click menu (notesy::sections), whose run() takes nothing: drawings::current() says which drawing it was. Taking the button out (remove()) takes it out of every drawing. It takes the ui permission.

#Hearing about drawings

Event::DrawingOpened (drawing.opened) when one is opened in a tab, and Event::DrawingSaved (drawing.saved) when its tab writes it to its file, each with its path; Event::AnyDrawing (drawing.*) for both. They take notes.read (notesy::events).

#An example

examples/plugins/example.flowchart adds flowchart shapes to the Stamps list from an Excalidraw library, a button that lines up the shapes picked in a row (or, from its menu, a column), one that joins them with arrows bound to both ends, and "New flowchart" in the palette.

Every page