Merge pull request #71 from OptimistikSAS/issues/65
#65 restore the feature of start/end marker lines
@@ -42,6 +42,7 @@ export class SePromptDialog extends HTMLElement {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error('unkonw attr for:', name, 'newValue =', newValue);
|
console.error('unkonw attr for:', name, 'newValue =', newValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default {
|
|||||||
function setIcon (pos, id) {
|
function setIcon (pos, id) {
|
||||||
if (id.substr(0, 1) !== '\\') { id = '\\textmarker'; }
|
if (id.substr(0, 1) !== '\\') { id = '\\textmarker'; }
|
||||||
const ci = '#' + idPrefix + pos + '_' + id.substr(1);
|
const ci = '#' + idPrefix + pos + '_' + id.substr(1);
|
||||||
// svgEditor.setIcon('#cur_' + pos + '_marker_list', $(ci).children());
|
svgEditor.setIcon('#cur_' + pos + '_marker_list', $(ci).children());
|
||||||
$(ci).addClass('current').siblings().removeClass('current');
|
$(ci).addClass('current').siblings().removeClass('current');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ export default {
|
|||||||
buttons.push({
|
buttons.push({
|
||||||
id: idPrefix + pos + '_' + id,
|
id: idPrefix + pos + '_' + id,
|
||||||
svgicon: id,
|
svgicon: id,
|
||||||
icon: 'markers-' + id + '.png',
|
icon: id + '.svg',
|
||||||
title,
|
title,
|
||||||
type: 'context',
|
type: 'context',
|
||||||
events: {click: setArrowFromButton},
|
events: {click: setArrowFromButton},
|
||||||
@@ -565,7 +565,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
name: strings.name,
|
name: strings.name,
|
||||||
svgicons: 'markers-icons.xml',
|
svgicons: '',
|
||||||
callback () {
|
callback () {
|
||||||
$('#marker_panel').addClass('toolset').hide();
|
$('#marker_panel').addClass('toolset').hide();
|
||||||
},
|
},
|
||||||
@@ -595,7 +595,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
elementChanged (opts) {
|
elementChanged (opts) {
|
||||||
// console.log('elementChanged',opts);
|
|
||||||
const elem = opts.elems[0];
|
const elem = opts.elems[0];
|
||||||
if (elem && (
|
if (elem && (
|
||||||
elem.getAttribute('marker-start') ||
|
elem.getAttribute('marker-start') ||
|
||||||
|
|||||||
3
src/editor/images/box.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="#f9bc01" d="m-30,-30l0,60l60,0l0,-60z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 331 B |
3
src/editor/images/box_o.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m-30,-30l0,60l60,0l0,-60z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 328 B |
3
src/editor/images/forwardslash.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m-20,50l40,-100"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 318 B |
3
src/editor/images/leftarrow.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="#f9bc01" d="m-50,0l100,40l-30,-40l30,-40z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 335 B |
3
src/editor/images/leftarrow_o.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m-50,0l100,40l-30,-40l30,-40z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 332 B |
3
src/editor/images/mcircle.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<circle stroke-width="10" stroke="#f9bc01" fill="#f9bc01" cy="0" cx="0" r="30"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 324 B |
3
src/editor/images/mcircle_o.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<circle stroke-width="10" stroke="#f9bc01" fill="none" cy="0" cx="0" r="30"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 321 B |
3
src/editor/images/nomarker.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="#f9bc01" d="m-50,0l100,0"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 318 B |
3
src/editor/images/reverseslash.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m-20,-50l40,100"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 318 B |
3
src/editor/images/rightarrow.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="#f9bc01" d="m50,0l-100,40l30,-40l-30,-40z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 335 B |
3
src/editor/images/rightarrow_o.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m50,0l-100,40l30,-40l-30,-40z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 332 B |
3
src/editor/images/star_o.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m-40,-20l80,0l-70,60l30,-80l30,80z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 337 B |
3
src/editor/images/textmarker.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<text xml:space="preserve" text-anchor="middle" font-family="serif" font-size="120" y="40" x="0" stroke-width="0" stroke="#f9bc01" fill="#f9bc01">T</text>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 392 B |
3
src/editor/images/triangle.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="#f9bc01" d="M-30,30 L0,-30 L30,30 Z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 329 B |
3
src/editor/images/triangle_o.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="M-30,30 L0,-30 L30,30 Z"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 326 B |
3
src/editor/images/verticalslash.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="none" d="m0,-50l0,100"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 315 B |
3
src/editor/images/xmark.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" xmlns:xlink="http://www.w3.org/1999/xlink" class="svg_icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="-60 -60 120 120">
|
||||||
|
<path stroke-width="10" stroke="#f9bc01" fill="#f9bc01" d="m-30,30l60,-60m0,60l-60,-60"/>
|
||||||
|
</svg></svg>
|
||||||
|
After Width: | Height: | Size: 333 B |
@@ -352,7 +352,7 @@
|
|||||||
<se-spin-input size="3" id="opacity" min=0 max=100 step=5 title="Change selected item opacity" src="./images/opacity.svg"></se-spin-input>
|
<se-spin-input size="3" id="opacity" min=0 max=100 step=5 title="Change selected item opacity" src="./images/opacity.svg"></se-spin-input>
|
||||||
<se-palette id="palette"></se-palette>
|
<se-palette id="palette"></se-palette>
|
||||||
</div> <!-- tools_bottom -->
|
</div> <!-- tools_bottom -->
|
||||||
|
<div id="option_lists" class="dropdown"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -722,5 +722,8 @@ ul li.current {
|
|||||||
z-index: 20001;
|
z-index: 20001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------ */
|
||||||
|
|
||||||
|
.dropdown li.tool_button {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import './touch.js';
|
import './touch.js';
|
||||||
import {isChrome, isMac} from '../common/browser.js';
|
import {isChrome, isMac, isTouch} from '../common/browser.js';
|
||||||
import {convertUnit, isValidUnit} from '../common/units.js';
|
import {convertUnit, isValidUnit} from '../common/units.js';
|
||||||
|
|
||||||
import SvgCanvas from '../svgcanvas/svgcanvas.js';
|
import SvgCanvas from '../svgcanvas/svgcanvas.js';
|
||||||
@@ -77,6 +77,7 @@ class Editor extends EditorStartup {
|
|||||||
* @name module:SVGthis.uiStrings
|
* @name module:SVGthis.uiStrings
|
||||||
* @type {PlainObject}
|
* @type {PlainObject}
|
||||||
*/
|
*/
|
||||||
|
this.flyoutFuncs = {};
|
||||||
this.uiStrings = {};
|
this.uiStrings = {};
|
||||||
this.svgCanvas = null;
|
this.svgCanvas = null;
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
@@ -839,6 +840,271 @@ class Editor extends EditorStartup {
|
|||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure the current selected paint is available to work with.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
/* prepPaints () {
|
||||||
|
paintBox.fill.prep();
|
||||||
|
paintBox.stroke.prep();
|
||||||
|
} */
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
setFlyoutPositions () {
|
||||||
|
$('.tools_flyout').each(function () {
|
||||||
|
const shower = $('#' + this.id + '_show');
|
||||||
|
const {left, top} = shower.offset();
|
||||||
|
const w = shower.outerWidth();
|
||||||
|
this.css({left: (left + w) * editor.tool_scale, top});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
setFlyoutTitles () {
|
||||||
|
$('.tools_flyout').each(function () {
|
||||||
|
const shower = $('#' + this.id + '_show');
|
||||||
|
if (shower.data('isLibrary')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tooltips = this.children().map(function () {
|
||||||
|
return this.title;
|
||||||
|
}).get();
|
||||||
|
shower[0].title = tooltips.join(' / ');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PlainObject<string, module:SVGEditor.ToolButton>} holders Key is a selector
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
setupFlyouts (holders) {
|
||||||
|
const allHolders = {};
|
||||||
|
$.each(holders, function (holdSel, btnOpts) {
|
||||||
|
if (!allHolders[holdSel]) {
|
||||||
|
allHolders[holdSel] = [];
|
||||||
|
}
|
||||||
|
allHolders[holdSel].push(...btnOpts);
|
||||||
|
|
||||||
|
const buttons = $(holdSel).children().not('.tool_button_evt_handled');
|
||||||
|
const showSel = holdSel + '_show';
|
||||||
|
const shower = $(showSel);
|
||||||
|
let def = false;
|
||||||
|
buttons.addClass('tool_button tool_button_evt_handled')
|
||||||
|
.unbind('click mousedown mouseup') // may not be necessary
|
||||||
|
.each(function () {
|
||||||
|
// Get this button's options
|
||||||
|
const idSel = '#' + this.getAttribute('id');
|
||||||
|
const [i, opts] = Object.entries(btnOpts).find(([_, {sel}]) => {
|
||||||
|
return sel === idSel;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remember the function that goes with this ID
|
||||||
|
this.flyoutFuncs[opts.sel] = opts.fn;
|
||||||
|
|
||||||
|
if (opts.isDefault) { def = i; }
|
||||||
|
|
||||||
|
const flyoutAction = function (ev) {
|
||||||
|
let options = opts;
|
||||||
|
// Find the currently selected tool if comes from keystroke
|
||||||
|
if (ev.type === 'keydown') {
|
||||||
|
const flyoutIsSelected = $(options.parent + '_show').hasClass('tool_button_current');
|
||||||
|
const currentOperation = $(options.parent + '_show').attr('data-curopt');
|
||||||
|
Object.entries(holders[opts.parent]).some(([j, tool]) => {
|
||||||
|
if (tool.sel !== currentOperation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ev.shiftKey || !flyoutIsSelected) {
|
||||||
|
options = tool;
|
||||||
|
} else {
|
||||||
|
// If flyout is selected, allow shift key to iterate through subitems
|
||||||
|
j = Number.parseInt(j);
|
||||||
|
// Use `allHolders` to include both extension `includeWith` and toolbarButtons
|
||||||
|
options = allHolders[opts.parent][j + 1] ||
|
||||||
|
holders[opts.parent][0];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($(this).hasClass('disabled')) { return false; }
|
||||||
|
/* if (toolButtonClick(showSel)) {
|
||||||
|
options.fn();
|
||||||
|
} */
|
||||||
|
const icon = (options.icon) ? $.getSvgIcon(options.icon, true) : $(options.sel).children().eq(0).clone();
|
||||||
|
icon[0].setAttribute('width', shower.width());
|
||||||
|
icon[0].setAttribute('height', shower.height());
|
||||||
|
shower.children(':not(.flyout_arrow_horiz)').remove();
|
||||||
|
shower.append(icon).attr('data-curopt', options.sel); // This sets the current mode
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$(this).mouseup(flyoutAction);
|
||||||
|
|
||||||
|
if (opts.key) {
|
||||||
|
$(document).bind('keydown', opts.key[0] + ' shift+' + opts.key[0], flyoutAction);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (def) {
|
||||||
|
shower.attr('data-curopt', btnOpts[def].sel);
|
||||||
|
} else if (!shower.attr('data-curopt')) {
|
||||||
|
// Set first as default
|
||||||
|
shower.attr('data-curopt', btnOpts[0].sel);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer;
|
||||||
|
|
||||||
|
// Clicking the "show" icon should set the current mode
|
||||||
|
shower.mousedown(function (evt) {
|
||||||
|
if (shower.hasClass('disabled')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const holder = $(holdSel);
|
||||||
|
const pos = $(showSel).position();
|
||||||
|
const l = pos.left + 34;
|
||||||
|
const w = holder.width() * -1;
|
||||||
|
const time = holder.data('shown_popop') ? 200 : 0;
|
||||||
|
timer = setTimeout(function () {
|
||||||
|
// Show corresponding menu
|
||||||
|
if (!shower.data('isLibrary')) {
|
||||||
|
holder.css('left', w).show().animate({
|
||||||
|
left: l
|
||||||
|
}, 150);
|
||||||
|
} else {
|
||||||
|
holder.css('left', l).show();
|
||||||
|
}
|
||||||
|
holder.data('shown_popop', true);
|
||||||
|
}, time);
|
||||||
|
evt.preventDefault();
|
||||||
|
return true;
|
||||||
|
}).mouseup(function (evt) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
/* const opt = $(this).attr('data-curopt');
|
||||||
|
// Is library and popped up, so do nothing
|
||||||
|
if (shower.data('isLibrary') && $(showSel.replace('_show', '')).is(':visible')) {
|
||||||
|
toolButtonClick(showSel, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (toolButtonClick(showSel) && this.flyoutFuncs[opt]) {
|
||||||
|
this.flyoutFuncs[opt]();
|
||||||
|
} */
|
||||||
|
});
|
||||||
|
// $('#tools_rect').mouseleave(function () { $('#tools_rect').fadeOut(); });
|
||||||
|
});
|
||||||
|
this.setFlyoutTitles();
|
||||||
|
// this.setFlyoutPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @param {external:jQuery} child
|
||||||
|
* @returns {external:jQuery}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
makeFlyoutHolder (id, child) {
|
||||||
|
const div = $('<div>', {
|
||||||
|
class: 'tools_flyout',
|
||||||
|
id
|
||||||
|
}).appendTo('#svg_editor').append(child);
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function module:SVGEditor.setIcon
|
||||||
|
* @param {string|Element|external:jQuery} elem
|
||||||
|
* @param {string|external:jQuery} iconId
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
setIcon (elem, iconId) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const icon = (typeof iconId === 'string') ? $('<img src="' + this.configObj.curConfig.imgPath + iconId + '">') : iconId.clone();
|
||||||
|
if (!icon) {
|
||||||
|
// Todo: Investigate why this still occurs in some cases
|
||||||
|
console.log('NOTE: Icon image missing: ' + iconId); // eslint-disable-line no-console
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(elem).empty().append(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} elemSel
|
||||||
|
* @param {string} listSel
|
||||||
|
* @param {external:jQuery.Function} callback
|
||||||
|
* @param {PlainObject} opts
|
||||||
|
* @param {boolean} opts.dropUp
|
||||||
|
* @param {boolean} opts.seticon
|
||||||
|
* @param {boolean} opts.multiclick
|
||||||
|
* @todo Combine this with `addDropDown` or find other way to optimize.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
addAltDropDown (elemSel, listSel, callback, opts) {
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
const self = this;
|
||||||
|
const button = $(elemSel);
|
||||||
|
const {dropUp} = opts;
|
||||||
|
const list = $(listSel);
|
||||||
|
if (dropUp) {
|
||||||
|
$(elemSel).addClass('dropup');
|
||||||
|
}
|
||||||
|
list.find('li').bind('mouseup', function (...args) {
|
||||||
|
if (opts.seticon) {
|
||||||
|
self.setIcon('#cur_' + button[0].id, $(this).children());
|
||||||
|
$(this).addClass('current').siblings().removeClass('current');
|
||||||
|
}
|
||||||
|
callback.apply(this, ...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
let onButton = false;
|
||||||
|
$(window).mouseup(function (evt) {
|
||||||
|
if (!onButton) {
|
||||||
|
button.removeClass('down');
|
||||||
|
list.hide();
|
||||||
|
list.css({top: 0, left: 0});
|
||||||
|
}
|
||||||
|
onButton = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// const height = list.height(); // Currently unused
|
||||||
|
button.bind('mousedown', function () {
|
||||||
|
const off = button.offset();
|
||||||
|
if (dropUp) {
|
||||||
|
off.top -= list.height();
|
||||||
|
off.left += 8;
|
||||||
|
} else {
|
||||||
|
off.top += button.height();
|
||||||
|
}
|
||||||
|
list.offset(off);
|
||||||
|
|
||||||
|
if (!button.hasClass('down')) {
|
||||||
|
list.show();
|
||||||
|
onButton = true;
|
||||||
|
} else {
|
||||||
|
// CSS position must be reset for Webkit
|
||||||
|
list.hide();
|
||||||
|
list.css({top: 0, left: 0});
|
||||||
|
}
|
||||||
|
button.toggleClass('down');
|
||||||
|
}).hover(function () {
|
||||||
|
onButton = true;
|
||||||
|
}).mouseout(function () {
|
||||||
|
onButton = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts.multiclick) {
|
||||||
|
list.mousedown(function () {
|
||||||
|
onButton = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {external:Window} win
|
* @param {external:Window} win
|
||||||
* @param {module:svgcanvas.SvgCanvas#event:extension_added} ext
|
* @param {module:svgcanvas.SvgCanvas#event:extension_added} ext
|
||||||
@@ -846,6 +1112,9 @@ class Editor extends EditorStartup {
|
|||||||
* @returns {Promise<void>|void} Resolves to `undefined`
|
* @returns {Promise<void>|void} Resolves to `undefined`
|
||||||
*/
|
*/
|
||||||
async extAdded (win, ext) {
|
async extAdded (win, ext) {
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
const self = this;
|
||||||
|
const btnSelects = [];
|
||||||
if (!ext) {
|
if (!ext) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -921,7 +1190,9 @@ class Editor extends EditorStartup {
|
|||||||
break;
|
break;
|
||||||
} case 'button-select': {
|
} case 'button-select': {
|
||||||
html = '<div id="' + tool.id + '" class="dropdown toolset" title="' + tool.title + '">' +
|
html = '<div id="' + tool.id + '" class="dropdown toolset" title="' + tool.title + '">' +
|
||||||
'<div id="cur_' + tool.id + '" class="icon_label"></div><button></button></div>';
|
'<div id="cur_' + tool.id + '" class="icon_label"></div>' +
|
||||||
|
'<button><img class="svg_icon" src="./images/arrow_down.svg" alt="icon" width="7" height="7"></button>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
const list = $('<ul id="' + tool.id + '_opts"></ul>').appendTo('#option_lists');
|
const list = $('<ul id="' + tool.id + '_opts"></ul>').appendTo('#option_lists');
|
||||||
|
|
||||||
@@ -931,6 +1202,13 @@ class Editor extends EditorStartup {
|
|||||||
|
|
||||||
// Creates the tool, hides & adds it, returns the select element
|
// Creates the tool, hides & adds it, returns the select element
|
||||||
/* const dropdown = */ $(html).appendTo(panel).children();
|
/* const dropdown = */ $(html).appendTo(panel).children();
|
||||||
|
btnSelects.push({
|
||||||
|
elem: ('#' + tool.id),
|
||||||
|
list: ('#' + tool.id + '_opts'),
|
||||||
|
title: tool.title,
|
||||||
|
callback: tool.events.change,
|
||||||
|
cur: ('#cur_' + tool.id)
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
} case 'input': {
|
} case 'input': {
|
||||||
html = '<label' + contId + '>' +
|
html = '<label' + contId + '>' +
|
||||||
@@ -956,7 +1234,274 @@ class Editor extends EditorStartup {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const {svgicons} = ext;
|
||||||
|
if (ext.buttons) {
|
||||||
|
const fallbackObj = {},
|
||||||
|
altsObj = {},
|
||||||
|
placementObj = {},
|
||||||
|
holders = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {GenericArray} module:SVGEditor.KeyArray
|
||||||
|
* @property {string} 0 The key to bind (on `keydown`)
|
||||||
|
* @property {boolean} 1 Whether to `preventDefault` on the `keydown` event
|
||||||
|
* @property {boolean} 2 Not apparently in use (NoDisableInInput)
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {string|module:SVGEditor.KeyArray} module:SVGEditor.Key
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {PlainObject} module:SVGEditor.Button
|
||||||
|
* @property {string} id A unique identifier for this button. If SVG icons are used, this must match the ID used in the icon file. Required.
|
||||||
|
* @property {"mode_flyout"|"mode"|"context"|"app_menu"} type Type of button. Required.
|
||||||
|
* @property {string} title The tooltip text that will appear when the user hovers over the icon. Required.
|
||||||
|
* @property {PlainObject<string, external:jQuery.Function>|PlainObject<"click", external:jQuery.Function>} events DOM event names with associated functions. Example: `{click () { alert('Button was clicked') } }`. Click is used with `includeWith` and `type` of "mode_flyout" (and "mode"); any events may be added if `list` is not present. Expected.
|
||||||
|
* @property {string} panel The ID of the context panel to be included, if type is "context". Required only if type is "context".
|
||||||
|
* @property {string} icon The file path to the raster version of the icon image source. Required only if no `svgicons` is supplied from [ExtensionInitResponse]{@link module:svgcanvas.ExtensionInitResponse}.
|
||||||
|
* @property {string} [svgicon] If absent, will utilize the button "id"; used to set "placement" on the `svgIcons` call
|
||||||
|
* @property {string} [list] Points to the "id" of a `context_tools` item of type "button-select" into which the button will be added as a panel list item
|
||||||
|
* @property {Integer} [position] The numeric index for placement; defaults to last position (as of the time of extension addition) if not present. For use with {@link http://api.jquery.com/eq/}.
|
||||||
|
* @property {boolean} [isDefault] Whether or not the button is the default. Used with `list`.
|
||||||
|
* @property {PlainObject} [includeWith] Object with flyout menu data
|
||||||
|
* @property {boolean} [includeWith.isDefault] Indicates whether button is default in flyout list or not.
|
||||||
|
* @property {string} includeWith.button jQuery selector of the existing button to be joined. Example: '#tool_line'. Required if `includeWith` is used.
|
||||||
|
* @property {"last"|Integer} [includeWith.position] Position of icon in flyout list; will be added to end if not indicated. Integer is for use with {@link http://api.jquery.com/eq/}.
|
||||||
|
* @property {module:SVGEditor.Key} [key] The key to bind to the button
|
||||||
|
*/
|
||||||
|
// Add buttons given by extension
|
||||||
|
$.each(ext.buttons, function (i, /** @type {module:SVGEditor.Button} */ btn) {
|
||||||
|
let {id} = btn;
|
||||||
|
let num = i;
|
||||||
|
// Give button a unique ID
|
||||||
|
while ($('#' + id).length) {
|
||||||
|
id = btn.id + '_' + (++num);
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon;
|
||||||
|
if (!svgicons) {
|
||||||
|
icon = $(
|
||||||
|
'<img src="' + self.configObj.curConfig.imgPath + btn.icon +
|
||||||
|
(btn.title ? '" alt="' + btn.title : '') +
|
||||||
|
'">'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fallbackObj[id] = btn.icon;
|
||||||
|
altsObj[id] = btn.title;
|
||||||
|
const svgicon = btn.svgicon || btn.id;
|
||||||
|
if (btn.type === 'app_menu') {
|
||||||
|
placementObj['#' + id + ' > div'] = svgicon;
|
||||||
|
} else {
|
||||||
|
placementObj['#' + id] = svgicon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cls, parent;
|
||||||
|
|
||||||
|
// Set button up according to its type
|
||||||
|
switch (btn.type) {
|
||||||
|
case 'mode_flyout':
|
||||||
|
case 'mode':
|
||||||
|
cls = 'tool_button';
|
||||||
|
parent = '#tools_left';
|
||||||
|
break;
|
||||||
|
case 'context':
|
||||||
|
cls = 'tool_button';
|
||||||
|
parent = '#' + btn.panel;
|
||||||
|
// create the panel if it doesn't exist
|
||||||
|
if (!$(parent).length) {
|
||||||
|
$('<div>', {id: btn.panel}).appendTo('#tools_top');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'app_menu':
|
||||||
|
cls = '';
|
||||||
|
parent = '#main_menu ul';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// refData
|
||||||
|
let flyoutHolder, showBtn, refBtn;
|
||||||
|
const button = $((btn.list || btn.type === 'app_menu') ? '<li/>' : '<div/>')
|
||||||
|
.attr('id', id)
|
||||||
|
.attr('title', btn.title)
|
||||||
|
.addClass(cls);
|
||||||
|
if (!btn.includeWith && !btn.list) {
|
||||||
|
if ('position' in btn) {
|
||||||
|
if ($(parent).children().eq(btn.position).length) {
|
||||||
|
$(parent).children().eq(btn.position).before(button);
|
||||||
|
} else {
|
||||||
|
$(parent).children().last().after(button);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
button.appendTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn.type === 'mode_flyout') {
|
||||||
|
// Add to flyout menu / make flyout menu
|
||||||
|
// const opts = btn.includeWith;
|
||||||
|
// // opts.button, default, position
|
||||||
|
refBtn = $(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($('<div>', {class: 'flyout_arrow_horiz'}));
|
||||||
|
|
||||||
|
refBtn.before(showBtn);
|
||||||
|
|
||||||
|
// Create a flyout div
|
||||||
|
flyoutHolder = self.makeFlyoutHolder(tlsId, refBtn);
|
||||||
|
flyoutHolder.data('isLibrary', true);
|
||||||
|
showBtn.data('isLibrary', true);
|
||||||
|
}
|
||||||
|
// refData = Actions.getButtonData(opts.button);
|
||||||
|
|
||||||
|
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
|
||||||
|
/* curH = */ holders['#' + flyoutHolder[0].id] = [{
|
||||||
|
sel: '#' + id,
|
||||||
|
fn: btn.events.click,
|
||||||
|
icon: btn.id,
|
||||||
|
// key: btn.key,
|
||||||
|
isDefault: true
|
||||||
|
}]; // , 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();
|
||||||
|
// }
|
||||||
|
} else if (btn.type === 'app_menu') {
|
||||||
|
button.append('<div>').append(btn.title);
|
||||||
|
}
|
||||||
|
} else if (btn.list) {
|
||||||
|
// Add button to list
|
||||||
|
button.addClass('push_button');
|
||||||
|
$('#' + btn.list + '_opts').append(button);
|
||||||
|
if (btn.isDefault) {
|
||||||
|
$('#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 = $(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($('<div>', {class: 'flyout_arrow_horiz'}));
|
||||||
|
|
||||||
|
refBtn.before(showBtn);
|
||||||
|
// Create a flyout div
|
||||||
|
flyoutHolder = self.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
|
||||||
|
$.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) {
|
||||||
|
$(document).bind('keydown', btn.key, func);
|
||||||
|
if (btn.title) {
|
||||||
|
button.attr('title', btn.title + ' [' + btn.key + ']');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
button.bind(name, func);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.setupFlyouts(holders);
|
||||||
|
});
|
||||||
|
$.each(btnSelects, function () {
|
||||||
|
self.addAltDropDown(this.elem, this.list, this.callback, {seticon: true});
|
||||||
|
});
|
||||||
|
/* if (svgicons) {
|
||||||
|
return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
|
||||||
|
$.svgIcons(`${this.configObj.curConfig.imgPath}${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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} */
|
||||||
|
}
|
||||||
if (ext.events) {
|
if (ext.events) {
|
||||||
this.leftPanelHandlers.add(ext.events.id, ext.events.click);
|
this.leftPanelHandlers.add(ext.events.id, ext.events.click);
|
||||||
}
|
}
|
||||||
|
|||||||