From ef1b438c22cdea8ff31dc3eb870366cf9ad0a815 Mon Sep 17 00:00:00 2001 From: Pax Dickinson Date: Thu, 15 Apr 2021 14:59:52 -0400 Subject: [PATCH 01/15] docker: allow setting Ames port via docker run argument --- nix/pkgs/docker-image/default.nix | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/nix/pkgs/docker-image/default.nix b/nix/pkgs/docker-image/default.nix index 2b63d0498..c798f5454 100644 --- a/nix/pkgs/docker-image/default.nix +++ b/nix/pkgs/docker-image/default.nix @@ -5,6 +5,20 @@ let set -eu + # set defaults + amesPort=${toString amesPort} + + # check args + for i in "$@" + do + case $i in + -p=*|--port=*) + amesPort="${i#*=}" + shift + ;; + esac + done + # If the container is not started with the `-i` flag # then STDIN will be closed and we need to start # Urbit/vere with the `-t` flag. @@ -23,7 +37,7 @@ let mv $keyname /tmp # Boot urbit with the key, exit when done booting - urbit $ttyflag -w $(basename $keyname .key) -k /tmp/$keyname -c $(basename $keyname .key) -p ${toString amesPort} -x + urbit $ttyflag -w $(basename $keyname .key) -k /tmp/$keyname -c $(basename $keyname .key) -p $amesPort -x # Remove the keyfile for security rm /tmp/$keyname @@ -34,7 +48,7 @@ let cometname=''${comets[0]} rm *.comet - urbit $ttyflag -c $(basename $cometname .comet) -p ${toString amesPort} -x + urbit $ttyflag -c $(basename $cometname .comet) -p $amesPort -x fi # Find the first directory and start urbit with the ship therein @@ -42,7 +56,7 @@ let dirs=( $dirnames ) dirname=''${dirnames[0]} - exec urbit $ttyflag -p ${toString amesPort} $dirname + exec urbit $ttyflag -p $amesPort $dirname ''; From 0892767f646cf31cf964f2478424e6dbc602bf17 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 15 Apr 2021 15:39:52 -0400 Subject: [PATCH 02/15] Revert "Revert "build: pkg/interface builds pkg/npm preinstall"" This reverts commit ebcd2136eb4739262a859edf5d4b8098276b9867. --- pkg/interface/package.json | 3 ++- pkg/interface/preinstall.sh | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100755 pkg/interface/preinstall.sh diff --git a/pkg/interface/package.json b/pkg/interface/package.json index ae2d1129c..ff6dd60dd 100644 --- a/pkg/interface/package.json +++ b/pkg/interface/package.json @@ -98,8 +98,9 @@ "lint-file": "eslint", "tsc": "tsc", "tsc:watch": "tsc --watch", + "preinstall": "./preinstall.sh", "build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js", - "build:prod": "cd ../npm/api && npm i && cd ../../interface && cross-env NODE_ENV=production webpack --config config/webpack.prod.js", + "build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js", "start": "webpack-dev-server --config config/webpack.dev.js", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/pkg/interface/preinstall.sh b/pkg/interface/preinstall.sh new file mode 100755 index 000000000..986808087 --- /dev/null +++ b/pkg/interface/preinstall.sh @@ -0,0 +1,12 @@ +#!/bin/sh +cd ../npm + +for i in $(find . -type d -maxdepth 1) ; do + packageJson="${i}/package.json" + if [ -f "${packageJson}" ]; then + echo "installing ${i}..." + cd ./${i} + npm install + cd .. + fi +done \ No newline at end of file From aefc267477fc4c0f522282fbf8f9f53ebcee8488 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Thu, 15 Apr 2021 15:41:20 -0400 Subject: [PATCH 03/15] landscape: install using ci in most cases --- pkg/interface/CONTRIBUTING.md | 6 +++--- pkg/interface/package.json | 2 +- pkg/interface/preinstall.sh | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/interface/CONTRIBUTING.md b/pkg/interface/CONTRIBUTING.md index 711e359d8..f3bfbb5a0 100644 --- a/pkg/interface/CONTRIBUTING.md +++ b/pkg/interface/CONTRIBUTING.md @@ -32,7 +32,7 @@ same (if [developing on a local development ship][local]). Then, from 'pkg/interface': ``` -npm install +npm ci npm run start ``` @@ -59,7 +59,7 @@ module.exports = { ``` The dev environment will attempt to match the subdomain against the keys of this -object, and if matched will proxy to the corresponding URL. For example, the +object, and if matched will proxy to the corresponding URL. For example, the above config will proxy `zod.localhost:9000` to `http://localhost:8080`, `bus.localhost:9000` to `http://localhost:8081` and so on and so forth. If no match is found, then it will fallback to the `URL` property. @@ -71,7 +71,7 @@ linter and for usage through the command, do the following: ```bash $ cd ./pkg/interface -$ npm install +$ npm ci $ npm run lint ``` diff --git a/pkg/interface/package.json b/pkg/interface/package.json index ff6dd60dd..831507293 100644 --- a/pkg/interface/package.json +++ b/pkg/interface/package.json @@ -100,7 +100,7 @@ "tsc:watch": "tsc --watch", "preinstall": "./preinstall.sh", "build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js", - "build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js", + "build:prod": "cd ../npm/api && npm ci && cd ../../interface && cross-env NODE_ENV=production webpack --config config/webpack.prod.js", "start": "webpack-dev-server --config config/webpack.dev.js", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/pkg/interface/preinstall.sh b/pkg/interface/preinstall.sh index 986808087..4f35cfc0f 100755 --- a/pkg/interface/preinstall.sh +++ b/pkg/interface/preinstall.sh @@ -6,7 +6,7 @@ for i in $(find . -type d -maxdepth 1) ; do if [ -f "${packageJson}" ]; then echo "installing ${i}..." cd ./${i} - npm install + npm ci cd .. fi done \ No newline at end of file From 2bab57d6b52d41f0cbc9fddd9e344f9dfd4df515 Mon Sep 17 00:00:00 2001 From: wexpertsystems <82326228+wexpertsystems@users.noreply.github.com> Date: Thu, 15 Apr 2021 17:28:15 -0400 Subject: [PATCH 04/15] Escape the nix quotation properly. Fixes nix syntax error Co-authored-by: Edward Amsden --- nix/pkgs/docker-image/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/pkgs/docker-image/default.nix b/nix/pkgs/docker-image/default.nix index c798f5454..8d2f5af89 100644 --- a/nix/pkgs/docker-image/default.nix +++ b/nix/pkgs/docker-image/default.nix @@ -13,7 +13,7 @@ let do case $i in -p=*|--port=*) - amesPort="${i#*=}" + amesPort="''${i#*=}" shift ;; esac From dd9dfc0a9f6dd32520561880b7fb5e09fe466dfc Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 16 Apr 2021 15:57:19 +1000 Subject: [PATCH 05/15] Author: fix mono spacing --- pkg/interface/src/views/components/Author.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/components/Author.tsx b/pkg/interface/src/views/components/Author.tsx index d03b62f7d..41d0fb723 100644 --- a/pkg/interface/src/views/components/Author.tsx +++ b/pkg/interface/src/views/components/Author.tsx @@ -18,7 +18,7 @@ import { PropFunc } from '~/types'; interface AuthorProps { ship: string; - date: number; + date?: number; showImage?: boolean; children?: ReactNode; unread?: boolean; @@ -113,11 +113,13 @@ export default function Author(props: AuthorProps & PropFunc): React lineHeight='tall' fontFamily={showNickname ? 'sans' : 'mono'} fontWeight={showNickname ? '500' : '400'} + mr={showNickname ? 0 : "2px"} + mt={showNickname ? 0 : "0px"} onClick={doCopy} > {copyDisplay} - { !dontShowTime && ( + { !dontShowTime && time && ( Date: Fri, 16 Apr 2021 15:57:42 +1000 Subject: [PATCH 06/15] Timestamp: set line-height --- pkg/interface/src/views/components/Timestamp.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/interface/src/views/components/Timestamp.tsx b/pkg/interface/src/views/components/Timestamp.tsx index ec526b885..6b5599630 100644 --- a/pkg/interface/src/views/components/Timestamp.tsx +++ b/pkg/interface/src/views/components/Timestamp.tsx @@ -23,6 +23,7 @@ const Timestamp = (props: TimestampProps): ReactElement | null => { relative, dateNotRelative, fontSize, + lineHeight, ...rest } = { time: true, @@ -62,7 +63,7 @@ const Timestamp = (props: TimestampProps): ReactElement | null => { title={stamp.format(DateFormat + ' ' + TimeFormat)} > {time && ( - + {timestamp} )} @@ -70,6 +71,7 @@ const Timestamp = (props: TimestampProps): ReactElement | null => { From 86552306c25dd94527b439be146083d0e405572f Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 16 Apr 2021 15:59:02 +1000 Subject: [PATCH 07/15] interface: add getters for stores --- pkg/interface/src/logic/state/group.ts | 2 +- pkg/interface/src/logic/state/metadata.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/interface/src/logic/state/group.ts b/pkg/interface/src/logic/state/group.ts index 4fe3999f6..c8f5eef2a 100644 --- a/pkg/interface/src/logic/state/group.ts +++ b/pkg/interface/src/logic/state/group.ts @@ -16,7 +16,7 @@ const useGroupState = createState('Group', { }, ['groups']); export function useGroup(group: string) { - return useGroupState(useCallback(s => s.groups[group], [group])); + return useGroupState(useCallback(s => s.groups[group] as Group | undefined, [group])); } export function useGroupForAssoc(association: Association) { diff --git a/pkg/interface/src/logic/state/metadata.ts b/pkg/interface/src/logic/state/metadata.ts index fabffca86..f09e824ee 100644 --- a/pkg/interface/src/logic/state/metadata.ts +++ b/pkg/interface/src/logic/state/metadata.ts @@ -1,4 +1,5 @@ -import { MetadataUpdatePreview, Associations } from "@urbit/api"; +import { useCallback } from 'react'; +import { MetadataUpdatePreview, Association, Associations } from "@urbit/api"; import { BaseState, createState } from "./base"; @@ -9,6 +10,14 @@ export interface MetadataState extends BaseState { // preview: (group: string) => Promise; }; +export function useAssocForGraph(graph: string) { + return useMetadataState(useCallback(s => s.associations.graph[graph] as Association | undefined, [graph])); +} + +export function useAssocForGroup(group: string) { + return useMetadataState(useCallback(s => s.associations.groups[group] as Association | undefined, [group])); +} + const useMetadataState = createState('Metadata', { associations: { groups: {}, graph: {}, contacts: {}, chat: {}, link: {}, publish: {} }, // preview: async (group): Promise => { @@ -54,4 +63,4 @@ const useMetadataState = createState('Metadata', { }); -export default useMetadataState; \ No newline at end of file +export default useMetadataState; From 08028efcd7e697292eeedf8bd601995ede0fce76 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 16 Apr 2021 15:59:33 +1000 Subject: [PATCH 08/15] interface: add utilities, fix useLazyScroll --- pkg/interface/src/logic/lib/useLazyScroll.ts | 8 +++++++- pkg/interface/src/logic/lib/useRunIO.ts | 2 +- pkg/interface/src/logic/lib/util.ts | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/logic/lib/useLazyScroll.ts b/pkg/interface/src/logic/lib/useLazyScroll.ts index a7b7235ee..df0bd74ad 100644 --- a/pkg/interface/src/logic/lib/useLazyScroll.ts +++ b/pkg/interface/src/logic/lib/useLazyScroll.ts @@ -41,6 +41,12 @@ export function useLazyScroll( } }, [count]); + useEffect(() => { + if(!ready) { + setIsDone(false); + } + }, [ready]); + useEffect(() => { if (!ref.current || isDone || !ready) { return; @@ -58,7 +64,7 @@ export function useLazyScroll( return () => { ref.current?.removeEventListener('scroll', onScroll); }; - }, [ref?.current, count, ready]); + }, [ref?.current, ready, isDone]); return { isDone, isLoading }; } diff --git a/pkg/interface/src/logic/lib/useRunIO.ts b/pkg/interface/src/logic/lib/useRunIO.ts index 12d8628cd..bfaee2c54 100644 --- a/pkg/interface/src/logic/lib/useRunIO.ts +++ b/pkg/interface/src/logic/lib/useRunIO.ts @@ -10,7 +10,7 @@ export function useRunIO( io: (i: I) => Promise, after: (o: O) => void, key: string -) { +): () => Promise { const [resolve, setResolve] = useState<() => void>(() => () => {}); const [reject, setReject] = useState<(e: any) => void>(() => () => {}); const [output, setOutput] = useState(null); diff --git a/pkg/interface/src/logic/lib/util.ts b/pkg/interface/src/logic/lib/util.ts index 6e6ef0740..f34e8f76b 100644 --- a/pkg/interface/src/logic/lib/util.ts +++ b/pkg/interface/src/logic/lib/util.ts @@ -63,6 +63,16 @@ export function unixToDa(unix: number) { return DA_UNIX_EPOCH.add(timeSinceEpoch); } +export function dmCounterparty(resource: string) { + const [,,ship,name] = resource.split('/'); + return ship === `~${window.ship}` ? `~${name.slice(4)}` : ship; +} + +export function isDm(resource: string) { + const [,,,name] = resource.split('/'); + return name.startsWith('dm--'); +} + export function makePatDa(patda: string) { return bigInt(udToDec(patda)); } From 992b607e3cee8289deff687e4cebfad45fd6760f Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Fri, 16 Apr 2021 16:01:05 +1000 Subject: [PATCH 09/15] notifications: refactor graph notification to match spec --- .../src/views/apps/notifications/graph.tsx | 494 ++++++++++-------- 1 file changed, 272 insertions(+), 222 deletions(-) diff --git a/pkg/interface/src/views/apps/notifications/graph.tsx b/pkg/interface/src/views/apps/notifications/graph.tsx index 2f8180d7c..2dfdd3f69 100644 --- a/pkg/interface/src/views/apps/notifications/graph.tsx +++ b/pkg/interface/src/views/apps/notifications/graph.tsx @@ -1,65 +1,80 @@ -import React, { ReactNode, useCallback } from 'react'; -import moment from 'moment'; -import { Row, Box, Col, Text, Anchor, Icon, Action } from '@tlon/indigo-react'; -import { Link, useHistory } from 'react-router-dom'; -import _ from 'lodash'; +import React, { ReactNode, useCallback } from "react"; +import moment from "moment"; +import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react"; +import { Link, useHistory } from "react-router-dom"; +import _ from "lodash"; import { GraphNotifIndex, GraphNotificationContents, Associations, Rolodex, - Groups -} from '~/types'; -import { Header } from './header'; -import { cite, deSig, pluralize, useShowNickname } from '~/logic/lib/util'; -import Author from '~/views/components/Author'; -import GlobalApi from '~/logic/api/global'; -import { getSnippet } from '~/logic/lib/publish'; -import styled from 'styled-components'; -import { MentionText } from '~/views/components/MentionText'; -import ChatMessage from '../chat/components/ChatMessage'; -import useContactState from '~/logic/state/contact'; -import useGroupState from '~/logic/state/group'; -import useMetadataState from '~/logic/state/metadata'; -import {PermalinkEmbed} from '../permalinks/embed'; -import {parsePermalink, referenceToPermalink} from '~/logic/lib/permalinks'; + Groups, +} from "~/types"; +import { Header } from "./header"; +import { + cite, + deSig, + pluralize, + useShowNickname, + isDm, +} from "~/logic/lib/util"; +import { GraphContentWide } from "~/views/landscape/components/Graph/GraphContentWide"; +import Author from "~/views/components/Author"; +import GlobalApi from "~/logic/api/global"; +import styled from "styled-components"; +import useContactState from "~/logic/state/contact"; +import useGroupState from "~/logic/state/group"; +import useMetadataState, { + useAssocForGraph, + useAssocForGroup, +} from "~/logic/state/metadata"; +import { PermalinkEmbed } from "../permalinks/embed"; +import { parsePermalink, referenceToPermalink } from "~/logic/lib/permalinks"; +import { Post, Group, Association } from "@urbit/api"; +import { BigInteger } from "big-integer"; + +const TruncBox = styled(Box)<{ truncate?: number }>` + -webkit-line-clamp: ${(p) => p.truncate ?? "unset"}; + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + color: ${(p) => p.theme.colors.black}; +`; function getGraphModuleIcon(module: string) { - if (module === 'link') { - return 'Collection'; + if (module === "link") { + return "Collection"; } - if(module === 'post') { - return 'Groups'; + if (module === "post") { + return "Groups"; } return _.capitalize(module); } -const FilterBox = styled(Box)` - background: linear-gradient( - to bottom, - transparent, - ${(p) => p.theme.colors.white} - ); -`; - -function describeNotification(description: string, plural: boolean): string { +function describeNotification( + description: string, + plural: boolean, + isDm: boolean, + singleAuthor: boolean +): string { switch (description) { - case 'post': - return 'replied to you'; - case 'link': - return `added ${pluralize('new link', plural)} to`; - case 'comment': - return `left ${pluralize('comment', plural)} on`; - case 'edit-comment': - return `updated ${pluralize('comment', plural)} on`; - case 'note': - return `posted ${pluralize('note', plural)} to`; - case 'edit-note': - return `updated ${pluralize('note', plural)} in`; - case 'mention': - return 'mentioned you on'; - case 'message': - return `sent ${pluralize('message', plural)} to`; + case "post": + return singleAuthor ? "replied to you" : "Your post received replies"; + case "link": + return `New link${plural ? "s" : ""} in`; + case "comment": + return `New comment${plural ? "s" : ""} on`; + case "note": + return `New Note${plural ? "s" : ""} in`; + case "edit-note": + return `updated ${pluralize("note", plural)} in`; + case "mention": + return singleAuthor ? "mentioned you in" : "You were mentioned in"; + case "message": + if (isDm) { + return "messaged you"; + } + return `New message${plural ? "s" : ""} in`; default: return description; } @@ -67,105 +82,88 @@ function describeNotification(description: string, plural: boolean): string { const GraphUrl = ({ contents, api }) => { const [{ text }, link] = contents; - - if('reference' in link) { + if ("reference" in link) { return ( - ); + /> + ); } return ( - - - + + + {text} ); +}; + +function ContentSummary({ icon, name, author, to }) { + return ( + + + + + + {name} + + + + + by + + + + + + ); } -export const GraphNodeContent = ({ - group, - association, - post, - mod, - index, -}) => { +export const GraphNodeContent = ({ post, mod, index, hidden, association }) => { const { contents } = post; - const idx = index.slice(1).split('/'); - if (mod === 'link') { - if (idx.length === 1) { - return ; - } else if (idx.length === 3) { - return ; - } - return null; - } - if (mod === 'publish') { - if (idx[1] === '2') { - return ( - - ); - } else if (idx[1] === '1') { - const [{ text: header }, { text: body }] = contents; - const snippet = getSnippet(body); - return ( - - - {header} - - - {snippet} - - - - ); - } - } - if(mod === 'post') { - return ; - } - - if (mod === 'chat') { + const idx = index.slice(1).split("/"); + const { group, resource } = association; + const url = getNodeUrl(mod, hidden, group, resource, index); + if (mod === "link" && idx.length === 1) { + const [{ text: title }] = contents; return ( - - - + ); } - return null; + if (mod === "publish" && idx[1] === "1") { + const [{ text: title }] = contents; + return ( + + ); + } + return ( + + + + ); }; function getNodeUrl( @@ -175,78 +173,103 @@ function getNodeUrl( graph: string, index: string ) { - if (hidden && mod === 'chat') { - groupPath = '/messages'; + if (hidden && mod === "chat") { + groupPath = "/messages"; } else if (hidden) { - groupPath = '/home'; + groupPath = "/home"; } const graphUrl = `/~landscape${groupPath}/resource/${mod}${graph}`; - const idx = index.slice(1).split('/'); - if (mod === 'publish') { - const [noteId] = idx; - return `${graphUrl}/note/${noteId}`; - } else if (mod === 'link') { - const [linkId] = idx; - return `${graphUrl}/index/${linkId}`; - } else if (mod === 'chat') { - if(idx.length > 0) { + const idx = index.slice(1).split("/"); + if (mod === "publish") { + console.log(idx); + const [noteId, kind, commId] = idx; + const selected = kind === "2" ? `?selected=${commId}` : ""; + return `${graphUrl}/note/${noteId}${selected}`; + } else if (mod === "link") { + const [linkId, commId] = idx; + return `${graphUrl}/index/${linkId}${commId ? `?selected=${commId}` : ""}`; + } else if (mod === "chat") { + if (idx.length > 0) { return `${graphUrl}?msg=${idx[0]}`; } return graphUrl; - } else if( mod === 'post') { + } else if (mod === "post") { return `/~landscape${groupPath}/feed${index}`; } - return ''; + return ""; } -const GraphNode = ({ - post, - author, - mod, - description, - time, - index, - graph, - groupPath, - group, - read, - onRead, - showContact = false -}) => { - author = deSig(author); - const history = useHistory(); - const contacts = useContactState((state) => state.contacts); - const nodeUrl = getNodeUrl(mod, group?.hidden, groupPath, graph, index); - const association = useMetadataState( - useCallback(s => s.associations.graph[graph], [graph]) +interface PostsByAuthor { + author: string; + posts: Post[]; +} +const GraphNodes = (props: { + posts: Post[]; + graph: string; + hideAuthors?: boolean; + group?: Group; + groupPath: string; + description: string; + index: string; + mod: string; + association: Association; + hidden: boolean; +}) => { + const { + posts, + mod, + hidden, + index, + description, + hideAuthors = false, + association, + } = props; + + const postsByConsecAuthor = _.reduce( + posts, + (acc: PostsByAuthor[], val: Post, key: number) => { + const lent = acc.length; + if (lent > 0 && acc?.[lent - 1]?.author === val.author) { + const last = acc[lent - 1]; + const rest = acc.slice(0, -1); + return [...rest, { ...last, posts: [...last.posts, val] }]; + } + return [...acc, { author: val.author, posts: [val] }]; + }, + [] ); - const onClick = useCallback(() => { - if (!read) { - onRead(); - } - history.push(nodeUrl); - }, [read, onRead]); - return ( - - - {showContact && ( - - )} - - - - - + <> + {_.map(postsByConsecAuthor, ({ posts, author }, idx) => { + const time = posts[0]?.["time-sent"]; + return ( + + {!hideAuthors && ( + + )} + + {_.map(posts, (post) => ( +