[READ-ONLY] Mirror of https://github.com/flo-bit/mandala. flo-bit.github.io/mandala/
0

Configure Feed

Select the types of activity you want to include in your feed.

mandala / script.js
17 kB 678 lines
1paper.install(window); 2 3var isTouchGesture = false; 4var mandalaDrawer; 5 6window.onload = function () { 7 paper.setup("myCanvas"); 8 9 mandalaDrawer = new MandalaDrawer(); 10 11 initPinchZoom(); 12 setupEventListeners(); 13}; 14 15function setupEventListeners() { 16 let canvas = document.getElementById("myCanvas"); 17 18 canvas.addEventListener( 19 "wheel", 20 function (e) { 21 e.preventDefault(); 22 if (e.ctrlKey) { 23 view.zoom -= e.deltaY * 0.005; 24 } else { 25 view.translate(-e.deltaX, -e.deltaY); 26 } 27 }, 28 { passive: false } 29 ); 30 31 document.addEventListener("keydown", function (event) { 32 if (event.key == "s" && (event.metaKey || event.ctrlKey)) { 33 event.preventDefault(); 34 35 mandalaDrawer.saveAsSVG(); 36 } 37 38 if (event.key == "f" && (event.metaKey || event.ctrlKey)) { 39 openSettingsModal(); 40 } 41 42 if (event.key == "b" && (event.metaKey || event.ctrlKey)) { 43 openBrushModal(); 44 } 45 46 if (event.key == "+" && (event.metaKey || event.ctrlKey)) { 47 event.preventDefault(); 48 view.zoom *= 1.1; 49 } 50 if (event.key == "-" && (event.metaKey || event.ctrlKey)) { 51 event.preventDefault(); 52 view.zoom /= 1.1; 53 } 54 55 // move around 56 if (event.key == "ArrowUp") { 57 view.translate(0, 10); 58 } 59 if (event.key == "ArrowDown") { 60 view.translate(0, -10); 61 } 62 if (event.key == "ArrowLeft") { 63 view.translate(10, 0); 64 } 65 if (event.key == "ArrowRight") { 66 view.translate(-10, 0); 67 } 68 69 if (event.key == "z" && (event.metaKey || event.ctrlKey)) { 70 mandalaDrawer.undo(); 71 } 72 }); 73 74 let mirrorButton = document.getElementById("mirror-button"); 75 mirrorButton.addEventListener("click", function (e) { 76 mandalaDrawer.mirror = !mandalaDrawer.mirror; 77 if (mandalaDrawer.mirror) { 78 mirrorButton.classList.remove("is-light"); 79 } else { 80 mirrorButton.classList.add("is-light"); 81 } 82 }); 83 84 let simplifyButton = document.getElementById("simplify-button"); 85 simplifyButton.addEventListener("click", function (e) { 86 mandalaDrawer.simplify = !mandalaDrawer.simplify; 87 if (mandalaDrawer.simplify) { 88 simplifyButton.classList.remove("is-light"); 89 } else { 90 simplifyButton.classList.add("is-light"); 91 } 92 }); 93 94 let brushColorInput = document.getElementById("brush-color-input"); 95 brushColorInput.addEventListener("change", function (e) { 96 mandalaDrawer.brushColor = brushColorInput.value; 97 }); 98 99 setupBrushSizeSlider(); 100 setupRotationsSlider(); 101 102 let backgroundColorInput = document.getElementById("background-color-input"); 103 backgroundColorInput.addEventListener("change", function (e) { 104 mandalaDrawer.backgroundColor = backgroundColorInput.value; 105 closeBrushModal(); 106 }); 107 108 // file modal stuff 109 let fileInput = document.getElementById("svg-file-input"); 110 fileInput.onchange = () => { 111 if (fileInput.files.length > 0) { 112 console.log(fileInput.files[0].name); 113 project.clear(); 114 project.importSVG(fileInput.files[0], { 115 applyMatrix: false, 116 expandShapes: true, 117 insert: false, 118 onLoad: (item) => { 119 if ( 120 item.children.length == 2 && 121 item.children[1].children.length > 0 122 ) { 123 let groups = item.children[1].children; 124 for (let g of groups) { 125 project.activeLayer.addChild(g); 126 } 127 } 128 }, 129 onError: (error) => { 130 console.log(error); 131 }, 132 }); 133 134 closeFileModal(); 135 } 136 }; 137 138 let exportPNGButton = document.getElementById("png-export-button"); 139 exportPNGButton.addEventListener("click", () => { 140 mandalaDrawer.saveAsPNG(); 141 closeFileModal(); 142 }); 143 144 let exportSVGButton = document.getElementById("svg-export-button"); 145 exportSVGButton.addEventListener("click", () => { 146 mandalaDrawer.saveAsSVG(); 147 closeFileModal(); 148 }); 149 150 let clearButton = document.getElementById("clear-button"); 151 clearButton.addEventListener("click", () => { 152 project.clear(); 153 closeFileModal(); 154 mandalaDrawer.unsavedChanges = false; 155 }); 156} 157 158function initPinchZoom() { 159 const canvasElement = paper.view.element; 160 const box = canvasElement.getBoundingClientRect(); 161 const offset = new paper.Point(box.left, box.top); 162 163 const hammer = new Hammer(canvasElement, {}); 164 hammer.get("pinch").set({ enable: true }); 165 hammer.get("tap").set({ enable: true, pointers: 2 }); 166 167 let startMatrix, startMatrixInverted, p0ProjectCoords; 168 169 hammer.on("pinchstart", (e) => { 170 isTouchGesture = true; 171 172 startMatrix = paper.view.matrix.clone(); 173 startMatrixInverted = startMatrix.inverted(); 174 const p0 = getCenterPoint(e); 175 p0ProjectCoords = paper.view.viewToProject(p0); 176 }); 177 178 hammer.on("pinch", (e) => { 179 // Translate and scale view using pinch event's 'center' and 'scale' properties. 180 // Translation computes center's distance from initial center (considering current scale). 181 const p = getCenterPoint(e); 182 const pProject0 = p.transform(startMatrixInverted); 183 const delta = pProject0.subtract(p0ProjectCoords).divide(e.scale); 184 paper.view.matrix = startMatrix 185 .clone() 186 .scale(e.scale, p0ProjectCoords) 187 .translate(delta); 188 }); 189 190 hammer.on("pinchend", (e) => { 191 isTouchGesture = false; 192 }); 193 194 hammer.on("tap", (e) => { 195 mandalaDrawer.undo(); 196 }); 197 198 function getCenterPoint(e) { 199 return new paper.Point(e.center.x, e.center.y).subtract(offset); 200 } 201} 202 203function setupBrushSizeSlider() { 204 let brushSizeInput = document.getElementById("brush-size-input"); 205 let brushSizeBar = document.getElementById("brush-size-bar"); 206 207 brushSizeBar.addEventListener("click", function (e) { 208 let brushSize = e.offsetX / brushSizeBar.offsetWidth; 209 mandalaDrawer.percentageBrushSize = brushSize; 210 211 brushSizeBar.value = mandalaDrawer.percentageBrushSize * 100; 212 brushSizeInput.value = mandalaDrawer.brushSize; 213 }); 214 215 let mouseDown = false; 216 brushSizeBar.addEventListener("pointerdown", function (e) { 217 mouseDown = true; 218 }); 219 document.addEventListener("pointerup", function (e) { 220 mouseDown = false; 221 }); 222 brushSizeBar.addEventListener("pointermove", function (e) { 223 if (mouseDown == false) return; 224 let brushSize = e.offsetX / brushSizeBar.offsetWidth; 225 mandalaDrawer.percentageBrushSize = brushSize; 226 227 brushSizeBar.value = mandalaDrawer.percentageBrushSize * 100; 228 brushSizeInput.value = mandalaDrawer.brushSize; 229 }); 230 231 brushSizeInput.addEventListener("change", function (e) { 232 let brushSize = brushSizeInput.value; 233 mandalaDrawer.brushSize = brushSize; 234 235 brushSizeBar.value = mandalaDrawer.percentageBrushSize * 100; 236 brushSizeInput.value = mandalaDrawer.brushSize; 237 }); 238} 239 240function setupRotationsSlider() { 241 let rotationsInput = document.getElementById("rotations-input"); 242 let rotationsBar = document.getElementById("rotations-bar"); 243 244 rotationsBar.addEventListener("click", function (e) { 245 let rotations = e.offsetX / rotationsBar.offsetWidth; 246 mandalaDrawer.percentageRotations = rotations; 247 248 rotationsBar.value = mandalaDrawer.percentageRotations * 100; 249 rotationsInput.value = mandalaDrawer.rotations; 250 }); 251 252 let mouseDown = false; 253 rotationsBar.addEventListener("pointerdown", function (e) { 254 mouseDown = true; 255 }); 256 document.addEventListener("pointerup", function (e) { 257 mouseDown = false; 258 }); 259 rotationsBar.addEventListener("pointermove", function (e) { 260 if (mouseDown == false) return; 261 let rotations = e.offsetX / rotationsBar.offsetWidth; 262 mandalaDrawer.percentageRotations = rotations; 263 264 rotationsBar.value = mandalaDrawer.percentageRotations * 100; 265 rotationsInput.value = mandalaDrawer.rotations; 266 }); 267 rotationsInput.addEventListener("change", function (e) { 268 let rotations = rotationsInput.value; 269 mandalaDrawer.rotations = rotations; 270 271 rotationsBar.value = mandalaDrawer.percentageRotations * 100; 272 rotationsInput.value = mandalaDrawer.rotations; 273 }); 274} 275 276function downloadURI(uri, name) { 277 var link = document.createElement("a"); 278 link.download = name; 279 link.href = uri; 280 document.body.appendChild(link); 281 link.click(); 282 document.body.removeChild(link); 283 delete link; 284} 285 286class MandalaDrawer { 287 constructor() { 288 this.tool = new Tool(); 289 290 this.path = undefined; 291 this.paths = undefined; 292 293 this.minRotations = 0; 294 this.maxRotations = 32; 295 296 this.maxBrushSize = 15; 297 this.minBrushSize = 0.2; 298 299 this.unsavedChanges = false; 300 301 this.loadSettings(); 302 this.setupTool(); 303 304 view.translate(view.center); 305 } 306 307 saveSettings() { 308 let settings = { 309 backgroundColor: this.backgroundColor, 310 brushColor: this.brushColor, 311 brushSize: this.brushSize, 312 mirror: this.mirror, 313 rotations: this.rotations, 314 simplify: this.simplify, 315 }; 316 317 localStorage.setItem("settings", JSON.stringify(settings)); 318 } 319 320 loadSettings() { 321 let settings = JSON.parse(localStorage.getItem("settings")); 322 323 this._simplify = settings?.simplify ?? false; 324 this._mirror = settings?.mirror ?? true; 325 326 this._brushColor = settings?.brushColor ?? "#000000"; 327 328 this._rotations = settings?.rotations ?? 16; 329 this._brushSize = settings?.brushSize ?? 1; 330 331 this._backgroundColor = settings?.backgroundColor ?? "#FFFFFF"; 332 view.element.style.backgroundColor = this._backgroundColor; 333 334 this.updateUI(); 335 } 336 337 updateUI() { 338 let brushSizeSlider = document.getElementById("brush-size-bar"); 339 brushSizeSlider.value = this.percentageBrushSize * 100; 340 341 let brushSizeLabel = document.getElementById("brush-size-input"); 342 brushSizeLabel.value = this.brushSize; 343 344 let rotationsSlider = document.getElementById("rotations-bar"); 345 rotationsSlider.value = this.percentageRotations * 100; 346 347 let rotationsLabel = document.getElementById("rotations-input"); 348 rotationsLabel.value = this.rotations; 349 350 let brushColorInput = document.getElementById("brush-color-input"); 351 brushColorInput.value = this.brushColor; 352 353 let backgroundColorInput = document.getElementById( 354 "background-color-input" 355 ); 356 backgroundColorInput.value = this.backgroundColor; 357 358 let mirrorButton = document.getElementById("mirror-button"); 359 if (this.mirror) { 360 mirrorButton.classList.remove("is-light"); 361 } else { 362 mirrorButton.classList.add("is-light"); 363 } 364 365 let simplifyButton = document.getElementById("simplify-button"); 366 if (this.simplify) { 367 simplifyButton.classList.remove("is-light"); 368 } else { 369 simplifyButton.classList.add("is-light"); 370 } 371 } 372 373 saveAsPNG() { 374 view.element.toBlob((blob) => { 375 let dataURL = URL.createObjectURL(blob); 376 downloadURI(dataURL, "mandala.png"); 377 }); 378 } 379 380 saveAsSVG() { 381 var fileName = "mandala.svg"; 382 var url = 383 "data:image/svg+xml;utf8," + 384 encodeURIComponent( 385 project.exportSVG({ 386 asString: true, 387 bounds: "content", 388 }) 389 ); 390 downloadURI(url, fileName); 391 392 this.unsavedChanges = false; 393 } 394 395 saveAsJSON() { 396 let json = project.exportJSON(); 397 let dataURL = "data:text/json;charset=utf-8," + encodeURIComponent(json); 398 downloadURI(dataURL, "mandala.json"); 399 } 400 401 set rotations(rotations) { 402 this._rotations = rotations; 403 this.saveSettings(); 404 } 405 get rotations() { 406 return this._rotations; 407 } 408 409 set percentageRotations(percentage) { 410 if (isNaN(percentage)) percentage = 0; 411 percentage = Math.max(0, percentage); 412 percentage = Math.min(1, percentage); 413 414 let rotations = 415 this.minRotations + (this.maxRotations - this.minRotations) * percentage; 416 417 this.rotations = Math.round(rotations); 418 } 419 get percentageRotations() { 420 let percentage = 421 (this.rotations - this.minRotations) / 422 (this.maxRotations - this.minRotations); 423 percentage = Math.max(0, percentage); 424 percentage = Math.min(1, percentage); 425 426 return percentage; 427 } 428 429 set mirror(mirror) { 430 this._mirror = mirror; 431 this.saveSettings(); 432 } 433 get mirror() { 434 return this._mirror; 435 } 436 437 set simplify(simplify) { 438 this._simplify = simplify; 439 this.saveSettings(); 440 } 441 get simplify() { 442 return this._simplify; 443 } 444 445 set brushColor(color) { 446 this._brushColor = color; 447 this.saveSettings(); 448 } 449 get brushColor() { 450 return this._brushColor; 451 } 452 453 set brushSize(size) { 454 this._brushSize = size; 455 this.saveSettings(); 456 } 457 get brushSize() { 458 return this._brushSize; 459 } 460 461 set percentageBrushSize(percentage) { 462 if (isNaN(percentage)) percentage = 0; 463 464 percentage = Math.max(0, percentage); 465 percentage = Math.min(1, percentage); 466 467 this.brushSizePercentage = percentage; 468 469 let brushSize = 470 this.minBrushSize + (this.maxBrushSize - this.minBrushSize) * percentage; 471 472 this.brushSize = Math.round(brushSize * 10) / 10; 473 } 474 get percentageBrushSize() { 475 let percentage = 476 (this.brushSize - this.minBrushSize) / 477 (this.maxBrushSize - this.minBrushSize); 478 percentage = Math.max(0, percentage); 479 percentage = Math.min(1, percentage); 480 481 return percentage; 482 } 483 484 set backgroundColor(color) { 485 this._backgroundColor = color; 486 view.element.style.backgroundColor = color; 487 this.saveSettings(); 488 } 489 get backgroundColor() { 490 return this._backgroundColor; 491 } 492 493 setupTool() { 494 this.tool.onMouseDown = (event) => { 495 if (event.modifiers.shift || isTouchGesture) { 496 return; 497 } 498 499 this.paths = [this.makePath()]; 500 501 if (this.mirror) { 502 let mirrorPath = this.makePath(); 503 this.paths.push(mirrorPath); 504 } 505 506 for (let i = 1; i <= this.rotations; i++) { 507 let p = this.makePath(); 508 this.paths.push(p); 509 510 if (this.mirror) { 511 let mp = this.makePath(); 512 this.paths.push(mp); 513 } 514 } 515 516 let group = new Group(this.paths); 517 518 this.addPoint(event.point); 519 520 this.lastPathOnePoint = true; 521 }; 522 523 this.tool.onMouseDrag = (event) => { 524 if (event.modifiers.shift || isTouchGesture) { 525 view.translate(event.delta); 526 return; 527 } 528 529 if (this.paths === undefined) { 530 return; 531 } 532 533 this.addPoint(event.point); 534 535 this.lastPathOnePoint = false; 536 }; 537 538 this.tool.onMouseUp = (event) => { 539 if (event.modifiers.shift || this.paths === undefined || isTouchGesture) { 540 return; 541 } 542 543 if (this.simplify) { 544 for (let p of this.paths) { 545 p.simplify(10); 546 } 547 } 548 549 this.paths = undefined; 550 551 // remove path if it only has one point 552 if (this.lastPathOnePoint) { 553 this.undo(); 554 return; 555 } 556 557 this.unsavedChanges = true; 558 }; 559 } 560 561 makePath() { 562 let p = new Path(); 563 p.strokeColor = this.brushColor; 564 p.strokeWidth = this.brushSize; 565 return p; 566 } 567 568 mirrorPoint(point) { 569 return new Point(point.x, -point.y); 570 } 571 572 addPoint(point) { 573 if (this.paths === undefined || this.paths.length < 1) return; 574 this.paths[0].add(point); 575 576 let index = 1; 577 if (this.mirror) { 578 this.paths[index].add(this.mirrorPoint(point)); 579 index += 1; 580 } 581 582 for (let i = 1; i <= this.rotations; i++) { 583 let p = point.rotate((360 / this.rotations) * i); 584 this.paths[index].add(p); 585 index += 1; 586 587 if (this.mirror) { 588 this.paths[index].add(this.mirrorPoint(p)); 589 index += 1; 590 } 591 } 592 } 593 594 undo() { 595 let last = paper.project.activeLayer.lastChild; 596 if (last == undefined) return; 597 last.remove(); 598 } 599} 600 601window.onbeforeunload = function (e) { 602 e = e || window.event; 603 604 if (!mandalaDrawer.unsavedChanges) return; 605 606 // For IE and Firefox prior to version 4 607 if (e) { 608 e.returnValue = "Sure?"; 609 } 610 611 // For Safari 612 return "Sure?"; 613}; 614 615function openModal($el) { 616 $el.classList.add("is-active"); 617} 618 619function closeModal($el) { 620 $el.classList.remove("is-active"); 621} 622 623function openBrushModal() { 624 openModal(document.getElementById("brush-modal")); 625} 626 627function closeBrushModal() { 628 closeModal(document.getElementById("brush-modal")); 629} 630 631function closeFileModal() { 632 closeModal(document.getElementById("file-modal")); 633} 634 635function openFileModal() { 636 openModal(document.getElementById("file-modal")); 637} 638 639document.addEventListener("DOMContentLoaded", () => { 640 function closeAllModals() { 641 (document.querySelectorAll(".modal") || []).forEach(($modal) => { 642 closeModal($modal); 643 }); 644 } 645 646 // Add a click event on buttons to open a specific modal 647 (document.querySelectorAll(".js-modal-trigger") || []).forEach(($trigger) => { 648 const modal = $trigger.dataset.target; 649 const $target = document.getElementById(modal); 650 651 $trigger.addEventListener("click", () => { 652 openModal($target); 653 }); 654 }); 655 656 // Add a click event on various child elements to close the parent modal 657 ( 658 document.querySelectorAll( 659 ".modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button" 660 ) || [] 661 ).forEach(($close) => { 662 const $target = $close.closest(".modal"); 663 664 $close.addEventListener("click", () => { 665 closeModal($target); 666 }); 667 }); 668 669 // Add a keyboard event to close all modals 670 document.addEventListener("keydown", (event) => { 671 const e = event || window.event; 672 673 if (e.keyCode === 27) { 674 // Escape key 675 closeAllModals(); 676 } 677 }); 678});