-
Notifications
You must be signed in to change notification settings - Fork 0
/
plugin.js
147 lines (129 loc) · 4.46 KB
/
plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import Graffiti from '@graffiti-garden/graffiti-p2p'
import GraffitiLinks from './GraffitiLinks.vue'
import { shallowReactive, reactive, unref, isRef, watch, onScopeDispose, computed, watchEffect } from 'vue'
import { isReactive } from '@vue/reactivity'
const REFRESH_RATE = 100 // milliseconds
export default {
install(app, options) {
const graffiti = new Graffiti(options)
const graffitiPlugin = shallowReactive({})
// Add static functions and constants
for (const key of [
'logIn',
'logOut',
'postLink',
'deleteLink',
'addMeListener',
'removeMeListener',
'addLinkListener',
'removeLinkListener',
'createPostLinkCapability',
'createDeleteLinkCapability',
'useLinkCapability'
]) {
Object.defineProperty(graffitiPlugin, key, {
enumerable: true,
value: graffiti[key].bind(graffiti)
})
}
// Make me reactive
graffiti.addMeListener(me=> {
graffitiPlugin.me = me
})
// A composable that returns a collection of objects
Object.defineProperty(graffitiPlugin, 'useLinks', { value: (sourceOrSources)=> {
const linkMap = reactive({})
const batch = {}
let timeoutID = null
function listener(link) {
// Add to the batch
batch[link.id] = link
if (!timeoutID) {
timeoutID = setTimeout(()=> {
const links = Object.values(batch)
for (const link of links) {
if (link.deleted && link.id in linkMap) {
delete linkMap[link.id]
} else if (!link.deleted) {
linkMap[link.id] = link
}
// Remove from the batch
delete batch[link.id]
}
timeoutID = null
}, REFRESH_RATE)
}
}
let sources
if (typeof sourceOrSources == 'string' || (
isRef(sourceOrSources) && typeof sourceOrSources.value == 'string')){
sources = [sourceOrSources]
} else {
sources = sourceOrSources
}
unref(sources).forEach(source=> {
graffiti.addLinkListener(unref(source), listener)
})
const unwatchers = []
if (isReactive(sources) || isRef(sources)) {
const unwatch =
watch(sources, (newSources, oldSources)=> {
if (!isReactive(newSources) &&
unref(newSources).length == unref(oldSources).length &&
unref(newSources)?.every((v,i)=>unref(v)==unref(oldSources[i]))
) {
return
}
oldSources.forEach(source=>
graffiti.removeLinkListener(unref(source), listener)
)
Object.keys(linkMap).forEach(k=> delete linkMap[k])
clearTimeout(timeoutID)
timeoutID = null
newSources.forEach(source=>
graffiti.addLinkListener(unref(source), listener)
)
}, {deep: true})
unwatchers.push(unwatch)
}
for (const source of unref(sources)) {
const unwatch =
isRef(source)?
watch(source, (newSource, oldSource)=> {
// Clear the object map and restart the loop
graffiti.removeLinkListener(oldSource, listener)
Object.keys(linkMap).forEach(k=> delete linkMap[k])
clearTimeout(timeoutID)
timeoutID = null
graffiti.addLinkListener(newSource, listener)
}, {deep: true}) : ()=> {}
unwatchers.push(unwatch)
}
onScopeDispose(()=> {
// Stop the loop
unwatchers.forEach(unwatch=> unwatch())
clearTimeout(timeoutID)
unref(sources).forEach(source=> {
graffiti.removeLinkListener(unref(source), listener)
})
})
// Strip IDs without creating a ref
const links = reactive([])
watchEffect(()=> {
const mapValues = Object.values(linkMap)
Object.assign(links, mapValues)
links.length = mapValues.length
})
return { links }
}})
// Begin to define a global property that mirrors
// the vanilla spec but with some reactive props
const glob = app.config.globalProperties
Object.defineProperty(glob, "$graffiti", { value: graffitiPlugin })
Object.defineProperty(glob, "$gf", { value: graffitiPlugin })
// Provide it globally to setup
app.provide('graffiti', graffitiPlugin)
// Add the component
app.component('GraffitiLinks', GraffitiLinks)
}
}