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) { pop_image_transform(image); 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) { pop_image_transform(image); 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'); } function push_image_move(image, x, y) { if (image.transform_head < image.transform_history.length) { image.transform_history[image.transform_head] = image.at.x; image.transform_history[image.transform_head + 1] = image.at.y; image.transform_history[image.transform_head + 2] = image.width; image.transform_history[image.transform_head + 3] = image.height; } else { image.transform_history.push(image.at.x, image.at.y, image.width, image.height); } image.at.x = x; image.at.y = y; image.transform_head += 4; } function push_image_scale(image, corner, x, y) { if (image.transform_head < image.transform_history.length) { image.transform_history[image.transform_head] = image.at.x; image.transform_history[image.transform_head + 1] = image.at.y; image.transform_history[image.transform_head + 2] = image.width; image.transform_history[image.transform_head + 3] = image.height; } else { image.transform_history.push(image.at.x, image.at.y, image.width, image.height); } scale_image(image, corner, {'x': x, 'y': y}); image.transform_head += 4; } function pop_image_transform(image, corner, x, y) { image.transform_head -= 4; image.at.x = image.transform_history[image.transform_head - 4]; image.at.y = image.transform_history[image.transform_head - 3]; image.width = image.transform_history[image.transform_head - 2]; image.height = image.transform_history[image.transform_head - 1]; }