-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix select all and change ordinal in edit mode in io #3109
Conversation
I gave this a quick test, and it seems to fix the problem for me in both edit mode and the add screen. |
Initial test works, but after adding some notes with group mode then again in edit mode, ungroup last shapes then group some other shapes. The ordinal seems to change and causing error something like No cloze found on number. |
dc0eb14
to
49d6c38
Compare
The solution after changing group item can be fixed by introducing a new method which checks for ordinal number in cloze strings and reorder, the issue also fixed by Edit: |
Sorry Mani, could I trouble you to explain the issue in more detail? If a user reduces the number of occlusions, I'd expect that to break one or more of their cards. It would be nice if we could display a useful message during review in that case, like we do when there's an empty cloze. But I don't think we need to be cleaning up the cards until the user uses Tools>Empty Cards. |
In this demo, first I have created group shapes cloze, then ungroup it in edit mode, after that again group the shapes, it needs to remove empty cards. output.mp4 |
The ordinal is undefined for all shapes when group/ungroup tools used, so that the cloze number should be in order. |
Hmm, this doesn't seem to work - if I add two rectangles to a new card, then edit the card and select all+group, the two shapes stay disconnected when the editor is closed. |
Now it will work as expected, but the users still have to use I have used, to fix I have to use
|
I've pushed a fix for the dprint error. Tests aren't currently passing, but seems to work well when I tried the grouping operation again with the latest code. |
5ea6334
to
49cd788
Compare
Thanks, I have rebased it to main. |
@@ -166,7 +168,7 @@ function shapeOrShapesToCloze( | |||
|
|||
// Maintain existing ordinal in editing mode | |||
let ordinal = shapeOrShapes.ordinal; | |||
if (ordinal === undefined) { | |||
if (ordinal === undefined || get(groupItemsModified)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can break the ordinals of ungrouped masks when a grouping operation is done on other masks in edit mode.
Can we instead update the ordinals of grouped objects when they are grouped?
For example, If we group objects with the ordinals 2 and 4, all objects in the group will be assigned a single ordinal (maybe the smallest one, so 2).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like a good way of decreasing the number of empty cards we end up producing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, because let's say a user have created group cloze with more than two shapes as single group cloze, then during review he edited the shape to ungroup to individual shapes. Now total cards increased from one to total number of shapes. Now again during review he split it into two group, now ordinal must start from beginning to have consistent result, (Card 1 and Card 2)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can solve this for all situations, especially if the user is closing and opening the editor between changes. I'd assumed that if we give new ordinals to shapes when they're removed from a group, that would be sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The solution in this PR should be accepted because if user modified the group items, there needs to implement to change ordinal for group items only, needs to keep track of group items, so instead of handling those cases, we should warn user that group and ungroup of shapes leads to changes of ordinal so refrain from doing this. But if no group shapes are touched the ordinal also does not modified.
I'd assumed that if we give new ordinals to shapes when they're removed from a group, that would be sufficient?
I am implementing and testing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to check we're on the same page - would you like this PR merged in without the changes that Abdo suggested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have thought of implementation idea for Abdo suggestion, I have implemented it in next commit, and it seems, it also solves the empty card issues. But it still needs some review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Users need to do Tools -> Empty Cards
, I have tried to solve but once cards added, the ordinal decreases but Cards remains same, so it needs to be fixed separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm not quite following - what do you think needs to be done differently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing, I was implementing different solution when ungroup shapes. In case of ungroup shapes, find all the ordinal from shapes excluding groups shapes then start loop from beginning and put the ordinal to shapes in order. But I implemented simplest solution, use max ordinal (increment if required).
d330ed2
to
05bd940
Compare
@@ -63,12 +63,19 @@ export const groupShapes = (canvas: fabric.Canvas): void => { | |||
|
|||
const activeObject = canvas.getActiveObject() as fabric.ActiveSelection; | |||
const items = activeObject.getObjects(); | |||
|
|||
// @ts-expect-error not defined | |||
const minOrdinal = Math.min(...items.map((item) => item.ordinal)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that might be nice in the future (not in this PR) is to tell TypeScript about the additional properties we've added to these objects, so we don't need to ignore errors/cast them to any each time we access them. I think it may be possible in a .d.ts file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done, I will look into this.
Thanks Mani, this seems to work well. @abdnh, any thoughts/comments? If not, I'll merge this in tomorrow and get a 24.04.1rc out. |
|
||
items.forEach((item, index) => { | ||
// @ts-expect-error not defined | ||
item.set({ opacity: get(opacityStateStore) ? 0.4 : 1, ordinal: maxOrdinal + index + 1 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not working as expected. It can result in two unrelated masks getting the same ordinal, causing them to be grouped. To reproduce:
- Add 3 masks
- Group two of the masks
- Ungroup
- Reload the note (e.g. by selecting another browser note then back to the note)
- Notice one mask from the original group is grouped with the third.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated the implementation, it should work now, but order cannot be maintained in case of ungrouping of shapes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that Abdo! And thank you Mani for your continued work on this. I gave it a quick try again and couldn't seem to trip it up. When you say the order can't be maintained, I wonder if that's a problem? As long as we're not reusing ordinals, what problems do you think it would cause?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, we can go with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dae While the issue I reported is fixed, this is re-introducing the original problem fixed by #2987 when there's an ungroup operation. Unrelated masks could get reordered.
This appears to work better:
diff --git a/ts/routes/image-occlusion/shapes/to-cloze.ts b/ts/routes/image-occlusion/shapes/to-cloze.ts
index 01d8b62c9..fbdd03f07 100644
--- a/ts/routes/image-occlusion/shapes/to-cloze.ts
+++ b/ts/routes/image-occlusion/shapes/to-cloze.ts
@@ -3,9 +3,7 @@
import { fabric } from "fabric";
import { cloneDeep } from "lodash-es";
-import { get } from "svelte/store";
-import { shapeUngroupState } from "../store";
import { getBoundingBox } from "../tools/lib";
import type { Size } from "../types";
import type { Shape, ShapeOrShapes } from "./base";
@@ -22,6 +20,7 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
let clozes = "";
let index = 0;
+ let nextOrdinal = 1;
shapes.forEach((shapeOrShapes) => {
// shapes with width or height less than 5 are not valid
if (shapeOrShapes === null) {
@@ -31,7 +30,33 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
if (shapeOrShapes instanceof Rectangle && shapeOrShapes.fill === "transparent") {
return;
}
- clozes += shapeOrShapesToCloze(shapeOrShapes, index, occludeInactive);
+ // Maintain existing ordinal in editing mode
+ let ordinal: number | undefined;
+ if (Array.isArray(shapeOrShapes)) {
+ ordinal = shapeOrShapes[0].ordinal;
+ } else {
+ ordinal = shapeOrShapes.ordinal;
+ }
+ if (ordinal === undefined) {
+ if (shapeOrShapes instanceof Text) {
+ ordinal = 0;
+ } else {
+ ordinal = nextOrdinal;
+ }
+ if (Array.isArray(shapeOrShapes)) {
+ shapeOrShapes.forEach((shape) => (shape.ordinal = ordinal));
+ } else {
+ shapeOrShapes.ordinal = ordinal;
+ }
+ }
+ nextOrdinal = ordinal + 1;
+
+ clozes += shapeOrShapesToCloze(
+ shapeOrShapes,
+ index,
+ ordinal,
+ occludeInactive,
+ );
if (!(shapeOrShapes instanceof Text)) {
index++;
}
@@ -134,6 +159,7 @@ function fabricObjectToBaseShapeOrShapes(
function shapeOrShapesToCloze(
shapeOrShapes: ShapeOrShapes,
index: number,
+ ordinal: number,
occludeInactive: boolean,
): string {
let text = "";
@@ -145,7 +171,7 @@ function shapeOrShapesToCloze(
let type: string;
if (Array.isArray(shapeOrShapes)) {
return shapeOrShapes
- .map((shape) => shapeOrShapesToCloze(shape, index, occludeInactive))
+ .map((shape) => shapeOrShapesToCloze(shape, index, ordinal, occludeInactive))
.join("");
} else if (shapeOrShapes instanceof Rectangle) {
type = "rect";
@@ -166,16 +192,6 @@ function shapeOrShapesToCloze(
addKeyValue("oi", "1");
}
- // Maintain existing ordinal in editing mode
- let ordinal = shapeOrShapes.ordinal;
- if (ordinal === undefined || Number.isNaN(ordinal) || get(shapeUngroupState)) {
- if (type === "text") {
- ordinal = 0;
- } else {
- ordinal = index + 1;
- }
- shapeOrShapes.ordinal = ordinal;
- }
text = `{{c${ordinal}::image-occlusion:${type}${text}}}<br>`;
return text;
diff --git a/ts/routes/image-occlusion/store.ts b/ts/routes/image-occlusion/store.ts
index b1653f4fa..5847250dd 100644
--- a/ts/routes/image-occlusion/store.ts
+++ b/ts/routes/image-occlusion/store.ts
@@ -17,5 +17,3 @@ export const ioImageLoadedStore = writable(false);
export const opacityStateStore = writable(false);
// store state of text editing
export const textEditingState = writable(false);
-// store state of shape ungroup
-export const shapeUngroupState = writable(false);
diff --git a/ts/routes/image-occlusion/tools/lib.ts b/ts/routes/image-occlusion/tools/lib.ts
index 9a6738b10..bc0606cdc 100644
--- a/ts/routes/image-occlusion/tools/lib.ts
+++ b/ts/routes/image-occlusion/tools/lib.ts
@@ -4,7 +4,7 @@
import { fabric } from "fabric";
import { get } from "svelte/store";
-import { opacityStateStore, shapeUngroupState } from "../store";
+import { opacityStateStore } from "../store";
export const SHAPE_MASK_COLOR = "#ffeba2";
export const BORDER_COLOR = "#212121";
@@ -94,11 +94,14 @@ export const unGroupShapes = (canvas: fabric.Canvas): void => {
canvas.remove(group);
items.forEach((item) => {
- item.set({ opacity: get(opacityStateStore) ? 0.4 : 1 });
+ item.set({
+ opacity: get(opacityStateStore) ? 0.4 : 1,
+ // @ts-expect-error not defined
+ ordinal: undefined,
+ });
canvas.add(item);
});
- shapeUngroupState.set(true);
redraw(canvas);
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit:
It needs to check Number.isNaN(ordinal)
, working now. Thanks Abdo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch @abdnh! And thanks for the quick fix @krmanik.
Unfortunately I ran into another issue. I created three rectangles and merged them into a group, then created three more, and left them ungrouped. I added the note. Then I edited it, and ungrouped the three rectangles. Two surprises:
- The group's ordinal was not reused, so the first card is now empty and needs to be cleaned up with tools>empty cards
- One of the shapes in the group was given the same ordinal as one of the shapes that was never in a group, so it ended up in a new group.
05bd940
to
2511c57
Compare
I was trying to implement this, but it works sometimes and sometimes does not, but the simplest solution will be just use svelte store only for ungroup shapes which works now. Codediff --git a/ts/routes/image-occlusion/tools/lib.ts b/ts/routes/image-occlusion/tools/lib.ts
index f7d00846c..38fccd703 100644
--- a/ts/routes/image-occlusion/tools/lib.ts
+++ b/ts/routes/image-occlusion/tools/lib.ts
@@ -93,18 +93,53 @@ export const unGroupShapes = (canvas: fabric.Canvas): void => {
group.destroyed = true;
canvas.remove(group);
- // @ts-expect-error not defined
- const maxOrdinal = Math.max(canvas.getObjects().map((item) => item.ordinal));
+ // take ordinals for all shape excluding the ones in the group
+ const excludedObjectOrdinals = getCanvasObjects(canvas)
+ .filter((obj) => !items.includes(obj))
+ .map((item) => item.ordinal)
+ .filter((ordinal) => ordinal !== undefined || !Number.isNaN(ordinal));
+
+ console.log("excludedObjectOrdinals", excludedObjectOrdinals);
+
+ const totalShapes = getCanvasObjects(canvas).length;
+
+ console.log("totalShapes", totalShapes);
+
+ const newOrdinal = [];
+ for (let i = 1; i < totalShapes; i++) {
+ if (!excludedObjectOrdinals.includes(i)) {
+ newOrdinal.push(i);
+ }
+ }
+
+ console.log("newOrdinal", newOrdinal);
items.forEach((item, index) => {
// @ts-expect-error not defined
- item.set({ opacity: get(opacityStateStore) ? 0.4 : 1, ordinal: maxOrdinal + index + 1 });
+ item.set({ opacity: get(opacityStateStore) ? 0.4 : 1, ordinal: newOrdinal[index] });
canvas.add(item);
});
redraw(canvas);
};
+const getCanvasObjects = (canvas) => {
+ const objectList = [];
+ canvas.forEachObject(function(object) {
+ if (object.type === 'group') {
+ const group = object;
+ group.forEachObject(function(obj) {
+ objectList.push(obj);
+ });
+ } else {
+ objectList.push(object);
+ }
+ });
+
+ // filter out the transparent bounding box from the selection
+ return objectList.filter((obj) => obj.fill !== "transparent");
+}
+
const copyItem = (canvas: fabric.Canvas): void => {
const activeObject = canvas.getActiveObject();
if (!activeObject) {
|
@abdnh any other thoughts, or happy for this to be merged in? |
2511c57
to
d134dde
Compare
@@ -21,6 +21,9 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): { | |||
let clozes = ""; | |||
let index = 0; | |||
let nextOrdinal = 1; | |||
|
|||
console.log(shapes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this console log seems to fix the issue, so is the issue race condition? Then may be we should use promise and resolve or settimeout. (It works when Ankiweb inspect opened)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid for me, the same issue still happens when I update to the latest commit which has this console.log call. I tried 3 times, and it happened on the 1st and 2nd time, but not the 3rd. If you try a few more times, does it ever happen? Could it be something other than a race condition, such as unpredictable dictionary ordering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't understand the underlying issues, so I have changed the implementation, it works as expected. The general idea is takeout ordinal which does not assign to any shape from a list of ordinal and assign to shape.
cf431a7
to
458c9e1
Compare
The order of shapes in group issue is resolved now in latest commit. |
Great, thanks Mani. Will review this tomorrow. Abdo, if you have a chance
before then, your input would be appreciated.
…On Wed, 10 Apr 2024 at 5:34 pm, Mani ***@***.***> wrote:
The issue is resolved now in latest commit.
—
Reply to this email directly, view it on GitHub
<#3109 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABMCPRKYCPV5NO5AROFRIDY4UISJAVCNFSM6AAAAABFROZJ32VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBXGE3TANJZGQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Thanks Mani, this seems to work well, and if there are any issues with the code, I didn't notice them. ^_^; I'm going to merge this in now so I can get a beta out today, but if you have any concerns @abdnh, please let us know. |
* fix select all and change ordinal in edit mode in io * make ordinal undefined for all shapes in group/ungroup * fix group shapes and some ui fixes * Don't add node_modules/* to dprint deps * use minimum ordinal when shape merged, use max ordinal++ when ungrouped, in add mode no ordinal preset so NaN * use state for ungroup shape * maintain existing ordinal in editing mode * fix order of ordinal in ungroup shape * refactor: remove for loop, use forEach (cherry picked from commit 477f932)
Looking good to me. Didn't notice any issues either. |
The edit mode needs careful review, I have come up with the solution, but it may need improvement.
@abdnh pinging for review.
Closes #3109