From a8aa759408fcc2eb6ddb7d55d7f1ba5b724405ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C5=BEe=20Fortun?= Date: Fri, 31 Mar 2023 15:20:46 +0200 Subject: [PATCH] Add expression evaluation to page layouts --- client/web/compose/src/mixins/index.js | 2 + client/web/compose/src/mixins/uiHelpers.js | 24 +++++++ .../src/views/Public/Pages/Records/View.vue | 47 +++++++++++--- .../compose/src/views/Public/Pages/View.vue | 62 ++++++++++++++----- 4 files changed, 112 insertions(+), 23 deletions(-) create mode 100644 client/web/compose/src/mixins/uiHelpers.js diff --git a/client/web/compose/src/mixins/index.js b/client/web/compose/src/mixins/index.js index 2193f25af..0b8262821 100644 --- a/client/web/compose/src/mixins/index.js +++ b/client/web/compose/src/mixins/index.js @@ -3,7 +3,9 @@ import Vue from 'vue' import toast from './toast' import resourceTranslations from './resource-translations' import vueSelectPosition from './vue-select-position' +import uiHelpers from './uiHelpers' Vue.mixin(toast) Vue.mixin(resourceTranslations) Vue.mixin(vueSelectPosition) +Vue.mixin(uiHelpers) diff --git a/client/web/compose/src/mixins/uiHelpers.js b/client/web/compose/src/mixins/uiHelpers.js new file mode 100644 index 000000000..32d2b5777 --- /dev/null +++ b/client/web/compose/src/mixins/uiHelpers.js @@ -0,0 +1,24 @@ +export default { + methods: { + getBreakpoint () { + let breakpoint + const width = window.innerWidth + + if (width < 576) { + breakpoint = 'xs' + } else if (width >= 576 && width < 768) { + breakpoint = 'sm' + } else if (width >= 768 && width < 992) { + breakpoint = 'md' + } else if (width >= 992 && width < 1200) { + breakpoint = 'lg' + } else if (width >= 1200 && width < 1400) { + breakpoint = 'xl' + } else { + breakpoint = 'xxl' + } + + return breakpoint + }, + }, +} diff --git a/client/web/compose/src/views/Public/Pages/Records/View.vue b/client/web/compose/src/views/Public/Pages/Records/View.vue index 6ac390134..de9de4596 100644 --- a/client/web/compose/src/views/Public/Pages/Records/View.vue +++ b/client/web/compose/src/views/Public/Pages/Records/View.vue @@ -133,8 +133,7 @@ export default { layouts: [], layout: undefined, - layoutButtons: {}, - + layoutButtons: new Set(), blocks: [], } }, @@ -166,7 +165,7 @@ export default { recordToolbarLabels () { // Use an intermediate object so we can reflect all changes in one go; const aux = {} - const { config = {} } = this.layout + const { config = {} } = this.layout || {} const { buttons = {} } = config Object.entries(buttons).forEach(([key, { label = '' }]) => { @@ -176,7 +175,7 @@ export default { }, layoutActions () { - const { config = {} } = this.layout + const { config = {} } = this.layout || {} const { actions = [] } = config return actions @@ -208,6 +207,9 @@ export default { immediate: true, handler () { this.layouts = this.getPageLayouts(this.page.pageID) + this.layout = undefined + this.blocks = [] + this.determineLayout() }, }, @@ -317,12 +319,43 @@ export default { } }, - determineLayout (pageLayoutID) { - this.layout = this.layouts.find(l => { - const { roles = [] } = l.config.visibility + evaluateLayoutExpressions () { + const expressions = {} + const variables = { + user: this.$auth.user, + record: this.record || {}, + screen: { + width: window.innerWidth, + height: window.innerHeight, + userAgent: navigator.userAgent, + breakpoint: this.getBreakpoint(), // This is from a global mixin uiHelpers + }, + layout: this.layout || {}, + } + this.layouts.forEach(({ pageLayoutID, config }) => { + if (!config.visibility.expression) return + + expressions[pageLayoutID] = config.visibility.expression + }) + + return this.$SystemAPI.expressionEvaluate({ variables, expressions }).catch(() => { + Object.keys(expressions).forEach(key => (expressions[key] = false)) + return expressions + }) + }, + + async determineLayout (pageLayoutID) { + const expressions = await this.evaluateLayoutExpressions() + + // Check layouts for expressions/roles and find the first one that fits + this.layout = this.layouts.find(l => { if (pageLayoutID && l.pageLayoutID !== pageLayoutID) return + const { expression, roles = [] } = l.config.visibility + + if (expression && !expressions[l.pageLayoutID]) return false + if (!roles.length) return true return this.$auth.user.roles.some(roleID => roles.includes(roleID)) diff --git a/client/web/compose/src/views/Public/Pages/View.vue b/client/web/compose/src/views/Public/Pages/View.vue index 42730d58a..578c1ac84 100644 --- a/client/web/compose/src/views/Public/Pages/View.vue +++ b/client/web/compose/src/views/Public/Pages/View.vue @@ -50,7 +50,7 @@ class="flex-grow-1 overflow-auto d-flex px-2 w-100" > { + if (!config.visibility.expression) return + + expressions[pageLayoutID] = config.visibility.expression + }) + + return this.$SystemAPI.expressionEvaluate({ variables, expressions }).catch(() => { + Object.keys(expressions).forEach(key => (expressions[key] = false)) + return expressions + }) + }, + + async determineLayout () { this.layouts = this.getPageLayouts(this.page.pageID) - this.layout = this.layouts.find(l => { - const { roles = [] } = l.config.visibility + + const expressions = await this.evaluateLayoutExpressions() + + // Check layouts for expressions/roles and find the first one that fits + this.layout = this.layouts.find(({ pageLayoutID, config = {} }) => { + const { expression, roles = [] } = config.visibility + + if (expression && !expressions[pageLayoutID]) return false if (!roles.length) return true