diff --git a/CHANGES.md b/CHANGES.md index e4ae358d..f201ac5c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,7 @@ # SVG-Edit CHANGES +## 7.1.2 +- add the current document title in the toolbar +- allow user extensions to define optional parameters ## 7.1.1 - Fix an issue when moving a text with an existing transformation (issue #689) ## 7.1.0 diff --git a/package-lock.json b/package-lock.json index c8d3c3c7..23f2b8ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,12 @@ "dependencies": { "@babel/polyfill": "7.12.1", "browser-fs-access": "0.24.0", - "canvg": "3.0.9", + "canvg": "3.0.10", "core-js": "3.20.3", "elix": "15.0.1", "html2canvas": "1.4.1", - "i18next": "21.6.7", - "jspdf": "2.5.0", + "i18next": "21.6.10", + "jspdf": "2.5.1", "pathseg": "1.2.1", "regenerator-runtime": "0.13.9", "rollup-plugin-polyfill-node": "0.8.0", @@ -36,16 +36,16 @@ "@rollup/plugin-replace": "3.0.1", "@rollup/plugin-url": "6.1.0", "@web/dev-server": "0.1.29", - "@web/dev-server-rollup": "0.3.14", + "@web/dev-server-rollup": "0.3.15", "babel-plugin-transform-object-rest-spread": "7.0.0-beta.3", "copyfiles": "2.4.1", "core-js-bundle": "3.20.3", "cp-cli": "2.0.0", - "cypress": "9.3.1", + "cypress": "9.4.1", "cypress-multi-reporters": "1.5.0", "cypress-plugin-snapshots": "1.4.4", "jamilih": "0.54.0", - "jsdoc": "3.6.9", + "jsdoc": "3.6.10", "node-static": "0.7.11", "npm-run-all": "4.1.5", "nyc": "15.1.0", @@ -56,10 +56,10 @@ "remark-cli": "10.0.1", "remark-lint-ordered-list-marker-value": "3.1.1", "rimraf": "3.0.2", - "rollup": "2.66.0", + "rollup": "2.66.1", "rollup-plugin-copy": "3.4.0", "rollup-plugin-filesize": "9.1.2", - "rollup-plugin-html": "^0.2.1", + "rollup-plugin-html": "0.2.1", "rollup-plugin-node-polyfills": "0.2.1", "rollup-plugin-progress": "1.1.2", "rollup-plugin-re": "1.0.7", @@ -4442,16 +4442,16 @@ } }, "node_modules/@web/dev-server-rollup": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.3.14.tgz", - "integrity": "sha512-nx55QXh0e3ZtEFQcQ17n2nWIK1H7uGG0OC1S1PnT1akg0eCYMwHgeJe35hKnXecw8YVY93KsvgS/aKAV+on5Iw==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.3.15.tgz", + "integrity": "sha512-hhxvBmNIY19vXeocYB1IBOuhpVpy1L7jbwBarmvC0QJKZsgkxssNTzXJ8iga70c2+H0c/rBz1xUaKuAcov0uOA==", "dev": true, "dependencies": { "@rollup/plugin-node-resolve": "^11.0.1", "@web/dev-server-core": "^0.3.16", "nanocolors": "^0.2.1", "parse5": "^6.0.1", - "rollup": "^2.58.0", + "rollup": "^2.66.1", "whatwg-url": "^11.0.0" }, "engines": { @@ -6072,9 +6072,9 @@ } }, "node_modules/canvg": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.9.tgz", - "integrity": "sha512-rDXcnRPuz4QHoCilMeoTxql+fvGqNAxp+qV/KHD8rOiJSAfVjFclbdUNHD2Uqfthr+VMg17bD2bVuk6F07oLGw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", "dependencies": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", @@ -7211,9 +7211,9 @@ "dev": true }, "node_modules/cypress": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz", - "integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.4.1.tgz", + "integrity": "sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -7254,10 +7254,10 @@ "pretty-bytes": "^5.6.0", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", + "semver": "^7.3.2", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", - "url": "^0.11.0", "yauzl": "^2.10.0" }, "bin": { @@ -7506,6 +7506,21 @@ "node": ">=6" } }, + "node_modules/cypress/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cypress/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -10548,9 +10563,23 @@ } }, "node_modules/i18next": { - "version": "21.6.7", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.7.tgz", - "integrity": "sha512-26dTDa2gBz+vMk6WPf1pxTx3S5HIAptbyODmni/JsN6R1W2WNkGVFXBusUK7T6y1wLeJi5CIrqmQ2gl18vdh3A==", + "version": "21.6.10", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.10.tgz", + "integrity": "sha512-Xw+tEGQ61BF6SXtBlFffhM/YhJKHZf2cyDrcNK/l2dE6yVbkPkSasC3VhkAsHXX30vUJ0yG04WIUtf7UvwjOxg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], "dependencies": { "@babel/runtime": "^7.12.0" } @@ -11654,11 +11683,10 @@ "dev": true }, "node_modules/jsdoc": { - "version": "3.6.9", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.9.tgz", - "integrity": "sha512-bVrM2DT2iLmv6jd2IdTRk67tC4iaSDUicD+47y+cNCYlE8dccd4xZnlANG4M+OmGyV389bABSTKKfoPCOofbKw==", + "version": "3.6.10", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", + "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", "dev": true, - "hasInstallScript": true, "dependencies": { "@babel/parser": "^7.9.4", "@types/markdown-it": "^12.2.3", @@ -11861,9 +11889,9 @@ } }, "node_modules/jspdf": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.0.tgz", - "integrity": "sha512-XT0E2m8A9P1xl7ItA2OUbmhokzbDQEyZEdWQZD2olADiTiBEZGDRiK1J1zWxBRUG2KezQJOZq//GYZTkvEZuJg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", "dependencies": { "@babel/runtime": "^7.14.0", "atob": "^2.1.2", @@ -18456,9 +18484,9 @@ } }, "node_modules/rollup": { - "version": "2.66.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.0.tgz", - "integrity": "sha512-L6mKOkdyP8HK5kKJXaiWG7KZDumPJjuo1P+cfyHOJPNNTK3Moe7zCH5+fy7v8pVmHXtlxorzaBjvkBMB23s98g==", + "version": "2.66.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.1.tgz", + "integrity": "sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==", "bin": { "rollup": "dist/bin/rollup" }, @@ -26746,16 +26774,16 @@ } }, "@web/dev-server-rollup": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.3.14.tgz", - "integrity": "sha512-nx55QXh0e3ZtEFQcQ17n2nWIK1H7uGG0OC1S1PnT1akg0eCYMwHgeJe35hKnXecw8YVY93KsvgS/aKAV+on5Iw==", + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.3.15.tgz", + "integrity": "sha512-hhxvBmNIY19vXeocYB1IBOuhpVpy1L7jbwBarmvC0QJKZsgkxssNTzXJ8iga70c2+H0c/rBz1xUaKuAcov0uOA==", "dev": true, "requires": { "@rollup/plugin-node-resolve": "^11.0.1", "@web/dev-server-core": "^0.3.16", "nanocolors": "^0.2.1", "parse5": "^6.0.1", - "rollup": "^2.58.0", + "rollup": "^2.66.1", "whatwg-url": "^11.0.0" }, "dependencies": { @@ -28049,9 +28077,9 @@ "dev": true }, "canvg": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.9.tgz", - "integrity": "sha512-rDXcnRPuz4QHoCilMeoTxql+fvGqNAxp+qV/KHD8rOiJSAfVjFclbdUNHD2Uqfthr+VMg17bD2bVuk6F07oLGw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", "requires": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", @@ -28977,9 +29005,9 @@ } }, "cypress": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz", - "integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.4.1.tgz", + "integrity": "sha512-+JgMG9uT+QFx97JU9kOHE3jO3+0UdkQ9H1oCBiC7A74qme7Jkdy2sYDBCPjjGczutnWnGUTMRlwiNMP/Uq6LrQ==", "dev": true, "requires": { "@cypress/request": "^2.88.10", @@ -29019,10 +29047,10 @@ "pretty-bytes": "^5.6.0", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", + "semver": "^7.3.2", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", - "url": "^0.11.0", "yauzl": "^2.10.0" }, "dependencies": { @@ -29156,6 +29184,15 @@ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -31654,9 +31691,9 @@ } }, "i18next": { - "version": "21.6.7", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.7.tgz", - "integrity": "sha512-26dTDa2gBz+vMk6WPf1pxTx3S5HIAptbyODmni/JsN6R1W2WNkGVFXBusUK7T6y1wLeJi5CIrqmQ2gl18vdh3A==", + "version": "21.6.10", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.10.tgz", + "integrity": "sha512-Xw+tEGQ61BF6SXtBlFffhM/YhJKHZf2cyDrcNK/l2dE6yVbkPkSasC3VhkAsHXX30vUJ0yG04WIUtf7UvwjOxg==", "requires": { "@babel/runtime": "^7.12.0" } @@ -32503,9 +32540,9 @@ "dev": true }, "jsdoc": { - "version": "3.6.9", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.9.tgz", - "integrity": "sha512-bVrM2DT2iLmv6jd2IdTRk67tC4iaSDUicD+47y+cNCYlE8dccd4xZnlANG4M+OmGyV389bABSTKKfoPCOofbKw==", + "version": "3.6.10", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.10.tgz", + "integrity": "sha512-IdQ8ppSo5LKZ9o3M+LKIIK8i00DIe5msDvG3G81Km+1dhy0XrOWD0Ji8H61ElgyEj/O9KRLokgKbAM9XX9CJAg==", "dev": true, "requires": { "@babel/parser": "^7.9.4", @@ -32672,9 +32709,9 @@ } }, "jspdf": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.0.tgz", - "integrity": "sha512-XT0E2m8A9P1xl7ItA2OUbmhokzbDQEyZEdWQZD2olADiTiBEZGDRiK1J1zWxBRUG2KezQJOZq//GYZTkvEZuJg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", "requires": { "@babel/runtime": "^7.14.0", "atob": "^2.1.2", @@ -37608,9 +37645,9 @@ } }, "rollup": { - "version": "2.66.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.0.tgz", - "integrity": "sha512-L6mKOkdyP8HK5kKJXaiWG7KZDumPJjuo1P+cfyHOJPNNTK3Moe7zCH5+fy7v8pVmHXtlxorzaBjvkBMB23s98g==", + "version": "2.66.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.66.1.tgz", + "integrity": "sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==", "requires": { "fsevents": "~2.3.2" }, diff --git a/package.json b/package.json index 39905d08..c4228a32 100644 --- a/package.json +++ b/package.json @@ -83,12 +83,12 @@ "dependencies": { "@babel/polyfill": "7.12.1", "browser-fs-access": "0.24.0", - "canvg": "3.0.9", + "canvg": "3.0.10", "core-js": "3.20.3", "elix": "15.0.1", "html2canvas": "1.4.1", - "i18next": "21.6.7", - "jspdf": "2.5.0", + "i18next": "21.6.10", + "jspdf": "2.5.1", "pathseg": "1.2.1", "regenerator-runtime": "0.13.9", "rollup-plugin-polyfill-node": "0.8.0", @@ -108,16 +108,16 @@ "@rollup/plugin-replace": "3.0.1", "@rollup/plugin-url": "6.1.0", "@web/dev-server": "0.1.29", - "@web/dev-server-rollup": "0.3.14", + "@web/dev-server-rollup": "0.3.15", "babel-plugin-transform-object-rest-spread": "7.0.0-beta.3", "copyfiles": "2.4.1", "core-js-bundle": "3.20.3", "cp-cli": "2.0.0", - "cypress": "9.3.1", + "cypress": "9.4.1", "cypress-multi-reporters": "1.5.0", "cypress-plugin-snapshots": "1.4.4", "jamilih": "0.54.0", - "jsdoc": "3.6.9", + "jsdoc": "3.6.10", "node-static": "0.7.11", "npm-run-all": "4.1.5", "nyc": "15.1.0", @@ -128,10 +128,10 @@ "remark-cli": "10.0.1", "remark-lint-ordered-list-marker-value": "3.1.1", "rimraf": "3.0.2", - "rollup": "2.66.0", + "rollup": "2.66.1", "rollup-plugin-copy": "3.4.0", "rollup-plugin-filesize": "9.1.2", - "rollup-plugin-html": "^0.2.1", + "rollup-plugin-html": "0.2.1", "rollup-plugin-node-polyfills": "0.2.1", "rollup-plugin-progress": "1.1.2", "rollup-plugin-re": "1.0.7", diff --git a/src/editor/ConfigObj.js b/src/editor/ConfigObj.js index a5b05d39..68b950e9 100644 --- a/src/editor/ConfigObj.js +++ b/src/editor/ConfigObj.js @@ -351,6 +351,7 @@ export default class ConfigObj { const cached = this.editor.storage.getItem(name) if (cached) { this.editor.loadFromString(cached) + this.editor.topPanel.updateTitle(this.editor.storage.getItem(`title-${name}`) ?? 'untitled.svg') } } diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 41012187..179471c4 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -1,17 +1,17 @@ /* globals seConfirm seAlert */ /** -* The main module for the visual SVG this. -* -* @license MIT -* -* @copyright 2010 Alexis Deveria -* 2010 Pavol Rusnak -* 2010 Jeff Schiller -* 2010 Narendra Sisodiya -* 2014 Brett Zamir -* 2020 OptimistikSAS -* @module SVGEditor -*/ + * The main module for the visual SVG this. + * + * @license MIT + * + * @copyright 2010 Alexis Deveria + * 2010 Pavol Rusnak + * 2010 Jeff Schiller + * 2010 Narendra Sisodiya + * 2014 Brett Zamir + * 2020 OptimistikSAS + * @module SVGEditor + */ import './components/index.js' import './dialogs/index.js' @@ -28,7 +28,7 @@ import LayersPanel from './panels/LayersPanel.js' import MainMenu from './MainMenu.js' import { getParentsUntil } from './components/jgraduate/Util.js' -const { $id, $qa, $click, decode64, blankPageObjectURL } = SvgCanvas +const { $id, $click, decode64, blankPageObjectURL } = SvgCanvas /** * @@ -40,18 +40,22 @@ class Editor extends EditorStartup { constructor (div = null) { super(div) /** - * @type {boolean} - */ + * @type {boolean} + */ this.langChanged = false /** - * @type {boolean} - */ + * @type {boolean} + */ this.showSaveWarning = false /** * Will be set to a boolean by `ext-storage.js` * @type {"ignore"|"waiting"|"closed"} - */ + */ this.storagePromptState = 'ignore' + /** + * document title + */ + this.title = 'untitled.svg' this.svgCanvas = null this.$click = $click @@ -136,50 +140,50 @@ class Editor extends EditorStartup { } /** - * All methods are optional. - * @interface module:SVGthis.CustomHandler - * @type {PlainObject} - */ + * All methods are optional. + * @interface module:SVGthis.CustomHandler + * @type {PlainObject} + */ /** - * Its responsibilities are: - * - invoke a file chooser dialog in 'open' mode - * - let user pick a SVG file - * - calls [svgCanvas.setSvgString()]{@link module:svgcanvas.SvgCanvas#setSvgString} with the string contents of that file. - * Not passed any parameters. - * @function module:SVGthis.CustomHandler#open - * @returns {void} - */ + * Its responsibilities are: + * - invoke a file chooser dialog in 'open' mode + * - let user pick a SVG file + * - calls [svgCanvas.setSvgString()]{@link module:svgcanvas.SvgCanvas#setSvgString} with the string contents of that file. + * Not passed any parameters. + * @function module:SVGthis.CustomHandler#open + * @returns {void} + */ /** - * Its responsibilities are: - * - accept the string contents of the current document - * - invoke a file chooser dialog in 'save' mode - * - save the file to location chosen by the user. - * @function module:SVGthis.CustomHandler#save - * @param {external:Window} win - * @param {module:svgcanvas.SvgCanvas#event:saved} svgStr A string of the SVG - * @listens module:svgcanvas.SvgCanvas#event:saved - * @returns {void} - */ + * Its responsibilities are: + * - accept the string contents of the current document + * - invoke a file chooser dialog in 'save' mode + * - save the file to location chosen by the user. + * @function module:SVGthis.CustomHandler#save + * @param {external:Window} win + * @param {module:svgcanvas.SvgCanvas#event:saved} svgStr A string of the SVG + * @listens module:svgcanvas.SvgCanvas#event:saved + * @returns {void} + */ /** - * Its responsibilities (with regard to the object it is supplied in its 2nd argument) are: - * - inform user of any issues supplied via the "issues" property - * - convert the "svg" property SVG string into an image for export; - * utilize the properties "type" (currently 'PNG', 'JPEG', 'BMP', - * 'WEBP', 'PDF'), "mimeType", and "quality" (for 'JPEG' and 'WEBP' - * types) to determine the proper output. - * @function module:SVGthis.CustomHandler#exportImage - * @param {external:Window} win - * @param {module:svgcanvas.SvgCanvas#event:exported} data - * @listens module:svgcanvas.SvgCanvas#event:exported - * @returns {void} - */ + * Its responsibilities (with regard to the object it is supplied in its 2nd argument) are: + * - inform user of any issues supplied via the "issues" property + * - convert the "svg" property SVG string into an image for export; + * utilize the properties "type" (currently 'PNG', 'JPEG', 'BMP', + * 'WEBP', 'PDF'), "mimeType", and "quality" (for 'JPEG' and 'WEBP' + * types) to determine the proper output. + * @function module:SVGthis.CustomHandler#exportImage + * @param {external:Window} win + * @param {module:svgcanvas.SvgCanvas#event:exported} data + * @listens module:svgcanvas.SvgCanvas#event:exported + * @returns {void} + */ /** - * @function module:SVGthis.CustomHandler#exportPDF - * @param {external:Window} win - * @param {module:svgcanvas.SvgCanvas#event:exportedPDF} data - * @listens module:svgcanvas.SvgCanvas#event:exportedPDF - * @returns {void} - */ + * @function module:SVGthis.CustomHandler#exportPDF + * @param {external:Window} win + * @param {module:svgcanvas.SvgCanvas#event:exportedPDF} data + * @listens module:svgcanvas.SvgCanvas#event:exportedPDF + * @returns {void} + */ /** * @function module:SVGthis.randomizeIds @@ -192,12 +196,12 @@ class Editor extends EditorStartup { /** @lends module:SVGEditor~Actions */ /** - * @returns {void} - */ + * @returns {void} + */ setAll () { const keyHandler = {} // will contain the action for each pressed key - this.shortcuts.forEach((shortcut) => { + this.shortcuts.forEach(shortcut => { // Bind function to shortcut key if (shortcut.key) { // Set shortcut based on options @@ -205,20 +209,26 @@ class Editor extends EditorStartup { let pd = false if (Array.isArray(shortcut.key)) { keyval = shortcut.key[0] - if (shortcut.key.length > 1) { pd = shortcut.key[1] } + if (shortcut.key.length > 1) { + pd = shortcut.key[1] + } } keyval = String(keyval) const { fn } = shortcut - keyval.split('/').forEach((key) => { keyHandler[key] = { fn, pd } }) + keyval.split('/').forEach(key => { + keyHandler[key] = { fn, pd } + }) } return true }) // register the keydown event - document.addEventListener('keydown', (e) => { + 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.altKey) ? 'alt+' : ''}${(e.shiftKey) ? 'shift+' : ''}${(e.metaKey) ? 'meta+' : ''}${(e.ctrlKey) ? 'ctrl+' : ''}${e.key.toLowerCase()}` + const key = `${e.altKey ? 'alt+' : ''}${e.shiftKey ? 'shift+' : ''}${ + e.metaKey ? 'meta+' : '' + }${e.ctrlKey ? 'ctrl+' : ''}${e.key.toLowerCase()}` // return if no shortcut defined for this key if (!keyHandler[key]) return // launch associated handler and preventDefault if necessary @@ -265,46 +275,11 @@ class Editor extends EditorStartup { } /** - * @returns {void} - */ - setTitles () { - // Tooltips not directly associated with a single function - const keyAssocs = { - '4/Shift+4': 'tools_rect', - '5/Shift+5': 'tools_ellipse' - } - Object.entries(keyAssocs).forEach(([keyval, sel]) => { - const parentsElements = this.getParents($id(sel), $id('main_menu')) - const menu = (parentsElements.length) - - $qa(sel).forEach((element) => { - const t = (menu) ? element.textContent.split(' [')[0] : element.title.split(' [')[0] - let keyStr = '' - // Shift+Up - keyval.split('/').forEach((key, i) => { - const modBits = key.split('+') - let mod = '' - if (modBits.length > 1) { - mod = modBits[0] + '+' - key = modBits[1] - } - keyStr += (i ? '/' : '') + mod + (this.i18next.t('key_' + key) || key) - }) - if (menu) { - this.lastChild.textContent = t + ' [' + keyStr + ']' - } else { - this.title = t + ' [' + keyStr + ']' - } - }) - }) - } - - /** - * @param {string} sel Selector to match - * @returns {module:SVGthis.ToolButton} - */ + * @param {string} sel Selector to match + * @returns {module:SVGthis.ToolButton} + */ getButtonData (sel) { - return Object.values(this.shortcuts).find((btn) => { + return Object.values(this.shortcuts).find(btn => { return btn.sel === sel }) } @@ -328,12 +303,18 @@ class Editor extends EditorStartup { this.exportWindow.location.href = data.bloburl || data.datauri const done = this.configObj.pref('export_notice_done') if (done !== 'all') { - let note = this.i18next.t('notification.saveFromBrowser', { type: data.type }) + let note = this.i18next.t('notification.saveFromBrowser', { + type: data.type + }) // Check if there are issues if (issues.length) { const pre = '\n \u2022 ' - note += ('\n\n' + this.i18next.t('notification..noteTheseIssues') + pre + issues.join(pre)) + note += + '\n\n' + + this.i18next.t('notification..noteTheseIssues') + + pre + + issues.join(pre) } // Note that this will also prevent the notice even though new issues may appear later. @@ -359,19 +340,22 @@ class Editor extends EditorStartup { } /** - * @function module:SVGthis.updateCanvas - * @param {boolean} center - * @param {module:math.XYObject} newCtr - * @returns {void} - */ + * @function module:SVGthis.updateCanvas + * @param {boolean} center + * @param {module:math.XYObject} newCtr + * @returns {void} + */ updateCanvas (center, newCtr) { const zoom = this.svgCanvas.getZoom() const { workarea } = this const cnvs = $id('svgcanvas') let w = parseFloat(getComputedStyle(workarea, null).width.replace('px', '')) - let h = parseFloat(getComputedStyle(workarea, null).height.replace('px', '')) - const wOrig = w; const hOrig = h + let h = parseFloat( + getComputedStyle(workarea, null).height.replace('px', '') + ) + const wOrig = w + const hOrig = h const oldCtr = { x: workarea.scrollLeft + wOrig / 2, y: workarea.scrollTop + hOrig / 2 @@ -386,8 +370,10 @@ class Editor extends EditorStartup { workarea.style.overflow = 'scroll' } - const oldCanY = parseFloat(getComputedStyle(cnvs, null).height.replace('px', '')) / 2 - const oldCanX = parseFloat(getComputedStyle(cnvs, null).width.replace('px', '')) / 2 + const oldCanY = + parseFloat(getComputedStyle(cnvs, null).height.replace('px', '')) / 2 + const oldCanX = + parseFloat(getComputedStyle(cnvs, null).width.replace('px', '')) / 2 cnvs.style.width = w + 'px' cnvs.style.height = h + 'px' @@ -418,7 +404,10 @@ class Editor extends EditorStartup { if (center) { // Go to top-left for larger documents - if (this.svgCanvas.contentW > parseFloat(getComputedStyle(workarea, null).width.replace('px', ''))) { + if ( + this.svgCanvas.contentW > + parseFloat(getComputedStyle(workarea, null).width.replace('px', '')) + ) { // Top-left workarea.scrollLeft = offset.x - 10 workarea.scrollTop = offset.y - 10 @@ -436,15 +425,18 @@ class Editor extends EditorStartup { workarea.scroll() } - if (this.configObj.urldata.storagePrompt !== true && this.storagePromptState === 'ignore') { + if ( + this.configObj.urldata.storagePrompt !== true && + this.storagePromptState === 'ignore' + ) { if ($id('dialog_box') != null) $id('dialog_box').style.display = 'none' } } /** - * - * @returns {void} - */ + * + * @returns {void} + */ updateWireFrame () { const rule = ` #workarea.wireframe #svgcontent * { @@ -452,34 +444,21 @@ class Editor extends EditorStartup { } ` if (document.querySelectorAll('#wireframe_rules').length > 0) { - document.querySelector('#wireframe_rules').textContent = (this.workarea.classList.contains('wireframe') ? rule : '') + document.querySelector( + '#wireframe_rules' + ).textContent = this.workarea.classList.contains('wireframe') ? rule : '' } } - /** - * @param {string} [title=svgCanvas.getDocumentTitle()] - * @returns {void} - */ - updateTitle (title) { - title = title || this.svgCanvas.getDocumentTitle() - const newTitle = document.querySelector('title').text + (title ? ': ' + title : '') - - // Remove title update with current context info, isn't really necessary - // if (this.curContext) { - // new_title = new_title + this.curContext; - // } - document.querySelector('title').textContent = newTitle - } - // called when we've selected a different element /** - * - * @param {external:Window} win - * @param {module:svgcanvas.SvgCanvas#event:selected} elems Array of elements that were selected - * @listens module:svgcanvas.SvgCanvas#event:selected - * @fires module:svgcanvas.SvgCanvas#event:ext_selectedChanged - * @returns {void} - */ + * + * @param {external:Window} win + * @param {module:svgcanvas.SvgCanvas#event:selected} elems Array of elements that were selected + * @listens module:svgcanvas.SvgCanvas#event:selected + * @fires module:svgcanvas.SvgCanvas#event:ext_selectedChanged + * @returns {void} + */ selectedChanged (win, elems) { const mode = this.svgCanvas.getMode() if (mode === 'select') { @@ -487,8 +466,8 @@ class Editor extends EditorStartup { } const isNode = mode === 'pathedit' // if this.elems[1] is present, then we have more than one element - this.selectedElement = (elems.length === 1 || !elems[1] ? elems[0] : null) - this.multiselected = (elems.length >= 2 && elems[1]) + this.selectedElement = elems.length === 1 || !elems[1] ? elems[0] : null + this.multiselected = elems.length >= 2 && elems[1] if (this.selectedElement && !isNode) { this.topPanel.update() } // if (elem) @@ -496,11 +475,14 @@ class Editor extends EditorStartup { // Deal with pathedit mode this.topPanel.togglePathEditMode(isNode, elems) this.topPanel.updateContextPanel() - this.svgCanvas.runExtensions('selectedChanged', /** @type {module:svgcanvas.SvgCanvas#event:ext_selectedChanged} */ { - elems, - selectedElement: this.selectedElement, - multiselected: this.multiselected - }) + this.svgCanvas.runExtensions( + 'selectedChanged', + /** @type {module:svgcanvas.SvgCanvas#event:ext_selectedChanged} */ { + elems, + selectedElement: this.selectedElement, + multiselected: this.multiselected + } + ) } // Call when part of element is in process of changing, generally @@ -520,21 +502,26 @@ class Editor extends EditorStartup { return } - this.multiselected = (elems.length >= 2 && elems[1]) + this.multiselected = elems.length >= 2 && elems[1] // Only updating fields for single elements for now if (!this.multiselected) { switch (mode) { case 'rotate': { const ang = this.svgCanvas.getRotationAngle(elem) - $id('angle').value = ang; - (ang === 0) ? $id('tool_reorient').classList.add('disabled') : $id('tool_reorient').classList.remove('disabled') + $id('angle').value = ang + ang === 0 + ? $id('tool_reorient').classList.add('disabled') + : $id('tool_reorient').classList.remove('disabled') break } } } - this.svgCanvas.runExtensions('elementTransition', /** @type {module:svgcanvas.SvgCanvas#event:ext_elementTransition} */ { - elems - }) + this.svgCanvas.runExtensions( + 'elementTransition', + /** @type {module:svgcanvas.SvgCanvas#event:ext_elementTransition} */ { + elems + } + ) } // called when any element has changed @@ -551,8 +538,8 @@ class Editor extends EditorStartup { this.leftPanel.clickSelect() } - elems.forEach((elem) => { - const isSvgElem = (elem?.tagName === 'svg') + elems.forEach(elem => { + const isSvgElem = elem?.tagName === 'svg' if (isSvgElem || this.svgCanvas.isLayer(elem)) { this.layersPanel.populateLayers() // if the element changed was the svg, then it could be a resolution change @@ -582,9 +569,12 @@ class Editor extends EditorStartup { this.bottomPanel.updateColorpickers() } - this.svgCanvas.runExtensions('elementChanged', /** @type {module:svgcanvas.SvgCanvas#event:ext_elementChanged} */ { - elems - }) + this.svgCanvas.runExtensions( + 'elementChanged', + /** @type {module:svgcanvas.SvgCanvas#event:ext_elementChanged} */ { + elems + } + ) } /** @@ -595,28 +585,38 @@ class Editor extends EditorStartup { } /** - * @typedef {PlainObject} module:SVGthis.BBoxObjectWithFactor (like `DOMRect`) - * @property {Float} x - * @property {Float} y - * @property {Float} width - * @property {Float} height - * @property {Float} [factor] Needed if width or height are 0 - * @property {Float} [zoom] - * @see module:svgcanvas.SvgCanvas#event:zoomed - */ + * @typedef {PlainObject} module:SVGthis.BBoxObjectWithFactor (like `DOMRect`) + * @property {Float} x + * @property {Float} y + * @property {Float} width + * @property {Float} height + * @property {Float} [factor] Needed if width or height are 0 + * @property {Float} [zoom] + * @see module:svgcanvas.SvgCanvas#event:zoomed + */ /** - * @function module:svgcanvas.SvgCanvas#zoomChanged - * @param {external:Window} win - * @param {module:svgcanvas.SvgCanvas#event:zoomed} bbox - * @param {boolean} autoCenter - * @listens module:svgcanvas.SvgCanvas#event:zoomed - * @returns {void} - */ + * @function module:svgcanvas.SvgCanvas#zoomChanged + * @param {external:Window} win + * @param {module:svgcanvas.SvgCanvas#event:zoomed} bbox + * @param {boolean} autoCenter + * @listens module:svgcanvas.SvgCanvas#event:zoomed + * @returns {void} + */ zoomChanged (win, bbox, autoCenter) { const scrbar = 15 - const zInfo = this.svgCanvas.setBBoxZoom(bbox, parseFloat(getComputedStyle(this.workarea, null).width.replace('px', '')) - scrbar, parseFloat(getComputedStyle(this.workarea, null).height.replace('px', '')) - scrbar) - if (!zInfo) { return } + const zInfo = this.svgCanvas.setBBoxZoom( + bbox, + parseFloat( + getComputedStyle(this.workarea, null).width.replace('px', '') + ) - scrbar, + parseFloat( + getComputedStyle(this.workarea, null).height.replace('px', '') + ) - scrbar + ) + if (!zInfo) { + return + } const zoomlevel = zInfo.zoom const bb = zInfo.bbox @@ -630,10 +630,10 @@ class Editor extends EditorStartup { if (autoCenter) { this.updateCanvas() } else { - this.updateCanvas( - false, - { x: bb.x * zoomlevel + (bb.width * zoomlevel) / 2, y: bb.y * zoomlevel + (bb.height * zoomlevel) / 2 } - ) + this.updateCanvas(false, { + x: bb.x * zoomlevel + (bb.width * zoomlevel) / 2, + y: bb.y * zoomlevel + (bb.height * zoomlevel) / 2 + }) } if (this.svgCanvas.getMode() === 'zoom' && bb.width) { @@ -654,12 +654,18 @@ class Editor extends EditorStartup { let linkStr = '' if (context) { let str = '' - linkStr = '' + this.svgCanvas.getCurrentDrawing().getCurrentLayerName() + '' + linkStr = + '' + + this.svgCanvas.getCurrentDrawing().getCurrentLayerName() + + '' const parentsUntil = getParentsUntil(context, '#svgcontent') parentsUntil.forEach(function (parent) { if (parent.id) { str += ' > ' + parent.id - linkStr += (parent !== context) ? ` > ${parent.id}` : ` > ${parent.id}` + linkStr += + parent !== context + ? ` > ${parent.id}` + : ` > ${parent.id}` } }) @@ -669,27 +675,27 @@ class Editor extends EditorStartup { } $id('cur_context_panel').style.display = context ? 'block' : 'none' $id('cur_context_panel').innerHTML = linkStr - - this.updateTitle() } /** - * @function module:SVGEditor.setIcon - * @param {string|Element|external:jQuery} elem - * @param {string|external:jQuery} iconId - * @returns {void} - */ + * @function module:SVGEditor.setIcon + * @param {string|Element|external:jQuery} elem + * @param {string|external:jQuery} iconId + * @returns {void} + */ setIcon (elem, iconId) { const img = document.createElement('img') img.src = this.configObj.curConfig.imgPath + iconId - const icon = (typeof iconId === 'string') ? img : iconId.cloneNode(true) + const icon = typeof iconId === 'string' ? img : iconId.cloneNode(true) if (!icon) { // Todo: Investigate why this still occurs in some cases console.warn('NOTE: Icon image missing: ' + iconId) return } // empty() - while ($id(elem).firstChild) { $id(elem).removeChild($id(elem).firstChild) } + while ($id(elem).firstChild) { + $id(elem).removeChild($id(elem).firstChild) + } $id(elem).appendChild(icon) } @@ -706,9 +712,9 @@ class Editor extends EditorStartup { let cbCalled = false /** - * - * @returns {void} - */ + * + * @returns {void} + */ const runCallback = () => { if (ext.callback && !cbCalled) { cbCalled = true @@ -723,9 +729,9 @@ class Editor extends EditorStartup { } /** - * @param {Float} multiplier - * @returns {void} - */ + * @param {Float} multiplier + * @returns {void} + */ zoomImage (multiplier) { const resolution = this.svgCanvas.getResolution() multiplier = multiplier ? resolution.zoom * multiplier : 1 @@ -737,9 +743,9 @@ class Editor extends EditorStartup { } /** - * - * @returns {void} - */ + * + * @returns {void} + */ cutSelected () { if (this.selectedElement || this.multiselected) { this.svgCanvas.cutSelectedElements() @@ -747,9 +753,9 @@ class Editor extends EditorStartup { } /** - * @function copySelected - * @returns {void} - */ + * @function copySelected + * @returns {void} + */ copySelected () { if (this.selectedElement || this.multiselected) { this.svgCanvas.copySelectedElements() @@ -757,21 +763,31 @@ class Editor extends EditorStartup { } /** - * - * @returns {void} - */ + * + * @returns {void} + */ pasteInCenter () { const { workarea } = this const zoom = this.svgCanvas.getZoom() - const x = (workarea.scrollLeft + parseFloat(getComputedStyle(workarea, null).width.replace('px', '')) / 2) / zoom - this.svgCanvas.contentW - const y = (workarea.scrollTop + parseFloat(getComputedStyle(workarea, null).height.replace('px', '')) / 2) / zoom - this.svgCanvas.contentH + const x = + (workarea.scrollLeft + + parseFloat(getComputedStyle(workarea, null).width.replace('px', '')) / + 2) / + zoom - + this.svgCanvas.contentW + const y = + (workarea.scrollTop + + parseFloat(getComputedStyle(workarea, null).height.replace('px', '')) / + 2) / + zoom - + this.svgCanvas.contentH this.svgCanvas.pasteElements('point', x, y) } /** - * @param {"Up"|"Down"} dir - * @returns {void} - */ + * @param {"Up"|"Down"} dir + * @returns {void} + */ moveUpDownSelected (dir) { if (this.selectedElement) { this.svgCanvas.moveUpDownSelected(dir) @@ -779,15 +795,16 @@ class Editor extends EditorStartup { } /** - * @param {Float} dx - * @param {Float} dy - * @returns {void} - */ + * @param {Float} dx + * @param {Float} dy + * @returns {void} + */ moveSelected (dx, dy) { if (this.selectedElement || this.multiselected) { if (this.configObj.curConfig.gridSnapping) { // Use grid snap value regardless of zoom level - const multi = this.svgCanvas.getZoom() * this.configObj.curConfig.snappingStep + const multi = + this.svgCanvas.getZoom() * this.configObj.curConfig.snappingStep dx *= multi dy *= multi } @@ -796,47 +813,51 @@ class Editor extends EditorStartup { } /** - * - * @returns {void} - */ + * + * @returns {void} + */ selectNext () { this.svgCanvas.cycleElement(1) } /** - * - * @returns {void} - */ + * + * @returns {void} + */ selectPrev () { this.svgCanvas.cycleElement(0) } /** - * @param {0|1} cw - * @param {Integer} step - * @returns {void} - */ + * @param {0|1} cw + * @param {Integer} step + * @returns {void} + */ rotateSelected (cw, step) { - if (!this.selectedElement || this.multiselected) { return } - if (!cw) { step *= -1 } + if (!this.selectedElement || this.multiselected) { + return + } + if (!cw) { + step *= -1 + } const angle = Number.parseFloat($id('angle').value) + step this.svgCanvas.setRotationAngle(angle) this.topPanel.updateContextPanel() } /** - * - * @returns {void} - */ + * + * @returns {void} + */ hideSourceEditor () { const $editorDialog = $id('se-svg-editor-dialog') $editorDialog.setAttribute('dialog', 'closed') } /** - * @param {Event} e - * @returns {void} Resolves to `undefined` - */ + * @param {Event} e + * @returns {void} Resolves to `undefined` + */ async saveSourceEditor (e) { const $editorDialog = $id('se-svg-editor-dialog') if ($editorDialog.getAttribute('dialog') !== 'open') return @@ -845,11 +866,12 @@ class Editor extends EditorStartup { this.hideSourceEditor() this.zoomImage() this.layersPanel.populateLayers() - this.updateTitle() } if (!this.svgCanvas.setSvgString(e.detail.value)) { - const ok = await seConfirm(this.i18next.t('notification.QerrorsRevertToSource')) + const ok = await seConfirm( + this.i18next.t('notification.QerrorsRevertToSource') + ) if (ok === false || ok === 'Cancel') { return } @@ -861,9 +883,9 @@ class Editor extends EditorStartup { } /** - * @param {Event} e - * @returns {void} Resolves to `undefined` - */ + * @param {Event} e + * @returns {void} Resolves to `undefined` + */ cancelOverlays (e) { if ($id('dialog_box') != null) $id('dialog_box').style.display = 'none' const $editorDialog = $id('se-svg-editor-dialog') @@ -878,7 +900,9 @@ class Editor extends EditorStartup { if (editingsource) { const origSource = this.svgCanvas.getSvgString() if (origSource !== e.detail.value) { - const ok = seConfirm(this.i18next.t('notification.QignoreSourceChanges')) + const ok = seConfirm( + this.i18next.t('notification.QignoreSourceChanges') + ) if (ok) { this.hideSourceEditor() } @@ -895,8 +919,13 @@ class Editor extends EditorStartup { let svgeditClipboard try { svgeditClipboard = this.localStorage.getItem('svgedit_clipboard') - } catch (err) { /* empty fn */ } - this.canvMenu.setAttribute((svgeditClipboard ? 'en' : 'dis') + 'ablemenuitems', '#paste,#paste_in_place') + } catch (err) { + /* empty fn */ + } + this.canvMenu.setAttribute( + (svgeditClipboard ? 'en' : 'dis') + 'ablemenuitems', + '#paste,#paste_in_place' + ) } /** @@ -944,43 +973,51 @@ class Editor extends EditorStartup { } /** - * @function module:SVGthis.setLang - * @param {string} lang The language code - * @param {module:locale.LocaleStrings} allStrings See {@tutorial LocaleDocs} - * @fires module:svgcanvas.SvgCanvas#event:ext_langReady - * @fires module:svgcanvas.SvgCanvas#event:ext_langChanged - * @returns {void} A Promise which resolves to `undefined` - */ + * @function module:SVGthis.setLang + * @param {string} lang The language code + * @param {module:locale.LocaleStrings} allStrings See {@tutorial LocaleDocs} + * @fires module:svgcanvas.SvgCanvas#event:ext_langReady + * @fires module:svgcanvas.SvgCanvas#event:ext_langChanged + * @returns {void} A Promise which resolves to `undefined` + */ setLang (lang) { this.langChanged = true this.configObj.pref('lang', lang) const $editDialog = $id('se-edit-prefs') $editDialog.setAttribute('lang', lang) - const oldLayerName = ($id('#layerlist')) ? $id('#layerlist').querySelector('tr.layersel td.layername').textContent : '' - const renameLayer = (oldLayerName === this.i18next.t('notification.common.layer') + ' 1') + const oldLayerName = $id('#layerlist') + ? $id('#layerlist').querySelector('tr.layersel td.layername').textContent + : '' + const renameLayer = + oldLayerName === this.i18next.t('notification.common.layer') + ' 1' this.setTitles() if (renameLayer) { - this.svgCanvas.renameCurrentLayer(this.i18next.t('notification.common.layer') + ' 1') + this.svgCanvas.renameCurrentLayer( + this.i18next.t('notification.common.layer') + ' 1' + ) this.layersPanel.populateLayers() } - this.svgCanvas.runExtensions('langChanged', /** @type {module:svgcanvas.SvgCanvas#event:ext_langChanged} */ lang) + this.svgCanvas.runExtensions( + 'langChanged', + /** @type {module:svgcanvas.SvgCanvas#event:ext_langChanged} */ lang + ) } /** -* @callback module:SVGthis.ReadyCallback -* @returns {Promise|void} -*/ + * @callback module:SVGthis.ReadyCallback + * @returns {Promise|void} + */ /** -* Queues a callback to be invoked when the editor is ready (or -* to be invoked immediately if it is already ready--i.e., -* if `runCallbacks` has been run). -* @function module:SVGthis.ready -* @param {module:SVGthis.ReadyCallback} cb Callback to be queued to invoke -* @returns {Promise} Resolves when all callbacks, including the supplied have resolved -*/ + * Queues a callback to be invoked when the editor is ready (or + * to be invoked immediately if it is already ready--i.e., + * if `runCallbacks` has been run). + * @function module:SVGthis.ready + * @param {module:SVGthis.ReadyCallback} cb Callback to be queued to invoke + * @returns {Promise} Resolves when all callbacks, including the supplied have resolved + */ ready (cb) { return new Promise((resolve, reject) => { if (this.isReady) { @@ -992,15 +1029,17 @@ class Editor extends EditorStartup { } /** -* Invokes the callbacks previous set by `svgthis.ready` -* @function module:SVGthis.runCallbacks -* @returns {Promise} Resolves to `undefined` if all callbacks succeeded and rejects otherwise -*/ + * Invokes the callbacks previous set by `svgthis.ready` + * @function module:SVGthis.runCallbacks + * @returns {Promise} Resolves to `undefined` if all callbacks succeeded and rejects otherwise + */ async runCallbacks () { try { - await Promise.all(this.callbacks.map(([cb]) => { - return cb() - })) + await Promise.all( + this.callbacks.map(([cb]) => { + return cb() + }) + ) } catch (err) { this.callbacks.forEach(([, , reject]) => { reject() @@ -1014,12 +1053,12 @@ class Editor extends EditorStartup { } /** - * @function module:SVGthis.loadFromString - * @param {string} str The SVG string to load - * @param {PlainObject} [opts={}] - * @param {boolean} [opts.noAlert=false] Option to avoid alert to user and instead get rejected promise - * @returns {Promise} - */ + * @function module:SVGthis.loadFromString + * @param {string} str The SVG string to load + * @param {PlainObject} [opts={}] + * @param {boolean} [opts.noAlert=false] Option to avoid alert to user and instead get rejected promise + * @returns {Promise} + */ loadFromString (str, { noAlert } = {}) { return this.ready(async () => { try { @@ -1033,25 +1072,25 @@ class Editor extends EditorStartup { } /** - * @callback module:SVGthis.URLLoadCallback - * @param {boolean} success - * @returns {void} - */ + * @callback module:SVGthis.URLLoadCallback + * @param {boolean} success + * @returns {void} + */ /** - * @function module:SVGthis.loadFromURL - * @param {string} url URL from which to load an SVG string via Ajax - * @param {PlainObject} [opts={}] May contain properties: `cache`, `callback` - * @param {boolean} [opts.cache] - * @param {boolean} [opts.noAlert] - * @returns {Promise} Resolves to `undefined` or rejects upon bad loading of - * the SVG (or upon failure to parse the loaded string) when `noAlert` is - * enabled - */ + * @function module:SVGthis.loadFromURL + * @param {string} url URL from which to load an SVG string via Ajax + * @param {PlainObject} [opts={}] May contain properties: `cache`, `callback` + * @param {boolean} [opts.cache] + * @param {boolean} [opts.noAlert] + * @returns {Promise} Resolves to `undefined` or rejects upon bad loading of + * the SVG (or upon failure to parse the loaded string) when `noAlert` is + * enabled + */ loadFromURL (url, { cache, noAlert } = {}) { return this.ready(() => { return new Promise((resolve, reject) => { fetch(url, { cache: cache ? 'force-cache' : 'no-cache' }) - .then((response) => { + .then(response => { if (!response.ok) { if (noAlert) { reject(new Error('URLLoadFail')) @@ -1062,11 +1101,11 @@ class Editor extends EditorStartup { } return response.text() }) - .then((str) => { + .then(str => { this.loadSvgString(str, { noAlert }) return str }) - .catch((error) => { + .catch(error => { if (noAlert) { reject(new Error('URLLoadFail')) return @@ -1079,12 +1118,12 @@ class Editor extends EditorStartup { } /** -* @function module:SVGthis.loadFromDataURI -* @param {string} str The Data URI to base64-decode (if relevant) and load -* @param {PlainObject} [opts={}] -* @param {boolean} [opts.noAlert] -* @returns {Promise} Resolves to `undefined` and rejects if loading SVG string fails and `noAlert` is enabled -*/ + * @function module:SVGthis.loadFromDataURI + * @param {string} str The Data URI to base64-decode (if relevant) and load + * @param {PlainObject} [opts={}] + * @param {boolean} [opts.noAlert] + * @returns {Promise} Resolves to `undefined` and rejects if loading SVG string fails and `noAlert` is enabled + */ loadFromDataURI (str, { noAlert } = {}) { return this.ready(() => { let base64 = false @@ -1098,18 +1137,21 @@ class Editor extends EditorStartup { pre = pre[0] } const src = str.slice(pre.length) - return this.loadSvgString(base64 ? decode64(src) : decodeURIComponent(src), { noAlert }) + return this.loadSvgString( + base64 ? decode64(src) : decodeURIComponent(src), + { noAlert } + ) }) } /** - * @function module:SVGthis.addExtension - * @param {string} name Used internally; no need for i18n. - * @param {module:svgcanvas.ExtensionInitCallback} initfn Config to be invoked on this module - * @param {module:svgcanvas.ExtensionInitArgs} initArgs - * @throws {Error} If called too early - * @returns {Promise} Resolves to `undefined` -*/ + * @function module:SVGthis.addExtension + * @param {string} name Used internally; no need for i18n. + * @param {module:svgcanvas.ExtensionInitCallback} initfn Config to be invoked on this module + * @param {module:svgcanvas.ExtensionInitArgs} initArgs + * @throws {Error} If called too early + * @returns {Promise} Resolves to `undefined` + */ addExtension (name, initfn, initArgs) { // Note that we don't want this on this.ready since some extensions // may want to run before then (like server_opensave). diff --git a/src/editor/EditorStartup.js b/src/editor/EditorStartup.js index 59429db5..d4104343 100644 --- a/src/editor/EditorStartup.js +++ b/src/editor/EditorStartup.js @@ -630,7 +630,7 @@ class EditorStartup { ) // load user extensions (given as pathNames) await Promise.all( - this.configObj.curConfig.userExtensions.map(async (extPathName) => { + this.configObj.curConfig.userExtensions.map(async ({ pathName, config }) => { /** * @tutorial ExtensionDocs * @typedef {PlainObject} module:SVGthis.ExtensionObject @@ -641,12 +641,12 @@ class EditorStartup { /** * @type {module:SVGthis.ExtensionObject} */ - const imported = await import(encodeURI(extPathName)) + const imported = await import(encodeURI(pathName)) const { name, init: initfn } = imported.default - return this.addExtension(name, (initfn && initfn.bind(this)), {}) + return this.addExtension(name, (initfn && initfn.bind(this, config)), {}) } catch (err) { // Todo: Add config to alert any errors - console.error('Extension failed to load: ' + extPathName + '; ', err) + console.error('Extension failed to load: ' + pathName + '; ', err) return undefined } }) diff --git a/src/editor/extensions/ext-opensave/ext-opensave.js b/src/editor/extensions/ext-opensave/ext-opensave.js index 8490e086..2d93469a 100644 --- a/src/editor/extensions/ext-opensave/ext-opensave.js +++ b/src/editor/extensions/ext-opensave/ext-opensave.js @@ -138,6 +138,7 @@ export default { svgEditor.zoomImage() svgEditor.layersPanel.populateLayers() svgEditor.topPanel.updateContextPanel() + svgEditor.topPanel.updateTitle('untitled.svg') svgEditor.svgCanvas.runExtensions('onNewDocument') } @@ -160,6 +161,7 @@ export default { await svgEditor.loadSvgString(svgContent) svgEditor.updateCanvas() handle = blob.handle + svgEditor.topPanel.updateTitle(blob.name) svgEditor.svgCanvas.runExtensions('onOpenedDocument', { name: blob.name, lastModified: blob.lastModified, @@ -192,7 +194,7 @@ export default { * * @returns {void} */ - const clickSave = async function (type, _) { + const clickSave = async function (type) { const $editorDialog = $id('se-svg-editor-dialog') const editingsource = $editorDialog.getAttribute('dialog') === 'open' if (editingsource) { @@ -222,15 +224,16 @@ export default { if (type === 'save' && handle !== null) { const throwIfExistingHandleNotGood = false handle = await fileSave(blob, { - fileName: 'icon.svg', + fileName: 'untitled.svg', extensions: ['.svg'] }, handle, throwIfExistingHandleNotGood) } else { handle = await fileSave(blob, { - fileName: 'icon.svg', + fileName: svgEditor.title, extensions: ['.svg'] }) } + svgEditor.topPanel.updateTitle(handle.name) svgCanvas.runExtensions('onSavedDocument', { name: handle.name, kind: handle.kind diff --git a/src/editor/extensions/ext-storage/ext-storage.js b/src/editor/extensions/ext-storage/ext-storage.js index 99dbeec6..0bf4b7e6 100644 --- a/src/editor/extensions/ext-storage/ext-storage.js +++ b/src/editor/extensions/ext-storage/ext-storage.js @@ -17,7 +17,7 @@ * and `imagePath`, but other currently used config in the extensions) * @todo We might provide control of storage settings through the UI besides the * initial (or URL-forced) dialog. * -*/ + */ import './storageDialog.js' /** @@ -32,8 +32,9 @@ const removeStoragePrefCookie = () => { * @param {string} cookie * @returns {void} */ -const expireCookie = (cookie) => { - document.cookie = encodeURIComponent(cookie) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' +const expireCookie = cookie => { + document.cookie = + encodeURIComponent(cookie) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' } /** @@ -42,12 +43,16 @@ const expireCookie = (cookie) => { * @returns {void} * @todo Replace the string manipulation with `searchParams.set` */ -const replaceStoragePrompt = (val) => { +const replaceStoragePrompt = val => { val = val ? 'storagePrompt=' + val : '' const loc = top.location // Allow this to work with the embedded editor as well if (loc.href.includes('storagePrompt=')) { - loc.href = loc.href.replace(/([&?])storagePrompt=[^&]*(&?)/, function (n0, n1, amp) { - return (val ? n1 : '') + val + (!val && amp ? n1 : (amp || '')) + loc.href = loc.href.replace(/([&?])storagePrompt=[^&]*(&?)/, function ( + n0, + n1, + amp + ) { + return (val ? n1 : '') + val + (!val && amp ? n1 : amp || '') }) } else { loc.href += (loc.href.includes('?') ? '&' : '?') + val @@ -87,21 +92,29 @@ export default { // manage the change in the storageDialog - storageBox.addEventListener('change', (e) => { + storageBox.addEventListener('change', e => { storageBox.setAttribute('dialog', 'close') if (e?.detail?.trigger === 'ok') { if (e?.detail?.select !== 'noPrefsOrContent') { - const storagePrompt = new URL(top.location).searchParams.get('storagePrompt') - document.cookie = 'svgeditstore=' + encodeURIComponent(e.detail.select) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT' + const storagePrompt = new URL(top.location).searchParams.get( + 'storagePrompt' + ) + document.cookie = + 'svgeditstore=' + + encodeURIComponent(e.detail.select) + + '; expires=Fri, 31 Dec 9999 23:59:59 GMT' if (storagePrompt === 'true' && e?.detail?.checkbox) { replaceStoragePrompt() return } } else { removeStoragePrefCookie() - if (svgEditor.configObj.curConfig.emptyStorageOnDecline && e?.detail?.checkbox) { - this.setSvgContentStorage('') - Object.keys(svgEditor.curPrefs).forEach((name) => { + if ( + svgEditor.configObj.curConfig.emptyStorageOnDecline && + e?.detail?.checkbox + ) { + setSvgContentStorage('') + Object.keys(svgEditor.curPrefs).forEach(name => { name = 'svg-edit-' + name if (svgEditor.storage) { svgEditor.storage.removeItem(name) @@ -125,46 +138,49 @@ export default { /** * Sets SVG content as a string with "svgedit-" and the current * canvas name as namespace. - * @param {string} val + * @param {string} svgString * @returns {void} */ - function setSvgContentStorage (val) { - if (storage) { - const name = 'svgedit-' + svgEditor.configObj.curConfig.canvasName - if (!val) { - storage.removeItem(name) - } else { - storage.setItem(name, val) - } + const setSvgContentStorage = (svgString) => { + const name = `svgedit-${svgEditor.configObj.curConfig.canvasName}` + if (!svgString) { + storage.removeItem(name) + storage.removeItem(`${name}-title`) + } else { + storage.setItem(name, svgString) + storage.setItem(`title-${name}`, svgEditor.title) } } /** - * Listen for unloading: If and only if opted in by the user, set the content - * document and preferences into storage: - * 1. Prevent save warnings (since we're automatically saving unsaved - * content into storage) - * 2. Use localStorage to set SVG contents (potentially too large to allow in cookies) - * 3. Use localStorage (where available) or cookies to set preferences. - * @returns {void} - */ + * Listen for unloading: If and only if opted in by the user, set the content + * document and preferences into storage: + * 1. Prevent save warnings (since we're automatically saving unsaved + * content into storage) + * 2. Use localStorage to set SVG contents (potentially too large to allow in cookies) + * 3. Use localStorage (where available) or cookies to set preferences. + * @returns {void} + */ function setupBeforeUnloadListener () { window.addEventListener('beforeunload', function () { // Don't save anything unless the user opted in to storage - if (!(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/).test(document.cookie)) { + if ( + !/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/.test( + document.cookie + ) + ) { return } - if ((/(?:^|;\s*)svgeditstore=prefsAndContent/).test(document.cookie)) { + if (/(?:^|;\s*)svgeditstore=prefsAndContent/.test(document.cookie)) { setSvgContentStorage(svgCanvas.getSvgString()) } svgEditor.setConfig({ no_save_warning: true }) // No need for explicit saving at all once storage is on - // svgEditor.showSaveWarning = false; const { curPrefs } = svgEditor.configObj Object.entries(curPrefs).forEach(([key, val]) => { - const store = (val !== undefined) + const store = val !== undefined key = 'svg-edit-' + key if (!store) { return @@ -175,7 +191,11 @@ export default { window.widget.setPreferenceForKey(val, key) } else { val = encodeURIComponent(val) - document.cookie = encodeURIComponent(key) + '=' + val + '; expires=Fri, 31 Dec 9999 23:59:59 GMT' + document.cookie = + encodeURIComponent(key) + + '=' + + val + + '; expires=Fri, 31 Dec 9999 23:59:59 GMT' } }) }) @@ -185,7 +205,9 @@ export default { return { name: 'storage', callback () { - const storagePrompt = new URL(top.location).searchParams.get('storagePrompt') + const storagePrompt = new URL(top.location).searchParams.get( + 'storagePrompt' + ) // No need to run this one-time dialog again just because the user // changes the language if (loaded) { @@ -197,22 +219,23 @@ export default { // set to false; to avoid any chance of storage, avoid this // extension! (and to avoid using any prior storage, set the // config option "noStorageOnLoad" to true). - if (!forceStorage && ( + if ( + !forceStorage && // If the URL has been explicitly set to always prompt the // user (e.g., so one can be pointed to a URL where one // can alter one's settings, say to prevent future storage)... - storagePrompt === 'true' || - ( + (storagePrompt === 'true' || // ...or...if the URL at least doesn't explicitly prevent a // storage prompt (as we use for users who // don't want to set cookies at all but who don't want // continual prompts about it)... - storagePrompt !== 'false' && - // ...and this user hasn't previously indicated a desire for storage - !(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/).test(document.cookie) - ) + (storagePrompt !== 'false' && + // ...and this user hasn't previously indicated a desire for storage + !/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/.test( + document.cookie + ))) // ...then show the storage prompt. - )) { + ) { const options = Boolean(storage) // Open select-with-checkbox dialog // From svg-editor.js diff --git a/src/editor/extensions/ext-storage/storageDialog.js b/src/editor/extensions/ext-storage/storageDialog.js index 8faae974..720f26ee 100644 --- a/src/editor/extensions/ext-storage/storageDialog.js +++ b/src/editor/extensions/ext-storage/storageDialog.js @@ -142,23 +142,6 @@ export class SeStorageDialog extends HTMLElement { svgEditor.$click(this.$okBtn, (evt) => onSubmitHandler(evt, 'ok')) svgEditor.$click(this.$cancelBtn, (evt) => onSubmitHandler(evt, 'cancel')) } - - /** - * Sets SVG content as a string with "svgedit-" and the current - * canvas name as namespace. - * @param {string} val - * @returns {void} - */ - setSvgContentStorage (val) { - if (this.storage) { - const name = 'svgedit-' + this.configObj.curConfig.canvasName - if (!val) { - this.storage.removeItem(name) - } else { - this.storage.setItem(name, val) - } - } - } } // Register diff --git a/src/editor/panels/TopPanel.html b/src/editor/panels/TopPanel.html index 7b28fa8c..c70eed64 100644 --- a/src/editor/panels/TopPanel.html +++ b/src/editor/panels/TopPanel.html @@ -1,4 +1,7 @@
+
+

untitled.svg

+
diff --git a/src/editor/panels/TopPanel.js b/src/editor/panels/TopPanel.js index 5e703c8e..73dbb589 100644 --- a/src/editor/panels/TopPanel.js +++ b/src/editor/panels/TopPanel.js @@ -26,14 +26,16 @@ class TopPanel { */ displayTool (className) { // default display is 'none' so removing the property will make the panel visible - $qa(`.${className}`).map((el) => el.style.removeProperty('display')) + $qa(`.${className}`).map(el => el.style.removeProperty('display')) } /** * @type {module} */ hideTool (className) { - $qa(`.${className}`).forEach((el) => { el.style.display = 'none' }) + $qa(`.${className}`).forEach(el => { + el.style.display = 'none' + }) } /** @@ -72,9 +74,12 @@ class TopPanel { this.svgCanvas.setStrokeAttr('stroke-' + pre, val) } opt.classList.add('current') - const elements = Array.prototype.filter.call(opt.parentNode.children, function (child) { - return child !== opt - }) + const elements = Array.prototype.filter.call( + opt.parentNode.children, + function (child) { + return child !== opt + } + ) Array.from(elements).forEach(function (element) { element.classList.remove('current') }) @@ -87,7 +92,10 @@ class TopPanel { * @returns {void} */ update () { - let i; let len + let i + let len + // set title + $qa('#title_panel > p')[0].textContent = this.editor.title if (this.selectedElement) { switch (this.selectedElement.tagName) { case 'use': @@ -109,15 +117,17 @@ class TopPanel { } } - $id('stroke_width').value = (gWidth === null ? '' : gWidth) + $id('stroke_width').value = gWidth === null ? '' : gWidth this.editor.bottomPanel.updateColorpickers(true) break } default: { this.editor.bottomPanel.updateColorpickers(true) - $id('stroke_width').value = this.selectedElement.getAttribute('stroke-width') || 1 - $id('stroke_style').value = this.selectedElement.getAttribute('stroke-dasharray') || 'none' + $id('stroke_width').value = + this.selectedElement.getAttribute('stroke-width') || 1 + $id('stroke_style').value = + this.selectedElement.getAttribute('stroke-dasharray') || 'none' $id('stroke_style').setAttribute('value', $id('stroke_style').value) let attr = @@ -226,13 +236,14 @@ class TopPanel { if (['line', 'circle', 'ellipse'].includes(elname)) { this.hideTool('xy_panel') } else { - let x; let y + let x + let y // Get BBox vals for g, polyline and path if (['g', 'polyline', 'path'].includes(elname)) { const bb = this.editor.svgCanvas.getStrokedBBox([elem]) if (bb) { - ({ x, y } = bb) + ;({ x, y } = bb) } } else { x = elem.getAttribute('x') @@ -244,19 +255,13 @@ class TopPanel { y = convertUnit(y) } - $id('selected_x').value = (x || 0) - $id('selected_y').value = (y || 0) + $id('selected_x').value = x || 0 + $id('selected_y').value = y || 0 this.displayTool('xy_panel') } // Elements in this array cannot be converted to a path - if ([ - 'image', - 'text', - 'path', - 'g', - 'use' - ].includes(elname)) { + if (['image', 'text', 'path', 'g', 'use'].includes(elname)) { this.hideTool('tool_topath') } else { this.displayTool('tool_topath') @@ -266,11 +271,13 @@ class TopPanel { } else { this.hideTool('tool_reorient') } - $id('tool_reorient').disabled = (angle === 0) + $id('tool_reorient').disabled = angle === 0 } else { const point = this.path.getNodePoint() - $id('tool_add_subpath').pressed = false; - (!this.path.canDeleteNodes) ? $id('tool_node_delete').classList.add('disabled') : $id('tool_node_delete').classList.remove('disabled') + $id('tool_add_subpath').pressed = false + !this.path.canDeleteNodes + ? $id('tool_node_delete').classList.add('disabled') + : $id('tool_node_delete').classList.remove('disabled') // Show open/close button based on selected point // setIcon('#tool_openclose_path', path.closed_subpath ? 'open_path' : 'close_path'); @@ -281,10 +288,10 @@ class TopPanel { point.x = convertUnit(point.x) point.y = convertUnit(point.y) } - $id('path_node_x').value = (point.x) - $id('path_node_y').value = (point.y) + $id('path_node_x').value = point.x + $id('path_node_y').value = point.y if (point.type) { - segType.value = (point.type) + segType.value = point.type segType.removeAttribute('disabled') } else { segType.value = 4 @@ -316,9 +323,12 @@ class TopPanel { } // siblings if (elem.parentNode) { - const selements = Array.prototype.filter.call(elem.parentNode.children, function (child) { - return child !== elem - }) + const selements = Array.prototype.filter.call( + elem.parentNode.children, + function (child) { + return child !== elem + } + ) if (elem.parentNode.tagName === 'a' && !selements.length) { this.displayTool('a_panel') linkHref = this.editor.svgCanvas.getHref(elem.parentNode) @@ -339,7 +349,7 @@ class TopPanel { const curPanel = panels[tagName] this.displayTool(tagName + '_panel') - curPanel.forEach((item) => { + curPanel.forEach(item => { let attrVal = elem.getAttribute(item) if (this.editor.configObj.curConfig.baseUnit !== 'px' && elem[item]) { const bv = elem[item].baseVal.value @@ -352,16 +362,31 @@ class TopPanel { this.displayTool('text_panel') $id('tool_italic').pressed = this.editor.svgCanvas.getItalic() $id('tool_bold').pressed = this.editor.svgCanvas.getBold() - $id('tool_text_decoration_underline').pressed = this.editor.svgCanvas.hasTextDecoration('underline') - $id('tool_text_decoration_linethrough').pressed = this.editor.svgCanvas.hasTextDecoration('line-through') - $id('tool_text_decoration_overline').pressed = this.editor.svgCanvas.hasTextDecoration('overline') - $id('tool_font_family').setAttribute('value', elem.getAttribute('font-family')) - $id('tool_text_anchor').setAttribute('value', elem.getAttribute('text-anchor')) + $id( + 'tool_text_decoration_underline' + ).pressed = this.editor.svgCanvas.hasTextDecoration('underline') + $id( + 'tool_text_decoration_linethrough' + ).pressed = this.editor.svgCanvas.hasTextDecoration('line-through') + $id( + 'tool_text_decoration_overline' + ).pressed = this.editor.svgCanvas.hasTextDecoration('overline') + $id('tool_font_family').setAttribute( + 'value', + elem.getAttribute('font-family') + ) + $id('tool_text_anchor').setAttribute( + 'value', + elem.getAttribute('text-anchor') + ) $id('font_size').value = elem.getAttribute('font-size') - $id('tool_letter_spacing').value = elem.getAttribute('letter-spacing') ?? 0 - $id('tool_word_spacing').value = elem.getAttribute('word-spacing') ?? 0 + $id('tool_letter_spacing').value = + elem.getAttribute('letter-spacing') ?? 0 + $id('tool_word_spacing').value = + elem.getAttribute('word-spacing') ?? 0 $id('tool_text_length').value = elem.getAttribute('textLength') ?? 0 - $id('tool_length_adjust').value = elem.getAttribute('lengthAdjust') ?? 0 + $id('tool_length_adjust').value = + elem.getAttribute('lengthAdjust') ?? 0 $id('text').value = elem.textContent if (this.editor.svgCanvas.addedNew) { // Timeout needed for IE9 @@ -375,16 +400,14 @@ class TopPanel { tagName === 'image' && this.editor.svgCanvas.getMode() === 'image' ) { - this.editor.svgCanvas.setImageURL( - this.editor.svgCanvas.getHref(elem) - ) + this.editor.svgCanvas.setImageURL(this.editor.svgCanvas.getHref(elem)) // image } else if (tagName === 'g' || tagName === 'use') { this.displayTool('container_panel') const title = this.editor.svgCanvas.getTitle() const label = $id('g_title') label.value = title - $id('g_title').disabled = (tagName === 'use') + $id('g_title').disabled = tagName === 'use' } } menuItems.setAttribute( @@ -393,7 +416,7 @@ class TopPanel { ) menuItems.setAttribute( (tagName === 'g' || !this.multiselected ? 'dis' : 'en') + - 'ablemenuitems', + 'ablemenuitems', '#group' ) @@ -463,7 +486,9 @@ class TopPanel { fcRules.setAttribute('id', 'wireframe_rules') document.getElementsByTagName('head')[0].appendChild(fcRules) } else { - while (wfRules.firstChild) { wfRules.removeChild(wfRules.firstChild) } + while (wfRules.firstChild) { + wfRules.removeChild(wfRules.firstChild) + } } this.editor.updateWireFrame() } @@ -513,8 +538,10 @@ class TopPanel { * @type {module} */ changeRotationAngle (e) { - this.editor.svgCanvas.setRotationAngle(e.target.value); - (Number.parseInt(e.target.value) === 0) ? $id('tool_reorient').classList.add('disabled') : $id('tool_reorient').classList.remove('disabled') + this.editor.svgCanvas.setRotationAngle(e.target.value) + ;(Number.parseInt(e.target.value) === 0) + ? $id('tool_reorient').classList.add('disabled') + : $id('tool_reorient').classList.remove('disabled') } /** @@ -655,8 +682,8 @@ class TopPanel { * @returns {void} */ linkControlPoints () { - $id('tool_node_link').pressed = !($id('tool_node_link').pressed) - const linked = !!($id('tool_node_link').pressed) + $id('tool_node_link').pressed = !$id('tool_node_link').pressed + const linked = !!$id('tool_node_link').pressed this.path.linkControlPoints(linked) } @@ -806,11 +833,11 @@ class TopPanel { } /** - * Set a selected image's URL. - * @function module:SVGthis.setImageURL - * @param {string} url - * @returns {void} - */ + * Set a selected image's URL. + * @function module:SVGthis.setImageURL + * @param {string} url + * @returns {void} + */ setImageURL (url) { const { editor } = this if (!url) { @@ -828,24 +855,39 @@ class TopPanel { // eslint-disable-next-line promise/catch-or-return promised // eslint-disable-next-line promise/always-return - .then(() => { - // switch into "select" mode if we've clicked on an element - editor.svgCanvas.setMode('select') - editor.svgCanvas.selectOnly(editor.svgCanvas.getSelectedElements(), true) - }, (error) => { - console.error('error =', error) - seAlert(editor.i18next.t('tools.no_embed')) - editor.svgCanvas.deleteSelectedElements() - }) + .then( + () => { + // switch into "select" mode if we've clicked on an element + editor.svgCanvas.setMode('select') + editor.svgCanvas.selectOnly( + editor.svgCanvas.getSelectedElements(), + true + ) + }, + error => { + console.error('error =', error) + seAlert(editor.i18next.t('tools.no_embed')) + editor.svgCanvas.deleteSelectedElements() + } + ) this.displayTool('image_url') } } /** - * @param {boolean} editmode - * @param {module:svgcanvas.SvgCanvas#event:selected} elems - * @returns {void} - */ + * + */ + updateTitle (title) { + if (title) this.editor.title = title + const titleElement = $qa('#title_panel > p')[0] + if (titleElement) titleElement.textContent = this.editor.title + } + + /** + * @param {boolean} editmode + * @param {module:svgcanvas.SvgCanvas#event:selected} elems + * @returns {void} + */ togglePathEditMode (editMode, elems) { if (editMode) { this.displayTool('path_node_panel') @@ -883,6 +925,7 @@ class TopPanel { ) newSeEditorDialog.setAttribute('id', 'se-svg-editor-dialog') this.editor.$container.append(newSeEditorDialog) + this.updateTitle() newSeEditorDialog.init(i18next) $id('tool_link_url').setAttribute('title', i18next.t('tools.set_link_url')) // register action to top panel buttons @@ -954,7 +997,7 @@ class TopPanel { 'image_height', 'path_node_x', 'path_node_y' - ].forEach((attrId) => + ].forEach(attrId => $id(attrId).addEventListener('change', this.attrChanger.bind(this)) ) } diff --git a/src/editor/svgedit.css b/src/editor/svgedit.css index b409c9b5..77b07ac1 100644 --- a/src/editor/svgedit.css +++ b/src/editor/svgedit.css @@ -33,6 +33,13 @@ height: 100%; } +#title_panel > p { + color: white; + padding-left: 5px; + padding-right: 3px; + font-weight: bold; +} + /* on smaller screen, allow 2 lines for the toolbar */ @media screen and (max-width:1250px) { .svg_editor { @@ -42,7 +49,7 @@ /* class to open the right panel */ .svg_editor.open { - grid-template-columns: 34px 15px 50px 1fr 150px; + grid-template-columns: 34px 15px 50px 1fr 220px; } #svgroot { @@ -179,6 +186,8 @@ hr { #selLayerNames { display: block; + top: -8px; + position: relative; } /* Main button