Add visibility condition to page blocks
This commit is contained in:
committed by
Jože Fortun
parent
8ed2248f0c
commit
0c91950f15
@@ -264,7 +264,7 @@ export default {
|
||||
|
||||
updateSize () {
|
||||
this.$nextTick(() => {
|
||||
this.api().updateSize()
|
||||
this.api() && this.api().updateSize()
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -228,6 +228,114 @@
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
v-if="block.options.magnifyOption !== undefined"
|
||||
cols="12"
|
||||
lg="6"
|
||||
:offset-lg="block.options.showRefresh !== undefined ? 6 : 0"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('general.magnifyLabel')"
|
||||
label-class="text-primary"
|
||||
>
|
||||
<b-form-select
|
||||
v-model="block.options.magnifyOption"
|
||||
:options="magnifyOptions"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="12"
|
||||
>
|
||||
<hr>
|
||||
|
||||
<h5 class="mb-3">
|
||||
{{ $t('general.visibility.label') }}
|
||||
</h5>
|
||||
|
||||
<b-form-group
|
||||
label-class="d-flex align-items-center text-primary mb-0"
|
||||
>
|
||||
<template #label>
|
||||
{{ $t('general.visibility.condition.label') }}
|
||||
<c-hint
|
||||
:tooltip="$t('general.visibility.tooltip.performance.condition')"
|
||||
icon-class="text-warning"
|
||||
/>
|
||||
</template>
|
||||
<b-input-group>
|
||||
<b-input-group-prepend>
|
||||
<b-button variant="extra-light">
|
||||
ƒ
|
||||
</b-button>
|
||||
</b-input-group-prepend>
|
||||
<b-form-input
|
||||
v-model="block.meta.visibility.expression"
|
||||
:placeholder="$t('general.visibility.condition.placeholder')"
|
||||
/>
|
||||
<b-input-group-append>
|
||||
<b-button
|
||||
variant="outline-secondary"
|
||||
:href="visibilityDocumentationURL"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
target="_blank"
|
||||
>
|
||||
?
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
|
||||
<i18next
|
||||
v-if="isRecordPage"
|
||||
path="general.visibility.condition.description.record-page"
|
||||
tag="small"
|
||||
class="text-muted"
|
||||
>
|
||||
<code>record.values.fieldName</code>
|
||||
<code>user.(userID/email...)</code>
|
||||
<code>screen.(width/height)</code>
|
||||
<code>isView/isCreate/isEdit</code>
|
||||
<code>user.userID == record.values.createdBy</code>
|
||||
<code>screen.width < 1024</code>
|
||||
</i18next>
|
||||
|
||||
<i18next
|
||||
v-else
|
||||
path="general.visibility.condition.description.non-record-page"
|
||||
tag="small"
|
||||
class="text-muted"
|
||||
>
|
||||
<code>user.(userID/email...)</code>
|
||||
<code>screen.(width/height)</code>
|
||||
<code>user.email == "test@mail.com"</code>
|
||||
<code>screen.width < 1024</code>
|
||||
</i18next>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="12"
|
||||
class="pt-2"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('general.visibility.roles.label')"
|
||||
label-class="text-primary"
|
||||
>
|
||||
<c-input-select
|
||||
v-model="currentRoles"
|
||||
:options="roles.options"
|
||||
:loading="roles.processing"
|
||||
:placeholder="$t('general.visibility.roles.placeholder')"
|
||||
:get-option-label="role => role.name"
|
||||
:reduce="role => role.roleID"
|
||||
:selectable="role => !currentRoles.includes(role.roleID)"
|
||||
multiple
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-tab>
|
||||
|
||||
@@ -277,6 +385,16 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
roles: {
|
||||
processing: false,
|
||||
options: [],
|
||||
},
|
||||
abortableRequests: [],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
textVariants () {
|
||||
return [
|
||||
@@ -318,6 +436,39 @@ export default {
|
||||
activeTab () {
|
||||
return this.isNew ? 0 : 1
|
||||
},
|
||||
|
||||
isRecordPage () {
|
||||
return this.page && this.page.moduleID !== NoID
|
||||
},
|
||||
|
||||
visibilityDocumentationURL () {
|
||||
// eslint-disable-next-line no-undef
|
||||
const [year, month] = VERSION.split('.')
|
||||
return `https://docs.cortezaproject.org/corteza-docs/${year}.${month}/integrator-guide/compose-configuration/page-layouts.html#visibility-condition`
|
||||
},
|
||||
|
||||
currentRoles: {
|
||||
get () {
|
||||
if (!this.block.meta.visibility.roles) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.block.meta.visibility.roles
|
||||
},
|
||||
|
||||
set (roles) {
|
||||
this.$set(this.block.meta.visibility, 'roles', roles)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
created () {
|
||||
this.fetchRoles()
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
this.abortRequests()
|
||||
this.setDefaultValues()
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -325,6 +476,36 @@ export default {
|
||||
// If value is less than 5 but greater than 0 make it 5. Otherwise value stays the same.
|
||||
this.block.options.refreshRate = e.target.value < 5 && e.target.value > 0 ? 5 : e.target.value
|
||||
},
|
||||
|
||||
fetchRoles () {
|
||||
this.roles.processing = true
|
||||
|
||||
const { response, cancel } = this.$SystemAPI
|
||||
.roleListCancellable({})
|
||||
|
||||
this.abortableRequests.push(cancel)
|
||||
|
||||
response()
|
||||
.then(({ set: roles = [] }) => {
|
||||
this.roles.options = roles.filter(({ meta }) => !(meta.context && meta.context.resourceTypes))
|
||||
}).finally(() => {
|
||||
this.roles.processing = false
|
||||
})
|
||||
},
|
||||
|
||||
abortRequests () {
|
||||
this.abortableRequests.forEach((cancel) => {
|
||||
cancel()
|
||||
})
|
||||
},
|
||||
|
||||
setDefaultValues () {
|
||||
this.roles = {
|
||||
processing: false,
|
||||
options: [],
|
||||
}
|
||||
this.abortableRequests = []
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
200
client/web/compose/src/mixins/page.js
Normal file
200
client/web/compose/src/mixins/page.js
Normal file
@@ -0,0 +1,200 @@
|
||||
import { compose } from '@cortezaproject/corteza-js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
namespace: {
|
||||
// via router-view
|
||||
type: compose.Namespace,
|
||||
required: true,
|
||||
},
|
||||
page: {
|
||||
// via route-view
|
||||
type: compose.Page,
|
||||
required: true,
|
||||
},
|
||||
// We're using recordID to check if we need to display router-view or grid component
|
||||
recordID: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
layouts: [],
|
||||
layout: undefined,
|
||||
blocks: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getPageLayouts: 'pageLayout/getByPageID',
|
||||
}),
|
||||
|
||||
isRecordPage () {
|
||||
return this.recordID || this.$route.name === 'page.record.create'
|
||||
},
|
||||
|
||||
expressionVariables () {
|
||||
return {
|
||||
user: this.$auth.user,
|
||||
record: this.record ? this.record.serialize() : {},
|
||||
screen: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
userAgent: navigator.userAgent,
|
||||
breakpoint: this.getBreakpoint(), // This is from a global mixin uiHelpers
|
||||
},
|
||||
oldLayout: this.layout,
|
||||
layout: undefined,
|
||||
...(this.isRecordPage && {
|
||||
isView: !this.edit && !this.isNew,
|
||||
isCreate: this.isNew,
|
||||
isEdit: this.edit && !this.isNew,
|
||||
}),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.createEvents()
|
||||
},
|
||||
|
||||
methods: {
|
||||
evaluateLayoutExpressions (variables = {}) {
|
||||
const expressions = {}
|
||||
variables = {
|
||||
...this.expressionVariables,
|
||||
...variables,
|
||||
}
|
||||
|
||||
this.layouts.forEach(layout => {
|
||||
const { config = {} } = layout
|
||||
if (!config.visibility.expression) return
|
||||
|
||||
variables.layout = layout
|
||||
|
||||
expressions[layout.pageLayoutID] = config.visibility.expression
|
||||
})
|
||||
|
||||
return this.$SystemAPI.expressionEvaluate({ variables, expressions }).catch(e => {
|
||||
this.toastErrorHandler(this.$t('notification:evaluate.failed'))(e)
|
||||
Object.keys(expressions).forEach(key => (expressions[key] = false))
|
||||
|
||||
return expressions
|
||||
})
|
||||
},
|
||||
|
||||
async determineLayout (pageLayoutID, variables = {}, redirectOnFail = true) {
|
||||
// Clear stored records so they can be refetched with latest values
|
||||
this.clearRecordSet()
|
||||
|
||||
if (this.isRecordPage) {
|
||||
this.resetErrors()
|
||||
}
|
||||
|
||||
let expressions = {}
|
||||
|
||||
// Only evaluate if one of the layouts has an expressions variable
|
||||
if (this.layouts.some(({ config = {} }) => config.visibility.expression)) {
|
||||
expressions = await this.evaluateLayoutExpressions(variables)
|
||||
}
|
||||
|
||||
// Check layouts for expressions/roles and find the first one that fits
|
||||
const matchedLayout = 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))
|
||||
})
|
||||
|
||||
this.processing = false
|
||||
|
||||
if (!matchedLayout) {
|
||||
this.toastWarning(this.$t('notification:page.page-layout.notFound.view'))
|
||||
|
||||
if (redirectOnFail) {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
|
||||
return this.$router.go(-1)
|
||||
}
|
||||
|
||||
if (this.isRecordPage) {
|
||||
this.inEditing = this.edit
|
||||
}
|
||||
|
||||
if (this.layout && matchedLayout.pageLayoutID === this.layout.pageLayoutID) {
|
||||
return
|
||||
}
|
||||
|
||||
this.layout = matchedLayout
|
||||
|
||||
if (this.isRecordPage) {
|
||||
this.handleRecordButtons()
|
||||
} else {
|
||||
const { handle, meta = {} } = this.layout || {}
|
||||
const title = meta.title || this.page.title
|
||||
this.pageTitle = title || handle || this.$t('navigation:noPageTitle')
|
||||
document.title = [title, this.namespace.name, this.$t('general:label.app-name.public')].filter(v => v).join(' | ')
|
||||
}
|
||||
|
||||
await this.updateBlocks(variables)
|
||||
},
|
||||
|
||||
async updateBlocks (variables = {}) {
|
||||
const tempBlocks = []
|
||||
const { blocks = [] } = this.layout || {}
|
||||
|
||||
let blocksExpressions = {}
|
||||
|
||||
if (blocks.some(({ meta = {} }) => (meta.visibility || {}).expression)) {
|
||||
blocksExpressions = await this.evaluateBlocksExpressions(variables)
|
||||
}
|
||||
|
||||
blocks.forEach(({ blockID, xywh, meta }) => {
|
||||
const block = this.page.blocks.find(b => b.blockID === blockID)
|
||||
const { roles = [], expression = '' } = meta.visibility || {}
|
||||
|
||||
if (block && (!expression || blocksExpressions[blockID])) {
|
||||
block.xywh = xywh
|
||||
|
||||
if (!roles.length || this.$auth.user.roles.some(roleID => roles.includes(roleID))) {
|
||||
tempBlocks.push(block)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.blocks = tempBlocks
|
||||
},
|
||||
|
||||
evaluateBlocksExpressions (variables = {}) {
|
||||
const expressions = {}
|
||||
variables = {
|
||||
...this.expressionVariables,
|
||||
...variables,
|
||||
}
|
||||
|
||||
this.layout.blocks.forEach(block => {
|
||||
const { visibility } = block.meta
|
||||
if (!(visibility || {}).expression) return
|
||||
|
||||
expressions[block.blockID] = visibility.expression
|
||||
})
|
||||
|
||||
return this.$SystemAPI.expressionEvaluate({ variables, expressions }).catch(e => {
|
||||
this.toastErrorHandler(this.$t('notification:evaluate.failed'))(e)
|
||||
Object.keys(expressions).forEach(key => (expressions[key] = false))
|
||||
|
||||
return expressions
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -184,6 +184,7 @@
|
||||
</b-modal>
|
||||
|
||||
<b-modal
|
||||
scrollable
|
||||
:ok-title="$t('build.addBlock')"
|
||||
ok-variant="primary"
|
||||
:ok-disabled="blockEditorOkDisabled"
|
||||
@@ -222,6 +223,7 @@
|
||||
</b-modal>
|
||||
|
||||
<b-modal
|
||||
scrollable
|
||||
size="xl"
|
||||
:visible="showEditor"
|
||||
body-class="p-0 border-top-0"
|
||||
|
||||
@@ -117,6 +117,7 @@ import { mapGetters, mapActions } from 'vuex'
|
||||
import Grid from 'corteza-webapp-compose/src/components/Public/Page/Grid'
|
||||
import RecordToolbar from 'corteza-webapp-compose/src/components/Common/RecordToolbar'
|
||||
import record from 'corteza-webapp-compose/src/mixins/record'
|
||||
import page from 'corteza-webapp-compose/src/mixins/page'
|
||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
|
||||
@@ -135,6 +136,7 @@ export default {
|
||||
mixins: [
|
||||
// The record mixin contains all of the logic for creating/editing/deleting/undeleting the record
|
||||
record,
|
||||
page,
|
||||
],
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
@@ -155,28 +157,12 @@ export default {
|
||||
},
|
||||
|
||||
props: {
|
||||
namespace: {
|
||||
type: compose.Namespace,
|
||||
required: true,
|
||||
},
|
||||
|
||||
module: {
|
||||
type: compose.Module,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
|
||||
page: {
|
||||
type: compose.Page,
|
||||
required: true,
|
||||
},
|
||||
|
||||
recordID: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
|
||||
// When creating from related record blocks
|
||||
refRecord: {
|
||||
type: compose.Record,
|
||||
@@ -207,10 +193,7 @@ export default {
|
||||
return {
|
||||
inEditing: this.edit,
|
||||
|
||||
layouts: [],
|
||||
layout: undefined,
|
||||
layoutButtons: new Set(),
|
||||
blocks: undefined,
|
||||
|
||||
recordNavigation: {
|
||||
prev: undefined,
|
||||
@@ -224,7 +207,6 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getNextAndPrevRecord: 'ui/getNextAndPrevRecord',
|
||||
getPageLayouts: 'pageLayout/getByPageID',
|
||||
previousPages: 'ui/previousPages',
|
||||
modalPreviousPages: 'ui/modalPreviousPages',
|
||||
}),
|
||||
@@ -354,14 +336,6 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$root.$on('refetch-record-blocks', this.refetchRecordBlocks)
|
||||
|
||||
if (this.showRecordModal) {
|
||||
this.$root.$on('bv::modal::hide', this.checkUnsavedChanges)
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
this.abortRequests()
|
||||
this.destroyEvents()
|
||||
@@ -375,6 +349,23 @@ export default {
|
||||
popModalPreviousPage: 'ui/popModalPreviousPage',
|
||||
}),
|
||||
|
||||
createEvents () {
|
||||
this.$root.$on('refetch-record-blocks', this.refetchRecordBlocks)
|
||||
this.$root.$on('record-field-change', this.validateBlocksVisibilityCondition)
|
||||
|
||||
if (this.showRecordModal) {
|
||||
this.$root.$on('bv::modal::hide', this.checkUnsavedChanges)
|
||||
}
|
||||
},
|
||||
|
||||
validateBlocksVisibilityCondition ({ fieldName }) {
|
||||
const { blocks = [] } = this.page
|
||||
|
||||
if (blocks.some(({ meta = {} }) => ((meta.visibility || {}).expression).includes(fieldName))) {
|
||||
this.updateBlocks()
|
||||
}
|
||||
},
|
||||
|
||||
async loadRecord (recordID = this.recordID) {
|
||||
if (!this.page) {
|
||||
return
|
||||
@@ -507,19 +498,7 @@ export default {
|
||||
evaluateLayoutExpressions (variables = {}) {
|
||||
const expressions = {}
|
||||
variables = {
|
||||
user: this.$auth.user,
|
||||
record: this.record ? this.record.serialize() : {},
|
||||
screen: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
userAgent: navigator.userAgent,
|
||||
breakpoint: this.getBreakpoint(), // This is from a global mixin uiHelpers
|
||||
},
|
||||
oldLayout: this.layout,
|
||||
layout: undefined,
|
||||
isView: !this.edit && !this.isNew,
|
||||
isCreate: this.isNew,
|
||||
isEdit: this.edit && !this.isNew,
|
||||
...this.expressionVariables,
|
||||
...variables,
|
||||
}
|
||||
|
||||
@@ -540,51 +519,8 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
async determineLayout (pageLayoutID, variables = {}, redirectOnFail = true) {
|
||||
// Clear stored records so they can be refetched with latest values
|
||||
this.clearRecordSet()
|
||||
this.resetErrors()
|
||||
let expressions = {}
|
||||
|
||||
// Only evaluate if one of the layouts has an expressions variable
|
||||
if (this.layouts.some(({ config = {} }) => config.visibility.expression)) {
|
||||
expressions = await this.evaluateLayoutExpressions(variables)
|
||||
}
|
||||
|
||||
// Check layouts for expressions/roles and find the first one that fits
|
||||
const matchedLayout = 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))
|
||||
})
|
||||
|
||||
this.processing = false
|
||||
|
||||
if (!matchedLayout) {
|
||||
this.toastWarning(this.$t('notification:page.page-layout.notFound.view'))
|
||||
|
||||
if (redirectOnFail) {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.inEditing = this.edit
|
||||
|
||||
if (this.layout && matchedLayout.pageLayoutID === this.layout.pageLayoutID) {
|
||||
return
|
||||
}
|
||||
|
||||
this.layout = matchedLayout
|
||||
|
||||
const { blocks = [], config = {} } = this.layout || {}
|
||||
handleRecordButtons () {
|
||||
const { config = {} } = this.layout
|
||||
const { buttons = [] } = config
|
||||
|
||||
this.layoutButtons = Object.entries(buttons).reduce((acc, [key, value]) => {
|
||||
@@ -593,19 +529,6 @@ export default {
|
||||
}
|
||||
return acc
|
||||
}, new Set())
|
||||
|
||||
const tempBlocks = []
|
||||
|
||||
blocks.forEach(({ blockID, xywh }) => {
|
||||
const block = this.page.blocks.find(b => b.blockID === blockID)
|
||||
|
||||
if (block) {
|
||||
block.xywh = xywh
|
||||
tempBlocks.push(block)
|
||||
}
|
||||
})
|
||||
|
||||
this.blocks = tempBlocks
|
||||
},
|
||||
|
||||
refetchRecordBlocks () {
|
||||
@@ -668,6 +591,7 @@ export default {
|
||||
|
||||
destroyEvents () {
|
||||
this.$root.$off('refetch-record-blocks', this.refetchRecordBlocks)
|
||||
this.$root.$off('record-field-change', this.validateBlocksVisibilityCondition)
|
||||
|
||||
if (this.showRecordModal) {
|
||||
this.$root.$off('bv::modal::hide', this.checkUnsavedChanges)
|
||||
|
||||
@@ -88,7 +88,8 @@ import Grid from 'corteza-webapp-compose/src/components/Public/Page/Grid'
|
||||
import RecordModal from 'corteza-webapp-compose/src/components/Public/Record/Modal'
|
||||
import MagnificationModal from 'corteza-webapp-compose/src/components/Public/Page/Block/Modal'
|
||||
import PageTranslator from 'corteza-webapp-compose/src/components/Admin/Page/PageTranslator'
|
||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||
import { NoID } from '@cortezaproject/corteza-js'
|
||||
import page from 'corteza-webapp-compose/src/mixins/page'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
@@ -102,6 +103,10 @@ export default {
|
||||
MagnificationModal,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
page,
|
||||
],
|
||||
|
||||
beforeRouteLeave (to, from, next) {
|
||||
this.setPreviousPages([])
|
||||
next()
|
||||
@@ -123,30 +128,8 @@ export default {
|
||||
next()
|
||||
},
|
||||
|
||||
props: {
|
||||
namespace: { // via router-view
|
||||
type: compose.Namespace,
|
||||
required: true,
|
||||
},
|
||||
|
||||
page: { // via route-view
|
||||
type: compose.Page,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// We're using recordID to check if we need to display router-view or grid component
|
||||
recordID: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
layouts: [],
|
||||
layout: undefined,
|
||||
blocks: undefined,
|
||||
|
||||
pageTitle: '',
|
||||
}
|
||||
},
|
||||
@@ -154,13 +137,8 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
recordPaginationUsable: 'ui/recordPaginationUsable',
|
||||
getPageLayouts: 'pageLayout/getByPageID',
|
||||
}),
|
||||
|
||||
isRecordPage () {
|
||||
return this.recordID || this.$route.name === 'page.record.create'
|
||||
},
|
||||
|
||||
module () {
|
||||
if (this.page.moduleID && this.page.moduleID !== NoID) {
|
||||
return this.$store.getters['module/getByID'](this.page.moduleID)
|
||||
@@ -194,8 +172,9 @@ export default {
|
||||
handler (pageID) {
|
||||
if (pageID === NoID) return
|
||||
|
||||
this.layouts = []
|
||||
this.layouts = this.getPageLayouts(this.page.pageID)
|
||||
this.layout = undefined
|
||||
this.pageTitle = this.page.title
|
||||
|
||||
if (!this.isRecordPage) {
|
||||
this.determineLayout()
|
||||
@@ -213,10 +192,6 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$root.$on('refetch-records', this.refetchRecords)
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
this.destroyEvents()
|
||||
this.setDefaultValues()
|
||||
@@ -232,84 +207,8 @@ export default {
|
||||
clearRecordSet: 'record/clearSet',
|
||||
}),
|
||||
|
||||
evaluateLayoutExpressions () {
|
||||
const expressions = {}
|
||||
const variables = {
|
||||
screen: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
userAgent: navigator.userAgent,
|
||||
breakpoint: this.getBreakpoint(), // This is from a global mixin uiHelpers
|
||||
},
|
||||
user: this.$auth.user,
|
||||
oldLayout: this.layout,
|
||||
layout: undefined,
|
||||
}
|
||||
|
||||
this.layouts.forEach(layout => {
|
||||
const { config = {} } = layout
|
||||
if (!config.visibility.expression) return
|
||||
|
||||
variables.layout = layout
|
||||
|
||||
expressions[layout.pageLayoutID] = config.visibility.expression
|
||||
})
|
||||
|
||||
return this.$SystemAPI.expressionEvaluate({ variables, expressions }).catch(e => {
|
||||
this.toastErrorHandler(this.$t('notification:evaluate.failed'))(e)
|
||||
Object.keys(expressions).forEach(key => (expressions[key] = false))
|
||||
|
||||
return expressions
|
||||
})
|
||||
},
|
||||
|
||||
async determineLayout () {
|
||||
// Clear stored records so they can be refetched with latest values
|
||||
this.clearRecordSet()
|
||||
this.layouts = this.getPageLayouts(this.page.pageID)
|
||||
|
||||
let expressions = {}
|
||||
|
||||
// Only evaluate if one of the layouts has an expressions variable
|
||||
if (this.layouts.some(({ config = {} }) => config.visibility.expression)) {
|
||||
this.pageTitle = this.page.title
|
||||
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
|
||||
|
||||
return this.$auth.user.roles.some(roleID => roles.includes(roleID))
|
||||
})
|
||||
|
||||
if (!this.layout) {
|
||||
this.toastWarning(this.$t('notification:page.page-layout.notFound.view'))
|
||||
return this.$router.go(-1)
|
||||
}
|
||||
|
||||
const { handle, meta = {} } = this.layout || {}
|
||||
const title = meta.title || this.page.title
|
||||
this.pageTitle = title || handle || this.$t('navigation:noPageTitle')
|
||||
document.title = [title, this.namespace.name, this.$t('general:label.app-name.public')].filter(v => v).join(' | ')
|
||||
|
||||
const tempBlocks = []
|
||||
const { blocks = [] } = this.layout || {}
|
||||
|
||||
blocks.forEach(({ blockID, xywh }) => {
|
||||
const block = this.page.blocks.find(b => b.blockID === blockID)
|
||||
|
||||
if (block) {
|
||||
block.xywh = xywh
|
||||
tempBlocks.push(block)
|
||||
}
|
||||
})
|
||||
|
||||
this.blocks = tempBlocks
|
||||
createEvents () {
|
||||
this.$root.$on('refetch-records', this.refetchRecords)
|
||||
},
|
||||
|
||||
refetchRecords () {
|
||||
|
||||
@@ -21,11 +21,17 @@ interface PageBlockStyle {
|
||||
border?: PageBlockStyleBorder;
|
||||
}
|
||||
|
||||
interface Visibility {
|
||||
expression: string;
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
interface PageBlockMeta {
|
||||
hidden?: boolean;
|
||||
tempID?: string;
|
||||
customID?: string;
|
||||
customCSSClass?: string;
|
||||
visibility: Visibility;
|
||||
}
|
||||
|
||||
export type PageBlockInput = PageBlock | Partial<PageBlock>
|
||||
@@ -49,6 +55,10 @@ export class PageBlock {
|
||||
tempID: undefined,
|
||||
customID: undefined,
|
||||
customCSSClass: undefined,
|
||||
visibility: {
|
||||
expression: '',
|
||||
roles: [],
|
||||
},
|
||||
}
|
||||
|
||||
public style: PageBlockStyle = {
|
||||
|
||||
@@ -145,6 +145,20 @@ general:
|
||||
placeholder: custom-class-1 custom-class-2
|
||||
description: Used to reference the block in Custom CSS (.custom-class)
|
||||
invalid-state: Can contain only letters, numbers, underscores and dashes. Must end with letter or number. Use spaces to separate multiple classes
|
||||
visibility:
|
||||
label: Visibility
|
||||
roles:
|
||||
label: Roles
|
||||
placeholder: Pick roles that the block will be shown to
|
||||
condition:
|
||||
label: Condition
|
||||
placeholder: When will the block be shown
|
||||
description:
|
||||
record-page: "You can use {{0}}, {{1}}, {{2}}, {{3}} inside the expression, for example: {{4}} or {{5}}"
|
||||
non-record-page: "You can use {{0}}, {{1}} inside the expression, for example: {{2}} or {{3}}"
|
||||
tooltip:
|
||||
performance:
|
||||
condition: Using visibility conditions will impact performance
|
||||
magnifyOptions:
|
||||
disabled: Disabled
|
||||
modal: Modal
|
||||
@@ -513,7 +527,7 @@ recordList:
|
||||
permissions: Permissions
|
||||
recordDisplayOptions: On record click
|
||||
recordSelectorDisplayOptions: On record selector click
|
||||
addRecordOptions: On add record click
|
||||
addRecordOptions: On add record click
|
||||
textStyles: Text Styles
|
||||
configureNonWrappingFelids: Configure non-wrapping fields
|
||||
showFullText: Show full text
|
||||
|
||||
Reference in New Issue
Block a user