From 0c98276d324db649c4a15be7859a501588257972 Mon Sep 17 00:00:00 2001 From: s-ol Date: Thu, 13 Jan 2022 18:27:20 +0100 Subject: move everything into client/ --- src/config.js | 3 - src/graph.js | 205 ------------------------------------------ src/index.js | 162 --------------------------------- src/layout.js | 48 ---------- src/ui/Attachment.js | 26 ------ src/ui/Discussion.js | 122 ------------------------- src/ui/Links.js | 50 ----------- src/ui/Menu.js | 139 ---------------------------- src/ui/Note.js | 249 --------------------------------------------------- src/ui/Selection.js | 93 ------------------- src/ui/css.js | 13 --- src/ui/index.js | 10 --- src/ui/theme.js | 39 -------- src/viz.es.js | 194 --------------------------------------- 14 files changed, 1353 deletions(-) delete mode 100644 src/config.js delete mode 100644 src/graph.js delete mode 100644 src/index.js delete mode 100644 src/layout.js delete mode 100644 src/ui/Attachment.js delete mode 100644 src/ui/Discussion.js delete mode 100644 src/ui/Links.js delete mode 100644 src/ui/Menu.js delete mode 100644 src/ui/Note.js delete mode 100644 src/ui/Selection.js delete mode 100644 src/ui/css.js delete mode 100644 src/ui/index.js delete mode 100644 src/ui/theme.js delete mode 100644 src/viz.es.js (limited to 'src') diff --git a/src/config.js b/src/config.js deleted file mode 100644 index 91d6231..0000000 --- a/src/config.js +++ /dev/null @@ -1,3 +0,0 @@ -export const PUBLIC_URL = process.env.PUBLIC_URL || `${location.origin}`; -export const API_PREFIX = process.env.API_PREFIX || `${location.origin}`; -export const CORS_PREFIX = process.env.CORS_PREFIX || `${API_PREFIX}/remote`; diff --git a/src/graph.js b/src/graph.js deleted file mode 100644 index 7fe842c..0000000 --- a/src/graph.js +++ /dev/null @@ -1,205 +0,0 @@ -import { Component } from 'preact'; -import * as jsonld from 'jsonld'; -import { CORS_PREFIX } from './config'; - -const cors = (url) => `${CORS_PREFIX}/${url}`; - -function wrapCache(target) { - const orig = target.descriptor.value; - - target.descriptor.value = function (id, ...args) { - this.cache[id] ||= orig.call(this, id, ...args); - return this.cache[id]; - }; - - return target; -}; - -const context = [ - 'https://www.w3.org/ns/activitystreams', - { - replies: { '@id': 'as:replies', '@container': '@set' }, - inReplyTo: { '@id': 'as:inReplyTo', '@container': '@set' }, - items: { '@id': 'as:items', '@type': '@id', '@container': '@set' }, - }, -]; - -export class GraphContainer extends Component { - state = { - loading: true, - name: "loading…", - items: {}, - error: null, - }; - - cache = {}; - - componentDidMount() { - this.componentDidUpdate({}); - } - - componentDidUpdate(prevProps) { - if (prevProps.url === this.props.url) return; - - this.loadData(this.props.url) - .catch(error => { - console.error(`Error loading ${this.props.url}:`, error); - this.setState({ - loading: false, - error, - }); - }); - } - - @wrapCache - async loadUser(id) { - 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.state = this.state; - return this.props.render(this.state); - } -} - -export class GraphContainerMastodon extends GraphContainer { - @wrapCache - async loadCollection(id, ...args) { - const collection = await jsonld.compact(cors(id), context); - let items = []; - - const addItems = (i) => { - if (Array.isArray(i)) - items = items.concat(i); - else if (i) - items.push(i); - }; - - // discover items - if (collection.items) { - addItems(collection.items); - } else { - let page = collection.first; - const seen = []; - while (page && seen.indexOf(page.id) < 0) { - seen.push(page.id); - addItems(page.items); - - page = page.next && await jsonld.compact(cors(page.next), context); - } - } - - // dereference items - return await Promise.all(items.map(item => this.loadNote(item.id ?? item, ...args))); - } - - @wrapCache - 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, - 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); - items[item.id] = item; - return item; - } - - async loadData(url) { - const items = {}; - const root = await this.loadNote(url, items, []); - - this.setState({ - name: root.name || root.content, - items, - loading: false, - }); - } -} - -export class GraphContainerJSONLD extends GraphContainer { - async loadData(url) { - const response = await fetch(url, { - headers: { 'Accept': 'application/ld+json, application/json' }, - credentials: 'include', - }); - - const discussion = await jsonld.frame( - await response.json(), - { - '@context': context, - type: 'Document', - first: { '@embed': '@never' }, - items: { - context: { '@embed': '@never' }, - replies: { '@embed': '@never' }, - inReplyTo: { '@embed': '@never' }, - attributedTo: { '@embed': '@always' }, - }, - }, - { omitGraph: true } - ); - - const indexedItems = {}; - await Promise.all(discussion.items.map(async (item) => { - item.attributedTo = await this.loadUser(item.attributedTo); - indexedItems[item.id] = item; - })); - - discussion.items = indexedItems; - - this.setState({ - ...discussion, - loading: false, - }); - } -} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index a30dbf1..0000000 --- a/src/index.js +++ /dev/null @@ -1,162 +0,0 @@ -import 'preact/debug'; -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; -import { h, Fragment, Component, render } from 'preact'; -import { GraphContainerJSONLD, GraphContainerMastodon } from './graph'; -import { Menu, Discussion, Selection } from './ui'; - -class SelectionContainer extends Component { - state = { - selection: [], - }; - - toggle = (id) => { - const { selection } = this.state; - if (selection.indexOf(id) < 0) - this.setState({ selection: [ ...selection, id ] }); - else - this.setState({ selection: selection.filter(i => i !== id) }); - } - - render() { - return this.props.render({ - ...this.state, - toggleSelected: this.toggle, - }); - } -} - -class CollapseContainer extends Component { - /** - * Contains the collapse-state for each node. - * - * - `false`: visible - * - `true`: implicitly hidden - * - `'explicit'`: explicitly hidden - */ - state = {}; - - /** - * Toggle a node's collapse-state. - * - `false` → `'explicit'` - * - `true`, `'explicit'` → `false` - */ - toggle = (id) => { - const { items } = this.props; - - const state = Object.assign({}, this.state); - state[id] = state[id] ? false : 'explicit'; - - this.setState(this.update(items[id], state)); - } - - /** - * Recursively update child node's collapse-state. - * - * If a node has parents and they are all collapsed, - * mark that node as implicitly hidden (`true`). - * - * Then update all of that node's children. - */ - update(node, state) { - const { items } = this.props; - - if (state[node.id] !== 'explicit') { - state[node.id] = - node.inReplyTo.length - && node.inReplyTo.every(p => state[p.id]); - } - - for (const child of node.replies) - this.update(items[child.id], state); - - return state; - } - - render() { - return this.props.render({ - collapsed: this.state, - toggleCollapsed: this.toggle, - }); - } -} - -const graphRender = ({ loading, error, name, items }) => { - if (loading) { - return ( -
-
loading...
-
- ); - } - - if (error) { - return ( -
-

error loading

-
-

{error.toString()}

-
-						
-							{error.stack}
-						
-					
-
-
- ); - } - - return ( - ( - <> - ( - - )} - /> - items[id])} - toggleSelected={toggleSelected} - /> - - )} /> - ); -}; - -const search = new URLSearchParams(window.location.search); -const graph = search.has('graph') ? search.get('graph') : 'lib/graph.json'; - -let app; -if (search.has('document')) { - app = ( - - ); -} else if (search.has('note')) { - app = ( - - ); -} else if (search.has('graph')) { - app = ( - - ); -} else { - app = ; -} - -render(app, document.body); diff --git a/src/layout.js b/src/layout.js deleted file mode 100644 index c9c2a4d..0000000 --- a/src/layout.js +++ /dev/null @@ -1,48 +0,0 @@ -import Viz from './viz.es'; - -const toNumber = (i) => Number(i); -const gv = new Viz({ workerURL: 'lib/lite.render.js' }); - -export default class Layout { - constructor() { - this.src = 'digraph { node [shape="rectangle"];\n'; - } - - addNode(id, width, height) { - this.src += ` "${id}" [fixedsize=true; width=${width / 72}; height=${height / 72}];\n`; - } - - addLink(from, to) { - this.src += ` "${from}" -> "${to}";\n`; - } - - async render() { - this.src += '}'; - - const info = await gv.renderJSONObject(this.src, { format: 'json0' }); - info.objects ||= []; - info.edges ||= []; - const [width, height] = info.bb.split(',').map(toNumber).slice(2); - - const positions = {}; - for (const object of info.objects) { - const [x, y] = object.pos.split(',').map(toNumber); - positions[object.name] = [ x, height - y ]; - } - - const links = info.edges.map((edge) => { - const pos = edge.pos.slice(2); - return pos.split(' ').map(p => { - const [x, y] = p.split(','); - return [toNumber(x), height - toNumber(y)]; - }); - - }); - - return { - width, height, - positions, - links, - }; - } -} diff --git a/src/ui/Attachment.js b/src/ui/Attachment.js deleted file mode 100644 index 3c07ee5..0000000 --- a/src/ui/Attachment.js +++ /dev/null @@ -1,26 +0,0 @@ -import { h } from 'preact'; -import css from './css'; - -css` -.attachment { - margin: 1rem; - - height: 7rem; - object-fit: contain; -} - -.attachment + .attachment { - margin-top: 0; -} -`; - -export const Attachment = ({ type, mediaType, url, style }) => { - let content; - if (mediaType.startsWith('video/')) { - content =