mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-15 01:52:42 +03:00
Merge remote-tracking branch 'origin/release/next-js' into james/transclusion-polish
This commit is contained in:
commit
ff49268d6f
@ -124,15 +124,15 @@
|
||||
::
|
||||
++ poke-noun
|
||||
|= non=*
|
||||
?> ?=(%rewatch-dms non)
|
||||
=/ graphs=(list resource)
|
||||
~(tap in get-keys:gra)
|
||||
:- ~
|
||||
%_ state
|
||||
watching
|
||||
%- ~(gas in watching)
|
||||
(murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~)))
|
||||
==
|
||||
[~ state]
|
||||
:: ?> ?=(%rewatch-dms non)
|
||||
:: =/ graphs=(list resource)
|
||||
:: ~(tap in get-keys:gra)
|
||||
:: %_ state
|
||||
:: watching
|
||||
:: %- ~(gas in watching)
|
||||
:: (murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~)))
|
||||
:: ==
|
||||
::
|
||||
++ hark-graph-hook-action
|
||||
|= =action:hook
|
||||
@ -201,7 +201,9 @@
|
||||
::
|
||||
%add-nodes
|
||||
=* rid resource.q.update
|
||||
(check-nodes ~(val by nodes.q.update) rid)
|
||||
=/ assoc=(unit association:metadata)
|
||||
(peek-association:met %graph rid)
|
||||
(check-nodes ~(val by nodes.q.update) rid assoc)
|
||||
==
|
||||
:: this is awful, but notification kind should always switch
|
||||
:: on the index, so hopefully doesn't matter
|
||||
@ -255,9 +257,11 @@
|
||||
(get-graph-mop:gra rid)
|
||||
=/ node=(unit node:graph-store)
|
||||
(bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node))
|
||||
=/ assoc=(unit association:metadata)
|
||||
(peek-association:met %graph rid)
|
||||
=^ cards state
|
||||
(check-nodes (drop node) rid)
|
||||
?. (should-watch:ha rid)
|
||||
(check-nodes (drop node) rid assoc)
|
||||
?. (should-watch:ha rid assoc)
|
||||
[cards state]
|
||||
:_ state(watching (~(put in watching) [rid ~]))
|
||||
(weld cards (give:ha ~[/updates] %listen [rid ~]))
|
||||
@ -265,20 +269,18 @@
|
||||
++ check-nodes
|
||||
|= $: nodes=(list node:graph-store)
|
||||
rid=resource
|
||||
assoc=(unit association:metadata)
|
||||
==
|
||||
=/ group=(unit resource)
|
||||
(peek-group:met %graph rid)
|
||||
?~ group
|
||||
~& no-group+rid
|
||||
?~ assoc
|
||||
~& no-assoc+rid
|
||||
`state
|
||||
=/ metadatum=(unit metadatum:metadata)
|
||||
(peek-metadatum:met %graph rid)
|
||||
?~ metadatum `state
|
||||
=* group group.u.assoc
|
||||
=* metadatum metadatum.u.assoc
|
||||
=/ module=term
|
||||
?: ?=(%empty -.config.u.metadatum) %$
|
||||
?: ?=(%group -.config.u.metadatum) %$
|
||||
module.config.u.metadatum
|
||||
abet:check:(abed:handle-update:ha rid nodes u.group module)
|
||||
?: ?=(%empty -.config.metadatum) %$
|
||||
?: ?=(%group -.config.metadatum) %$
|
||||
module.config.metadatum
|
||||
abet:check:(abed:handle-update:ha rid nodes group module)
|
||||
--
|
||||
::
|
||||
++ on-peek on-peek:def
|
||||
@ -340,12 +342,11 @@
|
||||
$(contents t.contents)
|
||||
::
|
||||
++ should-watch
|
||||
|= rid=resource
|
||||
|= [rid=resource assoc=(unit association:metadata)]
|
||||
^- ?
|
||||
=/ group-rid=(unit resource)
|
||||
(peek-group:met %graph rid)
|
||||
?~ group-rid %.n
|
||||
?| !(is-managed:grp u.group-rid)
|
||||
?~ assoc
|
||||
%.n
|
||||
?| !(is-managed:grp group.u.assoc)
|
||||
&(watch-on-self =(our.bowl entity.rid))
|
||||
==
|
||||
::
|
||||
@ -364,7 +365,9 @@
|
||||
update-core(rid r, updates upds, group grp, module mod)
|
||||
::
|
||||
++ get-conversion
|
||||
(^get-conversion rid)
|
||||
:: LA: this tube should be cached in %hark-graph-hook state
|
||||
:: instead of just trying to keep it warm, as the scry overhead is large
|
||||
~+ (^get-conversion rid)
|
||||
::
|
||||
++ abet
|
||||
^- (quip card _state)
|
||||
@ -418,7 +421,8 @@
|
||||
update-core
|
||||
=* pos p.post.node
|
||||
=+ !< notif-kind=(unit notif-kind:hook)
|
||||
(get-conversion !>([0 pos]))
|
||||
%- get-conversion
|
||||
!>(`indexed-post:graph-store`[0 pos])
|
||||
?~ notif-kind
|
||||
update-core
|
||||
=/ desc=@t
|
||||
|
73
pkg/interface/package-lock.json
generated
73
pkg/interface/package-lock.json
generated
@ -1318,6 +1318,50 @@
|
||||
"warning": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"@react-spring/animated": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.1.1.tgz",
|
||||
"integrity": "sha512-u8Assg5uySwqyoeb1f7eBAUSl8sleJTewdfhVi1EtcM9ngU2Snhcp6snF8NGxvf4gZp5z7v+Dfx3KdB2V8NnXQ==",
|
||||
"requires": {
|
||||
"@react-spring/shared": "~9.1.1",
|
||||
"@react-spring/types": "~9.1.1"
|
||||
}
|
||||
},
|
||||
"@react-spring/core": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.1.1.tgz",
|
||||
"integrity": "sha512-flHeLN56idxQ1YIpUYY1m3r2ZAM2xg7Zb/pHBFSCbnOKP7TtlhAAOfmrabERqaThmrqkFKiq9FjyF76d3OjE5g==",
|
||||
"requires": {
|
||||
"@react-spring/animated": "~9.1.1",
|
||||
"@react-spring/shared": "~9.1.1",
|
||||
"@react-spring/types": "~9.1.1"
|
||||
}
|
||||
},
|
||||
"@react-spring/shared": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.1.1.tgz",
|
||||
"integrity": "sha512-GA9A9l5JxC50eDTDPu5IDUMhQ4MiBrXd3ZdlI6/wUCgAsZ1wPx77sxaccomxlUomRet0IUcXCEKcL1Flax7ZMQ==",
|
||||
"requires": {
|
||||
"@react-spring/types": "~9.1.1",
|
||||
"rafz": "^0.1.14"
|
||||
}
|
||||
},
|
||||
"@react-spring/types": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.1.1.tgz",
|
||||
"integrity": "sha512-GaesYowey+nmDw/yhZ5jErEH2UaDl4jxax8aQtW5h3OpNu/QS8swoEn/jxQgffLb0n6gjsER7QyIx/dmZIWlyw=="
|
||||
},
|
||||
"@react-spring/web": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.1.1.tgz",
|
||||
"integrity": "sha512-py4c/Agqz9Unf+Apame29XYTLqHnKXSwJA6Q44jcNtHRFMuRjIpCyhS13C1ZI5PcJT0g9b8CvtMQFiShne8wNQ==",
|
||||
"requires": {
|
||||
"@react-spring/animated": "~9.1.1",
|
||||
"@react-spring/core": "~9.1.1",
|
||||
"@react-spring/shared": "~9.1.1",
|
||||
"@react-spring/types": "~9.1.1"
|
||||
}
|
||||
},
|
||||
"@styled-system/background": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz",
|
||||
@ -1431,9 +1475,9 @@
|
||||
"integrity": "sha512-xO8hj2Ak6cEYe2QCM3w7UuaSB8ubg6G0G6/OkPVMVrz6b5ztccZmkbmYCYJ/Ot6976lGzKFsWFKRUhwRgCHfHQ=="
|
||||
},
|
||||
"@tlon/indigo-react": {
|
||||
"version": "1.2.21",
|
||||
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.21.tgz",
|
||||
"integrity": "sha512-DqU4Mgcqyzv5pCe2qcclVAASnd3D7eznk2e9yV9W3tJwcOrpFsHRDdOR89aPoulyW7m5dctMdrGKcq70DJoaVQ==",
|
||||
"version": "1.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.22.tgz",
|
||||
"integrity": "sha512-8w2TkYicch+R0kkZT+MZ4oG0pIJFNjhmVlbXgqyXhOCPRJB2WrAh6OM5Cbb389r7lA+CXXfu3Nx7Rdiuxjf/vg==",
|
||||
"requires": {
|
||||
"@reach/menu-button": "^0.10.5",
|
||||
"react": "^16.13.1",
|
||||
@ -8533,6 +8577,11 @@
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true
|
||||
},
|
||||
"rafz": {
|
||||
"version": "0.1.14",
|
||||
"resolved": "https://registry.npmjs.org/rafz/-/rafz-0.1.14.tgz",
|
||||
"integrity": "sha512-YiQkedSt1urYtYbvHhTQR3l67M8SZbUvga5eJFM/v4vx/GmDdtXlE2hjJIyRjhhO/PjcdGC+CXCYOUA4onit8w=="
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.27.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
|
||||
@ -8726,6 +8775,11 @@
|
||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.1.tgz",
|
||||
"integrity": "sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ=="
|
||||
},
|
||||
"react-use-gesture": {
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz",
|
||||
"integrity": "sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg=="
|
||||
},
|
||||
"react-virtuoso": {
|
||||
"version": "0.20.3",
|
||||
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-0.20.3.tgz",
|
||||
@ -10940,6 +10994,7 @@
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"arr-flatten": "^1.1.0",
|
||||
"array-unique": "^0.3.2",
|
||||
@ -10958,6 +11013,7 @@
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
@ -10998,6 +11054,7 @@
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"is-number": "^3.0.0",
|
||||
@ -11010,6 +11067,7 @@
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
@ -11065,6 +11123,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.0.2"
|
||||
},
|
||||
@ -11074,6 +11133,7 @@
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
@ -11084,13 +11144,15 @@
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"arr-diff": "^4.0.0",
|
||||
"array-unique": "^0.3.2",
|
||||
@ -11112,6 +11174,7 @@
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
|
||||
"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"assign-symbols": "^1.0.0",
|
||||
"is-extendable": "^1.0.1"
|
||||
@ -11122,6 +11185,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-plain-object": "^2.0.4"
|
||||
}
|
||||
@ -11171,6 +11235,7 @@
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
||||
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-number": "^3.0.0",
|
||||
"repeat-string": "^1.6.1"
|
||||
|
@ -8,9 +8,10 @@
|
||||
"@reach/disclosure": "^0.10.5",
|
||||
"@reach/menu-button": "^0.10.5",
|
||||
"@reach/tabs": "^0.10.5",
|
||||
"@react-spring/web": "^9.1.1",
|
||||
"@tlon/indigo-dark": "^1.0.6",
|
||||
"@tlon/indigo-light": "^1.0.7",
|
||||
"@tlon/indigo-react": "^1.2.21",
|
||||
"@tlon/indigo-react": "^1.2.22",
|
||||
"@tlon/sigil-js": "^1.4.3",
|
||||
"@urbit/api": "file:../npm/api",
|
||||
"any-ascii": "^0.1.7",
|
||||
@ -38,6 +39,7 @@
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-oembed-container": "^1.0.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use-gesture": "^9.1.3",
|
||||
"react-virtuoso": "^0.20.3",
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"remark-breaks": "^2.0.1",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp } from '@urbit/api';
|
||||
import { ContactEdit } from '@urbit/api/contacts';
|
||||
import { ContactEditField } from '@urbit/api/contacts';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
@ -14,7 +14,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
return this.storeAction({ remove: { ship } });
|
||||
}
|
||||
|
||||
edit(ship: Patp, editField: ContactEdit) {
|
||||
edit(ship: Patp, editField: ContactEditField) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
{email: ''}
|
||||
|
@ -3,7 +3,7 @@ import { StoreState } from '../store/type';
|
||||
import { Patp, Path, Resource } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import { makeResource, resourceFromPath } from '../lib/group';
|
||||
import { GroupPolicy, Enc, Post, Content } from '@urbit/api';
|
||||
import { GroupPolicy, Enc, Post, Content, GraphNode } from '@urbit/api';
|
||||
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util';
|
||||
|
||||
export const createBlankNodeWithChildPost = (
|
||||
@ -211,7 +211,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
return this.addNodes(ship, name, nodes);
|
||||
}
|
||||
|
||||
addNode(ship: Patp, name: string, node: Object) {
|
||||
addNode(ship: Patp, name: string, node: GraphNode) {
|
||||
const nodes = {};
|
||||
nodes[node.post.index] = node;
|
||||
|
||||
|
@ -7,8 +7,4 @@ export default class LocalApi extends BaseApi<StoreState> {
|
||||
this.store.handleEvent({ data: { baseHash } });
|
||||
});
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
this.store.dehydrate();
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Key,
|
||||
Value,
|
||||
Bucket
|
||||
Bucket,
|
||||
SettingsUpdate
|
||||
} from '@urbit/api/settings';
|
||||
|
||||
export default class SettingsApi extends BaseApi<StoreState> {
|
||||
private storeAction(action: SettingsEvent): Promise<any> {
|
||||
private storeAction(action: SettingsUpdate): Promise<any> {
|
||||
return this.action('settings-store', 'settings-event', action);
|
||||
}
|
||||
|
||||
@ -47,14 +48,14 @@ export default class SettingsApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
const { all } = await this.scry("settings-store", "/all");
|
||||
this.store.handleEvent({data:
|
||||
{"settings-data": { all } }
|
||||
const { all } = await this.scry('settings-store', '/all');
|
||||
this.store.handleEvent({ data:
|
||||
{ 'settings-data': { all } }
|
||||
});
|
||||
}
|
||||
|
||||
async getBucket(bucket: Key) {
|
||||
const data = await this.scry('settings-store', `/bucket/${bucket}`);
|
||||
const data: Record<string, unknown> = await this.scry('settings-store', `/bucket/${bucket}`);
|
||||
this.store.handleEvent({ data: { 'settings-data': {
|
||||
'bucket-key': bucket,
|
||||
'bucket': data.bucket
|
||||
@ -62,7 +63,7 @@ export default class SettingsApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
async getEntry(bucket: Key, entry: Key) {
|
||||
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
|
||||
const data: Record<string, unknown> = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
|
||||
this.store.handleEvent({ data: { 'settings-data': {
|
||||
'bucket-key': bucket,
|
||||
'entry-key': entry,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import f from 'lodash/fp';
|
||||
import { Unreads, NotificationGraphConfig } from '@urbit/api';
|
||||
import { Unreads, NotificationGraphConfig, IndexedNotification } from '@urbit/api';
|
||||
|
||||
export function getLastSeen(
|
||||
unreads: Unreads,
|
||||
@ -44,3 +44,16 @@ export function isWatching(
|
||||
watch => watch.graph === graph && watch.index === index
|
||||
);
|
||||
}
|
||||
|
||||
export function getNotificationKey(time: BigInteger, notification: IndexedNotification): string {
|
||||
const base = time.toString();
|
||||
if('graph' in notification.index) {
|
||||
const { graph, index } = notification.index.graph;
|
||||
return `${base}-${graph}-${index}`;
|
||||
} else if('group' in notification.index) {
|
||||
const { group } = notification.index.group;
|
||||
return `${base}-${group}`;
|
||||
}
|
||||
return `${base}-unknown`;
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { IconRef } from '~/types';
|
||||
import f, { compose, memoize } from 'lodash/fp';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import { Association, Contact } from '@urbit/api';
|
||||
import useLocalState from '../state/local';
|
||||
import produce, { enableMapSet } from 'immer';
|
||||
import { enableMapSet } from 'immer';
|
||||
import useSettingsState from '../state/settings';
|
||||
import { State, UseStore } from 'zustand';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { BaseState } from '../state/base';
|
||||
|
||||
import anyAscii from 'any-ascii';
|
||||
|
||||
enableMapSet();
|
||||
@ -24,7 +23,9 @@ export const MOMENT_CALENDAR_DATE = {
|
||||
sameElse: '~YYYY.M.D'
|
||||
};
|
||||
|
||||
export const getModuleIcon = (mod: string) => {
|
||||
type GraphModule = 'link' | 'post' | 'chat' | 'publish';
|
||||
|
||||
export const getModuleIcon = (mod: GraphModule): IconRef => {
|
||||
if (mod === 'link') {
|
||||
return 'Collection';
|
||||
}
|
||||
@ -33,7 +34,7 @@ export const getModuleIcon = (mod: string) => {
|
||||
return 'Dashboard';
|
||||
}
|
||||
|
||||
return _.capitalize(mod);
|
||||
return _.capitalize(mod) as IconRef;
|
||||
};
|
||||
|
||||
export function wait(ms: number) {
|
||||
@ -172,9 +173,9 @@ export function dateToDa(d: Date, mil = false) {
|
||||
);
|
||||
}
|
||||
|
||||
export function deSig(ship: string) {
|
||||
export function deSig(ship: string): string {
|
||||
if (!ship) {
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
@ -226,11 +227,11 @@ export function writeText(str: string) {
|
||||
}
|
||||
|
||||
// trim patps to match dojo, chat-cli
|
||||
export function cite(ship: string) {
|
||||
export function cite(ship: string): string {
|
||||
let patp = ship,
|
||||
shortened = '';
|
||||
if (patp === null || patp === '') {
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
if (patp.startsWith('~')) {
|
||||
patp = patp.substr(1);
|
||||
@ -425,7 +426,7 @@ export const useHovering = (): useHoveringInterface => {
|
||||
};
|
||||
|
||||
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
||||
export function getItemTitle(association: Association) {
|
||||
export function getItemTitle(association: Association): string {
|
||||
if (DM_REGEX.test(association.resource)) {
|
||||
const [, , ship, name] = association.resource.split('/');
|
||||
if (ship.slice(1) === window.ship) {
|
||||
@ -433,6 +434,6 @@ export function getItemTitle(association: Association) {
|
||||
}
|
||||
return cite(ship);
|
||||
}
|
||||
return association.metadata.title || association.resource;
|
||||
return association.metadata.title ?? association.resource ?? '';
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import { ReactElement } from "react";
|
||||
import { UseStore } from "zustand";
|
||||
import { BaseState } from "../state/base";
|
||||
|
||||
|
@ -18,10 +18,10 @@ export function getTitleFromWorkspace(
|
||||
|
||||
export function getGroupFromWorkspace(
|
||||
workspace: Workspace
|
||||
): string | undefined {
|
||||
): string {
|
||||
if (workspace.type === 'group') {
|
||||
return workspace.group;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
|
@ -45,16 +45,15 @@ export interface BaseState<StateType> extends State {
|
||||
set: (fn: (state: StateType) => void) => void;
|
||||
}
|
||||
|
||||
export const createState = <StateType extends BaseState<any>>(
|
||||
export const createState = <T extends {}>(
|
||||
name: string,
|
||||
properties: Omit<StateType, 'set'>,
|
||||
properties: T,
|
||||
blacklist: string[] = []
|
||||
): UseStore<StateType> => create(persist((set, get) => ({
|
||||
// TODO why does this typing break?
|
||||
): UseStore<T & BaseState<T>> => create(persist((set, get) => ({
|
||||
set: fn => stateSetter(fn, set),
|
||||
...properties
|
||||
}), {
|
||||
blacklist,
|
||||
name: stateStorageKey(name),
|
||||
version: process.env.LANDSCAPE_SHORTHASH
|
||||
version: process.env.LANDSCAPE_SHORTHASH as any
|
||||
}));
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import f from 'lodash/fp';
|
||||
import create, { State } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
@ -7,7 +7,7 @@ import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgre
|
||||
|
||||
|
||||
export interface LocalState {
|
||||
theme: "light" | "dark" | "auto";
|
||||
theme: 'light' | 'dark' | 'auto';
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
remoteContentPolicy: RemoteContentPolicy;
|
||||
@ -21,6 +21,7 @@ export interface LocalState {
|
||||
hideLeapCats: LeapCategories[];
|
||||
setTutorialRef: (el: HTMLElement | null) => void;
|
||||
dark: boolean;
|
||||
mobile: boolean;
|
||||
background: BackgroundConfig;
|
||||
omniboxShown: boolean;
|
||||
suspendedFocus?: HTMLElement;
|
||||
@ -35,8 +36,9 @@ export const selectLocalState =
|
||||
|
||||
const useLocalState = create<LocalStateZus>(persist((set, get) => ({
|
||||
dark: false,
|
||||
mobile: false,
|
||||
background: undefined,
|
||||
theme: "auto",
|
||||
theme: 'auto',
|
||||
hideAvatars: false,
|
||||
hideNicknames: false,
|
||||
hideLeapCats: [],
|
||||
|
@ -1,6 +1,6 @@
|
||||
export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'profile', 'leap', 'notifications', 'done', 'exit'] as const;
|
||||
|
||||
export const leapCategories = ["mychannel", "messages", "updates", "profile", "logout"] as const;
|
||||
export const leapCategories = ["mychannel", "messages", "updates", "profile", "logout"];
|
||||
|
||||
export type LeapCategories = typeof leapCategories[number];
|
||||
|
||||
|
@ -93,16 +93,21 @@ class App extends React.Component {
|
||||
new GlobalSubscription(this.store, this.api, this.appChannel);
|
||||
|
||||
this.updateTheme = this.updateTheme.bind(this);
|
||||
this.updateMobile = this.updateMobile.bind(this);
|
||||
this.faviconString = this.faviconString.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription.start();
|
||||
const theme = this.getTheme();
|
||||
this.themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mobileWatcher = window.matchMedia(`(max-width: ${theme.breakpoints[0]})`);
|
||||
this.themeWatcher.onchange = this.updateTheme;
|
||||
this.mobileWatcher.onchange = this.updateMobile;
|
||||
setTimeout(() => {
|
||||
// Something about how the store works doesn't like changing it
|
||||
// before the app has actually rendered, hence the timeout.
|
||||
this.updateMobile(this.mobileWatcher);
|
||||
this.updateTheme(this.themeWatcher);
|
||||
}, 500);
|
||||
this.api.local.getBaseHash();
|
||||
@ -117,6 +122,7 @@ class App extends React.Component {
|
||||
|
||||
componentWillUnmount() {
|
||||
this.themeWatcher.onchange = undefined;
|
||||
this.mobileWatcher.onchange = undefined;
|
||||
}
|
||||
|
||||
updateTheme(e) {
|
||||
@ -125,6 +131,12 @@ class App extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
updateMobile(e) {
|
||||
this.props.set(state => {
|
||||
state.mobile = e.matches;
|
||||
});
|
||||
}
|
||||
|
||||
faviconString() {
|
||||
let background = '#ffffff';
|
||||
if (this.props.contacts.hasOwnProperty(`~${window.ship}`)) {
|
||||
@ -141,12 +153,16 @@ class App extends React.Component {
|
||||
return dataurl;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const theme =
|
||||
((props.dark && props?.display?.theme == "auto") ||
|
||||
getTheme() {
|
||||
const { props } = this;
|
||||
return ((props.dark && props?.display?.theme == "auto") ||
|
||||
props?.display?.theme == "dark"
|
||||
) ? dark : light;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
const theme = this.getTheme();
|
||||
|
||||
const ourContact = this.props.contacts[`~${this.ship}`] || null;
|
||||
return (
|
||||
|
@ -6,8 +6,6 @@ import { Sigil } from '~/logic/lib/sigil';
|
||||
import { createPost } from '~/logic/api/graph';
|
||||
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Envelope } from '~/types/chat-update';
|
||||
import { StorageState } from '~/types';
|
||||
import { Contact, Contacts, Content, Post } from '@urbit/api';
|
||||
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
|
||||
import withStorage from '~/views/components/withStorage';
|
||||
|
@ -3,44 +3,23 @@ import bigInt from 'big-integer';
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
Component,
|
||||
PureComponent,
|
||||
useCallback
|
||||
useMemo
|
||||
} from 'react';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
import { Box, Row, Text, Rule, BaseImage, Icon, Col } from '@tlon/indigo-react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import OverlaySigil from '~/views/components/OverlaySigil';
|
||||
import {
|
||||
uxToHex,
|
||||
cite,
|
||||
writeText,
|
||||
useShowNickname,
|
||||
useHideAvatar,
|
||||
useHovering,
|
||||
daToUnix
|
||||
} from '~/logic/lib/util';
|
||||
import {
|
||||
Group,
|
||||
Association,
|
||||
Contacts,
|
||||
Post,
|
||||
Groups,
|
||||
Associations
|
||||
} from '~/types';
|
||||
import TextContent from '../../../landscape/components/Graph/content/text';
|
||||
import CodeContent from '../../../landscape/components/Graph/content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import { Post } from '@urbit/api';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import styled from 'styled-components';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import Timestamp from '~/views/components/Timestamp';
|
||||
import useContactState, {useContact} from '~/logic/state/contact';
|
||||
import { useIdlingState } from '~/logic/lib/idling';
|
||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||
|
@ -48,7 +48,7 @@ class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
return isWriter(group, association.resource);
|
||||
}
|
||||
|
||||
renderItem = React.forwardRef(({ index, scrollWindow }, ref) => {
|
||||
renderItem = React.forwardRef<HTMLDivElement>(({ index, scrollWindow }, ref) => {
|
||||
const { props } = this;
|
||||
const { association, graph, api } = props;
|
||||
const [, , ship, name] = association.resource.split("/");
|
||||
|
@ -180,7 +180,6 @@ function getNodeUrl(
|
||||
const graphUrl = `/~landscape${groupPath}/resource/${mod}${graph}`;
|
||||
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}`;
|
||||
|
@ -26,6 +26,7 @@ import { useLazyScroll } from '~/logic/lib/useLazyScroll';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useInviteState from '~/logic/state/invite';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import {getNotificationKey} from '~/logic/lib/hark';
|
||||
|
||||
type DatedTimebox = [BigInteger, Timebox];
|
||||
|
||||
@ -121,13 +122,14 @@ export default function Inbox(props: {
|
||||
);
|
||||
|
||||
return (
|
||||
<Col p="1" ref={scrollRef} position="relative" height="100%" overflowY="auto">
|
||||
<Col p="1" ref={scrollRef} position="relative" height="100%" overflowY="auto" overflowX="hidden">
|
||||
<Invites pendingJoin={props.pendingJoin} api={api} />
|
||||
{[...notificationsByDayMap.keys()].sort().reverse().map((day, index) => {
|
||||
const timeboxes = notificationsByDayMap.get(day)!;
|
||||
return timeboxes.length > 0 && (
|
||||
<DaySection
|
||||
key={day}
|
||||
time={day}
|
||||
label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
|
||||
timeboxes={timeboxes}
|
||||
archive={Boolean(props.showArchive)}
|
||||
@ -166,6 +168,7 @@ function DaySection({
|
||||
label,
|
||||
archive,
|
||||
timeboxes,
|
||||
time,
|
||||
api,
|
||||
}) {
|
||||
const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
|
||||
@ -178,7 +181,7 @@ function DaySection({
|
||||
{_.map(timeboxes.sort(sortTimeboxes), ([date, nots], i: number) =>
|
||||
_.map(nots.sort(sortIndexedNotification), (not, j: number) => (
|
||||
<Notification
|
||||
key={j}
|
||||
key={getNotificationKey(time, not)}
|
||||
api={api}
|
||||
notification={not}
|
||||
archived={archive}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { ReactNode, useCallback, useMemo, useState } from "react";
|
||||
import { Row, Box, Icon } from "@tlon/indigo-react";
|
||||
import { Row, Box, Icon, Button } from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
GraphNotificationContents,
|
||||
@ -19,7 +19,13 @@ import { GraphNotification } from "./graph";
|
||||
import { BigInteger } from "big-integer";
|
||||
import { useHovering } from "~/logic/lib/util";
|
||||
import useHarkState from "~/logic/state/hark";
|
||||
import {IS_MOBILE} from "~/logic/lib/platform";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
import { IS_MOBILE } from "~/logic/lib/platform";
|
||||
import styled from "styled-components";
|
||||
import { useSpring, animated } from "@react-spring/web";
|
||||
import { useDrag } from "react-use-gesture";
|
||||
import { SwipeMenu } from "~/views/components/SwipeMenu";
|
||||
import {getNotificationKey} from "~/logic/lib/hark";
|
||||
|
||||
interface NotificationProps {
|
||||
notification: IndexedNotification;
|
||||
@ -62,6 +68,8 @@ export function NotificationWrapper(props: {
|
||||
}) {
|
||||
const { api, time, notification, children } = props;
|
||||
|
||||
const isMobile = useLocalState(s => s.mobile);
|
||||
|
||||
const onArchive = useCallback(async () => {
|
||||
if (!(time && notification)) {
|
||||
return;
|
||||
@ -83,7 +91,7 @@ export function NotificationWrapper(props: {
|
||||
return api.hark[func](notification);
|
||||
}, [notification, api, isMuted]);
|
||||
|
||||
const onClick = () => {
|
||||
const onClick = (e: any) => {
|
||||
if (!(time && notification) || notification.notification.read) {
|
||||
return;
|
||||
}
|
||||
@ -92,8 +100,18 @@ export function NotificationWrapper(props: {
|
||||
|
||||
const { hovering, bind } = useHovering();
|
||||
|
||||
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
|
||||
return (
|
||||
<SwipeMenu
|
||||
key={(time && notification && getNotificationKey(time, notification)) ?? 'unknown'}
|
||||
m={2}
|
||||
menuWidth={100}
|
||||
disabled={!isMobile}
|
||||
menu={
|
||||
<Button onClick={onArchive} ml="2" height="100%" width="92px" primary destructive>
|
||||
Remove
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
onClick={onClick}
|
||||
bg={
|
||||
@ -107,7 +125,6 @@ export function NotificationWrapper(props: {
|
||||
gridTemplateRows="auto"
|
||||
gridTemplateAreas="'header actions' 'main main'"
|
||||
p={2}
|
||||
m={2}
|
||||
{...bind}
|
||||
>
|
||||
{children}
|
||||
@ -116,7 +133,7 @@ export function NotificationWrapper(props: {
|
||||
gapX="2"
|
||||
gridArea="actions"
|
||||
justifyContent="flex-end"
|
||||
opacity={[1, (hovering || IS_MOBILE) ? 1 : 0]}
|
||||
opacity={[0, hovering ? 1 : 0]}
|
||||
>
|
||||
{time && notification && (
|
||||
<StatelessAsyncAction
|
||||
@ -130,6 +147,7 @@ export function NotificationWrapper(props: {
|
||||
)}
|
||||
</Row>
|
||||
</Box>
|
||||
</SwipeMenu>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ export function ProfileImages(props: any): ReactElement {
|
||||
const { contact, hideCover, ship } = { ...props };
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
|
||||
|
||||
const anchorRef = useRef<HTMLElement | null>(null)
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useTutorialModal('profile', ship === `~${window.ship}`, anchorRef);
|
||||
|
||||
|
@ -41,8 +41,8 @@ const DropdownOptions = styled(Box)`
|
||||
|
||||
export function Dropdown(props: DropdownProps): ReactElement {
|
||||
const { children, options, offsetX = 0, offsetY = 0, flexShrink = 1 } = props;
|
||||
const dropdownRef = useRef<HTMLElement>(null);
|
||||
const anchorRef = useRef<HTMLElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const { pathname } = useLocation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [coords, setCoords] = useState({});
|
||||
|
@ -45,7 +45,7 @@ type DropdownSearchProps<C> = PropFunc<typeof Box> &
|
||||
DropdownSearchExtraProps<C>;
|
||||
|
||||
export function DropdownSearch<C>(props: DropdownSearchProps<C>): ReactElement {
|
||||
const textarea = useRef<HTMLTextAreaElement>();
|
||||
const textarea = useRef<HTMLTextAreaElement>(null);
|
||||
const {
|
||||
candidates,
|
||||
getKey,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { RefObject } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
@ -21,11 +21,11 @@ interface HoverBoxLinkProps {
|
||||
to: string;
|
||||
}
|
||||
|
||||
export const HoverBoxLink = React.forwardRef(({
|
||||
export const HoverBoxLink = React.forwardRef<HTMLAnchorElement, HoverBoxLinkProps & PropFunc<typeof HoverBox>>(({
|
||||
to,
|
||||
children,
|
||||
...rest
|
||||
}: HoverBoxLinkProps & PropFunc<typeof HoverBox>, ref) => (
|
||||
}, ref) => (
|
||||
<Link ref={ref} to={to}>
|
||||
<HoverBox {...rest}>{children}</HoverBox>
|
||||
</Link>
|
||||
|
@ -24,7 +24,7 @@ const prompt = (field, uploading, meta, clickUploadButton) => {
|
||||
if (!field.value && !uploading && meta.error === undefined) {
|
||||
return (
|
||||
<Text
|
||||
black
|
||||
color='black'
|
||||
fontWeight='500'
|
||||
position='absolute'
|
||||
left={2}
|
||||
|
@ -3,7 +3,7 @@ import { Box } from '@tlon/indigo-react';
|
||||
import { PropFunc } from '~/types/util';
|
||||
|
||||
interface ModalOverlayProps {
|
||||
spacing: PropFunc<typeof Box>['m'];
|
||||
spacing?: PropFunc<typeof Box>['m'];
|
||||
dismiss: () => void;
|
||||
}
|
||||
type Props = ModalOverlayProps & PropFunc<typeof Box>;
|
||||
|
@ -55,8 +55,8 @@ const ProfileOverlay = (props: ProfileOverlayProps) => {
|
||||
const [coords, setCoords] = useState({});
|
||||
const [visible, setVisible] = useState(false);
|
||||
const history = useHistory();
|
||||
const outerRef = useRef<HTMLElement | null>(null);
|
||||
const innerRef = useRef<HTMLElement | null>(null);
|
||||
const outerRef = useRef<HTMLDivElement>(null);
|
||||
const innerRef = useRef<HTMLDivElement>(null);
|
||||
const hideAvatars = useSettingsState(state => state.calm.hideAvatars);
|
||||
const hideNicknames = useSettingsState(state => state.calm.hideNicknames);
|
||||
const isOwn = useMemo(() => window.ship === ship, [ship]);
|
||||
|
@ -5,7 +5,7 @@ import { LoadingSpinner, Action } from '@tlon/indigo-react';
|
||||
|
||||
interface AsyncActionProps {
|
||||
children: ReactNode;
|
||||
name: string;
|
||||
name?: string;
|
||||
disabled?: boolean;
|
||||
onClick: (e: React.MouseEvent) => Promise<void>;
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ export function StatelessAsyncToggle({
|
||||
} = useStatelessAsyncClickable(onClick, name);
|
||||
|
||||
return state === 'error' ? (
|
||||
<Text mr="2">Error</Text>
|
||||
<Text>Error</Text>
|
||||
) : state === 'loading' ? (
|
||||
<LoadingSpinner mr="2" foreground={'white'} background="gray" />
|
||||
<LoadingSpinner foreground={'white'} background="gray" />
|
||||
) : state === 'success' ? (
|
||||
<Text mr="2">Done</Text>
|
||||
<Text mx="2">Done</Text>
|
||||
) : (
|
||||
<Toggle onClick={handleClick} {...rest} />
|
||||
);
|
||||
|
@ -121,13 +121,7 @@ const StatusBar = (props) => {
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text color='#000000'>
|
||||
Submit{' '}
|
||||
<Text color='#000000' display={['none', 'inline']}>
|
||||
an
|
||||
</Text>{' '}
|
||||
issue
|
||||
</Text>
|
||||
<Icon icon="Bug" color="#000000" />
|
||||
</StatusBarItem>
|
||||
<StatusBarItem
|
||||
width='32px'
|
||||
|
93
pkg/interface/src/views/components/SwipeMenu.tsx
Normal file
93
pkg/interface/src/views/components/SwipeMenu.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import React, { useMemo, useState, ReactNode, ReactChildren } from "react";
|
||||
import { animated, useSpring } from "@react-spring/web";
|
||||
import { useDrag } from "react-use-gesture";
|
||||
import { Box, Row } from "@tlon/indigo-react";
|
||||
import styled from "styled-components";
|
||||
import { PropFunc } from "~/types";
|
||||
|
||||
const DEFAULT_THRESHOLD = 10;
|
||||
const AnimBox = styled(animated(Box))`
|
||||
touch-action: pan-y;
|
||||
`;
|
||||
const AnimRow = styled(animated(Row))`
|
||||
touch-action: pan-y;
|
||||
`;
|
||||
const NoScrollBox = styled(Box)`
|
||||
touch-action: pan-y;
|
||||
`;
|
||||
|
||||
export function SwipeMenu(
|
||||
props: {
|
||||
children: ReactNode;
|
||||
disabled?: boolean;
|
||||
menu: ReactNode;
|
||||
menuWidth: number;
|
||||
threshold?: number;
|
||||
} & PropFunc<typeof Box>
|
||||
) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
const {
|
||||
children,
|
||||
disabled = false,
|
||||
menu,
|
||||
menuWidth,
|
||||
threshold = DEFAULT_THRESHOLD,
|
||||
...rest
|
||||
} = props;
|
||||
const [{ x, opacity }, springApi] = useSpring(() => ({
|
||||
x: 0,
|
||||
opacity: 0,
|
||||
config: {
|
||||
tension: 240,
|
||||
friction: 30
|
||||
}
|
||||
}));
|
||||
const activationDistance = threshold - menuWidth;
|
||||
|
||||
const sliderBind = useDrag(
|
||||
({ active, movement: [x], tap }) => {
|
||||
if (dragging !== active) {
|
||||
setDragging(active);
|
||||
}
|
||||
if (active && x < activationDistance) {
|
||||
setOpen(true);
|
||||
} else if (active && x > -1 * threshold) {
|
||||
setOpen(false);
|
||||
}
|
||||
return springApi.start({
|
||||
x: active ? Math.min(0, x) : open ? -1 * menuWidth : 0,
|
||||
opacity: open
|
||||
? 1
|
||||
: active
|
||||
? Math.abs(Math.min(1, Math.min(0, x) / activationDistance))
|
||||
: 0,
|
||||
});
|
||||
},
|
||||
{
|
||||
enabled: !disabled,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<NoScrollBox {...rest} position="relative">
|
||||
<AnimBox {...sliderBind()}>
|
||||
<AnimBox style={{ x }}>{children}</AnimBox>
|
||||
</AnimBox>
|
||||
<AnimRow
|
||||
top="0px"
|
||||
position="absolute"
|
||||
zIndex={1}
|
||||
height="100%"
|
||||
right="0px"
|
||||
style={{
|
||||
translateX: x.to((x) => x + menuWidth),
|
||||
opacity,
|
||||
}}
|
||||
>
|
||||
{menu}
|
||||
</AnimRow>
|
||||
</NoScrollBox>
|
||||
);
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { PropFunc } from '@urbit/api';
|
||||
import { PropFunc } from '~/types';
|
||||
|
||||
export type Direction = 'East' | 'South' | 'West' | 'North';
|
||||
type TriangleProps = PropFunc<typeof Box> & {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, MutableRefObject } from "react";
|
||||
import { TutorialProgress } from "@urbit/api";
|
||||
import useLocalState, { selectLocalState } from "~/logic/state/local";
|
||||
import { useEffect, MutableRefObject } from 'react';
|
||||
import { TutorialProgress } from '~/types';
|
||||
import useLocalState, { selectLocalState } from '~/logic/state/local';
|
||||
|
||||
const localSelector = selectLocalState(['tutorialProgress', 'setTutorialRef']);
|
||||
|
||||
@ -16,7 +16,7 @@ export function useTutorialModal(
|
||||
setTutorialRef(anchorRef.current);
|
||||
}
|
||||
|
||||
return () => {}
|
||||
return () => {};
|
||||
}, [tutorialProgress, show, anchorRef]);
|
||||
|
||||
return show && onProgress === tutorialProgress;
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React from 'react';
|
||||
import useStorage, {IuseStorage} from '~/logic/lib/useStorage';
|
||||
import useStorage, { IuseStorage } from '~/logic/lib/useStorage';
|
||||
|
||||
const withStorage = <P, C extends React.ComponentType<P>>(Component: C, params = {}) => {
|
||||
const withStorage = <P, C extends React.ComponentType<P & IuseStorage>>(
|
||||
Component: C,
|
||||
params = {}
|
||||
) => {
|
||||
return React.forwardRef<C, Omit<C, keyof IuseStorage>>((props, ref) => {
|
||||
const storage = useStorage(params);
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
import React from "react";
|
||||
import { Post, ReferenceContent } from "@urbit/api";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import React from 'react';
|
||||
import { Content, Post, ReferenceContent } from '@urbit/api';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import TextContent from "./content/text";
|
||||
import CodeContent from "./content/code";
|
||||
import RemoteContent from "~/views/components/RemoteContent";
|
||||
import { Mention } from "~/views/components/MentionText";
|
||||
import { PermalinkEmbed } from "~/views/apps/permalinks/embed";
|
||||
import { referenceToPermalink } from "~/logic/lib/permalinks";
|
||||
import { PropFunc } from "~/types";
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import TextContent from './content/text';
|
||||
import CodeContent from './content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import { PermalinkEmbed } from '~/views/apps/permalinks/embed';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { PropFunc } from '~/types';
|
||||
|
||||
function GraphContentWideInner(
|
||||
props: {
|
||||
@ -23,21 +23,20 @@ function GraphContentWideInner(
|
||||
|
||||
return (
|
||||
<Box {...rest}>
|
||||
{post.contents.map((content, i) => {
|
||||
switch (Object.keys(content)[0]) {
|
||||
case "text":
|
||||
{post.contents.map((content: Content, i) => {
|
||||
if ('text' in content) {
|
||||
return (
|
||||
<TextContent
|
||||
key={i}
|
||||
api={api}
|
||||
fontSize={1}
|
||||
lineHeight={"20px"}
|
||||
lineHeight={'20px'}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
case "code":
|
||||
} else if ('code' in content) {
|
||||
return <CodeContent key={i} content={content} />;
|
||||
case "reference":
|
||||
} else if ('reference' in content) {
|
||||
const { link } = referenceToPermalink(content as ReferenceContent);
|
||||
return (
|
||||
<PermalinkEmbed
|
||||
@ -47,7 +46,7 @@ function GraphContentWideInner(
|
||||
showOurContact={showOurContact}
|
||||
/>
|
||||
);
|
||||
case "url":
|
||||
} else if ('url' in content) {
|
||||
return (
|
||||
<Box
|
||||
key={i}
|
||||
@ -65,18 +64,14 @@ function GraphContentWideInner(
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
case "mention":
|
||||
const first = (i) => i === 0;
|
||||
return (
|
||||
<Mention
|
||||
} else if ('mention' in content) {
|
||||
const first = i => i === 0;
|
||||
return (<Mention
|
||||
key={i}
|
||||
first={first(i)}
|
||||
ship={content.mention}
|
||||
api={api}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
/>);
|
||||
}
|
||||
})}
|
||||
</Box>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { ReactElement, ReactNode, useRef } from 'react';
|
||||
import { Metadata, PropFunc } from '@urbit/api';
|
||||
import { Metadata } from '@urbit/api';
|
||||
import { PropFunc } from '~/types';
|
||||
import { Col, Row, Text } from '@tlon/indigo-react';
|
||||
import { MetadataIcon } from './MetadataIcon';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
|
@ -31,7 +31,7 @@ const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
|
||||
</Link>
|
||||
);
|
||||
|
||||
function RecentGroups(props: { recent: string[]; associations: Associations }) {
|
||||
function RecentGroups(props: { recent: string[] }) {
|
||||
const { recent } = props;
|
||||
if (recent.length < 2) {
|
||||
return null;
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React, { useEffect, ReactNode } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
Switch,
|
||||
Route,
|
||||
RouteComponentProps
|
||||
} from 'react-router-dom';
|
||||
import { Col, Box, Text } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
@ -28,7 +27,6 @@ import { getGroupFromWorkspace } from '~/logic/lib/workspace';
|
||||
import { GroupHome } from './Home/GroupHome';
|
||||
import { EmptyGroupHome } from './Home/EmptyGroupHome';
|
||||
import { Workspace } from '~/types/workspace';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
@ -42,17 +40,12 @@ type GroupsPaneProps = StoreState & {
|
||||
export function GroupsPane(props: GroupsPaneProps) {
|
||||
const { baseUrl, api, workspace } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
const notificationsCount = useHarkState(state => state.notificationsCount);
|
||||
|
||||
const relativePath = (path: string) => baseUrl + path;
|
||||
const groupPath = getGroupFromWorkspace(workspace);
|
||||
const groups = useGroupState(state => state.groups);
|
||||
|
||||
const groupContacts = Object.assign({}, ...Array.from(groups?.[groupPath]?.members ?? []).filter(e => contacts[`~${e}`]).map(e => {
|
||||
return {[e]: contacts[`~${e}`]};
|
||||
})) || {};
|
||||
const rootIdentity = contacts?.["/~/default"]?.[window.ship];
|
||||
const groupAssociation =
|
||||
(groupPath && associations.groups[groupPath]) || undefined;
|
||||
const group = (groupPath && groups[groupPath]) || undefined;
|
||||
@ -75,8 +68,6 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
const popovers = (routeProps: RouteComponentProps, baseUrl: string) =>
|
||||
( <>
|
||||
{groupPath && ( <PopoverRoutes
|
||||
contacts={groupContacts || {}}
|
||||
rootIdentity={rootIdentity}
|
||||
association={groupAssociation!}
|
||||
group={group!}
|
||||
api={api}
|
||||
@ -202,10 +193,11 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
</title>
|
||||
</Helmet>
|
||||
<Skeleton
|
||||
{...props}
|
||||
mobileHide={shouldHideSidebar}
|
||||
recentGroups={recentGroups}
|
||||
baseUrl={baseUrl}
|
||||
{...props}>
|
||||
>
|
||||
{ workspace.type === 'group' ? (
|
||||
<GroupHome
|
||||
api={api}
|
||||
|
@ -1,27 +1,24 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { ModalOverlay } from "~/views/components/ModalOverlay";
|
||||
import { Formik, Form, FormikHelpers } from "formik";
|
||||
import React, { useCallback } from 'react';
|
||||
import { ModalOverlay } from '~/views/components/ModalOverlay';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
import {
|
||||
GroupFeedPermissions,
|
||||
GroupFeedPermsInput,
|
||||
} from "./Post/GroupFeedPerms";
|
||||
import { Text, Button, Col, Row } from "@tlon/indigo-react";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { resourceFromPath, Tag, resourceAsPath } from "@urbit/api";
|
||||
import useGroupState, { useGroup } from "~/logic/state/group";
|
||||
GroupFeedPermsInput
|
||||
} from './Post/GroupFeedPerms';
|
||||
import { Text, Button, Col, Row } from '@tlon/indigo-react';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { resourceFromPath, Tag, resourceAsPath } from '@urbit/api';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import useMetadataState from "~/logic/state/metadata";
|
||||
|
||||
|
||||
interface FormSchema {
|
||||
permissions: GroupFeedPermissions;
|
||||
permissions: any;
|
||||
}
|
||||
|
||||
export function EnableGroupFeed(props: {
|
||||
groupPath: string;
|
||||
dismiss: () => void;
|
||||
api: GlobalApi;
|
||||
baseUrl: string;
|
||||
}) {
|
||||
const { api, groupPath, baseUrl } = props;
|
||||
|
||||
@ -31,7 +28,7 @@ export function EnableGroupFeed(props: {
|
||||
};
|
||||
|
||||
const initialValues: FormSchema = {
|
||||
permissions: "everyone",
|
||||
permissions: 'everyone'
|
||||
};
|
||||
const onSubmit =
|
||||
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
||||
|
@ -44,6 +44,7 @@ interface NewChannelProps {
|
||||
api: GlobalApi;
|
||||
group?: string;
|
||||
workspace: Workspace;
|
||||
baseUrl?: string;
|
||||
}
|
||||
|
||||
export function NewChannel(props: NewChannelProps): ReactElement {
|
||||
@ -118,7 +119,7 @@ export function NewChannel(props: NewChannelProps): ReactElement {
|
||||
<Box
|
||||
pb='3'
|
||||
display={workspace?.type === 'messages' ? 'none' : ['block', 'none']}
|
||||
onClick={() => history.push(props.baseUrl)}
|
||||
onClick={() => history.push(props?.baseUrl ?? '/')}
|
||||
>
|
||||
<Text>{'<- Back'}</Text>
|
||||
</Box>
|
||||
|
@ -2,7 +2,8 @@ import React, {
|
||||
useState,
|
||||
useMemo,
|
||||
useCallback,
|
||||
ChangeEvent
|
||||
ChangeEvent,
|
||||
ReactElement
|
||||
} from 'react';
|
||||
import {
|
||||
Col,
|
||||
@ -30,10 +31,8 @@ import { roleForShip, resourceFromPath } from '~/logic/lib/group';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import {deSig} from '@urbit/api';
|
||||
|
||||
const TruncText = styled(Text)`
|
||||
white-space: nowrap;
|
||||
|
@ -2,8 +2,6 @@ import React, { useRef, useCallback, ReactElement } from 'react';
|
||||
import { Route, Switch, RouteComponentProps, Link } from 'react-router-dom';
|
||||
import { Box, Col, Text } from '@tlon/indigo-react';
|
||||
|
||||
import { GroupNotificationsConfig, Associations } from '@urbit/api';
|
||||
import { Contacts, Contact } from '@urbit/api/contacts';
|
||||
import { Group } from '@urbit/api/groups';
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
|
||||
@ -15,7 +13,6 @@ import { DeleteGroup } from './DeleteGroup';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import { ModalOverlay } from '~/views/components/ModalOverlay';
|
||||
import { SidebarItem } from '~/views/landscape/components/SidebarItem';
|
||||
import { StorageState } from '~/types';
|
||||
|
||||
export function PopoverRoutes(
|
||||
props: {
|
||||
@ -23,8 +20,6 @@ export function PopoverRoutes(
|
||||
group: Group;
|
||||
association: Association;
|
||||
api: GlobalApi;
|
||||
notificationsGroupConfig: GroupNotificationsConfig;
|
||||
rootIdentity: Contact;
|
||||
} & RouteComponentProps
|
||||
): ReactElement {
|
||||
const relativeUrl = (url: string) => `${props.baseUrl}/popover${url}`;
|
||||
|
@ -15,7 +15,7 @@ import useGroupState from '~/logic/state/group';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import {Workspace} from '~/types';
|
||||
import { Workspace } from '~/types';
|
||||
|
||||
type ResourceProps = StoreState & {
|
||||
association: Association;
|
||||
@ -25,23 +25,20 @@ type ResourceProps = StoreState & {
|
||||
};
|
||||
|
||||
export function Resource(props: ResourceProps): ReactElement {
|
||||
const { association, api, notificationsGraphConfig } = props;
|
||||
const { association, api } = props;
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const notificationsCount = useHarkState(state => state.notificationsCount);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
const app = association.metadata?.config?.graph || association['app-name'];
|
||||
const rid = association.resource;
|
||||
const selectedGroup = association.group;
|
||||
const relativePath = (p: string) =>
|
||||
`${props.baseUrl}/resource/${app}${rid}${p}`;
|
||||
const { resource: rid, group: selectedGroup } = association;
|
||||
const relativePath = (p: string) => `${props.baseUrl}/resource/${app}${rid}${p}`;
|
||||
const skelProps = { api, association, groups, contacts };
|
||||
let title = props.association.metadata.title;
|
||||
if ('workspace' in props) {
|
||||
if ('group' in props.workspace && props.workspace.group in associations.groups) {
|
||||
title = `${associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
|
@ -47,7 +47,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
|
||||
? getItemTitle(association)
|
||||
: association?.metadata?.title;
|
||||
|
||||
let recipient = "";
|
||||
let recipient = '';
|
||||
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
|
||||
|
@ -6,13 +6,8 @@ import {
|
||||
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { GroupSwitcher } from '../GroupSwitcher';
|
||||
import {
|
||||
Associations,
|
||||
Workspace,
|
||||
Groups,
|
||||
Invites,
|
||||
Rolodex
|
||||
} from '@urbit/api';
|
||||
import { Workspace } from '~/types';
|
||||
import { SidebarListConfig } from './types';
|
||||
import { SidebarListHeader } from './SidebarListHeader';
|
||||
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
||||
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
|
||||
@ -21,7 +16,6 @@ import { SidebarList } from './SidebarList';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const ScrollbarLessCol = styled(Col)`
|
||||
scrollbar-width: none !important;
|
||||
@ -32,7 +26,6 @@ const ScrollbarLessCol = styled(Col)`
|
||||
`;
|
||||
|
||||
interface SidebarProps {
|
||||
children: ReactNode;
|
||||
recentGroups: string[];
|
||||
api: GlobalApi;
|
||||
selected?: string;
|
||||
@ -61,7 +54,7 @@ export function Sidebar(props: SidebarProps): ReactElement | null {
|
||||
const role = groups?.[groupPath] ? roleForShip(groups[groupPath], window.ship) : undefined;
|
||||
const isAdmin = (role === 'admin') || (workspace?.type === 'home');
|
||||
|
||||
const anchorRef = useRef<HTMLElement | null>(null);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
useTutorialModal('channels', true, anchorRef);
|
||||
|
||||
return (
|
||||
@ -91,7 +84,6 @@ export function Sidebar(props: SidebarProps): ReactElement | null {
|
||||
selected={selected || ''}
|
||||
workspace={workspace}
|
||||
api={props.api}
|
||||
history={props.history}
|
||||
/>
|
||||
<SidebarList
|
||||
config={config}
|
||||
|
@ -2,36 +2,20 @@ import React, { ReactElement, useRef } from 'react';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
import { Icon, Row, Box, Text, BaseImage } from '@tlon/indigo-react';
|
||||
import { Groups, Association, Rolodex } from '@urbit/api';
|
||||
import { Association } from '@urbit/api';
|
||||
|
||||
import { HoverBoxLink } from '~/views/components/HoverBox';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { getModuleIcon, getItemTitle, uxToHex } from '~/logic/lib/util';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
|
||||
import { SidebarAppConfigs, SidebarItemStatus } from './types';
|
||||
import { SidebarAppConfigs } from './types';
|
||||
import { Workspace } from '~/types/workspace';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import Dot from '~/views/components/Dot';
|
||||
|
||||
|
||||
function SidebarItemIndicator(props: { status?: SidebarItemStatus }) {
|
||||
switch (props.status) {
|
||||
case 'disconnected':
|
||||
return <Icon ml={2} fill="red" icon="X" />;
|
||||
case 'unsubscribed':
|
||||
return <Icon ml={2} icon="Circle" fill="gray" />;
|
||||
case 'mention':
|
||||
return <Icon ml={2} icon="Circle" />;
|
||||
case 'loading':
|
||||
return <Icon ml={2} icon="Bullet" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
export function SidebarItem(props: {
|
||||
hideUnjoined: boolean;
|
||||
@ -48,7 +32,7 @@ export function SidebarItem(props: {
|
||||
const rid = association?.resource;
|
||||
const groupPath = association?.group;
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const anchorRef = useRef<HTMLElement | null>(null);
|
||||
const anchorRef = useRef<HTMLAnchorElement>(null);
|
||||
const { hideAvatars, hideNicknames } = useSettingsState(selectCalmState);
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
useTutorialModal(
|
||||
@ -99,11 +83,11 @@ export function SidebarItem(props: {
|
||||
return null;
|
||||
}
|
||||
|
||||
let img = null;
|
||||
let img: null | JSX.Element = null;
|
||||
|
||||
if (urbitOb.isValidPatp(title)) {
|
||||
if (contacts?.[title]?.avatar && !hideAvatars) {
|
||||
img = <BaseImage referrerPolicy="no-referrer" src={contacts[title].avatar} width='16px' height='16px' borderRadius={2} />;
|
||||
img = <BaseImage referrerPolicy="no-referrer" src={contacts?.[title].avatar ?? ''} width='16px' height='16px' borderRadius={2} />;
|
||||
} else {
|
||||
img = <Sigil ship={title} color={`#${uxToHex(contacts?.[title]?.color || '0x0')}`} icon padding={2} size={16} />;
|
||||
}
|
||||
@ -137,7 +121,7 @@ export function SidebarItem(props: {
|
||||
<Icon
|
||||
display="block"
|
||||
color={isSynced ? 'black' : 'lightGray'}
|
||||
icon={getModuleIcon(mod) as any}
|
||||
icon={getModuleIcon(mod)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export function SidebarListHeader(props: {
|
||||
baseUrl: string;
|
||||
selected: string;
|
||||
workspace: Workspace;
|
||||
handleSubmit: (c: SidebarListConfig) => void;
|
||||
handleSubmit: (s: any) => void;
|
||||
}): ReactElement {
|
||||
const history = useHistory();
|
||||
const onSubmit = useCallback(
|
||||
|
@ -18,5 +18,5 @@ export interface SidebarAppConfig {
|
||||
}
|
||||
|
||||
export type SidebarAppConfigs = {
|
||||
[a in 'chat' | 'link' | 'publish']: SidebarAppConfig;
|
||||
graph: SidebarAppConfig;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { Row, Icon, Text } from '@tlon/indigo-react';
|
||||
|
||||
import { IconRef, PropFunc } from '~/types/util';
|
||||
|
@ -52,7 +52,6 @@ export function Skeleton(props: SkeletonProps): ReactElement {
|
||||
baseUrl={props.baseUrl}
|
||||
mobileHide={props.mobileHide}
|
||||
workspace={props.workspace}
|
||||
history={props.history}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
{props.children}
|
||||
|
@ -43,7 +43,7 @@ export function TutorialModal(props: { api: GlobalApi }) {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
arrow,
|
||||
arrow = 'North',
|
||||
alignX,
|
||||
alignY,
|
||||
offsetX,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component, useEffect, useCallback, ReactElement } from 'react';
|
||||
import React, { useEffect, useCallback, ReactElement } from 'react';
|
||||
import { Route, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
@ -18,8 +18,7 @@ import { Loading } from '../components/Loading';
|
||||
import { Workspace } from '~/types/workspace';
|
||||
import GlobalSubscription from '~/logic/subscription/global';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import useHarkState, { withHarkState } from '~/logic/state/hark';
|
||||
import withState from '~/logic/lib/withState';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
|
41
pkg/npm/api/README.md
Normal file
41
pkg/npm/api/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Urbit API in JavaScript
|
||||
|
||||
This package simplifies the process of working with Urbit's APIs into fluent, typed functions organized by app. Pairs well with `@urbit/http-api`. Compare:
|
||||
|
||||
Without:
|
||||
```ts
|
||||
import UrbitInterface from '@urbit/http-api';
|
||||
const api: UrbitInterface = useApi();
|
||||
api.poke({
|
||||
app: 'settings-store',
|
||||
mark: 'settings-event',
|
||||
json: {
|
||||
'put-entry': {
|
||||
'bucket-key': bucket,
|
||||
'entry-key': key,
|
||||
'value': value
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
With:
|
||||
```ts
|
||||
import UrbitInterface from '@urbit/http-api';
|
||||
import { settings } from '@urbit/api';
|
||||
const api: UrbitInterface = useApi();
|
||||
api.poke(setings.putEntry(bucket, key, value));
|
||||
```
|
||||
|
||||
You may import single functions
|
||||
```ts
|
||||
import { putEntry } from '@urbit/api';
|
||||
```
|
||||
or whole apps:
|
||||
```ts
|
||||
import { settings } from '@urbit/api';
|
||||
```
|
||||
|
||||
This package also provides types and utilities for working with Urbit's internal data structures, such as Nouns, Das, Tas, and so forth.
|
||||
|
||||
This package was originally developed as part of Tlon's Landscape client and therefore the best reference material exists [there](https://github.com/urbit/urbit/tree/master/pkg/interface/src).
|
@ -13,9 +13,11 @@ import {
|
||||
ContactUpdateSetPublic,
|
||||
} from "./types";
|
||||
|
||||
const storeAction = <T extends ContactUpdate>(data: T): Poke<T> => ({
|
||||
export const CONTACT_UPDATE_VERSION: number = 0;
|
||||
|
||||
const storeAction = <T extends ContactUpdate>(data: T, version: number = CONTACT_UPDATE_VERSION): Poke<T> => ({
|
||||
app: "contact-store",
|
||||
mark: "contact-action",
|
||||
mark: `contact-update-${version}`,
|
||||
json: data,
|
||||
});
|
||||
|
||||
@ -34,9 +36,9 @@ export const removeContact = (ship: Patp): Poke<ContactUpdateRemove> =>
|
||||
remove: { ship },
|
||||
});
|
||||
|
||||
export const share = (recipient: Patp): Poke<ContactShare> => ({
|
||||
export const share = (recipient: Patp, version: number = CONTACT_UPDATE_VERSION): Poke<ContactShare> => ({
|
||||
app: "contact-push-hook",
|
||||
mark: "contact-action",
|
||||
mark: `contact-update-${version}`,
|
||||
json: { share: recipient },
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import { GroupPolicy, makeResource, resourceFromPath } from '../groups';
|
||||
import { GroupPolicy, makeResource, Resource, resourceFromPath } from '../groups';
|
||||
|
||||
import { deSig, unixToDa } from '../lib';
|
||||
import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types';
|
||||
import { Content, GraphChildrenPoke, GraphNode, GraphNodePoke, Post } from './types';
|
||||
import { Content, Graph, GraphChildrenPoke, GraphNode, GraphNodePoke, Post } from './types';
|
||||
|
||||
export const GRAPH_UPDATE_VERSION: number = 1;
|
||||
|
||||
export const createBlankNodeWithChildPost = (
|
||||
ship: PatpNoSig,
|
||||
@ -40,12 +42,15 @@ export const createBlankNodeWithChildPost = (
|
||||
};
|
||||
};
|
||||
|
||||
export const markPending = (nodes: any): void => {
|
||||
_.forEach(nodes, node => {
|
||||
node.post.author = deSig(node.post.author);
|
||||
node.post.pending = true;
|
||||
markPending(node.children || {});
|
||||
export const markPending = (nodes: any): any => {
|
||||
Object.keys(nodes).forEach((key) => {
|
||||
nodes[key].post.author = deSig(nodes[key].post.author);
|
||||
nodes[key].post.pending = true;
|
||||
if (nodes[key].children) {
|
||||
nodes[key].children = markPending(nodes[key].children);
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
};
|
||||
|
||||
export const createPost = (
|
||||
@ -80,9 +85,9 @@ function moduleToMark(mod: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const storeAction = <T>(data: T): Poke<T> => ({
|
||||
const storeAction = <T>(data: T, version: number = GRAPH_UPDATE_VERSION): Poke<T> => ({
|
||||
app: 'graph-store',
|
||||
mark: 'graph-update',
|
||||
mark: `graph-update-${version}`,
|
||||
json: data
|
||||
});
|
||||
|
||||
@ -97,9 +102,9 @@ const viewAction = <T>(threadName: string, action: T): Thread<T> => ({
|
||||
|
||||
export { viewAction as graphViewAction };
|
||||
|
||||
const hookAction = <T>(data: T): Poke<T> => ({
|
||||
const hookAction = <T>(data: T, version: number = GRAPH_UPDATE_VERSION): Poke<T> => ({
|
||||
app: 'graph-push-hook',
|
||||
mark: 'graph-update',
|
||||
mark: `graph-update-${version}`,
|
||||
json: data
|
||||
});
|
||||
|
||||
@ -223,22 +228,23 @@ export const addNodes = (
|
||||
ship: Patp,
|
||||
name: string,
|
||||
nodes: Object
|
||||
): Poke<any> => {
|
||||
const action = {
|
||||
): Thread<any> => ({
|
||||
inputMark: `graph-update-${GRAPH_UPDATE_VERSION}`,
|
||||
outputMark: 'graph-view-action',
|
||||
threadName: 'graph-add-nodes',
|
||||
body: {
|
||||
'add-nodes': {
|
||||
resource: { ship, name },
|
||||
nodes
|
||||
}
|
||||
};
|
||||
|
||||
return hookAction(action);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const addPost = (
|
||||
ship: Patp,
|
||||
name: string,
|
||||
post: Post
|
||||
) => {
|
||||
): Thread<any> => {
|
||||
let nodes: Record<string, GraphNode> = {};
|
||||
nodes[post.index] = {
|
||||
post,
|
||||
@ -251,13 +257,40 @@ export const addNode = (
|
||||
ship: Patp,
|
||||
name: string,
|
||||
node: GraphNode
|
||||
): Poke<any> => {
|
||||
): Thread<any> => {
|
||||
let nodes: Record<string, GraphNode> = {};
|
||||
nodes[node.post.index] = node;
|
||||
|
||||
return addNodes(ship, name, nodes);
|
||||
}
|
||||
|
||||
export const createGroupFeed = (
|
||||
group: Resource,
|
||||
vip: any = ''
|
||||
): Thread<any> => ({
|
||||
inputMark: 'graph-view-action',
|
||||
outputMark: 'resource',
|
||||
threadName: 'graph-create-group-feed',
|
||||
body: {
|
||||
'create-group-feed': {
|
||||
resource: group,
|
||||
vip
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const disableGroupFeed = (
|
||||
group: Resource
|
||||
): Thread<any> => ({
|
||||
inputMark: 'graph-view-action',
|
||||
outputMark: 'json',
|
||||
threadName: 'graph-disable-group-feed',
|
||||
body: {
|
||||
'disable-group-feed': {
|
||||
resource: group
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const removeNodes = (
|
||||
ship: Patp,
|
||||
|
@ -4,15 +4,17 @@ import { Enc, Path, Patp, PatpNoSig, Poke, Thread } from '../lib/types';
|
||||
import { Group, GroupPolicy, GroupPolicyDiff, GroupUpdateAddMembers, GroupUpdateAddTag, GroupUpdateChangePolicy, GroupUpdateRemoveGroup, GroupUpdateRemoveMembers, GroupUpdateRemoveTag, Resource, RoleTags, Tag } from './types';
|
||||
import { GroupUpdate } from './update';
|
||||
|
||||
export const proxyAction = <T>(data: T): Poke<T> => ({
|
||||
export const GROUP_UPDATE_VERSION = 0;
|
||||
|
||||
export const proxyAction = <T>(data: T, version: number = GROUP_UPDATE_VERSION): Poke<T> => ({
|
||||
app: 'group-push-hook',
|
||||
mark: 'group-update',
|
||||
mark: `group-update-${version}`,
|
||||
json: data
|
||||
});
|
||||
|
||||
const storeAction = <T extends GroupUpdate>(data: T): Poke<T> => ({
|
||||
const storeAction = <T extends GroupUpdate>(data: T, version: number = GROUP_UPDATE_VERSION): Poke<T> => ({
|
||||
app: 'group-store',
|
||||
mark: 'group-update',
|
||||
mark: `group-update-${version}`,
|
||||
json: data
|
||||
});
|
||||
|
||||
@ -146,6 +148,12 @@ export const invite = (
|
||||
}
|
||||
});
|
||||
|
||||
export const hide = (
|
||||
resource: string
|
||||
): Poke<any> => viewAction({
|
||||
hide: resource
|
||||
});
|
||||
|
||||
export const roleTags = ['janitor', 'moderator', 'admin'];
|
||||
// TODO make this type better?
|
||||
|
||||
|
@ -245,6 +245,6 @@ export const getNotificationCount = (
|
||||
): number => {
|
||||
const unread = unreads.graph?.[path] || {};
|
||||
return Object.keys(unread)
|
||||
.map(index => unread[index]?.notifications || 0)
|
||||
.map(index => unread[index]?.notifications as number || 0)
|
||||
.reduce(f.add, 0);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export type GraphNotifDescription = "link" | "comment" | "note" | "mention" | "m
|
||||
|
||||
export interface UnreadStats {
|
||||
unreads: Set<string> | number;
|
||||
notifications: NotifRef[];
|
||||
notifications: NotifRef[] | number;
|
||||
last: number;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,18 @@
|
||||
export * from './contacts';
|
||||
export * as contacts from './contacts';
|
||||
export * from './graph';
|
||||
export * as graph from './graph';
|
||||
export * from './groups';
|
||||
export * as groups from './groups';
|
||||
export * from './hark';
|
||||
export * as hark from './hark';
|
||||
export * from './invite';
|
||||
export * as invite from './invite';
|
||||
export * from './metadata';
|
||||
export * as metadata from './metadata';
|
||||
export * from './settings';
|
||||
export * as settings from './settings';
|
||||
export * from './s3';
|
||||
export * as s3 from './s3';
|
||||
export * from './lib';
|
||||
export * from './lib/BigIntOrderedMap';
|
@ -1,9 +1,11 @@
|
||||
import { AppName, Path, Poke, uxToHex, PatpNoSig } from "../lib";
|
||||
import { Association, Metadata, MetadataUpdate, MetadataUpdateAdd, MetadataUpdateRemove } from './types';
|
||||
|
||||
export const metadataAction = <T extends MetadataUpdate>(data: T): Poke<T> => ({
|
||||
export const METADATA_UPDATE_VERSION = 1;
|
||||
|
||||
export const metadataAction = <T extends MetadataUpdate>(data: T, version: number = METADATA_UPDATE_VERSION): Poke<T> => ({
|
||||
app: 'metadata-push-hook',
|
||||
mark: 'metadata-update',
|
||||
mark: `metadata-update-${version}`,
|
||||
json: data
|
||||
});
|
||||
|
||||
@ -30,8 +32,9 @@ export const add = (
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator: `~${ship}`,
|
||||
'module': moduleName,
|
||||
config: { graph: moduleName },
|
||||
picture: '',
|
||||
hidden: false,
|
||||
preview: false,
|
||||
vip: ''
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Resource } from "..";
|
||||
import { AppName, Path, Patp } from "../lib";
|
||||
|
||||
export type MetadataUpdate =
|
||||
@ -48,8 +49,6 @@ export type AppAssociations = {
|
||||
[p in Path]: Association;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type Association = MdResource & {
|
||||
group: Path;
|
||||
metadata: Metadata;
|
||||
@ -67,10 +66,20 @@ export interface Metadata {
|
||||
'date-created': string;
|
||||
description: string;
|
||||
title: string;
|
||||
module: string;
|
||||
config: MetadataConfig;
|
||||
picture: string;
|
||||
hidden: boolean;
|
||||
preview: boolean;
|
||||
vip: PermVariation;
|
||||
}
|
||||
|
||||
type MetadataConfig = GroupConfig | GraphConfig;
|
||||
|
||||
interface GroupConfig {
|
||||
group: null | {} | Resource;
|
||||
}
|
||||
interface GraphConfig {
|
||||
graph: string;
|
||||
}
|
||||
|
||||
export type PermVariation = '' | 'reader-comments' | 'member-metadata' | 'host-feed' | 'admin-feed';
|
||||
|
592
pkg/npm/api/package-lock.json
generated
592
pkg/npm/api/package-lock.json
generated
@ -1,17 +1,387 @@
|
||||
{
|
||||
"name": "@urbit/api",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@urbit/api",
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@urbit/eslint-config": "^1.0.0",
|
||||
"big-integer": "^1.6.48",
|
||||
"immer": "^9.0.1",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"onchange": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
|
||||
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@blakeembrey/deque": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@blakeembrey/deque/-/deque-1.0.5.tgz",
|
||||
"integrity": "sha512-6xnwtvp9DY1EINIKdTfvfeAtCYw4OqBZJhtiqkT3ivjnEfa25VQ3TsKvaFfKm8MyGIEfE95qLe+bNEt3nB0Ylg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@blakeembrey/template": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@blakeembrey/template/-/template-1.0.0.tgz",
|
||||
"integrity": "sha512-J6WGZqCLdRMHUkyRG6fBSIFJ0rL60/nsQNh5rQvsYZ5u0PsKw6XQcJcA3DWvd9cN3j/IQx5yB1fexhCafwwUUw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.168",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
|
||||
"integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q=="
|
||||
},
|
||||
"node_modules/@urbit/eslint-config": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@urbit/eslint-config/-/eslint-config-1.0.0.tgz",
|
||||
"integrity": "sha512-Xmzb6MvM7KorlPJEq/hURZZ4BHSVy/7CoQXWogsBSTv5MOZnMqwNKw6yt24k2AO/2UpHwjGptimaNLqFfesJbw=="
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
||||
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.1.tgz",
|
||||
"integrity": "sha512-7CCw1DSgr8kKYXTYOI1qMM/f5qxT5vIVMeGLDCDX8CSxsggr1Sjdoha4OhsP0AZ1UvWbyZlILHvLjaynuu02Mg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/onchange": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/onchange/-/onchange-7.1.0.tgz",
|
||||
"integrity": "sha512-ZJcqsPiWUAUpvmnJri5TPBooqJOPmC0ttN65juhN15Q8xA+Nbg3BaxBHXQ45EistKKlKElb0edmbPWnKSBkvMg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@blakeembrey/deque": "^1.0.5",
|
||||
"@blakeembrey/template": "^1.0.0",
|
||||
"arg": "^4.1.3",
|
||||
"chokidar": "^3.3.1",
|
||||
"cross-spawn": "^7.0.1",
|
||||
"ignore": "^5.1.4",
|
||||
"tree-kill": "^1.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"onchange": "dist/bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.13.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.7.tgz",
|
||||
"integrity": "sha512-h+ilqoX998mRVM5FtB5ijRuHUDVt5l3yfoOi2uh18Z/O3hvyaHQ39NpxVkCIG5yFs+mLq/ewFp8Bss6zmWv6ZA==",
|
||||
"version": "7.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
|
||||
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@blakeembrey/deque": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@blakeembrey/deque/-/deque-1.0.5.tgz",
|
||||
"integrity": "sha512-6xnwtvp9DY1EINIKdTfvfeAtCYw4OqBZJhtiqkT3ivjnEfa25VQ3TsKvaFfKm8MyGIEfE95qLe+bNEt3nB0Ylg==",
|
||||
"dev": true
|
||||
},
|
||||
"@blakeembrey/template": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@blakeembrey/template/-/template-1.0.0.tgz",
|
||||
"integrity": "sha512-J6WGZqCLdRMHUkyRG6fBSIFJ0rL60/nsQNh5rQvsYZ5u0PsKw6XQcJcA3DWvd9cN3j/IQx5yB1fexhCafwwUUw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.168",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
|
||||
@ -22,25 +392,231 @@
|
||||
"resolved": "https://registry.npmjs.org/@urbit/eslint-config/-/eslint-config-1.0.0.tgz",
|
||||
"integrity": "sha512-Xmzb6MvM7KorlPJEq/hURZZ4BHSVy/7CoQXWogsBSTv5MOZnMqwNKw6yt24k2AO/2UpHwjGptimaNLqFfesJbw=="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
||||
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"immer": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.1.tgz",
|
||||
"integrity": "sha512-7CCw1DSgr8kKYXTYOI1qMM/f5qxT5vIVMeGLDCDX8CSxsggr1Sjdoha4OhsP0AZ1UvWbyZlILHvLjaynuu02Mg=="
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"onchange": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/onchange/-/onchange-7.1.0.tgz",
|
||||
"integrity": "sha512-ZJcqsPiWUAUpvmnJri5TPBooqJOPmC0ttN65juhN15Q8xA+Nbg3BaxBHXQ45EistKKlKElb0edmbPWnKSBkvMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@blakeembrey/deque": "^1.0.5",
|
||||
"@blakeembrey/template": "^1.0.0",
|
||||
"arg": "^4.1.3",
|
||||
"chokidar": "^3.3.1",
|
||||
"cross-spawn": "^7.0.1",
|
||||
"ignore": "^5.1.4",
|
||||
"tree-kill": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||
"dev": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"dev": true
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@urbit/api",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -11,6 +11,7 @@
|
||||
"types": "dist/index.d",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"watch": "onchange './**/*.ts' -e './dist/**' -- npm run build",
|
||||
"build": "npm run clean && tsc -p tsconfig.json",
|
||||
"clean": "rm -rf dist/*"
|
||||
},
|
||||
@ -23,5 +24,8 @@
|
||||
"big-integer": "^1.6.48",
|
||||
"immer": "^9.0.1",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"onchange": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,2 @@
|
||||
export * from './lib';
|
||||
export * from './types';
|
@ -20,7 +20,7 @@ export interface PutEntry {
|
||||
"put-entry": {
|
||||
"bucket-key": Key;
|
||||
"entry-key": Key;
|
||||
"value": Value;
|
||||
"value"?: Value;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,9 @@
|
||||
"exclude": ["node_modules", "dist", "@types"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"module": "ESNext",
|
||||
"module": "ES2020",
|
||||
"noImplicitAny": true,
|
||||
"target": "ESNext",
|
||||
"target": "ES2020",
|
||||
"pretty": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
|
3476
pkg/npm/eslint-config/package-lock.json
generated
3476
pkg/npm/eslint-config/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
1
pkg/npm/http-api/.gitignore
vendored
1
pkg/npm/http-api/.gitignore
vendored
@ -1 +0,0 @@
|
||||
example/*.js
|
521
pkg/npm/http-api/Urbit.ts
Normal file
521
pkg/npm/http-api/Urbit.ts
Normal file
@ -0,0 +1,521 @@
|
||||
import { isBrowser, isNode } from 'browser-or-node';
|
||||
import { Action, Scry, Thread } from '@urbit/api';
|
||||
import { fetchEventSource, EventSourceMessage, EventStreamContentType } from '@microsoft/fetch-event-source';
|
||||
|
||||
import { AuthenticationInterface, SubscriptionInterface, CustomEventHandler, PokeInterface, SubscriptionRequestInterface, headers, UrbitInterface, SSEOptions, PokeHandlers, Message } from './types';
|
||||
import { uncamelize, hexString } from './utils';
|
||||
|
||||
/**
|
||||
* A class for interacting with an urbit ship, given its URL and code
|
||||
*/
|
||||
export class Urbit implements UrbitInterface {
|
||||
/**
|
||||
* UID will be used for the channel: The current unix time plus a random hex string
|
||||
*/
|
||||
uid: string = `${Math.floor(Date.now() / 1000)}-${hexString(6)}`;
|
||||
|
||||
/**
|
||||
* Last Event ID is an auto-updated index of which events have been sent over this channel
|
||||
*/
|
||||
lastEventId: number = 0;
|
||||
|
||||
lastAcknowledgedEventId: number = 0;
|
||||
|
||||
/**
|
||||
* SSE Client is null for now; we don't want to start polling until it the channel exists
|
||||
*/
|
||||
sseClientInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* Cookie gets set when we log in.
|
||||
*/
|
||||
cookie?: string | undefined;
|
||||
|
||||
/**
|
||||
* A registry of requestId to successFunc/failureFunc
|
||||
*
|
||||
* These functions are registered during a +poke and are executed
|
||||
* in the onServerEvent()/onServerError() callbacks. Only one of
|
||||
* the functions will be called, and the outstanding poke will be
|
||||
* removed after calling the success or failure function.
|
||||
*/
|
||||
|
||||
outstandingPokes: Map<number, PokeHandlers> = new Map();
|
||||
|
||||
/**
|
||||
* A registry of requestId to subscription functions.
|
||||
*
|
||||
* These functions are registered during a +subscribe and are
|
||||
* executed in the onServerEvent()/onServerError() callbacks. The
|
||||
* event function will be called whenever a new piece of data on this
|
||||
* subscription is available, which may be 0, 1, or many times. The
|
||||
* disconnect function may be called exactly once.
|
||||
*/
|
||||
|
||||
outstandingSubscriptions: Map<number, SubscriptionRequestInterface> = new Map();
|
||||
|
||||
/**
|
||||
* Ship can be set, in which case we can do some magic stuff like send chats
|
||||
*/
|
||||
ship?: string | null;
|
||||
|
||||
/**
|
||||
* If verbose, logs output eagerly.
|
||||
*/
|
||||
verbose?: boolean;
|
||||
|
||||
onError?: (error: any) => void = null;
|
||||
|
||||
/** This is basic interpolation to get the channel URL of an instantiated Urbit connection. */
|
||||
get channelUrl(): string {
|
||||
return `${this.url}/~/channel/${this.uid}`;
|
||||
}
|
||||
|
||||
get fetchOptions(): any {
|
||||
const headers: headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (!isBrowser) {
|
||||
headers.Cookie = this.cookie;
|
||||
}
|
||||
return {
|
||||
credentials: 'include',
|
||||
accept: '*',
|
||||
headers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Urbit connection.
|
||||
*
|
||||
* @param url The URL (with protocol and port) of the ship to be accessed
|
||||
* @param code The access code for the ship at that address
|
||||
*/
|
||||
constructor(
|
||||
public url: string,
|
||||
public code: string
|
||||
) {
|
||||
if (isBrowser) {
|
||||
window.addEventListener('beforeunload', this.delete);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* All-in-one hook-me-up.
|
||||
*
|
||||
* Given a ship, url, and code, this returns an airlock connection
|
||||
* that is ready to go. It `|hi`s itself to create the channel,
|
||||
* then opens the channel via EventSource.
|
||||
*
|
||||
* @param AuthenticationInterface
|
||||
*/
|
||||
static async authenticate({ ship, url, code, verbose = false }: AuthenticationInterface) {
|
||||
const airlock = new Urbit(`http://${url}`, code);
|
||||
airlock.verbose = verbose;
|
||||
airlock.ship = ship;
|
||||
await airlock.connect();
|
||||
await airlock.poke({ app: 'hood', mark: 'helm-hi', json: 'opening airlock' });
|
||||
await airlock.eventSource();
|
||||
return airlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the Urbit ship. Nothing can be done until this is called.
|
||||
* That's why we roll it into this.authenticate
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.verbose) {
|
||||
console.log(`password=${this.code} `, isBrowser ? "Connecting in browser context at " + `${this.url}/~/login` : "Connecting from node context");
|
||||
}
|
||||
return fetch(`${this.url}/~/login`, {
|
||||
method: 'post',
|
||||
body: `password=${this.code}`,
|
||||
credentials: 'include',
|
||||
}).then(response => {
|
||||
if (this.verbose) {
|
||||
console.log('Received authentication response', response);
|
||||
}
|
||||
const cookie = response.headers.get('set-cookie');
|
||||
if (!this.ship) {
|
||||
this.ship = new RegExp(/urbauth-~([\w-]+)/).exec(cookie)[1];
|
||||
}
|
||||
if (!isBrowser) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the SSE pipe for the appropriate channel.
|
||||
*/
|
||||
eventSource(): void {
|
||||
if (!this.sseClientInitialized) {
|
||||
const sseOptions: SSEOptions = {
|
||||
headers: {}
|
||||
};
|
||||
if (isBrowser) {
|
||||
sseOptions.withCredentials = true;
|
||||
} else if (isNode) {
|
||||
sseOptions.headers.Cookie = this.cookie;
|
||||
}
|
||||
if (this.lastEventId === 0) {
|
||||
// Can't receive events until the channel is open
|
||||
this.poke({ app: 'hood', mark: 'helm-hi', json: 'Opening API channel' });
|
||||
}
|
||||
fetchEventSource(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
openWhenHidden: true,
|
||||
onopen: async (response) => {
|
||||
if (this.verbose) {
|
||||
console.log('Opened eventsource', response);
|
||||
}
|
||||
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
||||
return; // everything's good
|
||||
} else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
||||
if (this.onError) {
|
||||
this.onError(response.statusText);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} else {
|
||||
if (this.onError) {
|
||||
this.onError(response.statusText);
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
},
|
||||
onmessage: (event: EventSourceMessage) => {
|
||||
if (this.verbose) {
|
||||
console.log('Received SSE: ', event);
|
||||
}
|
||||
if (!event.id) return;
|
||||
this.ack(Number(event.id));
|
||||
if (event.data && JSON.parse(event.data)) {
|
||||
|
||||
const data: any = JSON.parse(event.data);
|
||||
|
||||
if (data.response === 'diff') {
|
||||
this.clearQueue();
|
||||
}
|
||||
|
||||
if (data.response === 'poke' && this.outstandingPokes.has(data.id)) {
|
||||
const funcs = this.outstandingPokes.get(data.id);
|
||||
if (data.hasOwnProperty('ok')) {
|
||||
funcs.onSuccess();
|
||||
} else if (data.hasOwnProperty('err')) {
|
||||
funcs.onError(data.err);
|
||||
} else {
|
||||
console.error('Invalid poke response', data);
|
||||
}
|
||||
this.outstandingPokes.delete(data.id);
|
||||
} else if (data.response === 'subscribe' ||
|
||||
(data.response === 'poke' && this.outstandingSubscriptions.has(data.id))) {
|
||||
const funcs = this.outstandingSubscriptions.get(data.id);
|
||||
if (data.hasOwnProperty('err')) {
|
||||
funcs.err(data.err);
|
||||
this.outstandingSubscriptions.delete(data.id);
|
||||
}
|
||||
} else if (data.response === 'diff' && this.outstandingSubscriptions.has(data.id)) {
|
||||
const funcs = this.outstandingSubscriptions.get(data.id);
|
||||
funcs.event(data.json);
|
||||
} else if (data.response === 'quit' && this.outstandingSubscriptions.has(data.id)) {
|
||||
const funcs = this.outstandingSubscriptions.get(data.id);
|
||||
funcs.quit(data);
|
||||
this.outstandingSubscriptions.delete(data.id);
|
||||
} else {
|
||||
console.log('Unrecognized response', data);
|
||||
}
|
||||
}
|
||||
},
|
||||
onerror: (error) => {
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.sseClientInitialized = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoincrements the next event ID for the appropriate channel.
|
||||
*/
|
||||
getEventId(): number {
|
||||
this.lastEventId = Number(this.lastEventId) + 1;
|
||||
return this.lastEventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges an event.
|
||||
*
|
||||
* @param eventId The event to acknowledge.
|
||||
*/
|
||||
async ack(eventId: number): Promise<number | void> {
|
||||
const message: Message = {
|
||||
action: 'ack',
|
||||
'event-id': eventId
|
||||
};
|
||||
await this.sendJSONtoChannel(message);
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a wrapper method that can be used to send any action with data.
|
||||
*
|
||||
* Every message sent has some common parameters, like method, headers, and data
|
||||
* structure, so this method exists to prevent duplication.
|
||||
*
|
||||
* @param action The action to send
|
||||
* @param data The data to send with the action
|
||||
*
|
||||
* @returns void | number If successful, returns the number of the message that was sent
|
||||
*/
|
||||
// async sendMessage(action: Action, data?: object): Promise<number | void> {
|
||||
// const id = this.getEventId();
|
||||
// if (this.verbose) {
|
||||
// console.log(`Sending message ${id}:`, action, data,);
|
||||
// }
|
||||
// const message: Message = { id, action, ...data };
|
||||
// await this.sendJSONtoChannel(message);
|
||||
// return id;
|
||||
// }
|
||||
|
||||
outstandingJSON: Message[] = [];
|
||||
|
||||
debounceTimer: any = null;
|
||||
debounceInterval = 500;
|
||||
calm = true;
|
||||
|
||||
sendJSONtoChannel(json: Message): Promise<boolean | void> {
|
||||
this.outstandingJSON.push(json);
|
||||
return this.processQueue();
|
||||
}
|
||||
|
||||
processQueue(): Promise<boolean | void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const process = async () => {
|
||||
if (this.calm) {
|
||||
if (this.outstandingJSON.length === 0) resolve(true);
|
||||
this.calm = false; // We are now occupied
|
||||
const json = this.outstandingJSON;
|
||||
const body = JSON.stringify(json);
|
||||
this.outstandingJSON = [];
|
||||
if (body === '[]') {
|
||||
this.calm = true;
|
||||
return resolve(false);
|
||||
}
|
||||
try {
|
||||
await fetch(this.channelUrl, {
|
||||
...this.fetchOptions,
|
||||
method: 'PUT',
|
||||
body
|
||||
});
|
||||
} catch (error) {
|
||||
json.forEach(failed => this.outstandingJSON.push(failed));
|
||||
if (this.onError) {
|
||||
this.onError(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this.calm = true;
|
||||
if (!this.sseClientInitialized) {
|
||||
this.eventSource(); // We can open the channel for subscriptions once we've sent data over it
|
||||
}
|
||||
resolve(true);
|
||||
} else {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
this.debounceTimer = setTimeout(process, this.debounceInterval);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// resetDebounceTimer() {
|
||||
// if (this.debounceTimer) {
|
||||
// clearTimeout(this.debounceTimer);
|
||||
// this.debounceTimer = null;
|
||||
// }
|
||||
// this.calm = false;
|
||||
// this.debounceTimer = setTimeout(() => {
|
||||
// this.calm = true;
|
||||
// }, this.debounceInterval);
|
||||
// }
|
||||
|
||||
clearQueue() {
|
||||
clearTimeout(this.debounceTimer);
|
||||
this.debounceTimer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pokes a ship with data.
|
||||
*
|
||||
* @param app The app to poke
|
||||
* @param mark The mark of the data being sent
|
||||
* @param json The data to send
|
||||
*/
|
||||
poke<T>(params: PokeInterface<T>): Promise<number> {
|
||||
const {
|
||||
app,
|
||||
mark,
|
||||
json,
|
||||
ship,
|
||||
onSuccess,
|
||||
onError
|
||||
} = {
|
||||
onSuccess: () => { },
|
||||
onError: () => { },
|
||||
ship: this.ship,
|
||||
...params
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const message: Message = {
|
||||
id: this.getEventId(),
|
||||
action: 'poke',
|
||||
ship,
|
||||
app,
|
||||
mark,
|
||||
json
|
||||
};
|
||||
this.outstandingPokes.set(message.id, {
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
resolve(message.id);
|
||||
},
|
||||
onError: (event) => {
|
||||
onError(event);
|
||||
reject(event.err);
|
||||
}
|
||||
});
|
||||
this.sendJSONtoChannel(message).then(() => {
|
||||
resolve(message.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to a path on an app on a ship.
|
||||
*
|
||||
* @param app The app to subsribe to
|
||||
* @param path The path to which to subscribe
|
||||
* @param handlers Handlers to deal with various events of the subscription
|
||||
*/
|
||||
async subscribe(params: SubscriptionRequestInterface): Promise<number> {
|
||||
const {
|
||||
app,
|
||||
path,
|
||||
ship,
|
||||
err,
|
||||
event,
|
||||
quit
|
||||
} = {
|
||||
err: () => { },
|
||||
event: () => { },
|
||||
quit: () => { },
|
||||
ship: this.ship,
|
||||
...params
|
||||
};
|
||||
|
||||
const message: Message = {
|
||||
id: this.getEventId(),
|
||||
action: 'subscribe',
|
||||
ship,
|
||||
app,
|
||||
path
|
||||
};
|
||||
|
||||
this.outstandingSubscriptions.set(message.id, {
|
||||
app, path, err, event, quit
|
||||
});
|
||||
|
||||
await this.sendJSONtoChannel(message);
|
||||
|
||||
return message.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes to a given subscription.
|
||||
*
|
||||
* @param subscription
|
||||
*/
|
||||
async unsubscribe(subscription: number) {
|
||||
return this.sendJSONtoChannel({
|
||||
id: this.getEventId(),
|
||||
action: 'unsubscribe',
|
||||
subscription
|
||||
}).then(() => {
|
||||
this.outstandingSubscriptions.delete(subscription);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the connection to a channel.
|
||||
*/
|
||||
delete() {
|
||||
if (isBrowser) {
|
||||
navigator.sendBeacon(this.channelUrl, JSON.stringify([{
|
||||
action: 'delete'
|
||||
}]));
|
||||
} else {
|
||||
// TODO
|
||||
// this.sendMessage('delete');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param app The app into which to scry
|
||||
* @param path The path at which to scry
|
||||
*/
|
||||
async scry(params: Scry): Promise<void | any> {
|
||||
const { app, path } = params;
|
||||
const response = await fetch(`${this.url}/~/scry/${app}${path}.json`, this.fetchOptions);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param inputMark The mark of the data being sent
|
||||
* @param outputMark The mark of the data being returned
|
||||
* @param threadName The thread to run
|
||||
* @param body The data to send to the thread
|
||||
*/
|
||||
async thread<T>(params: Thread<T>): Promise<T> {
|
||||
const { inputMark, outputMark, threadName, body } = params;
|
||||
const res = await fetch(`${this.url}/spider/${inputMark}/${threadName}/${outputMark}.json`, {
|
||||
...this.fetchOptions,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to connect to a ship that has its *.arvo.network domain configured.
|
||||
*
|
||||
* @param name Name of the ship e.g. zod
|
||||
* @param code Code to log in
|
||||
*/
|
||||
static async onArvoNetwork(ship: string, code: string): Promise<Urbit> {
|
||||
const url = `https://${ship}.arvo.network`;
|
||||
return await Urbit.authenticate({ ship, url, code });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Urbit;
|
3
pkg/npm/http-api/example/browser.js
Normal file
3
pkg/npm/http-api/example/browser.js
Normal file
@ -0,0 +1,3 @@
|
||||
// import Urbit from '../../dist/browser';
|
||||
|
||||
// window.Urbit = Urbit;
|
14
pkg/npm/http-api/example/node.js
Normal file
14
pkg/npm/http-api/example/node.js
Normal file
@ -0,0 +1,14 @@
|
||||
// import Urbit from '../../dist/index';
|
||||
|
||||
// async function blastOff() {
|
||||
// const airlock = await Urbit.authenticate({
|
||||
// ship: 'zod',
|
||||
// url: 'localhost:8080',
|
||||
// code: 'lidlut-tabwed-pillex-ridrup',
|
||||
// verbose: true
|
||||
// });
|
||||
|
||||
// airlock.subscribe('chat-view', '/primary');
|
||||
// }
|
||||
|
||||
// blastOff();
|
3
pkg/npm/http-api/index.ts
Normal file
3
pkg/npm/http-api/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
import Urbit from './Urbit';
|
||||
export { Urbit as default };
|
7703
pkg/npm/http-api/package-lock.json
generated
7703
pkg/npm/http-api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@urbit/http-api",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"description": "Library to interact with an Urbit ship over HTTP",
|
||||
"repository": {
|
||||
@ -16,6 +16,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"watch": "onchange './*.ts' -- npm run build",
|
||||
"build": "npm run clean && tsc -p tsconfig.json",
|
||||
"clean": "rm -rf dist/*"
|
||||
},
|
||||
@ -40,6 +41,7 @@
|
||||
"@typescript-eslint/parser": "^4.7.0",
|
||||
"babel-loader": "^8.2.1",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"onchange": "^7.1.0",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^3.9.7",
|
||||
"util": "^0.12.3",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"include": ["src/*.ts"],
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist", "@types"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
@ -15,5 +15,9 @@
|
||||
"strict": false,
|
||||
"noErrorTruncation": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*" : ["./node_modules/@types/*", "*"]
|
||||
}
|
||||
}
|
||||
}
|
72
pkg/npm/http-api/types.ts
Normal file
72
pkg/npm/http-api/types.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { Action, Poke, Scry, Thread } from '@urbit/api';
|
||||
|
||||
export interface PokeHandlers {
|
||||
onSuccess?: () => void;
|
||||
onError?: (e: any) => void;
|
||||
}
|
||||
|
||||
export type PokeInterface<T> = PokeHandlers & Poke<T>;
|
||||
|
||||
export interface AuthenticationInterface {
|
||||
ship: string;
|
||||
url: string;
|
||||
code: string;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export interface SubscriptionInterface {
|
||||
err?(error: any): void;
|
||||
event?(data: any): void;
|
||||
quit?(data: any): void;
|
||||
}
|
||||
|
||||
export type SubscriptionRequestInterface = SubscriptionInterface & {
|
||||
app: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface headers {
|
||||
'Content-Type': string;
|
||||
Cookie?: string;
|
||||
}
|
||||
|
||||
export interface UrbitInterface {
|
||||
uid: string;
|
||||
lastEventId: number;
|
||||
lastAcknowledgedEventId: number;
|
||||
sseClientInitialized: boolean;
|
||||
cookie?: string | undefined;
|
||||
outstandingPokes: Map<number, PokeHandlers>;
|
||||
outstandingSubscriptions: Map<number, SubscriptionRequestInterface>;
|
||||
verbose?: boolean;
|
||||
ship?: string | null;
|
||||
onError?: (error: any) => void;
|
||||
connect(): void;
|
||||
connect(): Promise<void>;
|
||||
eventSource(): void;
|
||||
getEventId(): number;
|
||||
ack(eventId: number): Promise<void | number>;
|
||||
// sendMessage(action: Action, data?: object): Promise<void | number>;
|
||||
poke<T>(params: PokeInterface<T>): Promise<number>;
|
||||
subscribe(params: SubscriptionRequestInterface): Promise<number>;
|
||||
unsubscribe(subscription: number): Promise<boolean | void>;
|
||||
delete(): void;
|
||||
scry(params: Scry): Promise<void | any>;
|
||||
thread<T>(params: Thread<T>): Promise<T>;
|
||||
}
|
||||
|
||||
export interface CustomEventHandler {
|
||||
(data: any, response: string): void;
|
||||
}
|
||||
|
||||
export interface SSEOptions {
|
||||
headers?: {
|
||||
Cookie?: string
|
||||
};
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
|
||||
export interface Message extends Record<string, any> {
|
||||
action: Action;
|
||||
id?: number;
|
||||
}
|
82
pkg/npm/http-api/utils.ts
Normal file
82
pkg/npm/http-api/utils.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import * as http from 'http';
|
||||
|
||||
interface HttpResponse {
|
||||
req: http.ClientRequest;
|
||||
res: http.IncomingMessage;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export function request(
|
||||
url: string,
|
||||
options: http.ClientRequestArgs,
|
||||
body?: string
|
||||
): Promise<HttpResponse> {
|
||||
return new Promise<HttpResponse>((resolve, reject) => {
|
||||
const req = http.request(url, options, res => {
|
||||
let data = "";
|
||||
res.on("data", chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
resolve({ req, res, data });
|
||||
});
|
||||
res.on("error", e => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
if (body) {
|
||||
req.write(body);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export function camelize(str: string) {
|
||||
return str
|
||||
.replace(/\s(.)/g, function($1: string) { return $1.toUpperCase(); })
|
||||
.replace(/\s/g, '')
|
||||
.replace(/^(.)/, function($1: string) { return $1.toLowerCase(); });
|
||||
}
|
||||
|
||||
export function uncamelize(str: string, separator = '-') {
|
||||
// Replace all capital letters by separator followed by lowercase one
|
||||
var str = str.replace(/[A-Z]/g, function (letter: string) {
|
||||
return separator + letter.toLowerCase();
|
||||
});
|
||||
return str.replace(new RegExp('^' + separator), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hex string of given length.
|
||||
*
|
||||
* Poached from StackOverflow.
|
||||
*
|
||||
* @param len Length of hex string to return.
|
||||
*/
|
||||
export function hexString(len: number): string {
|
||||
const maxlen = 8;
|
||||
const min = Math.pow(16, Math.min(len, maxlen) - 1);
|
||||
const max = Math.pow(16, Math.min(len, maxlen)) - 1;
|
||||
const n = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
let r = n.toString(16);
|
||||
while (r.length < len) {
|
||||
r = r + hexString(len - maxlen);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random UID.
|
||||
*
|
||||
* Copied from https://github.com/urbit/urbit/blob/137e4428f617c13f28ed31e520eff98d251ed3e9/pkg/interface/src/lib/util.js#L3
|
||||
*/
|
||||
export function uid(): string {
|
||||
let str = '0v';
|
||||
str += Math.ceil(Math.random() * 8) + '.';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random() * 10000000).toString(32);
|
||||
_str = ('00000' + _str).substr(-5, 5);
|
||||
str += _str + '.';
|
||||
}
|
||||
return str.slice(0, -1);
|
||||
}
|
Loading…
Reference in New Issue
Block a user