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..de0ac51f 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,62 @@ 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) {
+ const model = o.model;
+ if (!model) continue;
+ if (`${model.get(query)}`.search(val) > -1) {
+ const l = model.attributes.layer;
+ if (state) {
+ if (l.search(prefix) > -1) {
+ model.attributes.layer = l.replace(`${prefix}_`, "");
+ d20plus.engine.objectsStashProps(model, true);
+ o.saveState();
+ model.save();
+ some = true;
+ }
+ } else {
+ if (l.search(prefix) === -1) {
+ model.attributes.layer = `${prefix}_${l}`;
+ d20plus.engine.objectsStashProps(model, false);
+ 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..a3de579d
--- /dev/null
+++ b/js/base-views.js
@@ -0,0 +1,268 @@
+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 = $(`
+
+ `).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();
+ $(`#editinglayer .choose${window.currentEditingLayer}`).click();
+ }
+
+ d20plus.views.checkPageSettings = () => {
+ if (!d20.Campaign.activePage() || !d20.Campaign.activePage().get) {
+ setTimeout(d20plus.views.checkPageSettings, 50);
+ } else {
+ d20plus.views.populateMenu();
+ }
+ }
+
+ d20plus.views.addViews = () => {
+ 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();
+ }
+ }
+}
+
+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
- Configure
+ Configure Weather
+
+
+ Configure Views
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",