').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'),
/* eslint-disable unicorn/no-fn-reference-in-iterator */
preTool = container.find('#tool_' + itool),
regTool = container.find('#' + itool);
/* eslint-enable unicorn/no-fn-reference-in-iterator */
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
}
})();
// TO BUILD AN IIFE VERSION OF THIS FILE (AS CAN WORK ON OLDER BROWSERS),
editor.setConfig({
/**
To override the ability for URLs to set URL-based SVG content,
uncomment the following:
*/
// preventURLContentLoading: true,
/**
To override the ability for URLs to set other configuration (including
extension config), uncomment the following:
*/
// preventAllURLConfig: true,
/**
To override the ability for URLs to set their own extensions,
uncomment the following (note that if `setConfig()` is used in
extension code, it will still be additive to extensions,
however):
*/
// lockExtensions: true,
});
editor.setConfig({
/*
Provide default values here which differ from that of the editor but
which the URL can override
*/
}, {
allowInitialUserOverride: true
}); // EXTENSION CONFIG
editor.setConfig({
extensions: [// 'ext-overview_window.js', 'ext-markers.js', 'ext-connector.js',
// 'ext-eyedropper.js', 'ext-shapes.js', 'ext-imagelib.js',
// 'ext-grid.js', 'ext-polygon.js', 'ext-star.js', 'ext-panning.js',
// 'ext-storage.js'
],
// noDefaultExtensions can only be meaningfully used in
// `svgedit-config-iife.js` or in the URL
noDefaultExtensions: false
}); // STYLESHEET CONFIG
editor.setConfig({
stylesheets: ['@default', '../svgedit-custom.css']
}); // OTHER CONFIG
editor.setConfig({// canvasName: 'default',
// canvas_expansion: 3,
// initFill: {
// color: 'FF0000', // solid red
// opacity: 1
// },
// initStroke: {
// width: 5,
// color: '000000', // solid black
// opacity: 1
// },
// initOpacity: 1,
// colorPickerCSS: null,
// initTool: 'select',
// exportWindowType: 'new', // 'same'
// wireframe: false,
// showlayers: false,
// no_save_warning: false,
// PATH CONFIGURATION
// imgPath: 'images/',
// langPath: 'locale/',
// extPath: 'extensions/',
// jGraduatePath: 'jgraduate/images/',
/*
Uncomment the following to allow at least same domain (embedded) access,
including `file:///` access.
Setting as `['*']` would allow any domain to access but would be unsafe to
data privacy and integrity.
*/
// May be 'null' (as a string) when used as a `file:///` URL
// allowedOrigins: [location.origin || 'null'],
// DOCUMENT PROPERTIES
// dimensions: [640, 480],
// EDITOR OPTIONS
// gridSnapping: false,
// gridColor: '#000',
// baseUnit: 'px',
// snappingStep: 10,
// showRulers: true,
// EXTENSION-RELATED (GRID)
// showGrid: false, // Set by ext-grid.js
// EXTENSION-RELATED (STORAGE)
// Some interaction with `ext-storage.js`; prevent even the loading of
// previously saved local storage
// noStorageOnLoad: false,
// Some interaction with `ext-storage.js`; strongly discouraged from
// modification as it bypasses user privacy by preventing them from
// choosing whether to keep local storage or not
// forceStorage: false,
// Used by `ext-storage.js`; empty any prior storage if the user
// declines to store
// emptyStorageOnDecline: true,
}); // PREF CHANGES
/**
setConfig() can also be used to set preferences in addition to
configuration (see defaultPrefs in svg-editor.js for a list of
possible settings), but at least if you are using ext-storage.js
to store preferences, it will probably be better to let your
users control these.
As with configuration, one may use allowInitialUserOverride, but
in the case of preferences, any previously stored preferences
will also thereby be enabled to override this setting (and at a
higher priority than any URL preference setting overrides).
Failing to use allowInitialUserOverride will ensure preferences
are hard-coded here regardless of URL or prior user storage setting.
*/
editor.setConfig({// Set dynamically within locale.js if not previously set
// lang: '',
// Will default to 's' if the window height is smaller than the minimum
// height and 'm' otherwise
// iconsize: '',
/**
* When showing the preferences dialog, svg-editor.js currently relies
* on `curPrefs` instead of `svgEditor.pref`, so allowing an override for
* `bkgd_color` means that this value won't have priority over block
* auto-detection as far as determining which color shows initially
* in the preferences dialog (though it can be changed and saved).
*/
// bkgd_color: '#FFF',
// bkgd_url: '',
// img_save: 'embed',
// Only shows in UI as far as alert notices
// save_notice_done: false,
// export_notice_done: false
});
editor.setConfig({// Indicate pref settings here if you wish to allow user storage or URL
// settings to be able to override your default preferences (unless
// other config options have already explicitly prevented one or the
// other)
}, {
allowInitialUserOverride: true
});
}());