Edits and rebasing
A drawing can have more than one writer. A person draws in the editor on screen while an agent adds or changes elements. The second writer sends an edit by id instead of a whole document, and the editor rebases its unsaved edits when a newer document arrives. Both live on @adecore/drawing.
Editing by id
import { DrawingEditSchema, applyDrawingEdit } from '@adecore/drawing';
const edit = DrawingEditSchema.parse({
add: [{ kind: 'ellipse', x: 620, y: 40, w: 90, h: 90, stroke: 'orange', fill: 'solid', fillColor: 'yellow' }],
update: [{ id: 'box', stroke: 'blue' }],
remove: ['arrow']
});
const outcome = applyDrawingEdit(drawing.elements, edit, { id: () => crypto.randomUUID(), seed: () => Math.floor(Math.random() * 2 ** 31) });DrawingEditSchema (type DrawingEdit) has three optional lists:
addtakesNewDrawingElementSchema(NewDrawingElement), an element withoutid,seedandlocked.strokedefaults toink,strokeWidthto2, and a text or notesizeto20.updatetakesDrawingElementPatchSchema(DrawingElementPatch), anidand the fields that change. The kind stays.removetakes ids.
Every level is strict, so a misspelled field fails on its path. Parse what an agent sends with the schema, and pass the problems back so it can correct its next edit.
The outcome
applyDrawingEdit returns a DrawingEditOutcome. That is { ok: true, elements, added } with the new ids in order, or { ok: false, problems }, a list of DrawingEditProblem with a path and a message:
applyDrawingEdit(elements, { update: [{ id: 'arrow', text: 'Next' }] }, mint);
// { ok: false, problems: [{ path: ['update', 0, 'text'], message: 'arrow is a line, which has no field text' }] }It applies all or nothing. These are problems:
- An unknown, locked or repeated id.
- A field of another kind, such as
texton arect. - An edit beyond
DRAWING_EDIT_LIMITS: more than 200 elements touched in one edit, or a drawing left with more than 5000 elements.
Updated elements keep their stacking place; added ones go on top. The DrawingEditMint makes the id and seed of each new element. Its ids must not be ones the drawing already holds.
Rebasing unsaved edits
When a newer document arrives before the editor saved its own edits, the editor puts its edits on top by id.
import { rebaseDrawingElements } from '@adecore/drawing';
const outcome = rebaseDrawingElements(saved.elements, onScreen.elements, incoming.elements);
if (outcome.ok) {
show(outcome.elements);
} else {
askAbout(outcome.conflicts);
}rebaseDrawingElements(base, mine, theirs) returns a DrawingRebase: { ok: true, elements }, or { ok: false, conflicts } with the ids both sides changed. base is the document both sides started from. A change on one side wins; the same change on both sides is no conflict. The stacking order follows the side that changed it, or theirs when neither or both did.