mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 08:32:39 +03:00
Merge remote-tracking branch 'origin/release/next-js' into lf/graph-unification
This commit is contained in:
commit
623e13bd3e
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:abc163491b53cc9d48a70ea378dde8bb455648e3f6e7f57a0c7a8e164d4ca105
|
||||
size 10226599
|
||||
oid sha256:accbadc701471f6b071ed286164a0bf4d3f8a2e64cfaea9019e123bd9edca569
|
||||
size 10292454
|
||||
|
@ -5,7 +5,7 @@
|
||||
/- glob
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ hash 0v2i7ds.j99ka.5dpja.pef1e.b04e0
|
||||
++ hash 0v4.7tk5q.9ha4l.tbmji.fvkno.s9pfq
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ all-states
|
||||
$% state-0
|
||||
|
@ -46,19 +46,8 @@
|
||||
%0
|
||||
%_ $
|
||||
-.old %1
|
||||
::
|
||||
validators.old
|
||||
(~(put in validators.old) %graph-validator-link)
|
||||
::
|
||||
cards
|
||||
%+ weld cards
|
||||
%+ turn
|
||||
~(tap in (~(put in validators.old) %graph-validator-link))
|
||||
|= validator=@t
|
||||
^- card
|
||||
=/ =wire /validator/[validator]
|
||||
=/ =rave:clay [%sing %b [%da now.bowl] /[validator]]
|
||||
[%pass wire %arvo %c %warp our.bowl [%home `rave]]
|
||||
cards cards
|
||||
validators.old validators.old
|
||||
::
|
||||
graphs.old
|
||||
%- ~(run by graphs.old)
|
||||
@ -285,19 +274,10 @@
|
||||
graphs (~(put by graphs) resource [graph mark])
|
||||
update-logs (~(put by update-logs) resource update-log)
|
||||
archive (~(del by archive) resource)
|
||||
::
|
||||
validators
|
||||
?~ mark validators
|
||||
(~(put in validators) u.mark)
|
||||
==
|
||||
%- zing
|
||||
:~ (give [/keys ~] %keys (~(put in ~(key by graphs)) resource))
|
||||
(give [/updates ~] %add-graph resource *graph:store mark overwrite)
|
||||
?~ mark ~
|
||||
?: (~(has in validators) u.mark) ~
|
||||
=/ wire /validator/[u.mark]
|
||||
=/ =rave:clay [%sing %b [%da now.bowl] /[u.mark]]
|
||||
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
|
||||
==
|
||||
::
|
||||
++ remove-graph
|
||||
@ -1119,13 +1099,8 @@
|
||||
?+ wire (on-arvo:def wire sign-arvo)
|
||||
::
|
||||
:: old wire, do nothing
|
||||
[%graph *] [~ this]
|
||||
::
|
||||
[%validator @ ~]
|
||||
:_ this
|
||||
=* validator i.t.wire
|
||||
=/ =rave:clay [%next %b [%da now.bowl] /[validator]]
|
||||
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
|
||||
[%graph *] [~ this]
|
||||
[%validator @ ~] [~ this]
|
||||
::
|
||||
[%try-rejoin @ *]
|
||||
=/ rid=resource:store (de-path:res t.t.wire)
|
||||
|
@ -24,6 +24,6 @@
|
||||
<div id="portal-root"></div>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.3a0ba646997cd338b513.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.857da0dbc92427b1460b.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -14,6 +14,7 @@
|
||||
[%2 observers=(map serial observer:sur)]
|
||||
[%3 observers=(map serial observer:sur)]
|
||||
[%4 observers=(map serial observer:sur)]
|
||||
[%5 observers=(map serial observer:sur) warm-cache=_|]
|
||||
==
|
||||
::
|
||||
+$ serial @uv
|
||||
@ -27,7 +28,7 @@
|
||||
--
|
||||
::
|
||||
%- agent:dbug
|
||||
=| [%4 observers=(map serial observer:sur)]
|
||||
=| [%5 observers=(map serial observer:sur) warm-cache=_|]
|
||||
=* state -
|
||||
::
|
||||
^- agent:gall
|
||||
@ -42,6 +43,7 @@
|
||||
(act [%watch %group-store /groups %group-on-leave])
|
||||
(act [%watch %group-store /groups %group-on-remove-member])
|
||||
(act [%watch %metadata-store /updates %md-on-add-group-feed])
|
||||
(act [%warm-cache-all ~])
|
||||
==
|
||||
::
|
||||
++ act
|
||||
@ -66,8 +68,13 @@
|
||||
=| cards=(list card)
|
||||
|-
|
||||
?- -.old-state
|
||||
%4
|
||||
%5
|
||||
[cards this(state old-state)]
|
||||
%4
|
||||
=. cards
|
||||
:_ cards
|
||||
(act [%warm-cache-all ~])
|
||||
$(old-state [%5 observers.old-state %.n])
|
||||
::
|
||||
%3
|
||||
=. cards
|
||||
@ -110,11 +117,19 @@
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?. ?=(%observe-action mark)
|
||||
(on-poke:def mark vase)
|
||||
|^
|
||||
=/ =action:sur !<(action:sur vase)
|
||||
=* observer observer.action
|
||||
=/ vals (silt ~(val by observers))
|
||||
?- -.action
|
||||
%watch
|
||||
%watch (watch observer vals)
|
||||
%ignore (ignore observer vals)
|
||||
%warm-cache-all warm-cache-all
|
||||
%cool-cache-all cool-cache-all
|
||||
==
|
||||
::
|
||||
++ watch
|
||||
|= [=observer:sur vals=(set observer:sur)]
|
||||
?: ?|(=(app.observer %spider) =(app.observer %observe-hook))
|
||||
~|('we avoid infinite loops' !!)
|
||||
?: (~(has in vals) observer)
|
||||
@ -129,7 +144,8 @@
|
||||
path.observer
|
||||
==
|
||||
::
|
||||
%ignore
|
||||
++ ignore
|
||||
|= [=observer:sur vals=(set observer:sur)]
|
||||
?. (~(has in vals) observer)
|
||||
~|('cannot remove nonexistent observer' !!)
|
||||
=/ key (got-by-val observers observer)
|
||||
@ -142,7 +158,19 @@
|
||||
%leave
|
||||
~
|
||||
==
|
||||
==
|
||||
::
|
||||
++ warm-cache-all
|
||||
?: warm-cache
|
||||
~|('cannot warm up cache that is already warm' !!)
|
||||
:_ this(warm-cache %.y)
|
||||
=/ =rave:clay [%sing [%t da+now.bowl /mar]]
|
||||
[%pass /warm-cache %arvo %c %warp our.bowl %home `rave]~
|
||||
::
|
||||
++ cool-cache-all
|
||||
?. warm-cache
|
||||
~|('cannot cool down cache that is already cool' !!)
|
||||
[~ this(warm-cache %.n)]
|
||||
--
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
@ -260,9 +288,48 @@
|
||||
== ==
|
||||
--
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
:_ this
|
||||
?+ wire (on-arvo:def wire sign-arvo)
|
||||
[%warm-cache ~]
|
||||
?. warm-cache
|
||||
~
|
||||
?> ?=([%clay %writ *] sign-arvo)
|
||||
=* riot p.sign-arvo
|
||||
?~ riot
|
||||
=/ =rave:clay [%next [%t da+now.bowl /mar]]
|
||||
[%pass /warm-cache %arvo %c %warp our.bowl %home `rave]~
|
||||
:- =/ =rave:clay [%next [%t q.p.u.riot /mar]]
|
||||
[%pass /warm-cache %arvo %c %warp our.bowl %home `rave]
|
||||
%+ turn !<((list path) q.r.u.riot)
|
||||
|= pax=path
|
||||
^- card
|
||||
=. pax (snip (slag 1 pax))
|
||||
=/ mark=@ta
|
||||
%+ roll pax
|
||||
|= [=term mark=term]
|
||||
?: ?=(%$ mark)
|
||||
term
|
||||
:((cury cat 3) mark '-' term)
|
||||
=/ =rave:clay [%sing %b da+now.bowl /[mark]]
|
||||
[%pass [%mar mark ~] %arvo %c %warp our.bowl %home `rave]
|
||||
::
|
||||
[%mar ^]
|
||||
?. warm-cache
|
||||
~
|
||||
?> ?=([%clay %writ *] sign-arvo)
|
||||
=* riot p.sign-arvo
|
||||
=* mark t.wire
|
||||
?~ riot
|
||||
~
|
||||
=/ =rave:clay [%next %b q.p.u.riot mark]
|
||||
[%pass wire %arvo %c %warp our.bowl %home `rave]~
|
||||
==
|
||||
::
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
|
@ -1,7 +1,14 @@
|
||||
|%
|
||||
+$ observer [app=term =path thread=term]
|
||||
+$ action
|
||||
$% [%watch =observer]
|
||||
$% :: %gall actions
|
||||
::
|
||||
[%watch =observer]
|
||||
[%ignore =observer]
|
||||
::
|
||||
:: %clay actions
|
||||
::
|
||||
[%warm-cache-all ~]
|
||||
[%cool-cache-all ~]
|
||||
==
|
||||
--
|
||||
|
@ -6,7 +6,6 @@
|
||||
/- gcp, spider, settings
|
||||
/+ strandio
|
||||
=, strand=strand:spider
|
||||
=, enjs:format
|
||||
^- thread:spider
|
||||
|^
|
||||
|= *
|
||||
@ -22,7 +21,7 @@
|
||||
==
|
||||
%- pure:m
|
||||
!>
|
||||
%+ frond %gcp-configured
|
||||
^- json
|
||||
b+has
|
||||
::
|
||||
++ has-settings
|
||||
|
18
pkg/interface/package-lock.json
generated
18
pkg/interface/package-lock.json
generated
@ -1783,36 +1783,30 @@
|
||||
"dependencies": {
|
||||
"@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==",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.168",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
|
||||
"integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q=="
|
||||
"bundled": true
|
||||
},
|
||||
"@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=="
|
||||
"bundled": 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=="
|
||||
"bundled": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
"bundled": true
|
||||
},
|
||||
"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=="
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,19 +1,16 @@
|
||||
import BaseApi from './base';
|
||||
import {StoreState} from '../store/type';
|
||||
import {GcpToken} from '../types/gcp-state';
|
||||
import type {StoreState} from '../store/type';
|
||||
import type {GcpToken} from '../../types/gcp-state';
|
||||
|
||||
|
||||
export default class GcpApi extends BaseApi<StoreState> {
|
||||
isConfigured() {
|
||||
return this.spider('noun', 'json', 'gcp-is-configured', {})
|
||||
.then((data) => {
|
||||
this.store.handleEvent({
|
||||
data
|
||||
});
|
||||
});
|
||||
// Does not touch the store; use the value manually.
|
||||
async isConfigured(): Promise<boolean> {
|
||||
return this.spider('noun', 'json', 'gcp-is-configured', {});
|
||||
}
|
||||
|
||||
getToken() {
|
||||
// Does not return the token; read it out of the store.
|
||||
async getToken(): Promise<void> {
|
||||
return this.spider('noun', 'gcp-token', 'gcp-get-token', {})
|
||||
.then((token) => {
|
||||
this.store.handleEvent({
|
||||
|
@ -1,4 +1,5 @@
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import { immerable } from 'immer';
|
||||
|
||||
interface NonemptyNode<V> {
|
||||
n: [BigInteger, V];
|
||||
@ -14,6 +15,7 @@ type MapNode<V> = NonemptyNode<V> | null;
|
||||
*/
|
||||
export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
private root: MapNode<V> = null;
|
||||
[immerable] = true;
|
||||
size = 0;
|
||||
|
||||
constructor(initial: [BigInteger, V][] = []) {
|
||||
|
@ -26,7 +26,7 @@ class GcpUpload implements StorageUpload {
|
||||
this.#accessKey = accessKey;
|
||||
}
|
||||
|
||||
async promise(): UploadResult {
|
||||
async promise(): Promise<UploadResult> {
|
||||
const {Bucket, Key, ContentType, Body} = this.#params;
|
||||
const urlParams = {
|
||||
uploadType: 'media',
|
||||
|
@ -5,10 +5,9 @@
|
||||
// 1. call configure with a GlobalApi and GlobalStore.
|
||||
// 2. call start() to start the token refresh loop.
|
||||
//
|
||||
// If the ship does not have GCP storage configured, we don't try to get
|
||||
// a token, but we keep checking at regular intervals to see if it gets
|
||||
// configured. If GCP storage is configured, we try to invoke the GCP
|
||||
// get-token thread on the ship until it gives us an access token. Once
|
||||
// If the ship does not have GCP storage configured, we don't try to
|
||||
// get a token. If GCP storage is configured, we try to invoke the GCP
|
||||
// get-token thread on the ship until it gives us an access token. Once
|
||||
// we have a token, we refresh it every hour or so according to its
|
||||
// intrinsic expiry.
|
||||
//
|
||||
@ -25,7 +24,7 @@ class GcpManager {
|
||||
}
|
||||
|
||||
#running = false;
|
||||
#timeoutId: number | null = null;
|
||||
#timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
start() {
|
||||
if (this.#running) {
|
||||
@ -61,19 +60,17 @@ class GcpManager {
|
||||
}
|
||||
|
||||
#consecutiveFailures: number = 0;
|
||||
|
||||
private isConfigured() {
|
||||
return useStorageState.getState().gcp.configured;
|
||||
}
|
||||
#configured: boolean = false;
|
||||
|
||||
private refreshLoop() {
|
||||
if (!this.isConfigured()) {
|
||||
this.#api.gcp.isConfigured()
|
||||
.then(() => {
|
||||
if (this.isConfigured() === undefined) {
|
||||
if (!this.#configured) {
|
||||
this.#api!.gcp.isConfigured()
|
||||
.then((configured) => {
|
||||
if (configured === undefined) {
|
||||
throw new Error("can't check whether GCP is configured?");
|
||||
}
|
||||
if (this.isConfigured()) {
|
||||
this.#configured = configured;
|
||||
if (this.#configured) {
|
||||
this.refreshLoop();
|
||||
} else {
|
||||
console.log('GcpManager: GCP storage not configured; stopping.');
|
||||
@ -86,7 +83,7 @@ class GcpManager {
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.#api.gcp.getToken()
|
||||
this.#api!.gcp.getToken()
|
||||
.then(() => {
|
||||
const token = useStorageState.getState().gcp.token;
|
||||
if (token) {
|
||||
|
52
pkg/interface/src/logic/lib/useRunIO.ts
Normal file
52
pkg/interface/src/logic/lib/useRunIO.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useWaitForProps } from "./useWaitForProps";
|
||||
import {unstable_batchedUpdates} from "react-dom";
|
||||
|
||||
export type IOInstance<I, P, O> = (
|
||||
input: I
|
||||
) => (props: P) => Promise<[(p: P) => boolean, O]>;
|
||||
|
||||
export function useRunIO<I, O>(
|
||||
io: (i: I) => Promise<O>,
|
||||
after: (o: O) => void,
|
||||
key: string
|
||||
) {
|
||||
const [resolve, setResolve] = useState<() => void>(() => () => {});
|
||||
const [reject, setReject] = useState<(e: any) => void>(() => () => {});
|
||||
const [output, setOutput] = useState<O | null>(null);
|
||||
const [done, setDone] = useState(false);
|
||||
const run = (i: I) =>
|
||||
new Promise((res, rej) => {
|
||||
setResolve(() => res);
|
||||
setReject(() => rej);
|
||||
io(i)
|
||||
.then((o) => {
|
||||
unstable_batchedUpdates(() => {
|
||||
setOutput(o);
|
||||
setDone(true);
|
||||
});
|
||||
})
|
||||
.catch(rej);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reject(new Error("useRunIO: key changed"));
|
||||
setDone(false);
|
||||
setOutput(null);
|
||||
}, [key]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!done) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
after(output!);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}, [done]);
|
||||
|
||||
return run;
|
||||
}
|
||||
|
@ -7,20 +7,11 @@ import { reduceState } from '../state/base';
|
||||
export default class GcpReducer {
|
||||
reduce(json: Cage) {
|
||||
reduceState<StorageState, any>(useStorageState, json, [
|
||||
reduceConfigured,
|
||||
reduceToken
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const reduceConfigured = (json, state: StorageState): StorageState => {
|
||||
let data = json['gcp-configured'];
|
||||
if (data !== undefined) {
|
||||
state.gcp.configured = data;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
const reduceToken = (json: Cage, state: StorageState): StorageState => {
|
||||
let data = json['gcp-token'];
|
||||
if (data) {
|
||||
|
@ -4,6 +4,5 @@ export interface GcpToken {
|
||||
};
|
||||
|
||||
export interface GcpState {
|
||||
configured?: boolean;
|
||||
token?: GcpToken
|
||||
};
|
||||
|
@ -100,7 +100,7 @@ function TranscludedPublishNode(props: {
|
||||
?.get(bigInt.one)
|
||||
?.children?.peekLargest()?.[1]!;
|
||||
return (
|
||||
<Col gapY="2">
|
||||
<Col color="black" gapY="2">
|
||||
<Author
|
||||
px="2"
|
||||
showImage
|
||||
|
@ -93,9 +93,6 @@ export function EditProfile(props: any): ReactElement {
|
||||
};
|
||||
|
||||
const history = useHistory();
|
||||
if (contact) {
|
||||
contact.isPublic = isPublic;
|
||||
}
|
||||
|
||||
const onSubmit = async (values: any, actions: any) => {
|
||||
try {
|
||||
@ -143,7 +140,7 @@ export function EditProfile(props: any): ReactElement {
|
||||
<>
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={contact || emptyContact}
|
||||
initialValues={{...contact, isPublic } || emptyContact}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ setFieldValue }) => (
|
||||
|
@ -1,27 +1,28 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import {
|
||||
MetadataUpdatePreview,
|
||||
Contacts,
|
||||
JoinRequests,
|
||||
Groups,
|
||||
Associations
|
||||
} from '@urbit/api';
|
||||
import { Invite } from '@urbit/api/invite';
|
||||
import { Text, Icon, Row } from '@tlon/indigo-react';
|
||||
Associations,
|
||||
} from "@urbit/api";
|
||||
import { Invite } from "@urbit/api/invite";
|
||||
import { Text, Icon, Row } from "@tlon/indigo-react";
|
||||
|
||||
import { cite, useShowNickname } from '~/logic/lib/util';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import { GroupInvite } from './Group';
|
||||
import { InviteSkeleton } from './InviteSkeleton';
|
||||
import { JoinSkeleton } from './JoinSkeleton';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import { cite, useShowNickname } from "~/logic/lib/util";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { resourceFromPath } from "~/logic/lib/group";
|
||||
import { GroupInvite } from "./Group";
|
||||
import { InviteSkeleton } from "./InviteSkeleton";
|
||||
import { JoinSkeleton } from "./JoinSkeleton";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import useGroupState from "~/logic/state/group";
|
||||
import useContactState from "~/logic/state/contact";
|
||||
import useMetadataState from "~/logic/state/metadata";
|
||||
import useGraphState from "~/logic/state/graph";
|
||||
import { useRunIO } from "~/logic/lib/useRunIO";
|
||||
|
||||
interface InviteItemProps {
|
||||
invite?: Invite;
|
||||
@ -32,62 +33,81 @@ interface InviteItemProps {
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
export function useInviteAccept(
|
||||
resource: string,
|
||||
api: GlobalApi,
|
||||
app?: string,
|
||||
uid?: string,
|
||||
invite?: Invite,
|
||||
) {
|
||||
const { ship, name } = resourceFromPath(resource);
|
||||
const history = useHistory();
|
||||
const associations = useMetadataState((s) => s.associations);
|
||||
const groups = useGroupState((s) => s.groups);
|
||||
const graphKeys = useGraphState((s) => s.graphKeys);
|
||||
|
||||
const waiter = useWaitForProps({ associations, graphKeys, groups });
|
||||
return useRunIO<void, boolean>(
|
||||
async () => {
|
||||
if (!(app && invite && uid)) {
|
||||
return false;
|
||||
}
|
||||
if (resource in groups) {
|
||||
await api.invite.decline(app, uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
await api.groups.join(ship, name);
|
||||
await api.invite.accept(app, uid);
|
||||
await waiter((p) => {
|
||||
return (
|
||||
(resource in p.groups &&
|
||||
resource in (p.associations?.graph ?? {}) &&
|
||||
p.graphKeys.has(resource.slice(7))) ||
|
||||
resource in (p.associations?.groups ?? {})
|
||||
);
|
||||
});
|
||||
return true;
|
||||
},
|
||||
(success: boolean) => {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
if (groups?.[resource]?.hidden) {
|
||||
const { metadata } = associations.graph[resource];
|
||||
if (metadata && "graph" in metadata.config) {
|
||||
if (metadata.config.graph === "chat") {
|
||||
history.push(
|
||||
`/~landscape/messages/resource/${metadata.config.graph}${resource}`
|
||||
);
|
||||
} else {
|
||||
history.push(
|
||||
`/~landscape/home/resource/${metadata.config.graph}${resource}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error("unknown metadata: ", metadata);
|
||||
}
|
||||
} else {
|
||||
history.push(`/~landscape${resource}`);
|
||||
}
|
||||
},
|
||||
resource
|
||||
);
|
||||
}
|
||||
|
||||
export function InviteItem(props: InviteItemProps) {
|
||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||
const { pendingJoin, invite, resource, uid, app, api } = props;
|
||||
const { ship, name } = resourceFromPath(resource);
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const graphKeys = useGraphState(s => s.graphKeys);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
const { name } = resourceFromPath(resource);
|
||||
const contacts = useContactState((state) => state.contacts);
|
||||
const contact = contacts?.[`~${invite?.ship}`] ?? {};
|
||||
const showNickname = useShowNickname(contact);
|
||||
const waiter = useWaitForProps(
|
||||
{ associations, groups, pendingJoin, graphKeys: Array.from(graphKeys) },
|
||||
50000
|
||||
);
|
||||
|
||||
const history = useHistory();
|
||||
const inviteAccept = useCallback(async () => {
|
||||
if (!(app && invite && uid)) {
|
||||
return;
|
||||
}
|
||||
if(resource in groups) {
|
||||
await api.invite.decline(app, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
api.groups.join(ship, name);
|
||||
await waiter(p => !!p.pendingJoin);
|
||||
|
||||
api.invite.accept(app, uid);
|
||||
await waiter((p) => {
|
||||
return (
|
||||
resource in p.groups &&
|
||||
(resource in (p.associations?.graph ?? {}) ||
|
||||
resource in (p.associations?.groups ?? {}))
|
||||
);
|
||||
});
|
||||
|
||||
if (groups?.[resource]?.hidden) {
|
||||
await waiter(p => p.graphKeys.includes(resource.slice(7)));
|
||||
const { metadata } = associations.graph[resource];
|
||||
if (metadata && 'graph' in metadata.config) {
|
||||
if (metadata.config.graph === 'chat') {
|
||||
history.push(`/~landscape/messages/resource/${metadata.config.graph}${resource}`);
|
||||
} else {
|
||||
history.push(`/~landscape/home/resource/${metadata.config.graph}${resource}`);
|
||||
}
|
||||
} else {
|
||||
console.error('unknown metadata: ', metadata);
|
||||
}
|
||||
} else {
|
||||
history.push(`/~landscape${resource}`);
|
||||
}
|
||||
}, [app, history, waiter, invite, uid, resource, groups, associations]);
|
||||
const inviteAccept = useInviteAccept(resource, api, app, uid, invite);
|
||||
|
||||
const inviteDecline = useCallback(async () => {
|
||||
if(!(app && uid)) {
|
||||
if (!(app && uid)) {
|
||||
return;
|
||||
}
|
||||
await api.invite.decline(app, uid);
|
||||
@ -96,7 +116,7 @@ export function InviteItem(props: InviteItemProps) {
|
||||
const handlers = { onAccept: inviteAccept, onDecline: inviteDecline };
|
||||
|
||||
useEffect(() => {
|
||||
if (!app || app === 'groups') {
|
||||
if (!app || app === "groups") {
|
||||
(async () => {
|
||||
setPreview(await api.metadata.preview(resource));
|
||||
})();
|
||||
@ -108,7 +128,7 @@ export function InviteItem(props: InviteItemProps) {
|
||||
}
|
||||
}, [invite]);
|
||||
|
||||
if(pendingJoin?.hidden) {
|
||||
if (pendingJoin?.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -123,7 +143,7 @@ export function InviteItem(props: InviteItemProps) {
|
||||
{...handlers}
|
||||
/>
|
||||
);
|
||||
} else if (invite && name.startsWith('dm--')) {
|
||||
} else if (invite && name.startsWith("dm--")) {
|
||||
return (
|
||||
<InviteSkeleton
|
||||
gapY="3"
|
||||
@ -133,16 +153,18 @@ export function InviteItem(props: InviteItemProps) {
|
||||
>
|
||||
<Row py="1" alignItems="center">
|
||||
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
||||
<Text mr="1"
|
||||
<Text
|
||||
mr="1"
|
||||
mono={!showNickname}
|
||||
fontWeight={showNickname ? '500' : '400'}>
|
||||
fontWeight={showNickname ? "500" : "400"}
|
||||
>
|
||||
{showNickname ? contact?.nickname : cite(`~${invite!.ship}`)}
|
||||
</Text>
|
||||
<Text mr="1">invited you to a DM</Text>
|
||||
</Row>
|
||||
</InviteSkeleton>
|
||||
);
|
||||
} else if (status && name.startsWith('dm--')) {
|
||||
} else if (status && name.startsWith("dm--")) {
|
||||
return (
|
||||
<JoinSkeleton api={api} resource={resource} status={status} gapY="3">
|
||||
<Row py="1" alignItems="center">
|
||||
@ -162,9 +184,11 @@ export function InviteItem(props: InviteItemProps) {
|
||||
>
|
||||
<Row py="1" alignItems="center">
|
||||
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
||||
<Text mr="1"
|
||||
<Text
|
||||
mr="1"
|
||||
mono={!showNickname}
|
||||
fontWeight={showNickname ? '500' : '400'}>
|
||||
fontWeight={showNickname ? "500" : "400"}
|
||||
>
|
||||
{showNickname ? contact?.nickname : cite(`~${invite!.ship}`)}
|
||||
</Text>
|
||||
<Text mr="1">
|
||||
@ -174,14 +198,12 @@ export function InviteItem(props: InviteItemProps) {
|
||||
</InviteSkeleton>
|
||||
);
|
||||
} else if (pendingJoin) {
|
||||
const [, , ship, name] = resource.split('/');
|
||||
const [, , ship, name] = resource.split("/");
|
||||
return (
|
||||
<JoinSkeleton api={api} resource={resource} status={pendingJoin}>
|
||||
<Row py="1" alignItems="center">
|
||||
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
||||
<Text mr="1">
|
||||
You are joining
|
||||
</Text>
|
||||
<Text mr="1">You are joining</Text>
|
||||
<Text mono>
|
||||
{cite(ship)}/{name}
|
||||
</Text>
|
||||
|
@ -56,7 +56,7 @@ function GraphContentWideInner(
|
||||
lineHeight="20px"
|
||||
color="black"
|
||||
width="fit-content"
|
||||
maxWidth="500px"
|
||||
maxWidth="min(500px, 100%)"
|
||||
>
|
||||
<RemoteContent key={content.url} url={content.url} />
|
||||
</Box>
|
||||
|
@ -23,7 +23,7 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc<typeof Col>): R
|
||||
anchorRef
|
||||
);
|
||||
return (
|
||||
<Col {...rest} ref={anchorRef} gapY="4">
|
||||
<Col {...rest} ref={anchorRef} gapY="4" maxWidth={['100%', '288px']}>
|
||||
<Row gapX="2" width="100%">
|
||||
<MetadataIcon
|
||||
width="40px"
|
||||
|
@ -151,47 +151,50 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
||||
</StatelessAsyncButton>
|
||||
</Col>
|
||||
) : preview ? (
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview?.members}
|
||||
channelCount={preview?.['channel-count']}
|
||||
>
|
||||
{ Object.keys(preview.channels).length > 0 && (
|
||||
<Col
|
||||
gapY="2"
|
||||
p="2"
|
||||
borderRadius="2"
|
||||
border="1"
|
||||
borderColor="washedGray"
|
||||
bg="washedBlue"
|
||||
maxHeight="300px"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Text gray fontSize="1">
|
||||
Channels
|
||||
</Text>
|
||||
<Box width="100%" flexShrink="0">
|
||||
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||
<Row width="100%">
|
||||
<Icon
|
||||
mr="2"
|
||||
color="blue"
|
||||
icon={getModuleIcon(metadata?.config?.graph) as any}
|
||||
/>
|
||||
<Text color="blue">{metadata.title} </Text>
|
||||
</Row>
|
||||
))}
|
||||
</Box>
|
||||
</Col>
|
||||
)}
|
||||
<>
|
||||
<GroupSummary
|
||||
metadata={preview.metadata}
|
||||
memberCount={preview?.members}
|
||||
channelCount={preview?.['channel-count']}
|
||||
>
|
||||
{ Object.keys(preview.channels).length > 0 && (
|
||||
<Col
|
||||
gapY="2"
|
||||
p="2"
|
||||
borderRadius="2"
|
||||
border="1"
|
||||
borderColor="washedGray"
|
||||
bg="washedBlue"
|
||||
maxHeight="300px"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Text gray fontSize="1">
|
||||
Channels
|
||||
</Text>
|
||||
<Box width="100%" flexShrink="0">
|
||||
{Object.values(preview.channels).map(({ metadata }: any) => (
|
||||
<Row width="100%">
|
||||
<Icon
|
||||
mr="2"
|
||||
color="blue"
|
||||
icon={getModuleIcon(metadata?.config?.graph) as any}
|
||||
/>
|
||||
<Text color="blue">{metadata.title} </Text>
|
||||
</Row>
|
||||
))}
|
||||
</Box>
|
||||
</Col>
|
||||
)}
|
||||
</GroupSummary>
|
||||
<StatelessAsyncButton
|
||||
marginTop={3}
|
||||
primary
|
||||
name="join"
|
||||
onClick={() => onConfirm(preview.group)}
|
||||
>
|
||||
Join {preview.metadata.title}
|
||||
</StatelessAsyncButton>
|
||||
</GroupSummary>
|
||||
</>
|
||||
) : (
|
||||
<Col width="100%" gapY="4">
|
||||
<Formik
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BigInteger } from "big-integer";
|
||||
import { immerable } from 'immer';
|
||||
|
||||
interface NonemptyNode<V> {
|
||||
n: [BigInteger, V];
|
||||
@ -14,6 +15,7 @@ type MapNode<V> = NonemptyNode<V> | null;
|
||||
*/
|
||||
export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
private root: MapNode<V> = null;
|
||||
[immerable] = true;
|
||||
size: number = 0;
|
||||
|
||||
constructor(initial: [BigInteger, V][] = []) {
|
||||
|
@ -21,6 +21,7 @@
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@urbit/eslint-config": "^1.0.0",
|
||||
"big-integer": "^1.6.48",
|
||||
"immer": "^9.0.1",
|
||||
"lodash": "^4.17.20"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user