').append(btn.title);
}
} else if (btn.list) {
// Add button to list
button.addClass('push_button');
$$c('#' + btn.list + '_opts').append(button);
if (btn.isDefault) {
$$c('#cur_' + btn.list).append(button.children().clone());
const svgicon = btn.svgicon || btn.id;
placementObj['#cur_' + btn.list] = svgicon;
}
} else if (btn.includeWith) {
// Add to flyout menu / make flyout menu
const opts = btn.includeWith; // opts.button, default, position
refBtn = $$c(opts.button);
flyoutHolder = refBtn.parent(); // Create a flyout menu if there isn't one already
let tlsId;
if (!refBtn.parent().hasClass('tools_flyout')) {
// Create flyout placeholder
tlsId = refBtn[0].id.replace('tool_', 'tools_');
showBtn = refBtn.clone().attr('id', tlsId + '_show').append($$c('
', {
class: 'flyout_arrow_horiz'
}));
refBtn.before(showBtn); // Create a flyout div
flyoutHolder = makeFlyoutHolder(tlsId, refBtn);
}
refData = Actions.getButtonData(opts.button);
if (opts.isDefault) {
placementObj['#' + tlsId + '_show'] = btn.id;
} // TODO: Find way to set the current icon using the iconloader if this is not default
// Include data for extension button as well as ref button
const curH = holders['#' + flyoutHolder[0].id] = [{
sel: '#' + id,
fn: btn.events.click,
icon: btn.id,
key: btn.key,
isDefault: Boolean(btn.includeWith && btn.includeWith.isDefault)
}, refData]; // {sel:'#tool_rect', fn: clickRect, evt: 'mouseup', key: 4, parent: '#tools_rect', icon: 'rect'}
const pos = 'position' in opts ? opts.position : 'last';
const len = flyoutHolder.children().length; // Add at given position or end
if (!isNaN(pos) && pos >= 0 && pos < len) {
flyoutHolder.children().eq(pos).before(button);
} else {
flyoutHolder.append(button);
curH.reverse();
}
}
if (!svgicons) {
button.append(icon);
}
if (!btn.list) {
// Add given events to button
$$c.each(btn.events, function (name, func) {
if (name === 'click' && btn.type === 'mode') {
// `touch.js` changes `touchstart` to `mousedown`,
// so we must map extension click events as well
if (isTouch() && name === 'click') {
name = 'mousedown';
}
if (btn.includeWith) {
button.bind(name, func);
} else {
button.bind(name, function () {
if (toolButtonClick(button)) {
func();
}
});
}
if (btn.key) {
$$c(document).bind('keydown', btn.key, func);
if (btn.title) {
button.attr('title', btn.title + ' [' + btn.key + ']');
}
}
} else {
button.bind(name, func);
}
});
}
setupFlyouts(holders);
});
$$c.each(btnSelects, function () {
addAltDropDown(this.elem, this.list, this.callback, {
seticon: true
});
});
if (svgicons) {
return new Promise((resolve, reject) => {
// eslint-disable-line promise/avoid-new
$$c.svgIcons(svgicons, {
w: 24,
h: 24,
id_match: false,
no_img: !isWebkit(),
fallback: fallbackObj,
placement: placementObj,
callback(icons) {
// Non-ideal hack to make the icon match the current size
// if (curPrefs.iconsize && curPrefs.iconsize !== 'm') {
if (editor.pref('iconsize') !== 'm') {
prepResize();
}
runCallback();
resolve();
}
});
});
}
}
return runCallback();
};
/**
* @param {string} color
* @param {Float} opac
* @param {string} type
* @returns {module:jGraduate~Paint}
*/
const getPaint = function (color, opac, type) {
// update the editor's fill paint
const opts = {
alpha: opac
};
if (color.startsWith('url(#')) {
let refElem = svgCanvas.getRefElem(color);
if (refElem) {
refElem = refElem.cloneNode(true);
} else {
refElem = $$c('#' + type + '_color defs *')[0];
}
opts[refElem.tagName] = refElem;
} else if (color.startsWith('#')) {
opts.solidColor = color.substr(1);
} else {
opts.solidColor = 'none';
}
return new $$c.jGraduate.Paint(opts);
}; // $('#text').focus(function () { textBeingEntered = true; });
// $('#text').blur(function () { textBeingEntered = false; });
// bind the selected event to our function that handles updates to the UI
svgCanvas.bind('selected', selectedChanged);
svgCanvas.bind('transition', elementTransition);
svgCanvas.bind('changed', elementChanged);
svgCanvas.bind('saved', saveHandler);
svgCanvas.bind('exported', exportHandler);
svgCanvas.bind('exportedPDF', function (win, data) {
if (!data.output) {
// Ignore Chrome
return;
}
const {
exportWindowName
} = data;
if (exportWindowName) {
exportWindow = window.open('', exportWindowName); // A hack to get the window via JSON-able name without opening a new one
}
if (!exportWindow || exportWindow.closed) {
/* await */
$$c.alert(uiStrings$1.notification.popupWindowBlocked);
return;
}
exportWindow.location.href = data.output;
});
svgCanvas.bind('zoomed', zoomChanged);
svgCanvas.bind('zoomDone', zoomDone);
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
}) {
updateCanvas(center, newCtr);
});
svgCanvas.bind('contextset', contextChanged);
svgCanvas.bind('extension_added', extAdded);
svgCanvas.textActions.setInputElem($$c('#text')[0]);
let str = '
';
$$c.each(palette, function (i, item) {
str += '
';
});
$$c('#palette').append(str); // Set up editor background functionality
const colorBlocks = ['#FFF', '#888', '#000', 'chessboard'];
str = '';
$$c.each(colorBlocks, function (i, e) {
if (e === 'chessboard') {
str += '
';
} else {
str += '
';
}
});
$$c('#bg_blocks').append(str);
const blocks = $$c('#bg_blocks div');
const curBg = 'cur_background';
blocks.each(function () {
const blk = $$c(this);
blk.click(function () {
blocks.removeClass(curBg);
$$c(this).addClass(curBg);
});
});
setBackground(editor.pref('bkgd_color'), editor.pref('bkgd_url'));
$$c('#image_save_opts input').val([editor.pref('img_save')]);
/**
* @type {module:jQuerySpinButton.ValueCallback}
*/
const changeRectRadius = function (ctl) {
svgCanvas.setRectRadius(ctl.value);
};
/**
* @type {module:jQuerySpinButton.ValueCallback}
*/
const changeFontSize = function (ctl) {
svgCanvas.setFontSize(ctl.value);
};
/**
* @type {module:jQuerySpinButton.ValueCallback}
*/
const changeStrokeWidth = function (ctl) {
let val = ctl.value;
if (val === 0 && selectedElement && ['line', 'polyline'].includes(selectedElement.nodeName)) {
val = ctl.value = 1;
}
svgCanvas.setStrokeWidth(val);
};
/**
* @type {module:jQuerySpinButton.ValueCallback}
*/
const changeRotationAngle = function (ctl) {
svgCanvas.setRotationAngle(ctl.value);
$$c('#tool_reorient').toggleClass('disabled', Number.parseInt(ctl.value) === 0);
};
/**
* @param {external:jQuery.fn.SpinButton} ctl Spin Button
* @param {string} [val=ctl.value]
* @returns {void}
*/
const changeOpacity = function (ctl, val) {
if (isNullish(val)) {
val = ctl.value;
}
$$c('#group_opacity').val(val);
if (!ctl || !ctl.handle) {
$$c('#opac_slider').slider('option', 'value', val);
}
svgCanvas.setOpacity(val / 100);
};
/**
* @param {external:jQuery.fn.SpinButton} ctl Spin Button
* @param {string} [val=ctl.value]
* @param {boolean} noUndo
* @returns {void}
*/
const changeBlur = function (ctl, val, noUndo) {
if (isNullish(val)) {
val = ctl.value;
}
$$c('#blur').val(val);
let complete = false;
if (!ctl || !ctl.handle) {
$$c('#blur_slider').slider('option', 'value', val);
complete = true;
}
if (noUndo) {
svgCanvas.setBlurNoUndo(val);
} else {
svgCanvas.setBlur(val, complete);
}
};
$$c('#stroke_style').change(function () {
svgCanvas.setStrokeAttr('stroke-dasharray', $$c(this).val());
operaRepaint();
});
$$c('#stroke_linejoin').change(function () {
svgCanvas.setStrokeAttr('stroke-linejoin', $$c(this).val());
operaRepaint();
}); // Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
$$c('select').change(function () {
$$c(this).blur();
}); // fired when user wants to move elements to another layer
let promptMoveLayerOnce = false;
$$c('#selLayerNames').change(async function () {
const destLayer = this.options[this.selectedIndex].value;
const confirmStr = uiStrings$1.notification.QmoveElemsToLayer.replace('%s', destLayer);
/**
* @param {boolean} ok
* @returns {void}
*/
const moveToLayer = function (ok) {
if (!ok) {
return;
}
promptMoveLayerOnce = true;
svgCanvas.moveSelectedToLayer(destLayer);
svgCanvas.clearSelection();
populateLayers();
};
if (destLayer) {
if (promptMoveLayerOnce) {
moveToLayer(true);
} else {
const ok = await $$c.confirm(confirmStr);
if (!ok) {
return;
}
moveToLayer(true);
}
}
});
$$c('#font_family').change(function () {
svgCanvas.setFontFamily(this.value);
});
$$c('#seg_type').change(function () {
svgCanvas.setSegType($$c(this).val());
});
$$c('#text').bind('keyup input', function () {
svgCanvas.setTextContent(this.value);
});
$$c('#image_url').change(function () {
setImageURL(this.value);
});
$$c('#link_url').change(function () {
if (this.value.length) {
svgCanvas.setLinkURL(this.value);
} else {
svgCanvas.removeHyperlink();
}
});
$$c('#g_title').change(function () {
svgCanvas.setGroupTitle(this.value);
});
$$c('.attr_changer').change(function () {
const attr = this.getAttribute('data-attr');
let val = this.value;
const valid = isValidUnit(attr, val, selectedElement);
if (!valid) {
this.value = selectedElement.getAttribute(attr);
/* await */
$$c.alert(uiStrings$1.notification.invalidAttrValGiven);
return false;
}
if (attr !== 'id' && attr !== 'class') {
if (isNaN(val)) {
val = svgCanvas.convertToNum(attr, val);
} else if (curConfig.baseUnit !== 'px') {
// Convert unitless value to one with given unit
const unitData = getTypeMap();
if (selectedElement[attr] || svgCanvas.getMode() === 'pathedit' || attr === 'x' || attr === 'y') {
val *= unitData[curConfig.baseUnit];
}
}
} // if the user is changing the id, then de-select the element first
// change the ID, then re-select it with the new ID
if (attr === 'id') {
const elem = selectedElement;
svgCanvas.clearSelection();
elem.id = val;
svgCanvas.addToSelection([elem], true);
} else {
svgCanvas.changeSelectedAttribute(attr, val);
}
this.blur();
return true;
}); // Prevent selection of elements when shift-clicking
$$c('#palette').mouseover(function () {
const inp = $$c('
');
$$c(this).append(inp);
inp.focus().remove();
});
$$c('.palette_item').mousedown(function (evt) {
// shift key or right click for stroke
const picker = evt.shiftKey || evt.button === 2 ? 'stroke' : 'fill';
let color = $$c(this).data('rgb');
let paint; // Webkit-based browsers returned 'initial' here for no stroke
if (color === 'none' || color === 'transparent' || color === 'initial') {
color = 'none';
paint = new $$c.jGraduate.Paint();
} else {
paint = new $$c.jGraduate.Paint({
alpha: 100,
solidColor: color.substr(1)
});
}
paintBox[picker].setPaint(paint);
svgCanvas.setColor(picker, color);
if (color !== 'none' && svgCanvas.getPaintOpacity(picker) !== 1) {
svgCanvas.setPaintOpacity(picker, 1.0);
}
updateToolButtonState();
}).bind('contextmenu', function (e) {
e.preventDefault();
});
$$c('#toggle_stroke_tools').on('click', function () {
$$c('#tools_bottom').toggleClass('expanded');
});
(function () {
const wArea = workarea[0];
let lastX = null,
lastY = null,
panning = false,
keypan = false;
$$c('#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;
});
$$c(window).mouseup(function () {
panning = false;
});
$$c(document).bind('keydown', 'space', function (evt) {
svgCanvas.spaceKey = keypan = true;
evt.preventDefault();
}).bind('keyup', 'space', function (evt) {
evt.preventDefault();
svgCanvas.spaceKey = keypan = false;
}).bind('keydown', 'shift', function (evt) {
if (svgCanvas.getMode() === 'zoom') {
workarea.css('cursor', zoomOutIcon);
}
}).bind('keyup', 'shift', function (evt) {
if (svgCanvas.getMode() === 'zoom') {
workarea.css('cursor', zoomInIcon);
}
});
/**
* @function module:SVGEditor.setPanning
* @param {boolean} active
* @returns {void}
*/
editor.setPanning = function (active) {
svgCanvas.spaceKey = keypan = active;
};
})();
(function () {
const button = $$c('#main_icon');
const overlay = $$c('#main_icon span');
const list = $$c('#main_menu');
let onButton = false;
let height = 0;
let jsHover = true;
let setClick = false;
/*
// Currently unused
const hideMenu = function () {
list.fadeOut(200);
};
*/
$$c(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;
$$c(evt.target).click(function () {
list.css('margin-left', '-9999px').show();
});
}
}
onButton = false;
}).mousedown(function (evt) {
// $('.contextMenu').hide();
const islib = $$c(evt.target).closest('div.tools_flyout, .contextMenu').length;
if (!islib) {
$$c('.tools_flyout:visible,.contextMenu').fadeOut(250);
}
});
overlay.bind('mousedown', function () {
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(function () {
onButton = true;
}).mouseout(function () {
onButton = false;
});
const listItems = $$c('#main_menu li'); // Check if JS method of hovering needs to be used (Webkit bug)
listItems.mouseover(function () {
jsHover = $$c(this).css('background-color') === 'rgba(0, 0, 0, 0)';
listItems.unbind('mouseover');
if (jsHover) {
listItems.mouseover(function () {
this.style.backgroundColor = '#FFC';
}).mouseout(function () {
this.style.backgroundColor = 'transparent';
return true;
});
}
});
})(); // Made public for UI customization.
// TODO: Group UI functions into a public editor.ui interface.
/**
* See {@link http://api.jquery.com/bind/#bind-eventType-eventData-handler}.
* @callback module:SVGEditor.DropDownCallback
* @param {external:jQuery.Event} ev See {@link http://api.jquery.com/Types/#Event}
* @listens external:jQuery.Event
* @returns {void|boolean} Calls `preventDefault()` and `stopPropagation()`
*/
/**
* @function module:SVGEditor.addDropDown
* @param {Element|string} elem DOM Element or selector
* @param {module:SVGEditor.DropDownCallback} callback Mouseup callback
* @param {boolean} dropUp
* @returns {void}
*/
editor.addDropDown = function (elem, callback, dropUp) {
if (!$$c(elem).length) {
return;
} // Quit if called on non-existent element
const button = $$c(elem).find('button');
const list = $$c(elem).find('ul').attr('id', $$c(elem)[0].id + '-list');
if (dropUp) {
$$c(elem).addClass('dropup');
} else {
// Move list to place where it can overflow container
$$c('#option_lists').append(list);
}
list.find('li').bind('mouseup', callback);
let onButton = false;
$$c(window).mouseup(function (evt) {
if (!onButton) {
button.removeClass('down');
list.hide();
}
onButton = false;
});
button.bind('mousedown', function () {
if (!button.hasClass('down')) {
if (!dropUp) {
const pos = $$c(elem).position();
list.css({
top: pos.top + 24,
left: pos.left - 10
});
}
list.show();
onButton = true;
} else {
list.hide();
}
button.toggleClass('down');
}).hover(function () {
onButton = true;
}).mouseout(function () {
onButton = false;
});
};
editor.addDropDown('#font_family_dropdown', function () {
$$c('#font_family').val($$c(this).text()).change();
});
editor.addDropDown('#opacity_dropdown', function () {
if ($$c(this).find('div').length) {
return;
}
const perc = Number.parseInt($$c(this).text().split('%')[0]);
changeOpacity(false, perc);
}, true); // For slider usage, see: http://jqueryui.com/demos/slider/
$$c('#opac_slider').slider({
start() {
$$c('#opacity_dropdown li:not(.special)').hide();
},
stop() {
$$c('#opacity_dropdown li').show();
$$c(window).mouseup();
},
slide(evt, ui) {
changeOpacity(ui);
}
});
editor.addDropDown('#blur_dropdown', $$c.noop);
let slideStart = false;
$$c('#blur_slider').slider({
max: 10,
step: 0.1,
stop(evt, ui) {
slideStart = false;
changeBlur(ui);
$$c('#blur_dropdown li').show();
$$c(window).mouseup();
},
start() {
slideStart = true;
},
slide(evt, ui) {
changeBlur(ui, null, slideStart);
}
});
editor.addDropDown('#zoom_dropdown', function () {
const item = $$c(this);
const val = item.data('val');
if (val) {
zoomChanged(window, val);
} else {
changeZoom({
value: Number.parseFloat(item.text())
});
}
}, true);
addAltDropDown('#stroke_linecap', '#linecap_opts', function () {
setStrokeOpt(this, true);
}, {
dropUp: true
});
addAltDropDown('#stroke_linejoin', '#linejoin_opts', function () {
setStrokeOpt(this, true);
}, {
dropUp: true
});
addAltDropDown('#tool_position', '#position_opts', function () {
const letter = this.id.replace('tool_pos', '').charAt(0);
svgCanvas.alignSelectedElements(letter, 'page');
}, {
multiclick: true
});
/*
When a flyout icon is selected
(if flyout) {
- Change the icon
- Make pressing the button run its stuff
}
- Run its stuff
When its shortcut key is pressed
- If not current in list, do as above
, else:
- Just run its stuff
*/
// Unfocus text input when workarea is mousedowned.
(function () {
let inp;
/**
*
* @returns {void}
*/
const unfocus = function () {
$$c(inp).blur();
};
$$c('#svg_editor').find('button, select, input:not(#text)').focus(function () {
inp = this; // eslint-disable-line consistent-this
uiContext = 'toolbars';
workarea.mousedown(unfocus);
}).blur(function () {
uiContext = 'canvas';
workarea.unbind('mousedown', unfocus); // Go back to selecting text if in textedit mode
if (svgCanvas.getMode() === 'textedit') {
$$c('#text').focus();
}
});
})();
/**
*
* @returns {void}
*/
const clickFHPath = function () {
if (toolButtonClick('#tool_fhpath')) {
svgCanvas.setMode('fhpath');
}
};
/**
*
* @returns {void}
*/
const clickLine = function () {
if (toolButtonClick('#tool_line')) {
svgCanvas.setMode('line');
}
};
/**
*
* @returns {void}
*/
const clickSquare = function () {
if (toolButtonClick('#tool_square')) {
svgCanvas.setMode('square');
}
};
/**
*
* @returns {void}
*/
const clickRect = function () {
if (toolButtonClick('#tool_rect')) {
svgCanvas.setMode('rect');
}
};
/**
*
* @returns {void}
*/
const clickFHRect = function () {
if (toolButtonClick('#tool_fhrect')) {
svgCanvas.setMode('fhrect');
}
};
/**
*
* @returns {void}
*/
const clickCircle = function () {
if (toolButtonClick('#tool_circle')) {
svgCanvas.setMode('circle');
}
};
/**
*
* @returns {void}
*/
const clickEllipse = function () {
if (toolButtonClick('#tool_ellipse')) {
svgCanvas.setMode('ellipse');
}
};
/**
*
* @returns {void}
*/
const clickFHEllipse = function () {
if (toolButtonClick('#tool_fhellipse')) {
svgCanvas.setMode('fhellipse');
}
};
/**
*
* @returns {void}
*/
const clickImage = function () {
if (toolButtonClick('#tool_image')) {
svgCanvas.setMode('image');
}
};
/**
*
* @returns {void}
*/
const clickZoom = function () {
if (toolButtonClick('#tool_zoom')) {
svgCanvas.setMode('zoom');
workarea.css('cursor', zoomInIcon);
}
};
/**
* @param {Float} multiplier
* @returns {void}
*/
const zoomImage = function (multiplier) {
const res = svgCanvas.getResolution();
multiplier = multiplier ? res.zoom * multiplier : 1; // setResolution(res.w * multiplier, res.h * multiplier, true);
$$c('#zoom').val(multiplier * 100);
svgCanvas.setZoom(multiplier);
zoomDone();
updateCanvas(true);
};
/**
*
* @returns {void}
*/
const dblclickZoom = function () {
if (toolButtonClick('#tool_zoom')) {
zoomImage();
setSelectMode();
}
};
/**
*
* @returns {void}
*/
const clickText = function () {
if (toolButtonClick('#tool_text')) {
svgCanvas.setMode('text');
}
};
/**
*
* @returns {void}
*/
const clickPath = function () {
if (toolButtonClick('#tool_path')) {
svgCanvas.setMode('path');
}
};
/**
* Delete is a contextual tool that only appears in the ribbon if
* an element has been selected.
* @returns {void}
*/
const deleteSelected = function () {
if (!isNullish(selectedElement) || multiselected) {
svgCanvas.deleteSelectedElements();
}
};
/**
*
* @returns {void}
*/
const cutSelected = function () {
if (!isNullish(selectedElement) || multiselected) {
svgCanvas.cutSelectedElements();
}
};
/**
*
* @returns {void}
*/
const copySelected = function () {
if (!isNullish(selectedElement) || multiselected) {
svgCanvas.copySelectedElements();
}
};
/**
*
* @returns {void}
*/
const pasteInCenter = function () {
const zoom = svgCanvas.getZoom();
const x = (workarea[0].scrollLeft + workarea.width() / 2) / zoom - svgCanvas.contentW;
const y = (workarea[0].scrollTop + workarea.height() / 2) / zoom - svgCanvas.contentH;
svgCanvas.pasteElements('point', x, y);
};
/**
*
* @returns {void}
*/
const moveToTopSelected = function () {
if (!isNullish(selectedElement)) {
svgCanvas.moveToTopSelectedElement();
}
};
/**
*
* @returns {void}
*/
const moveToBottomSelected = function () {
if (!isNullish(selectedElement)) {
svgCanvas.moveToBottomSelectedElement();
}
};
/**
* @param {"Up"|"Down"} dir
* @returns {void}
*/
const moveUpDownSelected = function (dir) {
if (!isNullish(selectedElement)) {
svgCanvas.moveUpDownSelected(dir);
}
};
/**
*
* @returns {void}
*/
const convertToPath = function () {
if (!isNullish(selectedElement)) {
svgCanvas.convertToPath();
}
};
/**
*
* @returns {void}
*/
const reorientPath = function () {
if (!isNullish(selectedElement)) {
path.reorient();
}
};
/**
*
* @returns {Promise
} Resolves to `undefined`
*/
const makeHyperlink = async function () {
if (!isNullish(selectedElement) || multiselected) {
const url = await $$c.prompt(uiStrings$1.notification.enterNewLinkURL, 'http://');
if (url) {
svgCanvas.makeHyperlink(url);
}
}
};
/**
* @param {Float} dx
* @param {Float} dy
* @returns {void}
*/
const moveSelected = function (dx, dy) {
if (!isNullish(selectedElement) || multiselected) {
if (curConfig.gridSnapping) {
// Use grid snap value regardless of zoom level
const multi = svgCanvas.getZoom() * curConfig.snappingStep;
dx *= multi;
dy *= multi;
}
svgCanvas.moveSelectedElements(dx, dy);
}
};
/**
*
* @returns {void}
*/
const linkControlPoints = function () {
$$c('#tool_node_link').toggleClass('push_button_pressed tool_button');
const linked = $$c('#tool_node_link').hasClass('push_button_pressed');
path.linkControlPoints(linked);
};
/**
*
* @returns {void}
*/
const clonePathNode = function () {
if (path.getNodePoint()) {
path.clonePathNode();
}
};
/**
*
* @returns {void}
*/
const deletePathNode = function () {
if (path.getNodePoint()) {
path.deletePathNode();
}
};
/**
*
* @returns {void}
*/
const addSubPath = function () {
const button = $$c('#tool_add_subpath');
const sp = !button.hasClass('push_button_pressed');
button.toggleClass('push_button_pressed tool_button');
path.addSubPath(sp);
};
/**
*
* @returns {void}
*/
const opencloseSubPath = function () {
path.opencloseSubPath();
};
/**
*
* @returns {void}
*/
const selectNext = function () {
svgCanvas.cycleElement(1);
};
/**
*
* @returns {void}
*/
const selectPrev = function () {
svgCanvas.cycleElement(0);
};
/**
* @param {0|1} cw
* @param {Integer} step
* @returns {void}
*/
const rotateSelected = function (cw, step) {
if (isNullish(selectedElement) || multiselected) {
return;
}
if (!cw) {
step *= -1;
}
const angle = Number.parseFloat($$c('#angle').val()) + step;
svgCanvas.setRotationAngle(angle);
updateContextPanel();
};
/**
* @fires module:svgcanvas.SvgCanvas#event:ext_onNewDocument
* @returns {Promise} Resolves to `undefined`
*/
const clickClear = async function () {
const [x, y] = curConfig.dimensions;
const ok = await $$c.confirm(uiStrings$1.notification.QwantToClear);
if (!ok) {
return;
}
setSelectMode();
svgCanvas.clear();
svgCanvas.setResolution(x, y);
updateCanvas(true);
zoomImage();
populateLayers();
updateContextPanel();
prepPaints();
svgCanvas.runExtensions('onNewDocument');
};
/**
*
* @returns {false}
*/
const clickBold = function () {
svgCanvas.setBold(!svgCanvas.getBold());
updateContextPanel();
return false;
};
/**
*
* @returns {false}
*/
const clickItalic = function () {
svgCanvas.setItalic(!svgCanvas.getItalic());
updateContextPanel();
return false;
};
/**
*
* @returns {void}
*/
const clickSave = function () {
// In the future, more options can be provided here
const saveOpts = {
images: editor.pref('img_save'),
round_digits: 6
};
svgCanvas.save(saveOpts);
};
let loadingURL;
/**
*
* @returns {Promise} Resolves to `undefined`
*/
const clickExport = async function () {
const imgType = await $$c.select('Select an image type for export: ', [// See http://kangax.github.io/jstests/toDataUrl_mime_type_test/ for a useful list of MIME types and browser support
// 'ICO', // Todo: Find a way to preserve transparency in SVG-Edit if not working presently and do full packaging for x-icon; then switch back to position after 'PNG'
'PNG', 'JPEG', 'BMP', 'WEBP', 'PDF'], function () {
const sel = $$c(this);
if (sel.val() === 'JPEG' || sel.val() === 'WEBP') {
if (!$$c('#image-slider').length) {
$$c(``).appendTo(sel.parent());
}
} else {
$$c('#image-slider').parent().remove();
}
}); // todo: replace hard-coded msg with uiStrings.notification.
if (!imgType) {
return;
} // Open placeholder window (prevents popup)
let exportWindowName;
/**
*
* @returns {void}
*/
function openExportWindow() {
const {
loadingImage
} = uiStrings$1.notification;
if (curConfig.exportWindowType === 'new') {
editor.exportWindowCt++;
}
exportWindowName = curConfig.canvasName + editor.exportWindowCt;
let popHTML, popURL;
if (loadingURL) {
popURL = loadingURL;
} else {
popHTML = `
${loadingImage}
${loadingImage}
`;
if (typeof URL !== 'undefined' && URL.createObjectURL) {
const blob = new Blob([popHTML], {
type: 'text/html'
});
popURL = URL.createObjectURL(blob);
} else {
popURL = 'data:text/html;base64;charset=utf-8,' + encode64(popHTML);
}
loadingURL = popURL;
}
exportWindow = window.open(popURL, exportWindowName);
}
const chrome = isChrome();
if (imgType === 'PDF') {
if (!customExportPDF && !chrome) {
openExportWindow();
}
svgCanvas.exportPDF(exportWindowName);
} else {
if (!customExportImage) {
openExportWindow();
}
const quality = Number.parseInt($$c('#image-slider').val()) / 100;
/* const results = */
await svgCanvas.rasterExport(imgType, quality, exportWindowName);
}
};
/**
* By default, svgCanvas.open() is a no-op. It is up to an extension
* mechanism (opera widget, etc.) to call `setCustomHandlers()` which
* will make it do something.
* @returns {void}
*/
const clickOpen = function () {
svgCanvas.open();
};
/**
*
* @returns {void}
*/
const clickImport = function () {
/* */
};
/**
*
* @returns {void}
*/
const clickUndo = function () {
if (undoMgr.getUndoStackSize() > 0) {
undoMgr.undo();
populateLayers();
}
};
/**
*
* @returns {void}
*/
const clickRedo = function () {
if (undoMgr.getRedoStackSize() > 0) {
undoMgr.redo();
populateLayers();
}
};
/**
*
* @returns {void}
*/
const clickGroup = function () {
// group
if (multiselected) {
svgCanvas.groupSelectedElements(); // ungroup
} else if (selectedElement) {
svgCanvas.ungroupSelectedElement();
}
};
/**
*
* @returns {void}
*/
const clickClone = function () {
svgCanvas.cloneSelectedElements(20, 20);
};
/**
*
* @returns {void}
*/
const clickAlign = function () {
const letter = this.id.replace('tool_align', '').charAt(0);
svgCanvas.alignSelectedElements(letter, $$c('#align_relative_to').val());
};
/**
*
* @returns {void}
*/
const clickWireframe = function () {
$$c('#tool_wireframe').toggleClass('push_button_pressed tool_button');
workarea.toggleClass('wireframe');
if (supportsNonSS) {
return;
}
const wfRules = $$c('#wireframe_rules');
if (!wfRules.length) {
/* wfRules = */
$$c('').appendTo('head');
} else {
wfRules.empty();
}
updateWireFrame();
};
$$c('#svg_docprops_container, #svg_prefs_container').draggable({
cancel: 'button,fieldset',
containment: 'window'
}).css('position', 'absolute');
let docprops = false;
let preferences = false;
/**
*
* @returns {void}
*/
const showDocProperties = function () {
if (docprops) {
return;
}
docprops = true; // This selects the correct radio button by using the array notation
$$c('#image_save_opts input').val([editor.pref('img_save')]); // update resolution option with actual resolution
const res = svgCanvas.getResolution();
if (curConfig.baseUnit !== 'px') {
res.w = convertUnit(res.w) + curConfig.baseUnit;
res.h = convertUnit(res.h) + curConfig.baseUnit;
}
$$c('#canvas_width').val(res.w);
$$c('#canvas_height').val(res.h);
$$c('#canvas_title').val(svgCanvas.getDocumentTitle());
$$c('#svg_docprops').show();
};
/**
*
* @returns {void}
*/
const showPreferences = function () {
if (preferences) {
return;
}
preferences = true;
$$c('#main_menu').hide(); // Update background color with current one
const canvasBg = curPrefs.bkgd_color;
const url = editor.pref('bkgd_url');
blocks.each(function () {
const blk = $$c(this);
const isBg = blk.data('bgcolor') === canvasBg;
blk.toggleClass(curBg, isBg);
});
if (!canvasBg) {
blocks.eq(0).addClass(curBg);
}
if (url) {
$$c('#canvas_bg_url').val(url);
}
$$c('#grid_snapping_on').prop('checked', curConfig.gridSnapping);
$$c('#grid_snapping_step').attr('value', curConfig.snappingStep);
$$c('#grid_color').attr('value', curConfig.gridColor);
$$c('#svg_prefs').show();
};
/**
*
* @returns {void}
*/
const openHomePage = function () {
window.open(homePage, '_blank');
};
/**
*
* @returns {void}
*/
const hideSourceEditor = function () {
$$c('#svg_source_editor').hide();
editingsource = false;
$$c('#svg_source_textarea').blur();
};
/**
*
* @returns {Promise} Resolves to `undefined`
*/
const saveSourceEditor = async function () {
if (!editingsource) {
return;
}
const saveChanges = function () {
svgCanvas.clearSelection();
hideSourceEditor();
zoomImage();
populateLayers();
updateTitle();
prepPaints();
};
if (!svgCanvas.setSvgString($$c('#svg_source_textarea').val())) {
const ok = await $$c.confirm(uiStrings$1.notification.QerrorsRevertToSource);
if (!ok) {
return;
}
saveChanges();
return;
}
saveChanges();
setSelectMode();
};
/**
*
* @returns {void}
*/
const hideDocProperties = function () {
$$c('#svg_docprops').hide();
$$c('#canvas_width,#canvas_height').removeAttr('disabled');
$$c('#resolution')[0].selectedIndex = 0;
$$c('#image_save_opts input').val([editor.pref('img_save')]);
docprops = false;
};
/**
*
* @returns {void}
*/
const hidePreferences = function () {
$$c('#svg_prefs').hide();
preferences = false;
};
/**
*
* @returns {boolean} Whether there were problems saving the document properties
*/
const saveDocProperties = function () {
// set title
const newTitle = $$c('#canvas_title').val();
updateTitle(newTitle);
svgCanvas.setDocumentTitle(newTitle); // update resolution
const width = $$c('#canvas_width'),
w = width.val();
const height = $$c('#canvas_height'),
h = height.val();
if (w !== 'fit' && !isValidUnit('width', w)) {
width.parent().addClass('error');
/* await */
$$c.alert(uiStrings$1.notification.invalidAttrValGiven);
return false;
}
width.parent().removeClass('error');
if (h !== 'fit' && !isValidUnit('height', h)) {
height.parent().addClass('error');
/* await */
$$c.alert(uiStrings$1.notification.invalidAttrValGiven);
return false;
}
height.parent().removeClass('error');
if (!svgCanvas.setResolution(w, h)) {
/* await */
$$c.alert(uiStrings$1.notification.noContentToFitTo);
return false;
} // Set image save option
editor.pref('img_save', $$c('#image_save_opts :checked').val());
updateCanvas();
hideDocProperties();
return true;
};
/**
* Save user preferences based on current values in the UI.
* @function module:SVGEditor.savePreferences
* @returns {Promise}
*/
const savePreferences = editor.savePreferences = async function () {
// Set background
const color = $$c('#bg_blocks div.cur_background').data('bgcolor') || '#FFF';
setBackground(color, $$c('#canvas_bg_url').val()); // set language
const lang = $$c('#lang_select').val();
if (lang && lang !== editor.pref('lang')) {
const {
langParam,
langData
} = await editor.putLocale(lang, goodLangs, curConfig);
await setLang(langParam, langData);
} // set icon size
setIconSize($$c('#iconsize').val()); // set grid setting
curConfig.gridSnapping = $$c('#grid_snapping_on')[0].checked;
curConfig.snappingStep = $$c('#grid_snapping_step').val();
curConfig.gridColor = $$c('#grid_color').val();
curConfig.showRulers = $$c('#show_rulers')[0].checked;
$$c('#rulers').toggle(curConfig.showRulers);
if (curConfig.showRulers) {
updateRulers();
}
curConfig.baseUnit = $$c('#base_unit').val();
svgCanvas.setConfig(curConfig);
updateCanvas();
hidePreferences();
};
let resetScrollPos = $$c.noop;
/**
*
* @returns {Promise} Resolves to `undefined`
*/
const cancelOverlays = async function () {
$$c('#dialog_box').hide();
if (!editingsource && !docprops && !preferences) {
if (curContext) {
svgCanvas.leaveContext();
}
return;
}
if (editingsource) {
if (origSource !== $$c('#svg_source_textarea').val()) {
const ok = await $$c.confirm(uiStrings$1.notification.QignoreSourceChanges);
if (ok) {
hideSourceEditor();
}
} else {
hideSourceEditor();
}
} else if (docprops) {
hideDocProperties();
} else if (preferences) {
hidePreferences();
}
resetScrollPos();
};
const winWh = {
width: $$c(window).width(),
height: $$c(window).height()
}; // Fix for Issue 781: Drawing area jumps to top-left corner on window resize (IE9)
if (isIE()) {
resetScrollPos = function () {
if (workarea[0].scrollLeft === 0 && workarea[0].scrollTop === 0) {
workarea[0].scrollLeft = curScrollPos.left;
workarea[0].scrollTop = curScrollPos.top;
}
};
curScrollPos = {
left: workarea[0].scrollLeft,
top: workarea[0].scrollTop
};
$$c(window).resize(resetScrollPos);
editor.ready(function () {
// TODO: Find better way to detect when to do this to minimize
// flickering effect
return new Promise((resolve, reject) => {
// eslint-disable-line promise/avoid-new
setTimeout(function () {
resetScrollPos();
resolve();
}, 500);
});
});
workarea.scroll(function () {
curScrollPos = {
left: workarea[0].scrollLeft,
top: workarea[0].scrollTop
};
});
}
$$c(window).resize(function (evt) {
$$c.each(winWh, function (type, val) {
const curval = $$c(window)[type]();
workarea[0]['scroll' + (type === 'width' ? 'Left' : 'Top')] -= (curval - val) / 2;
winWh[type] = curval;
});
setFlyoutPositions();
});
workarea.scroll(function () {
// TODO: jQuery's scrollLeft/Top() wouldn't require a null check
if ($$c('#ruler_x').length) {
$$c('#ruler_x')[0].scrollLeft = workarea[0].scrollLeft;
}
if ($$c('#ruler_y').length) {
$$c('#ruler_y')[0].scrollTop = workarea[0].scrollTop;
}
});
$$c('#url_notice').click(function () {
/* await */
$$c.alert(this.title);
});
$$c('#change_image_url').click(promptImgURL); // added these event handlers for all the push buttons so they
// behave more like buttons being pressed-in and not images
(function () {
const toolnames = ['clear', 'open', 'save', 'source', 'delete', 'delete_multi', 'paste', 'clone', 'clone_multi', 'move_top', 'move_bottom'];
const curClass = 'tool_button_current';
let allTools = '';
$$c.each(toolnames, function (i, item) {
allTools += (i ? ',' : '') + '#tool_' + item;
});
$$c(allTools).mousedown(function () {
$$c(this).addClass(curClass);
}).bind('mousedown mouseout', function () {
$$c(this).removeClass(curClass);
});
$$c('#tool_undo, #tool_redo').mousedown(function () {
if (!$$c(this).hasClass('disabled')) {
$$c(this).addClass(curClass);
}
}).bind('mousedown mouseout', function () {
$$c(this).removeClass(curClass);
});
})(); // switch modifier key in tooltips if mac
// NOTE: This code is not used yet until I can figure out how to successfully bind ctrl/meta
// in Opera and Chrome
if (isMac() && !window.opera) {
const shortcutButtons = ['tool_clear', 'tool_save', 'tool_source', 'tool_undo', 'tool_redo', 'tool_clone'];
let i = shortcutButtons.length;
while (i--) {
const button = document.getElementById(shortcutButtons[i]);
if (button) {
const {
title
} = button;
const index = title.indexOf('Ctrl+');
button.title = [title.substr(0, index), 'Cmd+', title.substr(index + 5)].join('');
}
}
}
/**
* @param {external:jQuery} elem
* @todo Go back to the color boxes having white background-color and then setting
* background-image to none.png (otherwise partially transparent gradients look weird)
* @returns {void}
*/
const colorPicker = function (elem) {
const picker = elem.attr('id') === 'stroke_color' ? 'stroke' : 'fill'; // const opacity = (picker == 'stroke' ? $('#stroke_opacity') : $('#fill_opacity'));
const title = picker === 'stroke' ? uiStrings$1.ui.pick_stroke_paint_opacity : uiStrings$1.ui.pick_fill_paint_opacity; // let wasNone = false; // Currently unused
const pos = elem.offset();
let {
paint
} = paintBox[picker];
$$c('#color_picker').draggable({
cancel: '.jGraduate_tabs, .jGraduate_colPick, .jGraduate_gradPick, .jPicker',
containment: 'window'
}).css(curConfig.colorPickerCSS || {
left: pos.left - 140,
bottom: 40
}).jGraduate({
paint,
window: {
pickerTitle: title
},
images: {
clientPath: curConfig.jGraduatePath
},
newstop: 'inverse'
}, function (p) {
paint = new $$c.jGraduate.Paint(p);
paintBox[picker].setPaint(paint);
svgCanvas.setPaint(picker, paint);
$$c('#color_picker').hide();
}, function () {
$$c('#color_picker').hide();
});
};
/**
* Paint box class.
*/
class PaintBox {
/**
* @param {string|Element|external:jQuery} container
* @param {"fill"} type
*/
constructor(container, type) {
const cur = curConfig[type === 'fill' ? 'initFill' : 'initStroke']; // set up gradients to be used for the buttons
const svgdocbox = new DOMParser().parseFromString(``, 'text/xml');
let docElem = svgdocbox.documentElement;
docElem = $$c(container)[0].appendChild(document.importNode(docElem, true));
docElem.setAttribute('width', 16.5);
this.rect = docElem.firstElementChild;
this.defs = docElem.getElementsByTagName('defs')[0];
this.grad = this.defs.firstElementChild;
this.paint = new $$c.jGraduate.Paint({
solidColor: cur.color
});
this.type = type;
}
/**
* @param {module:jGraduate~Paint} paint
* @param {boolean} apply
* @returns {void}
*/
setPaint(paint, apply) {
this.paint = paint;
const ptype = paint.type;
const opac = paint.alpha / 100;
let fillAttr = 'none';
switch (ptype) {
case 'solidColor':
fillAttr = paint[ptype] !== 'none' ? '#' + paint[ptype] : paint[ptype];
break;
case 'linearGradient':
case 'radialGradient':
{
this.grad.remove();
this.grad = this.defs.appendChild(paint[ptype]);
const id = this.grad.id = 'gradbox_' + this.type;
fillAttr = 'url(#' + id + ')';
break;
}
}
this.rect.setAttribute('fill', fillAttr);
this.rect.setAttribute('opacity', opac);
if (apply) {
svgCanvas.setColor(this.type, this._paintColor, true);
svgCanvas.setPaintOpacity(this.type, this._paintOpacity, true);
}
}
/**
* @param {boolean} apply
* @returns {void}
*/
update(apply) {
if (!selectedElement) {
return;
}
const {
type
} = this;
switch (selectedElement.tagName) {
case 'use':
case 'image':
case 'foreignObject':
// These elements don't have fill or stroke, so don't change
// the current value
return;
case 'g':
case 'a':
{
const childs = selectedElement.getElementsByTagName('*');
let gPaint = null;
for (let i = 0, len = childs.length; i < len; i++) {
const elem = childs[i];
const p = elem.getAttribute(type);
if (i === 0) {
gPaint = p;
} else if (gPaint !== p) {
gPaint = null;
break;
}
}
if (gPaint === null) {
// No common color, don't update anything
this._paintColor = null;
return;
}
this._paintColor = gPaint;
this._paintOpacity = 1;
break;
}
default:
{
this._paintOpacity = Number.parseFloat(selectedElement.getAttribute(type + '-opacity'));
if (Number.isNaN(this._paintOpacity)) {
this._paintOpacity = 1.0;
}
const defColor = type === 'fill' ? 'black' : 'none';
this._paintColor = selectedElement.getAttribute(type) || defColor;
}
}
if (apply) {
svgCanvas.setColor(type, this._paintColor, true);
svgCanvas.setPaintOpacity(type, this._paintOpacity, true);
}
this._paintOpacity *= 100;
const paint = getPaint(this._paintColor, this._paintOpacity, type); // update the rect inside #fill_color/#stroke_color
this.setPaint(paint);
}
/**
* @returns {void}
*/
prep() {
const ptype = this.paint.type;
switch (ptype) {
case 'linearGradient':
case 'radialGradient':
{
const paint = new $$c.jGraduate.Paint({
copy: this.paint
});
svgCanvas.setPaint(this.type, paint);
break;
}
}
}
}
PaintBox.ctr = 0;
paintBox.fill = new PaintBox('#fill_color', 'fill');
paintBox.stroke = new PaintBox('#stroke_color', 'stroke');
$$c('#stroke_width').val(curConfig.initStroke.width);
$$c('#group_opacity').val(curConfig.initOpacity * 100); // Use this SVG elem to test vectorEffect support
const testEl = paintBox.fill.rect.cloneNode(false);
testEl.setAttribute('style', 'vector-effect:non-scaling-stroke');
const supportsNonSS = testEl.style.vectorEffect === 'non-scaling-stroke';
testEl.removeAttribute('style');
const svgdocbox = paintBox.fill.rect.ownerDocument; // Use this to test support for blur element. Seems to work to test support in Webkit
const blurTest = svgdocbox.createElementNS(NS.SVG, 'feGaussianBlur');
if (blurTest.stdDeviationX === undefined) {
$$c('#tool_blur').hide();
}
$$c(blurTest).remove(); // Test for zoom icon support
(function () {
const pre = '-' + uaPrefix.toLowerCase() + '-zoom-';
const zoom = pre + 'in';
workarea.css('cursor', zoom);
if (workarea.css('cursor') === zoom) {
zoomInIcon = zoom;
zoomOutIcon = pre + 'out';
}
workarea.css('cursor', 'auto');
})(); // Test for embedImage support (use timeout to not interfere with page load)
setTimeout(function () {
svgCanvas.embedImage('images/logo.png', function (datauri) {
if (!datauri) {
// Disable option
$$c('#image_save_opts [value=embed]').attr('disabled', 'disabled');
$$c('#image_save_opts input').val(['ref']);
editor.pref('img_save', 'ref');
$$c('#image_opt_embed').css('color', '#666').attr('title', uiStrings$1.notification.featNotSupported);
}
});
}, 1000);
$$c('#fill_color, #tool_fill .icon_label').click(function () {
colorPicker($$c('#fill_color'));
updateToolButtonState();
});
$$c('#stroke_color, #tool_stroke .icon_label').click(function () {
colorPicker($$c('#stroke_color'));
updateToolButtonState();
});
$$c('#group_opacityLabel').click(function () {
$$c('#opacity_dropdown button').mousedown();
$$c(window).mouseup();
});
$$c('#zoomLabel').click(function () {
$$c('#zoom_dropdown button').mousedown();
$$c(window).mouseup();
});
$$c('#tool_move_top').mousedown(function (evt) {
$$c('#tools_stacking').show();
evt.preventDefault();
});
$$c('.layer_button').mousedown(function () {
$$c(this).addClass('layer_buttonpressed');
}).mouseout(function () {
$$c(this).removeClass('layer_buttonpressed');
}).mouseup(function () {
$$c(this).removeClass('layer_buttonpressed');
});
$$c('.push_button').mousedown(function () {
if (!$$c(this).hasClass('disabled')) {
$$c(this).addClass('push_button_pressed').removeClass('push_button');
}
}).mouseout(function () {
$$c(this).removeClass('push_button_pressed').addClass('push_button');
}).mouseup(function () {
$$c(this).removeClass('push_button_pressed').addClass('push_button');
}); // ask for a layer name
$$c('#layer_new').click(async function () {
let uniqName,
i = svgCanvas.getCurrentDrawing().getNumLayers();
do {
uniqName = uiStrings$1.layers.layer + ' ' + ++i;
} while (svgCanvas.getCurrentDrawing().hasLayer(uniqName));
const newName = await $$c.prompt(uiStrings$1.notification.enterUniqueLayerName, uniqName);
if (!newName) {
return;
}
if (svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */
$$c.alert(uiStrings$1.notification.dupeLayerName);
return;
}
svgCanvas.createLayer(newName);
updateContextPanel();
populateLayers();
});
/**
*
* @returns {void}
*/
function deleteLayer() {
if (svgCanvas.deleteCurrentLayer()) {
updateContextPanel();
populateLayers(); // This matches what SvgCanvas does
// TODO: make this behavior less brittle (svg-editor should get which
// layer is selected from the canvas and then select that one in the UI)
$$c('#layerlist tr.layer').removeClass('layersel');
$$c('#layerlist tr.layer:first').addClass('layersel');
}
}
/**
*
* @returns {Promise}
*/
async function cloneLayer() {
const name = svgCanvas.getCurrentDrawing().getCurrentLayerName() + ' copy';
const newName = await $$c.prompt(uiStrings$1.notification.enterUniqueLayerName, name);
if (!newName) {
return;
}
if (svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */
$$c.alert(uiStrings$1.notification.dupeLayerName);
return;
}
svgCanvas.cloneLayer(newName);
updateContextPanel();
populateLayers();
}
/**
*
* @returns {void}
*/
function mergeLayer() {
if ($$c('#layerlist tr.layersel').index() === svgCanvas.getCurrentDrawing().getNumLayers() - 1) {
return;
}
svgCanvas.mergeLayer();
updateContextPanel();
populateLayers();
}
/**
* @param {Integer} pos
* @returns {void}
*/
function moveLayer(pos) {
const total = svgCanvas.getCurrentDrawing().getNumLayers();
let curIndex = $$c('#layerlist tr.layersel').index();
if (curIndex > 0 || curIndex < total - 1) {
curIndex += pos;
svgCanvas.setCurrentLayerPosition(total - curIndex - 1);
populateLayers();
}
}
$$c('#layer_delete').click(deleteLayer);
$$c('#layer_up').click(() => {
moveLayer(-1);
});
$$c('#layer_down').click(() => {
moveLayer(1);
});
$$c('#layer_rename').click(async function () {
// const curIndex = $('#layerlist tr.layersel').prevAll().length; // Currently unused
const oldName = $$c('#layerlist tr.layersel td.layername').text();
const newName = await $$c.prompt(uiStrings$1.notification.enterNewLayerName, '');
if (!newName) {
return;
}
if (oldName === newName || svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */
$$c.alert(uiStrings$1.notification.layerHasThatName);
return;
}
svgCanvas.renameCurrentLayer(newName);
populateLayers();
});
const SIDEPANEL_MAXWIDTH = 300;
const SIDEPANEL_OPENWIDTH = 150;
let sidedrag = -1,
sidedragging = false,
allowmove = false;
/**
* @param {Float} delta
* @fires module:svgcanvas.SvgCanvas#event:ext_workareaResized
* @returns {void}
*/
const changeSidePanelWidth = function (delta) {
const rulerX = $$c('#ruler_x');
$$c('#sidepanels').width('+=' + delta);
$$c('#layerpanel').width('+=' + delta);
rulerX.css('right', Number.parseInt(rulerX.css('right')) + delta);
workarea.css('right', Number.parseInt(workarea.css('right')) + delta);
svgCanvas.runExtensions('workareaResized');
};
/**
* @param {Event} evt
* @returns {void}
*/
const resizeSidePanel = function (evt) {
if (!allowmove) {
return;
}
if (sidedrag === -1) {
return;
}
sidedragging = true;
let deltaX = sidedrag - evt.pageX;
const sideWidth = $$c('#sidepanels').width();
if (sideWidth + deltaX > SIDEPANEL_MAXWIDTH) {
deltaX = SIDEPANEL_MAXWIDTH - sideWidth; // sideWidth = SIDEPANEL_MAXWIDTH;
} else if (sideWidth + deltaX < 2) {
deltaX = 2 - sideWidth; // sideWidth = 2;
}
if (deltaX === 0) {
return;
}
sidedrag -= deltaX;
changeSidePanelWidth(deltaX);
};
/**
* If width is non-zero, then fully close it; otherwise fully open it.
* @param {boolean} close Forces the side panel closed
* @returns {void}
*/
const toggleSidePanel = function (close) {
const dpr = window.devicePixelRatio || 1;
const w = $$c('#sidepanels').width();
const isOpened = (dpr < 1 ? w : w / dpr) > 2;
const zoomAdjustedSidepanelWidth = (dpr < 1 ? 1 : dpr) * SIDEPANEL_OPENWIDTH;
const deltaX = (isOpened || close ? 0 : zoomAdjustedSidepanelWidth) - w;
changeSidePanelWidth(deltaX);
};
$$c('#sidepanel_handle').mousedown(function (evt) {
sidedrag = evt.pageX;
$$c(window).mousemove(resizeSidePanel);
allowmove = false; // Silly hack for Chrome, which always runs mousemove right after mousedown
setTimeout(function () {
allowmove = true;
}, 20);
}).mouseup(function (evt) {
if (!sidedragging) {
toggleSidePanel();
}
sidedrag = -1;
sidedragging = false;
});
$$c(window).mouseup(function () {
sidedrag = -1;
sidedragging = false;
$$c('#svg_editor').unbind('mousemove', resizeSidePanel);
});
populateLayers(); // function changeResolution (x,y) {
// const {zoom} = svgCanvas.getResolution();
// setResolution(x * zoom, y * zoom);
// }
const centerCanvas = () => {
// this centers the canvas vertically in the workarea (horizontal handled in CSS)
workarea.css('line-height', workarea.height() + 'px');
};
$$c(window).bind('load resize', centerCanvas);
/**
* @type {module:jQuerySpinButton.StepCallback}
*/
function stepFontSize(elem, step) {
const origVal = Number(elem.value);
const sugVal = origVal + step;
const increasing = sugVal >= origVal;
if (step === 0) {
return origVal;
}
if (origVal >= 24) {
if (increasing) {
return Math.round(origVal * 1.1);
}
return Math.round(origVal / 1.1);
}
if (origVal <= 1) {
if (increasing) {
return origVal * 2;
}
return origVal / 2;
}
return sugVal;
}
/**
* @type {module:jQuerySpinButton.StepCallback}
*/
function stepZoom(elem, step) {
const origVal = Number(elem.value);
if (origVal === 0) {
return 100;
}
const sugVal = origVal + step;
if (step === 0) {
return origVal;
}
if (origVal >= 100) {
return sugVal;
}
if (sugVal >= origVal) {
return origVal * 2;
}
return origVal / 2;
} // function setResolution (w, h, center) {
// updateCanvas();
// // w -= 0; h -= 0;
// // $('#svgcanvas').css({width: w, height: h});
// // $('#canvas_width').val(w);
// // $('#canvas_height').val(h);
// //
// // if (center) {
// // const wArea = workarea;
// // const scrollY = h/2 - wArea.height()/2;
// // const scrollX = w/2 - wArea.width()/2;
// // wArea[0].scrollTop = scrollY;
// // wArea[0].scrollLeft = scrollX;
// // }
// }
$$c('#resolution').change(function () {
const wh = $$c('#canvas_width,#canvas_height');
if (!this.selectedIndex) {
if ($$c('#canvas_width').val() === 'fit') {
wh.removeAttr('disabled').val(100);
}
} else if (this.value === 'content') {
wh.val('fit').attr('disabled', 'disabled');
} else {
const dims = this.value.split('x');
$$c('#canvas_width').val(dims[0]);
$$c('#canvas_height').val(dims[1]);
wh.removeAttr('disabled');
}
}); // Prevent browser from erroneously repopulating fields
$$c('input,select').attr('autocomplete', 'off');
const dialogSelectors = ['#tool_source_cancel', '#tool_docprops_cancel', '#tool_prefs_cancel', '.overlay'];
/* eslint-disable jsdoc/require-property */
/**
* Associate all button actions as well as non-button keyboard shortcuts.
* @namespace {PlainObject} module:SVGEditor~Actions
*/
const Actions = function () {
/* eslint-enable jsdoc/require-property */
/**
* @typedef {PlainObject} module:SVGEditor.ToolButton
* @property {string} sel The CSS selector for the tool
* @property {external:jQuery.Function} fn A handler to be attached to the `evt`
* @property {string} evt The event for which the `fn` listener will be added
* @property {module:SVGEditor.Key} [key] [key, preventDefault, NoDisableInInput]
* @property {string} [parent] Selector
* @property {boolean} [hidekey] Whether to show key value in title
* @property {string} [icon] The button ID
* @property {boolean} isDefault For flyout holders
*/
/**
*
* @name module:SVGEditor~ToolButtons
* @type {module:SVGEditor.ToolButton[]}
*/
const toolButtons = [{
sel: '#tool_select',
fn: clickSelect,
evt: 'click',
key: ['V', true]
}, {
sel: '#tool_fhpath',
fn: clickFHPath,
evt: 'click',
key: ['Q', true]
}, {
sel: '#tool_line',
fn: clickLine,
evt: 'click',
key: ['L', true],
parent: '#tools_line',
prepend: true
}, {
sel: '#tool_rect',
fn: clickRect,
evt: 'mouseup',
key: ['R', true],
parent: '#tools_rect',
icon: 'rect'
}, {
sel: '#tool_square',
fn: clickSquare,
evt: 'mouseup',
parent: '#tools_rect',
icon: 'square'
}, {
sel: '#tool_fhrect',
fn: clickFHRect,
evt: 'mouseup',
parent: '#tools_rect',
icon: 'fh_rect'
}, {
sel: '#tool_ellipse',
fn: clickEllipse,
evt: 'mouseup',
key: ['E', true],
parent: '#tools_ellipse',
icon: 'ellipse'
}, {
sel: '#tool_circle',
fn: clickCircle,
evt: 'mouseup',
parent: '#tools_ellipse',
icon: 'circle'
}, {
sel: '#tool_fhellipse',
fn: clickFHEllipse,
evt: 'mouseup',
parent: '#tools_ellipse',
icon: 'fh_ellipse'
}, {
sel: '#tool_path',
fn: clickPath,
evt: 'click',
key: ['P', true]
}, {
sel: '#tool_text',
fn: clickText,
evt: 'click',
key: ['T', true]
}, {
sel: '#tool_image',
fn: clickImage,
evt: 'mouseup'
}, {
sel: '#tool_zoom',
fn: clickZoom,
evt: 'mouseup',
key: ['Z', true]
}, {
sel: '#tool_clear',
fn: clickClear,
evt: 'mouseup',
key: ['N', true]
}, {
sel: '#tool_save',
fn() {
if (editingsource) {
saveSourceEditor();
} else {
clickSave();
}
},
evt: 'mouseup',
key: ['S', true]
}, {
sel: '#tool_export',
fn: clickExport,
evt: 'mouseup'
}, {
sel: '#tool_open',
fn: clickOpen,
evt: 'mouseup',
key: ['O', true]
}, {
sel: '#tool_import',
fn: clickImport,
evt: 'mouseup'
}, {
sel: '#tool_source',
fn: showSourceEditor,
evt: 'click',
key: ['U', true]
}, {
sel: '#tool_wireframe',
fn: clickWireframe,
evt: 'click',
key: ['F', true]
}, {
key: ['esc', false, false],
fn() {
if (dialogSelectors.every(sel => {
return $$c(sel + ':hidden').length;
})) {
svgCanvas.clearSelection();
}
},
hidekey: true
}, {
sel: dialogSelectors.join(','),
fn: cancelOverlays,
evt: 'click',
key: ['esc', false, false],
hidekey: true
}, {
sel: '#tool_source_save',
fn: saveSourceEditor,
evt: 'click'
}, {
sel: '#tool_docprops_save',
fn: saveDocProperties,
evt: 'click'
}, {
sel: '#tool_docprops',
fn: showDocProperties,
evt: 'click'
}, {
sel: '#tool_prefs_save',
fn: savePreferences,
evt: 'click'
}, {
sel: '#tool_editor_prefs',
fn: showPreferences,
evt: 'click'
}, {
sel: '#tool_editor_homepage',
fn: openHomePage,
evt: 'click'
}, {
sel: '#tool_open',
fn() {
window.dispatchEvent(new CustomEvent('openImage'));
},
evt: 'click'
}, {
sel: '#tool_import',
fn() {
window.dispatchEvent(new CustomEvent('importImage'));
},
evt: 'click'
}, {
sel: '#tool_delete,#tool_delete_multi',
fn: deleteSelected,
evt: 'click',
key: ['del/backspace', true]
}, {
sel: '#tool_reorient',
fn: reorientPath,
evt: 'click'
}, {
sel: '#tool_node_link',
fn: linkControlPoints,
evt: 'click'
}, {
sel: '#tool_node_clone',
fn: clonePathNode,
evt: 'click'
}, {
sel: '#tool_node_delete',
fn: deletePathNode,
evt: 'click'
}, {
sel: '#tool_openclose_path',
fn: opencloseSubPath,
evt: 'click'
}, {
sel: '#tool_add_subpath',
fn: addSubPath,
evt: 'click'
}, {
sel: '#tool_move_top',
fn: moveToTopSelected,
evt: 'click',
key: 'ctrl+shift+]'
}, {
sel: '#tool_move_bottom',
fn: moveToBottomSelected,
evt: 'click',
key: 'ctrl+shift+['
}, {
sel: '#tool_topath',
fn: convertToPath,
evt: 'click'
}, {
sel: '#tool_make_link,#tool_make_link_multi',
fn: makeHyperlink,
evt: 'click'
}, {
sel: '#tool_undo',
fn: clickUndo,
evt: 'click'
}, {
sel: '#tool_redo',
fn: clickRedo,
evt: 'click'
}, {
sel: '#tool_clone,#tool_clone_multi',
fn: clickClone,
evt: 'click',
key: ['D', true]
}, {
sel: '#tool_group_elements',
fn: clickGroup,
evt: 'click',
key: ['G', true]
}, {
sel: '#tool_ungroup',
fn: clickGroup,
evt: 'click'
}, {
sel: '#tool_unlink_use',
fn: clickGroup,
evt: 'click'
}, {
sel: '[id^=tool_align]',
fn: clickAlign,
evt: 'click'
}, // these two lines are required to make Opera work properly with the flyout mechanism
// {sel: '#tools_rect_show', fn: clickRect, evt: 'click'},
// {sel: '#tools_ellipse_show', fn: clickEllipse, evt: 'click'},
{
sel: '#tool_bold',
fn: clickBold,
evt: 'mousedown'
}, {
sel: '#tool_italic',
fn: clickItalic,
evt: 'mousedown'
}, {
sel: '#sidepanel_handle',
fn: toggleSidePanel,
key: ['X']
}, {
sel: '#copy_save_done',
fn: cancelOverlays,
evt: 'click'
}, // Shortcuts not associated with buttons
{
key: 'ctrl+left',
fn() {
rotateSelected(0, 1);
}
}, {
key: 'ctrl+right',
fn() {
rotateSelected(1, 1);
}
}, {
key: 'ctrl+shift+left',
fn() {
rotateSelected(0, 5);
}
}, {
key: 'ctrl+shift+right',
fn() {
rotateSelected(1, 5);
}
}, {
key: 'shift+O',
fn: selectPrev
}, {
key: 'shift+P',
fn: selectNext
}, {
key: [modKey + 'up', true],
fn() {
zoomImage(2);
}
}, {
key: [modKey + 'down', true],
fn() {
zoomImage(0.5);
}
}, {
key: [modKey + ']', true],
fn() {
moveUpDownSelected('Up');
}
}, {
key: [modKey + '[', true],
fn() {
moveUpDownSelected('Down');
}
}, {
key: ['up', true],
fn() {
moveSelected(0, -1);
}
}, {
key: ['down', true],
fn() {
moveSelected(0, 1);
}
}, {
key: ['left', true],
fn() {
moveSelected(-1, 0);
}
}, {
key: ['right', true],
fn() {
moveSelected(1, 0);
}
}, {
key: 'shift+up',
fn() {
moveSelected(0, -10);
}
}, {
key: 'shift+down',
fn() {
moveSelected(0, 10);
}
}, {
key: 'shift+left',
fn() {
moveSelected(-10, 0);
}
}, {
key: 'shift+right',
fn() {
moveSelected(10, 0);
}
}, {
key: ['alt+up', true],
fn() {
svgCanvas.cloneSelectedElements(0, -1);
}
}, {
key: ['alt+down', true],
fn() {
svgCanvas.cloneSelectedElements(0, 1);
}
}, {
key: ['alt+left', true],
fn() {
svgCanvas.cloneSelectedElements(-1, 0);
}
}, {
key: ['alt+right', true],
fn() {
svgCanvas.cloneSelectedElements(1, 0);
}
}, {
key: ['alt+shift+up', true],
fn() {
svgCanvas.cloneSelectedElements(0, -10);
}
}, {
key: ['alt+shift+down', true],
fn() {
svgCanvas.cloneSelectedElements(0, 10);
}
}, {
key: ['alt+shift+left', true],
fn() {
svgCanvas.cloneSelectedElements(-10, 0);
}
}, {
key: ['alt+shift+right', true],
fn() {
svgCanvas.cloneSelectedElements(10, 0);
}
}, {
key: 'a',
fn() {
svgCanvas.selectAllInCurrentLayer();
}
}, {
key: modKey + 'a',
fn() {
svgCanvas.selectAllInCurrentLayer();
}
}, // Standard shortcuts
{
key: modKey + 'z',
fn: clickUndo
}, {
key: modKey + 'shift+z',
fn: clickRedo
}, {
key: modKey + 'y',
fn: clickRedo
}, {
key: modKey + 'x',
fn: cutSelected
}, {
key: modKey + 'c',
fn: copySelected
}, {
key: modKey + 'v',
fn: pasteInCenter
}]; // Tooltips not directly associated with a single function
const keyAssocs = {
'4/Shift+4': '#tools_rect_show',
'5/Shift+5': '#tools_ellipse_show'
};
return {
/** @lends module:SVGEditor~Actions */
/**
* @returns {void}
*/
setAll() {
const flyouts = {};
const keyHandler = {}; // will contain the action for each pressed key
toolButtons.forEach(opts => {
// Bind function to button
let btn;
if (opts.sel) {
btn = $q$1(opts.sel);
if (btn === null) {
return true;
} // Skip if markup does not exist
if (opts.evt) {
// `touch.js` changes `touchstart` to `mousedown`,
// so we must map tool button click events as well
if (isTouch() && opts.evt === 'click') {
opts.evt = 'mousedown';
}
btn.addEventListener(opts.evt, opts.fn);
} // Add to parent flyout menu, if able to be displayed
if (opts.parent && $$c(opts.parent + '_show').length) {
let fH = $$c(opts.parent);
if (!fH.length) {
fH = makeFlyoutHolder(opts.parent.substr(1));
}
if (opts.prepend) {
btn.style.margin = 'initial';
}
fH[opts.prepend ? 'prepend' : 'append'](btn);
if (!Array.isArray(flyouts[opts.parent])) {
flyouts[opts.parent] = [];
}
flyouts[opts.parent].push(opts);
}
} // Bind function to shortcut key
if (opts.key) {
// Set shortcut based on options
let keyval = opts.key;
let pd = false;
if (Array.isArray(opts.key)) {
keyval = opts.key[0];
if (opts.key.length > 1) {
pd = opts.key[1];
}
}
keyval = String(keyval);
const {
fn
} = opts;
keyval.split('/').forEach(key => {
keyHandler[key] = {
fn,
pd
};
}); // Put shortcut in title
if (opts.sel && !opts.hidekey && btn.title) {
const newTitle = `${btn.title.split('[')[0]} (${keyval})`;
keyAssocs[keyval] = opts.sel; // Disregard for menu items
if (btn.closest('#main_menu') === null) {
btn.title = newTitle;
}
}
}
return true;
}); // register the keydown event
document.addEventListener('keydown', e => {
// only track keyboard shortcuts for the body containing the SVG-Editor
if (e.target.nodeName !== 'BODY') return; // normalize key
const key = `${e.metaKey ? 'meta+' : ''}${e.ctrlKey ? 'ctrl+' : ''}${e.key.toLowerCase()}`; // return if no shortcut defined for this key
if (!keyHandler[key]) return; // launch associated handler and preventDefault if necessary
keyHandler[key].fn();
if (keyHandler[key].pd) {
e.preventDefault();
}
}); // Setup flyouts
setupFlyouts(flyouts); // Misc additional actions
// Make 'return' keypress trigger the change event
$$c('.attr_changer, #image_url').bind('keydown', 'return', function (evt) {
$$c(this).change();
evt.preventDefault();
});
$$c(window).bind('keydown', 'tab', function (e) {
if (uiContext === 'canvas') {
e.preventDefault();
selectNext();
}
}).bind('keydown', 'shift+tab', function (e) {
if (uiContext === 'canvas') {
e.preventDefault();
selectPrev();
}
});
$$c('#tool_zoom').dblclick(dblclickZoom);
},
/**
* @returns {void}
*/
setTitles() {
$$c.each(keyAssocs, function (keyval, sel) {
const menu = $$c(sel).parents('#main_menu').length;
$$c(sel).each(function () {
let t;
if (menu) {
t = $$c(this).text().split(' [')[0];
} else {
t = this.title.split(' [')[0];
}
let keyStr = ''; // Shift+Up
$$c.each(keyval.split('/'), function (i, key) {
const modBits = key.split('+');
let mod = '';
if (modBits.length > 1) {
mod = modBits[0] + '+';
key = modBits[1];
}
keyStr += (i ? '/' : '') + mod + (uiStrings$1['key_' + key] || key);
});
if (menu) {
this.lastChild.textContent = t + ' [' + keyStr + ']';
} else {
this.title = t + ' [' + keyStr + ']';
}
});
});
},
/**
* @param {string} sel Selector to match
* @returns {module:SVGEditor.ToolButton}
*/
getButtonData(sel) {
return Object.values(toolButtons).find(btn => {
return btn.sel === sel;
});
}
};
}(); // Select given tool
editor.ready(function () {
let tool;
const itool = curConfig.initTool,
container = $$c('#tools_left, #svg_editor .tools_flyout'),
preTool = container.find('#tool_' + itool),
regTool = container.find('#' + itool);
if (preTool.length) {
tool = preTool;
} else if (regTool.length) {
tool = regTool;
} else {
tool = $$c('#tool_select');
}
tool.click().mouseup();
if (curConfig.wireframe) {
$$c('#tool_wireframe').click();
}
if (curConfig.showlayers) {
toggleSidePanel();
}
$$c('#rulers').toggle(Boolean(curConfig.showRulers));
if (curConfig.showRulers) {
$$c('#show_rulers')[0].checked = true;
}
if (curConfig.baseUnit) {
$$c('#base_unit').val(curConfig.baseUnit);
}
if (curConfig.gridSnapping) {
$$c('#grid_snapping_on')[0].checked = true;
}
if (curConfig.snappingStep) {
$$c('#grid_snapping_step').val(curConfig.snappingStep);
}
if (curConfig.gridColor) {
$$c('#grid_color').val(curConfig.gridColor);
}
}); // init SpinButtons
$$c('#rect_rx').SpinButton({
min: 0,
max: 1000,
stateObj,
callback: changeRectRadius
});
$$c('#stroke_width').SpinButton({
min: 0,
max: 99,
smallStep: 0.1,
stateObj,
callback: changeStrokeWidth
});
$$c('#angle').SpinButton({
min: -180,
max: 180,
step: 5,
stateObj,
callback: changeRotationAngle
});
$$c('#font_size').SpinButton({
min: 0.001,
stepfunc: stepFontSize,
stateObj,
callback: changeFontSize
});
$$c('#group_opacity').SpinButton({
min: 0,
max: 100,
step: 5,
stateObj,
callback: changeOpacity
});
$$c('#blur').SpinButton({
min: 0,
max: 10,
step: 0.1,
stateObj,
callback: changeBlur
});
$$c('#zoom').SpinButton({
min: 0.001,
max: 10000,
step: 50,
stepfunc: stepZoom,
stateObj,
callback: changeZoom // Set default zoom
}).val(svgCanvas.getZoom() * 100);
$$c('#workarea').contextMenu({
menu: 'cmenu_canvas',
inSpeed: 0
}, function (action, el, pos) {
switch (action) {
case 'delete':
deleteSelected();
break;
case 'cut':
cutSelected();
break;
case 'copy':
copySelected();
break;
case 'paste':
svgCanvas.pasteElements();
break;
case 'paste_in_place':
svgCanvas.pasteElements('in_place');
break;
case 'group':
case 'group_elements':
svgCanvas.groupSelectedElements();
break;
case 'ungroup':
svgCanvas.ungroupSelectedElement();
break;
case 'move_front':
moveToTopSelected();
break;
case 'move_up':
moveUpDownSelected('Up');
break;
case 'move_down':
moveUpDownSelected('Down');
break;
case 'move_back':
moveToBottomSelected();
break;
default:
if (hasCustomHandler(action)) {
getCustomHandler(action).call();
}
break;
}
});
/**
* Implements {@see module:jQueryContextMenu.jQueryContextMenuListener}.
* @param {"dupe"|"delete"|"merge_down"|"merge_all"} action
* @param {external:jQuery} el
* @param {{x: Float, y: Float, docX: Float, docY: Float}} pos
* @returns {void}
*/
const lmenuFunc = function (action, el, pos) {
switch (action) {
case 'dupe':
/* await */
cloneLayer();
break;
case 'delete':
deleteLayer();
break;
case 'merge_down':
mergeLayer();
break;
case 'merge_all':
svgCanvas.mergeAllLayers();
updateContextPanel();
populateLayers();
break;
}
};
$$c('#layerlist').contextMenu({
menu: 'cmenu_layers',
inSpeed: 0
}, lmenuFunc);
$$c('#layer_moreopts').contextMenu({
menu: 'cmenu_layers',
inSpeed: 0,
allowLeft: true
}, lmenuFunc);
$$c('.contextMenu li').mousedown(function (ev) {
ev.preventDefault();
});
$$c('#cmenu_canvas li').disableContextMenu();
canvMenu.enableContextMenuItems('#delete,#cut,#copy');
/**
* @returns {void}
*/
function enableOrDisableClipboard() {
let svgeditClipboard;
try {
svgeditClipboard = localStorage.getItem('svgedit_clipboard');
} catch (err) {}
canvMenu[(svgeditClipboard ? 'en' : 'dis') + 'ableContextMenuItems']('#paste,#paste_in_place');
}
enableOrDisableClipboard();
window.addEventListener('storage', function (e) {
if (e.key !== 'svgedit_clipboard') {
return;
}
enableOrDisableClipboard();
});
window.addEventListener('beforeunload', function (e) {
// Suppress warning if page is empty
if (undoMgr.getUndoStackSize() === 0) {
editor.showSaveWarning = false;
} // showSaveWarning is set to 'false' when the page is saved.
if (!curConfig.no_save_warning && editor.showSaveWarning) {
// Browser already asks question about closing the page
e.returnValue = uiStrings$1.notification.unsavedChanges; // Firefox needs this when beforeunload set by addEventListener (even though message is not used)
return uiStrings$1.notification.unsavedChanges;
}
return true;
});
/**
* Expose the `uiStrings`.
* @function module:SVGEditor.canvas.getUIStrings
* @returns {module:SVGEditor.uiStrings}
*/
editor.canvas.getUIStrings = function () {
return uiStrings$1;
};
/**
* @function module:SVGEditor.openPrep
* @returns {boolean|Promise} Resolves to boolean indicating `true` if there were no changes
* and `false` after the user confirms.
*/
editor.openPrep = function () {
$$c('#main_menu').hide();
if (undoMgr.getUndoStackSize() === 0) {
return true;
}
return $$c.confirm(uiStrings$1.notification.QwantToOpen);
};
/**
*
* @param {Event} e
* @returns {void}
*/
function onDragEnter(e) {
e.stopPropagation();
e.preventDefault(); // and indicator should be displayed here, such as "drop files here"
}
/**
*
* @param {Event} e
* @returns {void}
*/
function onDragOver(e) {
e.stopPropagation();
e.preventDefault();
}
/**
*
* @param {Event} e
* @returns {void}
*/
function onDragLeave(e) {
e.stopPropagation();
e.preventDefault(); // hypothetical indicator should be removed here
} // 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) {
$$c.process_cancel(uiStrings$1.notification.loadingImage);
e.stopPropagation();
e.preventDefault();
$$c('#workarea').removeAttr('style');
$$c('#main_menu').hide();
const file = e.type === 'drop' ? e.dataTransfer.files[0] : this.files[0];
if (!file) {
$$c('#dialog_box').hide();
return;
}
/* if (file.type === 'application/pdf') { // Todo: Handle PDF imports
}
else */
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 = svgCanvas.importSvgString(ev.target.result, true);
svgCanvas.ungroupSelectedElement();
svgCanvas.ungroupSelectedElement();
svgCanvas.groupSelectedElements();
svgCanvas.alignSelectedElements('m', 'page');
svgCanvas.alignSelectedElements('c', 'page'); // highlight imported element, otherwise we get strange empty selectbox
svgCanvas.selectOnly([newElement]);
$$c('#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 = svgCanvas.addSVGElementFromJson({
element: 'image',
attr: {
x: 0,
y: 0,
width,
height,
id: svgCanvas.getNextId(),
style: 'pointer-events:inherit'
}
});
svgCanvas.setHref(newImage, result);
svgCanvas.selectOnly([newImage]);
svgCanvas.alignSelectedElements('m', 'page');
svgCanvas.alignSelectedElements('c', 'page');
updateContextPanel();
$$c('#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', function () {
imgWidth = img.offsetWidth || img.naturalWidth || img.width;
imgHeight = img.offsetHeight || img.naturalHeight || img.height;
insertNewImage(imgWidth, imgHeight);
});
img.src = result;
};
reader.readAsDataURL(file);
}
};
workarea[0].addEventListener('dragenter', onDragEnter);
workarea[0].addEventListener('dragover', onDragOver);
workarea[0].addEventListener('dragleave', onDragLeave);
workarea[0].addEventListener('drop', importImage);
const open = $$c('').change(async function (e) {
const ok = await editor.openPrep();
if (!ok) {
return;
}
svgCanvas.clear();
if (this.files.length === 1) {
$$c.process_cancel(uiStrings$1.notification.loadingImage);
const reader = new FileReader();
reader.onloadend = async function ({
target
}) {
await loadSvgString(target.result);
updateCanvas();
};
reader.readAsText(this.files[0]);
}
});
$$c('#tool_open').show();
$$c(window).on('openImage', () => open.click());
const imgImport = $$c('').change(importImage);
$$c('#tool_import').show();
$$c(window).on('importImage', () => imgImport.click());
}
updateCanvas(true); // const revnums = 'svg-editor.js ($Rev$) ';
// revnums += svgCanvas.getVersion();
// $('#copyright')[0].setAttribute('title', revnums);
const loadedExtensionNames = [];
/**
* @function module:SVGEditor.setLang
* @param {string} lang The language code
* @param {module:locale.LocaleStrings} allStrings See {@tutorial LocaleDocs}
* @fires module:svgcanvas.SvgCanvas#event:ext_langReady
* @fires module:svgcanvas.SvgCanvas#event:ext_langChanged
* @returns {Promise} A Promise which resolves to `undefined`
*/
const setLang = editor.setLang = async function (lang, allStrings) {
editor.langChanged = true;
editor.pref('lang', lang);
$$c('#lang_select').val(lang);
if (!allStrings) {
return;
} // Todo: Remove `allStrings.lang` property in locale in
// favor of just `lang`?
document.documentElement.lang = allStrings.lang; // lang;
// Todo: Add proper RTL Support!
// Todo: Use RTL detection instead and take out of locales?
// document.documentElement.dir = allStrings.dir;
$$c.extend(uiStrings$1, allStrings); // const notif = allStrings.notification; // Currently unused
// $.extend will only replace the given strings
const oldLayerName = $$c('#layerlist tr.layersel td.layername').text();
const renameLayer = oldLayerName === uiStrings$1.common.layer + ' 1';
svgCanvas.setUiStrings(allStrings);
Actions.setTitles();
if (renameLayer) {
svgCanvas.renameCurrentLayer(uiStrings$1.common.layer + ' 1');
populateLayers();
} // In case extensions loaded before the locale, now we execute a callback on them
if (extsPreLang.length) {
await Promise.all(extsPreLang.map(ext => {
loadedExtensionNames.push(ext.name);
return ext.langReady({
lang,
uiStrings: uiStrings$1,
importLocale: getImportLocale({
defaultLang: lang,
defaultName: ext.name
})
});
}));
extsPreLang.length = 0;
} else {
loadedExtensionNames.forEach(loadedExtensionName => {
svgCanvas.runExtension(loadedExtensionName, 'langReady',
/** @type {module:svgcanvas.SvgCanvas#event:ext_langReady} */
{
lang,
uiStrings: uiStrings$1,
importLocale: getImportLocale({
defaultLang: lang,
defaultName: loadedExtensionName
})
});
});
}
svgCanvas.runExtensions('langChanged',
/** @type {module:svgcanvas.SvgCanvas#event:ext_langChanged} */
lang); // Update flyout tooltips
setFlyoutTitles(); // Copy title for certain tool elements
const elems = {
'#stroke_color': '#tool_stroke .icon_label, #tool_stroke .color_block',
'#fill_color': '#tool_fill label, #tool_fill .color_block',
'#linejoin_miter': '#cur_linejoin',
'#linecap_butt': '#cur_linecap'
};
$$c.each(elems, function (source, dest) {
$$c(dest).attr('title', $$c(source)[0].title);
}); // Copy alignment titles
$$c('#multiselected_panel div[id^=tool_align]').each(function () {
$$c('#tool_pos' + this.id.substr(10))[0].title = this.title;
});
};
init$7(
/**
* @implements {module:locale.LocaleEditorInit}
*/
{
/**
* Gets an array of results from extensions with a `addLangData` method,
* returning an object with a `data` property set to its locales (to be
* merged with regular locales).
* @param {string} langParam
* @fires module:svgcanvas.SvgCanvas#event:ext_addLangData
* @todo Can we forego this in favor of `langReady` (or forego `langReady`)?
* @returns {module:locale.AddLangExtensionLocaleData[]}
*/
addLangData(langParam) {
return svgCanvas.runExtensions('addLangData',
/**
* @function
* @type {module:svgcanvas.ExtensionVarBuilder}
* @param {string} name
* @returns {module:svgcanvas.SvgCanvas#event:ext_addLangData}
*/
name => {
// We pass in a function as we don't know the extension name here when defining this `addLangData` method
return {
lang: langParam,
importLocale: getImportLocale({
defaultLang: langParam,
defaultName: name
})
};
}, true);
},
curConfig
}); // Load extensions
// Bit of a hack to run extensions in local Opera/IE9
if (document.location.protocol === 'file:') {
setTimeout(extAndLocaleFunc, 100);
} else {
// Returns a promise (if we wanted to fire 'extensions-loaded' event,
// potentially useful to hide interface as some extension locales
// are only available after this)
extAndLocaleFunc();
}
};
/**
* @callback module:SVGEditor.ReadyCallback
* @returns {Promise|void}
*/
/**
* Queues a callback to be invoked when the editor is ready (or
* to be invoked immediately if it is already ready--i.e.,
* if `runCallbacks` has been run).
* @function module:SVGEditor.ready
* @param {module:SVGEditor.ReadyCallback} cb Callback to be queued to invoke
* @returns {Promise} Resolves when all callbacks, including the supplied have resolved
*/
editor.ready = function (cb) {
// eslint-disable-line promise/prefer-await-to-callbacks
return new Promise((resolve, reject) => {
// eslint-disable-line promise/avoid-new
if (isReady) {
resolve(cb()); // eslint-disable-line node/callback-return, promise/prefer-await-to-callbacks
return;
}
callbacks.push([cb, resolve, reject]);
});
};
/**
* Invokes the callbacks previous set by `svgEditor.ready`
* @function module:SVGEditor.runCallbacks
* @returns {Promise} Resolves to `undefined` if all callbacks succeeded and rejects otherwise
*/
editor.runCallbacks = async function () {
try {
await Promise.all(callbacks.map(([cb]) => {
return cb(); // eslint-disable-line promise/prefer-await-to-callbacks
}));
} catch (err) {
callbacks.forEach(([,, reject]) => {
reject();
});
throw err;
}
callbacks.forEach(([, resolve]) => {
resolve();
});
isReady = true;
};
/**
* @function module:SVGEditor.loadFromString
* @param {string} str The SVG string to load
* @param {PlainObject} [opts={}]
* @param {boolean} [opts.noAlert=false] Option to avoid alert to user and instead get rejected promise
* @returns {Promise}
*/
editor.loadFromString = function (str, {
noAlert
} = {}) {
return editor.ready(async function () {
try {
await loadSvgString(str, {
noAlert
});
} catch (err) {
if (noAlert) {
throw err;
}
}
});
};
/**
* Not presently in use.
* @function module:SVGEditor.disableUI
* @param {PlainObject} featList
* @returns {void}
*/
editor.disableUI = function (featList) {// $(function () {
// $('#tool_wireframe, #tool_image, #main_button, #tool_source, #sidepanels').remove();
// $('#tools_top').css('left', 5);
// });
};
/**
* @callback module:SVGEditor.URLLoadCallback
* @param {boolean} success
* @returns {void}
*/
/**
* @function module:SVGEditor.loadFromURL
* @param {string} url URL from which to load an SVG string via Ajax
* @param {PlainObject} [opts={}] May contain properties: `cache`, `callback`
* @param {boolean} [opts.cache]
* @param {boolean} [opts.noAlert]
* @returns {Promise} Resolves to `undefined` or rejects upon bad loading of
* the SVG (or upon failure to parse the loaded string) when `noAlert` is
* enabled
*/
editor.loadFromURL = function (url, {
cache,
noAlert
} = {}) {
return editor.ready(function () {
return new Promise((resolve, reject) => {
// eslint-disable-line promise/avoid-new
$$c.ajax({
url,
dataType: 'text',
cache: Boolean(cache),
beforeSend() {
$$c.process_cancel(uiStrings$1.notification.loadingImage);
},
success(str) {
resolve(loadSvgString(str, {
noAlert
}));
},
error(xhr, stat, err) {
if (xhr.status !== 404 && xhr.responseText) {
resolve(loadSvgString(xhr.responseText, {
noAlert
}));
return;
}
if (noAlert) {
reject(new Error('URLLoadFail'));
return;
}
$$c.alert(uiStrings$1.notification.URLLoadFail + ': \n' + err);
resolve();
},
complete() {
$$c('#dialog_box').hide();
}
});
});
});
};
/**
* @function module:SVGEditor.loadFromDataURI
* @param {string} str The Data URI to base64-decode (if relevant) and load
* @param {PlainObject} [opts={}]
* @param {boolean} [opts.noAlert]
* @returns {Promise} Resolves to `undefined` and rejects if loading SVG string fails and `noAlert` is enabled
*/
editor.loadFromDataURI = function (str, {
noAlert
} = {}) {
return editor.ready(function () {
let base64 = false;
let pre = str.match(/^data:image\/svg\+xml;base64,/);
if (pre) {
base64 = true;
} else {
pre = str.match(/^data:image\/svg\+xml(?:;|;utf8)?,/);
}
if (pre) {
pre = pre[0];
}
const src = str.slice(pre.length);
return loadSvgString(base64 ? decode64(src) : decodeURIComponent(src), {
noAlert
});
});
};
/**
* @function module:SVGEditor.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.ExtensionInitArgs} initArgs
* @throws {Error} If called too early
* @returns {Promise} Resolves to `undefined`
*/
editor.addExtension = function (name, init, initArgs) {
// Note that we don't want this on editor.ready since some extensions
// may want to run before then (like server_opensave).
// $(function () {
if (!svgCanvas) {
throw new Error('Extension added too early');
}
return svgCanvas.addExtension.call(this, name, init, initArgs); // });
}; // Defer injection to wait out initial menu processing. This probably goes
// away once all context menu behavior is brought to context menu.
editor.ready(() => {
injectExtendedContextMenuItemsIntoDom();
});
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
svgCanvas.call('message', messageObj);
}
};
window.addEventListener('message', messageListener); // Run init once DOM is loaded
// jQuery(editor.init);
(async () => {
try {
// We wait a micro-task to let the svgEditor variable be defined for module checks
await Promise.resolve();
editor.init();
} catch (err) {
console.error(err); // eslint-disable-line no-console
}
})();
export default editor;