WIP WYSIWYG ~3D SVG editor.
0
fork

Configure Feed

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

Add undo/redo shortcuts, refactor history.

Added keyboard shortcuts, and patched a number of small bugs that were
interfering with undo/redo working properly.

Commands now make copies of the active selection, TranslateCommands
now accept an outside source of oldTransforms, and the EditCommands
update the path of the selection.

+38 -16
+38 -16
zoodle.js
··· 64 64 uiElem.addEventListener("gotpointercapture", _ => uiElem.classList.add("active")); 65 65 uiElem.addEventListener("lostpointercapture", _ => uiElem.classList.remove("active")); 66 66 67 + // TODO: Maybe we shouldn't listen to these if they're in an input element 68 + this.registerShortcut("z", this.history.undo.bind(this.history), true); 69 + this.registerShortcut("Z", this.history.redo.bind(this.history), true); 70 + this.registerShortcut("y", this.history.redo.bind(this.history), true); 71 + 72 + this.selection = [this.scene.children[0].children[0]]; 73 + 67 74 this.props.updatePanel(); 68 75 this.updateHighlights(); 69 76 this.updateUI(); 70 77 this.update(); 71 78 } 72 79 80 + registerShortcut(key, callback, preventDefault) { 81 + document.body.addEventListener("keydown", e => { 82 + if (e.ctrlKey && e.key === key) { 83 + if (preventDefault) { 84 + e.preventDefault(); 85 + } 86 + callback(); 87 + } 88 + }); 89 + } 90 + 73 91 update() { 74 92 this.syncLayers(); 75 93 this.scene.updateRenderGraph(); ··· 306 324 } 307 325 start(ptr, target, x, y) { 308 326 this.widget = target; 309 - this.targets = this.editor.selection; 327 + this.targets = this.editor.selection.slice(0); 310 328 this.mode = TranslateTool.MODE_NONE; 311 329 if (!this.targets.length || target.layer !== Zoodle.LAYER_UI || !target.color) { 312 330 return; ··· 439 457 440 458 start(ptr, target, x, y) { 441 459 this.widget = target; 442 - this.targets = this.editor.selection; 460 + this.targets = this.editor.selection.slice(0); 443 461 this.mode = RotateTool.MODE_NONE; 444 462 if (!this.targets.length || target.layer !== Zoodle.LAYER_UI || !target.color) { 445 463 return; ··· 555 573 this.target = target; 556 574 } 557 575 do() { 558 - this.oldSelection = this.editor.selection; 576 + this.oldSelection = this.editor.selection.slice( 0 ); 559 577 if (!this.target) { 560 578 this.editor.clearSelection(); 561 579 } else if (this.replace) { ··· 577 595 } 578 596 579 597 class TranslateCommand extends Command { 580 - constructor(editor, target, delta) { 598 + constructor(editor, target, delta, oldTranslate = null) { 581 599 super(editor); 582 600 if (!Array.isArray(target)) { 583 601 target = [target]; 584 602 } 585 603 this.target = target; 586 604 this.delta = delta; 587 - this.oldTranslate = target.map((t) => t.translate.copy()); 605 + this.oldTranslate = oldTranslate || target.map((t) => t.translate.copy()); 588 606 } 589 607 590 608 do() { ··· 593 611 this.target.forEach((t, i) => { 594 612 t.translate.set(this.oldTranslate[i]).add(this.delta); 595 613 }); 614 + this.refresh(); 596 615 } 597 616 undo() { 598 617 if (!this.target) return console.error("Undoing TranslateCommand with no target."); ··· 600 619 this.target.forEach((t, i) => { 601 620 t.translate.set(this.oldTranslate[i]); 602 621 }); 622 + this.refresh(); 623 + } 624 + refresh() { 625 + this.editor.updateHighlights(); 626 + this.editor.updateUI(); 627 + this.editor.props.updatePanel(); 603 628 } 604 629 } 605 630 ··· 650 675 do() { 651 676 this.target.forEach( (t) => { 652 677 t[this.propId] = this.value; 678 + if (t.updatePath) t.updatePath(); 653 679 }); 654 680 } 655 681 undo() { 656 682 this.target.forEach( (t, i) => { 657 683 t[this.propId] = this.oldValue[i]; 684 + if (t.updatePath) t.updatePath(); 658 685 }); 659 686 } 660 687 } ··· 665 692 this.redoStack = []; 666 693 } 667 694 668 - push(command, newCommand = true) { 669 - if (newCommand) { 670 - this.undoStack[this.undoStack.length - 1] = command; 671 - } else { 672 - this.undoStack.push(command); 673 - } 674 - 695 + push(command) { 696 + this.undoStack.push(command); 675 697 this.redoStack = []; 676 698 } 677 699 ··· 719 741 const newValue = this.readPanel(propElem); 720 742 // start a new edit command 721 743 if (!this.command) { 722 - this.command = new EditCommand(this.editor, this.editor.selection, propElem.id, newValue, oldValue); 744 + this.command = new EditCommand(this.editor, this.editor.selection.slice(0), propElem.id, newValue, [oldValue]); 723 745 } else { 724 746 this.command.value = newValue; 725 747 } ··· 866 888 // Write property to the selected objects 867 889 // If value is null, it reads the value from the properties panel. 868 890 writeProperty(propElem, value = null) { 869 - value = value || this.readPanel(propElem); 891 + if (value === null) value = this.readPanel(propElem); 870 892 let targets = this.editor.selection; 871 893 targets.forEach( (target) => { 872 894 target[propElem.id] = value; 873 - // TODO: Add additional type information so we can check if we actually need to do this. 874 - if (target.updatePath) target.updatePath(); 895 + // TODO: Add additional type information to props so we can check if we actually need to do this. 896 + if (t.updatePath) target.updatePath(); 875 897 }); 876 898 } 877 899