@pooder/kit 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/ruler.ts CHANGED
@@ -1,239 +1,325 @@
1
- import { Command, Editor, EditorState, Extension, OptionSchema, PooderLayer, Rect, Line, Text } from '@pooder/core';
2
-
3
- export interface RulerToolOptions {
4
- unit: 'px' | 'mm' | 'cm' | 'in';
5
- thickness: number;
6
- backgroundColor: string;
7
- textColor: string;
8
- lineColor: string;
9
- fontSize: number;
10
- }
11
-
12
- export class RulerTool implements Extension<RulerToolOptions> {
13
- public name = 'RulerTool';
14
- public options: RulerToolOptions = {
15
- unit: 'px',
16
- thickness: 20,
17
- backgroundColor: '#f0f0f0',
18
- textColor: '#333333',
19
- lineColor: '#999999',
20
- fontSize: 10
21
- };
22
-
23
- public schema: Record<keyof RulerToolOptions, OptionSchema> = {
24
- unit: {
25
- type: 'select',
26
- options: ['px', 'mm', 'cm', 'in'],
27
- label: 'Unit'
28
- },
29
- thickness: { type: 'number', min: 10, max: 100, label: 'Thickness' },
30
- backgroundColor: { type: 'color', label: 'Background Color' },
31
- textColor: { type: 'color', label: 'Text Color' },
32
- lineColor: { type: 'color', label: 'Line Color' },
33
- fontSize: { type: 'number', min: 8, max: 24, label: 'Font Size' }
34
- };
35
-
36
- onMount(editor: Editor) {
37
- this.createLayer(editor);
38
- this.updateRuler(editor);
39
- }
40
-
41
- onUnmount(editor: Editor) {
42
- this.destroyLayer(editor);
43
- }
44
-
45
- onUpdate(editor: Editor, state: EditorState) {
46
- this.updateRuler(editor);
47
- }
48
-
49
- onDestroy(editor: Editor) {
50
- this.destroyLayer(editor);
51
- }
52
-
53
- private getLayer(editor: Editor) {
54
- return editor.canvas.getObjects().find((obj: any) => obj.data?.id === 'ruler-overlay') as PooderLayer | undefined;
55
- }
56
-
57
- private createLayer(editor: Editor) {
58
- let layer = this.getLayer(editor);
59
-
60
- if (!layer) {
61
- const width = editor.canvas.width || 800;
62
- const height = editor.canvas.height || 600;
63
-
64
- layer = new PooderLayer([], {
65
- width,
66
- height,
67
- selectable: false,
68
- evented: false,
69
- data: { id: 'ruler-overlay' }
70
- } as any);
71
-
72
- editor.canvas.add(layer);
73
- }
74
-
75
- editor.canvas.bringObjectToFront(layer);
76
- }
77
-
78
- private destroyLayer(editor: Editor) {
79
- const layer = this.getLayer(editor);
80
- if (layer) {
81
- editor.canvas.remove(layer);
82
- }
83
- }
84
-
85
- private updateRuler(editor: Editor) {
86
- const layer = this.getLayer(editor);
87
- if (!layer) return;
88
-
89
- layer.remove(...layer.getObjects());
90
-
91
- const { thickness, backgroundColor, lineColor, textColor, fontSize } = this.options;
92
- const width = editor.canvas.width || 800;
93
- const height = editor.canvas.height || 600;
94
-
95
- // Backgrounds
96
- const topBg = new Rect({
97
- left: 0,
98
- top: 0,
99
- width: width,
100
- height: thickness,
101
- fill: backgroundColor,
102
- selectable: false,
103
- evented: false
104
- });
105
-
106
- const leftBg = new Rect({
107
- left: 0,
108
- top: 0,
109
- width: thickness,
110
- height: height,
111
- fill: backgroundColor,
112
- selectable: false,
113
- evented: false
114
- });
115
-
116
- const cornerBg = new Rect({
117
- left: 0,
118
- top: 0,
119
- width: thickness,
120
- height: thickness,
121
- fill: backgroundColor,
122
- stroke: lineColor,
123
- strokeWidth: 1,
124
- selectable: false,
125
- evented: false
126
- });
127
-
128
- layer.add(topBg, leftBg, cornerBg);
129
-
130
- // Drawing Constants (Pixel based for now)
131
- const step = 100; // Major tick
132
- const subStep = 10; // Minor tick
133
- const midStep = 50; // Medium tick
134
-
135
- // Top Ruler
136
- for (let x = 0; x <= width; x += subStep) {
137
- if (x < thickness) continue; // Skip corner
138
-
139
- let len = thickness * 0.25;
140
- if (x % step === 0) len = thickness * 0.8;
141
- else if (x % midStep === 0) len = thickness * 0.5;
142
-
143
- const line = new Line([x, thickness - len, x, thickness], {
144
- stroke: lineColor,
145
- strokeWidth: 1,
146
- selectable: false,
147
- evented: false
148
- });
149
- layer.add(line);
150
-
151
- if (x % step === 0) {
152
- const text = new Text(x.toString(), {
153
- left: x + 2,
154
- top: 2,
155
- fontSize: fontSize,
156
- fill: textColor,
157
- fontFamily: 'Arial',
158
- selectable: false,
159
- evented: false
160
- });
161
- layer.add(text);
162
- }
163
- }
164
-
165
- // Left Ruler
166
- for (let y = 0; y <= height; y += subStep) {
167
- if (y < thickness) continue; // Skip corner
168
-
169
- let len = thickness * 0.25;
170
- if (y % step === 0) len = thickness * 0.8;
171
- else if (y % midStep === 0) len = thickness * 0.5;
172
-
173
- const line = new Line([thickness - len, y, thickness, y], {
174
- stroke: lineColor,
175
- strokeWidth: 1,
176
- selectable: false,
177
- evented: false
178
- });
179
- layer.add(line);
180
-
181
- if (y % step === 0) {
182
- const text = new Text(y.toString(), {
183
- angle: -90,
184
- left: thickness / 2 - fontSize / 3, // approximate centering
185
- top: y + fontSize,
186
- fontSize: fontSize,
187
- fill: textColor,
188
- fontFamily: 'Arial',
189
- originX: 'center',
190
- originY: 'center',
191
- selectable: false,
192
- evented: false
193
- });
194
-
195
- layer.add(text);
196
- }
197
- }
198
-
199
- // Always bring ruler to front
200
- editor.canvas.bringObjectToFront(layer);
201
- editor.canvas.requestRenderAll();
202
- }
203
-
204
- commands: Record<string, Command> = {
205
- setUnit: {
206
- execute: (editor: Editor, unit: 'px' | 'mm' | 'cm' | 'in') => {
207
- if (this.options.unit === unit) return true;
208
- this.options.unit = unit;
209
- this.updateRuler(editor);
210
- return true;
211
- },
212
- schema: {
213
- unit: {
214
- type: 'string',
215
- label: 'Unit',
216
- options: ['px', 'mm', 'cm', 'in'],
217
- required: true
218
- }
219
- }
220
- },
221
- setTheme: {
222
- execute: (editor: Editor, theme: Partial<RulerToolOptions>) => {
223
- const newOptions = { ...this.options, ...theme };
224
- if (JSON.stringify(newOptions) === JSON.stringify(this.options)) return true;
225
-
226
- this.options = newOptions;
227
- this.updateRuler(editor);
228
- return true;
229
- },
230
- schema: {
231
- theme: {
232
- type: 'object',
233
- label: 'Theme',
234
- required: true
235
- }
236
- }
237
- }
238
- };
239
- }
1
+ import {
2
+ Extension,
3
+ ExtensionContext,
4
+ ContributionPointIds,
5
+ CommandContribution,
6
+ ConfigurationContribution,
7
+ } from "@pooder/core";
8
+ import { Rect, Line, Text } from "fabric";
9
+ import CanvasService from "./CanvasService";
10
+
11
+ export class RulerTool implements Extension {
12
+ id = "pooder.kit.ruler";
13
+
14
+ public metadata = {
15
+ name: "RulerTool",
16
+ };
17
+
18
+ private unit: "px" | "mm" | "cm" | "in" = "px";
19
+ private thickness: number = 20;
20
+ private backgroundColor: string = "#f0f0f0";
21
+ private textColor: string = "#333333";
22
+ private lineColor: string = "#999999";
23
+ private fontSize: number = 10;
24
+
25
+ private canvasService?: CanvasService;
26
+
27
+ constructor(
28
+ options?: Partial<{
29
+ unit: "px" | "mm" | "cm" | "in";
30
+ thickness: number;
31
+ backgroundColor: string;
32
+ textColor: string;
33
+ lineColor: string;
34
+ fontSize: number;
35
+ }>,
36
+ ) {
37
+ if (options) {
38
+ Object.assign(this, options);
39
+ }
40
+ }
41
+
42
+ activate(context: ExtensionContext) {
43
+ this.canvasService = context.services.get<CanvasService>("CanvasService");
44
+ if (!this.canvasService) {
45
+ console.warn("CanvasService not found for RulerTool");
46
+ return;
47
+ }
48
+
49
+ const configService = context.services.get<any>("ConfigurationService");
50
+ if (configService) {
51
+ // Load initial config
52
+ this.unit = configService.get("ruler.unit", this.unit);
53
+ this.thickness = configService.get("ruler.thickness", this.thickness);
54
+ this.backgroundColor = configService.get(
55
+ "ruler.backgroundColor",
56
+ this.backgroundColor,
57
+ );
58
+ this.textColor = configService.get("ruler.textColor", this.textColor);
59
+ this.lineColor = configService.get("ruler.lineColor", this.lineColor);
60
+ this.fontSize = configService.get("ruler.fontSize", this.fontSize);
61
+
62
+ // Listen for changes
63
+ configService.onAnyChange((e: { key: string; value: any }) => {
64
+ if (e.key.startsWith("ruler.")) {
65
+ const prop = e.key.split(".")[1];
66
+ if (prop && prop in this) {
67
+ (this as any)[prop] = e.value;
68
+ this.updateRuler();
69
+ }
70
+ }
71
+ });
72
+ }
73
+
74
+ this.createLayer();
75
+ this.updateRuler();
76
+ }
77
+
78
+ deactivate(context: ExtensionContext) {
79
+ this.destroyLayer();
80
+ this.canvasService = undefined;
81
+ }
82
+
83
+ contribute() {
84
+ return {
85
+ [ContributionPointIds.CONFIGURATIONS]: [
86
+ {
87
+ id: "ruler.unit",
88
+ type: "select",
89
+ label: "Unit",
90
+ options: ["px", "mm", "cm", "in"],
91
+ default: "px",
92
+ },
93
+ {
94
+ id: "ruler.thickness",
95
+ type: "number",
96
+ label: "Thickness",
97
+ min: 10,
98
+ max: 100,
99
+ default: 20,
100
+ },
101
+ {
102
+ id: "ruler.backgroundColor",
103
+ type: "color",
104
+ label: "Background Color",
105
+ default: "#f0f0f0",
106
+ },
107
+ {
108
+ id: "ruler.textColor",
109
+ type: "color",
110
+ label: "Text Color",
111
+ default: "#333333",
112
+ },
113
+ {
114
+ id: "ruler.lineColor",
115
+ type: "color",
116
+ label: "Line Color",
117
+ default: "#999999",
118
+ },
119
+ {
120
+ id: "ruler.fontSize",
121
+ type: "number",
122
+ label: "Font Size",
123
+ min: 8,
124
+ max: 24,
125
+ default: 10,
126
+ },
127
+ ] as ConfigurationContribution[],
128
+ [ContributionPointIds.COMMANDS]: [
129
+ {
130
+ command: "setUnit",
131
+ title: "Set Ruler Unit",
132
+ handler: (unit: "px" | "mm" | "cm" | "in") => {
133
+ if (this.unit === unit) return true;
134
+ this.unit = unit;
135
+ this.updateRuler();
136
+ return true;
137
+ },
138
+ },
139
+ {
140
+ command: "setTheme",
141
+ title: "Set Ruler Theme",
142
+ handler: (
143
+ theme: Partial<{
144
+ backgroundColor: string;
145
+ textColor: string;
146
+ lineColor: string;
147
+ fontSize: number;
148
+ thickness: number;
149
+ }>,
150
+ ) => {
151
+ const oldState = {
152
+ backgroundColor: this.backgroundColor,
153
+ textColor: this.textColor,
154
+ lineColor: this.lineColor,
155
+ fontSize: this.fontSize,
156
+ thickness: this.thickness,
157
+ };
158
+ const newState = { ...oldState, ...theme };
159
+ if (JSON.stringify(newState) === JSON.stringify(oldState))
160
+ return true;
161
+
162
+ Object.assign(this, newState);
163
+ this.updateRuler();
164
+ return true;
165
+ },
166
+ },
167
+ ] as CommandContribution[],
168
+ };
169
+ }
170
+
171
+ private getLayer() {
172
+ return this.canvasService?.getLayer("ruler-overlay");
173
+ }
174
+
175
+ private createLayer() {
176
+ if (!this.canvasService) return;
177
+
178
+ const canvas = this.canvasService.canvas;
179
+ const width = canvas.width || 800;
180
+ const height = canvas.height || 600;
181
+
182
+ const layer = this.canvasService.createLayer("ruler-overlay", {
183
+ width,
184
+ height,
185
+ selectable: false,
186
+ evented: false,
187
+ left: 0,
188
+ top: 0,
189
+ originX: "left",
190
+ originY: "top",
191
+ });
192
+
193
+ canvas.bringObjectToFront(layer);
194
+ }
195
+
196
+ private destroyLayer() {
197
+ if (!this.canvasService) return;
198
+ const layer = this.getLayer();
199
+ if (layer) {
200
+ this.canvasService.canvas.remove(layer);
201
+ }
202
+ }
203
+
204
+ private updateRuler() {
205
+ if (!this.canvasService) return;
206
+ const layer = this.getLayer();
207
+ if (!layer) return;
208
+
209
+ layer.remove(...layer.getObjects());
210
+
211
+ const { thickness, backgroundColor, lineColor, textColor, fontSize } = this;
212
+ const width = this.canvasService.canvas.width || 800;
213
+ const height = this.canvasService.canvas.height || 600;
214
+
215
+ // Backgrounds
216
+ const topBg = new Rect({
217
+ left: 0,
218
+ top: 0,
219
+ width: width,
220
+ height: thickness,
221
+ fill: backgroundColor,
222
+ selectable: false,
223
+ evented: false,
224
+ });
225
+
226
+ const leftBg = new Rect({
227
+ left: 0,
228
+ top: 0,
229
+ width: thickness,
230
+ height: height,
231
+ fill: backgroundColor,
232
+ selectable: false,
233
+ evented: false,
234
+ });
235
+
236
+ const cornerBg = new Rect({
237
+ left: 0,
238
+ top: 0,
239
+ width: thickness,
240
+ height: thickness,
241
+ fill: backgroundColor,
242
+ stroke: lineColor,
243
+ strokeWidth: 1,
244
+ selectable: false,
245
+ evented: false,
246
+ });
247
+
248
+ layer.add(topBg);
249
+ layer.add(leftBg);
250
+ layer.add(cornerBg);
251
+
252
+ // Drawing Constants (Pixel based for now)
253
+ const step = 100; // Major tick
254
+ const subStep = 10; // Minor tick
255
+ const midStep = 50; // Medium tick
256
+
257
+ // Top Ruler
258
+ for (let x = 0; x <= width; x += subStep) {
259
+ if (x < thickness) continue; // Skip corner
260
+
261
+ let len = thickness * 0.25;
262
+ if (x % step === 0) len = thickness * 0.8;
263
+ else if (x % midStep === 0) len = thickness * 0.5;
264
+
265
+ const line = new Line([x, thickness - len, x, thickness], {
266
+ stroke: lineColor,
267
+ strokeWidth: 1,
268
+ selectable: false,
269
+ evented: false,
270
+ });
271
+ layer.add(line);
272
+
273
+ if (x % step === 0) {
274
+ const text = new Text(x.toString(), {
275
+ left: x + 2,
276
+ top: 2,
277
+ fontSize: fontSize,
278
+ fill: textColor,
279
+ fontFamily: "Arial",
280
+ selectable: false,
281
+ evented: false,
282
+ });
283
+ layer.add(text);
284
+ }
285
+ }
286
+
287
+ // Left Ruler
288
+ for (let y = 0; y <= height; y += subStep) {
289
+ if (y < thickness) continue; // Skip corner
290
+
291
+ let len = thickness * 0.25;
292
+ if (y % step === 0) len = thickness * 0.8;
293
+ else if (y % midStep === 0) len = thickness * 0.5;
294
+
295
+ const line = new Line([thickness - len, y, thickness, y], {
296
+ stroke: lineColor,
297
+ strokeWidth: 1,
298
+ selectable: false,
299
+ evented: false,
300
+ });
301
+ layer.add(line);
302
+
303
+ if (y % step === 0) {
304
+ const text = new Text(y.toString(), {
305
+ angle: -90,
306
+ left: thickness / 2 - fontSize / 3, // approximate centering
307
+ top: y + fontSize,
308
+ fontSize: fontSize,
309
+ fill: textColor,
310
+ fontFamily: "Arial",
311
+ originX: "center",
312
+ originY: "center",
313
+ selectable: false,
314
+ evented: false,
315
+ });
316
+
317
+ layer.add(text);
318
+ }
319
+ }
320
+
321
+ // Always bring ruler to front
322
+ this.canvasService.canvas.bringObjectToFront(layer);
323
+ this.canvasService.canvas.requestRenderAll();
324
+ }
325
+ }