diff --git a/client/web/admin/src/components/Application/CApplicationEditorUnify.vue b/client/web/admin/src/components/Application/CApplicationEditorUnify.vue
index a186b45ee..354147122 100644
--- a/client/web/admin/src/components/Application/CApplicationEditorUnify.vue
+++ b/client/web/admin/src/components/Application/CApplicationEditorUnify.vue
@@ -58,6 +58,7 @@
data-test-id="file-logo-upload"
accept="image/*"
:placeholder="$t('logo.placeholder')"
+ @change="$emit('change-detected')"
/>
diff --git a/client/web/admin/src/components/Role/CRoleEditorMembers.vue b/client/web/admin/src/components/Role/CRoleEditorMembers.vue
index ff56cdc30..202cc5db5 100644
--- a/client/web/admin/src/components/Role/CRoleEditorMembers.vue
+++ b/client/web/admin/src/components/Role/CRoleEditorMembers.vue
@@ -154,7 +154,7 @@ export default {
this.$set(this.members, i, { ...this.members[i], label: label, dirty: true })
}
- this.memberUsers.push({ value: member.userID, label })
+ this.memberUsers.push(member)
}
},
diff --git a/client/web/admin/src/views/Automation/Workflow/Editor.vue b/client/web/admin/src/views/Automation/Workflow/Editor.vue
index e5a88646e..c0a6f89a9 100644
--- a/client/web/admin/src/views/Automation/Workflow/Editor.vue
+++ b/client/web/admin/src/views/Automation/Workflow/Editor.vue
@@ -48,6 +48,7 @@
diff --git a/client/web/admin/src/views/Federation/Nodes/Editor.vue b/client/web/admin/src/views/Federation/Nodes/Editor.vue
index db4906b39..edd73d85b 100644
--- a/client/web/admin/src/views/Federation/Nodes/Editor.vue
+++ b/client/web/admin/src/views/Federation/Nodes/Editor.vue
@@ -131,6 +131,7 @@ import { mapGetters } from 'vuex'
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
import CFederationEditorInfo from 'corteza-webapp-admin/src/components/Federation/CFederationEditorInfo'
import CSubmitButton from 'corteza-webapp-admin/src/components/CSubmitButton'
+import { cloneDeep, isEqual } from 'lodash'
export default {
i18nOptions: {
@@ -158,6 +159,7 @@ export default {
data () {
return {
node: {},
+ initialNodeState: {},
// Processing and success flags for each form
info: {
@@ -175,6 +177,14 @@ export default {
}
},
+ beforeRouteUpdate (to, from, next) {
+ this.checkUnsavedChanges(next, to)
+ },
+
+ beforeRouteLeave (to, from, next) {
+ this.checkUnsavedChanges(next, to)
+ },
+
computed: {
...mapGetters({
can: 'rbac/can',
@@ -201,7 +211,17 @@ export default {
this.fetchNode()
this.fetchGeneratedUrl()
} else {
- this.node = {}
+ this.node = {
+ name: '',
+ baseURL: '',
+ contact: '',
+ }
+
+ this.initialNodeState = {
+ name: '',
+ baseURL: '',
+ contact: '',
+ }
}
},
},
@@ -214,6 +234,7 @@ export default {
this.$FederationAPI.nodeRead({ nodeID: this.nodeID })
.then(node => {
this.node = node // new federation.Node(node)
+ this.initialNodeState = cloneDeep(node)
})
.catch(this.toastErrorHandler(this.$t('notification:federation.fetch.error')))
.finally(() => {
@@ -250,6 +271,7 @@ export default {
this.$FederationAPI.nodeUpdate(payload)
.then(node => {
this.node = node
+ this.initialNodeState = cloneDeep(node)
this.animateSuccess('info')
this.toastSuccess(this.$t('notification:federation.update.success'))
@@ -343,6 +365,16 @@ export default {
copyUrl () {
navigator.clipboard.writeText(this.generate.url)
},
+
+ checkUnsavedChanges (next, to) {
+ const isNewPage = this.$route.path.includes('/new') && to.name.includes('edit')
+
+ if (isNewPage) {
+ next(true)
+ } else if (!to.name.includes('edit')) {
+ next(!isEqual(this.node, this.initialNodeState) ? window.confirm(this.$t('general:editor.unsavedChanges')) : true)
+ }
+ },
},
}
diff --git a/client/web/admin/src/views/System/Apigw/Editor.vue b/client/web/admin/src/views/System/Apigw/Editor.vue
index 58022127e..aed0dd3ef 100644
--- a/client/web/admin/src/views/System/Apigw/Editor.vue
+++ b/client/web/admin/src/views/System/Apigw/Editor.vue
@@ -53,6 +53,7 @@
diff --git a/client/web/admin/src/views/System/Application/Editor.vue b/client/web/admin/src/views/System/Application/Editor.vue
index 03e95a259..b70f8040c 100644
--- a/client/web/admin/src/views/System/Application/Editor.vue
+++ b/client/web/admin/src/views/System/Application/Editor.vue
@@ -7,10 +7,11 @@
:title="title"
>
diff --git a/client/web/admin/src/views/System/Connection/Editor.vue b/client/web/admin/src/views/System/Connection/Editor.vue
index ec6fd7568..474de8327 100644
--- a/client/web/admin/src/views/System/Connection/Editor.vue
+++ b/client/web/admin/src/views/System/Connection/Editor.vue
@@ -63,6 +63,7 @@
diff --git a/client/web/admin/src/views/System/Queue/Editor.vue b/client/web/admin/src/views/System/Queue/Editor.vue
index 94845de6d..54bae0d74 100644
--- a/client/web/admin/src/views/System/Queue/Editor.vue
+++ b/client/web/admin/src/views/System/Queue/Editor.vue
@@ -34,6 +34,7 @@
diff --git a/client/web/admin/src/views/System/Role/Editor.vue b/client/web/admin/src/views/System/Role/Editor.vue
index f11e14c64..12b12b944 100644
--- a/client/web/admin/src/views/System/Role/Editor.vue
+++ b/client/web/admin/src/views/System/Role/Editor.vue
@@ -70,6 +70,7 @@
diff --git a/client/web/admin/src/views/System/SensitivityLevel/Editor.vue b/client/web/admin/src/views/System/SensitivityLevel/Editor.vue
index 09b93b1bd..56069f59c 100644
--- a/client/web/admin/src/views/System/SensitivityLevel/Editor.vue
+++ b/client/web/admin/src/views/System/SensitivityLevel/Editor.vue
@@ -32,6 +32,7 @@
diff --git a/client/web/admin/src/views/System/Template/Editor.vue b/client/web/admin/src/views/System/Template/Editor.vue
index 4a9d3bd45..04f95f03e 100644
--- a/client/web/admin/src/views/System/Template/Editor.vue
+++ b/client/web/admin/src/views/System/Template/Editor.vue
@@ -62,6 +62,7 @@
diff --git a/client/web/admin/src/views/System/User/Editor.vue b/client/web/admin/src/views/System/User/Editor.vue
index ae51b3012..040e3b261 100644
--- a/client/web/admin/src/views/System/User/Editor.vue
+++ b/client/web/admin/src/views/System/User/Editor.vue
@@ -100,6 +100,7 @@
diff --git a/client/web/compose/src/components/Admin/Module/UniqueValues.vue b/client/web/compose/src/components/Admin/Module/UniqueValues.vue
index e138a78f1..a35252437 100644
--- a/client/web/compose/src/components/Admin/Module/UniqueValues.vue
+++ b/client/web/compose/src/components/Admin/Module/UniqueValues.vue
@@ -25,7 +25,7 @@
@@ -145,12 +145,6 @@ export default {
computed: {
rules: {
get () {
- this.module.config.recordDeDup.rules.forEach(rule => {
- if (rule.constraints === null) {
- rule.constraints = []
- }
- })
-
return this.module.config.recordDeDup.rules
},
set (value) {
@@ -185,6 +179,10 @@ export default {
},
updateRuleConstraint (rule) {
+ if (!rule.constraints) {
+ rule.constraints = []
+ }
+
rule.constraints.push({
attribute: rule.currentField.name,
modifier: 'case-sensitive',
@@ -197,7 +195,7 @@ export default {
},
filterFieldOptions (rule) {
- const selectedFields = rule.constraints.map(({ attribute }) => attribute)
+ const selectedFields = rule.constraints ? rule.constraints.map(({ attribute }) => attribute) : []
return this.module.fields.filter(({ name }) => !selectedFields.includes(name))
},
diff --git a/client/web/compose/src/views/Admin/Charts/Edit.vue b/client/web/compose/src/views/Admin/Charts/Edit.vue
index d789c2624..d9c5b321d 100644
--- a/client/web/compose/src/views/Admin/Charts/Edit.vue
+++ b/client/web/compose/src/views/Admin/Charts/Edit.vue
@@ -311,6 +311,7 @@
diff --git a/client/web/compose/src/views/Admin/Modules/Edit.vue b/client/web/compose/src/views/Admin/Modules/Edit.vue
index da5baf712..ebdcb3c1a 100644
--- a/client/web/compose/src/views/Admin/Modules/Edit.vue
+++ b/client/web/compose/src/views/Admin/Modules/Edit.vue
@@ -452,6 +452,7 @@
diff --git a/client/web/compose/src/views/Namespace/Edit.vue b/client/web/compose/src/views/Namespace/Edit.vue
index 99e73fd48..04995b7e6 100644
--- a/client/web/compose/src/views/Namespace/Edit.vue
+++ b/client/web/compose/src/views/Namespace/Edit.vue
@@ -318,6 +318,7 @@
diff --git a/client/web/reporter/src/components/Report/Blocks/index.vue b/client/web/reporter/src/components/Report/Blocks/index.vue
index 81fac5bda..79501662b 100644
--- a/client/web/reporter/src/components/Report/Blocks/index.vue
+++ b/client/web/reporter/src/components/Report/Blocks/index.vue
@@ -165,6 +165,8 @@ export default {
sizes.forEach((size, index) => {
this.block.elements[index].meta.size = size
})
+
+ this.$emit('item-updated', this.index)
},
getScenarioDefinition (element) {
diff --git a/client/web/reporter/src/components/Report/Grid.vue b/client/web/reporter/src/components/Report/Grid.vue
index f92b7abcd..ec118d96d 100644
--- a/client/web/reporter/src/components/Report/Grid.vue
+++ b/client/web/reporter/src/components/Report/Grid.vue
@@ -28,6 +28,8 @@
:y="item.y"
:class="{ 'editable-grid-item': editable }"
drag-ignore-from=".gutter"
+ @moved="onBlockUpdated(index)"
+ @resized="onBlockUpdated(index)"
>
diff --git a/client/web/reporter/src/mixins/report.js b/client/web/reporter/src/mixins/report.js
index 0c0a9524a..ed1349e32 100644
--- a/client/web/reporter/src/mixins/report.js
+++ b/client/web/reporter/src/mixins/report.js
@@ -15,6 +15,7 @@ export default {
return this.$SystemAPI.reportRead({ reportID })
.then(report => {
this.report = new system.Report(report)
+ this.initialReportState = this.report.clone()
})
.catch(this.toastErrorHandler(this.$t('notification:report.fetchFailed')))
.finally(() => {
@@ -44,6 +45,8 @@ export default {
return this.$SystemAPI.reportCreate(report)
.then(report => {
this.report = new system.Report(report)
+ this.initialReportState = this.report.clone()
+ this.detectStateChange = false
this.toastSuccess(this.$t('notification:report.created'))
this.$router.push({ name: 'report.edit', params: { reportID: report.reportID } })
})
@@ -55,6 +58,8 @@ export default {
return this.$SystemAPI.reportUpdate(report)
.then(report => {
this.report = new system.Report(report)
+ this.initialReportState = this.report.clone()
+ this.detectStateChange = false
this.toastSuccess(this.$t('notification:report.updated'))
})
.catch(this.toastErrorHandler(this.$t('notification:report.updateFailed')))
diff --git a/client/web/reporter/src/views/Report/Builder.vue b/client/web/reporter/src/views/Report/Builder.vue
index efd30684d..4d4804434 100644
--- a/client/web/reporter/src/views/Report/Builder.vue
+++ b/client/web/reporter/src/views/Report/Builder.vue
@@ -85,6 +85,7 @@
v-if="report && canRead && showReport"
:blocks.sync="reportBlocks"
editable
+ @item-updated="onBlockUpdated"
>
+
+
+
+
@@ -411,6 +424,8 @@ export default {
report: undefined,
+ unsavedBlocks: new Set(),
+
dataframes: [],
blocks: {
@@ -630,10 +645,19 @@ export default {
},
},
+ beforeRouteUpdate (to, from, next) {
+ this.checkUnsavedBlocks(next)
+ },
+
+ beforeRouteLeave (to, from, next) {
+ this.checkUnsavedBlocks(next)
+ },
+
watch: {
reportID: {
immediate: true,
handler (reportID) {
+ this.unsavedBlocks.clear()
this.scenarios.selected = undefined
if (reportID) {
@@ -814,6 +838,7 @@ export default {
.then(() => {
this.mapBlocks()
this.refreshReport()
+ this.unsavedBlocks.clear()
})
},
@@ -842,6 +867,8 @@ export default {
},
updateBlock () {
+ this.unsavedBlocks.add(this.blocks.currentIndex)
+
if (this.currentBlock) {
const elements = this.currentBlock.elements
@@ -860,6 +887,7 @@ export default {
deleteBlock (index = undefined) {
this.reindexBlocks(this.reportBlocks.filter((p, i) => index !== i))
+ this.unsavedBlocks.add(index)
},
// Display elements
@@ -887,6 +915,8 @@ export default {
this.editBlock(this.blocks.currentIndex)
this.setCurrentDisplayElement(this.currentBlock.elements.length - 1)
+
+ this.updateBlock()
},
// Scenarios
@@ -924,6 +954,15 @@ export default {
getOptionKey (scenario) {
return scenario
},
+
+ // Trigger browser dialog on page leave to prevent unsaved changes
+ checkUnsavedBlocks (next) {
+ next(!this.unsavedBlocks.size || window.confirm(this.$t('builder:unsaved-changes')))
+ },
+
+ onBlockUpdated (index) {
+ this.unsavedBlocks.add(index)
+ },
},
}
diff --git a/client/web/reporter/src/views/Report/Edit.vue b/client/web/reporter/src/views/Report/Edit.vue
index e0a672a18..f7a3303c8 100644
--- a/client/web/reporter/src/views/Report/Edit.vue
+++ b/client/web/reporter/src/views/Report/Edit.vue
@@ -103,6 +103,7 @@
:placeholder="$t('name')"
required
:state="nameState"
+ @input="handleDetectStateChange"
/>
@@ -121,6 +122,7 @@
:placeholder="$t('placeholder-handle')"
required
:state="handleState"
+ @input="handleDetectStateChange"
/>
@@ -178,6 +181,7 @@ import { handle } from '@cortezaproject/corteza-vue'
import report from 'corteza-webapp-reporter/src/mixins/report'
import EditorToolbar from 'corteza-webapp-reporter/src/components/EditorToolbar'
import { mapGetters } from 'vuex'
+import { isEqual } from 'lodash'
export default {
name: 'EditReport',
@@ -197,6 +201,9 @@ export default {
processing: false,
report: undefined,
+ initialReportState: undefined,
+
+ detectStateChange: false,
}
},
@@ -268,9 +275,44 @@ export default {
this.fetchReport(reportID)
} else {
this.report = new system.Report()
+ this.initialReportState = new system.Report()
}
},
},
},
+
+ beforeRouteUpdate (to, from, next) {
+ this.checkUnsavedChart(next)
+ },
+
+ beforeRouteLeave (to, from, next) {
+ this.checkUnsavedChart(next)
+ },
+
+ methods: {
+ handleDetectStateChange () {
+ this.detectStateChange = true
+ },
+
+ checkUnsavedChart (next) {
+ const reportState = {
+ handle: this.report.handle,
+ meta: {
+ name: this.report.meta.name,
+ description: this.report.meta.description,
+ },
+ }
+
+ const initialReportState = {
+ handle: this.initialReportState.handle,
+ meta: {
+ name: this.initialReportState.meta.name,
+ description: this.initialReportState.meta.description,
+ },
+ }
+
+ next(!isEqual(reportState, initialReportState) ? window.confirm(this.$t('unsavedChanges')) : true)
+ },
+ },
}
diff --git a/lib/js/src/compose/types/module-field/base.ts b/lib/js/src/compose/types/module-field/base.ts
index 98decb6ae..376f7c0cb 100644
--- a/lib/js/src/compose/types/module-field/base.ts
+++ b/lib/js/src/compose/types/module-field/base.ts
@@ -121,7 +121,23 @@ export class ModuleField {
applyOptions (o?: Partial): void {
if (!o) return
- Apply(this.options, o, Object, 'description', 'hint')
+ if (o.description) {
+ this.options.description = {
+ ...this.options.description,
+ ...o.description,
+ }
+
+ this.options.description.edit = this.options.description.edit || undefined
+ }
+
+ if (o.hint) {
+ this.options.hint = {
+ ...this.options.hint,
+ ...o.hint,
+ }
+
+ this.options.hint.edit = this.options.hint.edit || undefined
+ }
}
clone (): ModuleField {
diff --git a/lib/js/src/system/index.ts b/lib/js/src/system/index.ts
index ca63ee84f..c403b5e76 100644
--- a/lib/js/src/system/index.ts
+++ b/lib/js/src/system/index.ts
@@ -6,6 +6,7 @@ export { Reminder } from './types/reminder'
export { Template } from './types/template'
export { Report } from './types/report'
export { DalConnection } from './types/dalConnection'
+export { AuthClient } from './types/authClient'
export {
SystemEvent,
RoleEvent,
diff --git a/lib/js/src/system/types/application.ts b/lib/js/src/system/types/application.ts
index 14003137f..518aaf136 100644
--- a/lib/js/src/system/types/application.ts
+++ b/lib/js/src/system/types/application.ts
@@ -1,4 +1,71 @@
+import { Apply, CortezaID, ISO8601Date, NoID } from '../../cast'
+import { IsOf } from '../../guards'
+
+interface PartialApplication extends Partial> {
+ createdAt?: string|number|Date;
+ updatedAt?: string|number|Date;
+}
+
+interface Unify {
+ name: string;
+ listed: boolean,
+ url: string,
+ config: string,
+ iconID: string,
+ logoID: string
+}
+
export class Application {
- // @todo port application class here
- [_: string]: unknown
+ public applicationID = undefined
+ public name = ''
+ public ownerID?: number = 0;
+ public enabled = false
+ public weight?: number = 0;
+
+ public unify?: Unify = {
+ name: '',
+ listed: false,
+ url: '',
+ config: '',
+ iconID: NoID,
+ logoID: NoID,
+ };
+
+ public canGrant: boolean = true;
+ public canUpdateApplication: boolean = true;
+ public canDeleteApplication: boolean = true;
+
+ constructor (r?: PartialApplication) {
+ this.apply(r)
+ }
+
+ apply (r?: PartialApplication): void {
+ Apply(this, r, CortezaID, 'applicationID')
+ Apply(this, r, String, 'name')
+ Apply(this, r, Number, 'weight', 'ownerID')
+ Apply(this, r, Boolean, 'enabled', 'canGrant', 'canUpdateApplication', 'canDeleteApplication')
+
+
+ if (r && IsOf(r, 'unify')) {
+ this.unify = r.unify
+ }
+ }
+
+ /**
+ * Returns resource ID
+ */
+ get resourceID (): string {
+ return `${this.resourceType}:${this.applicationID}`
+ }
+
+ /**
+ * Resource type
+ */
+ get resourceType (): string {
+ return 'system:application'
+ }
+
+ clone (): Application {
+ return new Application(JSON.parse(JSON.stringify(this)))
+ }
}
diff --git a/lib/js/src/system/types/authClient.ts b/lib/js/src/system/types/authClient.ts
new file mode 100644
index 000000000..14f7dc857
--- /dev/null
+++ b/lib/js/src/system/types/authClient.ts
@@ -0,0 +1,88 @@
+import { Apply, CortezaID, ISO8601Date, NoID } from "../../cast";
+import { IsOf } from "../../guards";
+
+interface PartialAuthClient
+ extends Partial<
+ Omit
+ > {
+ createdAt?: string | number | Date;
+ updatedAt?: string | number | Date;
+ deletedAt?: string | number | Date;
+ lastUsedAt?: string | number | Date;
+}
+
+interface AuthClientMeta {
+ name: string;
+ description: string;
+}
+
+interface DefSecurity {
+ impersonateUser: string;
+ permittedRoles: Array;
+ prohibitedRoles: Array;
+ forcedRoles: Array;
+}
+
+export class AuthClient {
+ public authClientID = NoID;
+ public handle = "";
+ public scope = "profile api";
+ public redirectURI = "";
+ public validGrant = 'authorization_code';
+
+ public meta: AuthClientMeta = {
+ name: "",
+ description: "",
+ };
+
+ public security: DefSecurity = {
+ impersonateUser: "0",
+ permittedRoles: [],
+ prohibitedRoles: [],
+ forcedRoles: [],
+ };
+
+ public enabled = true;
+ public trusted = false;
+
+ public createdAt?: Date = undefined;
+ public updatedAt?: Date = undefined;
+ public deletedAt?: Date = undefined;
+
+ public createdBy = NoID;
+ public updatedBy = NoID;
+ public deletedBy = NoID;
+
+ public canDeleteAuthClient = false;
+ public canGrant = false;
+ public canUpdateAuthClient = false;
+
+ constructor (o?: PartialAuthClient) {
+ this.apply(o)
+ }
+
+ apply (o?: PartialAuthClient): void {
+ Apply(this, o, CortezaID, 'authClientID')
+ Apply(this, o, ISO8601Date, 'createdAt', 'updatedAt', 'deletedAt');
+ Apply(this, o, String, 'handle', 'scope', 'redirectURI', 'validGrant');
+ Apply(this, o, Boolean, 'enabled', 'trusted', 'canDeleteAuthClient', 'canGrant', 'canUpdateAuthClient');
+
+
+ if (IsOf(o, 'meta')) {
+ this.meta = { ...o.meta }
+ }
+
+ if (IsOf(o, 'security')) {
+ this.security = {
+ ...this.security,
+ ...o.security
+ }
+ }
+
+ Apply(this, o, CortezaID, 'createdBy', 'updatedBy', 'deletedBy');
+ }
+
+ clone(): AuthClient {
+ return new AuthClient(JSON.parse(JSON.stringify(this)));
+ }
+}
diff --git a/lib/js/src/system/types/dalConnection.ts b/lib/js/src/system/types/dalConnection.ts
index 5ca834c6f..52669122e 100644
--- a/lib/js/src/system/types/dalConnection.ts
+++ b/lib/js/src/system/types/dalConnection.ts
@@ -89,9 +89,9 @@ export class DalConnection {
public updatedAt?: Date = undefined
public deletedAt?: Date = undefined
- public createdBy = ''
- public updatedBy = ''
- public deletedBy = ''
+ public createdBy = NoID
+ public updatedBy = NoID
+ public deletedBy = NoID
public canDeleteConnection = false
public canManageDalConfig = false
@@ -112,7 +112,7 @@ export class DalConnection {
}
if (IsOf(dc, 'config')) {
- this.config = { ...dc.config.privacy }
+ this.config = { ...dc.config }
if (this.connectionID !== NoID && this.canManageDalConfig) {
this.config = {
@@ -124,6 +124,12 @@ export class DalConnection {
},
...dc.config,
}
+
+ if (!this.config.privacy.sensitivityLevelID) {
+ this.config.privacy = {
+ sensitivityLevelID: NoID,
+ }
+ }
}
}
@@ -141,4 +147,8 @@ export class DalConnection {
}
}
}
+
+ clone (): DalConnection {
+ return new DalConnection(JSON.parse(JSON.stringify(this)))
+ }
}
diff --git a/lib/js/src/system/types/report.ts b/lib/js/src/system/types/report.ts
index 6e440f52f..bf6334fe3 100644
--- a/lib/js/src/system/types/report.ts
+++ b/lib/js/src/system/types/report.ts
@@ -35,7 +35,10 @@ interface ReportScenario {
export class Report {
public reportID = NoID
public handle = ''
- public meta: Meta = {}
+ public meta: Meta = {
+ name: '',
+ description: '',
+ }
public sources: Array = []
public blocks: Array = []
public scenarios: Array = []
@@ -102,4 +105,8 @@ export class Report {
'canRunReport',
)
}
+
+ clone (): Report {
+ return new Report(JSON.parse(JSON.stringify(this)))
+ }
}
diff --git a/lib/js/src/system/types/role.ts b/lib/js/src/system/types/role.ts
index 4c6f0f335..1f3d12a3d 100644
--- a/lib/js/src/system/types/role.ts
+++ b/lib/js/src/system/types/role.ts
@@ -100,4 +100,8 @@ export class Role {
get isContext (): boolean {
return this.meta?.context?.expr?.length > 0
}
+
+ clone (): Role {
+ return new Role(JSON.parse(JSON.stringify(this)))
+ }
}
diff --git a/lib/js/src/system/types/template.ts b/lib/js/src/system/types/template.ts
index 2194aeda1..09e3cab43 100644
--- a/lib/js/src/system/types/template.ts
+++ b/lib/js/src/system/types/template.ts
@@ -19,7 +19,10 @@ export class Template {
public language = ''
public type = 'text/html'
public partial = false
- public meta: Meta = {}
+ public meta: Meta = {
+ short: '',
+ description: '',
+ }
public template = ''
public labels: object = {}
public ownerID = NoID
@@ -63,4 +66,8 @@ export class Template {
get resourceType (): string {
return 'system:template'
}
+
+ clone (): Template {
+ return new Template(JSON.parse(JSON.stringify(this)))
+ }
}
diff --git a/lib/js/src/system/types/user.ts b/lib/js/src/system/types/user.ts
index 0de36438b..9c5759409 100644
--- a/lib/js/src/system/types/user.ts
+++ b/lib/js/src/system/types/user.ts
@@ -106,4 +106,8 @@ export class User {
this.userID,
].join(' ').toLocaleLowerCase()
}
+
+ clone (): User {
+ return new User(JSON.parse(JSON.stringify(this)))
+ }
}
diff --git a/locale/en/corteza-webapp-admin/general.yaml b/locale/en/corteza-webapp-admin/general.yaml
index 0f7cb9638..d78e90b18 100644
--- a/locale/en/corteza-webapp-admin/general.yaml
+++ b/locale/en/corteza-webapp-admin/general.yaml
@@ -47,4 +47,5 @@ label:
delete: Delete
undelete: Undelete
fileTypeNotAllowed: File type not allowed
-
+editor:
+ unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
diff --git a/locale/en/corteza-webapp-compose/chart.yaml b/locale/en/corteza-webapp-compose/chart.yaml
index aeb8c8101..f9449982f 100644
--- a/locale/en/corteza-webapp-compose/chart.yaml
+++ b/locale/en/corteza-webapp-compose/chart.yaml
@@ -220,6 +220,7 @@ name: Chart name *
handle: Handle
generalSettings: General settings
notification:
+ unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
loadFailed: Could not load chart
saveFailed: Could not save this chart
saved: Chart saved
diff --git a/locale/en/corteza-webapp-compose/module.yaml b/locale/en/corteza-webapp-compose/module.yaml
index dce38d400..cf6a923da 100644
--- a/locale/en/corteza-webapp-compose/module.yaml
+++ b/locale/en/corteza-webapp-compose/module.yaml
@@ -193,6 +193,7 @@ forModule:
recordPage: Record page for module
general:
fields: Module fields
+ unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
label:
attributes: Attributes
handle: Handle
diff --git a/locale/en/corteza-webapp-compose/namespace.yaml b/locale/en/corteza-webapp-compose/namespace.yaml
index 7390c92cc..8572320c3 100644
--- a/locale/en/corteza-webapp-compose/namespace.yaml
+++ b/locale/en/corteza-webapp-compose/namespace.yaml
@@ -58,6 +58,7 @@ manage:
title: Manage namespaces
list-view: Back to namespace list
delete: Delete
+ unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
disabled: Disabled
diff --git a/locale/en/corteza-webapp-compose/page.yaml b/locale/en/corteza-webapp-compose/page.yaml
index 5c548e57a..e9f183096 100644
--- a/locale/en/corteza-webapp-compose/page.yaml
+++ b/locale/en/corteza-webapp-compose/page.yaml
@@ -88,6 +88,7 @@ moduleEdit: Edit module
navigation:
page: Pages
viewPage: View Page
+unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
newPlaceholder: Page title
noBlock: No block added yet
noPages: No pages
diff --git a/locale/en/corteza-webapp-reporter/builder.yaml b/locale/en/corteza-webapp-reporter/builder.yaml
index 58f01522c..82bbf75e4 100644
--- a/locale/en/corteza-webapp-reporter/builder.yaml
+++ b/locale/en/corteza-webapp-reporter/builder.yaml
@@ -73,6 +73,7 @@ select-all: Select all
unselect-all: Unselect all
value: Value
name: Name
+unsaved-changes: Unsaved changes will be lost. Do you wish to leave the page?
filter:
operators:
like: Like
diff --git a/locale/en/corteza-webapp-reporter/edit.yaml b/locale/en/corteza-webapp-reporter/edit.yaml
index 4ad8b4388..7aa8d0142 100644
--- a/locale/en/corteza-webapp-reporter/edit.yaml
+++ b/locale/en/corteza-webapp-reporter/edit.yaml
@@ -5,6 +5,7 @@ name: Name
name-with-star: 'Name*'
new-report: New Report
permissions: Permissions
+unsavedChanges: Unsaved changes will be lost. Do you wish to leave the page?
report:
create: Create Report
builder: Report Builder