@live-change/frontend-auto-form 0.9.6 → 0.9.8
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/front/src/components/crud/AutoObjectIdentification.vue +79 -0
- package/front/src/components/crud/EditorButtons.vue +53 -4
- package/front/src/components/crud/ModelEditor.vue +38 -7
- package/front/src/components/crud/ModelList.vue +147 -0
- package/front/src/logic/editorData.js +34 -13
- package/front/src/pages/Editor.vue +27 -15
- package/front/src/pages/List.vue +3 -13
- package/front/src/pages/Models.vue +15 -3
- package/front/src/router.js +7 -0
- package/package.json +13 -13
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template v-if="typeof identificationConfig === 'string'">
|
|
3
|
+
<span><i class="pi pi-box mr-1"></i>{{ objectData[identificationConfig] }}</span>
|
|
4
|
+
</template>
|
|
5
|
+
<template v-else>
|
|
6
|
+
<span>
|
|
7
|
+
<i class="pi pi-box mr-1"></i>
|
|
8
|
+
<strong>{{ objectType }}</strong>: {{ object ?? objectData.to ?? objectData.id }}
|
|
9
|
+
</span>
|
|
10
|
+
</template>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup>
|
|
14
|
+
|
|
15
|
+
import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
|
|
16
|
+
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
objectType: {
|
|
19
|
+
type: String,
|
|
20
|
+
required: true
|
|
21
|
+
},
|
|
22
|
+
object: {
|
|
23
|
+
type: String,
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
data: {
|
|
27
|
+
type: Object,
|
|
28
|
+
default: null
|
|
29
|
+
},
|
|
30
|
+
inline: {
|
|
31
|
+
type: Boolean,
|
|
32
|
+
default: false
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
const { objectType, object, data, inline } = toRefs(props)
|
|
36
|
+
|
|
37
|
+
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
38
|
+
const api = useApi()
|
|
39
|
+
const path = usePath()
|
|
40
|
+
|
|
41
|
+
const serviceAndModel = computed(() => {
|
|
42
|
+
const [service, model] = objectType.value.split('_')
|
|
43
|
+
return { service, model }
|
|
44
|
+
})
|
|
45
|
+
const service = computed(() => serviceAndModel.value.service)
|
|
46
|
+
const model = computed(() => serviceAndModel.value.model)
|
|
47
|
+
|
|
48
|
+
const modelDefinition = computed(() => {
|
|
49
|
+
return api.services?.[service.value]?.models?.[model.value]
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const identificationViewName = computed(() => {
|
|
53
|
+
return modelDefinition.value?.crud?.identification || modelDefinition.value?.crud?.read
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const identificationConfig = computed(() => {
|
|
57
|
+
return modelDefinition.value?.identification
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const objectDataPath = computed(() => {
|
|
61
|
+
if(data.value) return null
|
|
62
|
+
if(!identificationConfig.value) return null
|
|
63
|
+
const viewName = identificationViewName.value
|
|
64
|
+
if(!viewName) return null
|
|
65
|
+
const modelName = model.value
|
|
66
|
+
return path[service.value][viewName]({
|
|
67
|
+
[modelName[0].toLowerCase() + modelName.slice(1)]: object.value
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const loadedObjectData = await live(objectDataPath)
|
|
72
|
+
const objectData = computed(() => data.value || loadedObjectData.value)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<style scoped>
|
|
78
|
+
|
|
79
|
+
</style>
|
|
@@ -1,17 +1,46 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
2
|
+
<div class="flex flex-column-reverse md:flex-row justify-content-between align-items-center mt-3">
|
|
3
|
+
<div class="flex flex-column mt-2 md:mt-0">
|
|
4
|
+
<div v-if="savingDraft" class="text-500 mr-2 flex flex-row align-items-center">
|
|
5
|
+
<i class="pi pi-spin pi-spinner mr-2" style="font-size: 1.23rem"></i>
|
|
6
|
+
<span>Saving draft...</span>
|
|
7
|
+
</div>
|
|
8
|
+
<div v-else-if="draftChanged" class="text-sm text-500 mr-2">
|
|
9
|
+
Draft changed
|
|
10
|
+
</div>
|
|
11
|
+
<div v-else-if="validationResult" class="font-semibold p-error mr-2">
|
|
12
|
+
Before saving, please correct the errors above.
|
|
13
|
+
</div>
|
|
14
|
+
<div v-else-if="!changed" class="">
|
|
15
|
+
No changes to save.
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="flex flex-row">
|
|
19
|
+
<slot name="submit" v-if="!validationResult">
|
|
20
|
+
<div class="ml-2">
|
|
21
|
+
<Button v-if="exists" type="submit" :label="'Save '+model.name" :disabled="!changed" icon="pi pi-save" />
|
|
22
|
+
<Button v-else type="submit" :label="'Create '+model.name" :disabled="!changed" icon="pi pi-sparkles" />
|
|
23
|
+
</div>
|
|
24
|
+
</slot>
|
|
25
|
+
<slot name="reset" v-if="resetButton">
|
|
26
|
+
<div>
|
|
27
|
+
<Button type="reset" label="Reset" class="ml-2" :disabled="!changed" icon="pi pi-eraser"/>
|
|
28
|
+
</div>
|
|
29
|
+
</slot>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
3
32
|
</template>
|
|
4
33
|
|
|
5
34
|
<script setup>
|
|
6
35
|
|
|
7
|
-
import { ref, computed, onMounted, defineProps, defineEmits, toRefs } from 'vue'
|
|
36
|
+
import { ref, computed, onMounted, defineProps, defineEmits, toRefs, getCurrentInstance } from 'vue'
|
|
8
37
|
|
|
9
38
|
const props = defineProps({
|
|
10
39
|
editor: {
|
|
11
40
|
type: Object,
|
|
12
41
|
required: true,
|
|
13
42
|
},
|
|
14
|
-
|
|
43
|
+
resetButton: {
|
|
15
44
|
type: Boolean,
|
|
16
45
|
required: true,
|
|
17
46
|
},
|
|
@@ -24,9 +53,29 @@
|
|
|
24
53
|
default: ''
|
|
25
54
|
}
|
|
26
55
|
})
|
|
27
|
-
const { editor,
|
|
56
|
+
const { editor, resetButton, options, i18n } = toRefs(props)
|
|
57
|
+
|
|
58
|
+
const draftEnabled = computed(() => !!editor.value.discardDraft)
|
|
59
|
+
const model = computed(() => editor.value.model)
|
|
28
60
|
|
|
61
|
+
const changed = computed(() => editor.value.changed.value)
|
|
62
|
+
const exists = computed(() => !!editor.value.saved.value)
|
|
63
|
+
const draftChanged = computed(() => editor.value.draftChanged?.value)
|
|
64
|
+
const savingDraft = computed(() => editor.value.savingDraft?.value)
|
|
65
|
+
const sourceChanged = computed(() => editor.value.sourceChanged?.value)
|
|
66
|
+
const saving = computed(() => editor.value.saving?.value)
|
|
29
67
|
|
|
68
|
+
const appContext = getCurrentInstance().appContext
|
|
69
|
+
|
|
70
|
+
import { validateData } from "@live-change/vue3-components"
|
|
71
|
+
const validationResult = computed(() => {
|
|
72
|
+
const currentValue = editor.value.value.value
|
|
73
|
+
const validationResult = validateData(model.value, currentValue, 'validation', appContext,
|
|
74
|
+
props.propName, props.rootValue, true)
|
|
75
|
+
const softValidationResult = validateData(model.value, currentValue, 'softValidation', appContext,
|
|
76
|
+
props.propName, props.rootValue, true)
|
|
77
|
+
return validationResult || softValidationResult
|
|
78
|
+
})
|
|
30
79
|
|
|
31
80
|
</script>
|
|
32
81
|
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
|
|
3
|
+
<!--
|
|
4
|
+
<h4>identifiers as object</h4>
|
|
5
|
+
<pre>{{ identifiersObject }}</pre>
|
|
6
|
+
|
|
7
|
+
<h4>definition</h4>
|
|
8
|
+
<pre>{{ modelDefinition }}</pre>-->
|
|
9
|
+
|
|
10
|
+
<div class="">
|
|
11
|
+
Service <strong>{{ service }}</strong>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="text-2xl mb-4">
|
|
14
|
+
<span v-if="isNew">Create </span>
|
|
15
|
+
<span v-else>Edit </span>
|
|
16
|
+
<strong>{{ model }}</strong>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<form v-if="editor" @submit="handleSave" @reset="handleReset">
|
|
4
20
|
<auto-editor
|
|
5
21
|
:definition="modelDefinition"
|
|
6
22
|
v-model="editor.value.value"
|
|
7
23
|
:rootValue="editor.value.value"
|
|
8
24
|
:i18n="i18n" />
|
|
9
|
-
<EditorButtons :editor="editor"
|
|
10
|
-
</
|
|
25
|
+
<EditorButtons :editor="editor" reset-button />
|
|
26
|
+
</form>
|
|
11
27
|
</div>
|
|
12
28
|
</template>
|
|
13
29
|
|
|
@@ -68,7 +84,7 @@
|
|
|
68
84
|
draft: draft.value,
|
|
69
85
|
autoSave: true,
|
|
70
86
|
...options.value,
|
|
71
|
-
onSaved: (...args) =>
|
|
87
|
+
onSaved: (...args) => handleSaved(...args),
|
|
72
88
|
onDraftSaved: (...args) => emit('draftSaved', ...args),
|
|
73
89
|
onDraftDiscarded: (...args) => emit('draftDiscarded', ...args),
|
|
74
90
|
onSaveError: (...args) => emit('saveError', ...args),
|
|
@@ -82,10 +98,25 @@
|
|
|
82
98
|
}
|
|
83
99
|
})
|
|
84
100
|
|
|
85
|
-
const isNew = computed(() => editor
|
|
101
|
+
const isNew = computed(() => !editor?.value?.saved?.value)
|
|
102
|
+
|
|
103
|
+
function handleSave(ev) {
|
|
104
|
+
ev.preventDefault()
|
|
105
|
+
editor.value.save()
|
|
106
|
+
}
|
|
86
107
|
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
function handleReset(ev) {
|
|
109
|
+
ev.preventDefault()
|
|
110
|
+
editor.value.reset()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleSaved(saveResult) {
|
|
114
|
+
console.log("SAVED", saveResult, isNew.value, editor.value.isNew)
|
|
115
|
+
if(saveResult && isNew.value && editor.value.isNew) {
|
|
116
|
+
emit('created', saveResult)
|
|
117
|
+
}
|
|
118
|
+
emit('saved', saveResult)
|
|
119
|
+
}
|
|
89
120
|
|
|
90
121
|
</script>
|
|
91
122
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full lg:w-8 md:w-11">
|
|
3
|
+
<!-- <h4>definition</h4>
|
|
4
|
+
<pre>{{ modelDefinition }}</pre>-->
|
|
5
|
+
|
|
6
|
+
<div class="surface-card w-full p-3 shadow-1 border-round mb-2">
|
|
7
|
+
<div class="">
|
|
8
|
+
Service <strong>{{ service }}</strong>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="text-2xl">
|
|
11
|
+
<strong>{{ pluralize(model) }}</strong>
|
|
12
|
+
<span class="ml-1">list</span>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="surface-card p-3 shadow-1 border-round">
|
|
17
|
+
<range-viewer :key="JSON.stringify(modelsPathRangeConfig)"
|
|
18
|
+
:pathFunction="modelsPathRangeFunction"
|
|
19
|
+
:canLoadTop="false" canDropBottom
|
|
20
|
+
loadBottomSensorSize="4000px" dropBottomSensorSize="3000px">
|
|
21
|
+
<template #empty>
|
|
22
|
+
<div class="text-xl text-800 my-3 mx-3">
|
|
23
|
+
No {{ pluralize(model) }} found
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
<template #default="{ item: object }">
|
|
27
|
+
<div class="flex flex-row align-items-center justify-content-between">
|
|
28
|
+
<ObjectIdentification
|
|
29
|
+
:objectType="service + '_' + model"
|
|
30
|
+
:object="object.to ?? object.id"
|
|
31
|
+
:data="object"
|
|
32
|
+
/>
|
|
33
|
+
<div class="flex flex-row">
|
|
34
|
+
<router-link :to="editRoute(object)" class="no-underline">
|
|
35
|
+
<Button icon="pi pi-pencil" severity="primary" label="Edit" class="mr-2" />
|
|
36
|
+
</router-link>
|
|
37
|
+
|
|
38
|
+
<Button icon="pi pi-eraser" severity="primary" label="Delete" class="mr-2" />
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
</range-viewer>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="mt-3 flex flex-row justify-content-end mr-2">
|
|
46
|
+
<router-link :to="createRoute" class="no-underline2">
|
|
47
|
+
<Button icon="pi pi-plus" :label="'Create new '+model" />
|
|
48
|
+
</router-link>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup>
|
|
56
|
+
|
|
57
|
+
import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
|
|
58
|
+
import { RangeViewer, injectComponent } from "@live-change/vue3-components"
|
|
59
|
+
import pluralize from 'pluralize'
|
|
60
|
+
|
|
61
|
+
const props = defineProps({
|
|
62
|
+
service: {
|
|
63
|
+
type: String,
|
|
64
|
+
required: true,
|
|
65
|
+
},
|
|
66
|
+
model: {
|
|
67
|
+
type: String,
|
|
68
|
+
required: true,
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
const { service, model } = toRefs(props)
|
|
72
|
+
|
|
73
|
+
import AutoObjectIdentification from './AutoObjectIdentification.vue'
|
|
74
|
+
|
|
75
|
+
const ObjectIdentification = computed(() =>
|
|
76
|
+
injectComponent({
|
|
77
|
+
name: 'ObjectIdentification',
|
|
78
|
+
type: service.value + '_' + model.value,
|
|
79
|
+
service: service.value,
|
|
80
|
+
model: model.value
|
|
81
|
+
}, AutoObjectIdentification)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
import { useApi, usePath, live, reverseRange } from '@live-change/vue3-ssr'
|
|
85
|
+
const api = useApi()
|
|
86
|
+
const path = usePath()
|
|
87
|
+
|
|
88
|
+
const modelDefinition = computed(() => {
|
|
89
|
+
return api.services?.[service.value]?.models?.[model.value]
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const modelsPathRangeConfig = computed(() => {
|
|
93
|
+
return {
|
|
94
|
+
service: service.value,
|
|
95
|
+
model: model.value,
|
|
96
|
+
definition: modelDefinition.value,
|
|
97
|
+
reverse: true
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
const modelsPathRangeFunction = computed(() => {
|
|
101
|
+
const config = modelsPathRangeConfig.value
|
|
102
|
+
const rangeView = config.definition?.crud?.range
|
|
103
|
+
return (range) => path[config.service][rangeView]({
|
|
104
|
+
...(config.reverse ? reverseRange(range) : range),
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
function objectIdentifiers(object) {
|
|
109
|
+
const identifiers = {}
|
|
110
|
+
for(const identifierDefinition of modelDefinition.value.identifiers) {
|
|
111
|
+
if(typeof identifierDefinition === 'string') {
|
|
112
|
+
identifiers[identifierDefinition] = object[identifierDefinition]
|
|
113
|
+
} else {
|
|
114
|
+
if(identifierDefinition.field === 'id') {
|
|
115
|
+
identifiers[identifierDefinition.name] = object?.to ?? object.id
|
|
116
|
+
} else {
|
|
117
|
+
identifiers[identifierDefinition.name] = object[identifierDefinition.field]
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return identifiers
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function editRoute(object) {
|
|
125
|
+
return {
|
|
126
|
+
name: 'auto-form:editor',
|
|
127
|
+
params: {
|
|
128
|
+
serviceName: service.value,
|
|
129
|
+
modelName: model.value,
|
|
130
|
+
identifiers: Object.values(objectIdentifiers(object))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const createRoute = computed(() => ({
|
|
136
|
+
name: 'auto-form:editor',
|
|
137
|
+
params: {
|
|
138
|
+
serviceName: service.value,
|
|
139
|
+
modelName: model.name
|
|
140
|
+
}
|
|
141
|
+
}))
|
|
142
|
+
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<style scoped>
|
|
146
|
+
|
|
147
|
+
</style>
|
|
@@ -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
|
|
|
@@ -57,12 +59,8 @@ export default function editorData(options) {
|
|
|
57
59
|
let draftIdParts = []
|
|
58
60
|
for(const identifier of identifiersNames) {
|
|
59
61
|
if(typeof identifier === 'object') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
idKey = key
|
|
63
|
-
}
|
|
64
|
-
draftIdParts.push(value)
|
|
65
|
-
}
|
|
62
|
+
if(identifier.field === 'id') idKey = identifier.name
|
|
63
|
+
draftIdParts.push('id')
|
|
66
64
|
} else {
|
|
67
65
|
draftIdParts.push(identifier)
|
|
68
66
|
}
|
|
@@ -108,6 +106,9 @@ export default function editorData(options) {
|
|
|
108
106
|
saving.value = true
|
|
109
107
|
savePromise = (async () => {
|
|
110
108
|
try {
|
|
109
|
+
if(createOrUpdateAction) {
|
|
110
|
+
return createOrUpdateAction(requestData)
|
|
111
|
+
}
|
|
111
112
|
if(savedData.value) {
|
|
112
113
|
return updateAction(requestData)
|
|
113
114
|
} else {
|
|
@@ -122,7 +123,7 @@ export default function editorData(options) {
|
|
|
122
123
|
})()
|
|
123
124
|
if(!autoSave && workingZone)
|
|
124
125
|
workingZone.addPromise('save:'+serviceName+':'+modelName, savePromise.catch(() => {}))
|
|
125
|
-
await savePromise
|
|
126
|
+
return await savePromise
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
if(draft) {
|
|
@@ -138,6 +139,7 @@ export default function editorData(options) {
|
|
|
138
139
|
autoSave: true,
|
|
139
140
|
debounce,
|
|
140
141
|
timeField,
|
|
142
|
+
isNew,
|
|
141
143
|
resetOnError: false,
|
|
142
144
|
onSave: () => {
|
|
143
145
|
onDraftSaved()
|
|
@@ -151,14 +153,14 @@ export default function editorData(options) {
|
|
|
151
153
|
})
|
|
152
154
|
|
|
153
155
|
const changed = computed(() =>
|
|
154
|
-
JSON.stringify(editableSavedData.value) !== JSON.stringify(synchronizedData.value.value))
|
|
156
|
+
JSON.stringify(editableSavedData.value ?? {}) !== JSON.stringify(synchronizedData.value.value))
|
|
155
157
|
const sourceChanged = computed(() =>
|
|
156
158
|
JSON.stringify(draftData.value.from) !== JSON.stringify(editableSavedData.value))
|
|
157
159
|
|
|
158
160
|
async function save() {
|
|
159
|
-
await saveData(synchronizedData.value.value)
|
|
161
|
+
const saveResult = await saveData(synchronizedData.value.value)
|
|
160
162
|
if(draftData.value) await removeDraftAction(draftIdentifiers)
|
|
161
|
-
onSaved()
|
|
163
|
+
onSaved(saveResult)
|
|
162
164
|
if(toast && savedToast) toast.add({ severity: 'success', summary: savedToast, life: 1500 })
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -171,13 +173,25 @@ export default function editorData(options) {
|
|
|
171
173
|
if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: discardedDraftToast, life: 1500 })
|
|
172
174
|
}
|
|
173
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
|
+
|
|
174
186
|
return {
|
|
175
187
|
value: synchronizedData.value,
|
|
176
188
|
changed,
|
|
177
189
|
save,
|
|
178
190
|
saving,
|
|
191
|
+
reset,
|
|
179
192
|
discardDraft,
|
|
180
193
|
model,
|
|
194
|
+
isNew,
|
|
181
195
|
resetOnError: false,
|
|
182
196
|
draftChanged: synchronizedData.changed,
|
|
183
197
|
saveDraft: synchronizedData.save,
|
|
@@ -189,14 +203,14 @@ export default function editorData(options) {
|
|
|
189
203
|
} else {
|
|
190
204
|
const synchronizedData = synchronized({
|
|
191
205
|
source,
|
|
192
|
-
update:
|
|
206
|
+
update: saveData,
|
|
193
207
|
updateDataProperty,
|
|
194
208
|
identifiers,
|
|
195
209
|
recursive,
|
|
196
210
|
autoSave,
|
|
197
211
|
debounce,
|
|
198
|
-
onSave: () => {
|
|
199
|
-
onSaved()
|
|
212
|
+
onSave: (result) => {
|
|
213
|
+
onSaved(result)
|
|
200
214
|
if(toast && savedToast) toast.add({ severity: 'success', summary: savedToast, life: 1500 })
|
|
201
215
|
},
|
|
202
216
|
onSaveError(e) {
|
|
@@ -207,12 +221,19 @@ export default function editorData(options) {
|
|
|
207
221
|
}
|
|
208
222
|
})
|
|
209
223
|
|
|
224
|
+
async function reset() {
|
|
225
|
+
synchronizedData.value.value = editableSavedData.value || defaultData(model)
|
|
226
|
+
if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: resetToast, life: 1500 })
|
|
227
|
+
onReset()
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
return {
|
|
211
231
|
value: synchronizedData.value,
|
|
212
232
|
changed: synchronizedData.changed,
|
|
213
233
|
save: synchronizedData.save,
|
|
214
234
|
saving: synchronizedData.saving,
|
|
215
235
|
saved: savedData,
|
|
236
|
+
reset,
|
|
216
237
|
model,
|
|
217
238
|
}
|
|
218
239
|
|
|
@@ -2,17 +2,8 @@
|
|
|
2
2
|
<div class="w-full lg:w-8 md:w-11">
|
|
3
3
|
<div class="surface-card p-3 shadow-1 border-round">
|
|
4
4
|
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<h4>identifiers as object</h4>
|
|
10
|
-
<pre>{{ identifiersObject }}</pre>
|
|
11
|
-
|
|
12
|
-
<h4>definition</h4>
|
|
13
|
-
<pre>{{ modelDefinition }}</pre>
|
|
14
|
-
|
|
15
|
-
<ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft />
|
|
5
|
+
<ModelEditor :service="serviceName" :model="modelName" :identifiers="identifiersObject" draft
|
|
6
|
+
@created="handleCreated"/>
|
|
16
7
|
|
|
17
8
|
</div>
|
|
18
9
|
</div>
|
|
@@ -22,6 +13,8 @@
|
|
|
22
13
|
|
|
23
14
|
import ModelEditor from "../components/crud/ModelEditor.vue"
|
|
24
15
|
|
|
16
|
+
|
|
17
|
+
|
|
25
18
|
import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
|
|
26
19
|
|
|
27
20
|
const props = defineProps({
|
|
@@ -59,15 +52,34 @@
|
|
|
59
52
|
if(typeof identifierDefinition === 'string') {
|
|
60
53
|
result[identifierDefinition] = identifier
|
|
61
54
|
} else {
|
|
62
|
-
result[
|
|
55
|
+
result[identifierDefinition.name] = identifier
|
|
63
56
|
}
|
|
64
57
|
}
|
|
65
58
|
return result
|
|
66
59
|
})
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
import { useRouter } from 'vue-router'
|
|
62
|
+
const router = useRouter()
|
|
63
|
+
|
|
64
|
+
function handleCreated(id) {
|
|
65
|
+
const newIdentifiers = modelDefinition.value.identifiers.map((identifier, i) => {
|
|
66
|
+
if(typeof identifier === 'object' && identifier.field === 'id') {
|
|
67
|
+
return id
|
|
68
|
+
}
|
|
69
|
+
return identifiers.value[i]
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
//console.log("newIdentifiers", newIdentifiers)
|
|
73
|
+
if(JSON.stringify(identifiers.value) !== JSON.stringify(newIdentifiers)) {
|
|
74
|
+
router.push({
|
|
75
|
+
name: 'auto-form:editor',
|
|
76
|
+
params: {
|
|
77
|
+
serviceName: serviceName.value,
|
|
78
|
+
modelName: modelName.value,
|
|
79
|
+
identifiers: newIdentifiers
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
</script>
|
package/front/src/pages/List.vue
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<div class="surface-card p-3 shadow-1 border-round">
|
|
4
|
-
List
|
|
5
|
-
</div>
|
|
6
|
-
</div>
|
|
2
|
+
<ModelList :service="serviceName" :model="modelName" />
|
|
7
3
|
</template>
|
|
8
4
|
|
|
9
5
|
<script setup>
|
|
6
|
+
import ModelList from "../components/crud/ModelList.vue"
|
|
10
7
|
|
|
11
8
|
import { ref, computed, onMounted, defineProps, toRefs } from 'vue'
|
|
12
9
|
|
|
@@ -18,21 +15,14 @@
|
|
|
18
15
|
modelName: {
|
|
19
16
|
type: String,
|
|
20
17
|
required: true,
|
|
21
|
-
},
|
|
22
|
-
identifiers: {
|
|
23
|
-
type: Array,
|
|
24
|
-
default: []
|
|
25
18
|
}
|
|
26
19
|
})
|
|
27
|
-
const { serviceName } = toRefs(props)
|
|
20
|
+
const { serviceName, modelName } = toRefs(props)
|
|
28
21
|
|
|
29
22
|
import { useApi, usePath, live } from '@live-change/vue3-ssr'
|
|
30
23
|
const api = useApi()
|
|
31
24
|
const path = usePath()
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
26
|
</script>
|
|
37
27
|
|
|
38
28
|
<style scoped>
|
|
@@ -10,14 +10,16 @@
|
|
|
10
10
|
<div class="text-xl flex flex-row align-items-center mr-4">
|
|
11
11
|
<strong>{{ model.name }}</strong>
|
|
12
12
|
<span class="mx-1">model</span>
|
|
13
|
-
<div v-for="relation of model.relations"
|
|
13
|
+
<div v-for="relation of model.relations">
|
|
14
14
|
<span class="mr-1">-</span>
|
|
15
15
|
<span>{{ relation.name }}</span>
|
|
16
16
|
<!-- <span v-if="relation.config.what"></span>-->
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
19
|
-
<div>
|
|
20
|
-
<
|
|
19
|
+
<div class="mt-2 md:mt-0">
|
|
20
|
+
<router-link :to="listRoute(serviceWithModels.name, model)" class="no-underline">
|
|
21
|
+
<Button icon="pi pi-list" severity="primary" label="List" class="mr-2" />
|
|
22
|
+
</router-link>
|
|
21
23
|
<router-link :to="createRoute(serviceWithModels.name, model)" class="no-underline">
|
|
22
24
|
<Button icon="pi pi-plus" severity="warning" :label="'Create new '+model.name" />
|
|
23
25
|
</router-link>
|
|
@@ -79,6 +81,16 @@
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
function listRoute(serviceName, model) {
|
|
85
|
+
return {
|
|
86
|
+
name: 'auto-form:list',
|
|
87
|
+
params: {
|
|
88
|
+
serviceName,
|
|
89
|
+
modelName: model.name,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
</script>
|
|
83
95
|
|
|
84
96
|
<style scoped>
|
package/front/src/router.js
CHANGED
|
@@ -14,6 +14,13 @@ export function autoFormRoutes(config = {}) {
|
|
|
14
14
|
props: true
|
|
15
15
|
}),
|
|
16
16
|
|
|
17
|
+
route({
|
|
18
|
+
name: 'auto-form:list', path: prefix + '/models/:serviceName/:modelName', meta: { },
|
|
19
|
+
component: () => import("./pages/List.vue"),
|
|
20
|
+
props: true
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
|
|
17
24
|
]
|
|
18
25
|
}
|
|
19
26
|
|
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.8",
|
|
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.8",
|
|
26
|
+
"@live-change/dao": "^0.9.8",
|
|
27
|
+
"@live-change/dao-vue3": "^0.9.8",
|
|
28
|
+
"@live-change/dao-websocket": "^0.9.8",
|
|
29
|
+
"@live-change/framework": "^0.9.8",
|
|
30
|
+
"@live-change/image-frontend": "^0.9.8",
|
|
31
|
+
"@live-change/image-service": "^0.9.8",
|
|
32
|
+
"@live-change/session-service": "^0.9.8",
|
|
33
|
+
"@live-change/vue3-components": "^0.9.8",
|
|
34
|
+
"@live-change/vue3-ssr": "^0.9.8",
|
|
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.8",
|
|
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": "bb09550504f28df5f4fdd73273de45dc91a857b5"
|
|
67
67
|
}
|