Skip to content

Conversation

@simonasarvasova
Copy link
Contributor

Introducing layer hierarchy, synced multi-selection, selection import/export, and renewed fixed board edit mode.

@simonasarvasova simonasarvasova requested a review from Aiosa October 28, 2025 07:06
@gemini-code-assist
Copy link

Important

Installation incomplete: to start using Gemini Code Assist, please ask the organization owner(s) to visit the Gemini Code Assist Admin Console and sign the Terms of Services.

Copy link
Member

@Aiosa Aiosa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, address the concerns in my comments, and also please go thgough the functionality after the merge and fix bugs. I tried to get the selection back as much as possible, but it is not polished. For example, object deselection from group misbehaves.

if (!options.exportsObjects) return undefined;
let objs = context.toObject(exportAll, ...exportedProps).objects;

const ids = options?.filter?.ids;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please fix the jsdoc for options to now cover all the options we might use? It was not properly documented, but since it is getting more complex (it had only flags up till now), I want a proper docs,,

@@ -0,0 +1,2 @@
/*! Sortable 1.15.6 - MIT | git://github.com/SortableJS/Sortable.git */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a module? Annotations would depend on the module, but other parts of the system could also use it. Later, we might even want to move the dependency away from annotations module and just have the UI logics in the annotations-gui.

objects[i].lockScalingY = false;
objects[i].lockUniScaling = false;
}
if (this.cachedTargetCanvasSelection) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cachedTargetCanvasSelection looks very much like a private prop, should start with _. If not, I am missing any documentation on usage...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says outdated but the code just moved, the issue still persists.

this._layer.setActive(false);
this._layer = undefined;

this.raiseEvent('active-layer-changed', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs

return;
}
const objs = ids
.map(id => this.findObjectOnCanvasByIncrementId(Number(id)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very expensive call, prefer using array of objects as input argument

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says outdated but the code just moved, the issue still persists.

: (annotationIds !== undefined && annotationIds !== null ? String(annotationIds) : annotationIds);

// todo selection event collision
this.raiseEvent('annotation-selection-changed', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass around arrays of objects, not ids, mapping object -> id is much cheaper. Also, prefer using selected and deselected, instead of 'isSelected' flag.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says outdated but the code just moved, the issue still persists.


const handleDeselectionFromCanvas = (e) => {
const ids = (e?.deselected || []).map(obj => obj.incrementId);
if (ids.length) _this._emitAnnotationSelectionChanged(ids, false, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should fiure out how to prevent re-insertion, right now mouse click internally fires deselection, and then our logics fires selection... a bit too expensive

Copy link
Member

@Aiosa Aiosa Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what might be problem:

  • object is selected
  • we click on different object
    • first deselection event here is called because fabric swaps the selection
    • then objectClicked is called, we change selection agan (potentially)
    • again deselection event here is called (e.g. active selection created)
      So it would be good to add console logs to inspect whether these events are not over-firing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says outdated but the code just moved, the issue still persists.

if (withObjects) {
scope = this.exportOptions.scope === 'selected' ? 'selected' : 'all';
if (scope === 'selected') {
const selectedAnns = (this.context.getSelectedAnnotations?.() || []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logics should be within the convertor. You should send only flags, options, but the logic itself should be part of convertor API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says outdated but the code just moved, the issue still persists.

@Aiosa
Copy link
Member

Aiosa commented Nov 12, 2025

Okay, I fixed few more things, and added more changes that later simplify the transfer to multiviewport sessions. Some final remarks:

  • avoid VIEWER global prop access, get the viewer reference from the OSDAnnotations module, which keeps track of the active reference for you (there will be more viewers in v3)
  • correctly differentiate whether you need to work with presets, factories (OSDAnnotations) or the annotation objects (new class OSDAnnotations.FabricWrapper available as OSDAnnotations.instance().fabric )

I don't also know why selection is driven by increment id instead of internal one - increment changes wildly, while internal stays consistent -> such selection is more stable (e.g. wrt. history undo/redo). Also, remove all the calls to findObjectOnCanvasByIncrementId where not necessary. You rely on this too much and eagerly throw away objects instead of passing the actual objects as arguments. Each call iterates the whole canvas so you have quadratic complexity even on such simple things like annotation selection when you even have the reference on hand. This method should be used only where you cannot keep the object reference, e.g. strings in history UI that reference the object.

Copy link
Member

@Aiosa Aiosa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor issues with deletion of large amount of objects.

if (activeObject.type === 'activeSelection') {
activeObject.getObjects().forEach(obj => {
// todo this overloads history, needs to do in one step
this.deleteObject(obj);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the deletion will overload history with unit operations, we need single undo/redo. I propose that deleteObject supports active selection as well and deletes it as a whole within history steep.

*/
deleteObject(o, _raise=true) {
// this._deletedObject = o;
if (this.isAnnotation(o)) return this.deleteAnnotation(o, _raise);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must also use history, changed to non-private call. I would support active selection or array of objects, or even fabric canvas reference. It would delete anything that is thrown at it and ensure the deletion is a single histor step.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe even accept layer as argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants