diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.js | 110 | ||||
| -rw-r--r-- | src/ui/Note.js | 14 |
2 files changed, 73 insertions, 51 deletions
diff --git a/src/index.js b/src/index.js index 090b67a..4b46ac7 100644 --- a/src/index.js +++ b/src/index.js @@ -95,8 +95,42 @@ class GraphContainer extends Component { } } -const App = ({ discussion }) => { - const { name, items } = discussion; +class CollapseContainer extends Component { + state = {}; + + 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)); + } + + 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 App = ({ name, items, collapsed, toggleCollapsed }) => { const [{ width, height, positions, links }, setState] = useState({ width: 0, height: 0, @@ -109,7 +143,7 @@ const App = ({ discussion }) => { useEffect(() => { layout.render().then(setState); - }, [ items ]); + }, [ items, collapsed ]); return ( <article> @@ -118,14 +152,20 @@ const App = ({ discussion }) => { <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]); + if (hidden) + return; + const onlyParent = item.inReplyTo.length === 1 && items[item.inReplyTo[0].id]; for (const reply of item.replies) { - const to = items[reply.id]; - if (item.collapsed && to.collapsed) + if (isCollapsed && collapsed[reply.id]) continue; - layout.addLink(item.id, to.id); + layout.addLink(item.id, reply.id); } return ( @@ -136,8 +176,9 @@ const App = ({ discussion }) => { && onlyParent.attributedTo === item.attributedTo // posted by same user && onlyParent.published === item.published // in the same second } + collapsed={isCollapsed} position={positions[item.id]} - onHide={hideNote} + onHide={toggleCollapsed} onSize={setNodeSize} /> ); @@ -149,45 +190,20 @@ const App = ({ discussion }) => { }; const graph = document.location.hash ? document.location.hash.substr(1) : 'lib/graph.json'; -const app = <GraphContainer graph={graph} render={discussion => <App discussion={discussion} /> } />; +const app = ( + <GraphContainer graph={graph} render={({ name, items }) => ( + <CollapseContainer + items={items} + render={({ collapsed, toggleCollapsed }) => ( + <App + name={name} + items={items} + collapsed={collapsed} + toggleCollapsed={toggleCollapsed} + /> + )} + /> + )} /> +); render(app, document.body); window.app = app; - -// update collapsed nodes starting with id -// returns list of (potentially) affected nodes -const updateCollapsed = (graph, node, updated=[]) => { - updated.push(node.data.id); - - if (node.collapsed !== 'explicit') { - node.collapsed = - node.data.inReplyTo.length - && node.data.inReplyTo.every(p => graph[p.id].collapsed); - } - - for (const child of node.data.replies) - updateCollapsed(graph, graph[child.id], updated); - - return updated; -}; - -const hideNote = (e) => { - const clicked = graph[e.currentTarget.parentElement.dataset.id]; - clicked.collapsed = clicked.collapsed ? false : 'explicit'; - - for (const id of updateCollapsed(graph, clicked)) { - const node = graph[id]; - - if (node.collapsed) { - const noParents = node.data.inReplyTo.every(p => graph[p.id].collapsed); - const noChildren = node.data.replies.every(c => graph[c.id].collapsed); - node.dom.className = noParents && noChildren ? 'hidden' : 'collapsed'; - } else { - node.dom.className = ''; - } - } - - layoutNodes(graph) - .then(() => clicked.dom.scrollIntoView({ block: 'nearest', inline: 'nearest' })) - .catch(err => console.error(err)); -}; - diff --git a/src/ui/Note.js b/src/ui/Note.js index b7387a8..36691d4 100644 --- a/src/ui/Note.js +++ b/src/ui/Note.js @@ -2,6 +2,7 @@ import { render, h, Fragment } 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'; const now = new Date(); @@ -121,7 +122,7 @@ section.collapsed > main { const colors = new ColorHash({ lightness: 0.8 }); export const Note = ({ id, type, attributedTo, published, content, - smallHeader = false, position, + smallHeader = false, collapsed = false, position, onHide, onSize, }) => { const backgroundColor = attributedTo.color || colors.hex(attributedTo.id); @@ -131,6 +132,11 @@ export const Note = ({ onSize(id, root.current.offsetWidth, root.current.offsetHeight); }); + const onClickHeader = (e) => { + e.preventDefault(); + onHide(id); + }; + return ( <section style={{ @@ -139,13 +145,13 @@ export const Note = ({ 'top': position && `${position[1] - root.current.offsetHeight / 2}px`, 'visibility': position ? 'visible' : 'hidden', }} - className={`type-${type}`} + class={cn(`type-${type}`, collapsed && 'collapsed')} data-id={id} ref={root} > {smallHeader - ? <header className="small" onClick={onHide} /> - : <Header onClick={onHide} user={attributedTo} published={published} /> + ? <header className="small" onClick={onClickHeader} /> + : <Header onClick={onClickHeader} user={attributedTo} published={published} /> } <main> {content} |
