{this.value-=2,this.value<=0&&(this.value=2),this.lineWidthElement.innerText=this.value,this.updateWidthValueOnScreen(this.value)})),this.increaseBtn.addEventListener("click",(()=>{this.value+=2,this.value>=20&&(this.value=20),this.innerText=this.value,this.updateWidthValueOnScreen(this.value)}))}adjustColor(){this.color=document.getElementById("color"),this.color.addEventListener("change",(t=>{this.color=t.target.value}))}updateWidthValueOnScreen(t){this.lineWidthElement.innerText=t}drawLine(t,e,i,n){this.context.lineWidth=2*this.lineWidthElement.innerText,this.context.beginPath(),this.context.moveTo(t,e),this.context.lineTo(i,n),this.context.strokeStyle=this.color,this.context.stroke()}drawCircle(t,e){this.context.beginPath(),this.context.arc(t,e,this.lineWidthElement.innerText,0,2*Math.PI),this.context.fillStyle=this.color,this.context.fill()}clearBoard(){document.getElementById("trash").addEventListener("click",(()=>{this.context.clearRect(0,0,this.canvas.width,this.canvas.height)}))}}("sketch").setup()})();
--------------------------------------------------------------------------------
/scripts/modules/sketch.js:
--------------------------------------------------------------------------------
1 | import Drawing from "./drawing.js";
2 |
3 | export default class Sketch extends Drawing {
4 | constructor(canvas) {
5 | super(canvas);
6 | this.pressed = false;
7 | this.x = undefined;
8 | this.y = undefined;
9 | this.lineWidthLimit = 76;
10 | this.ongoingTouches = new Array;
11 | this.paths = new Array;
12 | this.states = new Array;
13 | this.color = "#000000"
14 | this.value = 2
15 | this.user_sketches = JSON.parse(localStorage.getItem("user_sketches")) || []
16 | this.handleStart = this.handleStart.bind(this);
17 | this.handleEnd = this.handleEnd.bind(this);
18 | this.handleMove = this.handleMove.bind(this);
19 | this.handleCancel = this.handleCancel.bind(this);
20 | this.ongoingTouchIndexById = this.ongoingTouchIndexById.bind(this);
21 | this.copyTouch = this.copyTouch.bind(this);
22 | this.UpdateWidthValueOnScreen();
23 | this.UpdateSavesModal();
24 | this.UpdateCanvasSize();
25 | }
26 |
27 | //#region Updates functions
28 | UpdateSavesModal() {
29 | const modalSaves = document.getElementById('modalSaves');
30 | modalSaves.innerHTML = "";
31 | if (this.user_sketches.length > 0)
32 | this.user_sketches.map(sketch => modalSaves.innerHTML += `${sketch.title.length > 7 ? sketch.title.slice(0, 7).trim() + "..." : sketch.title}
`);
33 | else
34 | modalSaves.innerHTML = `You have no saves !
`;
35 | document.querySelectorAll('.save').forEach(save => save.addEventListener('click', event => this.Save_Handle(event)));
36 | document.querySelectorAll('.delete').forEach(deleteBtn => deleteBtn.addEventListener('click', event => this.DeleteSave_Handle(event)));
37 | }
38 |
39 | UpdateWidthValueOnScreen() {
40 | document.getElementById('widthValue').innerText = this.value;
41 | }
42 | //#endregion
43 |
44 | //#region Screen recognitions
45 | MouseRecognitions() {
46 | this.canvas.addEventListener('mousedown', event => {
47 | if (document.getElementById('text').classList.contains("selected")) this.CreateTextModal(event.offsetX, event.offsetY);
48 | else if (document.querySelector(".polygon.selected")) {
49 | const id = document.querySelector(".polygon.selected").id;
50 | this.paths.push({ states: {id, x: event.offsetX, y: event.offsetY, size: this.value}, type: "polygon", colorStyle: this.color, erasedStates: new Array()});
51 | this.DrawPolygon(id, event.offsetX, event.offsetY, this.color, this.value);
52 | }
53 | else {
54 | this.pressed = true;
55 | this.x = event.offsetX;
56 | this.y = event.offsetY;
57 | }
58 | })
59 |
60 | this.canvas.addEventListener('mouseup', () => {
61 | if (this.states.length > 0) {
62 | this.paths.push({ states: new Array(...this.states), type: "sketch", colorStyle: this.color, erasedStates: new Array()});
63 | this.states = [];
64 | }
65 | this.pressed = false;
66 | this.x = undefined;
67 | this.y = undefined;
68 | })
69 |
70 | this.canvas.addEventListener('mousemove', event => {
71 | if(this.pressed) {
72 | const x2 = event.offsetX;
73 | const y2 = event.offsetY;
74 | if (document.getElementById('eraser').classList.contains("selected")) {
75 | const size = this.value * 4;
76 | this.paths[this.paths.length - 1].erasedStates.push({x: x2 - size / 2, y: y2 - size / 2, size});
77 | this.context.clearRect(x2 - size / 2, y2 - size / 2, size, size);
78 | } else {
79 | this.DrawCircle(x2, y2, this.value, this.color)
80 | this.DrawLine(this.x, this.y, x2, y2, this.value, this.color);
81 | this.states.push({x: x2, y: y2, prevX: this.x, prevY: this.y, size: this.value});
82 | this.x = x2;
83 | this.y = y2;
84 | }
85 | }
86 | })
87 | }
88 |
89 | touchRecognitions() {
90 | this.canvas.addEventListener('touchstart', this.handleStart, true);
91 | this.canvas.addEventListener('touchend', this.handleEnd, true);
92 | this.canvas.addEventListener('touchmove', this.handleMove, false);
93 | this.canvas.addEventListener('touchcancel', this.handleCancel, false);
94 | this.canvas.addEventListener('touchleave', this.handleEnd, false)
95 | }
96 | //#endregion
97 |
98 | //#region Click/Key handlers
99 | Undo_Handle() {
100 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
101 | this.paths.forEach((path, index) => {
102 | if (index < this.paths.length - 1)
103 | this.ReDraw(path);
104 | else
105 | this.paths.pop();
106 | })
107 | }
108 |
109 | DecreaseBtn_Handle() {
110 | this.value -= 2;
111 | if(this.value <= 0) this.value = 2;
112 | this.UpdateWidthValueOnScreen();
113 | }
114 |
115 | IncreaseBtn_Handle() {
116 | this.value += 2;
117 | if(this.value >= this.lineWidthLimit) this.value = this.lineWidthLimit;
118 | this.UpdateWidthValueOnScreen();
119 | }
120 |
121 | DeleteSave_Handle(e) {
122 | this.user_sketches = this.user_sketches.filter(save => save.title != e.target.getAttribute("key"));
123 | localStorage.setItem("user_sketches", JSON.stringify(this.user_sketches))
124 | this.UpdateSavesModal();
125 | }
126 |
127 | Save_Handle(e) {
128 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
129 | const selectedSketch = this.user_sketches.find(save => save.title === e.target.getAttribute("key"));
130 | this.paths = selectedSketch.paths;
131 | this.canvas.style.backgroundColor = selectedSketch.backgroundColor;
132 | selectedSketch.paths.map(path => this.ReDraw(path));
133 | }
134 |
135 | FormSave_Handle(e) {
136 | e.preventDefault();
137 | const title = e.target[0].value.trim();
138 | if (this.user_sketches.some(sketch => sketch.title == title)) {
139 | if (confirm(`This title is already been used by another sketch, do you want to update the sketch with the same name ?`))
140 | this.user_sketches = this.user_sketches.filter(sketch => sketch.title != title);
141 | else return;
142 | }
143 | this.user_sketches.push({paths: this.paths, backgroundColor: this.canvas.style.backgroundColor, title})
144 | localStorage.setItem("user_sketches", JSON.stringify(this.user_sketches))
145 | e.target.reset();
146 | document.getElementById('modalSave').classList.toggle("desappear")
147 | this.UpdateSavesModal();
148 | }
149 |
150 | CanvasClear_Handle() {
151 | this.states = [];
152 | this.paths = [];
153 | this.canvas.style.backgroundColor = "#EEEE";
154 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
155 | }
156 |
157 | FormText_Handle(e, x, y) {
158 | e.preventDefault();
159 | this.DrawText(e.target[0].value, x, y, this.color, this.value);
160 | this.paths.push({states: { text: e.target[0].value, x, y, size: this.value}, type: "text", colorStyle: this.color, erasedStates: new Array()});
161 | e.target.remove();
162 | }
163 | //#endregion
164 |
165 | // #region Mobile drawing handlers
166 | handleStart(event) {
167 | const touches = event.changedTouches;
168 | event.preventDefault();
169 |
170 | for(let i = 0; i < touches.length; i++) {
171 | this.ongoingTouches.push(this.copyTouch(touches[i]));
172 | }
173 | }
174 |
175 | handleMove(event) {
176 | event.preventDefault();
177 | const touches = event.changedTouches;
178 |
179 | for(let i = 0; i < touches.length; i++) {
180 | const idx = this.ongoingTouchIndexById(touches[i].identifier);
181 | this.x = this.ongoingTouches[idx].clientX;
182 | this.y = this.ongoingTouches[idx].clientY;
183 | const x2 = touches[i].clientX;
184 | const y2 = touches[i].clientY;
185 |
186 | if(idx >= 0) {
187 | this.DrawLine(this.x, this.y, x2, y2, this.value, this.color)
188 | this.DrawCircle(this.x, this.y, this.value, this.color)
189 | this.ongoingTouches.splice(idx, 1, this.copyTouch(touches[i]));
190 | } else console.error("Can't figure out which touch to continue.");
191 | }
192 | }
193 |
194 | handleEnd(event) {
195 | event.preventDefault();
196 | const touches = event.changedTouches;
197 |
198 | for(let i = 0; i < touches.length; i++) {
199 | const idx = this.ongoingTouchIndexById(touches[i].identifier);
200 | if(idx >= 0) {
201 | this.context.beginPath();
202 | this.ongoingTouches.splice(idx, 1);
203 | } else {
204 | console.error("Can't figure out which touch to end.")
205 | }
206 | }
207 | }
208 |
209 | handleCancel(event) {
210 | event.preventDefault();
211 | const touches = event.changedTouches;
212 |
213 | for(let i = 0; i < touches.length; i++)
214 | this.ongoingTouches.splice(i, 1);
215 | }
216 |
217 | copyTouch(touch) {
218 | return { identifier: touch.identifier, clientX: touch.clientX, clientY: touch.clientY };
219 | }
220 |
221 | ongoingTouchIndexById(idToFind) {
222 | for (var i=0; i < this.ongoingTouches.length; i++) {
223 | var id = this.ongoingTouches[i].identifier;
224 |
225 | if (id == idToFind) return i;
226 | }
227 | return -1;
228 | }
229 | //#endregion
230 |
231 | Setup() {
232 | this.AddEventListeners();
233 | this.MouseRecognitions();
234 | this.touchRecognitions();
235 | }
236 |
237 | AddEventListeners() {
238 | document.getElementById('canvas-color').addEventListener('input', event => this.canvas.style.backgroundColor = event.target.value);
239 | document.getElementById('color').addEventListener('input', event => this.color = event.target.value);
240 | document.getElementById('decrease').addEventListener('click', () => this.DecreaseBtn_Handle());
241 | document.getElementById('increase').addEventListener('click', () => this.IncreaseBtn_Handle());
242 | document.getElementById('trash').addEventListener('click', () => this.CanvasClear_Handle());
243 | document.getElementById('undo').addEventListener('click', () => this.Undo_Handle());
244 | document.getElementById('formSave').addEventListener('submit', event => this.FormSave_Handle(event));
245 | document.getElementById("save").addEventListener('click', () => document.getElementById('modalSave').classList.toggle("desappear"));
246 | document.getElementById("load").addEventListener('click', () => document.getElementById('modalSaves').classList.toggle("desappear"));
247 | document.getElementById("info").addEventListener('click', () => document.getElementById('modalInfo').classList.toggle("desappear"));
248 | window.addEventListener('resize', () => this.UpdateCanvasSize());
249 | document.querySelectorAll('.icon.selectable').forEach(icon => icon.addEventListener('click', event => this.ToggleTools(event)));
250 | window.addEventListener('keydown', e => {
251 | switch (e.code) {
252 | case "Numpad1":
253 | document.getElementById('modalInfo').classList.toggle("desappear");
254 | break;
255 |
256 | case "Numpad2":
257 | this.CanvasClear_Handle()
258 | break;
259 |
260 | case "Numpad3":
261 | this.DecreaseBtn_Handle();
262 | break;
263 |
264 | case "Numpad4":
265 | this.IncreaseBtn_Handle();
266 | break;
267 |
268 | case "Numpad5":
269 | this.Undo_Handle();
270 | break;
271 |
272 | case "Numpad6":
273 | this.UpdateSavesModal();
274 | document.getElementById('modalSaves').classList.toggle("desappear");
275 | break;
276 |
277 | case "Numpad7":
278 | document.getElementById('modalSave').classList.toggle("desappear")
279 | break;
280 |
281 | default:
282 | break;
283 | }
284 | })
285 | }
286 |
287 | ToggleTools(e) {
288 | document.querySelector(".icon.selected").classList.remove("selected");
289 | e.target.classList.add("selected");
290 | }
291 |
292 | CreateTextModal(x, y) {
293 | this.pressed = false;
294 | const txtModal = document.createElement('form')
295 | txtModal.innerHTML = ``;
296 | document.body.appendChild(txtModal);
297 | txtModal.children[1].addEventListener('click', () => txtModal.remove());
298 | txtModal.children[0].addEventListener('input', e => e.target.setAttribute("style", `background-color: ${this.canvas.style.backgroundColor}; font-size: ${this.value * 2}px; color: ${this.color}`));
299 | txtModal.addEventListener('submit', e => this.FormText_Handle(e, x, y));
300 | txtModal.setAttribute('class', "textModal");
301 | txtModal.setAttribute('style', `top: ${y - txtModal.clientHeight / 2}px; left: ${x - txtModal.clientWidth / 2}px`);
302 | }
303 | }
--------------------------------------------------------------------------------