graph-store: make JSON conversion invertible

This commit is contained in:
Liam Fitzgerald 2020-12-02 13:17:49 +10:00
parent 9ee62a2e55
commit 54c469ecfa
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
12 changed files with 142 additions and 107 deletions

View File

@ -198,14 +198,17 @@
++ graph
|= g=^graph
^- json
:- %a
%+ turn (tap:orm g)
%- pairs
%+ turn
(tap:orm g)
|= [a=atom n=^node]
^- json
:- %a
:~ (index [a]~)
(node n)
==
^- [@t json]
:_ (node n)
=/ idx
(numb a)
?> ?=(%n -.idx)
p.idx
::
++ node
|= n=^node
^- json
@ -222,14 +225,15 @@
++ nodes
|= m=(map ^index ^node)
^- json
:- %a
%- pairs
%+ turn ~(tap by m)
|= [n=^index o=^node]
^- json
:- %a
:~ (index n)
(node o)
==
^- [@t json]
:_ (node o)
=/ idx
(index n)
?> ?=(%s -.idx)
p.idx
::
++ indices
|= i=(set ^index)
@ -310,12 +314,12 @@
==
::
++ internal-graph
^- $-(json ^internal-graph)
%- of
:~ [%empty ul]
[%graph graph]
==
::
|= jon=json
^- ^internal-graph
?~ jon
[%empty ~]
[%graph (graph jon)]
::
++ post
%- ot
:~ [%author (su ;~(pfix sig fed:ag))]

View File

@ -4,7 +4,7 @@ import { Patp, Path, PatpNoSig } from '~/types/noun';
import _ from 'lodash';
import {makeResource, resourceFromPath} from '../lib/group';
import {GroupPolicy, Enc, Post, NodeMap, Content} from '~/types';
import { numToUd, unixToDa, decToUd } from '~/logic/lib/util';
import { numToUd, unixToDa, decToUd, deSig } from '~/logic/lib/util';
export const createBlankNodeWithChildPost = (
parentIndex: string = '',
@ -24,7 +24,7 @@ export const createBlankNodeWithChildPost = (
hash: null,
signatures: []
},
children: { empty: null }
children: null
};
return {
@ -36,12 +36,18 @@ export const createBlankNodeWithChildPost = (
hash: null,
signatures: []
},
children: {
graph: childGraph
}
children: childGraph
};
};
function markPending(nodes: any) {
_.forEach(nodes, node => {
node.post.author = deSig(node.post.author);
node.post.pending = true;
markPending(node.children || {});
});
}
export const createPost = (
contents: Content[],
parentIndex: string = '',
@ -182,40 +188,34 @@ export default class GraphApi extends BaseApi<StoreState> {
addPost(ship: Patp, name: string, post: Post) {
let nodes = {};
const resource = { ship, name };
nodes[post.index] = {
post,
children: { empty: null }
children: null
};
return this.hookAction(ship, {
'add-nodes': {
resource,
nodes
}
});
return this.addNodes(ship, name, nodes);
}
addNode(ship: Patp, name: string, node: Object) {
let nodes = {};
const resource = { ship, name };
nodes[node.post.index] = node;
return this.hookAction(ship, {
'add-nodes': {
resource,
nodes
}
});
return this.addNodes(ship, name, nodes);
}
addNodes(ship: Patp, name: string, nodes: Object) {
return this.hookAction(ship, {
const action = {
'add-nodes': {
resource: { ship, name },
nodes
}
});
};
const promise = this.hookAction(ship, action);
markPending(action['add-nodes'].nodes);
action['add-nodes'].resource.ship = action['add-nodes'].resource.ship.slice(1);
console.log(action);
this.store.handleEvent({ data: { 'graph-update': action } });
return promise;
}
removeNodes(ship: Patp, name: string, indices: string[]) {

View File

@ -15,3 +15,22 @@ export function getLastSeen(
lastSeenIdx
);
}
export function getUnreadCount(
unreads: Unreads,
path: string,
index: string
): number {
const graphUnreads = unreads.graph?.[path]?.[index]?.unreads ?? 0;
return typeof graphUnreads === 'number' ? graphUnreads : graphUnreads.size;
}
export function getNotificationCount(
unreads: Unreads,
path: string
): number {
const unread = unreads.graph?.[path] || {};
return Object.keys(unread)
.map(index => unread[index]?.notifications || 0)
.reduce(f.add, 0);
}

View File

@ -32,23 +32,19 @@ export function newPost(
[root.index]: {
post: root,
children: {
graph: {
1: {
post: revContainer,
children: {
graph: {
1: {
post: firstRevision,
children: { empty: null },
},
1: {
post: firstRevision,
children: null,
},
},
},
2: {
post: commentsContainer,
children: { empty: null },
children: null
},
},
},
},
};
@ -69,7 +65,7 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s
const nodes = {
[newRev.index]: {
post: newRev,
children: { empty: null }
children: null
}
};

View File

@ -47,7 +47,7 @@ const tokenizeMessage = (text) => {
if (isUrl(str) && !isInCodeBlock) {
if (message.length > 0) {
// If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join('') });
messages.push({ text: message.join(' ') });
message = [];
}
messages.push({ url: str });
@ -55,7 +55,7 @@ const tokenizeMessage = (text) => {
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
if (message.length > 0) {
// If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join('') });
messages.push({ text: message.join(' ') });
message = [];
}
messages.push({ mention: str });
@ -70,7 +70,7 @@ const tokenizeMessage = (text) => {
if (message.length) {
// Add any remaining message
messages.push({ text: message.join('') });
messages.push({ text: message.join(' ') });
}
return messages;
};

View File

@ -34,17 +34,13 @@ const addGraph = (json, state) => {
// is graph
let converted = new BigIntOrderedMap();
for (let i in node.children) {
let item = node.children[i];
let index = item[0].split('/').slice(1).map((ind) => {
return bigInt(ind);
});
if (index.length === 0) { break; }
for (let idx in node.children) {
let item = node.children[idx];
let index = bigInt(idx);
converted.set(
index[index.length - 1],
_processNode(item[1])
index,
_processNode(item)
);
}
node.children = converted;
@ -60,18 +56,14 @@ const addGraph = (json, state) => {
let resource = data.resource.ship + '/' + data.resource.name;
state.graphs[resource] = new BigIntOrderedMap();
for (let i in data.graph) {
let item = data.graph[i];
let index = item[0].split('/').slice(1).map((ind) => {
return bigInt(ind);
});
if (index.length === 0) { break; }
for (let idx in data.graph) {
let item = data.graph[idx];
let index = bigInt(idx);
let node = _processNode(item[1]);
let node = _processNode(item);
state.graphs[resource].set(
index[index.length - 1],
index,
node
);
}
@ -93,9 +85,10 @@ const removeGraph = (json, state) => {
const mapifyChildren = (children) => {
return new BigIntOrderedMap(
children.map(([idx, node]) => {
const nd = {...node, children: mapifyChildren(node.children || []) };
return [bigInt(idx.slice(1)), nd];
_.map(children, (node, idx) => {
idx = idx && idx.startsWith('/') ? idx.slice(1) : idx;
const nd = {...node, children: mapifyChildren(node.children || {}) };
return [bigInt(idx), nd];
}));
};
@ -127,24 +120,23 @@ const addNodes = (json, state) => {
state.graphs[resource] = new BigIntOrderedMap();
}
for (let i in data.nodes) {
let item = data.nodes[i];
if (item[0].split('/').length === 0) { return; }
for (let index in data.nodes) {
let node = data.nodes[index];
if (index.split('/').length === 0) { return; }
let index = item[0].split('/').slice(1).map((ind) => {
index = index.split('/').slice(1).map((ind) => {
return bigInt(ind);
});
if (index.length === 0) { return; }
item[1].children = mapifyChildren(item[1].children || []);
node.children = mapifyChildren(node?.children || {});
state.graphs[resource] = _addNode(
state.graphs[resource],
index,
item[1]
node
);
}
}

View File

@ -7,7 +7,7 @@ import { Envelope } from './chat-update';
export type GraphNotifDescription = "link" | "comment" | "note" | "mention";
export interface UnreadStats {
unreads: Set<string> | { index: string; count: number; };
unreads: Set<string> | number;
notifications: number;
last: number;
}

View File

@ -109,7 +109,10 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
calculateUnreadIndex() {
const { graph, unreadCount } = this.props;
const unreadIndex = graph.keys()[unreadCount];
if(!unreadIndex) {
if(!unreadIndex || unreadCount === 0) {
this.setState({
unreadIndex: bigInt.zero
});
return;
}
this.setState({

View File

@ -5,6 +5,7 @@ import _ from "lodash";
import { Associations, Association, Unreads, UnreadStats } from "~/types";
import { alphabeticalOrder } from "~/logic/lib/util";
import { getUnreadCount, getNotificationCount } from "~/logic/lib/hark";
import Tile from "../components/tiles/tile";
interface GroupsProps {
@ -15,13 +16,22 @@ const sortGroupsAlph = (a: Association, b: Association) =>
alphabeticalOrder(a.metadata.title, b.metadata.title);
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string): number =>
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
f.flow(
(x) => x['graph'],
f.pickBy((_v, key) => associations.graph?.[key]["group-path"] === path),
f.map((x: Record<string, UnreadStats>) => 0), // x?.['/']?.unreads?.size),
f.pickBy((a: Association) => a['group-path'] === path),
f.map('app-path'),
f.map(appPath => getUnreadCount(unreads, appPath, '/')),
f.reduce(f.add, 0)
)(unreads);
)(associations.graph);
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
f.flow(
f.pickBy((a: Association) => a['group-path'] === path),
f.map('app-path'),
f.map(appPath => getNotificationCount(unreads, appPath)),
f.reduce(f.add, 0)
)(associations.graph);
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
const { associations, unreads, inbox, ...boxProps } = props;
@ -30,15 +40,18 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
.filter((e) => e?.["group-path"] in props.groups)
.sort(sortGroupsAlph);
const graphUnreads = getGraphUnreads(associations || {}, unreads);
const graphNotifications = getGraphNotifications(associations || {}, unreads);
return (
<>
{groups.map((group) => {
const path = group?.["group-path"];
const unreadCount = graphUnreads(path)
const notCount = graphNotifications(path);
return (
<Group
updates={notCount}
unreads={unreadCount}
path={group?.["group-path"]}
title={group.metadata.title}
@ -52,18 +65,24 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
interface GroupProps {
path: string;
title: string;
updates?: number;
updates: number;
unreads: number;
}
function Group(props: GroupProps) {
const { path, title, unreads } = props;
const { path, title, unreads, updates } = props;
return (
<Tile to={`/~landscape${path}`}>
<Col height="100%" justifyContent="space-between">
<Text>{title}</Text>
{unreads > 0 &&
(<Text color="blue" gray>{unreads} update{unreads !== 1 && 's'} </Text>)
}
<Col>
{unreads > 0 &&
(<Text gray>{unreads} unread{unreads !== 1 && 's'} </Text>)
}
{updates > 0 &&
(<Text mt="1" color="blue">{updates} update{updates !== 1 && 's'} </Text>)
}
</Col>
</Col>
</Tile>
);

View File

@ -44,7 +44,7 @@ export function CommentItem(props: CommentItemProps) {
const updateUrl = `${props.baseUrl}/${commentIndex}`
return (
<Box mb={4} opacity={props.pending ? '60%' : '100%'}>
<Box mb={4} opacity={post?.pending ? '60%' : '100%'}>
<Row bg="white" my={3}>
<Author
showImage

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import bigInt from 'big-integer';
import { Col } from '@tlon/indigo-react';
import { CommentItem } from './CommentItem';
@ -10,7 +10,7 @@ import { GraphNode, LocalUpdateRemoteContentPolicy, Unreads, Association } from
import { createPost, createBlankNodeWithChildPost } from '~/logic/api/graph';
import { getLatestCommentRevision } from '~/logic/lib/publish';
import { scanForMentions } from '~/logic/lib/graph';
import { getLastSeen } from '~/logic/lib/hark';
import { getUnreadCount } from '~/logic/lib/hark';
interface CommentsProps {
comments: GraphNode;
@ -54,7 +54,7 @@ const { association, comments, ship, name, api, history } = props;
actions: FormikHelpers<{ comment: string }>
) => {
try {
const commentNode = comments.children.get(bigInt(props.editCommentId));
const commentNode = comments.children.get(bigInt(props.editCommentId))!;
const [idx, _] = getLatestCommentRevision(commentNode);
const content = scanForMentions(comment);
@ -93,15 +93,16 @@ const { association, comments, ship, name, api, history } = props;
const children = Array.from(comments.children);
const [latestIdx, latest] = children?.[0] || [];
useEffect(() => {
return latest
? () => api.hark.markSinceAsRead(association, parentIndex, 'comment', latest.post.index)
: () => {};
}, [association])
console.log(`dismissing ${association?.['app-path']}`);
return () => {
api.hark.markCountAsRead(association, parentIndex, 'comment')
};
}, [comments.post.index])
const lastSeen = getLastSeen(props.unreads, association['app-path'], parentIndex) || latestIdx || bigInt.zero;
const readCount = children.length - getUnreadCount(props.unreads, association['app-path'], parentIndex)
console.log(readCount);
return (
<Col>
@ -115,7 +116,7 @@ const { association, comments, ship, name, api, history } = props;
/>
) : null )}
{children.reverse()
.map(([idx, comment]) => {
.map(([idx, comment], i) => {
return (
<CommentItem
comment={comment}
@ -124,7 +125,7 @@ const { association, comments, ship, name, api, history } = props;
api={api}
name={name}
ship={ship}
unread={lastSeen.lt(idx)}
unread={i >= readCount}
hideNicknames={props.hideNicknames}
hideAvatars={props.hideAvatars}
remoteContentPolicy={props.remoteContentPolicy}

View File

@ -46,7 +46,8 @@ export function useGraphModule(
): SidebarAppConfig {
const getStatus = useCallback(
(s: string) => {
if((graphUnreads?.[s]?.['/']?.unreads?.size || 0) > 0) {
const unreads = graphUnreads?.[s]?.['/']?.unreads;
if(typeof unreads === 'number' ? unreads > 0 : unreads?.size ?? 0 > 0) {
return 'unread';
}
const [, , host, name] = s.split("/");