-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathpage.ts
173 lines (156 loc) · 5.36 KB
/
page.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { fabric } from "fabric";
import AssertType from "../types/assert";
import { FabricObject, isFabricCollection, ObjectId } from "../types/fabric";
export type Cursor = { x: number; y: number };
export default class Page extends fabric.Canvas {
cursor: Cursor | undefined;
// assert not undefined because fitToWindow, which is called at init, sets these
canvasWidth!: number;
canvasHeight!: number;
modified = false;
fitToWindow = (canvasWidth: number, canvasHeight: number): void => {
const widthRatio = window.innerWidth / canvasWidth;
const heightRatio = window.innerHeight / canvasHeight;
this.setZoom(Math.min(widthRatio, heightRatio));
this.setWidth(canvasWidth * this.getZoom());
this.setHeight(canvasHeight * this.getZoom());
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
};
deactivateSelection = (): void => {
this.isDrawingMode = false;
this.selection = false;
this.discardActiveObject();
this.forEachObject((object) => {
object.selectable = false;
});
this.requestRenderAll();
};
activateSelection = (): void => {
this.isDrawingMode = false;
this.selection = true;
this.forEachObject((object) => {
object.selectable = true;
});
};
// kind of inefficient
getObjectByIds = (ids: readonly number[]): fabric.Object[] =>
this.getObjects().filter((object) => {
AssertType<ObjectId>(object);
return (
object.idVersion === 1 && object.id != null && ids.includes(object.id)
);
});
serialize = (objects: readonly fabric.Object[]): fabric.Object[] => {
const selection = this.getActiveObjects();
const reselect =
selection.length > 1 && objects.some((obj) => selection.includes(obj));
if (reselect) {
this.discardActiveObject();
this.setActiveObject(
new fabric.ActiveSelection(selection, { canvas: this })
);
}
return objects
.map((obj) =>
// This is needed for selection groups to be serialized properly.
// If directly using `obj.toObject` it somehow depends on the selection remaining active,
// as claimed in <https://github.com/fabricjs/fabric.js/blob/2eabc92a3221dd628576b1bb029a5dc1156bdc06/src/canvas.class.js#L1262-L1272>.
//
// We tried using that method in b9cb04c3dacd951785ce4e94ce0c629c09319ec3 but this caused issue #171.
// See https://github.com/cjquines/qboard/issues/171
// and https://github.com/cjquines/qboard/issues/176
// for more details.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this as any)._toObject(obj, "toObject", ["data", "strokeUniform"])
)
.map((obj) => {
delete obj.id;
return obj;
});
};
apply = (
ids: readonly number[],
newObjects: fabric.Object[] | null
): void => {
const oldObjects = this.getObjectByIds(ids);
this.remove(...oldObjects);
if (newObjects?.length) {
const addObjects = (objects: ObjectId[]) => {
objects.forEach((object, i) => {
object.id = ids[i];
});
this.add(...objects);
this.requestRenderAll();
};
fabric.util.enlivenObjects(newObjects, addObjects, "fabric");
}
this.requestRenderAll();
};
loadFromJSONAsync = async (json: unknown): Promise<void> =>
new Promise<void>((resolve) => {
super.loadFromJSON(json, () => {
resolve();
});
});
/**
* Create a Fabric Image from {@param imageURL},
* placed at the location defined by {@param cursor},
* with custom options given by {@param options}.
*
* @warn Make sure that {@param options} does not contain enough properties to satisfy {@link isFabricCollection}
*/
addImage = async <T extends fabric.IImageOptions>(
imageURL: string,
cursor?: Partial<Cursor>,
options?: T
): Promise<fabric.Image & (typeof options extends undefined ? unknown : T)> =>
new Promise((resolve) =>
fabric.Image.fromURL(
imageURL,
(obj) => {
AssertType<typeof options extends undefined ? unknown : T>(obj);
// We are confident that we don't need this return value because we should get the original image back
// Technically not true because `options` might be so large that it makes `obj` pass `isFabricCollection`
// so we just warn against it
this.placeObject(obj, cursor);
resolve(obj);
},
options
)
);
/**
* Places {@param obj} at ({@param x}, {@param y}).
* Returns the array of subobjects, if obj is a collection, or else a singleton containing obj
*/
placeObject = <T extends FabricObject>(
obj: T,
{
x = this.canvasWidth / 2,
y = this.canvasHeight / 2,
}: Partial<Cursor> = this.cursor ?? {}
): T extends fabric.ICollection<unknown> ? fabric.Object[] : [T] => {
this.discardActiveObject();
((obj as FabricObject) as ObjectId).set({
left: x,
top: y,
originX: "center",
originY: "center",
});
let returnObjects;
if (isFabricCollection(obj)) {
obj.canvas = this;
obj.forEachObject((object) => {
this.add(object);
});
obj.setCoords();
returnObjects = obj.getObjects();
} else {
this.add(obj);
returnObjects = [obj];
}
this.setActiveObject(obj);
this.requestRenderAll();
return returnObjects;
};
}