mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-16 10:49:26 +03:00
Merge branch 'release/next-js' into james/image-input
This commit is contained in:
commit
cc0caa5aa5
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:abc163491b53cc9d48a70ea378dde8bb455648e3f6e7f57a0c7a8e164d4ca105
|
oid sha256:accbadc701471f6b071ed286164a0bf4d3f8a2e64cfaea9019e123bd9edca569
|
||||||
size 10226599
|
size 10292454
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
/- glob
|
/- glob
|
||||||
/+ default-agent, verb, dbug
|
/+ 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))]
|
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||||
+$ all-states
|
+$ all-states
|
||||||
$% state-0
|
$% state-0
|
||||||
|
@ -46,19 +46,8 @@
|
|||||||
%0
|
%0
|
||||||
%_ $
|
%_ $
|
||||||
-.old %1
|
-.old %1
|
||||||
::
|
cards cards
|
||||||
validators.old
|
validators.old 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]]
|
|
||||||
::
|
::
|
||||||
graphs.old
|
graphs.old
|
||||||
%- ~(run by graphs.old)
|
%- ~(run by graphs.old)
|
||||||
@ -285,19 +274,10 @@
|
|||||||
graphs (~(put by graphs) resource [graph mark])
|
graphs (~(put by graphs) resource [graph mark])
|
||||||
update-logs (~(put by update-logs) resource update-log)
|
update-logs (~(put by update-logs) resource update-log)
|
||||||
archive (~(del by archive) resource)
|
archive (~(del by archive) resource)
|
||||||
::
|
|
||||||
validators
|
|
||||||
?~ mark validators
|
|
||||||
(~(put in validators) u.mark)
|
|
||||||
==
|
==
|
||||||
%- zing
|
%- zing
|
||||||
:~ (give [/keys ~] %keys (~(put in ~(key by graphs)) resource))
|
:~ (give [/keys ~] %keys (~(put in ~(key by graphs)) resource))
|
||||||
(give [/updates ~] %add-graph resource *graph:store mark overwrite)
|
(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
|
++ remove-graph
|
||||||
@ -1120,12 +1100,7 @@
|
|||||||
::
|
::
|
||||||
:: old wire, do nothing
|
:: old wire, do nothing
|
||||||
[%graph *] [~ this]
|
[%graph *] [~ this]
|
||||||
::
|
[%validator @ ~] [~ this]
|
||||||
[%validator @ ~]
|
|
||||||
:_ this
|
|
||||||
=* validator i.t.wire
|
|
||||||
=/ =rave:clay [%next %b [%da now.bowl] /[validator]]
|
|
||||||
[%pass wire %arvo %c %warp our.bowl [%home `rave]]~
|
|
||||||
::
|
::
|
||||||
[%try-rejoin @ *]
|
[%try-rejoin @ *]
|
||||||
=/ rid=resource:store (de-path:res t.t.wire)
|
=/ rid=resource:store (de-path:res t.t.wire)
|
||||||
|
@ -24,6 +24,6 @@
|
|||||||
<div id="portal-root"></div>
|
<div id="portal-root"></div>
|
||||||
<script src="/~landscape/js/channel.js"></script>
|
<script src="/~landscape/js/channel.js"></script>
|
||||||
<script src="/~landscape/js/session.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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
[%2 observers=(map serial observer:sur)]
|
[%2 observers=(map serial observer:sur)]
|
||||||
[%3 observers=(map serial observer:sur)]
|
[%3 observers=(map serial observer:sur)]
|
||||||
[%4 observers=(map serial observer:sur)]
|
[%4 observers=(map serial observer:sur)]
|
||||||
|
[%5 observers=(map serial observer:sur) warm-cache=_|]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ serial @uv
|
+$ serial @uv
|
||||||
@ -27,7 +28,7 @@
|
|||||||
--
|
--
|
||||||
::
|
::
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
=| [%4 observers=(map serial observer:sur)]
|
=| [%5 observers=(map serial observer:sur) warm-cache=_|]
|
||||||
=* state -
|
=* state -
|
||||||
::
|
::
|
||||||
^- agent:gall
|
^- agent:gall
|
||||||
@ -42,6 +43,7 @@
|
|||||||
(act [%watch %group-store /groups %group-on-leave])
|
(act [%watch %group-store /groups %group-on-leave])
|
||||||
(act [%watch %group-store /groups %group-on-remove-member])
|
(act [%watch %group-store /groups %group-on-remove-member])
|
||||||
(act [%watch %metadata-store /updates %md-on-add-group-feed])
|
(act [%watch %metadata-store /updates %md-on-add-group-feed])
|
||||||
|
(act [%warm-cache-all ~])
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ act
|
++ act
|
||||||
@ -66,8 +68,13 @@
|
|||||||
=| cards=(list card)
|
=| cards=(list card)
|
||||||
|-
|
|-
|
||||||
?- -.old-state
|
?- -.old-state
|
||||||
%4
|
%5
|
||||||
[cards this(state old-state)]
|
[cards this(state old-state)]
|
||||||
|
%4
|
||||||
|
=. cards
|
||||||
|
:_ cards
|
||||||
|
(act [%warm-cache-all ~])
|
||||||
|
$(old-state [%5 observers.old-state %.n])
|
||||||
::
|
::
|
||||||
%3
|
%3
|
||||||
=. cards
|
=. cards
|
||||||
@ -110,11 +117,19 @@
|
|||||||
?> (team:title our.bowl src.bowl)
|
?> (team:title our.bowl src.bowl)
|
||||||
?. ?=(%observe-action mark)
|
?. ?=(%observe-action mark)
|
||||||
(on-poke:def mark vase)
|
(on-poke:def mark vase)
|
||||||
|
|^
|
||||||
=/ =action:sur !<(action:sur vase)
|
=/ =action:sur !<(action:sur vase)
|
||||||
=* observer observer.action
|
=* observer observer.action
|
||||||
=/ vals (silt ~(val by observers))
|
=/ vals (silt ~(val by observers))
|
||||||
?- -.action
|
?- -.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))
|
?: ?|(=(app.observer %spider) =(app.observer %observe-hook))
|
||||||
~|('we avoid infinite loops' !!)
|
~|('we avoid infinite loops' !!)
|
||||||
?: (~(has in vals) observer)
|
?: (~(has in vals) observer)
|
||||||
@ -129,7 +144,8 @@
|
|||||||
path.observer
|
path.observer
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
%ignore
|
++ ignore
|
||||||
|
|= [=observer:sur vals=(set observer:sur)]
|
||||||
?. (~(has in vals) observer)
|
?. (~(has in vals) observer)
|
||||||
~|('cannot remove nonexistent observer' !!)
|
~|('cannot remove nonexistent observer' !!)
|
||||||
=/ key (got-by-val observers observer)
|
=/ key (got-by-val observers observer)
|
||||||
@ -142,7 +158,19 @@
|
|||||||
%leave
|
%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
|
++ on-agent
|
||||||
|= [=wire =sign:agent:gall]
|
|= [=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-watch on-watch:def
|
||||||
++ on-leave on-leave:def
|
++ on-leave on-leave:def
|
||||||
++ on-peek on-peek:def
|
++ on-peek on-peek:def
|
||||||
++ on-arvo on-arvo:def
|
|
||||||
++ on-fail on-fail:def
|
++ on-fail on-fail:def
|
||||||
--
|
--
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
|%
|
|%
|
||||||
+$ observer [app=term =path thread=term]
|
+$ observer [app=term =path thread=term]
|
||||||
+$ action
|
+$ action
|
||||||
$% [%watch =observer]
|
$% :: %gall actions
|
||||||
|
::
|
||||||
|
[%watch =observer]
|
||||||
[%ignore =observer]
|
[%ignore =observer]
|
||||||
|
::
|
||||||
|
:: %clay actions
|
||||||
|
::
|
||||||
|
[%warm-cache-all ~]
|
||||||
|
[%cool-cache-all ~]
|
||||||
==
|
==
|
||||||
--
|
--
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
/- gcp, spider, settings
|
/- gcp, spider, settings
|
||||||
/+ strandio
|
/+ strandio
|
||||||
=, strand=strand:spider
|
=, strand=strand:spider
|
||||||
=, enjs:format
|
|
||||||
^- thread:spider
|
^- thread:spider
|
||||||
|^
|
|^
|
||||||
|= *
|
|= *
|
||||||
@ -22,7 +21,7 @@
|
|||||||
==
|
==
|
||||||
%- pure:m
|
%- pure:m
|
||||||
!>
|
!>
|
||||||
%+ frond %gcp-configured
|
^- json
|
||||||
b+has
|
b+has
|
||||||
::
|
::
|
||||||
++ has-settings
|
++ has-settings
|
||||||
|
18
pkg/interface/package-lock.json
generated
18
pkg/interface/package-lock.json
generated
@ -1783,36 +1783,30 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.12.5",
|
"version": "7.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
|
"bundled": true,
|
||||||
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/lodash": {
|
"@types/lodash": {
|
||||||
"version": "4.14.168",
|
"version": "4.14.168",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
|
"bundled": true
|
||||||
"integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q=="
|
|
||||||
},
|
},
|
||||||
"@urbit/eslint-config": {
|
"@urbit/eslint-config": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@urbit/eslint-config/-/eslint-config-1.0.0.tgz",
|
"bundled": true
|
||||||
"integrity": "sha512-Xmzb6MvM7KorlPJEq/hURZZ4BHSVy/7CoQXWogsBSTv5MOZnMqwNKw6yt24k2AO/2UpHwjGptimaNLqFfesJbw=="
|
|
||||||
},
|
},
|
||||||
"big-integer": {
|
"big-integer": {
|
||||||
"version": "1.6.48",
|
"version": "1.6.48",
|
||||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
"bundled": true
|
||||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
|
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"bundled": true
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
|
||||||
},
|
},
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.7",
|
"version": "0.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
"bundled": true
|
||||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import BaseApi from './base';
|
import BaseApi from './base';
|
||||||
import {StoreState} from '../store/type';
|
import type {StoreState} from '../store/type';
|
||||||
import {GcpToken} from '../types/gcp-state';
|
import type {GcpToken} from '../../types/gcp-state';
|
||||||
|
|
||||||
|
|
||||||
export default class GcpApi extends BaseApi<StoreState> {
|
export default class GcpApi extends BaseApi<StoreState> {
|
||||||
isConfigured() {
|
// Does not touch the store; use the value manually.
|
||||||
return this.spider('noun', 'json', 'gcp-is-configured', {})
|
async isConfigured(): Promise<boolean> {
|
||||||
.then((data) => {
|
return this.spider('noun', 'json', 'gcp-is-configured', {});
|
||||||
this.store.handleEvent({
|
|
||||||
data
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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', {})
|
return this.spider('noun', 'gcp-token', 'gcp-get-token', {})
|
||||||
.then((token) => {
|
.then((token) => {
|
||||||
this.store.handleEvent({
|
this.store.handleEvent({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import bigInt, { BigInteger } from 'big-integer';
|
import bigInt, { BigInteger } from 'big-integer';
|
||||||
|
import { immerable } from 'immer';
|
||||||
|
|
||||||
interface NonemptyNode<V> {
|
interface NonemptyNode<V> {
|
||||||
n: [BigInteger, V];
|
n: [BigInteger, V];
|
||||||
@ -14,6 +15,7 @@ type MapNode<V> = NonemptyNode<V> | null;
|
|||||||
*/
|
*/
|
||||||
export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||||
private root: MapNode<V> = null;
|
private root: MapNode<V> = null;
|
||||||
|
[immerable] = true;
|
||||||
size = 0;
|
size = 0;
|
||||||
|
|
||||||
constructor(initial: [BigInteger, V][] = []) {
|
constructor(initial: [BigInteger, V][] = []) {
|
||||||
|
@ -26,7 +26,7 @@ class GcpUpload implements StorageUpload {
|
|||||||
this.#accessKey = accessKey;
|
this.#accessKey = accessKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
async promise(): UploadResult {
|
async promise(): Promise<UploadResult> {
|
||||||
const {Bucket, Key, ContentType, Body} = this.#params;
|
const {Bucket, Key, ContentType, Body} = this.#params;
|
||||||
const urlParams = {
|
const urlParams = {
|
||||||
uploadType: 'media',
|
uploadType: 'media',
|
||||||
|
@ -5,9 +5,8 @@
|
|||||||
// 1. call configure with a GlobalApi and GlobalStore.
|
// 1. call configure with a GlobalApi and GlobalStore.
|
||||||
// 2. call start() to start the token refresh loop.
|
// 2. call start() to start the token refresh loop.
|
||||||
//
|
//
|
||||||
// If the ship does not have GCP storage configured, we don't try to get
|
// If the ship does not have GCP storage configured, we don't try to
|
||||||
// a token, but we keep checking at regular intervals to see if it gets
|
// get a token. If GCP storage is configured, we try to invoke the GCP
|
||||||
// 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
|
// 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
|
// we have a token, we refresh it every hour or so according to its
|
||||||
// intrinsic expiry.
|
// intrinsic expiry.
|
||||||
@ -25,7 +24,7 @@ class GcpManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#running = false;
|
#running = false;
|
||||||
#timeoutId: number | null = null;
|
#timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (this.#running) {
|
if (this.#running) {
|
||||||
@ -61,19 +60,17 @@ class GcpManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#consecutiveFailures: number = 0;
|
#consecutiveFailures: number = 0;
|
||||||
|
#configured: boolean = false;
|
||||||
private isConfigured() {
|
|
||||||
return useStorageState.getState().gcp.configured;
|
|
||||||
}
|
|
||||||
|
|
||||||
private refreshLoop() {
|
private refreshLoop() {
|
||||||
if (!this.isConfigured()) {
|
if (!this.#configured) {
|
||||||
this.#api.gcp.isConfigured()
|
this.#api!.gcp.isConfigured()
|
||||||
.then(() => {
|
.then((configured) => {
|
||||||
if (this.isConfigured() === undefined) {
|
if (configured === undefined) {
|
||||||
throw new Error("can't check whether GCP is configured?");
|
throw new Error("can't check whether GCP is configured?");
|
||||||
}
|
}
|
||||||
if (this.isConfigured()) {
|
this.#configured = configured;
|
||||||
|
if (this.#configured) {
|
||||||
this.refreshLoop();
|
this.refreshLoop();
|
||||||
} else {
|
} else {
|
||||||
console.log('GcpManager: GCP storage not configured; stopping.');
|
console.log('GcpManager: GCP storage not configured; stopping.');
|
||||||
@ -86,7 +83,7 @@ class GcpManager {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#api.gcp.getToken()
|
this.#api!.gcp.getToken()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const token = useStorageState.getState().gcp.token;
|
const token = useStorageState.getState().gcp.token;
|
||||||
if (token) {
|
if (token) {
|
||||||
|
@ -16,5 +16,5 @@ export function useCopy(copied: string, display: string) {
|
|||||||
display,
|
display,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { copyDisplay, doCopy };
|
return { copyDisplay, doCopy, didCopy };
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import f, { compose, memoize } from 'lodash/fp';
|
import f, { compose, memoize } from 'lodash/fp';
|
||||||
import bigInt, { BigInteger } from 'big-integer';
|
import bigInt, { BigInteger } from 'big-integer';
|
||||||
@ -400,11 +400,15 @@ interface useHoveringInterface {
|
|||||||
|
|
||||||
export const useHovering = (): useHoveringInterface => {
|
export const useHovering = (): useHoveringInterface => {
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
const bind = {
|
const onMouseOver = useCallback(() => setHovering(true), [])
|
||||||
onMouseOver: () => setHovering(true),
|
const onMouseLeave = useCallback(() => setHovering(false), [])
|
||||||
onMouseLeave: () => setHovering(false)
|
const bind = useMemo(() => ({
|
||||||
};
|
onMouseOver,
|
||||||
return { hovering, bind };
|
onMouseLeave,
|
||||||
|
}), [onMouseLeave, onMouseOver]);
|
||||||
|
|
||||||
|
|
||||||
|
return useMemo(() => ({ hovering, bind }), [hovering, bind]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
||||||
|
@ -1,27 +1,17 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import {StoreState} from '../store/type';
|
import {StoreState} from '../store/type';
|
||||||
import {GcpToken} from '../../types/gcp-state';
|
import type {GcpToken} from '../../types/gcp-state';
|
||||||
import { Cage } from '~/types/cage';
|
import type {Cage} from '~/types/cage';
|
||||||
import useStorageState, { StorageState } from '../state/storage';
|
import useStorageState, { StorageState } from '../state/storage';
|
||||||
import { reduceState } from '../state/base';
|
import { reduceState } from '../state/base';
|
||||||
|
|
||||||
export default class GcpReducer {
|
export default class GcpReducer {
|
||||||
reduce(json: Cage) {
|
reduce(json: Cage) {
|
||||||
reduceState<StorageState, any>(useStorageState, json, [
|
reduceState<StorageState, any>(useStorageState, json, [
|
||||||
reduceConfigured,
|
|
||||||
reduceToken
|
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 => {
|
const reduceToken = (json: Cage, state: StorageState): StorageState => {
|
||||||
let data = json['gcp-token'];
|
let data = json['gcp-token'];
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -37,7 +27,7 @@ const setToken = (data: any, state: StorageState): StorageState => {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isToken = (token: any): boolean => {
|
const isToken = (token: any): token is GcpToken => {
|
||||||
return (typeof(token.accessKey) === 'string' &&
|
return (typeof(token.accessKey) === 'string' &&
|
||||||
typeof(token.expiresIn) === 'number');
|
typeof(token.expiresIn) === 'number');
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restart() {
|
restart() {
|
||||||
this.start();
|
this.openSubscriptions = {};
|
||||||
|
super.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,5 @@ export interface GcpToken {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface GcpState {
|
export interface GcpState {
|
||||||
configured?: boolean;
|
|
||||||
token?: GcpToken
|
token?: GcpToken
|
||||||
};
|
};
|
||||||
|
@ -29,8 +29,8 @@ import {
|
|||||||
Groups,
|
Groups,
|
||||||
Associations
|
Associations
|
||||||
} from '~/types';
|
} from '~/types';
|
||||||
import TextContent from './content/text';
|
import TextContent from '../../../landscape/components/Graph/content/text';
|
||||||
import CodeContent from './content/code';
|
import CodeContent from '../../../landscape/components/Graph/content/code';
|
||||||
import RemoteContent from '~/views/components/RemoteContent';
|
import RemoteContent from '~/views/components/RemoteContent';
|
||||||
import { Mention } from '~/views/components/MentionText';
|
import { Mention } from '~/views/components/MentionText';
|
||||||
import { Dropdown } from '~/views/components/Dropdown';
|
import { Dropdown } from '~/views/components/Dropdown';
|
||||||
@ -42,8 +42,7 @@ import useContactState from '~/logic/state/contact';
|
|||||||
import { useIdlingState } from '~/logic/lib/idling';
|
import { useIdlingState } from '~/logic/lib/idling';
|
||||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||||
import {useCopy} from '~/logic/lib/useCopy';
|
import {useCopy} from '~/logic/lib/useCopy';
|
||||||
import {PermalinkEmbed} from '../../permalinks/embed';
|
import {GraphContentWide} from '~/views/landscape/components/Graph/GraphContentWide';
|
||||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
|
||||||
|
|
||||||
|
|
||||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||||
@ -284,8 +283,7 @@ class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
hideHover
|
hideHover
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let onReply = this.props?.onReply;
|
let onReply = this.props?.onReply ?? (() => {});
|
||||||
onReply ??= () => {};
|
|
||||||
const transcluded = this.props?.transcluded ?? 0;
|
const transcluded = this.props?.transcluded ?? 0;
|
||||||
let { renderSigil } = this.props;
|
let { renderSigil } = this.props;
|
||||||
|
|
||||||
@ -397,12 +395,11 @@ export const MessageAuthor = ({
|
|||||||
msg.author !== window.ship) &&
|
msg.author !== window.ship) &&
|
||||||
`~${msg.author}` in contacts
|
`~${msg.author}` in contacts
|
||||||
? contacts[`~${msg.author}`]
|
? contacts[`~${msg.author}`]
|
||||||
: false;
|
: undefined;
|
||||||
|
|
||||||
const showNickname = useShowNickname(contact);
|
const showNickname = useShowNickname(contact);
|
||||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||||
const shipName = showNickname ? contact.nickname : cite(msg.author);
|
const shipName = showNickname && contact?.nickname || cite(msg.author) || `~${msg.author}`;
|
||||||
const copyNotice = 'Copied';
|
|
||||||
const color = contact
|
const color = contact
|
||||||
? `#${uxToHex(contact.color)}`
|
? `#${uxToHex(contact.color)}`
|
||||||
: dark
|
: dark
|
||||||
@ -413,28 +410,10 @@ export const MessageAuthor = ({
|
|||||||
: dark
|
: dark
|
||||||
? 'mix-blend-diff'
|
? 'mix-blend-diff'
|
||||||
: 'mix-blend-darken';
|
: 'mix-blend-darken';
|
||||||
const [displayName, setDisplayName] = useState(shipName);
|
|
||||||
const [nameMono, setNameMono] = useState(showNickname ? false : true);
|
const { copyDisplay, doCopy, didCopy } = useCopy(`~${msg.author}`, shipName);
|
||||||
const { hovering, bind } = useHovering();
|
const { hovering, bind } = useHovering();
|
||||||
const [showOverlay, setShowOverlay] = useState(false);
|
const nameMono = !(showNickname || didCopy);
|
||||||
|
|
||||||
const toggleOverlay = () => {
|
|
||||||
setShowOverlay((value) => !value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showCopyNotice = () => {
|
|
||||||
setDisplayName(copyNotice);
|
|
||||||
setNameMono(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const resetDisplay = () => {
|
|
||||||
setDisplayName(shipName);
|
|
||||||
setNameMono(showNickname ? false : true);
|
|
||||||
};
|
|
||||||
const timer = setTimeout(() => resetDisplay(), 800);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [shipName, displayName]);
|
|
||||||
|
|
||||||
const img =
|
const img =
|
||||||
contact?.avatar && !hideAvatars ? (
|
contact?.avatar && !hideAvatars ? (
|
||||||
@ -471,9 +450,6 @@ export const MessageAuthor = ({
|
|||||||
return (
|
return (
|
||||||
<Box display='flex' alignItems='flex-start' {...rest}>
|
<Box display='flex' alignItems='flex-start' {...rest}>
|
||||||
<Box
|
<Box
|
||||||
onClick={() => {
|
|
||||||
setShowOverlay(true);
|
|
||||||
}}
|
|
||||||
height={24}
|
height={24}
|
||||||
pr={2}
|
pr={2}
|
||||||
mt={'1px'}
|
mt={'1px'}
|
||||||
@ -501,13 +477,10 @@ export const MessageAuthor = ({
|
|||||||
mono={nameMono}
|
mono={nameMono}
|
||||||
fontWeight={nameMono ? '400' : '500'}
|
fontWeight={nameMono ? '400' : '500'}
|
||||||
cursor='pointer'
|
cursor='pointer'
|
||||||
onClick={() => {
|
onClick={doCopy}
|
||||||
writeText(`~${msg.author}`);
|
|
||||||
showCopyNotice();
|
|
||||||
}}
|
|
||||||
title={`~${msg.author}`}
|
title={`~${msg.author}`}
|
||||||
>
|
>
|
||||||
{displayName}
|
{copyDisplay}
|
||||||
</Text>
|
</Text>
|
||||||
<Text flexShrink={0} fontSize={0} gray>
|
<Text flexShrink={0} fontSize={0} gray>
|
||||||
{timestamp}
|
{timestamp}
|
||||||
@ -539,7 +512,6 @@ export const Message = ({
|
|||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { hovering, bind } = useHovering();
|
const { hovering, bind } = useHovering();
|
||||||
const contacts = useContactState((state) => state.contacts);
|
|
||||||
return (
|
return (
|
||||||
<Box width="100%" position='relative' {...rest}>
|
<Box width="100%" position='relative' {...rest}>
|
||||||
{timestampHover ? (
|
{timestampHover ? (
|
||||||
@ -558,66 +530,14 @@ export const Message = ({
|
|||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
<Box width="100%" {...bind}>
|
<GraphContentWide
|
||||||
{msg.contents.map((content, i) => {
|
{...bind}
|
||||||
switch (Object.keys(content)[0]) {
|
width="100%"
|
||||||
case 'text':
|
post={msg}
|
||||||
return (
|
|
||||||
<TextContent
|
|
||||||
key={i}
|
|
||||||
api={api}
|
|
||||||
fontSize={1}
|
|
||||||
lineHeight={'20px'}
|
|
||||||
content={content}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'code':
|
|
||||||
return <CodeContent key={i} content={content} />;
|
|
||||||
case 'reference':
|
|
||||||
const { link } = referenceToPermalink(content);
|
|
||||||
return (
|
|
||||||
<PermalinkEmbed
|
|
||||||
link={link}
|
|
||||||
api={api}
|
|
||||||
transcluded={transcluded}
|
transcluded={transcluded}
|
||||||
|
api={api}
|
||||||
showOurContact={showOurContact}
|
showOurContact={showOurContact}
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
case 'url':
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={i}
|
|
||||||
flexShrink={0}
|
|
||||||
fontSize={1}
|
|
||||||
lineHeight='20px'
|
|
||||||
color='black'
|
|
||||||
width="fit-content"
|
|
||||||
maxWidth="500px"
|
|
||||||
>
|
|
||||||
<RemoteContent
|
|
||||||
key={content.url}
|
|
||||||
url={content.url}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
case 'mention':
|
|
||||||
const first = (i) => i === 0;
|
|
||||||
return (
|
|
||||||
<Mention
|
|
||||||
key={i}
|
|
||||||
first={first(i)}
|
|
||||||
group={group}
|
|
||||||
scrollWindow={scrollWindow}
|
|
||||||
ship={content.mention}
|
|
||||||
contact={contacts?.[`~${content.mention}`]}
|
|
||||||
api={api}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -100,7 +100,7 @@ function TranscludedPublishNode(props: {
|
|||||||
?.get(bigInt.one)
|
?.get(bigInt.one)
|
||||||
?.children?.peekLargest()?.[1]!;
|
?.children?.peekLargest()?.[1]!;
|
||||||
return (
|
return (
|
||||||
<Col gapY="2">
|
<Col color="black" gapY="2">
|
||||||
<Author
|
<Author
|
||||||
px="2"
|
px="2"
|
||||||
showImage
|
showImage
|
||||||
|
@ -93,9 +93,6 @@ export function EditProfile(props: any): ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
if (contact) {
|
|
||||||
contact.isPublic = isPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async (values: any, actions: any) => {
|
const onSubmit = async (values: any, actions: any) => {
|
||||||
try {
|
try {
|
||||||
@ -143,7 +140,7 @@ export function EditProfile(props: any): ReactElement {
|
|||||||
<>
|
<>
|
||||||
<Formik
|
<Formik
|
||||||
validationSchema={formSchema}
|
validationSchema={formSchema}
|
||||||
initialValues={contact || emptyContact}
|
initialValues={{...contact, isPublic } || emptyContact}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
{({ setFieldValue }) => (
|
{({ setFieldValue }) => (
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { AsyncButton } from "../../../components/AsyncButton";
|
|
||||||
import * as Yup from "yup";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
ManagedTextInputField as Input,
|
|
||||||
ManagedCheckboxField as Checkbox,
|
|
||||||
Col,
|
|
||||||
Button,
|
|
||||||
Center,
|
|
||||||
} from "@tlon/indigo-react";
|
|
||||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
|
||||||
import GlobalApi from "~/logic/api/global";
|
|
||||||
import { Notebook } from "~/types/publish-update";
|
|
||||||
import { Contacts } from "@urbit/api/contacts";
|
|
||||||
import { FormError } from "~/views/components/FormError";
|
|
||||||
import { RouteComponentProps, useHistory } from "react-router-dom";
|
|
||||||
import {Association} from "@urbit/api";
|
|
||||||
import { uxToHex } from "~/logic/lib/util";
|
|
||||||
|
|
||||||
interface MetadataFormProps {
|
|
||||||
host: string;
|
|
||||||
book: string;
|
|
||||||
association: Association;
|
|
||||||
api: GlobalApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormSchema {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
|
||||||
name: Yup.string().required("Notebook must have a name"),
|
|
||||||
description: Yup.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
const ResetOnPropsChange = (props: { init: FormSchema; book: string }) => {
|
|
||||||
const { resetForm } = useFormikContext<FormSchema>();
|
|
||||||
useEffect(() => {
|
|
||||||
resetForm({ values: props.init });
|
|
||||||
}, [props.book]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export function MetadataForm(props: MetadataFormProps) {
|
|
||||||
const { api, book } = props;
|
|
||||||
const { metadata } = props.association || {};
|
|
||||||
|
|
||||||
const initialValues: FormSchema = {
|
|
||||||
name: metadata?.title,
|
|
||||||
description: metadata?.description,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const onSubmit = async (
|
|
||||||
values: FormSchema,
|
|
||||||
actions: FormikHelpers<FormSchema>
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { name, description } = values;
|
|
||||||
await api.metadata.metadataAdd(
|
|
||||||
"publish",
|
|
||||||
props.association.resource,
|
|
||||||
props.association.group,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
props.association.metadata["date-created"],,
|
|
||||||
uxToHex(props.association.metadata.color)
|
|
||||||
);
|
|
||||||
actions.setStatus({ success: null });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
actions.setStatus({ error: e.message });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Formik
|
|
||||||
validationSchema={formSchema}
|
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
<Form style={{ display: "contents" }}>
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
label="Rename"
|
|
||||||
caption="Change the name of this notebook"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
id="description"
|
|
||||||
label="Change description"
|
|
||||||
caption="Change the description of this notebook"
|
|
||||||
/>
|
|
||||||
<ResetOnPropsChange init={initialValues} book={book} />
|
|
||||||
<AsyncButton primary loadingText="Updating.." border>
|
|
||||||
Save
|
|
||||||
</AsyncButton>
|
|
||||||
<FormError message="Failed to update settings" />
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
}
|
|
@ -15,6 +15,7 @@ import { getLatestCommentRevision } from '~/logic/lib/publish';
|
|||||||
import {useCopy} from '~/logic/lib/useCopy';
|
import {useCopy} from '~/logic/lib/useCopy';
|
||||||
import { getPermalinkForGraph} from '~/logic/lib/permalinks';
|
import { getPermalinkForGraph} from '~/logic/lib/permalinks';
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
import useMetadataState from '~/logic/state/metadata';
|
||||||
|
import {GraphContentWide} from '../landscape/components/Graph/GraphContentWide';
|
||||||
|
|
||||||
const ClickBox = styled(Box)`
|
const ClickBox = styled(Box)`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -101,20 +102,17 @@ export function CommentItem(props: CommentItemProps): ReactElement {
|
|||||||
</Row>
|
</Row>
|
||||||
</Author>
|
</Author>
|
||||||
</Row>
|
</Row>
|
||||||
<Box
|
<GraphContentWide
|
||||||
borderRadius="1"
|
borderRadius="1"
|
||||||
p="1"
|
p="1"
|
||||||
mb="1"
|
mb="1"
|
||||||
backgroundColor={props.highlighted ? 'washedBlue' : 'white'}
|
backgroundColor={props.highlighted ? 'washedBlue' : 'white'}
|
||||||
>
|
|
||||||
<MentionText
|
|
||||||
transcluded={0}
|
transcluded={0}
|
||||||
api={api}
|
api={api}
|
||||||
group={group}
|
post={post}
|
||||||
content={post?.contents}
|
showOurContact
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useEffect, useState, useLayoutEffect, ReactElement } from 'react';
|
import React, { useEffect, useState, useLayoutEffect, ReactElement } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Box, Text, Row, Col } from '@tlon/indigo-react';
|
import { Box, Text, Row, Col } from '@tlon/indigo-react';
|
||||||
import { Associations, Groups } from '@urbit/api';
|
import { Associations, Groups } from '@urbit/api';
|
||||||
|
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
||||||
import { JoinGroup } from '../landscape/components/JoinGroup';
|
import { JoinGroup } from '../landscape/components/JoinGroup';
|
||||||
@ -23,27 +22,12 @@ export function GroupLink(
|
|||||||
const name = resource.slice(6);
|
const name = resource.slice(6);
|
||||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||||
const associations = useMetadataState(state => state.associations);
|
const associations = useMetadataState(state => state.associations);
|
||||||
|
const { save, restore } = useVirtual();
|
||||||
|
const history = useHistory();
|
||||||
const joined = resource in associations.groups;
|
const joined = resource in associations.groups;
|
||||||
|
|
||||||
const { save, restore } = useVirtual();
|
|
||||||
|
|
||||||
const { modal, showModal } = useModal({
|
const { modal, showModal } = useModal({
|
||||||
modal:
|
modal: <JoinGroup api={api} autojoin={name} />
|
||||||
joined && preview ? (
|
|
||||||
<Box width="fit-content" p="4">
|
|
||||||
<GroupSummary
|
|
||||||
metadata={preview.metadata}
|
|
||||||
memberCount={preview.members}
|
|
||||||
channelCount={preview?.['channel-count']}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<JoinGroup
|
|
||||||
api={api}
|
|
||||||
autojoin={name}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -72,7 +56,9 @@ export function GroupLink(
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
py="2"
|
py="2"
|
||||||
pr="2"
|
pr="2"
|
||||||
onClick={showModal}
|
onClick={
|
||||||
|
joined ? () => history.push(`/~landscape/ship/${name}`) : showModal
|
||||||
|
}
|
||||||
cursor='pointer'
|
cursor='pointer'
|
||||||
opacity={preview ? '1' : '0.6'}
|
opacity={preview ? '1' : '0.6'}
|
||||||
>
|
>
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MetadataUpdatePreview,
|
MetadataUpdatePreview,
|
||||||
Contacts,
|
Contacts,
|
||||||
JoinRequests,
|
JoinRequests,
|
||||||
Groups,
|
Groups,
|
||||||
Associations
|
Associations,
|
||||||
} from '@urbit/api';
|
} from "@urbit/api";
|
||||||
import { Invite } from '@urbit/api/invite';
|
import { Invite } from "@urbit/api/invite";
|
||||||
import { Text, Icon, Row } from '@tlon/indigo-react';
|
import { Text, Icon, Row } from "@tlon/indigo-react";
|
||||||
|
|
||||||
import { cite, useShowNickname } from '~/logic/lib/util';
|
import { cite, useShowNickname } from "~/logic/lib/util";
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from "~/logic/api/global";
|
||||||
import { resourceFromPath } from '~/logic/lib/group';
|
import { resourceFromPath } from "~/logic/lib/group";
|
||||||
import { GroupInvite } from './Group';
|
import { GroupInvite } from "./Group";
|
||||||
import { InviteSkeleton } from './InviteSkeleton';
|
import { InviteSkeleton } from "./InviteSkeleton";
|
||||||
import { JoinSkeleton } from './JoinSkeleton';
|
import { JoinSkeleton } from "./JoinSkeleton";
|
||||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||||
import useGroupState from '~/logic/state/group';
|
import useGroupState from "~/logic/state/group";
|
||||||
import useContactState from '~/logic/state/contact';
|
import useContactState from "~/logic/state/contact";
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
import useMetadataState from "~/logic/state/metadata";
|
||||||
import useGraphState from '~/logic/state/graph';
|
import useGraphState from "~/logic/state/graph";
|
||||||
|
import { useRunIO } from "~/logic/lib/useRunIO";
|
||||||
|
|
||||||
interface InviteItemProps {
|
interface InviteItemProps {
|
||||||
invite?: Invite;
|
invite?: Invite;
|
||||||
@ -32,62 +33,81 @@ interface InviteItemProps {
|
|||||||
api: GlobalApi;
|
api: GlobalApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InviteItem(props: InviteItemProps) {
|
export function useInviteAccept(
|
||||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
resource: string,
|
||||||
const { pendingJoin, invite, resource, uid, app, api } = props;
|
api: GlobalApi,
|
||||||
|
app?: string,
|
||||||
|
uid?: string,
|
||||||
|
invite?: Invite,
|
||||||
|
) {
|
||||||
const { ship, name } = resourceFromPath(resource);
|
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 contact = contacts?.[`~${invite?.ship}`] ?? {};
|
|
||||||
const showNickname = useShowNickname(contact);
|
|
||||||
const waiter = useWaitForProps(
|
|
||||||
{ associations, groups, pendingJoin, graphKeys: Array.from(graphKeys) },
|
|
||||||
50000
|
|
||||||
);
|
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const inviteAccept = useCallback(async () => {
|
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)) {
|
if (!(app && invite && uid)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if(resource in groups) {
|
if (resource in groups) {
|
||||||
await api.invite.decline(app, uid);
|
await api.invite.decline(app, uid);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
api.groups.join(ship, name);
|
await api.groups.join(ship, name);
|
||||||
await waiter(p => !!p.pendingJoin);
|
await api.invite.accept(app, uid);
|
||||||
|
|
||||||
api.invite.accept(app, uid);
|
|
||||||
await waiter((p) => {
|
await waiter((p) => {
|
||||||
return (
|
return (
|
||||||
resource in p.groups &&
|
(resource in p.groups &&
|
||||||
(resource in (p.associations?.graph ?? {}) ||
|
resource in (p.associations?.graph ?? {}) &&
|
||||||
resource in (p.associations?.groups ?? {}))
|
p.graphKeys.has(resource.slice(7))) ||
|
||||||
|
resource in (p.associations?.groups ?? {})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
(success: boolean) => {
|
||||||
|
if (!success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (groups?.[resource]?.hidden) {
|
if (groups?.[resource]?.hidden) {
|
||||||
await waiter(p => p.graphKeys.includes(resource.slice(7)));
|
|
||||||
const { metadata } = associations.graph[resource];
|
const { metadata } = associations.graph[resource];
|
||||||
if (metadata && 'graph' in metadata.config) {
|
if (metadata && "graph" in metadata.config) {
|
||||||
if (metadata.config.graph === 'chat') {
|
if (metadata.config.graph === "chat") {
|
||||||
history.push(`/~landscape/messages/resource/${metadata.config.graph}${resource}`);
|
history.push(
|
||||||
|
`/~landscape/messages/resource/${metadata.config.graph}${resource}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
history.push(`/~landscape/home/resource/${metadata.config.graph}${resource}`);
|
history.push(
|
||||||
|
`/~landscape/home/resource/${metadata.config.graph}${resource}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('unknown metadata: ', metadata);
|
console.error("unknown metadata: ", metadata);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
history.push(`/~landscape${resource}`);
|
history.push(`/~landscape${resource}`);
|
||||||
}
|
}
|
||||||
}, [app, history, waiter, invite, uid, resource, groups, associations]);
|
},
|
||||||
|
resource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InviteItem(props: InviteItemProps) {
|
||||||
|
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||||
|
const { pendingJoin, invite, resource, uid, app, api } = props;
|
||||||
|
const { name } = resourceFromPath(resource);
|
||||||
|
const contacts = useContactState((state) => state.contacts);
|
||||||
|
const contact = contacts?.[`~${invite?.ship}`] ?? {};
|
||||||
|
const showNickname = useShowNickname(contact);
|
||||||
|
|
||||||
|
const inviteAccept = useInviteAccept(resource, api, app, uid, invite);
|
||||||
|
|
||||||
const inviteDecline = useCallback(async () => {
|
const inviteDecline = useCallback(async () => {
|
||||||
if(!(app && uid)) {
|
if (!(app && uid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await api.invite.decline(app, uid);
|
await api.invite.decline(app, uid);
|
||||||
@ -96,7 +116,7 @@ export function InviteItem(props: InviteItemProps) {
|
|||||||
const handlers = { onAccept: inviteAccept, onDecline: inviteDecline };
|
const handlers = { onAccept: inviteAccept, onDecline: inviteDecline };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!app || app === 'groups') {
|
if (!app || app === "groups") {
|
||||||
(async () => {
|
(async () => {
|
||||||
setPreview(await api.metadata.preview(resource));
|
setPreview(await api.metadata.preview(resource));
|
||||||
})();
|
})();
|
||||||
@ -108,7 +128,7 @@ export function InviteItem(props: InviteItemProps) {
|
|||||||
}
|
}
|
||||||
}, [invite]);
|
}, [invite]);
|
||||||
|
|
||||||
if(pendingJoin?.hidden) {
|
if (pendingJoin?.hidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +143,7 @@ export function InviteItem(props: InviteItemProps) {
|
|||||||
{...handlers}
|
{...handlers}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (invite && name.startsWith('dm--')) {
|
} else if (invite && name.startsWith("dm--")) {
|
||||||
return (
|
return (
|
||||||
<InviteSkeleton
|
<InviteSkeleton
|
||||||
gapY="3"
|
gapY="3"
|
||||||
@ -133,16 +153,18 @@ export function InviteItem(props: InviteItemProps) {
|
|||||||
>
|
>
|
||||||
<Row py="1" alignItems="center">
|
<Row py="1" alignItems="center">
|
||||||
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
||||||
<Text mr="1"
|
<Text
|
||||||
|
mr="1"
|
||||||
mono={!showNickname}
|
mono={!showNickname}
|
||||||
fontWeight={showNickname ? '500' : '400'}>
|
fontWeight={showNickname ? "500" : "400"}
|
||||||
|
>
|
||||||
{showNickname ? contact?.nickname : cite(`~${invite!.ship}`)}
|
{showNickname ? contact?.nickname : cite(`~${invite!.ship}`)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text mr="1">invited you to a DM</Text>
|
<Text mr="1">invited you to a DM</Text>
|
||||||
</Row>
|
</Row>
|
||||||
</InviteSkeleton>
|
</InviteSkeleton>
|
||||||
);
|
);
|
||||||
} else if (status && name.startsWith('dm--')) {
|
} else if (status && name.startsWith("dm--")) {
|
||||||
return (
|
return (
|
||||||
<JoinSkeleton api={api} resource={resource} status={status} gapY="3">
|
<JoinSkeleton api={api} resource={resource} status={status} gapY="3">
|
||||||
<Row py="1" alignItems="center">
|
<Row py="1" alignItems="center">
|
||||||
@ -162,9 +184,11 @@ export function InviteItem(props: InviteItemProps) {
|
|||||||
>
|
>
|
||||||
<Row py="1" alignItems="center">
|
<Row py="1" alignItems="center">
|
||||||
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
||||||
<Text mr="1"
|
<Text
|
||||||
|
mr="1"
|
||||||
mono={!showNickname}
|
mono={!showNickname}
|
||||||
fontWeight={showNickname ? '500' : '400'}>
|
fontWeight={showNickname ? "500" : "400"}
|
||||||
|
>
|
||||||
{showNickname ? contact?.nickname : cite(`~${invite!.ship}`)}
|
{showNickname ? contact?.nickname : cite(`~${invite!.ship}`)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text mr="1">
|
<Text mr="1">
|
||||||
@ -174,14 +198,12 @@ export function InviteItem(props: InviteItemProps) {
|
|||||||
</InviteSkeleton>
|
</InviteSkeleton>
|
||||||
);
|
);
|
||||||
} else if (pendingJoin) {
|
} else if (pendingJoin) {
|
||||||
const [, , ship, name] = resource.split('/');
|
const [, , ship, name] = resource.split("/");
|
||||||
return (
|
return (
|
||||||
<JoinSkeleton api={api} resource={resource} status={pendingJoin}>
|
<JoinSkeleton api={api} resource={resource} status={pendingJoin}>
|
||||||
<Row py="1" alignItems="center">
|
<Row py="1" alignItems="center">
|
||||||
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
<Icon display="block" color="blue" icon="Bullet" mr="2" />
|
||||||
<Text mr="1">
|
<Text mr="1">You are joining</Text>
|
||||||
You are joining
|
|
||||||
</Text>
|
|
||||||
<Text mono>
|
<Text mono>
|
||||||
{cite(ship)}/{name}
|
{cite(ship)}/{name}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -6,7 +6,7 @@ import RichText from '~/views/components/RichText';
|
|||||||
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
|
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
|
||||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import useContactState from '~/logic/state/contact';
|
import useContactState, {useContact} from '~/logic/state/contact';
|
||||||
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
import {referenceToPermalink} from '~/logic/lib/permalinks';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
|
|
||||||
@ -40,35 +40,18 @@ export function MentionText(props: MentionTextProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Mention(props: {
|
export function Mention(props: {
|
||||||
contact: Contact;
|
|
||||||
group: Group;
|
|
||||||
scrollWindow?: HTMLElement;
|
|
||||||
ship: string;
|
ship: string;
|
||||||
first?: Boolean;
|
first?: Boolean;
|
||||||
api: any;
|
api: any;
|
||||||
}) {
|
}) {
|
||||||
const { ship, scrollWindow, first, api, ...rest } = props;
|
const { ship, first, api, ...rest } = props;
|
||||||
let { contact } = props;
|
const contact = useContact(ship);
|
||||||
const contacts = useContactState(state => state.contacts);
|
|
||||||
contact = contact?.color ? contact : contacts?.[`~${ship}`];
|
|
||||||
const history = useHistory();
|
|
||||||
const showNickname = useShowNickname(contact);
|
const showNickname = useShowNickname(contact);
|
||||||
const name = showNickname ? contact?.nickname : cite(ship);
|
const name = showNickname ? contact?.nickname : cite(ship);
|
||||||
const group = props.group ?? { hidden: true };
|
|
||||||
const [showOverlay, setShowOverlay] = useState(false);
|
|
||||||
|
|
||||||
const toggleOverlay = useCallback(() => {
|
|
||||||
setShowOverlay((value) => !value);
|
|
||||||
}, [showOverlay]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position='relative' display='inline-block' cursor='pointer' {...rest}>
|
<ProfileOverlay ship={ship} api={api}>
|
||||||
<ProfileOverlay
|
|
||||||
ship={ship}
|
|
||||||
api={api}
|
|
||||||
>
|
|
||||||
<Text
|
<Text
|
||||||
onClick={() => toggleOverlay()}
|
|
||||||
marginLeft={first? 0 : 1}
|
marginLeft={first? 0 : 1}
|
||||||
marginRight={1}
|
marginRight={1}
|
||||||
px={1}
|
px={1}
|
||||||
@ -80,6 +63,5 @@ export function Mention(props: {
|
|||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
</ProfileOverlay>
|
</ProfileOverlay>
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { PureComponent, useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
import React, { PureComponent, useCallback, useEffect, useRef, useState, useMemo, ReactNode } from 'react';
|
||||||
import { Contact, Group, uxToHex } from '@urbit/api';
|
import { Contact, Group, uxToHex } from '@urbit/api';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import VisibilitySensor from 'react-visibility-sensor';
|
import VisibilitySensor from 'react-visibility-sensor';
|
||||||
@ -40,6 +40,7 @@ const FixedOverlay = styled(Col)`
|
|||||||
type ProfileOverlayProps = BoxProps & {
|
type ProfileOverlayProps = BoxProps & {
|
||||||
ship: string;
|
ship: string;
|
||||||
api: any;
|
api: any;
|
||||||
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProfileOverlay = (props: ProfileOverlayProps) => {
|
const ProfileOverlay = (props: ProfileOverlayProps) => {
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { 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";
|
||||||
|
|
||||||
|
function GraphContentWideInner(
|
||||||
|
props: {
|
||||||
|
transcluded?: number;
|
||||||
|
post: Post;
|
||||||
|
api: GlobalApi;
|
||||||
|
showOurContact: boolean;
|
||||||
|
} & PropFunc<typeof Box>
|
||||||
|
) {
|
||||||
|
const { post, transcluded = 0, showOurContact, api, ...rest } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box {...rest}>
|
||||||
|
{post.contents.map((content, i) => {
|
||||||
|
switch (Object.keys(content)[0]) {
|
||||||
|
case "text":
|
||||||
|
return (
|
||||||
|
<TextContent
|
||||||
|
key={i}
|
||||||
|
api={api}
|
||||||
|
fontSize={1}
|
||||||
|
lineHeight={"20px"}
|
||||||
|
content={content}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "code":
|
||||||
|
return <CodeContent key={i} content={content} />;
|
||||||
|
case "reference":
|
||||||
|
const { link } = referenceToPermalink(content as ReferenceContent);
|
||||||
|
return (
|
||||||
|
<PermalinkEmbed
|
||||||
|
link={link}
|
||||||
|
api={api}
|
||||||
|
transcluded={transcluded}
|
||||||
|
showOurContact={showOurContact}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "url":
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={i}
|
||||||
|
flexShrink={0}
|
||||||
|
fontSize={1}
|
||||||
|
lineHeight="20px"
|
||||||
|
color="black"
|
||||||
|
width="fit-content"
|
||||||
|
maxWidth="min(500px, 100%)"
|
||||||
|
>
|
||||||
|
<RemoteContent key={content.url} url={content.url} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
case "mention":
|
||||||
|
const first = (i) => i === 0;
|
||||||
|
return (
|
||||||
|
<Mention
|
||||||
|
key={i}
|
||||||
|
first={first(i)}
|
||||||
|
ship={content.mention}
|
||||||
|
api={api}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GraphContentWide = React.memo(GraphContentWideInner);
|
@ -23,7 +23,7 @@ export function GroupSummary(props: GroupSummaryProps & PropFunc<typeof Col>): R
|
|||||||
anchorRef
|
anchorRef
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Col {...rest} ref={anchorRef} gapY="4">
|
<Col {...rest} ref={anchorRef} gapY="4" maxWidth={['100%', '288px']}>
|
||||||
<Row gapX="2" width="100%">
|
<Row gapX="2" width="100%">
|
||||||
<MetadataIcon
|
<MetadataIcon
|
||||||
width="40px"
|
width="40px"
|
||||||
|
@ -1,30 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Col } from '@tlon/indigo-react';
|
import { Col, Box } from '@tlon/indigo-react';
|
||||||
import { MentionText } from '~/views/components/MentionText';
|
import { GraphContentWide } from "~/views/landscape/components/Graph/GraphContentWide";
|
||||||
import useContactState from '~/logic/state/contact';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const TruncatedBox = styled(Col)`
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: ${p => p.truncate ?? 'unset'};
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
export function PostContent(props) {
|
export function PostContent(props) {
|
||||||
const { post, isParent, api, isReply } = props;
|
const { post, isParent, api, isReply } = props;
|
||||||
const contacts = useContactState(state => state.contacts);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col
|
<TruncatedBox
|
||||||
|
display="-webkit-box"
|
||||||
width="100%"
|
width="100%"
|
||||||
pl="2"
|
px="2"
|
||||||
pr="2"
|
pb="2"
|
||||||
pb={isParent || isReply ? "0" : "2"}
|
truncate={isParent ? null : 8}
|
||||||
maxHeight={ isParent ? "none" : "300px" }
|
|
||||||
textOverflow="ellipsis"
|
textOverflow="ellipsis"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
display="inline-block">
|
>
|
||||||
<MentionText
|
<GraphContentWide
|
||||||
contacts={contacts}
|
|
||||||
content={post.contents}
|
|
||||||
api={api}
|
|
||||||
transcluded={0}
|
transcluded={0}
|
||||||
|
post={post}
|
||||||
|
api={api}
|
||||||
|
showOurContact
|
||||||
/>
|
/>
|
||||||
</Col>
|
</TruncatedBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +151,7 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
|||||||
</StatelessAsyncButton>
|
</StatelessAsyncButton>
|
||||||
</Col>
|
</Col>
|
||||||
) : preview ? (
|
) : preview ? (
|
||||||
|
<>
|
||||||
<GroupSummary
|
<GroupSummary
|
||||||
metadata={preview.metadata}
|
metadata={preview.metadata}
|
||||||
memberCount={preview?.members}
|
memberCount={preview?.members}
|
||||||
@ -184,14 +185,16 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
|||||||
</Box>
|
</Box>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
</GroupSummary>
|
||||||
<StatelessAsyncButton
|
<StatelessAsyncButton
|
||||||
|
marginTop={3}
|
||||||
primary
|
primary
|
||||||
name="join"
|
name="join"
|
||||||
onClick={() => onConfirm(preview.group)}
|
onClick={() => onConfirm(preview.group)}
|
||||||
>
|
>
|
||||||
Join {preview.metadata.title}
|
Join {preview.metadata.title}
|
||||||
</StatelessAsyncButton>
|
</StatelessAsyncButton>
|
||||||
</GroupSummary>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Col width="100%" gapY="4">
|
<Col width="100%" gapY="4">
|
||||||
<Formik
|
<Formik
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { BigInteger } from "big-integer";
|
import { BigInteger } from "big-integer";
|
||||||
|
import { immerable } from 'immer';
|
||||||
|
|
||||||
interface NonemptyNode<V> {
|
interface NonemptyNode<V> {
|
||||||
n: [BigInteger, V];
|
n: [BigInteger, V];
|
||||||
@ -14,6 +15,7 @@ type MapNode<V> = NonemptyNode<V> | null;
|
|||||||
*/
|
*/
|
||||||
export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
export default class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||||
private root: MapNode<V> = null;
|
private root: MapNode<V> = null;
|
||||||
|
[immerable] = true;
|
||||||
size: number = 0;
|
size: number = 0;
|
||||||
|
|
||||||
constructor(initial: [BigInteger, V][] = []) {
|
constructor(initial: [BigInteger, V][] = []) {
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@urbit/eslint-config": "^1.0.0",
|
"@urbit/eslint-config": "^1.0.0",
|
||||||
"big-integer": "^1.6.48",
|
"big-integer": "^1.6.48",
|
||||||
|
"immer": "^9.0.1",
|
||||||
"lodash": "^4.17.20"
|
"lodash": "^4.17.20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user