3
0

Fix redirecting to record view with workflow prompt

This commit is contained in:
Jože Fortun 2024-05-10 19:16:42 +02:00
parent bc0b185c6b
commit e766b4be2c
11 changed files with 172 additions and 193 deletions

View File

@ -29,7 +29,7 @@
pill
size="lg"
variant="outline-primary"
:disabled="!record || processing || !recordNavigation.prev"
:disabled="!isCreated || !record || processing || !recordNavigation.prev"
@click="navigateToRecord(recordNavigation.prev)"
>
<font-awesome-icon :icon="['fas', 'angle-left']" />
@ -40,7 +40,7 @@
size="lg"
pill
variant="outline-primary"
:disabled="!record || processing || !recordNavigation.next"
:disabled="!isCreated || !record || processing || !recordNavigation.next"
@click="navigateToRecord(recordNavigation.next)"
>
<font-awesome-icon :icon="['fas', 'angle-right']" />
@ -58,7 +58,7 @@
<c-input-confirm
v-if="isCreated && !(isDeleted || hideDelete || settings.hideDelete)"
:disabled="!record || !canDeleteRecord"
:disabled="!record || !canDeleteRecord || processing"
:processing="processingDelete"
:text="labels.delete || $t('label.delete')"
size="lg"
@ -69,7 +69,7 @@
<c-input-confirm
v-if="isDeleted && !(hideDelete || settings.hideDelete)"
:disabled="!record || !canUndeleteRecord"
:disabled="!record || !canUndeleteRecord || processing"
:processing="processingUndelete"
:text="$t('label.restore')"
size="lg"
@ -128,7 +128,7 @@
<c-button-submit
v-if="inEditing && !(hideSubmit || settings.hideSubmit)"
data-test-id="button-save"
:disabled="!record || !canSaveRecord || processingSubmit"
:disabled="!record || !canSaveRecord || processingSubmit || processing"
:processing="processingSubmit"
:text="labels.submit || $t('label.save')"
size="lg"

View File

@ -86,12 +86,21 @@ export default {
recordPaginationUsable: 'ui/recordPaginationUsable',
modalPreviousPages: 'ui/modalPreviousPages',
}),
uniqueID () {
const { recordPageID, recordID, edit = false } = this.$route.query
const isEdit = typeof edit === 'string' ? edit === 'true' : Boolean(edit)
return [recordPageID, recordID, isEdit]
},
},
watch: {
'$route.query.recordPageID': {
uniqueID: {
immediate: true,
handler (recordPageID, oldRecordPageID) {
handler (value = [], oldValue = []) {
const [recordPageID, recordID, edit] = value
const [oldRecordPageID] = oldValue
if (!recordPageID) {
this.setDefaultValues()
this.clearModalPreviousPage()
@ -105,6 +114,14 @@ export default {
this.clearRecordIDs()
}
}
if (!recordPageID) {
return
}
if (recordID !== this.recordID || recordPageID !== (this.page || {}).pageID || edit !== this.edit) {
this.loadRecord({ recordID, recordPageID, edit })
}
},
},
},
@ -112,12 +129,6 @@ export default {
mounted () {
this.$root.$on('show-record-modal', this.loadRecord)
this.$root.$on('refetch-records', this.refetchRecords)
const { recordID, recordPageID } = this.$route.query
if (recordID && recordPageID) {
this.loadRecord({ recordID, recordPageID })
}
},
beforeDestroy () {
@ -133,7 +144,7 @@ export default {
clearModalPreviousPage: 'ui/clearModalPreviousPage',
}),
loadRecord ({ recordID, recordPageID, values, refRecord, edit, pushModalPreviousPage = true }) {
loadRecord ({ recordID, recordPageID, values, refRecord, edit = this.edit, pushModalPreviousPage = true }) {
if (!recordID && !recordPageID) {
this.onHidden()
return
@ -148,7 +159,7 @@ export default {
// Push the previous modal view page to the modal route history stack on the store so we can go back to it
if (pushModalPreviousPage) {
this.pushModalPreviousPage({ recordID, recordPageID })
this.pushModalPreviousPage({ recordID, recordPageID, edit })
}
setTimeout(() => {
@ -157,9 +168,10 @@ export default {
...this.$route.query,
recordID,
recordPageID,
edit,
},
})
}, 300)
})
},
loadModal ({ recordID, recordPageID }) {
@ -191,10 +203,11 @@ export default {
recordID: undefined,
moduleID: undefined,
recordPageID: undefined,
edit: undefined,
},
})
}
}, 300)
})
},
refetchRecords () {
@ -203,6 +216,7 @@ export default {
setDefaultValues () {
this.showModal = false
this.edit = false
this.recordID = undefined
this.module = undefined
this.page = undefined

View File

@ -12,6 +12,7 @@ export default {
processingDelete: false,
processingUndelete: false,
processingSubmit: false,
processingEdit: false,
record: undefined,
initialRecordState: undefined,
errors: new validator.Validated(),
@ -162,37 +163,20 @@ export default {
if (record.valueErrors.set) {
this.toastWarning(this.$t('notification:record.validationWarnings'))
} else {
// We do this prop mutation (BAD!!) so that prompts can use the edit prop properly since just redirecting to the /edit route doesn't work (for now)
if (this.edit) {
this.edit = false
}
this.inCreating = false
this.inEditing = false
// reset the record initial state in cases where the record edit page is redirected to the record view page
this.initialRecordState = record.clone()
this.record = record
this.initialRecordState = this.record.clone()
if (this.showRecordModal) {
this.$emit('handle-record-redirect', { recordID: record.recordID, recordPageID: this.page.pageID })
this.$emit('handle-record-redirect', { recordID: this.record.recordID, recordPageID: this.page.pageID, edit: false })
// If we are in a modal we need to refresh blocks not in modal
this.$root.$emit('module-records-updated', {
moduleID: this.module.moduleID,
notPageID: this.page.pageID,
})
}
if (isNew) {
this.$router.push({ name: route, params: { ...this.$route.params, recordID: record.recordID } })
} else {
this.record = record
// reset the record initial state in cases where the record edit page is opened on a modal
this.initialRecordState = this.record.clone()
this.determineLayout().then(() => {
this.$root.$emit(`refetch-non-record-blocks:${this.page.pageID}`)
})
this.$router.push({ name: route, params: { ...this.$route.params, recordID: this.record.recordID } })
}
}
@ -245,8 +229,7 @@ export default {
if (this.record.valueErrors.set) {
this.toastWarning(this.$t('notification:record.validationWarnings'))
} else {
this.inCreating = false
this.inEditing = false
this.edit = false
this.record = record
this.initialRecordState = this.record.clone()

View File

@ -18,7 +18,7 @@
:options="layouts"
:reduce="layout => layout.pageLayoutID"
size="sm"
style="max-width: 300px;"
style="min-width: 250px; max-width: 300px;"
@input="setLayout"
/>

View File

@ -1,15 +0,0 @@
<script>
import ViewRecord from './View'
export default {
name: 'CreateRecord',
extends: ViewRecord,
data () {
return {
inEditing: true,
inCreating: true,
}
},
}
</script>

View File

@ -1,22 +0,0 @@
<script>
import ViewRecord from './View'
export default {
name: 'EditRecord',
extends: ViewRecord,
data () {
return {
inEditing: true,
inCreating: false,
}
},
methods: {
handleBack () {
this.$router.push({ name: 'page.record', params: { recordID: this.recordID, pageID: this.page.pageID } })
},
},
}
</script>

View File

@ -70,6 +70,7 @@
v-for="(action, index) in layoutActions.filter(a => a.placement === 'start')"
:key="index"
:variant="action.meta.style.variant"
:disabled="processing"
size="lg"
class="text-nowrap"
@click.prevent="handleAction(action)"
@ -83,6 +84,7 @@
v-for="(action, index) in layoutActions.filter(a => a.placement === 'center')"
:key="index"
:variant="action.meta.style.variant"
:disabled="processing"
size="lg"
class="text-nowrap"
@click.prevent="handleAction(action)"
@ -96,6 +98,7 @@
v-for="(action, index) in layoutActions.filter(a => a.placement === 'end')"
:key="index"
:variant="action.meta.style.variant"
:disabled="processing"
size="lg"
class="text-nowrap"
@click.prevent="handleAction(action)"
@ -109,6 +112,7 @@
</template>
<script>
import axios from 'axios'
import { isEqual } from 'lodash'
import { mapGetters, mapActions } from 'vuex'
import Grid from 'corteza-webapp-compose/src/components/Public/Page/Grid'
@ -116,7 +120,6 @@ import RecordToolbar from 'corteza-webapp-compose/src/components/Common/RecordTo
import record from 'corteza-webapp-compose/src/mixins/record'
import { compose, NoID } from '@cortezaproject/corteza-js'
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
import axios from 'axios'
export default {
i18nOptions: {
@ -203,8 +206,7 @@ export default {
data () {
return {
inEditing: false,
inCreating: false,
inEditing: this.edit,
layouts: [],
layout: undefined,
@ -228,6 +230,10 @@ export default {
modalPreviousPages: 'ui/modalPreviousPages',
}),
isNew () {
return !this.recordID || this.recordID === NoID
},
portalTopbarTitle () {
return this.showRecordModal ? 'record-modal-header' : 'topbar-title'
},
@ -289,7 +295,7 @@ export default {
const { name, handle } = this.module
const titlePrefix = this.inCreating ? 'create' : this.inEditing ? 'edit' : 'view'
const titlePrefix = this.isNew ? 'create' : this.edit ? 'edit' : 'view'
return this.$t(`page:public.record.${titlePrefix}.title`, { name: name || handle, interpolation: { escapeValue: false } })
},
@ -308,27 +314,32 @@ export default {
},
uniqueID () {
return `${this.page.pageID}-${this.recordID}`
return [(this.page || {}).pageID, this.recordID, this.edit]
},
},
watch: {
'page.pageID': {
immediate: true,
handler () {
if (this.page.pageID === NoID) return
this.layouts = this.getPageLayouts(this.page.pageID)
this.layout = undefined
},
},
uniqueID: {
immediate: true,
handler () {
this.record = undefined
this.initialRecordState = undefined
this.refresh()
handler (value = [], oldValue = []) {
const [pageID = '', recordID = '', edit = false] = value
const [oldPageID = '', oldRecordID = '', oldEdit = false] = oldValue
// If page changed, get layouts
if (pageID && pageID !== NoID && pageID !== oldPageID) {
this.layout = undefined
this.layouts = this.getPageLayouts(this.page.pageID)
}
// If page or record changed refresh record and determine layout
if (pageID !== oldPageID || recordID !== oldRecordID) {
this.record = undefined
this.initialRecordState = undefined
this.refresh()
} else if (edit !== oldEdit) {
this.determineLayout()
}
},
},
@ -348,16 +359,6 @@ export default {
}
},
},
edit: {
immediate: true,
handler (value) {
if (value) {
this.inEditing = true
this.inCreating = true
}
},
},
},
mounted () {
@ -411,20 +412,17 @@ export default {
}
})
} else {
if (this.refRecord) {
// Record create form called from a related records block,
// we'll try to find an appropriate field and cross-link this new record to ref
const recRefField = this.module.fields.find(f => f.kind === 'Record' && f.options.moduleID === this.refRecord.moduleID)
if (recRefField) {
this.values[recRefField.name] = this.refRecord.recordID
}
}
this.record = new compose.Record(module, { values: this.values })
this.initialRecordState = this.record.clone()
this.inEditing = true
this.inCreating = true
}
if (this.refRecord) {
// Record create form called from a related records block,
// we'll try to find an appropriate field and cross-link this new record to ref
const recRefField = this.module.fields.find(f => f.kind === 'Record' && f.options.moduleID === this.refRecord.moduleID)
if (recRefField) {
this.record.values[recRefField.name] = this.refRecord.recordID
}
}
}
},
@ -435,81 +433,78 @@ export default {
* came from (and "where" is back).
*/
if (this.showRecordModal) {
this.popModalPreviousPage().then(({ recordID, recordPageID }) => {
this.$emit('on-modal-back', { recordID, recordPageID, pushModalPreviousPage: false })
this.inCreating = recordID === NoID
this.inEditing = false
})
if (this.checkUnsavedChangesOnModal()) {
this.popModalPreviousPage().then(({ recordID, recordPageID, edit }) => {
this.$emit('on-modal-back', { recordID, recordPageID, pushModalPreviousPage: false, edit })
})
}
} else {
const previousPage = await this.popPreviousPages()
const extraPop = !this.isNew
return
}
const previousPage = await this.popPreviousPages()
const extraPop = !this.inCreating
this.$router.push(previousPage || { name: 'pages', params: { slug: this.namespace.slug || this.namespace.namespaceID } })
// Pop an additional time so that the route we went back to isn't added to previousPages
if (extraPop) {
this.popPreviousPages()
this.$router.push(previousPage || { name: 'pages', params: { slug: this.namespace.slug || this.namespace.namespaceID } })
// Pop an additional time so that the route we went back to isn't added to previousPages
if (extraPop) {
this.popPreviousPages()
}
}
},
handleAdd () {
if (!this.showRecordModal) {
this.$router.push({ name: 'page.record.create', params: this.newRouteParams })
return
}
this.processing = true
this.inEditing = true
this.inCreating = true
this.record = new compose.Record(this.module, { values: this.values })
this.initialRecordState = this.record.clone()
this.$emit('handle-record-redirect', { recordID: NoID, recordPageID: this.page.pageID })
if (this.showRecordModal) {
if (this.checkUnsavedChangesOnModal()) {
this.$emit('handle-record-redirect', { recordID: NoID, recordPageID: this.page.pageID, edit: true })
}
} else {
this.$router.push({ name: 'page.record.create', params: this.newRouteParams })
}
},
handleClone () {
if (!this.showRecordModal) {
this.$router.push({ name: 'page.record.create', params: { pageID: this.page.pageID, values: this.record.values } })
return
}
this.processing = true
this.inEditing = true
this.inCreating = true
this.record = new compose.Record(this.module, { values: this.record.values })
this.initialRecordState = this.record.clone()
this.$emit('handle-record-redirect', { recordID: NoID, recordPageID: this.page.pageID, values: this.record.values })
if (this.showRecordModal) {
if (this.checkUnsavedChangesOnModal()) {
this.$emit('handle-record-redirect', { recordID: NoID, recordPageID: this.page.pageID, values: this.record.values, edit: true })
}
} else {
this.$router.push({ name: 'page.record.create', params: { pageID: this.page.pageID, values: this.record.values } })
}
},
handleEdit () {
this.processing = true
this.refresh({ isView: false, isEdit: true, isCreate: false }).then(() => {
this.inEditing = true
this.inCreating = false
this.$root.$emit(`refetch-non-record-blocks:${this.page.pageID}`)
}).finally(() => {
this.processing = false
})
if (this.showRecordModal) {
this.$emit('handle-record-redirect', { recordID: this.recordID, recordPageID: this.page.pageID, edit: true })
} else {
this.$router.push({ name: 'page.record.edit', params: { recordID: this.recordID, pageID: this.page.pageID } })
}
},
handleView () {
this.processing = true
this.refresh({ isView: true, isEdit: false, isCreate: false }).then(() => {
this.inEditing = false
this.inCreating = false
this.$root.$emit(`refetch-non-record-blocks:${this.page.pageID}`)
}).finally(() => {
this.processing = false
})
if (this.showRecordModal) {
if (this.checkUnsavedChangesOnModal()) {
this.$emit('handle-record-redirect', { recordID: this.recordID, recordPageID: this.page.pageID, edit: false })
}
} else {
this.$router.push({ name: 'page.record', params: { recordID: this.recordID, pageID: this.page.pageID } })
}
},
handleRedirectToPrevOrNext (recordID) {
if (!recordID) return
this.processing = true
if (this.showRecordModal) {
this.$emit('handle-record-redirect', { recordID, recordPageID: this.page.pageID })
if (this.checkUnsavedChangesOnModal()) {
this.$emit('handle-record-redirect', { recordID, recordPageID: this.page.pageID })
}
} else {
this.$router.push({
params: { ...this.$route.params, recordID },
@ -531,9 +526,9 @@ export default {
},
oldLayout: this.layout,
layout: undefined,
isView: !this.inEditing && !this.inCreating,
isCreate: this.inCreating,
isEdit: this.inEditing && !this.inCreating,
isView: !this.edit && !this.isNew,
isCreate: this.isNew,
isEdit: this.edit && !this.isNew,
...variables,
}
@ -577,18 +572,24 @@ export default {
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'))
return this.$router.go(-1)
this.$router.go(-1)
return
}
this.inEditing = this.edit
if (this.layout && matchedLayout.pageLayoutID === this.layout.pageLayoutID) {
return
}
this.layout = matchedLayout
const { config = {} } = this.layout
const { blocks = [], config = {} } = this.layout || {}
const { buttons = [] } = config
this.layoutButtons = Object.entries(buttons).reduce((acc, [key, value]) => {
@ -599,7 +600,6 @@ export default {
}, new Set())
const tempBlocks = []
const { blocks = [] } = this.layout || {}
blocks.forEach(({ blockID, xywh }) => {
const block = this.page.blocks.find(b => b.blockID === blockID)
@ -615,7 +615,7 @@ export default {
refetchRecordBlocks () {
// Don't refresh when creating and prompt user before refreshing when editing
if (this.inCreating || (this.inEditing && this.compareRecordValues() && !window.confirm(this.$t('notification:record.staleDataRefresh')))) {
if (this.isNew || (this.edit && this.compareRecordValues() && !window.confirm(this.$t('notification:record.staleDataRefresh')))) {
return
}
@ -625,14 +625,22 @@ export default {
},
async refresh (variables = {}) {
this.processing = true
return this.loadRecord().then(() => {
return this.determineLayout(undefined, variables)
}).finally(() => {
this.processing = false
})
},
handleAction ({ kind, params = {} }) {
if (kind === 'toLayout') {
this.determineLayout(params.pageLayoutID)
this.processing = true
this.determineLayout(params.pageLayoutID).finally(() => {
this.processing = false
})
} else if (kind === 'toURL') {
window.open(params.url, params.openIn === 'newTab' ? '_blank' : '_self')
}
@ -640,7 +648,6 @@ export default {
setDefaultValues () {
this.inEditing = false
this.inCreating = false
this.layouts = []
this.layout = undefined
this.layoutButtons.clear()
@ -671,22 +678,28 @@ export default {
},
checkUnsavedChanges (next) {
if (!this.inEditing) {
if (!this.edit) {
next(true)
return
}
next(this.compareRecordValues() ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
next(this.compareRecordValues() ? window.confirm(this.$t('general:module.unsavedChanges')) : true)
},
checkUnsavedChangesOnModal (bvEvent, modalId) {
if (modalId !== 'record-modal' || !this.inEditing) return
if ((bvEvent && modalId !== 'record-modal') || !this.edit) return true
const recordStateChange = this.compareRecordValues() ? window.confirm(this.$t('general:editor.unsavedChanges')) : true
const recordStateChange = this.compareRecordValues() ? window.confirm(this.$t('general:module.unsavedChanges')) : true
if (!recordStateChange) {
if (bvEvent && !recordStateChange) {
bvEvent.preventDefault()
}
if (recordStateChange) {
this.initialRecordState = this.record.clone()
}
return recordStateChange
},
},
}

View File

@ -1,10 +1,12 @@
// public route builder/helper
function r (name, path, component) {
function r (name, path, component, defaultProps = {}) {
return {
path,
name,
component: () => import('./' + component + '.vue'),
props: true,
props: r => {
return { ...defaultProps, ...r.params }
},
}
}
@ -37,9 +39,9 @@ export default [
...r('page', ':pageID?', 'Public/Pages/View'),
children: [
r('page.record.edit', 'record/:recordID/edit', 'Public/Pages/Records/Edit'),
r('page.record', 'record/:recordID', 'Public/Pages/Records/View'),
r('page.record.create', 'record', 'Public/Pages/Records/Create'),
r('page.record.edit', 'record/:recordID/edit', 'Public/Pages/Records/View', { edit: true }),
r('page.record', 'record/:recordID', 'Public/Pages/Records/View', { edit: false }),
r('page.record.create', 'record', 'Public/Pages/Records/View', { edit: true }),
],
},
],

View File

@ -108,7 +108,7 @@ const definitions: Record<string, PromptDefinition> = {
const module = pVal(v, 'module')
const namespace = pVal(v, 'namespace')
const record = pVal(v, 'record')
let edit = !!pVal(v, 'edit')
const edit = !!pVal(v, 'edit')
const delay = (pVal(v, 'delay') || 0) as number
const openMode = pVal(v, 'openMode')
@ -185,8 +185,12 @@ const definitions: Record<string, PromptDefinition> = {
// @ts-ignore
if (this.$root.$options.name === 'compose') {
let name = 'page.record'
edit = edit || !recordID || recordID === NoID
name += edit ? '.edit' : '.create'
if (!recordID || recordID === NoID) {
name += '.create'
} else if (edit) {
name += '.edit'
}
// If name and params match, make sure to refresh page instead of push
// @ts-ignore

View File

@ -54,10 +54,10 @@ label:
fileTypeNotAllowed: File type not allowed
editor:
unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
unsavedChanges: Unsaved changes will be lost. Do you wish to close the record?
resourceList:
pagination:
recordsPerPage: Records per page
themes:
labels:
light: Light

View File

@ -124,8 +124,8 @@ page:
invalidBlock: Not a valid page block
page-layout:
notFound:
edit: No page layout, create one to edit it
view: No page layout found, create one to view the page
edit: No page layout found
view: No page layout found
save:
success: Layout saved
failed: Failed to save layout