From 0d5e63dde3da4116a49f6c83eb6094d68a41b914 Mon Sep 17 00:00:00 2001 From: s-ol Date: Wed, 12 Jan 2022 10:17:26 +0100 Subject: handle errors while loading more gracefully --- src/index.js | 156 ++++++++++++++++++++++++++++++++++++++++----------------- src/ui/Note.js | 14 +++++- 2 files changed, 122 insertions(+), 48 deletions(-) diff --git a/src/index.js b/src/index.js index 38dc8cc..5fdda36 100644 --- a/src/index.js +++ b/src/index.js @@ -33,6 +33,7 @@ class GraphContainer extends Component { state = { name: "loading…", items: {}, + error: null, }; cache = {}; @@ -42,10 +43,22 @@ class GraphContainer extends Component { if (this.props.graph.startsWith('http')) this.loadDataMastodon(this.props.graph) - .catch(err => console.error(err)); + .catch(error => { + console.error(`Error loading Mastodon graph ${this.props.graph}:`, error); + this.setState({ + name: "error loading", + error, + }); + }); else this.loadData(this.props.graph) - .catch(err => console.error(err)); + .catch(error => { + console.error("Error loading default graph:", error); + this.setState({ + name: "error loading", + error, + }); + }); } @wrapCache @@ -79,29 +92,46 @@ class GraphContainer extends Component { } @wrapCache - async loadNote(id, items) { - const item = await jsonld.frame( - cors(id), - { + async loadNote(id, items, inReplyTo) { + let item; + try { + item = await jsonld.frame( + cors(id), + { + '@context': context, + type: 'Note', + context: { '@embed': '@never' }, + replies: { '@embed': '@always' }, + inReplyTo: { '@embed': '@never' }, + }, + { omitGraph: true } + ); + + if (item.replies.length) + item.replies = await this.loadCollection(item.replies[0].id, items, [id]); + } catch (error) { + console.error(`Error loading note '${id}':`, error); + item = { '@context': context, - type: 'Note', - context: { '@embed': '@never' }, - replies: { '@embed': '@always' }, - inReplyTo: { '@embed': '@never' }, - }, - { omitGraph: true } - ); + id, + type: 'Tombstone', + formerType: 'Note', + attributedTo: 'https://dag.s-ol.nu/users/unknown', + published: '1970-01-01T00:00:00Z', + content: `Error loading note: ${error.toString()}`, + replies: [], + inReplyTo, + }; + } item.attributedTo = await this.loadUser(item.attributedTo); - item.replies = await this.loadCollection(item.replies[0].id, items); - items[item.id] = item; return item; } async loadDataMastodon(url) { const items = {}; - const root = await this.loadNote(url, items); + const root = await this.loadNote(url, items, []); this.setState({ name: root.name || root.content, @@ -111,9 +141,7 @@ class GraphContainer extends Component { async loadData(url) { const response = await fetch(url, { - headers: { - 'Accept': 'application/json', - }, + headers: { 'Accept': 'application/json' }, }); const discussion = await jsonld.frame( await response.json(), @@ -143,13 +171,34 @@ class GraphContainer extends Component { @wrapCache async loadUser(id) { - const user = await jsonld.compact(id, context); - user.id = id; - return user; + if (id === "https://dag.s-ol.nu/users/unknown") { + return { + '@context': context, + id, + type: 'Person', + name: 'unknown', + summary: 'unknown user', + }; + } + + try { + const user = await jsonld.compact(id, context); + user.id = id; + return user; + } catch (error) { + console.error(`Error loading user '${id}':`, error); + return { + '@context': context, + id, + type: 'Person', + name: id, + summary: 'failed to load user information', + }; + } } render() { - window.s = this.state; + window.state = this.state; return this.props.render(this.state); } } @@ -213,28 +262,43 @@ class CollapseContainer extends Component { const search = new URLSearchParams(window.location.search); const graph = search.has('graph') ? search.get('graph') : 'lib/graph.json'; const app = ( - ( - ( - <> - ( - - )} - /> - items[id])} - toggleSelected={toggleSelected} - /> - - )} /> - )} /> + { + if (error) { + return ( +
+

{name}

+

{error.toString()}

+
+						
+							{error.stack}
+						
+					
+
+ ); + } + + return ( + ( + <> + ( + + )} + /> + items[id])} + toggleSelected={toggleSelected} + /> + + )} /> + ); + }} /> ); render(app, document.body); -window.app = app; diff --git a/src/ui/Note.js b/src/ui/Note.js index d4ef30a..63905e4 100644 --- a/src/ui/Note.js +++ b/src/ui/Note.js @@ -49,6 +49,14 @@ section > header > .avatar { flex: 0 0 auto; margin: 0.25rem; border-radius: 0.3rem; + + font-size: 1.5rem; + font-weight: bold; + line-height: 2rem; + text-align: center; + text-decoration: none; + background: white; + color: black; } section > header > .avatar img { width: inherit; @@ -78,7 +86,7 @@ section > header > .user { } section > header > .time { - flex: 0 1 auto; + flex: 0 0 auto; font-size: 0.8em; color: #363636; } @@ -94,7 +102,9 @@ const Header = ({ id, user, published, collapsed, onCollapse }) => { return (
- + {user.icon + ? + : "?"} {username} -- cgit v1.2.3