diff options
Diffstat (limited to '')
| -rw-r--r-- | client/src/graph.js | 132 |
1 files changed, 97 insertions, 35 deletions
diff --git a/client/src/graph.js b/client/src/graph.js index 7fe842c..1b27ea1 100644 --- a/client/src/graph.js +++ b/client/src/graph.js @@ -1,5 +1,7 @@ -import { Component } from 'preact'; +import { h } from 'preact'; +import { useMemo, useEffect } from 'preact/hooks'; import * as jsonld from 'jsonld'; +import { useDispatcher, fetchJSON } from './actions'; import { CORS_PREFIX } from './config'; const cors = (url) => `${CORS_PREFIX}/${url}`; @@ -20,36 +22,41 @@ const context = [ { replies: { '@id': 'as:replies', '@container': '@set' }, inReplyTo: { '@id': 'as:inReplyTo', '@container': '@set' }, - items: { '@id': 'as:items', '@type': '@id', '@container': '@set' }, + items: { '@id': 'as:items', '@container': '@set' }, }, ]; -export class GraphContainer extends Component { - state = { - loading: true, - name: "loading…", - items: {}, - error: null, - }; +export const GraphContainer = ({ url, mode, children }) => { + const api = useMemo(() => { + const API = mode === "mastodon" ? GraphAPIMastodon : GraphAPIJSONLD; + return new API(url); + }, [url, mode]); - cache = {}; + const { state, dispatch, DispatchProvider } = useDispatcher({ + reducer: api.reducer, + initialState: { + loading: true, + name: "loading…", + items: {}, + error: null, + }, + }); - componentDidMount() { - this.componentDidUpdate({}); - } + useEffect(() => dispatch({ type: 'reload' }), []); + + return ( + <DispatchProvider> + {children(state)} + </DispatchProvider> + ); +}; - componentDidUpdate(prevProps) { - if (prevProps.url === this.props.url) return; +class GraphAPI { + cache = {}; - this.loadData(this.props.url) - .catch(error => { - console.error(`Error loading ${this.props.url}:`, error); - this.setState({ - loading: false, - error, - }); - }); - } + constructor(url) { + this.url = url; + } @wrapCache async loadUser(id) { @@ -79,13 +86,21 @@ export class GraphContainer extends Component { } } - render() { - window.state = this.state; - return this.props.render(this.state); + reducer = (action) => { + switch (action.type) { + case 'reload': + return this.loadData(); + + case 'reply': + return this.reply(action); + + default: + return undefined; + } } } -export class GraphContainerMastodon extends GraphContainer { +export class GraphAPIMastodon extends GraphAPI { @wrapCache async loadCollection(id, ...args) { const collection = await jsonld.compact(cors(id), context); @@ -154,11 +169,11 @@ export class GraphContainerMastodon extends GraphContainer { return item; } - async loadData(url) { + async loadData() { const items = {}; - const root = await this.loadNote(url, items, []); + const root = await this.loadNote(this.url, items, []); - this.setState({ + return () => ({ name: root.name || root.content, items, loading: false, @@ -166,9 +181,9 @@ export class GraphContainerMastodon extends GraphContainer { } } -export class GraphContainerJSONLD extends GraphContainer { - async loadData(url) { - const response = await fetch(url, { +class GraphAPIJSONLD extends GraphAPI { + async loadData() { + const response = await fetch(this.url, { headers: { 'Accept': 'application/ld+json, application/json' }, credentials: 'include', }); @@ -197,9 +212,56 @@ export class GraphContainerJSONLD extends GraphContainer { discussion.items = indexedItems; - this.setState({ + return () => ({ ...discussion, loading: false, }); } + + async reply({ content, to }) { + const result = await fetchJSON(this.url, 'POST', { + type: 'Create', + object: { + type: 'Note', + inReplyTo: to, + content, + }, + }); + + const changed = await jsonld.frame( + result, + { + '@context': context, + items: { + context: { '@embed': '@never' }, + replies: { '@embed': '@never' }, + inReplyTo: { '@embed': '@never' }, + attributedTo: { '@embed': '@always' }, + }, + }, + { omitGraph: true } + ); + + await Promise.all(changed.items.map(async (item) => { + item.attributedTo = await this.loadUser(item.attributedTo); + })); + + return (state) => { + const items = {...state.items}; + for (const note of changed.items) { + items[note.id] = note; + + for (const { id } of note.inReplyTo) { + if (items[id].replies.indexOf(note.id) > -1) continue; + + items[id] = { + ...items[id], + replies: [...items[id].replies, { id: note.id }], + }; + } + } + + return { ...state, items }; + } + } } |
