@graffiti-garden/wrapper-vue 0.0.1
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/.github/workflows/main.yml +49 -0
- package/demo/App.vue +232 -0
- package/demo/index.html +15 -0
- package/demo/main.ts +22 -0
- package/dist/plugin.js +124 -0
- package/dist/plugin.js.map +1 -0
- package/dist/stats.html +4842 -0
- package/package.json +46 -0
- package/src/Discover.vue +21 -0
- package/src/composables.ts +148 -0
- package/src/injections.ts +24 -0
- package/src/plugin.ts +63 -0
- package/tsconfig.json +13 -0
- package/vite-demo.config.ts +8 -0
- package/vite.config.ts +26 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Vite Build
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
|
|
7
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
# Allow one concurrent deployment
|
|
17
|
+
concurrency:
|
|
18
|
+
group: "pages"
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
# Single deploy job since we're just deploying
|
|
23
|
+
deploy:
|
|
24
|
+
environment:
|
|
25
|
+
name: github-pages
|
|
26
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout
|
|
30
|
+
uses: actions/checkout@v3
|
|
31
|
+
- name: Set up Node
|
|
32
|
+
uses: actions/setup-node@v3
|
|
33
|
+
with:
|
|
34
|
+
node-version: 18
|
|
35
|
+
cache: "npm"
|
|
36
|
+
- name: Install dependencies
|
|
37
|
+
run: npm install
|
|
38
|
+
- name: Build
|
|
39
|
+
run: npm run build-demo
|
|
40
|
+
- name: Setup Pages
|
|
41
|
+
uses: actions/configure-pages@v3
|
|
42
|
+
- name: Upload artifact
|
|
43
|
+
uses: actions/upload-pages-artifact@v1
|
|
44
|
+
with:
|
|
45
|
+
# Upload dist repository
|
|
46
|
+
path: "./demo/dist"
|
|
47
|
+
- name: Deploy to GitHub Pages
|
|
48
|
+
id: deployment
|
|
49
|
+
uses: actions/deploy-pages@v1
|
package/demo/App.vue
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import { useGraffiti, useGraffitiSession } from "../src/plugin";
|
|
4
|
+
import { type GraffitiObject, type JSONSchema4 } from "@graffiti-garden/api";
|
|
5
|
+
|
|
6
|
+
const graffiti = useGraffiti();
|
|
7
|
+
const session = useGraffitiSession();
|
|
8
|
+
|
|
9
|
+
const channels = ref(["graffiti-client-demo"]);
|
|
10
|
+
|
|
11
|
+
const noteSchema = {
|
|
12
|
+
properties: {
|
|
13
|
+
value: {
|
|
14
|
+
properties: {
|
|
15
|
+
type: {
|
|
16
|
+
enum: ["Note"],
|
|
17
|
+
type: "string",
|
|
18
|
+
},
|
|
19
|
+
content: {
|
|
20
|
+
type: "string",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ["type", "content"],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
} as const satisfies JSONSchema4;
|
|
27
|
+
|
|
28
|
+
const posting = ref(false);
|
|
29
|
+
const myNote = ref("");
|
|
30
|
+
async function postNote() {
|
|
31
|
+
if (!myNote.value.length) return;
|
|
32
|
+
if (!session.value) {
|
|
33
|
+
alert("You are not logged in!");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
posting.value = true;
|
|
37
|
+
await graffiti.put<typeof noteSchema>(
|
|
38
|
+
{
|
|
39
|
+
channels: channels.value,
|
|
40
|
+
value: {
|
|
41
|
+
type: "Note",
|
|
42
|
+
content: myNote.value,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
session.value,
|
|
46
|
+
);
|
|
47
|
+
myNote.value = "";
|
|
48
|
+
posting.value = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const editing = ref<string>("");
|
|
52
|
+
const editText = ref<string>("");
|
|
53
|
+
function startEditing(result: GraffitiObject<typeof noteSchema>) {
|
|
54
|
+
editing.value = graffiti.locationToUri(result);
|
|
55
|
+
editText.value = result.value.content;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const savingEdits = ref(false);
|
|
59
|
+
async function saveEdits(result: GraffitiObject<typeof noteSchema>) {
|
|
60
|
+
if (!session.value) {
|
|
61
|
+
alert("You are not logged in!");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
savingEdits.value = true;
|
|
65
|
+
await graffiti.patch(
|
|
66
|
+
{
|
|
67
|
+
value: [{ op: "replace", path: "/content", value: editText.value }],
|
|
68
|
+
},
|
|
69
|
+
result,
|
|
70
|
+
session.value,
|
|
71
|
+
);
|
|
72
|
+
editText.value = "";
|
|
73
|
+
editing.value = "";
|
|
74
|
+
savingEdits.value = false;
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
79
|
+
<h1>Graffiti Vue Wrapper Demo</h1>
|
|
80
|
+
<div v-if="session">
|
|
81
|
+
Logged in as: {{ session.actor }}
|
|
82
|
+
<button @click="graffiti.logout(session)">Log out</button>
|
|
83
|
+
</div>
|
|
84
|
+
<div v-else>
|
|
85
|
+
<button @click="graffiti.login()">Log in</button>
|
|
86
|
+
</div>
|
|
87
|
+
<GraffitiDiscover
|
|
88
|
+
:channels="channels"
|
|
89
|
+
:schema="noteSchema"
|
|
90
|
+
v-slot="{ results, poll, isPolling }"
|
|
91
|
+
>
|
|
92
|
+
<div class="controls">
|
|
93
|
+
<form @submit.prevent="postNote">
|
|
94
|
+
<label for="my-note">Note:</label>
|
|
95
|
+
<input
|
|
96
|
+
type="text"
|
|
97
|
+
id="my-note"
|
|
98
|
+
name="my-note"
|
|
99
|
+
v-model="myNote"
|
|
100
|
+
/>
|
|
101
|
+
<input type="submit" value="Post" />
|
|
102
|
+
<span v-if="posting">Posting...</span>
|
|
103
|
+
</form>
|
|
104
|
+
|
|
105
|
+
<button @click="poll">Refresh</button>
|
|
106
|
+
|
|
107
|
+
Change the channel:
|
|
108
|
+
<input
|
|
109
|
+
type="text"
|
|
110
|
+
:value="channels[0]"
|
|
111
|
+
@input="
|
|
112
|
+
(event) =>
|
|
113
|
+
event.target &&
|
|
114
|
+
'value' in event.target &&
|
|
115
|
+
typeof event.target.value === 'string' &&
|
|
116
|
+
(channels = [event.target.value])
|
|
117
|
+
"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
<ul>
|
|
121
|
+
<li v-if="isPolling">Loading...</li>
|
|
122
|
+
<li
|
|
123
|
+
v-for="result in results.sort(
|
|
124
|
+
(a, b) =>
|
|
125
|
+
// Sort by lastModified, most recent first
|
|
126
|
+
// lastModified are ISO strings
|
|
127
|
+
new Date(b.lastModified).getTime() -
|
|
128
|
+
new Date(a.lastModified).getTime(),
|
|
129
|
+
)"
|
|
130
|
+
class="post"
|
|
131
|
+
>
|
|
132
|
+
<div class="actor">
|
|
133
|
+
{{ result.actor }}
|
|
134
|
+
</div>
|
|
135
|
+
<div class="timestamp">
|
|
136
|
+
{{ result.lastModified.toLocaleString() }}
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div
|
|
140
|
+
class="content"
|
|
141
|
+
v-if="editing !== $graffiti.locationToUri(result)"
|
|
142
|
+
>
|
|
143
|
+
{{ result.value.content }}
|
|
144
|
+
</div>
|
|
145
|
+
<form
|
|
146
|
+
v-else
|
|
147
|
+
@submit.prevent="saveEdits(result)"
|
|
148
|
+
class="content"
|
|
149
|
+
>
|
|
150
|
+
<input type="text" v-model="editText" />
|
|
151
|
+
<input type="submit" value="Save" />
|
|
152
|
+
<span v-if="savingEdits">Saving...</span>
|
|
153
|
+
</form>
|
|
154
|
+
|
|
155
|
+
<menu>
|
|
156
|
+
<li>
|
|
157
|
+
<a
|
|
158
|
+
target="_blank"
|
|
159
|
+
:href="$graffiti.locationToUri(result)"
|
|
160
|
+
>
|
|
161
|
+
🌐
|
|
162
|
+
</a>
|
|
163
|
+
</li>
|
|
164
|
+
<li v-if="result.actor === session?.actor">
|
|
165
|
+
<button @click="$graffiti.delete(result, session)">
|
|
166
|
+
Delete
|
|
167
|
+
</button>
|
|
168
|
+
</li>
|
|
169
|
+
<li v-if="result.actor === session?.actor">
|
|
170
|
+
<button @click="startEditing(result)">Edit</button>
|
|
171
|
+
</li>
|
|
172
|
+
</menu>
|
|
173
|
+
</li>
|
|
174
|
+
</ul>
|
|
175
|
+
</GraffitiDiscover>
|
|
176
|
+
</template>
|
|
177
|
+
|
|
178
|
+
<style>
|
|
179
|
+
:root {
|
|
180
|
+
font-family: Arial, sans-serif;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.graffiti-session-manager {
|
|
184
|
+
border-radius: 0.5rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
ul {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-direction: column;
|
|
190
|
+
gap: 1rem;
|
|
191
|
+
padding: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.post {
|
|
195
|
+
list-style: none;
|
|
196
|
+
border: 1px solid #ccc;
|
|
197
|
+
border-radius: 0.5rem;
|
|
198
|
+
margin: 0;
|
|
199
|
+
padding: 1rem;
|
|
200
|
+
|
|
201
|
+
.actor {
|
|
202
|
+
font-size: 1rem;
|
|
203
|
+
font-weight: bold;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.timestamp {
|
|
207
|
+
font-size: 0.8rem;
|
|
208
|
+
color: #666;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.content {
|
|
212
|
+
margin: 1rem;
|
|
213
|
+
margin-left: 0;
|
|
214
|
+
margin-right: 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
menu {
|
|
218
|
+
display: flex;
|
|
219
|
+
padding: 0;
|
|
220
|
+
gap: 1rem;
|
|
221
|
+
|
|
222
|
+
li {
|
|
223
|
+
list-style: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
a {
|
|
227
|
+
text-decoration: none;
|
|
228
|
+
color: #000;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
</style>
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Graffiti Vue.JS Client Demo</title>
|
|
6
|
+
<meta
|
|
7
|
+
name="viewport"
|
|
8
|
+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
9
|
+
/>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="app"></div>
|
|
13
|
+
<script type="module" src="./main.ts"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
package/demo/main.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
import App from "./App.vue";
|
|
3
|
+
import { GraffitiPlugin } from "../src/plugin";
|
|
4
|
+
import { GraffitiPouchDB } from "@graffiti-garden/implementation-pouchdb";
|
|
5
|
+
|
|
6
|
+
// Horrible, I know
|
|
7
|
+
// this is just for testing
|
|
8
|
+
const one = "Sandbank8803";
|
|
9
|
+
const two = "hb#&6CQBx!ua%q";
|
|
10
|
+
const three = "tracker.graffiti.garden";
|
|
11
|
+
const four = "graffiti";
|
|
12
|
+
|
|
13
|
+
createApp(App)
|
|
14
|
+
.use(GraffitiPlugin, {
|
|
15
|
+
useGraffiti: () =>
|
|
16
|
+
new GraffitiPouchDB({
|
|
17
|
+
pouchDBOptions: {
|
|
18
|
+
name: `https://${encodeURIComponent(one)}:${encodeURIComponent(two)}@${three}/${four}`,
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
22
|
+
.mount("#app");
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { inject as b, ref as y, watch as j, onScopeDispose as L, toValue as h, defineComponent as R, toRef as G, renderSlot as $, unref as w } from "vue";
|
|
2
|
+
const M = Symbol(), P = Symbol();
|
|
3
|
+
function x() {
|
|
4
|
+
const o = b(M);
|
|
5
|
+
if (!o)
|
|
6
|
+
throw new Error("No Graffiti instance provided");
|
|
7
|
+
return o;
|
|
8
|
+
}
|
|
9
|
+
function K() {
|
|
10
|
+
const o = b(P);
|
|
11
|
+
if (!o)
|
|
12
|
+
throw new Error("No Graffiti session provided");
|
|
13
|
+
return o;
|
|
14
|
+
}
|
|
15
|
+
function N(o, f, t) {
|
|
16
|
+
const s = x(), n = K(), r = y([]), c = /* @__PURE__ */ new Map();
|
|
17
|
+
function d() {
|
|
18
|
+
r.value = Array.from(c.values()).reduce((e, u) => {
|
|
19
|
+
const { tombstone: l, value: _ } = u;
|
|
20
|
+
return l || e.push({ ...u, tombstone: l, value: _ }), e;
|
|
21
|
+
}, []);
|
|
22
|
+
}
|
|
23
|
+
function E(e) {
|
|
24
|
+
const u = s.objectToUri(e), l = c.get(u);
|
|
25
|
+
l && (l.lastModified > e.lastModified || l.lastModified === e.lastModified && !l.tombstone) || c.set(u, e);
|
|
26
|
+
}
|
|
27
|
+
const v = () => h(o), m = () => h(f), p = () => h(t) ?? (n == null ? void 0 : n.value);
|
|
28
|
+
let i;
|
|
29
|
+
async function D() {
|
|
30
|
+
i == null || i.return(), i = s.synchronize(
|
|
31
|
+
v(),
|
|
32
|
+
m(),
|
|
33
|
+
p()
|
|
34
|
+
);
|
|
35
|
+
for await (const e of i) {
|
|
36
|
+
if (e.error) {
|
|
37
|
+
console.error(e.error);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
E(e.value), d();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const g = y(!1);
|
|
44
|
+
let a;
|
|
45
|
+
async function S() {
|
|
46
|
+
a == null || a.return(), g.value = !0;
|
|
47
|
+
try {
|
|
48
|
+
a = s.discover(
|
|
49
|
+
v(),
|
|
50
|
+
m(),
|
|
51
|
+
p()
|
|
52
|
+
);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(e), d(), g.value = !1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for await (const e of a) {
|
|
58
|
+
if (e.error) {
|
|
59
|
+
console.error(e.error);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
E(e.value), d();
|
|
63
|
+
}
|
|
64
|
+
g.value = !1;
|
|
65
|
+
}
|
|
66
|
+
return j(
|
|
67
|
+
[v, m, p],
|
|
68
|
+
() => {
|
|
69
|
+
c.clear(), d(), S(), D();
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
immediate: !0
|
|
73
|
+
}
|
|
74
|
+
), L(() => i == null ? void 0 : i.return()), {
|
|
75
|
+
results: r,
|
|
76
|
+
poll: S,
|
|
77
|
+
isPolling: g
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const V = /* @__PURE__ */ R({
|
|
81
|
+
__name: "Discover",
|
|
82
|
+
props: {
|
|
83
|
+
channels: {},
|
|
84
|
+
schema: {},
|
|
85
|
+
session: {}
|
|
86
|
+
},
|
|
87
|
+
setup(o) {
|
|
88
|
+
const f = o, { results: t, poll: s, isPolling: n } = N(
|
|
89
|
+
G(f, "channels"),
|
|
90
|
+
G(f, "schema"),
|
|
91
|
+
G(f, "session")
|
|
92
|
+
);
|
|
93
|
+
return (r, c) => $(r.$slots, "default", {
|
|
94
|
+
results: w(t),
|
|
95
|
+
poll: w(s),
|
|
96
|
+
isPolling: w(n)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}), A = {
|
|
100
|
+
install(o, f) {
|
|
101
|
+
const t = f.useGraffiti(), s = y(void 0);
|
|
102
|
+
t.sessionEvents.addEventListener("login", (n) => {
|
|
103
|
+
const r = n.detail;
|
|
104
|
+
if (r.error) {
|
|
105
|
+
console.error("Error logging in:"), console.error(r.error);
|
|
106
|
+
return;
|
|
107
|
+
} else
|
|
108
|
+
s.value = r.session;
|
|
109
|
+
}), t.sessionEvents.addEventListener("logout", (n) => {
|
|
110
|
+
const r = n.detail;
|
|
111
|
+
r.error ? (console.error("Error logging out:"), console.error(r.error)) : s.value = void 0;
|
|
112
|
+
}), o.provide(M, t), o.provide(P, s), o.component("GraffitiDiscover", V), o.config.globalProperties.$graffiti = t, o.config.globalProperties.$graffitiSession = s;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
export {
|
|
116
|
+
V as GraffitiDiscover,
|
|
117
|
+
A as GraffitiPlugin,
|
|
118
|
+
M as graffitiInjectKey,
|
|
119
|
+
P as graffitiSessionInjectKey,
|
|
120
|
+
x as useGraffiti,
|
|
121
|
+
N as useGraffitiDiscover,
|
|
122
|
+
K as useGraffitiSession
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["../src/injections.ts","../src/composables.ts","../src/Discover.vue","../src/plugin.ts"],"sourcesContent":["import { inject } from \"vue\";\nimport type { InjectionKey, Ref } from \"vue\";\nimport type { Graffiti, GraffitiSession } from \"@graffiti-garden/api\";\n\nexport const graffitiInjectKey = Symbol() as InjectionKey<Graffiti>;\nexport const graffitiSessionInjectKey = Symbol() as InjectionKey<\n Ref<GraffitiSession | undefined>\n>;\n\nexport function useGraffiti() {\n const graffiti = inject(graffitiInjectKey);\n if (!graffiti) {\n throw new Error(\"No Graffiti instance provided\");\n }\n return graffiti;\n}\n\nexport function useGraffitiSession() {\n const session = inject(graffitiSessionInjectKey);\n if (!session) {\n throw new Error(\"No Graffiti session provided\");\n }\n return session;\n}\n","import {\n onScopeDispose,\n ref,\n toValue,\n watch,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport type {\n GraffitiObject,\n GraffitiSession,\n JSONSchema4,\n} from \"@graffiti-garden/api\";\nimport { useGraffiti, useGraffitiSession } from \"./injections\";\n\n/**\n * A reactive version of the [`Graffiti.discover`](https://api.graffiti.garden/classes/Graffiti.html#discover)\n * method.\n *\n * @returns An object containing\n * - `results`: a reactive array of Graffiti objects\n * - `poll`: a method to poll for new results\n * - `isPolling`: a boolean ref indicating if the poll is currently running\n */\nexport function useGraffitiDiscover<Schema extends JSONSchema4>(\n /**\n * A list of channels to discover objects from.\n * It may be a Vue ref or getter.\n */\n channels: MaybeRefOrGetter<string[]>,\n /**\n * A [JSON Schema](https://json-schema.org/) object describing the schema\n * of the objects to discover. All other objects will be filtered out\n * and the output will be typed as `GraffitiObject<Schema>`.\n */\n schema: MaybeRefOrGetter<Schema>,\n /**\n * A Graffiti session object. If not provided, the\n * global plugin session will be used.\n */\n session?: MaybeRefOrGetter<GraffitiSession>,\n) {\n const graffiti = useGraffiti();\n const sessionInjected = useGraffitiSession();\n\n const results = ref<(GraffitiObject<Schema> & { tombstone: false })[]>([]);\n const resultsRaw = new Map<string, GraffitiObject<Schema>>();\n function flattenResults() {\n results.value = Array.from(resultsRaw.values()).reduce<\n (GraffitiObject<Schema> & { tombstone: false })[]\n >((acc, o) => {\n const { tombstone, value } = o;\n if (!tombstone) {\n acc.push({ ...o, tombstone, value });\n }\n return acc;\n }, []);\n }\n\n function onValue(value: GraffitiObject<Schema>) {\n const url = graffiti.objectToUri(value);\n const existing = resultsRaw.get(url);\n if (\n existing &&\n (existing.lastModified > value.lastModified ||\n (existing.lastModified === value.lastModified && !existing.tombstone))\n ) {\n return;\n }\n resultsRaw.set(url, value);\n }\n\n const channelsGetter = () => toValue(channels);\n const schemaGetter = () => toValue(schema);\n const sessionGetter = () => toValue(session) ?? sessionInjected?.value;\n\n let localIterator:\n | ReturnType<typeof graffiti.synchronize<Schema>>\n | undefined = undefined;\n async function pollLocalModifications() {\n localIterator?.return();\n localIterator = graffiti.synchronize(\n channelsGetter(),\n schemaGetter(),\n sessionGetter(),\n );\n for await (const value of localIterator) {\n if (value.error) {\n console.error(value.error);\n continue;\n }\n onValue(value.value);\n flattenResults();\n }\n }\n\n const isPolling = ref(false);\n let iterator: ReturnType<typeof graffiti.discover<Schema>> | undefined =\n undefined;\n async function poll() {\n iterator?.return();\n isPolling.value = true;\n\n try {\n iterator = graffiti.discover(\n channelsGetter(),\n schemaGetter(),\n sessionGetter(),\n );\n } catch (e) {\n console.error(e);\n flattenResults();\n isPolling.value = false;\n return;\n }\n\n for await (const result of iterator) {\n if (result.error) {\n console.error(result.error);\n continue;\n }\n onValue(result.value);\n flattenResults();\n }\n\n isPolling.value = false;\n }\n\n watch(\n [channelsGetter, schemaGetter, sessionGetter],\n () => {\n resultsRaw.clear();\n flattenResults();\n poll();\n pollLocalModifications();\n },\n\n {\n immediate: true,\n },\n );\n onScopeDispose(() => localIterator?.return());\n\n return {\n results,\n poll,\n isPolling,\n };\n}\n","<script setup lang=\"ts\" generic=\"Schema extends JSONSchema4\">\nimport { toRef } from \"vue\";\nimport type { GraffitiSession, JSONSchema4 } from \"@graffiti-garden/api\";\nimport { useGraffitiDiscover } from \"./composables\";\n\nconst props = defineProps<{\n channels: string[];\n schema: Schema;\n session?: GraffitiSession;\n}>();\n\nconst { results, poll, isPolling } = useGraffitiDiscover<Schema>(\n toRef(props, \"channels\"),\n toRef(props, \"schema\"),\n toRef(props, \"session\"),\n);\n</script>\n\n<template>\n <slot :results=\"results\" :poll=\"poll\" :isPolling=\"isPolling\"></slot>\n</template>\n","import type { App, Plugin, Ref } from \"vue\";\nimport { ref } from \"vue\";\nimport Discover from \"./Discover.vue\";\nimport type {\n GraffitiFactory,\n Graffiti,\n GraffitiSession,\n GraffitiLoginEvent,\n GraffitiLogoutEvent,\n} from \"@graffiti-garden/api\";\nimport { graffitiInjectKey, graffitiSessionInjectKey } from \"./injections\";\n\ndeclare module \"vue\" {\n export interface ComponentCustomProperties {\n $graffiti: Graffiti;\n $graffitiSession: Ref<GraffitiSession | undefined>;\n }\n\n export interface GlobalComponents {\n GraffitiDiscover: typeof Discover;\n }\n}\n\nexport interface GraffitiPluginOptions {\n useGraffiti: GraffitiFactory;\n}\n\nexport const GraffitiPlugin: Plugin<GraffitiPluginOptions> = {\n install(app: App, options: GraffitiPluginOptions) {\n const graffiti = options.useGraffiti();\n const graffitiSession = ref<GraffitiSession | undefined>(undefined);\n graffiti.sessionEvents.addEventListener(\"login\", (evt) => {\n const detail = (evt as GraffitiLoginEvent).detail;\n if (detail.error) {\n console.error(\"Error logging in:\");\n console.error(detail.error);\n return;\n } else {\n graffitiSession.value = detail.session;\n }\n });\n graffiti.sessionEvents.addEventListener(\"logout\", (evt) => {\n const detail = (evt as GraffitiLogoutEvent).detail;\n if (detail.error) {\n console.error(\"Error logging out:\");\n console.error(detail.error);\n } else {\n graffitiSession.value = undefined;\n }\n });\n\n app.provide(graffitiInjectKey, graffiti);\n app.provide(graffitiSessionInjectKey, graffitiSession);\n\n app.component(\"GraffitiDiscover\", Discover);\n app.config.globalProperties.$graffiti = graffiti;\n app.config.globalProperties.$graffitiSession = graffitiSession;\n },\n};\n\nexport * from \"./composables\";\nexport * from \"./injections\";\nexport { Discover as GraffitiDiscover };\n"],"names":["graffitiInjectKey","graffitiSessionInjectKey","useGraffiti","graffiti","inject","useGraffitiSession","session","useGraffitiDiscover","channels","schema","sessionInjected","results","ref","resultsRaw","flattenResults","acc","o","tombstone","value","onValue","url","existing","channelsGetter","toValue","schemaGetter","sessionGetter","localIterator","pollLocalModifications","isPolling","iterator","poll","result","watch","onScopeDispose","props","__props","toRef","GraffitiPlugin","app","options","graffitiSession","evt","detail","Discover"],"mappings":"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"}
|