From c496ab89782458930b9e58202dde2abc0d31c6f0 Mon Sep 17 00:00:00 2001
From: JFH <20402845+jfhenon@users.noreply.github.com>
Date: Sat, 2 Jan 2021 23:54:08 +0100
Subject: [PATCH] move init (600 lines+) in its own file
---
src/editor/EditorStartup.js | 855 ++++++++++++++++++++++++++++++++++++
src/editor/svgedit.js | 844 +----------------------------------
2 files changed, 862 insertions(+), 837 deletions(-)
create mode 100644 src/editor/EditorStartup.js
diff --git a/src/editor/EditorStartup.js b/src/editor/EditorStartup.js
new file mode 100644
index 00000000..6426e961
--- /dev/null
+++ b/src/editor/EditorStartup.js
@@ -0,0 +1,855 @@
+/* globals $ seConfirm seAlert */
+import './touch.js';
+import {convertUnit} from '../common/units.js';
+import {
+ hasCustomHandler, getCustomHandler, injectExtendedContextMenuItemsIntoDom
+} from './contextmenu.js';
+
+import SvgCanvas from '../svgcanvas/svgcanvas.js';
+import LayersPanel from './panels/LayersPanel.js';
+import LeftPanelHandlers from './panels/LeftPanelHandlers.js';
+import BottomPanelHandlers from './panels/BottomPanelHandlers.js';
+import TopPanelHandlers from './panels/TopPanelHandlers.js';
+import Rulers from './Rulers.js';
+
+/**
+ * @fires module:svgcanvas.SvgCanvas#event:svgEditorReady
+ * @returns {void}
+ */
+const readySignal = () => {
+ // let the opener know SVG Edit is ready (now that config is set up)
+ const w = window.opener || window.parent;
+ if (w) {
+ try {
+ /**
+ * Triggered on a containing `document` (of `window.opener`
+ * or `window.parent`) when the editor is loaded.
+ * @event module:SVGEditor#event:svgEditorReadyEvent
+ * @type {Event}
+ * @property {true} bubbles
+ * @property {true} cancelable
+ */
+ /**
+ * @name module:SVGthis.svgEditorReadyEvent
+ * @type {module:SVGEditor#event:svgEditorReadyEvent}
+ */
+ const svgEditorReadyEvent = new w.CustomEvent('svgEditorReady', {
+ bubbles: true,
+ cancelable: true
+ });
+ w.document.documentElement.dispatchEvent(svgEditorReadyEvent);
+ } catch (e) {}
+ }
+};
+
+const {$id} = SvgCanvas;
+
+/**
+ *
+ */
+class EditorStartup {
+ /**
+ *
+ */
+ constructor () {
+ this.extensionsAdded = false;
+ this.messageQueue = [];
+ }
+ /**
+ * Auto-run after a Promise microtask.
+ * @function module:SVGthis.init
+ * @returns {void}
+ */
+ async init () {
+ try {
+ // Image props dialog added to DOM
+ const newSeImgPropDialog = document.createElement('se-img-prop-dialog');
+ newSeImgPropDialog.setAttribute('id', 'se-img-prop');
+ document.body.append(newSeImgPropDialog);
+ // editor prefences dialoag added to DOM
+ const newSeEditPrefsDialog = document.createElement('se-edit-prefs-dialog');
+ newSeEditPrefsDialog.setAttribute('id', 'se-edit-prefs');
+ document.body.append(newSeEditPrefsDialog);
+ // canvas menu added to DOM
+ const dialogBox = document.createElement('se-cmenu_canvas-dialog');
+ dialogBox.setAttribute('id', 'se-cmenu_canvas');
+ document.body.append(dialogBox);
+ // alertDialog added to DOM
+ const alertBox = document.createElement('se-alert-dialog');
+ alertBox.setAttribute('id', 'se-alert-dialog');
+ document.body.append(alertBox);
+ // promptDialog added to DOM
+ const promptBox = document.createElement('se-prompt-dialog');
+ promptBox.setAttribute('id', 'se-prompt-dialog');
+ document.body.append(promptBox);
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }
+
+ if ('localStorage' in window) { // && onWeb removed so Webkit works locally
+ this.storage = window.localStorage;
+ }
+
+ this.configObj.load();
+
+ /**
+ * @name module:SVGthis.canvas
+ * @type {module:svgcanvas.SvgCanvas}
+ */
+ this.svgCanvas = new SvgCanvas(
+ $id('svgcanvas'),
+ this.configObj.curConfig
+ );
+
+ this.leftPanelHandlers = new LeftPanelHandlers(this);
+ this.bottomPanelHandlers = new BottomPanelHandlers(this);
+ this.topPanelHandlers = new TopPanelHandlers(this);
+ this.layersPanel = new LayersPanel(this);
+
+ const {undoMgr} = this.svgCanvas;
+ this.workarea = $('#workarea');
+ this.canvMenu = document.getElementById('se-cmenu_canvas');
+ this.exportWindow = null;
+ this.defaultImageURL = this.configObj.curConfig.imgPath + 'logo.svg';
+ const zoomInIcon = 'crosshair';
+ const zoomOutIcon = 'crosshair';
+ this.uiContext = 'toolbars';
+
+ // For external openers
+ readySignal();
+
+ this.rulers = new Rulers(this);
+
+ this.layersPanel.populateLayers();
+ this.selectedElement = null;
+ this.multiselected = false;
+
+ $('#cur_context_panel').delegate('a', 'click', (evt) => {
+ const link = $(evt.currentTarget);
+ if (link.attr('data-root')) {
+ this.svgCanvas.leaveContext();
+ } else {
+ this.svgCanvas.setContext(link.text());
+ }
+ this.svgCanvas.clearSelection();
+ return false;
+ });
+ // bind the selected event to our function that handles updates to the UI
+ this.svgCanvas.bind('selected', this.selectedChanged.bind(this));
+ this.svgCanvas.bind('transition', this.elementTransition.bind(this));
+ this.svgCanvas.bind('changed', this.elementChanged.bind(this));
+ this.svgCanvas.bind('saved', this.saveHandler.bind(this));
+ this.svgCanvas.bind('exported', this.exportHandler.bind(this));
+ this.svgCanvas.bind('exportedPDF', function (win, data) {
+ if (!data.output) { // Ignore Chrome
+ return;
+ }
+ const {exportWindowName} = data;
+ if (exportWindowName) {
+ this.exportWindow = window.open('', this.exportWindowName); // A hack to get the window via JSON-able name without opening a new one
+ }
+ if (!this.exportWindow || this.exportWindow.closed) {
+ seAlert(this.uiStrings.notification.popupWindowBlocked);
+ return;
+ }
+ this.exportWindow.location.href = data.output;
+ }.bind(this));
+ this.svgCanvas.bind('zoomed', this.zoomChanged.bind(this));
+ this.svgCanvas.bind('zoomDone', this.zoomDone.bind(this));
+ this.svgCanvas.bind(
+ 'updateCanvas',
+ /**
+ * @param {external:Window} win
+ * @param {PlainObject} centerInfo
+ * @param {false} centerInfo.center
+ * @param {module:math.XYObject} centerInfo.newCtr
+ * @listens module:svgcanvas.SvgCanvas#event:updateCanvas
+ * @returns {void}
+ */
+ function (win, {center, newCtr}) {
+ this.updateCanvas(center, newCtr);
+ }.bind(this)
+ );
+ this.svgCanvas.bind('contextset', this.contextChanged.bind(this));
+ this.svgCanvas.bind('extension_added', this.extAdded.bind(this));
+ this.svgCanvas.textActions.setInputElem($('#text')[0]);
+
+ this.setBackground(this.configObj.pref('bkgd_color'), this.configObj.pref('bkgd_url'));
+
+ // update resolution option with actual resolution
+ const res = this.svgCanvas.getResolution();
+ if (this.configObj.curConfig.baseUnit !== 'px') {
+ res.w = convertUnit(res.w) + this.configObj.curConfig.baseUnit;
+ res.h = convertUnit(res.h) + this.configObj.curConfig.baseUnit;
+ }
+ $('#se-img-prop').attr('dialog', 'close');
+ $('#se-img-prop').attr('title', this.svgCanvas.getDocumentTitle());
+ $('#se-img-prop').attr('width', res.w);
+ $('#se-img-prop').attr('height', res.h);
+ $('#se-img-prop').attr('save', this.configObj.pref('img_save'));
+
+ // Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
+ $('select').change((evt) => { $(evt.currentTarget).blur(); });
+
+ // fired when user wants to move elements to another layer
+ let promptMoveLayerOnce = false;
+ $('#selLayerNames').change((evt) => {
+ const destLayer = evt.currentTarget.options[evt.currentTarget.selectedIndex].value;
+ const confirmStr = this.uiStrings.notification.Qmovethis.elemsToLayer.replace('%s', destLayer);
+ /**
+ * @param {boolean} ok
+ * @returns {void}
+ */
+ const moveToLayer = (ok) => {
+ if (!ok) { return; }
+ promptMoveLayerOnce = true;
+ this.svgCanvas.moveSelectedToLayer(destLayer);
+ this.svgCanvas.clearSelection();
+ this.layersPanel.populateLayers();
+ };
+ if (destLayer) {
+ if (promptMoveLayerOnce) {
+ moveToLayer(true);
+ } else {
+ const ok = seConfirm(confirmStr);
+ if (!ok) {
+ return;
+ }
+ moveToLayer(true);
+ }
+ }
+ });
+
+ $('#font_family').change((evt) => {
+ this.svgCanvas.setFontFamily(evt.currentTarget.value);
+ });
+
+ $('#seg_type').change((evt) => {
+ this.svgCanvas.setSegType($(evt.currentTarget).val());
+ });
+
+ $('#text').bind('keyup input', (evt) => {
+ this.svgCanvas.setTextContent(evt.currentTarget.value);
+ });
+
+ $('#image_url').change((evt) => {
+ this.setImageURL(evt.currentTarget.value);
+ });
+
+ $('#link_url').change((evt) => {
+ if (evt.currentTarget.value.length) {
+ this.svgCanvas.setLinkURL(evt.currentTarget.value);
+ } else {
+ this.svgCanvas.removeHyperlink();
+ }
+ });
+
+ $('#g_title').change((evt) => {
+ this.svgCanvas.setGroupTitle(evt.currentTarget.value);
+ });
+
+ const wArea = this.workarea[0];
+
+ let lastX = null, lastY = null,
+ panning = false, keypan = false;
+
+ $('#svgcanvas').bind('mousemove mouseup', function (evt) {
+ if (panning === false) { return true; }
+
+ wArea.scrollLeft -= (evt.clientX - lastX);
+ wArea.scrollTop -= (evt.clientY - lastY);
+
+ lastX = evt.clientX;
+ lastY = evt.clientY;
+
+ if (evt.type === 'mouseup') { panning = false; }
+ return false;
+ }).mousedown(function (evt) {
+ if (evt.button === 1 || keypan === true) {
+ panning = true;
+ lastX = evt.clientX;
+ lastY = evt.clientY;
+ return false;
+ }
+ return true;
+ });
+
+ $(window).mouseup(() => {
+ panning = false;
+ });
+
+ $(document).bind('keydown', 'space', function (evt) {
+ this.svgCanvas.spaceKey = keypan = true;
+ evt.preventDefault();
+ }.bind(this)).bind('keyup', 'space', function (evt) {
+ evt.preventDefault();
+ this.svgCanvas.spaceKey = keypan = false;
+ }.bind(this)).bind('keydown', 'shift', function (evt) {
+ if (this.svgCanvas.getMode() === 'zoom') {
+ this.workarea.css('cursor', zoomOutIcon);
+ }
+ }.bind(this)).bind('keyup', 'shift', function (evt) {
+ if (this.svgCanvas.getMode() === 'zoom') {
+ this.workarea.css('cursor', zoomInIcon);
+ }
+ }.bind(this));
+
+ /**
+ * @function module:SVGthis.setPanning
+ * @param {boolean} active
+ * @returns {void}
+ */
+ this.setPanning = (active) => {
+ this.svgCanvas.spaceKey = keypan = active;
+ };
+
+ const button = $('#main_icon');
+ const overlay = $('#main_icon span');
+ const list = $('#main_menu');
+
+ let onButton = false;
+ let height = 0;
+ let jsHover = true;
+ let setClick = false;
+
+ $(window).mouseup(function (evt) {
+ if (!onButton) {
+ button.removeClass('buttondown');
+ // do not hide if it was the file input as that input needs to be visible
+ // for its change event to fire
+ if (evt.target.tagName !== 'INPUT') {
+ list.fadeOut(200);
+ } else if (!setClick) {
+ setClick = true;
+ $(evt.target).click(() => {
+ list.css('margin-left', '-9999px').show();
+ });
+ }
+ }
+ onButton = false;
+ }).mousedown(function (evt) {
+ // $('.contextMenu').hide();
+ const islib = $(evt.target).closest('.contextMenu').length;
+ if (!islib) {
+ $('.contextMenu').fadeOut(250);
+ }
+ });
+
+ overlay.bind('mousedown', () => {
+ if (!button.hasClass('buttondown')) {
+ // Margin must be reset in case it was changed before;
+ list.css('margin-left', 0).show();
+ if (!height) {
+ height = list.height();
+ }
+ // Using custom animation as slideDown has annoying 'bounce effect'
+ list.css('height', 0).animate({
+ height
+ }, 200);
+ onButton = true;
+ } else {
+ list.fadeOut(200);
+ }
+ button.toggleClass('buttondown buttonup');
+ }).hover(() => {
+ onButton = true;
+ }).mouseout(() => {
+ onButton = false;
+ });
+
+ const listItems = $('#main_menu li');
+
+ // Check if JS method of hovering needs to be used (Webkit bug)
+ listItems.mouseover(function () {
+ jsHover = ($(this).css('background-color') === 'rgba(0, 0, 0, 0)');
+
+ listItems.unbind('mouseover');
+ if (jsHover) {
+ listItems.mouseover(() => {
+ this.style.backgroundColor = '#FFC';
+ }).mouseout((evt) => {
+ evt.currentTarget.style.backgroundColor = 'transparent';
+ return true;
+ });
+ }
+ });
+ // Unfocus text input when this.workarea is mousedowned.
+ let inp;
+ /**
+ *
+ * @returns {void}
+ */
+ const unfocus = () => {
+ $(inp).blur();
+ };
+
+ $('#svg_editor').find('button, select, input:not(#text)').focus(() => {
+ inp = this;
+ this.uiContext = 'toolbars';
+ this.workarea.mousedown(unfocus);
+ }).blur(() => {
+ this.uiContext = 'canvas';
+ this.workarea.unbind('mousedown', unfocus);
+ // Go back to selecting text if in textedit mode
+ if (this.svgCanvas.getMode() === 'textedit') {
+ $('#text').focus();
+ }
+ });
+ const winWh = {width: $(window).width(), height: $(window).height()};
+
+ window.addEventListener('resize', (evt) => {
+ Object.entries(winWh).forEach(([type, val]) => {
+ const curval = $(window)[type]();
+ this.workarea[0]['scroll' + (type === 'width' ? 'Left' : 'Top')] -= (curval - val) / 2;
+ winWh[type] = curval;
+ });
+ });
+
+ this.workarea.scroll(() => {
+ // TODO: jQuery's scrollLeft/Top() wouldn't require a null check
+ this.rulers.manageScroll();
+ });
+
+ $('#url_notice').click(() => {
+ seAlert(this.title);
+ });
+
+ $('#stroke_width').val(this.configObj.curConfig.initStroke.width);
+ $('#group_opacity').val(this.configObj.curConfig.initOpacity * 100);
+
+ $('#group_opacityLabel').click(() => {
+ $('#opacity_dropdown button').mousedown();
+ $(window).mouseup();
+ });
+
+ $('.push_button').mousedown(() => {
+ if (!$(this).hasClass('disabled')) {
+ $(this).addClass('push_button_pressed').removeClass('push_button');
+ }
+ }).mouseout(() => {
+ $(this).removeClass('push_button_pressed').addClass('push_button');
+ }).mouseup(() => {
+ $(this).removeClass('push_button_pressed').addClass('push_button');
+ });
+
+ this.layersPanel.populateLayers();
+
+ const centerCanvas = () => {
+ // this centers the canvas vertically in the this.workarea (horizontal handled in CSS)
+ this.workarea.css('line-height', this.workarea.height() + 'px');
+ };
+
+ $(window).bind('load resize', centerCanvas);
+
+ // Prevent browser from erroneously repopulating fields
+ $('input,select').attr('autocomplete', 'off');
+
+ /**
+ * Associate all button actions as well as non-button keyboard shortcuts.
+ */
+ this.leftPanelHandlers.init();
+ this.bottomPanelHandlers.init();
+ this.topPanelHandlers.init();
+ this.layersPanel.init();
+
+ $id('tool_clear').addEventListener('click', this.clickClear.bind(this));
+ $id('tool_open').addEventListener('click', function (e) {
+ this.clickOpen();
+ window.dispatchEvent(new CustomEvent('openImage'));
+ }.bind(this));
+ $id('tool_import').addEventListener('click', (e) => {
+ this.clickImport();
+ window.dispatchEvent(new CustomEvent('importImage'));
+ });
+ $id('tool_save').addEventListener('click', function (e) {
+ const $editorDialog = document.getElementById('se-svg-editor-dialog');
+ const editingsource = $editorDialog.getAttribute('dialog') === 'open';
+ if (editingsource) {
+ this.saveSourceEditor();
+ } else {
+ this.clickSave();
+ }
+ }.bind(this));
+ $id('tool_export').addEventListener('click', this.clickExport.bind(this));
+ $id('tool_docprops').addEventListener('click', this.showDocProperties.bind(this));
+ $id('tool_editor_prefs').addEventListener('click', this.showPreferences.bind(this));
+ $id('tool_editor_homepage').addEventListener('click', this.openHomePage.bind(this));
+ $id('se-img-prop').addEventListener('change', function (e) {
+ if (e.detail.dialog === 'closed') {
+ this.hideDocProperties();
+ } else {
+ this.saveDocProperties(e);
+ }
+ }.bind(this));
+ $id('se-edit-prefs').addEventListener('change', function (e) {
+ if (e.detail.dialog === 'closed') {
+ this.hidePreferences();
+ } else {
+ this.savePreferences(e);
+ }
+ }.bind(this));
+ $id('se-svg-editor-dialog').addEventListener('change', function (e) {
+ if (e?.detail?.copy === 'click') {
+ this.cancelOverlays(e);
+ } else if (e?.detail?.dialog === 'closed') {
+ this.hideSourceEditor();
+ } else {
+ this.saveSourceEditor(e);
+ }
+ }.bind(this));
+ $id('se-cmenu_canvas').addEventListener('change', function (e) {
+ const action = e?.detail?.trigger;
+ switch (action) {
+ case 'delete':
+ this.svgCanvas.deleteSelectedElements();
+ break;
+ case 'cut':
+ this.cutSelected();
+ break;
+ case 'copy':
+ this.copySelected();
+ break;
+ case 'paste':
+ this.svgCanvas.pasteElements();
+ break;
+ case 'paste_in_place':
+ this.svgCanvas.pasteElements('in_place');
+ break;
+ case 'group':
+ case 'group_elements':
+ this.svgCanvas.groupSelectedElements();
+ break;
+ case 'ungroup':
+ this.svgCanvas.ungroupSelectedElement();
+ break;
+ case 'move_front':
+ this.svgCanvas.moveToTopSelectedElement();
+ break;
+ case 'move_up':
+ this.moveUpDownSelected('Up');
+ break;
+ case 'move_down':
+ this.moveUpDownSelected('Down');
+ break;
+ case 'move_back':
+ this.svgCanvas.moveToBottomSelected();
+ break;
+ default:
+ if (hasCustomHandler(action)) {
+ getCustomHandler(action).call();
+ }
+ break;
+ }
+ }.bind(this));
+
+ // Select given tool
+ this.ready(function () {
+ const preTool = $id(`tool_${this.configObj.curConfig.initTool}`);
+ const regTool = $id(this.configObj.curConfig.initTool);
+ const selectTool = $id('tool_select');
+ const $editDialog = $id('se-edit-prefs');
+
+ if (preTool) {
+ preTool.click();
+ } else if (regTool) {
+ regTool.click();
+ } else {
+ selectTool.click();
+ }
+
+ if (this.configObj.curConfig.wireframe) {
+ $id('tool_wireframe').click();
+ }
+
+ $('#rulers').toggle(Boolean(this.configObj.curConfig.showRulers));
+
+ if (this.configObj.curConfig.showRulers) {
+ $editDialog.setAttribute('showrulers', true);
+ }
+
+ if (this.configObj.curConfig.baseUnit) {
+ $editDialog.setAttribute('baseunit', this.configObj.curConfig.baseUnit);
+ }
+
+ if (this.configObj.curConfig.gridSnapping) {
+ $editDialog.setAttribute('gridsnappingon', true);
+ }
+
+ if (this.configObj.curConfig.snappingStep) {
+ $editDialog.setAttribute('gridsnappingstep', this.configObj.curConfig.snappingStep);
+ }
+
+ if (this.configObj.curConfig.gridColor) {
+ $editDialog.setAttribute('gridcolor', this.configObj.curConfig.gridColor);
+ }
+ }.bind(this));
+
+ // zoom
+ $id('zoom').value = (this.svgCanvas.getZoom() * 100).toFixed(1);
+ this.canvMenu.setAttribute('disableallmenu', true);
+ this.canvMenu.setAttribute('enablemenuitems', '#delete,#cut,#copy');
+
+ this.enableOrDisableClipboard();
+
+ window.addEventListener('storage', function (e) {
+ if (e.key !== 'svgedit_clipboard') { return; }
+
+ this.enableOrDisableClipboard();
+ }.bind(this));
+
+ window.addEventListener('beforeunload', function (e) {
+ // Suppress warning if page is empty
+ if (undoMgr.getUndoStackSize() === 0) {
+ this.showSaveWarning = false;
+ }
+
+ // showSaveWarning is set to 'false' when the page is saved.
+ if (!this.configObj.curConfig.no_save_warning && this.showSaveWarning) {
+ // Browser already asks question about closing the page
+ e.returnValue = this.uiStrings.notification.unsavedChanges; // Firefox needs this when beforeunload set by addEventListener (even though message is not used)
+ return this.uiStrings.notification.unsavedChanges;
+ }
+ return true;
+ }.bind(this));
+
+ // Use HTML5 File API: http://www.w3.org/TR/FileAPI/
+ // if browser has HTML5 File API support, then we will show the open menu item
+ // and provide a file input to click. When that change event fires, it will
+ // get the text contents of the file and send it to the canvas
+ if (window.FileReader) {
+ /**
+ * @param {Event} e
+ * @returns {void}
+ */
+ const importImage = function (e) {
+ $.process_cancel(this.uiStrings.notification.loadingImage);
+ e.stopPropagation();
+ e.preventDefault();
+ $('#main_menu').hide();
+ const file = (e.type === 'drop') ? e.dataTransfer.files[0] : this.files[0];
+ if (!file) {
+ $('#dialog_box').hide();
+ return;
+ }
+
+ if (!file.type.includes('image')) {
+ return;
+ }
+ // Detected an image
+ // svg handling
+ let reader;
+ if (file.type.includes('svg')) {
+ reader = new FileReader();
+ reader.onloadend = function (ev) {
+ const newElement = this.svgCanvas.importSvgString(ev.target.result, true);
+ this.svgCanvas.ungroupSelectedElement();
+ this.svgCanvas.ungroupSelectedElement();
+ this.svgCanvas.groupSelectedElements();
+ this.svgCanvas.alignSelectedElements('m', 'page');
+ this.svgCanvas.alignSelectedElements('c', 'page');
+ // highlight imported element, otherwise we get strange empty selectbox
+ this.svgCanvas.selectOnly([newElement]);
+ $('#dialog_box').hide();
+ };
+ reader.readAsText(file);
+ } else {
+ // bitmap handling
+ reader = new FileReader();
+ reader.onloadend = function ({target: {result}}) {
+ /**
+ * Insert the new image until we know its dimensions.
+ * @param {Float} imageWidth
+ * @param {Float} imageHeight
+ * @returns {void}
+ */
+ const insertNewImage = function (imageWidth, imageHeight) {
+ const newImage = this.svgCanvas.addSVGElementFromJson({
+ element: 'image',
+ attr: {
+ x: 0,
+ y: 0,
+ width: imageWidth,
+ height: imageHeight,
+ id: this.svgCanvas.getNextId(),
+ style: 'pointer-events:inherit'
+ }
+ });
+ this.svgCanvas.setHref(newImage, result);
+ this.svgCanvas.selectOnly([newImage]);
+ this.svgCanvas.alignSelectedElements('m', 'page');
+ this.svgCanvas.alignSelectedElements('c', 'page');
+ this.topPanelHandlers.updateContextPanel();
+ $('#dialog_box').hide();
+ };
+ // create dummy img so we know the default dimensions
+ let imgWidth = 100;
+ let imgHeight = 100;
+ const img = new Image();
+ img.style.opacity = 0;
+ img.addEventListener('load', () => {
+ imgWidth = img.offsetWidth || img.naturalWidth || img.width;
+ imgHeight = img.offsetHeight || img.naturalHeight || img.height;
+ insertNewImage(imgWidth, imgHeight);
+ });
+ img.src = result;
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ this.workarea[0].addEventListener('dragenter', this.onDragEnter);
+ this.workarea[0].addEventListener('dragover', this.onDragOver);
+ this.workarea[0].addEventListener('dragleave', this.onDragLeave);
+ this.workarea[0].addEventListener('drop', importImage);
+
+ const open = $('').change(async function (e) {
+ const ok = await this.openPrep();
+ if (!ok) { return; }
+ this.svgCanvas.clear();
+ if (this.files.length === 1) {
+ $.process_cancel(this.uiStrings.notification.loadingImage);
+ const reader = new FileReader();
+ reader.onloadend = async function ({target}) {
+ await this.loadSvgString(target.result);
+ this.updateCanvas();
+ };
+ reader.readAsText(this.files[0]);
+ }
+ });
+ $('#tool_open').show();
+ $(window).on('openImage', () => open.click());
+
+ const imgImport = $('').change(importImage);
+ $('#tool_import').show();
+ $(window).on('importImage', () => imgImport.click());
+ }
+
+ this.updateCanvas(true);
+ // Load extensions
+ this.extAndLocaleFunc();
+ // Defer injection to wait out initial menu processing. This probably goes
+ // away once all context menu behavior is brought to context menu.
+ this.ready(() => {
+ injectExtendedContextMenuItemsIntoDom();
+ });
+ // run callbacks stored by this.ready
+ await this.runCallbacks();
+ window.addEventListener('message', this.messageListener.bind(this));
+ }
+ /**
+ * @fires module:svgcanvas.SvgCanvas#event:ext_addLangData
+ * @fires module:svgcanvas.SvgCanvas#event:ext_langReady
+ * @fires module:svgcanvas.SvgCanvas#event:ext_langChanged
+ * @fires module:svgcanvas.SvgCanvas#event:extensions_added
+ * @returns {Promise} Resolves to result of {@link module:locale.readLang}
+ */
+ async extAndLocaleFunc () {
+ const {langParam, langData} = await this.putLocale(this.configObj.pref('lang'), this.goodLangs);
+ await this.setLang(langParam, langData);
+
+ $id('svg_container').style.visibility = 'visible';
+
+ try {
+ // load standard extensions
+ await Promise.all(
+ this.configObj.curConfig.extensions.map(async (extname) => {
+ /**
+ * @tutorial ExtensionDocs
+ * @typedef {PlainObject} module:SVGthis.ExtensionObject
+ * @property {string} [name] Name of the extension. Used internally; no need for i18n. Defaults to extension name without beginning "ext-" or ending ".js".
+ * @property {module:svgcanvas.ExtensionInitCallback} [init]
+ */
+ try {
+ /**
+ * @type {module:SVGthis.ExtensionObject}
+ */
+ const imported = await import(`./extensions/${encodeURIComponent(extname)}/${encodeURIComponent(extname)}.js`);
+ const {name = extname, init: initfn} = imported.default;
+ return this.addExtension(name, (initfn && initfn.bind(this)), {$, langParam});
+ } catch (err) {
+ // Todo: Add config to alert any errors
+ console.error('Extension failed to load: ' + extname + '; ', err); // eslint-disable-line no-console
+ return undefined;
+ }
+ })
+ );
+ // load user extensions (given as pathNames)
+ await Promise.all(
+ this.configObj.curConfig.userExtensions.map(async (extPathName) => {
+ /**
+ * @tutorial ExtensionDocs
+ * @typedef {PlainObject} module:SVGthis.ExtensionObject
+ * @property {string} [name] Name of the extension. Used internally; no need for i18n. Defaults to extension name without beginning "ext-" or ending ".js".
+ * @property {module:svgcanvas.ExtensionInitCallback} [init]
+ */
+ try {
+ /**
+ * @type {module:SVGthis.ExtensionObject}
+ */
+ const imported = await import(encodeURI(extPathName));
+ const {name, init: initfn} = imported.default;
+ return this.addExtension(name, (initfn && initfn.bind(this)), {$, langParam});
+ } catch (err) {
+ // Todo: Add config to alert any errors
+ console.error('Extension failed to load: ' + extPathName + '; ', err); // eslint-disable-line no-console
+ return undefined;
+ }
+ })
+ );
+ this.svgCanvas.bind(
+ 'extensions_added',
+ /**
+ * @param {external:Window} win
+ * @param {module:svgcanvas.SvgCanvas#event:extensions_added} data
+ * @listens module:SvgCanvas#event:extensions_added
+ * @returns {void}
+ */
+ (win, data) => {
+ this.extensionsAdded = true;
+ this.setAll();
+
+ if (this.storagePromptState === 'ignore') {
+ this.updateCanvas(true);
+ }
+
+ this.messageQueue.forEach(
+ /**
+ * @param {module:svgcanvas.SvgCanvas#event:message} messageObj
+ * @fires module:svgcanvas.SvgCanvas#event:message
+ * @returns {void}
+ */
+ (messageObj) => {
+ this.svgCanvas.call('message', messageObj);
+ }
+ );
+ }
+ );
+ this.svgCanvas.call('extensions_added');
+ } catch (err) {
+ // Todo: Report errors through the UI
+ console.log(err); // eslint-disable-line no-console
+ }
+ }
+
+ /**
+ * @param {PlainObject} info
+ * @param {any} info.data
+ * @param {string} info.origin
+ * @fires module:svgcanvas.SvgCanvas#event:message
+ * @returns {void}
+ */
+ messageListener ({data, origin}) { // eslint-disable-line no-shadow
+ // console.log('data, origin, extensionsAdded', data, origin, extensionsAdded);
+ const messageObj = {data, origin};
+ if (!this.extensionsAdded) {
+ this.messageQueue.push(messageObj);
+ } else {
+ // Extensions can handle messages at this stage with their own
+ // canvas `message` listeners
+ this.svgCanvas.call('message', messageObj);
+ }
+ }
+}
+
+export default EditorStartup;
diff --git a/src/editor/svgedit.js b/src/editor/svgedit.js
index c7aabab3..36009fae 100644
--- a/src/editor/svgedit.js
+++ b/src/editor/svgedit.js
@@ -19,24 +19,18 @@
import './touch.js';
import {isChrome, isGecko, isMac} from '../common/browser.js';
import {convertUnit, isValidUnit} from '../common/units.js';
-import {
- hasCustomHandler, getCustomHandler, injectExtendedContextMenuItemsIntoDom
-} from './contextmenu.js';
import SvgCanvas from '../svgcanvas/svgcanvas.js';
import jQueryPluginJSHotkeys from './js-hotkeys/jquery.hotkeys.min.js';
import ConfigObj from './ConfigObj.js';
-import LayersPanel from './panels/LayersPanel.js';
-import LeftPanelHandlers from './panels/LeftPanelHandlers.js';
-import BottomPanelHandlers from './panels/BottomPanelHandlers.js';
-import TopPanelHandlers from './panels/TopPanelHandlers.js';
-import Rulers from './Rulers.js';
import {
readLang, putLocale,
setStrings
} from './locale.js';
+import EditorStartup from './EditorStartup.js';
+
const {$id, $qa, isNullish, encode64, decode64, blankPageObjectURL} = SvgCanvas;
// JFH hotkey is used for text input.
@@ -45,11 +39,12 @@ const homePage = 'https://github.com/SVG-Edit/svgedit';
/**
*
*/
-class Editor {
+class Editor extends EditorStartup {
/**
*
*/
constructor () {
+ super();
/**
* @type {Float}
*/
@@ -341,129 +336,7 @@ class Editor {
return btn.sel === sel;
});
}
- /**
- * @fires module:svgcanvas.SvgCanvas#event:ext_addLangData
- * @fires module:svgcanvas.SvgCanvas#event:ext_langReady
- * @fires module:svgcanvas.SvgCanvas#event:ext_langChanged
- * @fires module:svgcanvas.SvgCanvas#event:extensions_added
- * @returns {Promise} Resolves to result of {@link module:locale.readLang}
- */
- async extAndLocaleFunc () {
- const {langParam, langData} = await this.putLocale(this.configObj.pref('lang'), this.goodLangs);
- await this.setLang(langParam, langData);
- $id('svg_container').style.visibility = 'visible';
-
- try {
- // load standard extensions
- await Promise.all(
- this.configObj.curConfig.extensions.map(async (extname) => {
- /**
- * @tutorial ExtensionDocs
- * @typedef {PlainObject} module:SVGthis.ExtensionObject
- * @property {string} [name] Name of the extension. Used internally; no need for i18n. Defaults to extension name without beginning "ext-" or ending ".js".
- * @property {module:svgcanvas.ExtensionInitCallback} [init]
- */
- try {
- /**
- * @type {module:SVGthis.ExtensionObject}
- */
- const imported = await import(`./extensions/${encodeURIComponent(extname)}/${encodeURIComponent(extname)}.js`);
- const {name = extname, init} = imported.default;
- return this.addExtension(name, (init && init.bind(this)), {$, langParam});
- } catch (err) {
- // Todo: Add config to alert any errors
- console.error('Extension failed to load: ' + extname + '; ', err); // eslint-disable-line no-console
- return undefined;
- }
- })
- );
- // load user extensions (given as pathNames)
- await Promise.all(
- this.configObj.curConfig.userExtensions.map(async (extPathName) => {
- /**
- * @tutorial ExtensionDocs
- * @typedef {PlainObject} module:SVGthis.ExtensionObject
- * @property {string} [name] Name of the extension. Used internally; no need for i18n. Defaults to extension name without beginning "ext-" or ending ".js".
- * @property {module:svgcanvas.ExtensionInitCallback} [init]
- */
- try {
- /**
- * @type {module:SVGthis.ExtensionObject}
- */
- const imported = await import(encodeURI(extPathName));
- const {name, init} = imported.default;
- return this.addExtension(name, (init && init.bind(this)), {$, langParam});
- } catch (err) {
- // Todo: Add config to alert any errors
- console.error('Extension failed to load: ' + extPathName + '; ', err); // eslint-disable-line no-console
- return undefined;
- }
- })
- );
- this.svgCanvas.bind(
- 'extensions_added',
- /**
- * @param {external:Window} win
- * @param {module:svgcanvas.SvgCanvas#event:extensions_added} data
- * @listens module:SvgCanvas#event:extensions_added
- * @returns {void}
- */
- (win, data) => {
- extensionsAdded = true;
- this.setAll();
-
- if (this.storagePromptState === 'ignore') {
- this.updateCanvas(true);
- }
-
- messageQueue.forEach(
- /**
- * @param {module:svgcanvas.SvgCanvas#event:message} messageObj
- * @fires module:svgcanvas.SvgCanvas#event:message
- * @returns {void}
- */
- (messageObj) => {
- this.svgCanvas.call('message', messageObj);
- }
- );
- }
- );
- this.svgCanvas.call('extensions_added');
- } catch (err) {
- // Todo: Report errors through the UI
- console.log(err); // eslint-disable-line no-console
- }
- }
- /**
- * @fires module:svgcanvas.SvgCanvas#event:svgEditorReady
- * @returns {void}
- */
- static readySignal () {
- // let the opener know SVG Edit is ready (now that config is set up)
- const w = window.opener || window.parent;
- if (w) {
- try {
- /**
- * Triggered on a containing `document` (of `window.opener`
- * or `window.parent`) when the editor is loaded.
- * @event module:SVGEditor#event:svgEditorReadyEvent
- * @type {Event}
- * @property {true} bubbles
- * @property {true} cancelable
- */
- /**
- * @name module:SVGthis.svgEditorReadyEvent
- * @type {module:SVGEditor#event:svgEditorReadyEvent}
- */
- const svgEditorReadyEvent = new w.CustomEvent('svgEditorReady', {
- bubbles: true,
- cancelable: true
- });
- w.document.documentElement.dispatchEvent(svgEditorReadyEvent);
- } catch (e) {}
- }
- }
/**
* Expose the `uiStrings`.
* @function module:SVGthis.canvas.getUIStrings
@@ -473,688 +346,6 @@ class Editor {
return this.uiStrings;
}
- /**
- * Auto-run after a Promise microtask.
- * @function module:SVGthis.init
- * @returns {void}
- */
- async init () {
- try {
- // Image props dialog added to DOM
- const newSeImgPropDialog = document.createElement('se-img-prop-dialog');
- newSeImgPropDialog.setAttribute('id', 'se-img-prop');
- document.body.append(newSeImgPropDialog);
- // editor prefences dialoag added to DOM
- const newSeEditPrefsDialog = document.createElement('se-edit-prefs-dialog');
- newSeEditPrefsDialog.setAttribute('id', 'se-edit-prefs');
- document.body.append(newSeEditPrefsDialog);
- // canvas menu added to DOM
- const dialogBox = document.createElement('se-cmenu_canvas-dialog');
- dialogBox.setAttribute('id', 'se-cmenu_canvas');
- document.body.append(dialogBox);
- // alertDialog added to DOM
- const alertBox = document.createElement('se-alert-dialog');
- alertBox.setAttribute('id', 'se-alert-dialog');
- document.body.append(alertBox);
- // promptDialog added to DOM
- const promptBox = document.createElement('se-prompt-dialog');
- promptBox.setAttribute('id', 'se-prompt-dialog');
- document.body.append(promptBox);
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error(err);
- }
-
- if ('localStorage' in window) { // && onWeb removed so Webkit works locally
- this.storage = window.localStorage;
- }
-
- this.configObj.load();
-
- /**
- * @name module:SVGthis.canvas
- * @type {module:svgcanvas.SvgCanvas}
- */
- this.svgCanvas = new SvgCanvas(
- $id('svgcanvas'),
- this.configObj.curConfig
- );
-
- this.leftPanelHandlers = new LeftPanelHandlers(this);
- this.bottomPanelHandlers = new BottomPanelHandlers(this);
- this.topPanelHandlers = new TopPanelHandlers(this);
- this.layersPanel = new LayersPanel(this);
-
- const {undoMgr} = this.svgCanvas;
- this.workarea = $('#workarea');
- this.canvMenu = document.getElementById('se-cmenu_canvas');
- this.exportWindow = null;
- this.defaultImageURL = this.configObj.curConfig.imgPath + 'logo.svg';
- const zoomInIcon = 'crosshair';
- const zoomOutIcon = 'crosshair';
- this.uiContext = 'toolbars';
-
- // For external openers
- Editor.readySignal();
-
- this.rulers = new Rulers(this);
-
- this.layersPanel.populateLayers();
- this.selectedElement = null;
- this.multiselected = false;
-
- $('#cur_context_panel').delegate('a', 'click', (evt) => {
- const link = $(evt.currentTarget);
- if (link.attr('data-root')) {
- this.svgCanvas.leaveContext();
- } else {
- this.svgCanvas.setContext(link.text());
- }
- this.svgCanvas.clearSelection();
- return false;
- });
- // bind the selected event to our function that handles updates to the UI
- this.svgCanvas.bind('selected', this.selectedChanged.bind(this));
- this.svgCanvas.bind('transition', this.elementTransition.bind(this));
- this.svgCanvas.bind('changed', this.elementChanged.bind(this));
- this.svgCanvas.bind('saved', this.saveHandler.bind(this));
- this.svgCanvas.bind('exported', this.exportHandler.bind(this));
- this.svgCanvas.bind('exportedPDF', function (win, data) {
- if (!data.output) { // Ignore Chrome
- return;
- }
- const {exportWindowName} = data;
- if (exportWindowName) {
- this.exportWindow = window.open('', this.exportWindowName); // A hack to get the window via JSON-able name without opening a new one
- }
- if (!this.exportWindow || this.exportWindow.closed) {
- seAlert(this.uiStrings.notification.popupWindowBlocked);
- return;
- }
- this.exportWindow.location.href = data.output;
- }.bind(this));
- this.svgCanvas.bind('zoomed', this.zoomChanged.bind(this));
- this.svgCanvas.bind('zoomDone', this.zoomDone.bind(this));
- this.svgCanvas.bind(
- 'updateCanvas',
- /**
- * @param {external:Window} win
- * @param {PlainObject} centerInfo
- * @param {false} centerInfo.center
- * @param {module:math.XYObject} centerInfo.newCtr
- * @listens module:svgcanvas.SvgCanvas#event:updateCanvas
- * @returns {void}
- */
- function (win, {center, newCtr}) {
- this.updateCanvas(center, newCtr);
- }.bind(this)
- );
- this.svgCanvas.bind('contextset', this.contextChanged.bind(this));
- this.svgCanvas.bind('extension_added', this.extAdded.bind(this));
- this.svgCanvas.textActions.setInputElem($('#text')[0]);
-
- this.setBackground(this.configObj.pref('bkgd_color'), this.configObj.pref('bkgd_url'));
-
- // update resolution option with actual resolution
- const res = this.svgCanvas.getResolution();
- if (this.configObj.curConfig.baseUnit !== 'px') {
- res.w = convertUnit(res.w) + this.configObj.curConfig.baseUnit;
- res.h = convertUnit(res.h) + this.configObj.curConfig.baseUnit;
- }
- $('#se-img-prop').attr('dialog', 'close');
- $('#se-img-prop').attr('title', this.svgCanvas.getDocumentTitle());
- $('#se-img-prop').attr('width', res.w);
- $('#se-img-prop').attr('height', res.h);
- $('#se-img-prop').attr('save', this.configObj.pref('img_save'));
-
- // Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
- $('select').change((evt) => { $(evt.currentTarget).blur(); });
-
- // fired when user wants to move elements to another layer
- let promptMoveLayerOnce = false;
- $('#selLayerNames').change((evt) => {
- const destLayer = evt.currentTarget.options[evt.currentTarget.selectedIndex].value;
- const confirmStr = this.uiStrings.notification.Qmovethis.elemsToLayer.replace('%s', destLayer);
- /**
- * @param {boolean} ok
- * @returns {void}
- */
- const moveToLayer = (ok) => {
- if (!ok) { return; }
- promptMoveLayerOnce = true;
- this.svgCanvas.moveSelectedToLayer(destLayer);
- this.svgCanvas.clearSelection();
- this.layersPanel.populateLayers();
- };
- if (destLayer) {
- if (promptMoveLayerOnce) {
- moveToLayer(true);
- } else {
- const ok = seConfirm(confirmStr);
- if (!ok) {
- return;
- }
- moveToLayer(true);
- }
- }
- });
-
- $('#font_family').change((evt) => {
- this.svgCanvas.setFontFamily(evt.currentTarget.value);
- });
-
- $('#seg_type').change((evt) => {
- this.svgCanvas.setSegType($(evt.currentTarget).val());
- });
-
- $('#text').bind('keyup input', (evt) => {
- this.svgCanvas.setTextContent(evt.currentTarget.value);
- });
-
- $('#image_url').change((evt) => {
- this.setImageURL(evt.currentTarget.value);
- });
-
- $('#link_url').change((evt) => {
- if (evt.currentTarget.value.length) {
- this.svgCanvas.setLinkURL(evt.currentTarget.value);
- } else {
- this.svgCanvas.removeHyperlink();
- }
- });
-
- $('#g_title').change((evt) => {
- this.svgCanvas.setGroupTitle(evt.currentTarget.value);
- });
-
- const wArea = this.workarea[0];
-
- let lastX = null, lastY = null,
- panning = false, keypan = false;
-
- $('#svgcanvas').bind('mousemove mouseup', function (evt) {
- if (panning === false) { return true; }
-
- wArea.scrollLeft -= (evt.clientX - lastX);
- wArea.scrollTop -= (evt.clientY - lastY);
-
- lastX = evt.clientX;
- lastY = evt.clientY;
-
- if (evt.type === 'mouseup') { panning = false; }
- return false;
- }).mousedown(function (evt) {
- if (evt.button === 1 || keypan === true) {
- panning = true;
- lastX = evt.clientX;
- lastY = evt.clientY;
- return false;
- }
- return true;
- });
-
- $(window).mouseup(() => {
- panning = false;
- });
-
- $(document).bind('keydown', 'space', function (evt) {
- this.svgCanvas.spaceKey = keypan = true;
- evt.preventDefault();
- }.bind(this)).bind('keyup', 'space', function (evt) {
- evt.preventDefault();
- this.svgCanvas.spaceKey = keypan = false;
- }.bind(this)).bind('keydown', 'shift', function (evt) {
- if (this.svgCanvas.getMode() === 'zoom') {
- this.workarea.css('cursor', zoomOutIcon);
- }
- }.bind(this)).bind('keyup', 'shift', function (evt) {
- if (this.svgCanvas.getMode() === 'zoom') {
- this.workarea.css('cursor', zoomInIcon);
- }
- }.bind(this));
-
- /**
- * @function module:SVGthis.setPanning
- * @param {boolean} active
- * @returns {void}
- */
- this.setPanning = (active) => {
- this.svgCanvas.spaceKey = keypan = active;
- };
-
- const button = $('#main_icon');
- const overlay = $('#main_icon span');
- const list = $('#main_menu');
-
- let onButton = false;
- let height = 0;
- let jsHover = true;
- let setClick = false;
-
- $(window).mouseup(function (evt) {
- if (!onButton) {
- button.removeClass('buttondown');
- // do not hide if it was the file input as that input needs to be visible
- // for its change event to fire
- if (evt.target.tagName !== 'INPUT') {
- list.fadeOut(200);
- } else if (!setClick) {
- setClick = true;
- $(evt.target).click(() => {
- list.css('margin-left', '-9999px').show();
- });
- }
- }
- onButton = false;
- }).mousedown(function (evt) {
- // $('.contextMenu').hide();
- const islib = $(evt.target).closest('.contextMenu').length;
- if (!islib) {
- $('.contextMenu').fadeOut(250);
- }
- });
-
- overlay.bind('mousedown', () => {
- if (!button.hasClass('buttondown')) {
- // Margin must be reset in case it was changed before;
- list.css('margin-left', 0).show();
- if (!height) {
- height = list.height();
- }
- // Using custom animation as slideDown has annoying 'bounce effect'
- list.css('height', 0).animate({
- height
- }, 200);
- onButton = true;
- } else {
- list.fadeOut(200);
- }
- button.toggleClass('buttondown buttonup');
- }).hover(() => {
- onButton = true;
- }).mouseout(() => {
- onButton = false;
- });
-
- const listItems = $('#main_menu li');
-
- // Check if JS method of hovering needs to be used (Webkit bug)
- listItems.mouseover(function () {
- jsHover = ($(this).css('background-color') === 'rgba(0, 0, 0, 0)');
-
- listItems.unbind('mouseover');
- if (jsHover) {
- listItems.mouseover(() => {
- this.style.backgroundColor = '#FFC';
- }).mouseout((evt) => {
- evt.currentTarget.style.backgroundColor = 'transparent';
- return true;
- });
- }
- });
- // Unfocus text input when this.workarea is mousedowned.
- let inp;
- /**
- *
- * @returns {void}
- */
- const unfocus = () => {
- $(inp).blur();
- };
-
- $('#svg_editor').find('button, select, input:not(#text)').focus(() => {
- inp = this;
- this.uiContext = 'toolbars';
- this.workarea.mousedown(unfocus);
- }).blur(() => {
- this.uiContext = 'canvas';
- this.workarea.unbind('mousedown', unfocus);
- // Go back to selecting text if in textedit mode
- if (this.svgCanvas.getMode() === 'textedit') {
- $('#text').focus();
- }
- });
- const winWh = {width: $(window).width(), height: $(window).height()};
-
- window.addEventListener('resize', (evt) => {
- Object.entries(winWh).forEach(([type, val]) => {
- const curval = $(window)[type]();
- this.workarea[0]['scroll' + (type === 'width' ? 'Left' : 'Top')] -= (curval - val) / 2;
- winWh[type] = curval;
- });
- });
-
- this.workarea.scroll(() => {
- // TODO: jQuery's scrollLeft/Top() wouldn't require a null check
- this.rulers.manageScroll();
- });
-
- $('#url_notice').click(() => {
- seAlert(this.title);
- });
-
- $('#stroke_width').val(this.configObj.curConfig.initStroke.width);
- $('#group_opacity').val(this.configObj.curConfig.initOpacity * 100);
-
- $('#group_opacityLabel').click(() => {
- $('#opacity_dropdown button').mousedown();
- $(window).mouseup();
- });
-
- $('.push_button').mousedown(() => {
- if (!$(this).hasClass('disabled')) {
- $(this).addClass('push_button_pressed').removeClass('push_button');
- }
- }).mouseout(() => {
- $(this).removeClass('push_button_pressed').addClass('push_button');
- }).mouseup(() => {
- $(this).removeClass('push_button_pressed').addClass('push_button');
- });
-
- this.layersPanel.populateLayers();
-
- const centerCanvas = () => {
- // this centers the canvas vertically in the this.workarea (horizontal handled in CSS)
- this.workarea.css('line-height', this.workarea.height() + 'px');
- };
-
- $(window).bind('load resize', centerCanvas);
-
- // Prevent browser from erroneously repopulating fields
- $('input,select').attr('autocomplete', 'off');
-
- /**
- * Associate all button actions as well as non-button keyboard shortcuts.
- */
- this.leftPanelHandlers.init();
- this.bottomPanelHandlers.init();
- this.topPanelHandlers.init();
- this.layersPanel.init();
-
- $id('tool_clear').addEventListener('click', this.clickClear.bind(this));
- $id('tool_open').addEventListener('click', function (e) {
- this.clickOpen();
- window.dispatchEvent(new CustomEvent('openImage'));
- }.bind(this));
- $id('tool_import').addEventListener('click', (e) => {
- this.clickImport();
- window.dispatchEvent(new CustomEvent('importImage'));
- });
- $id('tool_save').addEventListener('click', function (e) {
- const $editorDialog = document.getElementById('se-svg-editor-dialog');
- const editingsource = $editorDialog.getAttribute('dialog') === 'open';
- if (editingsource) {
- this.saveSourceEditor();
- } else {
- this.clickSave();
- }
- }.bind(this));
- $id('tool_export').addEventListener('click', this.clickExport.bind(this));
- $id('tool_docprops').addEventListener('click', this.showDocProperties.bind(this));
- $id('tool_editor_prefs').addEventListener('click', this.showPreferences.bind(this));
- $id('tool_editor_homepage').addEventListener('click', this.openHomePage.bind(this));
- $id('se-img-prop').addEventListener('change', function (e) {
- if (e.detail.dialog === 'closed') {
- this.hideDocProperties();
- } else {
- this.saveDocProperties(e);
- }
- }.bind(this));
- $id('se-edit-prefs').addEventListener('change', function (e) {
- if (e.detail.dialog === 'closed') {
- this.hidePreferences();
- } else {
- this.savePreferences(e);
- }
- }.bind(this));
- $id('se-svg-editor-dialog').addEventListener('change', function (e) {
- if (e?.detail?.copy === 'click') {
- this.cancelOverlays(e);
- } else if (e?.detail?.dialog === 'closed') {
- this.hideSourceEditor();
- } else {
- this.saveSourceEditor(e);
- }
- }.bind(this));
- $id('se-cmenu_canvas').addEventListener('change', function (e) {
- const action = e?.detail?.trigger;
- switch (action) {
- case 'delete':
- this.svgCanvas.deleteSelectedElements();
- break;
- case 'cut':
- this.cutSelected();
- break;
- case 'copy':
- this.copySelected();
- break;
- case 'paste':
- this.svgCanvas.pasteElements();
- break;
- case 'paste_in_place':
- this.svgCanvas.pasteElements('in_place');
- break;
- case 'group':
- case 'group_elements':
- this.svgCanvas.groupSelectedElements();
- break;
- case 'ungroup':
- this.svgCanvas.ungroupSelectedElement();
- break;
- case 'move_front':
- this.svgCanvas.moveToTopSelectedElement();
- break;
- case 'move_up':
- this.moveUpDownSelected('Up');
- break;
- case 'move_down':
- this.moveUpDownSelected('Down');
- break;
- case 'move_back':
- this.svgCanvas.moveToBottomSelected();
- break;
- default:
- if (hasCustomHandler(action)) {
- getCustomHandler(action).call();
- }
- break;
- }
- }.bind(this));
-
- // Select given tool
- this.ready(function () {
- const preTool = $id(`tool_${this.configObj.curConfig.initTool}`);
- const regTool = $id(this.configObj.curConfig.initTool);
- const selectTool = $id('tool_select');
- const $editDialog = $id('se-edit-prefs');
-
- if (preTool) {
- preTool.click();
- } else if (regTool) {
- regTool.click();
- } else {
- selectTool.click();
- }
-
- if (this.configObj.curConfig.wireframe) {
- $id('tool_wireframe').click();
- }
-
- $('#rulers').toggle(Boolean(this.configObj.curConfig.showRulers));
-
- if (this.configObj.curConfig.showRulers) {
- $editDialog.setAttribute('showrulers', true);
- }
-
- if (this.configObj.curConfig.baseUnit) {
- $editDialog.setAttribute('baseunit', this.configObj.curConfig.baseUnit);
- }
-
- if (this.configObj.curConfig.gridSnapping) {
- $editDialog.setAttribute('gridsnappingon', true);
- }
-
- if (this.configObj.curConfig.snappingStep) {
- $editDialog.setAttribute('gridsnappingstep', this.configObj.curConfig.snappingStep);
- }
-
- if (this.configObj.curConfig.gridColor) {
- $editDialog.setAttribute('gridcolor', this.configObj.curConfig.gridColor);
- }
- }.bind(this));
-
- // zoom
- $id('zoom').value = (this.svgCanvas.getZoom() * 100).toFixed(1);
- this.canvMenu.setAttribute('disableallmenu', true);
- this.canvMenu.setAttribute('enablemenuitems', '#delete,#cut,#copy');
-
- this.enableOrDisableClipboard();
-
- window.addEventListener('storage', function (e) {
- if (e.key !== 'svgedit_clipboard') { return; }
-
- this.enableOrDisableClipboard();
- }.bind(this));
-
- window.addEventListener('beforeunload', function (e) {
- // Suppress warning if page is empty
- if (undoMgr.getUndoStackSize() === 0) {
- this.showSaveWarning = false;
- }
-
- // showSaveWarning is set to 'false' when the page is saved.
- if (!this.configObj.curConfig.no_save_warning && this.showSaveWarning) {
- // Browser already asks question about closing the page
- e.returnValue = this.uiStrings.notification.unsavedChanges; // Firefox needs this when beforeunload set by addEventListener (even though message is not used)
- return this.uiStrings.notification.unsavedChanges;
- }
- return true;
- }.bind(this));
-
- // Use HTML5 File API: http://www.w3.org/TR/FileAPI/
- // if browser has HTML5 File API support, then we will show the open menu item
- // and provide a file input to click. When that change event fires, it will
- // get the text contents of the file and send it to the canvas
- if (window.FileReader) {
- /**
- * @param {Event} e
- * @returns {void}
- */
- const importImage = function (e) {
- $.process_cancel(this.uiStrings.notification.loadingImage);
- e.stopPropagation();
- e.preventDefault();
- $('#main_menu').hide();
- const file = (e.type === 'drop') ? e.dataTransfer.files[0] : this.files[0];
- if (!file) {
- $('#dialog_box').hide();
- return;
- }
-
- if (!file.type.includes('image')) {
- return;
- }
- // Detected an image
- // svg handling
- let reader;
- if (file.type.includes('svg')) {
- reader = new FileReader();
- reader.onloadend = function (ev) {
- const newElement = this.svgCanvas.importSvgString(ev.target.result, true);
- this.svgCanvas.ungroupSelectedElement();
- this.svgCanvas.ungroupSelectedElement();
- this.svgCanvas.groupSelectedElements();
- this.svgCanvas.alignSelectedElements('m', 'page');
- this.svgCanvas.alignSelectedElements('c', 'page');
- // highlight imported element, otherwise we get strange empty selectbox
- this.svgCanvas.selectOnly([newElement]);
- $('#dialog_box').hide();
- };
- reader.readAsText(file);
- } else {
- // bitmap handling
- reader = new FileReader();
- reader.onloadend = function ({target: {result}}) {
- /**
- * Insert the new image until we know its dimensions.
- * @param {Float} width
- * @param {Float} height
- * @returns {void}
- */
- const insertNewImage = function (width, height) {
- const newImage = this.svgCanvas.addSVGElementFromJson({
- element: 'image',
- attr: {
- x: 0,
- y: 0,
- width,
- height,
- id: this.svgCanvas.getNextId(),
- style: 'pointer-events:inherit'
- }
- });
- this.svgCanvas.setHref(newImage, result);
- this.svgCanvas.selectOnly([newImage]);
- this.svgCanvas.alignSelectedElements('m', 'page');
- this.svgCanvas.alignSelectedElements('c', 'page');
- this.topPanelHandlers.updateContextPanel();
- $('#dialog_box').hide();
- };
- // create dummy img so we know the default dimensions
- let imgWidth = 100;
- let imgHeight = 100;
- const img = new Image();
- img.style.opacity = 0;
- img.addEventListener('load', () => {
- imgWidth = img.offsetWidth || img.naturalWidth || img.width;
- imgHeight = img.offsetHeight || img.naturalHeight || img.height;
- insertNewImage(imgWidth, imgHeight);
- });
- img.src = result;
- };
- reader.readAsDataURL(file);
- }
- };
-
- this.workarea[0].addEventListener('dragenter', this.onDragEnter);
- this.workarea[0].addEventListener('dragover', this.onDragOver);
- this.workarea[0].addEventListener('dragleave', this.onDragLeave);
- this.workarea[0].addEventListener('drop', importImage);
-
- const open = $('').change(async function (e) {
- const ok = await this.openPrep();
- if (!ok) { return; }
- this.svgCanvas.clear();
- if (this.files.length === 1) {
- $.process_cancel(this.uiStrings.notification.loadingImage);
- const reader = new FileReader();
- reader.onloadend = async function ({target}) {
- await this.loadSvgString(target.result);
- this.updateCanvas();
- };
- reader.readAsText(this.files[0]);
- }
- });
- $('#tool_open').show();
- $(window).on('openImage', () => open.click());
-
- const imgImport = $('').change(importImage);
- $('#tool_import').show();
- $(window).on('importImage', () => imgImport.click());
- }
-
- this.updateCanvas(true);
- // Load extensions
- this.extAndLocaleFunc();
- // Defer injection to wait out initial menu processing. This probably goes
- // away once all context menu behavior is brought to context menu.
- this.ready(() => {
- injectExtendedContextMenuItemsIntoDom();
- });
- // run callbacks stored by this.ready
- await this.runCallbacks();
- }
-
/**
* @param {boolean} editmode
* @param {module:svgcanvas.SvgCanvas#event:selected} elems
@@ -2499,43 +1690,22 @@ class Editor {
/**
* @function module:SVGthis.addExtension
* @param {string} name Used internally; no need for i18n.
- * @param {module:svgcanvas.ExtensionInitCallback} init Config to be invoked on this module
+ * @param {module:svgcanvas.ExtensionInitCallback} initfn Config to be invoked on this module
* @param {module:svgcanvas.ExtensionInitArgs} initArgs
* @throws {Error} If called too early
* @returns {Promise} Resolves to `undefined`
*/
- addExtension (name, init, initArgs) {
+ addExtension (name, initfn, initArgs) {
// Note that we don't want this on this.ready since some extensions
// may want to run before then (like server_opensave).
if (!this.svgCanvas) {
throw new Error('Extension added too early');
}
- return this.svgCanvas.addExtension.call(this, name, init, initArgs);
+ return this.svgCanvas.addExtension.call(this, name, initfn, initArgs);
}
}
const editor = new Editor();
editor.init();
-let extensionsAdded = false;
-const messageQueue = [];
-/**
- * @param {PlainObject} info
- * @param {any} info.data
- * @param {string} info.origin
- * @fires module:svgcanvas.SvgCanvas#event:message
- * @returns {void}
- */
-const messageListener = ({data, origin}) => { // eslint-disable-line no-shadow
- // console.log('data, origin, extensionsAdded', data, origin, extensionsAdded);
- const messageObj = {data, origin};
- if (!extensionsAdded) {
- messageQueue.push(messageObj);
- } else {
- // Extensions can handle messages at this stage with their own
- // canvas `message` listeners
- this.svgCanvas.call('message', messageObj);
- }
-};
-window.addEventListener('message', messageListener);
export default editor;