move init (600 lines+) in its own file
parent
83ee29c37e
commit
c496ab8978
|
@ -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 = $('<input type="file">').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 = $('<input type="file">').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<module:locale.LangAndData>} 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;
|
|
@ -19,24 +19,18 @@
|
||||||
import './touch.js';
|
import './touch.js';
|
||||||
import {isChrome, isGecko, isMac} from '../common/browser.js';
|
import {isChrome, isGecko, isMac} from '../common/browser.js';
|
||||||
import {convertUnit, isValidUnit} from '../common/units.js';
|
import {convertUnit, isValidUnit} from '../common/units.js';
|
||||||
import {
|
|
||||||
hasCustomHandler, getCustomHandler, injectExtendedContextMenuItemsIntoDom
|
|
||||||
} from './contextmenu.js';
|
|
||||||
|
|
||||||
import SvgCanvas from '../svgcanvas/svgcanvas.js';
|
import SvgCanvas from '../svgcanvas/svgcanvas.js';
|
||||||
import jQueryPluginJSHotkeys from './js-hotkeys/jquery.hotkeys.min.js';
|
import jQueryPluginJSHotkeys from './js-hotkeys/jquery.hotkeys.min.js';
|
||||||
import ConfigObj from './ConfigObj.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 {
|
import {
|
||||||
readLang, putLocale,
|
readLang, putLocale,
|
||||||
setStrings
|
setStrings
|
||||||
} from './locale.js';
|
} from './locale.js';
|
||||||
|
|
||||||
|
import EditorStartup from './EditorStartup.js';
|
||||||
|
|
||||||
const {$id, $qa, isNullish, encode64, decode64, blankPageObjectURL} = SvgCanvas;
|
const {$id, $qa, isNullish, encode64, decode64, blankPageObjectURL} = SvgCanvas;
|
||||||
|
|
||||||
// JFH hotkey is used for text input.
|
// 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 () {
|
constructor () {
|
||||||
|
super();
|
||||||
/**
|
/**
|
||||||
* @type {Float}
|
* @type {Float}
|
||||||
*/
|
*/
|
||||||
|
@ -341,129 +336,7 @@ class Editor {
|
||||||
return btn.sel === sel;
|
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<module:locale.LangAndData>} 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`.
|
* Expose the `uiStrings`.
|
||||||
* @function module:SVGthis.canvas.getUIStrings
|
* @function module:SVGthis.canvas.getUIStrings
|
||||||
|
@ -473,688 +346,6 @@ class Editor {
|
||||||
return this.uiStrings;
|
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 = $('<input type="file">').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 = $('<input type="file">').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 {boolean} editmode
|
||||||
* @param {module:svgcanvas.SvgCanvas#event:selected} elems
|
* @param {module:svgcanvas.SvgCanvas#event:selected} elems
|
||||||
|
@ -2499,43 +1690,22 @@ class Editor {
|
||||||
/**
|
/**
|
||||||
* @function module:SVGthis.addExtension
|
* @function module:SVGthis.addExtension
|
||||||
* @param {string} name Used internally; no need for i18n.
|
* @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
|
* @param {module:svgcanvas.ExtensionInitArgs} initArgs
|
||||||
* @throws {Error} If called too early
|
* @throws {Error} If called too early
|
||||||
* @returns {Promise<void>} Resolves to `undefined`
|
* @returns {Promise<void>} Resolves to `undefined`
|
||||||
*/
|
*/
|
||||||
addExtension (name, init, initArgs) {
|
addExtension (name, initfn, initArgs) {
|
||||||
// Note that we don't want this on this.ready since some extensions
|
// Note that we don't want this on this.ready since some extensions
|
||||||
// may want to run before then (like server_opensave).
|
// may want to run before then (like server_opensave).
|
||||||
if (!this.svgCanvas) {
|
if (!this.svgCanvas) {
|
||||||
throw new Error('Extension added too early');
|
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();
|
const editor = new Editor();
|
||||||
editor.init();
|
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;
|
export default editor;
|
||||||
|
|
Loading…
Reference in New Issue