extract LayersPanel

master
JFH 2020-12-17 01:15:03 +01:00
parent 6a1ff560f8
commit 594b4c68d8
5 changed files with 480 additions and 458 deletions

238
src/editor/LayersPanel.js Normal file
View File

@ -0,0 +1,238 @@
/* globals $ */
/**
*
*/
class LayersPanel {
/**
* @param {PlainObject} svgCanvas svgCanvas
* @param {PlainObject} uiStrings uiStrings
* @param {GenericCallBack} updateContextPanel updateContextPanel
*/
constructor (svgCanvas, uiStrings, updateContextPanel) {
this.svgCanvas = svgCanvas;
this.uiStrings = uiStrings;
this.updateContextPanel = updateContextPanel;
}
/**
* @returns {void}
*/
addEvents () {
document.getElementById('layer_new').addEventListener('click', this.newLayer.bind(this));
document.getElementById('layer_delete').addEventListener('click', this.deleteLayer.bind(this));
document.getElementById('layer_up').addEventListener('click', () => this.moveLayer.bind(this)(-1));
document.getElementById('layer_down').addEventListener('click', () => this.moveLayer.bind(this)(1));
document.getElementById('layer_rename').addEventListener('click', this.layerRename.bind(this));
const lmenuFunc = (action, el, pos) => {
switch (action) {
case 'dupe':
/* await */ this.cloneLayer();
break;
case 'delete':
this.deleteLayer();
break;
case 'merge_down':
this.mergeLayer();
break;
case 'merge_all':
this.svgCanvas.mergeAllLayers();
this.updateContextPanel();
this.populateLayers();
break;
}
};
$('#layer_moreopts').contextMenu(
{
menu: 'cmenu_layers',
inSpeed: 0,
allowLeft: true
},
lmenuFunc
);
$('#layerlist').contextMenu(
{
menu: 'cmenu_layers',
inSpeed: 0
},
lmenuFunc
);
}
/**
* @returns {void}
*/
async newLayer () {
let uniqName;
let i = this.svgCanvas.getCurrentDrawing().getNumLayers();
do {
uniqName = this.uiStrings.layers.layer + ' ' + (++i);
} while (this.svgCanvas.getCurrentDrawing().hasLayer(uniqName));
const newName = await $.prompt(this.uiStrings.notification.enterUniqueLayerName, uniqName);
if (!newName) { return; }
if (this.svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */ $.alert(this.uiStrings.notification.dupeLayerName);
return;
}
this.svgCanvas.createLayer(newName);
this.updateContextPanel();
this.populateLayers();
}
/**
*
* @returns {void}
*/
deleteLayer () {
if (this.svgCanvas.deleteCurrentLayer()) {
this.updateContextPanel();
this.populateLayers();
// This matches what this.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)
$('#layerlist tr.layer').removeClass('layersel');
$('#layerlist tr.layer:first').addClass('layersel');
}
}
/**
*
* @returns {Promise<void>}
*/
async cloneLayer () {
const name = this.svgCanvas.getCurrentDrawing().getCurrentLayerName() + ' copy';
const newName = await $.prompt(this.uiStrings.notification.enterUniqueLayerName, name);
if (!newName) { return; }
if (this.svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */ $.alert(this.uiStrings.notification.dupeLayerName);
return;
}
this.svgCanvas.cloneLayer(newName);
this.updateContextPanel();
this.populateLayers();
}
/**
*
* @returns {void}
*/
mergeLayer () {
if ($('#layerlist tr.layersel').index() === this.svgCanvas.getCurrentDrawing().getNumLayers() - 1) {
return;
}
this.svgCanvas.mergeLayer();
this.updateContextPanel();
this.populateLayers();
}
/**
* @param {Integer} pos
* @returns {void}
*/
moveLayer (pos) {
const total = this.svgCanvas.getCurrentDrawing().getNumLayers();
let curIndex = $('#layerlist tr.layersel').index();
if (curIndex > 0 || curIndex < total - 1) {
curIndex += pos;
this.svgCanvas.setCurrentLayerPosition(total - curIndex - 1);
this.populateLayers();
}
}
/**
* @returns {void}
*/
async layerRename () {
// const curIndex = $('#layerlist tr.layersel').prevAll().length; // Currently unused
const oldName = $('#layerlist tr.layersel td.layername').text();
const newName = await $.prompt(this.uiStrings.notification.enterNewLayerName, '');
if (!newName) { return; }
if (oldName === newName || this.svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */ $.alert(this.uiStrings.notification.layerHasThatName);
return;
}
this.svgCanvas.renameCurrentLayer(newName);
this.populateLayers();
}
/**
* This function highlights the layer passed in (by fading out the other layers).
* If no layer is passed in, this function restores the other layers.
* @param {string} [layerNameToHighlight]
* @returns {void}
*/
toggleHighlightLayer (layerNameToHighlight) {
let i;
const curNames = [], numLayers = this.svgCanvas.getCurrentDrawing().getNumLayers();
for (i = 0; i < numLayers; i++) {
curNames[i] = this.svgCanvas.getCurrentDrawing().getLayerName(i);
}
if (layerNameToHighlight) {
curNames.forEach((curName) => {
if (curName !== layerNameToHighlight) {
this.svgCanvas.getCurrentDrawing().setLayerOpacity(curName, 0.5);
}
});
} else {
curNames.forEach((curName) => {
this.svgCanvas.getCurrentDrawing().setLayerOpacity(curName, 1.0);
});
}
}
/**
* @returns {void}
*/
populateLayers () {
this.svgCanvas.clearSelection();
const layerlist = $('#layerlist tbody').empty();
const selLayerNames = $('#selLayerNames').empty();
const drawing = this.svgCanvas.getCurrentDrawing();
const currentLayerName = drawing.getCurrentLayerName();
let layer = this.svgCanvas.getCurrentDrawing().getNumLayers();
// we get the layers in the reverse z-order (the layer rendered on top is listed first)
while (layer--) {
const name = drawing.getLayerName(layer);
const layerTr = $('<tr class="layer">').toggleClass('layersel', name === currentLayerName);
const layerVis = $('<td class="layervis">').toggleClass('layerinvis', !drawing.getLayerVisibility(name));
const layerName = $('<td class="layername">' + name + '</td>');
layerlist.append(layerTr.append(layerVis, layerName));
selLayerNames.append('<option value="' + name + '">' + name + '</option>');
}
// handle selection of layer
$('#layerlist td.layername')
.mouseup((evt) => {
$('#layerlist tr.layer').removeClass('layersel');
$(evt.currentTarget.parentNode).addClass('layersel');
this.svgCanvas.setCurrentLayer(evt.currentTarget.textContent);
evt.preventDefault();
})
.mouseover((evt) => {
this.toggleHighlightLayer(this.svgCanvas, evt.currentTarget.textContent);
})
.mouseout(() => {
this.toggleHighlightLayer(this.svgCanvas);
});
$('#layerlist td.layervis').click((evt) => {
const row = $(evt.currentTarget.parentNode).prevAll().length;
const name = $('#layerlist tr.layer:eq(' + row + ') td.layername').text();
const vis = $(evt.currentTarget).hasClass('layerinvis');
this.svgCanvas.setLayerVisibility(name, vis);
$(evt.currentTarget).toggleClass('layerinvis');
});
// if there were too few rows, let's add a few to make it not so lonely
let num = 5 - $('#layerlist tr.layer').size();
while (num-- > 0) {
// TODO: there must a better way to do this
layerlist.append('<tr><td style="color:white">_</td><td/></tr>');
}
}
}
export default LayersPanel;

View File

@ -32,7 +32,6 @@ import {
} from './contextmenu.js';
import SvgCanvas from '../svgcanvas/svgcanvas.js';
import Layer from '../common/layer.js';
import jQueryPluginJSHotkeys from './js-hotkeys/jquery.hotkeys.min.js';
import jQueryPluginJGraduate from './jgraduate/jQuery.jGraduate.js';
@ -40,6 +39,8 @@ import jQueryPluginContextMenu from './contextmenu/jQuery.contextMenu.js';
import jQueryPluginJPicker from './jgraduate/jQuery.jPicker.js';
import jQueryPluginDBox from '../svgcanvas/dbox.js';
import LayersPanel from './LayersPanel.js';
import {
readLang, putLocale,
setStrings
@ -731,7 +732,7 @@ editor.init = () => {
const {ok, cancel} = uiStrings.common;
jQueryPluginDBox($, {ok, cancel});
$('#svg_container')[0].style.visibility = 'visible';
$id('svg_container').style.visibility = 'visible';
try {
// load standard extensions
@ -844,6 +845,220 @@ editor.init = () => {
curConfig
);
/**
* Updates the context panel tools based on the selected element.
* @returns {void}
*/
const updateContextPanel = () => {
let elem = selectedElement;
// If element has just been deleted, consider it null
if (!Utils.isNullish(elem) && !elem.parentNode) { elem = null; }
const currentLayerName = svgCanvas.getCurrentDrawing().getCurrentLayerName();
const currentMode = svgCanvas.getMode();
const unit = curConfig.baseUnit !== 'px' ? curConfig.baseUnit : null;
const isNode = currentMode === 'pathedit'; // elem ? (elem.id && elem.id.startsWith('pathpointgrip')) : false;
const menuItems = $('#cmenu_canvas li');
$('#selected_panel, #multiselected_panel, #g_panel, #rect_panel, #circle_panel,' +
'#ellipse_panel, #line_panel, #text_panel, #image_panel, #container_panel,' +
' #use_panel, #a_panel').hide();
if (!Utils.isNullish(elem)) {
const elname = elem.nodeName;
// If this is a link with no transform and one child, pretend
// its child is selected
// if (elname === 'a') { // && !$(elem).attr('transform')) {
// elem = elem.firstChild;
// }
const angle = svgCanvas.getRotationAngle(elem);
$('#angle').val(angle);
const blurval = svgCanvas.getBlur(elem) * 10;
$id('blur').value = blurval;
if (svgCanvas.addedNew) {
if (elname === 'image' && svgCanvas.getMode() === 'image') {
// Prompt for URL if not a data URL
if (!svgCanvas.getHref(elem).startsWith('data:')) {
/* await */ promptImgURL({cancelDeletes: true});
}
}
/* else if (elname == 'text') {
// TODO: Do something here for new text
} */
}
if (!isNode && currentMode !== 'pathedit') {
$('#selected_panel').show();
// Elements in this array already have coord fields
if (['line', 'circle', 'ellipse'].includes(elname)) {
$('#xy_panel').hide();
} else {
let x, y;
// Get BBox vals for g, polyline and path
if (['g', 'polyline', 'path'].includes(elname)) {
const bb = svgCanvas.getStrokedBBox([elem]);
if (bb) {
({x, y} = bb);
}
} else {
x = elem.getAttribute('x');
y = elem.getAttribute('y');
}
if (unit) {
x = convertUnit(x);
y = convertUnit(y);
}
$('#selected_x').val(x || 0);
$('#selected_y').val(y || 0);
$('#xy_panel').show();
}
// Elements in this array cannot be converted to a path
$id('tool_topath').style.display = ['image', 'text', 'path', 'g', 'use'].includes(elname) ? 'none' : 'block';
$id('tool_reorient').style.display = (elname === 'path') ? 'block' : 'none';
$id('tool_reorient').disabled = (angle === 0);
} else {
const point = path.getNodePoint();
$('#tool_add_subpath').pressed = false;
$('#tool_node_delete').toggleClass('disabled', !path.canDeleteNodes);
// Show open/close button based on selected point
// setIcon('#tool_openclose_path', path.closed_subpath ? 'open_path' : 'close_path');
if (point) {
const segType = $('#seg_type');
if (unit) {
point.x = convertUnit(point.x);
point.y = convertUnit(point.y);
}
$('#path_node_x').val(point.x);
$('#path_node_y').val(point.y);
if (point.type) {
segType.val(point.type).removeAttr('disabled');
} else {
segType.val(4).attr('disabled', 'disabled');
}
}
return;
}
// update contextual tools here
const panels = {
g: [],
a: [],
rect: ['rx', 'width', 'height'],
image: ['width', 'height'],
circle: ['cx', 'cy', 'r'],
ellipse: ['cx', 'cy', 'rx', 'ry'],
line: ['x1', 'y1', 'x2', 'y2'],
text: [],
use: []
};
const {tagName} = elem;
// if ($(elem).data('gsvg')) {
// $('#g_panel').show();
// }
let linkHref = null;
if (tagName === 'a') {
linkHref = svgCanvas.getHref(elem);
$('#g_panel').show();
}
if (elem.parentNode.tagName === 'a') {
if (!$(elem).siblings().length) {
$('#a_panel').show();
linkHref = svgCanvas.getHref(elem.parentNode);
}
}
// Hide/show the make_link buttons
$('#tool_make_link, #tool_make_link_multi').toggle(!linkHref);
if (linkHref) {
$('#link_url').val(linkHref);
}
if (panels[tagName]) {
const curPanel = panels[tagName];
$('#' + tagName + '_panel').show();
$.each(curPanel, function (i, item) {
let attrVal = elem.getAttribute(item);
if (curConfig.baseUnit !== 'px' && elem[item]) {
const bv = elem[item].baseVal.value;
attrVal = convertUnit(bv);
}
$('#' + tagName + '_' + item).val(attrVal || 0);
});
if (tagName === 'text') {
$('#text_panel').css('display', 'inline');
$('#tool_font_size').css('display', 'inline');
$id('tool_italic').pressed = svgCanvas.getItalic();
$id('tool_bold').pressed = svgCanvas.getBold();
$('#font_family').val(elem.getAttribute('font-family'));
$('#font_size').val(elem.getAttribute('font-size'));
$('#text').val(elem.textContent);
if (svgCanvas.addedNew) {
// Timeout needed for IE9
setTimeout(() => {
$('#text').focus().select();
}, 100);
}
// text
} else if (tagName === 'image' && svgCanvas.getMode() === 'image') {
setImageURL(svgCanvas.getHref(elem));
// image
} else if (tagName === 'g' || tagName === 'use') {
$('#container_panel').show();
const title = svgCanvas.getTitle();
const label = $('#g_title')[0];
label.value = title;
setInputWidth(label);
$('#g_title').prop('disabled', tagName === 'use');
}
}
menuItems[(tagName === 'g' ? 'en' : 'dis') + 'ableContextMenuItems']('#ungroup');
menuItems[((tagName === 'g' || !multiselected) ? 'dis' : 'en') + 'ableContextMenuItems']('#group');
// if (!Utils.isNullish(elem))
} else if (multiselected) {
$('#multiselected_panel').show();
menuItems
.enableContextMenuItems('#group')
.disableContextMenuItems('#ungroup');
} else {
menuItems.disableContextMenuItems('#delete,#cut,#copy,#group,#ungroup,#move_front,#move_up,#move_down,#move_back');
}
// update history buttons
$id('tool_undo').disabled = (undoMgr.getUndoStackSize() === 0);
$id('tool_redo').disabled = (undoMgr.getRedoStackSize() === 0);
svgCanvas.addedNew = false;
if ((elem && !isNode) || multiselected) {
// update the selected elements' layer
$('#selLayerNames').removeAttr('disabled').val(currentLayerName);
// Enable regular menu options
canvMenu.enableContextMenuItems(
'#delete,#cut,#copy,#move_front,#move_up,#move_down,#move_back'
);
} else {
$('#selLayerNames').attr('disabled', 'disabled');
}
};
const layersPanel = new LayersPanel(svgCanvas, uiStrings, updateContextPanel);
const modKey = (isMac() ? 'meta+' : 'ctrl+');
const path = svgCanvas.pathActions;
const {undoMgr} = svgCanvas;
@ -884,7 +1099,6 @@ editor.init = () => {
}
}());
const origTitle = $('title:first').text();
// Make [1,2,5] array
const rIntervals = [];
for (let i = 0.1; i < 1e5; i *= 10) {
@ -893,81 +1107,7 @@ editor.init = () => {
rIntervals.push(5 * i);
}
/**
* This function highlights the layer passed in (by fading out the other layers).
* If no layer is passed in, this function restores the other layers.
* @param {string} [layerNameToHighlight]
* @returns {void}
*/
const toggleHighlightLayer = function (layerNameToHighlight) {
let i;
const curNames = [], numLayers = svgCanvas.getCurrentDrawing().getNumLayers();
for (i = 0; i < numLayers; i++) {
curNames[i] = svgCanvas.getCurrentDrawing().getLayerName(i);
}
if (layerNameToHighlight) {
curNames.forEach((curName) => {
if (curName !== layerNameToHighlight) {
svgCanvas.getCurrentDrawing().setLayerOpacity(curName, 0.5);
}
});
} else {
curNames.forEach((curName) => {
svgCanvas.getCurrentDrawing().setLayerOpacity(curName, 1.0);
});
}
};
/**
*
* @returns {void}
*/
const populateLayers = () => {
svgCanvas.clearSelection();
const layerlist = $('#layerlist tbody').empty();
const selLayerNames = $('#selLayerNames').empty();
const drawing = svgCanvas.getCurrentDrawing();
const currentLayerName = drawing.getCurrentLayerName();
let layer = svgCanvas.getCurrentDrawing().getNumLayers();
// we get the layers in the reverse z-order (the layer rendered on top is listed first)
while (layer--) {
const name = drawing.getLayerName(layer);
const layerTr = $('<tr class="layer">').toggleClass('layersel', name === currentLayerName);
const layerVis = $('<td class="layervis">').toggleClass('layerinvis', !drawing.getLayerVisibility(name));
const layerName = $('<td class="layername">' + name + '</td>');
layerlist.append(layerTr.append(layerVis, layerName));
selLayerNames.append('<option value="' + name + '">' + name + '</option>');
}
// handle selection of layer
$('#layerlist td.layername')
.mouseup(function (evt) {
$('#layerlist tr.layer').removeClass('layersel');
$(evt.currentTarget.parentNode).addClass('layersel');
svgCanvas.setCurrentLayer(evt.currentTarget.textContent);
evt.preventDefault();
})
.mouseover((evt) => {
toggleHighlightLayer(evt.currentTarget.textContent);
})
.mouseout(() => {
toggleHighlightLayer();
});
$('#layerlist td.layervis').click((evt) => {
const row = $(evt.currentTarget.parentNode).prevAll().length;
const name = $('#layerlist tr.layer:eq(' + row + ') td.layername').text();
const vis = $(evt.currentTarget).hasClass('layerinvis');
svgCanvas.setLayerVisibility(name, vis);
$(evt.currentTarget).toggleClass('layerinvis');
});
// if there were too few rows, let's add a few to make it not so lonely
let num = 5 - $('#layerlist tr.layer').size();
while (num-- > 0) {
// TODO: there must a better way to do this
layerlist.append('<tr><td style="color:white">_</td><td/></tr>');
}
};
layersPanel.populateLayers();
let editingsource = false;
let origSource = '';
@ -1570,218 +1710,6 @@ editor.init = () => {
updateToolButtonState();
};
/**
* Updates the context panel tools based on the selected element.
* @returns {void}
*/
const updateContextPanel = () => {
let elem = selectedElement;
// If element has just been deleted, consider it null
if (!Utils.isNullish(elem) && !elem.parentNode) { elem = null; }
const currentLayerName = svgCanvas.getCurrentDrawing().getCurrentLayerName();
const currentMode = svgCanvas.getMode();
const unit = curConfig.baseUnit !== 'px' ? curConfig.baseUnit : null;
const isNode = currentMode === 'pathedit'; // elem ? (elem.id && elem.id.startsWith('pathpointgrip')) : false;
const menuItems = $('#cmenu_canvas li');
$('#selected_panel, #multiselected_panel, #g_panel, #rect_panel, #circle_panel,' +
'#ellipse_panel, #line_panel, #text_panel, #image_panel, #container_panel,' +
' #use_panel, #a_panel').hide();
if (!Utils.isNullish(elem)) {
const elname = elem.nodeName;
// If this is a link with no transform and one child, pretend
// its child is selected
// if (elname === 'a') { // && !$(elem).attr('transform')) {
// elem = elem.firstChild;
// }
const angle = svgCanvas.getRotationAngle(elem);
$('#angle').val(angle);
const blurval = svgCanvas.getBlur(elem) * 10;
$id('blur').value = blurval;
if (svgCanvas.addedNew) {
if (elname === 'image' && svgCanvas.getMode() === 'image') {
// Prompt for URL if not a data URL
if (!svgCanvas.getHref(elem).startsWith('data:')) {
/* await */ promptImgURL({cancelDeletes: true});
}
}
/* else if (elname == 'text') {
// TODO: Do something here for new text
} */
}
if (!isNode && currentMode !== 'pathedit') {
$('#selected_panel').show();
// Elements in this array already have coord fields
if (['line', 'circle', 'ellipse'].includes(elname)) {
$('#xy_panel').hide();
} else {
let x, y;
// Get BBox vals for g, polyline and path
if (['g', 'polyline', 'path'].includes(elname)) {
const bb = svgCanvas.getStrokedBBox([elem]);
if (bb) {
({x, y} = bb);
}
} else {
x = elem.getAttribute('x');
y = elem.getAttribute('y');
}
if (unit) {
x = convertUnit(x);
y = convertUnit(y);
}
$('#selected_x').val(x || 0);
$('#selected_y').val(y || 0);
$('#xy_panel').show();
}
// Elements in this array cannot be converted to a path
$id('tool_topath').style.display = ['image', 'text', 'path', 'g', 'use'].includes(elname) ? 'none' : 'block';
$id('tool_reorient').style.display = (elname === 'path') ? 'block' : 'none';
$id('tool_reorient').disabled = (angle === 0);
} else {
const point = path.getNodePoint();
$('#tool_add_subpath').pressed = false;
$('#tool_node_delete').toggleClass('disabled', !path.canDeleteNodes);
// Show open/close button based on selected point
// setIcon('#tool_openclose_path', path.closed_subpath ? 'open_path' : 'close_path');
if (point) {
const segType = $('#seg_type');
if (unit) {
point.x = convertUnit(point.x);
point.y = convertUnit(point.y);
}
$('#path_node_x').val(point.x);
$('#path_node_y').val(point.y);
if (point.type) {
segType.val(point.type).removeAttr('disabled');
} else {
segType.val(4).attr('disabled', 'disabled');
}
}
return;
}
// update contextual tools here
const panels = {
g: [],
a: [],
rect: ['rx', 'width', 'height'],
image: ['width', 'height'],
circle: ['cx', 'cy', 'r'],
ellipse: ['cx', 'cy', 'rx', 'ry'],
line: ['x1', 'y1', 'x2', 'y2'],
text: [],
use: []
};
const {tagName} = elem;
// if ($(elem).data('gsvg')) {
// $('#g_panel').show();
// }
let linkHref = null;
if (tagName === 'a') {
linkHref = svgCanvas.getHref(elem);
$('#g_panel').show();
}
if (elem.parentNode.tagName === 'a') {
if (!$(elem).siblings().length) {
$('#a_panel').show();
linkHref = svgCanvas.getHref(elem.parentNode);
}
}
// Hide/show the make_link buttons
$('#tool_make_link, #tool_make_link_multi').toggle(!linkHref);
if (linkHref) {
$('#link_url').val(linkHref);
}
if (panels[tagName]) {
const curPanel = panels[tagName];
$('#' + tagName + '_panel').show();
$.each(curPanel, function (i, item) {
let attrVal = elem.getAttribute(item);
if (curConfig.baseUnit !== 'px' && elem[item]) {
const bv = elem[item].baseVal.value;
attrVal = convertUnit(bv);
}
$('#' + tagName + '_' + item).val(attrVal || 0);
});
if (tagName === 'text') {
$('#text_panel').css('display', 'inline');
$('#tool_font_size').css('display', 'inline');
$id('tool_italic').pressed = svgCanvas.getItalic();
$id('tool_bold').pressed = svgCanvas.getBold();
$('#font_family').val(elem.getAttribute('font-family'));
$('#font_size').val(elem.getAttribute('font-size'));
$('#text').val(elem.textContent);
if (svgCanvas.addedNew) {
// Timeout needed for IE9
setTimeout(() => {
$('#text').focus().select();
}, 100);
}
// text
} else if (tagName === 'image' && svgCanvas.getMode() === 'image') {
setImageURL(svgCanvas.getHref(elem));
// image
} else if (tagName === 'g' || tagName === 'use') {
$('#container_panel').show();
const title = svgCanvas.getTitle();
const label = $('#g_title')[0];
label.value = title;
setInputWidth(label);
$('#g_title').prop('disabled', tagName === 'use');
}
}
menuItems[(tagName === 'g' ? 'en' : 'dis') + 'ableContextMenuItems']('#ungroup');
menuItems[((tagName === 'g' || !multiselected) ? 'dis' : 'en') + 'ableContextMenuItems']('#group');
// if (!Utils.isNullish(elem))
} else if (multiselected) {
$('#multiselected_panel').show();
menuItems
.enableContextMenuItems('#group')
.disableContextMenuItems('#ungroup');
} else {
menuItems.disableContextMenuItems('#delete,#cut,#copy,#group,#ungroup,#move_front,#move_up,#move_down,#move_back');
}
// update history buttons
$id('tool_undo').disabled = (undoMgr.getUndoStackSize() === 0);
$id('tool_redo').disabled = (undoMgr.getRedoStackSize() === 0);
svgCanvas.addedNew = false;
if ((elem && !isNode) || multiselected) {
// update the selected elements' layer
$('#selLayerNames').removeAttr('disabled').val(currentLayerName);
// Enable regular menu options
canvMenu.enableContextMenuItems(
'#delete,#cut,#copy,#move_front,#move_up,#move_down,#move_back'
);
} else {
$('#selLayerNames').attr('disabled', 'disabled');
}
};
/**
*
* @returns {void}
@ -1806,7 +1734,7 @@ editor.init = () => {
*/
const updateTitle = function (title) {
title = title || svgCanvas.getDocumentTitle();
const newTitle = origTitle + (title ? ': ' + title : '');
const newTitle = document.querySelector('title').text + (title ? ': ' + title : '');
// Remove title update with current context info, isn't really necessary
// if (curContext) {
@ -1893,15 +1821,6 @@ editor.init = () => {
});
};
/**
* Test whether an element is a layer or not.
* @param {SVGGElement} elem - The SVGGElement to test.
* @returns {boolean} True if the element is a layer
*/
function isLayer (elem) {
return elem && elem.tagName === 'g' && Layer.CLASS_REGEX.test(elem.getAttribute('class'));
}
// called when any element has changed
/**
* @param {external:Window} win
@ -1918,8 +1837,8 @@ editor.init = () => {
elems.forEach((elem) => {
const isSvgElem = (elem && elem.tagName === 'svg');
if (isSvgElem || isLayer(elem)) {
populateLayers();
if (isSvgElem || svgCanvas.isLayer(elem)) {
layersPanel.populateLayers();
// if the element changed was the svg, then it could be a resolution change
if (isSvgElem) {
updateCanvas();
@ -2377,7 +2296,7 @@ editor.init = () => {
promptMoveLayerOnce = true;
svgCanvas.moveSelectedToLayer(destLayer);
svgCanvas.clearSelection();
populateLayers();
layersPanel.populateLayers();
};
if (destLayer) {
if (promptMoveLayerOnce) {
@ -3118,7 +3037,7 @@ editor.init = () => {
svgCanvas.setResolution(x, y);
updateCanvas(true);
zoomImage();
populateLayers();
layersPanel.populateLayers();
updateContextPanel();
prepPaints();
svgCanvas.runExtensions('onNewDocument');
@ -3258,7 +3177,7 @@ editor.init = () => {
const clickUndo = () => {
if (undoMgr.getUndoStackSize() > 0) {
undoMgr.undo();
populateLayers();
layersPanel.populateLayers();
}
};
@ -3269,7 +3188,7 @@ editor.init = () => {
const clickRedo = () => {
if (undoMgr.getRedoStackSize() > 0) {
undoMgr.redo();
populateLayers();
layersPanel.populateLayers();
}
};
@ -3412,7 +3331,7 @@ editor.init = () => {
svgCanvas.clearSelection();
hideSourceEditor();
zoomImage();
populateLayers();
layersPanel.populateLayers();
updateTitle();
prepPaints();
};
@ -3818,101 +3737,6 @@ editor.init = () => {
$(this).removeClass('push_button_pressed').addClass('push_button');
});
// ask for a layer name
const newLayer = async () => {
let uniqName,
i = svgCanvas.getCurrentDrawing().getNumLayers();
do {
uniqName = uiStrings.layers.layer + ' ' + (++i);
} while (svgCanvas.getCurrentDrawing().hasLayer(uniqName));
const newName = await $.prompt(uiStrings.notification.enterUniqueLayerName, uniqName);
if (!newName) { return; }
if (svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */ $.alert(uiStrings.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)
$('#layerlist tr.layer').removeClass('layersel');
$('#layerlist tr.layer:first').addClass('layersel');
}
}
/**
*
* @returns {Promise<void>}
*/
async function cloneLayer () {
const name = svgCanvas.getCurrentDrawing().getCurrentLayerName() + ' copy';
const newName = await $.prompt(uiStrings.notification.enterUniqueLayerName, name);
if (!newName) { return; }
if (svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */ $.alert(uiStrings.notification.dupeLayerName);
return;
}
svgCanvas.cloneLayer(newName);
updateContextPanel();
populateLayers();
}
/**
*
* @returns {void}
*/
function mergeLayer () {
if ($('#layerlist tr.layersel').index() === svgCanvas.getCurrentDrawing().getNumLayers() - 1) {
return;
}
svgCanvas.mergeLayer();
updateContextPanel();
populateLayers();
}
/**
* @param {Integer} pos
* @returns {void}
*/
const moveLayer = (pos) => {
const total = svgCanvas.getCurrentDrawing().getNumLayers();
let curIndex = $('#layerlist tr.layersel').index();
if (curIndex > 0 || curIndex < total - 1) {
curIndex += pos;
svgCanvas.setCurrentLayerPosition(total - curIndex - 1);
populateLayers();
}
};
const layerRename = async () => {
// const curIndex = $('#layerlist tr.layersel').prevAll().length; // Currently unused
const oldName = $('#layerlist tr.layersel td.layername').text();
const newName = await $.prompt(uiStrings.notification.enterNewLayerName, '');
if (!newName) { return; }
if (oldName === newName || svgCanvas.getCurrentDrawing().hasLayer(newName)) {
/* await */ $.alert(uiStrings.notification.layerHasThatName);
return;
}
svgCanvas.renameCurrentLayer(newName);
populateLayers();
};
const SIDEPANEL_MAXWIDTH = 300;
const SIDEPANEL_OPENWIDTH = 150;
let sidedrag = -1, sidedragging = false, allowmove = false;
@ -3989,7 +3813,7 @@ editor.init = () => {
$('#svg_editor').unbind('mousemove', resizeSidePanel);
});
populateLayers();
layersPanel.populateLayers();
const centerCanvas = () => {
// this centers the canvas vertically in the workarea (horizontal handled in CSS)
@ -4121,13 +3945,6 @@ editor.init = () => {
$id('sidepanel_handle').addEventListener('click', toggleSidePanel);
$id('copy_save_done').addEventListener('click', cancelOverlays);
// register actions for layer toolbar
$id('layer_new').addEventListener('click', newLayer);
$id('layer_delete').addEventListener('click', deleteLayer);
$id('layer_up').addEventListener('click', () => moveLayer(-1));
$id('layer_down').addEventListener('click', () => moveLayer(1));
$id('layer_rename').addEventListener('click', layerRename);
$id('tool_bold').addEventListener('click', clickBold);
$id('tool_italic').addEventListener('click', clickItalic);
$id('palette').addEventListener('change', handlePalette);
@ -4166,6 +3983,7 @@ editor.init = () => {
savePreferences(e);
}
});
layersPanel.addEvents();
const toolButtons = [
// Shortcuts not associated with buttons
{key: 'ctrl+left', fn () { rotateSelected(0, 1); }},
@ -4414,56 +4232,12 @@ editor.init = () => {
}
);
/**
* 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;
}
};
$('#layerlist').contextMenu(
{
menu: 'cmenu_layers',
inSpeed: 0
},
lmenuFunc
);
$('#layer_moreopts').contextMenu(
{
menu: 'cmenu_layers',
inSpeed: 0,
allowLeft: true
},
lmenuFunc
);
$('.contextMenu li').mousedown(function (ev) {
ev.preventDefault();
});
$('#cmenu_canvas li').disableContextMenu();
canvMenu.enableContextMenuItems('#delete,#cut,#copy');
/**
* @returns {void}
*/
@ -4703,7 +4477,7 @@ editor.init = () => {
if (renameLayer) {
svgCanvas.renameCurrentLayer(uiStrings.common.layer + ' 1');
populateLayers();
layersPanel.populateLayers();
}
svgCanvas.runExtensions('langChanged', /** @type {module:svgcanvas.SvgCanvas#event:ext_langChanged} */ lang);

View File

@ -6,7 +6,7 @@
* @copyright 2011 Jeff Schiller
*/
import Layer from '../common/layer.js';
import Layer from './layer.js';
import HistoryRecordingService from './historyrecording.js';
import {NS} from '../common/namespaces.js';

View File

@ -7,8 +7,8 @@
* @copyright 2011 Jeff Schiller, 2016 Flint O'Brien
*/
import {NS} from './namespaces.js';
import {toXml, walkTree, isNullish} from './utilities.js';
import {NS} from '../common/namespaces.js';
import {toXml, walkTree, isNullish} from '../common/utilities.js';
const $ = jQuery;
@ -193,6 +193,14 @@ class Layer {
this.group_ = undefined;
return group;
}
/**
* Test whether an element is a layer or not.
* @param {SVGGElement} elem - The SVGGElement to test.
* @returns {boolean} True if the element is a layer
*/
static isLayer (elem) {
return elem && elem.tagName === 'g' && Layer.CLASS_REGEX.test(elem.getAttribute('class'));
}
}
/**
* @property {string} CLASS_NAME - class attribute assigned to all layer groups.

View File

@ -190,6 +190,8 @@ class SvgCanvas {
const canvas = this;
this.isLayer = draw.Layer.isLayer;
// "document" element associated with the container (same as window.document using default svg-editor.js)
// NOTE: This is not actually a SVG document, but an HTML document.
// JFH const svgdoc = container.ownerDocument;