move init (600 lines+) in its own file

master
JFH 2021-01-02 23:54:08 +01:00
parent 83ee29c37e
commit c496ab8978
2 changed files with 862 additions and 837 deletions

855
src/editor/EditorStartup.js Normal file
View File

@ -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;

View File

@ -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<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`.
* @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 = $('<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 {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<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
// 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;