@live-change/vue3-components 0.1.17 → 0.2.6

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 CHANGED
@@ -19,6 +19,6 @@ import createReactiveObject from "./utils/createReactiveObject.mjs"
19
19
 
20
20
  export { createReactiveObject }
21
21
 
22
- import { analytics, useAnalytics, installRouterAnalytics } from "./logic"
23
- export { analytics, useAnalytics, installRouterAnalytics }
22
+ import { analytics, useAnalytics, installRouterAnalytics, synchronized, synchronizedList } from "./logic"
23
+ export { analytics, useAnalytics, installRouterAnalytics, synchronized, synchronizedList }
24
24
 
package/logic/index.js CHANGED
@@ -8,6 +8,12 @@ export { Loading, LoadingZone, WorkingZone, Observe }
8
8
  import { analytics, useAnalytics, installRouterAnalytics } from "./analytics.js"
9
9
  export { analytics, useAnalytics, installRouterAnalytics }
10
10
 
11
+ import { synchronized } from "./synchronized.js"
12
+ export { synchronized }
13
+
14
+ import { synchronizedList } from "./synchronizedList.js"
15
+ export { synchronizedList }
16
+
11
17
  function registerLogicComponents(app) {
12
18
  app.component("loading", Loading)
13
19
  app.component("loading-zone", LoadingZone)
@@ -0,0 +1,91 @@
1
+ import { computed, ref, watch } from "vue"
2
+ import { useThrottleFn } from "@vueuse/core"
3
+
4
+ function synchronized(options) {
5
+ const {
6
+ source,
7
+ update,
8
+ identifiers = {},
9
+ timeField = 'lastUpdate',
10
+ timeSource = () => (new Date()).toISOString(),
11
+ onChange = () => {},
12
+ onSave = () => {},
13
+ recursive = false,
14
+ throttle = 300,
15
+ autoSave = true
16
+ } = options
17
+ if(!source) throw new Error('source must be defined')
18
+ if(!update) throw new Error('update function must be defined')
19
+ if(recursive) {
20
+ const synchronizedValue = ref(JSON.parse(JSON.stringify(source.value || {})))
21
+ const synchronizedJSON = computed(() => JSON.stringify(synchronizedValue.value))
22
+ const lastLocalUpdate = ref(synchronized.value ? synchronizedValue.value[timeField] : '')
23
+
24
+ let lastSavedUpdate = lastLocalUpdate.value
25
+
26
+ const changed = computed(() => (JSON.stringify(source.value) != synchronizedJSON.value)
27
+ && (lastLocalUpdate.value > ((source.value && source.value[timeField]) ?? '')))
28
+ async function save() {
29
+ if((JSON.stringify(source.value) == JSON.stringify(synchronizedValue.value))
30
+ || (lastLocalUpdate.value <= ((source.value && source.value[timeField]) ?? ''))) {
31
+ return false // identical, no need to save
32
+ }
33
+ if(lastSavedUpdate == lastLocalUpdate.value) {
34
+ return false // duplicated save action
35
+ }
36
+ lastSavedUpdate = lastLocalUpdate.value
37
+ const data = JSON.parse(synchronizedJSON.value)
38
+ // console.log("LAST LOCAL UPDATE", lastLocalUpdate.value)
39
+ // console.log("LAST REMOTE UPDATE", ((source.value && source.value[timeField]) ?? ''))
40
+ // console.log("SOURCE JSON", JSON.stringify(source.value))
41
+ // console.log("SYNCHRONIZED JSON", JSON.stringify(synchronizedValue.value))
42
+ await update({ ...data, [timeField]: lastLocalUpdate.value, ...identifiers })
43
+ onSave()
44
+ return true
45
+ }
46
+ const throttledSave = throttle ? useThrottleFn(save, throttle) : save
47
+ watch(() => synchronizedJSON.value, json => {
48
+ lastLocalUpdate.value = timeSource()
49
+ onChange()
50
+ if(autoSave) throttledSave()
51
+ })
52
+ watch(() => source.value, sourceData => {
53
+ if(sourceData) {
54
+ console.log("SRC DATA", sourceData)
55
+ lastLocalUpdate.value = sourceData[timeField]
56
+ }
57
+ })
58
+ return { value: synchronizedValue, save, changed }
59
+ } else {
60
+ const local = ref(source.value)
61
+ const changed = computed(() => (JSON.stringify(source.value) != JSON.stringify(local.value))
62
+ && ( ((local.value && local.value[timeField]) ?? '')
63
+ > ((source.value && source.value[timeField]) ?? '')))
64
+ async function save() {
65
+ if((JSON.stringify(source.value) == JSON.stringify(local.value))
66
+ || ( ((local.value && local.value[timeField]) ?? '')
67
+ <= ((source.value && source.value[timeField]) ?? ''))) return false // identical, no need to save
68
+ const data = JSON.parse(JSON.stringify(local.value))
69
+ await update({ ...data, ...identifiers })
70
+ onSave()
71
+ return true
72
+ }
73
+ const throttledSave = throttle ? useThrottleFn(save, throttle) : save
74
+ const synchronizedComputed = computed({
75
+ get: () => {
76
+ const localTime = ((local.value && local.value[timeField]) ?? '')
77
+ const sourceTime = ((source.value && source.value[timeField]) ?? '')
78
+ return localTime > sourceTime ? local.value : source.value
79
+ },
80
+ set: newValue => {
81
+ local.value = { ...newValue, [timeField]: timeSource() }
82
+ onChange()
83
+ if(autoSave) throttledSave()
84
+ }
85
+ })
86
+ return { value: synchronizedComputed, save, changed }
87
+ }
88
+ }
89
+
90
+ export { synchronized }
91
+ export default synchronized
@@ -0,0 +1,146 @@
1
+ import { computed, ref, watch } from "vue"
2
+ import { useThrottleFn } from "@vueuse/core"
3
+ import { synchronized } from './synchronized.js'
4
+
5
+ function sortedArraysMerge(merge, ...arrays) {
6
+ const result = []
7
+ const positions = arrays.map(a => 0)
8
+ let incremented = false
9
+ do {
10
+ incremented = false
11
+ let lowestId = '\xFF\xFF\xFF\xFF\xFF'
12
+ for(const arrayIndex in arrays) {
13
+ const position = positions[arrayIndex]
14
+ const array = arrays[arrayIndex]
15
+ if(position < array.length && array[position].id < lowestId) {
16
+ lowestId = array[position].id
17
+ }
18
+ }
19
+ let objects = new Array(arrays.length)
20
+ for(const arrayIndex in arrays) {
21
+ const position = positions[arrayIndex]
22
+ const array = arrays[arrayIndex]
23
+ if(position < array.length && array[position].id == lowestId) {
24
+ objects[arrayIndex] = array[position]
25
+ positions[arrayIndex]++
26
+ } else {
27
+ objects[arrayIndex] = null
28
+ }
29
+ }
30
+ const mergeResult = merge(...objects)
31
+ if(mergeResult) {
32
+ result.push(mergeResult)
33
+ }
34
+ } while (incremented)
35
+ return result
36
+ }
37
+
38
+ function synchronizedList(options) {
39
+ const {
40
+ source,
41
+ update: updateAction,
42
+ insert: insertAction,
43
+ delete: deleteAction,
44
+ move: moveAction,
45
+ identifiers = {},
46
+ objectIdentifiers = object => ({ id: object.id }),
47
+ timeField = 'lastUpdate',
48
+ timeSource = () => (new Date()).toISOString(),
49
+ onChange = () => {},
50
+ onSave = () => {},
51
+ recursive = false,
52
+ throttle = 300,
53
+ autoSave = true,
54
+ mapper = source => synchronized({
55
+ source, update: updateAction, identifiers: { ...identifiers, ...objectIdentifiers(source.value) }, timeField, timeSource,
56
+ recursive, throttle, autoSave, onSave, onChange
57
+ })
58
+ } = options
59
+ if(!source) throw new Error('source must be defined')
60
+ const synchronizedList = ref([])
61
+ const locallyAdded = ref([])
62
+ const locallyDeleted = ref([])
63
+
64
+ function createSynchronizedElement(sourceData) {
65
+ const elementSource = ref(sourceData)
66
+ const synchronizedElement = mapper(elementSource)
67
+ synchronizedElement.source = elementSource
68
+ synchronizedElement.id = sourceData.id
69
+ return synchronizedElement
70
+ }
71
+ function synchronizeFromSource() {
72
+ console.log("SYNCHRONIZE FROM SOURCE!")
73
+ let obsoleteLocallyAdded = new Set()
74
+ let obsoleteLocallyDeleted = new Set()
75
+ let newSynchronized = sortedArraysMerge(
76
+ (synchronizedElement, sourceElement, locallyAddedElement, locallyDeletedElement) => {
77
+
78
+ if(locallyAddedElement && sourceElement) {
79
+ obsoleteLocallyAdded.add(locallyAddedElement.id)
80
+ }
81
+ if(locallyDeletedElement && !sourceElement) {
82
+ obsoleteLocallyDeleted.add(locallyAddedElement.id)
83
+ }
84
+
85
+ if(synchronizedElement) {
86
+ if(locallyDeletedElement) {
87
+ return null // synchronized element locally
88
+ }
89
+ if(sourceElement) {
90
+ synchronizedElement.source.value = sourceElement
91
+ } else if(locallyAddedElement) {
92
+ synchronizedElement.source.value = locallyAddedElement
93
+ } else {
94
+ return null // synchronized element deleted
95
+ }
96
+ } else if(sourceElement) {
97
+ console.log("CREATE SYNCHRONIZED FROM SOURCE!")
98
+ return createSynchronizedElement(sourceElement)
99
+ } else if(locallyAddedElement) {
100
+ return createSynchronizedElement(locallyAddedElement)
101
+ }
102
+ }, synchronizedList.value, source.value || [], locallyAdded.value, locallyDeleted.value)
103
+ if(obsoleteLocallyAdded.length > 0) {
104
+ locallyAdded.value = locallyAdded.value.filter(
105
+ locallyAddedElement => obsoleteLocallyAdded.has(locallyAddedElement.id)
106
+ )
107
+ }
108
+ if(obsoleteLocallyDeleted.length > 0) {
109
+ locallyDeleted.value = locallyDeleted.value.filter(
110
+ locallyDeletedElement => obsoleteLocallyDeleted.has(locallyDeletedElement.id)
111
+ )
112
+ }
113
+ synchronizedList.value = newSynchronized
114
+ }
115
+
116
+ watch(() => (source.value ?? []).map(({ id }) => id), sourceIds => synchronizeFromSource())
117
+ synchronizeFromSource()
118
+
119
+ const changed = computed(() => (synchronizedList.value.some(({ changed }) => changed.value))
120
+ || locallyAdded.length || locallyDeleted.length)
121
+
122
+ async function save() {
123
+ const results = await Promise.app(synchronizedList.value.map(synchronizedElement => synchronizedElement.save()))
124
+ return results.some(res => res)
125
+ }
126
+
127
+ async function insert(element) {
128
+ locallyAdded.value.push(element)
129
+ await insertAction({ ...element, [timeField]: timeSource(), ...identifiers })
130
+ }
131
+
132
+ async function deleteElement(element) {
133
+ locallyDeleted.value.push(element)
134
+ await deleteAction({ ...element, ...identifiers })
135
+ }
136
+
137
+ async function move(element, toId) {
138
+ throw new Error('not implemented yet')
139
+ }
140
+
141
+ const synchronizedValue = computed(() => synchronizedList.value.map(synchronizedElement => synchronizedElement.value))
142
+ return { value: synchronizedValue, save, changed, insert, delete: deleteElement, move }
143
+ }
144
+
145
+ export default synchronizedList
146
+ export { synchronizedList }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/vue3-components",
3
- "version": "0.1.17",
3
+ "version": "0.2.6",
4
4
  "description": "Live Change Framework - vue components",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/live-change/vue3-components.git"
11
+ "url": "git+https://github.com/live-change/live-change-framework-vue3.git"
12
12
  },
13
13
  "author": {
14
14
  "email": "m8@em8.pl",
@@ -17,13 +17,14 @@
17
17
  },
18
18
  "license": "MIT",
19
19
  "bugs": {
20
- "url": "https://github.com/live-change/vue3-components/issues"
20
+ "url": "https://github.com/live-change/live-change-framework-vue3/issues"
21
21
  },
22
- "homepage": "https://github.com/live-change/vue3-components",
22
+ "homepage": "https://github.com/live-change/live-change-framework-vue3",
23
23
  "dependencies": {
24
+ "@live-change/vue3-ssr": "^0.2.6",
24
25
  "debug": "^4.3.2",
25
- "vue": "^3.2.21",
26
- "@live-change/vue3-ssr": "^0.1.9",
27
- "mitt": "3.0.0"
28
- }
26
+ "mitt": "3.0.0",
27
+ "vue": "^3.2.31"
28
+ },
29
+ "gitHead": "88f2d2f0dca78f3147db250a62999f1f90d20b85"
29
30
  }