aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2022-01-12 09:17:26 +0000
committers-ol <s+removethis@s-ol.nu>2022-01-12 09:33:13 +0000
commit0d5e63dde3da4116a49f6c83eb6094d68a41b914 (patch)
tree4d7c347d8ef39ab92502a289a83fa237efef9648
parentcollapse using ellipsis, hide media (diff)
downloadfedidag-0d5e63dde3da4116a49f6c83eb6094d68a41b914.tar.gz
fedidag-0d5e63dde3da4116a49f6c83eb6094d68a41b914.zip
handle errors while loading more gracefully
-rw-r--r--src/index.js156
-rw-r--r--src/ui/Note.js14
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 = (
- <GraphContainer graph={graph} render={({ name, items }) => (
- <SelectionContainer render={({ selection, toggleSelected }) => (
- <>
- <CollapseContainer
- items={items}
- render={({ collapsed, toggleCollapsed }) => (
- <Discussion
- name={name}
- items={items}
- collapsed={collapsed}
- toggleCollapsed={toggleCollapsed}
- toggleSelected={toggleSelected}
- />
- )}
- />
- <Selection
- items={selection.map(id => items[id])}
- toggleSelected={toggleSelected}
- />
- </>
- )} />
- )} />
+ <GraphContainer graph={graph} render={({ name, items, error }) => {
+ if (error) {
+ return (
+ <article>
+ <h1>{name}</h1>
+ <p>{error.toString()}</p>
+ <pre>
+ <code>
+ {error.stack}
+ </code>
+ </pre>
+ </article>
+ );
+ }
+
+ return (
+ <SelectionContainer render={({ selection, toggleSelected }) => (
+ <>
+ <CollapseContainer
+ items={items}
+ render={({ collapsed, toggleCollapsed }) => (
+ <Discussion
+ name={name}
+ items={items}
+ collapsed={collapsed}
+ toggleCollapsed={toggleCollapsed}
+ toggleSelected={toggleSelected}
+ />
+ )}
+ />
+ <Selection
+ items={selection.map(id => 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 (
<header>
<a class="avatar" href={user.id}>
- <img src={new URL(user.icon.url, user.id)} />
+ {user.icon
+ ? <img src={new URL(user.icon.url, user.id)} />
+ : "?"}
</a>
<span class="user">{username}</span>
<Space />