@live-change/frontend-auto-form 0.9.162 → 0.9.164

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.
@@ -154,7 +154,7 @@
154
154
 
155
155
  const returnType = computed(() => {
156
156
  const returnType = actionFormData.action.definition.returns
157
- return returnType.type
157
+ return returnType?.type
158
158
  })
159
159
 
160
160
  function handleSubmit(ev) {
@@ -178,7 +178,7 @@
178
178
  resultWithType.value = null
179
179
  if(type === 'task_Task') {
180
180
  task.value = result
181
- } else if(type.split('_').length === 1) {
181
+ } else if(type && type.split('_').length === 1) {
182
182
  if(typeof result === 'object') {
183
183
  resultWithType.value = {
184
184
  type: type,
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <prism-editor v-if="isMounted"
3
+ class="code-editor p-component p-textarea" :highlight="highlight"
4
+ :style="{ height: (codeLines * 1.35 + 1.23) + 'em' }"
5
+ v-model="code"
6
+ :readonly="readOnly" :line-numbers="codeLines > 1" />
7
+ <Message v-if="editResult.error" severity="error" variant="simple" size="small">
8
+ {{ editResult.error }}
9
+ </Message>
10
+ <Textarea class="hidden" />
11
+ </template>
12
+
13
+ <script setup>
14
+
15
+ import Textarea from 'primevue/textarea'
16
+ import { stringify } from "javascript-stringify"
17
+
18
+ import 'vue-prism-editor/dist/prismeditor.min.css'
19
+ import 'prismjs/themes/prism-coy.css'
20
+ import * as Prism from 'prismjs/components/prism-core'
21
+ import 'prismjs/components/prism-clike'
22
+ import 'prismjs/components/prism-javascript'
23
+
24
+ import Message from "primevue/message"
25
+
26
+ import { PrismEditor } from 'vue-prism-editor'
27
+
28
+ import { ref, computed, watch, onMounted } from 'vue'
29
+
30
+ const isMounted = ref(false)
31
+ onMounted(() => isMounted.value = true)
32
+
33
+ const props = defineProps({
34
+ initialCode: {
35
+ type: String
36
+ },
37
+ initialData: {
38
+ },
39
+ readOnly: {
40
+ type: Boolean,
41
+ default: false
42
+ },
43
+ env: {
44
+ type: Object
45
+ },
46
+ dbSugar: {
47
+ type: Object
48
+ }
49
+ })
50
+
51
+ const { readOnly, env, dbSugar } = props
52
+
53
+ const emit = defineEmits(['result'])
54
+
55
+
56
+ const code = ref(props.initialCode !== undefined ? props.initialCode : stringify(props.initialData, null, " "))
57
+
58
+ watch(() => props.initialCode, () => {
59
+ if(props.initialCode == code.value) return
60
+ code.value = props.initialCode !== undefined ? props.initialCode : stringify(props.initialData, null, " ")
61
+ })
62
+
63
+ function highlight(code) {
64
+ return Prism.highlight(code, Prism.languages.js, "js")
65
+ }
66
+
67
+ const codeLines = computed(() => code.value.split('\n').length)
68
+
69
+ const modified = computed(() => code.value != props.initialCode)
70
+
71
+ const editResult = computed(() => {
72
+ if(!modified.value && props.initialData) return { data: props.initialData, code: code.value }
73
+ try {
74
+ const $ = env || {}
75
+ const db = dbSugar || {}
76
+ //console.log("COMPILE CODE", code.value)
77
+ const result = eval(`(${code.value})`)
78
+ if(result === false) return { data: false, code: code.value }
79
+ if(result) return { data: result, code: code.value }
80
+ return { error: 'empty' }
81
+ } catch(e) {
82
+ if(e instanceof SyntaxError) {
83
+ if(e.lineNumber) return {
84
+ error: `${e.message} at ${e.lineNumber}:${e.columnNumber - (e.lineNumber ? 0 : -1)}`
85
+ }
86
+ }
87
+ return { error: e.message }
88
+ }
89
+ })
90
+
91
+ function reset() {
92
+ code.value = props.initialCode !== undefined ? props.initialCode : stringify(props.initialData, null, " ")
93
+ }
94
+
95
+ watch(() => editResult.value, () => emit('result', editResult.value))
96
+
97
+ defineExpose({ reset })
98
+
99
+ </script>
100
+
101
+ <style lang="scss">
102
+ .code-editor {
103
+ font-family: monospace;
104
+ outline: none;
105
+ .prism-editor__container {
106
+ textarea {
107
+ outline: none;
108
+ }
109
+ }
110
+ }
111
+ </style>
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <div>
3
+
4
+ <CodeEditor :readOnly="false" :initialData="initialData" @result="result => handleEditResult(result)"
5
+ :ref="el => editorElementFound(el)" />
6
+
7
+ </div>
8
+ </template>
9
+
10
+ <script setup>
11
+
12
+ import CodeEditor from './CodeEditor.vue'
13
+ import Textarea from 'primevue/textarea'
14
+
15
+ import { defineProps, defineEmits, toRefs, ref, defineModel, computed, watch } from 'vue'
16
+ import { useElementSize } from '@vueuse/core'
17
+
18
+ const props = defineProps({
19
+ definition: {
20
+ type: Object
21
+ },
22
+ properties: {
23
+ type: Object,
24
+ default: () => ({})
25
+ },
26
+ rootValue: {
27
+ type: Object,
28
+ default: () => ({})
29
+ },
30
+ propName: {
31
+ type: String,
32
+ default: ''
33
+ },
34
+ i18n: {
35
+ type: String,
36
+ default: ''
37
+ }
38
+ })
39
+
40
+ const { definition, properties, rootValue, propName, i18n: i18nPrefix } = toRefs(props)
41
+
42
+ const model = defineModel({
43
+ required: true
44
+ })
45
+
46
+
47
+ const initialData = ref(JSON.parse(JSON.stringify(model.value ?? null)))
48
+ const edited = ref(false)
49
+ let editTimeout = null
50
+
51
+ function handleEditResult(result) {
52
+ edited.value = true
53
+ model.value = result.data
54
+ if(editTimeout) clearTimeout(editTimeout)
55
+ editTimeout = setTimeout(() => {
56
+ edited.value = false
57
+ editTimeout = null
58
+ }, 1000)
59
+ }
60
+
61
+ watch(() => model.value, currentData => {
62
+ const refresh = !edited.value
63
+ initialData.value = JSON.parse(JSON.stringify(currentData))
64
+ if(refresh) {
65
+ //editedData.value = JSON.parse(currentData)
66
+ setTimeout(() => {
67
+ codeEditor.value.reset()
68
+ })
69
+ }
70
+ })
71
+
72
+ const codeEditor = ref()
73
+ function editorElementFound(element) {
74
+ codeEditor.value = element
75
+ }
76
+
77
+ </script>
78
+
79
+ <style>
80
+
81
+ </style>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <div>
3
+ <div ref="selectElement" class="p-select p-component p-inputwrapper w-full" @click="toggleObjectPicker">
4
+ <span v-if="!(model?.name && model?.service)" class="p-select-label p-placeholder" tabindex="0" role="combobox">
5
+ <span>
6
+ Select trigger
7
+ </span>
8
+ </span>
9
+ <span v-else class="p-select-label">
10
+ <span>
11
+ {{ model.service }} - {{ model.name }}
12
+ </span>
13
+ </span>
14
+ <div class="p-select-dropdown" data-pc-section="dropdown">
15
+ <ChevronDownIcon />
16
+ </div>
17
+ </div>
18
+
19
+ <!-- <pre>objt = {{ objectType }}</pre> -->
20
+
21
+ <Popover ref="triggerPickerPopover" :pt="{
22
+ root: {
23
+ class: 'trigger-picker-popover overflow-y-auto',
24
+ style: {
25
+ minWidth: `${width}px`,
26
+ maxHeight: `calc(50vh - 30px)`
27
+ }
28
+ }
29
+ }">
30
+ <TriggerPicker v-model="model" :definition="definition" :properties="properties"
31
+ :rootValue="rootValue" :propName="propName" :i18n="i18n" @selected="handleSelected" />
32
+ </Popover>
33
+
34
+ <!-- needed to autoload styles: -->
35
+ <Select class="hidden" :options="[1,2,3]" />
36
+
37
+ <AutoField v-if="triggerDefinition" v-model="triggerProperties"
38
+ :definition="{ ...triggerDefinition, type: 'Object' }"
39
+ :rootValue="rootValue" :propName="propName+'.properties'"
40
+ :i18n="i18n" label="trigger.properties" />
41
+
42
+ <div class="flex items-center gap-2 mt-2">
43
+ <ToggleSwitch v-model="returnTask" id="returnTask" />
44
+ <label for="returnTask">Trigger returns task to wait for</label>
45
+ </div>
46
+
47
+ </div>
48
+ </template>
49
+
50
+ <script setup>
51
+ import ChevronDownIcon from '@primevue/icons/chevrondown'
52
+ import Select from 'primevue/select'
53
+ import Popover from 'primevue/popover'
54
+ import ToggleSwitch from 'primevue/toggleswitch'
55
+ import TriggerPicker from './TriggerPicker.vue'
56
+
57
+ import AutoField from './AutoField.vue'
58
+
59
+ import { defineProps, defineEmits, toRefs, ref, defineModel, computed } from 'vue'
60
+ import { useElementSize } from '@vueuse/core'
61
+
62
+ const props = defineProps({
63
+ definition: {
64
+ type: Object
65
+ },
66
+ properties: {
67
+ type: Object,
68
+ default: () => ({})
69
+ },
70
+ rootValue: {
71
+ type: Object,
72
+ default: () => ({})
73
+ },
74
+ propName: {
75
+ type: String,
76
+ default: ''
77
+ },
78
+ i18n: {
79
+ type: String,
80
+ default: ''
81
+ }
82
+ })
83
+
84
+ const { definition, properties, rootValue, propName, i18n: i18nPrefix } = toRefs(props)
85
+
86
+ const model = defineModel({
87
+ required: true
88
+ })
89
+
90
+ import { useApi } from '@live-change/vue3-ssr'
91
+ const api = useApi()
92
+
93
+ const triggerPickerPopover = ref()
94
+ const selectElement = ref()
95
+ const { width } = useElementSize(selectElement)
96
+
97
+ const toggleObjectPicker = (event) => {
98
+ triggerPickerPopover.value.toggle(event)
99
+ }
100
+
101
+ const handleSelected = (object) => {
102
+ triggerPickerPopover.value.hide()
103
+ }
104
+
105
+ const triggerDefinition = computed(() => {
106
+ if(!model.value?.service || !model.value?.name) return null
107
+ const service = api.metadata.api.value.services.find(s => s.name === model.value.service)
108
+ if(!service) return null
109
+ return service.triggers[model.value.name][0]
110
+ })
111
+
112
+ const triggerProperties = computed({
113
+ get() {
114
+ return model.value?.properties
115
+ },
116
+ set(value) {
117
+ model.value = { ...(model.value ?? {}), properties: value }
118
+ }
119
+ })
120
+
121
+ const returnTask = computed({
122
+ get() {
123
+ return model.value?.returnTask
124
+ },
125
+ set(value) {
126
+ model.value = { ...(model.value ?? {}), returnTask: value }
127
+ }
128
+ })
129
+
130
+ </script>
131
+
132
+ <style>
133
+ .trigger-picker-popover {
134
+ margin-top: 1px;
135
+ }
136
+ .trigger-picker-popover:before {
137
+ display: none;
138
+ }
139
+ .trigger-picker-popover:after {
140
+ display: none;
141
+ }
142
+ </style>
@@ -0,0 +1,145 @@
1
+ <template>
2
+ <div>
3
+ <div>
4
+ <div v-if="!(model?.service)" class="p-1 py-2 sticky top-0 z-10">
5
+ <div class="flex flex-col gap-2">
6
+ <!-- <label :for="`type-select-${uid}`">{{ t('objectPicker.selectObjectType') }}</label> -->
7
+ <!-- <Select v-model="selectedService" :options="serviceOptions" :id="`service-select-${uid}`"
8
+ :placeholder="t('triggerPicker.selectService')" /> -->
9
+ <div class="text-sm text-surface-600 dark:text-surface-400 mb-2">{{ t('triggerPicker.selectService') }}</div>
10
+ <div class="flex flex-col gap-2">
11
+ <div v-for="service in serviceOptions" :key="service"
12
+ class="p-2 hover:bg-surface-100 dark:hover:bg-surface-800 cursor-pointer"
13
+ @click="selectedService = service">
14
+ {{ service }}
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </div>
19
+
20
+ <div v-if="model?.service">
21
+ <div class="text-sm text-surface-600 dark:text-surface-400 mb-2">
22
+ {{ t('triggerPicker.selectedService') }} {{ model.service }}
23
+ <Button class="ml-2" icon="pi pi-times" size="small" outlined rounded @click="selectedService = null" />
24
+ </div>
25
+ <div class="text-sm text-surface-600 dark:text-surface-400 mb-2">{{ t('triggerPicker.selectTrigger') }}</div>
26
+ <div class="flex flex-col gap-2">
27
+ <div v-for="trigger in triggerOptions" :key="trigger"
28
+ class="p-2 hover:bg-surface-100 dark:hover:bg-surface-800 cursor-pointer"
29
+ @click="selectedTrigger = trigger">
30
+ {{ trigger }}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+
37
+ <!-- <pre>isTypeNeeded = {{ isTypeNeeded }}</pre>
38
+ <pre>typePropertyName = {{ typePropertyName }}</pre>
39
+ <pre>typePropertyPath = {{ typePropertyPath }}</pre>
40
+ <pre>typeModel = {{ typeModel }}</pre> -->
41
+ <!-- <div>
42
+ <pre>objectsPathRangeConfig = {{ objectsPathRangeConfig }}</pre>
43
+ <pre>objectsPathRangeFunction = {{ objectsPathRangeFunction }}</pre>
44
+ <pre>selectedType = {{ selectedType }}</pre>
45
+ <pre>definition = {{ definition }}</pre>
46
+ <pre>properties = {{ properties }}</pre>
47
+ <pre>typeOptions = {{ typeOptions }}</pre>
48
+ <pre>model = {{ model }}</pre>
49
+ </div> -->
50
+ </div>
51
+ </template>
52
+
53
+ <script setup>
54
+ import Select from 'primevue/select'
55
+ import Button from 'primevue/button'
56
+ import AutoObjectIdentification from '../crud/DefaultObjectIdentification.vue'
57
+ import { RangeViewer, injectComponent } from "@live-change/vue3-components"
58
+
59
+ import { defineProps, defineEmits, toRefs, ref, defineModel, computed, useId, inject, unref } from 'vue'
60
+ import pluralize from 'pluralize'
61
+
62
+ const uid = useId()
63
+
64
+ const props = defineProps({
65
+ definition: {
66
+ type: Object
67
+ },
68
+ properties: {
69
+ type: Object,
70
+ default: () => ({})
71
+ },
72
+ rootValue: {
73
+ type: Object,
74
+ default: () => ({})
75
+ },
76
+ propName: {
77
+ type: String,
78
+ default: ''
79
+ },
80
+ i18n: {
81
+ type: String,
82
+ default: ''
83
+ },
84
+ showServiceName: {
85
+ type: Boolean,
86
+ default: false
87
+ },
88
+ view: {
89
+ type: String,
90
+ default: 'range'
91
+ }
92
+ })
93
+
94
+ const { definition, properties, rootValue, propName, i18n, view } = toRefs(props)
95
+
96
+ const model = defineModel({
97
+ required: true
98
+ })
99
+
100
+ const emit = defineEmits(['selected'])
101
+
102
+ import { useI18n } from 'vue-i18n'
103
+ const { t: tI18n, te } = useI18n()
104
+ const t = (key, ...params) => tI18n(
105
+ te(i18n.value + propName.value + key)
106
+ ? i18n.value + propName.value + key
107
+ : key, ...params
108
+ )
109
+
110
+ import { useApi } from '@live-change/vue3-ssr'
111
+ const api = useApi()
112
+ const serviceOptions = computed(() => {
113
+ return api.metadata.api.value.services.map(s => s.name)
114
+ })
115
+
116
+ const selectedService = computed({
117
+ get() {
118
+ return model.value?.service
119
+ },
120
+ set(value) {
121
+ model.value = { ...(model.value ?? {}), service: value }
122
+ }
123
+ })
124
+
125
+ const triggerOptions = computed(() => {
126
+ if(!selectedService.value) return []
127
+ const service = api.metadata.api.value.services.find(s => s.name === selectedService.value)
128
+ if(!service) return []
129
+ return Object.keys(service.triggers)
130
+ })
131
+
132
+ const selectedTrigger = computed({
133
+ get() {
134
+ return model.value?.name
135
+ },
136
+ set(value) {
137
+ model.value = { ...(model.value ?? {}), name: value }
138
+ emit('selected', model.value)
139
+ }
140
+ })
141
+
142
+ </script>
143
+
144
+ <style scoped>
145
+ </style>
@@ -73,6 +73,10 @@ types.Boolean = inputs.switch = {
73
73
 
74
74
  types.any = inputs.object = inputConfig(() => import('./ObjectInput.vue'))
75
75
 
76
+ inputs.json = inputConfig(() => import('./JsonInput.vue'))
77
+
78
+ inputs.trigger = inputConfig(() => import('./TriggerInput.vue'))
79
+
76
80
 
77
81
  export function provideAutoInputConfiguration() {
78
82
  for(let type in types) {
@@ -6,6 +6,7 @@ import { synchronized, defaultData } from '@live-change/vue3-components'
6
6
  import { propertiesValidationErrors } from './validation.js'
7
7
 
8
8
  import { cyrb128 } from './utils.js'
9
+ import deepmerge from 'deepmerge'
9
10
 
10
11
  export default async function actionData(options) {
11
12
  if(!options) throw new Error('options must be provided')
@@ -81,7 +82,16 @@ export default async function actionData(options) {
81
82
 
82
83
  const propertiesServerErrors = ref({})
83
84
  const lastSentData = ref(null)
84
- const formData = ref(JSON.parse(JSON.stringify(initialValue)))
85
+
86
+ console.log("INITIAL VALUE", initialValue)
87
+ console.log("DEFAULT DATA", defaultData(action.definition))
88
+
89
+ const mergedInitialValue = deepmerge(
90
+ defaultData(action.definition),
91
+ JSON.parse(JSON.stringify(initialValue))
92
+ )
93
+
94
+ const formData = ref(mergedInitialValue)
85
95
  const done = ref(false)
86
96
 
87
97
  let commandPromise = null
@@ -121,7 +131,7 @@ export default async function actionData(options) {
121
131
  function saveDraft(data){
122
132
  return createOrUpdateDraftAction({ ...data, from: formData.value })
123
133
  }
124
- const source = computed(() => draftData.value?.data || initialValue)
134
+ const source = computed(() => draftData.value?.data || mergedInitialValue)
125
135
  const synchronizedData = synchronized({
126
136
  source,
127
137
  update: saveDraft,
@@ -143,7 +153,7 @@ export default async function actionData(options) {
143
153
  }
144
154
  })
145
155
  const changed = computed(() =>
146
- JSON.stringify(initialValue ?? {})
156
+ JSON.stringify(mergedInitialValue ?? {})
147
157
  !== JSON.stringify({ ...synchronizedData.value.value, [timeField]: undefined })
148
158
  )
149
159
 
@@ -172,7 +182,7 @@ export default async function actionData(options) {
172
182
  if(workingZone)
173
183
  workingZone.addPromise('discardDraft:'+serviceName+':'+actionName, discardPromise)
174
184
  await discardPromise
175
- synchronizedData.value.value = editableSavedData.value || defaultData(model)
185
+ synchronizedData.value.value = editableSavedData.value || JSON.parse(JSON.stringify(mergedInitialValue))
176
186
  if(toast && discardedDraftToast) toast.add({ severity: 'info', summary: resetToast, life: 1500 })
177
187
  done.value = false
178
188
  onReset()
@@ -181,6 +191,7 @@ export default async function actionData(options) {
181
191
  return {
182
192
  parameters,
183
193
  initialValue,
194
+ mergedInitialValue,
184
195
  editableProperties,
185
196
  value: synchronizedData.value,
186
197
  changed,
@@ -207,12 +218,12 @@ export default async function actionData(options) {
207
218
  }
208
219
 
209
220
  const changed = computed(() =>
210
- JSON.stringify(initialValue ?? {})
221
+ JSON.stringify(mergedInitialValue ?? {})
211
222
  !== JSON.stringify(formData.value)
212
223
  )
213
224
 
214
225
  function reset() {
215
- formData.value = JSON.parse(JSON.stringify(initialValue))
226
+ formData.value = JSON.parse(JSON.stringify(mergedInitialValue))
216
227
  }
217
228
 
218
229
  return {
@@ -93,7 +93,7 @@ export default function editorData(options) {
93
93
  const removeDraftAction = draft && api.actions.draft.resetMyDraft
94
94
 
95
95
  return Promise.all([
96
- live(savedDataPath), live(draftDataPath)
96
+ live(savedDataPath, appContext), live(draftDataPath, appContext)
97
97
  ]).then(([savedData, draftData]) => {
98
98
 
99
99
  const isNew = computed(() => !savedData.value)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/frontend-auto-form",
3
- "version": "0.9.162",
3
+ "version": "0.9.164",
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.7.2",
25
- "@live-change/cli": "^0.9.162",
26
- "@live-change/dao": "^0.9.162",
27
- "@live-change/dao-vue3": "^0.9.162",
28
- "@live-change/dao-websocket": "^0.9.162",
29
- "@live-change/framework": "^0.9.162",
30
- "@live-change/image-frontend": "^0.9.162",
31
- "@live-change/image-service": "^0.9.162",
32
- "@live-change/session-service": "^0.9.162",
33
- "@live-change/vue3-components": "^0.9.162",
34
- "@live-change/vue3-ssr": "^0.9.162",
25
+ "@live-change/cli": "^0.9.164",
26
+ "@live-change/dao": "^0.9.164",
27
+ "@live-change/dao-vue3": "^0.9.164",
28
+ "@live-change/dao-websocket": "^0.9.164",
29
+ "@live-change/framework": "^0.9.164",
30
+ "@live-change/image-frontend": "^0.9.164",
31
+ "@live-change/image-service": "^0.9.164",
32
+ "@live-change/session-service": "^0.9.164",
33
+ "@live-change/vue3-components": "^0.9.164",
34
+ "@live-change/vue3-ssr": "^0.9.164",
35
35
  "@vueuse/core": "^12.3.0",
36
36
  "codeceptjs-assert": "^0.0.5",
37
37
  "compression": "^1.7.5",
@@ -52,7 +52,7 @@
52
52
  "vue3-scroll-border": "0.1.7"
53
53
  },
54
54
  "devDependencies": {
55
- "@live-change/codeceptjs-helper": "^0.9.162",
55
+ "@live-change/codeceptjs-helper": "^0.9.164",
56
56
  "codeceptjs": "^3.6.10",
57
57
  "generate-password": "1.7.1",
58
58
  "playwright": "1.49.1",
@@ -63,5 +63,5 @@
63
63
  "author": "Michał Łaszczewski <michal@laszczewski.pl>",
64
64
  "license": "ISC",
65
65
  "description": "",
66
- "gitHead": "59cd1485ca6d76bd87a6634a92d6e661e5803d5e"
66
+ "gitHead": "38f9fb8b01a9527d8f6036e174edd1fa41443301"
67
67
  }