@live-change/frontend-auto-form 0.8.111 → 0.8.112
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.
- package/index.js +3 -0
- package/logic/editorData.js +196 -9
- package/package.json +13 -13
package/index.js
CHANGED
|
@@ -7,6 +7,9 @@ export { AutoInput, AutoField, AutoEditor }
|
|
|
7
7
|
import * as inputConfig from './config.js'
|
|
8
8
|
export { inputConfig }
|
|
9
9
|
|
|
10
|
+
import editorData from './logic/editorData.js'
|
|
11
|
+
export { editorData }
|
|
12
|
+
|
|
10
13
|
import en from "./locales/en.json"
|
|
11
14
|
const locales = { en }
|
|
12
15
|
export { locales }
|
package/logic/editorData.js
CHANGED
|
@@ -1,20 +1,207 @@
|
|
|
1
1
|
import { useToast } from 'primevue/usetoast'
|
|
2
2
|
import { usePath, live, useApi } from '@live-change/vue3-ssr'
|
|
3
|
+
import { ref, computed, inject } from 'vue'
|
|
4
|
+
import { synchronized, defaultData } from '@live-change/vue3-components'
|
|
3
5
|
|
|
4
|
-
function editorData(options
|
|
5
|
-
|
|
6
|
+
export default function editorData(options) {
|
|
7
|
+
if(!options) throw new Error('options must be provided')
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
identifiers,
|
|
6
11
|
service: serviceName,
|
|
7
12
|
model: modelName,
|
|
8
|
-
draft = false,
|
|
9
|
-
methodNames = {}
|
|
10
|
-
} = options
|
|
11
|
-
if(!service || !model) throw new Error('service and model must be defined')
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
draft = true,
|
|
15
|
+
autoSave = false,
|
|
16
|
+
|
|
17
|
+
updateDataProperty = undefined,
|
|
18
|
+
recursive = true,
|
|
19
|
+
debounce = 600,
|
|
20
|
+
timeField = 'lastUpdate',
|
|
21
|
+
|
|
22
|
+
savedToast = "Saved",
|
|
23
|
+
savedDraftToast = "Draft saved",
|
|
24
|
+
discardedDraftToast = "Draft discarded",
|
|
25
|
+
saveDraftErrorToast = "Error saving draft",
|
|
26
|
+
saveErrorToast = "Error saving",
|
|
27
|
+
|
|
28
|
+
onSaved = () => {},
|
|
29
|
+
onDraftSaved = () => {},
|
|
30
|
+
onDraftDiscarded = () => {},
|
|
31
|
+
onSaveError = () => {},
|
|
32
|
+
|
|
33
|
+
toast = useToast(),
|
|
34
|
+
path = usePath(),
|
|
35
|
+
api = useApi(),
|
|
36
|
+
workingZone = inject('workingZone')
|
|
37
|
+
|
|
38
|
+
} = options
|
|
39
|
+
if(!identifiers) throw new Error('identifiers must be defined')
|
|
40
|
+
if(!serviceName || !modelName) throw new Error('service and model must be defined')
|
|
16
41
|
|
|
17
42
|
const service = api.services[serviceName]
|
|
18
43
|
const model = service.models[modelName]
|
|
44
|
+
const {
|
|
45
|
+
crudMethods = model.crud,
|
|
46
|
+
identifiersNames = model.identifiers,
|
|
47
|
+
editableProperties = model.editableProperties
|
|
48
|
+
} = options
|
|
49
|
+
if(!crudMethods) throw new Error('crud methods must be defined in model or options')
|
|
50
|
+
if(!identifiers) throw new Error('identifiers names must be defined in model or options')
|
|
51
|
+
if(!editableProperties) throw new Error('editableProperties must be defined in model or options')
|
|
52
|
+
|
|
53
|
+
let idKey = null
|
|
54
|
+
let draftIdParts = []
|
|
55
|
+
for(const identifier of identifiersNames) {
|
|
56
|
+
if(typeof identifier === 'object') {
|
|
57
|
+
for(const [key, value] of Object.entries(identifier)) {
|
|
58
|
+
if(value === 'id') {
|
|
59
|
+
idKey = key
|
|
60
|
+
}
|
|
61
|
+
draftIdParts.push(value)
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
draftIdParts.push(identifier)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const draftId = idKey ? identifiers[idKey]
|
|
68
|
+
: identifiersNames.map(identifier => draftIdParts.map(key => JSON.stringify(identifier[key])).join('_'))
|
|
69
|
+
|
|
70
|
+
const draftIdentifiers = {
|
|
71
|
+
actionType: serviceName, action: crudMethods.read, targetType: modelName, target: draftId
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const savedDataPath = path[serviceName][crudMethods.read](identifiers)
|
|
75
|
+
const draftDataPath = (draft && path.draft.myDraft(draftIdentifiers)) || null
|
|
76
|
+
|
|
77
|
+
const updateAction = api.actions[serviceName][crudMethods.update]
|
|
78
|
+
const createOrUpdateAction = api.actions[serviceName][crudMethods.createOrUpdate]
|
|
79
|
+
const createAction = api.actions[serviceName][crudMethods.create]
|
|
80
|
+
const createOrUpdateDraftAction = draft && api.actions.draft.setOrUpdateMyDraft
|
|
81
|
+
const removeDraftAction = draft && api.actions.draft.resetMyDraft
|
|
82
|
+
|
|
83
|
+
return Promise.all([
|
|
84
|
+
live(savedDataPath), live(draftDataPath)
|
|
85
|
+
]).then(([savedData, draftData]) => {
|
|
86
|
+
const editableSavedData = computed(() => savedData.value && Object.fromEntries(
|
|
87
|
+
editableProperties.map(prop => [prop, savedData.value[prop]])
|
|
88
|
+
.concat([[timeField, savedData.value[timeField]]])
|
|
89
|
+
))
|
|
90
|
+
const editableDraftData = computed(() => draft && draftData.value && Object.fromEntries(
|
|
91
|
+
editableProperties.map(prop => [prop, draftData.value.data[prop]])
|
|
92
|
+
.concat([[timeField, draftData.value.data[timeField]]])
|
|
93
|
+
))
|
|
94
|
+
const source = computed(() => editableDraftData.value || editableSavedData.value || defaultData(model))
|
|
95
|
+
|
|
96
|
+
let savePromise = null
|
|
97
|
+
const saving = ref(false)
|
|
98
|
+
async function saveData(data){
|
|
99
|
+
if(savePromise) await savePromise // wait for previous save
|
|
100
|
+
saving.value = true
|
|
101
|
+
savePromise = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
if(savedData.value) {
|
|
104
|
+
return updateAction(data)
|
|
105
|
+
} else {
|
|
106
|
+
return createAction(data)
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
saving.value = false
|
|
110
|
+
savePromise = null
|
|
111
|
+
}
|
|
112
|
+
})()
|
|
113
|
+
if(!autoSave && workingZone) workingZone.addPromise(savePromise.catch(() => {}))
|
|
114
|
+
await savePromise
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if(draft) {
|
|
118
|
+
function saveDraft(data){
|
|
119
|
+
return createOrUpdateDraftAction({ ...data, from: editableSavedData.value })
|
|
120
|
+
}
|
|
121
|
+
const synchronizedData = synchronized({
|
|
122
|
+
source,
|
|
123
|
+
update: saveDraft,
|
|
124
|
+
updateDataProperty: 'data',
|
|
125
|
+
identifiers: draftIdentifiers,
|
|
126
|
+
recursive,
|
|
127
|
+
autoSave: true,
|
|
128
|
+
debounce,
|
|
129
|
+
timeField,
|
|
130
|
+
resetOnError: false,
|
|
131
|
+
onSave: () => {
|
|
132
|
+
onDraftSaved()
|
|
133
|
+
if(toast && savedDraftToast) toast.add({ severity: 'info', summary: savedDraftToast, life: 1500 })
|
|
134
|
+
},
|
|
135
|
+
onSaveError: (e) => {
|
|
136
|
+
console.error("DRAFT SAVE ERROR", e)
|
|
137
|
+
if(toast && saveDraftErrorToast)
|
|
138
|
+
toast.add({ severity: 'error', summary: saveDraftErrorToast, detail: e.message ?? e, life: 5000 })
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const changed = computed(() =>
|
|
143
|
+
JSON.stringify(editableSavedData.value) !== JSON.stringify(synchronizedData.value.value))
|
|
144
|
+
const sourceChanged = computed(() =>
|
|
145
|
+
JSON.stringify(draftData.value.from) !== JSON.stringify(editableSavedData.value))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async function save() {
|
|
149
|
+
await saveData()
|
|
150
|
+
if(draftData.value) await removeDraftAction(draftIdentifiers)
|
|
151
|
+
onSaved()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function discardDraft() {
|
|
155
|
+
const discardPromise = removeDraftAction(draftIdentifiers)
|
|
156
|
+
if(workingZone) workingZone.addPromise(discardPromise)
|
|
157
|
+
await discardPromise
|
|
158
|
+
onDraftDiscarded()
|
|
159
|
+
if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: discardedDraftToast, life: 1500 })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
value: synchronizedData.value,
|
|
164
|
+
changed,
|
|
165
|
+
save,
|
|
166
|
+
saving,
|
|
167
|
+
discardDraft,
|
|
168
|
+
model,
|
|
169
|
+
resetOnError: false,
|
|
170
|
+
draftChanged: synchronizedData.changed,
|
|
171
|
+
saveDraft: synchronizedData.save,
|
|
172
|
+
savingDraft: synchronizedData.saving,
|
|
173
|
+
sourceChanged /// needed for draft discard on concurrent save
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
const synchronizedData = synchronized({
|
|
177
|
+
source,
|
|
178
|
+
update: createOrUpdateAction ?? saveData,
|
|
179
|
+
updateDataProperty,
|
|
180
|
+
identifiers,
|
|
181
|
+
recursive,
|
|
182
|
+
autoSave,
|
|
183
|
+
debounce,
|
|
184
|
+
onSave: () => {
|
|
185
|
+
onSaved()
|
|
186
|
+
if(toast && savedDraftToast) toast.add({ severity: 'info', summary: savedDraftToast, life: 1500 })
|
|
187
|
+
},
|
|
188
|
+
onSaveError(e) {
|
|
189
|
+
console.error("SAVE ERROR", e)
|
|
190
|
+
if(toast && saveDraftErrorToast)
|
|
191
|
+
toast.add({ severity: 'error', summary: saveErrorToast, detail: e.message ?? e, life: 5000 })
|
|
192
|
+
onSaveError(e)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
value: synchronizedData.value,
|
|
198
|
+
changed: synchronizedData.changed,
|
|
199
|
+
save: synchronizedData.save,
|
|
200
|
+
saving: synchronizedData.saving,
|
|
201
|
+
model,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
})
|
|
19
206
|
|
|
20
207
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/frontend-auto-form",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.112",
|
|
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.8.
|
|
26
|
-
"@live-change/dao": "^0.8.
|
|
27
|
-
"@live-change/dao-vue3": "^0.8.
|
|
28
|
-
"@live-change/dao-websocket": "^0.8.
|
|
29
|
-
"@live-change/framework": "^0.8.
|
|
30
|
-
"@live-change/image-frontend": "^0.8.
|
|
31
|
-
"@live-change/image-service": "^0.8.
|
|
32
|
-
"@live-change/session-service": "^0.8.
|
|
33
|
-
"@live-change/vue3-components": "^0.8.
|
|
34
|
-
"@live-change/vue3-ssr": "^0.8.
|
|
25
|
+
"@live-change/cli": "^0.8.112",
|
|
26
|
+
"@live-change/dao": "^0.8.112",
|
|
27
|
+
"@live-change/dao-vue3": "^0.8.112",
|
|
28
|
+
"@live-change/dao-websocket": "^0.8.112",
|
|
29
|
+
"@live-change/framework": "^0.8.112",
|
|
30
|
+
"@live-change/image-frontend": "^0.8.112",
|
|
31
|
+
"@live-change/image-service": "^0.8.112",
|
|
32
|
+
"@live-change/session-service": "^0.8.112",
|
|
33
|
+
"@live-change/vue3-components": "^0.8.112",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.8.112",
|
|
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.8.
|
|
55
|
+
"@live-change/codeceptjs-helper": "^0.8.112",
|
|
56
56
|
"codeceptjs": "^3.6.5",
|
|
57
57
|
"generate-password": "1.7.1",
|
|
58
58
|
"playwright": "^1.41.2",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
64
64
|
"license": "ISC",
|
|
65
65
|
"description": "",
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "e0d0db33777f8f9fc9f5ebe1a977cf574b837be6"
|
|
67
67
|
}
|