Merge branch 'master' of https://github.com/SVG-Edit/svgedit
@@ -6,12 +6,9 @@
|
||||
* @copyright 2010 Jeff Schiller, 2010 Alexis Deveria
|
||||
*/
|
||||
|
||||
// Dependencies:
|
||||
// 1) jQuery (for $.alert())
|
||||
|
||||
import 'pathseg';
|
||||
|
||||
import {NS} from './namespaces.js';
|
||||
import { NS } from './namespaces.js';
|
||||
|
||||
const supportsSVG_ = (function () {
|
||||
return Boolean(document.createElementNS && document.createElementNS(NS.SVG, 'svg').createSVGRect);
|
||||
@@ -23,14 +20,13 @@ return Boolean(document.createElementNS && document.createElementNS(NS.SVG, 'svg
|
||||
*/
|
||||
export const supportsSvg = () => supportsSVG_;
|
||||
|
||||
const {userAgent} = navigator;
|
||||
const { userAgent } = navigator;
|
||||
const svg = document.createElementNS(NS.SVG, 'svg');
|
||||
|
||||
// Note: Browser sniffing should only be used if no other detection method is possible
|
||||
const isOpera_ = Boolean(window.opera);
|
||||
const isWebkit_ = userAgent.includes('AppleWebKit');
|
||||
const isGecko_ = userAgent.includes('Gecko/');
|
||||
const isIE_ = userAgent.includes('MSIE');
|
||||
const isChrome_ = userAgent.includes('Chrome/');
|
||||
const isWindows_ = userAgent.includes('Windows');
|
||||
const isMac_ = userAgent.includes('Macintosh');
|
||||
@@ -53,7 +49,7 @@ const seg = path.createSVGPathSegLinetoAbs(5, 5);
|
||||
try {
|
||||
seglist.replaceItem(seg, 1);
|
||||
return true;
|
||||
} catch (err) {}
|
||||
}catch (err) {/* empty */}
|
||||
return false;
|
||||
}());
|
||||
|
||||
@@ -65,7 +61,7 @@ const seg = path.createSVGPathSegLinetoAbs(5, 5);
|
||||
try {
|
||||
seglist.insertItemBefore(seg, 1);
|
||||
return true;
|
||||
} catch (err) {}
|
||||
}catch (err) {/* empty */}
|
||||
return false;
|
||||
}());
|
||||
|
||||
@@ -165,11 +161,6 @@ export const isWebkit = () => isWebkit_;
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isGecko = () => isGecko_;
|
||||
/**
|
||||
* @function module:browser.isIE
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isIE = () => isIE_;
|
||||
/**
|
||||
* @function module:browser.isChrome
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -16,6 +16,7 @@ export const NS = {
|
||||
SE: 'http://svg-edit.googlecode.com',
|
||||
SVG: 'http://www.w3.org/2000/svg',
|
||||
XLINK: 'http://www.w3.org/1999/xlink',
|
||||
OI: 'http://www.optimistik.fr/namespace/svg/OIdata',
|
||||
XML: 'http://www.w3.org/XML/1998/namespace',
|
||||
XMLNS: 'http://www.w3.org/2000/xmlns/' // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
|
||||
};
|
||||
@@ -26,7 +27,7 @@ export const NS = {
|
||||
*/
|
||||
export const getReverseNS = function () {
|
||||
const reverseNS = {};
|
||||
Object.entries(NS).forEach(([name, URI]) => {
|
||||
Object.entries(NS).forEach(([ name, URI ]) => {
|
||||
reverseNS[URI] = name.toLowerCase();
|
||||
});
|
||||
return reverseNS;
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {NS} from './namespaces.js';
|
||||
import { NS } from './namespaces.js';
|
||||
|
||||
const wAttrs = ['x', 'x1', 'cx', 'rx', 'width'];
|
||||
const hAttrs = ['y', 'y1', 'cy', 'ry', 'height'];
|
||||
const unitAttrs = ['r', 'radius', ...wAttrs, ...hAttrs];
|
||||
const wAttrs = [ 'x', 'x1', 'cx', 'rx', 'width' ];
|
||||
const hAttrs = [ 'y', 'y1', 'cy', 'ry', 'height' ];
|
||||
const unitAttrs = [ 'r', 'radius', ...wAttrs, ...hAttrs ];
|
||||
// unused
|
||||
/*
|
||||
const unitNumMap = {
|
||||
@@ -55,7 +55,6 @@ let typeMap_ = {};
|
||||
* @returns {Integer} The number of digits number should be rounded to
|
||||
*/
|
||||
|
||||
/* eslint-disable jsdoc/valid-types */
|
||||
/**
|
||||
* @typedef {PlainObject} module:units.TypeMap
|
||||
* @property {Float} em
|
||||
@@ -68,7 +67,6 @@ let typeMap_ = {};
|
||||
* @property {Integer} px
|
||||
* @property {0} %
|
||||
*/
|
||||
/* eslint-enable jsdoc/valid-types */
|
||||
|
||||
/**
|
||||
* Initializes this module.
|
||||
@@ -204,14 +202,14 @@ export const setUnitAttr = function (elem, attr, val) {
|
||||
};
|
||||
|
||||
const attrsToConvert = {
|
||||
line: ['x1', 'x2', 'y1', 'y2'],
|
||||
circle: ['cx', 'cy', 'r'],
|
||||
ellipse: ['cx', 'cy', 'rx', 'ry'],
|
||||
foreignObject: ['x', 'y', 'width', 'height'],
|
||||
rect: ['x', 'y', 'width', 'height'],
|
||||
image: ['x', 'y', 'width', 'height'],
|
||||
use: ['x', 'y', 'width', 'height'],
|
||||
text: ['x', 'y']
|
||||
line: [ 'x1', 'x2', 'y1', 'y2' ],
|
||||
circle: [ 'cx', 'cy', 'r' ],
|
||||
ellipse: [ 'cx', 'cy', 'rx', 'ry' ],
|
||||
foreignObject: [ 'x', 'y', 'width', 'height' ],
|
||||
rect: [ 'x', 'y', 'width', 'height' ],
|
||||
image: [ 'x', 'y', 'width', 'height' ],
|
||||
use: [ 'x', 'y', 'width', 'height' ],
|
||||
text: [ 'x', 'y' ]
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -230,13 +228,8 @@ export const convertAttrs = function (element) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const attr = attrs[i];
|
||||
const cur = element.getAttribute(attr);
|
||||
if (cur) {
|
||||
if (!isNaN(cur)) {
|
||||
element.setAttribute(attr, (cur / typeMap_[unit]) + unit);
|
||||
}
|
||||
// else {
|
||||
// Convert existing?
|
||||
// }
|
||||
if (cur && !isNaN(cur)) {
|
||||
element.setAttribute(attr, (cur / typeMap_[unit]) + unit);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -306,7 +299,7 @@ export const isValidUnit = function (attr, val, selectedElement) {
|
||||
try {
|
||||
const elem = elementContainer_.getElement(val);
|
||||
result = (!elem || elem === selectedElement);
|
||||
} catch (e) {}
|
||||
} catch (e) {/* empty fn */}
|
||||
return result;
|
||||
}
|
||||
return true;
|
||||
|
||||
493
src/editor/ConfigObj.js
Normal file
@@ -0,0 +1,493 @@
|
||||
// eslint-disable-next-line node/no-unpublished-import
|
||||
import deparam from 'deparam';
|
||||
import { mergeDeep } from './components/jgraduate/Util.js';
|
||||
|
||||
/**
|
||||
* Escapes special characters in a regular expression.
|
||||
* @function regexEscape
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export const regexEscape = function (str) {
|
||||
// Originally from: http://phpjs.org/functions
|
||||
return String(str).replace(/[.\\+*?[^\]$(){}=!<>|:-]/g, '\\$&');
|
||||
};
|
||||
/**
|
||||
* @class configObj
|
||||
*/
|
||||
export default class ConfigObj {
|
||||
/**
|
||||
* @param {PlainObject} editor
|
||||
*/
|
||||
constructor (editor) {
|
||||
/**
|
||||
* Preferences.
|
||||
* @interface module:SVGEditor.Prefs
|
||||
* @property {string} [lang="en"] Two-letter language code. The language must exist in the Editor Preferences language list. Defaults to "en" if `locale.js` detection does not detect another language.
|
||||
* @property {module:SVGEditor.IconSize} [iconsize="s" || "m"] Size of the toolbar icons. Will default to 's' if the window height is smaller than the minimum height and 'm' otherwise.
|
||||
* @property {string} [bkgd_color="#FFF"] Color hex for canvas background color. Defaults to white.
|
||||
* @property {string} [bkgd_url=""] Background raster image URL. This image will fill the background of the document; useful for tracing purposes.
|
||||
* @property {"embed"|"ref"} [img_save="embed"] Defines whether included raster images should be saved as Data URIs when possible, or as URL references. Settable in the Document Properties dialog.
|
||||
* @property {boolean} [save_notice_done=false] Used to track alert status
|
||||
* @property {boolean} [export_notice_done=false] Used to track alert status
|
||||
* @todo `save_notice_done` and `export_notice_done` should be changed to flags rather than preferences
|
||||
*/
|
||||
this.defaultPrefs = {
|
||||
// EDITOR OPTIONS (DIALOG)
|
||||
/**
|
||||
* Default to "en" if locale.js detection does not detect another language.
|
||||
*/
|
||||
lang: 'en',
|
||||
/**
|
||||
* Will default to 's' if the window height is smaller than the minimum
|
||||
* height and 'm' otherwise.
|
||||
*/
|
||||
iconsize: '',
|
||||
bkgd_color: '#FFF',
|
||||
bkgd_url: '',
|
||||
// DOCUMENT PROPERTIES (DIALOG)
|
||||
img_save: 'embed',
|
||||
// ALERT NOTICES
|
||||
// Only shows in UI as far as alert notices, but useful to remember, so keeping as pref
|
||||
save_notice_done: false,
|
||||
export_notice_done: false
|
||||
};
|
||||
/**
|
||||
* @tutorial ConfigOptions
|
||||
* @interface module:SVGEditor.Config
|
||||
* @property {string} [canvasName="default"] Used to namespace storage provided via `ext-storage.js`; you can use this if you wish to have multiple independent instances of SVG Edit on the same domain
|
||||
* @property {boolean} [no_save_warning=false] If `true`, prevents the warning dialog box from appearing when closing/reloading the page. Mostly useful for testing.
|
||||
* @property {string} [imgPath="images/"] The path where the SVG icons are located, with trailing slash. Note that as of version 2.7, this is not configurable by URL for security reasons.
|
||||
* @property {boolean} [preventAllURLConfig=false] Set to `true` to override the ability for URLs to set non-content configuration (including extension config).
|
||||
* Must be set early, i.e., in `svgedit-config-iife.js`; extension loading is too late!
|
||||
* @property {boolean} [preventURLContentLoading=false] Set to `true` to override the ability for URLs to set URL-based SVG content.
|
||||
* Must be set early, i.e., in `svgedit-config-iife.js`; extension loading is too late!
|
||||
* @property {boolean} [lockExtensions=false] Set to `true` to override the ability for URLs to set their own extensions; disallowed in URL setting. There is no need for this when `preventAllURLConfig` is used.
|
||||
* Must be set early, i.e., in `svgedit-config-iife.js`; extension loading is too late!
|
||||
* @property {boolean} [noDefaultExtensions=false] If set to `true`, prohibits automatic inclusion of default extensions (though "extensions" can still be used to add back any desired default extensions along with any other extensions).
|
||||
* This can only be meaningfully used in `svgedit-config-iife.js` or in the URL
|
||||
* @property {boolean} [noStorageOnLoad=false] Some interaction with `ext-storage.js`; prevent even the loading of previously saved local storage.
|
||||
* @property {boolean} [forceStorage=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 (and may be required by law in some regions)
|
||||
* @property {boolean} [emptyStorageOnDecline=false] Used by `ext-storage.js`; empty any prior storage if the user declines to store
|
||||
* @property {boolean} [avoidClientSide=false] DEPRECATED (use `avoidClientSideDownload` instead); Used by `ext-server_opensave.js`; set to `true` if you wish to always save to server and not only as fallback when client support is lacking
|
||||
* @property {boolean} [avoidClientSideDownload=false] Used by `ext-server_opensave.js`; set to `true` if you wish to always save to server and not only as fallback when client support is lacking
|
||||
* @property {boolean} [avoidClientSideOpen=false] Used by `ext-server_opensave.js`; set to `true` if you wish to always open from the server and not only as fallback when FileReader client support is lacking
|
||||
* @property {string[]} [extensions=[]] Extensions to load on startup. Use an array in `setConfig` and comma separated file names in the URL.Extension names must begin with "ext-".
|
||||
* Note that as of version 2.7, paths containing "/", "\", or ":", are disallowed for security reasons.
|
||||
* Although previous versions of this list would entirely override the default list, as of version 2.7, the defaults will always be added to this explicit list unless the configuration `noDefaultExtensions` is included.
|
||||
* See {@link module:SVGEditor~defaultExtensions}.
|
||||
* @property {string[]} [allowedOrigins=[]] Used by `ext-xdomain-messaging.js` to indicate which origins are permitted for cross-domain messaging (e.g., between the embedded editor and main editor code).
|
||||
* Besides explicit domains, one might add '*' to allow all domains (not recommended for privacy/data integrity of your user's content!),
|
||||
* `window.location.origin` for allowing the same origin (should be safe if you trust all apps on your domain), 'null' to allow `file:///` URL usage
|
||||
* @property {string} [paramurl] This was available via URL only. Allowed an un-encoded URL within the query string (use "url" or "source" with a data: URI instead)
|
||||
* @property {Float} [canvas_expansion=3] The minimum area visible outside the canvas, as a multiple of the image dimensions. The larger the number, the more one can scroll outside the canvas.
|
||||
* @property {PlainObject} [initFill] Init fill properties
|
||||
* @property {string} [initFill.color="FF0000"] The initial fill color. Must be a hex code string. Defaults to solid red.
|
||||
* @property {Float} [initFill.opacity=1] The initial fill opacity. Must be a number between 0 and 1
|
||||
* @property {PlainObject} [initStroke] Init stroke properties
|
||||
* @property {Float} [initStroke.width=5] The initial stroke width. Must be a positive number.
|
||||
* @property {string} [initStroke.color="000000"] The initial stroke color. Must be a hex code. Defaults to solid black.
|
||||
* @property {Float} [initStroke.opacity=1] The initial stroke opacity. Must be a number between 0 and 1.
|
||||
* @property {PlainObject} text Text style properties
|
||||
* @property {Float} [text.stroke_width=0] Text stroke width
|
||||
* @property {Float} [text.font_size=24] Text font size
|
||||
* @property {string} [text.font_family="serif"] Text font family
|
||||
* @property {Float} [initOpacity=1] Initial opacity (multiplied by 100)
|
||||
* @property {module:SVGEditor.XYDimensions} [dimensions=[640, 480]] The default width/height of a new document. Use an array in `setConfig` (e.g., `[800, 600]`) and comma separated numbers in the URL.
|
||||
* @property {boolean} [gridSnapping=false] Enable snap to grid by default. Set in Editor Options.
|
||||
* @property {string} [gridColor="#000"] Accepts hex, e.g., '#000'. Set in Editor Options. Defaults to black.
|
||||
* @property {string} [baseUnit="px"] Set in Editor Options.
|
||||
* @property {Float} [snappingStep=10] Set the default grid snapping value. Set in Editor Options.
|
||||
* @property {boolean} [showRulers=true] Initial state of ruler display (v2.6). Set in Editor Options.
|
||||
* @property {string} [initTool="select"] The initially selected tool. Must be either the ID of the button for the tool, or the ID without `tool_` prefix (e.g., "select").
|
||||
* @property {boolean} [wireframe=false] Start in wireframe mode
|
||||
* @property {boolean} [showlayers=false] Open the layers side-panel by default.
|
||||
* @property {"new"|"same"} [exportWindowType="new"] Can be "new" or "same" to indicate whether new windows will be generated for each export;
|
||||
* the `window.name` of the export window is namespaced based on the `canvasName` (and incremented if "new" is selected as the type). Introduced 2.8.
|
||||
* @property {boolean} [showGrid=false] Set by `ext-grid.js`; determines whether or not to show the grid by default
|
||||
* @property {boolean} [show_outside_canvas=true] Defines whether or not elements outside the canvas should be visible. Set and used in `svgcanvas.js`.
|
||||
* @property {boolean} [selectNew=true] If true, will replace the selection with the current element and automatically select element objects (when not in "path" mode) after they are created, showing their grips (v2.6).
|
||||
* Set and used in `svgcanvas.js` (`mouseUp`).
|
||||
*/
|
||||
this.defaultConfig = {
|
||||
canvasName: 'default',
|
||||
canvas_expansion: 3,
|
||||
initFill: {
|
||||
color: 'FF0000', // solid red
|
||||
opacity: 1
|
||||
},
|
||||
initStroke: {
|
||||
width: 5,
|
||||
color: '000000', // solid black
|
||||
opacity: 1
|
||||
},
|
||||
text: {
|
||||
stroke_width: 0,
|
||||
font_size: 24,
|
||||
font_family: 'serif'
|
||||
},
|
||||
initOpacity: 1,
|
||||
initTool: 'select',
|
||||
exportWindowType: 'new', // 'same' (todo: also support 'download')
|
||||
wireframe: false,
|
||||
showlayers: false,
|
||||
no_save_warning: false,
|
||||
// PATH CONFIGURATION
|
||||
// The following path configuration items are disallowed in the URL (as should any future path configurations)
|
||||
imgPath: './images/',
|
||||
// DOCUMENT PROPERTIES
|
||||
// Change the following to a preference (already in the Document Properties dialog)?
|
||||
dimensions: [ 640, 480 ],
|
||||
// EDITOR OPTIONS
|
||||
// Change the following to preferences (already in the Editor Options dialog)?
|
||||
gridSnapping: false,
|
||||
gridColor: '#000',
|
||||
baseUnit: 'px',
|
||||
snappingStep: 10,
|
||||
showRulers: true,
|
||||
// URL BEHAVIOR CONFIGURATION
|
||||
preventAllURLConfig: false,
|
||||
preventURLContentLoading: false,
|
||||
// EXTENSION CONFIGURATION (see also preventAllURLConfig)
|
||||
lockExtensions: false, // Disallowed in URL setting
|
||||
noDefaultExtensions: false, // noDefaultExtensions can only be meaningfully used in `svgedit-config-iife.js` or in the URL
|
||||
// EXTENSION-RELATED (GRID)
|
||||
showGrid: false, // Set by ext-grid.js
|
||||
// EXTENSION-RELATED (STORAGE)
|
||||
noStorageOnLoad: false, // Some interaction with ext-storage.js; prevent even the loading of previously saved local storage
|
||||
forceStorage: 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
|
||||
emptyStorageOnDecline: false, // Used by ext-storage.js; empty any prior storage if the user declines to store
|
||||
// EXTENSION (CLIENT VS. SERVER SAVING/OPENING)
|
||||
avoidClientSide: false, // Deprecated in favor of `avoidClientSideDownload`
|
||||
avoidClientSideDownload: false,
|
||||
avoidClientSideOpen: false
|
||||
};
|
||||
|
||||
this.curPrefs = {};
|
||||
// Note: The difference between Prefs and Config is that Prefs
|
||||
// can be changed in the UI and are stored in the browser,
|
||||
// while config cannot
|
||||
this.urldata = {};
|
||||
/**
|
||||
* @name module:SVGEditor~defaultExtensions
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.defaultExtensions = [
|
||||
'ext-connector',
|
||||
'ext-eyedropper',
|
||||
'ext-grid',
|
||||
'ext-imagelib',
|
||||
// 'ext-arrows',
|
||||
// 'ext-markers',
|
||||
'ext-overview_window',
|
||||
'ext-panning',
|
||||
'ext-shapes',
|
||||
'ext-polystar',
|
||||
'ext-storage',
|
||||
'ext-opensave',
|
||||
// 'ext-helloworld',
|
||||
];
|
||||
this.curConfig = {
|
||||
// We do not put on defaultConfig to simplify object copying
|
||||
// procedures (we obtain instead from defaultExtensions)
|
||||
extensions: [],
|
||||
userExtensions: [],
|
||||
/**
|
||||
* Can use `location.origin` to indicate the current
|
||||
* origin. Can contain a '*' to allow all domains or 'null' (as
|
||||
* a string) to support all `file:///` URLs. Cannot be set by
|
||||
* URL for security reasons (not safe, at least for
|
||||
* privacy or data integrity of SVG content).
|
||||
* Might have been fairly safe to allow
|
||||
* `new URL(location.href).origin` by default but
|
||||
* avoiding it ensures some more security that even third
|
||||
* party apps on the same domain also cannot communicate
|
||||
* with this app by default.
|
||||
* For use with `ext-xdomain-messaging.js`
|
||||
* @todo We might instead make as a user-facing preference.
|
||||
*/
|
||||
allowedOrigins: []
|
||||
};
|
||||
this.editor = editor;
|
||||
}
|
||||
/**
|
||||
* @function setupCurPrefs
|
||||
* @returns {void}
|
||||
*/
|
||||
setupCurPrefs () {
|
||||
const curPrefs = { ...this.defaultPrefs, ...this.curPrefs }; // Now safe to merge with priority for curPrefs in the event any are already set
|
||||
// Export updated prefs
|
||||
this.curPrefs = curPrefs;
|
||||
}
|
||||
/**
|
||||
* Sets up current config based on defaults.
|
||||
* @returns {void}
|
||||
*/
|
||||
setupCurConfig () {
|
||||
const curConfig = { ...this.defaultConfig, ...this.curConfig }; // Now safe to merge with priority for curConfig in the event any are already set
|
||||
|
||||
// Now deal with extensions and other array config
|
||||
if (!curConfig.noDefaultExtensions) {
|
||||
curConfig.extensions = [ ...this.defaultExtensions ];
|
||||
}
|
||||
// Export updated config
|
||||
this.curConfig = curConfig;
|
||||
}
|
||||
/**
|
||||
* @function loadFromURL Load config/data from URL if given
|
||||
* @returns {void}
|
||||
*/
|
||||
loadFromURL () {
|
||||
const { search, searchParams } = new URL(location);
|
||||
|
||||
if (search) {
|
||||
this.urldata = deparam(searchParams.toString());
|
||||
|
||||
[ 'initStroke', 'initFill' ].forEach((prop) => {
|
||||
if (searchParams.has(`${prop}[color]`)) {
|
||||
// Restore back to original non-deparamed value to avoid color
|
||||
// strings being converted to numbers
|
||||
this.urldata[prop].color = searchParams.get(`${prop}[color]`);
|
||||
}
|
||||
});
|
||||
|
||||
if (searchParams.has('bkgd_color')) {
|
||||
this.urldata.bkgd_color = '#' + searchParams.get('bkgd_color');
|
||||
}
|
||||
|
||||
if (this.urldata.dimensions) {
|
||||
this.urldata.dimensions = this.urldata.dimensions.split(',');
|
||||
}
|
||||
|
||||
if (this.urldata.extensions) {
|
||||
// For security reasons, disallow cross-domain or cross-folder
|
||||
// extensions via URL
|
||||
this.urldata.extensions = (/[:/\\]/).test(this.urldata.extensions)
|
||||
? ''
|
||||
: this.urldata.extensions.split(',');
|
||||
}
|
||||
|
||||
// Disallowing extension paths via URL for
|
||||
// security reasons, even for same-domain
|
||||
// ones given potential to interact in undesirable
|
||||
// ways with other script resources
|
||||
[ 'userExtensions', 'imgPath' ]
|
||||
.forEach(function (pathConfig) {
|
||||
if (this.urldata[pathConfig]) {
|
||||
delete this.urldata[pathConfig];
|
||||
}
|
||||
});
|
||||
|
||||
// Note: `source` and `url` (as with `storagePrompt` later) are not
|
||||
// set on config but are used below
|
||||
this.setConfig(this.urldata, { overwrite: false });
|
||||
this.setupCurConfig();
|
||||
|
||||
if (!this.curConfig.preventURLContentLoading) {
|
||||
let { source } = this.urldata;
|
||||
if (!source) { // urldata.source may have been null if it ended with '='
|
||||
const src = searchParams.get('source');
|
||||
if (src && src.startsWith('data:')) {
|
||||
source = src;
|
||||
}
|
||||
}
|
||||
if (source) {
|
||||
if (source.startsWith('data:')) {
|
||||
this.editor.loadFromDataURI(source);
|
||||
} else {
|
||||
this.editor.loadFromString(source);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.urldata.url) {
|
||||
this.editor.loadFromURL(this.urldata.url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!this.urldata.noStorageOnLoad || this.curConfig.forceStorage) {
|
||||
this.loadContentAndPrefs();
|
||||
}
|
||||
} else {
|
||||
this.setupCurConfig();
|
||||
this.loadContentAndPrefs();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Where permitted, sets canvas and/or `configObj.defaultPrefs` based on previous
|
||||
* storage. This will override URL settings (for security reasons) but
|
||||
* not `svgedit-config-iife.js` configuration (unless initial user
|
||||
* overriding is explicitly permitted there via `allowInitialUserOverride`).
|
||||
* @function module:SVGEditor.loadContentAndPrefs
|
||||
* @todo Split `allowInitialUserOverride` into `allowOverrideByURL` and
|
||||
* `allowOverrideByUserStorage` so `svgedit-config-iife.js` can disallow some
|
||||
* individual items for URL setting but allow for user storage AND/OR
|
||||
* change URL setting so that it always uses a different namespace,
|
||||
* so it won't affect pre-existing user storage (but then if users saves
|
||||
* that, it will then be subject to tampering
|
||||
* @returns {void}
|
||||
*/
|
||||
loadContentAndPrefs () {
|
||||
if (!this.curConfig.forceStorage &&
|
||||
(this.curConfig.noStorageOnLoad ||
|
||||
!(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/).test(document.cookie)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// LOAD CONTENT
|
||||
if (this.editor.storage && // Cookies do not have enough available memory to hold large documents
|
||||
(this.curConfig.forceStorage ||
|
||||
(!this.curConfig.noStorageOnLoad &&
|
||||
(/(?:^|;\s*)svgeditstore=prefsAndContent/).test(document.cookie))
|
||||
)
|
||||
) {
|
||||
const name = 'svgedit-' + this.curConfig.canvasName;
|
||||
const cached = this.editor.storage.getItem(name);
|
||||
if (cached) {
|
||||
this.editor.loadFromString(cached);
|
||||
}
|
||||
}
|
||||
|
||||
// LOAD PREFS
|
||||
Object.keys(this.defaultPrefs).forEach((key) => {
|
||||
const storeKey = 'svg-edit-' + key;
|
||||
if (this.editor.storage) {
|
||||
const val = this.editor.storage.getItem(storeKey);
|
||||
if (val) {
|
||||
this.defaultPrefs[key] = String(val); // Convert to string for FF (.value fails in Webkit)
|
||||
}
|
||||
} else if (window.widget) {
|
||||
this.defaultPrefs[key] = window.widget.preferenceForKey(storeKey);
|
||||
} else {
|
||||
const result = document.cookie.match(
|
||||
new RegExp('(?:^|;\\s*)' + regexEscape(
|
||||
encodeURIComponent(storeKey)
|
||||
) + '=([^;]+)')
|
||||
);
|
||||
this.defaultPrefs[key] = result ? decodeURIComponent(result[1]) : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows setting of preferences or configuration (including extensions).
|
||||
* @function module:SVGEditor.setConfig
|
||||
* @param {module:SVGEditor.Config|module:SVGEditor.Prefs} opts The preferences or configuration (including extensions). See the tutorial on {@tutorial ConfigOptions} for info on config and preferences.
|
||||
* @param {PlainObject} [cfgCfg] Describes configuration which applies to the
|
||||
* particular batch of supplied options
|
||||
* @param {boolean} [cfgCfg.allowInitialUserOverride=false] Set to true if you wish
|
||||
* to allow initial overriding of settings by the user via the URL
|
||||
* (if permitted) or previously stored preferences (if permitted);
|
||||
* note that it will be too late if you make such calls in extension
|
||||
* code because the URL or preference storage settings will
|
||||
* have already taken place.
|
||||
* @param {boolean} [cfgCfg.overwrite=true] Set to false if you wish to
|
||||
* prevent the overwriting of prior-set preferences or configuration
|
||||
* (URL settings will always follow this requirement for security
|
||||
* reasons, so `svgedit-config-iife.js` settings cannot be overridden unless it
|
||||
* explicitly permits via `allowInitialUserOverride` but extension config
|
||||
* can be overridden as they will run after URL settings). Should
|
||||
* not be needed in `svgedit-config-iife.js`.
|
||||
* @returns {void}
|
||||
*/
|
||||
setConfig (opts, cfgCfg = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {module:SVGEditor.Config|module:SVGEditor.Prefs} cfgObj
|
||||
* @param {string} key
|
||||
* @param {any} val See {@link module:SVGEditor.Config} or {@link module:SVGEditor.Prefs}
|
||||
* @returns {void}
|
||||
*/
|
||||
const extendOrAdd = (cfgObj, key, val) => {
|
||||
if (cfgObj[key] && typeof cfgObj[key] === 'object') {
|
||||
// $.extend(true, cfgObj[key], val);
|
||||
cfgObj[key] = mergeDeep(cfgObj[key], val);
|
||||
} else {
|
||||
cfgObj[key] = val;
|
||||
}
|
||||
};
|
||||
Object.entries(opts).forEach(([ key, val ]) => {
|
||||
// Only allow prefs defined in configObj.defaultPrefs or...
|
||||
if (this.defaultPrefs[key]) {
|
||||
if (cfgCfg.overwrite === false && (
|
||||
this.curConfig.preventAllURLConfig ||
|
||||
this.curPrefs[key])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (cfgCfg.allowInitialUserOverride === true) {
|
||||
this.defaultPrefs[key] = val;
|
||||
} else {
|
||||
this.pref(key, val);
|
||||
}
|
||||
} else if ([ 'extensions', 'userExtensions', 'allowedOrigins' ].includes(key)) {
|
||||
if (cfgCfg.overwrite === false &&
|
||||
(
|
||||
this.curConfig.preventAllURLConfig ||
|
||||
[ 'allowedOrigins' ].includes(key) ||
|
||||
(key === 'extensions' && this.curConfig.lockExtensions)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.curConfig[key] = this.curConfig[key].concat(val); // We will handle any dupes later
|
||||
// Only allow other configObj.curConfig if defined in configObj.defaultConfig
|
||||
} else if ({}.hasOwnProperty.call(this.defaultConfig, key)) {
|
||||
if (cfgCfg.overwrite === false && (
|
||||
this.curConfig.preventAllURLConfig ||
|
||||
{}.hasOwnProperty.call(this.curConfig, key)
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
// Potentially overwriting of previously set config
|
||||
if ({}.hasOwnProperty.call(this.curConfig, key)) {
|
||||
if (cfgCfg.overwrite === false) {
|
||||
return;
|
||||
}
|
||||
extendOrAdd(this.curConfig, key, val);
|
||||
} else if (cfgCfg.allowInitialUserOverride === true) {
|
||||
extendOrAdd(this.defaultConfig, key, val);
|
||||
} else if (this.defaultConfig[key] && typeof this.defaultConfig[key] === 'object') {
|
||||
this.curConfig[key] = Array.isArray(this.defaultConfig[key]) ? [] : {};
|
||||
this.curConfig[key] = mergeDeep(this.curConfig[key], val);
|
||||
// $.extend(true, this.curConfig[key], val); // Merge properties recursively, e.g., on initFill, initStroke objects
|
||||
} else {
|
||||
this.curConfig[key] = val;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Store and retrieve preferences.
|
||||
* @function pref
|
||||
* @param {string} key The preference name to be retrieved or set
|
||||
* @param {string} [val] The value. If the value supplied is missing or falsey, no change to the preference will
|
||||
* be made unless `mayBeEmpty` is set.
|
||||
* @param {boolean} [mayBeEmpty] If value may be falsey.
|
||||
* @returns {string|void} If val is missing or falsey and `mayBeEmpty` is not set, the
|
||||
* value of the previously stored preference will be returned.
|
||||
* @todo Review whether any remaining existing direct references to
|
||||
* getting `curPrefs` can be changed to use `svgEditor.configObj.pref()` getting to ensure
|
||||
* `defaultPrefs` fallback (also for sake of `allowInitialUserOverride`);
|
||||
* specifically, `bkgd_color` could be changed so that the pref dialog has a
|
||||
* button to auto-calculate background, but otherwise uses `svgEditor.configObj.pref()` to
|
||||
* be able to get default prefs or overridable settings
|
||||
*/
|
||||
pref (key, val, mayBeEmpty) {
|
||||
if (mayBeEmpty || val) {
|
||||
this.curPrefs[key] = val;
|
||||
return undefined;
|
||||
}
|
||||
return (key in this.curPrefs) ? this.curPrefs[key] : this.defaultPrefs[key];
|
||||
}
|
||||
/**
|
||||
* @function load load Config
|
||||
* @returns {void}
|
||||
*/
|
||||
load () {
|
||||
this.loadFromURL(this.editor);
|
||||
this.setupCurPrefs(this.editor);
|
||||
}
|
||||
}
|
||||
1212
src/editor/Editor.js
Normal file
811
src/editor/EditorStartup.js
Normal file
@@ -0,0 +1,811 @@
|
||||
/* globals $ seConfirm seAlert */
|
||||
import './touch.js';
|
||||
import { convertUnit } from '../common/units.js';
|
||||
import {
|
||||
putLocale
|
||||
} from './locale.js';
|
||||
import {
|
||||
hasCustomHandler, getCustomHandler, injectExtendedContextMenuItemsIntoDom
|
||||
} from './contextmenu.js';
|
||||
import editorTemplate from './templates/editorTemplate.js';
|
||||
import SvgCanvas from '../svgcanvas/svgcanvas.js';
|
||||
import Rulers from './Rulers.js';
|
||||
|
||||
/**
|
||||
* @fires module:svgcanvas.SvgCanvas#event:svgEditorReady
|
||||
* @returns {void}
|
||||
*/
|
||||
const readySignal = () => {
|
||||
// let the opener know SVG Edit is ready (now that config is set up)
|
||||
const w = window.opener || window.parent;
|
||||
if (w) {
|
||||
try {
|
||||
/**
|
||||
* Triggered on a containing `document` (of `window.opener`
|
||||
* or `window.parent`) when the editor is loaded.
|
||||
* @event module:SVGEditor#event:svgEditorReadyEvent
|
||||
* @type {Event}
|
||||
* @property {true} bubbles
|
||||
* @property {true} cancelable
|
||||
*/
|
||||
/**
|
||||
* @name module:SVGthis.svgEditorReadyEvent
|
||||
* @type {module:SVGEditor#event:svgEditorReadyEvent}
|
||||
*/
|
||||
const svgEditorReadyEvent = new w.CustomEvent('svgEditorReady', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
w.document.documentElement.dispatchEvent(svgEditorReadyEvent);
|
||||
} catch (e) {/* empty fn */}
|
||||
}
|
||||
};
|
||||
|
||||
const { $id } = SvgCanvas;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class EditorStartup {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor () {
|
||||
this.extensionsAdded = false;
|
||||
this.messageQueue = [];
|
||||
this.$svgEditor = $id('svg_editor');
|
||||
}
|
||||
/**
|
||||
* Auto-run after a Promise microtask.
|
||||
* @function module:SVGthis.init
|
||||
* @returns {void}
|
||||
*/
|
||||
async init () {
|
||||
if ('localStorage' in window) { // && onWeb removed so Webkit works locally
|
||||
this.storage = window.localStorage;
|
||||
}
|
||||
this.configObj.load();
|
||||
const self = this;
|
||||
const { i18next } = await putLocale(this.configObj.pref('lang'), this.goodLangs);
|
||||
this.i18next = i18next;
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
await import(`./components/index.js`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
await import(`./dialogs/index.js`);
|
||||
// allow to prepare the dom without display
|
||||
this.$svgEditor.style.visibility = 'hidden';
|
||||
try {
|
||||
// add editor components to the DOM
|
||||
document.body.append(editorTemplate.content.cloneNode(true));
|
||||
// Image props dialog added to DOM
|
||||
const newSeImgPropDialog = document.createElement('se-img-prop-dialog');
|
||||
newSeImgPropDialog.setAttribute('id', 'se-img-prop');
|
||||
document.body.append(newSeImgPropDialog);
|
||||
newSeImgPropDialog.init(this.i18next);
|
||||
// editor prefences dialoag added to DOM
|
||||
const newSeEditPrefsDialog = document.createElement('se-edit-prefs-dialog');
|
||||
newSeEditPrefsDialog.setAttribute('id', 'se-edit-prefs');
|
||||
document.body.append(newSeEditPrefsDialog);
|
||||
newSeEditPrefsDialog.init(this.i18next);
|
||||
// canvas menu added to DOM
|
||||
const dialogBox = document.createElement('se-cmenu_canvas-dialog');
|
||||
dialogBox.setAttribute('id', 'se-cmenu_canvas');
|
||||
document.body.append(dialogBox);
|
||||
dialogBox.init(this.i18next);
|
||||
// alertDialog added to DOM
|
||||
const alertBox = document.createElement('se-alert-dialog');
|
||||
alertBox.setAttribute('id', 'se-alert-dialog');
|
||||
document.body.append(alertBox);
|
||||
// promptDialog added to DOM
|
||||
const promptBox = document.createElement('se-prompt-dialog');
|
||||
promptBox.setAttribute('id', 'se-prompt-dialog');
|
||||
document.body.append(promptBox);
|
||||
// Export dialog added to DOM
|
||||
const exportDialog = document.createElement('se-export-dialog');
|
||||
exportDialog.setAttribute('id', 'se-export-dialog');
|
||||
document.body.append(exportDialog);
|
||||
exportDialog.init(this.i18next);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name module:SVGthis.canvas
|
||||
* @type {module:svgcanvas.SvgCanvas}
|
||||
*/
|
||||
this.svgCanvas = new SvgCanvas(
|
||||
$id('svgcanvas'),
|
||||
this.configObj.curConfig
|
||||
);
|
||||
|
||||
this.leftPanel.init();
|
||||
this.bottomPanel.init();
|
||||
this.topPanel.init();
|
||||
this.layersPanel.init();
|
||||
this.mainMenu.init();
|
||||
|
||||
const { undoMgr } = this.svgCanvas;
|
||||
this.workarea = document.getElementById('workarea');
|
||||
this.canvMenu = document.getElementById('se-cmenu_canvas');
|
||||
this.exportWindow = null;
|
||||
this.defaultImageURL = this.configObj.curConfig.imgPath + 'logo.svg';
|
||||
const zoomInIcon = 'crosshair';
|
||||
const zoomOutIcon = 'crosshair';
|
||||
this.uiContext = 'toolbars';
|
||||
|
||||
// For external openers
|
||||
readySignal();
|
||||
|
||||
this.rulers = new Rulers(this);
|
||||
|
||||
this.layersPanel.populateLayers();
|
||||
this.selectedElement = null;
|
||||
this.multiselected = false;
|
||||
|
||||
const aLinks = $id('cur_context_panel').querySelectorAll('a');
|
||||
|
||||
for (const aLink of aLinks) {
|
||||
aLink.addEventListener('click', (evt) => {
|
||||
const link = evt.currentTarget;
|
||||
if (link.hasAttribute('data-root')) {
|
||||
this.svgCanvas.leaveContext();
|
||||
} else {
|
||||
this.svgCanvas.setContext(link.textContent);
|
||||
}
|
||||
this.svgCanvas.clearSelection();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// bind the selected event to our function that handles updates to the UI
|
||||
this.svgCanvas.bind('selected', this.selectedChanged.bind(this));
|
||||
this.svgCanvas.bind('transition', this.elementTransition.bind(this));
|
||||
this.svgCanvas.bind('changed', this.elementChanged.bind(this));
|
||||
this.svgCanvas.bind('exported', this.exportHandler.bind(this));
|
||||
this.svgCanvas.bind('exportedPDF', function (win, data) {
|
||||
if (!data.output) { // Ignore Chrome
|
||||
return;
|
||||
}
|
||||
const { exportWindowName } = data;
|
||||
if (exportWindowName) {
|
||||
this.exportWindow = window.open('', this.exportWindowName); // A hack to get the window via JSON-able name without opening a new one
|
||||
}
|
||||
if (!this.exportWindow || this.exportWindow.closed) {
|
||||
seAlert(this.i18next.t('notification.popupWindowBlocked'));
|
||||
return;
|
||||
}
|
||||
this.exportWindow.location.href = data.output;
|
||||
}.bind(this));
|
||||
this.svgCanvas.bind('zoomed', this.zoomChanged.bind(this));
|
||||
this.svgCanvas.bind('zoomDone', this.zoomDone.bind(this));
|
||||
this.svgCanvas.bind(
|
||||
'updateCanvas',
|
||||
/**
|
||||
* @param {external:Window} win
|
||||
* @param {PlainObject} centerInfo
|
||||
* @param {false} centerInfo.center
|
||||
* @param {module:math.XYObject} centerInfo.newCtr
|
||||
* @listens module:svgcanvas.SvgCanvas#event:updateCanvas
|
||||
* @returns {void}
|
||||
*/
|
||||
function (win, { center, newCtr }) {
|
||||
this.updateCanvas(center, newCtr);
|
||||
}.bind(this)
|
||||
);
|
||||
this.svgCanvas.bind('contextset', this.contextChanged.bind(this));
|
||||
this.svgCanvas.bind('extension_added', this.extAdded.bind(this));
|
||||
this.svgCanvas.textActions.setInputElem($id('text'));
|
||||
|
||||
this.setBackground(this.configObj.pref('bkgd_color'), this.configObj.pref('bkgd_url'));
|
||||
|
||||
// update resolution option with actual resolution
|
||||
const res = this.svgCanvas.getResolution();
|
||||
if (this.configObj.curConfig.baseUnit !== 'px') {
|
||||
res.w = convertUnit(res.w) + this.configObj.curConfig.baseUnit;
|
||||
res.h = convertUnit(res.h) + this.configObj.curConfig.baseUnit;
|
||||
}
|
||||
$id('se-img-prop').setAttribute('dialog', 'close');
|
||||
$id('se-img-prop').setAttribute('title', this.svgCanvas.getDocumentTitle());
|
||||
$id('se-img-prop').setAttribute('width', res.w);
|
||||
$id('se-img-prop').setAttribute('height', res.h);
|
||||
$id('se-img-prop').setAttribute('save', this.configObj.pref('img_save'));
|
||||
|
||||
// Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
|
||||
const selElements = document.querySelectorAll("select");
|
||||
Array.from(selElements).forEach(function(element) {
|
||||
element.addEventListener('change', function(evt) {
|
||||
evt.currentTarget.blur();
|
||||
});
|
||||
});
|
||||
|
||||
// fired when user wants to move elements to another layer
|
||||
let promptMoveLayerOnce = false;
|
||||
$id('selLayerNames').addEventListener('change', function(evt) {
|
||||
const destLayer = evt.currentTarget.options[evt.currentTarget.selectedIndex].value;
|
||||
const confirmStr = self.i18next.t('notification.QmoveElemsToLayer').replace('%s', destLayer);
|
||||
/**
|
||||
* @param {boolean} ok
|
||||
* @returns {void}
|
||||
*/
|
||||
const moveToLayer = (ok) => {
|
||||
if (!ok) { return; }
|
||||
promptMoveLayerOnce = true;
|
||||
self.svgCanvas.moveSelectedToLayer(destLayer);
|
||||
self.svgCanvas.clearSelection();
|
||||
self.layersPanel.populateLayers();
|
||||
};
|
||||
if (destLayer) {
|
||||
if (promptMoveLayerOnce) {
|
||||
moveToLayer(true);
|
||||
} else {
|
||||
const ok = seConfirm(confirmStr);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
moveToLayer(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
$id('tool_font_family').addEventListener('change', function(evt) {
|
||||
self.svgCanvas.setFontFamily(evt.detail.value);
|
||||
});
|
||||
|
||||
$id('seg_type').addEventListener('change', function(evt) {
|
||||
self.svgCanvas.setSegType(evt.currentTarget.value);
|
||||
});
|
||||
|
||||
function addListenerMulti(element, eventNames, listener) {
|
||||
var events = eventNames.split(' ');
|
||||
for (var i=0, iLen=events.length; i<iLen; i++) {
|
||||
element.addEventListener(events[i], listener, false);
|
||||
}
|
||||
}
|
||||
|
||||
addListenerMulti($id('text'), 'keyup input', function(evt){
|
||||
self.svgCanvas.setTextContent(evt.currentTarget.value);
|
||||
});
|
||||
$id('image_url').addEventListener('change', function(evt) {
|
||||
self.setImageURL(evt.currentTarget.value);
|
||||
});
|
||||
|
||||
$id('link_url').addEventListener('change', function(evt) {
|
||||
if (evt.currentTarget.value.length) {
|
||||
self.svgCanvas.setLinkURL(evt.currentTarget.value);
|
||||
} else {
|
||||
self.svgCanvas.removeHyperlink();
|
||||
}
|
||||
});
|
||||
|
||||
$id('g_title').addEventListener('change', function(evt) {
|
||||
self.svgCanvas.setGroupTitle(evt.currentTarget.value);
|
||||
});
|
||||
|
||||
const wArea = this.workarea;
|
||||
|
||||
let lastX = null, lastY = null,
|
||||
panning = false, keypan = false;
|
||||
|
||||
$id('svgcanvas').addEventListener('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;
|
||||
});
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
$id('svgcanvas').addEventListener('mousemove', 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;
|
||||
});
|
||||
$id('svgcanvas').addEventListener('mousedown', function(evt) {
|
||||
if (evt.button === 1 || keypan === true) {
|
||||
panning = true;
|
||||
lastX = evt.clientX;
|
||||
lastY = evt.clientY;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', function() {
|
||||
panning = false;
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.nodeName !== 'BODY') return;
|
||||
if(e.code.toLowerCase() === 'space'){
|
||||
this.svgCanvas.spaceKey = keypan = true;
|
||||
e.preventDefault();
|
||||
} else if((e.key.toLowerCase() === 'shift') && (this.svgCanvas.getMode() === 'zoom')){
|
||||
this.workarea.style.cursor = zoomOutIcon;
|
||||
e.preventDefault();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.target.nodeName !== 'BODY') return;
|
||||
if(e.code.toLowerCase() === 'space'){
|
||||
this.svgCanvas.spaceKey = keypan = false;
|
||||
e.preventDefault();
|
||||
} else if((e.key.toLowerCase() === 'shift') && (this.svgCanvas.getMode() === 'zoom')){
|
||||
this.workarea.style.cursor = zoomInIcon;
|
||||
e.preventDefault();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @function module:SVGthis.setPanning
|
||||
* @param {boolean} active
|
||||
* @returns {void}
|
||||
*/
|
||||
this.setPanning = (active) => {
|
||||
this.svgCanvas.spaceKey = keypan = active;
|
||||
};
|
||||
let inp;
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const unfocus = () => {
|
||||
inp.blur();
|
||||
};
|
||||
|
||||
const liElems = document.getElementById('svg_editor').querySelectorAll('button, select, input:not(#text)');
|
||||
Array.prototype.forEach.call(liElems, function(el){
|
||||
el.addEventListener("focus", (e) => {
|
||||
inp = e.currentTarget;
|
||||
this.uiContext = 'toolbars';
|
||||
this.workarea.addEventListener('mousedown', unfocus);
|
||||
});
|
||||
el.addEventListener("blur", () => {
|
||||
this.uiContext = 'canvas';
|
||||
this.workarea.removeEventListener('mousedown', unfocus);
|
||||
// Go back to selecting text if in textedit mode
|
||||
if (this.svgCanvas.getMode() === 'textedit') {
|
||||
$id('text').focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
// ref: https://stackoverflow.com/a/1038781
|
||||
function getWidth() {
|
||||
return Math.max(
|
||||
document.body.scrollWidth,
|
||||
document.documentElement.scrollWidth,
|
||||
document.body.offsetWidth,
|
||||
document.documentElement.offsetWidth,
|
||||
document.documentElement.clientWidth
|
||||
);
|
||||
}
|
||||
|
||||
function getHeight() {
|
||||
return Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight,
|
||||
document.documentElement.offsetHeight,
|
||||
document.documentElement.clientHeight
|
||||
);
|
||||
}
|
||||
const winWh = {
|
||||
width: getWidth(),
|
||||
height: getHeight()
|
||||
};
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
Object.entries(winWh).forEach(([ type, val ]) => {
|
||||
const curval = (type === 'width') ? window.innerWidth - 15 : window.innerHeight;
|
||||
this.workarea['scroll' + (type === 'width' ? 'Left' : 'Top')] -= (curval - val) / 2;
|
||||
winWh[type] = curval;
|
||||
});
|
||||
});
|
||||
|
||||
this.workarea.addEventListener('scroll', () => {
|
||||
// TODO: jQuery's scrollLeft/Top() wouldn't require a null check
|
||||
this.rulers.manageScroll();
|
||||
});
|
||||
|
||||
$id('url_notice').addEventListener('click', () => {
|
||||
seAlert(this.title);
|
||||
});
|
||||
|
||||
$id('stroke_width').value = this.configObj.curConfig.initStroke.width;
|
||||
$id('opacity').value = this.configObj.curConfig.initOpacity * 100;
|
||||
const elements = document.getElementsByClassName("push_button");
|
||||
Array.from(elements).forEach(function(element) {
|
||||
element.addEventListener('mousedown', function(event) {
|
||||
if (!event.currentTarget.classList.contains('disabled')) {
|
||||
event.currentTarget.classList.add('push_button_pressed');
|
||||
event.currentTarget.classList.remove('push_button');
|
||||
}
|
||||
});
|
||||
element.addEventListener('mouseout', function(event) {
|
||||
event.currentTarget.classList.add('push_button');
|
||||
event.currentTarget.classList.remove('push_button_pressed');
|
||||
});
|
||||
element.addEventListener('mouseup', function(event) {
|
||||
event.currentTarget.classList.add('push_button');
|
||||
event.currentTarget.classList.remove('push_button_pressed');
|
||||
});
|
||||
});
|
||||
|
||||
this.layersPanel.populateLayers();
|
||||
|
||||
const centerCanvas = () => {
|
||||
// this centers the canvas vertically in the this.workarea (horizontal handled in CSS)
|
||||
this.workarea.style.lineHeight = this.workarea.style.height;
|
||||
};
|
||||
|
||||
addListenerMulti(window, 'load resize', centerCanvas);
|
||||
|
||||
// Prevent browser from erroneously repopulating fields
|
||||
const inputEles = document.querySelectorAll('input');
|
||||
Array.from(inputEles).forEach(function(inputEle) {
|
||||
inputEle.setAttribute('autocomplete', 'off');
|
||||
});
|
||||
const selectEles = document.querySelectorAll('select');
|
||||
Array.from(selectEles).forEach(function(inputEle) {
|
||||
inputEle.setAttribute('autocomplete', 'off');
|
||||
});
|
||||
|
||||
$id('se-svg-editor-dialog').addEventListener('change', function (e) {
|
||||
if (e?.detail?.copy === 'click') {
|
||||
this.cancelOverlays(e);
|
||||
} else if (e?.detail?.dialog === 'closed') {
|
||||
this.hideSourceEditor();
|
||||
} else {
|
||||
this.saveSourceEditor(e);
|
||||
}
|
||||
}.bind(this));
|
||||
$id('se-cmenu_canvas').addEventListener('change', function (e) {
|
||||
const action = e?.detail?.trigger;
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
this.svgCanvas.deleteSelectedElements();
|
||||
break;
|
||||
case 'cut':
|
||||
this.cutSelected();
|
||||
break;
|
||||
case 'copy':
|
||||
this.copySelected();
|
||||
break;
|
||||
case 'paste':
|
||||
this.svgCanvas.pasteElements();
|
||||
break;
|
||||
case 'paste_in_place':
|
||||
this.svgCanvas.pasteElements('in_place');
|
||||
break;
|
||||
case 'group':
|
||||
case 'group_elements':
|
||||
this.svgCanvas.groupSelectedElements();
|
||||
break;
|
||||
case 'ungroup':
|
||||
this.svgCanvas.ungroupSelectedElement();
|
||||
break;
|
||||
case 'move_front':
|
||||
this.svgCanvas.moveToTopSelectedElement();
|
||||
break;
|
||||
case 'move_up':
|
||||
this.moveUpDownSelected('Up');
|
||||
break;
|
||||
case 'move_down':
|
||||
this.moveUpDownSelected('Down');
|
||||
break;
|
||||
case 'move_back':
|
||||
this.svgCanvas.moveToBottomSelectedElement();
|
||||
break;
|
||||
default:
|
||||
if (hasCustomHandler(action)) {
|
||||
getCustomHandler(action).call();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// Select given tool
|
||||
this.ready(function () {
|
||||
const preTool = $id(`tool_${this.configObj.curConfig.initTool}`);
|
||||
const regTool = $id(this.configObj.curConfig.initTool);
|
||||
const selectTool = $id('tool_select');
|
||||
const $editDialog = $id('se-edit-prefs');
|
||||
|
||||
if (preTool) {
|
||||
preTool.click();
|
||||
} else if (regTool) {
|
||||
regTool.click();
|
||||
} else {
|
||||
selectTool.click();
|
||||
}
|
||||
|
||||
if (this.configObj.curConfig.wireframe) {
|
||||
$id('tool_wireframe').click();
|
||||
}
|
||||
|
||||
$id('rulers').style.display = (this.configObj.curConfig.showRulers) ? 'block' : 'none';
|
||||
|
||||
if (this.configObj.curConfig.showRulers) {
|
||||
$editDialog.setAttribute('showrulers', true);
|
||||
}
|
||||
|
||||
if (this.configObj.curConfig.baseUnit) {
|
||||
$editDialog.setAttribute('baseunit', this.configObj.curConfig.baseUnit);
|
||||
}
|
||||
|
||||
if (this.configObj.curConfig.gridSnapping) {
|
||||
$editDialog.setAttribute('gridsnappingon', true);
|
||||
}
|
||||
|
||||
if (this.configObj.curConfig.snappingStep) {
|
||||
$editDialog.setAttribute('gridsnappingstep', this.configObj.curConfig.snappingStep);
|
||||
}
|
||||
|
||||
if (this.configObj.curConfig.gridColor) {
|
||||
$editDialog.setAttribute('gridcolor', this.configObj.curConfig.gridColor);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// zoom
|
||||
$id('zoom').value = (this.svgCanvas.getZoom() * 100).toFixed(1);
|
||||
this.canvMenu.setAttribute('disableallmenu', true);
|
||||
this.canvMenu.setAttribute('enablemenuitems', '#delete,#cut,#copy');
|
||||
|
||||
this.enableOrDisableClipboard();
|
||||
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key !== 'svgedit_clipboard') { return; }
|
||||
|
||||
this.enableOrDisableClipboard();
|
||||
}.bind(this));
|
||||
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
// Suppress warning if page is empty
|
||||
if (undoMgr.getUndoStackSize() === 0) {
|
||||
this.showSaveWarning = false;
|
||||
}
|
||||
|
||||
// showSaveWarning is set to 'false' when the page is saved.
|
||||
if (!this.configObj.curConfig.no_save_warning && this.showSaveWarning) {
|
||||
// Browser already asks question about closing the page
|
||||
e.returnValue = this.i18next.t('notification.unsavedChanges'); // Firefox needs this when beforeunload set by addEventListener (even though message is not used)
|
||||
return this.i18next.t('notification.unsavedChanges');
|
||||
}
|
||||
return true;
|
||||
}.bind(this));
|
||||
|
||||
// Use HTML5 File API: http://www.w3.org/TR/FileAPI/
|
||||
// if browser has HTML5 File API support, then we will show the open menu item
|
||||
// and provide a file input to click. When that change event fires, it will
|
||||
// get the text contents of the file and send it to the canvas
|
||||
if (window.FileReader) {
|
||||
/**
|
||||
* @param {Event} e
|
||||
* @returns {void}
|
||||
*/
|
||||
const editorObj = this;
|
||||
const importImage = function (e) {
|
||||
document.getElementById('se-prompt-dialog').title = editorObj.i18next.t('notification.loadingImage');
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const file = (e.type === 'drop') ? e.dataTransfer.files[0] : this.files[0];
|
||||
if (!file) {
|
||||
document.getElementById('se-prompt-dialog').setAttribute('close', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.type.includes('image')) {
|
||||
return;
|
||||
}
|
||||
// Detected an image
|
||||
// svg handling
|
||||
let reader;
|
||||
if (file.type.includes('svg')) {
|
||||
reader = new FileReader();
|
||||
reader.onloadend = function (ev) {
|
||||
const newElement = editorObj.svgCanvas.importSvgString(ev.target.result, true);
|
||||
editorObj.svgCanvas.ungroupSelectedElement();
|
||||
editorObj.svgCanvas.ungroupSelectedElement();
|
||||
editorObj.svgCanvas.groupSelectedElements();
|
||||
editorObj.svgCanvas.alignSelectedElements('m', 'page');
|
||||
editorObj.svgCanvas.alignSelectedElements('c', 'page');
|
||||
// highlight imported element, otherwise we get strange empty selectbox
|
||||
editorObj.svgCanvas.selectOnly([ newElement ]);
|
||||
document.getElementById('se-prompt-dialog').setAttribute('close', true);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
// bitmap handling
|
||||
reader = new FileReader();
|
||||
reader.onloadend = function ({ target: { result } }) {
|
||||
/**
|
||||
* Insert the new image until we know its dimensions.
|
||||
* @param {Float} imageWidth
|
||||
* @param {Float} imageHeight
|
||||
* @returns {void}
|
||||
*/
|
||||
const insertNewImage = function (imageWidth, imageHeight) {
|
||||
const newImage = editorObj.svgCanvas.addSVGElementFromJson({
|
||||
element: 'image',
|
||||
attr: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
id: editorObj.svgCanvas.getNextId(),
|
||||
style: 'pointer-events:inherit'
|
||||
}
|
||||
});
|
||||
editorObj.svgCanvas.setHref(newImage, result);
|
||||
editorObj.svgCanvas.selectOnly([ newImage ]);
|
||||
editorObj.svgCanvas.alignSelectedElements('m', 'page');
|
||||
editorObj.svgCanvas.alignSelectedElements('c', 'page');
|
||||
editorObj.topPanelHandlers.updateContextPanel();
|
||||
document.getElementById('se-prompt-dialog').setAttribute('close', true);
|
||||
};
|
||||
// create dummy img so we know the default dimensions
|
||||
let imgWidth = 100;
|
||||
let imgHeight = 100;
|
||||
const img = new Image();
|
||||
img.style.opacity = 0;
|
||||
img.addEventListener('load', () => {
|
||||
imgWidth = img.offsetWidth || img.naturalWidth || img.width;
|
||||
imgHeight = img.offsetHeight || img.naturalHeight || img.height;
|
||||
insertNewImage(imgWidth, imgHeight);
|
||||
});
|
||||
img.src = result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
this.workarea.addEventListener('dragenter', this.onDragEnter);
|
||||
this.workarea.addEventListener('dragover', this.onDragOver);
|
||||
this.workarea.addEventListener('dragleave', this.onDragLeave);
|
||||
this.workarea.addEventListener('drop', importImage);
|
||||
const imgImport = document.createElement('input');
|
||||
imgImport.type="file";
|
||||
imgImport.addEventListener('change', importImage);
|
||||
window.addEventListener('importImages', () => imgImport.click());
|
||||
}
|
||||
|
||||
this.updateCanvas(true);
|
||||
// Load extensions
|
||||
this.extAndLocaleFunc();
|
||||
// Defer injection to wait out initial menu processing. This probably goes
|
||||
// away once all context menu behavior is brought to context menu.
|
||||
this.ready(() => {
|
||||
injectExtendedContextMenuItemsIntoDom();
|
||||
});
|
||||
// run callbacks stored by this.ready
|
||||
await this.runCallbacks();
|
||||
window.addEventListener('message', this.messageListener.bind(this));
|
||||
}
|
||||
/**
|
||||
* @fires module:svgcanvas.SvgCanvas#event:ext_addLangData
|
||||
* @fires module:svgcanvas.SvgCanvas#event:ext_langReady
|
||||
* @fires module:svgcanvas.SvgCanvas#event:ext_langChanged
|
||||
* @fires module:svgcanvas.SvgCanvas#event:extensions_added
|
||||
* @returns {Promise<module:locale.LangAndData>} Resolves to result of {@link module:locale.readLang}
|
||||
*/
|
||||
async extAndLocaleFunc () {
|
||||
this.$svgEditor.style.visibility = 'visible';
|
||||
try {
|
||||
// load standard extensions
|
||||
await Promise.all(
|
||||
this.configObj.curConfig.extensions.map(async (extname) => {
|
||||
/**
|
||||
* @tutorial ExtensionDocs
|
||||
* @typedef {PlainObject} module:SVGthis.ExtensionObject
|
||||
* @property {string} [name] Name of the extension. Used internally; no need for i18n. Defaults to extension name without beginning "ext-" or ending ".js".
|
||||
* @property {module:svgcanvas.ExtensionInitCallback} [init]
|
||||
*/
|
||||
try {
|
||||
/**
|
||||
* @type {module:SVGthis.ExtensionObject}
|
||||
*/
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
const imported = await import(`./extensions/${encodeURIComponent(extname)}/${encodeURIComponent(extname)}.js`);
|
||||
const { name = extname, init: initfn } = imported.default;
|
||||
return this.addExtension(name, (initfn && initfn.bind(this)), { $, langParam: 'en' }); /** @todo change to current lng */
|
||||
} catch (err) {
|
||||
// Todo: Add config to alert any errors
|
||||
console.error('Extension failed to load: ' + extname + '; ', err);
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
// load user extensions (given as pathNames)
|
||||
await Promise.all(
|
||||
this.configObj.curConfig.userExtensions.map(async (extPathName) => {
|
||||
/**
|
||||
* @tutorial ExtensionDocs
|
||||
* @typedef {PlainObject} module:SVGthis.ExtensionObject
|
||||
* @property {string} [name] Name of the extension. Used internally; no need for i18n. Defaults to extension name without beginning "ext-" or ending ".js".
|
||||
* @property {module:svgcanvas.ExtensionInitCallback} [init]
|
||||
*/
|
||||
try {
|
||||
/**
|
||||
* @type {module:SVGthis.ExtensionObject}
|
||||
*/
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
const imported = await import(encodeURI(extPathName));
|
||||
const { name, init: initfn } = imported.default;
|
||||
return this.addExtension(name, (initfn && initfn.bind(this)), { $ });
|
||||
} catch (err) {
|
||||
// Todo: Add config to alert any errors
|
||||
console.error('Extension failed to load: ' + extPathName + '; ', err);
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
this.svgCanvas.bind(
|
||||
'extensions_added',
|
||||
/**
|
||||
* @param {external:Window} _win
|
||||
* @param {module:svgcanvas.SvgCanvas#event:extensions_added} _data
|
||||
* @listens module:SvgCanvas#event:extensions_added
|
||||
* @returns {void}
|
||||
*/
|
||||
(_win, _data) => {
|
||||
this.extensionsAdded = true;
|
||||
this.setAll();
|
||||
|
||||
if (this.storagePromptState === 'ignore') {
|
||||
this.updateCanvas(true);
|
||||
}
|
||||
|
||||
this.messageQueue.forEach(
|
||||
/**
|
||||
* @param {module:svgcanvas.SvgCanvas#event:message} messageObj
|
||||
* @fires module:svgcanvas.SvgCanvas#event:message
|
||||
* @returns {void}
|
||||
*/
|
||||
(messageObj) => {
|
||||
this.svgCanvas.call('message', messageObj);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
this.svgCanvas.call('extensions_added');
|
||||
} catch (err) {
|
||||
// Todo: Report errors through the UI
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PlainObject} info
|
||||
* @param {any} info.data
|
||||
* @param {string} info.origin
|
||||
* @fires module:svgcanvas.SvgCanvas#event:message
|
||||
* @returns {void}
|
||||
*/
|
||||
messageListener ({ data, origin }) {
|
||||
const messageObj = { data, origin };
|
||||
if (!this.extensionsAdded) {
|
||||
this.messageQueue.push(messageObj);
|
||||
} else {
|
||||
// Extensions can handle messages at this stage with their own
|
||||
// canvas `message` listeners
|
||||
this.svgCanvas.call('message', messageObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default EditorStartup;
|
||||
400
src/editor/MainMenu.js
Normal file
@@ -0,0 +1,400 @@
|
||||
/* globals seConfirm, seAlert */
|
||||
import SvgCanvas from "../svgcanvas/svgcanvas.js";
|
||||
import { convertUnit, isValidUnit } from '../common/units.js';
|
||||
import { isChrome } from '../common/browser.js';
|
||||
|
||||
const { $id } = SvgCanvas;
|
||||
const homePage = 'https://github.com/SVG-Edit/svgedit';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class MainMenu {
|
||||
/**
|
||||
* @param {PlainObject} editor svgedit handler
|
||||
*/
|
||||
constructor(editor) {
|
||||
this.editor = editor;
|
||||
/**
|
||||
* @type {Integer}
|
||||
*/
|
||||
this.exportWindowCt = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @fires module:svgcanvas.SvgCanvas#event:ext_onNewDocument
|
||||
* @returns {void}
|
||||
*/
|
||||
async clickClear() {
|
||||
const [ x, y ] = this.editor.configObj.curConfig.dimensions;
|
||||
const ok = await seConfirm(this.editor.i18next.t('notification.QwantToClear'));
|
||||
if (ok === "Cancel") {
|
||||
return;
|
||||
}
|
||||
this.editor.leftPanel.clickSelect();
|
||||
this.editor.svgCanvas.clear();
|
||||
this.editor.svgCanvas.setResolution(x, y);
|
||||
this.editor.updateCanvas(true);
|
||||
this.editor.zoomImage();
|
||||
this.editor.layersPanel.populateLayers();
|
||||
this.editor.topPanel.updateContextPanel();
|
||||
this.editor.svgCanvas.runExtensions("onNewDocument");
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
hideDocProperties() {
|
||||
const $imgDialog = document.getElementById("se-img-prop");
|
||||
$imgDialog.setAttribute("dialog", "close");
|
||||
$imgDialog.setAttribute("save", this.editor.configObj.pref("img_save"));
|
||||
this.editor.docprops = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
hidePreferences() {
|
||||
const $editDialog = document.getElementById("se-edit-prefs");
|
||||
$editDialog.setAttribute("dialog", "close");
|
||||
this.editor.configObj.preferences = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} e
|
||||
* @returns {boolean} Whether there were problems saving the document properties
|
||||
*/
|
||||
saveDocProperties(e) {
|
||||
// set title
|
||||
const { title, w, h, save } = e.detail;
|
||||
// set document title
|
||||
this.editor.svgCanvas.setDocumentTitle(title);
|
||||
|
||||
if (w !== "fit" && !isValidUnit("width", w)) {
|
||||
seAlert(this.editor.i18next.t('notification.invalidAttrValGiven'));
|
||||
return false;
|
||||
}
|
||||
if (h !== "fit" && !isValidUnit("height", h)) {
|
||||
seAlert(this.editor.i18next.t('notification.invalidAttrValGiven'));
|
||||
return false;
|
||||
}
|
||||
if (!this.editor.svgCanvas.setResolution(w, h)) {
|
||||
seAlert(this.editor.i18next.t('notification.noContentToFitTo'));
|
||||
return false;
|
||||
}
|
||||
// Set image save option
|
||||
this.editor.configObj.pref("img_save", save);
|
||||
this.editor.updateCanvas();
|
||||
this.hideDocProperties();
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Save user preferences based on current values in the UI.
|
||||
* @param {Event} e
|
||||
* @function module:SVGthis.savePreferences
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async savePreferences(e) {
|
||||
const {
|
||||
lang,
|
||||
bgcolor,
|
||||
bgurl,
|
||||
gridsnappingon,
|
||||
gridsnappingstep,
|
||||
gridcolor,
|
||||
showrulers,
|
||||
baseunit
|
||||
} = e.detail;
|
||||
// Set background
|
||||
this.editor.setBackground(bgcolor, bgurl);
|
||||
|
||||
// set language
|
||||
if (lang && lang !== this.editor.configObj.pref("lang")) {
|
||||
this.editor.configObj.pref("lang", lang);
|
||||
seAlert('Changing the language needs reload');
|
||||
}
|
||||
|
||||
// set grid setting
|
||||
this.editor.configObj.curConfig.gridSnapping = gridsnappingon;
|
||||
this.editor.configObj.curConfig.snappingStep = gridsnappingstep;
|
||||
this.editor.configObj.curConfig.gridColor = gridcolor;
|
||||
this.editor.configObj.curConfig.showRulers = showrulers;
|
||||
$id('rulers').style.display = (this.editor.configObj.curConfig.showRulers) ? 'block' : 'none';
|
||||
if (this.editor.configObj.curConfig.showRulers) {
|
||||
this.editor.rulers.updateRulers();
|
||||
}
|
||||
this.editor.configObj.curConfig.baseUnit = baseunit;
|
||||
this.editor.svgCanvas.setConfig(this.editor.configObj.curConfig);
|
||||
this.editor.updateCanvas();
|
||||
this.hidePreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
clickSave() {
|
||||
// In the future, more options can be provided here
|
||||
const saveOpts = {
|
||||
images: this.editor.configObj.pref("img_save"),
|
||||
round_digits: 6
|
||||
};
|
||||
this.editor.svgCanvas.save(saveOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
* @returns {Promise<void>} Resolves to `undefined`
|
||||
*/
|
||||
async clickExport(e) {
|
||||
if (e?.detail?.trigger !== "ok" || e?.detail?.imgType === undefined) {
|
||||
return;
|
||||
}
|
||||
const imgType = e?.detail?.imgType;
|
||||
const quality = e?.detail?.quality ? e?.detail?.quality / 100 : 1;
|
||||
// Open placeholder window (prevents popup)
|
||||
let exportWindowName;
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const openExportWindow = () => {
|
||||
const loadingImage = this.editor.i18next.t('notification.loadingImage');
|
||||
if (this.editor.configObj.curConfig.exportWindowType === "new") {
|
||||
this.editor.exportWindowCt++;
|
||||
}
|
||||
this.editor.exportWindowName =
|
||||
this.editor.configObj.curConfig.canvasName + this.editor.exportWindowCt;
|
||||
let popHTML, popURL;
|
||||
if (this.editor.loadingURL) {
|
||||
popURL = this.editor.loadingURL;
|
||||
} else {
|
||||
popHTML = `<!DOCTYPE html><html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>${loadingImage}</title>
|
||||
</head>
|
||||
<body><h1>${loadingImage}</h1></body>
|
||||
<html>`;
|
||||
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," + popHTML;
|
||||
}
|
||||
this.editor.loadingURL = popURL;
|
||||
}
|
||||
this.editor.exportWindow = window.open(
|
||||
popURL,
|
||||
this.editor.exportWindowName
|
||||
);
|
||||
};
|
||||
const chrome = isChrome();
|
||||
if (imgType === "PDF") {
|
||||
if (!this.editor.customExportPDF && !chrome) {
|
||||
openExportWindow();
|
||||
}
|
||||
this.editor.svgCanvas.exportPDF(exportWindowName);
|
||||
} else {
|
||||
if (!this.editor.customExportImage) {
|
||||
openExportWindow();
|
||||
}
|
||||
/* const results = */ await this.editor.svgCanvas.rasterExport(
|
||||
imgType,
|
||||
quality,
|
||||
this.editor.exportWindowName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, this.editor.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}
|
||||
*/
|
||||
clickOpen() {
|
||||
this.editor.svgCanvas.open();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
clickImport() {
|
||||
/* empty fn */
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
showDocProperties() {
|
||||
if (this.editor.docprops) {
|
||||
return;
|
||||
}
|
||||
this.editor.docprops = true;
|
||||
const $imgDialog = document.getElementById("se-img-prop");
|
||||
|
||||
// update resolution option with actual resolution
|
||||
const resolution = this.editor.svgCanvas.getResolution();
|
||||
if (this.editor.configObj.curConfig.baseUnit !== "px") {
|
||||
resolution.w =
|
||||
convertUnit(resolution.w) + this.editor.configObj.curConfig.baseUnit;
|
||||
resolution.h =
|
||||
convertUnit(resolution.h) + this.editor.configObj.curConfig.baseUnit;
|
||||
}
|
||||
$imgDialog.setAttribute("save", this.editor.configObj.pref("img_save"));
|
||||
$imgDialog.setAttribute("width", resolution.w);
|
||||
$imgDialog.setAttribute("height", resolution.h);
|
||||
$imgDialog.setAttribute("title", this.editor.svgCanvas.getDocumentTitle());
|
||||
$imgDialog.setAttribute("dialog", "open");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
showPreferences() {
|
||||
if (this.editor.configObj.preferences) {
|
||||
return;
|
||||
}
|
||||
this.editor.configObj.preferences = true;
|
||||
const $editDialog = document.getElementById("se-edit-prefs");
|
||||
// Update background color with current one
|
||||
const canvasBg = this.editor.configObj.curPrefs.bkgd_color;
|
||||
const url = this.editor.configObj.pref("bkgd_url");
|
||||
if (url) {
|
||||
$editDialog.setAttribute("bgurl", url);
|
||||
}
|
||||
$editDialog.setAttribute(
|
||||
"gridsnappingon",
|
||||
this.editor.configObj.curConfig.gridSnapping
|
||||
);
|
||||
$editDialog.setAttribute(
|
||||
"gridsnappingstep",
|
||||
this.editor.configObj.curConfig.snappingStep
|
||||
);
|
||||
$editDialog.setAttribute(
|
||||
"gridcolor",
|
||||
this.editor.configObj.curConfig.gridColor
|
||||
);
|
||||
$editDialog.setAttribute("canvasbg", canvasBg);
|
||||
$editDialog.setAttribute("dialog", "open");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
openHomePage() {
|
||||
window.open(homePage, "_blank");
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {module}
|
||||
*/
|
||||
init() {
|
||||
// add Top panel
|
||||
const template = document.createElement("template");
|
||||
const { i18next } = this.editor;
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<se-menu id="main_button" label="SVG-Edit" src="./images/logo.svg" alt="logo">
|
||||
<!-- File-like buttons: New, Save, Source -->
|
||||
<se-menu-item id="tool_clear" label="${i18next.t('tools.new_doc')}" shortcut="N" src="./images/new.svg">
|
||||
</se-menu-item>
|
||||
<se-menu-item id="tool_open" label="${i18next.t('tools.open_doc')}" src="./images/open.svg">
|
||||
</se-menu-item>
|
||||
<se-menu-item id="tool_save" label="${i18next.t('tools.save_doc')}" shortcut="S" src="./images/saveImg.svg">
|
||||
</se-menu-item>
|
||||
<se-menu-item id="tool_import" label="${i18next.t('tools.import_doc')}" src="./images/importImg.svg"></se-menu-item>
|
||||
<se-menu-item id="tool_export" label="${i18next.t('tools.export_img')}" src="./images/export.svg"></se-menu-item>
|
||||
<se-menu-item id="tool_docprops" label="${i18next.t('tools.docprops')}" shortcut="D" src="./images/docprop.svg">
|
||||
</se-menu-item>
|
||||
<se-menu-item id="tool_editor_prefs" label="${i18next.t('config.editor_prefs')}" src="./images/editPref.svg">
|
||||
</se-menu-item>
|
||||
<se-menu-item id="tool_editor_homepage" label="${i18next.t('tools.editor_homepage')}" src="./images/logo.svg">
|
||||
</se-menu-item>
|
||||
</se-menu>
|
||||
`;
|
||||
this.editor.$svgEditor.append(template.content.cloneNode(true));
|
||||
|
||||
// register action to main menu entries
|
||||
/**
|
||||
* Associate all button actions as well as non-button keyboard shortcuts.
|
||||
*/
|
||||
|
||||
$id("tool_clear").addEventListener("click", this.clickClear.bind(this));
|
||||
$id("tool_open").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.clickOpen();
|
||||
window.dispatchEvent(new CustomEvent("openImage"));
|
||||
});
|
||||
$id("tool_import").addEventListener("click", () => {
|
||||
this.clickImport();
|
||||
window.dispatchEvent(new CustomEvent("importImages"));
|
||||
});
|
||||
$id("tool_save").addEventListener(
|
||||
"click",
|
||||
function() {
|
||||
const $editorDialog = document.getElementById("se-svg-editor-dialog");
|
||||
const editingsource = $editorDialog.getAttribute("dialog") === "open";
|
||||
if (editingsource) {
|
||||
this.saveSourceEditor();
|
||||
} else {
|
||||
this.clickSave();
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
// this.clickExport.bind(this)
|
||||
$id("tool_export").addEventListener("click", function() {
|
||||
document
|
||||
.getElementById("se-export-dialog")
|
||||
.setAttribute("dialog", "open");
|
||||
});
|
||||
$id("se-export-dialog").addEventListener(
|
||||
"change",
|
||||
this.clickExport.bind(this)
|
||||
);
|
||||
$id("tool_docprops").addEventListener(
|
||||
"click",
|
||||
this.showDocProperties.bind(this)
|
||||
);
|
||||
$id("tool_editor_prefs").addEventListener(
|
||||
"click",
|
||||
this.showPreferences.bind(this)
|
||||
);
|
||||
$id("tool_editor_homepage").addEventListener(
|
||||
"click",
|
||||
this.openHomePage.bind(this)
|
||||
);
|
||||
$id("se-img-prop").addEventListener(
|
||||
"change",
|
||||
function(e) {
|
||||
if (e.detail.dialog === "closed") {
|
||||
this.hideDocProperties();
|
||||
} else {
|
||||
this.saveDocProperties(e);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
$id("se-edit-prefs").addEventListener(
|
||||
"change",
|
||||
function(e) {
|
||||
if (e.detail.dialog === "closed") {
|
||||
this.hidePreferences();
|
||||
} else {
|
||||
this.savePreferences(e);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MainMenu;
|
||||
202
src/editor/Rulers.js
Normal file
@@ -0,0 +1,202 @@
|
||||
import { getTypeMap } from '../common/units.js';
|
||||
import rulersTemplate from './templates/rulersTemplate.js';
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Rulers {
|
||||
/**
|
||||
* @type {Module}
|
||||
*/
|
||||
constructor (editor) {
|
||||
// Make [1,2,5] array
|
||||
this.rulerIntervals = [];
|
||||
for (let i = 0.1; i < 1e5; i *= 10) {
|
||||
this.rulerIntervals.push(i);
|
||||
this.rulerIntervals.push(2 * i);
|
||||
this.rulerIntervals.push(5 * i);
|
||||
}
|
||||
this.svgCanvas = editor.svgCanvas;
|
||||
this.editor = editor;
|
||||
// add rulers component to the DOM
|
||||
document.body.append(rulersTemplate.content.cloneNode(true));
|
||||
}
|
||||
/**
|
||||
* @type {Module}
|
||||
*/
|
||||
manageScroll () {
|
||||
const rulerX = document.getElementById('ruler_x');
|
||||
const rulerY = document.getElementById('ruler_y');
|
||||
if (rulerX) rulerX.scrollLeft = this.editor.workarea.scrollLeft;
|
||||
if (rulerY) rulerY.scrollTop = this.editor.workarea.scrollTop;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLDivElement} [scanvas]
|
||||
* @param {Float} [zoom]
|
||||
* @returns {void}
|
||||
*/
|
||||
updateRulers (scanvas, zoom) {
|
||||
if (!zoom) { zoom = this.svgCanvas.getZoom(); }
|
||||
if (!scanvas) { scanvas = document.getElementById('svgcanvas'); }
|
||||
|
||||
let d, i;
|
||||
const limit = 30000;
|
||||
const contentElem = this.svgCanvas.getContentElem();
|
||||
const units = getTypeMap();
|
||||
const unit = units[this.editor.configObj.curConfig.baseUnit]; // 1 = 1px
|
||||
|
||||
// draw x ruler then y ruler
|
||||
for (d = 0; d < 2; d++) {
|
||||
const isX = (d === 0);
|
||||
const dim = isX ? 'x' : 'y';
|
||||
const lentype = isX ? 'width' : 'height';
|
||||
const contentDim = Number(contentElem.getAttribute(dim));
|
||||
const { $id } = this.svgCanvas;
|
||||
const $hcanvOrig = $id('ruler_' + dim).querySelector('canvas');
|
||||
|
||||
// Bit of a hack to fully clear the canvas in Safari & IE9
|
||||
const $hcanv = $hcanvOrig.cloneNode(true);
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
$hcanvOrig.replaceWith($hcanv);
|
||||
|
||||
const hcanv = $hcanv;
|
||||
|
||||
// Set the canvas size to the width of the container
|
||||
let rulerLen;
|
||||
if(lentype === 'width'){
|
||||
rulerLen = parseFloat(getComputedStyle(scanvas, null).width.replace("px", ""));
|
||||
} else if(lentype === 'height'){
|
||||
rulerLen = parseFloat(getComputedStyle(scanvas, null).height.replace("px", ""));
|
||||
}
|
||||
const totalLen = rulerLen;
|
||||
hcanv.parentNode.style[lentype] = totalLen + 'px';
|
||||
let ctx = hcanv.getContext('2d');
|
||||
let ctxArr, num, ctxArrNum;
|
||||
|
||||
ctx.fillStyle = 'rgb(200,0,0)';
|
||||
ctx.fillRect(0, 0, hcanv.width, hcanv.height);
|
||||
|
||||
// Remove any existing canvasses
|
||||
const elements = Array.prototype.filter.call($hcanv.parentNode.children, function(child){
|
||||
return child !== $hcanv;
|
||||
});
|
||||
Array.from(elements).forEach(function(element) {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
// Create multiple canvases when necessary (due to browser limits)
|
||||
if (rulerLen >= limit) {
|
||||
ctxArrNum = Number.parseInt(rulerLen / limit) + 1;
|
||||
ctxArr = [];
|
||||
ctxArr[0] = ctx;
|
||||
let copy;
|
||||
for (i = 1; i < ctxArrNum; i++) {
|
||||
hcanv[lentype] = limit;
|
||||
copy = hcanv.cloneNode(true);
|
||||
hcanv.parentNode.append(copy);
|
||||
ctxArr[i] = copy.getContext('2d');
|
||||
}
|
||||
|
||||
copy[lentype] = rulerLen % limit;
|
||||
|
||||
// set copy width to last
|
||||
rulerLen = limit;
|
||||
}
|
||||
|
||||
hcanv[lentype] = rulerLen;
|
||||
|
||||
const uMulti = unit * zoom;
|
||||
|
||||
// Calculate the main number interval
|
||||
const rawM = 50 / uMulti;
|
||||
let multi = 1;
|
||||
for (i = 0; i < this.rulerIntervals.length; i++) {
|
||||
num = this.rulerIntervals[i];
|
||||
multi = num;
|
||||
if (rawM <= num) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const bigInt = multi * uMulti;
|
||||
|
||||
ctx.font = '9px sans-serif';
|
||||
|
||||
let rulerD = ((contentDim / uMulti) % multi) * uMulti;
|
||||
let labelPos = rulerD - bigInt;
|
||||
// draw big intervals
|
||||
let ctxNum = 0;
|
||||
while (rulerD < totalLen) {
|
||||
labelPos += bigInt;
|
||||
// const realD = rulerD - contentDim; // Currently unused
|
||||
|
||||
const curD = Math.round(rulerD) + 0.5;
|
||||
if (isX) {
|
||||
ctx.moveTo(curD, 15);
|
||||
ctx.lineTo(curD, 0);
|
||||
} else {
|
||||
ctx.moveTo(15, curD);
|
||||
ctx.lineTo(0, curD);
|
||||
}
|
||||
|
||||
num = (labelPos - contentDim) / uMulti;
|
||||
let label;
|
||||
if (multi >= 1) {
|
||||
label = Math.round(num);
|
||||
} else {
|
||||
const decs = String(multi).split('.')[1].length;
|
||||
label = num.toFixed(decs);
|
||||
}
|
||||
|
||||
// Change 1000s to Ks
|
||||
if (label !== 0 && label !== 1000 && label % 1000 === 0) {
|
||||
label = (label / 1000) + 'K';
|
||||
}
|
||||
|
||||
if (isX) {
|
||||
ctx.fillText(label, rulerD + 2, 8);
|
||||
} else {
|
||||
// draw label vertically
|
||||
const str = String(label).split('');
|
||||
for (i = 0; i < str.length; i++) {
|
||||
ctx.fillText(str[i], 1, (rulerD + 9) + i * 9);
|
||||
}
|
||||
}
|
||||
|
||||
const part = bigInt / 10;
|
||||
// draw the small intervals
|
||||
for (i = 1; i < 10; i++) {
|
||||
let subD = Math.round(rulerD + part * i) + 0.5;
|
||||
if (ctxArr && subD > rulerLen) {
|
||||
ctxNum++;
|
||||
ctx.stroke();
|
||||
if (ctxNum >= ctxArrNum) {
|
||||
i = 10;
|
||||
rulerD = totalLen;
|
||||
continue;
|
||||
}
|
||||
ctx = ctxArr[ctxNum];
|
||||
rulerD -= limit;
|
||||
subD = Math.round(rulerD + part * i) + 0.5;
|
||||
}
|
||||
|
||||
// odd lines are slighly longer
|
||||
const lineNum = (i % 2) ? 12 : 10;
|
||||
if (isX) {
|
||||
ctx.moveTo(subD, 15);
|
||||
ctx.lineTo(subD, lineNum);
|
||||
} else {
|
||||
ctx.moveTo(15, subD);
|
||||
ctx.lineTo(lineNum, subD);
|
||||
}
|
||||
}
|
||||
rulerD += bigInt;
|
||||
}
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Rulers;
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
|
||||
<link rel="icon" type="image/png" href="images/logo.png"/>
|
||||
<link rel="icon" type="image/png" href="images/logo.svg"/>
|
||||
<link rel="stylesheet" href="svgedit.css"/>
|
||||
<title>Browser does not support SVG | SVG-edit</title>
|
||||
<style>
|
||||
@@ -35,7 +35,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img id="logo" src="images/logo.png" width="48" height="48" alt="SVG-edit logo" />
|
||||
<img id="logo" src="images/logo.svg" width="48" height="48" alt="SVG-edit logo" />
|
||||
<p>Sorry, but your browser does not support SVG. Below is a list of
|
||||
alternate browsers and versions that support SVG and SVG-edit
|
||||
(from <a href="https://caniuse.com/#cats=SVG">caniuse.com</a>).
|
||||
|
||||
144
src/editor/components/PaintBox.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import { jGraduate } from './jgraduate/jQuery.jGraduate.js';
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class PaintBox {
|
||||
/**
|
||||
* @param {string|Element|external:jQuery} container
|
||||
* @param {"fill"} type
|
||||
*/
|
||||
constructor (container, type) {
|
||||
// set up gradients to be used for the buttons
|
||||
const svgdocbox = new DOMParser().parseFromString(
|
||||
`<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14">
|
||||
<rect
|
||||
fill="#000000" opacity="1" width="14" height="14"/>
|
||||
<defs><linearGradient id="gradbox_${PaintBox.ctr++}"/></defs>
|
||||
</svg>`,
|
||||
'text/xml'
|
||||
);
|
||||
|
||||
let docElem = svgdocbox.documentElement;
|
||||
docElem = document.importNode(docElem, true);
|
||||
container.appendChild(docElem);
|
||||
|
||||
this.rect = docElem.firstElementChild;
|
||||
this.defs = docElem.getElementsByTagName('defs')[0];
|
||||
this.grad = this.defs.firstElementChild;
|
||||
// this.paint = new $.jGraduate.Paint({solidColor: color});
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {module:jGraduate~Paint} paint
|
||||
* @returns {void}
|
||||
*/
|
||||
setPaint (paint) {
|
||||
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 = paint[ptype];
|
||||
this.defs.appendChild(this.grad);
|
||||
const id = this.grad.id = 'gradbox_' + this.type;
|
||||
fillAttr = 'url(#' + id + ')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.rect.setAttribute('fill', fillAttr);
|
||||
this.rect.setAttribute('opacity', opac);
|
||||
}
|
||||
/**
|
||||
* @param {PlainObject} svgCanvas
|
||||
* @param {string} color
|
||||
* @param {Float} opac
|
||||
* @param {string} type
|
||||
* @returns {module:jGraduate~Paint}
|
||||
*/
|
||||
static getPaint (svgCanvas, color, opac, type) {
|
||||
// update the editor's fill paint
|
||||
const opts = { alpha: opac };
|
||||
if (color.startsWith('url(#')) {
|
||||
let refElem = svgCanvas.getRefElem(color);
|
||||
refElem = (refElem) ? refElem.cloneNode(true) : document.querySelectorAll('#' + type + '_color defs *')[0];
|
||||
opts[refElem.tagName] = refElem;
|
||||
} else if (color.startsWith('#')) {
|
||||
opts.solidColor = color.substr(1);
|
||||
} else {
|
||||
opts.solidColor = 'none';
|
||||
}
|
||||
return new jGraduate.Paint(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PlainObject} svgcanvas
|
||||
* @param {PlainObject} selectedElement
|
||||
* @returns {any}
|
||||
*/
|
||||
update (svgcanvas, selectedElement) {
|
||||
if (!selectedElement) { return null; }
|
||||
|
||||
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 null;
|
||||
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 null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this._paintOpacity *= 100;
|
||||
|
||||
const paint = PaintBox.getPaint(svgcanvas, this._paintColor, this._paintOpacity, type);
|
||||
// update the rect inside #fill_color/#stroke_color
|
||||
this.setPaint(paint);
|
||||
return (paint);
|
||||
}
|
||||
}
|
||||
PaintBox.ctr = 0;
|
||||
|
||||
export default PaintBox;
|
||||
12
src/editor/components/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import './seButton.js';
|
||||
import './seFlyingButton.js';
|
||||
import './seExplorerButton.js';
|
||||
import './seZoom.js';
|
||||
import './seInput.js';
|
||||
import './seSpinInput.js';
|
||||
import './sePalette.js';
|
||||
import './seMenu.js';
|
||||
import './seMenuItem.js';
|
||||
import './seList.js';
|
||||
import './seListItem.js';
|
||||
import './seColorPicker.js';
|
||||
391
src/editor/components/jgraduate/ColorValuePicker.js
Normal file
@@ -0,0 +1,391 @@
|
||||
/* eslint-disable max-len */
|
||||
/* eslint-disable no-bitwise */
|
||||
/**
|
||||
* @external Math
|
||||
*/
|
||||
/**
|
||||
* @memberof external:Math
|
||||
* @param {Float} value
|
||||
* @param {Float} precision
|
||||
* @returns {Float}
|
||||
*/
|
||||
function toFixedNumeric (value, precision) {
|
||||
if (precision === undefined) precision = 0;
|
||||
return Math.round(value * (10 ** precision)) / (10 ** precision);
|
||||
}
|
||||
/**
|
||||
* Whether a value is `null` or `undefined`.
|
||||
* @param {any} val
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isNullish = (val) => {
|
||||
return val === null || val === undefined;
|
||||
};
|
||||
/**
|
||||
* Controls for all the input elements for the typing in color values.
|
||||
*/
|
||||
export default class ColorValuePicker {
|
||||
/**
|
||||
* @param {external:jQuery} picker
|
||||
* @param {external:jQuery.jPicker.Color} color
|
||||
* @param {external:jQuery.fn.$.fn.jPicker} bindedHex
|
||||
* @param {Float} alphaPrecision
|
||||
*/
|
||||
constructor (picker, color, bindedHex, alphaPrecision) {
|
||||
const that = this; // private properties and methods
|
||||
const inputs = picker.querySelectorAll('td.Text input');
|
||||
// input box key down - use arrows to alter color
|
||||
/**
|
||||
*
|
||||
* @param {Event} e
|
||||
* @returns {Event|false|void}
|
||||
*/
|
||||
function keyDown (e) {
|
||||
if (e.target.value === '' && e.target !== hex && ((!isNullish(bindedHex) && e.target !== bindedHex) || isNullish(bindedHex))) return undefined;
|
||||
if (!validateKey(e)) return e;
|
||||
switch (e.target) {
|
||||
case red:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
red.value = setValueInRange.call(that, (red.value << 0) + 1, 0, 255);
|
||||
color.val('r', red.value, e.target);
|
||||
return false;
|
||||
case 40:
|
||||
red.value = setValueInRange.call(that, (red.value << 0) - 1, 0, 255);
|
||||
color.val('r', red.value, e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case green:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
green.value = setValueInRange.call(that, (green.value << 0) + 1, 0, 255);
|
||||
color.val('g', green.value, e.target);
|
||||
return false;
|
||||
case 40:
|
||||
green.value = setValueInRange.call(that, (green.value << 0) - 1, 0, 255);
|
||||
color.val('g', green.value, e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case blue:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
blue.value = setValueInRange.call(that, (blue.value << 0) + 1, 0, 255);
|
||||
color.val('b', blue.value, e.target);
|
||||
return false;
|
||||
case 40:
|
||||
blue.value = setValueInRange.call(that, (blue.value << 0) - 1, 0, 255);
|
||||
color.val('b', blue.value, e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case alpha:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
alpha.value = setValueInRange.call(that, Number.parseFloat(alpha.value) + 1, 0, 100);
|
||||
color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target);
|
||||
return false;
|
||||
case 40:
|
||||
alpha.value = setValueInRange.call(that, Number.parseFloat(alpha.value) - 1, 0, 100);
|
||||
color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case hue:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
hue.value = setValueInRange.call(that, (hue.value << 0) + 1, 0, 360);
|
||||
color.val('h', hue.value, e.target);
|
||||
return false;
|
||||
case 40:
|
||||
hue.value =setValueInRange.call(that, (hue.value << 0) - 1, 0, 360);
|
||||
color.val('h', hue.value, e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case saturation:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
saturation.value = setValueInRange.call(that, (saturation.value << 0) + 1, 0, 100);
|
||||
color.val('s', saturation.value, e.target);
|
||||
return false;
|
||||
case 40:
|
||||
saturation.value = setValueInRange.call(that, (saturation.value << 0) - 1, 0, 100);
|
||||
color.val('s', saturation.value, e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value:
|
||||
switch (e.keyCode) {
|
||||
case 38:
|
||||
value.value = setValueInRange.call(that, (value.value << 0) + 1, 0, 100);
|
||||
color.val('v', value.value, e.target);
|
||||
return false;
|
||||
case 40:
|
||||
value.value = setValueInRange.call(that, (value.value << 0) - 1, 0, 100);
|
||||
color.val('v', value.value, e.target);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// input box key up - validate value and set color
|
||||
/**
|
||||
* @param {Event} e
|
||||
* @returns {Event|void}
|
||||
* @todo Why is this returning an event?
|
||||
*/
|
||||
function keyUp (e) {
|
||||
if (e.target.value === '' && e.target !== hex &&
|
||||
((!isNullish(bindedHex) && e.target !== bindedHex) ||
|
||||
isNullish(bindedHex))) return undefined;
|
||||
if (!validateKey(e)) return e;
|
||||
switch (e.target) {
|
||||
case red:
|
||||
red.value = setValueInRange.call(that, red.value, 0, 255);
|
||||
color.val('r', red.value, e.target);
|
||||
break;
|
||||
case green:
|
||||
green.value = setValueInRange.call(that, green.value, 0, 255);
|
||||
color.val('g', green.value, e.target);
|
||||
break;
|
||||
case blue:
|
||||
blue.value = setValueInRange.call(that, blue.value, 0, 255);
|
||||
color.val('b', blue.value, e.target);
|
||||
break;
|
||||
case alpha:
|
||||
alpha.value = setValueInRange.call(that, alpha.value, 0, 100);
|
||||
color.val('a', toFixedNumeric((alpha.value * 255) / 100, alphaPrecision), e.target);
|
||||
break;
|
||||
case hue:
|
||||
hue.value = setValueInRange.call(that, hue.value, 0, 360);
|
||||
color.val('h', hue.value, e.target);
|
||||
break;
|
||||
case saturation:
|
||||
saturation.value = setValueInRange.call(that, saturation.value, 0, 100);
|
||||
color.val('s', saturation.value, e.target);
|
||||
break;
|
||||
case value:
|
||||
value.value = setValueInRange.call(that, value.value, 0, 100);
|
||||
color.val('v', value.value, e.target);
|
||||
break;
|
||||
case hex:
|
||||
hex.value = hex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6);
|
||||
bindedHex && bindedHex.val(hex.value);
|
||||
color.val('hex', hex.value !== '' ? hex.value : null, e.target);
|
||||
break;
|
||||
case bindedHex:
|
||||
bindedHex.value = bindedHex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 6);
|
||||
hex.val(bindedHex.value);
|
||||
color.val('hex', bindedHex.value !== '' ? bindedHex.value : null, e.target);
|
||||
break;
|
||||
case ahex:
|
||||
ahex.value = ahex.value.replace(/[^a-fA-F\d]/g, '').toLowerCase().substring(0, 2);
|
||||
color.val('a', !isNullish(ahex.value) ? Number.parseInt(ahex.value, 16) : null, e.target);
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
// input box blur - reset to original if value empty
|
||||
/**
|
||||
* @param {Event} e
|
||||
* @returns {void}
|
||||
*/
|
||||
function blur (e) {
|
||||
if (!isNullish(color.value)) {
|
||||
switch (e.target) {
|
||||
case red:
|
||||
color.value = 'r';
|
||||
red.value = color.value;
|
||||
break;
|
||||
case green:
|
||||
color.value = 'g';
|
||||
green.value = color.value;
|
||||
break;
|
||||
case blue:
|
||||
color.value = 'b';
|
||||
blue.value = color.value;
|
||||
break;
|
||||
case alpha:
|
||||
color.value = 'a';
|
||||
alpha.value = toFixedNumeric((color.value * 100) / 255, alphaPrecision);
|
||||
break;
|
||||
case hue:
|
||||
color.value = 'h';
|
||||
hue.value = color.value;
|
||||
break;
|
||||
case saturation:
|
||||
color.value = 's';
|
||||
saturation.value = color.value;
|
||||
break;
|
||||
case value:
|
||||
color.value = 'v';
|
||||
value.value = color.value;
|
||||
break;
|
||||
case hex:
|
||||
case bindedHex:
|
||||
color.value = 'hex';
|
||||
hex.value = color.value;
|
||||
bindedHex.value = color.value;
|
||||
break;
|
||||
case ahex:
|
||||
color.value = 'ahex';
|
||||
ahex.value = color.value.substring(6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Event} e
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function validateKey (e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
case 16:
|
||||
case 29:
|
||||
case 37:
|
||||
case 39:
|
||||
return false;
|
||||
case 'c'.charCodeAt():
|
||||
case 'v'.charCodeAt():
|
||||
if (e.ctrlKey) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrain value within range.
|
||||
* @param {Float|string} value
|
||||
* @param {Float} min
|
||||
* @param {Float} max
|
||||
* @returns {Float|string} Returns a number or numeric string
|
||||
*/
|
||||
function setValueInRange (value, min, max) {
|
||||
if (value === '' || isNaN(value)) return min;
|
||||
if (value > max) return max;
|
||||
if (value < min) return min;
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* @param {external:jQuery} ui
|
||||
* @param {Element} context
|
||||
* @returns {void}
|
||||
*/
|
||||
function colorChanged (ui, context) {
|
||||
const all = ui.val('all');
|
||||
if (context !== red) red.value = (!isNullish(all) ? all.r : '');
|
||||
if (context !== green) green.value = (!isNullish(all) ? all.g : '');
|
||||
if (context !== blue) blue.value = (!isNullish(all) ? all.b : '');
|
||||
if (alpha && context !== alpha) alpha.value = (!isNullish(all) ? toFixedNumeric((all.a * 100) / 255, alphaPrecision) : '');
|
||||
if (context !== hue) hue.value = (!isNullish(all) ? all.h : '');
|
||||
if (context !== saturation) saturation.value = (!isNullish(all) ? all.s : '');
|
||||
if (context !== value) value.value = (!isNullish(all) ? all.v : '');
|
||||
if (context !== hex && ((bindedHex && context !== bindedHex) || !bindedHex)) hex.value = (!isNullish(all) ? all.hex : '');
|
||||
if (bindedHex && context !== bindedHex && context !== hex) bindedHex.value = (!isNullish(all) ? all.hex : '');
|
||||
if (ahex && context !== ahex) ahex.value = (!isNullish(all) ? all.ahex.substring(6) : '');
|
||||
}
|
||||
/**
|
||||
* Unbind all events and null objects.
|
||||
* @returns {void}
|
||||
*/
|
||||
function destroy () {
|
||||
red.removeEventListener('keyup', keyUp);
|
||||
green.removeEventListener('keyup', keyUp);
|
||||
blue.removeEventListener('keyup', keyUp);
|
||||
hue.removeEventListener('keyup', keyUp);
|
||||
saturation.removeEventListener('keyup', keyUp);
|
||||
value.removeEventListener('keyup', keyUp);
|
||||
hex.removeEventListener('keyup', keyUp);
|
||||
|
||||
red.removeEventListener('blur', blur);
|
||||
green.removeEventListener('blur', blur);
|
||||
blue.removeEventListener('blur', blur);
|
||||
hue.removeEventListener('blur', blur);
|
||||
saturation.removeEventListener('blur', blur);
|
||||
value.removeEventListener('blur', blur);
|
||||
hex.removeEventListener('blur', blur);
|
||||
|
||||
red.removeEventListener('keydown', keyDown);
|
||||
green.removeEventListener('keydown', keyDown);
|
||||
blue.removeEventListener('keydown', keyDown);
|
||||
hue.removeEventListener('keydown', keyDown);
|
||||
saturation.removeEventListener('keydown', keyDown);
|
||||
value.removeEventListener('keydown', keyDown);
|
||||
|
||||
if (alpha !== null) {
|
||||
alpha.removeEventListener('keyup', keyUp);
|
||||
alpha.removeEventListener('blur', blur);
|
||||
alpha.removeEventListener('keydown', keyDown);
|
||||
}
|
||||
if (ahex !== null) {
|
||||
ahex.removeEventListener('keyup', keyUp);
|
||||
ahex.removeEventListener('blur', blur);
|
||||
}
|
||||
if (bindedHex !== null) {
|
||||
bindedHex.removeEventListener('keyup', keyUp);
|
||||
bindedHex.removeEventListener('blur', blur);
|
||||
}
|
||||
color.unbind(colorChanged);
|
||||
red = null;
|
||||
green = null;
|
||||
blue = null;
|
||||
alpha = null;
|
||||
hue = null;
|
||||
saturation = null;
|
||||
value = null;
|
||||
hex = null;
|
||||
ahex = null;
|
||||
}
|
||||
let
|
||||
red = inputs[3],
|
||||
green = inputs[4],
|
||||
blue = inputs[5],
|
||||
alpha = inputs.length > 7 ? inputs[6] : null,
|
||||
hue = inputs[0],
|
||||
saturation = inputs[1],
|
||||
value = inputs[2],
|
||||
hex = inputs[(inputs.length > 7) ? 7 : 6],
|
||||
ahex = inputs.length > 7 ? inputs[8] : null;
|
||||
Object.assign(that, { destroy });
|
||||
red.addEventListener('keyup', keyUp);
|
||||
green.addEventListener('keyup', keyUp);
|
||||
blue.addEventListener('keyup', keyUp);
|
||||
hue.addEventListener('keyup', keyUp);
|
||||
saturation.addEventListener('keyup', keyUp);
|
||||
value.addEventListener('keyup', keyUp);
|
||||
hex.addEventListener('keyup', keyUp);
|
||||
|
||||
red.addEventListener('blur', blur);
|
||||
green.addEventListener('blur', blur);
|
||||
blue.addEventListener('blur', blur);
|
||||
hue.addEventListener('blur', blur);
|
||||
saturation.addEventListener('blur', blur);
|
||||
value.addEventListener('blur', blur);
|
||||
hex.addEventListener('blur', blur);
|
||||
|
||||
red.addEventListener('keydown', keyDown);
|
||||
green.addEventListener('keydown', keyDown);
|
||||
blue.addEventListener('keydown', keyDown);
|
||||
hue.addEventListener('keydown', keyDown);
|
||||
saturation.addEventListener('keydown', keyDown);
|
||||
value.addEventListener('keydown', keyDown);
|
||||
|
||||
if (alpha !== null) {
|
||||
alpha.addEventListener('keyup', keyUp);
|
||||
alpha.addEventListener('blur', blur);
|
||||
alpha.addEventListener('keydown', keyDown);
|
||||
}
|
||||
if (ahex !== null) {
|
||||
ahex.addEventListener('keyup', keyUp);
|
||||
ahex.addEventListener('blur', blur);
|
||||
}
|
||||
if (bindedHex !== null) {
|
||||
bindedHex.addEventListener('keyup', keyUp);
|
||||
bindedHex.addEventListener('blur', blur);
|
||||
}
|
||||
color.bind(colorChanged);
|
||||
}
|
||||
}
|
||||
338
src/editor/components/jgraduate/Slider.js
Normal file
@@ -0,0 +1,338 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
import { findPos } from './Util.js';
|
||||
/**
|
||||
* Whether a value is `null` or `undefined`.
|
||||
* @param {any} val
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isNullish = (val) => {
|
||||
return val === null || val === undefined;
|
||||
};
|
||||
/**
|
||||
* Encapsulate slider functionality for the ColorMap and ColorBar -
|
||||
* could be useful to use a jQuery UI draggable for this with certain extensions.
|
||||
* @memberof module:jPicker
|
||||
*/
|
||||
export default class Slider {
|
||||
/**
|
||||
* @param {external:jQuery} bar
|
||||
* @param {module:jPicker.SliderOptions} options
|
||||
*/
|
||||
constructor (bar, options) {
|
||||
const that = this;
|
||||
/**
|
||||
* Fire events on the supplied `context`
|
||||
* @param {module:jPicker.JPickerInit} context
|
||||
* @returns {void}
|
||||
*/
|
||||
function fireChangeEvents (context) {
|
||||
changeEvents.forEach((changeEvent) => {
|
||||
changeEvent.call(that, that, context);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the mousedown to the bar not the arrow for quick snapping to the clicked location.
|
||||
* @param {external:jQuery.Event} e
|
||||
* @returns {void}
|
||||
*/
|
||||
function mouseDown (e) {
|
||||
const off = findPos(bar);
|
||||
offset = { l: off.left | 0, t: off.top | 0 };
|
||||
clearTimeout(timeout);
|
||||
// using setTimeout for visual updates - once the style is updated the browser will re-render internally allowing the next Javascript to run
|
||||
timeout = setTimeout(function () {
|
||||
setValuesFromMousePosition.call(that, e);
|
||||
}, 0);
|
||||
// Bind mousemove and mouseup event to the document so it responds when dragged of of the bar - we will unbind these when on mouseup to save processing
|
||||
document.addEventListener('mousemove', mouseMove);
|
||||
document.addEventListener('mouseup', mouseUp);
|
||||
e.preventDefault(); // don't try to select anything or drag the image to the desktop
|
||||
}
|
||||
/**
|
||||
* Set the values as the mouse moves.
|
||||
* @param {external:jQuery.Event} e
|
||||
* @returns {false}
|
||||
*/
|
||||
function mouseMove (e) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(function () {
|
||||
setValuesFromMousePosition.call(that, e);
|
||||
}, 0);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Unbind the document events - they aren't needed when not dragging.
|
||||
* @param {external:jQuery.Event} e
|
||||
* @returns {false}
|
||||
*/
|
||||
function mouseUp (e) {
|
||||
document.removeEventListener('mousemove', mouseMove);
|
||||
document.removeEventListener('mouseup', mouseUp);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate mouse position and set value within the current range.
|
||||
* @param {Event} e
|
||||
* @returns {void}
|
||||
*/
|
||||
function setValuesFromMousePosition (e) {
|
||||
const barW = bar.w, // local copies for YUI compressor
|
||||
barH = bar.h;
|
||||
let locX = e.pageX - offset.l,
|
||||
locY = e.pageY - offset.t;
|
||||
// keep the arrow within the bounds of the bar
|
||||
if (locX < 0) locX = 0;
|
||||
else if (locX > barW) locX = barW;
|
||||
if (locY < 0) locY = 0;
|
||||
else if (locY > barH) locY = barH;
|
||||
val.call(that, 'xy', {
|
||||
x: ((locX / barW) * rangeX) + minX,
|
||||
y: ((locY / barH) * rangeY) + minY
|
||||
});
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function draw () {
|
||||
const
|
||||
barW = bar.w,
|
||||
barH = bar.h,
|
||||
arrowW = arrow.w,
|
||||
arrowH = arrow.h;
|
||||
let arrowOffsetX = 0,
|
||||
arrowOffsetY = 0;
|
||||
setTimeout(function () {
|
||||
if (rangeX > 0) { // range is greater than zero
|
||||
// constrain to bounds
|
||||
if (x === maxX) arrowOffsetX = barW;
|
||||
else arrowOffsetX = ((x / rangeX) * barW) | 0;
|
||||
}
|
||||
if (rangeY > 0) { // range is greater than zero
|
||||
// constrain to bounds
|
||||
if (y === maxY) arrowOffsetY = barH;
|
||||
else arrowOffsetY = ((y / rangeY) * barH) | 0;
|
||||
}
|
||||
// if arrow width is greater than bar width, center arrow and prevent horizontal dragging
|
||||
if (arrowW >= barW) arrowOffsetX = (barW >> 1) - (arrowW >> 1); // number >> 1 - superfast bitwise divide by two and truncate (move bits over one bit discarding lowest)
|
||||
else arrowOffsetX -= arrowW >> 1;
|
||||
// if arrow height is greater than bar height, center arrow and prevent vertical dragging
|
||||
if (arrowH >= barH) arrowOffsetY = (barH >> 1) - (arrowH >> 1);
|
||||
else arrowOffsetY -= arrowH >> 1;
|
||||
// set the arrow position based on these offsets
|
||||
arrow.style.left = arrowOffsetX + 'px';
|
||||
arrow.style.top = arrowOffsetY + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set a value.
|
||||
* @param {?("xy"|"x"|"y")} name
|
||||
* @param {module:math.XYObject} value
|
||||
* @param {module:jPicker.Slider} context
|
||||
* @returns {module:math.XYObject|Float|void}
|
||||
*/
|
||||
function val (name, value, context) {
|
||||
const set = value !== undefined;
|
||||
if (!set) {
|
||||
if (isNullish(name)) name = 'xy';
|
||||
switch (name.toLowerCase()) {
|
||||
case 'x': return x;
|
||||
case 'y': return y;
|
||||
case 'xy':
|
||||
default: return { x, y };
|
||||
}
|
||||
}
|
||||
if (!isNullish(context) && context === that) return undefined;
|
||||
let changed = false;
|
||||
|
||||
let newX, newY;
|
||||
if (isNullish(name)) name = 'xy';
|
||||
switch (name.toLowerCase()) {
|
||||
case 'x':
|
||||
newX = (value && ((value.x && value.x | 0) || value | 0)) || 0;
|
||||
break;
|
||||
case 'y':
|
||||
newY = (value && ((value.y && value.y | 0) || value | 0)) || 0;
|
||||
break;
|
||||
case 'xy':
|
||||
default:
|
||||
newX = (value && value.x && value.x | 0) || 0;
|
||||
newY = (value && value.y && value.y | 0) || 0;
|
||||
break;
|
||||
}
|
||||
if (!isNullish(newX)) {
|
||||
if (newX < minX) newX = minX;
|
||||
else if (newX > maxX) newX = maxX;
|
||||
if (x !== newX) {
|
||||
x = newX;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!isNullish(newY)) {
|
||||
if (newY < minY) newY = minY;
|
||||
else if (newY > maxY) newY = maxY;
|
||||
if (y !== newY) {
|
||||
y = newY;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
changed && fireChangeEvents.call(that, context || that);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {PlainObject} module:jPicker.MinMaxRangeX
|
||||
* @property {Float} minX
|
||||
* @property {Float} maxX
|
||||
* @property {Float} rangeX
|
||||
*/
|
||||
/**
|
||||
* @typedef {PlainObject} module:jPicker.MinMaxRangeY
|
||||
* @property {Float} minY
|
||||
* @property {Float} maxY
|
||||
* @property {Float} rangeY
|
||||
*/
|
||||
/**
|
||||
* @typedef {module:jPicker.MinMaxRangeY|module:jPicker.MinMaxRangeX} module:jPicker.MinMaxRangeXY
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"minx"|"maxx"|"rangex"|"miny"|"maxy"|"rangey"|"all"} name
|
||||
* @param {module:jPicker.MinMaxRangeXY} value
|
||||
* @returns {module:jPicker.MinMaxRangeXY|module:jPicker.MinMaxRangeX|module:jPicker.MinMaxRangeY|void}
|
||||
*/
|
||||
function range (name, value) {
|
||||
const set = value !== undefined;
|
||||
if (!set) {
|
||||
if (isNullish(name)) name = 'all';
|
||||
switch (name.toLowerCase()) {
|
||||
case 'minx': return minX;
|
||||
case 'maxx': return maxX;
|
||||
case 'rangex': return { minX, maxX, rangeX };
|
||||
case 'miny': return minY;
|
||||
case 'maxy': return maxY;
|
||||
case 'rangey': return { minY, maxY, rangeY };
|
||||
case 'all':
|
||||
default: return { minX, maxX, rangeX, minY, maxY, rangeY };
|
||||
}
|
||||
}
|
||||
let // changed = false,
|
||||
newMinX,
|
||||
newMaxX,
|
||||
newMinY,
|
||||
newMaxY;
|
||||
if (isNullish(name)) name = 'all';
|
||||
switch (name.toLowerCase()) {
|
||||
case 'minx':
|
||||
newMinX = (value && ((value.minX && value.minX | 0) || value | 0)) || 0;
|
||||
break;
|
||||
case 'maxx':
|
||||
newMaxX = (value && ((value.maxX && value.maxX | 0) || value | 0)) || 0;
|
||||
break;
|
||||
case 'rangex':
|
||||
newMinX = (value && value.minX && value.minX | 0) || 0;
|
||||
newMaxX = (value && value.maxX && value.maxX | 0) || 0;
|
||||
break;
|
||||
case 'miny':
|
||||
newMinY = (value && ((value.minY && value.minY | 0) || value | 0)) || 0;
|
||||
break;
|
||||
case 'maxy':
|
||||
newMaxY = (value && ((value.maxY && value.maxY | 0) || value | 0)) || 0;
|
||||
break;
|
||||
case 'rangey':
|
||||
newMinY = (value && value.minY && value.minY | 0) || 0;
|
||||
newMaxY = (value && value.maxY && value.maxY | 0) || 0;
|
||||
break;
|
||||
case 'all':
|
||||
default:
|
||||
newMinX = (value && value.minX && value.minX | 0) || 0;
|
||||
newMaxX = (value && value.maxX && value.maxX | 0) || 0;
|
||||
newMinY = (value && value.minY && value.minY | 0) || 0;
|
||||
newMaxY = (value && value.maxY && value.maxY | 0) || 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isNullish(newMinX) && minX !== newMinX) {
|
||||
minX = newMinX;
|
||||
rangeX = maxX - minX;
|
||||
}
|
||||
if (!isNullish(newMaxX) && maxX !== newMaxX) {
|
||||
maxX = newMaxX;
|
||||
rangeX = maxX - minX;
|
||||
}
|
||||
if (!isNullish(newMinY) && minY !== newMinY) {
|
||||
minY = newMinY;
|
||||
rangeY = maxY - minY;
|
||||
}
|
||||
if (!isNullish(newMaxY) && maxY !== newMaxY) {
|
||||
maxY = newMaxY;
|
||||
rangeY = maxY - minY;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* @param {GenericCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
function bind (callback) { // eslint-disable-line promise/prefer-await-to-callbacks
|
||||
if (typeof callback === 'function') changeEvents.push(callback);
|
||||
}
|
||||
/**
|
||||
* @param {GenericCallback} callback
|
||||
* @returns {void}
|
||||
*/
|
||||
function unbind (callback) { // eslint-disable-line promise/prefer-await-to-callbacks
|
||||
if (typeof callback !== 'function') return;
|
||||
let i;
|
||||
while ((i = changeEvents.includes(callback))) changeEvents.splice(i, 1);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function destroy () {
|
||||
// unbind all possible events and null objects
|
||||
document.removeEventListener('mousemove', mouseMove);
|
||||
document.removeEventListener('mouseup', mouseUp);
|
||||
bar.removeEventListener('mousedown', mouseDown);
|
||||
bar = null;
|
||||
arrow = null;
|
||||
changeEvents = null;
|
||||
}
|
||||
let offset,
|
||||
timeout,
|
||||
x = 0,
|
||||
y = 0,
|
||||
minX = 0,
|
||||
maxX = 100,
|
||||
rangeX = 100,
|
||||
minY = 0,
|
||||
maxY = 100,
|
||||
rangeY = 100,
|
||||
arrow = bar.querySelector('img'), // the arrow image to drag
|
||||
changeEvents = [];
|
||||
Object.assign(that, {
|
||||
val,
|
||||
range,
|
||||
bind,
|
||||
unbind,
|
||||
destroy
|
||||
});
|
||||
// initialize this control
|
||||
arrow.src = options.arrow && options.arrow.image;
|
||||
arrow.w = (options.arrow && options.arrow.width) || parseFloat(getComputedStyle(arrow, null).width.replace("px", ""));
|
||||
arrow.h = (options.arrow && options.arrow.height) || parseFloat(getComputedStyle(arrow, null).height.replace("px", ""));
|
||||
bar.w = (options.map && options.map.width) || parseFloat(getComputedStyle(bar, null).width.replace("px", ""));
|
||||
bar.h = (options.map && options.map.height) || parseFloat(getComputedStyle(bar, null).height.replace("px", ""));
|
||||
bar.addEventListener('mousedown', mouseDown);
|
||||
bind.call(that, draw);
|
||||
}
|
||||
}
|
||||
218
src/editor/components/jgraduate/Util.js
Normal file
@@ -0,0 +1,218 @@
|
||||
/* eslint-disable sonarjs/no-collapsible-if */
|
||||
/**
|
||||
* @param {any} obj
|
||||
* @returns {any}
|
||||
*/
|
||||
export function findPos(obj) {
|
||||
let curleft = 0;
|
||||
let curtop = 0;
|
||||
if (obj.offsetParent) {
|
||||
do {
|
||||
curleft += obj.offsetLeft;
|
||||
curtop += obj.offsetTop;
|
||||
} while (obj == obj.offsetParent);
|
||||
return { left: curleft, top: curtop };
|
||||
}
|
||||
return { left: curleft, top: curtop };
|
||||
}
|
||||
|
||||
export function isObject(item) {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item));
|
||||
}
|
||||
|
||||
export function mergeDeep(target, source) {
|
||||
let output = Object.assign({}, target);
|
||||
if (isObject(target) && isObject(source)) {
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (isObject(source[key])) {
|
||||
if (!(key in target))
|
||||
Object.assign(output, { [key]: source[key] });
|
||||
else
|
||||
output[key] = mergeDeep(target[key], source[key]);
|
||||
} else {
|
||||
Object.assign(output, { [key]: source[key] });
|
||||
}
|
||||
});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest matching element up the DOM tree.
|
||||
* @param {Element} elem Starting element
|
||||
* @param {String} selector Selector to match against (class, ID, data attribute, or tag)
|
||||
* @return {Boolean|Element} Returns null if not match found
|
||||
*/
|
||||
export function getClosest(elem, selector) {
|
||||
const firstChar = selector.charAt(0);
|
||||
const supports = 'classList' in document.documentElement;
|
||||
let attribute, value;
|
||||
// If selector is a data attribute, split attribute from value
|
||||
if (firstChar === '[') {
|
||||
selector = selector.substr(1, selector.length - 2);
|
||||
attribute = selector.split('=');
|
||||
if (attribute.length > 1) {
|
||||
value = true;
|
||||
attribute[1] = attribute[1].replace(/"/g, '').replace(/'/g, '');
|
||||
}
|
||||
}
|
||||
// Get closest match
|
||||
for (; elem && elem !== document && elem.nodeType === 1; elem = elem.parentNode) {
|
||||
// If selector is a class
|
||||
if (firstChar === '.') {
|
||||
if (supports) {
|
||||
if (elem.classList.contains(selector.substr(1))) {
|
||||
return elem;
|
||||
}
|
||||
} else {
|
||||
if (new RegExp('(^|\\s)' + selector.substr(1) + '(\\s|$)').test(elem.className)) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If selector is an ID
|
||||
if (firstChar === '#') {
|
||||
if (elem.id === selector.substr(1)) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
// If selector is a data attribute
|
||||
if (firstChar === '[') {
|
||||
if (elem.hasAttribute(attribute[0])) {
|
||||
if (value) {
|
||||
if (elem.getAttribute(attribute[0]) === attribute[1]) {
|
||||
return elem;
|
||||
}
|
||||
} else {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If selector is a tag
|
||||
if (elem.tagName.toLowerCase() === selector) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all DOM element up the tree that contain a class, ID, or data attribute
|
||||
* @param {Node} elem The base element
|
||||
* @param {String} selector The class, id, data attribute, or tag to look for
|
||||
* @return {Array} Null if no match
|
||||
*/
|
||||
export function getParents(elem, selector) {
|
||||
const parents = [];
|
||||
let firstChar;
|
||||
if ( selector ) {
|
||||
firstChar = selector.charAt(0);
|
||||
}
|
||||
// Get matches
|
||||
for ( ; elem && elem !== document; elem = elem.parentNode ) {
|
||||
if ( selector ) {
|
||||
// If selector is a class
|
||||
if ( firstChar === '.' ) {
|
||||
if ( elem.classList.contains( selector.substr(1) ) ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// If selector is an ID
|
||||
if ( firstChar === '#' ) {
|
||||
if ( elem.id === selector.substr(1) ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// If selector is a data attribute
|
||||
if ( firstChar === '[' ) {
|
||||
if ( elem.hasAttribute( selector.substr(1, selector.length - 1) ) ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// If selector is a tag
|
||||
if ( elem.tagName.toLowerCase() === selector ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
} else {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// Return parents if any exist
|
||||
if ( parents.length === 0 ) {
|
||||
return null;
|
||||
} else {
|
||||
return parents;
|
||||
}
|
||||
}
|
||||
|
||||
export function getParentsUntil(elem, parent, selector) {
|
||||
let parents = [];
|
||||
let parentType;
|
||||
let selectorType;
|
||||
if ( parent ) {
|
||||
parentType = parent.charAt(0);
|
||||
}
|
||||
if ( selector ) {
|
||||
selectorType = selector.charAt(0);
|
||||
}
|
||||
// Get matches
|
||||
for ( ; elem && elem !== document; elem = elem.parentNode ) {
|
||||
// Check if parent has been reached
|
||||
if ( parent ) {
|
||||
// If parent is a class
|
||||
if ( parentType === '.' ) {
|
||||
if ( elem.classList.contains( parent.substr(1) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If parent is an ID
|
||||
if ( parentType === '#' ) {
|
||||
if ( elem.id === parent.substr(1) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If parent is a data attribute
|
||||
if ( parentType === '[' ) {
|
||||
if ( elem.hasAttribute( parent.substr(1, parent.length - 1) ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If parent is a tag
|
||||
if ( elem.tagName.toLowerCase() === parent ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( selector ) {
|
||||
// If selector is a class
|
||||
if ( selectorType === '.' ) {
|
||||
if ( elem.classList.contains( selector.substr(1) ) ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// If selector is an ID
|
||||
if ( selectorType === '#' ) {
|
||||
if ( elem.id === selector.substr(1) ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// If selector is a data attribute
|
||||
if ( selectorType === '[' ) {
|
||||
if ( elem.hasAttribute( selector.substr(1, selector.length - 1) ) ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// If selector is a tag
|
||||
if ( elem.tagName.toLowerCase() === selector ) {
|
||||
parents.push( elem );
|
||||
}
|
||||
} else {
|
||||
parents.push( elem );
|
||||
}
|
||||
}
|
||||
// Return parents if any exist
|
||||
if ( parents.length === 0 ) {
|
||||
return null;
|
||||
} else {
|
||||
return parents;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 473 B |
|
Before Width: | Height: | Size: 80 B After Width: | Height: | Size: 80 B |
|
Before Width: | Height: | Size: 81 B After Width: | Height: | Size: 81 B |
|
Before Width: | Height: | Size: 93 B After Width: | Height: | Size: 93 B |
|
Before Width: | Height: | Size: 141 B After Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 144 B |
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 80 B After Width: | Height: | Size: 80 B |
|
Before Width: | Height: | Size: 76 B After Width: | Height: | Size: 76 B |
|
Before Width: | Height: | Size: 93 B After Width: | Height: | Size: 93 B |
1288
src/editor/components/jgraduate/jQuery.jGraduate.js
Normal file
1878
src/editor/components/jgraduate/jQuery.jPicker.js
Executable file
78
src/editor/components/jgraduate/paint.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class Paint {
|
||||
/**
|
||||
* @param {module:jGraduate.jGraduatePaintOptions} [opt]
|
||||
*/
|
||||
constructor (opt) {
|
||||
const options = opt || {};
|
||||
this.alpha = isNaN(options.alpha) ? 100 : options.alpha;
|
||||
// copy paint object
|
||||
if (options.copy) {
|
||||
/**
|
||||
* @name module:jGraduate~Paint#type
|
||||
* @type {"none"|"solidColor"|"linearGradient"|"radialGradient"}
|
||||
*/
|
||||
this.type = options.copy.type;
|
||||
/**
|
||||
* Represents opacity (0-100).
|
||||
* @name module:jGraduate~Paint#alpha
|
||||
* @type {Float}
|
||||
*/
|
||||
this.alpha = options.copy.alpha;
|
||||
/**
|
||||
* Represents #RRGGBB hex of color.
|
||||
* @name module:jGraduate~Paint#solidColor
|
||||
* @type {string}
|
||||
*/
|
||||
this.solidColor = null;
|
||||
/**
|
||||
* @name module:jGraduate~Paint#linearGradient
|
||||
* @type {SVGLinearGradientElement}
|
||||
*/
|
||||
this.linearGradient = null;
|
||||
/**
|
||||
* @name module:jGraduate~Paint#radialGradient
|
||||
* @type {SVGRadialGradientElement}
|
||||
*/
|
||||
this.radialGradient = null;
|
||||
|
||||
switch (this.type) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'solidColor':
|
||||
this.solidColor = options.copy.solidColor;
|
||||
break;
|
||||
case 'linearGradient':
|
||||
this.linearGradient = options.copy.linearGradient.cloneNode(true);
|
||||
break;
|
||||
case 'radialGradient':
|
||||
this.radialGradient = options.copy.radialGradient.cloneNode(true);
|
||||
break;
|
||||
}
|
||||
// create linear gradient paint
|
||||
} else if (options.linearGradient) {
|
||||
this.type = 'linearGradient';
|
||||
this.solidColor = null;
|
||||
this.radialGradient = null;
|
||||
this.linearGradient = options.linearGradient.cloneNode(true);
|
||||
// create linear gradient paint
|
||||
} else if (options.radialGradient) {
|
||||
this.type = 'radialGradient';
|
||||
this.solidColor = null;
|
||||
this.linearGradient = null;
|
||||
this.radialGradient = options.radialGradient.cloneNode(true);
|
||||
// create solid color paint
|
||||
} else if (options.solidColor) {
|
||||
this.type = 'solidColor';
|
||||
this.solidColor = options.solidColor;
|
||||
// create empty paint
|
||||
} else {
|
||||
this.type = 'none';
|
||||
this.solidColor = null;
|
||||
this.linearGradient = null;
|
||||
this.radialGradient = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
227
src/editor/components/seButton.js
Normal file
@@ -0,0 +1,227 @@
|
||||
// import {isMac} from '../../common/browser.js';
|
||||
// if (isMac() && !window.opera) 'Ctrl+' 'Cmd+'
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:host(:hover) :not(.disabled)
|
||||
{
|
||||
background-color: var(--icon-bg-color-hover);
|
||||
}
|
||||
div
|
||||
{
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin: 2px 1px 4px;
|
||||
padding: 3px;
|
||||
background-color: var(--icon-bg-color);
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.small {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
padding: 1px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
img {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.pressed {
|
||||
background-color: var(--icon-bg-color-hover);
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
<div title="title">
|
||||
<img src="./images/logo.svg" alt="icon">
|
||||
</div>
|
||||
`;
|
||||
/**
|
||||
* @class ToolButton
|
||||
*/
|
||||
export class ToolButton extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
// locate the component
|
||||
this.$div = this._shadowRoot.querySelector('div');
|
||||
this.$img = this._shadowRoot.querySelector('img');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'src', 'pressed', 'disabled', 'size', 'style' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'title':
|
||||
{
|
||||
const shortcut = this.getAttribute('shortcut');
|
||||
this.$div.setAttribute('title', `${newValue} ${shortcut ? `[${shortcut}]` : ''}`);
|
||||
}
|
||||
break;
|
||||
case 'style':
|
||||
this.$div.style = newValue;
|
||||
break;
|
||||
case 'src':
|
||||
this.$img.setAttribute('src', newValue);
|
||||
break;
|
||||
case 'pressed':
|
||||
if (newValue) {
|
||||
this.$div.classList.add('pressed');
|
||||
} else {
|
||||
this.$div.classList.remove('pressed');
|
||||
}
|
||||
break;
|
||||
case 'size':
|
||||
if (newValue === 'small') {
|
||||
this.$div.classList.add('small');
|
||||
} else {
|
||||
this.$div.classList.remove('small');
|
||||
}
|
||||
break;
|
||||
case 'disabled':
|
||||
if (newValue) {
|
||||
this.$div.classList.add('disabled');
|
||||
} else {
|
||||
this.$div.classList.remove('disabled');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get title () {
|
||||
return this.getAttribute('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set title (value) {
|
||||
this.setAttribute('title', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get pressed () {
|
||||
return this.hasAttribute('pressed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set pressed (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('pressed', 'true');
|
||||
} else {
|
||||
this.removeAttribute('pressed', '');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get disabled () {
|
||||
return this.hasAttribute('disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set disabled (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.removeAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get src () {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get size () {
|
||||
return this.getAttribute('size');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set size (value) {
|
||||
this.setAttribute('size', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
// capture shortcuts
|
||||
const shortcut = this.getAttribute('shortcut');
|
||||
if (shortcut) {
|
||||
// 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.toUpperCase()}`;
|
||||
if (shortcut !== key) return;
|
||||
// launch the click event
|
||||
this.click();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-button', ToolButton);
|
||||
807
src/editor/components/seColorPicker.js
Normal file
@@ -0,0 +1,807 @@
|
||||
/* eslint-disable max-len */
|
||||
/* gl#bals svgEditor */
|
||||
import { jGraduate, jGraduateMethod } from './jgraduate/jQuery.jGraduate.js';
|
||||
import PaintBox from './PaintBox.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
.jPicker .Icon {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 25px
|
||||
}
|
||||
|
||||
.jPicker .Icon span.Color, .jPicker .Icon span.Alpha {
|
||||
background-position: 2px 2px;
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.jPicker .Icon span.Image {
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.jPicker.Container {
|
||||
z-index: 10
|
||||
}
|
||||
|
||||
table.jPicker {
|
||||
background-color: #efefef;
|
||||
border: 1px outset #666;
|
||||
font-family: Arial, Helvetica, Sans-Serif;
|
||||
font-size: 12px!important;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
width: 545px;
|
||||
z-index: 20
|
||||
}
|
||||
|
||||
.jPicker .Move {
|
||||
background-color: #ddd;
|
||||
border-color: #fff #666 #666 #fff;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
cursor: move;
|
||||
height: 12px;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.jPicker .Title {
|
||||
font-size: 11px!important;
|
||||
font-weight: bold;
|
||||
margin: -2px 0 0 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.jPicker div.Map {
|
||||
border-bottom: 2px solid #fff;
|
||||
border-left: 2px solid #9a9a9a;
|
||||
border-right: 2px solid #fff;
|
||||
border-top: 2px solid #9a9a9a;
|
||||
cursor: crosshair;
|
||||
height: 260px;
|
||||
margin: 0 5px 0 5px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: 260px
|
||||
}
|
||||
|
||||
.jPicker div[class="Map"] {
|
||||
height: 256px;
|
||||
width: 256px
|
||||
}
|
||||
|
||||
.jPicker div.Bar {
|
||||
border-bottom: 2px solid #fff;
|
||||
border-left: 2px solid #9a9a9a;
|
||||
border-right: 2px solid #fff;
|
||||
border-top: 2px solid #9a9a9a;
|
||||
cursor: n-resize;
|
||||
height: 260px;
|
||||
margin: 12px 10px 0 5px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
width: 24px
|
||||
}
|
||||
|
||||
.jPicker div[class="Bar"] {
|
||||
height: 256px;
|
||||
width: 20px
|
||||
}
|
||||
|
||||
.jPicker .Map .Map1, .jPicker .Map .Map2, .jPicker .Map .Map3, .jPicker .Bar .Map1, .jPicker .Bar .Map2, .jPicker .Bar .Map3, .jPicker .Bar .Map4, .jPicker .Bar .Map5, .jPicker .Bar .Map6 {
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0
|
||||
}
|
||||
|
||||
.jPicker .Map .Map1, .jPicker .Map .Map2, .jPicker .Map .Map3 {
|
||||
height: 2596px;
|
||||
width: 256px
|
||||
}
|
||||
|
||||
.jPicker .Bar .Map1, .jPicker .Bar .Map2, .jPicker .Bar .Map3, .jPicker .Bar .Map4 {
|
||||
height: 3896px;
|
||||
width: 20px
|
||||
}
|
||||
|
||||
.jPicker .Bar .Map5, .jPicker .Bar .Map6 {
|
||||
height: 256px;
|
||||
width: 20px
|
||||
}
|
||||
|
||||
.jPicker .Map .Map1, .jPicker .Map .Map2, .jPicker .Bar .Map6 {
|
||||
background-repeat: no-repeat
|
||||
}
|
||||
|
||||
.jPicker .Map .Map3, .jPicker .Bar .Map5 {
|
||||
background-repeat: repeat
|
||||
}
|
||||
|
||||
.jPicker .Bar .Map1, .jPicker .Bar .Map2, .jPicker .Bar .Map3, .jPicker .Bar .Map4 {
|
||||
background-repeat: repeat-x
|
||||
}
|
||||
|
||||
.jPicker .Map .Arrow {
|
||||
display: block;
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.jPicker .Bar .Arrow {
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.jPicker .Preview {
|
||||
font-size: 9px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.jPicker .Preview div {
|
||||
border: 2px inset #eee;
|
||||
height: 62px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 62px
|
||||
}
|
||||
|
||||
.jPicker .Preview div span {
|
||||
border: 1px solid #000;
|
||||
display: block;
|
||||
height: 30px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 60px
|
||||
}
|
||||
|
||||
.jPicker .Preview .Active {
|
||||
border-bottom-width: 0
|
||||
}
|
||||
|
||||
.jPicker .Preview .Current {
|
||||
border-top-width: 0;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.jPicker .Button {
|
||||
text-align: center;
|
||||
width: 115px
|
||||
}
|
||||
|
||||
.jPicker .Button input {
|
||||
width: 100px
|
||||
}
|
||||
|
||||
.jPicker .Button .Ok {
|
||||
margin: 12px 0 5px 0
|
||||
}
|
||||
|
||||
.jPicker td.Radio {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 31px
|
||||
}
|
||||
|
||||
.jPicker td.Radio input {
|
||||
margin: 0 5px 0 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.jPicker td.Text {
|
||||
font-size: 12px!important;
|
||||
height: 22px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
width: 70px
|
||||
}
|
||||
|
||||
.jPicker tr.Hex td.Text {
|
||||
width: 100px
|
||||
}
|
||||
|
||||
.jPicker td.Text input {
|
||||
background-color: #fff;
|
||||
border: 1px inset #aaa;
|
||||
height: 19px;
|
||||
margin: 0 0 0 5px;
|
||||
text-align: left;
|
||||
width: 30px
|
||||
}
|
||||
|
||||
.jPicker td[class="Text"] input {
|
||||
height: 15px
|
||||
}
|
||||
|
||||
.jPicker tr.Hex td.Text input.Hex {
|
||||
width: 50px
|
||||
}
|
||||
|
||||
.jPicker tr.Hex td.Text input.AHex {
|
||||
width: 20px
|
||||
}
|
||||
|
||||
.jPicker .Grid {
|
||||
text-align: center;
|
||||
width: 114px
|
||||
}
|
||||
|
||||
.jPicker .Grid span.QuickColor {
|
||||
border: 1px inset #aaa;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 19px
|
||||
}
|
||||
|
||||
.jPicker .Grid span[class="QuickColor"] {
|
||||
width: 17px
|
||||
}
|
||||
/*
|
||||
* jGraduate Default CSS
|
||||
*
|
||||
* Copyright (c) 2010 Jeff Schiller
|
||||
* http://blog.codedread.com/
|
||||
*
|
||||
* Copyright (c) 2010 Alexis Deveria
|
||||
* http://a.deveria.com/
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*/
|
||||
|
||||
h2.jGraduate_Title {
|
||||
font-family: Arial, Helvetica, Sans-Serif;
|
||||
font-size: 11px !important;
|
||||
font-weight: bold;
|
||||
margin: -13px 0px 0px 0px;
|
||||
padding: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jGraduate_Picker {
|
||||
font-family: Arial, Helvetica, Sans-Serif;
|
||||
font-size: 12px;
|
||||
border-style: solid;
|
||||
border-color: lightgrey black black lightgrey;
|
||||
border-width: 1px;
|
||||
background-color: #EFEFEF;
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.jGraduate_tabs li {
|
||||
background-color: #ccc;
|
||||
display: inline;
|
||||
border: solid 1px grey;
|
||||
padding: 3px;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.jGraduate_tab_current {
|
||||
background-color: #EFEFEF;
|
||||
display: inline;
|
||||
padding: 3px;
|
||||
margin: 2px;
|
||||
border: solid 1px black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jGraduate_colPick {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jGraduate_gradPick {
|
||||
display: none;
|
||||
border: outset 1px #666;
|
||||
padding: 10px 7px 5px 5px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.jGraduate_gradPick {
|
||||
display: none;
|
||||
border: outset 1px #666;
|
||||
padding: 10px 7px 5px 5px;
|
||||
overflow: auto;
|
||||
/* position: relative;*/
|
||||
}
|
||||
|
||||
.jGraduate_tabs {
|
||||
position: relative;
|
||||
background-color: #EFEFEF;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
div.jGraduate_Swatch {
|
||||
float: left;
|
||||
margin: 8px;
|
||||
}
|
||||
div.jGraduate_GradContainer {
|
||||
border: 2px inset #EEE;
|
||||
background-image: url(./components/jgraduate/images/map-opacity.png);
|
||||
background-position: 0px 0px;
|
||||
height: 256px;
|
||||
width: 256px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.jGraduate_GradContainer div.grad_coord {
|
||||
background: #000;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
margin: -5px -5px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
font-size: xx-small;
|
||||
line-height: 10px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.jGraduate_AlphaArrows {
|
||||
position: absolute;
|
||||
margin-top: -10px;
|
||||
margin-left: 250.5px;
|
||||
}
|
||||
|
||||
div.jGraduate_Opacity {
|
||||
border: 2px inset #eee;
|
||||
margin-top: 14px;
|
||||
background-color: black;
|
||||
background-image: url(../images/Maps.png);
|
||||
background-position: 0px -2816px;
|
||||
height: 20px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
div.jGraduate_StopSlider {
|
||||
/* border: 2px inset #eee;*/
|
||||
margin: 0 0 0 -10px;
|
||||
width: 276px;
|
||||
overflow: visible;
|
||||
background: #efefef;
|
||||
height: 45px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.jGraduate_StopSection {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
input.jGraduate_Ok, input.jGraduate_Cancel {
|
||||
display: block;
|
||||
width: 100px;
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
}
|
||||
input.jGraduate_Ok {
|
||||
margin: 9px -4px 5px -4px;
|
||||
}
|
||||
|
||||
.colorBox {
|
||||
float: left;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
border: 1px solid #808080;
|
||||
cursor: pointer;
|
||||
margin: 4px 4px 4px 30px;
|
||||
}
|
||||
|
||||
.colorBox + label {
|
||||
float: left;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
label.jGraduate_Form_Heading {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
background-color: #EFEFEF;
|
||||
padding: 2px;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.jGraduate_Form_Section {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: grey;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
padding: 15px 5px 5px 5px;
|
||||
margin: 5px 2px;
|
||||
width: 110px;
|
||||
text-align: center;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.jGraduate_Form_Section label {
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
div.jGraduate_StopSection input[type=text],
|
||||
div.jGraduate_Slider input[type=text] {
|
||||
width: 33px;
|
||||
}
|
||||
|
||||
div.jGraduate_LightBox {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: #000;
|
||||
opacity: 0.5;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.jGraduate_stopPicker {
|
||||
position: absolute;
|
||||
display: none;
|
||||
background: #E8E8E8;
|
||||
}
|
||||
|
||||
|
||||
.jGraduate_gradPick {
|
||||
width: 535px;
|
||||
}
|
||||
|
||||
.jGraduate_gradPick div.jGraduate_OpacField {
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 5px;
|
||||
/*
|
||||
width: 270px;
|
||||
|
||||
left: 284px;
|
||||
width: 266px;
|
||||
height: 200px;
|
||||
top: 167px;
|
||||
margin: -3px 3px 0px 4px;
|
||||
*/
|
||||
}
|
||||
|
||||
.jGraduate_gradPick .jGraduate_Form {
|
||||
float: left;
|
||||
width: 270px;
|
||||
position: absolute;
|
||||
left: 284px;
|
||||
width: 266px;
|
||||
height: 200px;
|
||||
top: 167px;
|
||||
margin: -3px 3px 0px 10px;
|
||||
}
|
||||
|
||||
.jGraduate_gradPick .jGraduate_Points {
|
||||
position: static;
|
||||
width: 150px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.jGraduate_SpreadMethod {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.jGraduate_Colorblocks {
|
||||
display: table;
|
||||
border-spacing: 0 5px;
|
||||
}
|
||||
|
||||
.jGraduate_colorblock {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.jGraduate_Colorblocks .jGraduate_colorblock > * {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.jGraduate_gradPick div.jGraduate_StopSection {
|
||||
float: left;
|
||||
width: 133px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
|
||||
.jGraduate_gradPick .jGraduate_Form_Section {
|
||||
padding-top: 9px;
|
||||
}
|
||||
|
||||
|
||||
.jGraduate_Slider {
|
||||
text-align: center;
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.jGraduate_Slider .jGraduate_Form_Section {
|
||||
border: none;
|
||||
width: 250px;
|
||||
padding: 0 2px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.jGraduate_Slider label {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
line-height: 50px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.jGraduate_Slider label.prelabel {
|
||||
width: 40px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.jGraduate_SliderBar {
|
||||
width: 140px;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
border:1px solid #BBB;
|
||||
height:20px;
|
||||
margin-top:14px;
|
||||
margin-left:5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.jGraduate_Slider input {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
div.jGraduate_Slider img {
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
margin-top: -10px;
|
||||
cursor:ew-resize;
|
||||
}
|
||||
|
||||
|
||||
.jGraduate_gradPick .jGraduate_OkCancel {
|
||||
position: absolute;
|
||||
top: 39px;
|
||||
right: 10px;
|
||||
width: 113px;
|
||||
|
||||
}
|
||||
|
||||
.jGraduate_OpacField {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
bottom: 0;
|
||||
}
|
||||
#logo {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
#block {
|
||||
height: 13px;
|
||||
width: 14px;
|
||||
float: right;
|
||||
background-color: darkgrey;
|
||||
}
|
||||
#picker {
|
||||
background: var(--input-color);
|
||||
height: 19px;
|
||||
line-height: 19px;
|
||||
border-radius: 3px;
|
||||
width: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
margin-top: 1px;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
#color_picker {
|
||||
z-index: 1000;
|
||||
top: -350px;
|
||||
}
|
||||
</style>
|
||||
<div id="picker">
|
||||
<img src="./images/logo.svg" alt="icon" id="logo">
|
||||
<label for="color" title="#svgEditor.i18next.t('config.change_xxx_color')}" id="label"></label>
|
||||
<div id="block">
|
||||
</div>
|
||||
</div>
|
||||
<!-- hidden div -->
|
||||
<div id="color_picker"></div>
|
||||
`;
|
||||
/**
|
||||
* @class SeColorPicker
|
||||
*/
|
||||
export class SeColorPicker extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$logo = this._shadowRoot.getElementById('logo');
|
||||
this.$label = this._shadowRoot.getElementById('label');
|
||||
this.$block = this._shadowRoot.getElementById('block');
|
||||
this.paintBox = null;
|
||||
this.$picker = this._shadowRoot.getElementById('picker');
|
||||
this.$color_picker = this._shadowRoot.getElementById('color_picker');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'label', 'src', 'type' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'src':
|
||||
this.$logo.setAttribute('src', newValue);
|
||||
break;
|
||||
case 'label':
|
||||
this.setAttribute('title', newValue);
|
||||
break;
|
||||
case 'type':
|
||||
this.$label.setAttribute('title', 'config.pick_paint_opavity');
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get label () {
|
||||
return this.$label.getAttribute('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set label (value) {
|
||||
this.setAttribute('label', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get type () {
|
||||
return this.getAttribute('type');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set type (value) {
|
||||
this.setAttribute('type', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get src () {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PlainObject} svgCanvas
|
||||
* @param {PlainObject} selectedElement
|
||||
* @param {bool} apply
|
||||
* @returns {void}
|
||||
*/
|
||||
update (svgCanvas, selectedElement, apply) {
|
||||
const paint = this.paintBox.update(svgCanvas, selectedElement);
|
||||
if (paint && apply) {
|
||||
const changeEvent = new CustomEvent('change', { detail: {
|
||||
paint
|
||||
} });
|
||||
this.dispatchEvent(changeEvent);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {PlainObject} paint
|
||||
* @returns {void}
|
||||
*/
|
||||
setPaint (paint) {
|
||||
this.paintBox.setPaint(paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
this.paintBox = new PaintBox(this.$block, this.type);
|
||||
this.$picker.addEventListener('click', () => {
|
||||
let { paint } = this.paintBox;
|
||||
jGraduateMethod(
|
||||
this.$color_picker,
|
||||
{
|
||||
images: { clientPath: './components/jgraduate/images/' },
|
||||
paint,
|
||||
window: { pickerTitle: this.label },
|
||||
newstop: 'inverse'
|
||||
},
|
||||
(p) => {
|
||||
paint = new jGraduate.Paint(p);
|
||||
this.setPaint(paint);
|
||||
const changeEvent = new CustomEvent('change', { detail: {
|
||||
paint
|
||||
} });
|
||||
this.dispatchEvent(changeEvent);
|
||||
this.$color_picker.style.display = 'none';
|
||||
},
|
||||
() => {
|
||||
this.$color_picker.style.display = 'none';
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-colorpicker', SeColorPicker);
|
||||
178
src/editor/components/seDropdown.js
Normal file
@@ -0,0 +1,178 @@
|
||||
import ListComboBox from 'elix/define/ListComboBox.js';
|
||||
import { defaultState } from 'elix/src/base/internal.js';
|
||||
import { templateFrom, fragmentFrom } from 'elix/src/core/htmlLiterals.js';
|
||||
import { internal } from 'elix';
|
||||
import NumberSpinBox from '../dialogs/se-elix/define/NumberSpinBox.js';
|
||||
|
||||
/**
|
||||
* @class Dropdown
|
||||
*/
|
||||
class Dropdown extends ListComboBox {
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [defaultState] () {
|
||||
return Object.assign(super[defaultState], {
|
||||
inputPartType: NumberSpinBox,
|
||||
src: './images/logo.svg',
|
||||
inputsize: '100%'
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [internal.template] () {
|
||||
const result = super[internal.template];
|
||||
const source = result.content.getElementById('source');
|
||||
// add a icon before our dropdown
|
||||
source.prepend(fragmentFrom.html`
|
||||
<img src="./images/logo.svg" alt="icon" width="18" height="18"></img>
|
||||
`.cloneNode(true));
|
||||
// change the style so it fits in our toolbar
|
||||
result.content.append(
|
||||
templateFrom.html`
|
||||
<style>
|
||||
[part~="source"] {
|
||||
grid-template-columns: 20px 1fr auto;
|
||||
}
|
||||
::slotted(*) {
|
||||
padding: 4px;
|
||||
background: #E8E8E8;
|
||||
border: 1px solid #5a6162;
|
||||
width: 100%;
|
||||
}
|
||||
[part~="popup"] {
|
||||
width: 150%;
|
||||
}
|
||||
</style>
|
||||
`.content
|
||||
);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'src', 'inputsize', 'value' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'title':
|
||||
// this.$span.setAttribute('title', `${newValue} ${shortcut ? `[${shortcut}]` : ''}`);
|
||||
break;
|
||||
case 'src':
|
||||
this.src = newValue;
|
||||
break;
|
||||
case 'inputsize':
|
||||
this.inputsize = newValue;
|
||||
break;
|
||||
default:
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function [internal.render]
|
||||
* @param {PlainObject} changed
|
||||
* @returns {void}
|
||||
*/
|
||||
[internal.render] (changed) {
|
||||
super[internal.render](changed);
|
||||
if (this[internal.firstRender]) {
|
||||
this.$img = this.shadowRoot.querySelector('img');
|
||||
this.$input = this.shadowRoot.getElementById('input');
|
||||
}
|
||||
if (changed.src) {
|
||||
this.$img.setAttribute('src', this[internal.state].src);
|
||||
}
|
||||
if (changed.inputsize) {
|
||||
this.$input.shadowRoot.querySelector('[part~="input"]').style.width = this[internal.state].inputsize;
|
||||
}
|
||||
if (changed.inputPartType) {
|
||||
// Wire up handler on new input.
|
||||
this.addEventListener('close', (e) => {
|
||||
e.preventDefault();
|
||||
const value = e.detail?.closeResult?.getAttribute('value');
|
||||
if (value) {
|
||||
const closeEvent = new CustomEvent('change', { detail: { value } });
|
||||
this.dispatchEvent(closeEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function src
|
||||
* @returns {string} src
|
||||
*/
|
||||
get src () {
|
||||
return this[internal.state].src;
|
||||
}
|
||||
/**
|
||||
* @function src
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (src) {
|
||||
this[internal.setState]({ src });
|
||||
}
|
||||
/**
|
||||
* @function inputsize
|
||||
* @returns {string} src
|
||||
*/
|
||||
get inputsize () {
|
||||
return this[internal.state].inputsize;
|
||||
}
|
||||
/**
|
||||
* @function src
|
||||
* @returns {void}
|
||||
*/
|
||||
set inputsize (inputsize) {
|
||||
this[internal.setState]({ inputsize });
|
||||
}
|
||||
/**
|
||||
* @function value
|
||||
* @returns {string} src
|
||||
*/
|
||||
get value () {
|
||||
return this[internal.state].value;
|
||||
}
|
||||
/**
|
||||
* @function value
|
||||
* @returns {void}
|
||||
*/
|
||||
set value (value) {
|
||||
this[internal.setState]({ value });
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-dropdown', Dropdown);
|
||||
|
||||
/*
|
||||
{TODO
|
||||
min: 0.001, max: 10000, step: 50, stepfunc: stepZoom,
|
||||
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;
|
||||
}
|
||||
*/
|
||||
315
src/editor/components/seExplorerButton.js
Normal file
@@ -0,0 +1,315 @@
|
||||
/* eslint-disable no-unsanitized/property */
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
position:relative;
|
||||
}
|
||||
.menu-button:hover, se-button:hover, .menu-item:hover
|
||||
{
|
||||
background-color: var(--icon-bg-color-hover);
|
||||
}
|
||||
img {
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.overall.pressed .button-icon,
|
||||
.overall.pressed,
|
||||
.menu-item.pressed {
|
||||
background-color: var(--icon-bg-color-hover) !important;
|
||||
}
|
||||
.overall.pressed .menu-button {
|
||||
background-color: var(--icon-bg-color-hover) !important;
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
}
|
||||
.menu-button {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin: 2px 1px 4px;
|
||||
padding: 3px;
|
||||
background-color: var(--icon-bg-color);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-image: url(./images/handle.svg);
|
||||
position:absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.button-icon {
|
||||
}
|
||||
.menu {
|
||||
position: absolute;
|
||||
top:0px;
|
||||
left:204px;
|
||||
background: none !important;
|
||||
display:none;
|
||||
}
|
||||
.image-lib {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left:34px;
|
||||
background: #E8E8E8;
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
width: 170px;
|
||||
}
|
||||
.menu-item {
|
||||
line-height: 1em;
|
||||
padding: 0.5em;
|
||||
border: 1px solid #5a6162;
|
||||
background: #E8E8E8;
|
||||
margin-bottom: -1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.open-lib {
|
||||
display: inline-flex;
|
||||
}
|
||||
.open {
|
||||
display: block;
|
||||
}
|
||||
.overall {
|
||||
background: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="overall">
|
||||
<div class="menu-button">
|
||||
<img class="button-icon" src="./images/logo.svg" alt="icon">
|
||||
<div class="handle"></div>
|
||||
</div>
|
||||
<div class="image-lib"">
|
||||
<se-button></se-button>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<div class="menu-item">menu</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
/**
|
||||
* @class ExplorerButton
|
||||
*/
|
||||
export class ExplorerButton extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
// locate the component
|
||||
this.$button = this._shadowRoot.querySelector('.menu-button');
|
||||
this.$overall = this._shadowRoot.querySelector('.overall');
|
||||
this.$img = this._shadowRoot.querySelector('.menu-button img');
|
||||
this.$menu = this._shadowRoot.querySelector('.menu');
|
||||
this.$handle = this._shadowRoot.querySelector('.handle');
|
||||
this.$lib = this._shadowRoot.querySelector('.image-lib');
|
||||
this.files = [];
|
||||
this.request = new XMLHttpRequest();
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'pressed', 'disabled', 'lib', 'src' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
async attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'title':
|
||||
{
|
||||
const shortcut = this.getAttribute('shortcut');
|
||||
this.$button.setAttribute('title', `${newValue} [${shortcut}]`);
|
||||
}
|
||||
break;
|
||||
case 'pressed':
|
||||
if (newValue) {
|
||||
this.$overall.classList.add('pressed');
|
||||
} else {
|
||||
this.$overall.classList.remove('pressed');
|
||||
}
|
||||
break;
|
||||
case 'disabled':
|
||||
if (newValue) {
|
||||
this.$div.classList.add('disabled');
|
||||
} else {
|
||||
this.$div.classList.remove('disabled');
|
||||
}
|
||||
break;
|
||||
case 'lib':
|
||||
try {
|
||||
const response = await fetch(`${newValue}index.json`);
|
||||
const json = await response.json();
|
||||
const { lib } = json;
|
||||
this.$menu.innerHTML = lib.map((menu, i) => (
|
||||
`<div data-menu="${menu}" class="menu-item ${(i === 0) ? 'pressed' : ''} ">${menu}</div>`
|
||||
)).join('');
|
||||
await this.updateLib(lib[0]);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
break;
|
||||
case 'src':
|
||||
this.$img.setAttribute('src', newValue);
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get title () {
|
||||
return this.getAttribute('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set title (value) {
|
||||
this.setAttribute('title', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get pressed () {
|
||||
return this.hasAttribute('pressed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set pressed (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('pressed', 'true');
|
||||
} else {
|
||||
this.removeAttribute('pressed', '');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get disabled () {
|
||||
return this.hasAttribute('disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set disabled (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.removeAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
// capture click event on the button to manage the logic
|
||||
const onClickHandler = (ev) => {
|
||||
ev.stopPropagation();
|
||||
switch (ev.target.nodeName) {
|
||||
case 'SE-EXPLORERBUTTON':
|
||||
this.$menu.classList.add('open');
|
||||
this.$lib.classList.add('open-lib');
|
||||
break;
|
||||
case 'SE-BUTTON':
|
||||
// change to the current action
|
||||
this.currentAction = ev.target;
|
||||
this.$img.setAttribute('src', this.currentAction.getAttribute('src'));
|
||||
this.dataset.draw = this.data[this.currentAction.dataset.shape];
|
||||
this._shadowRoot.querySelectorAll('.image-lib [pressed]').forEach((b) => { b.pressed = false; });
|
||||
this.currentAction.setAttribute('pressed', 'pressed');
|
||||
// and close the menu
|
||||
this.$menu.classList.remove('open');
|
||||
this.$lib.classList.remove('open-lib');
|
||||
break;
|
||||
case 'DIV':
|
||||
if (ev.target.classList[0] === 'handle') {
|
||||
// this is a click on the handle so let's open/close the menu.
|
||||
this.$menu.classList.toggle('open');
|
||||
this.$lib.classList.toggle('open-lib');
|
||||
} else {
|
||||
this._shadowRoot.querySelectorAll('.menu > .pressed').forEach((b) => { b.classList.remove('pressed'); });
|
||||
ev.target.classList.add('pressed');
|
||||
this.updateLib(ev.target.dataset.menu);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('unknown nodeName for:', ev.target, ev.target.className);
|
||||
}
|
||||
};
|
||||
// capture event from slots
|
||||
this.addEventListener('click', onClickHandler);
|
||||
this.$menu.addEventListener('click', onClickHandler);
|
||||
this.$lib.addEventListener('click', onClickHandler);
|
||||
this.$handle.addEventListener('click', onClickHandler);
|
||||
}
|
||||
/**
|
||||
* @function updateLib
|
||||
* @param {string} lib
|
||||
* @returns {void}
|
||||
*/
|
||||
async updateLib (lib) {
|
||||
const libDir = this.getAttribute('lib');
|
||||
try {
|
||||
// initialize buttons for all shapes defined for this library
|
||||
const response = await fetch(`${libDir}${lib}.json`);
|
||||
const json = await response.json();
|
||||
this.data = json.data;
|
||||
const size = json.size ?? 300;
|
||||
const fill = json.fill ? '#333' : 'none';
|
||||
const off = size * 0.05;
|
||||
const vb = [ -off, -off, size + off * 2, size + off * 2 ].join(' ');
|
||||
const stroke = json.fill ? 0 : (size / 30);
|
||||
this.$lib.innerHTML = Object.entries(this.data).map(([ key, path ]) => {
|
||||
const encoded = btoa(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
||||
<svg viewBox="${vb}"><path fill="${fill}" stroke="#000" stroke-width="${stroke}" d="${path}"></path></svg>
|
||||
</svg>`);
|
||||
return `<se-button data-shape="${key}"src="data:image/svg+xml;base64,${encoded}"></se-button>`;
|
||||
}).join('');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`could not read file:${libDir}${lib}.json`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-explorerbutton', ExplorerButton);
|
||||
279
src/editor/components/seFlyingButton.js
Normal file
@@ -0,0 +1,279 @@
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
position:relative;
|
||||
}
|
||||
.overall:hover *
|
||||
{
|
||||
background-color: var(--icon-bg-color-hover);
|
||||
}
|
||||
img {
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.overall.pressed .button-icon,
|
||||
.overall.pressed .handle {
|
||||
background-color: var(--icon-bg-color-hover) !important;
|
||||
}
|
||||
.overall.pressed .menu-button {
|
||||
background-color: var(--icon-bg-color-hover) !important;
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
}
|
||||
.menu-button {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin: 2px 1px 4px;
|
||||
padding: 3px;
|
||||
background-color: var(--icon-bg-color);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.handle {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-image: url(./images/handle.svg);
|
||||
position:absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.button-icon {
|
||||
}
|
||||
.menu {
|
||||
position: absolute;
|
||||
top:-2px;
|
||||
left:32px;
|
||||
background: none !important;
|
||||
display:none;
|
||||
}
|
||||
.open {
|
||||
display: flex;
|
||||
}
|
||||
.menu-item {
|
||||
align-content: flex-start;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
top:0px;
|
||||
left:0px;
|
||||
}
|
||||
.overall {
|
||||
background: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="overall">
|
||||
<div class="menu-button">
|
||||
<img class="button-icon" src="./images/logo.svg" alt="icon">
|
||||
<div class="handle"></div>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
/**
|
||||
* @class FlyingButton
|
||||
*/
|
||||
export class FlyingButton extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
// locate the component
|
||||
this.$button = this._shadowRoot.querySelector('.menu-button');
|
||||
this.$handle = this._shadowRoot.querySelector('.handle');
|
||||
this.$overall = this._shadowRoot.querySelector('.overall');
|
||||
this.$img = this._shadowRoot.querySelector('img');
|
||||
this.$menu = this._shadowRoot.querySelector('.menu');
|
||||
// the last element of the div is the slot
|
||||
// we retrieve all elements added in the slot (i.e. se-buttons)
|
||||
this.$elements = this.$menu.lastElementChild.assignedElements();
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'pressed', 'disabled', 'opened' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'title':
|
||||
{
|
||||
const shortcut = this.getAttribute('shortcut');
|
||||
this.$button.setAttribute('title', `${newValue} [${shortcut}]`);
|
||||
}
|
||||
break;
|
||||
case 'pressed':
|
||||
if (newValue) {
|
||||
this.$overall.classList.add('pressed');
|
||||
} else {
|
||||
this.$overall.classList.remove('pressed');
|
||||
}
|
||||
break;
|
||||
case 'opened':
|
||||
if (newValue) {
|
||||
this.$menu.classList.add('open');
|
||||
} else {
|
||||
this.$menu.classList.remove('open');
|
||||
}
|
||||
break;
|
||||
case 'disabled':
|
||||
if (newValue) {
|
||||
this.$div.classList.add('disabled');
|
||||
} else {
|
||||
this.$div.classList.remove('disabled');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get title () {
|
||||
return this.getAttribute('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set title (value) {
|
||||
this.setAttribute('title', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get pressed () {
|
||||
return this.hasAttribute('pressed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set pressed (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('pressed', 'true');
|
||||
} else {
|
||||
this.removeAttribute('pressed', '');
|
||||
// close also the menu if open
|
||||
this.removeAttribute('opened');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get opened () {
|
||||
return this.hasAttribute('opened');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set opened (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('opened', 'opened');
|
||||
} else {
|
||||
this.removeAttribute('opened');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get disabled () {
|
||||
return this.hasAttribute('disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set disabled (value) {
|
||||
// boolean value => existence = true
|
||||
if (value) {
|
||||
this.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.removeAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
// initialize currentAction with the first slot of the list
|
||||
this.activeSlot = this.shadowRoot.querySelector('slot').assignedElements()[0];
|
||||
this.$img.setAttribute('src', this.activeSlot.getAttribute('src'));
|
||||
// capture click event on the button to manage the logic
|
||||
const onClickHandler = (ev) => {
|
||||
ev.stopPropagation();
|
||||
switch (ev.target.nodeName) {
|
||||
case 'SE-FLYINGBUTTON':
|
||||
if (this.pressed) {
|
||||
this.setAttribute('opened', 'opened');
|
||||
} else {
|
||||
// launch current action
|
||||
this.activeSlot.click();
|
||||
this.setAttribute('pressed', 'pressed');
|
||||
}
|
||||
break;
|
||||
case 'SE-BUTTON':
|
||||
// change to the current action
|
||||
this.$img.setAttribute('src', ev.target.getAttribute('src'));
|
||||
this.activeSlot = ev.target;
|
||||
this.setAttribute('pressed', 'pressed');
|
||||
// and close the menu
|
||||
this.$menu.classList.remove('open');
|
||||
break;
|
||||
case 'DIV':
|
||||
// this is a click on the handle so let's open/close the menu.
|
||||
if (this.opened) {
|
||||
this.removeAttribute('opened');
|
||||
} else {
|
||||
this.setAttribute('opened', 'opened');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('unkonw nodeName for:', ev.target, ev.target.className);
|
||||
}
|
||||
};
|
||||
// capture event from slots
|
||||
this.addEventListener('click', onClickHandler);
|
||||
this.$handle.addEventListener('click', onClickHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-flyingbutton', FlyingButton);
|
||||
158
src/editor/components/seInput.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import 'elix/define/Input.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
img {
|
||||
top: 2px;
|
||||
left: 4px;
|
||||
position: relative;
|
||||
}
|
||||
span {
|
||||
bottom: 1px;
|
||||
right: -4px;
|
||||
position: relative;
|
||||
margin-right: 4px;
|
||||
color: #fff;
|
||||
}
|
||||
elix-input {
|
||||
background-color: var(--input-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
<img src="./images/logo.svg" alt="icon" width="12" height="12" />
|
||||
<span id="label">label</span>
|
||||
<elix-input></elix-input>
|
||||
`;
|
||||
|
||||
/**
|
||||
* @class SEInput
|
||||
*/
|
||||
export class SEInput extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
// locate the component
|
||||
this.$img = this._shadowRoot.querySelector('img');
|
||||
this.$label = this.shadowRoot.getElementById('label');
|
||||
this.$event = new CustomEvent('change');
|
||||
this.$input = this._shadowRoot.querySelector('elix-input');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'value', 'label', 'src', 'size' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'src':
|
||||
this.$img.setAttribute('src', newValue);
|
||||
this.$label.remove();
|
||||
break;
|
||||
case 'size':
|
||||
this.$input.setAttribute('size', newValue);
|
||||
break;
|
||||
case 'label':
|
||||
this.$label.textContent = newValue;
|
||||
this.$img.remove();
|
||||
break;
|
||||
case 'value':
|
||||
this.$input.value = newValue;
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get label () {
|
||||
return this.getAttribute('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set label (value) {
|
||||
this.setAttribute('label', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get value () {
|
||||
return this.$input.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set value (value) {
|
||||
this.$input.value = value;
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get src () {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get size () {
|
||||
return this.getAttribute('size');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set size (value) {
|
||||
this.setAttribute('size', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
this.addEventListener('change', (e) => {
|
||||
e.preventDefault();
|
||||
this.value = e.target.value;
|
||||
});
|
||||
this.dispatchEvent(this.$event);
|
||||
}
|
||||
}
|
||||
// Register
|
||||
customElements.define('se-input', SEInput);
|
||||
140
src/editor/components/seList.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'elix/define/DropdownList.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
elix-dropdown-list {
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
elix-dropdown-list:hover {
|
||||
background-color: var(--icon-bg-color-hover);
|
||||
}
|
||||
|
||||
::part(popup-toggle) {
|
||||
display: none;
|
||||
}
|
||||
::slotted(*) {
|
||||
padding:0;
|
||||
width:100%;
|
||||
}
|
||||
</style>
|
||||
<label>Label</label>
|
||||
<elix-dropdown-list>
|
||||
<slot></slot>
|
||||
</elix-dropdown-list>
|
||||
|
||||
`;
|
||||
/**
|
||||
* @class SeList
|
||||
*/
|
||||
export class SeList extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$dropdown = this._shadowRoot.querySelector('elix-dropdown-list');
|
||||
this.$label = this._shadowRoot.querySelector('label');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'label', 'width', 'height' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'label':
|
||||
this.$label.textContent = newValue;
|
||||
break;
|
||||
case 'height':
|
||||
this.$dropdown.style.height = newValue;
|
||||
break;
|
||||
case 'width':
|
||||
this.$dropdown.style.width = newValue;
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get label () {
|
||||
return this.getAttribute('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set label (value) {
|
||||
this.setAttribute('label', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get width () {
|
||||
return this.getAttribute('width');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set width (value) {
|
||||
this.setAttribute('width', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get height () {
|
||||
return this.getAttribute('height');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set height (value) {
|
||||
this.setAttribute('height', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
const currentObj = this;
|
||||
this.$dropdown.addEventListener('selectedindexchange', (e) => {
|
||||
e.preventDefault();
|
||||
if (e?.detail?.selectedIndex !== undefined) {
|
||||
const value = this.$dropdown.selectedItem.getAttribute('value');
|
||||
const closeEvent = new CustomEvent('change', { detail: { value } });
|
||||
currentObj.dispatchEvent(closeEvent);
|
||||
currentObj.value = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-list', SeList);
|
||||
80
src/editor/components/seListItem.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'elix/define/Option.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
elix-option{
|
||||
padding:0.25rem 0.125rem !important;
|
||||
background-color: var(--icon-bg-color);
|
||||
}
|
||||
elix-option:hover{
|
||||
background-color: var(--icon-bg-color-hover);
|
||||
}
|
||||
</style>
|
||||
<elix-option aria-label="option">
|
||||
<slot></slot>
|
||||
</elix-option>
|
||||
`;
|
||||
/**
|
||||
* @class SeMenu
|
||||
*/
|
||||
export class SeListItem extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$menuitem = this._shadowRoot.querySelector('elix-option');
|
||||
this.$svg = this.$menuitem.shadowRoot.querySelector('#checkmark');
|
||||
this.$svg.setAttribute('style', 'display: none;');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'option' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
// eslint-disable-next-line sonarjs/no-small-switch
|
||||
switch (name) {
|
||||
case 'option':
|
||||
this.$menuitem.setAttribute('option', newValue);
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get option () {
|
||||
return this.getAttribute('option');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set option (value) {
|
||||
this.setAttribute('option', value);
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-list-item', SeListItem);
|
||||
127
src/editor/components/seMenu.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'elix/define/MenuItem.js';
|
||||
import './sePlainMenuButton.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
padding: 0px;
|
||||
}
|
||||
elix-menu-button::part(menu) {
|
||||
background-color: var(--icon-bg-color) !important;
|
||||
color: #fff;
|
||||
}
|
||||
elix-menu-button::part(popup-toggle) {
|
||||
padding: 0.25em 0.30em !important
|
||||
}
|
||||
:host ::slotted([current]){
|
||||
background-color: var(--icon-bg-color-hover) !important;
|
||||
color: #fff;
|
||||
}
|
||||
:host ::slotted(*){
|
||||
padding: 0.25em 1.25em 0.25em 0.25em !important;
|
||||
margin: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<elix-menu-button id="MenuButton" aria-label="Main Menu">
|
||||
<slot></slot>
|
||||
</elix-menu-button>
|
||||
|
||||
`;
|
||||
/**
|
||||
* @class SeMenu
|
||||
*/
|
||||
export class SeMenu extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$menu = this._shadowRoot.querySelector('elix-menu-button');
|
||||
this.$label = this.$menu.shadowRoot.querySelector('#popupToggle').shadowRoot;
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'label', 'src' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
const image = new Image();
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'src':
|
||||
image.src = newValue;
|
||||
image.width = 24;
|
||||
image.height = 24;
|
||||
this.$label.prepend(image);
|
||||
break;
|
||||
case 'label':
|
||||
this.$label.prepend(newValue);
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get label () {
|
||||
return this.getAttribute('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set label (value) {
|
||||
this.setAttribute('label', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get src () {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
/* connectedCallback () {
|
||||
this.$menu.addEventListener('openedchange', (e) => {
|
||||
e.preventDefault();
|
||||
const selectedItem = e?.detail?.closeResult;
|
||||
if (selectedItem !== undefined && selectedItem?.id !== undefined) {
|
||||
document.getElementById(selectedItem.id).click();
|
||||
}
|
||||
});
|
||||
} */
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-menu', SeMenu);
|
||||
122
src/editor/components/seMenuItem.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'elix/define/Menu.js';
|
||||
import 'elix/define/MenuItem.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
</style>
|
||||
<elix-menu-item>
|
||||
<div style="display:flex; align-items: center;">
|
||||
<img src="./images/logo.svg" alt="icon" style="display:none;" width="24"/>
|
||||
<span style="margin-left: 7px;"></span>
|
||||
</div>
|
||||
</elix-menu-item>
|
||||
`;
|
||||
/**
|
||||
* @class SeMenuItem
|
||||
*/
|
||||
export class SeMenuItem extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$img = this._shadowRoot.querySelector('img');
|
||||
this.$label = this._shadowRoot.querySelector('span');
|
||||
this.$menuitem = this._shadowRoot.querySelector('elix-menu-item');
|
||||
this.$svg = this.$menuitem.shadowRoot.querySelector('#checkmark');
|
||||
this.$svg.setAttribute('style', 'display: none;');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'label', 'src' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
let shortcut = '';
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'src':
|
||||
this.$img.setAttribute('src', newValue);
|
||||
this.$img.style.display = 'inline-block';
|
||||
break;
|
||||
case 'label':
|
||||
shortcut = this.getAttribute('shortcut');
|
||||
this.$label.textContent = `${newValue} ${shortcut ? `(${shortcut})` : ''}`;
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get label () {
|
||||
return this.getAttribute('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set label (value) {
|
||||
this.setAttribute('label', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get src () {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
// capture shortcuts
|
||||
const shortcut = this.getAttribute('shortcut');
|
||||
if (shortcut) {
|
||||
// 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.toUpperCase()}`;
|
||||
if (shortcut !== key) return;
|
||||
// launch the click event
|
||||
if (this.id) {
|
||||
document.getElementById(this.id).click();
|
||||
}
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-menu-item', SeMenuItem);
|
||||
139
src/editor/components/sePalette.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/* eslint-disable max-len */
|
||||
/* gl#bals svgEditor */
|
||||
const palette = [
|
||||
// Todo: Make into configuration item?
|
||||
'none', '#000000', '#3f3f3f', '#7f7f7f', '#bfbfbf', '#ffffff',
|
||||
'#ff0000', '#ff7f00', '#ffff00', '#7fff00',
|
||||
'#00ff00', '#00ff7f', '#00ffff', '#007fff',
|
||||
'#0000ff', '#7f00ff', '#ff00ff', '#ff007f',
|
||||
'#7f0000', '#7f3f00', '#7f7f00', '#3f7f00',
|
||||
'#007f00', '#007f3f', '#007f7f', '#003f7f',
|
||||
'#00007f', '#3f007f', '#7f007f', '#7f003f',
|
||||
'#ffaaaa', '#ffd4aa', '#ffffaa', '#d4ffaa',
|
||||
'#aaffaa', '#aaffd4', '#aaffff', '#aad4ff',
|
||||
'#aaaaff', '#d4aaff', '#ffaaff', '#ffaad4'
|
||||
];
|
||||
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
.square {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
float: left;
|
||||
}
|
||||
#palette_holder {
|
||||
overflow: hidden;
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
height: 16px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#js-se-palette {
|
||||
float: left;
|
||||
width: 632px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
div.palette_item {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.palette_item:first-child {
|
||||
background: white;
|
||||
}
|
||||
@media screen and (max-width:1100px) {
|
||||
#palette_holder {
|
||||
left: 410px;
|
||||
overflow-x: scroll;
|
||||
padding: 0 5px;
|
||||
margin-top: 2px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width:1250px) {
|
||||
#palette_holder {
|
||||
left: 560px;
|
||||
overflow-x: scroll;
|
||||
padding: 0 5px;
|
||||
margin-top: 2px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width:540px) {
|
||||
#palette_holder {
|
||||
left: 0px;
|
||||
overflow-x: scroll;
|
||||
padding: 0 5px;
|
||||
margin-top: 32px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="palette_holder" title="$
|
||||
#{svgEditor.i18next.t('ui.palette_info')}">
|
||||
<div id="js-se-palette">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* @class SEPalette
|
||||
*/
|
||||
export class SEPalette extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$strip = this._shadowRoot.querySelector('#js-se-palette');
|
||||
palette.forEach((rgb) => {
|
||||
const newDiv = document.createElement('div');
|
||||
newDiv.classList.add('square');
|
||||
if(rgb === 'none') {
|
||||
const img = document.createElement('img');
|
||||
img.src = `data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9InN2Z19pY29uIj48c3ZnIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8bGluZSBmaWxsPSJub25lIiBzdHJva2U9IiNkNDAwMDAiIGlkPSJzdmdfOTAiIHkyPSIyNCIgeDI9IjI0IiB5MT0iMCIgeDE9IjAiLz4KICAgIDxsaW5lIGlkPSJzdmdfOTIiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2Q0MDAwMCIgeTI9IjI0IiB4Mj0iMCIgeTE9IjAiIHgxPSIyNCIvPgogIDwvc3ZnPjwvc3ZnPg==`;
|
||||
img.style.width = "15px";
|
||||
img.style.height = "15px";
|
||||
newDiv.append(img);
|
||||
} else {
|
||||
newDiv.style.backgroundColor = rgb;
|
||||
}
|
||||
newDiv.dataset.rgb = rgb;
|
||||
newDiv.addEventListener('click', (evt) => {
|
||||
evt.preventDefault();
|
||||
// shift key or right click for stroke
|
||||
const picker = evt.shiftKey || evt.button === 2 ? 'stroke' : 'fill';
|
||||
let color = newDiv.dataset.rgb;
|
||||
// Webkit-based browsers returned 'initial' here for no stroke
|
||||
if (color === 'none' || color === 'transparent' || color === 'initial') {
|
||||
color = 'none';
|
||||
}
|
||||
const paletteEvent = new CustomEvent('change', { detail: { picker, color }, bubbles: false });
|
||||
this.dispatchEvent(paletteEvent);
|
||||
});
|
||||
this.$strip.append(newDiv);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-palette', SEPalette);
|
||||
32
src/editor/components/sePlainBorderButton.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { template } from 'elix/src/base/internal.js';
|
||||
import { fragmentFrom } from 'elix/src/core/htmlLiterals.js';
|
||||
import PlainButton from 'elix/src/plain/PlainButton.js';
|
||||
|
||||
/**
|
||||
* @class SePlainBorderButton
|
||||
* Button with a border in the Plain reference design system
|
||||
*
|
||||
* @inherits PlainButton
|
||||
*/
|
||||
class SePlainBorderButton extends PlainButton {
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [template] () {
|
||||
const result = super[template];
|
||||
result.content.append(
|
||||
fragmentFrom.html`
|
||||
<style>
|
||||
[part~="button"] {
|
||||
background: #72797A;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default SePlainBorderButton;
|
||||
20
src/editor/components/sePlainMenuButton.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import PlainMenuButton from 'elix/src/plain/PlainMenuButton.js';
|
||||
import { defaultState } from 'elix/src/base/internal.js';
|
||||
import sePlainBorderButton from './sePlainBorderButton.js';
|
||||
|
||||
/**
|
||||
* @class ElixMenuButton
|
||||
*/
|
||||
export default class ElixMenuButton extends PlainMenuButton {
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [defaultState] () {
|
||||
return Object.assign(super[defaultState], {
|
||||
sourcePartType: sePlainBorderButton
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('elix-menu-button', ElixMenuButton);
|
||||
191
src/editor/components/seSpinInput.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import '../dialogs/se-elix/define/NumberSpinBox.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
img {
|
||||
position: relative;
|
||||
right: -4px;
|
||||
}
|
||||
span {
|
||||
bottom: -3px;
|
||||
right: -4px;
|
||||
position: relative;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
color: #fff;
|
||||
vertical-align: ;
|
||||
}
|
||||
elix-number-spin-box {
|
||||
background-color: var(--input-color);
|
||||
border-radius: 3px;
|
||||
height: 20px;
|
||||
margin-top: 1px;
|
||||
vertical-align: top;
|
||||
}
|
||||
elix-number-spin-box::part(spin-button) {
|
||||
padding: 0px;
|
||||
}
|
||||
elix-number-spin-box::part(input) {
|
||||
width: 3em;
|
||||
}
|
||||
elix-number-spin-box{
|
||||
width: 54px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
<img src="./images/logo.svg" alt="icon" width="24" height="24" aria-labelledby="label" />
|
||||
<span id="label">label</span>
|
||||
<elix-number-spin-box min="1" step="1"></elix-number-spin-box>
|
||||
`;
|
||||
|
||||
/**
|
||||
* @class SESpinInput
|
||||
*/
|
||||
export class SESpinInput extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
// locate the component
|
||||
this.$img = this._shadowRoot.querySelector('img');
|
||||
this.$label = this.shadowRoot.getElementById('label');
|
||||
this.$event = new CustomEvent('change');
|
||||
this.$input = this._shadowRoot.querySelector('elix-number-spin-box');
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'value', 'label', 'src', 'size', 'min', 'max', 'step' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'src':
|
||||
this.$img.setAttribute('src', newValue);
|
||||
this.$label.remove();
|
||||
break;
|
||||
case 'size':
|
||||
// access to the underlying input box
|
||||
this.$input.shadowRoot.getElementById('input').size = newValue;
|
||||
// below seems mandatory to override the default width style that takes precedence on size
|
||||
this.$input.shadowRoot.getElementById('input').style.width = 'unset';
|
||||
break;
|
||||
case 'step':
|
||||
this.$input.setAttribute('step', newValue);
|
||||
break;
|
||||
case 'min':
|
||||
this.$input.setAttribute('min', newValue);
|
||||
break;
|
||||
case 'max':
|
||||
this.$input.setAttribute('max', newValue);
|
||||
break;
|
||||
case 'label':
|
||||
this.$label.textContent = newValue;
|
||||
this.$img.remove();
|
||||
break;
|
||||
case 'value':
|
||||
this.$input.value = newValue;
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`unknown attribute: ${name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get label () {
|
||||
return this.getAttribute('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set label (value) {
|
||||
this.setAttribute('label', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get value () {
|
||||
return this.$input.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set value (value) {
|
||||
this.$input.value = value;
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get src () {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (value) {
|
||||
this.setAttribute('src', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get size () {
|
||||
return this.getAttribute('size');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set size (value) {
|
||||
this.setAttribute('size', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
this.$input.addEventListener('change', (e) => {
|
||||
e.preventDefault();
|
||||
this.value = e.target.value;
|
||||
this.dispatchEvent(this.$event);
|
||||
});
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
this.$input.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.value = e.target.value;
|
||||
this.dispatchEvent(this.$event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-spin-input', SESpinInput);
|
||||
188
src/editor/components/seZoom.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import ListComboBox from 'elix/define/ListComboBox.js';
|
||||
import * as internal from 'elix/src/base/internal.js';
|
||||
import { templateFrom, fragmentFrom } from 'elix/src/core/htmlLiterals.js';
|
||||
import NumberSpinBox from '../dialogs/se-elix/define/NumberSpinBox.js';
|
||||
|
||||
/**
|
||||
* @class Dropdown
|
||||
*/
|
||||
class Zoom extends ListComboBox {
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [internal.defaultState] () {
|
||||
return Object.assign(super[internal.defaultState], {
|
||||
inputPartType: NumberSpinBox,
|
||||
src: './images/logo.svg',
|
||||
inputsize: '100%'
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [internal.template] () {
|
||||
const result = super[internal.template];
|
||||
const source = result.content.getElementById('source');
|
||||
// add a icon before our dropdown
|
||||
source.prepend(fragmentFrom.html`
|
||||
<img src="./images/logo.svg" alt="icon" width="18" height="18"></img>
|
||||
`.cloneNode(true));
|
||||
// change the style so it fits in our toolbar
|
||||
result.content.append(
|
||||
templateFrom.html`
|
||||
<style>
|
||||
[part~="source"] {
|
||||
grid-template-columns: 20px 1fr auto;
|
||||
}
|
||||
::slotted(*) {
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
background-color: var(--icon-bg-color);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
[part~="popup"] {
|
||||
width: 150%;
|
||||
}
|
||||
elix-number-spin-box {
|
||||
background-color: var(--input-color);
|
||||
border-radius: 3px;
|
||||
height: 20px !important;
|
||||
margin-top: 1px;
|
||||
}
|
||||
elix-number-spin-box::part(spin-button) {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
`.content
|
||||
);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'src', 'inputsize', 'value' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
switch (name) {
|
||||
case 'title':
|
||||
// this.$span.setAttribute('title', `${newValue} ${shortcut ? `[${shortcut}]` : ''}`);
|
||||
break;
|
||||
case 'src':
|
||||
this.src = newValue;
|
||||
break;
|
||||
case 'inputsize':
|
||||
this.inputsize = newValue;
|
||||
break;
|
||||
default:
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function [internal.render]
|
||||
* @param {PlainObject} changed
|
||||
* @returns {void}
|
||||
*/
|
||||
[internal.render] (changed) {
|
||||
super[internal.render](changed);
|
||||
if (this[internal.firstRender]) {
|
||||
this.$img = this.shadowRoot.querySelector('img');
|
||||
this.$input = this.shadowRoot.getElementById('input');
|
||||
}
|
||||
if (changed.src) {
|
||||
this.$img.setAttribute('src', this[internal.state].src);
|
||||
}
|
||||
if (changed.inputsize) {
|
||||
this.$input.shadowRoot.querySelector('[part~="input"]').style.width = this[internal.state].inputsize;
|
||||
}
|
||||
if (changed.inputPartType) {
|
||||
// Wire up handler on new input.
|
||||
this.addEventListener('close', (e) => {
|
||||
e.preventDefault();
|
||||
const value = e.detail?.closeResult?.getAttribute('value');
|
||||
if (value) {
|
||||
const closeEvent = new CustomEvent('change', { detail: { value } });
|
||||
this.dispatchEvent(closeEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function src
|
||||
* @returns {string} src
|
||||
*/
|
||||
get src () {
|
||||
return this[internal.state].src;
|
||||
}
|
||||
/**
|
||||
* @function src
|
||||
* @returns {void}
|
||||
*/
|
||||
set src (src) {
|
||||
this[internal.setState]({ src });
|
||||
}
|
||||
/**
|
||||
* @function inputsize
|
||||
* @returns {string} src
|
||||
*/
|
||||
get inputsize () {
|
||||
return this[internal.state].inputsize;
|
||||
}
|
||||
/**
|
||||
* @function src
|
||||
* @returns {void}
|
||||
*/
|
||||
set inputsize (inputsize) {
|
||||
this[internal.setState]({ inputsize });
|
||||
}
|
||||
/**
|
||||
* @function value
|
||||
* @returns {string} src
|
||||
*/
|
||||
get value () {
|
||||
return this[internal.state].value;
|
||||
}
|
||||
/**
|
||||
* @function value
|
||||
* @returns {void}
|
||||
*/
|
||||
set value (value) {
|
||||
this[internal.setState]({ value });
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-zoom', Zoom);
|
||||
|
||||
/*
|
||||
{TODO
|
||||
min: 0.001, max: 10000, step: 50, stepfunc: stepZoom,
|
||||
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;
|
||||
}
|
||||
*/
|
||||
@@ -48,7 +48,6 @@ export const add = function (menuItem) {
|
||||
throw new Error('Cannot add extension "' + menuItem.id + '", an extension by that name already exists"');
|
||||
}
|
||||
// Register menuItem action, see below for deferred menu dom injection
|
||||
console.log('Registered contextmenu item: {id:' + menuItem.id + ', label:' + menuItem.label + '}'); // eslint-disable-line no-console
|
||||
contextMenuExtensions[menuItem.id] = menuItem;
|
||||
// TODO: Need to consider how to handle custom enable/disable behavior
|
||||
};
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
/**
|
||||
* @file jQuery Context Menu Plugin
|
||||
* Cory S.N. LaViska
|
||||
* A Beautiful Site ({@link https://abeautifulsite.net/})
|
||||
* Modified by Alexis Deveria
|
||||
*
|
||||
* More info: {@link https://abeautifulsite.net/2008/09/jquery-context-menu-plugin/}
|
||||
*
|
||||
* @module jQueryContextMenu
|
||||
* @todo Update to latest version and adapt (and needs jQuery update as well): {@link https://github.com/swisnl/jQuery-contextMenu}
|
||||
* @version 1.0.1
|
||||
*
|
||||
* @license (MIT OR GPL-2.0-or-later)
|
||||
*
|
||||
* This plugin is dual-licensed under the GNU General Public License
|
||||
* and the MIT License and is copyright A Beautiful Site, LLC.
|
||||
*
|
||||
*/
|
||||
import {isMac} from '../../common/browser.js';
|
||||
|
||||
/**
|
||||
* @callback module:jQueryContextMenu.jQueryContextMenuListener
|
||||
* @param {string} href The `href` value after the first character (for bypassing an initial `#`)
|
||||
* @param {external:jQuery} srcElement The wrapped jQuery srcElement
|
||||
* @param {{x: Float, y: Float, docX: Float, docY: Float}} coords
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {PlainObject} module:jQueryContextMenu.jQueryContextMenuConfig
|
||||
* @property {string} menu
|
||||
* @property {Float} inSpeed
|
||||
* @property {Float} outSpeed
|
||||
* @property {boolean} allowLeft
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds {@link external:jQuery.fn.contextMenu},
|
||||
* {@link external:jQuery.fn.disableContextMenuItems},
|
||||
* {@link external:jQuery.fn.enableContextMenuItems},
|
||||
* {@link external:jQuery.fn.disableContextMenu},
|
||||
* {@link external:jQuery.fn.enableContextMenu},
|
||||
* {@link external:jQuery.fn.destroyContextMenu}.
|
||||
* @function module:jQueryContextMenu.jQueryContextMenu
|
||||
* @param {external:jQuery} $ The jQuery object to wrap (with `contextMenu`, `disableContextMenuItems`, `enableContextMenuItems`, `disableContextMenu`, `enableContextMenu`, `destroyContextMenu`)
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
function jQueryContextMenu ($) {
|
||||
const win = $(window);
|
||||
const doc = $(document);
|
||||
|
||||
$.extend($.fn, {
|
||||
/**
|
||||
* @memberof external:jQuery.fn
|
||||
* @param {module:jQueryContextMenu.jQueryContextMenuConfig} o
|
||||
* @param {module:jQueryContextMenu.jQueryContextMenuListener} listener
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
contextMenu (o, listener) {
|
||||
// Defaults
|
||||
if (o.menu === undefined) return false;
|
||||
if (o.inSpeed === undefined) o.inSpeed = 150;
|
||||
if (o.outSpeed === undefined) o.outSpeed = 75;
|
||||
// 0 needs to be -1 for expected results (no fade)
|
||||
if (o.inSpeed === 0) o.inSpeed = -1;
|
||||
if (o.outSpeed === 0) o.outSpeed = -1;
|
||||
// Loop each context menu
|
||||
$(this).each(function () {
|
||||
const el = $(this);
|
||||
const offset = $(el).offset();
|
||||
|
||||
const menu = $('#' + o.menu);
|
||||
|
||||
// Add contextMenu class
|
||||
menu.addClass('contextMenu');
|
||||
// Simulate a true right click
|
||||
$(this).bind('mousedown', function (evt) {
|
||||
$(this).mouseup(function (e) {
|
||||
const srcElement = $(this);
|
||||
srcElement.unbind('mouseup');
|
||||
|
||||
if (!(evt.button === 2 || o.allowLeft ||
|
||||
(evt.ctrlKey && isMac()))) {
|
||||
return undefined;
|
||||
}
|
||||
e.stopPropagation();
|
||||
// Hide context menus that may be showing
|
||||
$('.contextMenu').hide();
|
||||
// Get this context menu
|
||||
|
||||
if (el.hasClass('disabled')) return false;
|
||||
|
||||
// Detect mouse position
|
||||
let x = e.pageX, y = e.pageY;
|
||||
|
||||
const xOff = win.width() - menu.width(),
|
||||
yOff = win.height() - menu.height();
|
||||
|
||||
if (x > xOff - 15) x = xOff - 15;
|
||||
if (y > yOff - 30) y = yOff - 30; // 30 is needed to prevent scrollbars in FF
|
||||
|
||||
// Show the menu
|
||||
doc.unbind('click');
|
||||
menu.css({top: y, left: x}).fadeIn(o.inSpeed);
|
||||
// Hover events
|
||||
menu.find('A').mouseover(function () {
|
||||
menu.find('LI.hover').removeClass('hover');
|
||||
$(this).parent().addClass('hover');
|
||||
}).mouseout(function () {
|
||||
menu.find('LI.hover').removeClass('hover');
|
||||
});
|
||||
|
||||
// Keyboard
|
||||
doc.keypress(function (ev) {
|
||||
switch (ev.keyCode) {
|
||||
case 38: // up
|
||||
if (!menu.find('LI.hover').length) {
|
||||
menu.find('LI:last').addClass('hover');
|
||||
} else {
|
||||
menu.find('LI.hover').removeClass('hover').prevAll('LI:not(.disabled)').eq(0).addClass('hover');
|
||||
if (!menu.find('LI.hover').length) menu.find('LI:last').addClass('hover');
|
||||
}
|
||||
break;
|
||||
case 40: // down
|
||||
if (!menu.find('LI.hover').length) {
|
||||
menu.find('LI:first').addClass('hover');
|
||||
} else {
|
||||
menu.find('LI.hover').removeClass('hover').nextAll('LI:not(.disabled)').eq(0).addClass('hover');
|
||||
if (!menu.find('LI.hover').length) menu.find('LI:first').addClass('hover');
|
||||
}
|
||||
break;
|
||||
case 13: // enter
|
||||
menu.find('LI.hover A').trigger('click');
|
||||
break;
|
||||
case 27: // esc
|
||||
doc.trigger('click');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// When items are selected
|
||||
menu.find('A').unbind('mouseup');
|
||||
menu.find('LI:not(.disabled) A').mouseup(function () {
|
||||
doc.unbind('click').unbind('keypress');
|
||||
$('.contextMenu').hide();
|
||||
if (listener) {
|
||||
listener($(this).attr('href').substr(1), $(srcElement), {
|
||||
x: x - offset.left, y: y - offset.top, docX: x, docY: y
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Hide bindings
|
||||
setTimeout(function () { // Delay for Mozilla
|
||||
doc.click(function () {
|
||||
doc.unbind('click').unbind('keypress');
|
||||
menu.fadeOut(o.outSpeed);
|
||||
return false;
|
||||
});
|
||||
}, 0);
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
// Disable text selection
|
||||
if ($.browser.mozilla) {
|
||||
$('#' + o.menu).each(function () { $(this).css({MozUserSelect: 'none'}); });
|
||||
} else if ($.browser.msie) {
|
||||
$('#' + o.menu).each(function () { $(this).bind('selectstart.disableTextSelect', function () { return false; }); });
|
||||
} else {
|
||||
$('#' + o.menu).each(function () { $(this).bind('mousedown.disableTextSelect', function () { return false; }); });
|
||||
}
|
||||
// Disable browser context menu (requires both selectors to work in IE/Safari + FF/Chrome)
|
||||
$(el).add($('UL.contextMenu')).bind('contextmenu', function () { return false; });
|
||||
});
|
||||
return $(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable context menu items on the fly.
|
||||
* @memberof external:jQuery.fn
|
||||
* @param {void|string} o Comma-separated
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
disableContextMenuItems (o) {
|
||||
if (o === undefined) {
|
||||
// Disable all
|
||||
$(this).find('LI').addClass('disabled');
|
||||
return $(this);
|
||||
}
|
||||
$(this).each(function () {
|
||||
if (o !== undefined) {
|
||||
const d = o.split(',');
|
||||
for (const href of d) {
|
||||
$(this).find('A[href="' + href + '"]').parent().addClass('disabled');
|
||||
}
|
||||
}
|
||||
});
|
||||
return $(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable context menu items on the fly.
|
||||
* @memberof external:jQuery.fn
|
||||
* @param {void|string} o Comma-separated
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
enableContextMenuItems (o) {
|
||||
if (o === undefined) {
|
||||
// Enable all
|
||||
$(this).find('LI.disabled').removeClass('disabled');
|
||||
return $(this);
|
||||
}
|
||||
$(this).each(function () {
|
||||
if (o !== undefined) {
|
||||
const d = o.split(',');
|
||||
for (const href of d) {
|
||||
$(this).find('A[href="' + href + '"]').parent().removeClass('disabled');
|
||||
}
|
||||
}
|
||||
});
|
||||
return $(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable context menu(s).
|
||||
* @memberof external:jQuery.fn
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
disableContextMenu () {
|
||||
$(this).each(function () {
|
||||
$(this).addClass('disabled');
|
||||
});
|
||||
return $(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable context menu(s).
|
||||
* @memberof external:jQuery.fn
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
enableContextMenu () {
|
||||
$(this).each(function () {
|
||||
$(this).removeClass('disabled');
|
||||
});
|
||||
return $(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy context menu(s).
|
||||
* @memberof external:jQuery.fn
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
destroyContextMenu () {
|
||||
// Destroy specified context menus
|
||||
$(this).each(function () {
|
||||
// Disable action
|
||||
$(this).unbind('mousedown').unbind('mouseup');
|
||||
});
|
||||
return $(this);
|
||||
}
|
||||
});
|
||||
return $;
|
||||
}
|
||||
|
||||
export default jQueryContextMenu;
|
||||
68
src/editor/dialogs/SePlainAlertDialog.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import PlainAlertDialog from 'elix/src/plain/PlainAlertDialog.js';
|
||||
import { template } from 'elix/src/base/internal.js';
|
||||
import { fragmentFrom } from 'elix/src/core/htmlLiterals.js';
|
||||
|
||||
/**
|
||||
* @class SePlainAlertDialog
|
||||
*/
|
||||
export default class SePlainAlertDialog extends PlainAlertDialog {
|
||||
/**
|
||||
* @function get
|
||||
* @returns {PlainObject}
|
||||
*/
|
||||
get [template] () {
|
||||
const result = super[template];
|
||||
// Replace the default slot with a new default slot and a button container.
|
||||
const defaultSlot = result.content.querySelector('#frameContent');
|
||||
if (defaultSlot) {
|
||||
defaultSlot.replaceWith(fragmentFrom.html`
|
||||
<div id="alertDialogContent">
|
||||
<div id="se-content-alert">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="choiceButtonContainer" part="choice-button-container"></div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
result.content.append(
|
||||
fragmentFrom.html`
|
||||
<style>
|
||||
[part~="frame"] {
|
||||
padding: 1em;
|
||||
background: #CCC;
|
||||
width: 300px;
|
||||
border: 1px outset #777;
|
||||
font-size: 0.8em;
|
||||
font-family: Verdana,Helvetica,sans-serif;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
|
||||
[part~="choice-button-container"] {
|
||||
margin-top: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[part~="choice-button"]:not(:first-child) {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
#se-content-alert{
|
||||
height: 95px;
|
||||
background: #DDD;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
border: 1px solid #5a6162;
|
||||
padding: 1em;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('se-elix-alert-dialog', SePlainAlertDialog);
|
||||
341
src/editor/dialogs/cmenuDialog.js
Normal file
@@ -0,0 +1,341 @@
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
.contextMenu {
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
border: solid 1px rgba(0,0,0,.33);
|
||||
background: rgba(255,255,255,.95);
|
||||
padding: 5px 0;
|
||||
margin: 0px;
|
||||
display: none;
|
||||
font: 12px/15px Lucida Sans, Helvetica, Verdana, sans-serif;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-moz-box-shadow: 2px 5px 10px rgba(0,0,0,.3);
|
||||
-webkit-box-shadow: 2px 5px 10px rgba(0,0,0,.3);
|
||||
box-shadow: 2px 5px 10px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.contextMenu li {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.contextMenu .shortcut {
|
||||
width: 115px;
|
||||
text-align:right;
|
||||
float:right;
|
||||
}
|
||||
|
||||
.contextMenu a {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
background-position: 6px center;
|
||||
background-repeat: no-repeat;
|
||||
outline: none;
|
||||
padding: 0px 15px 1px 20px;
|
||||
}
|
||||
|
||||
.contextMenu li.hover a {
|
||||
background-color: #2e5dea;
|
||||
color: white;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contextMenu li.disabled a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.contextMenu li.hover.disabled a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.contextMenu li.separator {
|
||||
border-top: solid 1px #E3E3E3;
|
||||
padding-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
<ul id="cmenu_canvas" class="contextMenu">
|
||||
<li>
|
||||
<a href="#cut" id="se-cut">
|
||||
<span class="shortcut">META+X</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#copy" id="se-copy">
|
||||
<span class="shortcut">META+C</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#paste" id="se-paste"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#paste_in_place" id="se-paste-in-place"></a>
|
||||
</li>
|
||||
<li class="separator">
|
||||
<a href="#delete" id="se-delete">
|
||||
<span class="shortcut">BACKSPACE</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator">
|
||||
<a href="#group" id="se-group">
|
||||
<span class="shortcut">G</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#ungroup" id="se-ungroup">
|
||||
<span class="shortcut">G</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator">
|
||||
<a href="#move_front" id="se-move-front">
|
||||
<span class="shortcut">CTRL+SHFT+]</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#move_up" id="se-move-up">
|
||||
<span class="shortcut">CTRL+]</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#move_down" id="se-move-down">
|
||||
<span class="shortcut">CTRL+[</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#move_back" id="se-move-back">
|
||||
<span class="shortcut">CTRL+SHFT+[</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
/**
|
||||
* @class SeCMenuDialog
|
||||
*/
|
||||
export class SeCMenuDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this._workarea = document.getElementById('workarea');
|
||||
this.$dialog = this._shadowRoot.querySelector('#cmenu_canvas');
|
||||
this.$copyLink = this._shadowRoot.querySelector('#se-copy');
|
||||
this.$cutLink = this._shadowRoot.querySelector('#se-cut');
|
||||
this.$pasteLink = this._shadowRoot.querySelector('#se-paste');
|
||||
this.$pasteInPlaceLink = this._shadowRoot.querySelector('#se-paste-in-place');
|
||||
this.$deleteLink = this._shadowRoot.querySelector('#se-delete');
|
||||
this.$groupLink = this._shadowRoot.querySelector('#se-group');
|
||||
this.$ungroupLink = this._shadowRoot.querySelector('#se-ungroup');
|
||||
this.$moveFrontLink = this._shadowRoot.querySelector('#se-move-front');
|
||||
this.$moveUpLink = this._shadowRoot.querySelector('#se-move-up');
|
||||
this.$moveDownLink = this._shadowRoot.querySelector('#se-move-down');
|
||||
this.$moveBackLink = this._shadowRoot.querySelector('#se-move-back');
|
||||
}
|
||||
/**
|
||||
* @function init
|
||||
* @param {any} name
|
||||
* @returns {void}
|
||||
*/
|
||||
init (i18next) {
|
||||
this.setAttribute('tools-cut', i18next.t('tools.cut'));
|
||||
this.setAttribute('tools-copy', i18next.t('tools.copy'));
|
||||
this.setAttribute('tools-paste', i18next.t('tools.paste'));
|
||||
this.setAttribute('tools-paste_in_place', i18next.t('tools.paste_in_place'));
|
||||
this.setAttribute('tools-delete', i18next.t('tools.delete'));
|
||||
this.setAttribute('tools-group', i18next.t('tools.group'));
|
||||
this.setAttribute('tools-ungroup', i18next.t('tools.ungroup'));
|
||||
this.setAttribute('tools-move_front', i18next.t('tools.move_front'));
|
||||
this.setAttribute('tools-move_up', i18next.t('tools.move_up'));
|
||||
this.setAttribute('tools-move_down', i18next.t('tools.move_down'));
|
||||
this.setAttribute('tools-move_back', i18next.t('tools.move_back'));
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'disableallmenu', 'enablemenuitems', 'disablemenuitems', 'tools-cut',
|
||||
'tools-copy', 'tools-paste', 'tools-paste_in_place', 'tools-delete', 'tools-group',
|
||||
'tools-ungroup', 'tools-move_front', 'tools-move_up', 'tools-move_down',
|
||||
'tools-move_back' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
let eles = [];
|
||||
let textnode;
|
||||
const sdowRoot = this._shadowRoot;
|
||||
switch (name) {
|
||||
case 'disableallmenu':
|
||||
if (newValue === 'true') {
|
||||
const elesli = sdowRoot.querySelectorAll('li');
|
||||
elesli.forEach(function (eleli) {
|
||||
eleli.classList.add('disabled');
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'enablemenuitems':
|
||||
eles = newValue.split(',');
|
||||
eles.forEach(function (ele) {
|
||||
const selEle = sdowRoot.querySelector('a[href*="' + ele + '"]');
|
||||
selEle.parentElement.classList.remove('disabled');
|
||||
});
|
||||
break;
|
||||
case 'disablemenuitems':
|
||||
eles = newValue.split(',');
|
||||
eles.forEach(function (ele) {
|
||||
const selEle = sdowRoot.querySelector('a[href*="' + ele + '"]');
|
||||
selEle.parentElement.classList.add('disabled');
|
||||
});
|
||||
break;
|
||||
case 'tools-cut':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$cutLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-copy':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$copyLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-paste':
|
||||
this.$pasteLink.textContent = newValue;
|
||||
break;
|
||||
case 'tools-paste_in_place':
|
||||
this.$pasteInPlaceLink.textContent = newValue;
|
||||
break;
|
||||
case 'tools-delete':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$deleteLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-group':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$groupLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-ungroup':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$ungroupLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-move_front':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$moveFrontLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-move_up':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$moveUpLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-move_down':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$moveDownLink.prepend(textnode);
|
||||
break;
|
||||
case 'tools-move_back':
|
||||
textnode = document.createTextNode(newValue);
|
||||
this.$moveBackLink.prepend(textnode);
|
||||
break;
|
||||
default:
|
||||
// super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get disableallmenu () {
|
||||
return this.getAttribute('disableallmenu');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set disableallmenu (value) {
|
||||
this.setAttribute('disableallmenu', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get enablemenuitems () {
|
||||
return this.getAttribute('enablemenuitems');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set enablemenuitems (value) {
|
||||
this.setAttribute('enablemenuitems', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get disablemenuitems () {
|
||||
return this.getAttribute('disablemenuitems');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set disablemenuitems (value) {
|
||||
this.setAttribute('disablemenuitems', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
const current = this;
|
||||
const onMenuOpenHandler = (e) => {
|
||||
e.preventDefault();
|
||||
current.$dialog.style.top = e.pageY + 'px';
|
||||
current.$dialog.style.left = e.pageX + 'px';
|
||||
current.$dialog.style.display = 'block';
|
||||
};
|
||||
const onMenuCloseHandler = (e) => {
|
||||
if (e.button !== 2) {
|
||||
current.$dialog.style.display = 'none';
|
||||
}
|
||||
};
|
||||
const onMenuClickHandler = (e, action) => {
|
||||
const triggerEvent = new CustomEvent('change', { detail: {
|
||||
trigger: action
|
||||
} });
|
||||
this.dispatchEvent(triggerEvent);
|
||||
};
|
||||
this._workarea.addEventListener('contextmenu', onMenuOpenHandler);
|
||||
this._workarea.addEventListener('mousedown', onMenuCloseHandler);
|
||||
this.$cutLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'cut'));
|
||||
this.$copyLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'copy'));
|
||||
this.$pasteLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'paste'));
|
||||
this.$pasteInPlaceLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'paste_in_place'));
|
||||
this.$deleteLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'delete'));
|
||||
this.$groupLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'group'));
|
||||
this.$ungroupLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'ungroup'));
|
||||
this.$moveFrontLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'move_front'));
|
||||
this.$moveUpLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'move_up'));
|
||||
this.$moveDownLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'move_down'));
|
||||
this.$moveBackLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'move_back'));
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-cmenu_canvas-dialog', SeCMenuDialog);
|
||||
218
src/editor/dialogs/cmenuLayersDialog.js
Normal file
@@ -0,0 +1,218 @@
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
.contextMenu {
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
border: solid 1px rgba(0,0,0,.33);
|
||||
background: rgba(255,255,255,.95);
|
||||
padding: 5px 0;
|
||||
margin: 0px;
|
||||
display: none;
|
||||
font: 12px/15px Lucida Sans, Helvetica, Verdana, sans-serif;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-moz-box-shadow: 2px 5px 10px rgba(0,0,0,.3);
|
||||
-webkit-box-shadow: 2px 5px 10px rgba(0,0,0,.3);
|
||||
box-shadow: 2px 5px 10px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.contextMenu li {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.contextMenu .shortcut {
|
||||
width: 115px;
|
||||
text-align:right;
|
||||
float:right;
|
||||
}
|
||||
|
||||
.contextMenu a {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
color: #222;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
background-position: 6px center;
|
||||
background-repeat: no-repeat;
|
||||
outline: none;
|
||||
padding: 0px 15px 1px 20px;
|
||||
}
|
||||
|
||||
.contextMenu li.hover a {
|
||||
background-color: #2e5dea;
|
||||
color: white;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.contextMenu li.disabled a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.contextMenu li.hover.disabled a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.contextMenu li.separator {
|
||||
border-top: solid 1px #E3E3E3;
|
||||
padding-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
<ul id="cmenu_layers" class="contextMenu">
|
||||
<li><a href="#dupe" id="se-dupe">#{svgEditor.i18next.t('layers.dupe')}</a></li>
|
||||
<li><a href="#delete" id="se-layer-delete">#{svgEditor.i18next.t('layers.del')}</a></li>
|
||||
<li><a href="#merge_down" id="se-merge-down">#{svgEditor.i18next.t('layers.merge_down')}</a></li>
|
||||
<li><a href="#merge_all" id="se-merge-all">#{svgEditor.i18next.t('layers.merge_all')}</a></li>
|
||||
</ul>
|
||||
`;
|
||||
/**
|
||||
* @class SeCMenuLayerDialog
|
||||
*/
|
||||
export class SeCMenuLayerDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.source = '';
|
||||
this._workarea = undefined;
|
||||
this.$sidePanels = document.getElementById('sidepanels');
|
||||
this.$dialog = this._shadowRoot.querySelector('#cmenu_layers');
|
||||
this.$duplicateLink = this._shadowRoot.querySelector('#se-dupe');
|
||||
this.$deleteLink = this._shadowRoot.querySelector('#se-layer-delete');
|
||||
this.$mergeDownLink = this._shadowRoot.querySelector('#se-merge-down');
|
||||
this.$mergeAllLink = this._shadowRoot.querySelector('#se-merge-all');
|
||||
}
|
||||
/**
|
||||
* @function init
|
||||
* @param {any} name
|
||||
* @returns {void}
|
||||
*/
|
||||
init (i18next) {
|
||||
this.setAttribute('layers-dupe', i18next.t('layers.dupe'));
|
||||
this.setAttribute('layers-del', i18next.t('layers.del'));
|
||||
this.setAttribute('layers-merge_down', i18next.t('layers.merge_down'));
|
||||
this.setAttribute('layers-merge_all', i18next.t('layers.merge_all'));
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'value', 'leftclick', 'layers-dupe', 'layers-del', 'layers-merge_down', 'layers-merge_all' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
// eslint-disable-next-line sonarjs/no-small-switch
|
||||
switch (name) {
|
||||
case 'value':
|
||||
this.source = newValue;
|
||||
if (newValue !== '' && newValue !== undefined) {
|
||||
this._workarea = document.getElementById(this.source);
|
||||
}
|
||||
break;
|
||||
case 'layers-dupe':
|
||||
this.$duplicateLink.textContent = newValue;
|
||||
break;
|
||||
case 'layers-del':
|
||||
this.$deleteLink.textContent = newValue;
|
||||
break;
|
||||
case 'layers-merge_down':
|
||||
this.$mergeDownLink.textContent = newValue;
|
||||
break;
|
||||
case 'layers-merge_all':
|
||||
this.$mergeAllLink.textContent = newValue;
|
||||
break;
|
||||
default:
|
||||
// super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get value () {
|
||||
return this.getAttribute('value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set value (value) {
|
||||
this.setAttribute('value', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get leftclick () {
|
||||
return this.getAttribute('leftclick');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set leftclick (value) {
|
||||
this.setAttribute('leftclick', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
const current = this;
|
||||
const onMenuOpenHandler = (e) => {
|
||||
e.preventDefault();
|
||||
current.$dialog.style.top = e.pageY + 'px';
|
||||
current.$dialog.style.left = e.pageX + 'px';
|
||||
current.$dialog.style.display = 'block';
|
||||
};
|
||||
const onMenuCloseHandler = (e) => {
|
||||
if (e.button !== 2) {
|
||||
current.$dialog.style.display = 'none';
|
||||
}
|
||||
};
|
||||
const onMenuClickHandler = (e, action, id) => {
|
||||
const triggerEvent = new CustomEvent('change', { detail: {
|
||||
trigger: action,
|
||||
source: id
|
||||
} });
|
||||
this.dispatchEvent(triggerEvent);
|
||||
current.$dialog.style.display = 'none';
|
||||
};
|
||||
if (this._workarea !== undefined) {
|
||||
this._workarea.addEventListener('contextmenu', onMenuOpenHandler);
|
||||
if (this.getAttribute('leftclick') === 'true') {
|
||||
this._workarea.addEventListener('click', onMenuOpenHandler);
|
||||
}
|
||||
this._workarea.addEventListener('mousedown', onMenuCloseHandler);
|
||||
this.$sidePanels.addEventListener('mousedown', onMenuCloseHandler);
|
||||
}
|
||||
this.$duplicateLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'dupe', this.source));
|
||||
this.$deleteLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'delete', this.source));
|
||||
this.$mergeDownLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'merge_down', this.source));
|
||||
this.$mergeAllLink.addEventListener('click', (evt) => onMenuClickHandler(evt, 'merge_all', this.source));
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-cmenu-layers', SeCMenuLayerDialog);
|
||||
644
src/editor/dialogs/editorPreferencesDialog.js
Normal file
@@ -0,0 +1,644 @@
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Force the scroll bar to appear so we see it hide when overlay opens. */
|
||||
body::-webkit-scrollbar {
|
||||
background: lightgray;
|
||||
}
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background: darkgray;
|
||||
}
|
||||
.toolbar_button button {
|
||||
border:1px solid #dedede;
|
||||
line-height:130%;
|
||||
float: left;
|
||||
background: #E8E8E8 none;
|
||||
padding:5px 10px 5px 7px; /* Firefox */
|
||||
line-height:17px; /* Safari */
|
||||
margin: 5px 20px 0 0;
|
||||
border: 1px #808080 solid;
|
||||
border-top-color: #FFF;
|
||||
border-left-color: #FFF;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toolbar_button button:hover {
|
||||
border: 1px #e0a874 solid;
|
||||
border-top-color: #fcd9ba;
|
||||
border-left-color: #fcd9ba;
|
||||
background-color: #FFC;
|
||||
}
|
||||
.toolbar_button button:active {
|
||||
background-color: #F4E284;
|
||||
border-left: 1px solid #663300;
|
||||
border-top: 1px solid #663300;
|
||||
}
|
||||
|
||||
.toolbar_button button .svg_icon {
|
||||
margin: 0 3px -3px 0 !important;
|
||||
padding: 0;
|
||||
border: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.color_block {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.color_block svg {
|
||||
display: block;
|
||||
}
|
||||
#bg_blocks {
|
||||
overflow: auto;
|
||||
margin-left: 30px;
|
||||
}
|
||||
#bg_blocks .color_block {
|
||||
position: static;
|
||||
}
|
||||
#svginfo_bg_note {
|
||||
font-size: .9em;
|
||||
font-style: italic;
|
||||
color: #444;
|
||||
}
|
||||
#svg_prefs #svg_prefs_container {
|
||||
padding: 10px;
|
||||
background-color: #5a6162;
|
||||
color: #c5c5c5;
|
||||
border: 1px outset #777;
|
||||
opacity: 1.0;
|
||||
font-family: Verdana, Helvetica, sans-serif;
|
||||
font-size: .8em;
|
||||
z-index: 20001;
|
||||
}
|
||||
|
||||
#tool_prefs_back {
|
||||
margin-left: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#tool_prefs_save {
|
||||
width: 30%;
|
||||
background-color: #c79605;
|
||||
margin-left: 20%;
|
||||
}
|
||||
|
||||
#tool_prefs_cancel {
|
||||
width: 30%;
|
||||
background-color: #c8c8c8;
|
||||
}
|
||||
|
||||
#svg_prefs #svg_docprops_prefs {
|
||||
float: left;
|
||||
width: 221px;
|
||||
margin: 5px .7em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#svg_prefs_container fieldset + fieldset {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#svg_prefs legend {
|
||||
max-width: 195px;
|
||||
}
|
||||
|
||||
#svg_prefs_container > fieldset > legend {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
#svg_prefs fieldset {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
|
||||
#svg_prefs_container label {
|
||||
display: block;
|
||||
margin: .5em;
|
||||
}
|
||||
#svg_prefs_container div.color_block {
|
||||
float: left;
|
||||
margin: 2px;
|
||||
padding: 20px;
|
||||
border: 1px solid #6f6f6f;
|
||||
}
|
||||
|
||||
#change_background div.cur_background {
|
||||
border: 2px solid blue;
|
||||
padding: 19px;
|
||||
}
|
||||
#canvas_bg_url {
|
||||
display: block;
|
||||
width: 96%;
|
||||
}
|
||||
#svg_prefs button {
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
<elix-dialog id="svg_prefs" aria-label="Editor Preferences" closed>
|
||||
<div id="svg_prefs_container">
|
||||
<div id="tool_prefs_back" class="toolbar_button">
|
||||
<button id="tool_prefs_save"></button>
|
||||
<button id="tool_prefs_cancel"></button>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend id="svginfo_editor_prefs"></legend>
|
||||
<label>
|
||||
<span id="svginfo_lang"></span>
|
||||
<!-- Source: https://en.wikipedia.org/wiki/Language_names -->
|
||||
<select id="lang_select">
|
||||
<option id="lang_ar" value="ar">العربية</option>
|
||||
<option id="lang_cs" value="cs">Čeština</option>
|
||||
<option id="lang_de" value="de">Deutsch</option>
|
||||
<option id="lang_en" value="en" selected="selected">English</option>
|
||||
<option id="lang_es" value="es">Español</option>
|
||||
<option id="lang_fa" value="fa">فارسی</option>
|
||||
<option id="lang_fr" value="fr">Français</option>
|
||||
<option id="lang_fy" value="fy">Frysk</option>
|
||||
<option id="lang_hi" value="hi">हिन्दी, हिंदी</option>
|
||||
<option id="lang_it" value="it">Italiano</option>
|
||||
<option id="lang_ja" value="ja">日本語</option>
|
||||
<option id="lang_nl" value="nl">Nederlands</option>
|
||||
<option id="lang_pl" value="pl">Polski</option>
|
||||
<option id="lang_pt-BR" value="pt-BR">Português (BR)</option>
|
||||
<option id="lang_ro" value="ro">Română</option>
|
||||
<option id="lang_ru" value="ru">Русский</option>
|
||||
<option id="lang_sk" value="sk">Slovenčina</option>
|
||||
<option id="lang_sl" value="sl">Slovenščina</option>
|
||||
<option id="lang_zh-CN" value="zh-CN">简体中文</option>
|
||||
<option id="lang_zh-TW" value="zh-TW">繁體中文</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span id="svginfo_icons"></span>
|
||||
<select id="iconsize">
|
||||
<option id="icon_small" value="s"></option>
|
||||
<option id="icon_medium" value="m" selected="selected"></option>
|
||||
<option id="icon_large" value="l"></option>
|
||||
<option id="icon_xlarge" value="xl"></option>
|
||||
</select>
|
||||
</label>
|
||||
<fieldset id="change_background">
|
||||
<legend id="svginfo_change_background"></legend>
|
||||
<div id="bg_blocks"></div>
|
||||
<label>
|
||||
<span id="svginfo_bg_url"></span>
|
||||
<input type="text" id="canvas_bg_url" />
|
||||
</label>
|
||||
<p id="svginfo_bg_note"></p>
|
||||
</fieldset>
|
||||
<fieldset id="change_grid">
|
||||
<legend id="svginfo_grid_settings"></legend>
|
||||
<label for="svginfo_snap_onoff">
|
||||
<span id="svginfo_snap_onoff"></span>
|
||||
<input type="checkbox" value="snapping_on" id="grid_snapping_on" />
|
||||
</label>
|
||||
<label for="grid_snapping_step">
|
||||
<span id="svginfo_snap_step"></span>
|
||||
<input type="text" id="grid_snapping_step" size="3" value="10" />
|
||||
</label>
|
||||
<label>
|
||||
<span id="svginfo_grid_color"></span>
|
||||
<input type="text" id="grid_color" size="3" value="#000" />
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset id="units_rulers">
|
||||
<legend id="svginfo_units_rulers"></legend>
|
||||
<label>
|
||||
<span id="svginfo_rulers_onoff"></span>
|
||||
<input id="show_rulers" type="checkbox" value="show_rulers" checked="checked" />
|
||||
</label>
|
||||
<label>
|
||||
<span id="svginfo_unit"></span>
|
||||
<select id="base_unit">
|
||||
<option value="px">Pixels</option>
|
||||
<option value="cm">Centimeters</option>
|
||||
<option value="mm">Millimeters</option>
|
||||
<option value="in">Inches</option>
|
||||
<option value="pt">Points</option>
|
||||
<option value="pc">Picas</option>
|
||||
<option value="em">Ems</option>
|
||||
<option value="ex">Exs</option>
|
||||
</select>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</div>
|
||||
</elix-dialog>
|
||||
`;
|
||||
/**
|
||||
* @class SeEditPrefsDialog
|
||||
*/
|
||||
export class SeEditPrefsDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this.colorBlocks = [ '#FFF', '#888', '#000', 'chessboard' ];
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$dialog = this._shadowRoot.querySelector('#svg_prefs');
|
||||
this.$saveBtn = this._shadowRoot.querySelector('#tool_prefs_save');
|
||||
this.$cancelBtn = this._shadowRoot.querySelector('#tool_prefs_cancel');
|
||||
this.$langSelect = this._shadowRoot.querySelector('#lang_select');
|
||||
this.$iconSize = this._shadowRoot.querySelector('#iconsize');
|
||||
this.$bgBlocks = this._shadowRoot.querySelector('#bg_blocks');
|
||||
this.$bgURL = this._shadowRoot.querySelector('#canvas_bg_url');
|
||||
this.$gridSnappingOn = this._shadowRoot.querySelector('#grid_snapping_on');
|
||||
this.$gridSnappingStep = this._shadowRoot.querySelector('#grid_snapping_step');
|
||||
this.$gridColor = this._shadowRoot.querySelector('#grid_color');
|
||||
this.$showRulers = this._shadowRoot.querySelector('#show_rulers');
|
||||
this.$baseUnit = this._shadowRoot.querySelector('#base_unit');
|
||||
}
|
||||
/**
|
||||
* @function init
|
||||
* @param {any} name
|
||||
* @returns {void}
|
||||
*/
|
||||
init (i18next) {
|
||||
this.setAttribute('common-ok', i18next.t('common.ok'));
|
||||
this.setAttribute('common-cancel', i18next.t('common.cancel'));
|
||||
this.setAttribute('config-editor_prefs', i18next.t('config.editor_prefs'));
|
||||
this.setAttribute('config-language', i18next.t('config.language'));
|
||||
this.setAttribute('config-icon_size', i18next.t('config.icon_size'));
|
||||
this.setAttribute('config-icon_small', i18next.t('config.icon_small'));
|
||||
this.setAttribute('config-icon_medium', i18next.t('config.icon_medium'));
|
||||
this.setAttribute('config-icon_large', i18next.t('config.icon_large'));
|
||||
this.setAttribute('config-icon_xlarge', i18next.t('config.icon_xlarge'));
|
||||
this.setAttribute('config-background', i18next.t('config.background'));
|
||||
this.setAttribute('common-url', i18next.t('common.url'));
|
||||
this.setAttribute('config-editor_bg_note', i18next.t('config.editor_bg_note'));
|
||||
this.setAttribute('config-grid', i18next.t('config.grid'));
|
||||
this.setAttribute('config-snapping_onoff', i18next.t('config.snapping_onoff'));
|
||||
this.setAttribute('config-snapping_stepsize', i18next.t('config.snapping_stepsize'));
|
||||
this.setAttribute('config-grid_color', i18next.t('config.grid_color'));
|
||||
this.setAttribute('config-units_and_rulers', i18next.t('config.units_and_rulers'));
|
||||
this.setAttribute('config-show_rulers', i18next.t('config.show_rulers'));
|
||||
this.setAttribute('config-base_unit', i18next.t('config.base_unit'));
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
// eslint-disable-next-line max-len
|
||||
return [ 'dialog', 'lang', 'iconsize', 'canvasbg', 'bgurl', 'gridsnappingon', 'gridsnappingstep', 'gridcolor', 'showrulers', 'baseunit', 'common-ok', 'common-cancel', 'config-editor_prefs', 'config-language', 'config-icon_size', 'config-icon_small', 'config-icon_medium', 'config-icon_large', 'config-icon_xlarge', 'config-background', 'common-url', 'config-editor_bg_note', 'config-grid', 'config-snapping_onoff', 'config-snapping_stepsize', 'config-grid_color', 'config-units_and_rulers', 'config-show_rulers', 'config-base_unit' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
const blocks = this.$bgBlocks.querySelectorAll('div');
|
||||
const curBg = 'cur_background';
|
||||
let node;
|
||||
switch (name) {
|
||||
case 'dialog':
|
||||
if (newValue === 'open') {
|
||||
this.$dialog.open();
|
||||
} else {
|
||||
this.$dialog.close();
|
||||
}
|
||||
break;
|
||||
case 'lang':
|
||||
this.$langSelect.value = newValue;
|
||||
break;
|
||||
case 'iconsize':
|
||||
this.$iconSize.value = newValue;
|
||||
break;
|
||||
case 'canvasbg':
|
||||
if (!newValue) {
|
||||
if (blocks.length > 0) {
|
||||
blocks[0].classList.add(curBg);
|
||||
}
|
||||
} else {
|
||||
blocks.forEach(function (blk) {
|
||||
const isBg = blk.dataset.bgColor === newValue;
|
||||
if (isBg) {
|
||||
blk.classList.add(curBg);
|
||||
} else {
|
||||
blk.classList.remove(curBg);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'bgurl':
|
||||
this.$bgURL.value = newValue;
|
||||
break;
|
||||
case 'gridsnappingon':
|
||||
if (newValue === 'true') {
|
||||
this.$gridSnappingOn.checked = true;
|
||||
} else if (newValue === 'false') {
|
||||
this.$gridSnappingOn.checked = false;
|
||||
}
|
||||
break;
|
||||
case 'gridsnappingstep':
|
||||
this.$gridSnappingStep.value = newValue;
|
||||
break;
|
||||
case 'gridcolor':
|
||||
this.$gridColor.value = newValue;
|
||||
break;
|
||||
case 'showrulers':
|
||||
if (newValue === 'true') {
|
||||
this.$showRulers.checked = true;
|
||||
} else if (newValue === 'false') {
|
||||
this.$showRulers.checked = false;
|
||||
}
|
||||
break;
|
||||
case 'baseunit':
|
||||
this.$baseUnit.value = newValue;
|
||||
break;
|
||||
case 'common-ok':
|
||||
this.$saveBtn.textContent = newValue;
|
||||
break;
|
||||
case 'common-cancel':
|
||||
this.$cancelBtn.textContent = newValue;
|
||||
break;
|
||||
case 'config-editor_prefs':
|
||||
node = this._shadowRoot.querySelector('#svginfo_editor_prefs');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-language':
|
||||
node = this._shadowRoot.querySelector('#svginfo_lang');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-icon_size':
|
||||
node = this._shadowRoot.querySelector('#svginfo_icons');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-icon_small':
|
||||
node = this._shadowRoot.querySelector('#icon_small');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-icon_medium':
|
||||
node = this._shadowRoot.querySelector('#icon_medium');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-icon_large':
|
||||
node = this._shadowRoot.querySelector('#icon_large');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-icon_xlarge':
|
||||
node = this._shadowRoot.querySelector('#icon_xlarge');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-background':
|
||||
node = this._shadowRoot.querySelector('#svginfo_change_background');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'common-url':
|
||||
node = this._shadowRoot.querySelector('#svginfo_bg_url');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-editor_bg_note':
|
||||
node = this._shadowRoot.querySelector('#svginfo_bg_note');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-grid':
|
||||
node = this._shadowRoot.querySelector('#svginfo_grid_settings');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-snapping_onoff':
|
||||
node = this._shadowRoot.querySelector('#svginfo_snap_onoff');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-snapping_stepsize':
|
||||
node = this._shadowRoot.querySelector('#svginfo_snap_step');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-grid_color':
|
||||
node = this._shadowRoot.querySelector('#svginfo_grid_color');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-units_and_rulers':
|
||||
node = this._shadowRoot.querySelector('#svginfo_units_rulers');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-show_rulers':
|
||||
node = this._shadowRoot.querySelector('#svginfo_rulers_onoff');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-base_unit':
|
||||
node = this._shadowRoot.querySelector('#svginfo_unit');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
default:
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get lang () {
|
||||
return this.getAttribute('lang');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set lang (value) {
|
||||
this.setAttribute('lang', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get iconsize () {
|
||||
return this.getAttribute('iconsize');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set iconsize (value) {
|
||||
this.setAttribute('iconsize', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get canvasbg () {
|
||||
return this.getAttribute('canvasbg');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set canvasbg (value) {
|
||||
this.setAttribute('canvasbg', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get bgurl () {
|
||||
return this.getAttribute('bgurl');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set bgurl (value) {
|
||||
this.setAttribute('bgurl', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get dialog () {
|
||||
return this.getAttribute('dialog');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set dialog (value) {
|
||||
this.setAttribute('dialog', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get gridsnappingon () {
|
||||
return this.getAttribute('gridsnappingon');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set gridsnappingon (value) {
|
||||
this.setAttribute('gridsnappingon', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get gridsnappingstep () {
|
||||
return this.getAttribute('gridsnappingstep');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set gridsnappingstep (value) {
|
||||
this.setAttribute('gridsnappingstep', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get gridcolor () {
|
||||
return this.getAttribute('gridcolor');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set gridcolor (value) {
|
||||
this.setAttribute('gridcolor', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get showrulers () {
|
||||
return this.getAttribute('showrulers');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set showrulers (value) {
|
||||
this.setAttribute('showrulers', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get baseunit () {
|
||||
return this.getAttribute('baseunit');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set baseunit (value) {
|
||||
this.setAttribute('baseunit', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
const onCancelHandler = () => {
|
||||
const closeEvent = new CustomEvent('change', { detail: {
|
||||
dialog: 'closed'
|
||||
} });
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
const onSaveHandler = () => {
|
||||
const color = this.$bgBlocks.querySelector('.cur_background').dataset.bgColor || '#FFF';
|
||||
const closeEvent = new CustomEvent('change', { detail: {
|
||||
lang: this.$langSelect.value,
|
||||
dialog: 'close',
|
||||
iconsize: this.$iconSize.value,
|
||||
bgcolor: color,
|
||||
bgurl: this.$bgURL.value,
|
||||
gridsnappingon: this.$gridSnappingOn.checked,
|
||||
gridsnappingstep: this.$gridSnappingStep.value,
|
||||
showrulers: this.$showRulers.checked,
|
||||
baseunit: this.$baseUnit.value
|
||||
} });
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
// Set up editor background functionality
|
||||
const currentObj = this;
|
||||
this.colorBlocks.forEach(function (e) {
|
||||
const newdiv = document.createElement('div');
|
||||
if (e === 'chessboard') {
|
||||
newdiv.dataset.bgColor = e;
|
||||
// eslint-disable-next-line max-len
|
||||
newdiv.style.backgroundImage = 'url(data:image/gif;base64,R0lGODlhEAAQAIAAAP///9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG+gq4jM3IFLJgpswNly/XkcBpIiVaInlLJr9FZWAQA7)';
|
||||
newdiv.classList.add('color_block');
|
||||
} else {
|
||||
newdiv.dataset.bgColor = e; // setAttribute('data-bgcolor', e);
|
||||
newdiv.style.backgroundColor = e;
|
||||
newdiv.classList.add('color_block');
|
||||
}
|
||||
currentObj.$bgBlocks.append(newdiv);
|
||||
});
|
||||
const blocks = this.$bgBlocks.querySelectorAll('div');
|
||||
const curBg = 'cur_background';
|
||||
blocks.forEach(function (blk) {
|
||||
blk.addEventListener('click', function () {
|
||||
blocks.forEach((el) => el.classList.remove(curBg));
|
||||
blk.classList.add(curBg);
|
||||
});
|
||||
});
|
||||
this.$saveBtn.addEventListener('click', onSaveHandler);
|
||||
this.$cancelBtn.addEventListener('click', onCancelHandler);
|
||||
this.$dialog.addEventListener('close', onCancelHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-edit-prefs-dialog', SeEditPrefsDialog);
|
||||
207
src/editor/dialogs/exportDialog.js
Normal file
@@ -0,0 +1,207 @@
|
||||
import './se-elix/define/NumberSpinBox.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
|
||||
#dialog_content {
|
||||
margin: 10px 10px 5px 10px;
|
||||
background: #5a6162;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
border: 1px solid #c8c8c8;
|
||||
}
|
||||
|
||||
#dialog_content p, #dialog_content select, #dialog_content label {
|
||||
margin: 10px;
|
||||
line-height: 0.3em;
|
||||
}
|
||||
|
||||
#dialog_container {
|
||||
font-family: Verdana;
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
max-width: 400px;
|
||||
z-index: 50001;
|
||||
background: #5a6162;
|
||||
border: 1px outset #777;
|
||||
font-family:Verdana,Helvetica,sans-serif;
|
||||
font-size:0.8em;
|
||||
}
|
||||
|
||||
#dialog_container, #dialog_content {
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
|
||||
#dialog_buttons input[type=text] {
|
||||
width: 90%;
|
||||
display: block;
|
||||
margin: 0 0 5px 11px;
|
||||
}
|
||||
|
||||
#dialog_buttons input[type=button] {
|
||||
margin: 0 1em;
|
||||
}
|
||||
.se-select{
|
||||
text-align: center;
|
||||
}
|
||||
elix-number-spin-box{
|
||||
margin-left: 15px;
|
||||
}
|
||||
</style>
|
||||
<elix-dialog id="export_box" aria-label="export svg" closed>
|
||||
<div class="overlay"></div>
|
||||
<div id="dialog_container">
|
||||
<div id="dialog_content">
|
||||
<p class="se-select" id="export_select"></p>
|
||||
<p class="se-select">
|
||||
<select id="se-storage-pref">
|
||||
<option value="PNG">PNG</option>
|
||||
<option value="JPEG">JPEG</option>
|
||||
<option value="BMP">BMP</option>
|
||||
<option value="WEBP">WEBP</option>
|
||||
<option value="PDF">PDF</option>
|
||||
</select>
|
||||
</p>
|
||||
<p id="se-quality"><elix-number-spin-box min="-1" max="101" step="5" value="100"></elix-number-spin-box></p>
|
||||
</div>
|
||||
<div id="dialog_buttons">
|
||||
<button id="export_ok"></button>
|
||||
<button id="export_cancel"></button>
|
||||
</div>
|
||||
</div>
|
||||
</elix-dialog>
|
||||
`;
|
||||
/**
|
||||
* @class SeExportDialog
|
||||
*/
|
||||
export class SeExportDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$dialog = this._shadowRoot.querySelector('#export_box');
|
||||
this.$okBtn = this._shadowRoot.querySelector('#export_ok');
|
||||
this.$cancelBtn = this._shadowRoot.querySelector('#export_cancel');
|
||||
this.$exportOption = this._shadowRoot.querySelector('#se-storage-pref');
|
||||
this.$qualityCont = this._shadowRoot.querySelector('#se-quality');
|
||||
this.$input = this._shadowRoot.querySelector('elix-number-spin-box');
|
||||
this.value = 1;
|
||||
}
|
||||
/**
|
||||
* @function init
|
||||
* @param {any} name
|
||||
* @returns {void}
|
||||
*/
|
||||
init (i18next) {
|
||||
this.setAttribute('common-ok', i18next.t('common.ok'));
|
||||
this.setAttribute('common-cancel', i18next.t('common.cancel'));
|
||||
this.setAttribute('ui-quality', i18next.t('ui.quality'));
|
||||
this.setAttribute('ui-export_type_label', i18next.t('ui.export_type_label'));
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'dialog', 'common-ok', 'common-cancel', 'ui-quality', 'ui-export_type_label' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
// eslint-disable-next-line sonarjs/no-small-switch
|
||||
let node;
|
||||
switch (name) {
|
||||
case 'dialog':
|
||||
if (newValue === 'open') {
|
||||
this.$dialog.open();
|
||||
} else {
|
||||
this.$dialog.close();
|
||||
}
|
||||
break;
|
||||
case 'common-ok':
|
||||
this.$okBtn.textContent = newValue;
|
||||
break;
|
||||
case 'common-cancel':
|
||||
this.$cancelBtn.textContent = newValue;
|
||||
break;
|
||||
case 'ui-quality':
|
||||
node = this._shadowRoot.querySelector('#se-quality');
|
||||
node.prepend(newValue);
|
||||
break;
|
||||
case 'ui-export_type_label':
|
||||
node = this._shadowRoot.querySelector('#export_select');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
default:
|
||||
// super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get dialog () {
|
||||
return this.getAttribute('dialog');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set dialog (value) {
|
||||
this.setAttribute('dialog', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
this.$input.addEventListener('change', (e) => {
|
||||
e.preventDefault();
|
||||
this.value = e.target.value;
|
||||
});
|
||||
this.$input.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.value = e.target.value;
|
||||
});
|
||||
const onSubmitHandler = (e, action) => {
|
||||
if (action === 'cancel') {
|
||||
document.getElementById('se-export-dialog').setAttribute('dialog', 'close');
|
||||
} else {
|
||||
const triggerEvent = new CustomEvent('change', { detail: {
|
||||
trigger: action,
|
||||
imgType: this.$exportOption.value,
|
||||
quality: this.value
|
||||
} });
|
||||
this.dispatchEvent(triggerEvent);
|
||||
}
|
||||
};
|
||||
const onChangeHandler = (e) => {
|
||||
if (e.target.value === 'PDF') {
|
||||
this.$qualityCont.style.display = 'none';
|
||||
} else {
|
||||
this.$qualityCont.style.display = 'block';
|
||||
}
|
||||
};
|
||||
this.$okBtn.addEventListener('click', (evt) => onSubmitHandler(evt, 'ok'));
|
||||
this.$cancelBtn.addEventListener('click', (evt) => onSubmitHandler(evt, 'cancel'));
|
||||
this.$exportOption.addEventListener('change', (evt) => onChangeHandler(evt));
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-export-dialog', SeExportDialog);
|
||||
448
src/editor/dialogs/imagePropertiesDialog.js
Normal file
@@ -0,0 +1,448 @@
|
||||
import { isValidUnit } from '../../common/units.js';
|
||||
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Force the scroll bar to appear so we see it hide when overlay opens. */
|
||||
body::-webkit-scrollbar {
|
||||
background: lightgray;
|
||||
}
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background: darkgray;
|
||||
}
|
||||
#svg_docprops #svg_docprops_container {
|
||||
padding: 10px;
|
||||
background-color: #5a6162;
|
||||
color: #c5c5c5;
|
||||
border: 1px outset #777;
|
||||
opacity: 1.0;
|
||||
font-family: Verdana, Helvetica, sans-serif;
|
||||
font-size: .8em;
|
||||
z-index: 20001;
|
||||
}
|
||||
|
||||
#svg_docprops .error {
|
||||
border: 1px solid red;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
#svg_docprops #resolution {
|
||||
max-width: 14em;
|
||||
}
|
||||
|
||||
#tool_docprops_back {
|
||||
margin-left: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#svg_docprops_container #svg_docprops_docprops {
|
||||
float: left;
|
||||
width: 221px;
|
||||
margin: 5px .7em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#svg_docprops legend {
|
||||
max-width: 195px;
|
||||
}
|
||||
|
||||
#svg_docprops_docprops > legend {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
#svg_docprops_container fieldset {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
|
||||
#svg_docprops_container label {
|
||||
display: block;
|
||||
margin: .5em;
|
||||
}
|
||||
</style>
|
||||
<elix-dialog id="svg_docprops" aria-label="Sample dialog" closed>
|
||||
<div id="svg_docprops_container">
|
||||
<div id="tool_docprops_back" class="toolbar_button">
|
||||
<button id="tool_docprops_save"></button>
|
||||
<button id="tool_docprops_cancel"></button>
|
||||
</div>
|
||||
<fieldset id="svg_docprops_docprops">
|
||||
<legend id="svginfo_image_props"></legend>
|
||||
<label>
|
||||
<span id="svginfo_title"></span>
|
||||
<input type="text" id="canvas_title" />
|
||||
</label>
|
||||
<fieldset id="change_resolution">
|
||||
<legend id="svginfo_dim"></legend>
|
||||
<label>
|
||||
<span id="svginfo_width"></span>
|
||||
<input type="text" id="canvas_width" size="6" />
|
||||
</label>
|
||||
<label>
|
||||
<span id="svginfo_height"></span>
|
||||
<input type="text" id="canvas_height" size="6" />
|
||||
</label>
|
||||
<label>
|
||||
<select id="resolution">
|
||||
<option id="selectedPredefined" selected="selected"></option>
|
||||
<option>640x480</option>
|
||||
<option>800x600</option>
|
||||
<option>1024x768</option>
|
||||
<option>1280x960</option>
|
||||
<option>1600x1200</option>
|
||||
<option id="fitToContent" value="content"></option>
|
||||
</select>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset id="image_save_opts">
|
||||
<legend id="includedImages"></legend>
|
||||
<label>
|
||||
<input type="radio" id="image_embed" name="image_opt" value="embed" checked="checked" />
|
||||
<span id="image_opt_embed"></span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" id="image_ref" name="image_opt" value="ref" />
|
||||
<span id="image_opt_ref"></span>
|
||||
</label>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</div>
|
||||
</elix-dialog>
|
||||
|
||||
`;
|
||||
/**
|
||||
* @class SeImgPropDialog
|
||||
*/
|
||||
export class SeImgPropDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this.eventlisten = false;
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$saveBtn = this._shadowRoot.querySelector('#tool_docprops_save');
|
||||
this.$cancelBtn = this._shadowRoot.querySelector('#tool_docprops_cancel');
|
||||
this.$resolution = this._shadowRoot.querySelector('#resolution');
|
||||
this.$canvasTitle = this._shadowRoot.querySelector('#canvas_title');
|
||||
this.$canvasWidth = this._shadowRoot.querySelector('#canvas_width');
|
||||
this.$canvasHeight = this._shadowRoot.querySelector('#canvas_height');
|
||||
this.$imageOptEmbed = this._shadowRoot.querySelector('#image_embed');
|
||||
this.$imageOptRef = this._shadowRoot.querySelector('#image_ref');
|
||||
this.$dialog = this._shadowRoot.querySelector('#svg_docprops');
|
||||
}
|
||||
/**
|
||||
* @function init
|
||||
* @param {any} name
|
||||
* @returns {void}
|
||||
*/
|
||||
init (i18next) {
|
||||
this.setAttribute('common-ok', i18next.t('common.ok'));
|
||||
this.setAttribute('common-cancel', i18next.t('common.cancel'));
|
||||
this.setAttribute('config-image_props', i18next.t('config.image_props'));
|
||||
this.setAttribute('config-doc_title', i18next.t('config.doc_title'));
|
||||
this.setAttribute('config-doc_dims', i18next.t('config.doc_dims'));
|
||||
this.setAttribute('common-width', i18next.t('common.width'));
|
||||
this.setAttribute('common-height', i18next.t('common.height'));
|
||||
this.setAttribute('config-select_predefined', i18next.t('config.select_predefined'));
|
||||
this.setAttribute('tools-fit-to-content', i18next.t('tools.fitToContent'));
|
||||
this.setAttribute('config-included_images', i18next.t('config.included_images'));
|
||||
this.setAttribute('config-image_opt_embed', i18next.t('config.image_opt_embed'));
|
||||
this.setAttribute('config-image_opt_ref', i18next.t('config.image_opt_ref'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'width', 'height', 'save', 'dialog', 'embed', 'common-ok',
|
||||
'common-cancel', 'config-image_props', 'config-doc_title', 'config-doc_dims',
|
||||
'common-width', 'common-height', 'config-select_predefined',
|
||||
'tools-fit-to-content', 'config-included_images', 'config-image_opt_embed',
|
||||
'config-image_opt_ref' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
let node ;
|
||||
switch (name) {
|
||||
case 'title':
|
||||
this.$canvasTitle.value = newValue;
|
||||
break;
|
||||
case 'width':
|
||||
if (newValue === 'fit') {
|
||||
this.$canvasWidth.removeAttribute('disabled');
|
||||
this.$canvasWidth.value = 100;
|
||||
this.$canvasHeight.removeAttribute('disabled');
|
||||
this.$canvasHeight.value = 100;
|
||||
} else {
|
||||
this.$canvasWidth.value = newValue;
|
||||
}
|
||||
break;
|
||||
case 'height':
|
||||
if (newValue === 'fit') {
|
||||
this.$canvasWidth.removeAttribute('disabled');
|
||||
this.$canvasWidth.value = 100;
|
||||
this.$canvasHeight.removeAttribute('disabled');
|
||||
this.$canvasHeight.value = 100;
|
||||
} else {
|
||||
this.$canvasHeight.value = newValue;
|
||||
}
|
||||
break;
|
||||
case 'dialog':
|
||||
if (this.eventlisten) {
|
||||
if (newValue === 'open') {
|
||||
this.$dialog.open();
|
||||
} else {
|
||||
this.$dialog.close();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'save':
|
||||
if (newValue === 'ref') {
|
||||
this.$imageOptEmbed.setAttribute('checked', false);
|
||||
this.$imageOptRef.setAttribute('checked', true);
|
||||
} else {
|
||||
this.$imageOptEmbed.setAttribute('checked', true);
|
||||
this.$imageOptRef.setAttribute('checked', false);
|
||||
}
|
||||
break;
|
||||
case 'embed':
|
||||
if (newValue.includes('one')) {
|
||||
const data = newValue.split('|');
|
||||
if (data.length > 1) {
|
||||
this._shadowRoot.querySelector('#image_opt_embed').setAttribute('title', data[1]);
|
||||
this._shadowRoot.querySelector('#image_opt_embed').setAttribute('disabled', 'disabled');
|
||||
this._shadowRoot.querySelector('#image_opt_embed').style.color = '#666';
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'common-ok':
|
||||
this.$saveBtn.textContent = newValue;
|
||||
break;
|
||||
case 'common-cancel':
|
||||
this.$cancelBtn.textContent = newValue;
|
||||
break;
|
||||
case 'config-image_props':
|
||||
node = this._shadowRoot.querySelector('#svginfo_image_props');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-doc_title':
|
||||
node = this._shadowRoot.querySelector('#svginfo_title');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-doc_dims':
|
||||
node = this._shadowRoot.querySelector('#svginfo_dim');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'common-width':
|
||||
node = this._shadowRoot.querySelector('#svginfo_width');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'common-height':
|
||||
node = this._shadowRoot.querySelector('#svginfo_height');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-select_predefined':
|
||||
node = this._shadowRoot.querySelector('#selectedPredefined');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'tools-fit-to-content':
|
||||
node = this._shadowRoot.querySelector('#fitToContent');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-included_images':
|
||||
node = this._shadowRoot.querySelector('#includedImages');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-image_opt_embed':
|
||||
node = this._shadowRoot.querySelector('#image_opt_embed');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-image_opt_ref':
|
||||
node = this._shadowRoot.querySelector('#image_opt_ref');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
default:
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get title () {
|
||||
return this.getAttribute('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set title (value) {
|
||||
this.setAttribute('title', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get width () {
|
||||
return this.getAttribute('width');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set width (value) {
|
||||
this.setAttribute('width', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get height () {
|
||||
return this.getAttribute('height');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set height (value) {
|
||||
this.setAttribute('height', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get save () {
|
||||
return this.getAttribute('save');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set save (value) {
|
||||
this.setAttribute('save', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get dialog () {
|
||||
return this.getAttribute('dialog');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set dialog (value) {
|
||||
this.setAttribute('dialog', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get embed () {
|
||||
return this.getAttribute('embed');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set embed (value) {
|
||||
this.setAttribute('embed', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
const onChangeHandler = (ev) => {
|
||||
if (!ev.target.selectedIndex) {
|
||||
if (this.$canvasWidth.getAttribute('value') === 'fit') {
|
||||
this.$canvasWidth.removeAttribute('disabled');
|
||||
this.$canvasWidth.value = 100;
|
||||
this.$canvasHeight.removeAttribute('disabled');
|
||||
this.$canvasHeight.value = 100;
|
||||
}
|
||||
} else if (ev.target.value === 'content') {
|
||||
this.$canvasWidth.setAttribute('disabled', 'disabled');
|
||||
this.$canvasWidth.value = 'fit';
|
||||
this.$canvasHeight.setAttribute('disabled', 'disabled');
|
||||
this.$canvasHeight.value = 'fit';
|
||||
} else {
|
||||
const dims = ev.target.value.split('x');
|
||||
this.$canvasWidth.value = dims[0];
|
||||
this.$canvasWidth.removeAttribute('disabled');
|
||||
this.$canvasHeight.value = dims[1];
|
||||
this.$canvasHeight.removeAttribute('disabled');
|
||||
}
|
||||
};
|
||||
const onSaveHandler = () => {
|
||||
let saveOpt = '';
|
||||
const w = this.$canvasWidth.value;
|
||||
const h = this.$canvasHeight.value;
|
||||
if (w !== 'fit' && !isValidUnit('width', w)) {
|
||||
this.$canvasWidth.parentElement.classList.add('error');
|
||||
} else {
|
||||
this.$canvasWidth.parentElement.classList.remove('error');
|
||||
}
|
||||
if (h !== 'fit' && !isValidUnit('height', w)) {
|
||||
this.$canvasHeight.parentElement.classList.add('error');
|
||||
} else {
|
||||
this.$canvasHeight.parentElement.classList.remove('error');
|
||||
}
|
||||
if (this.$imageOptEmbed.getAttribute('checked') === 'true') {
|
||||
saveOpt = 'embed';
|
||||
}
|
||||
if (this.$imageOptRef.getAttribute('checked') === 'true') {
|
||||
saveOpt = 'ref';
|
||||
}
|
||||
const closeEvent = new CustomEvent('change', { detail: {
|
||||
title: this.$canvasTitle.value,
|
||||
w: this.$canvasWidth.value,
|
||||
h: this.$canvasHeight.value,
|
||||
save: saveOpt,
|
||||
dialog: 'close'
|
||||
} });
|
||||
this.$canvasWidth.removeAttribute('disabled');
|
||||
this.$canvasHeight.removeAttribute('disabled');
|
||||
this.$resolution.selectedIndex = 0;
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
const onCancelHandler = () => {
|
||||
const closeEvent = new CustomEvent('change', { detail: {
|
||||
dialog: 'closed'
|
||||
} });
|
||||
this.$canvasWidth.removeAttribute('disabled');
|
||||
this.$canvasHeight.removeAttribute('disabled');
|
||||
this.$resolution.selectedIndex = 0;
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
this.$resolution.addEventListener('change', onChangeHandler);
|
||||
this.$saveBtn.addEventListener('click', onSaveHandler);
|
||||
this.$cancelBtn.addEventListener('click', onCancelHandler);
|
||||
this.$dialog.addEventListener('close', onCancelHandler);
|
||||
this.eventlisten = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-img-prop-dialog', SeImgPropDialog);
|
||||
11
src/editor/dialogs/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'elix/define/Dialog.js';
|
||||
import './imagePropertiesDialog.js';
|
||||
import './editorPreferencesDialog.js';
|
||||
import './svgSourceDialog.js';
|
||||
import './cmenuDialog.js';
|
||||
import './cmenuLayersDialog.js';
|
||||
import './seSelectDialog.js';
|
||||
import './seConfirmDialog.js';
|
||||
import './sePromptDialog.js';
|
||||
import './seAlertDialog.js';
|
||||
import './exportDialog.js';
|
||||
7
src/editor/dialogs/se-elix/define/NumberSpinBox.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import PlainNumberSpinBox from '../src/plain/PlainNumberSpinBox.js';
|
||||
/**
|
||||
* @class ElixNumberSpinBox
|
||||
*/
|
||||
export default class ElixNumberSpinBox extends PlainNumberSpinBox {}
|
||||
|
||||
customElements.define('elix-number-spin-box', ElixNumberSpinBox);
|
||||
247
src/editor/dialogs/se-elix/src/base/NumberSpinBox.js
Normal file
@@ -0,0 +1,247 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
import {
|
||||
defaultState,
|
||||
setState,
|
||||
state,
|
||||
stateEffects
|
||||
} from 'elix/src/base/internal.js';
|
||||
import {
|
||||
SpinBox
|
||||
} from 'elix/src/base/SpinBox.js';
|
||||
|
||||
/**
|
||||
* @class NumberSpinBox
|
||||
*/
|
||||
class NumberSpinBox extends SpinBox {
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (name === 'max') {
|
||||
this.max = parseFloat(newValue);
|
||||
} else if (name === 'min') {
|
||||
this.min = parseFloat(newValue);
|
||||
} else if (name === 'step') {
|
||||
this.step = parseFloat(newValue);
|
||||
} else {
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
get [defaultState] () {
|
||||
return Object.assign(super[defaultState], {
|
||||
max: null,
|
||||
min: null,
|
||||
step: 1
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function formatValue
|
||||
* Format the numeric value as a string.
|
||||
*
|
||||
* This is used after incrementing/decrementing the value to reformat the
|
||||
* value as a string.
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {number} precision
|
||||
* @returns {number}
|
||||
*/
|
||||
formatValue (value, precision) {
|
||||
return Number(value).toFixed(precision);
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum allowable value of the `value` property.
|
||||
*
|
||||
* @type {number|null}
|
||||
* @default 1
|
||||
*/
|
||||
get max () {
|
||||
return this[state].max;
|
||||
}
|
||||
/**
|
||||
* The maximum allowable value of the `value` property.
|
||||
*
|
||||
* @type {number|null}
|
||||
* @default 1
|
||||
*/
|
||||
set max (max) {
|
||||
this[setState]({
|
||||
max
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum allowable value of the `value` property.
|
||||
*
|
||||
* @type {number|null}
|
||||
* @default 1
|
||||
*/
|
||||
get min () {
|
||||
return this[state].min;
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set min (min) {
|
||||
this[setState]({
|
||||
min
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function parseValue
|
||||
* @param {number} value
|
||||
* @param {number} precision
|
||||
* @returns {int}
|
||||
*/
|
||||
parseValue (value, precision) {
|
||||
const parsed = precision === 0 ? parseInt(value) : parseFloat(value);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
/**
|
||||
* @function stateEffects
|
||||
* @param {any} state
|
||||
* @param {any} changed
|
||||
* @returns {any}
|
||||
*/
|
||||
[stateEffects] (state, changed) {
|
||||
const effects = super[stateEffects];
|
||||
// If step changed, calculate its precision (number of digits after
|
||||
// the decimal).
|
||||
if (changed.step) {
|
||||
const {
|
||||
step
|
||||
} = state;
|
||||
const decimalRegex = /\.(\d)+$/;
|
||||
const match = decimalRegex.exec(String(step));
|
||||
const precision = match && match[1] ? match[1].length : 0;
|
||||
Object.assign(effects, {
|
||||
precision
|
||||
});
|
||||
}
|
||||
|
||||
if (changed.max || changed.min || changed.value) {
|
||||
// The value is valid if it falls between the min and max.
|
||||
// TODO: We need a way to let other classes/mixins on the prototype chain
|
||||
// contribute to validity -- if someone else thinks the value is invalid,
|
||||
// we should respect that, even if the value falls within the min/max
|
||||
// bounds.
|
||||
const {
|
||||
max,
|
||||
min,
|
||||
precision,
|
||||
value
|
||||
} = state;
|
||||
const parsed = parseInt(value, precision);
|
||||
if (value !== '' && isNaN(parsed)) {
|
||||
Object.assign(effects, {
|
||||
valid: false,
|
||||
validationMessage: 'Value must be a number'
|
||||
});
|
||||
} else if (!(max === null || parsed <= max)) {
|
||||
Object.assign(effects, {
|
||||
valid: false,
|
||||
validationMessage: `Value must be less than or equal to ${max}.`
|
||||
});
|
||||
} else if (!(min === null || parsed >= min)) {
|
||||
Object.assign(effects, {
|
||||
valid: false,
|
||||
validationMessage: `Value must be greater than or equal to ${min}.`
|
||||
});
|
||||
} else {
|
||||
Object.assign(effects, {
|
||||
valid: true,
|
||||
validationMessage: ''
|
||||
});
|
||||
}
|
||||
// We can only go up if we're below max.
|
||||
Object.assign(effects, {
|
||||
canGoUp: isNaN(parsed) || state.max === null || parsed <= state.max
|
||||
});
|
||||
|
||||
// We can only go down if we're above min.
|
||||
Object.assign(effects, {
|
||||
canGoDown: isNaN(parsed) || state.min === null || parsed >= state.min
|
||||
});
|
||||
}
|
||||
|
||||
return effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get step () {
|
||||
return this[state].step;
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set step (step) {
|
||||
if (!isNaN(step)) {
|
||||
this[setState]({
|
||||
step
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function stepDown
|
||||
* @returns {void}
|
||||
*/
|
||||
stepDown () {
|
||||
super.stepDown();
|
||||
const {
|
||||
max,
|
||||
precision,
|
||||
value
|
||||
} = this[state];
|
||||
let result = this.parseValue(value, precision) - this.step;
|
||||
if (max !== null) {
|
||||
result = Math.min(result, max);
|
||||
}
|
||||
const {
|
||||
min
|
||||
} = this[state];
|
||||
if (min === null || result >= min) {
|
||||
this.value = this.formatValue(result, precision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function stepUp
|
||||
* @returns {void}
|
||||
*/
|
||||
stepUp () {
|
||||
super.stepUp();
|
||||
const {
|
||||
min,
|
||||
precision,
|
||||
value
|
||||
} = this[state];
|
||||
let result = this.parseValue(value, precision) + this.step;
|
||||
if (min !== null) {
|
||||
result = Math.max(result, min);
|
||||
}
|
||||
const {
|
||||
max
|
||||
} = this[state];
|
||||
if (max === null || result <= max) {
|
||||
this.value = this.formatValue(result, precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberSpinBox;
|
||||
@@ -0,0 +1,9 @@
|
||||
import PlainSpinBoxMixin from 'elix/src/plain/PlainSpinBoxMixin.js';
|
||||
import NumberSpinBox from '../base/NumberSpinBox.js';
|
||||
|
||||
/**
|
||||
* @class PlainNumberSpinBox
|
||||
*/
|
||||
class PlainNumberSpinBox extends PlainSpinBoxMixin(NumberSpinBox) {}
|
||||
|
||||
export default PlainNumberSpinBox;
|
||||
10
src/editor/dialogs/seAlertDialog.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import SePlainAlertDialog from './SePlainAlertDialog.js';
|
||||
|
||||
const seAlert = (text) => {
|
||||
const dialog = new SePlainAlertDialog();
|
||||
dialog.textContent = text;
|
||||
dialog.choices = [ 'Ok' ];
|
||||
dialog.open();
|
||||
};
|
||||
|
||||
window.seAlert = seAlert;
|
||||
12
src/editor/dialogs/seConfirmDialog.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import SePlainAlertDialog from './SePlainAlertDialog.js';
|
||||
|
||||
const seConfirm = async (text, choices) => {
|
||||
const dialog = new SePlainAlertDialog();
|
||||
dialog.textContent = text;
|
||||
dialog.choices = (choices === undefined) ? [ 'Ok', 'Cancel' ] : choices;
|
||||
dialog.open();
|
||||
const response = await dialog.whenClosed();
|
||||
return response.choice;
|
||||
};
|
||||
|
||||
window.seConfirm = seConfirm;
|
||||
83
src/editor/dialogs/sePromptDialog.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import SePlainAlertDialog from './SePlainAlertDialog.js';
|
||||
/**
|
||||
* @class SePromptDialog
|
||||
*/
|
||||
export class SePromptDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this.dialog = new SePlainAlertDialog();
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'title', 'close' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
switch (name) {
|
||||
case 'title':
|
||||
if (this.dialog.opened) {
|
||||
this.dialog.close();
|
||||
}
|
||||
this.dialog.textContent = newValue;
|
||||
this.dialog.choices = [ 'Cancel' ];
|
||||
this.dialog.open();
|
||||
break;
|
||||
case 'close':
|
||||
if (this.dialog.opened) {
|
||||
this.dialog.close();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('unknown attr for:', name, 'newValue =', newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get title () {
|
||||
return this.getAttribute('title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set title (value) {
|
||||
this.setAttribute('title', value);
|
||||
}
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get close () {
|
||||
return this.getAttribute('close');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set close (value) {
|
||||
this.setAttribute('close', value);
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-prompt-dialog', SePromptDialog);
|
||||
12
src/editor/dialogs/seSelectDialog.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import SePlainAlertDialog from './SePlainAlertDialog.js';
|
||||
|
||||
const seSelect = async (text, choices) => {
|
||||
const dialog = new SePlainAlertDialog();
|
||||
dialog.textContent = text;
|
||||
dialog.choices = choices;
|
||||
dialog.open();
|
||||
const response = await dialog.whenClosed();
|
||||
return response.choice;
|
||||
};
|
||||
|
||||
window.seSelect = seSelect;
|
||||
265
src/editor/dialogs/svgSourceDialog.js
Normal file
@@ -0,0 +1,265 @@
|
||||
const template = document.createElement('template');
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Force the scroll bar to appear so we see it hide when overlay opens. */
|
||||
body::-webkit-scrollbar {
|
||||
background: lightgray;
|
||||
}
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background: darkgray;
|
||||
}
|
||||
|
||||
#svg_source_editor #svg_source_container {
|
||||
background-color: #5a6162;
|
||||
color: #c5c5c5;
|
||||
opacity: 1.0;
|
||||
text-align: center;
|
||||
border: 1px outset #777;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
#save_output_btns {
|
||||
display: none;
|
||||
text-align: left;
|
||||
}
|
||||
#save_output_btns p {
|
||||
margin: .5em 1.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
#svg_source_editor form {
|
||||
width: 100%;
|
||||
}
|
||||
#svg_source_editor #svg_source_textarea {
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
min-height: 200px;
|
||||
width: 95%;
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
#svg_source_editor #tool_source_back {
|
||||
text-align: left;
|
||||
height: 30px;
|
||||
}
|
||||
#tool_source_save {
|
||||
width: 20%;
|
||||
background-color: #c79605;
|
||||
margin-left: 30%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#tool_source_cancel {
|
||||
width: 20%;
|
||||
background-color: #c8c8c8;
|
||||
}
|
||||
</style>
|
||||
<elix-dialog id="svg_source_editor" aria-label="SVG Source Editor" closed>
|
||||
<div id="svg_source_container">
|
||||
<div id="tool_source_back" class="toolbar_button">
|
||||
<button id="tool_source_save"></button>
|
||||
<button id="tool_source_cancel"></button>
|
||||
</div>
|
||||
<div id="save_output_btns">
|
||||
<p id="copy_save_note"></p>
|
||||
<button id="copy_save_done"></button>
|
||||
</div>
|
||||
<form>
|
||||
<textarea id="svg_source_textarea" spellcheck="false" rows="5" cols="80"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</elix-dialog>
|
||||
`;
|
||||
/**
|
||||
* @class SeSvgSourceEditorDialog
|
||||
*/
|
||||
export class SeSvgSourceEditorDialog extends HTMLElement {
|
||||
/**
|
||||
* @function constructor
|
||||
*/
|
||||
constructor () {
|
||||
super();
|
||||
// create the shadowDom and insert the template
|
||||
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
||||
this._shadowRoot.append(template.content.cloneNode(true));
|
||||
this.$dialog = this._shadowRoot.querySelector('#svg_source_editor');
|
||||
this.$copyBtn = this._shadowRoot.querySelector('#copy_save_done');
|
||||
this.$saveBtn = this._shadowRoot.querySelector('#tool_source_save');
|
||||
this.$cancelBtn = this._shadowRoot.querySelector('#tool_source_cancel');
|
||||
this.$sourceTxt = this._shadowRoot.querySelector('#svg_source_textarea');
|
||||
this.$copySec = this._shadowRoot.querySelector('#save_output_btns');
|
||||
this.$applySec = this._shadowRoot.querySelector('#tool_source_back');
|
||||
}
|
||||
/**
|
||||
* @function init
|
||||
* @param {any} name
|
||||
* @returns {void}
|
||||
*/
|
||||
init (i18next) {
|
||||
this.setAttribute('tools-source_save', i18next.t('tools.source_save'));
|
||||
this.setAttribute('common-cancel', i18next.t('common.cancel'));
|
||||
this.setAttribute('notification-source_dialog_note', i18next.t('notification.source_dialog_note'));
|
||||
this.setAttribute('config-done', i18next.t('config.done'));
|
||||
}
|
||||
/**
|
||||
* @function observedAttributes
|
||||
* @returns {any} observed
|
||||
*/
|
||||
static get observedAttributes () {
|
||||
return [ 'dialog', 'value', 'applysec', 'copysec', 'tools-source_save', 'common-cancel', 'notification-source_dialog_note', 'config-done' ];
|
||||
}
|
||||
/**
|
||||
* @function attributeChangedCallback
|
||||
* @param {string} name
|
||||
* @param {string} oldValue
|
||||
* @param {string} newValue
|
||||
* @returns {void}
|
||||
*/
|
||||
attributeChangedCallback (name, oldValue, newValue) {
|
||||
if (oldValue === newValue) return;
|
||||
let node;
|
||||
switch (name) {
|
||||
case 'dialog':
|
||||
if (newValue === 'open') {
|
||||
this.$sourceTxt.focus();
|
||||
this.$dialog.open();
|
||||
} else {
|
||||
this.$dialog.close();
|
||||
this.$sourceTxt.blur();
|
||||
}
|
||||
break;
|
||||
case 'applysec':
|
||||
if (newValue === 'false') {
|
||||
this.$applySec.style.display = 'none';
|
||||
} else {
|
||||
this.$applySec.style.display = 'block';
|
||||
}
|
||||
break;
|
||||
case 'copysec':
|
||||
if (newValue === 'false') {
|
||||
this.$copySec.style.display = 'none';
|
||||
} else {
|
||||
this.$copySec.style.display = 'block';
|
||||
}
|
||||
break;
|
||||
case 'value':
|
||||
this.$sourceTxt.value = newValue;
|
||||
break;
|
||||
case 'tools-source_save':
|
||||
this.$saveBtn.textContent = newValue;
|
||||
break;
|
||||
case 'common-cancel':
|
||||
this.$cancelBtn.textContent = newValue;
|
||||
break;
|
||||
case 'notification-source_dialog_note':
|
||||
node = this._shadowRoot.querySelector('#copy_save_note');
|
||||
node.textContent = newValue;
|
||||
break;
|
||||
case 'config-done':
|
||||
this.$copyBtn.textContent = newValue;
|
||||
break;
|
||||
default:
|
||||
super.attributeChangedCallback(name, oldValue, newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get dialog () {
|
||||
return this.getAttribute('dialog');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set dialog (value) {
|
||||
this.setAttribute('dialog', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get value () {
|
||||
return this.getAttribute('value');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set value (value) {
|
||||
this.setAttribute('value', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get applysec () {
|
||||
return this.getAttribute('applysec');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set applysec (value) {
|
||||
this.setAttribute('applysec', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function get
|
||||
* @returns {any}
|
||||
*/
|
||||
get copysec () {
|
||||
return this.getAttribute('copysec');
|
||||
}
|
||||
/**
|
||||
* @function set
|
||||
* @returns {void}
|
||||
*/
|
||||
set copysec (value) {
|
||||
this.setAttribute('copysec', value);
|
||||
}
|
||||
/**
|
||||
* @function connectedCallback
|
||||
* @returns {void}
|
||||
*/
|
||||
connectedCallback () {
|
||||
const onCancelHandler = () => {
|
||||
const closeEvent = new CustomEvent('change', { detail: {
|
||||
dialog: 'closed'
|
||||
} });
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
const onCopyHandler = () => {
|
||||
const closeEvent = new CustomEvent('change', {
|
||||
detail: {
|
||||
copy: 'click',
|
||||
value: this.$sourceTxt.value
|
||||
}
|
||||
});
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
const onSaveHandler = () => {
|
||||
const closeEvent = new CustomEvent('change', { detail: {
|
||||
value: this.$sourceTxt.value,
|
||||
dialog: 'close'
|
||||
} });
|
||||
this.dispatchEvent(closeEvent);
|
||||
};
|
||||
this.$copyBtn.addEventListener('click', onCopyHandler);
|
||||
this.$saveBtn.addEventListener('click', onSaveHandler);
|
||||
this.$cancelBtn.addEventListener('click', onCancelHandler);
|
||||
this.$dialog.addEventListener('close', onCancelHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
customElements.define('se-svg-source-editor-dialog', SeSvgSourceEditorDialog);
|
||||
@@ -1,12 +1,9 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Attaches items to DOM for Embedded SVG support.
|
||||
* @module EmbeddedSVGEditDOM
|
||||
*/
|
||||
import EmbeddedSVGEdit from './embedapi.js';
|
||||
import {isChrome} from '../common/browser.js';
|
||||
|
||||
const $ = jQuery;
|
||||
import { isChrome } from '../common/browser.js';
|
||||
|
||||
let svgCanvas = null;
|
||||
|
||||
@@ -18,10 +15,10 @@ let svgCanvas = null;
|
||||
function handleSvgData (data, error) {
|
||||
if (error) {
|
||||
// Todo: This should be replaced with a general purpose dialog alert library call
|
||||
alert('error ' + error); // eslint-disable-line no-alert
|
||||
alert('error ' + error);
|
||||
} else {
|
||||
// Todo: This should be replaced with a general purpose dialog alert library call
|
||||
alert('Congratulations. Your SVG string is back in the host page, do with it what you will\n\n' + data); // eslint-disable-line no-alert
|
||||
alert('Congratulations. Your SVG string is back in the host page, do with it what you will\n\n' + data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,21 +95,25 @@ function exportPDF () {
|
||||
const frameBase = 'https://raw.githack.com/SVG-Edit/svgedit/master';
|
||||
// const frameBase = 'http://localhost:8001';
|
||||
const framePath = '/editor/xdomain-svg-editor-es.html?extensions=ext-xdomain-messaging.js';
|
||||
const iframe = $('<iframe width="900px" height="600px" id="svgedit" src="javascript:0"></iframe>');
|
||||
iframe[0].src = frameBase + framePath +
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = "svgedit";
|
||||
iframe.style.width = "900px";
|
||||
iframe.style.width = "600px";
|
||||
iframe.src = frameBase + framePath +
|
||||
(location.href.includes('?')
|
||||
// ? location.href.replace(/\?(?<search>.*)$/, '&$<search>')
|
||||
? location.href.replace(/\?(.*)$/, '&$1')
|
||||
: ''); // Append arguments to this file onto the iframe
|
||||
|
||||
iframe[0].addEventListener('load', function () {
|
||||
svgCanvas = new EmbeddedSVGEdit(frame, [new URL(frameBase).origin]);
|
||||
iframe.addEventListener('load', function () {
|
||||
svgCanvas = new EmbeddedSVGEdit(frame, [ new URL(frameBase).origin ]);
|
||||
const { $id } = svgCanvas;
|
||||
// Hide main button, as we will be controlling new, load, save, etc. from the host document
|
||||
let doc;
|
||||
try {
|
||||
doc = frame.contentDocument || frame.contentWindow.document;
|
||||
} catch (err) {
|
||||
console.log('Blocked from accessing document', err); // eslint-disable-line no-console
|
||||
console.error('Blocked from accessing document', err);
|
||||
}
|
||||
if (doc) {
|
||||
// Todo: Provide a way to get this to occur by `postMessage`
|
||||
@@ -121,10 +122,10 @@ iframe[0].addEventListener('load', function () {
|
||||
}
|
||||
|
||||
// Add event handlers now that `svgCanvas` is ready
|
||||
$('#load').click(loadSvg);
|
||||
$('#save').click(saveSvg);
|
||||
$('#exportPNG').click(exportPNG);
|
||||
$('#exportPDF').click(exportPDF);
|
||||
$id('load').addEventListener('click', loadSvg);
|
||||
$id('save').addEventListener('click', saveSvg);
|
||||
$id('exportPNG').addEventListener('click', exportPNG);
|
||||
$id('exportPDF').addEventListener('click', exportPDF);
|
||||
});
|
||||
$('body').append(iframe);
|
||||
document.body.appendChild(iframe);
|
||||
const frame = document.getElementById('svgedit');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Embed API</title>
|
||||
<link rel="icon" type="image/png" href="images/logo.png"/>
|
||||
<link rel="icon" type="image/png" href="images/logo.svg"/>
|
||||
<script src="jquery.min.js"></script>
|
||||
<script type="module" src="embedapi-dom.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -24,7 +24,7 @@ let cbid = 0;
|
||||
function getCallbackSetter (funcName) {
|
||||
return function (...args) {
|
||||
const that = this, // New callback
|
||||
callbackID = this.send(funcName, args, function () { /* empty */ }); // The callback (currently it's nothing, but will be set later)
|
||||
callbackID = this.send(funcName, args, function () { /* empty fn */ }); // The callback (currently it's nothing, but will be set later)
|
||||
|
||||
return function (newCallback) {
|
||||
that.callbacks[callbackID] = newCallback; // Set callback
|
||||
@@ -43,7 +43,7 @@ function getCallbackSetter (funcName) {
|
||||
* @param {Integer} data.id
|
||||
* @returns {void}
|
||||
*/
|
||||
function addCallback (t, {result, error, id: callbackID}) {
|
||||
function addCallback (t, { result, error, id: callbackID }) {
|
||||
if (typeof callbackID === 'number' && t.callbacks[callbackID]) {
|
||||
// These should be safe both because we check `cbid` is numeric and
|
||||
// because the calls are from trusted origins
|
||||
@@ -62,16 +62,15 @@ function addCallback (t, {result, error, id: callbackID}) {
|
||||
function messageListener (e) {
|
||||
// We accept and post strings as opposed to objects for the sake of IE9 support; this
|
||||
// will most likely be changed in the future
|
||||
if (!e.data || !['string', 'object'].includes(typeof e.data)) {
|
||||
if (!e.data || ![ 'string', 'object' ].includes(typeof e.data)) {
|
||||
return;
|
||||
}
|
||||
const {allowedOrigins} = this,
|
||||
const { allowedOrigins } = this,
|
||||
data = typeof e.data === 'object' ? e.data : JSON.parse(e.data);
|
||||
if (!data || typeof data !== 'object' || data.namespace !== 'svg-edit' ||
|
||||
e.source !== this.frame.contentWindow ||
|
||||
(!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin))
|
||||
) {
|
||||
// eslint-disable-next-line no-console -- Info for developers
|
||||
console.error(
|
||||
`The origin ${e.origin} was not whitelisted as an origin from ` +
|
||||
`which responses may be received by this ${window.origin} script.`
|
||||
@@ -331,10 +330,10 @@ class EmbeddedSVGEdit {
|
||||
// Older IE may need a polyfill for addEventListener, but so it would for SVG
|
||||
window.addEventListener('message', getMessageListener(this));
|
||||
window.addEventListener('keydown', (e) => {
|
||||
const {type, key} = e;
|
||||
const { type, key } = e;
|
||||
if (key === 'Backspace') {
|
||||
e.preventDefault();
|
||||
const keyboardEvent = new KeyboardEvent(type, {key});
|
||||
const keyboardEvent = new KeyboardEvent(type, { key });
|
||||
that.frame.contentDocument.dispatchEvent(keyboardEvent);
|
||||
}
|
||||
});
|
||||
@@ -346,7 +345,7 @@ class EmbeddedSVGEdit {
|
||||
* @param {GenericCallback} callback (This may be better than a promise in case adding an event.)
|
||||
* @returns {Integer}
|
||||
*/
|
||||
send (name, args, callback) { // eslint-disable-line promise/prefer-await-to-callbacks
|
||||
send (name, args, callback) {
|
||||
const that = this;
|
||||
cbid++;
|
||||
|
||||
@@ -364,8 +363,8 @@ class EmbeddedSVGEdit {
|
||||
let sameOriginWithGlobal = false;
|
||||
try {
|
||||
sameOriginWithGlobal = window.location.origin === that.frame.contentWindow.location.origin &&
|
||||
that.frame.contentWindow.svgEditor.canvas;
|
||||
} catch (err) {}
|
||||
that.frame.contentWindow.svgEditor.svgCanvas;
|
||||
}catch (err) {/* empty */}
|
||||
|
||||
if (sameOriginWithGlobal) {
|
||||
// Although we do not really need this API if we are working same
|
||||
@@ -374,8 +373,8 @@ class EmbeddedSVGEdit {
|
||||
// of the current JSON-based communication API (e.g., not passing
|
||||
// callbacks). We might be able to address these shortcomings; see
|
||||
// the todo elsewhere in this file.
|
||||
const message = {id: callbackID},
|
||||
{svgEditor: {canvas: svgCanvas}} = that.frame.contentWindow;
|
||||
const message = { id: callbackID },
|
||||
{ svgEditor: { canvas: svgCanvas } } = that.frame.contentWindow;
|
||||
try {
|
||||
message.result = svgCanvas[name](...args);
|
||||
} catch (err) {
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
const loadExtensionTranslation = async function (svgEditor) {
|
||||
let translationModule;
|
||||
const lang = svgEditor.configObj.pref('lang');
|
||||
try {
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -23,11 +25,12 @@ export default {
|
||||
name: 'arrows',
|
||||
async init (S) {
|
||||
const svgEditor = this;
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const svgCanvas = svgEditor.canvas;
|
||||
const strings = await loadExtensionTranslation(svgEditor);
|
||||
const { svgCanvas } = svgEditor;
|
||||
const { $id } = svgCanvas;
|
||||
const
|
||||
addElem = svgCanvas.addSVGElementFromJson,
|
||||
{nonce, $} = S,
|
||||
{ nonce } = S,
|
||||
prefix = 'se_arrow_';
|
||||
|
||||
let selElems, arrowprefix, randomizeIds = S.randomize_ids;
|
||||
@@ -48,7 +51,7 @@ export default {
|
||||
* @param {Window} win
|
||||
* @returns {void}
|
||||
*/
|
||||
function unsetArrowNonce (win) {
|
||||
function unsetArrowNonce (_win) {
|
||||
randomizeIds = false;
|
||||
arrowprefix = prefix;
|
||||
pathdata.fw.id = arrowprefix + 'fw';
|
||||
@@ -58,11 +61,11 @@ export default {
|
||||
svgCanvas.bind('setnonce', setArrowNonce);
|
||||
svgCanvas.bind('unsetnonce', unsetArrowNonce);
|
||||
|
||||
arrowprefix = randomizeIds ? prefix + nonce + '_' : prefix;
|
||||
arrowprefix = (randomizeIds) ? `${prefix}${nonce}_` : prefix;
|
||||
|
||||
const pathdata = {
|
||||
fw: {d: 'm0,0l10,5l-10,5l5,-5l-5,-5z', refx: 8, id: arrowprefix + 'fw'},
|
||||
bk: {d: 'm10,0l-10,5l10,5l-5,-5l5,-5z', refx: 2, id: arrowprefix + 'bk'}
|
||||
fw: { d: 'm0,0l10,5l-10,5l5,-5l-5,-5z', refx: 8, id: arrowprefix + 'fw' },
|
||||
bk: { d: 'm10,0l-10,5l10,5l-5,-5l5,-5z', refx: 2, id: arrowprefix + 'bk' }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -88,7 +91,7 @@ export default {
|
||||
* @returns {void}
|
||||
*/
|
||||
function showPanel (on) {
|
||||
$('#arrow_panel').toggle(on);
|
||||
$id('arrow_panel').style.display = (on) ? 'block' : 'none';
|
||||
if (on) {
|
||||
const el = selElems[0];
|
||||
const end = el.getAttribute('marker-end');
|
||||
@@ -112,7 +115,7 @@ export default {
|
||||
val = 'none';
|
||||
}
|
||||
|
||||
$('#arrow_list').val(val);
|
||||
$id('arrow_list').value = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,25 +214,26 @@ export default {
|
||||
*/
|
||||
function colorChanged (elem) {
|
||||
const color = elem.getAttribute('stroke');
|
||||
const mtypes = ['start', 'mid', 'end'];
|
||||
const mtypes = [ 'start', 'mid', 'end' ];
|
||||
const defs = svgCanvas.findDefs();
|
||||
|
||||
$.each(mtypes, function (i, type) {
|
||||
mtypes.forEach(function(type){
|
||||
const marker = getLinked(elem, 'marker-' + type);
|
||||
if (!marker) { return; }
|
||||
|
||||
const curColor = $(marker).children().attr('fill');
|
||||
const curD = $(marker).children().attr('d');
|
||||
const curColor = marker.children.getAttribute('fill');
|
||||
const curD = marker.children.getAttribute('d');
|
||||
if (curColor === color) { return; }
|
||||
|
||||
const allMarkers = $(defs).find('marker');
|
||||
const allMarkers = defs.querySelectorAll('marker');
|
||||
let newMarker = null;
|
||||
// Different color, check if already made
|
||||
allMarkers.each(function () {
|
||||
const attrs = $(this).children().attr(['fill', 'd']);
|
||||
if (attrs.fill === color && attrs.d === curD) {
|
||||
Array.from(allMarkers).forEach(function(marker) {
|
||||
const attrsFill = marker.children.getAttribute('fill');
|
||||
const attrsD = marker.children.getAttribute('d');
|
||||
if (attrsFill === color && attrsD === curD) {
|
||||
// Found another marker with this color and this path
|
||||
newMarker = this;
|
||||
newMarker = marker;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -240,17 +244,17 @@ export default {
|
||||
|
||||
newMarker = addMarker(dir, type, arrowprefix + dir + allMarkers.length);
|
||||
|
||||
$(newMarker).children().attr('fill', color);
|
||||
newMarker.children.setAttribute('fill', color);
|
||||
}
|
||||
|
||||
$(elem).attr('marker-' + type, 'url(#' + newMarker.id + ')');
|
||||
elem.setAttribute('marker-' + type, 'url(#' + newMarker.id + ')');
|
||||
|
||||
// Check if last marker can be removed
|
||||
let remove = true;
|
||||
$(S.svgcontent).find('line, polyline, path, polygon').each(function () {
|
||||
const element = this;
|
||||
$.each(mtypes, function (j, mtype) {
|
||||
if ($(element).attr('marker-' + mtype) === 'url(#' + marker.id + ')') {
|
||||
const sElements = S.svgcontent.querySelectorAll('line, polyline, path, polygon');
|
||||
Array.prototype.forEach.call(sElements, function(element){
|
||||
mtypes.forEach(function(mtype){
|
||||
if (element.getAttribute('marker-' + mtype) === 'url(#' + marker.id + ')') {
|
||||
remove = false;
|
||||
return remove;
|
||||
}
|
||||
@@ -262,7 +266,7 @@ export default {
|
||||
|
||||
// Not found, so can safely remove
|
||||
if (remove) {
|
||||
$(marker).remove();
|
||||
marker.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -285,12 +289,13 @@ export default {
|
||||
return Object.assign(contextTools[i], contextTool);
|
||||
}),
|
||||
callback () {
|
||||
$('#arrow_panel').hide();
|
||||
$id("arrow_panel").style.display = 'none';
|
||||
|
||||
// Set ID so it can be translated in locale file
|
||||
$('#arrow_list option')[0].id = 'connector_no_arrow';
|
||||
$id('arrow_list option').setAttribute('id', 'connector_no_arrow');
|
||||
},
|
||||
async addLangData ({lang, importLocale}) {
|
||||
const {langList} = await importLocale();
|
||||
async addLangData ({ _lang, importLocale }) {
|
||||
const { langList } = await importLocale();
|
||||
return {
|
||||
data: langList
|
||||
};
|
||||
@@ -299,7 +304,7 @@ export default {
|
||||
// Use this to update the current selected elements
|
||||
selElems = opts.elems;
|
||||
|
||||
const markerElems = ['line', 'path', 'polyline', 'polygon'];
|
||||
const markerElems = [ 'line', 'path', 'polyline', 'polygon' ];
|
||||
let i = selElems.length;
|
||||
while (i--) {
|
||||
const elem = selElems[i];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
name: 'Arrows',
|
||||
langList: [
|
||||
{id: 'arrow_none', textContent: 'No arrow'}
|
||||
{ id: 'arrow_none', textContent: 'No arrow' }
|
||||
],
|
||||
contextTools: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
name: 'Arrows',
|
||||
langList: [
|
||||
{id: 'arrow_none', textContent: 'Sans flèche'}
|
||||
{ id: 'arrow_none', textContent: 'Sans flèche' }
|
||||
],
|
||||
contextTools: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
name: '箭头',
|
||||
langList: [
|
||||
{id: 'arrow_none', textContent: '无箭头'}
|
||||
{ id: 'arrow_none', textContent: '无箭头' }
|
||||
],
|
||||
contextTools: [
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
let translationModule;
|
||||
try {
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -23,20 +24,22 @@ const loadExtensionTranslation = async function (lang) {
|
||||
// The button toggles whether the path is open or closed
|
||||
export default {
|
||||
name: 'closepath',
|
||||
async init ({importLocale, $}) {
|
||||
async init ({ _importLocale }) {
|
||||
const svgEditor = this;
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const { svgCanvas } = svgEditor;
|
||||
const { $id } = svgCanvas;
|
||||
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
|
||||
let selElems;
|
||||
const updateButton = function (path) {
|
||||
const seglist = path.pathSegList,
|
||||
closed = seglist.getItem(seglist.numberOfItems - 1).pathSegType === 1,
|
||||
showbutton = closed ? '#tool_openpath' : '#tool_closepath',
|
||||
hidebutton = closed ? '#tool_closepath' : '#tool_openpath';
|
||||
$(hidebutton).hide();
|
||||
$(showbutton).show();
|
||||
showbutton = closed ? 'tool_openpath' : 'tool_closepath',
|
||||
hidebutton = closed ? 'tool_closepath' : 'tool_openpath';
|
||||
$id(hidebutton).style.display = 'none';
|
||||
$id(showbutton).style.display = 'block';
|
||||
};
|
||||
const showPanel = function (on) {
|
||||
$('#closepath_panel').toggle(on);
|
||||
$id('closepath_panel').style.display = (on) ? 'block' : 'none';
|
||||
if (on) {
|
||||
const path = selElems[0];
|
||||
if (path) { updateButton(path); }
|
||||
@@ -89,7 +92,7 @@ export default {
|
||||
return Object.assign(buttons[i], button);
|
||||
}),
|
||||
callback () {
|
||||
$('#closepath_panel').hide();
|
||||
$id("closepath_panel").style.display = 'none';
|
||||
},
|
||||
selectedChanged (opts) {
|
||||
selElems = opts.elems;
|
||||
|
||||
@@ -7,41 +7,44 @@
|
||||
*
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
const name = "connector";
|
||||
|
||||
const loadExtensionTranslation = async function (svgEditor) {
|
||||
let translationModule;
|
||||
const lang = svgEditor.configObj.pref('lang');
|
||||
try {
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${lang}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Missing translation (${lang}) - using 'en'`);
|
||||
console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/en.js`);
|
||||
}
|
||||
return translationModule.default;
|
||||
svgEditor.i18next.addResourceBundle(lang, name, translationModule.default);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'connector',
|
||||
async init (S) {
|
||||
name,
|
||||
async init(S) {
|
||||
const svgEditor = this;
|
||||
const svgCanvas = svgEditor.canvas;
|
||||
const {getElem} = svgCanvas;
|
||||
const {$, svgroot} = S,
|
||||
const { svgCanvas } = svgEditor;
|
||||
const { getElem, $id, mergeDeep } = svgCanvas;
|
||||
const { $, svgroot } = S,
|
||||
addElem = svgCanvas.addSVGElementFromJson,
|
||||
selManager = S.selectorManager,
|
||||
connSel = '.se_connector',
|
||||
// connect_str = '-SE_CONNECT-',
|
||||
elData = $.data;
|
||||
selManager = S.selectorManager;
|
||||
await loadExtensionTranslation(svgEditor);
|
||||
|
||||
let startX,
|
||||
startY,
|
||||
curLine,
|
||||
startElem,
|
||||
endElem,
|
||||
seNs,
|
||||
{svgcontent} = S,
|
||||
started = false,
|
||||
connections = [],
|
||||
selElems = [];
|
||||
let startX;
|
||||
let startY;
|
||||
let curLine;
|
||||
let startElem;
|
||||
let endElem;
|
||||
let seNs;
|
||||
let { svgcontent } = S;
|
||||
let started = false;
|
||||
let connections = [];
|
||||
let selElems = [];
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -51,10 +54,10 @@ export default {
|
||||
* @param {Float} offset
|
||||
* @returns {module:math.XYObject}
|
||||
*/
|
||||
function getBBintersect (x, y, bb, offset) {
|
||||
const getBBintersect = (x, y, bb, offset) => {
|
||||
if (offset) {
|
||||
offset -= 0;
|
||||
bb = $.extend({}, bb);
|
||||
bb = mergeDeep({}, bb);
|
||||
bb.width += offset;
|
||||
bb.height += offset;
|
||||
bb.x -= offset / 2;
|
||||
@@ -81,34 +84,36 @@ export default {
|
||||
x: midX + lenX * ratio,
|
||||
y: midY + lenY * ratio
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {"start"|"end"} side
|
||||
* @param {Element} line
|
||||
* @returns {Float}
|
||||
*/
|
||||
function getOffset (side, line) {
|
||||
const getOffset = (side, line) => {
|
||||
const giveOffset = line.getAttribute('marker-' + side);
|
||||
// const giveOffset = $(line).data(side+'_off');
|
||||
|
||||
// TODO: Make this number (5) be based on marker width/height
|
||||
const size = line.getAttribute('stroke-width') * 5;
|
||||
return giveOffset ? size : 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {boolean} on
|
||||
* @returns {void}
|
||||
*/
|
||||
function showPanel (on) {
|
||||
let connRules = $('#connector_rules');
|
||||
if (!connRules.length) {
|
||||
connRules = $('<style id="connector_rules"></style>').appendTo('head');
|
||||
const showPanel = (on) => {
|
||||
let connRules = $id('connector_rules');
|
||||
if (!connRules) {
|
||||
connRules = document.createElement('style');
|
||||
connRules.setAttribute('id', 'connector_rules');
|
||||
document.getElementsByTagName("head")[0].appendChild(connRules);
|
||||
}
|
||||
connRules.text(!on ? '' : '#tool_clone, #tool_topath, #tool_angle, #xy_panel { display: none !important; }');
|
||||
$('#connector_panel').toggle(on);
|
||||
}
|
||||
connRules.textContent = (!on ? '' : '#tool_clone, #tool_topath, #tool_angle, #xy_panel { display: none !important; }');
|
||||
if ($id('connector_panel'))
|
||||
$id('connector_panel').style.display = (on) ? 'block' : 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element} elem
|
||||
@@ -118,7 +123,7 @@ export default {
|
||||
* @param {boolean} [setMid]
|
||||
* @returns {void}
|
||||
*/
|
||||
function setPoint (elem, pos, x, y, setMid) {
|
||||
const setPoint = (elem, pos, x, y, setMid) => {
|
||||
const pts = elem.points;
|
||||
const pt = svgroot.createSVGPoint();
|
||||
pt.x = x;
|
||||
@@ -144,14 +149,15 @@ export default {
|
||||
const ptEnd = pts.getItem(pts.numberOfItems - 1);
|
||||
setPoint(elem, 1, (ptEnd.x + ptStart.x) / 2, (ptEnd.y + ptStart.y) / 2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Float} diffX
|
||||
* @param {Float} diffY
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateLine (diffX, diffY) {
|
||||
const updateLine = (diffX, diffY) => {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
// Update line with element
|
||||
let i = connections.length;
|
||||
while (i--) {
|
||||
@@ -163,82 +169,79 @@ export default {
|
||||
// const sw = line.getAttribute('stroke-width') * 5;
|
||||
|
||||
// Update bbox for this element
|
||||
const bb = elData(line, pre + '_bb');
|
||||
const bb = dataStorage.get(line, pre + '_bb');
|
||||
bb.x = conn.start_x + diffX;
|
||||
bb.y = conn.start_y + diffY;
|
||||
elData(line, pre + '_bb', bb);
|
||||
dataStorage.put(line, pre + '_bb', bb);
|
||||
|
||||
const altPre = conn.is_start ? 'end' : 'start';
|
||||
|
||||
// Get center pt of connected element
|
||||
const bb2 = elData(line, altPre + '_bb');
|
||||
const bb2 = dataStorage.get(line, altPre + '_bb');
|
||||
const srcX = bb2.x + bb2.width / 2;
|
||||
const srcY = bb2.y + bb2.height / 2;
|
||||
|
||||
// Set point of element being moved
|
||||
const pt = getBBintersect(srcX, srcY, bb, getOffset(pre, line)); // $(line).data(pre+'_off')?sw:0
|
||||
const pt = getBBintersect(srcX, srcY, bb, getOffset(pre, line));
|
||||
setPoint(line, conn.is_start ? 0 : 'end', pt.x, pt.y, true);
|
||||
|
||||
// Set point of connected element
|
||||
const pt2 = getBBintersect(pt.x, pt.y, elData(line, altPre + '_bb'), getOffset(altPre, line));
|
||||
const pt2 = getBBintersect(pt.x, pt.y, dataStorage.get(line, altPre + '_bb'), getOffset(altPre, line));
|
||||
setPoint(line, conn.is_start ? 'end' : 0, pt2.x, pt2.y, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Element[]} [elems=selElems] Array of elements
|
||||
* @returns {void}
|
||||
*/
|
||||
function findConnectors (elems = selElems) {
|
||||
const connectors = $(svgcontent).find(connSel);
|
||||
const findConnectors = (elems = selElems) => {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
// const connectors = svgcontent.querySelectorAll('.se_connector');
|
||||
const connectors = svgcontent.querySelectorAll('.se_connector');
|
||||
connections = [];
|
||||
|
||||
// Loop through connectors to see if one is connected to the element
|
||||
connectors.each(function () {
|
||||
Array.prototype.forEach.call(connectors, function (ethis) {
|
||||
let addThis;
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function add () {
|
||||
if (elems.includes(this)) {
|
||||
// Pretend this element is selected
|
||||
addThis = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Grab the ends
|
||||
const parts = [];
|
||||
['start', 'end'].forEach(function (pos, i) {
|
||||
[ 'start', 'end' ].forEach(function (pos, i) {
|
||||
const key = 'c_' + pos;
|
||||
let part = elData(this, key);
|
||||
let part = dataStorage.get(ethis, key);
|
||||
if (part === null || part === undefined) { // Does this ever return nullish values?
|
||||
part = document.getElementById(
|
||||
this.attributes['se:connector'].value.split(' ')[i]
|
||||
ethis.attributes['se:connector'].value.split(' ')[i]
|
||||
);
|
||||
elData(this, 'c_' + pos, part.id);
|
||||
elData(this, pos + '_bb', svgCanvas.getStrokedBBox([part]));
|
||||
dataStorage.put(ethis, 'c_' + pos, part.id);
|
||||
dataStorage.put(ethis, pos + '_bb', svgCanvas.getStrokedBBox([ part ]));
|
||||
} else part = document.getElementById(part);
|
||||
parts.push(part);
|
||||
}, this);
|
||||
}, ethis);
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const cElem = parts[i];
|
||||
|
||||
addThis = false;
|
||||
// The connected element might be part of a selected group
|
||||
$(cElem).parents().each(add);
|
||||
const parents = svgCanvas.getParents(cElem.parentNode);
|
||||
Array.prototype.forEach.call(parents, function (el) {
|
||||
if (elems.includes(el)) {
|
||||
// Pretend this element is selected
|
||||
addThis = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!cElem || !cElem.parentNode) {
|
||||
$(this).remove();
|
||||
ethis.remove();
|
||||
continue;
|
||||
}
|
||||
if (elems.includes(cElem) || addThis) {
|
||||
const bb = svgCanvas.getStrokedBBox([cElem]);
|
||||
const bb = svgCanvas.getStrokedBBox([ cElem ]);
|
||||
connections.push({
|
||||
elem: cElem,
|
||||
connector: this,
|
||||
connector: ethis,
|
||||
is_start: (i === 0),
|
||||
start_x: bb.x,
|
||||
start_y: bb.y
|
||||
@@ -246,13 +249,14 @@ export default {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element[]} [elems=selElems]
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateConnectors (elems) {
|
||||
const updateConnectors = (elems) => {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
// Updates connector lines based on selected elements
|
||||
// Is not used on mousemove, as it runs getStrokedBBox every time,
|
||||
// which isn't necessary there.
|
||||
@@ -263,22 +267,22 @@ export default {
|
||||
while (i--) {
|
||||
const conn = connections[i];
|
||||
const line = conn.connector;
|
||||
const {elem} = conn;
|
||||
const { elem } = conn;
|
||||
|
||||
// const sw = line.getAttribute('stroke-width') * 5;
|
||||
const pre = conn.is_start ? 'start' : 'end';
|
||||
|
||||
// Update bbox for this element
|
||||
const bb = svgCanvas.getStrokedBBox([elem]);
|
||||
const bb = svgCanvas.getStrokedBBox([ elem ]);
|
||||
bb.x = conn.start_x;
|
||||
bb.y = conn.start_y;
|
||||
elData(line, pre + '_bb', bb);
|
||||
/* const addOffset = */ elData(line, pre + '_off');
|
||||
dataStorage.put(line, pre + '_bb', bb);
|
||||
/* const addOffset = */ dataStorage.get(line, pre + '_off');
|
||||
|
||||
const altPre = conn.is_start ? 'end' : 'start';
|
||||
|
||||
// Get center pt of connected element
|
||||
const bb2 = elData(line, altPre + '_bb');
|
||||
const bb2 = dataStorage.get(line, altPre + '_bb');
|
||||
const srcX = bb2.x + bb2.width / 2;
|
||||
const srcY = bb2.y + bb2.height / 2;
|
||||
|
||||
@@ -287,7 +291,7 @@ export default {
|
||||
setPoint(line, conn.is_start ? 0 : 'end', pt.x, pt.y, true);
|
||||
|
||||
// Set point of connected element
|
||||
const pt2 = getBBintersect(pt.x, pt.y, elData(line, altPre + '_bb'), getOffset(altPre, line));
|
||||
const pt2 = getBBintersect(pt.x, pt.y, dataStorage.get(line, altPre + '_bb'), getOffset(altPre, line));
|
||||
setPoint(line, conn.is_start ? 'end' : 0, pt2.x, pt2.y, true);
|
||||
|
||||
// Update points attribute manually for webkit
|
||||
@@ -303,14 +307,15 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Do once
|
||||
(function () {
|
||||
const gse = svgCanvas.groupSelectedElements;
|
||||
|
||||
svgCanvas.groupSelectedElements = function (...args) {
|
||||
svgCanvas.removeFromSelection($(connSel).toArray());
|
||||
|
||||
svgCanvas.removeFromSelection(document.querySelectorAll('.se_connector'));
|
||||
return gse.apply(this, args);
|
||||
};
|
||||
|
||||
@@ -329,74 +334,74 @@ export default {
|
||||
* Do on reset.
|
||||
* @returns {void}
|
||||
*/
|
||||
function init () {
|
||||
const init = () => {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
// Make sure all connectors have data set
|
||||
$(svgcontent).find('*').each(function () {
|
||||
const conn = this.getAttributeNS(seNs, 'connector');
|
||||
const elements = svgcontent.querySelectorAll('*');
|
||||
elements.forEach(function (curthis) {
|
||||
const conn = curthis.getAttributeNS(seNs, 'connector');
|
||||
if (conn) {
|
||||
this.setAttribute('class', connSel.substr(1));
|
||||
curthis.setAttribute('class', 'se_connector');
|
||||
const connData = conn.split(' ');
|
||||
const sbb = svgCanvas.getStrokedBBox([getElem(connData[0])]);
|
||||
const ebb = svgCanvas.getStrokedBBox([getElem(connData[1])]);
|
||||
$(this).data('c_start', connData[0])
|
||||
.data('c_end', connData[1])
|
||||
.data('start_bb', sbb)
|
||||
.data('end_bb', ebb);
|
||||
const sbb = svgCanvas.getStrokedBBox([ getElem(connData[0]) ]);
|
||||
const ebb = svgCanvas.getStrokedBBox([ getElem(connData[1]) ]);
|
||||
dataStorage.put(curthis, 'c_start', connData[0]);
|
||||
dataStorage.put(curthis, 'c_end', connData[1]);
|
||||
dataStorage.put(curthis, 'start_bb', sbb);
|
||||
dataStorage.put(curthis, 'end_bb', ebb);
|
||||
svgCanvas.getEditorNS(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const buttons = [{
|
||||
id: 'mode_connect',
|
||||
type: 'mode',
|
||||
icon: svgEditor.curConfig.imgPath + 'cut.png',
|
||||
includeWith: {
|
||||
button: '#tool_line',
|
||||
isDefault: false,
|
||||
position: 1
|
||||
},
|
||||
events: {
|
||||
click () {
|
||||
svgCanvas.setMode('connector');
|
||||
}
|
||||
}
|
||||
}];
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
return {
|
||||
name: strings.name,
|
||||
svgicons: 'conn.svg',
|
||||
buttons: strings.buttons.map((button, i) => {
|
||||
return Object.assign(buttons[i], button);
|
||||
}),
|
||||
/* async */ addLangData ({lang}) { // , importLocale: importLoc
|
||||
/** @todo JFH special flag */
|
||||
newUI: true,
|
||||
name: svgEditor.i18next.t(`${name}:name`),
|
||||
callback() {
|
||||
const btitle = svgEditor.i18next.t(`${name}:langListTitle`);
|
||||
// Add the button and its handler(s)
|
||||
const buttonTemplate = document.createElement("template");
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
buttonTemplate.innerHTML = `
|
||||
<se-button id="mode_connect" title="${btitle}" src="./images/conn.svg"></se-button>
|
||||
`;
|
||||
$id('tools_left').append(buttonTemplate.content.cloneNode(true));
|
||||
$id('mode_connect').addEventListener("click", () => {
|
||||
svgCanvas.setMode('connector');
|
||||
});
|
||||
},
|
||||
/* async */ addLangData({ _lang }) { // , importLocale: importLoc
|
||||
return {
|
||||
data: strings.langList
|
||||
data: [
|
||||
{ id: 'mode_connect', title: svgEditor.i18next.t(`${name}:langListTitle`) }
|
||||
]
|
||||
};
|
||||
},
|
||||
mouseDown (opts) {
|
||||
mouseDown(opts) {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
const e = opts.event;
|
||||
startX = opts.start_x;
|
||||
startY = opts.start_y;
|
||||
const mode = svgCanvas.getMode();
|
||||
const {curConfig: {initStroke}} = svgEditor;
|
||||
const { curConfig: { initStroke } } = svgEditor.configObj;
|
||||
|
||||
if (mode === 'connector') {
|
||||
if (started) { return undefined; }
|
||||
|
||||
const mouseTarget = e.target;
|
||||
|
||||
const parents = $(mouseTarget).parents();
|
||||
const parents = svgCanvas.getParents(mouseTarget.parentNode);
|
||||
|
||||
if ($.inArray(svgcontent, parents) !== -1) {
|
||||
// Connectable element
|
||||
|
||||
// If child of foreignObject, use parent
|
||||
const fo = $(mouseTarget).closest('foreignObject');
|
||||
startElem = fo.length ? fo[0] : mouseTarget;
|
||||
const fo = svgCanvas.getClosest(mouseTarget.parentNode, 'foreignObject');
|
||||
startElem = fo ? fo : mouseTarget;
|
||||
|
||||
// Get center of source element
|
||||
const bb = svgCanvas.getStrokedBBox([startElem]);
|
||||
const bb = svgCanvas.getStrokedBBox([ startElem ]);
|
||||
const x = bb.x + bb.width / 2;
|
||||
const y = bb.y + bb.height / 2;
|
||||
|
||||
@@ -415,7 +420,7 @@ export default {
|
||||
style: 'pointer-events:none'
|
||||
}
|
||||
});
|
||||
elData(curLine, 'start_bb', bb);
|
||||
dataStorage.put(curLine, 'start_bb', bb);
|
||||
}
|
||||
return {
|
||||
started: true
|
||||
@@ -426,7 +431,8 @@ export default {
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
mouseMove (opts) {
|
||||
mouseMove(opts) {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
const zoom = svgCanvas.getZoom();
|
||||
// const e = opts.event;
|
||||
const x = opts.mouse_x / zoom;
|
||||
@@ -440,7 +446,7 @@ export default {
|
||||
if (mode === 'connector' && started) {
|
||||
// const sw = curLine.getAttribute('stroke-width') * 3;
|
||||
// Set start point (adjusts based on bb)
|
||||
const pt = getBBintersect(x, y, elData(curLine, 'start_bb'), getOffset('start', curLine));
|
||||
const pt = getBBintersect(x, y, dataStorage.get(curLine, 'start_bb'), getOffset('start', curLine));
|
||||
startX = pt.x;
|
||||
startY = pt.y;
|
||||
|
||||
@@ -453,9 +459,9 @@ export default {
|
||||
while (slen--) {
|
||||
const elem = selElems[slen];
|
||||
// Look for selected connector elements
|
||||
if (elem && elData(elem, 'c_start')) {
|
||||
if (elem && dataStorage.has(elem, 'c_start')) {
|
||||
// Remove the "translate" transform given to move
|
||||
svgCanvas.removeFromSelection([elem]);
|
||||
svgCanvas.removeFromSelection([ elem ]);
|
||||
svgCanvas.getTransformList(elem).clear();
|
||||
}
|
||||
}
|
||||
@@ -464,7 +470,8 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mouseUp (opts) {
|
||||
mouseUp(opts) {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
// const zoom = svgCanvas.getZoom();
|
||||
const e = opts.event;
|
||||
// , x = opts.mouse_x / zoom,
|
||||
@@ -474,10 +481,10 @@ export default {
|
||||
if (svgCanvas.getMode() !== 'connector') {
|
||||
return undefined;
|
||||
}
|
||||
const fo = $(mouseTarget).closest('foreignObject');
|
||||
if (fo.length) { mouseTarget = fo[0]; }
|
||||
const fo = svgCanvas.getClosest(mouseTarget.parentNode, 'foreignObject');
|
||||
if (fo) { mouseTarget = fo; }
|
||||
|
||||
const parents = $(mouseTarget).parents();
|
||||
const parents = svgCanvas.getParents(mouseTarget.parentNode);
|
||||
|
||||
if (mouseTarget === startElem) {
|
||||
// Start line through click
|
||||
@@ -488,9 +495,10 @@ export default {
|
||||
started
|
||||
};
|
||||
}
|
||||
if ($.inArray(svgcontent, parents) === -1) {
|
||||
if (parents.indexOf(svgcontent) === -1) {
|
||||
// Not a valid target element, so remove line
|
||||
$(curLine).remove();
|
||||
if (curLine)
|
||||
curLine.remove();
|
||||
started = false;
|
||||
return {
|
||||
keep: false,
|
||||
@@ -501,17 +509,18 @@ export default {
|
||||
// Valid end element
|
||||
endElem = mouseTarget;
|
||||
|
||||
const startId = startElem.id, endId = endElem.id;
|
||||
const startId = (startElem) ? startElem.id : '';
|
||||
const endId = (endElem) ? endElem.id : '';
|
||||
const connStr = startId + ' ' + endId;
|
||||
const altStr = endId + ' ' + startId;
|
||||
// Don't create connector if one already exists
|
||||
const dupe = $(svgcontent).find(connSel).filter(function () {
|
||||
const conn = this.getAttributeNS(seNs, 'connector');
|
||||
const dupe = Array.prototype.filter.call(svgcontent.querySelectorAll('.se_connector'), function (aThis) {
|
||||
const conn = aThis.getAttributeNS(seNs, 'connector');
|
||||
if (conn === connStr || conn === altStr) { return true; }
|
||||
return false;
|
||||
});
|
||||
if (dupe.length) {
|
||||
$(curLine).remove();
|
||||
curLine.remove();
|
||||
return {
|
||||
keep: false,
|
||||
element: null,
|
||||
@@ -519,19 +528,18 @@ export default {
|
||||
};
|
||||
}
|
||||
|
||||
const bb = svgCanvas.getStrokedBBox([endElem]);
|
||||
const bb = svgCanvas.getStrokedBBox([ endElem ]);
|
||||
|
||||
const pt = getBBintersect(startX, startY, bb, getOffset('start', curLine));
|
||||
setPoint(curLine, 'end', pt.x, pt.y, true);
|
||||
$(curLine)
|
||||
.data('c_start', startId)
|
||||
.data('c_end', endId)
|
||||
.data('end_bb', bb);
|
||||
dataStorage.put(curLine, 'c_start', startId);
|
||||
dataStorage.put(curLine, 'c_end', endId);
|
||||
dataStorage.put(curLine, 'end_bb', bb);
|
||||
seNs = svgCanvas.getEditorNS(true);
|
||||
curLine.setAttributeNS(seNs, 'se:connector', connStr);
|
||||
curLine.setAttribute('class', connSel.substr(1));
|
||||
curLine.setAttribute('class', 'se_connector');
|
||||
curLine.setAttribute('opacity', 1);
|
||||
svgCanvas.addToSelection([curLine]);
|
||||
svgCanvas.addToSelection([ curLine ]);
|
||||
svgCanvas.moveToBottomSelectedElement();
|
||||
selManager.requestSelector(curLine).showGrips(false);
|
||||
started = false;
|
||||
@@ -541,9 +549,10 @@ export default {
|
||||
started
|
||||
};
|
||||
},
|
||||
selectedChanged (opts) {
|
||||
selectedChanged(opts) {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
// TODO: Find better way to skip operations if no connectors are in use
|
||||
if (!$(svgcontent).find(connSel).length) { return; }
|
||||
if (!svgcontent.querySelectorAll('.se_connector').length) { return; }
|
||||
|
||||
if (svgCanvas.getMode() === 'connector') {
|
||||
svgCanvas.setMode('select');
|
||||
@@ -555,7 +564,7 @@ export default {
|
||||
let i = selElems.length;
|
||||
while (i--) {
|
||||
const elem = selElems[i];
|
||||
if (elem && elData(elem, 'c_start')) {
|
||||
if (elem && dataStorage.has(elem, 'c_start')) {
|
||||
selManager.requestSelector(elem).showGrips(false);
|
||||
if (opts.selectedElement && !opts.multiselected) {
|
||||
// TODO: Set up context tools and hide most regular line tools
|
||||
@@ -569,7 +578,8 @@ export default {
|
||||
}
|
||||
updateConnectors();
|
||||
},
|
||||
elementChanged (opts) {
|
||||
elementChanged(opts) {
|
||||
const dataStorage = svgCanvas.getDataStorage();
|
||||
let elem = opts.elems[0];
|
||||
if (!elem) return;
|
||||
if (elem.tagName === 'svg' && elem.id === 'svgcontent') {
|
||||
@@ -588,9 +598,8 @@ export default {
|
||||
const mid = elem.getAttribute('marker-mid');
|
||||
const end = elem.getAttribute('marker-end');
|
||||
curLine = elem;
|
||||
$(elem)
|
||||
.data('start_off', Boolean(start))
|
||||
.data('end_off', Boolean(end));
|
||||
dataStorage.put(elem, 'start_off', Boolean(start));
|
||||
dataStorage.put(elem, 'end_off', Boolean(end));
|
||||
|
||||
if (elem.tagName === 'line' && mid) {
|
||||
// Convert to polyline to accept mid-arrow
|
||||
@@ -599,7 +608,7 @@ export default {
|
||||
const x2 = Number(elem.getAttribute('x2'));
|
||||
const y1 = Number(elem.getAttribute('y1'));
|
||||
const y2 = Number(elem.getAttribute('y2'));
|
||||
const {id} = elem;
|
||||
const { id } = elem;
|
||||
|
||||
const midPt = (' ' + ((x1 + x2) / 2) + ',' + ((y1 + y2) / 2) + ' ');
|
||||
const pline = addElem({
|
||||
@@ -613,22 +622,23 @@ export default {
|
||||
opacity: elem.getAttribute('opacity') || 1
|
||||
}
|
||||
});
|
||||
$(elem).after(pline).remove();
|
||||
elem.insertAdjacentElement('afterend', pline);
|
||||
elem.remove();
|
||||
svgCanvas.clearSelection();
|
||||
pline.id = id;
|
||||
svgCanvas.addToSelection([pline]);
|
||||
svgCanvas.addToSelection([ pline ]);
|
||||
elem = pline;
|
||||
}
|
||||
}
|
||||
// Update line if it's a connector
|
||||
if (elem.getAttribute('class') === connSel.substr(1)) {
|
||||
const start = getElem(elData(elem, 'c_start'));
|
||||
updateConnectors([start]);
|
||||
if (elem.getAttribute('class') === 'se_connector') {
|
||||
const start = getElem(dataStorage.get(elem, 'c_start'));
|
||||
updateConnectors([ start ]);
|
||||
} else {
|
||||
updateConnectors();
|
||||
}
|
||||
},
|
||||
IDsUpdated (input) {
|
||||
IDsUpdated(input) {
|
||||
const remove = [];
|
||||
input.elems.forEach(function (elem) {
|
||||
if ('se:connector' in elem.attr) {
|
||||
@@ -642,16 +652,14 @@ export default {
|
||||
}
|
||||
}
|
||||
});
|
||||
return {remove};
|
||||
return { remove };
|
||||
},
|
||||
toolButtonStateUpdate (opts) {
|
||||
if (opts.nostroke) {
|
||||
if ($('#mode_connect').hasClass('tool_button_current')) {
|
||||
svgEditor.clickSelect();
|
||||
}
|
||||
toolButtonStateUpdate(opts) {
|
||||
const button = document.getElementById('mode_connect');
|
||||
if (opts.nostroke && button.pressed === true) {
|
||||
svgEditor.clickSelect();
|
||||
}
|
||||
$('#mode_connect')
|
||||
.toggleClass('disabled', opts.nostroke);
|
||||
button.disabled = opts.nostroke;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export default {
|
||||
name: 'Connector',
|
||||
langListTitle: 'Connect two objects',
|
||||
langList: [
|
||||
{id: 'mode_connect', title: 'Connect two objects'}
|
||||
{ id: 'mode_connect', title: 'Connect two objects' }
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export default {
|
||||
name: 'Connector',
|
||||
langListTitle: 'Connecter deux objets',
|
||||
langList: [
|
||||
{id: 'mode_connect', title: 'Connecter deux objets'}
|
||||
{ id: 'mode_connect', title: 'Connecter deux objets' }
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export default {
|
||||
name: '连接器',
|
||||
langListTitle: '连接两个对象',
|
||||
langList: [
|
||||
{id: 'mode_connect', title: '连接两个对象'}
|
||||
{ id: 'mode_connect', title: '连接两个对象' }
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
|
||||
@@ -4,29 +4,35 @@
|
||||
* @license MIT
|
||||
*
|
||||
* @copyright 2010 Jeff Schiller
|
||||
* @copyright 2021 OptimistikSAS
|
||||
*
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
const name = "eyedropper";
|
||||
|
||||
const loadExtensionTranslation = async function (svgEditor) {
|
||||
let translationModule;
|
||||
const lang = svgEditor.configObj.pref('lang');
|
||||
try {
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${lang}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Missing translation (${lang}) - using 'en'`);
|
||||
console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/en.js`);
|
||||
}
|
||||
return translationModule.default;
|
||||
svgEditor.i18next.addResourceBundle(lang, name, translationModule.default);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'eyedropper',
|
||||
async init (S) {
|
||||
name,
|
||||
async init(S) {
|
||||
const svgEditor = this;
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const {$, ChangeElementCommand} = S, // , svgcontent,
|
||||
await loadExtensionTranslation(svgEditor);
|
||||
const { ChangeElementCommand } = S, // , svgcontent,
|
||||
// svgdoc = S.svgroot.parentNode.ownerDocument,
|
||||
svgCanvas = svgEditor.canvas,
|
||||
{ svgCanvas } = svgEditor,
|
||||
addToHistory = function (cmd) { svgCanvas.undoMgr.addCommandToHistory(cmd); },
|
||||
currentStyle = {
|
||||
fillPaint: 'red', fillOpacity: 1.0,
|
||||
@@ -36,25 +42,26 @@ export default {
|
||||
strokeLinecap: 'butt',
|
||||
strokeLinejoin: 'miter'
|
||||
};
|
||||
const { $id } = svgCanvas;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementChanged} opts
|
||||
* @returns {void}
|
||||
*/
|
||||
function getStyle (opts) {
|
||||
const getStyle = (opts) => {
|
||||
// if we are in eyedropper mode, we don't want to disable the eye-dropper tool
|
||||
const mode = svgCanvas.getMode();
|
||||
if (mode === 'eyedropper') { return; }
|
||||
|
||||
const tool = $('#tool_eyedropper');
|
||||
const tool = $id('tool_eyedropper');
|
||||
// enable-eye-dropper if one element is selected
|
||||
let elem = null;
|
||||
if (!opts.multiselected && opts.elems[0] &&
|
||||
!['svg', 'g', 'use'].includes(opts.elems[0].nodeName)
|
||||
![ 'svg', 'g', 'use' ].includes(opts.elems[0].nodeName)
|
||||
) {
|
||||
elem = opts.elems[0];
|
||||
tool.removeClass('disabled');
|
||||
tool.classList.remove('disabled');
|
||||
// grab the current style
|
||||
currentStyle.fillPaint = elem.getAttribute('fill') || 'black';
|
||||
currentStyle.fillOpacity = elem.getAttribute('fill-opacity') || 1.0;
|
||||
@@ -65,42 +72,37 @@ export default {
|
||||
currentStyle.strokeLinecap = elem.getAttribute('stroke-linecap');
|
||||
currentStyle.strokeLinejoin = elem.getAttribute('stroke-linejoin');
|
||||
currentStyle.opacity = elem.getAttribute('opacity') || 1.0;
|
||||
// disable eye-dropper tool
|
||||
// disable eye-dropper tool
|
||||
} else {
|
||||
tool.addClass('disabled');
|
||||
tool.classList.add('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
id: 'tool_eyedropper',
|
||||
icon: 'eyedropper.png',
|
||||
type: 'mode',
|
||||
events: {
|
||||
click () {
|
||||
svgCanvas.setMode('eyedropper');
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
return {
|
||||
name: strings.name,
|
||||
svgicons: 'eyedropper-icon.xml',
|
||||
buttons: strings.buttons.map((button, i) => {
|
||||
return Object.assign(buttons[i], button);
|
||||
}),
|
||||
|
||||
name: svgEditor.i18next.t(`${name}:name`),
|
||||
callback() {
|
||||
// Add the button and its handler(s)
|
||||
const buttonTemplate = document.createElement("template");
|
||||
const title = svgEditor.i18next.t(`${name}:buttons.0.title`);
|
||||
const key = svgEditor.i18next.t(`${name}:buttons.0.key`);
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
buttonTemplate.innerHTML = `
|
||||
<se-button id="tool_eyedropper" title="${title}" src="./images/eye_dropper.svg" shortcut=${key}></se-button>
|
||||
`;
|
||||
$id('tools_left').append(buttonTemplate.content.cloneNode(true));
|
||||
$id('tool_eyedropper').addEventListener("click", () => {
|
||||
svgCanvas.setMode('eyedropper');
|
||||
});
|
||||
},
|
||||
// if we have selected an element, grab its paint and enable the eye dropper button
|
||||
selectedChanged: getStyle,
|
||||
elementChanged: getStyle,
|
||||
|
||||
mouseDown (opts) {
|
||||
mouseDown(opts) {
|
||||
const mode = svgCanvas.getMode();
|
||||
if (mode === 'eyedropper') {
|
||||
const e = opts.event;
|
||||
const {target} = e;
|
||||
if (!['svg', 'g', 'use'].includes(target.nodeName)) {
|
||||
const { target } = e;
|
||||
if (![ 'svg', 'g', 'use' ].includes(target.nodeName)) {
|
||||
const changes = {};
|
||||
|
||||
const change = function (elem, attrname, newvalue) {
|
||||
|
||||
9
src/editor/extensions/ext-eyedropper/locale/fr.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
name: 'pipette',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Outil pipette',
|
||||
key: 'I'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
/* globals seConfirm */
|
||||
/**
|
||||
* @file ext-foreignobject.js
|
||||
*
|
||||
@@ -6,10 +7,10 @@
|
||||
* @copyright 2010 Jacques Distler, 2010 Alexis Deveria
|
||||
*
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
let translationModule;
|
||||
try {
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -23,19 +24,20 @@ export default {
|
||||
name: 'foreignobject',
|
||||
async init (S) {
|
||||
const svgEditor = this;
|
||||
const {$, text2xml, NS} = S;
|
||||
const svgCanvas = svgEditor.canvas;
|
||||
const { text2xml, NS } = S;
|
||||
const { svgCanvas } = svgEditor;
|
||||
const { $id } = svgCanvas;
|
||||
const
|
||||
// {svgcontent} = S,
|
||||
// addElem = svgCanvas.addSVGElementFromJson,
|
||||
svgdoc = S.svgroot.parentNode.ownerDocument;
|
||||
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
|
||||
|
||||
const properlySourceSizeTextArea = function () {
|
||||
// TODO: remove magic numbers here and get values from CSS
|
||||
const height = $('#svg_source_container').height() - 80;
|
||||
$('#svg_source_textarea').css('height', height);
|
||||
const height = parseFloat(getComputedStyle($id('svg_source_container'), null).height.replace("px", "")) - 80;
|
||||
$id('svg_source_textarea').style.height = height + "px";
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -43,12 +45,14 @@ export default {
|
||||
* @returns {void}
|
||||
*/
|
||||
function showPanel (on) {
|
||||
let fcRules = $('#fc_rules');
|
||||
if (!fcRules.length) {
|
||||
fcRules = $('<style id="fc_rules"></style>').appendTo('head');
|
||||
let fcRules = $id('fc_rules');
|
||||
if (!fcRules) {
|
||||
fcRules = document.createElement('style');
|
||||
fcRules.setAttribute('id', 'fc_rules');
|
||||
document.getElementsByTagName("head")[0].appendChild(fcRules);
|
||||
}
|
||||
fcRules.text(!on ? '' : ' #tool_topath { display: none !important; }');
|
||||
$('#foreignObject_panel').toggle(on);
|
||||
fcRules.textContent = !on ? '' : ' #tool_topath { display: none !important; }';
|
||||
$id('foreignObject_panel').style.display = (on) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,8 +60,10 @@ export default {
|
||||
* @returns {void}
|
||||
*/
|
||||
function toggleSourceButtons (on) {
|
||||
$('#tool_source_save, #tool_source_cancel').toggle(!on);
|
||||
$('#foreign_save, #foreign_cancel').toggle(on);
|
||||
$id('tool_source_save').style.display = (!on) ? 'block' : 'none';
|
||||
$id('tool_source_cancel').style.display = (!on) ? 'block' : 'none';
|
||||
$id('foreign_save').style.display = (on) ? 'block' : 'none';
|
||||
$id('foreign_cancel').style.display = (on) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
let selElems,
|
||||
@@ -74,15 +80,16 @@ export default {
|
||||
const elt = selElems[0]; // The parent `Element` to append to
|
||||
try {
|
||||
// convert string into XML document
|
||||
const newDoc = text2xml('<svg xmlns="' + NS.SVG + '" xmlns:xlink="' + NS.XLINK + '">' + xmlString + '</svg>');
|
||||
const oi = (xmlString.indexOf('xmlns:oi') !== -1) ? ' xmlns:oi="' + NS.OI + '"' : '';
|
||||
const newDoc = text2xml('<svg xmlns="' + NS.SVG + '" xmlns:xlink="' + NS.XLINK + '" '+ oi +'>' + xmlString + '</svg>');
|
||||
// run it through our sanitizer to remove anything we do not support
|
||||
svgCanvas.sanitizeSvg(newDoc.documentElement);
|
||||
elt.replaceWith(svgdoc.importNode(newDoc.documentElement.firstChild, true));
|
||||
svgCanvas.call('changed', [elt]);
|
||||
svgCanvas.call('changed', [ elt ]);
|
||||
svgCanvas.clearSelection();
|
||||
} catch (e) {
|
||||
// Todo: Surface error to user
|
||||
console.log(e); // eslint-disable-line no-console
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -101,10 +108,10 @@ export default {
|
||||
elt.removeAttribute('fill');
|
||||
|
||||
const str = svgCanvas.svgToString(elt, 0);
|
||||
$('#svg_source_textarea').val(str);
|
||||
$('#svg_source_editor').fadeIn();
|
||||
$id('svg_source_textarea').value = str;
|
||||
$id('#svg_source_editor').style.display = 'block';
|
||||
properlySourceSizeTextArea();
|
||||
$('#svg_source_textarea').focus();
|
||||
$id('svg_source_textarea').focus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +124,7 @@ export default {
|
||||
svgCanvas.call('changed', selElems);
|
||||
}
|
||||
|
||||
const buttons = [{
|
||||
const buttons = [ {
|
||||
id: 'tool_foreign',
|
||||
icon: 'foreignobject-tool.png',
|
||||
type: 'mode',
|
||||
@@ -136,7 +143,7 @@ export default {
|
||||
showForeignEditor();
|
||||
}
|
||||
}
|
||||
}];
|
||||
} ];
|
||||
|
||||
const contextTools = [
|
||||
{
|
||||
@@ -182,38 +189,50 @@ export default {
|
||||
return Object.assign(contextTools[i], contextTool);
|
||||
}),
|
||||
callback () {
|
||||
$('#foreignObject_panel').hide();
|
||||
$id("foreignObject_panel").style.display = 'none';
|
||||
|
||||
const endChanges = function () {
|
||||
$('#svg_source_editor').hide();
|
||||
$id("svg_source_editor").style.display = 'none';
|
||||
editingforeign = false;
|
||||
$('#svg_source_textarea').blur();
|
||||
$id('svg_source_textarea').blur();
|
||||
toggleSourceButtons(false);
|
||||
};
|
||||
|
||||
// TODO: Needs to be done after orig icon loads
|
||||
setTimeout(function () {
|
||||
// Create source save/cancel buttons
|
||||
/* const save = */ $('#tool_source_save').clone()
|
||||
.hide().attr('id', 'foreign_save').unbind()
|
||||
.appendTo('#tool_source_back').click(async function () {
|
||||
if (!editingforeign) { return; }
|
||||
const toolSourceSave = $id('tool_source_save').cloneNode(true);
|
||||
toolSourceSave.style.display = 'none';
|
||||
toolSourceSave.id = 'foreign_save';
|
||||
// unbind()
|
||||
// const oldElement = $id('tool_source_save');
|
||||
// oldElement.parentNode.replaceChild(toolSourceSave, oldElement);
|
||||
$id('tool_source_back').append(toolSourceSave);
|
||||
toolSourceSave.addEventListener('click', () => function () {
|
||||
if (!editingforeign) { return; }
|
||||
|
||||
if (!setForeignString($('#svg_source_textarea').val())) {
|
||||
const ok = await $.confirm('Errors found. Revert to original?');
|
||||
if (!ok) { return; }
|
||||
endChanges();
|
||||
} else {
|
||||
endChanges();
|
||||
}
|
||||
// setSelectMode();
|
||||
});
|
||||
|
||||
/* const cancel = */ $('#tool_source_cancel').clone()
|
||||
.hide().attr('id', 'foreign_cancel').unbind()
|
||||
.appendTo('#tool_source_back').click(function () {
|
||||
if (!setForeignString($id('svg_source_textarea').value)) {
|
||||
const ok = seConfirm('Errors found. Revert to original?');
|
||||
if (!ok) { return; }
|
||||
endChanges();
|
||||
});
|
||||
} else {
|
||||
endChanges();
|
||||
}
|
||||
// setSelectMode();
|
||||
});
|
||||
|
||||
var oldToolSourceCancel = $id('tool_source_cancel');
|
||||
const toolSourceCancel = oldToolSourceCancel.cloneNode(true);
|
||||
toolSourceCancel.style.display = 'none';
|
||||
toolSourceCancel.id = 'foreign_cancel';
|
||||
$id('tool_source_back').append(toolSourceCancel);
|
||||
toolSourceCancel.addEventListener('click', () => function () {
|
||||
endChanges();
|
||||
});
|
||||
// unbind()
|
||||
// var oldToolSourceCancel = $id('tool_source_cancel');
|
||||
// oldToolSourceCancel.parentNode.replaceChild(toolSourceCancel, oldToolSourceCancel);
|
||||
|
||||
}, 3000);
|
||||
},
|
||||
mouseDown (opts) {
|
||||
@@ -250,14 +269,17 @@ export default {
|
||||
started: true
|
||||
};
|
||||
},
|
||||
mouseUp (opts) {
|
||||
mouseUp (_opts) {
|
||||
// const e = opts.event;
|
||||
if (svgCanvas.getMode() !== 'foreign' || !started) {
|
||||
return undefined;
|
||||
}
|
||||
const attrs = $(newFO).attr(['width', 'height']);
|
||||
const attrs = {
|
||||
width: newFO.getAttribute('width'),
|
||||
height: newFO.getAttribute('height'),
|
||||
};
|
||||
const keep = (attrs.width !== '0' || attrs.height !== '0');
|
||||
svgCanvas.addToSelection([newFO], true);
|
||||
svgCanvas.addToSelection([ newFO ], true);
|
||||
|
||||
return {
|
||||
keep,
|
||||
@@ -273,9 +295,9 @@ export default {
|
||||
const elem = selElems[i];
|
||||
if (elem && elem.tagName === 'foreignObject') {
|
||||
if (opts.selectedElement && !opts.multiselected) {
|
||||
$('#foreign_font_size').val(elem.getAttribute('font-size'));
|
||||
$('#foreign_width').val(elem.getAttribute('width'));
|
||||
$('#foreign_height').val(elem.getAttribute('height'));
|
||||
$id('foreign_font_size').value = elem.getAttribute('font-size');
|
||||
$id('foreign_width').value = elem.getAttribute('width');
|
||||
$id('foreign_height').value = elem.getAttribute('height');
|
||||
showPanel(true);
|
||||
} else {
|
||||
showPanel(false);
|
||||
@@ -285,7 +307,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
elementChanged (opts) {
|
||||
elementChanged (_opts) {
|
||||
// const elem = opts.elems[0];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,33 +7,40 @@
|
||||
*
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
const name = "grid";
|
||||
|
||||
const loadExtensionTranslation = async function (svgEditor) {
|
||||
let translationModule;
|
||||
const lang = svgEditor.configObj.pref('lang');
|
||||
try {
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${lang}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Missing translation (${lang}) - using 'en'`);
|
||||
console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/en.js`);
|
||||
}
|
||||
return translationModule.default;
|
||||
svgEditor.i18next.addResourceBundle(lang, name, translationModule.default);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'grid',
|
||||
async init ({$, NS, getTypeMap}) {
|
||||
name,
|
||||
async init ({ NS, getTypeMap }) {
|
||||
const svgEditor = this;
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const svgCanvas = svgEditor.canvas;
|
||||
const svgdoc = document.getElementById('svgcanvas').ownerDocument,
|
||||
{assignAttributes} = svgCanvas,
|
||||
hcanvas = document.createElement('canvas'),
|
||||
canvBG = $('#canvasBackground'),
|
||||
units = getTypeMap(), // Assumes prior `init()` call on `units.js` module
|
||||
intervals = [0.01, 0.1, 1, 10, 100, 1000];
|
||||
let showGrid = svgEditor.curConfig.showGrid || false;
|
||||
await loadExtensionTranslation(svgEditor);
|
||||
const { svgCanvas } = svgEditor;
|
||||
const { $id } = svgCanvas;
|
||||
const svgdoc = document.getElementById('svgcanvas').ownerDocument;
|
||||
const { assignAttributes } = svgCanvas;
|
||||
const hcanvas = document.createElement('canvas');
|
||||
const canvBG = $id('canvasBackground');
|
||||
const units = getTypeMap(); // Assumes prior `init()` call on `units.js` module
|
||||
const intervals = [ 0.01, 0.1, 1, 10, 100, 1000 ];
|
||||
let showGrid = svgEditor.configObj.curConfig.showGrid || false;
|
||||
|
||||
$(hcanvas).hide().appendTo('body');
|
||||
hcanvas.style.display = 'none';
|
||||
document.body.appendChild(hcanvas);
|
||||
|
||||
const canvasGrid = svgdoc.createElementNS(NS.SVG, 'svg');
|
||||
assignAttributes(canvasGrid, {
|
||||
@@ -45,7 +52,7 @@ export default {
|
||||
overflow: 'visible',
|
||||
display: 'none'
|
||||
});
|
||||
canvBG.append(canvasGrid);
|
||||
canvBG.appendChild(canvasGrid);
|
||||
const gridDefs = svgdoc.createElementNS(NS.SVG, 'defs');
|
||||
// grid-pattern
|
||||
const gridPattern = svgdoc.createElementNS(NS.SVG, 'pattern');
|
||||
@@ -67,7 +74,7 @@ export default {
|
||||
});
|
||||
gridPattern.append(gridimg);
|
||||
gridDefs.append(gridPattern);
|
||||
$('#canvasGrid').append(gridDefs);
|
||||
$id('canvasGrid').appendChild(gridDefs);
|
||||
|
||||
// grid-box
|
||||
const gridBox = svgdoc.createElementNS(NS.SVG, 'rect');
|
||||
@@ -81,16 +88,16 @@ export default {
|
||||
fill: 'url(#gridpattern)',
|
||||
style: 'pointer-events: none; display:visible;'
|
||||
});
|
||||
$('#canvasGrid').append(gridBox);
|
||||
$id('canvasGrid').appendChild(gridBox);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Float} zoom
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateGrid (zoom) {
|
||||
const updateGrid = (zoom) => {
|
||||
// TODO: Try this with <line> elements, then compare performance difference
|
||||
const unit = units[svgEditor.curConfig.baseUnit]; // 1 = 1px
|
||||
const unit = units[svgEditor.configObj.curConfig.baseUnit]; // 1 = 1px
|
||||
const uMulti = unit * zoom;
|
||||
// Calculate the main number interval
|
||||
const rawM = 100 / uMulti;
|
||||
@@ -109,7 +116,7 @@ export default {
|
||||
const part = bigInt / 10;
|
||||
|
||||
ctx.globalAlpha = 0.2;
|
||||
ctx.strokeStyle = svgEditor.curConfig.gridColor;
|
||||
ctx.strokeStyle = svgEditor.configObj.curConfig.gridColor;
|
||||
for (let i = 1; i < 10; i++) {
|
||||
const subD = Math.round(part * i) + 0.5;
|
||||
// const lineNum = (i % 2)?12:10;
|
||||
@@ -135,46 +142,43 @@ export default {
|
||||
gridimg.parentNode.setAttribute('width', bigInt);
|
||||
gridimg.parentNode.setAttribute('height', bigInt);
|
||||
svgCanvas.setHref(gridimg, datauri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function gridUpdate () {
|
||||
const gridUpdate = () => {
|
||||
if (showGrid) {
|
||||
updateGrid(svgCanvas.getZoom());
|
||||
}
|
||||
$('#canvasGrid').toggle(showGrid);
|
||||
$('#view_grid').toggleClass('push_button_pressed tool_button');
|
||||
}
|
||||
const buttons = [{
|
||||
id: 'view_grid',
|
||||
icon: 'grid.png',
|
||||
type: 'context',
|
||||
panel: 'editor_panel',
|
||||
events: {
|
||||
click () {
|
||||
svgEditor.curConfig.showGrid = showGrid = !showGrid;
|
||||
gridUpdate();
|
||||
}
|
||||
}
|
||||
}];
|
||||
$id('canvasGrid').style.display = (showGrid) ? 'block' : 'none';
|
||||
document.getElementById('view_grid').pressed = showGrid;
|
||||
};
|
||||
return {
|
||||
name: strings.name,
|
||||
svgicons: 'grid-icon.xml',
|
||||
|
||||
name: svgEditor.i18next.t(`${name}:name`),
|
||||
zoomChanged (zoom) {
|
||||
if (showGrid) { updateGrid(zoom); }
|
||||
},
|
||||
callback () {
|
||||
// Add the button and its handler(s)
|
||||
const buttonTemplate = document.createElement("template");
|
||||
const title = svgEditor.i18next.t(`${name}:buttons.0.title`);
|
||||
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
buttonTemplate.innerHTML = `
|
||||
<se-button id="view_grid" title="${title}" src="./images/grid.svg"></se-button>
|
||||
`;
|
||||
$id('editor_panel').append(buttonTemplate.content.cloneNode(true));
|
||||
$id('view_grid').addEventListener("click", () => {
|
||||
svgEditor.configObj.curConfig.showGrid = showGrid = !showGrid;
|
||||
gridUpdate();
|
||||
});
|
||||
|
||||
if (showGrid) {
|
||||
gridUpdate();
|
||||
}
|
||||
},
|
||||
buttons: strings.buttons.map((button, i) => {
|
||||
return Object.assign(buttons[i], button);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
8
src/editor/extensions/ext-grid/locale/fr.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
name: 'Grille',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Afficher/Cacher Grille'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -13,55 +13,45 @@
|
||||
* will show the user the point on the canvas that was clicked on.
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
const name = "helloworld";
|
||||
|
||||
const loadExtensionTranslation = async function (svgEditor) {
|
||||
let translationModule;
|
||||
const lang = svgEditor.configObj.pref('lang');
|
||||
try {
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${lang}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Missing translation (${lang}) - using 'en'`);
|
||||
console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/en.js`);
|
||||
}
|
||||
return translationModule.default;
|
||||
svgEditor.i18next.addResourceBundle(lang, name, translationModule.default);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'helloworld',
|
||||
async init ({$, importLocale}) {
|
||||
name,
|
||||
async init ({ _importLocale }) {
|
||||
const svgEditor = this;
|
||||
const strings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const svgCanvas = svgEditor.canvas;
|
||||
await loadExtensionTranslation(svgEditor);
|
||||
const { svgCanvas } = svgEditor;
|
||||
const { $id } = svgCanvas;
|
||||
return {
|
||||
name: strings.name,
|
||||
// For more notes on how to make an icon file, see the source of
|
||||
// the helloworld-icon.xml
|
||||
svgicons: 'helloworld-icon.xml',
|
||||
|
||||
// Multiple buttons can be added in this array
|
||||
buttons: [{
|
||||
// Must match the icon ID in helloworld-icon.xml
|
||||
id: 'hello_world',
|
||||
|
||||
// Fallback, e.g., for `file:///` access
|
||||
icon: 'helloworld.png',
|
||||
|
||||
// This indicates that the button will be added to the "mode"
|
||||
// button panel on the left side
|
||||
type: 'mode',
|
||||
|
||||
// Tooltip text
|
||||
title: strings.buttons[0].title,
|
||||
|
||||
// Events
|
||||
events: {
|
||||
click () {
|
||||
// The action taken when the button is clicked on.
|
||||
// For "mode" buttons, any other button will
|
||||
// automatically be de-pressed.
|
||||
svgCanvas.setMode('hello_world');
|
||||
}
|
||||
}
|
||||
}],
|
||||
name: svgEditor.i18next.t(`${name}:name`),
|
||||
callback() {
|
||||
// Add the button and its handler(s)
|
||||
const buttonTemplate = document.createElement("template");
|
||||
const title = svgEditor.i18next.t(`${name}:buttons.0.title`);
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
buttonTemplate.innerHTML = `
|
||||
<se-button id="hello_world" title="${title}" src="./images/hello_world.svg"></se-button>
|
||||
`;
|
||||
$id('tools_left').append(buttonTemplate.content.cloneNode(true));
|
||||
$id('hello_world').addEventListener("click", () => {
|
||||
svgCanvas.setMode('hello_world');
|
||||
});
|
||||
},
|
||||
// This is triggered when the main mouse button is pressed down
|
||||
// on the editor canvas (not the tool panels)
|
||||
mouseDown () {
|
||||
@@ -69,7 +59,7 @@ export default {
|
||||
if (svgCanvas.getMode() === 'hello_world') {
|
||||
// The returned object must include "started" with
|
||||
// a value of true in order for mouseUp to be triggered
|
||||
return {started: true};
|
||||
return { started: true };
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
@@ -86,16 +76,9 @@ export default {
|
||||
const y = opts.mouse_y / zoom;
|
||||
|
||||
// We do our own formatting
|
||||
let {text} = strings;
|
||||
[
|
||||
['x', x],
|
||||
['y', y]
|
||||
].forEach(([prop, val]) => {
|
||||
text = text.replace('{' + prop + '}', val);
|
||||
});
|
||||
|
||||
let text = svgEditor.i18next.t(`${name}:text`, { x, y });
|
||||
// Show the text using the custom alert function
|
||||
$.alert(text);
|
||||
alert(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
name: 'Hello World',
|
||||
text: 'Hello World!\n\nYou clicked here: {x}, {y}',
|
||||
text: 'Hello World!\n\nYou clicked here: {{x}}, {{y}}',
|
||||
buttons: [
|
||||
{
|
||||
title: "Say 'Hello World'"
|
||||
|
||||
9
src/editor/extensions/ext-helloworld/locale/fr.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
name: 'Bonjour le Monde',
|
||||
text: 'Bonjour le Monde!\n\nVous avez cliqué ici: {{x}}, {{y}}',
|
||||
buttons: [
|
||||
{
|
||||
title: "Dire 'Bonjour le Monde'"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
name: 'Hello World',
|
||||
text: 'Hello World!\n\n 请点击: {x}, {y}',
|
||||
text: 'Hello World!\n\n 请点击: {{x}}, {{y}}',
|
||||
buttons: [
|
||||
{
|
||||
title: "输出 'Hello World'"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-unsanitized/property */
|
||||
/* globals seConfirm */
|
||||
/**
|
||||
* @file ext-imagelib.js
|
||||
*
|
||||
@@ -7,27 +9,46 @@
|
||||
*
|
||||
*/
|
||||
|
||||
const loadExtensionTranslation = async function (lang) {
|
||||
let translationModule;
|
||||
try {
|
||||
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Missing translation (${lang}) - using 'en'`);
|
||||
translationModule = await import(`./locale/en.js`);
|
||||
}
|
||||
return translationModule.default;
|
||||
};
|
||||
const name = "imagelib";
|
||||
|
||||
const loadExtensionTranslation = async function (svgEditor) {
|
||||
let translationModule;
|
||||
const lang = svgEditor.configObj.pref('lang');
|
||||
try {
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/${lang}.js`);
|
||||
} catch (_error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Missing translation (${lang}) for ${name} - using 'en'`);
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
translationModule = await import(`./locale/en.js`);
|
||||
}
|
||||
svgEditor.i18next.addResourceBundle(lang, name, translationModule.default);
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'imagelib',
|
||||
async init ({$, decode64, dropXMLInternalSubset}) {
|
||||
name,
|
||||
async init({ decode64, dropXMLInternalSubset }) {
|
||||
const svgEditor = this;
|
||||
const imagelibStrings = await loadExtensionTranslation(svgEditor.curPrefs.lang);
|
||||
const { $id } = svgEditor.svgCanvas;
|
||||
await loadExtensionTranslation(svgEditor);
|
||||
|
||||
const {uiStrings, canvas: svgCanvas} = svgEditor;
|
||||
const { svgCanvas } = svgEditor;
|
||||
|
||||
const allowedImageLibOrigins = imagelibStrings.imgLibs.map(({url}) => {
|
||||
const imgLibs = [
|
||||
{
|
||||
name: svgEditor.i18next.t(`${name}:imgLibs_0_name`),
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: svgEditor.i18next.t(`${name}:imgLibs_0_description`)
|
||||
},
|
||||
{
|
||||
name: svgEditor.i18next.t(`${name}:imgLibs_1_name`),
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: svgEditor.i18next.t(`${name}:imgLibs_1_description`)
|
||||
}
|
||||
];
|
||||
|
||||
const allowedImageLibOrigins = imgLibs.map(({ url }) => {
|
||||
try {
|
||||
return new URL(url).origin;
|
||||
} catch (err) {
|
||||
@@ -39,16 +60,16 @@ export default {
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function closeBrowser () {
|
||||
$('#imgbrowse_holder').hide();
|
||||
const closeBrowser = () => {
|
||||
$id("imgbrowse_holder").style.display = 'none';
|
||||
document.activeElement.blur(); // make sure focus is the body to correct issue #417
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {void}
|
||||
*/
|
||||
function importImage (url) {
|
||||
const importImage = (url) => {
|
||||
const newImage = svgCanvas.addSVGElementFromJson({
|
||||
element: 'image',
|
||||
attr: {
|
||||
@@ -61,9 +82,9 @@ export default {
|
||||
}
|
||||
});
|
||||
svgCanvas.clearSelection();
|
||||
svgCanvas.addToSelection([newImage]);
|
||||
svgCanvas.addToSelection([ newImage ]);
|
||||
svgCanvas.setImageURL(url);
|
||||
}
|
||||
};
|
||||
|
||||
const pending = {};
|
||||
|
||||
@@ -108,8 +129,9 @@ export default {
|
||||
* @param {ImageLibMetaMessage|ImageLibMessage|string} cfg.data String is deprecated when parsed to JSON `ImageLibMessage`
|
||||
* @returns {void}
|
||||
*/
|
||||
async function onMessage ({origin, data: response}) { // eslint-disable-line no-shadow
|
||||
if (!response || !['string', 'object'].includes(typeof response)) {
|
||||
async function onMessage({ origin, data }) {
|
||||
let response = data;
|
||||
if (!response || ![ 'string', 'object' ].includes(typeof response)) {
|
||||
// Do nothing
|
||||
return;
|
||||
}
|
||||
@@ -125,7 +147,7 @@ export default {
|
||||
}
|
||||
if (!allowedImageLibOrigins.includes('*') && !allowedImageLibOrigins.includes(origin)) {
|
||||
// Todo: Surface this error to user?
|
||||
console.log(`Origin ${origin} not whitelisted for posting to ${window.origin}`); // eslint-disable-line no-console
|
||||
console.error(`Origin ${origin} not whitelisted for posting to ${window.origin}`);
|
||||
return;
|
||||
}
|
||||
const hasName = 'name' in response;
|
||||
@@ -142,7 +164,9 @@ export default {
|
||||
}
|
||||
|
||||
// Hide possible transfer dialog box
|
||||
$('#dialog_box').hide();
|
||||
if (document.querySelector('se-elix-alert-dialog')) {
|
||||
document.querySelector('se-elix-alert-dialog').remove();
|
||||
}
|
||||
type = hasName
|
||||
? 'meta'
|
||||
: response.charAt(0);
|
||||
@@ -168,381 +192,418 @@ export default {
|
||||
|
||||
let entry, curMeta, svgStr, imgStr;
|
||||
switch (type) {
|
||||
case 'meta': {
|
||||
// Metadata
|
||||
transferStopped = false;
|
||||
curMeta = response;
|
||||
case 'meta': {
|
||||
// Metadata
|
||||
transferStopped = false;
|
||||
curMeta = response;
|
||||
|
||||
// Should be safe to add dynamic property as passed metadata
|
||||
pending[curMeta.id] = curMeta; // lgtm [js/remote-property-injection]
|
||||
// Should be safe to add dynamic property as passed metadata
|
||||
pending[curMeta.id] = curMeta; // lgtm [js/remote-property-injection]
|
||||
|
||||
const name = (curMeta.name || 'file');
|
||||
const name = (curMeta.name || 'file');
|
||||
|
||||
const message = uiStrings.notification.retrieving.replace('%s', name);
|
||||
const message = svgEditor.i18next.t('notification.retrieving').replace('%s', name);
|
||||
|
||||
if (mode !== 'm') {
|
||||
await $.process_cancel(message);
|
||||
transferStopped = true;
|
||||
// Should a message be sent back to the frame?
|
||||
if (mode !== 'm') {
|
||||
await seConfirm(message);
|
||||
transferStopped = true;
|
||||
} else {
|
||||
entry = document.createElement('div');
|
||||
entry.textContent = message;
|
||||
entry.dataset.id = curMeta.id;
|
||||
preview.appendChild(entry);
|
||||
curMeta.entry = entry;
|
||||
}
|
||||
|
||||
$('#dialog_box').hide();
|
||||
} else {
|
||||
entry = $('<div>').text(message).data('id', curMeta.id);
|
||||
preview.append(entry);
|
||||
curMeta.entry = entry;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case '<':
|
||||
svgStr = true;
|
||||
break;
|
||||
case 'd': {
|
||||
if (response.startsWith('data:image/svg+xml')) {
|
||||
const pre = 'data:image/svg+xml;base64,';
|
||||
const src = response.substring(pre.length);
|
||||
response = decode64(src);
|
||||
case '<':
|
||||
svgStr = true;
|
||||
break;
|
||||
} else if (response.startsWith('data:image/')) {
|
||||
imgStr = true;
|
||||
break;
|
||||
case 'd': {
|
||||
if (response.startsWith('data:image/svg+xml')) {
|
||||
const pre = 'data:image/svg+xml;base64,';
|
||||
const src = response.substring(pre.length);
|
||||
response = decode64(src);
|
||||
svgStr = true;
|
||||
break;
|
||||
} else if (response.startsWith('data:image/')) {
|
||||
imgStr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Else fall through
|
||||
default:
|
||||
// TODO: See if there's a way to base64 encode the binary data stream
|
||||
// const str = 'data:;base64,' + svgedit.utilities.encode64(response, true);
|
||||
// Else fall through
|
||||
default:
|
||||
// TODO: See if there's a way to base64 encode the binary data stream
|
||||
// const str = 'data:;base64,' + svgedit.utilities.encode64(response, true);
|
||||
|
||||
// Assume it's raw image data
|
||||
// importImage(str);
|
||||
// Assume it's raw image data
|
||||
// importImage(str);
|
||||
|
||||
// Don't give warning as postMessage may have been used by something else
|
||||
if (mode !== 'm') {
|
||||
closeBrowser();
|
||||
} else {
|
||||
pending[id].entry.remove();
|
||||
}
|
||||
// await $.alert('Unexpected data was returned: ' + response, function() {
|
||||
// if (mode !== 'm') {
|
||||
// closeBrowser();
|
||||
// } else {
|
||||
// pending[id].entry.remove();
|
||||
// }
|
||||
// });
|
||||
return;
|
||||
// Don't give warning as postMessage may have been used by something else
|
||||
if (mode !== 'm') {
|
||||
closeBrowser();
|
||||
} else {
|
||||
pending[id].entry.remove();
|
||||
}
|
||||
// await alert('Unexpected data was returned: ' + response, function() {
|
||||
// if (mode !== 'm') {
|
||||
// closeBrowser();
|
||||
// } else {
|
||||
// pending[id].entry.remove();
|
||||
// }
|
||||
// });
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 's':
|
||||
// Import one
|
||||
if (svgStr) {
|
||||
svgCanvas.importSvgString(response);
|
||||
} else if (imgStr) {
|
||||
importImage(response);
|
||||
}
|
||||
closeBrowser();
|
||||
break;
|
||||
case 'm': {
|
||||
// Import multiple
|
||||
multiArr.push([(svgStr ? 'svg' : 'img'), response]);
|
||||
curMeta = pending[id];
|
||||
let title;
|
||||
if (svgStr) {
|
||||
if (curMeta && curMeta.name) {
|
||||
title = curMeta.name;
|
||||
} else {
|
||||
// Try to find a title
|
||||
// `dropXMLInternalSubset` is to help prevent the billion laughs attack
|
||||
const xml = new DOMParser().parseFromString(dropXMLInternalSubset(response), 'text/xml').documentElement; // lgtm [js/xml-bomb]
|
||||
title = $(xml).children('title').first().text() || '(SVG #' + response.length + ')';
|
||||
case 's':
|
||||
// Import one
|
||||
if (svgStr) {
|
||||
svgEditor.svgCanvas.importSvgString(response);
|
||||
} else if (imgStr) {
|
||||
importImage(response);
|
||||
}
|
||||
if (curMeta) {
|
||||
preview.children().each(function () {
|
||||
if ($(this).data('id') === id) {
|
||||
if (curMeta.preview_url) {
|
||||
$(this).html(
|
||||
$('<span>').append(
|
||||
$('<img>').attr('src', curMeta.preview_url),
|
||||
title
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$(this).text(title);
|
||||
closeBrowser();
|
||||
break;
|
||||
case 'm': {
|
||||
// Import multiple
|
||||
multiArr.push([ (svgStr ? 'svg' : 'img'), response ]);
|
||||
curMeta = pending[id];
|
||||
let title;
|
||||
if (svgStr) {
|
||||
if (curMeta && curMeta.name) {
|
||||
title = curMeta.name;
|
||||
} else {
|
||||
// Try to find a title
|
||||
// `dropXMLInternalSubset` is to help prevent the billion laughs attack
|
||||
const xml = new DOMParser().parseFromString(dropXMLInternalSubset(response), 'text/xml').documentElement; // lgtm [js/xml-bomb]
|
||||
title = xml.querySelector('title').textContent || '(SVG #' + response.length + ')';
|
||||
}
|
||||
if (curMeta) {
|
||||
Array.from(preview.children).forEach(function (element) {
|
||||
if (element.dataset.id === id) {
|
||||
if (curMeta.preview_url) {
|
||||
const img = document.createElement("img");
|
||||
img.src = curMeta.preview_url;
|
||||
const span = document.createElement("span");
|
||||
span.appendChild(img);
|
||||
element.append(span);
|
||||
} else {
|
||||
element.textContent = title;
|
||||
}
|
||||
submit.removeAttribute('disabled');
|
||||
}
|
||||
submit.removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = title;
|
||||
preview.appendChild(div);
|
||||
submit.removeAttribute('disabled');
|
||||
}
|
||||
} else {
|
||||
preview.append(
|
||||
$('<div>').text(title)
|
||||
);
|
||||
submit.removeAttr('disabled');
|
||||
}
|
||||
} else {
|
||||
if (curMeta && curMeta.preview_url) {
|
||||
title = curMeta.name || '';
|
||||
entry = $('<span>').append(
|
||||
$('<img>').attr('src', curMeta.preview_url),
|
||||
title
|
||||
);
|
||||
} else {
|
||||
entry = $('<img>').attr('src', response);
|
||||
}
|
||||
if (curMeta && curMeta.preview_url) {
|
||||
title = curMeta.name || '';
|
||||
entry = document.createElement('span');
|
||||
const img = document.createElement("img");
|
||||
img.src = curMeta.preview_url;
|
||||
entry.appendChild(img);
|
||||
entry.appendChild(document.createTextNode(title));
|
||||
} else {
|
||||
entry = document.createElement("img");
|
||||
entry.src = response;
|
||||
}
|
||||
|
||||
if (curMeta) {
|
||||
preview.children().each(function () {
|
||||
if ($(this).data('id') === id) {
|
||||
$(this).html(entry);
|
||||
submit.removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
preview.append($('<div>').append(entry));
|
||||
submit.removeAttr('disabled');
|
||||
if (curMeta) {
|
||||
Array.from(preview.children).forEach(function (element) {
|
||||
if (element.dataset.id === id) {
|
||||
element.appendChild(entry);
|
||||
submit.removeAttribute('disabled');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const div = document.createElement("div");
|
||||
div.appendChild(entry);
|
||||
preview.appendChild(div);
|
||||
submit.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
break;
|
||||
} case 'o': {
|
||||
// Open
|
||||
if (!svgStr) { break; }
|
||||
closeBrowser();
|
||||
const ok = await svgEditor.openPrep();
|
||||
if (!ok) { return; }
|
||||
svgCanvas.clear();
|
||||
svgCanvas.setSvgString(response);
|
||||
// updateCanvas();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} case 'o': {
|
||||
// Open
|
||||
if (!svgStr) { break; }
|
||||
closeBrowser();
|
||||
const ok = await svgEditor.openPrep();
|
||||
if (!ok) { return; }
|
||||
svgCanvas.clear();
|
||||
svgCanvas.setSvgString(response);
|
||||
// updateCanvas();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receive `postMessage` data
|
||||
window.addEventListener('message', onMessage, true);
|
||||
|
||||
const insertAfter = (referenceNode, newNode) => {
|
||||
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
|
||||
};
|
||||
|
||||
const toggleMultiLoop = () => {
|
||||
multiArr.forEach(function(item, i){
|
||||
const type = item[0];
|
||||
const data = item[1];
|
||||
if (type === 'svg') {
|
||||
svgCanvas.importSvgString(data);
|
||||
} else {
|
||||
importImage(data);
|
||||
}
|
||||
svgCanvas.moveSelectedElements(i * 20, i * 20, false);
|
||||
});
|
||||
while (preview.firstChild)
|
||||
preview.removeChild(preview.firstChild);
|
||||
multiArr = [];
|
||||
$id("imgbrowse_holder").style.display = 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
* @returns {void}
|
||||
*/
|
||||
function toggleMulti (show) {
|
||||
$('#lib_framewrap, #imglib_opts').css({right: (show ? 200 : 10)});
|
||||
const toggleMulti = (show) => {
|
||||
$id('lib_framewrap').style.right = (show ? 200 : 10);
|
||||
$id('imglib_opts').style.right = (show ? 200 : 10);
|
||||
if (!preview) {
|
||||
preview = $('<div id=imglib_preview>').css({
|
||||
position: 'absolute',
|
||||
top: 45,
|
||||
right: 10,
|
||||
width: 180,
|
||||
bottom: 45,
|
||||
background: '#fff',
|
||||
overflow: 'auto'
|
||||
}).insertAfter('#lib_framewrap');
|
||||
preview = document.createElement('div');
|
||||
preview.setAttribute('id', 'imglib_preview');
|
||||
// eslint-disable-next-line max-len
|
||||
preview.setAttribute('style', `position: absolute;top: 45px;right: 10px;width: 180px;bottom: 45px;background: #fff;overflow: auto;`);
|
||||
insertAfter($id('lib_framewrap'), preview);
|
||||
|
||||
submit = $('<button disabled>Import selected</button>')
|
||||
.appendTo('#imgbrowse')
|
||||
.on('click touchend', function () {
|
||||
$.each(multiArr, function (i) {
|
||||
const type = this[0];
|
||||
const data = this[1];
|
||||
if (type === 'svg') {
|
||||
svgCanvas.importSvgString(data);
|
||||
} else {
|
||||
importImage(data);
|
||||
}
|
||||
svgCanvas.moveSelectedElements(i * 20, i * 20, false);
|
||||
});
|
||||
preview.empty();
|
||||
multiArr = [];
|
||||
$('#imgbrowse_holder').hide();
|
||||
}).css({
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
right: -10
|
||||
});
|
||||
submit = document.createElement('button');
|
||||
submit.setAttribute('content', 'Import selected');
|
||||
submit.setAttribute('disabled', true);
|
||||
submit.textContent = 'Import selected';
|
||||
submit.setAttribute('style', `position: absolute;bottom: 10px;right: -10px;`);
|
||||
$id('imgbrowse').appendChild(submit);
|
||||
submit.addEventListener('click', toggleMultiLoop);
|
||||
submit.addEventListener('touchend', toggleMultiLoop);
|
||||
}
|
||||
submit.style.display = (show) ? 'block' : 'none';
|
||||
preview.style.display = (show) ? 'block' : 'none';
|
||||
|
||||
preview.toggle(show);
|
||||
submit.toggle(show);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function showBrowser () {
|
||||
let browser = $('#imgbrowse');
|
||||
if (!browser.length) {
|
||||
$('<div id=imgbrowse_holder><div id=imgbrowse class=toolbar_button>' +
|
||||
'</div></div>').insertAfter('#svg_docprops');
|
||||
browser = $('#imgbrowse');
|
||||
const showBrowser = () => {
|
||||
let browser = $id('imgbrowse');
|
||||
if (!browser) {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'imgbrowse_holder';
|
||||
div.innerHTML = '<div id=imgbrowse class=toolbar_button></div>';
|
||||
insertAfter($id('svg_editor'), div);
|
||||
browser = $id('imgbrowse');
|
||||
|
||||
const allLibs = imagelibStrings.select_lib;
|
||||
const allLibs = svgEditor.i18next.t(`${name}:select_lib`);
|
||||
|
||||
const libOpts = $('<ul id=imglib_opts>').appendTo(browser);
|
||||
const frame = $('<iframe src="javascript:0"/>').prependTo(browser).hide().wrap('<div id=lib_framewrap>');
|
||||
const divFrameWrap = document.createElement('div');
|
||||
divFrameWrap.id = 'lib_framewrap';
|
||||
|
||||
const header = $('<h1>').prependTo(browser).text(allLibs).css({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%'
|
||||
const libOpts = document.createElement('ul');
|
||||
libOpts.id = 'imglib_opts';
|
||||
browser.append(libOpts);
|
||||
const frame = document.createElement('iframe');
|
||||
frame.src = "javascript:0";
|
||||
frame.style.display = 'none';
|
||||
divFrameWrap.append(frame);
|
||||
browser.prepend(divFrameWrap);
|
||||
|
||||
const header = document.createElement('h1');
|
||||
browser.prepend(header);
|
||||
header.textContent = allLibs;
|
||||
header.setAttribute('style', `position: absolute;top: 0;left: 0;width: 100%;`);
|
||||
|
||||
const button = document.createElement('button');
|
||||
// eslint-disable-next-line max-len
|
||||
button.innerHTML = '<img class="svg_icon" src="./images/cancel.svg" alt="icon" width="16" height="16" />' + svgEditor.i18next.t('common.cancel');
|
||||
browser.appendChild(button);
|
||||
button.addEventListener('click', function () {
|
||||
$id("imgbrowse_holder").style.display = 'none';
|
||||
});
|
||||
button.addEventListener('touchend', function () {
|
||||
$id("imgbrowse_holder").style.display = 'none';
|
||||
});
|
||||
button.setAttribute('style', `position: absolute;top: 5;right: -10;`);
|
||||
|
||||
const cancel = $('<button>' + uiStrings.common.cancel + '</button>')
|
||||
.appendTo(browser)
|
||||
.on('click touchend', function () {
|
||||
$('#imgbrowse_holder').hide();
|
||||
}).css({
|
||||
position: 'absolute',
|
||||
top: 5,
|
||||
right: -10
|
||||
});
|
||||
const leftBlock = document.createElement('span');
|
||||
leftBlock.setAttribute('style', `position: absolute;top: 5;left: 10;`);
|
||||
browser.appendChild(leftBlock);
|
||||
|
||||
const leftBlock = $('<span>').css({position: 'absolute', top: 5, left: 10}).appendTo(browser);
|
||||
const back = document.createElement('button');
|
||||
back.style.visibility = "hidden";
|
||||
// eslint-disable-next-line max-len
|
||||
back.innerHTML = '<img class="svg_icon" src="./images/library.svg" alt="icon" width="16" height="16" />' + svgEditor.i18next.t(`${name}:show_list`);
|
||||
leftBlock.appendChild(back);
|
||||
back.addEventListener('click', function () {
|
||||
frame.setAttribute('src', 'about:blank');
|
||||
frame.style.display = 'none';
|
||||
libOpts.style.display = 'block';
|
||||
header.textContent = allLibs;
|
||||
back.style.display = 'none';
|
||||
});
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
back.addEventListener('touchend', function () {
|
||||
frame.setAttribute('src', 'about:blank');
|
||||
frame.style.display = 'none';
|
||||
libOpts.style.display = 'block';
|
||||
header.textContent = allLibs;
|
||||
back.style.display = 'none';
|
||||
});
|
||||
back.setAttribute('style', `margin-right: 5px;`);
|
||||
back.style.display = 'none';
|
||||
|
||||
const back = $('<button hidden>' + imagelibStrings.show_list + '</button>')
|
||||
.appendTo(leftBlock)
|
||||
.on('click touchend', function () {
|
||||
frame.attr('src', 'about:blank').hide();
|
||||
libOpts.show();
|
||||
header.text(allLibs);
|
||||
back.hide();
|
||||
}).css({
|
||||
'margin-right': 5
|
||||
}).hide();
|
||||
|
||||
/* const type = */ $('<select><option value=s>' +
|
||||
imagelibStrings.import_single + '</option><option value=m>' +
|
||||
imagelibStrings.import_multi + '</option><option value=o>' +
|
||||
imagelibStrings.open + '</option></select>').appendTo(leftBlock).change(function () {
|
||||
mode = $(this).val();
|
||||
const select = document.createElement('select');
|
||||
select.innerHTML = '<select><option value=s>' +
|
||||
svgEditor.i18next.t(`${name}:import_single`) + '</option><option value=m>' +
|
||||
svgEditor.i18next.t(`${name}:import_multi`) + '</option><option value=o>' +
|
||||
svgEditor.i18next.t(`${name}:open`) + '</option>';
|
||||
leftBlock.appendChild(select);
|
||||
select.addEventListener('change', function () {
|
||||
mode = this.value;
|
||||
switch (mode) {
|
||||
case 's':
|
||||
case 'o':
|
||||
toggleMulti(false);
|
||||
break;
|
||||
case 's':
|
||||
case 'o':
|
||||
toggleMulti(false);
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
// Import multiple
|
||||
toggleMulti(true);
|
||||
break;
|
||||
case 'm':
|
||||
// Import multiple
|
||||
toggleMulti(true);
|
||||
break;
|
||||
}
|
||||
}).css({
|
||||
'margin-top': 10
|
||||
});
|
||||
select.setAttribute('style', `margin-top: 10px;`);
|
||||
|
||||
cancel.prepend($.getSvgIcon('cancel', true));
|
||||
back.prepend($.getSvgIcon('tool_imagelib', true));
|
||||
|
||||
imagelibStrings.imgLibs.forEach(function ({name, url, description}) {
|
||||
$('<li>')
|
||||
.appendTo(libOpts)
|
||||
.text(name)
|
||||
.on('click touchend', function () {
|
||||
frame.attr(
|
||||
'src',
|
||||
url
|
||||
).show();
|
||||
header.text(name);
|
||||
libOpts.hide();
|
||||
back.show();
|
||||
}).append(`<span>${description}</span>`);
|
||||
imgLibs.forEach(function ({ name, url, description }) {
|
||||
const li = document.createElement('li');
|
||||
libOpts.appendChild(li);
|
||||
li.textContent = name;
|
||||
li.addEventListener('click', function () {
|
||||
frame.setAttribute('src', url);
|
||||
frame.style.display = 'block';
|
||||
header.textContent = name;
|
||||
libOpts.style.display = 'none';
|
||||
back.style.display = 'block';
|
||||
});
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
li.addEventListener('touchend', function () {
|
||||
frame.setAttribute('src', url);
|
||||
frame.style.display = 'block';
|
||||
header.textContent = name;
|
||||
libOpts.style.display = 'none';
|
||||
back.style.display = 'block';
|
||||
});
|
||||
var span = document.createElement("span");
|
||||
span.textContent = description;
|
||||
li.appendChild(span);
|
||||
});
|
||||
} else {
|
||||
$('#imgbrowse_holder').show();
|
||||
$id("imgbrowse_holder").style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
const buttons = [{
|
||||
id: 'tool_imagelib',
|
||||
type: 'app_menu', // _flyout
|
||||
icon: 'imagelib.png',
|
||||
position: 4,
|
||||
events: {
|
||||
mouseup: showBrowser
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
return {
|
||||
svgicons: 'ext-imagelib.xml',
|
||||
buttons: imagelibStrings.buttons.map((button, i) => {
|
||||
return Object.assign(buttons[i], button);
|
||||
}),
|
||||
callback () {
|
||||
$('<style>').text(
|
||||
'#imgbrowse_holder {' +
|
||||
'position: absolute;' +
|
||||
'top: 0;' +
|
||||
'left: 0;' +
|
||||
'width: 100%;' +
|
||||
'height: 100%;' +
|
||||
'background-color: rgba(0, 0, 0, .5);' +
|
||||
'z-index: 5;' +
|
||||
callback() {
|
||||
// Add the button and its handler(s)
|
||||
const buttonTemplate = document.createElement("template");
|
||||
buttonTemplate.innerHTML = `
|
||||
<se-menu-item id="tool_imagelib" label="Image library" src="./images/library.svg"></se-menu-item>
|
||||
`;
|
||||
insertAfter($id('tool_export'), buttonTemplate.content.cloneNode(true));
|
||||
$id('tool_imagelib').addEventListener("click", () => {
|
||||
showBrowser();
|
||||
});
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = '#imgbrowse_holder {' +
|
||||
'position: absolute;' +
|
||||
'top: 0;' +
|
||||
'left: 0;' +
|
||||
'width: 100%;' +
|
||||
'height: 100%;' +
|
||||
'background-color: rgba(0, 0, 0, .5);' +
|
||||
'z-index: 5;' +
|
||||
'}' +
|
||||
'#imgbrowse {' +
|
||||
'position: absolute;' +
|
||||
'top: 25px;' +
|
||||
'left: 25px;' +
|
||||
'right: 25px;' +
|
||||
'bottom: 25px;' +
|
||||
'min-width: 300px;' +
|
||||
'min-height: 200px;' +
|
||||
'background: #B0B0B0;' +
|
||||
'border: 1px outset #777;' +
|
||||
'position: absolute;' +
|
||||
'top: 25px;' +
|
||||
'left: 25px;' +
|
||||
'right: 25px;' +
|
||||
'bottom: 25px;' +
|
||||
'min-width: 300px;' +
|
||||
'min-height: 200px;' +
|
||||
'background: #5a6162;' +
|
||||
'border: 1px outset #777;' +
|
||||
'}' +
|
||||
'#imgbrowse h1 {' +
|
||||
'font-size: 20px;' +
|
||||
'margin: .4em;' +
|
||||
'text-align: center;' +
|
||||
'font-size: 20px;' +
|
||||
'margin: .4em;' +
|
||||
'text-align: center;' +
|
||||
'}' +
|
||||
'#lib_framewrap,' +
|
||||
'#imgbrowse > ul {' +
|
||||
'position: absolute;' +
|
||||
'top: 45px;' +
|
||||
'left: 10px;' +
|
||||
'right: 10px;' +
|
||||
'bottom: 10px;' +
|
||||
'background: white;' +
|
||||
'margin: 0;' +
|
||||
'padding: 0;' +
|
||||
'position: absolute;' +
|
||||
'top: 45px;' +
|
||||
'left: 10px;' +
|
||||
'right: 10px;' +
|
||||
'bottom: 10px;' +
|
||||
'background: white;' +
|
||||
'margin: 0;' +
|
||||
'padding: 0;' +
|
||||
'}' +
|
||||
'#imgbrowse > ul {' +
|
||||
'overflow: auto;' +
|
||||
'overflow: auto;' +
|
||||
'}' +
|
||||
'#imgbrowse > div {' +
|
||||
'border: 1px solid #666;' +
|
||||
'border: 1px solid #666;' +
|
||||
'}' +
|
||||
'#imglib_preview > div {' +
|
||||
'padding: 5px;' +
|
||||
'font-size: 12px;' +
|
||||
'padding: 5px;' +
|
||||
'font-size: 12px;' +
|
||||
'}' +
|
||||
'#imglib_preview img {' +
|
||||
'display: block;' +
|
||||
'margin: 0 auto;' +
|
||||
'max-height: 100px;' +
|
||||
'display: block;' +
|
||||
'margin: 0 auto;' +
|
||||
'max-height: 100px;' +
|
||||
'}' +
|
||||
'#imgbrowse li {' +
|
||||
'list-style: none;' +
|
||||
'padding: .5em;' +
|
||||
'background: #E8E8E8;' +
|
||||
'border-bottom: 1px solid #B0B0B0;' +
|
||||
'line-height: 1.2em;' +
|
||||
'font-style: sans-serif;' +
|
||||
'}' +
|
||||
'list-style: none;' +
|
||||
'padding: .5em;' +
|
||||
'background: #E8E8E8;' +
|
||||
'border-bottom: 1px solid #5a6162;' +
|
||||
'line-height: 1.2em;' +
|
||||
'font-style: sans-serif;' +
|
||||
'}' +
|
||||
'#imgbrowse li > span {' +
|
||||
'color: #666;' +
|
||||
'font-size: 15px;' +
|
||||
'display: block;' +
|
||||
'}' +
|
||||
'color: #666;' +
|
||||
'font-size: 15px;' +
|
||||
'display: block;' +
|
||||
'}' +
|
||||
'#imgbrowse li:hover {' +
|
||||
'background: #FFC;' +
|
||||
'cursor: pointer;' +
|
||||
'}' +
|
||||
'background: #FFC;' +
|
||||
'cursor: pointer;' +
|
||||
'}' +
|
||||
'#imgbrowse iframe {' +
|
||||
'width: 100%;' +
|
||||
'height: 100%;' +
|
||||
'border: 0;' +
|
||||
'}'
|
||||
).appendTo('head');
|
||||
'width: 100%;' +
|
||||
'height: 100%;' +
|
||||
'border: 0;' +
|
||||
'}';
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>-</title>
|
||||
<link rel="icon" type="image/png" href="../../images/logo.png" />
|
||||
<link rel="icon" type="image/png" href="../../images/logo.svg" />
|
||||
|
||||
<!-- Lacking browser support -->
|
||||
<script nomodule="" src="../../redirect-on-no-module-support.js"></script>
|
||||
@@ -19,7 +19,7 @@
|
||||
<h1>Select an image:</h1>
|
||||
<a href="smiley.svg">smiley.svg</a>
|
||||
<br/>
|
||||
<a href="../../images/logo.png">logo.png</a>
|
||||
<a href="../../images/logo.svg">logo.svg</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,49 +1,52 @@
|
||||
/* globals jQuery */
|
||||
const $ = jQuery;
|
||||
$('a').click(function () {
|
||||
const {href} = this;
|
||||
const target = window.parent;
|
||||
const post = (message) => {
|
||||
// Todo: Make origin customizable as set by opening window
|
||||
// Todo: If dropping IE9, avoid stringifying
|
||||
target.postMessage(JSON.stringify({
|
||||
namespace: 'imagelib',
|
||||
...message
|
||||
}), '*');
|
||||
};
|
||||
// Convert Non-SVG images to data URL first
|
||||
// (this could also have been done server-side by the library)
|
||||
// Send metadata (also indicates file is about to be sent)
|
||||
post({
|
||||
name: $(this).text(),
|
||||
id: href
|
||||
});
|
||||
if (!href.includes('.svg')) {
|
||||
const img = new Image();
|
||||
img.addEventListener('load', function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = this.width;
|
||||
canvas.height = this.height;
|
||||
// load the raster image into the canvas
|
||||
canvas.getContext('2d').drawImage(this, 0, 0);
|
||||
// retrieve the data: URL
|
||||
let data;
|
||||
try {
|
||||
data = canvas.toDataURL();
|
||||
} catch (err) {
|
||||
// This fails in Firefox with `file:///` URLs :(
|
||||
// Todo: This could use a generic alert library instead
|
||||
alert('Data URL conversion failed: ' + err); // eslint-disable-line no-alert
|
||||
data = '';
|
||||
}
|
||||
post({href, data});
|
||||
const atags = document.querySelectorAll('a');
|
||||
Array.prototype.forEach.call(atags, function (aEle) {
|
||||
aEle.addEventListener('click', function (event) {
|
||||
const { href } = event.currentTarget;
|
||||
const target = window.parent;
|
||||
const post = (message) => {
|
||||
// Todo: Make origin customizable as set by opening window
|
||||
// Todo: If dropping IE9, avoid stringifying
|
||||
target.postMessage(JSON.stringify({
|
||||
namespace: 'imagelib',
|
||||
...message
|
||||
}), '*');
|
||||
};
|
||||
// Convert Non-SVG images to data URL first
|
||||
// (this could also have been done server-side by the library)
|
||||
// Send metadata (also indicates file is about to be sent)
|
||||
post({
|
||||
name: event.currentTarget.textContent,
|
||||
id: href
|
||||
});
|
||||
img.src = href;
|
||||
} else {
|
||||
// Do ajax request for image's href value
|
||||
$.get(href, function (data) {
|
||||
post({href, data});
|
||||
}, 'html'); // 'html' is necessary to keep returned data as a string
|
||||
}
|
||||
return false;
|
||||
if (!href.includes('.svg')) {
|
||||
const img = new Image();
|
||||
img.addEventListener('load', function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = this.width;
|
||||
canvas.height = this.height;
|
||||
// load the raster image into the canvas
|
||||
canvas.getContext('2d').drawImage(this, 0, 0);
|
||||
// retrieve the data: URL
|
||||
let data;
|
||||
try {
|
||||
data = canvas.toDataURL();
|
||||
} catch (err) {
|
||||
// This fails in Firefox with `file:///` URLs :(
|
||||
// Todo: This could use a generic alert library instead
|
||||
alert('Data URL conversion failed: ' + err);
|
||||
data = '';
|
||||
}
|
||||
post({ href, data });
|
||||
});
|
||||
img.src = href;
|
||||
} else {
|
||||
// Do ajax request for image's href value
|
||||
$.get(href, function (data) {
|
||||
post({ href, data });
|
||||
}, 'html'); // 'html' is necessary to keep returned data as a string
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: 'Bilder-Bibliothek'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,30 +9,8 @@ export default {
|
||||
title: 'Image library'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
// The site is no longer using our API, and they have added an
|
||||
// `X-Frame-Options` header which prevents our usage cross-origin:
|
||||
// Getting messages like this in console:
|
||||
// Refused to display 'https://openclipart.org/detail/307176/sign-bike' in a frame
|
||||
// because it set 'X-Frame-Options' to 'sameorigin'.
|
||||
// url: 'https://openclipart.org/svgedit',
|
||||
// However, they do have a custom API which we are using here:
|
||||
/*
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: '{path}imagelib/openclipart.html',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: "Bibliothèque d'images"
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: 'Biblioteka obrazów'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: 'Biblioteca de Imagens'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: 'Bibliotecă de Imagini'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: 'Knižnica obrázkov'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: 'Knjižnica slik'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -9,25 +9,8 @@ export default {
|
||||
title: '图像库'
|
||||
}
|
||||
],
|
||||
imgLibs: [
|
||||
{
|
||||
name: 'Demo library (local)',
|
||||
url: 'extensions/ext-imagelib/index.html',
|
||||
description: 'Demonstration library for SVG-edit on this server'
|
||||
},
|
||||
{
|
||||
name: 'IAN Symbol Libraries',
|
||||
url: 'https://ian.umces.edu/symbols/catalog/svgedit/album_chooser.php?svgedit=3',
|
||||
description: 'Free library of illustrations'
|
||||
}
|
||||
/*
|
||||
// See message in "en" locale for further details
|
||||
,
|
||||
{
|
||||
name: 'Openclipart',
|
||||
url: 'https://openclipart.org/svgedit',
|
||||
description: 'Share and Use Images. Over 100,000 Public Domain SVG Images and Growing.'
|
||||
}
|
||||
*/
|
||||
]
|
||||
imgLibs_0_name: 'Demo library (local)',
|
||||
imgLibs_0_description: 'Demonstration library for SVG-edit on this server',
|
||||
imgLibs_1_name: 'IAN Symbol Libraries',
|
||||
imgLibs_1_description: 'Free library of illustrations',
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>-</title>
|
||||
<link rel="icon" type="image/png" href="../../images/logo.png" />
|
||||
<link rel="icon" type="image/svg" href="../../images/logo.svg" />
|
||||
|
||||
<!-- Lacking browser support -->
|
||||
<script nomodule="" src="../../redirect-on-no-module-support.js"></script>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// eslint-disable-next-line node/no-unpublished-import
|
||||
import {jml, body, nbsp} from 'jamilih';
|
||||
// eslint-disable-next-line node/no-unpublished-import
|
||||
/* eslint-disable node/no-unpublished-import */
|
||||
import { jml, body, nbsp } from 'jamilih';
|
||||
import $ from 'query-result';
|
||||
// eslint-disable-next-line node/no-unpublished-import
|
||||
import {manipulation} from 'qr-manipulation';
|
||||
import { manipulation } from 'qr-manipulation';
|
||||
|
||||
manipulation($, jml);
|
||||
|
||||
@@ -22,32 +20,31 @@ async function processResults (url) {
|
||||
* @returns {external:JamilihArray}
|
||||
*/
|
||||
function queryLink (query) {
|
||||
return ['a', {
|
||||
return [ 'a', {
|
||||
href: jsVoid,
|
||||
dataset: {value: query},
|
||||
$on: {click (e) {
|
||||
dataset: { value: query },
|
||||
$on: { click (e) {
|
||||
e.preventDefault();
|
||||
const {value} = this.dataset;
|
||||
const { value } = this.dataset;
|
||||
$('#query')[0].$set(value);
|
||||
$('#openclipart')[0].$submit();
|
||||
}}
|
||||
}, [query]];
|
||||
} }
|
||||
}, [ query ] ];
|
||||
}
|
||||
|
||||
const r = await fetch(url);
|
||||
const json = await r.json();
|
||||
// console.log('json', json);
|
||||
|
||||
if (!json || json.msg !== 'success') {
|
||||
// Todo: This could use a generic alert library instead
|
||||
alert('There was a problem downloading the results'); // eslint-disable-line no-alert
|
||||
alert('There was a problem downloading the results');
|
||||
return;
|
||||
}
|
||||
const {payload, info: {
|
||||
const { payload, info: {
|
||||
results: numResults,
|
||||
pages,
|
||||
current_page: currentPage
|
||||
}} = json;
|
||||
} } = json;
|
||||
|
||||
// $('#page')[0].value = currentPage;
|
||||
// $('#page')[0].max = pages;
|
||||
@@ -62,21 +59,21 @@ async function processResults (url) {
|
||||
// - `svg`'s: `png_thumb`, `png_full_lossy`, `png_2400px`
|
||||
const semiColonSep = '; ' + nbsp;
|
||||
$('#results').jml('div', [
|
||||
['span', [
|
||||
[ 'span', [
|
||||
'Number of results: ',
|
||||
numResults
|
||||
]],
|
||||
] ],
|
||||
semiColonSep,
|
||||
['span', [
|
||||
[ 'span', [
|
||||
'page ',
|
||||
currentPage,
|
||||
' out of ',
|
||||
pages
|
||||
]],
|
||||
] ],
|
||||
...payload.map(({
|
||||
title, description, id,
|
||||
uploader, created,
|
||||
svg: {url: svgURL},
|
||||
svg: { url: svgURL },
|
||||
detail_link: detailLink,
|
||||
tags_array: tagsArray,
|
||||
downloaded_by: downloadedBy,
|
||||
@@ -84,12 +81,11 @@ async function processResults (url) {
|
||||
}) => {
|
||||
const imgHW = '100px';
|
||||
const colonSep = ': ' + nbsp;
|
||||
return ['div', [
|
||||
['button', {style: 'margin-right: 8px; border: 2px solid black;', dataset: {id, value: svgURL}, $on: {
|
||||
return [ 'div', [
|
||||
[ 'button', { style: 'margin-right: 8px; border: 2px solid black;', dataset: { id, value: svgURL }, $on: {
|
||||
async click (e) {
|
||||
e.preventDefault();
|
||||
const {value: svgurl} = this.dataset;
|
||||
// console.log('this', id, svgurl);
|
||||
const { value: svgurl } = this.dataset;
|
||||
const post = (message) => {
|
||||
// Todo: Make origin customizable as set by opening window
|
||||
// Todo: If dropping IE9, avoid stringifying
|
||||
@@ -105,79 +101,78 @@ async function processResults (url) {
|
||||
});
|
||||
const result = await fetch(svgurl);
|
||||
const svg = await result.text();
|
||||
// console.log('url and svg', svgurl, svg);
|
||||
post({
|
||||
href: svgurl,
|
||||
data: svg
|
||||
});
|
||||
}
|
||||
}}, [
|
||||
} }, [
|
||||
// If we wanted interactive versions despite security risk:
|
||||
// ['object', {data: svgURL, type: 'image/svg+xml'}]
|
||||
['img', {src: svgURL, style: `width: ${imgHW}; height: ${imgHW};`}]
|
||||
]],
|
||||
['b', [title]],
|
||||
[ 'img', { src: svgURL, style: `width: ${imgHW}; height: ${imgHW};` } ]
|
||||
] ],
|
||||
[ 'b', [ title ] ],
|
||||
' ',
|
||||
['i', [description]],
|
||||
[ 'i', [ description ] ],
|
||||
' ',
|
||||
['span', [
|
||||
[ 'span', [
|
||||
'(ID: ',
|
||||
['a', {
|
||||
[ 'a', {
|
||||
href: jsVoid,
|
||||
dataset: {value: id},
|
||||
dataset: { value: id },
|
||||
$on: {
|
||||
click (e) {
|
||||
e.preventDefault();
|
||||
const {value} = this.dataset;
|
||||
const { value } = this.dataset;
|
||||
$('#byids')[0].$set(value);
|
||||
$('#openclipart')[0].$submit();
|
||||
}
|
||||
}
|
||||
}, [id]],
|
||||
}, [ id ] ],
|
||||
')'
|
||||
]],
|
||||
] ],
|
||||
' ',
|
||||
['i', [
|
||||
['a', {
|
||||
[ 'i', [
|
||||
[ 'a', {
|
||||
href: detailLink,
|
||||
target: '_blank'
|
||||
}, ['Details']]
|
||||
]],
|
||||
['br'],
|
||||
['span', [
|
||||
['u', ['Uploaded by']], colonSep,
|
||||
}, [ 'Details' ] ]
|
||||
] ],
|
||||
[ 'br' ],
|
||||
[ 'span', [
|
||||
[ 'u', [ 'Uploaded by' ] ], colonSep,
|
||||
queryLink(uploader),
|
||||
semiColonSep
|
||||
]],
|
||||
['span', [
|
||||
['u', ['Download count']], colonSep,
|
||||
] ],
|
||||
[ 'span', [
|
||||
[ 'u', [ 'Download count' ] ], colonSep,
|
||||
downloadedBy,
|
||||
semiColonSep
|
||||
]],
|
||||
['span', [
|
||||
['u', ['Times used as favorite']], colonSep,
|
||||
] ],
|
||||
[ 'span', [
|
||||
[ 'u', [ 'Times used as favorite' ] ], colonSep,
|
||||
totalFavorites,
|
||||
semiColonSep
|
||||
]],
|
||||
['span', [
|
||||
['u', ['Created date']], colonSep,
|
||||
] ],
|
||||
[ 'span', [
|
||||
[ 'u', [ 'Created date' ] ], colonSep,
|
||||
created
|
||||
]],
|
||||
['br'],
|
||||
['u', ['Tags']], colonSep,
|
||||
] ],
|
||||
[ 'br' ],
|
||||
[ 'u', [ 'Tags' ] ], colonSep,
|
||||
...tagsArray.map((tag) => {
|
||||
return ['span', [
|
||||
return [ 'span', [
|
||||
' ',
|
||||
queryLink(tag)
|
||||
]];
|
||||
] ];
|
||||
})
|
||||
]];
|
||||
] ];
|
||||
}),
|
||||
['br'], ['br'],
|
||||
[ 'br' ], [ 'br' ],
|
||||
(currentPage === 1 || pages <= 2
|
||||
? ''
|
||||
: ['span', [
|
||||
['a', {
|
||||
: [ 'span', [
|
||||
[ 'a', {
|
||||
href: jsVoid,
|
||||
$on: {
|
||||
click (e) {
|
||||
@@ -186,14 +181,14 @@ async function processResults (url) {
|
||||
$('#openclipart')[0].$submit();
|
||||
}
|
||||
}
|
||||
}, ['First']],
|
||||
}, [ 'First' ] ],
|
||||
' '
|
||||
]]
|
||||
] ]
|
||||
),
|
||||
(currentPage === 1
|
||||
? ''
|
||||
: ['span', [
|
||||
['a', {
|
||||
: [ 'span', [
|
||||
[ 'a', {
|
||||
href: jsVoid,
|
||||
$on: {
|
||||
click (e) {
|
||||
@@ -202,14 +197,14 @@ async function processResults (url) {
|
||||
$('#openclipart')[0].$submit();
|
||||
}
|
||||
}
|
||||
}, ['Prev']],
|
||||
}, [ 'Prev' ] ],
|
||||
' '
|
||||
]]
|
||||
] ]
|
||||
),
|
||||
(currentPage === pages
|
||||
? ''
|
||||
: ['span', [
|
||||
['a', {
|
||||
: [ 'span', [
|
||||
[ 'a', {
|
||||
href: jsVoid,
|
||||
$on: {
|
||||
click (e) {
|
||||
@@ -218,14 +213,14 @@ async function processResults (url) {
|
||||
$('#openclipart')[0].$submit();
|
||||
}
|
||||
}
|
||||
}, ['Next']],
|
||||
}, [ 'Next' ] ],
|
||||
' '
|
||||
]]
|
||||
] ]
|
||||
),
|
||||
(currentPage === pages || pages <= 2
|
||||
? ''
|
||||
: ['span', [
|
||||
['a', {
|
||||
: [ 'span', [
|
||||
[ 'a', {
|
||||
href: jsVoid,
|
||||
$on: {
|
||||
click (e) {
|
||||
@@ -234,20 +229,20 @@ async function processResults (url) {
|
||||
$('#openclipart')[0].$submit();
|
||||
}
|
||||
}
|
||||
}, ['Last']],
|
||||
}, [ 'Last' ] ],
|
||||
' '
|
||||
]]
|
||||
] ]
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
jml('div', [
|
||||
['style', [
|
||||
[ 'style', [
|
||||
`.control {
|
||||
padding-top: 10px;
|
||||
}`
|
||||
]],
|
||||
['form', {
|
||||
] ],
|
||||
[ 'form', {
|
||||
id: 'openclipart',
|
||||
$custom: {
|
||||
async $submit () {
|
||||
@@ -255,7 +250,7 @@ jml('div', [
|
||||
[
|
||||
'query', 'sort', 'amount', 'page', 'byids'
|
||||
].forEach((prop) => {
|
||||
const {value} = $('#' + prop)[0];
|
||||
const { value } = $('#' + prop)[0];
|
||||
if (value) {
|
||||
url.searchParams.set(prop, value);
|
||||
}
|
||||
@@ -271,12 +266,12 @@ jml('div', [
|
||||
}
|
||||
}, [
|
||||
// Todo: i18nize
|
||||
['fieldset', [
|
||||
['legend', ['Search terms']],
|
||||
['div', {class: 'control'}, [
|
||||
['label', [
|
||||
[ 'fieldset', [
|
||||
[ 'legend', [ 'Search terms' ] ],
|
||||
[ 'div', { class: 'control' }, [
|
||||
[ 'label', [
|
||||
'Query (Title, description, uploader, or tag): ',
|
||||
['input', {id: 'query', name: 'query', placeholder: 'cat', $custom: {
|
||||
[ 'input', { id: 'query', name: 'query', placeholder: 'cat', $custom: {
|
||||
$set (value) {
|
||||
$('#byids')[0].value = '';
|
||||
this.value = value;
|
||||
@@ -285,16 +280,16 @@ jml('div', [
|
||||
change () {
|
||||
$('#byids')[0].value = '';
|
||||
}
|
||||
}}]
|
||||
]]
|
||||
]],
|
||||
['br'],
|
||||
} } ]
|
||||
] ]
|
||||
] ],
|
||||
[ 'br' ],
|
||||
' OR ',
|
||||
['br'],
|
||||
['div', {class: 'control'}, [
|
||||
['label', [
|
||||
[ 'br' ],
|
||||
[ 'div', { class: 'control' }, [
|
||||
[ 'label', [
|
||||
'IDs (single or comma-separated): ',
|
||||
['input', {id: 'byids', name: 'ids', placeholder: '271380, 265741', $custom: {
|
||||
[ 'input', { id: 'byids', name: 'ids', placeholder: '271380, 265741', $custom: {
|
||||
$set (value) {
|
||||
$('#query')[0].value = '';
|
||||
this.value = value;
|
||||
@@ -303,47 +298,47 @@ jml('div', [
|
||||
change () {
|
||||
$('#query')[0].value = '';
|
||||
}
|
||||
}}]
|
||||
]]
|
||||
]]
|
||||
]],
|
||||
['fieldset', [
|
||||
['legend', ['Configuring results']],
|
||||
['div', {class: 'control'}, [
|
||||
['label', [
|
||||
} } ]
|
||||
] ]
|
||||
] ]
|
||||
] ],
|
||||
[ 'fieldset', [
|
||||
[ 'legend', [ 'Configuring results' ] ],
|
||||
[ 'div', { class: 'control' }, [
|
||||
[ 'label', [
|
||||
'Sort by: ',
|
||||
['select', {id: 'sort'}, [
|
||||
[ 'select', { id: 'sort' }, [
|
||||
// Todo: i18nize first values
|
||||
['Date', 'date'],
|
||||
['Downloads', 'downloads'],
|
||||
['Favorited', 'favorites']
|
||||
].map(([text, value = text]) => {
|
||||
return ['option', {value}, [text]];
|
||||
})]
|
||||
]]
|
||||
]],
|
||||
['div', {class: 'control'}, [
|
||||
['label', [
|
||||
[ 'Date', 'date' ],
|
||||
[ 'Downloads', 'downloads' ],
|
||||
[ 'Favorited', 'favorites' ]
|
||||
].map(([ text, value = text ]) => {
|
||||
return [ 'option', { value }, [ text ] ];
|
||||
}) ]
|
||||
] ]
|
||||
] ],
|
||||
[ 'div', { class: 'control' }, [
|
||||
[ 'label', [
|
||||
'Results per page: ',
|
||||
['input', {
|
||||
[ 'input', {
|
||||
id: 'amount', name: 'amount', value: 10,
|
||||
type: 'number', min: 1, max: 200, step: 1, pattern: '\\d+'}]
|
||||
]]
|
||||
]],
|
||||
['div', {class: 'control'}, [
|
||||
['label', [
|
||||
type: 'number', min: 1, max: 200, step: 1, pattern: '\\d+' } ]
|
||||
] ]
|
||||
] ],
|
||||
[ 'div', { class: 'control' }, [
|
||||
[ 'label', [
|
||||
'Page number: ',
|
||||
['input', {
|
||||
[ 'input', {
|
||||
// max: 1, // We'll change this based on available results
|
||||
id: 'page', name: 'page', value: 1, style: 'width: 40px;',
|
||||
type: 'number', min: 1, step: 1, pattern: '\\d+'
|
||||
}]
|
||||
]]
|
||||
]]
|
||||
]],
|
||||
['div', {class: 'control'}, [
|
||||
['input', {type: 'submit'}]
|
||||
]]
|
||||
]],
|
||||
['div', {id: 'results'}]
|
||||
} ]
|
||||
] ]
|
||||
] ]
|
||||
] ],
|
||||
[ 'div', { class: 'control' }, [
|
||||
[ 'input', { type: 'submit' } ]
|
||||
] ]
|
||||
] ],
|
||||
[ 'div', { id: 'results' } ]
|
||||
], body);
|
||||
|
||||