3
0

Add page layout reordering and roles check

This commit is contained in:
Jože Fortun
2023-03-29 22:58:49 +02:00
parent a03025fb5f
commit 4f31add6a4
7 changed files with 139 additions and 119 deletions

View File

@@ -194,7 +194,7 @@ export default {
const reorder = () => {
const pageIDs = afterParent.children.map(p => p.pageID)
if (pageIDs.length) {
this.$ComposeAPI.pageReorder({ namespaceID, selfID: afterID, pageIDs: pageIDs }).then(() => {
this.$ComposeAPI.pageReorder({ namespaceID, selfID: afterID, pageIDs }).then(() => {
return this.$store.dispatch('page/load', { namespaceID, clear: true, force: true })
}).then(() => {
this.toastSuccess(this.$t('reordered'))

View File

@@ -9,57 +9,57 @@
>
<b-spinner />
</div>
<template v-else>
<div
class="w-100 h-100"
@mouseover="disableMap"
@mouseleave="enableMap"
>
<l-map
v-if="map"
ref="map"
:zoom="map.zoom"
:center="map.center"
:min-zoom="map.zoomMin"
:max-zoom="map.zoomMax"
:bounds="map.bounds"
:max-bounds="map.bounds"
class="w-100 h-100"
@locationfound="onLocationFound"
>
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
:attribution="map.attribution"
/>
<l-polygon
v-for="(geometry, i) in geometries"
:key="`polygon-${i}`"
:lat-lngs="geometry.map(value => value.geometry)"
:color="colors[i]"
/>
<l-marker
v-for="(marker, i) in localValue"
:key="`marker-${i}`"
:lat-lng="marker.value"
:icon="getIcon(marker)"
/>
<l-control class="leaflet-bar">
<a
:title="$t('geometry.tooltip.goToCurrentLocation')"
role="button"
class="d-flex justify-content-center align-items-center"
@click="goToCurrentLocation"
>
<font-awesome-icon
:icon="['fas', 'location-arrow']"
class="text-primary"
/>
</a>
</l-control>
</l-map>
</div>
</template>
<div
v-else
class="w-100 h-100"
@mouseover="disableMap"
@mouseleave="enableMap"
>
<l-map
v-if="map"
ref="map"
:zoom="map.zoom"
:center="map.center"
:min-zoom="map.zoomMin"
:max-zoom="map.zoomMax"
:bounds="map.bounds"
:max-bounds="map.bounds"
class="w-100 h-100"
@locationfound="onLocationFound"
>
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
:attribution="map.attribution"
/>
<l-polygon
v-for="(geometry, i) in geometries"
:key="`polygon-${i}`"
:lat-lngs="geometry.map(value => value.geometry)"
:color="colors[i]"
/>
<l-marker
v-for="(marker, i) in localValue"
:key="`marker-${i}`"
:lat-lng="marker.value"
:icon="getIcon(marker)"
/>
<l-control class="leaflet-bar">
<a
:title="$t('geometry.tooltip.goToCurrentLocation')"
role="button"
class="d-flex justify-content-center align-items-center"
@click="goToCurrentLocation"
>
<font-awesome-icon
:icon="['fas', 'location-arrow']"
class="text-primary"
/>
</a>
</l-control>
</l-map>
</div>
</wrap>
</template>
@@ -125,14 +125,18 @@ export default {
this.loadEvents()
},
},
options: {
deep: true,
handler () {
this.loadEvents()
},
},
boundingRect () {
this.loadEvents()
boundingRect: {
handler () {
this.loadEvents()
},
},
},
@@ -211,6 +215,10 @@ export default {
})
})).finally(() => {
this.processing = false
setTimeout(() => {
this.$refs.map.mapObject.invalidateSize()
})
})
},

View File

@@ -1,7 +1,5 @@
<template>
<grid
v-if="layout"
:key="layout.layoutID"
:blocks="blocks"
:editable="false"
>
@@ -11,7 +9,7 @@
<page-block
v-bind="{ ...$attrs }"
:page="page"
:blocks="page.blocks"
:blocks="blocks"
:block="block"
:bounding-rect="boundingRect"
:block-index="index"
@@ -22,7 +20,6 @@
</grid>
</template>
<script>
import { mapGetters } from 'vuex'
import Grid from '../../Common/Grid'
import PageBlock from '../../PageBlocks'
import { compose } from '@cortezaproject/corteza-js'
@@ -40,43 +37,10 @@ export default {
type: compose.Page,
required: true,
},
},
data () {
return {
layouts: [],
layout: undefined,
blocks: [],
}
},
computed: {
...mapGetters({
getPageLayouts: 'pageLayout/getByPageID',
}),
},
watch: {
'page.pageID': {
immediate: true,
handler (pageID) {
this.layouts = this.getPageLayouts(pageID)
const { layoutID } = this.$route.query
if (layoutID) {
this.layout = this.layouts.find(({ pageLayoutID }) => pageLayoutID === layoutID)
} else {
this.layout = this.layouts[0]
this.$router.replace({ ...this.$route, query: { ...this.$route.query, layoutID: this.layout.pageLayoutID } })
}
this.blocks = (this.layout || {}).blocks.map(({ blockID, xywh }) => {
const block = this.page.blocks.find(b => b.blockID === blockID)
block.xywh = xywh
return block
})
},
blocks: {
type: Array,
required: true,
},
},
}

View File

@@ -35,7 +35,7 @@ export default function (ComposeAPI) {
},
getByPageID (state) {
return (ID) => state.set.filter(({ pageID }) => ID === pageID)
return (ID) => state.set.filter(({ pageID }) => ID === pageID).sort((a, b) => a.weight - b.weight)
},
set (state) {
@@ -55,7 +55,7 @@ export default function (ComposeAPI) {
commit(types.loading)
commit(types.pending)
return ComposeAPI.pageLayoutListNamespace({ namespaceID }).then(({ set, filter }) => {
return ComposeAPI.pageLayoutListNamespace({ namespaceID, sort: 'weight ASC' }).then(({ set, filter }) => {
if (set && set.length > 0) {
commit(types.updateSet, set.map(pl => new compose.PageLayout(pl)))
}
@@ -91,7 +91,7 @@ export default function (ComposeAPI) {
}
commit(types.pending)
return ComposeAPI.pageLayoutList({ namespaceID, pageID }).then(({ set }) => {
return ComposeAPI.pageLayoutList({ namespaceID, pageID, sort: 'weight ASC' }).then(({ set }) => {
commit(types.updateSet, set.map(pl => new compose.PageLayout(pl)))
return set
}).finally(() => {

View File

@@ -329,6 +329,7 @@
v-model="currentLayoutRoles"
:options="roles.options"
:loading="roles.processing"
placeholder="Pick roles that the layout will be shown to"
:get-option-label="role => role.name"
:reduce="role => role.roleID"
:selectable="role => !currentLayoutRoles.includes(role.roleID)"
@@ -440,6 +441,7 @@
:hide-delete="hideDelete"
:hide-save="!page.canUpdatePage"
:disable-save="disableSave"
:processing="processing"
@clone="handleClone()"
@delete="handleDeletePage"
@save="handleSave()"
@@ -516,6 +518,8 @@ export default {
data () {
return {
processing: false,
page: new compose.Page(),
showIconModal: false,
@@ -523,7 +527,6 @@ export default {
selectedAttachmentID: '',
linkUrl: '',
processing: false,
layouts: [],
layoutEditor: {
@@ -634,13 +637,16 @@ export default {
this.deletedLayouts = new Set()
if (pageID) {
this.processing = true
const { namespaceID } = this.namespace
this.findPageByID({ namespaceID, pageID }).then((page) => {
this.page = page.clone()
return this.fetchAttachments()
}).catch(this.toastErrorHandler(this.$t('notification:page.loadFailed')))
this.fetchLayouts().catch(this.toastErrorHandler(this.$t('notification:page.loadFailed')))
}).then(this.fetchLayouts)
.finally(() => {
this.processing = false
}).catch(this.toastErrorHandler(this.$t('notification:page.loadFailed')))
}
},
},
@@ -718,27 +724,43 @@ export default {
})
},
async handlePageLayoutReorder () {
const { namespaceID } = this.namespace
const pageIDs = this.layouts.map(({ pageLayoutID }) => pageLayoutID)
return this.$ComposeAPI.pageLayoutReorder({ namespaceID, pageID: this.pageID, pageIDs }).then(() => {
return this.$store.dispatch('pageLayout/load', { namespaceID, clear: true, force: true })
})
},
handleSave ({ closeOnSuccess = false } = {}) {
this.processing = true
/**
* Pass a special tag alongside payload that
* instructs store layer to add content-language header to the API request
*/
const resourceTranslationLanguage = this.currentLanguage
const { namespaceID } = this.namespace
return this.saveIcon().then(icon => {
this.page.config.navItem.icon = icon
return this.updatePage({ namespaceID, ...this.page, resourceTranslationLanguage }).then((page) => {
this.page = page.clone()
return this.handleSaveLayouts().then(this.fetchLayouts)
})
}).then(() => {
this.deletedLayouts = new Set()
return this.updatePage({ namespaceID, ...this.page, resourceTranslationLanguage })
}).then(page => {
this.page = page.clone()
return this.handleSaveLayouts()
}).then(this.handlePageLayoutReorder)
.then(() => {
this.fetchLayouts()
this.deletedLayouts = new Set()
this.toastSuccess(this.$t('notification:page.saved'))
if (closeOnSuccess) {
this.$router.push({ name: 'admin.pages' })
}
}).catch(this.toastErrorHandler(this.$t('notification:page.saveFailed')))
this.toastSuccess(this.$t('notification:page.saved'))
if (closeOnSuccess) {
this.$router.push({ name: 'admin.pages' })
}
}).finally(() => {
this.processing = false
}).catch(this.toastErrorHandler(this.$t('notification:page.saveFailed')))
},
handleDeletePage (strategy = 'abort') {
@@ -752,8 +774,6 @@ export default {
},
async fetchAttachments () {
this.processing = true
return this.$ComposeAPI.iconList({ sort: 'id DESC' })
.then(({ set: attachments = [] }) => {
const baseURL = this.$ComposeAPI.baseURL
@@ -767,9 +787,6 @@ export default {
}
})
.catch(this.toastErrorHandler(this.$t('notification:page.iconFetchFailed')))
.finally(() => {
this.processing = false
})
},
async saveIcon () {

View File

@@ -62,6 +62,7 @@
:namespace="namespace"
:module="module"
:page="page"
:blocks="blocks"
/>
</div>
@@ -112,9 +113,19 @@ export default {
},
},
data () {
return {
layouts: [],
layout: undefined,
blocks: [],
}
},
computed: {
...mapGetters({
recordPaginationUsable: 'ui/recordPaginationUsable',
getPageLayouts: 'pageLayout/getByPageID',
}),
isRecordCreatePage () {
@@ -141,7 +152,8 @@ export default {
pageTitle () {
if (this.page.pageID !== NoID) {
const { title = '', handle = '' } = this.page
return title || handle || this.$t('navigation:noPageTitle')
const { meta = {} } = this.layout || {}
return meta.title || title || handle || this.$t('navigation:noPageTitle')
}
return ''
@@ -160,19 +172,37 @@ export default {
'page.title': {
immediate: true,
handler (title) {
document.title = [title, this.namespace.name, this.$t('general:label.app-name.public')].filter(v => v).join(' | ')
},
},
'page.pageID': {
immediate: true,
handler () {
handler (pageID) {
// If the page changed we need to clear the record pagination since its not relevant anymore
if (this.recordPaginationUsable) {
this.setRecordPaginationUsable(false)
} else {
this.clearRecordIDs()
}
this.layouts = this.getPageLayouts(pageID)
this.layout = this.layouts.find(l => {
const { roles = [] } = l.config.visibility
if (!roles.length) return true
return this.$auth.user.roles.some(roleID => roles.includes(roleID))
})
const { meta = {} } = this.layout || {}
const title = meta.title || this.page.title
document.title = [title, this.namespace.name, this.$t('general:label.app-name.public')].filter(v => v).join(' | ')
this.blocks = (this.layout || {}).blocks.map(({ blockID, xywh }) => {
const block = this.page.blocks.find(b => b.blockID === blockID)
block.xywh = xywh
return block
})
},
},
},