mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-04 13:19:48 +03:00
graph-store: make JSON conversion invertible
This commit is contained in:
parent
9ee62a2e55
commit
54c469ecfa
@ -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))]
|
||||
|
@ -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[]) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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("/");
|
||||
|
Loading…
Reference in New Issue
Block a user