From 083c63cec425bb2c8e6f4f9a90c3ddfec2273b36 Mon Sep 17 00:00:00 2001 From: s-ol Date: Fri, 5 Feb 2021 20:06:26 +0100 Subject: reimplement collapsing --- package.json | 1 + src/index.js | 110 +++++++++++++++++++++++++++++++++------------------------ src/ui/Note.js | 14 +++++--- yarn.lock | 5 +++ 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index d28dd72..4320201 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ ] }, "dependencies": { + "classnames": "^2.2.6", "color-hash": "^1.0.3", "core-js": "^3.8.3", "date-fns": "^2.16.1", 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 (
@@ -118,14 +152,20 @@ const App = ({ discussion }) => {
{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 = } />; +const app = ( + ( + ( + + )} + /> + )} /> +); 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 (
{smallHeader - ?
- :
+ ?
+ :
}
{content} diff --git a/yarn.lock b/yarn.lock index f3d27aa..95438a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1527,6 +1527,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" -- cgit v1.2.3