From 531253ca129a074850f77d43a9cc87693e4b5b97 Mon Sep 17 00:00:00 2001 From: Notanyone Date: Fri, 9 Sep 2022 21:37:22 +0300 Subject: [PATCH 1/5] Feature: views 1. Enable and rename views for your map in the Views dialog (next to Weather settings button) 2. Assign images or paths to your views via the context menu 3. Group of switches in the bottom of layer selection menu on the floating toolbar will allow you to quickly hide or show the items you assigned --- js/5etools-bootstrap.js | 1 + js/base-engine.js | 41 ++++ js/base-template.js | 22 ++ js/base-util.js | 8 + js/base-views.js | 265 +++++++++++++++++++++++++ js/core-bootstrap.js | 1 + js/templates/template-page-settings.js | 7 +- node/build-scripts.js | 2 + 8 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 js/base-views.js diff --git a/js/5etools-bootstrap.js b/js/5etools-bootstrap.js index e3c885f5..84a8ec6c 100644 --- a/js/5etools-bootstrap.js +++ b/js/5etools-bootstrap.js @@ -69,6 +69,7 @@ const betteR205etools = function () { d20plus.ut.fix3dDice(); d20plus.engine.addLayers(); d20plus.weather.addWeather(); + d20plus.views.addViews(); d20plus.engine.repairPrototypeMethods(); d20plus.engine.disableFrameRecorder(); // d20plus.ut.fixSidebarLayout(); diff --git a/js/base-engine.js b/js/base-engine.js index c50be5e2..c9286975 100644 --- a/js/base-engine.js +++ b/js/base-engine.js @@ -932,6 +932,20 @@ function d20plusEngine () { d20plus.anim.animatorTool.doStartScene(sceneUid); }); i(); + } else if (["assignview0", "assignview1", "assignview2", "assignview3"].includes(e)) { + const viewId = e.at(-1); + d20.engine.selected().forEach(it => { + if (it.model) { + if (it.model.get(`bR20_view${viewId}`)) { + it.model.set(`bR20_view${viewId}`, false); + } else { + it.model.set(`bR20_view${viewId}`, true); + } + it.saveState(); + it.model.save(); + } + }); + i(); } // END MOD return !1 @@ -1254,6 +1268,33 @@ function d20plusEngine () { }) }; + d20plus.engine.objectsHideUnhide = (q, val, prefix, state) => { + let some = false; + for (const o of d20.engine.canvas._objects) { + const model = o.model; + if (!model) continue; + if (`${model.get(q)}`.search(val) > -1) { + const l = model.attributes.layer; + if (state) { + if (l.search(prefix) > -1) { + model.attributes.layer = l.replace(`${prefix}_`, ""); + o.saveState(); + model.save(); + some = true; + } + } else { + if (l.search(prefix) === -1) { + model.attributes.layer = `${prefix}_${l}`; + o.saveState(); + model.save(); + some = true; + } + } + } + } + return some; + }; + d20plus.engine.addLayers = () => { d20plus.ut.log("Adding layers"); diff --git a/js/base-template.js b/js/base-template.js index c6f369af..edfe0f85 100644 --- a/js/base-template.js +++ b/js/base-template.js @@ -211,6 +211,28 @@ const baseTemplate = function () { <$ } $> + + <$ if(this.view && this.get && d20.Campaign.activePage().get && d20.Campaign.activePage().get('bR20cfg_viewsEnable')) { $> +
  • + Assign view » + +
  • + <$ } $> + + <$ if(this.view && this.get && this.get("sides") !== "" && this.get("cardid") === "") { $>
  • Multi-Sided » diff --git a/js/base-util.js b/js/base-util.js index 9840db39..acd41390 100644 --- a/js/base-util.js +++ b/js/base-util.js @@ -539,6 +539,14 @@ function baseUtil () { } }; + d20plus.ut.dynamicStyles = (slug) => { + if (!d20plus.css.dynamic) d20plus.css.dynamic = {}; + if (!d20plus.css.dynamic[slug]) { + d20plus.css.dynamic[slug] = $("").appendTo("body"); + } + return d20plus.css.dynamic[slug]; + } + /** * Assumes any other lists have been searched using the same term */ diff --git a/js/base-views.js b/js/base-views.js new file mode 100644 index 00000000..314d7891 --- /dev/null +++ b/js/base-views.js @@ -0,0 +1,265 @@ +function baseViews () { + d20plus.views = {}; + + d20plus.views._lastSettingsPageId = null; + + d20plus.views._initSettingsButton = () => { + $(`body`).on("click", ".Ve-btn-views", function () { + // close the parent page settings + hide the page overlay + const $this = $(this); + $this.closest(`[role="dialog"]`).find(`.ui-dialog-buttonpane button:contains("Save")`).click(); + const $barPage = $(`#page-toolbar`); + if (!$barPage.hasClass("closed")) { + $barPage.find(`.handle`).click() + } + + function doShowDialog (page) { + const mutExclusiveHelp = "Check this, if enabling this or PREVIOUS view should disable another one of them"; + const $dialog = $(` +
    + + +
    +

    Default view

    +
    + +
    +

    View 1

    +
    +
    + + +
    + +
    +

    View 2

    +
    +
    + + +
    + +
    +

    View 3

    +
    +
    + + +
    + +
    + `).appendTo($("body")); + + const handleProp = (propName) => $dialog.find(`[name="${propName}"]`).each((i, e) => { + const $e = $(e); + if ($e.is(":checkbox")) { + $e.prop("checked", !!page.get(`bR20cfg_${propName}`)); + } else { + $e.val(page.get(`bR20cfg_${propName}`)); + } + }); + const props = [ + "viewsEnable", + "views0Name", + "views1Enable", + "views1Exclusive", + "views1Name", + "views2Enable", + "views2Exclusive", + "views2Name", + "views3Enable", + "views3Exclusive", + "views3Name", + ]; + props.forEach(handleProp); + + function doSaveValues () { + props.forEach(propName => { + page.set(`bR20cfg_${propName}`, (() => { + const $e = $dialog.find(`[name="${propName}"]`); + if ($e.is(":checkbox")) { + return !!$e.prop("checked"); + } else { + return $e.val(); + } + })()) + }); + page.save(); + } + + $dialog.dialog({ + width: 500, + dialogClass: "no-close", + buttons: [ + { + text: "OK", + click: function () { + $(this).dialog("close"); + $dialog.remove(); + doSaveValues(); + }, + }, + { + text: "Apply", + click: function () { + doSaveValues(); + }, + }, + { + text: "Cancel", + click: function () { + $(this).dialog("close"); + $dialog.remove(); + }, + }, + ], + }); + } + + if (d20plus.views._lastSettingsPageId) { + const page = d20.Campaign.pages.get(d20plus.views._lastSettingsPageId); + if (page) { + doShowDialog(page); + } else d20plus.ut.error(`No page found with ID "${d20plus.views._lastSettingsPageId}"`); + } else d20plus.ut.error(`No page settings button was clicked?!`); + }).on("mousedown", ".chooseablepage .js__settings-page", function () { + const $this = $(this); + d20plus.views._lastSettingsPageId = $this.closest(`[data-pageid]`).data("pageid"); + }); + }; + + d20plus.views._initMenuActions = () => { + $(`body`).on("click", ".chooseViews > li", function () { + const page = d20.Campaign.activePage(); + const items = $(".chooseViews > li") + const id = items.index(this); + const startgroupindex = (() => { for (let i = id; i >= 0; i--) { if (!page.get(`bR20cfg_views${i}Exclusive`)) return i; } })(); + const endgroupindex = (() => { for (let i = id + 1; i <= 5; i++) { if (!page.get(`bR20cfg_views${i}Exclusive`)) return i - 1; } })(); + if (page.get(`bR20cfg_views${id}Off`)) { + d20plus.views.changeViewState(id, true); + for (let i = startgroupindex; i <= endgroupindex; i++) { + if (i !== id) d20plus.views.changeViewState(i, false); + } + } else { + d20plus.views.changeViewState(id, false); + } + }); + } + + d20plus.views._initViewsCss = () => { + d20plus.ut.dynamicStyles("viewsSelect").html(` + .ui-dialog label.half {display: inline-block; margin-bottom: 6px;} + .ui-dialog label.half span {margin-right: 20px;} + #floatingtoolbar ul.chooseViews li {border-width: 1px;border-style: solid; border-color: var(--dark-surface1);} + #floatingtoolbar ul.chooseViews:empty {display:none;} + #floatingtoolbar ul.chooseViews li {height: 19px; border-radius: 12px;} + #floatingtoolbar ul.chooseViews li.fst {border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; border-bottom-width: 0px;} + #floatingtoolbar ul.chooseViews li.lst {border-top-left-radius: 0px; border-top-right-radius: 0px; border-top-width: 0px;} + #floatingtoolbar ul.chooseViews li.mst {border-radius: 0px; border-top: 0px; border-bottom: 0px;} + #floatingtoolbar ul.chooseViews .pictos {padding: 0 3px 0 3px;} + #floatingtoolbar ul.chooseViews .view_toggle {padding: 4px 8px 3px 4px; margin-right: 8px; border-right: 1px solid; border-color: inherit;} + #floatingtoolbar ul.chooseViews li.off .view_toggle .pictos {color: #fff0;} + `); + } + + d20plus.views._initLayerMenu = () => { + d20plus.views.layerMenu = $(``).appendTo($("#editinglayer .submenu")); + } + + d20plus.views.populateMenu = () => { + const page = d20.Campaign.activePage(); + if (!page) return; + let menuhtml = ""; + if (page.get("bR20cfg_viewsEnable")) { + for (let id = 0; id <= 4; id++) { + if (!id || page.get(`bR20cfg_views${id}Enable`)) { + const viewname = page.get(`bR20cfg_views${id}Name`) || (id ? `View ${id}` : `Default view`); + const viewicon = page.get(`bR20cfg_views${id}Icon`) || "P"; + const viewexcl = page.get(`bR20cfg_views${id}Exclusive`) ? (page.get(`bR20cfg_views${id + 1}Exclusive`) ? "mst" : "lst") : page.get(`bR20cfg_views${id + 1}Exclusive`) ? "fst" : ""; + const viewactive = page.get(`bR20cfg_views${id}Off`) ? "off" : ""; + menuhtml += `
  • + E + ${viewicon} + ${viewname} +
  • `; + } + } + } + d20plus.views.layerMenu.html(menuhtml); + } + + d20plus.views.changeViewState = (id, state) => { + const page = d20.Campaign.activePage(); + const menuItem = $(".chooseViews > li").get(id); + if (state) { + $(menuItem).removeClass("off"); + page.set(`bR20cfg_views${id}Off`, false); + d20plus.engine.objectsHideUnhide(`bR20_view${id}`, true, `off${id}`, true); + } else { + $(menuItem).addClass("off"); + page.set(`bR20cfg_views${id}Off`, true); + d20plus.engine.objectsHideUnhide(`bR20_view${id}`, true, `off${id}`, false); + } + page.save(); + } + + d20plus.views.checkPageSettings = () => { + if (!d20.Campaign.activePage() || !d20.Campaign.activePage().get) { + setTimeout(d20plus.views.checkPageSettings, 50); + } else { + d20plus.views.populateMenu(); + } + } + + d20plus.views.addViews = () => { + d20plus.views._initSettingsButton(); + d20plus.views._initViewsCss(); + d20plus.views._initLayerMenu(); + d20plus.views._initMenuActions(); + document.addEventListener("VePageChange", d20plus.views.checkPageSettings); + d20plus.views.checkPageSettings(); + } +} + +SCRIPT_EXTENSIONS.push(baseViews); \ No newline at end of file diff --git a/js/core-bootstrap.js b/js/core-bootstrap.js index 72723ad6..072462f4 100644 --- a/js/core-bootstrap.js +++ b/js/core-bootstrap.js @@ -49,6 +49,7 @@ const betteR20Core = function () { d20plus.ut.fix3dDice(); d20plus.engine.addLayers(); d20plus.weather.addWeather(); + d20plus.views.addViews(); d20plus.engine.repairPrototypeMethods(); d20plus.engine.disableFrameRecorder(); // d20plus.ut.fixSidebarLayout(); diff --git a/js/templates/template-page-settings.js b/js/templates/template-page-settings.js index 9f6b8c1a..dc0670f8 100644 --- a/js/templates/template-page-settings.js +++ b/js/templates/template-page-settings.js @@ -379,10 +379,13 @@ Updated
    -

    Weather

    +

    Extensions by betteR20

    +
    diff --git a/node/build-scripts.js b/node/build-scripts.js index 7ad19525..83d10691 100644 --- a/node/build-scripts.js +++ b/node/build-scripts.js @@ -157,6 +157,7 @@ const SCRIPTS = { "overwrites/canvas-handler", "base-engine", "base-weather", + "base-views", "base-journal", "base-css", "base-ui", @@ -194,6 +195,7 @@ const SCRIPTS = { "overwrites/canvas-handler", "base-engine", "base-weather", + "base-views", "base-journal", "base-css", "base-ui", From 7ec33e5df9c8c51bfcef0fc0c9f0c45e154f1194 Mon Sep 17 00:00:00 2001 From: Notanyone Date: Sun, 11 Sep 2022 15:03:09 +0300 Subject: [PATCH 2/5] Replace single-letter var --- js/base-engine.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/base-engine.js b/js/base-engine.js index c9286975..cdfbbf1c 100644 --- a/js/base-engine.js +++ b/js/base-engine.js @@ -1268,12 +1268,12 @@ function d20plusEngine () { }) }; - d20plus.engine.objectsHideUnhide = (q, val, prefix, state) => { + d20plus.engine.objectsHideUnhide = (query, val, prefix, state) => { let some = false; for (const o of d20.engine.canvas._objects) { const model = o.model; if (!model) continue; - if (`${model.get(q)}`.search(val) > -1) { + if (`${model.get(query)}`.search(val) > -1) { const l = model.attributes.layer; if (state) { if (l.search(prefix) > -1) { From 5a983e7b0f957fe1a4eeaa3f6529173421502fca Mon Sep 17 00:00:00 2001 From: Notanyone Date: Sun, 11 Sep 2022 15:04:40 +0300 Subject: [PATCH 3/5] Fix: process Views for GMs only --- js/base-views.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/js/base-views.js b/js/base-views.js index 314d7891..37747232 100644 --- a/js/base-views.js +++ b/js/base-views.js @@ -253,12 +253,14 @@ function baseViews () { } d20plus.views.addViews = () => { - d20plus.views._initSettingsButton(); - d20plus.views._initViewsCss(); - d20plus.views._initLayerMenu(); - d20plus.views._initMenuActions(); - document.addEventListener("VePageChange", d20plus.views.checkPageSettings); - d20plus.views.checkPageSettings(); + if (window.is_gm) { + d20plus.views._initSettingsButton(); + d20plus.views._initViewsCss(); + d20plus.views._initLayerMenu(); + d20plus.views._initMenuActions(); + document.addEventListener("VePageChange", d20plus.views.checkPageSettings); + d20plus.views.checkPageSettings(); + } } } From cd46aa454294489f36fa29e8eaf7c8e772264317 Mon Sep 17 00:00:00 2001 From: Notanyone Date: Sun, 11 Sep 2022 15:07:37 +0300 Subject: [PATCH 4/5] Reset current layer after switching To avoid weird selection behavior --- js/base-views.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/base-views.js b/js/base-views.js index 37747232..a3de579d 100644 --- a/js/base-views.js +++ b/js/base-views.js @@ -242,6 +242,7 @@ function baseViews () { d20plus.engine.objectsHideUnhide(`bR20_view${id}`, true, `off${id}`, false); } page.save(); + $(`#editinglayer .choose${window.currentEditingLayer}`).click(); } d20plus.views.checkPageSettings = () => { From 54dbcd9f44c5d3727b0f16fe702dbddafe1546bd Mon Sep 17 00:00:00 2001 From: Notanyone Date: Sun, 11 Sep 2022 15:12:10 +0300 Subject: [PATCH 5/5] Check properties on switching views To completely hide items, including light sources & nameplates --- js/base-engine.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/js/base-engine.js b/js/base-engine.js index cdfbbf1c..de0ac51f 100644 --- a/js/base-engine.js +++ b/js/base-engine.js @@ -1268,6 +1268,33 @@ function d20plusEngine () { }) }; + d20plus.engine.objectsStashProps = (obj, state) => { + const props = [ + "emits_bright_light", + "emits_low_light", + "has_directional_bright_light", + "has_directional_dim_light", + "showplayers_bar1", + "showplayers_bar2", + "showplayers_bar3", + "showname", + ]; + props.each((prop) => { + if (!state) { + if (obj.attributes[prop]) { + obj.attributes[`bR20_${prop}`] = true; + obj.attributes[prop] = false; + } + } else { + if (obj.attributes[`bR20_${prop}`]) { + obj.attributes[prop] = true; + obj.attributes[`bR20_${prop}`] = false; + delete obj.attributes[`bR20_${prop}`]; + } + } + }); + } + d20plus.engine.objectsHideUnhide = (query, val, prefix, state) => { let some = false; for (const o of d20.engine.canvas._objects) { @@ -1278,6 +1305,7 @@ function d20plusEngine () { if (state) { if (l.search(prefix) > -1) { model.attributes.layer = l.replace(`${prefix}_`, ""); + d20plus.engine.objectsStashProps(model, true); o.saveState(); model.save(); some = true; @@ -1285,6 +1313,7 @@ function d20plusEngine () { } else { if (l.search(prefix) === -1) { model.attributes.layer = `${prefix}_${l}`; + d20plus.engine.objectsStashProps(model, false); o.saveState(); model.save(); some = true;