From 3fc624212307c5db4b0047f3cf2a347cc059b126 Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Wed, 15 Mar 2023 18:23:14 -0700 Subject: [PATCH 1/6] #352 Templating 1 --- API/Backend/Draw/models/userfiles.js | 25 ++++++++++++- API/Backend/Draw/routes/files.js | 1 + API/Backend/Draw/setup.js | 4 +++ package-lock.json | 52 +++++++++++++++------------- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/API/Backend/Draw/models/userfiles.js b/API/Backend/Draw/models/userfiles.js index c562c88e..8740224c 100644 --- a/API/Backend/Draw/models/userfiles.js +++ b/API/Backend/Draw/models/userfiles.js @@ -79,6 +79,11 @@ const attributes = { defaultValue: "0", unique: false, }, + template: { + type: Sequelize.JSON, + allowNull: true, + defaultValue: null, + }, }; const options = { @@ -128,5 +133,23 @@ const makeMasterFiles = (intents) => { } }; +// Adds to the table, never removes +const up = async () => { + // template column + await sequelize + .query( + `ALTER TABLE user_files ADD COLUMN IF NOT EXISTS template json NULL;` + ) + .then(() => { + logger("info", `Added template col`, "user_files"); + return null; + }) + .catch((err) => { + logger("info", `template. Nothing to do...`, "user_files"); + + return null; + }); +}; + // export User model for use in other files. -module.exports = { Userfiles, UserfilesTEST, makeMasterFiles }; +module.exports = { Userfiles, UserfilesTEST, makeMasterFiles, up }; diff --git a/API/Backend/Draw/routes/files.js b/API/Backend/Draw/routes/files.js index be475cac..7b6d8222 100644 --- a/API/Backend/Draw/routes/files.js +++ b/API/Backend/Draw/routes/files.js @@ -132,6 +132,7 @@ router.post("/make", function (req, res, next) { intent: req.body.intent, public: "1", hidden: "0", + template: req.body.template ? JSON.parse(req.body.template) : null, }; // Insert new userfile into the user_files table diff --git a/API/Backend/Draw/setup.js b/API/Backend/Draw/setup.js index 1da4071e..23901cf6 100644 --- a/API/Backend/Draw/setup.js +++ b/API/Backend/Draw/setup.js @@ -1,6 +1,7 @@ const routeFiles = require("./routes/files"); const routerFiles = routeFiles.router; const routerDraw = require("./routes/draw").router; +const ufiles = require("./models/userfiles"); let setup = { //Once the app initializes @@ -27,6 +28,9 @@ let setup = { onceStarted: (s) => {}, //Once all tables sync onceSynced: (s) => { + if (typeof ufiles.up === "function") { + ufiles.up(); + } routeFiles.makeMasterFiles([ "roi", "campaign", diff --git a/package-lock.json b/package-lock.json index d8b4f23b..413453d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8485,13 +8485,17 @@ } }, "node_modules/es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, "dependencies": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/es6-iterator": { @@ -11443,9 +11447,9 @@ } }, "node_modules/is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dependencies": { "has": "^1.0.3" }, @@ -13707,9 +13711,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "node_modules/nice-try": { "version": "1.0.5", @@ -28919,13 +28923,13 @@ } }, "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" } }, "es6-iterator": { @@ -31233,9 +31237,9 @@ } }, "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "requires": { "has": "^1.0.3" } @@ -33037,9 +33041,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "nice-try": { "version": "1.0.5", From 4793799c8563829002d39b6b0a2ec193b972e257 Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Thu, 16 Mar 2023 19:12:51 -0700 Subject: [PATCH 2/6] #352 Templating 2 --- config/js/config.js | 20 +++++++++++++------- src/essence/Basics/Formulae_/Formulae_.js | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/config/js/config.js b/config/js/config.js index 6a5318f2..451995f4 100644 --- a/config/js/config.js +++ b/config/js/config.js @@ -623,10 +623,7 @@ function initialize() { ) { $("#tab_look #look_fullscreen").prop("checked", true); } - if ( - cData.look && - (cData.look.info == true) - ) { + if (cData.look && cData.look.info == true) { $("#tab_look #look_info").prop("checked", true); } $("#tab_look #look_infourl").val( @@ -2204,9 +2201,18 @@ function save(returnJSON) { ); return; } - toolsjson["variables"] = JSON.parse( - editors[tData[i].name].getValue() - ); + try { + toolsjson["variables"] = JSON.parse( + editors[tData[i].name].getValue() + ); + } catch (err) { + toast( + "error", + `Error: ${tData[i].name} tool json is badly formed.`, + 5000 + ); + return; + } } } json.tools.push(toolsjson); diff --git a/src/essence/Basics/Formulae_/Formulae_.js b/src/essence/Basics/Formulae_/Formulae_.js index ba9237b9..3a604ab9 100644 --- a/src/essence/Basics/Formulae_/Formulae_.js +++ b/src/essence/Basics/Formulae_/Formulae_.js @@ -2292,6 +2292,25 @@ var Formulae_ = { ] : [arr] }, + /** + * From https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string + */ + humanFileSize(bytes, si) { + if (bytes == null) return null + var thresh = si ? 1000 : 1024 + if (Math.abs(bytes) < thresh) { + return bytes + ' B' + } + var units = si + ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + var u = -1 + do { + bytes /= thresh + ++u + } while (Math.abs(bytes) >= thresh && u < units.length - 1) + return bytes.toFixed(1) + ' ' + units[u] + }, getBrowser() { //Check if browser is IE if (navigator.userAgent.search('MSIE') >= 0) { From ce3ebdd52fa293f2e0c593159f62825330624c41 Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Fri, 17 Mar 2023 18:46:05 -0700 Subject: [PATCH 3/6] #352 Templating 3 --- src/css/mmgisUI.css | 163 ++++++++++++++++---------------- src/essence/Ancillary/Modal.css | 2 +- 2 files changed, 84 insertions(+), 81 deletions(-) diff --git a/src/css/mmgisUI.css b/src/css/mmgisUI.css index 27edd7a5..97060686 100644 --- a/src/css/mmgisUI.css +++ b/src/css/mmgisUI.css @@ -634,120 +634,123 @@ --handle2: #08ea77; --handle3: #ff2b66; --handle4: #f5d556; - } - - /* slider handle color overrides +} + +/* slider handle color overrides /* ****************************** */ - .svelteSlider .rangeHandle { +.svelteSlider .rangeHandle { --handle-focus: var(--handle); --handle-inactive: var(--handle); --handle-border: var(--handle); - } - - .svelteSlider .rangeHandle:nth-of-type(1) { +} + +.svelteSlider .rangeHandle:nth-of-type(1) { --handle: var(--handle1); - } - - .svelteSlider .rangeHandle:nth-of-type(2) { +} + +.svelteSlider .rangeHandle:nth-of-type(2) { --handle: var(--handle2); - } - - .svelteSlider .rangeHandle:nth-of-type(3) { +} + +.svelteSlider .rangeHandle:nth-of-type(3) { --handle: var(--handle3); - } - - .svelteSlider .rangeHandle:nth-of-type(4) { +} + +.svelteSlider .rangeHandle:nth-of-type(4) { --handle: var(--handle4); - } - - /* shared slider styles +} + +/* shared slider styles /* ****************************** */ - .svelteSlider { +.svelteSlider { height: 3px; font-size: 14px; - } - - .svelteSlider .rangeNub { +} + +.svelteSlider .rangeNub { opacity: 0.5; - } - .svelteSlider.focus .rangeNub, .svelteSlider:hover .rangeNub { +} +.svelteSlider.focus .rangeNub, +.svelteSlider:hover .rangeNub { opacity: 0.75; - } - .svelteSlider .rangeHandle.active .rangeNub { +} +.svelteSlider .rangeHandle.active .rangeNub { opacity: 1; - } - - .svelteSlider.rangeFloat { +} + +.svelteSlider.rangeFloat { opacity: 0.5; background: transparent; - } - - .svelteSliderr.focus .rangeFloat { +} + +.svelteSliderr.focus .rangeFloat { opacity: 1; - } - +} - - /* second slider styling +/* second slider styling /* ****************************** */ - .svelteSlider .rangeHandle { +.svelteSlider .rangeHandle { width: 15px; height: 15px; top: 1px; - } - .svelteSlider .rangeHandle .rangeNub { +} +.svelteSlider .rangeHandle .rangeNub { opacity: 1; - } - - /* pips under sliders styling +} + +/* pips under sliders styling /* ****************************** */ - .svelteSlider .pip { +.svelteSlider .pip { height: 4px; font-weight: 200; - } - .svelteSlider .pip.selected .pipVal { +} +.svelteSlider .pip.selected .pipVal { font-weight: 300; - } - .svelteSlider .pip .pipVal { +} +.svelteSlider .pip .pipVal { display: none; font-size: 12px; top: 8px; - } - - .svelteSlider .pip:nth-child(5n+1) { +} + +.svelteSlider .pip:nth-child(5n + 1) { height: 8px; - } - .svelteSlider .pip:nth-child(5n+1).selected { +} +.svelteSlider .pip:nth-child(5n + 1).selected { height: 12px; - } - .svelteSlider .pip:nth-child(5n+1).selected .pipVal { +} +.svelteSlider .pip:nth-child(5n + 1).selected .pipVal { top: 12px; - } - .svelteSlider .pip:nth-child(5n+1) .pipVal { +} +.svelteSlider .pip:nth-child(5n + 1) .pipVal { display: block; - } - - .svelteSlider .rangeBar { +} + +.svelteSlider .rangeBar { height: 3px; - background: linear-gradient(90deg, var(--handle1), var(--handle1) 25%, var(--handle2) 75%, var(--handle2)); - } - - .svelteSlider .rangePips .pip, - .svelteSlider.rangePips .pipVal { + background: linear-gradient( + 90deg, + var(--handle1), + var(--handle1) 25%, + var(--handle2) 75%, + var(--handle2) + ); +} + +.svelteSlider .rangePips .pip, +.svelteSlider.rangePips .pipVal { transition: all 0.66s ease-out; - } - - .svelteSlider .rangePips .pip.selected, - .svelteSlider .rangePips .pip.selected .pipVal { +} + +.svelteSlider .rangePips .pip.selected, +.svelteSlider .rangePips .pip.selected .pipVal { transition: none; - } - - .svelteSlider.range .rangePips .pip.selected, - .svelteSlider.range .rangePips .pip.selected .pipVal { +} + +.svelteSlider.range .rangePips .pip.selected, +.svelteSlider.range .rangePips .pip.selected .pipVal { transition: all 0.15s ease; - } - - +} /*Custom Scrollbar for webkit*/ .mmgisScrollbar::-webkit-scrollbar-track { @@ -1483,7 +1486,7 @@ input::-webkit-inner-spin-button { .mmgis-checkbox.small label { width: 16px; height: 16px; -} +} @media (pointer: fine) and (hover: hover) { .mmgis-checkbox label:hover { background-color: var(--color-j); @@ -1550,7 +1553,7 @@ input::-webkit-inner-spin-button { } #toast-container { position: fixed !important; - bottom: 40px!important; + bottom: 40px !important; right: 5px !important; } .mmgisToast { @@ -1560,12 +1563,12 @@ input::-webkit-inner-spin-button { width: auto; margin-top: 10px; position: relative; - max-width:100%; + max-width: 100%; height: auto; line-height: 1.5em; background-color: var(--color-mmgis); padding: 10px 25px; -/* + /* font-size: 1.1rem; font-weight: 300; */ diff --git a/src/essence/Ancillary/Modal.css b/src/essence/Ancillary/Modal.css index d16e183f..8482ffc5 100644 --- a/src/essence/Ancillary/Modal.css +++ b/src/essence/Ancillary/Modal.css @@ -6,7 +6,7 @@ width: 100%; height: 100%; background: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.5)); - z-index: 999999; + z-index: 9998; opacity: 0; } From cb21c0ac97882b913f35ff304d086027cdde0c8a Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Wed, 22 Mar 2023 18:40:31 -0700 Subject: [PATCH 4/6] #352 Update Modal for templating --- API/Backend/Draw/routes/files.js | 5 ++ docs/pages/Tools/Draw/Draw.md | 72 ++++++++++++++++++++++- docs/pages/Tools/Identifier/Identifier.md | 2 +- src/essence/Ancillary/Modal.css | 2 +- src/essence/Ancillary/Modal.js | 55 ++++++++++------- src/external/Dropy/dropy.css | 2 +- 6 files changed, 111 insertions(+), 27 deletions(-) diff --git a/API/Backend/Draw/routes/files.js b/API/Backend/Draw/routes/files.js index 7b6d8222..0ec36f34 100644 --- a/API/Backend/Draw/routes/files.js +++ b/API/Backend/Draw/routes/files.js @@ -425,6 +425,11 @@ router.post("/change", function (req, res, next) { ) { toUpdateTo.public = req.body.public; } + if (req.body.hasOwnProperty("template") && req.body.template != null) { + try { + toUpdateTo.template = JSON.parse(req.body.template); + } catch (err) {} + } let updateObj = { where: { diff --git a/docs/pages/Tools/Draw/Draw.md b/docs/pages/Tools/Draw/Draw.md index c886edc9..fcc30334 100644 --- a/docs/pages/Tools/Draw/Draw.md +++ b/docs/pages/Tools/Draw/Draw.md @@ -34,7 +34,72 @@ There are five files that are group editable with the correct permission. The gr "done" ], "hoverLengthOnLines": false - "leadsCanEditFileInfo": false + "leadsCanEditFileInfo": false, + "templates": { + "example_1": [ + { + "type": "slider", + "field": "a", + "min": 0, + "max": 100, + "step": 1, + "default": 0 + }, + { + "type": "number", + "field": "b", + "min": 0, + "max": 100, + "step": 1, + "required": true, + "default": 3 + }, + { + "type": "text", + "field": "c", + "minLength": 2, + "maxLength": 4, + "required": true, + "regex": null, + "default": null + }, + { + "type": "textarea", + "field": "d", + "maxLength": 10, + "required": true, + "default": "hi" + }, + { + "type": "checkbox", + "field": "e", + "default": true + }, + { + "type": "dropdown", + "field": "f", + "items": [ + "Yes", + "No", + "Maybe" + ], + "default": "No" + }, + { + "type": "date", + "field": "g", + "format": "HH:mm:ss", + "default": "now" + } + ], + "example_2": [ + { + "type": "checkbox", + "field": "h", + "default": false + } + ] + } } ``` @@ -42,10 +107,11 @@ _"intents"_: The names in quotes will be the group file names. _"preferredTags"_: Users can attach tags or keyword to files to organize them. Preferred Tags are curated tags and promoted over user generated ones. _"hoverLengthOnLines"_: If true, the hover text for line features will include the total length of the line in meters. _"leadsCanEditFileInfo"_: If true, lead roles can edit the file info, (name, description, tags, folder, make private) of any user's public file. +_"templates"_: Templates create forms for feature properties. For instance, all features in a given draw file could, in the feature's edit panel, have the field "Reviewed" be togglable via a checkbox. Users may make their own templates too but the ones configured here are promoted and cannot be delete. ## Tool Use -The Draw Tool has three panels: one for making files and controlling the intial feature creation, another for editing features and their properties, and lastly a panel for controlling the edit history. You can navigate between the panels by clicking on the icons at the top: Pencil icon (default) for panel 1, Shapes icon for panel 2, and Clock icon for panel 3. +The Draw Tool has three panels: one for making files and controlling the initial feature creation, another for editing features and their properties, and lastly a panel for controlling the edit history. You can navigate between the panels by clicking on the icons at the top: Pencil icon (default) for panel 1, Shapes icon for panel 2, and Clock icon for panel 3. ### Panels @@ -59,7 +125,7 @@ This panel creates files, manages them, and is where you initially make features #### Panel 2 -The shapes panel shows all the currently drawn features is a list. It serves as a quick way to view, select (and group select with CTRL and SHIFT) and navigate to the cooresponding features on the map. The bottom section allows users to copy selected features to other files. +The shapes panel shows all the currently drawn features is a list. It serves as a quick way to view, select (and group select with CTRL and SHIFT) and navigate to the corresponding features on the map. The bottom section allows users to copy selected features to other files. #### Panel 3 diff --git a/docs/pages/Tools/Identifier/Identifier.md b/docs/pages/Tools/Identifier/Identifier.md index cad41952..9799ed0e 100644 --- a/docs/pages/Tools/Identifier/Identifier.md +++ b/docs/pages/Tools/Identifier/Identifier.md @@ -7,7 +7,7 @@ parent: Tools # Identifier -Mouse over to query underlying datasets. This will read the raw values from a georeferenced dataset, which can be any bitdepth (8,16,32). You can set up multiple file to return values from. +Mouse over to query underlying datasets. This will read the raw values from a geo-referenced dataset, which can be any bit-depth (8,16,32). You can set up multiple file to return values from. ### Raw Variables diff --git a/src/essence/Ancillary/Modal.css b/src/essence/Ancillary/Modal.css index 8482ffc5..58649602 100644 --- a/src/essence/Ancillary/Modal.css +++ b/src/essence/Ancillary/Modal.css @@ -1,4 +1,4 @@ -#mmgisModal { +.mmgisModal { overflow: auto; position: fixed; top: 0; diff --git a/src/essence/Ancillary/Modal.js b/src/essence/Ancillary/Modal.js index d5b0c71f..d5573fb0 100644 --- a/src/essence/Ancillary/Modal.js +++ b/src/essence/Ancillary/Modal.js @@ -9,12 +9,17 @@ import $ from 'jquery' import './Modal.css' const Modal = { - _onRemoveCallback: null, - set: function (html, onAddCallback, onRemoveCallback) { - if ($('#mmgisModal')) $('#mmgisModal').remove() + _onRemoveCallback: {}, + _activeModalIds: {}, + set: function (html, onAddCallback, onRemoveCallback, modalId) { + modalId = modalId || 0 + Modal._activeModalIds[modalId] = true + const id = `mmgisModal_${modalId}` + const elmId = `#${id}` + if ($(elmId)) $(elmId).remove() // prettier-ignore $('body').append([ - "
", + `
`, "
", "
", html, @@ -22,17 +27,17 @@ const Modal = { "
" ].join('\n')) - if (typeof onAddCallback === 'function') onAddCallback('mmgisModal') + if (typeof onAddCallback === 'function') onAddCallback(id) - $('#mmgisModal').on('click', function () { - Modal.remove() - }) - $('#mmgisModalInner').on('click', function (e) { - e.stopPropagation() + $(elmId).on('click', (e) => { + if (!$(e.target).parents().hasClass('dontCloseWhenClicked')) + Modal.remove(false, modalId) }) - $('#main-container').css({ filter: 'blur(3px)' }) - $('#mmgisModal').animate( + $('#main-container').css({ + filter: `blur(${3 * Object.keys(Modal._activeModalIds).length}px)`, + }) + $(elmId).animate( { opacity: 1, }, @@ -40,25 +45,33 @@ const Modal = { ) if (typeof onRemoveCallback === 'function') - Modal._onRemoveCallback = onRemoveCallback - else Modal._onRemoveCallback = null + Modal._onRemoveCallback[modalId] = onRemoveCallback + else Modal._onRemoveCallback[modalId] = null }, //Remove everything CursorInfo created - remove: function (isImmediate) { + remove: function (isImmediate, modalId) { + modalId = modalId || 0 + const elmId = `#mmgisModal_${modalId}` const time = isImmediate ? 0 : 500 - if (typeof Modal._onRemoveCallback === 'function') - Modal._onRemoveCallback() - Modal._onRemoveCallback = null + if (typeof Modal._onRemoveCallback[modalId] === 'function') + Modal._onRemoveCallback[modalId]() + Modal._onRemoveCallback[modalId] = null - $('#main-container').css({ filter: 'blur(0px)' }) - $('#mmgisModal').animate( + $('#main-container').css({ + filter: `blur(${ + 3 * (Object.keys(Modal._activeModalIds).length - 1) + }px)`, + }) + $(elmId).animate( { opacity: 0, }, time, function () { - $('#mmgisModal').remove() + $(elmId).remove() + + delete Modal._activeModalIds[modalId] } ) }, diff --git a/src/external/Dropy/dropy.css b/src/external/Dropy/dropy.css index 63b1c765..d9127de9 100644 --- a/src/external/Dropy/dropy.css +++ b/src/external/Dropy/dropy.css @@ -127,7 +127,7 @@ dd { } .dropy.open .dropy__content ul { - max-height: 18.75em; + max-height: 20em; overflow-y: auto; opacity: 1; } From 6d762d0c7414c0bd16834f770f259d566fb7d7ae Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Thu, 23 Mar 2023 14:44:50 -0700 Subject: [PATCH 5/6] #352 Migrate DrawTool Templating --- run/server.js | 10 - src/css/mmgisUI.css | 42 + src/essence/Tools/Draw/DrawTool.css | 251 ++-- src/essence/Tools/Draw/DrawTool.js | 276 ++--- src/essence/Tools/Draw/DrawTool.test.js | 18 +- src/essence/Tools/Draw/DrawTool_Editing.js | 107 +- src/essence/Tools/Draw/DrawTool_FileModal.css | 182 +++ src/essence/Tools/Draw/DrawTool_FileModal.js | 417 +++++++ src/essence/Tools/Draw/DrawTool_Files.js | 155 ++- src/essence/Tools/Draw/DrawTool_Templater.css | 271 +++++ src/essence/Tools/Draw/DrawTool_Templater.js | 1012 +++++++++++++++++ src/essence/Tools/Draw/config.json | 56 +- 12 files changed, 2452 insertions(+), 345 deletions(-) create mode 100644 src/essence/Tools/Draw/DrawTool_FileModal.css create mode 100644 src/essence/Tools/Draw/DrawTool_FileModal.js create mode 100644 src/essence/Tools/Draw/DrawTool_Templater.css create mode 100644 src/essence/Tools/Draw/DrawTool_Templater.js diff --git a/run/server.js b/run/server.js index f1302d1e..7c74144c 100644 --- a/run/server.js +++ b/run/server.js @@ -598,16 +598,6 @@ setups.getBackendSetups(function (setups) { } ); - //help - app.get( - `${ROOT_PATH}/help`, - ensureUser(), - ensureGroup(permissions.users), - (req, res) => { - res.render("help", {}); - } - ); - // API //TEST app.post(`${ROOT_PATH}/api/test`, function (req, res) { diff --git a/src/css/mmgisUI.css b/src/css/mmgisUI.css index 97060686..590c5f03 100644 --- a/src/css/mmgisUI.css +++ b/src/css/mmgisUI.css @@ -1581,3 +1581,45 @@ input::-webkit-inner-spin-button { .mmgisToast.failure { background-color: #a11717; } + +/*spinner1*/ +.mmgis-spinner1 { + -webkit-animation: spinner1_rotate 2s linear infinite; + animation: spinner1_rotate 2s linear infinite; + z-index: 2; + position: absolute; + top: 50%; + left: 50%; + margin: 0; + width: 20px; + height: 20px; + top: calc(50% - 10px); + left: calc(50% - 10px); +} +.mmgis-spinner1 .path { + stroke: var(--color-c); + stroke-linecap: round; + -webkit-animation: spinner1_dash 1.5s ease-in-out infinite; + animation: spinner1_dash 1.5s ease-in-out infinite; +} + +@keyframes spinner1_rotate { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes spinner1_dash { + 0% { + stroke-dasharray: 1, 150; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -35; + } + 100% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -124; + } +} diff --git a/src/essence/Tools/Draw/DrawTool.css b/src/essence/Tools/Draw/DrawTool.css index 6473d7ac..213f890a 100644 --- a/src/essence/Tools/Draw/DrawTool.css +++ b/src/essence/Tools/Draw/DrawTool.css @@ -41,7 +41,7 @@ } .drawToolButton1 { - background: var(--color-o); + background: #067ca7; padding: 3px 8px; margin: 5px 0px; text-align: center; @@ -237,46 +237,61 @@ border-bottom: 1px solid var(--color-mmgis); border-left: 1px solid var(--color-mmgis); } -#drawToolDrawFilesNew { +#drawToolDrawFilesNewUpload { cursor: pointer; - width: 30px; + flex: 1; + line-height: 26px; height: 30px; background: var(--color-a1); + color: var(--color-a6); + text-align: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + transition: background 0.2s ease-out; + display: flex; + justify-content: center; + border-right: 1px solid var(--color-a1-5); +} +#drawToolDrawFilesNewUpload:hover { + background: var(--color-a2); color: var(--color-a7); +} +#drawToolDrawFilesNewUpload > div:first-child { + line-height: 31px; + font-size: 10px; +} +#drawToolDrawFilesNewUpload > div:last-child { line-height: 30px; + margin-left: 2px; +} +#drawToolDrawFilesNew { + cursor: pointer; + flex: 1; + line-height: 26px; + height: 30px; + background: var(--color-a1); + color: var(--color-a6); text-align: center; border-bottom: 1px solid rgba(0, 0, 0, 0.3); - transition: color 0.2s ease-out; + transition: background 0.2s ease-out; + display: flex; + justify-content: center; + border-left: 1px solid var(--color-a1-5); } - -#drawToolDrawFilesNewName:focus ~ #drawToolDrawFilesNew { - background: var(--color-mmgis); +#drawToolDrawFilesNew:hover { + background: var(--color-a2); + color: var(--color-a7); } -#drawToolDrawFilesNewLoading { - width: 100%; - height: 2px; - position: absolute; - top: 28px; - left: 0px; - overflow: hidden; - opacity: 0; - transition: opacity 0.1s cubic-bezier(0.445, 0.05, 0.55, 0.95); +#drawToolDrawFilesNew > div:first-child { + line-height: 31px; + font-size: 10px; } -#drawToolDrawFilesNewLoading > div { - width: 30%; - height: 100%; - background: var(--color-c); - animation: drawToolLoading1 1s ease-in-out infinite; +#drawToolDrawFilesNew > div:last-child { + line-height: 30px; + margin-left: 2px; } -@keyframes drawToolLoading1 { - 0% { - transform: translateX(-100%); - width: 30%; - } - 100% { - transform: translateX(230%); - width: 60%; - } + +#drawToolDrawFilesNewName:focus ~ #drawToolDrawFilesNew { + background: var(--color-mmgis); } .drawToolFileDelete { @@ -564,6 +579,7 @@ height: 100%; display: flex; flex-flow: column; + position: relative; } #drawToolMaster { @@ -1228,16 +1244,11 @@ margin-bottom: 8px; flex-flow: column; } -.drawToolContextMenuPropertiesName > div { - height: 30px; - line-height: 30px; -} .drawToolContextMenuPropertiesDescription { flex-flow: column; -} -.drawToolContextMenuPropertiesDescription > div { - height: 30px; - line-height: 30px; + border-bottom: 1px solid var(--color-a1); + padding-bottom: 8px; + margin-bottom: 8px; } #drawToolContextMenuPropertiesDescription { width: 100%; @@ -1356,14 +1367,13 @@ .drawToolFileEditOnDescription { display: flex; justify-content: space-between; - margin-bottom: 5px; } .drawToolFileDesc { background: var(--color-a); border: none; border-top: 1px solid black; color: var(--color-f); - min-width: 411px; + min-width: 452px; max-width: 750px; min-height: 100px; max-height: 400px; @@ -2197,35 +2207,6 @@ background: var(--color-c); } -#drawToolFileUpload { - cursor: pointer; - width: 30px; - height: 30px; - background: var(--color-a1); - color: var(--color-a5); - border-right: 1px solid var(--color-a); - box-sizing: border-box; - line-height: 30px; - text-align: center; - transition: background 0.2s ease-out; -} -#drawToolFileUpload:hover { - color: var(--color-a7); - background: var(--color-a2); -} -#drawToolFileUpload i { - cursor: pointer; - pointer-events: none; -} -#drawToolFileUpload input { - position: absolute; - left: 7px; - top: 7px; - opacity: 0; - height: 30px; - width: 26px; -} - .leaflet-marker-icon.leaflet-div-icon.leaflet-editing-icon.leaflet-touch-icon.leaflet-zoom-animated.leaflet-interactive.leaflet-marker-draggable { width: 12px !important; height: 12px !important; @@ -2537,7 +2518,7 @@ display: none; background: var(--color-k); color: var(--color-f); - padding: 15px; + padding: 10px; overflow-y: auto; height: calc(100vh - 186px); } @@ -2545,14 +2526,136 @@ display: inherit; } +.drawToolContextMenuPropertiesTitle { + height: 30px; + line-height: 30px; + color: var(--color-query); +} .drawToolContextMenuPropertiesExtended { - margin: 16px 0px; - padding: 8px 0px; - border-top: 1px solid var(--color-a1); + margin-bottom: 12px; + padding-bottom: 12px; border-bottom: 1px solid var(--color-a1); } .drawToolContextMenuPropertiesExtended > li { display: flex; justify-content: space-between; padding: 2px 0px; -} \ No newline at end of file +} + +#drawToolContextMenuPropertiesTemplate { + border-bottom: 1px solid var(--color-a1); + margin-bottom: 12px; +} + +.drawToolFileTemplate { +} +.drawToolFileTemplate > div:first-child { +} +.drawToolFileTemplate > div:last-child { + display: flex; + cursor: pointer; +} +.drawToolFileTemplate > div:last-child > div { + overflow: hidden; + text-overflow: ellipsis; + max-width: 120px; +} + +.drawToolFileTemplate > div:last-child > i { + background: var(--color-a6); + color: var(--color-a-5); + border-radius: 50%; + width: 16px; + height: 16px; + text-align: center; + line-height: 16px; + margin-left: 4px; + margin-top: -2px; + transition: background 0.2s ease-in-out; +} +.drawToolFileTemplate > div:last-child:hover > i { + background: var(--color-c); +} + +/*FILE INFO TEMPLATE MODAL*/ +#drawToolFileTemplateEditModal { + background: var(--color-a-5); + min-width: 600px; +} +#drawToolFileTemplateEditModalTitle { + padding: 0px 0px 0px 10px; + height: 40px; + line-height: 39px; + font-size: 18px; + background: var(--color-i); + border-top-left-radius: 3px; + border-top-right-radius: 3px; + display: flex; + justify-content: space-between; +} +#drawToolFileTemplateEditModalTitle > div:first-child { + display: flex; +} +#drawToolFileTemplateEditModalTitle > div:first-child > div { + margin-left: 6px; + line-height: 40px; +} +#drawToolFileTemplateEditModalClose { + width: 40px; + height: 40px; + text-align: center; +} +#drawToolFileTemplateEditModalActions { + display: flex; + padding: 6px 10px; + justify-content: space-between; + background: var(--color-a1); + border-top: 1px solid var(--color-a); +} +#drawToolFileTemplateEditModalActions > div { + width: 70px; + height: 35px; + line-height: 30px; + margin: 0; +} +#drawToolFileTemplateEditModalActionsCancel { + background: var(--color-a1); +} + +.drawToolContextMenuPropertiesCollapsible + > .drawToolContextMenuPropertiesTitle { + display: flex; + justify-content: space-between; + cursor: pointer; +} +.drawToolContextMenuPropertiesCollapsible + > .drawToolContextMenuPropertiesTitle + > i { + transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} +.drawToolContextMenuPropertiesCollapsible + > .drawToolContextMenuPropertiesTitle:hover + > i { + color: var(--color-c); +} +.drawToolContextMenuPropertiesCollapsible.state-collapsed + > .drawToolContextMenuPropertiesTitle + > i { + transform: rotateZ(-90deg); +} +.drawToolContextMenuPropertiesCollapsible.state-collapsed > div:last-child { + display: none; +} + +/*files loading spinner*/ +#drawToolFilesLoadingSpinner { + position: absolute; + pointer-events: none; + width: 100%; + height: 100%; + opacity: 0; + transition: opacity 0.2s ease-in-out; +} +#drawToolFilesLoadingSpinner.on { + opacity: 1; +} diff --git a/src/essence/Tools/Draw/DrawTool.js b/src/essence/Tools/Draw/DrawTool.js index a11ef96f..def4cbda 100644 --- a/src/essence/Tools/Draw/DrawTool.js +++ b/src/essence/Tools/Draw/DrawTool.js @@ -4,6 +4,7 @@ import DrawTool_Files from './DrawTool_Files' import DrawTool_History from './DrawTool_History' import DrawTool_Publish from './DrawTool_Publish' import DrawTool_Shapes from './DrawTool_Shapes' +import DrawTool_FileModal from './DrawTool_FileModal' import $ from 'jquery' import * as d3 from 'd3' @@ -17,13 +18,13 @@ import CursorInfo from '../../Ancillary/CursorInfo' import Description from '../../Ancillary/Description' import { Kinds } from '../../../pre/tools' import turf from 'turf' -import shp from '../../../external/shpjs/shapefile' -import shpwrite from '../../../external/SHPWrite/shpwrite' import calls from '../../../pre/calls' import './DrawTool.css' +import tippy from 'tippy.js' + // Plugins import DrawTool_Geologic from './Plugins/Geologic/DrawTool_Geologic' import DrawTool_SetOperations from './Plugins/SetOperations/DrawTool_SetOperations' @@ -58,24 +59,6 @@ var markup = [ "
", "
", - "
", - "
", - "", - "", - "
", - "", - "", - "", - "
", - "
", //"
Choose a file to draw in
", "
", "
", @@ -203,14 +186,36 @@ var markup = [ "
", "
    ", "
", + "
", + "", + "", + "", + "
", "
", "
", + + "
", + //"", + /* + "", + */ + "
UPLOAD
", + "
CREATE
", + "
", "
", "
", "
", "", - "
", + "
", "
", "
", "
", @@ -269,6 +274,7 @@ var DrawTool = { activeContent: 'draw', intentType: null, currentFileId: null, + _firstGetFiles: null, filesOn: [], allTags: {}, //: count, ... tags: [], @@ -501,6 +507,7 @@ var DrawTool = { DrawTool.activeContent = 'draw' DrawTool.intentType = null DrawTool.currentFileId = null + DrawTool._firstGetFiles = null //DrawTool.filesOn = []; DrawTool.isEditing = false @@ -569,147 +576,6 @@ var DrawTool = { $(this).parent().find('div').removeClass('active') $(this).addClass('active') }) - //Upload - $('#drawToolFileUpload > input').on('change', function (evt) { - $('#drawToolDrawFilesNewLoading').css('opacity', '1') - $('#drawToolFileUpload > i').css('color', '#1169d3') - - var files = evt.target.files // FileList object - - // use the 1st file from the list - var f = files[0] - var ext = F_.getExtension(f.name).toLowerCase() - switch (ext) { - case 'shp': - case 'dbf': - var shpFile - var dbfFile - for (var i = 0; i < files.length; i++) { - if ( - F_.getExtension(files[i].name).toLowerCase() == - 'shp' - ) - shpFile = files[i] - if ( - F_.getExtension(files[i].name).toLowerCase() == - 'dbf' - ) - dbfFile = files[i] - } - if (shpFile && dbfFile) { - var shpBuffer - var dbfBuffer - - var readerSHP = new FileReader() - readerSHP.onload = function (e) { - shpBuffer = e.target.result - var readerDBF = new FileReader() - readerDBF.onload = function (e) { - dbfBuffer = e.target.result - bothLoaded() - } - readerDBF.readAsArrayBuffer(dbfFile) - } - readerSHP.readAsArrayBuffer(shpFile) - - function bothLoaded() { - var featureArray = [] - shp.open(shpBuffer, dbfBuffer) - .then((source) => - source.read().then(function log(result) { - if (result.done) { - var geojsonResult = - F_.getBaseGeoJSON() - geojsonResult.features = - featureArray - var body = { - file_name: f.name, - intent: 'all', - geojson: - JSON.stringify( - geojsonResult - ), - } - DrawTool.makeFile( - body, - function () { - DrawTool.populateFiles() - endLoad() - } - ) - return - } - - featureArray.push( - F_.geoJSONFeatureMetersToDegrees( - result.value - ) - ) - return source.read().then(log) - }) - ) - .catch((error) => { - endLoad() - }) - } - } else { - CIU('Warning! FileManager - missing .shp or .dbf') - } - break - case 'json': - case 'geojson': - var reader = new FileReader() - // Closure to capture the file information. - - reader.onload = (function (file) { - return function (e) { - var body = { - file_name: file.name, - intent: 'all', - geojson: e.target.result, - } - if ( - body.geojson && - JSON.parse(body.geojson).type !== - 'FeatureCollection' - ) { - CIU( - 'Uploaded object has no type: "FeatureCollection". Are you sure this is geojson?' - ) - return - } - DrawTool.makeFile(body, function () { - DrawTool.populateFiles() - endLoad() - }) - } - })(f) - - // Read in the image file as a data URL. - reader.readAsText(f) - break - default: - CIU( - 'Only .json, .geojson and .shp (with .dbf) files may be uploaded' - ) - } - - function endLoad() { - $('#drawToolDrawFilesNewLoading').css('opacity', '0') - $('#drawToolFileUpload > i').css('color', 'unset') - } - function CIU(message) { - CursorInfo.update( - message, - 6000, - true, - { x: 305, y: 6 }, - '#e9ff26', - 'black' - ) - endLoad() - } - }) }, destroy: function () { this.MMGISInterface.separateFromMMGIS() @@ -1061,6 +927,11 @@ var DrawTool = { .trimEnd() }, getFiles: function (callback) { + // setLoading + if (DrawTool._firstGetFiles !== true) { + $(`#drawToolFilesLoadingSpinner`).addClass('on') + DrawTool._firstGetFiles = true + } calls.api( 'files_getfiles', {}, @@ -1088,29 +959,44 @@ var DrawTool = { DrawTool.files[i] ) } - DrawTool.allTags = DrawTool.getAllTags(true) DrawTool.tags = Object.keys(DrawTool.allTags) } if (typeof callback === 'function') callback() + + // endLoading + $(`#drawToolFilesLoadingSpinner`).removeClass('on') }, function (data) { if (data && data.message == 'User is not logged in.') { $('#drawToolNotLoggedIn').css('display', 'inherit') } + // endLoading + $(`#drawToolFilesLoadingSpinner`).removeClass('on') } ) }, makeFile: function (body, callback) { + const filename = body.file_name calls.api( 'files_make', body, function (data) { - if (data.status === 'success') + if (data.status === 'success') { DrawTool.getFiles(() => { callback(data.body.file_id) + CursorInfo.update( + `Successfully made new file: ${filename}`, + 4000, + false, + { x: 305, y: 6 }, + '#009eff', + 'white', + null, + true + ) }) - else + } else CursorInfo.update( 'Failed to add file.', 6000, @@ -1301,6 +1187,21 @@ var DrawTool = { } } }, + enforceTemplate(geojson, templateObj) { + if (templateObj == null || templateObj.template == null) return geojson + const templateEnforcedFeatures = [] + geojson.features.forEach((f) => { + const newF = JSON.parse(JSON.stringify(f)) + newF.properties = newF.properties || {} + templateObj.template.forEach((t) => { + if (!newF.properties.hasOwnProperty([t.field])) + newF.properties[t.field] = t.default + }) + templateEnforcedFeatures.push(newF) + }) + geojson.features = templateEnforcedFeatures + return geojson + }, } // @@ -1318,6 +1219,12 @@ function interfaceWithMMGIS() { tools = tools.append('div').style('height', '100%') //Add the markup to tools or do it manually tools.html(markup) + + tippy('#drawToolDrawFilesNew', { + content: 'New File', + placement: 'right', + theme: 'blue', + }) //Force intent mappings $('#drawToolNewFileAll').html(DrawTool.intentNameMapping.all) $('#drawToolNewFileROI').html(DrawTool.intentNameMapping.roi) @@ -1421,41 +1328,14 @@ function interfaceWithMMGIS() { } else return true }) + $('#drawToolDrawFilesNewUpload').on('click', function () { + DrawTool_FileModal.newFileModal(DrawTool, function () { + $('#drawToolFileUpload > input').click() + }) + }) //Adding a new file $('#drawToolDrawFilesNew').on('click', function () { - var val = $('#drawToolDrawFilesNewName').val() - var intent = $('#drawToolDrawFilesNewDiv > select').val() - if (val == null || val == '') { - CursorInfo.update( - 'Please enter a file name.', - 6000, - true, - { x: 305, y: 6 }, - '#e9ff26', - 'black' - ) - return - } - if (/[&\'\"<>]/g.test(val)) { - CursorInfo.update( - 'Invalid file name.', - 6000, - true, - { x: 305, y: 6 }, - '#e9ff26', - 'black' - ) - return - } - var body = { - file_name: val || 'New File', - intent: intent, - } - DrawTool.makeFile(body, function (file_id) { - DrawTool.populateFiles(file_id) - - $('#drawToolDrawFilesNewName').val('') - }) + DrawTool_FileModal.newFileModal(DrawTool) }) //Copy shapes diff --git a/src/essence/Tools/Draw/DrawTool.test.js b/src/essence/Tools/Draw/DrawTool.test.js index 39db7185..e8419797 100644 --- a/src/essence/Tools/Draw/DrawTool.test.js +++ b/src/essence/Tools/Draw/DrawTool.test.js @@ -852,20 +852,20 @@ var Test = { ).click() c( 'Can open file popup', - $('#mmgisModal .drawToolFileEditOn').css('display') != + $('.mmgisModal .drawToolFileEditOn').css('display') != 'none' ) - $('#mmgisModal .drawToolFileNameInput').val( + $('.mmgisModal .drawToolFileNameInput').val( 'Test Trail Altered' ) - $('#mmgisModal .drawToolFileDesc').text( + $('.mmgisModal .drawToolFileDesc').text( 'Description Altered' ) - $('#mmgisModal #drawToolFileEditOnPublicityDropdown').val( + $('.mmgisModal #drawToolFileEditOnPublicityDropdown').val( 'private' ) - $('#mmgisModal .drawToolFileSave').click() + $('.mmgisModal .drawToolFileSave').click() setTimeout(function () { $( @@ -880,22 +880,22 @@ var Test = { ) c( 'File name input updates', - $('#mmgisModal .drawToolFileNameInput').val() === + $('.mmgisModal .drawToolFileNameInput').val() === 'Test Trail Altered' ) c( 'File description updates', - $('#mmgisModal .drawToolFileDesc').text() === + $('.mmgisModal .drawToolFileDesc').text() === 'Description Altered' ) c( 'File publicity updates', $( - '#mmgisModal #drawToolFileEditOnPublicityDropdown' + '.mmgisModal #drawToolFileEditOnPublicityDropdown' ).val() == 'private' ) - $('#mmgisModal .drawToolFileCancel').click() + $('.mmgisModal .drawToolFileCancel').click() }, Test.timeout * 3) }, Test.timeout) }, diff --git a/src/essence/Tools/Draw/DrawTool_Editing.js b/src/essence/Tools/Draw/DrawTool_Editing.js index 3b38ba06..bcacc877 100644 --- a/src/essence/Tools/Draw/DrawTool_Editing.js +++ b/src/essence/Tools/Draw/DrawTool_Editing.js @@ -7,6 +7,8 @@ import Map_ from '../../Basics/Map_/Map_' import UserInterface_ from '../../Basics/UserInterface_/UserInterface_' import turf from 'turf' +import DrawTool_Templater from './DrawTool_Templater' + import calls from '../../../pre/calls' var DrawTool = null @@ -56,6 +58,8 @@ var Editing = { return } + let templater + //ctrl does lots. Here, if ctrl is pressed, check whether the layer is already selected. // If it is, remove it var deselecting = false @@ -591,44 +595,58 @@ var Editing = { "
", "
", "
", - "
Name
", + "
Name
", "", "
", "
", - "
Description
", - "", + "
Description
", + "", "
", - (hasLengthMetric) ? [ - "
", - "
Length
", - `
${F_.getFeatureLength(DrawTool.contextMenuLayer.feature, true)}
`, - "
", - ].join('\n') : "", - (hasPerimeterMetric) ? [ - "
", - "
Perimeter
", - `
${F_.getFeatureLength(DrawTool.contextMenuLayer.feature, true)}
`, - "
", - ].join('\n') : "", - (hasAreaMetric) ? [ - "
", - "
Area
", - `
${F_.getFeatureArea(DrawTool.contextMenuLayer.feature, true)}
`, + (file.template != null ) ? [ + "
", + `
Template (${file.template?.name})
`, + "
", + "
"].join('\n') : "", + "
", + "
Metrics
", + "
", + (hasLengthMetric) ? [ + "
", + "
Length
", + `
${F_.getFeatureLength(DrawTool.contextMenuLayer.feature, true)}
`, + "
", + ].join('\n') : "", + (hasPerimeterMetric) ? [ + "
", + "
Perimeter
", + `
${F_.getFeatureLength(DrawTool.contextMenuLayer.feature, true)}
`, + "
", + ].join('\n') : "", + (hasAreaMetric) ? [ + "
", + "
Area
", + `
${F_.getFeatureArea(DrawTool.contextMenuLayer.feature, true)}
`, + "
", + ].join('\n') : "", + (DrawTool.contextMenuLayer?.feature?.properties?._radius != null) ? [ + "
", + "
Radius
", + `
${DrawTool.contextMenuLayer.feature.properties._radius.toFixed(3)}m
`, + "
", + ].join('\n') : "", "
", - ].join('\n') : "", - (DrawTool.contextMenuLayer?.feature?.properties?._radius != null) ? [ - "
", - "
Radius
", - `
${DrawTool.contextMenuLayer.feature.properties._radius.toFixed(3)}m
`, + "
", + "
", + "
Properties
", + "
", + Object.keys(DrawTool.contextMenuLayer.feature.properties).map((p) => { + const pv = DrawTool.contextMenuLayer.feature.properties[p] + if(p === 'uuid') return '' + if( typeof pv === 'number' || typeof pv === 'string' || typeof pv === 'boolean') + return `
  • ${p}
    ${pv}
  • ` + else return '' + }).join('\n'), "
    ", - ].join('\n') : "", - "
    ", - Object.keys(DrawTool.contextMenuLayer.feature.properties).map((p) => { - const pv = DrawTool.contextMenuLayer.feature.properties[p] - if( typeof pv === 'number' || typeof pv === 'string') - return `
  • ${p}
    ${pv}
  • ` - else return '' - }).join('\n'), "
    ", (!displayOnly) ? ["
    ", "
    " + uuid + "
    ", @@ -851,6 +869,17 @@ var Editing = { ].join('\n'); $('#uiRightPanel').empty() $('#uiRightPanel').append(markup) + + templater = DrawTool_Templater.renderTemplate( + 'drawToolContextMenuPropertiesTemplate', + file.template, + DrawTool.contextMenuLayer?.feature?.properties + ) + $( + `.drawToolContextMenuPropertiesCollapsible > .drawToolContextMenuPropertiesTitle` + ).on('click', function () { + $(this).parent().toggleClass('state-collapsed') + }) UserInterface_.openRightPanel(360) $('#drawToolContextMenuPropertiesDescription').text(description) @@ -2321,6 +2350,9 @@ var Editing = { if (DrawTool.plugins?.Geologic?.custom?.resetGeologic) DrawTool.plugins.Geologic.custom.resetGeologic() + const templaterProperties = templater.getValues() + if (templaterProperties === false) return + if (!grouping) { //Then just a regular single save if (typeof DrawTool.contextMenuLayer.toGeoJSON === 'function') @@ -2341,12 +2373,14 @@ var Editing = { DrawTool.contextMenuLayer._latlng ) - var newProperties = properties + let newProperties = properties newProperties.style = properties.style || {} setProperties(newProperties) - var newGeometry + newProperties = { ...newProperties, ...templaterProperties } + + let newGeometry if (featureType != 'note' && featureType != 'arrow') { newGeometry = DrawTool.contextMenuLayer.toGeoJSON( L_.GEOJSON_PRECISION @@ -2459,6 +2493,11 @@ var Editing = { setProperties(newProperties) + newProperties = { + ...newProperties, + ...templaterProperties, + } + calls.api( 'draw_edit', { diff --git a/src/essence/Tools/Draw/DrawTool_FileModal.css b/src/essence/Tools/Draw/DrawTool_FileModal.css new file mode 100644 index 00000000..69ff0e33 --- /dev/null +++ b/src/essence/Tools/Draw/DrawTool_FileModal.css @@ -0,0 +1,182 @@ +.drawToolFileModal { + min-width: 615px; + color: var(--color-f); + background: var(--color-a); + box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.5); + border-radius: 2px; + margin: 30px 120px 100px 120px; + max-height: 80vh; + font-size: 14px; +} +.drawToolFileModal .drawToolButton1 { + padding: 5px 12px; +} + +.drawToolFileModal input[type='text'], +.drawToolFileModal input[type='number'] { + flex: 1 1; + border: none; + padding: 0px 8px; + font-size: 14px; + height: 30px; + width: 100%; + background: var(--color-a1-5); + color: var(--color-a7); +} +#drawToolFileModalHeading { + display: flex; + justify-content: space-between; + height: 42px; + padding: 8px 10px 0px 10px; + margin-bottom: 8px; + position: relative; + background: var(--color-a1); + border-bottom: 1px solid var(--color-a); +} +#drawToolFileModalHeadingName { + font-size: 18px; + line-height: 26px; + padding-left: 4px; +} +#drawToolFileModalHeading > div { + display: flex; + padding: 0px; +} +#drawToolFileModalBody { +} +#drawToolFileModalBody > div { + margin-bottom: 16px; +} + +#drawToolFileModalBodyName { + padding: 0px 12px; + flex: 1; +} +#drawToolFileModalBodyName > div { + line-height: 30px; + color: var(--color-blue); +} +#drawToolFileModelUploadedFrom { + color: var(--color-p0); + font-size: 14px; + text-align: center; +} +#drawToolFileModalBodyTemplate { + padding: 0px 12px; + flex: 1; +} +#drawToolFileModalBodyTemplate > div { + display: flex; +} +#drawToolFileModalBodyTemplate > div > i { + margin: 0px 4px; +} +#drawToolFileModalBodyTemplate > div:first-child { + padding-right: 8px; + line-height: 30px; + color: var(--color-blue); +} +#drawToolFileModalBodyTemplate > div > span { + margin: 0px 10px; + line-height: 30px; +} + +#drawToolFileModalTemplateDropdown { + flex: 1; + height: 30px; + background: var(--color-a1-5); +} +#drawToolFileModalTemplateDropdown .dropy { + margin-bottom: 0; +} +#drawToolFileModalTemplateDropdown .dropy__title { + line-height: 11px; + font-size: 14px; +} +#drawToolFileModalTemplateDropdown .dropy__title > i { + transform: translateY(30%); +} +#drawToolFileModalTemplateNew { + width: 72px; + margin: 0px; + height: 30px; + line-height: 22px; + font-size: 13px; + background: var(--color-a1); + transition: background 0.2s ease-in-out; +} +#drawToolFileModalTemplateNew:hover { + background: var(--color-a2); +} +#drawToolFileModalActions { + display: flex; + padding: 2px 10px; + justify-content: space-between; + background: var(--color-a1); + border-top: 1px solid var(--color-a); +} +#drawToolFileModalActionsCancel { + background: var(--color-a1); +} + +#drawToolFileUpload { + visibility: hidden; + pointer-events: none; + position: relative; + top: -4px; + font-size: 12px; + width: 74px; + padding: 0px 8px; + cursor: pointer; + height: 30px; + background: var(--color-a1-5); + color: var(--color-a5); + box-sizing: border-box; + line-height: 30px; + text-align: center; + transition: background 0.2s ease-out; +} +#drawToolFileUpload:hover { + color: var(--color-a7); + background: var(--color-a2); +} +#drawToolFileUpload i { + cursor: pointer; + pointer-events: none; +} +#drawToolFileUpload input { + cursor: pointer; + position: absolute; + left: 0px; + top: 0px; + opacity: 0; + height: 30px; + width: 68px; +} + +#drawToolDrawFilesNewLoading { + width: 100%; + height: 3px; + position: absolute; + bottom: -3px; + left: 0px; + overflow: hidden; + opacity: 0; + transition: opacity 0.1s cubic-bezier(0.445, 0.05, 0.55, 0.95); +} +#drawToolDrawFilesNewLoading > div { + width: 30%; + height: 100%; + background: var(--color-c); + animation: drawToolLoading1 1s ease-in-out infinite; +} +@keyframes drawToolLoading1 { + 0% { + transform: translateX(-100%); + width: 30%; + } + 100% { + transform: translateX(230%); + width: 60%; + } +} diff --git a/src/essence/Tools/Draw/DrawTool_FileModal.js b/src/essence/Tools/Draw/DrawTool_FileModal.js new file mode 100644 index 00000000..29adae6a --- /dev/null +++ b/src/essence/Tools/Draw/DrawTool_FileModal.js @@ -0,0 +1,417 @@ +import $ from 'jquery' +import * as d3 from 'd3' +import F_ from '../../Basics/Formulae_/Formulae_' +import L_ from '../../Basics/Layers_/Layers_' + +import CursorInfo from '../../Ancillary/CursorInfo' +import Modal from '../../Ancillary/Modal' +import Dropy from '../../../external/Dropy/dropy' +import tippy from 'tippy.js' +import shp from '../../../external/shpjs/shapefile' +import shpwrite from '../../../external/SHPWrite/shpwrite' + +import DrawTool_Templater from './DrawTool_Templater' + +import './DrawTool_FileModal.css' + +const DrawTool_FileModal = { + newFileModalTemplateIndex: 0, + newFileModal: function (DrawTool, cb) { + // prettier-ignore + const modalContent = [ + "
    ", + "
    ", + "
    ", + "", + "
    ", + "New File", + "
    ", + "
    ", + "
    ", + "Upload", + "", + "", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    File Name
    ", + "", + "
    ", + "
    ", + "
    Property Template
    ", + "
    ", + "", + "or", + "
    NEW
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    Cancel
    ", + "
    CREATE
    ", + "
    ", + "
    " + ].join('\n') + const templates = DrawTool.vars.templates || {} + + let allTemplates = {} + if (DrawTool.files) { + DrawTool.files.forEach((f) => { + if ( + f.template != null && + f.template.name != null && + f.template.template != null + ) { + allTemplates[f.template.name] = f.template.template + } + }) + } + + const templateItems = ['NONE'] + .concat(Object.keys(DrawTool.vars.templates)) + .concat(Object.keys(allTemplates).sort()) + + allTemplates = { + ...allTemplates, + ...JSON.parse(JSON.stringify(DrawTool.vars.templates || {})), + } + + let body = false + Modal.remove() + Modal.set(modalContent, function () { + tippy('#drawToolFileModalBodyTemplateInfo', { + content: `Assign a form to this file that each feature's properties must conform to.`, + placement: 'right', + theme: 'blue', + }) + + //Upload + $('#drawToolFileUpload > input').on('change', function (evt) { + $('#drawToolDrawFilesNewLoading').css('opacity', '1') + $('#drawToolFileUpload > i').css('color', '#1169d3') + + var files = evt.target.files // FileList object + + // use the 1st file from the list + var f = files[0] + var ext = F_.getExtension(f.name).toLowerCase() + switch (ext) { + case 'shp': + case 'dbf': + var shpFile + var dbfFile + for (var i = 0; i < files.length; i++) { + if ( + F_.getExtension(files[i].name).toLowerCase() == + 'shp' + ) + shpFile = files[i] + if ( + F_.getExtension(files[i].name).toLowerCase() == + 'dbf' + ) + dbfFile = files[i] + } + if (shpFile && dbfFile) { + var shpBuffer + var dbfBuffer + + var readerSHP = new FileReader() + readerSHP.onload = function (e) { + shpBuffer = e.target.result + var readerDBF = new FileReader() + readerDBF.onload = function (e) { + dbfBuffer = e.target.result + bothLoaded() + } + readerDBF.readAsArrayBuffer(dbfFile) + } + readerSHP.readAsArrayBuffer(shpFile) + + function bothLoaded() { + var featureArray = [] + shp.open(shpBuffer, dbfBuffer) + .then((source) => + source + .read() + .then(function log(result) { + if (result.done) { + var geojsonResult = + F_.getBaseGeoJSON() + geojsonResult.features = + featureArray + body = { + file_name: f.name, + intent: 'all', + geojson: + JSON.stringify( + geojsonResult + ), + } + uploaded(body, f) + return + } + + featureArray.push( + F_.geoJSONFeatureMetersToDegrees( + result.value + ) + ) + return source.read().then(log) + }) + ) + .catch((error) => { + endLoad() + }) + } + } else { + CIU('Warning! FileManager - missing .shp or .dbf') + } + break + case 'json': + case 'geojson': + var reader = new FileReader() + // Closure to capture the file information. + + reader.onload = (function (file) { + return function (e) { + body = { + file_name: file.name, + intent: 'all', + geojson: e.target.result, + } + if ( + body.geojson && + JSON.parse(body.geojson).type !== + 'FeatureCollection' + ) { + CIU( + 'Uploaded object has no type: "FeatureCollection". Are you sure this is geojson?' + ) + return + } + uploaded(body, file) + } + })(f) + + // Read in the image file as a data URL. + reader.readAsText(f) + break + case 'rksml': + const readerRKSML = new FileReader() + // Closure to capture the file information. + + readerRKSML.onload = (function (file) { + return function (e) { + let rksmlBody = { + to: 'geojson', + abbreviated: true, + rksml: e.target.result, + } + + $.ajax({ + type: 'POST', + url: `${ + window.mmgisglobal.ROOT_PATH + ? window.mmgisglobal.ROOT_PATH + '/' + : '' + }API/rksml/convert`, + data: rksmlBody, + xhrFields: { + withCredentials: true, + }, + success: function (data) { + if ( + data && + data.type !== 'FeatureCollection' + ) { + CIU( + 'RKSML failed to convert into GeoJSON.' + ) + return + } + + body = { + file_name: file.name, + intent: 'all', + geojson: JSON.stringify(data), + } + + uploaded(body, file) + }, + error: function () { + endLoad() + }, + }) + } + })(f) + + // Read in the image file as a data URL. + readerRKSML.readAsText(f) + break + default: + CIU( + 'Only .json, .geojson, .rksml and .shp (with .dbf) files may be uploaded' + ) + } + + function uploaded(body, file) { + $('#drawToolFileModelUploadedFrom').text( + `Uploaded from: ${body.file_name} (${F_.humanFileSize( + file.size + )})` + ) + $('.drawToolFileModalName').val( + (body.file_name || '').replace(/\.[^/.]+$/, '') + ) + if (body?.geojson) { + try { + const geojson = JSON.parse(body.geojson) + const templateFromThisFeature = + geojson.features[0] || null + DrawTool_Templater.renderDesignTemplate( + 'drawToolFileModalTemplateContainer', + null, + true, + templateFromThisFeature + ) + } catch (err) {} + } + endLoad() + } + + function endLoad() { + $('#drawToolDrawFilesNewLoading').css('opacity', '0') + $('#drawToolFileUpload > i').css('color', 'unset') + } + function CIU(message) { + CursorInfo.update( + message, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + endLoad() + } + }) + + DrawTool_FileModal.newFileModalTemplateIndex = 0 + $('#drawToolFileModalTemplateDropdown').html( + Dropy.construct( + templateItems, + 'Templates', + DrawTool_FileModal.newFileModalTemplateIndex, + { + openUp: false, + dark: true, + } + ) + ) + Dropy.init($('#drawToolFileModalTemplateDropdown'), function (idx) { + DrawTool_FileModal.newFileModalTemplateIndex = idx + + DrawTool_Templater.renderDesignTemplate( + 'drawToolFileModalTemplateContainer', + { + name: templateItems[idx], + template: allTemplates[templateItems[idx]], + } + ) + }) + + $('#drawToolFileModalTemplateNew').on('click', function () { + DrawTool_Templater.renderDesignTemplate( + 'drawToolFileModalTemplateContainer' + ) + }) + + $('#drawToolFileModalActionsCreate').on('click', function () { + const val = $('.drawToolFileModalName').val() + const intent = 'all' + //templateItems[DrawTool_FileModal.newFileModalTemplateIndex] + if (val == null || val === '') { + CursorInfo.update( + 'Please enter a file name.', + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return + } + if (/[&\'\"<>]/g.test(val)) { + CursorInfo.update( + 'Invalid file name.', + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return + } + let chosenTemplate = + templateItems[DrawTool_FileModal.newFileModalTemplateIndex] + if (chosenTemplate === 'NONE') chosenTemplate = null + else { + if (templates[chosenTemplate] != null) + chosenTemplate = JSON.stringify({ + name: chosenTemplate, + template: templates[chosenTemplate], + }) + else chosenTemplate = null + } + + let finalBody + if (body !== false) { + finalBody = body + finalBody.file_name = val || 'New File' + finalBody.template = chosenTemplate + } else + finalBody = { + file_name: val || 'New File', + intent: intent, + template: chosenTemplate, + } + + const designedTemplate = DrawTool_Templater.getDesignedTemplate( + 'drawToolFileModalTemplateContainer', + allTemplates + ) + if (designedTemplate === true) { + // Do nothing and continue; user was not designing a new template + } else if (designedTemplate === false) { + // User was designing, but it had errors + return + } else { + finalBody.template = JSON.stringify(designedTemplate) + } + + DrawTool.makeFile(finalBody, function (file_id) { + DrawTool.populateFiles(file_id) + + Modal.remove() + $('.drawToolFileModalName').val('') + }) + }) + + $('#drawToolFileModalActionsCancel').on('click', function () { + Modal.remove() + $('.drawToolFileModalName').val('') + }) + + if (typeof cb === 'function') cb() + }) + }, +} + +export default DrawTool_FileModal diff --git a/src/essence/Tools/Draw/DrawTool_Files.js b/src/essence/Tools/Draw/DrawTool_Files.js index 337e785d..377800e8 100644 --- a/src/essence/Tools/Draw/DrawTool_Files.js +++ b/src/essence/Tools/Draw/DrawTool_Files.js @@ -8,6 +8,8 @@ import Map_ from '../../Basics/Map_/Map_' import CursorInfo from '../../Ancillary/CursorInfo' import Modal from '../../Ancillary/Modal' +import DrawTool_Templater from './DrawTool_Templater' + import '../../../external/JQuery/jquery.autocomplete' import calls from '../../../pre/calls' @@ -533,23 +535,23 @@ var Files = { // prettier-ignore let markup = [ - "
    ", - '
      ', - (!isHead && L_.Coordinates.mainType != 'll') ? `
    • Export GeoJSON (${L_.Coordinates.getMainTypeName()})
    • ` : "", - !isHead ? `
    • Export GeoJSON ${L_.Coordinates.mainType != 'll' ? '(lonlat)' : '' }
    • ` : "", - //"
    • Export as .shp
    • ", - (!isHead && !isPub) ? `
    • Toggle Labels
    • ` : "", - isHead ? `
    • Rename ${activeTagFolType === 'tags' ? "Tag" : "Folder"}
    • ` : "", - isHead ? `
    • Remove ${activeTagFolType === 'tags' ? "Tag" : "Folder"}
    • ` : "", - '
    ', - '
    ', - ].join('\n') + "
    ", + '
      ', + (!isHead && L_.Coordinates.mainType != 'll') ? `
    • Export GeoJSON (${L_.Coordinates.getMainTypeName()})
    • ` : "", + !isHead ? `
    • Export GeoJSON ${L_.Coordinates.mainType != 'll' ? '(lonlat)' : '' }
    • ` : "", + //"
    • Export as .shp
    • ", + (!isHead && !isPub) ? `
    • Toggle Labels
    • ` : "", + isHead ? `
    • Rename ${activeTagFolType === 'tags' ? "Tag" : "Folder"}
    • ` : "", + isHead ? `
    • Remove ${activeTagFolType === 'tags' ? "Tag" : "Folder"}
    • ` : "", + '
    ', + '
    ', + ].join('\n') $('body').append(markup) @@ -615,6 +617,10 @@ var Files = { L_.convertGeoJSONLngLatsToPrimaryCoordinates( geojson ) + geojson = DrawTool.enforceTemplate( + geojson, + d?.file?.[0]?.template + ) F_.downloadObject(geojson, filename, '.geojson') }) } @@ -933,9 +939,13 @@ var Files = { `
    ${file.created_on.split('T')[0]}
    `, "
    ", "
    ", - "
    Last Modified:
    ", + "
    Modified:
    ", `
    ${file.updated_on.split('T')[0]}
    `, "
    ", + "
    ", + "
    Template:
    ", + `
    ${file.template?.name || 'NONE'}
    `, + "
    ", "
    ", "
    ", "", @@ -973,6 +983,8 @@ var Files = { "
    " ].join('\n') + let template = file.template || null + // prettier-ignore const modalContent = [ "
    ", @@ -993,9 +1005,13 @@ var Files = { `
    ${file.created_on.split('T')[0]}
    `, "
    ", "
    ", - "
    Last Modified:
    ", + "
    Modified:
    ", `
    ${file.updated_on.split('T')[0]}
    `, "
    ", + "
    ", + "
    Template:
    ", + `
    ${template?.name || 'NONE'}
    `, + "
    ", "
    ", "
    ", "", @@ -1028,6 +1044,106 @@ var Files = { ? modalContentEditable : modalContent, function () { + // + $('#drawToolFileTemplateEdit').on('click', () => { + // prettier-ignore + const templateEditMarkup = [ + `
    `, + `
    `, + `
    Template
    `, + `
    `, + `
    `, + `
    `, + `
    `, + `
    `, + `
    Cancel
    `, + `
    Done
    `, + `
    `, + `
    ` + ].join('\n') + Modal.set( + templateEditMarkup, + function () { + $(`#drawToolFileTemplateEditModalClose`).on( + 'click', + function () { + Modal.remove(false, 1) + } + ) + $( + `#drawToolFileTemplateEditModalActionsCancel` + ).on('click', function () { + Modal.remove(false, 1) + }) + DrawTool_Templater.renderDesignTemplate( + 'drawToolFileTemplateContainer', + { + name: template?.name, + template: template?.template, + }, + template?.name == null + ) + $( + `#drawToolFileTemplateEditModalActionsDone` + ).on('click', function () { + let allTemplates = {} + if (DrawTool.files) { + DrawTool.files.forEach((f) => { + if ( + f.template != null && + f.template.name != null && + f.template.template != null + ) { + allTemplates[f.template.name] = + f.template.template + } + }) + } + allTemplates = { + ...allTemplates, + ...JSON.parse( + JSON.stringify( + DrawTool.vars.templates || {} + ) + ), + } + const designedTemplate = + DrawTool_Templater.getDesignedTemplate( + 'drawToolFileTemplateContainer', + allTemplates + ) + if (designedTemplate === true) { + template = null + $( + `#drawToolFileTemplateEdit > div > div` + ) + .text('NONE') + .css({ + color: 'var(--color-green)', + }) + // Do nothing and continue; user was not designing a new template + } else if (designedTemplate === false) { + // User was designing, but it had errors + return + } else { + template = JSON.parse( + JSON.stringify(designedTemplate) + ) + $( + `#drawToolFileTemplateEdit > div > div` + ) + .text(template.name) + .css({ + color: 'var(--color-green)', + }) + } + Modal.remove(false, 1) + }) + }, + function () {}, + 1 + ) + }) // Set up events $('#drawToolFileEditOnTagsNew').autocomplete({ lookup: DrawTool.tags, @@ -1220,6 +1336,7 @@ var Files = { .val() == 'public' ? 1 : 0, + template: JSON.stringify(template), } DrawTool.changeFile( diff --git a/src/essence/Tools/Draw/DrawTool_Templater.css b/src/essence/Tools/Draw/DrawTool_Templater.css new file mode 100644 index 00000000..8f0907d9 --- /dev/null +++ b/src/essence/Tools/Draw/DrawTool_Templater.css @@ -0,0 +1,271 @@ +#drawToolTemplater { +} + +#drawToolTemplater > li { + list-style-type: none; + display: flex; + justify-content: space-between; + height: 30px; + margin-bottom: 8px; +} + +#drawToolTemplater > li > div:first-child { + line-height: 30px; + padding-right: 10px; + width: 150px; + text-overflow: ellipsis; + overflow: hidden; +} +#drawToolTemplater > li input { + text-align: right; + font-size: 14px; +} +#drawToolTemplater > li textarea { + font-size: 14px; +} + +.drawToolTemplatercheckbox .mmgis-checkbox { + margin-right: 5px; + margin-top: 5px; +} + +.drawToolTemplaternumber { +} +.drawToolTemplatertextarea { + height: auto !important; +} +.drawToolTemplatertextarea textarea { + width: 100%; + min-height: 60px; + resize: vertical; + box-sizing: border-box; + margin-bottom: 4px; + color: var(--color-f) !important; + background: var(--color-i) !important; + font-size: 16px; + padding: 8px; + border: none; +} + +.drawToolTemplaterrange { + position: relative; +} +.drawToolTemplaterrange > input { + margin-top: 7px; + padding: 6px 0px !important; +} +.drawToolTemplaterrange > span { + position: absolute; + top: 8px; + right: 7px; + font-size: 14px; + color: white; + z-index: 10; + pointer-events: none; + mix-blend-mode: difference; +} + +.drawToolTemplaterdropdown .dropdown { + width: 340px; + height: 30px; + background: var(--color-a1); +} +.drawToolTemplaterdropdown .dropdown .dropy { + margin-bottom: 0; +} +.drawToolTemplaterdropdown .dropdown .dropy__title { + line-height: 9px; +} +.drawToolTemplaterdropdown .dropdown .dropy__title > i { + transform: translateY(30%); +} + +#drawToolFileModalTemplateContainer { + background: var(--color-a-5); + margin-bottom: 0px !important; +} +#drawToolTemplaterDesignContentWrapper { + overflow-y: auto; + max-height: calc(80vh - 255px); + min-height: 320px; +} +#drawToolTemplaterDesignHeading { + display: flex; + justify-content: space-between; + padding: 12px; + border-top: 1px solid var(--color-a1); +} +#drawToolTemplaterDesignHeadingName { + display: flex; + flex: 1; +} +#drawToolTemplaterDesignHeadingName > div { + line-height: 30px; + color: var(--color-blue); + padding-right: 8px; +} +#drawToolTemplaterDesignHeadingCancel { + margin: 0; + padding: 5px 6px; + background: var(--color-a2); +} +#drawToolTemplaterDesignHeadingCancel:hover { + background: var(--color-p4); +} + +.drawToolTemplaterIsReadOnly #drawToolTemplaterDesignHeadingName > div { + font-size: 16px; + color: var(--color-mh); +} + +#drawToolTemplaterDesignHeadingAdd { + width: 90px; + margin: 8px auto 0px auto; + border-radius: 15px; + padding: 6px 6px 4px 10px; + background: var(--color-a); + text-transform: uppercase; + font-size: 11px; + color: var(--color-a5); + transition: all 0.2s ease-in-out; + display: flex; + justify-content: center; +} +#drawToolTemplaterDesignHeadingAdd:hover { + background: var(--color-a1); + color: var(--color-a7); +} +#drawToolTemplaterDesignHeadingAdd > div:first-child { + line-height: 20px; +} +#drawToolTemplaterDesignHeadingAdd > div:last-child { + margin-left: 2px; +} +#drawToolTemplaterDesign { + padding-bottom: 8px; +} +#drawToolTemplaterDesign input[type='text'], +#drawToolTemplaterDesign input[type='number'] { + flex: 1 1; + border: none; + padding: 0px 8px; + font-size: 14px; + height: 30px; + width: 100%; + background: var(--color-a1-5); + color: var(--color-a7); +} + +.drawToolTemplaterLi { + list-style-type: none; + padding-bottom: 5px; + margin: 0px 12px 4px 12px; + background: var(--color-a); + box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.8); +} +.drawToolTemplaterLiHead { + display: flex; + justify-content: space-between; + height: 30px; + margin-bottom: 5px; +} +.drawToolTemplaterLiIdx { + height: 30px; + line-height: 30px; + min-width: 30px; + text-align: center; + font-size: 14px; + background: var(--color-a2); +} +.drawToolTemplaterLiField { + display: flex; + flex: 1; +} +.drawToolTemplaterLiField input { + width: 300px !important; +} + +.drawToolTemplaterLiType > div { + width: 150px; + height: 30px; + background: var(--color-a2); +} +.drawToolTemplaterLiType > div .dropy { + margin-bottom: 0; +} +.drawToolTemplaterLiType > div .dropy__title { + line-height: 13px; + font-size: 14px; +} +.drawToolTemplaterLiType > div .dropy__title > i { + transform: translateY(30%); +} +.drawToolTemplaterDesignHeadingRemove { + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + cursor: pointer; + background: var(--color-a1); + transition: background 0.2s ease-in-out; +} +.drawToolTemplaterDesignHeadingRemove:hover { + background: var(--color-p4); +} + +.drawToolTemplaterLiBody { + height: 30px; + padding: 0px 5px 0px 1px; +} +.drawToolTemplaterLiBody > div { + display: flex; +} +.drawToolTemplaterLiBody > div > div { + display: flex; +} +.drawToolTemplaterLiBody > div > div > div:first-child { + margin: 0px 8px 0px 8px; + line-height: 31px; + color: var(--color-mh); + font-size: 13px; +} +.drawToolTemplaterLiBody_dropdown > div { + width: 100%; +} +.drawToolTemplaterLiBody_dropdown_default { + width: auto !important; +} + +.drawToolTemplaterLiBody_textarea_default { + flex: 1; +} +.drawToolTemplaterLiBody_text_default input { + width: 130px !important; +} +.drawToolTemplaterLiBody_number_default { +} +.drawToolTemplaterLiBody_number_min { +} +.drawToolTemplaterLiBody_number_max { +} +.drawToolTemplaterLiBody_number_step { +} +.drawToolTemplaterLiBody .mmgis-checkbox { + margin: 5px 3px !important; +} +.drawToolTemplaterLiBody_text_regex input { + width: 70px !important; +} + +.drawToolTemplaterLiBody input[type='number'] { + width: 50px !important; +} +.drawToolTemplaterLiBody_number_default input[type='number'] { + width: 160px !important; +} +.drawToolTemplaterLiBody_slider_default input[type='number'] { + width: 227px !important; +} +.drawToolTemplaterLiBody_dropdown_default input[type='number'] { + width: 90px !important; +} diff --git a/src/essence/Tools/Draw/DrawTool_Templater.js b/src/essence/Tools/Draw/DrawTool_Templater.js new file mode 100644 index 00000000..af3b6bd3 --- /dev/null +++ b/src/essence/Tools/Draw/DrawTool_Templater.js @@ -0,0 +1,1012 @@ +import $ from 'jquery' + +import CursorInfo from '../../Ancillary/CursorInfo' +import Dropy from '../../../external/Dropy/dropy' + +import * as moment from 'moment' +import { TempusDominus, Namespace } from '@eonasdan/tempus-dominus' +import '@eonasdan/tempus-dominus/dist/css/tempus-dominus.css' +import tippy from 'tippy.js' + +import './DrawTool_Templater.css' + +const DrawTool_Templater = { + renderTemplate: function (containerId, templateObj, properties) { + if (templateObj == null) return null + properties = properties || {} + const template = JSON.parse(JSON.stringify(templateObj.template)) + + // prettier-ignore + const markup = [ + "
      ", + template.map((t, idx) => { + + if( properties[t.field] != null) { + t.default = properties[t.field] + } + // prettier-ignore + switch(t.type) { + case 'checkbox': + return [ + `
    • `, + `
      ${t.field}:
      `, + `
      `, + `
    • ` + ].join('\n') + case 'number': + return [ + `
    • `, + `
      ${t.field}:
      `, + ``, + `
    • ` + ].join('\n') + case 'text': + return [ + `
    • `, + `
      ${t.field}:
      `, + ``, + `
    • ` + ].join('\n') + case 'textarea': + return [ + `
    • `, + `
      ${t.field}:
      `, + ``, + `
    • ` + ].join('\n') + case 'range': + case 'slider': + return [ + `
    • `, + `
      ${t.field}:
      `, + `${t.default != null && typeof t.default === 'number' ? t.default : 'N/A'}`, + ``, + `
    • ` + ].join('\n') + case 'dropdown': + return [ + `
    • `, + `
      ${t.field}:
      `, + ``, + `
    • ` + ].join('\n') + case 'date': + return [ + `
    • `, + `
      ${t.field}:
      `, + ``, + `
    • ` + ].join('\n') + default: + return null + } + }).join('\n'), + "
    " + ].join('\n') + + $(`#${containerId}`).append(markup) + + const helperStates = {} + // Attach events + template.forEach((t, idx) => { + switch (t.type) { + case 'range': + case 'slider': + $(`#drawToolTemplater_${idx} input`).on('input', () => { + $(`#drawToolTemplater_${idx} span`).text( + `${$(`#drawToolTemplater_${idx} input`).val()}` + ) + }) + break + case 'dropdown': + helperStates[idx] = 0 + $(`#drawToolFileModalTemplateDropdown_${idx}`).html( + Dropy.construct( + t.items || [], + t.field, + helperStates[idx], + { + openUp: false, + dark: true, + } + ) + ) + Dropy.init( + $(`#drawToolFileModalTemplateDropdown_${idx}`), + function (idx2) { + helperStates[idx] = idx2 + } + ) + break + case 'date': + const startElm = document.getElementById( + `drawToolFileModalTemplateDate_${idx}` + ) + const options = { + display: { + viewMode: 'months', + components: { + decades: true, + year: true, + month: true, + date: true, + hours: true, + minutes: true, + seconds: true, + }, + buttons: { + today: true, + clear: true, + close: true, + }, + theme: 'dark', + icons: { + type: 'icons', + time: 'mdi mdi-clock-outline mdi-18px', + date: 'mdi mdi-calendar-outline mdi-18px', + up: 'mdi mdi-chevron-up mdi-18px', + down: 'mdi mdi-chevron-down mdi-18px', + previous: 'mdi mdi-chevron-left mdi-18px', + next: 'mdi mdi-chevron-right mdi-18px', + today: 'mdi mdi-calendar-today mdi-18px', + clear: 'mdi mdi-delete mdi-18px', + close: 'mdi mdi-check-bold mdi-18px', + }, + }, + useCurrent: false, + //promptTimeOnDateChange: true, + promptTimeOnDateChangeTransitionDelay: 200, + } + const dateTempus = new TempusDominus(startElm, options) + dateTempus.dates.formatInput = (date) => { + return moment + .utc(DrawTool_Templater.removeOffset(date)) + .format(t.format || 'YYYY-MM-DDTHH:mm:ss') + } + if (t.default != null && t.default != '') { + let def = t.default + let d + if (def === 'now') d = new Date().getTime() + else { + d = moment + .utc(t.default) + .format(t.format || 'YYYY-MM-DDTHH:mm:ss') + d = DrawTool_Templater.addOffset( + moment.utc(d).valueOf() + ) + } + const parsed = dateTempus.dates.parseInput(new Date(d)) + dateTempus.dates.setValue(parsed) + } + break + default: + break + } + }) + + return { + getValues: () => { + const values = {} + const invalids = {} + template.forEach((t, idx) => { + switch (t.type) { + case 'checkbox': + values[t.field] = $( + `#${containerId} #drawToolTemplater_${idx} input` + ).prop('checked') + + break + case 'number': + values[t.field] = parseFloat( + $( + `#${containerId} #drawToolTemplater_${idx} input` + ).val() + ) + if (isNaN(values[t.field])) values[t.field] = null + + if ( + t.min != null && + t.min != '' && + values[t.field] < t.min + ) + invalids[ + t.field + ] = `'${t.field}' must be >= ${t.min}` + if ( + t.max != null && + t.max != '' && + values[t.field] > t.max + ) + invalids[ + t.field + ] = `'${t.field}' must be <= ${t.max}` + if ( + t.step != null && + t.step != '' && + values[t.field] / t.step != + parseInt(values[t.field] / t.step) + ) + invalids[ + t.field + ] = `'${t.field}' must be a multiple of ${t.step}` + + break + case 'text': + values[t.field] = $( + `#${containerId} #drawToolTemplater_${idx} input` + ).val() + if ( + t.minLength != null && + t.minLength != '' && + values[t.field].length < t.minLength + ) + invalids[ + t.field + ] = `'${t.field}' must be >= ${t.minLength} characters` + + if ( + t.maxLength != null && + t.maxLength != '' && + values[t.field] != null && + values[t.field].length > t.maxLength + ) + invalids[ + t.field + ] = `'${t.field}' must be <= ${t.maxLength} characters` + + if ( + t.regex != null && + t.regex != '' && + values[t.field] != null + ) { + try { + if ( + values[t.field].match( + new RegExp(t.regex) + ) == null + ) + invalids[ + t.field + ] = `'${t.field}' does not match regex: ${t.regex}` + } catch (error) { + // regex no good + } + } + + break + case 'textarea': + values[t.field] = $( + `#${containerId} #drawToolTemplater_${idx} textarea` + ).val() + if ( + t.required === true && + (values[t.field] == null || + values[t.field] == '') + ) + invalids[ + t.field + ] = `'${t.field}' cannot be empty` + if ( + t.maxLength != null && + t.maxLength != '' && + values[t.field] != null && + values[t.field].length > t.maxLength + ) + invalids[ + t.field + ] = `'${t.field}' must be <= ${t.maxLength} characters` + break + case 'range': + case 'slider': + values[t.field] = parseFloat( + $( + `#${containerId} #drawToolTemplater_${idx} input` + ).val() + ) + if (isNaN(values[t.field])) values[t.field] = null + break + case 'dropdown': + values[t.field] = t.items[helperStates[idx]] + break + case 'date': + values[t.field] = $( + `#${containerId} #drawToolFileModalTemplateDate_${idx}` + ).val() + break + default: + break + } + + if ( + t.required === true && + (values[t.field] == null || + values[t.field] == '' || + (t.type === 'number' && isNaN(values[t.field]))) + ) { + invalids[t.field] = `'${t.field}' is a required field` + } + }) + let hadInvalid = false + let bestMessage + template.forEach((t, idx) => { + if (invalids[t.field] != null) { + hadInvalid = true + bestMessage = invalids[t.field] + switch (t.type) { + case 'textarea': + $( + `#${containerId} #drawToolTemplater_${idx} textarea` + ).css('border-bottom', '2px solid red') + break + default: + $( + `#${containerId} #drawToolTemplater_${idx} input` + ).css('border-bottom', '2px solid red') + } + } else { + switch (t.type) { + case 'textarea': + $( + `#${containerId} #drawToolTemplater_${idx} textarea` + ).css('border-bottom', 'none') + break + default: + $( + `#${containerId} #drawToolTemplater_${idx} input` + ).css('border-bottom', 'none') + } + } + }) + if (hadInvalid) { + CursorInfo.update( + `This feature has invalid form values: ${bestMessage}`, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return false + } else return values + }, + } + }, + addOffset(timestamp) { + const date = new Date(timestamp) + const addedOffset = new Date( + date.getTime() + date.getTimezoneOffset() * 60000 + ) + return addedOffset + }, + removeOffset(timestamp) { + const date = new Date(timestamp) + const removedOffset = new Date( + date.getTime() - date.getTimezoneOffset() * 60000 + ) + return removedOffset + }, + _templateInDesignIdx: 0, + _templateInDesign: {}, + _TEMPLATE_TYPES: [ + 'checkbox', + 'date', + 'dropdown', + 'number', + 'slider', + 'text', + 'textarea', + ], + renderDesignTemplate: function ( + containerId, + templateObj, + isNew, + featureToMakeTemplateFrom + ) { + $(`#${containerId} #drawToolTemplaterDesign`).remove() + + if (featureToMakeTemplateFrom && featureToMakeTemplateFrom.properties) { + isNew = true + const featureTemplate = { + name: '', + template: [], + } + Object.keys(featureToMakeTemplateFrom.properties).forEach( + (propKey) => { + const newTemplateItem = { + field: propKey, + } + const v = featureToMakeTemplateFrom.properties[propKey] + switch (typeof v) { + case 'boolean': + newTemplateItem.type = 'checkbox' + break + case 'number': + newTemplateItem.type = 'number' + newTemplateItem.min = '' + newTemplateItem.max = '' + newTemplateItem.step = '' + break + case 'string': + newTemplateItem.type = 'text' + newTemplateItem.minLength = '' + newTemplateItem.maxLength = '' + newTemplateItem.regex = '' + break + default: + break + } + if (newTemplateItem.type != null) + featureTemplate.template.push(newTemplateItem) + } + ) + templateObj = featureTemplate + } + + const isReadOnly = templateObj?.name != null && isNew !== true + + DrawTool_Templater._templateInDesignIdx = 0 + DrawTool_Templater._templateInDesign = [] + // prettier-ignore + const markup = [ + `
    `, + "
    ", + `
    `, + `
    ${isReadOnly ? `Template: ${templateObj?.name}` : 'New Template'}
    `, + ``, + "
    ", + "
    ", + "
    ", + "
    ", + "
      ", + "
      Add Field
      ", + "
      ", + "
      " + ].join('\n') + + $(`#${containerId}`).append(markup) + + tippy('#drawToolTemplaterDesignHeadingCancel', { + content: `Remove New Template`, + placement: 'right', + theme: 'red', + }) + + $(`#drawToolTemplaterDesignHeadingCancel`).on('click', (e) => { + $(`#${containerId} #drawToolTemplaterDesign`).remove() + e.stopPropagation() + }) + + // Options is the inner template configuration object for the component + const add = (options) => { + options = options || {} + const idx = DrawTool_Templater._templateInDesignIdx + DrawTool_Templater._templateInDesign[idx] = {} + // prettier-ignore + const liMarkup = [ + `
    • `, + "
      ", + "
      ", + `
      ${idx + 1}
      `, + ``, + "
      ", + "
      ", + ``, + "
      ", + `
      `, + "
      ", + `
      `, + "
      ", + "
    • " + ].join('\n') + + $(`#drawToolTemplaterDesignContent`).append(liMarkup) + + $(`#drawToolTemplaterLiFieldInput_${idx}`).focus() + + $(`#drawToolTemplaterDesignHeadingRemove_${idx}`).on( + 'click', + () => { + $(`#drawToolTemplaterLi_${idx}`).remove() + } + ) + + const setType = (idx2, opts) => { + opts = opts || {} + DrawTool_Templater._templateInDesign[idx].type = + DrawTool_Templater._TEMPLATE_TYPES[idx2] + + const type = DrawTool_Templater._templateInDesign[idx].type + let typeMarkup = [] + switch (type) { + case 'checkbox': + // prettier-ignore + typeMarkup = [ + `
      `, + "
      ", + `
      Default:
      `, + `
      `, + "
      ", + "
      " + ] + break + case 'dropdown': + // prettier-ignore + typeMarkup = [ + `
      `, + `
      `, + `
      Default:
      `, + ``, + "
      ", + `
      `, + `
      Values:
      `, + ``, + "
      ", + "
      " + ] + break + case 'number': + // prettier-ignore + typeMarkup = [ + `
      `, + `
      `, + `
      Default:
      `, + ``, + "
      ", + `
      `, + `
      Min:
      `, + ``, + "
      ", + `
      `, + `
      Max:
      `, + ``, + "
      ", + `
      `, + `
      Step:
      `, + ``, + "
      ", + `
      `, + `
      Req:
      `, + `
      `, + "
      ", + "
      " + ] + break + case 'range': + case 'slider': + // prettier-ignore + typeMarkup = [ + `
      `, + `
      `, + `
      Default:
      `, + ``, + "
      ", + `
      `, + `
      Min:
      `, + ``, + "
      ", + `
      `, + `
      Max:
      `, + ``, + "
      ", + `
      `, + `
      Step:
      `, + ``, + "
      ", + "
      " + ] + break + case 'text': + // prettier-ignore + typeMarkup = [ + `
      `, + `
      `, + `
      Default:
      `, + ``, + "
      ", + `
      `, + `
      Min:
      `, + ``, + "
      ", + `
      `, + `
      Max:
      `, + ``, + "
      ", + `
      `, + `
      Regex:
      `, + ``, + "
      ", + `
      `, + `
      Req:
      `, + `
      `, + "
      ", + "
      " + ] + break + case 'textarea': + // prettier-ignore + typeMarkup = [ + `
      `, + `
      `, + `
      Default:
      `, + ``, + "
      ", + `
      `, + `
      Max length:
      `, + ``, + "
      ", + `
      `, + `
      Req:
      `, + `
      `, + "
      ", + "
      " + ] + break + case 'date': + // prettier-ignore + typeMarkup = [ + `
      `, + `
      `, + `
      Default:
      `, + ``, + "
      ", + `
      `, + `
      Format:
      `, + ``, + "
      ", + `
      `, + `
      Req:
      `, + `
      `, + "
      ", + "
      " + ] + break + default: + break + } + + $(`#drawToolTemplaterLiBody_${idx}`).html(typeMarkup.join('\n')) + } + + let initialType = options.type || 'checkbox' + if (initialType === 'range') initialType = 'slider' + + let initialTypeIdx = + DrawTool_Templater._TEMPLATE_TYPES.indexOf(initialType) + $(`#drawToolTemplaterLiTypeDropdown_${idx}`).html( + Dropy.construct( + DrawTool_Templater._TEMPLATE_TYPES, + 'Types', + initialTypeIdx, + { + openUp: false, + dark: true, + } + ) + ) + Dropy.init($(`#drawToolTemplaterLiTypeDropdown_${idx}`), (idx2) => { + setType(idx2) + }) + setType(initialTypeIdx, options) + + DrawTool_Templater._templateInDesignIdx++ + } + + $(`#drawToolTemplaterDesignHeadingAdd`).on('click', () => { + add() + }) + + if ( + (isReadOnly || featureToMakeTemplateFrom != null) && + Array.isArray(templateObj.template) + ) { + templateObj.template.forEach((t) => { + add(t) + }) + } + }, + getDesignedTemplate: function (containerId, reservedTemplates) { + // For if no template is being designed + if (!$(`#${containerId}`).length) { + return true + } + + reservedTemplates = reservedTemplates || {} + const reservedTemplatesNames = ['NONE'].concat( + Object.keys(reservedTemplates) + ) + + const name = $( + `#${containerId} #drawToolTemplaterDesignHeadingNameInput` + ).val() + + const items = [] + $(`#${containerId} #drawToolTemplaterDesignContent > li`).each( + function () { + const item = {} + item.field = $(this) + .find('.drawToolTemplaterLiField > input') + .val() + item.type = $(this) + .find( + '.drawToolTemplaterLiType .dropy__content li > a.selected' + ) + .text() + switch (item.type) { + case 'checkbox': + item.default = $(this) + .find('.drawToolTemplaterLiBody_checkbox input') + .prop('checked') + break + case 'dropdown': + item.default = $(this) + .find( + '.drawToolTemplaterLiBody_dropdown_default input' + ) + .val() + item.items = ( + $(this) + .find( + '.drawToolTemplaterLiBody_dropdown_values input' + ) + .val() || '' + ).split(',') + break + case 'number': + item.default = $(this) + .find( + '.drawToolTemplaterLiBody_number_default input' + ) + .val() + if (item.default != '') + item.default = parseFloat(item.default) + item.min = $(this) + .find('.drawToolTemplaterLiBody_number_min input') + .val() + if (item.min != '') item.min = parseFloat(item.min) + item.max = $(this) + .find('.drawToolTemplaterLiBody_number_max input') + .val() + if (item.max != '') item.max = parseFloat(item.max) + item.step = $(this) + .find('.drawToolTemplaterLiBody_number_step input') + .val() + if (item.step != '') item.step = parseFloat(item.step) + item.required = $(this) + .find( + '.drawToolTemplaterLiBody_number_required input' + ) + .prop('checked') + break + case 'slider': + item.default = $(this) + .find( + '.drawToolTemplaterLiBody_slider_default input' + ) + .val() + if (item.default != '') + item.default = parseFloat(item.default) + item.min = $(this) + .find('.drawToolTemplaterLiBody_slider_min input') + .val() + if (item.min != '') item.min = parseFloat(item.min) + item.max = $(this) + .find('.drawToolTemplaterLiBody_slider_max input') + .val() + if (item.max != '') item.max = parseFloat(item.max) + item.step = $(this) + .find('.drawToolTemplaterLiBody_slider_step input') + .val() + if (item.step != '') item.step = parseFloat(item.step) + break + case 'text': + item.default = $(this) + .find('.drawToolTemplaterLiBody_text_default input') + .val() + item.minLength = $(this) + .find('.drawToolTemplaterLiBody_text_min input') + .val() + if (item.minLength != '') + item.minLength = parseFloat(item.minLength) + item.maxLength = $(this) + .find('.drawToolTemplaterLiBody_text_max input') + .val() + if (item.maxLength != '') + item.maxLength = parseFloat(item.maxLength) + item.regex = $(this) + .find('.drawToolTemplaterLiBody_text_regex input') + .val() + item.required = $(this) + .find( + '.drawToolTemplaterLiBody_text_required input' + ) + .prop('checked') + break + case 'textarea': + item.default = $(this) + .find( + '.drawToolTemplaterLiBody_textarea_default input' + ) + .val() + item.maxLength = $(this) + .find('.drawToolTemplaterLiBody_textarea_max input') + .val() + if (item.maxLength != '') + item.maxLength = parseFloat(item.maxLength) + item.required = $(this) + .find( + '.drawToolTemplaterLiBody_textarea_required input' + ) + .prop('checked') + break + case 'date': + item.default = $(this) + .find('.drawToolTemplaterLiBody_date_default input') + .val() + item.format = $(this) + .find('.drawToolTemplaterLiBody_date_format input') + .val() + item.required = $(this) + .find( + '.drawToolTemplaterLiBody_date_required input' + ) + .prop('checked') + break + default: + break + } + items.push(item) + } + ) + + const template = { + name: name, + template: items || [], + } + if (template.template.length === 0) { + return true + } + // Validate + if (template.name == null || template.name == '') { + CursorInfo.update( + `Please enter a template name`, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return false + } + if (reservedTemplatesNames.includes(template.name)) { + if ( + !DrawTool_Templater.areTemplatesEqual( + reservedTemplates[template.name], + template.template + ) + ) { + CursorInfo.update( + `Use a different template name. A template by the name '${template.name}' already exists.`, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return false + } + } + for (let i = 0; i < template.template.length; i++) { + const t = template.template[i] + if (t.field == null || t.field == '') { + CursorInfo.update( + `Template cannot contain empty 'Field Names'`, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return false + } + if (t.regex != null) { + try { + new RegExp(t.regex) + } catch (error) { + // no good + CursorInfo.update( + `Template cannot contain invalid reges: ${t.regex}`, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return false + } + } + } + return template + }, + areTemplatesEqual: function (t1, t2) { + if (t1.length !== t2.length) return false + + for (let i = 0; i < t1.length; i++) { + const tA = t1[i] + const tB = t2[i] + + let keys = Object.keys(tA) + + for (let k = 0; k < keys.length; k++) { + const key = keys[k] + if (tA[key] !== tB[key]) { + if (key === 'required') { + if (tA[key] === true || tB[key] === true) { + return false + } + } else { + if ( + (tA[key] == null && tB[key] == '') || + (tA[key] == '' && tB[key] == null) + ) { + // Okay if null vs '' + } else { + if ( + Array.isArray(tA[key]) && + Array.isArray(tB[key]) && + JSON.stringify(tA[key]) === + JSON.stringify(tB[key]) + ) { + } else return false + } + } + } + } + + // And do the reverse (in case tB has more keys than tA) + keys = Object.keys(tB) + + for (let k = 0; k < keys.length; k++) { + const key = keys[k] + if (tA[key] !== tB[key]) { + if (key === 'required') { + if (tA[key] === true || tB[key] === true) { + return false + } + } else { + if ( + (tA[key] == null && tB[key] == '') || + (tA[key] == '' && tB[key] == null) + ) { + // Okay if null vs '' + } else { + if ( + Array.isArray(tA[key]) && + Array.isArray(tB[key]) && + JSON.stringify(tA[key]) === + JSON.stringify(tB[key]) + ) { + } else return false + } + } + } + } + } + return true + }, +} + +export default DrawTool_Templater diff --git a/src/essence/Tools/Draw/config.json b/src/essence/Tools/Draw/config.json index 16d2c37d..1bfe5fa2 100644 --- a/src/essence/Tools/Draw/config.json +++ b/src/essence/Tools/Draw/config.json @@ -13,7 +13,61 @@ "All_Alias" ], "leadsCanEditFileInfo": false, - "hoverLengthOnLines": false + "hoverLengthOnLines": false, + "templates": { + "myTemplate": [ + { + "type": "slider", + "field": "a", + "min": 0, + "max": 100, + "step": 1, + "default": 0 + }, + { + "type": "number", + "field": "b", + "min": 0, + "max": 100, + "step": 1, + "required": true, + "default": 3 + }, + { + "type": "text", + "field": "c", + "minLength": 2, + "maxLength": 4, + "required": true, + "regex": null, + "default": null + }, + { + "type": "textarea", + "field": "d", + "maxLength": 10, + "required": true, + "default": "hi" + }, + { + "type": "checkbox", + "field": "e", + "default": true + }, + { + "type": "dropdown", + "field": "f", + "items": ["Yes", "No", "Maybe"], + "default": "No" + }, + { + "type": "date", + "field": "g", + "format": "HH:mm:ss", + "default": "now" + } + ] + } } }, "hasVars": true, From b2307a98eb71a8bda8a188279c3081e6e6f1bf40 Mon Sep 17 00:00:00 2001 From: tariqksoliman Date: Wed, 29 Mar 2023 11:47:30 -0700 Subject: [PATCH 6/6] #352 Templating touchups --- API/Backend/Draw/models/userfiles.js | 10 +- config/js/config.js | 9 ++ docs/pages/Configure/Tabs/Time/Time_Tab.md | 6 +- docs/pages/Tools/Draw/Draw.md | 4 +- src/essence/Ancillary/Coordinates.js | 18 +++- src/essence/Tools/Draw/DrawTool.css | 12 ++- src/essence/Tools/Draw/DrawTool.js | 25 +++-- src/essence/Tools/Draw/DrawTool_FileModal.js | 4 +- src/essence/Tools/Draw/DrawTool_Files.js | 17 +++- src/essence/Tools/Draw/DrawTool_Templater.css | 7 ++ src/essence/Tools/Draw/DrawTool_Templater.js | 97 ++++++++++++++++--- views/configure.pug | 3 + 12 files changed, 176 insertions(+), 36 deletions(-) diff --git a/API/Backend/Draw/models/userfiles.js b/API/Backend/Draw/models/userfiles.js index 8740224c..e6dc8c8d 100644 --- a/API/Backend/Draw/models/userfiles.js +++ b/API/Backend/Draw/models/userfiles.js @@ -141,12 +141,16 @@ const up = async () => { `ALTER TABLE user_files ADD COLUMN IF NOT EXISTS template json NULL;` ) .then(() => { - logger("info", `Added template col`, "user_files"); return null; }) .catch((err) => { - logger("info", `template. Nothing to do...`, "user_files"); - + logger( + "error", + `Failed to adding user_files.template column. DB tables may be out of sync!`, + "user_files", + null, + err + ); return null; }); }; diff --git a/config/js/config.js b/config/js/config.js index 451995f4..7ddb5e87 100644 --- a/config/js/config.js +++ b/config/js/config.js @@ -672,6 +672,10 @@ function initialize() { "checked", cData.time.visible ? true : false ); + $("#tab_time #time_initiallyOpen").prop( + "checked", + cData.time.initiallyOpen ? true : false + ); } $("#tab_time #time_format").val( cData.time ? cData.time.format : "%Y-%m-%dT%H:%M:%SZ" @@ -2178,6 +2182,11 @@ function save(returnJSON) { } else { json.time.visible = false; } + if ($("#tab_time #time_initiallyOpen").prop("checked")) { + json.time.initiallyOpen = true; + } else { + json.time.initiallyOpen = false; + } json.time.format = $("#tab_time #time_format").val(); json.time.initialstart = $("#tab_time #time_initialstart").val(); json.time.initialend = $("#tab_time #time_initialend").val(); diff --git a/docs/pages/Configure/Tabs/Time/Time_Tab.md b/docs/pages/Configure/Tabs/Time/Time_Tab.md index 86619160..3c85871b 100644 --- a/docs/pages/Configure/Tabs/Time/Time_Tab.md +++ b/docs/pages/Configure/Tabs/Time/Time_Tab.md @@ -16,7 +16,11 @@ This enables the user interface for Time. If disabled, global time will not be u ### Visible -Whether or not the Time user interface should be visible. +Whether or not the Time user interface should be visible. This allows time to be enabled while restricting users from using its UI. + +### Initially Open + +If enabled and visible, the Time UI will be initially open on the bottom of the screen. ## Time Format diff --git a/docs/pages/Tools/Draw/Draw.md b/docs/pages/Tools/Draw/Draw.md index fcc30334..a4c8e238 100644 --- a/docs/pages/Tools/Draw/Draw.md +++ b/docs/pages/Tools/Draw/Draw.md @@ -88,8 +88,8 @@ There are five files that are group editable with the correct permission. The gr { "type": "date", "field": "g", - "format": "HH:mm:ss", - "default": "now" + "format": "YYYY-MM-DDTHH:mm:ss", + "default": "2000-01-01T00:00:00" // Can be "NOW", "STARTTIME" or "ENDTIME" too for dynamic defaults } ], "example_2": [ diff --git a/src/essence/Ancillary/Coordinates.js b/src/essence/Ancillary/Coordinates.js index a1df95ef..e6b3d586 100644 --- a/src/essence/Ancillary/Coordinates.js +++ b/src/essence/Ancillary/Coordinates.js @@ -120,11 +120,16 @@ const Coordinates = { offset: [0, 20], }) - if (!(L_.configData.time && L_.configData.time.enabled === true)) { + if ( + !( + L_.configData.time && + L_.configData.time.enabled === true && + L_.configData.time.visible === true + ) + ) { $('#toggleTimeUI').css({ display: 'none' }) $('#CoordinatesDiv').css({ marginRight: '0px' }) } - if (L_.configData.coordinates) { // ll if (L_.configData.coordinates.coordll == false) @@ -266,6 +271,15 @@ const Coordinates = { $('#toggleTimeUI').on('click', toggleTimeUI) Map_.map.on('mousemove', mouseLngLatMove) Map_.map.on('click', urlClick) + + if ( + L_.configData.time && + L_.configData.time.enabled === true && + L_.configData.time.visible === true && + L_.configData.time.initiallyOpen === true + ) { + toggleTimeUI() + } }, refreshDropdown: function () { const names = [] diff --git a/src/essence/Tools/Draw/DrawTool.css b/src/essence/Tools/Draw/DrawTool.css index 213f890a..b358e4e5 100644 --- a/src/essence/Tools/Draw/DrawTool.css +++ b/src/essence/Tools/Draw/DrawTool.css @@ -2577,10 +2577,20 @@ background: var(--color-c); } +#cmExportGeoJSON, +#cmExportSourceGeoJSON { + display: block; +} +#cmExportGeoJSON > div:last-child, +#cmExportSourceGeoJSON > div:last-child { + font-size: 12px; + text-align: right; +} + /*FILE INFO TEMPLATE MODAL*/ #drawToolFileTemplateEditModal { background: var(--color-a-5); - min-width: 600px; + min-width: 624px; } #drawToolFileTemplateEditModalTitle { padding: 0px 0px 0px 10px; diff --git a/src/essence/Tools/Draw/DrawTool.js b/src/essence/Tools/Draw/DrawTool.js index def4cbda..e27f3c73 100644 --- a/src/essence/Tools/Draw/DrawTool.js +++ b/src/essence/Tools/Draw/DrawTool.js @@ -1187,16 +1187,29 @@ var DrawTool = { } } }, - enforceTemplate(geojson, templateObj) { + enforceTemplate(geojson, templateObj, force) { if (templateObj == null || templateObj.template == null) return geojson const templateEnforcedFeatures = [] geojson.features.forEach((f) => { const newF = JSON.parse(JSON.stringify(f)) - newF.properties = newF.properties || {} - templateObj.template.forEach((t) => { - if (!newF.properties.hasOwnProperty([t.field])) - newF.properties[t.field] = t.default - }) + if (force) { + newF.properties = newF.properties || {} + const forcedTemplateProperties = {} + templateObj.template.forEach((t) => { + if (!newF.properties.hasOwnProperty([t.field])) + forcedTemplateProperties[t.field] = t.default + else + forcedTemplateProperties[t.field] = + newF.properties[t.field] + }) + newF.properties = forcedTemplateProperties + } else { + newF.properties = newF.properties || {} + templateObj.template.forEach((t) => { + if (!newF.properties.hasOwnProperty([t.field])) + newF.properties[t.field] = t.default + }) + } templateEnforcedFeatures.push(newF) }) geojson.features = templateEnforcedFeatures diff --git a/src/essence/Tools/Draw/DrawTool_FileModal.js b/src/essence/Tools/Draw/DrawTool_FileModal.js index 29adae6a..8f579164 100644 --- a/src/essence/Tools/Draw/DrawTool_FileModal.js +++ b/src/essence/Tools/Draw/DrawTool_FileModal.js @@ -74,12 +74,12 @@ const DrawTool_FileModal = { } const templateItems = ['NONE'] - .concat(Object.keys(DrawTool.vars.templates)) + .concat(Object.keys(templates)) .concat(Object.keys(allTemplates).sort()) allTemplates = { ...allTemplates, - ...JSON.parse(JSON.stringify(DrawTool.vars.templates || {})), + ...JSON.parse(JSON.stringify(templates || {})), } let body = false diff --git a/src/essence/Tools/Draw/DrawTool_Files.js b/src/essence/Tools/Draw/DrawTool_Files.js index 377800e8..93cb3757 100644 --- a/src/essence/Tools/Draw/DrawTool_Files.js +++ b/src/essence/Tools/Draw/DrawTool_Files.js @@ -531,8 +531,15 @@ var Files = { hideContextMenu(true) + const fileId = elm.attr('file_id') + const file = DrawTool.getFileObjectWithId(fileId) + + const hasTemplate = file?.template?.template != null + let rect = $(this).get(0).getBoundingClientRect() + // Export GeoJSON + // Export GeoJSON [Forced Template] // prettier-ignore let markup = [ "
      ", '
        ', - (!isHead && L_.Coordinates.mainType != 'll') ? `
      • Export GeoJSON (${L_.Coordinates.getMainTypeName()})
      • ` : "", - !isHead ? `
      • Export GeoJSON ${L_.Coordinates.mainType != 'll' ? '(lonlat)' : '' }
      • ` : "", + (!isHead && L_.Coordinates.mainType != 'll') ? `
      • Export GeoJSON
        Coords: (${L_.Coordinates.getMainTypeName()})
      • ` : "", + !isHead ? `
      • Export GeoJSON
        ${L_.Coordinates.mainType != 'll' ? 'Coords: lonlat' : '' }
      • ` : "", + (!isHead && hasTemplate && L_.Coordinates.mainType != 'll') ? `
      • Export GeoJSON
        Restrict to Template
      • ` : "", + (!isHead && hasTemplate) ? `
      • Export GeoJSON
        Restrict to Template
      • ` : "", //"
      • Export as .shp
      • ", (!isHead && !isPub) ? `
      • Toggle Labels
      • ` : "", isHead ? `
      • Rename ${activeTagFolType === 'tags' ? "Tag" : "Folder"}
      • ` : "", @@ -569,6 +578,7 @@ var Files = { (function (body, isPub) { return function () { const convert = $(this).attr('convert') + const templateForced = $(this).attr('templateforced') DrawTool.getFile(body, function (d) { let geojson = d.geojson let filename = '' @@ -619,7 +629,8 @@ var Files = { ) geojson = DrawTool.enforceTemplate( geojson, - d?.file?.[0]?.template + d?.file?.[0]?.template, + templateForced ) F_.downloadObject(geojson, filename, '.geojson') }) diff --git a/src/essence/Tools/Draw/DrawTool_Templater.css b/src/essence/Tools/Draw/DrawTool_Templater.css index 8f0907d9..619573dc 100644 --- a/src/essence/Tools/Draw/DrawTool_Templater.css +++ b/src/essence/Tools/Draw/DrawTool_Templater.css @@ -185,18 +185,25 @@ width: 300px !important; } +.drawToolTemplaterLiBodyDropdown_format, .drawToolTemplaterLiType > div { width: 150px; height: 30px; background: var(--color-a2); } +.drawToolTemplaterLiBodyDropdown_format { + width: 200px; +} +.drawToolTemplaterLiBodyDropdown_format .dropy, .drawToolTemplaterLiType > div .dropy { margin-bottom: 0; } +.drawToolTemplaterLiBodyDropdown_format .dropy__title, .drawToolTemplaterLiType > div .dropy__title { line-height: 13px; font-size: 14px; } +.drawToolTemplaterLiBodyDropdown_format .dropy__title > i, .drawToolTemplaterLiType > div .dropy__title > i { transform: translateY(30%); } diff --git a/src/essence/Tools/Draw/DrawTool_Templater.js b/src/essence/Tools/Draw/DrawTool_Templater.js index af3b6bd3..a7e5e66e 100644 --- a/src/essence/Tools/Draw/DrawTool_Templater.js +++ b/src/essence/Tools/Draw/DrawTool_Templater.js @@ -2,6 +2,7 @@ import $ from 'jquery' import CursorInfo from '../../Ancillary/CursorInfo' import Dropy from '../../../external/Dropy/dropy' +import TimeControl from '../../Ancillary/TimeControl' import * as moment from 'moment' import { TempusDominus, Namespace } from '@eonasdan/tempus-dominus' @@ -37,7 +38,7 @@ const DrawTool_Templater = { return [ `
      • `, `
        ${t.field}:
        `, - ``, `
        ${t.field}:
        `, - ``, `
        ${t.field}:
        `, - ``, + ``, `
      • ` ].join('\n') default: @@ -113,7 +114,10 @@ const DrawTool_Templater = { }) break case 'dropdown': - helperStates[idx] = 0 + helperStates[idx] = Math.max( + (t.items || []).indexOf(t.default), + 0 + ) $(`#drawToolFileModalTemplateDropdown_${idx}`).html( Dropy.construct( t.items || [], @@ -180,14 +184,22 @@ const DrawTool_Templater = { if (t.default != null && t.default != '') { let def = t.default let d - if (def === 'now') d = new Date().getTime() - else { - d = moment - .utc(t.default) - .format(t.format || 'YYYY-MM-DDTHH:mm:ss') + if (def === 'NOW') d = new Date().getTime() + else if (def === 'STARTTIME') + d = DrawTool_Templater.addOffset( + new Date(TimeControl.getStartTime()).getTime() + ) + else if (def === 'ENDTIME') d = DrawTool_Templater.addOffset( - moment.utc(d).valueOf() + new Date(TimeControl.getEndTime()).getTime() ) + else { + d = new moment( + def, + t.format || 'YYYY-MM-DDTHH:mm:ss' + ) + .utc() + .valueOf() } const parsed = dateTempus.dates.parseInput(new Date(d)) dateTempus.dates.setValue(parsed) @@ -326,6 +338,8 @@ const DrawTool_Templater = { values[t.field] = $( `#${containerId} #drawToolFileModalTemplateDate_${idx}` ).val() + if (values[t.field] === 'Invalid Date') + values[t.field] = null break default: break @@ -410,6 +424,14 @@ const DrawTool_Templater = { 'text', 'textarea', ], + _DATE_FORMATS: [ + 'YYYY-MM-DDTHH:mm:ss', + 'MMMM Do YYYY', + 'YYYY-MM-DD', + 'MMMM Do YYYY, h:mm:ss a', + 'HH:mm:ss', + 'h:mm:ss a', + ], renderDesignTemplate: function ( containerId, templateObj, @@ -519,8 +541,9 @@ const DrawTool_Templater = { $(`#drawToolTemplaterDesignHeadingRemove_${idx}`).on( 'click', - () => { + (e) => { $(`#drawToolTemplaterLi_${idx}`).remove() + e.stopPropagation() } ) @@ -592,7 +615,7 @@ const DrawTool_Templater = { `
        `, `
        `, `
        Default:
        `, - ``, + ``, "
        ", `
        `, `
        Min:
        `, @@ -660,12 +683,12 @@ const DrawTool_Templater = { typeMarkup = [ `
        `, `
        `, - `
        Default:
        `, + `
        Default:
        `, ``, "
        ", `
        `, `
        Format:
        `, - ``, + ``, "
        ", `
        `, `
        Req:
        `, @@ -678,7 +701,38 @@ const DrawTool_Templater = { break } + // Add html $(`#drawToolTemplaterLiBody_${idx}`).html(typeMarkup.join('\n')) + + // Init dropdowns + const f = + opts.format != null + ? opts.format + : DrawTool_Templater._DATE_FORMATS[0] + const initialFormatIndex = Math.max( + DrawTool_Templater._DATE_FORMATS.indexOf(f), + 0 + ) + $(`#drawToolTemplaterLiBody_${idx}_format`).html( + Dropy.construct( + DrawTool_Templater._DATE_FORMATS, + 'Formats', + initialFormatIndex, + { + openUp: false, + dark: true, + } + ) + ) + Dropy.init( + $(`#drawToolTemplaterLiBody_${idx}_format`), + (idx2) => { + $(`#drawToolTemplaterLiBody_${idx}_format`).attr( + 'value', + DrawTool_Templater._DATE_FORMATS[idx2] + ) + } + ) } let initialType = options.type || 'checkbox' @@ -857,8 +911,8 @@ const DrawTool_Templater = { .find('.drawToolTemplaterLiBody_date_default input') .val() item.format = $(this) - .find('.drawToolTemplaterLiBody_date_format input') - .val() + .find('.drawToolTemplaterLiBodyDropdown_format') + .attr('value') item.required = $(this) .find( '.drawToolTemplaterLiBody_date_required input' @@ -922,6 +976,17 @@ const DrawTool_Templater = { ) return false } + if (t.field == 'uuid') { + CursorInfo.update( + `Template cannot contain the field name 'uuid'`, + 6000, + true, + { x: 305, y: 6 }, + '#e9ff26', + 'black' + ) + return false + } if (t.regex != null) { try { new RegExp(t.regex) diff --git a/views/configure.pug b/views/configure.pug index a004ea25..4b9dface 100644 --- a/views/configure.pug +++ b/views/configure.pug @@ -495,6 +495,9 @@ script(type='text/javascript' src='config/pre/RefreshAuth.js') #time_visibledEl.input-field.col.s2.push-s2 input#time_visible.filled-in.checkbox-color(type='checkbox') label(for='time_visible' style='color: black;') Visible + #time_initiallyOpendEl.input-field.col.s2.push-s2 + input#time_initiallyOpen.filled-in.checkbox-color(type='checkbox') + label(for='time_initiallyOpen' style='color: black;') Initially Open li.row.title .col.s2.push-s2 Settings li.row