diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.js | 3 | ||||
| -rw-r--r-- | src/graph.js | 205 | ||||
| -rw-r--r-- | src/index.js | 162 | ||||
| -rw-r--r-- | src/layout.js | 48 | ||||
| -rw-r--r-- | src/ui/Attachment.js | 26 | ||||
| -rw-r--r-- | src/ui/Discussion.js | 122 | ||||
| -rw-r--r-- | src/ui/Links.js | 50 | ||||
| -rw-r--r-- | src/ui/Menu.js | 139 | ||||
| -rw-r--r-- | src/ui/Note.js | 249 | ||||
| -rw-r--r-- | src/ui/Selection.js | 93 | ||||
| -rw-r--r-- | src/ui/css.js | 13 | ||||
| -rw-r--r-- | src/ui/index.js | 10 | ||||
| -rw-r--r-- | src/ui/theme.js | 39 | ||||
| -rw-r--r-- | src/viz.es.js | 194 |
14 files changed, 0 insertions, 1353 deletions
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 ( - <article> - <div>loading...</div> - </article> - ); - } - - if (error) { - return ( - <article> - <h1>error loading</h1> - <div> - <p>{error.toString()}</p> - <pre> - <code> - {error.stack} - </code> - </pre> - </div> - </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} - /> - </> - )} /> - ); -}; - -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 = ( - <GraphContainerJSONLD - url={search.get('document')} - render={graphRender} - /> - ); -} else if (search.has('note')) { - app = ( - <GraphContainerMastodon - url={search.get('note')} - render={graphRender} - /> - ); -} else if (search.has('graph')) { - app = ( - <GraphContainerMastodon - url={search.get('graph')} - render={graphRender} - /> - ); -} else { - app = <Menu />; -} - -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 = <video class="attachment" src={url} controls />; - } else if (mediaType.startsWith('image/')) { - content = <img class="attachment" src={url} />; - } - - return content; -} diff --git a/src/ui/Discussion.js b/src/ui/Discussion.js deleted file mode 100644 index 5fef693..0000000 --- a/src/ui/Discussion.js +++ /dev/null @@ -1,122 +0,0 @@ -import { h } from 'preact'; -import { useState, useEffect } from 'preact/hooks'; -import Layout from '../layout'; -import css from './css'; -import { Links } from './Links'; -import { Note } from './Note'; - -css` -article { - position: absolute; - inset: 0; - - overflow: auto; -} - -article > h1 { - display: flex; - align-items: baseline; - - position: sticky; - inset: 0; - bottom: auto; - z-index: 200; - - margin: 0; - padding: 0.75rem 1.5rem; - background: var(--theme-title-bg); - color: var(--theme-title-fg); -} - -article > h1 > span { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -article > h1 .back { - font-size: 0.8em; - color: inherit; - opacity: 0.5; - - margin-left: 1em; -} - -article > div { - position: relative; - margin: 2rem 2rem 6rem; -} -`; - -const tmp = document.createElement('span'); - -export const Discussion = ({ - name, items, collapsed, - toggleCollapsed, toggleSelected, -}) => { - const [{ width, height, positions, links }, setState] = useState({ - width: 0, - height: 0, - positions: {}, - links: [], - }); - - const layout = new Layout(); - const setNodeSize = layout.addNode.bind(layout); - - useEffect(() => { - layout.render().then(setState); - }, [ items, collapsed ]); - - tmp.innerHTML = name; - name = tmp.innerText; - - return ( - <article> - <h1> - <span>{name}</span> - <a class="back" href="?">back</a> - </h1> - <div> - <Links width={width} height={height} links={links} /> - <div> - {Object.values(items).map((item) => { - const isCollapsed = collapsed[item.id]; - const hidden = - item.inReplyTo.every(p => collapsed[p.id]) - && item.replies.every(c => collapsed[c.id]) - && item.inReplyTo.length !== 0; - if (hidden) - return; - - const onlyParent = item.inReplyTo.length === 1 && items[item.inReplyTo[0].id]; - - for (const reply of item.replies) { - if (isCollapsed && collapsed[reply.id]) - continue; - - layout.addLink(item.id, reply.id); - } - - return ( - <Note - {...item} - smallHeader={ - onlyParent // only one parent post - && onlyParent.attributedTo === item.attributedTo // posted by same user - && onlyParent.published === item.published // in the same second - } - collapsed={isCollapsed} - position={positions[item.id]} - onCollapse={toggleCollapsed} - onSelect={toggleSelected} - onSize={setNodeSize} - /> - ); - })} - </div> - </div> - </article> - ); -}; - diff --git a/src/ui/Links.js b/src/ui/Links.js deleted file mode 100644 index 0a0b1ef..0000000 --- a/src/ui/Links.js +++ /dev/null @@ -1,50 +0,0 @@ -import { h } from 'preact'; -import { useRef, useLayoutEffect } from 'preact/hooks'; - -export const Links = ({ links, ...props }) => { - const canvas = useRef(null); - - useLayoutEffect(() => { - const ctx = canvas.current.getContext('2d'); - ctx.lineWidth = 4; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.strokeStyle = '#696969'; - ctx.fillStyle = '#696969'; - ctx.shadowBlur = 2; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 4; - - ctx.clearRect(0, 0, canvas.current.width, canvas.current.height); - - for (const points of links) { - ctx.shadowColor = 'rgba(0,0,0, 0.7)'; - ctx.beginPath(); - ctx.moveTo(...points[1]); - for (let i = 4; i < points.length; i += 3) { - const [ax, ay] = points[i - 2]; - const [bx, by] = points[i - 1]; - const [x, y] = points[i]; - ctx.bezierCurveTo(ax, ay, bx, by, x, y); - } - ctx.stroke(); - - const [lx, ly] = points[points.length - 1]; - const [tx, ty] = points[0]; - const [dx, dy] = [tx - lx, ty - ly]; - const [hx, hy] = [dy*0.7, dx*-0.7]; - - ctx.shadowColor = 'transparent'; - ctx.beginPath(); - ctx.moveTo(lx + dx*0.8, ly + dy*0.8); - ctx.lineTo(lx - dx*0.5 + hx, ly - dy*0.5 + hy); - ctx.lineTo(lx - dx*0.5 - hx, ly - dy*0.5 - hy); - ctx.fill(); - } - }); - - return ( - <canvas {...props} ref={canvas} /> - ); -}; - diff --git a/src/ui/Menu.js b/src/ui/Menu.js deleted file mode 100644 index 6f32280..0000000 --- a/src/ui/Menu.js +++ /dev/null @@ -1,139 +0,0 @@ -import { h, Fragment } from 'preact'; -import { useState, useEffect } from 'preact/hooks'; -import cn from 'classnames'; -import { API_PREFIX } from '../config'; -import css from './css'; - -const fetchJSON = async (url) => { - const res = await fetch(API_PREFIX + url, { credentials: 'include' }); - if (res.status !== 200) - throw new Error("wrong status"); - - return res.json(); -}; - -css` -a.discussion { - display: flex; - - padding: 0.25rem 0.5rem; - text-size: 2rem; - line-height: 2rem; - text-decoration: none; - - border-radius: 0.3rem; - - color: var(--theme-note-fg); - background: var(--theme-note-bg); -} - -a.discussion .access { - opacity: 0.5; - margin-right: 0.75rem; -} - -a.discussion .name { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -a.discussion > a { - margin-left: 0.75rem; - text-decoration: underline; - color: inherit; - opacity: 0.5; - - transition: opacity 0.3s; -} -a.discussion > a:hover { - opacity: 1; -} -`; - -const DiscussionLink = ({ id, name, url, attributedTo, user }) => { - const writable = attributedTo && attributedTo.indexOf(user) > -1; - - return ( - <a class="discussion" href={`?document=${id}`}> - <span class="access">{writable ? 'W' : 'R'}</span> - <span class="name">{name}</span> - {url && ( - <a href={url.href}>ext</a> - )} - </a> - ); -}; - -css` -ul.discussions { - list-style: none; - padding: 0; - - width: 26rem; -} - -ul.discussions > li { - margin: 0.2rem 0; -} -`; - -const DiscussionList = ({ discussions, user }) => { - return ( - <ul class="discussions"> - {discussions.map((discussion) => ( - <li> - <DiscussionLink {...discussion} user={user} /> - </li> - ))} - </ul> - ); -}; - -css` -div.menu { - margin: 1rem 2rem; -} -`; -export const Menu = () => { - const [user, setUser] = useState(null); - const [discussions, setDiscussions] = useState([]); - - useEffect(() => { - fetchJSON('/discdag/list') - .then(({ user, discussions }) => { - setUser(user ?? null); - setDiscussions(discussions); - }); - }, []); - - if (user === null) { - return ( - <div class="menu"> - <h2>Discussions</h2> - loading... - </div> - ); - } - - return ( - <div class="menu"> - <h2>Discussions</h2> - {/*user && user.name !== 'Guest' - ? ( - <> - <span>logged in as {user.name}</span> - <button>log out</button> - </> - ) - : ( - <> - <span>not logged in</span> - <button>log in</button> - </> - )*/} - <DiscussionList discussions={discussions} user={user?.id} /> - </div> - ); -}; diff --git a/src/ui/Note.js b/src/ui/Note.js deleted file mode 100644 index 16b31cb..0000000 --- a/src/ui/Note.js +++ /dev/null @@ -1,249 +0,0 @@ -import { h } from 'preact'; -import { useRef, useLayoutEffect } from 'preact/hooks'; -import ColorHash from 'color-hash'; -import { formatRelative } from 'date-fns'; -import cn from 'classnames'; -import css from './css'; -import { Attachment } from './Attachment'; - -const now = new Date(); -const Time = ({ time }) => { - const dt = new Date(time); - return formatRelative(dt, now); -}; - -css` -.flex-space { - flex: 1; - margin: 0; -} -`; -const Space = (props) => <div class="flex-space" {...props} />; - -css` -section > header { - display: flex; - flex-direction: row; - - height: 1.75rem; - - color: var(--theme-header-fg); - background: var(--theme-header-bg); -} - -section > header.small { - height: 0.5rem; -} - -section > header > * { - margin: 0 0.35rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.75rem; -} - -section > header > .avatar { - height: 1.25rem; - width: 1.25rem; - flex: 0 0 auto; - margin: 0.25rem; - border-radius: 0.2rem; - - font-size: 1.25rem; - font-weight: bold; - line-height: 1.25rem; - text-align: center; - text-decoration: none; - background: white; - color: black; -} -section > header > .avatar img { - width: inherit; - height: inherit; -} -section > header > .collapse { - height: 1rem; - width: 1rem; - flex: 0 0 auto; - margin: 0.3rem; - border-radius: 0.2rem; - border: 0.05rem solid var(--theme-header-fg); - - line-height: 0.8rem; - text-align: center; - text-decoration: none; - text-weight: bold; - - transition: background 0.3s; -} -section > header > .collapse:hover { - background: var(--theme-note-bg); -} - -section > header > .user { - flex: 0 0.5 auto; -} - -section > header > .time { - flex: 0 0 auto; - font-size: 0.8em; - color: #363636; -} -`; -const Header = ({ id, user, published, collapsed, onCollapse }) => { - const username = user.name || user.preferredUsername; - - const onClick = onCollapse && ((e) => { - e.preventDefault(); - onCollapse(id); - }); - - return ( - <header> - <a class="avatar" href={user.id}> - {user.icon - ? <img src={new URL(user.icon.url, user.id)} /> - : "?"} - </a> - <span class="user">{username}</span> - <Space /> - <a class="time" href={id}> - <Time time={published} /> - </a> - <a class="collapse" href="#" onClick={onClick}> - {collapsed ? '+' : '-'} - </a> - </header> - ); -}; - -css` -section { - display: flex; - flex-direction: column; - justify-content: space-between; - - width: 15rem; - - overflow: hidden; - color: var(--theme-note-fg); - background: var(--theme-note-bg); - box-shadow: rgba(0,0,0, 0.7) 2px 4px 5px; - - border-radius: 0.3rem; -} - -section.type-Tombstone { - color: var(--theme-note-bg); - background: var(--theme-note-fg); -} - -section > main { - position: relative; - padding: 0.5rem; - overflow: hidden; - font-family: serif; -} - -section > main > p:first-child { - margin-top: 0; -} -section > main > p:last-child { - margin-bottom: 0; -} - -section.tombstone > main { - font-family: inherit; -} - -section.hidden { - display: none; -} - -section.ellipsis > main { - max-height: 7em; -} -section.ellipsis > .attachment { - display: none; -} -section.ellipsis > main::after { - position: absolute; - display: block; - content: ''; - - pointer-events: none; - - left: 0; - right: 0; - bottom: 0; - height: 1.25em; - background: linear-gradient(0deg, var(--theme-note-bg) 5%, var(--theme-note-bg-trans) 100%); -} -section.type-Tombstone.ellipsis > main::after { - background: linear-gradient(0deg, var(--theme-note-fg) 5%, var(--theme-note-fg-trans) 100%); -} - -section.collapsed > main { - max-height: 2em; -} -`; - -const colors = new ColorHash({ lightness: 0.8 }); -export const Note = ({ - id, type, attributedTo, published, content, attachment, - smallHeader = false, collapsed = false, ellipsis = false, position, - onCollapse, onSelect, onSize, -}) => { - ellipsis ||= collapsed; - attachment ||= []; - if (!Array.isArray(attachment)) - attachment = [attachment]; - - const backgroundColor = attributedTo.color || colors.hex(attributedTo.id); - - const onClick = onSelect && ((e) => { - e.preventDefault(); - onSelect(id); - }); - - const root = useRef(null); - onSize && useLayoutEffect(() => { - onSize(id, root.current.offsetWidth, root.current.offsetHeight); - }); - - return ( - <section - style={{ - '--theme-header-bg': backgroundColor, - 'position': position && 'absolute', - 'left': position && `${position[0] - root.current.offsetWidth / 2}px`, - 'top': position && `${position[1] - root.current.offsetHeight / 2}px`, - 'visibility': onSize && !position ? 'hidden' : 'visible', - }} - class={cn( - `type-${type}`, - collapsed && 'collapsed', - ellipsis && 'ellipsis', - )} - data-id={id} - ref={root} - > - {smallHeader - ? <header className="small" onClick={onCollapse} /> - : <Header - id={id} - user={attributedTo} - published={published} - collapsed={collapsed} - onCollapse={onCollapse} - /> - } - <main - innerHTML={content} - onClick={onClick} - /> - {attachment.map(a => <Attachment {...a} />)} - </section> - ); -}; diff --git a/src/ui/Selection.js b/src/ui/Selection.js deleted file mode 100644 index d8dfa88..0000000 --- a/src/ui/Selection.js +++ /dev/null @@ -1,93 +0,0 @@ -import { h } from 'preact'; -import { useState, useEffect } from 'preact/hooks'; -import cn from 'classnames'; -import css from './css'; -import { Note } from './Note'; - -css` -.drawer { - position: fixed; - inset: 0; - top: auto; - - z-index: 200; - background: var(--theme-note-fg); - - /* wtf webkit */ - -webkit-backface-visibility: hidden; -} - -.drawer .handle { - height: 1.5rem; - margin-bottom: 1rem; -} - -.drawer .handle button { - display: block; - - height: 1rem; - line-height: 1rem; - margin: -1rem auto 0; - padding: 0.25rem; - background: var(--theme-note-bg); - border-radius: 0.2rem; -} - -.drawer .scroller { - display: flex; - overflow: auto; - width: 100%; - - transition: max-height 0.3s; -} - -.drawer .contents { - display: flex; - - align-items: flex-start; - - gap: 1rem; - padding: 0 1.5rem; -} - -.drawer .contents > * { - flex: 0 0 auto; -} -`; - -export const Drawer = ({ height, children }) => { - const [ hidden, setHidden ] = useState(true); - - return ( - <div class="drawer"> - <div class="handle"> - <button onClick={() => setHidden(!hidden)}> - ### - </button> - </div> - <div - class="scroller" - style={{ - height, - 'max-height': hidden ? '1rem' : height, - }} - > - <div class="contents"> - {children} - </div> - </div> - </div> - ); -}; - -export const Selection = ({ items, toggleSelected }) => ( - <Drawer height="8.55rem" > - {items.map((item) => ( - <Note - {...item} - ellipsis - onSelect={toggleSelected} - /> - ))} - </Drawer> -); diff --git a/src/ui/css.js b/src/ui/css.js deleted file mode 100644 index 9089f1e..0000000 --- a/src/ui/css.js +++ /dev/null @@ -1,13 +0,0 @@ -export default ([ code ]) => { - const style = document.createElement('style'); - document.head.appendChild(style); - style.appendChild(document.createTextNode('')); - - let i = 0; - for (const v of code.split('}')) { - if (v.indexOf('{') < 0) continue; - style.sheet.insertRule(v + '}', i++); - } - - return style; -}; diff --git a/src/ui/index.js b/src/ui/index.js deleted file mode 100644 index f9ebf7f..0000000 --- a/src/ui/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import './theme'; -import { Discussion } from './Discussion'; -import { Selection } from './Selection'; -import { Menu } from './Menu'; - -export { - Discussion, - Selection, - Menu, -}; diff --git a/src/ui/theme.js b/src/ui/theme.js deleted file mode 100644 index 8977791..0000000 --- a/src/ui/theme.js +++ /dev/null @@ -1,39 +0,0 @@ -import css from './css'; - -css` -html { - margin: 0; - padding: 0; -} - -body { - --theme-backdrop: #1e1e1e; - --theme-fg: #eeeeee; - --theme-note-bg: #eeeeee; - --theme-note-fg: #363636; - --theme-note-bg-trans: rgba(238,238,238,0); - --theme-note-fg-trans: rgba(54,54,54,0); - --theme-header-fg: #121212; - - --theme-title-fg: #eeeeee; - --theme-title-bg: #363636; -} - -body.darktheme { - --theme-backdrop: #1e1e1e; - --theme-note-bg: #363636; - --theme-note-fg: #eeeeee; - --theme-note-bg-trans: rgba(54,54,54,0); - --theme-note-fg-trans: rgba(238,238,238,0); - --theme-header-fg: #121212; -} - -body { - color: var(--theme-fg); - background: var(--theme-backdrop); - font-family: sans-serif; - - margin: 0; - padding: 0; -} -`; diff --git a/src/viz.es.js b/src/viz.es.js deleted file mode 100644 index 56f6a98..0000000 --- a/src/viz.es.js +++ /dev/null @@ -1,194 +0,0 @@ -/* -Viz.js 2.1.2 (Graphviz 2.40.1, Expat {{EXPAT_VERSION}}, Emscripten 1.37.36) -Copyright (c) 2014-2018 Michael Daines -Licensed under MIT license - -This distribution contains other software in object code form: - -Graphviz -Licensed under Eclipse Public License - v 1.0 -http://www.graphviz.org - -Expat -Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper -Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers. -Licensed under MIT license -http://www.libexpat.org - -zlib -Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler -http://www.zlib.net/zlib_license.html -*/ -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; -} : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; -}; - -var classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -}; - -var createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; -}(); - -var _extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; -}; - -var WorkerWrapper = function () { - function WorkerWrapper(worker) { - var _this = this; - - classCallCheck(this, WorkerWrapper); - - this.worker = worker; - this.listeners = []; - this.nextId = 0; - - this.worker.addEventListener('message', function (event) { - var id = event.data.id; - var error = event.data.error; - var result = event.data.result; - - _this.listeners[id](error, result); - delete _this.listeners[id]; - }); - } - - createClass(WorkerWrapper, [{ - key: 'render', - value: function render(src, options) { - var _this2 = this; - - return new Promise(function (resolve, reject) { - var id = _this2.nextId++; - - _this2.listeners[id] = function (error, result) { - if (error) { - reject(new Error(error.message, error.fileName, error.lineNumber)); - return; - } - resolve(result); - }; - - _this2.worker.postMessage({ id: id, src: src, options: options }); - }); - } - }]); - return WorkerWrapper; -}(); - -var ModuleWrapper = function ModuleWrapper(module, render) { - classCallCheck(this, ModuleWrapper); - - var instance = module(); - this.render = function (src, options) { - return new Promise(function (resolve, reject) { - try { - resolve(render(instance, src, options)); - } catch (error) { - reject(error); - } - }); - }; -}; - -// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding - -var Viz = function () { - function Viz() { - var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - workerURL = _ref3.workerURL, - worker = _ref3.worker, - Module = _ref3.Module, - render = _ref3.render; - - classCallCheck(this, Viz); - - if (typeof workerURL !== 'undefined') { - this.wrapper = new WorkerWrapper(new Worker(workerURL)); - } else if (typeof worker !== 'undefined') { - this.wrapper = new WorkerWrapper(worker); - } else if (typeof Module !== 'undefined' && typeof render !== 'undefined') { - this.wrapper = new ModuleWrapper(Module, render); - } else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') { - this.wrapper = new ModuleWrapper(Viz.Module, Viz.render); - } else { - throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.'); - } - } - - createClass(Viz, [{ - key: 'renderString', - value: function renderString(src) { - var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, - _ref4$format = _ref4.format, - format = _ref4$format === undefined ? 'svg' : _ref4$format, - _ref4$engine = _ref4.engine, - engine = _ref4$engine === undefined ? 'dot' : _ref4$engine, - _ref4$files = _ref4.files, - files = _ref4$files === undefined ? [] : _ref4$files, - _ref4$images = _ref4.images, - images = _ref4$images === undefined ? [] : _ref4$images, - _ref4$yInvert = _ref4.yInvert, - yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert, - _ref4$nop = _ref4.nop, - nop = _ref4$nop === undefined ? 0 : _ref4$nop; - - for (var i = 0; i < images.length; i++) { - files.push({ - path: images[i].path, - data: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg width="' + images[i].width + '" height="' + images[i].height + '"></svg>' - }); - } - - return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert, nop: nop }); - } - }, { - key: 'renderJSONObject', - value: function renderJSONObject(src) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var format = options.format; - - - if (format !== 'json' || format !== 'json0') { - format = 'json'; - } - - return this.renderString(src, _extends({}, options, { format: format })).then(function (str) { - return JSON.parse(str); - }); - } - }]); - return Viz; -}(); - -export default Viz; |
