function undo(state, context, event, options) { let need_draw = false; // Remove effect of latest own event, in a way that is recoverable // Iterate back to front to find the _latest_ event for (let i = state.events.length - 1; i >=0; --i) { const other_event = state.events[i]; let skipped = false; // Users can only undo their own, undeleted (not already undone) events if (other_event.user_id === event.user_id && !other_event.deleted) { // All "persistent" events (those that are pushed using SYN messages) should be handled here // "Transient" events are by design droppable, and should not be undone, nor saved in state.events at all switch (other_event.type) { case EVENT.STROKE: { other_event.deleted = true; if (other_event.bvh_node && !options.skip_bvh) { bvh_delete_stroke(state, other_event); } need_draw = true; break; } case EVENT.UNDO: { // do not undo an undo, we are not Notepad skipped = true; break; } case EVENT.IMAGE: { other_event.deleted = true; const image = get_image(context, other_event.image_id); if (image !== null) { image.deleted = true; } need_draw = true; break; } case EVENT.IMAGE_MOVE: { other_event.deleted = true; const image = get_image(context, other_event.image_id); if (image !== null) { image.move_head -= 2; image.at.x = image.move_history[image.move_head - 2]; image.at.y = image.move_history[image.move_head - 1]; need_draw = true; } else { console.warning('Undo image move for a non-existent image'); } break; } case EVENT.IMAGE_SCALE: { other_event.deleted = true; const image = get_image(context, other_event.image_id); if (image !== null) { image.scale_head -= 4; // NEXT: merge move and scale. Otherwise we can't know // that there have been move events inbetween scale image.at.x = image.scale_history[image.scale_head - 4]; image.at.y = image.scale_history[image.scale_head - 3]; image.width = image.scale_history[image.scale_head - 2]; image.height = image.scale_history[image.scale_head - 1]; need_draw = true; } else { console.warning('Undo image scale for a non-existent image'); } break; } case EVENT.ERASER: { other_event.deleted = true; const stroke = state.events[other_event.stroke_id]; stroke.deleted = false; if (!options.skip_bvh) { bvh_undelete_stroke(state, stroke); } need_draw = true; break; } default: { console.error('cant undo event type', other_event.type); break; } } if (!skipped) { break; } } } return need_draw; } function redo() { console.log('TODO'); }