senangwebs-photobooth 1.0.1 → 2.0.2

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.
Files changed (44) hide show
  1. package/README.md +219 -235
  2. package/dist/swp.css +884 -344
  3. package/dist/swp.js +1 -1
  4. package/examples/data-attribute.html +69 -0
  5. package/examples/index.html +56 -51
  6. package/examples/studio.html +83 -0
  7. package/package.json +12 -5
  8. package/src/css/swp.css +884 -344
  9. package/src/js/core/Canvas.js +398 -0
  10. package/src/js/core/EventEmitter.js +188 -0
  11. package/src/js/core/History.js +250 -0
  12. package/src/js/core/Keyboard.js +323 -0
  13. package/src/js/filters/FilterManager.js +248 -0
  14. package/src/js/index.js +48 -0
  15. package/src/js/io/Clipboard.js +52 -0
  16. package/src/js/io/FileManager.js +150 -0
  17. package/src/js/layers/BlendModes.js +342 -0
  18. package/src/js/layers/Layer.js +415 -0
  19. package/src/js/layers/LayerManager.js +459 -0
  20. package/src/js/selection/Selection.js +167 -0
  21. package/src/js/swp.js +297 -709
  22. package/src/js/tools/BaseTool.js +264 -0
  23. package/src/js/tools/BrushTool.js +314 -0
  24. package/src/js/tools/CropTool.js +400 -0
  25. package/src/js/tools/EraserTool.js +155 -0
  26. package/src/js/tools/EyedropperTool.js +184 -0
  27. package/src/js/tools/FillTool.js +109 -0
  28. package/src/js/tools/GradientTool.js +141 -0
  29. package/src/js/tools/HandTool.js +51 -0
  30. package/src/js/tools/MarqueeTool.js +103 -0
  31. package/src/js/tools/MoveTool.js +465 -0
  32. package/src/js/tools/ShapeTool.js +285 -0
  33. package/src/js/tools/TextTool.js +253 -0
  34. package/src/js/tools/ToolManager.js +277 -0
  35. package/src/js/tools/ZoomTool.js +68 -0
  36. package/src/js/ui/ColorManager.js +71 -0
  37. package/src/js/ui/UI.js +1211 -0
  38. package/swp_preview1.png +0 -0
  39. package/swp_preview2.png +0 -0
  40. package/webpack.config.js +4 -11
  41. package/dist/styles.js +0 -1
  42. package/examples/customization.html +0 -360
  43. package/spec.md +0 -239
  44. package/swp_preview.png +0 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * SenangWebs Studio - Tool Manager
3
+ * Manages tool selection and events
4
+ * @version 2.0.0
5
+ */
6
+
7
+ import { Events } from '../core/EventEmitter.js';
8
+ import { MoveTool } from './MoveTool.js';
9
+ import { BrushTool } from './BrushTool.js';
10
+ import { EraserTool } from './EraserTool.js';
11
+ import { ShapeTool } from './ShapeTool.js';
12
+ import { TextTool } from './TextTool.js';
13
+ import { CropTool } from './CropTool.js';
14
+ import { ZoomTool } from './ZoomTool.js';
15
+ import { HandTool } from './HandTool.js';
16
+ import { EyedropperTool } from './EyedropperTool.js';
17
+ import { GradientTool } from './GradientTool.js';
18
+ import { FillTool } from './FillTool.js';
19
+ import { MarqueeTool } from './MarqueeTool.js';
20
+
21
+ export class ToolManager {
22
+ constructor(app) {
23
+ this.app = app;
24
+ this.tools = new Map();
25
+ this.currentTool = null;
26
+ this.previousTool = null;
27
+ this.temporaryTool = null;
28
+
29
+ this.init();
30
+ }
31
+
32
+ /**
33
+ * Initialize tools
34
+ */
35
+ init() {
36
+ // Register all tools
37
+ this.registerTool('move', new MoveTool(this.app));
38
+ this.registerTool('marquee', new MarqueeTool(this.app));
39
+ this.registerTool('brush', new BrushTool(this.app));
40
+ this.registerTool('eraser', new EraserTool(this.app));
41
+ this.registerTool('shape', new ShapeTool(this.app));
42
+ this.registerTool('text', new TextTool(this.app));
43
+ this.registerTool('crop', new CropTool(this.app));
44
+ this.registerTool('zoom', new ZoomTool(this.app));
45
+ this.registerTool('hand', new HandTool(this.app));
46
+ this.registerTool('eyedropper', new EyedropperTool(this.app));
47
+ this.registerTool('gradient', new GradientTool(this.app));
48
+ this.registerTool('fill', new FillTool(this.app));
49
+
50
+ // Set default tool
51
+ this.setTool('move');
52
+ }
53
+
54
+ /**
55
+ * Register a tool
56
+ * @param {string} name - Tool name
57
+ * @param {BaseTool} tool - Tool instance
58
+ */
59
+ registerTool(name, tool) {
60
+ this.tools.set(name, tool);
61
+ }
62
+
63
+ /**
64
+ * Get tool by name
65
+ * @param {string} name - Tool name
66
+ * @returns {BaseTool|null}
67
+ */
68
+ getTool(name) {
69
+ return this.tools.get(name) || null;
70
+ }
71
+
72
+ /**
73
+ * Set active tool
74
+ * @param {string} name - Tool name
75
+ */
76
+ setTool(name) {
77
+ const tool = this.getTool(name);
78
+ if (!tool) {
79
+ console.warn(`Tool "${name}" not found`);
80
+ return;
81
+ }
82
+
83
+ // Deactivate current tool
84
+ if (this.currentTool) {
85
+ this.currentTool.deactivate();
86
+ this.previousTool = this.currentTool;
87
+ }
88
+
89
+ // Activate new tool
90
+ this.currentTool = tool;
91
+ this.currentTool.activate();
92
+
93
+ this.app.events.emit(Events.TOOL_SELECT, { tool: name });
94
+ }
95
+
96
+ /**
97
+ * Activate temporary tool (e.g., hand tool when space is pressed)
98
+ * @param {string} name - Tool name
99
+ */
100
+ activateTemporaryTool(name) {
101
+ if (this.temporaryTool) return;
102
+
103
+ const tool = this.getTool(name);
104
+ if (!tool) return;
105
+
106
+ this.temporaryTool = this.currentTool;
107
+ this.currentTool.deactivate();
108
+ this.currentTool = tool;
109
+ this.currentTool.activate();
110
+ }
111
+
112
+ /**
113
+ * Deactivate temporary tool
114
+ */
115
+ deactivateTemporaryTool() {
116
+ if (!this.temporaryTool) return;
117
+
118
+ this.currentTool.deactivate();
119
+ this.currentTool = this.temporaryTool;
120
+ this.currentTool.activate();
121
+ this.temporaryTool = null;
122
+ }
123
+
124
+ /**
125
+ * Get all tools
126
+ * @returns {Map} Tools map
127
+ */
128
+ getAllTools() {
129
+ return this.tools;
130
+ }
131
+
132
+ /**
133
+ * Get current tool name
134
+ * @returns {string|null}
135
+ */
136
+ getCurrentToolName() {
137
+ for (const [name, tool] of this.tools) {
138
+ if (tool === this.currentTool) return name;
139
+ }
140
+ return null;
141
+ }
142
+
143
+ /**
144
+ * Switch to previous tool
145
+ */
146
+ switchToPreviousTool() {
147
+ if (this.previousTool) {
148
+ for (const [name, tool] of this.tools) {
149
+ if (tool === this.previousTool) {
150
+ this.setTool(name);
151
+ return;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Increase brush size
159
+ */
160
+ increaseBrushSize() {
161
+ if (this.currentTool?.options?.size !== undefined) {
162
+ const size = this.currentTool.options.size;
163
+ const newSize = Math.min(500, size + (size < 10 ? 1 : size < 100 ? 5 : 20));
164
+ this.currentTool.setOption('size', newSize);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Decrease brush size
170
+ */
171
+ decreaseBrushSize() {
172
+ if (this.currentTool?.options?.size !== undefined) {
173
+ const size = this.currentTool.options.size;
174
+ const newSize = Math.max(1, size - (size <= 10 ? 1 : size <= 100 ? 5 : 20));
175
+ this.currentTool.setOption('size', newSize);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Increase brush hardness
181
+ */
182
+ increaseBrushHardness() {
183
+ if (this.currentTool?.options?.hardness !== undefined) {
184
+ const hardness = this.currentTool.options.hardness;
185
+ this.currentTool.setOption('hardness', Math.min(100, hardness + 10));
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Decrease brush hardness
191
+ */
192
+ decreaseBrushHardness() {
193
+ if (this.currentTool?.options?.hardness !== undefined) {
194
+ const hardness = this.currentTool.options.hardness;
195
+ this.currentTool.setOption('hardness', Math.max(0, hardness - 10));
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Start free transform on active layer
201
+ */
202
+ startTransform() {
203
+ // TODO: Implement transform mode
204
+ console.log('Transform mode');
205
+ }
206
+
207
+ /**
208
+ * Bind canvas events
209
+ * @param {HTMLCanvasElement} canvas - Display canvas
210
+ */
211
+ bindCanvasEvents(canvas) {
212
+ canvas.addEventListener('pointerdown', this.handlePointerDown.bind(this));
213
+ canvas.addEventListener('pointermove', this.handlePointerMove.bind(this));
214
+ canvas.addEventListener('pointerup', this.handlePointerUp.bind(this));
215
+ canvas.addEventListener('pointerleave', this.handlePointerLeave.bind(this));
216
+
217
+ // Prevent context menu on right-click
218
+ canvas.addEventListener('contextmenu', e => e.preventDefault());
219
+ }
220
+
221
+ /**
222
+ * Handle pointer down
223
+ * @param {PointerEvent} e - Pointer event
224
+ */
225
+ handlePointerDown(e) {
226
+ if (!this.currentTool) return;
227
+
228
+ // Set pointer capture
229
+ e.target.setPointerCapture(e.pointerId);
230
+
231
+ this.currentTool.onPointerDown(e);
232
+ }
233
+
234
+ /**
235
+ * Handle pointer move
236
+ * @param {PointerEvent} e - Pointer event
237
+ */
238
+ handlePointerMove(e) {
239
+ if (!this.currentTool) return;
240
+ this.currentTool.onPointerMove(e);
241
+ }
242
+
243
+ /**
244
+ * Handle pointer up
245
+ * @param {PointerEvent} e - Pointer event
246
+ */
247
+ handlePointerUp(e) {
248
+ if (!this.currentTool) return;
249
+
250
+ e.target.releasePointerCapture(e.pointerId);
251
+
252
+ this.currentTool.onPointerUp(e);
253
+ }
254
+
255
+ /**
256
+ * Handle pointer leave
257
+ * @param {PointerEvent} e - Pointer event
258
+ */
259
+ handlePointerLeave(e) {
260
+ if (!this.currentTool) return;
261
+ this.currentTool.onPointerLeave(e);
262
+ }
263
+
264
+ /**
265
+ * Destroy tool manager
266
+ */
267
+ destroy() {
268
+ this.tools.forEach(tool => {
269
+ if (tool.destroy) tool.destroy();
270
+ });
271
+ this.tools.clear();
272
+ this.currentTool = null;
273
+ this.previousTool = null;
274
+ }
275
+ }
276
+
277
+ export default ToolManager;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * SenangWebs Studio - Zoom Tool
3
+ * Zoom the canvas viewport
4
+ * @version 2.0.0
5
+ */
6
+
7
+ import { BaseTool } from './BaseTool.js';
8
+
9
+ export class ZoomTool extends BaseTool {
10
+ constructor(app) {
11
+ super(app);
12
+ this.name = 'zoom';
13
+ this.icon = 'zoom-in';
14
+ this.cursor = 'zoom-in';
15
+ this.shortcut = 'z';
16
+
17
+ this.options = {
18
+ zoomIn: true // false = zoom out
19
+ };
20
+ this.defaultOptions = { ...this.options };
21
+ }
22
+
23
+ onPointerDown(e) {
24
+ super.onPointerDown(e);
25
+
26
+ const viewPoint = this.getViewportPoint(e);
27
+
28
+ if (this.options.zoomIn) {
29
+ this.app.canvas.zoomIn();
30
+ } else {
31
+ this.app.canvas.zoomOut();
32
+ }
33
+ }
34
+
35
+ onPointerMove(e) {
36
+ super.onPointerMove(e);
37
+
38
+ // Update cursor based on Alt key
39
+ if (e.altKey || !this.options.zoomIn) {
40
+ this.cursor = 'zoom-out';
41
+ } else {
42
+ this.cursor = 'zoom-in';
43
+ }
44
+ this.updateCursor();
45
+ }
46
+
47
+ getOptionsUI() {
48
+ return {
49
+ zoomIn: {
50
+ type: 'checkbox',
51
+ label: 'Zoom In',
52
+ value: this.options.zoomIn
53
+ },
54
+ fitToScreen: {
55
+ type: 'button',
56
+ label: 'Fit to Screen',
57
+ action: () => this.app.canvas.fitToScreen()
58
+ },
59
+ actualSize: {
60
+ type: 'button',
61
+ label: '100%',
62
+ action: () => this.app.canvas.setZoom(100)
63
+ }
64
+ };
65
+ }
66
+ }
67
+
68
+ export default ZoomTool;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * SenangWebs Studio - Color Manager
3
+ * @version 2.0.0
4
+ */
5
+
6
+ import { Events } from '../core/EventEmitter.js';
7
+
8
+ export class ColorManager {
9
+ constructor(app) {
10
+ this.app = app;
11
+ this.foreground = '#000000';
12
+ this.background = '#ffffff';
13
+ this.swatches = [
14
+ '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff',
15
+ '#ffff00', '#00ffff', '#ff00ff', '#ff8800', '#8800ff',
16
+ '#888888', '#444444', '#cccccc', '#880000', '#008800'
17
+ ];
18
+ }
19
+
20
+ setForeground(color) {
21
+ this.foreground = color;
22
+ this.app.events.emit(Events.COLOR_FOREGROUND, { color });
23
+ }
24
+
25
+ setBackground(color) {
26
+ this.background = color;
27
+ this.app.events.emit(Events.COLOR_BACKGROUND, { color });
28
+ }
29
+
30
+ swap() {
31
+ const temp = this.foreground;
32
+ this.foreground = this.background;
33
+ this.background = temp;
34
+ this.app.events.emit(Events.COLOR_SWAP, {
35
+ foreground: this.foreground,
36
+ background: this.background
37
+ });
38
+ }
39
+
40
+ reset() {
41
+ this.foreground = '#000000';
42
+ this.background = '#ffffff';
43
+ this.app.events.emit(Events.COLOR_FOREGROUND, { color: this.foreground });
44
+ this.app.events.emit(Events.COLOR_BACKGROUND, { color: this.background });
45
+ }
46
+
47
+ addSwatch(color) {
48
+ if (!this.swatches.includes(color)) {
49
+ this.swatches.push(color);
50
+ }
51
+ }
52
+
53
+ removeSwatch(index) {
54
+ this.swatches.splice(index, 1);
55
+ }
56
+
57
+ hexToRgb(hex) {
58
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
59
+ return result ? {
60
+ r: parseInt(result[1], 16),
61
+ g: parseInt(result[2], 16),
62
+ b: parseInt(result[3], 16)
63
+ } : null;
64
+ }
65
+
66
+ rgbToHex(r, g, b) {
67
+ return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
68
+ }
69
+ }
70
+
71
+ export default ColorManager;