mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
fix select all and change ordinal in edit mode in io (#3109)
* 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
This commit is contained in:
parent
da4551e351
commit
477f932f35
5 changed files with 103 additions and 36 deletions
|
@ -292,7 +292,7 @@ fn build_and_check_reviewer(build: &mut Build) -> Result<()> {
|
||||||
fn check_web(build: &mut Build) -> Result<()> {
|
fn check_web(build: &mut Build) -> Result<()> {
|
||||||
let dprint_files = inputs![glob![
|
let dprint_files = inputs![glob![
|
||||||
"**/*.{ts,mjs,js,md,json,toml,svelte,scss}",
|
"**/*.{ts,mjs,js,md,json,toml,svelte,scss}",
|
||||||
"{target,ts/.svelte-kit}/**"
|
"{target,ts/.svelte-kit,node_modules}/**"
|
||||||
]];
|
]];
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"check:format:dprint",
|
"check:format:dprint",
|
||||||
|
|
|
@ -471,6 +471,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.top-tool-icon-button:active) {
|
||||||
|
background: var(--highlight-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-content {
|
.dropdown-content {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -480,7 +484,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
display: flex;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
|
|
@ -19,23 +19,71 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
|
||||||
const shapes = baseShapesFromFabric();
|
const shapes = baseShapesFromFabric();
|
||||||
|
|
||||||
let clozes = "";
|
let clozes = "";
|
||||||
let index = 0;
|
let noteCount = 0;
|
||||||
shapes.forEach((shapeOrShapes) => {
|
|
||||||
// shapes with width or height less than 5 are not valid
|
// take out all ordinal values from shapes
|
||||||
if (shapeOrShapes === null) {
|
const ordinalList = shapes.map((shape) => {
|
||||||
return;
|
if (Array.isArray(shape)) {
|
||||||
}
|
return shape[0].ordinal;
|
||||||
// if shape is Rect and fill is transparent, skip it
|
} else {
|
||||||
if (shapeOrShapes instanceof Rectangle && shapeOrShapes.fill === "transparent") {
|
return shape.ordinal;
|
||||||
return;
|
|
||||||
}
|
|
||||||
clozes += shapeOrShapesToCloze(shapeOrShapes, index, occludeInactive);
|
|
||||||
if (!(shapeOrShapes instanceof Text)) {
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { clozes, noteCount: index };
|
const filterOrdinalList: number[] = ordinalList.flatMap(v => typeof v === "number" ? [v] : []);
|
||||||
|
const maxOrdinal = Math.max(...filterOrdinalList, 0);
|
||||||
|
|
||||||
|
const missingOrdinals: number[] = [];
|
||||||
|
for (let i = 1; i <= maxOrdinal; i++) {
|
||||||
|
if (!ordinalList.includes(i)) {
|
||||||
|
missingOrdinals.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextOrdinal = maxOrdinal + 1;
|
||||||
|
|
||||||
|
shapes.map((shapeOrShapes) => {
|
||||||
|
if (shapeOrShapes === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ordinal is undefined, assign a missing ordinal if available
|
||||||
|
if (shapeOrShapes instanceof Text) {
|
||||||
|
ordinal = 0;
|
||||||
|
} else if (missingOrdinals.length > 0) {
|
||||||
|
ordinal = missingOrdinals.shift() as number;
|
||||||
|
} else {
|
||||||
|
ordinal = nextOrdinal;
|
||||||
|
nextOrdinal++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(shapeOrShapes)) {
|
||||||
|
shapeOrShapes.forEach((shape) => (shape.ordinal = ordinal));
|
||||||
|
} else {
|
||||||
|
shapeOrShapes.ordinal = ordinal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clozes += shapeOrShapesToCloze(
|
||||||
|
shapeOrShapes,
|
||||||
|
ordinal,
|
||||||
|
occludeInactive,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!(shapeOrShapes instanceof Text)) {
|
||||||
|
noteCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { clozes, noteCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gather all Fabric shapes, and convert them into BaseShapes or
|
/** Gather all Fabric shapes, and convert them into BaseShapes or
|
||||||
|
@ -50,6 +98,7 @@ export function baseShapesFromFabric(): ShapeOrShapes[] {
|
||||||
: null;
|
: null;
|
||||||
const objects = canvas.getObjects() as fabric.Object[];
|
const objects = canvas.getObjects() as fabric.Object[];
|
||||||
const boundingBox = getBoundingBox();
|
const boundingBox = getBoundingBox();
|
||||||
|
// filter transparent rectangles
|
||||||
return objects
|
return objects
|
||||||
.map((object) => {
|
.map((object) => {
|
||||||
// If the object is in the active selection containing multiple objects,
|
// If the object is in the active selection containing multiple objects,
|
||||||
|
@ -57,7 +106,9 @@ export function baseShapesFromFabric(): ShapeOrShapes[] {
|
||||||
const parent = selectionContainingMultipleObjects?.contains(object)
|
const parent = selectionContainingMultipleObjects?.contains(object)
|
||||||
? selectionContainingMultipleObjects
|
? selectionContainingMultipleObjects
|
||||||
: undefined;
|
: undefined;
|
||||||
if (object.width! < 5 || object.height! < 5) {
|
// shapes with width or height less than 5 are not valid
|
||||||
|
// if shape is Rect and fill is transparent, skip it
|
||||||
|
if (object.width! < 5 || object.height! < 5 || object.fill == "transparent") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return fabricObjectToBaseShapeOrShapes(
|
return fabricObjectToBaseShapeOrShapes(
|
||||||
|
@ -131,7 +182,7 @@ function fabricObjectToBaseShapeOrShapes(
|
||||||
{{c1::image-occlusion:rect:top=.1:left=.23:width=.4:height=.5}} */
|
{{c1::image-occlusion:rect:top=.1:left=.23:width=.4:height=.5}} */
|
||||||
function shapeOrShapesToCloze(
|
function shapeOrShapesToCloze(
|
||||||
shapeOrShapes: ShapeOrShapes,
|
shapeOrShapes: ShapeOrShapes,
|
||||||
index: number,
|
ordinal: number,
|
||||||
occludeInactive: boolean,
|
occludeInactive: boolean,
|
||||||
): string {
|
): string {
|
||||||
let text = "";
|
let text = "";
|
||||||
|
@ -143,7 +194,7 @@ function shapeOrShapesToCloze(
|
||||||
let type: string;
|
let type: string;
|
||||||
if (Array.isArray(shapeOrShapes)) {
|
if (Array.isArray(shapeOrShapes)) {
|
||||||
return shapeOrShapes
|
return shapeOrShapes
|
||||||
.map((shape) => shapeOrShapesToCloze(shape, index, occludeInactive))
|
.map((shape) => shapeOrShapesToCloze(shape, ordinal, occludeInactive))
|
||||||
.join("");
|
.join("");
|
||||||
} else if (shapeOrShapes instanceof Rectangle) {
|
} else if (shapeOrShapes instanceof Rectangle) {
|
||||||
type = "rect";
|
type = "rect";
|
||||||
|
@ -164,16 +215,6 @@ function shapeOrShapesToCloze(
|
||||||
addKeyValue("oi", "1");
|
addKeyValue("oi", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintain existing ordinal in editing mode
|
|
||||||
let ordinal = shapeOrShapes.ordinal;
|
|
||||||
if (ordinal === undefined) {
|
|
||||||
if (type === "text") {
|
|
||||||
ordinal = 0;
|
|
||||||
} else {
|
|
||||||
ordinal = index + 1;
|
|
||||||
}
|
|
||||||
shapeOrShapes.ordinal = ordinal;
|
|
||||||
}
|
|
||||||
text = `{{c${ordinal}::image-occlusion:${type}${text}}}<br>`;
|
text = `{{c${ordinal}::image-occlusion:${type}${text}}}<br>`;
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
@ -63,12 +63,20 @@ export const groupShapes = (canvas: fabric.Canvas): void => {
|
||||||
|
|
||||||
const activeObject = canvas.getActiveObject() as fabric.ActiveSelection;
|
const activeObject = canvas.getActiveObject() as fabric.ActiveSelection;
|
||||||
const items = activeObject.getObjects();
|
const items = activeObject.getObjects();
|
||||||
|
|
||||||
|
// @ts-expect-error not defined
|
||||||
|
let minOrdinal: number | undefined = Math.min(...items.map((item) => item.ordinal));
|
||||||
|
minOrdinal = Number.isNaN(minOrdinal) ? undefined : minOrdinal;
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
item.set({ opacity: 1 });
|
// @ts-expect-error not defined
|
||||||
|
item.set({ opacity: 1, ordinal: minOrdinal });
|
||||||
});
|
});
|
||||||
|
|
||||||
activeObject.toGroup().set({
|
activeObject.toGroup().set({
|
||||||
opacity: get(opacityStateStore) ? 0.4 : 1,
|
opacity: get(opacityStateStore) ? 0.4 : 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
redraw(canvas);
|
redraw(canvas);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,13 +92,17 @@ export const unGroupShapes = (canvas: fabric.Canvas): void => {
|
||||||
group._restoreObjectsState();
|
group._restoreObjectsState();
|
||||||
// @ts-expect-error not defined
|
// @ts-expect-error not defined
|
||||||
group.destroyed = true;
|
group.destroyed = true;
|
||||||
canvas.remove(group);
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
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);
|
canvas.add(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
canvas.remove(group);
|
||||||
redraw(canvas);
|
redraw(canvas);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -283,9 +295,13 @@ export const makeShapeRemainInCanvas = (canvas: fabric.Canvas, boundingBox: fabr
|
||||||
|
|
||||||
export const selectAllShapes = (canvas: fabric.Canvas) => {
|
export const selectAllShapes = (canvas: fabric.Canvas) => {
|
||||||
canvas.discardActiveObject();
|
canvas.discardActiveObject();
|
||||||
const sel = new fabric.ActiveSelection(canvas.getObjects(), {
|
// filter out the transparent bounding box from the selection
|
||||||
canvas: canvas,
|
const sel = new fabric.ActiveSelection(
|
||||||
});
|
canvas.getObjects().filter((obj) => obj.fill !== "transparent"),
|
||||||
|
{
|
||||||
|
canvas: canvas,
|
||||||
|
},
|
||||||
|
);
|
||||||
canvas.setActiveObject(sel);
|
canvas.setActiveObject(sel);
|
||||||
redraw(canvas);
|
redraw(canvas);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import { type fabric } from "fabric";
|
import { fabric } from "fabric";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { mdiRedo, mdiUndo } from "../icons";
|
import { mdiRedo, mdiUndo } from "../icons";
|
||||||
|
@ -87,6 +87,12 @@ class UndoStack {
|
||||||
emitChangeSignal();
|
emitChangeSignal();
|
||||||
this.locked = false;
|
this.locked = false;
|
||||||
});
|
});
|
||||||
|
// make bounding box unselectable
|
||||||
|
this.canvas?.forEachObject((obj) => {
|
||||||
|
if (obj instanceof fabric.Rect && obj.fill === "transparent") {
|
||||||
|
obj.selectable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onObjectAdded(id: string): void {
|
onObjectAdded(id: string): void {
|
||||||
|
|
Loading…
Reference in a new issue