@live-change/frontend-auto-form 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-row justify-content-between align-items-center mt-3">
|
|
3
|
+
<div class="flex flex-column">
|
|
4
|
+
<div v-if="draftChanged" class="text-sm text-500 mr-2">
|
|
5
|
+
Draft changed
|
|
6
|
+
</div>
|
|
7
|
+
<div v-if="savingDraft" class="text-500 mr-2">
|
|
8
|
+
<i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
|
|
9
|
+
<span>Saving draft...</span>
|
|
10
|
+
</div>
|
|
11
|
+
<div v-if="validationResult" class="font-semibold p-error mr-2">
|
|
12
|
+
Before saving, please correct the errors above.
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex flex-row">
|
|
16
|
+
<slot name="submit" v-if="!validationResult">
|
|
17
|
+
<div v-if="changed" class="ml-2">
|
|
18
|
+
<Button type="submit" label="Save" class="" icon="pi pi-save" />
|
|
19
|
+
</div>
|
|
20
|
+
<div v-else class="flex flex-row align-items-center ml-2">
|
|
21
|
+
<div class="mr-2">
|
|
22
|
+
No changes to save.
|
|
23
|
+
</div>
|
|
24
|
+
<Button type="submit" label="Save" class="" icon="pi pi-save" disabled />
|
|
25
|
+
</div>
|
|
26
|
+
</slot>
|
|
27
|
+
<slot name="reset" v-if="resetButton">
|
|
28
|
+
<div v-if="changed">
|
|
29
|
+
<Button label="Reset" class="ml-2" icon="pi pi-eraser" @click="editor.reset" />
|
|
30
|
+
</div>
|
|
31
|
+
</slot>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup>
|
|
37
|
+
|
|
38
|
+
import { ref, computed, onMounted, defineProps, defineEmits, toRefs, getCurrentInstance } from 'vue'
|
|
39
|
+
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
editor: {
|
|
42
|
+
type: Object,
|
|
43
|
+
required: true,
|
|
44
|
+
},
|
|
45
|
+
resetButton: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
options: {
|
|
50
|
+
type: Object,
|
|
51
|
+
default: () => ({})
|
|
52
|
+
},
|
|
53
|
+
i18n: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: ''
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
const { editor, resetButton, options, i18n } = toRefs(props)
|
|
59
|
+
|
|
60
|
+
const draftEnabled = computed(() => !!editor.value.discardDraft)
|
|
61
|
+
const model = computed(() => editor.value.model)
|
|
62
|
+
|
|
63
|
+
const changed = computed(() => editor.value.changed.value)
|
|
64
|
+
|
|
65
|
+
const draftChanged = computed(() => editor.value.draftChanged?.value)
|
|
66
|
+
const savingDraft = computed(() => editor.value.savingDraft?.value)
|
|
67
|
+
const sourceChanged = computed(() => editor.value.sourceChanged?.value)
|
|
68
|
+
const saving = computed(() => editor.value.saving?.value)
|
|
69
|
+
|
|
70
|
+
const appContext = getCurrentInstance().appContext
|
|
71
|
+
|
|
72
|
+
import { validateData } from "@live-change/vue3-components"
|
|
73
|
+
const validationResult = computed(() => {
|
|
74
|
+
const currentValue = editor.value.value.value
|
|
75
|
+
const validationResult = validateData(model.value, currentValue, 'validation', appContext,
|
|
76
|
+
props.propName, props.rootValue, true)
|
|
77
|
+
const softValidationResult = validateData(model.value, currentValue, 'softValidation', appContext,
|
|
78
|
+
props.propName, props.rootValue, true)
|
|
79
|
+
return validationResult || softValidationResult
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style scoped>
|
|
85
|
+
|
|
86
|
+
</style>
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
v-model="editor.value.value"
|
|
7
7
|
:rootValue="editor.value.value"
|
|
8
8
|
:i18n="i18n" />
|
|
9
|
-
<
|
|
10
|
-
save buttons
|
|
11
|
-
</div>
|
|
9
|
+
<EditorButtons :editor="editor" reset-button />
|
|
12
10
|
</div>
|
|
13
11
|
</div>
|
|
14
12
|
</template>
|
|
@@ -16,6 +14,7 @@
|
|
|
16
14
|
<script setup>
|
|
17
15
|
|
|
18
16
|
import AutoEditor from '../form/AutoEditor.vue'
|
|
17
|
+
import EditorButtons from './EditorButtons.vue'
|
|
19
18
|
|
|
20
19
|
import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
|
|
21
20
|
|
|
@@ -61,23 +60,33 @@
|
|
|
61
60
|
import { computedAsync } from "@vueuse/core"
|
|
62
61
|
|
|
63
62
|
const editor = computedAsync(async () => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
try {
|
|
64
|
+
const ed = await editorData({
|
|
65
|
+
service: service.value,
|
|
66
|
+
model: model.value,
|
|
67
|
+
identifiers: identifiers.value,
|
|
68
|
+
draft: draft.value,
|
|
69
|
+
autoSave: true,
|
|
70
|
+
...options.value,
|
|
71
|
+
onSaved: (...args) => emit('saved', ...args),
|
|
72
|
+
onDraftSaved: (...args) => emit('draftSaved', ...args),
|
|
73
|
+
onDraftDiscarded: (...args) => emit('draftDiscarded', ...args),
|
|
74
|
+
onSaveError: (...args) => emit('saveError', ...args),
|
|
75
|
+
onCreated: (...args) => emit('created', ...args),
|
|
76
|
+
})
|
|
77
|
+
//console.log("ED", ed)
|
|
78
|
+
return ed
|
|
79
|
+
} catch(e) {
|
|
80
|
+
console.error("EDITOR ERROR", e)
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
79
83
|
})
|
|
80
84
|
|
|
85
|
+
const isNew = computed(() => editor.value.saved.value)
|
|
86
|
+
|
|
87
|
+
/// TODO: detect when we should change identifiers
|
|
88
|
+
// const savedIdentifiers
|
|
89
|
+
|
|
81
90
|
</script>
|
|
82
91
|
|
|
83
92
|
<style scoped>
|
|
@@ -24,10 +24,12 @@ export default function editorData(options) {
|
|
|
24
24
|
discardedDraftToast = "Draft discarded",
|
|
25
25
|
saveDraftErrorToast = "Error saving draft",
|
|
26
26
|
saveErrorToast = "Error saving",
|
|
27
|
+
resetToast = "Reset done",
|
|
27
28
|
|
|
28
29
|
onSaved = () => {},
|
|
29
30
|
onDraftSaved = () => {},
|
|
30
31
|
onDraftDiscarded = () => {},
|
|
32
|
+
onReset = () => {},
|
|
31
33
|
onSaveError = () => {},
|
|
32
34
|
onCreated = (createResult) => {},
|
|
33
35
|
|
|
@@ -37,6 +39,7 @@ export default function editorData(options) {
|
|
|
37
39
|
workingZone = inject('workingZone')
|
|
38
40
|
|
|
39
41
|
} = options
|
|
42
|
+
|
|
40
43
|
if(!identifiers) throw new Error('identifiers must be defined')
|
|
41
44
|
if(!serviceName || !modelName) throw new Error('service and model must be defined')
|
|
42
45
|
|
|
@@ -45,10 +48,11 @@ export default function editorData(options) {
|
|
|
45
48
|
const {
|
|
46
49
|
crudMethods = model.crud,
|
|
47
50
|
identifiersNames = model.identifiers,
|
|
48
|
-
editableProperties = model.editableProperties
|
|
51
|
+
editableProperties = model.editableProperties ?? Object.keys(model.properties)
|
|
49
52
|
} = options
|
|
53
|
+
|
|
50
54
|
if(!crudMethods) throw new Error('crud methods must be defined in model or options')
|
|
51
|
-
if(!
|
|
55
|
+
if(!identifiersNames) throw new Error('identifiers names must be defined in model or options')
|
|
52
56
|
if(!editableProperties) throw new Error('editableProperties must be defined in model or options')
|
|
53
57
|
|
|
54
58
|
let idKey = null
|
|
@@ -65,14 +69,14 @@ export default function editorData(options) {
|
|
|
65
69
|
draftIdParts.push(identifier)
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
const isNew = (idKey ? (!identifiers[idKey]) : (!draftIdParts.some(key => !identifiers[key])))
|
|
73
|
+
const draftId = (idKey ? identifiers[idKey]
|
|
74
|
+
: draftIdParts.map(key => JSON.stringify(identifiers[key])).join('_')) ?? 'new'
|
|
71
75
|
const draftIdentifiers = {
|
|
72
76
|
actionType: serviceName, action: crudMethods.read, targetType: modelName, target: draftId
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
const savedDataPath = path[serviceName][crudMethods.read](identifiers)
|
|
79
|
+
const savedDataPath = isNew ? null : path[serviceName][crudMethods.read](identifiers)
|
|
76
80
|
const draftDataPath = (draft && path.draft.myDraft(draftIdentifiers)) || null
|
|
77
81
|
|
|
78
82
|
const updateAction = api.actions[serviceName][crudMethods.update]
|
|
@@ -84,6 +88,7 @@ export default function editorData(options) {
|
|
|
84
88
|
return Promise.all([
|
|
85
89
|
live(savedDataPath), live(draftDataPath)
|
|
86
90
|
]).then(([savedData, draftData]) => {
|
|
91
|
+
|
|
87
92
|
const editableSavedData = computed(() => savedData.value && Object.fromEntries(
|
|
88
93
|
editableProperties.map(prop => [prop, savedData.value[prop]])
|
|
89
94
|
.concat([[timeField, savedData.value[timeField]]])
|
|
@@ -148,7 +153,7 @@ export default function editorData(options) {
|
|
|
148
153
|
})
|
|
149
154
|
|
|
150
155
|
const changed = computed(() =>
|
|
151
|
-
JSON.stringify(editableSavedData.value) !== JSON.stringify(synchronizedData.value.value))
|
|
156
|
+
JSON.stringify(editableSavedData.value ?? {}) !== JSON.stringify(synchronizedData.value.value))
|
|
152
157
|
const sourceChanged = computed(() =>
|
|
153
158
|
JSON.stringify(draftData.value.from) !== JSON.stringify(editableSavedData.value))
|
|
154
159
|
|
|
@@ -168,17 +173,30 @@ export default function editorData(options) {
|
|
|
168
173
|
if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: discardedDraftToast, life: 1500 })
|
|
169
174
|
}
|
|
170
175
|
|
|
176
|
+
async function reset() {
|
|
177
|
+
const discardPromise = removeDraftAction(draftIdentifiers)
|
|
178
|
+
if(workingZone)
|
|
179
|
+
workingZone.addPromise('discardDraft:'+serviceName+':'+modelName, discardPromise)
|
|
180
|
+
await discardPromise
|
|
181
|
+
synchronizedData.value.value = editableSavedData.value || defaultData(model)
|
|
182
|
+
if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: resetToast, life: 1500 })
|
|
183
|
+
onReset()
|
|
184
|
+
}
|
|
185
|
+
|
|
171
186
|
return {
|
|
172
187
|
value: synchronizedData.value,
|
|
173
188
|
changed,
|
|
174
189
|
save,
|
|
175
190
|
saving,
|
|
191
|
+
reset,
|
|
176
192
|
discardDraft,
|
|
177
193
|
model,
|
|
178
194
|
resetOnError: false,
|
|
179
195
|
draftChanged: synchronizedData.changed,
|
|
180
196
|
saveDraft: synchronizedData.save,
|
|
181
197
|
savingDraft: synchronizedData.saving,
|
|
198
|
+
saved: savedData,
|
|
199
|
+
draft: draftData,
|
|
182
200
|
sourceChanged /// needed for draft discard on concurrent save
|
|
183
201
|
}
|
|
184
202
|
} else {
|
|
@@ -202,11 +220,19 @@ export default function editorData(options) {
|
|
|
202
220
|
}
|
|
203
221
|
})
|
|
204
222
|
|
|
223
|
+
async function reset() {
|
|
224
|
+
synchronizedData.value.value = editableSavedData.value || defaultData(model)
|
|
225
|
+
if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: resetToast, life: 1500 })
|
|
226
|
+
onReset()
|
|
227
|
+
}
|
|
228
|
+
|
|
205
229
|
return {
|
|
206
230
|
value: synchronizedData.value,
|
|
207
231
|
changed: synchronizedData.changed,
|
|
208
232
|
save: synchronizedData.save,
|
|
209
233
|
saving: synchronizedData.saving,
|
|
234
|
+
saved: savedData,
|
|
235
|
+
reset,
|
|
210
236
|
model,
|
|
211
237
|
}
|
|
212
238
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/frontend-auto-form",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "node server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"type": "module",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@fortawesome/fontawesome-free": "^6.5.2",
|
|
25
|
-
"@live-change/cli": "^0.9.
|
|
26
|
-
"@live-change/dao": "^0.9.
|
|
27
|
-
"@live-change/dao-vue3": "^0.9.
|
|
28
|
-
"@live-change/dao-websocket": "^0.9.
|
|
29
|
-
"@live-change/framework": "^0.9.
|
|
30
|
-
"@live-change/image-frontend": "^0.9.
|
|
31
|
-
"@live-change/image-service": "^0.9.
|
|
32
|
-
"@live-change/session-service": "^0.9.
|
|
33
|
-
"@live-change/vue3-components": "^0.9.
|
|
34
|
-
"@live-change/vue3-ssr": "^0.9.
|
|
25
|
+
"@live-change/cli": "^0.9.7",
|
|
26
|
+
"@live-change/dao": "^0.9.7",
|
|
27
|
+
"@live-change/dao-vue3": "^0.9.7",
|
|
28
|
+
"@live-change/dao-websocket": "^0.9.7",
|
|
29
|
+
"@live-change/framework": "^0.9.7",
|
|
30
|
+
"@live-change/image-frontend": "^0.9.7",
|
|
31
|
+
"@live-change/image-service": "^0.9.7",
|
|
32
|
+
"@live-change/session-service": "^0.9.7",
|
|
33
|
+
"@live-change/vue3-components": "^0.9.7",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.9.7",
|
|
35
35
|
"@vueuse/core": "^10.11.0",
|
|
36
36
|
"codeceptjs-assert": "^0.0.5",
|
|
37
37
|
"compression": "^1.7.4",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"vue3-scroll-border": "0.1.6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@live-change/codeceptjs-helper": "^0.9.
|
|
55
|
+
"@live-change/codeceptjs-helper": "^0.9.7",
|
|
56
56
|
"codeceptjs": "^3.6.5",
|
|
57
57
|
"generate-password": "1.7.1",
|
|
58
58
|
"playwright": "1.48.1",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
64
64
|
"license": "ISC",
|
|
65
65
|
"description": "",
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "f936468bf39ea3c5b07ce14666f4b3a4a4a9287d"
|
|
67
67
|
}
|