mirror of
https://github.com/urbit/shrub.git
synced 2025-01-03 10:02:32 +03:00
Merge branch 'release/next-userspace' into lf/keybinds
This commit is contained in:
commit
62ba7462d2
2
.github/workflows/glob.yml
vendored
2
.github/workflows/glob.yml
vendored
@ -2,7 +2,7 @@ name: glob
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'release/next-js'
|
||||
- 'release/next-userspace'
|
||||
jobs:
|
||||
glob:
|
||||
runs-on: ubuntu-latest
|
||||
|
4
.github/workflows/merge-master.yml
vendored
4
.github/workflows/merge-master.yml
vendored
@ -6,13 +6,13 @@ on:
|
||||
jobs:
|
||||
merge-to-next-js:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Merge master to release/next-js"
|
||||
name: "Merge master to release/next-userspace"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: devmasx/merge-branch@v1.3.1
|
||||
with:
|
||||
type: now
|
||||
target_branch: release/next-js
|
||||
target_branch: release/next-userspace
|
||||
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}
|
||||
|
||||
merge-to-group-timer:
|
||||
|
@ -309,9 +309,9 @@ the new binary, and restarting the pier with it.
|
||||
#### Continuous deployment
|
||||
|
||||
A subset of release branches are deployed continuously to the network. Thus far
|
||||
this only includes `release/next-js`, which deploys livenet-compatible
|
||||
JavaScript changes to select QA ships. Any push to master will automatically
|
||||
merge master into `release/next-js` to keep the streams at parity.
|
||||
this only includes `release/next-userspace`, which deploys livenet-compatible
|
||||
changes to select QA ships. Any push to master will automatically
|
||||
merge master into `release/next-userspace` to keep the streams at parity.
|
||||
|
||||
### Announce the update
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:90dd5f5b2821f1a057c053b141e1143f019193c8dd7da41b39b0a3799e0fda5a
|
||||
size 10999106
|
||||
oid sha256:d7b7cf24e56ab078cf1dcb82e4e7744f188c5221c08772d6cfb15f59ce81aaa5
|
||||
size 11198219
|
||||
|
@ -1394,8 +1394,6 @@
|
||||
^+ this
|
||||
?: =(~ dom)
|
||||
~|(%acme-empty-certificate-order !!)
|
||||
?: ?=(?(%earl %pawn) (clan:title our.bow))
|
||||
this
|
||||
=. ..emit (queue-next-order 1 | dom)
|
||||
=. ..emit cancel-current-order
|
||||
:: notify %dill
|
||||
|
@ -738,7 +738,8 @@
|
||||
::
|
||||
?. (is-chat-graph target)
|
||||
[[(note:sh-out "no such chat")]~ put-ses]
|
||||
=. viewing (~(put in viewing) target)
|
||||
=. audience target
|
||||
=. viewing (~(put in viewing) target)
|
||||
=^ cards state
|
||||
?: (~(has by bound) target)
|
||||
[~ state]
|
||||
|
@ -70,10 +70,11 @@
|
||||
::
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
^- (quip card (unit vase))
|
||||
:: TODO: should check if user is allowed to %add, %remove, %edit
|
||||
:: contact
|
||||
=/ =update:store !<(update:store vas)
|
||||
:- ~
|
||||
?- -.update
|
||||
%initial ~
|
||||
%add `vas
|
||||
|
@ -43,8 +43,8 @@
|
||||
::
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
`vas
|
||||
^- (quip card (unit vase))
|
||||
``vas
|
||||
::
|
||||
++ resource-for-update
|
||||
|= =vase
|
||||
|
@ -188,8 +188,11 @@
|
||||
?: ?=([%'~landscape' %js %session ~] site.req-line)
|
||||
%+ require-authorization-simple:app
|
||||
inbound-request
|
||||
%- js-response:gen
|
||||
(as-octt:mimes:html "window.ship = '{+:(scow %p our.bowl)}';")
|
||||
%. %- as-octs:mimes:html
|
||||
(rap 3 'window.ship = "' (rsh 3 (scot %p our.bowl)) '";' ~)
|
||||
%* . js-response:gen
|
||||
cache %.n
|
||||
==
|
||||
::
|
||||
=/ [payload=simple-payload:http public=?] (get-file req-line is-file)
|
||||
?: public payload
|
||||
@ -222,6 +225,7 @@
|
||||
[~ %js] (js-response:gen file)
|
||||
[~ %css] (css-response:gen file)
|
||||
[~ %png] (png-response:gen file)
|
||||
[~ %ico] (ico-response:gen file)
|
||||
::
|
||||
[~ %html]
|
||||
%. file
|
||||
@ -238,11 +242,9 @@
|
||||
[not-found:gen %.n]
|
||||
:_ public.u.content
|
||||
=/ mime-type=@t (rsh 3 (crip <p.u.data>))
|
||||
:: Should maybe inspect to see how long cache should hold
|
||||
::
|
||||
=/ headers
|
||||
:~ content-type+mime-type
|
||||
max-1-da:gen
|
||||
max-1-wk:gen
|
||||
'service-worker-allowed'^'/'
|
||||
==
|
||||
[[200 headers] `q.u.data]
|
||||
|
@ -5,7 +5,7 @@
|
||||
/- glob
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ hash 0v7.k043v.fjsi2.bpm4g.0ekbj.566c4 :: DO NOT MOVE FROM LINE 8
|
||||
++ hash 0v2.rvlfs.f97fq.hjrpe.d3h68.n54sj
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ all-states
|
||||
$% state-0
|
||||
|
@ -1,6 +1,6 @@
|
||||
/- *group, metadata=metadata-store
|
||||
/+ store=graph-store, mdl=metadata, res=resource, graph, group, default-agent,
|
||||
dbug, verb, push-hook
|
||||
dbug, verb, push-hook, agentio
|
||||
::
|
||||
~% %graph-push-hook-top ..part ~
|
||||
|%
|
||||
@ -25,6 +25,25 @@
|
||||
$% state-zero
|
||||
state-one
|
||||
==
|
||||
::
|
||||
:: TODO: come back to this and potentially use send a %t
|
||||
:: to be notified of validator changes
|
||||
+$ cache
|
||||
$: graph-to-mark=(map resource:res (unit mark))
|
||||
perm-marks=(map [mark @tas] tube:clay)
|
||||
transform-marks=(map mark tube:clay)
|
||||
==
|
||||
::
|
||||
+$ inflated-state
|
||||
$: state-one
|
||||
cache
|
||||
==
|
||||
::
|
||||
+$ cache-action
|
||||
$% [%graph-to-mark (pair resource:res (unit mark))]
|
||||
[%perm-marks (pair (pair mark @tas) tube:clay)]
|
||||
[%transform-marks (pair mark tube:clay)]
|
||||
==
|
||||
--
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -33,7 +52,8 @@
|
||||
%- (agent:push-hook config)
|
||||
^- agent
|
||||
=-
|
||||
=| state-one
|
||||
~% %graph-push-hook-agent ..scry.hook-core ~
|
||||
=| inflated-state
|
||||
=* state -
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
@ -41,10 +61,11 @@
|
||||
grp ~(. group bowl)
|
||||
gra ~(. graph bowl)
|
||||
met ~(. mdl bowl)
|
||||
hc ~(. hook-core bowl)
|
||||
hc ~(. hook-core bowl +.state)
|
||||
io ~(. agentio bowl)
|
||||
::
|
||||
++ on-init on-init:def
|
||||
++ on-save !>(state)
|
||||
++ on-save !>(-.state)
|
||||
++ on-load
|
||||
|= =vase
|
||||
=+ !<(old=versioned-state vase)
|
||||
@ -53,9 +74,26 @@
|
||||
=? old ?=(%0 -.old)
|
||||
[%1 ~]
|
||||
?> ?=(%1 -.old)
|
||||
`this(state old)
|
||||
`this(-.state old, +.state *cache)
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?. =(mark %graph-cache-hook)
|
||||
[~ this]
|
||||
=/ a=cache-action !<(cache-action vase)
|
||||
=* c +.state
|
||||
=* graph-to-mark graph-to-mark.c
|
||||
=* perm-marks perm-marks.c
|
||||
=* transform-marks transform-marks.c
|
||||
=. c
|
||||
?- -.a
|
||||
%graph-to-mark c(graph-to-mark (~(put by graph-to-mark) p.a q.a))
|
||||
%perm-marks c(perm-marks (~(put by perm-marks) p.a q.a))
|
||||
%transform-marks c(transform-marks (~(put by transform-marks) p.a q.a))
|
||||
==
|
||||
[~ this(+.state c)]
|
||||
::
|
||||
++ on-poke on-poke:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
@ -72,32 +110,64 @@
|
||||
::
|
||||
++ on-fail on-fail:def
|
||||
++ transform-proxy-update
|
||||
~/ %transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
^- (quip card (unit vase))
|
||||
=/ =update:store !<(update:store vas)
|
||||
=* rid resource.q.update
|
||||
=. p.update now.bowl
|
||||
?- -.q.update
|
||||
%add-nodes
|
||||
?. (is-allowed-add:hc rid nodes.q.update)
|
||||
~
|
||||
=/ mark (get-mark:gra rid)
|
||||
?~ mark `vas
|
||||
|^
|
||||
=/ transform
|
||||
!< $-([index:store post:store atom ?] [index:store post:store])
|
||||
%. !>(*indexed-post:store)
|
||||
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes))
|
||||
=/ [* result=(list [index:store node:store])]
|
||||
%+ roll
|
||||
(flatten-node-map ~(tap by nodes.q.update))
|
||||
(transform-list transform)
|
||||
=. nodes.q.update
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
result
|
||||
[~ !>(update)]
|
||||
=| cards=(list card)
|
||||
=^ allowed cards (is-allowed-add:hc rid nodes.q.update)
|
||||
?. allowed
|
||||
[cards ~]
|
||||
=/ mark-cached (~(has by graph-to-mark) rid)
|
||||
=/ mark
|
||||
?: mark-cached
|
||||
(~(got by graph-to-mark) rid)
|
||||
(get-mark:gra rid)
|
||||
?~ mark
|
||||
[cards `vas]
|
||||
=< $
|
||||
~% %transform-add-nodes ..transform-proxy-update ~
|
||||
|%
|
||||
++ $
|
||||
^- (quip card (unit vase))
|
||||
=/ transform-cached (~(has by transform-marks) u.mark)
|
||||
=/ =tube:clay
|
||||
?: transform-cached
|
||||
(~(got by transform-marks) u.mark)
|
||||
.^(tube:clay (scry:hc %cc %home /[u.mark]/transform-add-nodes))
|
||||
=/ transform
|
||||
!< $-([index:store post:store atom ?] [index:store post:store])
|
||||
%. !>(*indexed-post:store)
|
||||
tube
|
||||
=/ [* result=(list [index:store node:store])]
|
||||
%+ roll
|
||||
(flatten-node-map ~(tap by nodes.q.update))
|
||||
(transform-list transform)
|
||||
=. nodes.q.update
|
||||
%- ~(gas by *(map index:store node:store))
|
||||
result
|
||||
:_ [~ !>(update)]
|
||||
%+ weld cards
|
||||
%- zing
|
||||
:~ ?: mark-cached ~
|
||||
:_ ~
|
||||
%+ poke-self:pass:io %graph-cache-hook
|
||||
!> ^- cache-action
|
||||
[%graph-to-mark rid mark]
|
||||
::
|
||||
?: transform-cached ~
|
||||
:_ ~
|
||||
%+ poke-self:pass:io %graph-cache-hook
|
||||
!> ^- cache-action
|
||||
[%transform-marks u.mark tube]
|
||||
==
|
||||
::
|
||||
++ flatten-node-map
|
||||
~/ %flatten-node-map
|
||||
|= lis=(list [index:store node:store])
|
||||
^- (list [index:store node:store])
|
||||
|^
|
||||
@ -129,6 +199,7 @@
|
||||
--
|
||||
::
|
||||
++ transform-list
|
||||
~/ %transform-list
|
||||
|= transform=$-([index:store post:store atom ?] [index:store post:store])
|
||||
|= $: [=index:store =node:store]
|
||||
[indices=(set index:store) lis=(list [index:store node:store])]
|
||||
@ -151,27 +222,32 @@
|
||||
--
|
||||
::
|
||||
%remove-posts
|
||||
?. (is-allowed-remove:hc resource.q.update indices.q.update)
|
||||
=| cards=(list card)
|
||||
=^ allowed cards
|
||||
(is-allowed-remove:hc rid indices.q.update)
|
||||
:- cards
|
||||
?. allowed
|
||||
~
|
||||
`vas
|
||||
::
|
||||
%add-graph ~
|
||||
%remove-graph ~
|
||||
%add-signatures ~
|
||||
%remove-signatures ~
|
||||
%archive-graph ~
|
||||
%unarchive-graph ~
|
||||
%add-tag ~
|
||||
%remove-tag ~
|
||||
%keys ~
|
||||
%tags ~
|
||||
%tag-queries ~
|
||||
%run-updates ~
|
||||
%add-graph [~ ~]
|
||||
%remove-graph [~ ~]
|
||||
%add-signatures [~ ~]
|
||||
%remove-signatures [~ ~]
|
||||
%archive-graph [~ ~]
|
||||
%unarchive-graph [~ ~]
|
||||
%add-tag [~ ~]
|
||||
%remove-tag [~ ~]
|
||||
%keys [~ ~]
|
||||
%tags [~ ~]
|
||||
%tag-queries [~ ~]
|
||||
%run-updates [~ ~]
|
||||
==
|
||||
::
|
||||
++ resource-for-update resource-for-update:gra
|
||||
::
|
||||
++ initial-watch
|
||||
~/ %initial-watch
|
||||
|= [=path =resource:res]
|
||||
^- vase
|
||||
|^
|
||||
@ -211,11 +287,13 @@
|
||||
==
|
||||
--
|
||||
::
|
||||
^| ^= hook-core
|
||||
|_ =bowl:gall
|
||||
~% %graph-push-hook-helper ..card.hook-core ~
|
||||
^= hook-core
|
||||
|_ [=bowl:gall =cache]
|
||||
+* grp ~(. group bowl)
|
||||
met ~(. mdl bowl)
|
||||
gra ~(. graph bowl)
|
||||
io ~(. agentio bowl)
|
||||
::
|
||||
++ scry
|
||||
|= [care=@t desk=@t =path]
|
||||
@ -225,14 +303,38 @@
|
||||
::
|
||||
++ perm-mark
|
||||
|= [=resource:res perm=@t vip=vip-metadata:metadata =indexed-post:store]
|
||||
^- permissions:store
|
||||
^- [permissions:store (list card)]
|
||||
|^
|
||||
=- (check vip)
|
||||
!< check=$-(vip-metadata:metadata permissions:store)
|
||||
%. !>(indexed-post)
|
||||
=/ mark (get-mark:gra resource)
|
||||
?~ mark |=(=vase !>([%no %no %no]))
|
||||
.^(tube:clay (scry %cc %home /[u.mark]/(perm-mark-name perm)))
|
||||
=/ mark-cached (~(has by graph-to-mark.cache) resource)
|
||||
=/ mark
|
||||
?: mark-cached
|
||||
(~(got by graph-to-mark.cache) resource)
|
||||
(get-mark:gra resource)
|
||||
?~ mark
|
||||
[[%no %no %no] ~]
|
||||
=/ key [u.mark (perm-mark-name perm)]
|
||||
=/ perms-cached (~(has by perm-marks.cache) key)
|
||||
=/ =tube:clay
|
||||
?: perms-cached
|
||||
(~(got by perm-marks.cache) key)
|
||||
.^(tube:clay (scry %cc %home /[u.mark]/(perm-mark-name perm)))
|
||||
=/ check
|
||||
!< $-(vip-metadata:metadata permissions:store)
|
||||
(tube !>(indexed-post))
|
||||
:- (check vip)
|
||||
%- zing
|
||||
:~ ?: mark-cached ~
|
||||
:_ ~
|
||||
%+ poke-self:pass:io %graph-cache-hook
|
||||
!> ^- cache-action
|
||||
[%graph-to-mark resource mark]
|
||||
::
|
||||
?: perms-cached ~
|
||||
:_ ~
|
||||
%+ poke-self:pass:io %graph-cache-hook
|
||||
!> ^- cache-action
|
||||
[%perm-marks [u.mark (perm-mark-name perm)] tube]
|
||||
==
|
||||
::
|
||||
++ perm-mark-name
|
||||
|= perm=@t
|
||||
@ -252,15 +354,22 @@
|
||||
reader.permissions
|
||||
::
|
||||
++ get-roles-writers-variation
|
||||
~/ %get-roles-writers-variation
|
||||
|= =resource:res
|
||||
^- (unit [is-admin=? writers=(set ship) vip=vip-metadata:metadata])
|
||||
=/ assoc=(unit association:metadata)
|
||||
(peek-association:met %graph resource)
|
||||
(peek-association:met %graph resource)
|
||||
?~ assoc ~
|
||||
=/ group=(unit group:grp)
|
||||
(scry-group:grp group.u.assoc)
|
||||
?~ group ~
|
||||
=/ role=(unit (unit role-tag))
|
||||
(role-for-ship:grp group.u.assoc src.bowl)
|
||||
(role-for-ship-with-group:grp u.group group.u.assoc src.bowl)
|
||||
=/ writers=(set ship)
|
||||
(get-tagged-ships:grp group.u.assoc [%graph resource %writers])
|
||||
%^ get-tagged-ships-with-group:grp
|
||||
u.group
|
||||
group.u.assoc
|
||||
[%graph resource %writers]
|
||||
?~ role ~
|
||||
=/ is-admin=?
|
||||
?=(?([~ %admin] [~ %moderator]) u.role)
|
||||
@ -274,39 +383,58 @@
|
||||
[(snag (dec (lent index)) index) p.post.node]
|
||||
::
|
||||
++ is-allowed-add
|
||||
~/ %is-allowed-add
|
||||
|= [=resource:res nodes=(map index:store node:store)]
|
||||
^- ?
|
||||
^- [? (list card)]
|
||||
|^
|
||||
%- (bond |.(%.n))
|
||||
%- (bond |.([%.n ~]))
|
||||
%+ biff (get-roles-writers-variation resource)
|
||||
|= [is-admin=? writers=(set ship) vip=vip-metadata:metadata]
|
||||
^- (unit ?)
|
||||
^- (unit [? (list card)])
|
||||
%- some
|
||||
%+ levy ~(tap by nodes)
|
||||
|= [=index:store =node:store]
|
||||
=/ parent-index=index:store
|
||||
(scag (dec (lent index)) index)
|
||||
?: (~(has by nodes) parent-index) %.y
|
||||
?: ?=(%| -.post.node)
|
||||
%.n
|
||||
?. =(author.p.post.node src.bowl)
|
||||
%.n
|
||||
=/ =permissions:store
|
||||
%^ add-mark resource vip
|
||||
(node-to-indexed-post node)
|
||||
=/ =permission-level:store
|
||||
(get-permission permissions is-admin writers)
|
||||
?- permission-level
|
||||
%yes %.y
|
||||
%no %.n
|
||||
::
|
||||
%self
|
||||
=/ parent-node=node:store
|
||||
(got-node:gra resource parent-index)
|
||||
?: ?=(%| -.post.parent-node)
|
||||
%.n
|
||||
=(author.p.post.parent-node src.bowl)
|
||||
==
|
||||
=/ a ~(tap by nodes)
|
||||
=| cards=(list card)
|
||||
|- ^- [? (list card)]
|
||||
?~ a [& cards]
|
||||
=/ c (check i.a is-admin writers vip)
|
||||
?. -.c
|
||||
[| (weld cards +.c)]
|
||||
$(a t.a, cards (weld cards +.c))
|
||||
::
|
||||
++ check
|
||||
|= $: [=index:store =node:store]
|
||||
is-admin=?
|
||||
writers=(set ship)
|
||||
vip=vip-metadata:metadata
|
||||
==
|
||||
^- [? (list card)]
|
||||
=/ parent-index=index:store
|
||||
(scag (dec (lent index)) index)
|
||||
?: (~(has by nodes) parent-index)
|
||||
[%.y ~]
|
||||
?: ?=(%| -.post.node)
|
||||
[%.n ~]
|
||||
?. =(author.p.post.node src.bowl)
|
||||
[%.n ~]
|
||||
=/ added
|
||||
%^ add-mark resource vip
|
||||
(node-to-indexed-post node)
|
||||
=* permissions -.added
|
||||
=* cards +.added
|
||||
=/ =permission-level:store
|
||||
(get-permission permissions is-admin writers)
|
||||
:_ cards
|
||||
?- permission-level
|
||||
%yes %.y
|
||||
%no %.n
|
||||
::
|
||||
%self
|
||||
=/ parent-node=node:store
|
||||
(got-node:gra resource parent-index)
|
||||
?: ?=(%| -.post.parent-node)
|
||||
%.n
|
||||
=(author.p.post.parent-node src.bowl)
|
||||
==
|
||||
::
|
||||
++ add-mark
|
||||
|= [=resource:res vip=vip-metadata:metadata =indexed-post:store]
|
||||
@ -314,29 +442,43 @@
|
||||
--
|
||||
::
|
||||
++ is-allowed-remove
|
||||
~/ %is-allowed-remove
|
||||
|= [=resource:res indices=(set index:store)]
|
||||
^- ?
|
||||
^- [? (list card)]
|
||||
|^
|
||||
%- (bond |.(%.n))
|
||||
%- (bond |.([%.n ~]))
|
||||
%+ biff (get-roles-writers-variation resource)
|
||||
|= [is-admin=? writers=(set ship) vip=vip-metadata:metadata]
|
||||
%- some
|
||||
%+ levy ~(tap by indices)
|
||||
|= =index:store
|
||||
^- ?
|
||||
=/ =node:store
|
||||
(got-node:gra resource index)
|
||||
?: ?=(%| -.post.node) %.n
|
||||
=/ =permissions:store
|
||||
%^ remove-mark resource vip
|
||||
(node-to-indexed-post node)
|
||||
=/ =permission-level:store
|
||||
(get-permission permissions is-admin writers)
|
||||
?- permission-level
|
||||
%yes %.y
|
||||
%no %.n
|
||||
%self =(author.p.post.node src.bowl)
|
||||
==
|
||||
%- some
|
||||
=/ a ~(tap by indices)
|
||||
=| cards=(list card)
|
||||
|- ^- [? (list card)]
|
||||
?~ a [& cards]
|
||||
=/ c (check i.a is-admin writers vip)
|
||||
?. -.c
|
||||
[| (weld cards +.c)]
|
||||
$(a t.a, cards (weld cards +.c))
|
||||
::
|
||||
++ check
|
||||
|= [=index:store is-admin=? writers=(set ship) vip=vip-metadata:metadata]
|
||||
^- [? (list card)]
|
||||
=/ =node:store
|
||||
(got-node:gra resource index)
|
||||
?: ?=(%| -.post.node)
|
||||
[%.n ~]
|
||||
=/ removed
|
||||
%^ remove-mark resource vip
|
||||
(node-to-indexed-post node)
|
||||
=* permissions -.removed
|
||||
=* cards +.removed
|
||||
=/ =permission-level:store
|
||||
(get-permission permissions is-admin writers)
|
||||
:_ cards
|
||||
?- permission-level
|
||||
%yes %.y
|
||||
%no %.n
|
||||
%self =(author.p.post.node src.bowl)
|
||||
==
|
||||
::
|
||||
++ remove-mark
|
||||
|= [=resource:res vip=vip-metadata:metadata =indexed-post:store]
|
||||
|
@ -18,9 +18,20 @@
|
||||
++ orm orm:store
|
||||
++ orm-log orm-log:store
|
||||
+$ debug-input [%validate-graph =resource:store]
|
||||
::
|
||||
+$ cache
|
||||
$: validators=(map mark dais:clay)
|
||||
==
|
||||
::
|
||||
:: TODO: come back to this and potentially use ford runes or otherwise
|
||||
:: send a %t to be notified of validator changes
|
||||
+$ inflated-state
|
||||
$: state-5
|
||||
cache
|
||||
==
|
||||
--
|
||||
::
|
||||
=| state-5
|
||||
=| inflated-state
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -32,7 +43,7 @@
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init [~ this]
|
||||
++ on-save !>(state)
|
||||
++ on-save !>(-.state)
|
||||
++ on-load
|
||||
|= =old=vase
|
||||
^- (quip card _this)
|
||||
@ -108,7 +119,7 @@
|
||||
(gas:orm-log ~ [now.bowl logged-update] ~)
|
||||
==
|
||||
::
|
||||
%5 [cards this(state old)]
|
||||
%5 [cards this(-.state old, +.state *cache)]
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
@ -180,7 +191,9 @@
|
||||
!(~(has by graphs) resource)
|
||||
== ==
|
||||
~| "validation of graph {<resource>} failed using mark {<mark>}"
|
||||
?> (validate-graph graph mark)
|
||||
=^ is-valid state
|
||||
(validate-graph graph mark)
|
||||
?> is-valid
|
||||
=/ =logged-update:store
|
||||
[time %add-graph resource graph mark overwrite]
|
||||
=/ =update-log:store
|
||||
@ -217,6 +230,10 @@
|
||||
(~(got by graphs) resource)
|
||||
~| "cannot add duplicate nodes to {<resource>}"
|
||||
?< (check-for-duplicates graph ~(key by nodes))
|
||||
~| "validation of nodes failed using mark {<mark>}"
|
||||
=^ is-valid state
|
||||
(check-validity ~(tap by nodes) mark)
|
||||
?> is-valid
|
||||
=/ =update-log:store (~(got by update-logs) resource)
|
||||
=. update-log
|
||||
(put:orm-log update-log time [time [%add-nodes resource nodes]])
|
||||
@ -231,6 +248,17 @@
|
||||
(add-node-list resource graph mark (sort-nodes nodes))
|
||||
==
|
||||
::
|
||||
++ check-validity
|
||||
|= [lis=(list (pair index:store node:store)) mark=(unit ^mark)]
|
||||
^- [? _state]
|
||||
|-
|
||||
?~ lis [& state]
|
||||
=^ is-valid state
|
||||
(validate-graph (gas:orm ~ [(rear p.i.lis) q.i.lis]~) mark)
|
||||
?. is-valid
|
||||
[| state]
|
||||
$(lis t.lis)
|
||||
::
|
||||
++ check-for-duplicates
|
||||
|= [=graph:store nodes=(set index:store)]
|
||||
^- ?
|
||||
@ -288,8 +316,6 @@
|
||||
==
|
||||
^- graph:store
|
||||
?< ?=(~ index)
|
||||
~| "validation of node failed using mark {<mark>}"
|
||||
?> (validate-graph (gas:orm ~ [i.index node]~) mark)
|
||||
=* atom i.index
|
||||
%^ put:orm
|
||||
graph
|
||||
@ -588,18 +614,24 @@
|
||||
^- (quip card _state)
|
||||
=/ [=graph:store mark=(unit mark:store)]
|
||||
(~(got by graphs) resource.debug-input)
|
||||
?> (validate-graph graph mark)
|
||||
=^ is-valid state
|
||||
(validate-graph graph mark)
|
||||
?> is-valid
|
||||
[~ state]
|
||||
::
|
||||
++ validate-graph
|
||||
|= [=graph:store mark=(unit mark:store)]
|
||||
^- ?
|
||||
?~ mark %.y
|
||||
^- [? _state]
|
||||
?~ mark [%.y state]
|
||||
=/ has-dais (~(has by validators) u.mark)
|
||||
=/ =dais:clay
|
||||
?: has-dais
|
||||
(~(got by validators) u.mark)
|
||||
.^ =dais:clay
|
||||
%cb
|
||||
/(scot %p our.bowl)/[q.byk.bowl]/(scot %da now.bowl)/[u.mark]
|
||||
==
|
||||
:_ state(validators (~(put by validators) u.mark dais))
|
||||
|- ^- ?
|
||||
?~ graph %.y
|
||||
%+ roll (tap:orm graph)
|
||||
@ -617,7 +649,9 @@
|
||||
++ poke-import
|
||||
|= arc=*
|
||||
^- (quip card _state)
|
||||
(import:store arc our.bowl)
|
||||
=^ cards -.state
|
||||
(import:store arc our.bowl)
|
||||
[cards state]
|
||||
--
|
||||
::
|
||||
++ on-peek
|
||||
|
@ -47,8 +47,9 @@
|
||||
::
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
^- (quip card (unit vase))
|
||||
=/ =update:store !<(update:store vas)
|
||||
:- ~
|
||||
?: ?=(%initial -.update)
|
||||
~
|
||||
|^
|
||||
|
@ -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.0a121973708299f6b966.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.a6842e8d167b4e66a4e0.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -191,9 +191,14 @@
|
||||
^- (unit (unit cage))
|
||||
?. (team:title our.bowl src.bowl) ~
|
||||
?+ path [~ ~]
|
||||
[%x %tiles ~] ``noun+!>([tiles tile-ordering])
|
||||
[%x %first-time ~] ``noun+!>(first-time)
|
||||
[%x %keys ~] ``noun+!>(~(key by tiles))
|
||||
[%x %tiles ~] ``noun+!>([tiles tile-ordering])
|
||||
[%x %first-time ~] ``noun+!>(first-time)
|
||||
[%x %keys ~] ``noun+!>(~(key by tiles))
|
||||
::
|
||||
[%x %runtime-lag ~]
|
||||
:^ ~ ~ %json
|
||||
!> ^- json
|
||||
b+.^(? //(scot %p our.bowl)//(scot %da now.bowl)/zen/lag)
|
||||
==
|
||||
::
|
||||
++ on-arvo
|
||||
|
@ -59,8 +59,9 @@
|
||||
::
|
||||
++ transform-proxy-update
|
||||
|= vas=vase
|
||||
^- (unit vase)
|
||||
^- (quip card (unit vase))
|
||||
=/ =update:store !<(update:store vas)
|
||||
:- ~
|
||||
?. ?=(?(%add %remove) -.update)
|
||||
~
|
||||
=/ role=(unit (unit role-tag))
|
||||
|
@ -50,18 +50,17 @@
|
||||
==
|
||||
::
|
||||
++ index
|
||||
|= i=^index
|
||||
|= ind=^index
|
||||
^- json
|
||||
?: =(~ i) s+'/'
|
||||
=/ j=^tape ""
|
||||
|-
|
||||
?~ i [%s (crip j)]
|
||||
=/ k=json (numb i.i)
|
||||
?> ?=(%n -.k)
|
||||
%_ $
|
||||
i t.i
|
||||
j (weld j (weld "/" (trip +.k)))
|
||||
==
|
||||
:- %s
|
||||
?: =(~ ind)
|
||||
'/'
|
||||
%+ roll ind
|
||||
|= [cur=@ acc=@t]
|
||||
^- @t
|
||||
=/ num (numb cur)
|
||||
?> ?=(%n -.num)
|
||||
(rap 3 acc '/' p.num ~)
|
||||
::
|
||||
++ uid
|
||||
|= u=^uid
|
||||
|
@ -75,7 +75,12 @@
|
||||
=/ grp=(unit group)
|
||||
(scry-group rid)
|
||||
?~ grp ~
|
||||
=* group u.grp
|
||||
(role-for-ship-with-group u.grp rid ship)
|
||||
::
|
||||
++ role-for-ship-with-group
|
||||
|= [grp=group rid=resource =ship]
|
||||
^- (unit (unit role-tag))
|
||||
=* group grp
|
||||
=* policy policy.group
|
||||
=* tags tags.group
|
||||
=/ admins=(set ^ship)
|
||||
@ -106,8 +111,13 @@
|
||||
^- (set ship)
|
||||
=/ grp=(unit group)
|
||||
(scry-group rid)
|
||||
?~ grp ~
|
||||
(~(get ju tags.u.grp) tag)
|
||||
?~ grp ~
|
||||
(get-tagged-ships-with-group u.grp rid tag)
|
||||
::
|
||||
++ get-tagged-ships-with-group
|
||||
|= [grp=group rid=resource =tag]
|
||||
^- (set ship)
|
||||
(~(get ju tags.grp) tag)
|
||||
::
|
||||
++ is-managed
|
||||
|= rid=resource
|
||||
|
@ -23,13 +23,13 @@
|
||||
%+ turn ~(tap by associations)
|
||||
|= [=md-resource [group=resource =^metadatum]]
|
||||
^- [cord json]
|
||||
:-
|
||||
%- crip
|
||||
;: weld
|
||||
(trip (spat (en-path:resource group)))
|
||||
(weld "/" (trip app-name.md-resource))
|
||||
(trip (spat (en-path:resource resource.md-resource)))
|
||||
==
|
||||
:- %: rap 3
|
||||
(spat (en-path:resource group))
|
||||
'/'
|
||||
app-name.md-resource
|
||||
(spat (en-path:resource resource.md-resource))
|
||||
~
|
||||
==
|
||||
%- pairs
|
||||
:~ [%group s+(enjs-path:resource group)]
|
||||
[%app-name s+app-name.md-resource]
|
||||
|
@ -118,11 +118,10 @@
|
||||
::
|
||||
:: If %.n please leave note as to why renegotiation necessary
|
||||
::
|
||||
:: - Fixing incorrectly held unversioned subscriptions
|
||||
::
|
||||
++ diplomatic
|
||||
^- ?
|
||||
%.n
|
||||
%.y
|
||||
::
|
||||
++ default
|
||||
|* [pull-hook=* =config]
|
||||
|
@ -26,6 +26,7 @@
|
||||
::
|
||||
/- *push-hook
|
||||
/+ default-agent, resource, verb, versioning, agentio
|
||||
~% %push-hook-top ..part ~
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
::
|
||||
@ -76,14 +77,13 @@
|
||||
:: +diplomatic: only renegotiate if versions changed
|
||||
::
|
||||
:: If %.n please leave note as to why renegotiation necessary
|
||||
::
|
||||
:: - Fixing incorrectly held unversioned subscriptions
|
||||
::
|
||||
++ diplomatic
|
||||
^- ?
|
||||
%.n
|
||||
%.y
|
||||
::
|
||||
++ push-hook
|
||||
~/ %push-hook
|
||||
|* =config
|
||||
$_ ^|
|
||||
|_ bowl:gall
|
||||
@ -113,7 +113,7 @@
|
||||
::
|
||||
++ transform-proxy-update
|
||||
|~ vase
|
||||
*(unit vase)
|
||||
*[(list card) (unit vase)]
|
||||
:: +initial-watch: produce initial state for a subscription
|
||||
::
|
||||
:: .resource is the resource being subscribed to.
|
||||
@ -175,6 +175,7 @@
|
||||
=* state -
|
||||
^- agent:gall
|
||||
=<
|
||||
~% %push-agent-lib ..poke-hook-action ~
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
og ~(. push-hook bowl)
|
||||
@ -267,6 +268,7 @@
|
||||
!>(state)
|
||||
::
|
||||
++ on-poke
|
||||
~/ %on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?: =(mark %push-hook-action)
|
||||
@ -283,6 +285,7 @@
|
||||
[cards this]
|
||||
::
|
||||
++ on-watch
|
||||
~/ %on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?: ?=([%version ~] path)
|
||||
@ -320,6 +323,7 @@
|
||||
--
|
||||
::
|
||||
++ on-agent
|
||||
~/ %on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?. ?=([%helper %push-hook @ *] wire)
|
||||
@ -373,6 +377,7 @@
|
||||
[%x %min-version ~] ``version+!>(version.config)
|
||||
==
|
||||
--
|
||||
~% %push-helper-lib ..card ~
|
||||
|_ =bowl:gall
|
||||
+* og ~(. push-hook bowl)
|
||||
ver ~(. versioning [bowl [update-mark version min-version]:config])
|
||||
@ -380,6 +385,7 @@
|
||||
pass pass:io
|
||||
::
|
||||
++ poke-hook-action
|
||||
~/ %poke-hook-action
|
||||
|= =action
|
||||
^- (quip card:agent:gall _state)
|
||||
|^
|
||||
@ -448,6 +454,7 @@
|
||||
[%pass wire %agent [our.bowl store-name.config] %watch store-path.config]
|
||||
::
|
||||
++ push-updates
|
||||
~/ %push-updates
|
||||
|= =cage
|
||||
^- (list card:agent:gall)
|
||||
%+ roll (resource-for-update q.cage)
|
||||
@ -484,6 +491,7 @@
|
||||
--
|
||||
::
|
||||
++ forward-update
|
||||
~/ %forward-update
|
||||
|= =cage
|
||||
^- (list card:agent:gall)
|
||||
=- lis
|
||||
@ -494,35 +502,32 @@
|
||||
^- [(list card:agent:gall) (unit vase)]
|
||||
=/ =path
|
||||
resource+(en-path:resource rid)
|
||||
=/ =wire (make-wire path)
|
||||
=* ship entity.rid
|
||||
=. tf-vas
|
||||
=/ out=(pair (list card:agent:gall) (unit vase))
|
||||
?. =(our.bowl ship)
|
||||
:: do not transform before forwarding
|
||||
::
|
||||
`vas
|
||||
``vas
|
||||
:: use cached transform
|
||||
::
|
||||
?^ tf-vas tf-vas
|
||||
?^ tf-vas `tf-vas
|
||||
:: transform before poking store
|
||||
::
|
||||
(transform-proxy-update:og vas)
|
||||
~| "forwarding failed during transform. mark: {<p.cage>} resource: {<rid>}"
|
||||
?> ?=(^ tf-vas)
|
||||
=/ =dock
|
||||
:- ship
|
||||
?. =(our.bowl ship)
|
||||
:: forward to host
|
||||
::
|
||||
dap.bowl
|
||||
:: poke our store
|
||||
~| "forwarding failed during transform. mark: {<p.cage>} rid: {<rid>}"
|
||||
?> ?=(^ q.out)
|
||||
:_ q.out
|
||||
:_ (weld lis p.out)
|
||||
=/ =wire (make-wire path)
|
||||
=- [%pass wire %agent - %poke [current-version:ver u.q.out]]
|
||||
:- ship
|
||||
?. =(our.bowl ship)
|
||||
:: forward to host
|
||||
::
|
||||
store-name.config
|
||||
=/ cag=^cage
|
||||
:- current-version:ver
|
||||
u.tf-vas
|
||||
:_ tf-vas
|
||||
[[%pass wire %agent dock %poke cag] lis]
|
||||
dap.bowl
|
||||
:: poke our store
|
||||
::
|
||||
store-name.config
|
||||
::
|
||||
++ ver-from-path
|
||||
|= =path
|
||||
@ -532,6 +537,7 @@
|
||||
(slav %ud i.extra)
|
||||
::
|
||||
++ resource-for-update
|
||||
~/ %resource-for-update
|
||||
|= =vase
|
||||
^- (list resource)
|
||||
%~ tap in
|
||||
|
@ -39,10 +39,10 @@
|
||||
~! +:*handler
|
||||
(handler inbound-request)
|
||||
::
|
||||
=/ redirect=cord
|
||||
%- crip
|
||||
"/~/login?redirect={(trip url.request.inbound-request)}"
|
||||
[[307 ['location' redirect]~] ~]
|
||||
=- [[307 ['location' -]~] ~]
|
||||
%^ cat 3
|
||||
'/~/login?redirect='
|
||||
url.request.inbound-request
|
||||
::
|
||||
:: +require-authorization-simple:
|
||||
:: redirect to the login page when unauthenticated
|
||||
@ -56,10 +56,10 @@
|
||||
~! this
|
||||
simple-payload
|
||||
::
|
||||
=/ redirect=cord
|
||||
%- crip
|
||||
"/~/login?redirect={(trip url.request.inbound-request)}"
|
||||
[[307 ['location' redirect]~] ~]
|
||||
=- [[307 ['location' -]~] ~]
|
||||
%^ cat 3
|
||||
'/~/login?redirect='
|
||||
url.request.inbound-request
|
||||
::
|
||||
++ give-simple-payload
|
||||
|= [eyre-id=@ta =simple-payload:http]
|
||||
@ -86,36 +86,52 @@
|
||||
:_ `octs
|
||||
[200 [['content-type' 'text/html'] ?:(cache [max-1-wk ~] ~)]]
|
||||
::
|
||||
++ js-response
|
||||
|= =octs
|
||||
^- simple-payload:http
|
||||
[[200 [['content-type' 'text/javascript'] max-1-da ~]] `octs]
|
||||
::
|
||||
++ json-response
|
||||
|= =json
|
||||
^- simple-payload:http
|
||||
[[200 ['content-type' 'application/json']~] `(json-to-octs json)]
|
||||
::
|
||||
++ css-response
|
||||
=| cache=?
|
||||
|= =octs
|
||||
^- simple-payload:http
|
||||
[[200 [['content-type' 'text/css'] max-1-da ~]] `octs]
|
||||
:_ `octs
|
||||
[200 [['content-type' 'text/css'] ?:(cache [max-1-wk ~] ~)]]
|
||||
::
|
||||
++ manx-response
|
||||
|= man=manx
|
||||
++ js-response
|
||||
=| cache=?
|
||||
|= =octs
|
||||
^- simple-payload:http
|
||||
[[200 ['content-type' 'text/html']~] `(manx-to-octs man)]
|
||||
:_ `octs
|
||||
[200 [['content-type' 'text/javascript'] ?:(cache [max-1-wk ~] ~)]]
|
||||
::
|
||||
++ png-response
|
||||
=| cache=?
|
||||
|= =octs
|
||||
^- simple-payload:http
|
||||
[[200 [['content-type' 'image/png'] max-1-wk ~]] `octs]
|
||||
:_ `octs
|
||||
[200 [['content-type' 'image/png'] ?:(cache [max-1-wk ~] ~)]]
|
||||
::
|
||||
++ ico-response
|
||||
|= =octs
|
||||
^- simple-payload:http
|
||||
[[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs]
|
||||
::
|
||||
++ woff2-response
|
||||
=| cache=?
|
||||
|= =octs
|
||||
^- simple-payload:http
|
||||
[[200 [['content-type' 'font/woff2'] max-1-wk ~]] `octs]
|
||||
::
|
||||
++ json-response
|
||||
=| cache=_|
|
||||
|= =json
|
||||
^- simple-payload:http
|
||||
:_ `(json-to-octs json)
|
||||
[200 [['content-type' 'application/json'] ?:(cache [max-1-da ~] ~)]]
|
||||
::
|
||||
++ manx-response
|
||||
=| cache=_|
|
||||
|= man=manx
|
||||
^- simple-payload:http
|
||||
:_ `(manx-to-octs man)
|
||||
[200 [['content-type' 'text/html'] ?:(cache [max-1-da ~] ~)]]
|
||||
::
|
||||
++ not-found
|
||||
^- simple-payload:http
|
||||
[[404 ~] ~]
|
||||
@ -123,10 +139,10 @@
|
||||
++ login-redirect
|
||||
|= =request:http
|
||||
^- simple-payload:http
|
||||
=/ redirect=cord
|
||||
%- crip
|
||||
"/~/login?redirect={(trip url.request)}"
|
||||
[[307 ['location' redirect]~] ~]
|
||||
=- [[307 ['location' -]~] ~]
|
||||
%^ cat 3
|
||||
'/~/login?redirect='
|
||||
url.request
|
||||
::
|
||||
++ redirect
|
||||
|= redirect=cord
|
||||
|
20
pkg/arvo/mar/graph/cache/hook.hoon
vendored
Normal file
20
pkg/arvo/mar/graph/cache/hook.hoon
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/- metadata=metadata-store, res=resource
|
||||
|%
|
||||
+$ cache-action
|
||||
$% [%graph-to-mark (pair resource:res (unit mark))]
|
||||
[%perm-marks (pair (pair mark @tas) tube:clay)]
|
||||
[%transform-marks (pair mark tube:clay)]
|
||||
==
|
||||
--
|
||||
::
|
||||
|_ act=cache-action
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun act
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun cache-action
|
||||
--
|
||||
--
|
12
pkg/arvo/mar/ico.hoon
Normal file
12
pkg/arvo/mar/ico.hoon
Normal file
@ -0,0 +1,12 @@
|
||||
|_ dat=@
|
||||
++ grow
|
||||
|%
|
||||
++ mime [/image/x-icon (as-octs:mimes:html dat)]
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ mime |=([p=mite q=octs] q.q)
|
||||
++ noun @
|
||||
--
|
||||
++ grad %mime
|
||||
--
|
83
pkg/interface/package-lock.json
generated
83
pkg/interface/package-lock.json
generated
@ -4964,18 +4964,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
@ -6643,9 +6643,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"immer": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz",
|
||||
"integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA=="
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.2.tgz",
|
||||
"integrity": "sha512-mkcmzLtIfSp40vAqteRr1MbWNSoI7JE+/PB36FNPoSfJ9RQRmNKuTYCjKkyXyuq3Dgn07HuJBrwJd4ZSk2yUbw=="
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
@ -7192,9 +7192,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.20",
|
||||
@ -9133,9 +9133,38 @@
|
||||
}
|
||||
},
|
||||
"remark-breaks": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-2.0.1.tgz",
|
||||
"integrity": "sha512-CZKI8xdPUnvMqPxYEIBBUg8C0B0kyn14lkW0abzhfh/P71YRIxCC3wvBh6AejQL602OxF6kNRl1x4HAZA07JyQ=="
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-2.0.2.tgz",
|
||||
"integrity": "sha512-LsQnPPQ7Fzp9RTjj4IwdEmjPOr9bxe9zYKWhs9ZQOg9hMg8rOfeeqQ410cvVdIK87Famqza1CKRxNkepp2EvUA==",
|
||||
"requires": {
|
||||
"unist-util-visit": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"unist-util-is": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
|
||||
"integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg=="
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
|
||||
"integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^4.0.0",
|
||||
"unist-util-visit-parents": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"unist-util-visit-parents": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
|
||||
"integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
|
||||
"requires": {
|
||||
"@types/unist": "^2.0.0",
|
||||
"unist-util-is": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"remark-disable-tokenizers": {
|
||||
"version": "1.1.0",
|
||||
@ -10094,9 +10123,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
@ -11068,9 +11097,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
@ -12168,9 +12197,9 @@
|
||||
}
|
||||
},
|
||||
"zustand": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.3.1.tgz",
|
||||
"integrity": "sha512-o0rgrBsi29nCkPHdhtkAHisCIlmRUoXOV+1AmDMeCgkGG0i5edFSpGU0KiZYBvFmBYycnck4Z07JsLYDjSET9g=="
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.5.1.tgz",
|
||||
"integrity": "sha512-7J56Ve814z4zap71iaKFD+t65LFI//jEq/Vf55BTSVqJZCm+w9rov8OMBg+YSwIPQk54bfoIWHTrOWuAbpEDMw=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,8 @@
|
||||
"css-loader": "^3.6.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"formik": "^2.1.5",
|
||||
"immer": "^8.0.1",
|
||||
"lodash": "^4.17.20",
|
||||
"immer": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mousetrap-global-bind": "^1.1.0",
|
||||
@ -42,7 +42,7 @@
|
||||
"react-virtuoso": "^0.20.3",
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"remark": "^12.0.0",
|
||||
"remark-breaks": "^2.0.1",
|
||||
"remark-breaks": "^2.0.2",
|
||||
"remark-disable-tokenizers": "1.1.0",
|
||||
"stacktrace-js": "^2.0.2",
|
||||
"style-loader": "^1.3.0",
|
||||
@ -56,7 +56,7 @@
|
||||
"workbox-recipes": "^6.0.2",
|
||||
"workbox-routing": "^6.0.2",
|
||||
"yup": "^0.29.3",
|
||||
"zustand": "^3.3.1"
|
||||
"zustand": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { Patp } from '@urbit/api';
|
||||
import { ContactEditField } from '@urbit/api/contacts';
|
||||
import _ from 'lodash';
|
||||
import {edit} from '../reducers/contact-update';
|
||||
import {doOptimistically} from '../state/base';
|
||||
import useContactState from '../state/contact';
|
||||
import { StoreState } from '../store/type';
|
||||
import BaseApi from './base';
|
||||
|
||||
@ -26,13 +29,14 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
{add-group: {ship, name}}
|
||||
{remove-group: {ship, name}}
|
||||
*/
|
||||
return this.storeAction({
|
||||
const action = {
|
||||
edit: {
|
||||
ship,
|
||||
'edit-field': editField,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
}
|
||||
doOptimistically(useContactState, action, this.storeAction.bind(this), [edit])
|
||||
}
|
||||
|
||||
allowShips(ships: Patp[]) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Content, Enc, GraphNode, GroupPolicy, Path, Patp, Post, Resource } from '@urbit/api';
|
||||
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
import _ from 'lodash';
|
||||
import { decToUd, deSig, resourceAsPath, unixToDa } from '~/logic/lib/util';
|
||||
import { makeResource, resourceFromPath } from '../lib/group';
|
||||
@ -9,7 +10,7 @@ export const createBlankNodeWithChildPost = (
|
||||
parentIndex = '',
|
||||
childIndex = '',
|
||||
contents: Content[]
|
||||
) => {
|
||||
): GraphNode => {
|
||||
const date = unixToDa(Date.now()).toString();
|
||||
const nodeIndex = parentIndex + '/' + date;
|
||||
|
||||
@ -35,7 +36,7 @@ export const createBlankNodeWithChildPost = (
|
||||
hash: null,
|
||||
signatures: []
|
||||
},
|
||||
children: childGraph
|
||||
children: childGraph as BigIntOrderedMap<GraphNode>
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { Association, GraphNotifDescription, IndexedNotification, NotifIndex } from '@urbit/api';
|
||||
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
import { BigInteger } from 'big-integer';
|
||||
import { getParentIndex } from '../lib/notification';
|
||||
import { dateToDa, decToUd } from '../lib/util';
|
||||
import {reduce} from '../reducers/hark-update';
|
||||
import {doOptimistically, optReduceState} from '../state/base';
|
||||
import useHarkState from '../state/hark';
|
||||
import { StoreState } from '../store/type';
|
||||
import BaseApi from './base';
|
||||
@ -51,8 +54,15 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
archive(time: BigInteger, index: NotifIndex) {
|
||||
return this.actOnNotification('archive', time, index);
|
||||
async archive(intTime: BigInteger, index: NotifIndex) {
|
||||
const time = decToUd(intTime.toString());
|
||||
const action = {
|
||||
archive: {
|
||||
time,
|
||||
index
|
||||
}
|
||||
};
|
||||
await doOptimistically(useHarkState, action, this.harkAction.bind(this), [reduce])
|
||||
}
|
||||
|
||||
read(time: BigInteger, index: NotifIndex) {
|
||||
|
@ -7,4 +7,10 @@ export default class LocalApi extends BaseApi<StoreState> {
|
||||
this.store.handleEvent({ data: { baseHash } });
|
||||
});
|
||||
}
|
||||
|
||||
getRuntimeLag() {
|
||||
return this.scry<boolean>('launch', '/runtime-lag').then((runtimeLag) => {
|
||||
this.store.handleEvent({ data: { runtimeLag } });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { IndexedNotification, NotificationGraphConfig, Unreads } from '@urbit/api';
|
||||
import { GraphNotifIndex, GroupNotifIndex, IndexedNotification, NotificationGraphConfig, Post, Unreads } from '@urbit/api';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import { pluralize } from './util';
|
||||
|
||||
export function getLastSeen(
|
||||
unreads: Unreads,
|
||||
@ -31,7 +33,7 @@ export function getNotificationCount(
|
||||
): number {
|
||||
const unread = unreads.graph?.[path] || {};
|
||||
return Object.keys(unread)
|
||||
.map(index => unread[index]?.notifications?.length || 0)
|
||||
.map(index => _.get(unread[index], 'notifications.length', 0))
|
||||
.reduce(f.add, 0);
|
||||
}
|
||||
|
||||
@ -57,3 +59,54 @@ export function getNotificationKey(time: BigInteger, notification: IndexedNotifi
|
||||
return `${base}-unknown`;
|
||||
}
|
||||
|
||||
|
||||
export function notificationReferent(not: IndexedNotification) {
|
||||
if('graph' in not.index) {
|
||||
return not.index.graph.graph;
|
||||
} else {
|
||||
return not.index.group.group;
|
||||
}
|
||||
}
|
||||
export function describeNotification(notification: IndexedNotification) {
|
||||
function group(idx: GroupNotifIndex) {
|
||||
switch (idx.description) {
|
||||
case 'add-members':
|
||||
return 'joined';
|
||||
case 'remove-members':
|
||||
return 'left';
|
||||
default:
|
||||
return idx.description;
|
||||
}
|
||||
}
|
||||
function graph(idx: GraphNotifIndex, plural: boolean, singleAuthor: boolean) {
|
||||
const isDm = idx.graph.startsWith('dm--');
|
||||
switch (idx.description) {
|
||||
case 'post':
|
||||
return singleAuthor ? 'replied to you' : 'Your post received replies';
|
||||
case 'link':
|
||||
return `New link${plural ? 's' : ''} in`;
|
||||
case 'comment':
|
||||
return `New comment${plural ? 's' : ''} on`;
|
||||
case 'note':
|
||||
return `New Note${plural ? 's' : ''} in`;
|
||||
case 'edit-note':
|
||||
return `updated ${pluralize('note', plural)} in`;
|
||||
case 'mention':
|
||||
return singleAuthor ? 'mentioned you in' : 'You were mentioned in';
|
||||
case 'message':
|
||||
if (isDm) {
|
||||
return 'messaged you';
|
||||
}
|
||||
return `New message${plural ? 's' : ''} in`;
|
||||
default:
|
||||
return idx.description;
|
||||
}
|
||||
}
|
||||
if('group' in notification.index) {
|
||||
return group(notification.index.group);
|
||||
} else if('graph' in notification.index) {
|
||||
const contents = notification.notification?.contents?.graph ?? [] as Post[];
|
||||
return graph(notification.index.graph, contents.length > 1, _.uniq(_.map(contents, 'author')).length === 1)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -20,11 +20,12 @@ function getPermalinkForAssociatedGroup(group: string) {
|
||||
|
||||
type Permalink = GraphPermalink | GroupPermalink;
|
||||
|
||||
interface GroupPermalink {
|
||||
export interface GroupPermalink {
|
||||
type: 'group';
|
||||
group: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface GraphPermalink {
|
||||
type: 'graph';
|
||||
link: string;
|
||||
|
@ -58,13 +58,7 @@ const tokenizeMessage = (text) => {
|
||||
|| (str === '`' && !isInCodeBlock)
|
||||
) {
|
||||
isInCodeBlock = true;
|
||||
} else if (
|
||||
(str.endsWith('`') && str !== '`')
|
||||
|| (str === '`' && isInCodeBlock)
|
||||
) {
|
||||
isInCodeBlock = false;
|
||||
}
|
||||
|
||||
}
|
||||
if(isRef(str) && !isInCodeBlock) {
|
||||
if (currTextLine.length > 0 || currTextBlock.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
@ -105,6 +99,13 @@ const tokenizeMessage = (text) => {
|
||||
} else {
|
||||
currTextLine.push(str);
|
||||
}
|
||||
if (
|
||||
(str.endsWith('`') && str !== '`')
|
||||
|| (str === '`' && isInCodeBlock)
|
||||
) {
|
||||
isInCodeBlock = false;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
currTextBlock.push(currTextLine.join(' '))
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import React, {
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef, useState
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef, useState
|
||||
} from 'react';
|
||||
import { PropFunc } from '~/types';
|
||||
import { ModalOverlay } from '~/views/components/ModalOverlay';
|
||||
@ -57,7 +57,7 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
spacing="2"
|
||||
spacing={2}
|
||||
dismiss={dismiss}
|
||||
{...rest}
|
||||
>
|
||||
|
@ -1,12 +1,15 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
|
||||
import { Association, Contact } from '@urbit/api';
|
||||
import anyAscii from 'any-ascii';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import { enableMapSet } from 'immer';
|
||||
import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { foregroundFromBackground } from '~/logic/lib/sigil';
|
||||
import { IconRef, Workspace } from '~/types';
|
||||
import useContactState from '../state/contact';
|
||||
import useSettingsState from '../state/settings';
|
||||
|
||||
enableMapSet();
|
||||
@ -58,10 +61,10 @@ export const getChord = (e: KeyboardEvent) => {
|
||||
}
|
||||
|
||||
export function getResourcePath(workspace: Workspace, path: string, joined: boolean, mod: string) {
|
||||
const base = workspace.type === 'group'
|
||||
? `/~landscape${workspace.group}`
|
||||
: workspace.type === 'home'
|
||||
? `/~landscape/home`
|
||||
const base = workspace.type === 'group'
|
||||
? `/~landscape${workspace.group}`
|
||||
: workspace.type === 'home'
|
||||
? `/~landscape/home`
|
||||
: `/~landscape/messages`;
|
||||
return `${base}/${joined ? 'resource' : 'join'}/${mod}${path}`
|
||||
}
|
||||
@ -452,6 +455,13 @@ export const useHovering = (): useHoveringInterface => {
|
||||
return useMemo(() => ({ hovering, bind }), [hovering, bind]);
|
||||
};
|
||||
|
||||
export function withHovering<T>(Component: React.ComponentType<T>) {
|
||||
return React.forwardRef((props, ref) => {
|
||||
const { hovering, bind } = useHovering();
|
||||
return <Component ref={ref} hovering={hovering} bind={bind} {...props} />
|
||||
})
|
||||
}
|
||||
|
||||
const DM_REGEX = /ship\/~([a-z]|-)*\/dm--/;
|
||||
export function getItemTitle(association: Association): string {
|
||||
if (DM_REGEX.test(association.resource)) {
|
||||
@ -464,3 +474,22 @@ export function getItemTitle(association: Association): string {
|
||||
return association.metadata.title ?? association.resource ?? '';
|
||||
}
|
||||
|
||||
export const svgDataURL = (svg) => 'data:image/svg+xml;base64,' + btoa(svg);
|
||||
|
||||
export const svgBlobURL = (svg) => URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
|
||||
|
||||
export const favicon = () => {
|
||||
let background = '#ffffff';
|
||||
const contacts = useContactState.getState().contacts;
|
||||
if (contacts.hasOwnProperty(`~${window.ship}`)) {
|
||||
background = `#${uxToHex(contacts[`~${window.ship}`].color)}`;
|
||||
}
|
||||
const foreground = foregroundFromBackground(background);
|
||||
const svg = sigiljs({
|
||||
patp: window.ship,
|
||||
renderer: stringRenderer,
|
||||
size: 16,
|
||||
colors: [background, foreground]
|
||||
});
|
||||
return svg;
|
||||
}
|
@ -19,12 +19,14 @@ const withStateo = <
|
||||
});
|
||||
};
|
||||
|
||||
const withState = <
|
||||
StateType extends BaseState<StateType>,
|
||||
stateKey extends keyof StateType
|
||||
>(
|
||||
interface StatePicker extends Array<any> {
|
||||
0: UseStore<any>;
|
||||
1?: string[];
|
||||
}
|
||||
|
||||
const withState = (
|
||||
Component: any,
|
||||
stores: ([UseStore<StateType>, stateKey[]])[]
|
||||
stores: StatePicker[]
|
||||
) => {
|
||||
return React.forwardRef((props, ref) => {
|
||||
const stateProps: unknown = {};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ContactUpdate } from '@urbit/api';
|
||||
import { ContactUpdate, deSig } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import { reduceState } from '../state/base';
|
||||
import useContactState, { ContactState } from '../state/contact';
|
||||
@ -52,9 +52,9 @@ const remove = (json: ContactUpdate, state: ContactState): ContactState => {
|
||||
return state;
|
||||
};
|
||||
|
||||
const edit = (json: ContactUpdate, state: ContactState): ContactState => {
|
||||
export const edit = (json: ContactUpdate, state: ContactState): ContactState => {
|
||||
const data = _.get(json, 'edit', false);
|
||||
const ship = `~${data.ship}`;
|
||||
const ship = `~${deSig(data.ship)}`;
|
||||
if (
|
||||
data &&
|
||||
(ship in state.contacts)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { GraphNode } from '@urbit/api';
|
||||
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import produce from 'immer';
|
||||
@ -52,14 +53,14 @@ const keys = (json, state: GraphState): GraphState => {
|
||||
const processNode = (node) => {
|
||||
// is empty
|
||||
if (!node.children) {
|
||||
return produce(node, (draft) => {
|
||||
return produce<GraphNode>(node, (draft: GraphNode) => {
|
||||
draft.children = new BigIntOrderedMap();
|
||||
});
|
||||
}
|
||||
|
||||
// is graph
|
||||
return produce(node, (draft) => {
|
||||
draft.children = new BigIntOrderedMap()
|
||||
return produce<GraphNode>(node, (draft: GraphNode) => {
|
||||
draft.children = new BigIntOrderedMap<GraphNode>()
|
||||
.gas(_.map(draft.children, (item, idx) =>
|
||||
[bigInt(idx), processNode(item)] as [BigInteger, any]
|
||||
));
|
||||
|
@ -7,8 +7,10 @@ import { BigInteger } from 'big-integer';
|
||||
import _ from 'lodash';
|
||||
import { compose } from 'lodash/fp';
|
||||
import { makePatDa } from '~/logic/lib/util';
|
||||
import { describeNotification } from '../lib/hark';
|
||||
import { reduceState } from '../state/base';
|
||||
import useHarkState, { HarkState } from '../state/hark';
|
||||
import useMetadataState from '../state/metadata';
|
||||
|
||||
export const HarkReducer = (json: any) => {
|
||||
const data = _.get(json, 'harkUpdate', false);
|
||||
@ -35,7 +37,7 @@ export const HarkReducer = (json: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
function reduce(data, state) {
|
||||
export function reduce(data, state) {
|
||||
const reducers = [
|
||||
calculateCount,
|
||||
unread,
|
||||
@ -64,11 +66,19 @@ function calculateCount(json: any, state: HarkState) {
|
||||
let count = 0;
|
||||
_.forEach(state.unreads.graph, (graphs) => {
|
||||
_.forEach(graphs, (graph) => {
|
||||
count += (graph?.notifications || []).length;
|
||||
if (typeof graph?.notifications === 'object') {
|
||||
count += graph?.notifications.length;
|
||||
} else {
|
||||
count += 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
_.forEach(state.unreads.group, (group) => {
|
||||
count += (group?.notifications || []).length;
|
||||
if (typeof group?.notifications === 'object') {
|
||||
count += group?.notifications.length;
|
||||
} else {
|
||||
count += 0;
|
||||
}
|
||||
});
|
||||
state.notificationsCount = count;
|
||||
return state;
|
||||
@ -187,7 +197,7 @@ function readSince(json: any, state: HarkState): HarkState {
|
||||
|
||||
function unreadSince(json: any, state: HarkState): HarkState {
|
||||
const data = _.get(json, 'unread-count');
|
||||
if(data) {
|
||||
if (data) {
|
||||
updateUnreadCount(state, data.index, u => u + 1);
|
||||
}
|
||||
return state;
|
||||
@ -305,7 +315,7 @@ function removeNotificationFromUnread(state: HarkState, index: NotifIndex, time:
|
||||
}
|
||||
}
|
||||
|
||||
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number) {
|
||||
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number, notify = false) {
|
||||
if('graph' in index) {
|
||||
const curr: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
|
||||
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
|
||||
@ -320,6 +330,20 @@ function added(json: any, state: HarkState): HarkState {
|
||||
if (data) {
|
||||
const { index, notification } = data;
|
||||
const time = makePatDa(data.time);
|
||||
|
||||
if (!useHarkState.getState().doNotDisturb) {
|
||||
const description = describeNotification(data);
|
||||
const meta = useMetadataState.getState();
|
||||
const referent = 'graph' in data.index ? meta.associations.graph[data.index.graph.graph]?.metadata?.title ?? data.index.graph : meta.associations.groups[data.index.group.group]?.metadata?.title ?? data.index.group;
|
||||
new Notification(`${description} ${referent}`, {
|
||||
tag: 'landscape',
|
||||
image: '/img/favicon.png',
|
||||
icon: '/img/favicon.png',
|
||||
badge: '/img/favicon.png',
|
||||
renotify: true
|
||||
});
|
||||
}
|
||||
|
||||
const timebox = state.notifications.get(time) || [];
|
||||
addNotificationToUnread(state, index, time);
|
||||
|
||||
|
@ -37,6 +37,13 @@ export default class LaunchReducer {
|
||||
state.baseHash = baseHash;
|
||||
});
|
||||
}
|
||||
|
||||
const runtimeLag = _.get(json, 'runtimeLag', null);
|
||||
if (runtimeLag !== null) {
|
||||
useLaunchState.getState().set(state => {
|
||||
state.runtimeLag = runtimeLag;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,24 +1,39 @@
|
||||
import produce, { setAutoFreeze } from 'immer';
|
||||
import produce, { applyPatches, Patch, produceWithPatches, setAutoFreeze, enablePatches } from 'immer';
|
||||
import { compose } from 'lodash/fp';
|
||||
import create, { State, UseStore } from 'zustand';
|
||||
import _ from 'lodash';
|
||||
import create, { UseStore } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
||||
export const stateSetter = <StateType>(
|
||||
fn: (state: StateType) => void,
|
||||
set
|
||||
export const stateSetter = <T extends {}>(
|
||||
fn: (state: Readonly<T & BaseState<T>>) => void,
|
||||
set: (newState: T & BaseState<T>) => void
|
||||
): void => {
|
||||
set(produce(fn));
|
||||
set(produce(fn) as any);
|
||||
};
|
||||
|
||||
export const optStateSetter = <T extends {}>(
|
||||
fn: (state: T & BaseState<T>) => void,
|
||||
set: (newState: T & BaseState<T>) => void,
|
||||
get: () => T & BaseState<T>
|
||||
): string => {
|
||||
const old = get();
|
||||
const id = _.uniqueId()
|
||||
const [state, ,patches] = produceWithPatches(old, fn) as readonly [(T & BaseState<T>), any, Patch[]];
|
||||
set({ ...state, patches: { ...state.patches, [id]: patches }});
|
||||
return id;
|
||||
};
|
||||
|
||||
|
||||
export const reduceState = <
|
||||
StateType extends BaseState<StateType>,
|
||||
UpdateType
|
||||
S extends {},
|
||||
U
|
||||
>(
|
||||
state: UseStore<StateType>,
|
||||
data: UpdateType,
|
||||
reducers: ((data: UpdateType, state: StateType) => StateType)[]
|
||||
state: UseStore<S & BaseState<S>>,
|
||||
data: U,
|
||||
reducers: ((data: U, state: S & BaseState<S>) => S & BaseState<S>)[]
|
||||
): void => {
|
||||
const reducer = compose(reducers.map(r => sta => r(data, sta)));
|
||||
state.getState().set((state) => {
|
||||
@ -26,6 +41,18 @@ export const reduceState = <
|
||||
});
|
||||
};
|
||||
|
||||
export const optReduceState = <S, U>(
|
||||
state: UseStore<S & BaseState<S>>,
|
||||
data: U,
|
||||
reducers: ((data: U, state: S & BaseState<S>) => BaseState<S> & S)[]
|
||||
): string => {
|
||||
const reducer = compose(reducers.map(r => sta => r(data, sta)));
|
||||
return state.getState().optSet((state) => {
|
||||
reducer(state);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export let stateStorageKeys: string[] = [];
|
||||
|
||||
export const stateStorageKey = (stateName: string) => {
|
||||
@ -40,19 +67,56 @@ export const stateStorageKey = (stateName: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export interface BaseState<StateType> extends State {
|
||||
set: (fn: (state: StateType) => void) => void;
|
||||
export interface BaseState<StateType> {
|
||||
rollback: (id: string) => void;
|
||||
patches: {
|
||||
[id: string]: Patch[];
|
||||
};
|
||||
set: (fn: (state: BaseState<StateType>) => void) => void;
|
||||
addPatch: (id: string, ...patch: Patch[]) => void;
|
||||
removePatch: (id: string) => void;
|
||||
optSet: (fn: (state: BaseState<StateType>) => void) => string;
|
||||
}
|
||||
|
||||
export const createState = <T extends {}>(
|
||||
name: string,
|
||||
properties: T,
|
||||
blacklist: string[] = []
|
||||
): UseStore<T & BaseState<T>> => create(persist((set, get) => ({
|
||||
blacklist: (keyof BaseState<T> | keyof T)[] = []
|
||||
): UseStore<T & BaseState<T>> => create<T & BaseState<T>>(persist<T & BaseState<T>>((set, get) => ({
|
||||
set: fn => stateSetter(fn, set),
|
||||
optSet: fn => {
|
||||
return optStateSetter(fn, set, get);
|
||||
},
|
||||
patches: {},
|
||||
addPatch: (id: string, ...patch: Patch[]) => {
|
||||
set(({ patches }) => ({ patches: {...patches, [id]: patch }}));
|
||||
},
|
||||
removePatch: (id: string) => {
|
||||
set(({ patches }) => ({ patches: _.omit(patches, id)}));
|
||||
},
|
||||
rollback: (id: string) => {
|
||||
set(state => {
|
||||
const applying = state.patches[id]
|
||||
return {...applyPatches(state, applying), patches: _.omit(state.patches, id) }
|
||||
});
|
||||
},
|
||||
...properties
|
||||
}), {
|
||||
blacklist,
|
||||
name: stateStorageKey(name),
|
||||
version: process.env.LANDSCAPE_SHORTHASH as any
|
||||
}));
|
||||
|
||||
export async function doOptimistically<A, S extends {}>(state: UseStore<S & BaseState<S>>, action: A, call: (a: A) => Promise<any>, reduce: ((a: A, fn: S & BaseState<S>) => S & BaseState<S>)[]) {
|
||||
let num: string | undefined = undefined;
|
||||
try {
|
||||
num = optReduceState(state, action, reduce);
|
||||
await call(action);
|
||||
state.getState().removePatch(num)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if(num) {
|
||||
state.getState().rollback(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { NotificationGraphConfig, Timebox, Unreads } from '@urbit/api';
|
||||
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
// import { harkGraphHookReducer, harkGroupHookReducer, harkReducer } from "~/logic/subscription/hark";
|
||||
import { BaseState, createState } from './base';
|
||||
import { createState } from './base';
|
||||
|
||||
export const HARK_FETCH_MORE_COUNT = 3;
|
||||
|
||||
export interface HarkState extends BaseState<HarkState> {
|
||||
export interface HarkState {
|
||||
archivedNotifications: BigIntOrderedMap<Timebox>;
|
||||
doNotDisturb: boolean;
|
||||
// getMore: () => Promise<boolean>;
|
||||
|
@ -10,7 +10,8 @@ export interface LaunchState extends BaseState<LaunchState> {
|
||||
weather: WeatherState | null | Record<string, never> | boolean,
|
||||
userLocation: string | null;
|
||||
baseHash: string | null;
|
||||
}
|
||||
runtimeLag: boolean;
|
||||
};
|
||||
|
||||
const useLaunchState = createState<LaunchState>('Launch', {
|
||||
firstTime: true,
|
||||
@ -18,7 +19,8 @@ const useLaunchState = createState<LaunchState>('Launch', {
|
||||
tiles: {},
|
||||
weather: null,
|
||||
userLocation: null,
|
||||
baseHash: null
|
||||
baseHash: null,
|
||||
runtimeLag: false,
|
||||
});
|
||||
|
||||
export default useLaunchState;
|
||||
|
@ -13,6 +13,10 @@ interface LocalUpdateBaseHash {
|
||||
baseHash: string;
|
||||
}
|
||||
|
||||
interface LocalUpdateRuntimeLag {
|
||||
runtimeLag: boolean;
|
||||
}
|
||||
|
||||
interface LocalUpdateBackgroundConfig {
|
||||
backgroundConfig: BackgroundConfig;
|
||||
}
|
||||
@ -51,6 +55,7 @@ export type BackgroundConfig = BackgroundConfigUrl | BackgroundConfigColor | und
|
||||
export type LocalUpdate =
|
||||
| LocalUpdateSetDark
|
||||
| LocalUpdateBaseHash
|
||||
| LocalUpdateRuntimeLag
|
||||
| LocalUpdateBackgroundConfig
|
||||
| LocalUpdateHideAvatars
|
||||
| LocalUpdateHideNicknames
|
||||
|
@ -1,6 +1,5 @@
|
||||
import dark from '@tlon/indigo-dark';
|
||||
import light from '@tlon/indigo-light';
|
||||
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import 'mousetrap-global-bind';
|
||||
import * as React from 'react';
|
||||
@ -11,8 +10,7 @@ import { BrowserRouter as Router, withRouter } from 'react-router-dom';
|
||||
import styled, { ThemeProvider } from 'styled-components';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import gcpManager from '~/logic/lib/gcpManager';
|
||||
import { foregroundFromBackground } from '~/logic/lib/sigil';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { favicon, svgDataURL } from '~/logic/lib/util';
|
||||
import withState from '~/logic/lib/withState';
|
||||
import useContactState from '~/logic/state/contact';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
@ -88,7 +86,6 @@ class App extends React.Component {
|
||||
|
||||
this.updateTheme = this.updateTheme.bind(this);
|
||||
this.updateMobile = this.updateMobile.bind(this);
|
||||
this.faviconString = this.faviconString.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -105,6 +102,7 @@ class App extends React.Component {
|
||||
this.updateTheme(this.themeWatcher);
|
||||
}, 500);
|
||||
this.api.local.getBaseHash();
|
||||
this.api.local.getRuntimeLag(); //TODO consider polling periodically
|
||||
this.api.settings.getAll();
|
||||
gcpManager.start();
|
||||
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
|
||||
@ -131,22 +129,6 @@ class App extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
faviconString() {
|
||||
let background = '#ffffff';
|
||||
if (this.props.contacts.hasOwnProperty(`~${window.ship}`)) {
|
||||
background = `#${uxToHex(this.props.contacts[`~${window.ship}`].color)}`;
|
||||
}
|
||||
const foreground = foregroundFromBackground(background);
|
||||
const svg = sigiljs({
|
||||
patp: window.ship,
|
||||
renderer: stringRenderer,
|
||||
size: 16,
|
||||
colors: [background, foreground]
|
||||
});
|
||||
const dataurl = 'data:image/svg+xml;base64,' + btoa(svg);
|
||||
return dataurl;
|
||||
}
|
||||
|
||||
getTheme() {
|
||||
const { props } = this;
|
||||
return ((props.dark && props?.display?.theme == 'auto') ||
|
||||
@ -164,7 +146,7 @@ class App extends React.Component {
|
||||
<ShortcutContextProvider>
|
||||
<Helmet>
|
||||
{window.ship.length < 14
|
||||
? <link rel="icon" type="image/svg+xml" href={this.faviconString()} />
|
||||
? <link rel="icon" type="image/svg+xml" href={svgDataURL(favicon())} />
|
||||
: null}
|
||||
</Helmet>
|
||||
<Root>
|
||||
|
@ -108,8 +108,18 @@ interface ChatEditorState {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface CodeMirrorShim {
|
||||
setValue: (string) => void;
|
||||
setOption: (option: string, property: any) => void;
|
||||
focus: () => void;
|
||||
execCommand: (string) => void;
|
||||
getValue: () => string;
|
||||
getInputField: () => HTMLInputElement;
|
||||
element: HTMLElement;
|
||||
}
|
||||
|
||||
export default class ChatEditor extends Component<ChatEditorProps, ChatEditorState> {
|
||||
editor: ProxyHandler<unknown> | null;
|
||||
editor: CodeMirrorShim;
|
||||
constructor(props: ChatEditorProps) {
|
||||
super(props);
|
||||
|
||||
@ -239,7 +249,7 @@ export default class ChatEditor extends Component<ChatEditorProps, ChatEditorSta
|
||||
<Row
|
||||
backgroundColor='white'
|
||||
alignItems='center'
|
||||
flexGrow='1'
|
||||
flexGrow={1}
|
||||
height='100%'
|
||||
paddingTop={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
|
||||
paddingBottom={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
|
||||
@ -252,7 +262,7 @@ export default class ChatEditor extends Component<ChatEditorProps, ChatEditorSta
|
||||
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
|
||||
? <MobileBox
|
||||
data-value={this.state.message}
|
||||
fontSize="1"
|
||||
fontSize={1}
|
||||
lineHeight="tall"
|
||||
onClick={(event) => {
|
||||
if (this.editor) {
|
||||
@ -262,7 +272,7 @@ export default class ChatEditor extends Component<ChatEditorProps, ChatEditorSta
|
||||
>
|
||||
<BaseTextArea
|
||||
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
|
||||
fontSize="1"
|
||||
fontSize={1}
|
||||
lineHeight="tall"
|
||||
rows={1}
|
||||
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
|
||||
@ -271,7 +281,7 @@ export default class ChatEditor extends Component<ChatEditorProps, ChatEditorSta
|
||||
this.messageChange(null, null, event.target.value)
|
||||
}
|
||||
onKeyDown={event =>
|
||||
this.messageChange(null, null, event.target.value)
|
||||
this.messageChange(null, null, (event.target as any).value)
|
||||
}
|
||||
ref={(input) => {
|
||||
if (!input)
|
||||
|
@ -29,7 +29,7 @@ interface ChatInputState {
|
||||
currentInput: string;
|
||||
}
|
||||
|
||||
class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
export class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
private chatEditor: React.RefObject<ChatEditor>;
|
||||
|
||||
constructor(props) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import { BaseImage, Box, Col, Icon, Row, Rule, Text } from '@tlon/indigo-react';
|
||||
import { Contact, Post } from '@urbit/api';
|
||||
import { Contact, MentionContent, Post } from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import moment from 'moment';
|
||||
import React, {
|
||||
Ref,
|
||||
useEffect,
|
||||
useMemo, useState
|
||||
} from 'react';
|
||||
@ -20,13 +21,13 @@ import useLocalState from '~/logic/state/local';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import ProfileOverlay from '~/views/components/ProfileOverlay';
|
||||
import { GraphContent} from '~/views/landscape/components/Graph/GraphContent';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
interface DayBreakProps {
|
||||
when: string;
|
||||
when: string | number;
|
||||
shimTop?: boolean;
|
||||
}
|
||||
|
||||
@ -55,7 +56,7 @@ export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
|
||||
);
|
||||
|
||||
export const UnreadMarker = React.forwardRef(
|
||||
({ dismissUnread }: any, ref) => {
|
||||
({ dismissUnread }: any, ref: Ref<HTMLDivElement>) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const idling = useIdlingState();
|
||||
|
||||
@ -113,7 +114,7 @@ const MessageActionItem = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const MessageActions = ({ api, onReply, onDelete, association, msg, isAdmin, permalink }) => {
|
||||
const MessageActions = ({ onReply, onDelete, msg, isAdmin, permalink }) => {
|
||||
const isOwn = () => msg.author === window.ship;
|
||||
const { doCopy, copyDisplay } = useCopy(permalink, 'Copy Message Link');
|
||||
|
||||
@ -205,14 +206,14 @@ interface ChatMessageProps {
|
||||
msg: Post;
|
||||
previousMsg?: Post;
|
||||
nextMsg?: Post;
|
||||
isLastRead: boolean;
|
||||
permalink: string;
|
||||
isLastRead?: boolean;
|
||||
permalink?: string;
|
||||
transcluded?: number;
|
||||
className?: string;
|
||||
isPending: boolean;
|
||||
isPending?: boolean;
|
||||
style?: unknown;
|
||||
isLastMessage?: boolean;
|
||||
dismissUnread: () => void;
|
||||
dismissUnread?: () => void;
|
||||
api: GlobalApi;
|
||||
highlighted?: boolean;
|
||||
renderSigil?: boolean;
|
||||
@ -220,27 +221,24 @@ interface ChatMessageProps {
|
||||
innerRef: (el: HTMLDivElement | null) => void;
|
||||
onReply?: (msg: Post) => void;
|
||||
showOurContact: boolean;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
function ChatMessage(props: ChatMessageProps) {
|
||||
let { highlighted } = props;
|
||||
const {
|
||||
msg,
|
||||
previousMsg,
|
||||
nextMsg,
|
||||
isLastRead,
|
||||
group,
|
||||
association,
|
||||
isLastRead = false,
|
||||
className = '',
|
||||
isPending,
|
||||
isPending = false,
|
||||
style,
|
||||
isLastMessage,
|
||||
api,
|
||||
showOurContact,
|
||||
fontSize,
|
||||
hideHover,
|
||||
dismissUnread,
|
||||
permalink
|
||||
dismissUnread = () => null,
|
||||
permalink = ''
|
||||
} = props;
|
||||
|
||||
if (typeof msg === 'string' || !msg) {
|
||||
@ -257,7 +255,7 @@ function ChatMessage(props: ChatMessageProps) {
|
||||
msg.number === 1
|
||||
);
|
||||
|
||||
const ourMention = msg?.contents?.some((e) => {
|
||||
const ourMention = msg?.contents?.some((e: MentionContent) => {
|
||||
return e?.mention && e?.mention === window.ship;
|
||||
});
|
||||
|
||||
@ -291,12 +289,10 @@ function ChatMessage(props: ChatMessageProps) {
|
||||
const messageProps = {
|
||||
msg,
|
||||
timestamp,
|
||||
association,
|
||||
isPending,
|
||||
showOurContact,
|
||||
api,
|
||||
highlighted,
|
||||
fontSize,
|
||||
hideHover,
|
||||
transcluded,
|
||||
onReply,
|
||||
@ -494,8 +490,9 @@ export const Message = React.memo(({
|
||||
position='absolute'
|
||||
width='36px'
|
||||
textAlign='right'
|
||||
left='0'
|
||||
top='3px'
|
||||
left={0}
|
||||
top='2px'
|
||||
lineHeight="tall"
|
||||
fontSize={0}
|
||||
gray
|
||||
>
|
||||
@ -527,10 +524,10 @@ export const MessagePlaceholder = ({
|
||||
}) => (
|
||||
<Box
|
||||
width='100%'
|
||||
fontSize='2'
|
||||
pl='3'
|
||||
pt='4'
|
||||
pr='3'
|
||||
fontSize={2}
|
||||
pl={3}
|
||||
pt={4}
|
||||
pr={3}
|
||||
display='flex'
|
||||
lineHeight='tall'
|
||||
className={className}
|
||||
@ -538,7 +535,7 @@ export const MessagePlaceholder = ({
|
||||
{...props}
|
||||
>
|
||||
<Box
|
||||
pr='3'
|
||||
pr={3}
|
||||
verticalAlign='top'
|
||||
backgroundColor='white'
|
||||
style={{ float: 'left' }}
|
||||
@ -561,20 +558,20 @@ export const MessagePlaceholder = ({
|
||||
>
|
||||
<Box
|
||||
className='hide-child'
|
||||
paddingTop='4'
|
||||
paddingTop={4}
|
||||
style={{ visibility: index % 5 == 0 ? 'initial' : 'hidden' }}
|
||||
>
|
||||
<Text
|
||||
display='inline-block'
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
fontSize={0}
|
||||
color='washedGray'
|
||||
cursor='default'
|
||||
>
|
||||
<Text maxWidth='32rem' display='block'>
|
||||
<Text
|
||||
backgroundColor='washedGray'
|
||||
borderRadius='2'
|
||||
borderRadius={2}
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
@ -585,12 +582,12 @@ export const MessagePlaceholder = ({
|
||||
display='inline-block'
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
washedGray
|
||||
fontSize={0}
|
||||
color='washedGray'
|
||||
>
|
||||
<Text
|
||||
background='washedGray'
|
||||
borderRadius='2'
|
||||
borderRadius={2}
|
||||
display='block'
|
||||
height='1em'
|
||||
style={{ width: `${((index % 3) + 1) * 3}em` }}
|
||||
@ -599,16 +596,16 @@ export const MessagePlaceholder = ({
|
||||
<Text
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
ml='2'
|
||||
washedGray
|
||||
borderRadius='2'
|
||||
fontSize={0}
|
||||
ml={2}
|
||||
color='washedGray'
|
||||
borderRadius={2}
|
||||
display={['none', 'inline-block']}
|
||||
className='child'
|
||||
>
|
||||
<Text
|
||||
backgroundColor='washedGray'
|
||||
borderRadius='2'
|
||||
borderRadius={2}
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
@ -618,7 +615,7 @@ export const MessagePlaceholder = ({
|
||||
<Text
|
||||
display='block'
|
||||
backgroundColor='washedGray'
|
||||
borderRadius='2'
|
||||
borderRadius={2}
|
||||
height='1em'
|
||||
style={{ width: `${(index % 5) * 20}%` }}
|
||||
></Text>
|
||||
|
@ -11,7 +11,7 @@ import useGraphState from '~/logic/state/graph';
|
||||
import ShareProfile from '~/views/apps/chat/components/ShareProfile';
|
||||
import { Loading } from '~/views/components/Loading';
|
||||
import SubmitDragger from '~/views/components/SubmitDragger';
|
||||
import ChatInput from './ChatInput';
|
||||
import ChatInput, { ChatInput as NakedChatInput } from './ChatInput';
|
||||
import ChatWindow from './ChatWindow';
|
||||
|
||||
interface ChatPaneProps {
|
||||
@ -82,14 +82,14 @@ export function ChatPane(props: ChatPaneProps): ReactElement {
|
||||
} = props;
|
||||
const graphTimesentMap = useGraphState(state => state.graphTimesentMap);
|
||||
const ourContact = useOurContact();
|
||||
const chatInput = useRef<ChatInput>();
|
||||
const chatInput = useRef<NakedChatInput>();
|
||||
|
||||
const onFileDrag = useCallback(
|
||||
(files: FileList | File[]) => {
|
||||
if (!chatInput.current) {
|
||||
return;
|
||||
}
|
||||
chatInput.current?.uploadFiles(files);
|
||||
(chatInput.current as NakedChatInput)?.uploadFiles(files);
|
||||
},
|
||||
[chatInput.current]
|
||||
);
|
||||
|
@ -16,7 +16,7 @@ type ChatWindowProps = {
|
||||
unreadCount: number;
|
||||
graph: Graph;
|
||||
graphSize: number;
|
||||
station: unknown;
|
||||
station?: unknown;
|
||||
fetchMessages: (newer: boolean) => Promise<boolean>;
|
||||
api: GlobalApi;
|
||||
scrollTo?: BigInteger;
|
||||
@ -36,6 +36,11 @@ interface ChatWindowState {
|
||||
unreadIndex: BigInteger;
|
||||
}
|
||||
|
||||
interface RendererProps {
|
||||
index: bigInt.BigInteger;
|
||||
scrollWindow: any;
|
||||
}
|
||||
|
||||
const virtScrollerStyle = { height: '100%' };
|
||||
|
||||
class ChatWindow extends Component<
|
||||
@ -99,11 +104,12 @@ class ChatWindow extends Component<
|
||||
});
|
||||
}
|
||||
|
||||
dismissedInitialUnread(): void {
|
||||
dismissedInitialUnread(): boolean {
|
||||
const { unreadCount, graph } = this.props;
|
||||
|
||||
return this.state.unreadIndex.eq(bigInt.zero) ? unreadCount > graph.size :
|
||||
this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero);
|
||||
return this.state.unreadIndex.eq(bigInt.zero)
|
||||
? unreadCount > graph.size
|
||||
: this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero);
|
||||
}
|
||||
|
||||
handleWindowBlur(): void {
|
||||
@ -173,7 +179,7 @@ class ChatWindow extends Component<
|
||||
}
|
||||
}
|
||||
|
||||
renderer = React.forwardRef(({ index, scrollWindow }, ref) => {
|
||||
renderer = React.forwardRef(({ index, scrollWindow }: RendererProps, ref) => {
|
||||
const {
|
||||
api,
|
||||
showOurContact,
|
||||
|
@ -22,16 +22,16 @@ const UnreadNotice = (props): ReactElement | null => {
|
||||
className='unread-notice'
|
||||
>
|
||||
<Center>
|
||||
<Box backgroundColor='white' borderRadius='3' overflow='hidden'>
|
||||
<Box backgroundColor='white' borderRadius={3} overflow='hidden'>
|
||||
<Box
|
||||
backgroundColor='washedBlue'
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
p='2'
|
||||
fontSize='0'
|
||||
p={2}
|
||||
fontSize={0}
|
||||
justifyContent='space-between'
|
||||
borderRadius='3'
|
||||
border='1'
|
||||
borderRadius={3}
|
||||
border={1}
|
||||
borderColor='lightBlue'
|
||||
>
|
||||
<Text
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Center, Text } from '@tlon/indigo-react';
|
||||
import { GraphConfig } from '@urbit/api';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -40,8 +41,8 @@ const GraphApp = (props: GraphAppProps): ReactElement => {
|
||||
|
||||
if(!graphKeys.has(resource)) {
|
||||
autoJoin();
|
||||
} else if(Boolean(association) && 'graph' in association.config) {
|
||||
history.push(`/~landscape/home/resource/${association.metadata.config.graph}${path}`);
|
||||
} else if(Boolean(association) && 'graph' in association.metadata.config) {
|
||||
history.push(`/~landscape/home/resource/${(association.metadata.config as GraphConfig).graph}${path}`);
|
||||
}
|
||||
return (
|
||||
<Center width="100%" height="100%">
|
||||
|
@ -6,13 +6,13 @@ import { Helmet } from 'react-helmet';
|
||||
import styled from 'styled-components';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import {
|
||||
hasTutorialGroup,
|
||||
hasTutorialGroup,
|
||||
|
||||
TUTORIAL_BOOK,
|
||||
TUTORIAL_CHAT, TUTORIAL_GROUP,
|
||||
TUTORIAL_HOST,
|
||||
TUTORIAL_BOOK,
|
||||
TUTORIAL_CHAT, TUTORIAL_GROUP,
|
||||
TUTORIAL_HOST,
|
||||
|
||||
TUTORIAL_LINKS
|
||||
TUTORIAL_LINKS
|
||||
} from '~/logic/lib/tutorialModal';
|
||||
import { useModal } from '~/logic/lib/useModal';
|
||||
import { useQuery } from '~/logic/lib/useQuery';
|
||||
@ -50,7 +50,7 @@ interface LaunchAppProps {
|
||||
|
||||
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
const { connection } = props;
|
||||
const baseHash = useLaunchState(state => state.baseHash);
|
||||
const { baseHash, runtimeLag } = useLaunchState(state => state);
|
||||
const [hashText, setHashText] = useState(baseHash);
|
||||
const [exitingTut, setExitingTut] = useState(false);
|
||||
const seen = useSettingsState(s => s?.tutorial?.seen) ?? true;
|
||||
@ -67,8 +67,8 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
const hashBox = (
|
||||
<Box
|
||||
position={['relative', 'absolute']}
|
||||
left="0"
|
||||
bottom="0"
|
||||
left={0}
|
||||
bottom={0}
|
||||
backgroundColor="white"
|
||||
ml={3}
|
||||
mb={3}
|
||||
@ -84,7 +84,10 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<Box backgroundColor="washedGray" p={2}>
|
||||
<Box
|
||||
backgroundColor={runtimeLag ? 'yellow' : 'washedGray'}
|
||||
p={2}
|
||||
>
|
||||
<Text mono bold>{hashText || baseHash}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
@ -120,28 +123,28 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
dismiss();
|
||||
};
|
||||
return exitingTut ? (
|
||||
<Col maxWidth="350px" p="3">
|
||||
<Col maxWidth="350px" p={3}>
|
||||
<Icon icon="Info" fill="black"></Icon>
|
||||
<Text my="3" lineHeight="tall">
|
||||
<Text my={3} lineHeight="tall">
|
||||
You can always restart the tutorial by typing “tutorial” in Leap
|
||||
</Text>
|
||||
<Row gapX="2" justifyContent="flex-end">
|
||||
<Row gapX={2} justifyContent="flex-end">
|
||||
<Button primary onClick={onDismiss}>Ok</Button>
|
||||
</Row>
|
||||
</Col>
|
||||
) : (
|
||||
<Col maxWidth="350px" p="3">
|
||||
<Col maxWidth="350px" p={3}>
|
||||
<Box position="absolute" left="-16px" top="-16px">
|
||||
<StarIcon width="32px" height="32px" color="blue" display="block" />
|
||||
</Box>
|
||||
<Text mb="3" lineHeight="tall" fontWeight="medium">Welcome</Text>
|
||||
<Text mb="3" lineHeight="tall">
|
||||
<Text mb={3} lineHeight="tall" fontWeight="medium">Welcome</Text>
|
||||
<Text mb={3} lineHeight="tall">
|
||||
You have been invited to use Landscape, an interface to chat
|
||||
and interact with communities
|
||||
<br />
|
||||
Would you like a tour of Landscape?
|
||||
</Text>
|
||||
<Row gapX="2" justifyContent="flex-end">
|
||||
<Row gapX={2} justifyContent="flex-end">
|
||||
<Button
|
||||
backgroundColor="washedGray"
|
||||
onClick={() => setExitingTut(true)}
|
||||
@ -181,7 +184,7 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
<ScrollbarLessBox height='100%' overflowY='scroll' display="flex" flexDirection="column">
|
||||
{modal}
|
||||
<Box
|
||||
mx='2'
|
||||
mx={2}
|
||||
display='grid'
|
||||
gridTemplateColumns='repeat(auto-fill, minmax(128px, 1fr))'
|
||||
gridGap={3}
|
||||
@ -208,7 +211,7 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
color="black"
|
||||
icon="Home"
|
||||
/>
|
||||
<Text ml="2" mt='1px' color="black">My Channels</Text>
|
||||
<Text ml={2} mt='1px' color="black">My Channels</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
</Tile>
|
||||
|
@ -47,8 +47,8 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const groups = Object.values(associations?.groups || {})
|
||||
.filter(e => e?.group in groupState)
|
||||
.sort(sortGroupsAlph);
|
||||
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||
const graphNotifications = getGraphNotifications(associations || {}, unreads);
|
||||
const graphUnreads = getGraphUnreads(associations || {} as Associations, unreads);
|
||||
const graphNotifications = getGraphNotifications(associations || {} as Associations, unreads);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -56,9 +56,9 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const path = group?.group;
|
||||
const unreadCount = graphUnreads(path);
|
||||
const notCount = graphNotifications(path);
|
||||
|
||||
return (
|
||||
<Group
|
||||
key={group.metadata.title}
|
||||
updates={notCount}
|
||||
first={index === 0}
|
||||
unreads={unreadCount}
|
||||
@ -82,7 +82,7 @@ interface GroupProps {
|
||||
const selectJoined = (s: SettingsState) => s.tutorial.joined;
|
||||
function Group(props: GroupProps) {
|
||||
const { path, title, unreads, updates, first = false } = props;
|
||||
const anchorRef = useRef<HTMLElement>(null);
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
const isTutorialGroup = path === `/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`;
|
||||
useTutorialModal(
|
||||
'start',
|
||||
@ -104,7 +104,7 @@ function Group(props: GroupProps) {
|
||||
(<Text>{days} day{days !== 1 && 's'} remaining</Text>)
|
||||
}
|
||||
{updates > 0 &&
|
||||
(<Text mt="1" color="blue">{updates} update{updates !== 1 && 's'} </Text>)
|
||||
(<Text mt={1} color="blue">{updates} update{updates !== 1 && 's'} </Text>)
|
||||
}
|
||||
{unreads > 0 &&
|
||||
(<Text color="lightGray">{unreads}</Text>)
|
||||
|
@ -1,17 +1,18 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import { WeatherState } from '~/types';
|
||||
import BasicTile from './tiles/basic';
|
||||
import ClockTile from './tiles/clock';
|
||||
import CustomTile from './tiles/custom';
|
||||
import WeatherTile from './tiles/weather';
|
||||
|
||||
interface TileProps {
|
||||
export interface TileProps {
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
const Tiles = (props: TileProps): ReactElement => {
|
||||
const weather = useLaunchState(state => state.weather);
|
||||
const weather = useLaunchState(state => state.weather) as WeatherState;
|
||||
const tileOrdering = useLaunchState(state => state.tileOrdering);
|
||||
const tileState = useLaunchState(state => state.tiles);
|
||||
const tiles = tileOrdering.filter((key) => {
|
||||
@ -26,7 +27,6 @@ const Tiles = (props: TileProps): ReactElement => {
|
||||
<BasicTile
|
||||
key={key}
|
||||
title={basic.title}
|
||||
iconUrl={basic.iconUrl}
|
||||
linkedUrl={basic.linkedUrl}
|
||||
/>
|
||||
);
|
||||
|
@ -21,7 +21,7 @@ const BasicTile = (props: BasicTileProps): ReactElement => (
|
||||
display='inline-block'
|
||||
verticalAlign='top'
|
||||
mt='5px'
|
||||
mr='2'
|
||||
mr={2}
|
||||
/>
|
||||
: null
|
||||
}{props.title}
|
||||
|
@ -120,7 +120,11 @@ const SvgArc = ({ start, end, ...rest }) => {
|
||||
return <path d={d} {...rest} />;
|
||||
};
|
||||
|
||||
class ClockText extends React.Component<ClockTextProps, ClockTextState> {
|
||||
interface ClockTextState {
|
||||
time: number;
|
||||
}
|
||||
|
||||
class ClockText extends React.Component<{}, ClockTextState> {
|
||||
interval?: NodeJS.Timeout;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -176,7 +180,34 @@ class ClockText extends React.Component<ClockTextProps, ClockTextState> {
|
||||
}
|
||||
}
|
||||
|
||||
class Clock extends React.PureComponent {
|
||||
interface ClockProps {
|
||||
data: {
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface ClockState {
|
||||
time: number;
|
||||
lat: number;
|
||||
lon: number;
|
||||
geolocationSuccess: boolean;
|
||||
sunrise: number;
|
||||
sunsetStart: number;
|
||||
sunset: number;
|
||||
sunriseEnd: number;
|
||||
dusk: number;
|
||||
dawn: number;
|
||||
night: number;
|
||||
nightEnd: number;
|
||||
nauticalDawn: number;
|
||||
nauticalDusk: number;
|
||||
}
|
||||
|
||||
class Clock extends React.PureComponent<ClockProps, ClockState> {
|
||||
angle: number;
|
||||
referenceTime: moment.Moment;
|
||||
interval: NodeJS.Timeout;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.angle = 0;
|
||||
@ -188,7 +219,7 @@ class Clock extends React.PureComponent {
|
||||
geolocationSuccess: false,
|
||||
sunrise: 0,
|
||||
sunsetStart: 0,
|
||||
sunset:0,
|
||||
sunset: 0,
|
||||
sunriseEnd: 0,
|
||||
dusk: 0,
|
||||
dawn: 0,
|
||||
|
@ -13,7 +13,7 @@ export default class CustomTile extends React.PureComponent {
|
||||
backgroundColor='white'
|
||||
border='1px solid'
|
||||
borderColor='lightGray'
|
||||
borderRadius='2'
|
||||
borderRadius={2}
|
||||
>
|
||||
<BaseImage
|
||||
position='absolute'
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '@tlon/indigo-react';
|
||||
import React, { RefObject } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { PaddingProps } from 'styled-system';
|
||||
import defaultApps from '~/logic/lib/default-apps';
|
||||
|
||||
const SquareBox = styled(Box)`
|
||||
@ -20,7 +21,17 @@ const SquareBox = styled(Box)`
|
||||
`;
|
||||
const routeList = defaultApps.map(a => `/~${a}`);
|
||||
|
||||
const Tile = React.forwardRef((props, ref) => {
|
||||
type TileProps = BoxProps & {
|
||||
bg?: string;
|
||||
to?: string;
|
||||
href?: string;
|
||||
p?: PaddingProps;
|
||||
children: any;
|
||||
gridColumnStart?: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const Tile = React.forwardRef((props: TileProps, ref: RefObject<HTMLDivElement>) => {
|
||||
const { bg, to, href, p, boxShadow, gridColumnStart, ...rest } = props;
|
||||
|
||||
let childElement = (
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { BaseInput, Box, Icon, Text } from '@tlon/indigo-react';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import withState from '~/logic/lib/withState';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import ErrorBoundary from '~/views/components/ErrorBoundary';
|
||||
@ -36,8 +37,20 @@ const imperialCountries = [
|
||||
'Liberia',
|
||||
];
|
||||
|
||||
class WeatherTile extends React.Component {
|
||||
constructor(props) {
|
||||
interface WeatherTileProps {
|
||||
weather: any;
|
||||
api: GlobalApi;
|
||||
location: string;
|
||||
}
|
||||
|
||||
interface WeatherTileState {
|
||||
location: string;
|
||||
manualEntry: boolean;
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
class WeatherTile extends React.Component<WeatherTileProps, WeatherTileState> {
|
||||
constructor(props: WeatherTileProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
location: '',
|
||||
@ -50,11 +63,7 @@ class WeatherTile extends React.Component {
|
||||
locationSubmit() {
|
||||
navigator.geolocation.getCurrentPosition((res) => {
|
||||
const location = `${res.coords.latitude},${res.coords.longitude}`;
|
||||
this.setState({
|
||||
location
|
||||
}, (err) => {
|
||||
console.log(err);
|
||||
}, { maximumAge: Infinity, timeout: 10000 });
|
||||
this.setState({ location });
|
||||
this.props.api.launch.weather(location);
|
||||
this.setState({ manualEntry: !this.state.manualEntry });
|
||||
});
|
||||
@ -62,10 +71,8 @@ class WeatherTile extends React.Component {
|
||||
|
||||
manualLocationSubmit(event) {
|
||||
event.preventDefault();
|
||||
const location = document.getElementById('location').value;
|
||||
this.setState({ location }, (err) => {
|
||||
console.log(err);
|
||||
}, { maximumAge: Infinity, timeout: 10000 });
|
||||
const location = (document.getElementById('location') as HTMLInputElement).value;
|
||||
this.setState({ location });
|
||||
this.props.api.launch.weather(location);
|
||||
this.setState({ manualEntry: !this.state.manualEntry });
|
||||
}
|
||||
@ -91,7 +98,7 @@ class WeatherTile extends React.Component {
|
||||
let secureCheck;
|
||||
let error;
|
||||
if (this.state.error === true) {
|
||||
error = <Text display='block' color='red' pt='1'>Please try again.</Text>;
|
||||
error = <Text display='block' color='red' pt={1}>Please try again.</Text>;
|
||||
}
|
||||
if (location.protocol === 'https:') {
|
||||
secureCheck = (
|
||||
@ -128,15 +135,15 @@ class WeatherTile extends React.Component {
|
||||
{locationName ? ` Current location is near ${locationName}.` : ''}
|
||||
</Text>
|
||||
{error}
|
||||
<Box mt='auto' display='flex' marginBlockEnd='0'>
|
||||
<Box mt='auto' display='flex' marginBlockEnd={0}>
|
||||
<BaseInput
|
||||
id="location"
|
||||
size="10"
|
||||
size={10}
|
||||
width='100%'
|
||||
color='black'
|
||||
fontSize='0'
|
||||
fontSize={0}
|
||||
backgroundColor='transparent'
|
||||
border='0'
|
||||
border={0}
|
||||
type="text"
|
||||
autoFocus
|
||||
placeholder="GPS, ZIP, City"
|
||||
@ -150,10 +157,10 @@ class WeatherTile extends React.Component {
|
||||
backgroundColor='transparent'
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
flexShrink='0'
|
||||
pl='1'
|
||||
fontSize='0'
|
||||
border='0'
|
||||
flexShrink={0}
|
||||
pl={1}
|
||||
fontSize={0}
|
||||
border={0}
|
||||
type="submit"
|
||||
onClick={this.manualLocationSubmit.bind(this)}
|
||||
value="->"
|
||||
@ -173,7 +180,7 @@ class WeatherTile extends React.Component {
|
||||
onClick={() => this.setState({ manualEntry: !this.state.manualEntry })}
|
||||
>
|
||||
<Box>
|
||||
<Icon icon='Weather' display='inline-block' verticalAlign='top' mt='3px' mr='2' />
|
||||
<Icon icon='Weather' display='inline-block' verticalAlign='top' mt='3px' mr={2} />
|
||||
<Text>Weather</Text>
|
||||
</Box>
|
||||
<Text style={{ cursor: 'pointer' }}>
|
||||
@ -219,7 +226,7 @@ class WeatherTile extends React.Component {
|
||||
title={`${locationName} Weather`}
|
||||
>
|
||||
<Text>
|
||||
<Icon icon='Weather' display='inline' mr='2' style={{ position: 'relative', top: '.3em' }} />
|
||||
<Icon icon='Weather' display='inline' mr={2} style={{ position: 'relative', top: '.3em' }} />
|
||||
<Text
|
||||
cursor='pointer'
|
||||
onClick={() =>
|
||||
@ -269,7 +276,7 @@ class WeatherTile extends React.Component {
|
||||
flexDirection="column"
|
||||
justifyContent="flex-start"
|
||||
>
|
||||
<Text><Icon icon='Weather' color='black' display='inline' mr='2' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
|
||||
<Text><Icon icon='Weather' color='black' display='inline' mr={2} style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
|
||||
<Text width='100%' display='flex' flexDirection='column' mt={1}>
|
||||
Loading, please check again later...
|
||||
</Text>
|
||||
|
@ -125,7 +125,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
api={api}
|
||||
editCommentId={editCommentId}
|
||||
history={props.history}
|
||||
baseUrl={`${resourceUrl}/${props.match.params.index}`}
|
||||
baseUrl={`${resourceUrl}/index/${props.match.params.index}`}
|
||||
group={group}
|
||||
px={3}
|
||||
/>
|
||||
|
@ -2,7 +2,7 @@ import { Box, Col, Text } from '@tlon/indigo-react';
|
||||
import { Association, Graph, Group } from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import React, {
|
||||
Component
|
||||
Component, ReactNode
|
||||
} from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { isWriter } from '~/logic/lib/group';
|
||||
@ -31,6 +31,11 @@ const style = {
|
||||
alignItems: 'center'
|
||||
};
|
||||
|
||||
interface RendererProps {
|
||||
index: BigInteger;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
fetchLinks = async () => true;
|
||||
|
||||
@ -39,7 +44,7 @@ class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
return isWriter(group, association.resource);
|
||||
}
|
||||
|
||||
renderItem = React.forwardRef<HTMLDivElement>(({ index, scrollWindow }, ref) => {
|
||||
renderItem = React.forwardRef<HTMLDivElement>(({ index }: RendererProps, ref) => {
|
||||
const { props } = this;
|
||||
const { association, graph, api } = props;
|
||||
const [, , ship, name] = association.resource.split('/');
|
||||
@ -60,7 +65,7 @@ class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
ref={ref}
|
||||
key={index.toString()}
|
||||
mx="auto"
|
||||
mt="4"
|
||||
mt={4}
|
||||
maxWidth="768px"
|
||||
width="100%"
|
||||
flexShrink={0}
|
||||
@ -82,7 +87,7 @@ class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
}
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<LinkItem key={index.toString()} {...linkProps} />;
|
||||
<LinkItem key={index.toString()} {...linkProps} />
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
@ -96,7 +101,7 @@ class LinkWindow extends Component<LinkWindowProps, {}> {
|
||||
<Col
|
||||
key={0}
|
||||
mx="auto"
|
||||
mt="4"
|
||||
mt={4}
|
||||
maxWidth="768px"
|
||||
width="100%"
|
||||
flexShrink={0}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Action, Anchor, Box, Col, Icon, Row, Rule, Text } from '@tlon/indigo-react';
|
||||
import { Association, GraphNode, Group } from '@urbit/api';
|
||||
import React, { ReactElement, useCallback, useEffect, useRef } from 'react';
|
||||
import { Association, GraphNode, Group, TextContent, UrlContent } from '@urbit/api';
|
||||
import React, { ReactElement, RefObject, useCallback, useEffect, useRef } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
@ -15,8 +15,13 @@ import { PermalinkEmbed } from '../../permalinks/embed';
|
||||
interface LinkItemProps {
|
||||
node: GraphNode;
|
||||
association: Association;
|
||||
resource: string; api: GlobalApi; group: Group; path: string; }
|
||||
export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactElement => {
|
||||
resource: string;
|
||||
api: GlobalApi;
|
||||
group: Group;
|
||||
path: string;
|
||||
baseUrl: string;
|
||||
}
|
||||
export const LinkItem = React.forwardRef((props: LinkItemProps, ref: RefObject<HTMLDivElement>): ReactElement => {
|
||||
const {
|
||||
association,
|
||||
node,
|
||||
@ -61,7 +66,7 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
|
||||
|
||||
const author = node.post.author;
|
||||
const size = node.children ? node.children.size : 0;
|
||||
const contents = node.post.contents;
|
||||
const contents = node.post.contents as [TextContent, UrlContent];
|
||||
const hostname = URLparser.exec(contents[1].url) ? URLparser.exec(contents[1].url)[4] : null;
|
||||
const href = URLparser.exec(contents[1].url) ? contents[1].url : `http://${contents[1].url}`;
|
||||
|
||||
@ -130,8 +135,8 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
|
||||
<>
|
||||
<RemoteContent
|
||||
ref={(r) => {
|
||||
remoteRef.current = r;
|
||||
}}
|
||||
remoteRef.current = r;
|
||||
}}
|
||||
renderUrl={false}
|
||||
url={href}
|
||||
text={contents[0].text}
|
||||
@ -166,14 +171,14 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||
<Row minWidth={0} flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||
<Author
|
||||
showImage
|
||||
isRelativeTime
|
||||
ship={author}
|
||||
date={node.post['time-sent']}
|
||||
group={group}
|
||||
lineHeight="1"
|
||||
lineHeight={1}
|
||||
/>
|
||||
<Box ml="auto">
|
||||
<Link
|
||||
@ -208,7 +213,7 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactEleme
|
||||
</Col>
|
||||
}
|
||||
>
|
||||
<Icon ml="2" display="block" icon="Ellipsis" color="gray" />
|
||||
<Icon ml={2} display="block" icon="Ellipsis" color="gray" />
|
||||
</Dropdown>
|
||||
|
||||
</Row>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Anchor, Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
|
||||
import { Association, Group, Post } from '@urbit/api';
|
||||
import { Association, GraphNotificationContents, GraphNotifIndex, Post } from '@urbit/api';
|
||||
import { BigInteger } from 'big-integer';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
@ -15,9 +15,6 @@ import {
|
||||
useAssocForGraph,
|
||||
useAssocForGroup
|
||||
} from '~/logic/state/metadata';
|
||||
import {
|
||||
GraphNotificationContents, GraphNotifIndex
|
||||
} from '~/types';
|
||||
import Author from '~/views/components/Author';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
import { PermalinkEmbed } from '../permalinks/embed';
|
||||
@ -84,9 +81,9 @@ const GraphUrl = ({ contents, api }) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box borderRadius="2" p="2" bg="scales.black05">
|
||||
<Box borderRadius={2} p={2} bg="scales.black05">
|
||||
<Anchor underline={false} target="_blank" color="black" href={link.url}>
|
||||
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
||||
<Icon verticalAlign="bottom" mr={2} icon="ArrowExternal" />
|
||||
{text}
|
||||
</Anchor>
|
||||
</Box>
|
||||
@ -97,17 +94,17 @@ function ContentSummary({ icon, name, author, to }) {
|
||||
return (
|
||||
<Link to={to}>
|
||||
<Col
|
||||
gapY="1"
|
||||
gapY={1}
|
||||
flexDirection={['column', 'row']}
|
||||
alignItems={['flex-start', 'center']}
|
||||
>
|
||||
<Row
|
||||
alignItems="center"
|
||||
gapX="2"
|
||||
p="1"
|
||||
gapX={2}
|
||||
p={1}
|
||||
width="fit-content"
|
||||
borderRadius="2"
|
||||
border="1"
|
||||
borderRadius={2}
|
||||
border={1}
|
||||
borderColor="lightGray"
|
||||
>
|
||||
<Icon display="block" icon={icon} />
|
||||
@ -116,7 +113,7 @@ function ContentSummary({ icon, name, author, to }) {
|
||||
</Text>
|
||||
</Row>
|
||||
<Row ml={[0, 1]} alignItems="center">
|
||||
<Text lineHeight="1" fontWeight="medium" mr="1">
|
||||
<Text lineHeight={1} fontWeight="medium" mr={1}>
|
||||
by
|
||||
</Text>
|
||||
<Author
|
||||
@ -193,11 +190,7 @@ interface PostsByAuthor {
|
||||
}
|
||||
const GraphNodes = (props: {
|
||||
posts: Post[];
|
||||
graph: string;
|
||||
hideAuthors?: boolean;
|
||||
group?: Group;
|
||||
groupPath: string;
|
||||
description: string;
|
||||
index: string;
|
||||
mod: string;
|
||||
association: Association;
|
||||
@ -208,7 +201,6 @@ const GraphNodes = (props: {
|
||||
mod,
|
||||
hidden,
|
||||
index,
|
||||
description,
|
||||
hideAuthors = false,
|
||||
association
|
||||
} = props;
|
||||
@ -242,7 +234,7 @@ const GraphNodes = (props: {
|
||||
date={time}
|
||||
/>
|
||||
)}
|
||||
<Col gapY="2" py={hideAuthors ? 0 : 2} width="100%">
|
||||
<Col gapY={2} py={hideAuthors ? 0 : 2} width="100%">
|
||||
{_.map(posts, post => (
|
||||
<GraphNodeContent
|
||||
key={post.index}
|
||||
@ -328,18 +320,17 @@ export function GraphNotification(props: {
|
||||
groupTitle={groupTitle}
|
||||
content
|
||||
/>
|
||||
<Col onClick={onClick} gapY="2" flexGrow={1} width="100%" gridArea="main">
|
||||
<Col onClick={onClick} gapY={2} flexGrow={1} width="100%" gridArea="main">
|
||||
<GraphNodes
|
||||
hideAuthors={hideAuthors}
|
||||
posts={contents.slice(0, 4)}
|
||||
mod={index.module}
|
||||
description={index.description}
|
||||
index={contents?.[0].index}
|
||||
association={association}
|
||||
hidden={groups[association?.group]?.hidden}
|
||||
/>
|
||||
{contents.length > 4 && (
|
||||
<Text mb="2" gray>
|
||||
<Text mb={2} gray>
|
||||
+ {contents.length - 4} more
|
||||
</Text>
|
||||
)}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
GroupNotifIndex,
|
||||
GroupUpdate
|
||||
} from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import _ from 'lodash';
|
||||
import React, { ReactElement } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -37,7 +38,7 @@ interface GroupNotificationProps {
|
||||
archived: boolean;
|
||||
read: boolean;
|
||||
time: number;
|
||||
timebox: BigInteger;
|
||||
timebox: bigInt.BigInteger;
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ export function Header(
|
||||
return (
|
||||
<Row
|
||||
flexDirection={['column-reverse', 'row']}
|
||||
minHeight="4"
|
||||
minHeight={4}
|
||||
mb={content ? 2 : 0}
|
||||
onClick={props.onClick}
|
||||
flexWrap="wrap"
|
||||
@ -40,7 +40,7 @@ export function Header(
|
||||
gridArea="header"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Row gapX="1" overflow="hidden" alignItems="center">
|
||||
<Row gapX={1} overflow="hidden" alignItems="center">
|
||||
{authors.length > 0 && (
|
||||
<>
|
||||
<Author
|
||||
@ -58,15 +58,15 @@ export function Header(
|
||||
</>
|
||||
)}
|
||||
<Box whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
|
||||
<Text lineHeight="tall" mr="1">
|
||||
<Text lineHeight="tall" mr={1}>
|
||||
{description} {channelTitle}
|
||||
</Text>
|
||||
</Box>
|
||||
</Row>
|
||||
<Row ml={[0, 1]} mb={[1, 0]} gapX="1" alignItems="center">
|
||||
<Row ml={[0, 1]} mb={[1, 0]} gapX={1} alignItems="center">
|
||||
{groupTitle && (
|
||||
<>
|
||||
<Text lineHeight="tall" fontSize="1" gray>
|
||||
<Text lineHeight="tall" fontSize={1} gray>
|
||||
{groupTitle}
|
||||
</Text>
|
||||
<Dot color="gray" />
|
||||
@ -75,7 +75,7 @@ export function Header(
|
||||
{time && (
|
||||
<Timestamp
|
||||
lineHeight="tall"
|
||||
fontSize="1"
|
||||
fontSize={1}
|
||||
relative
|
||||
stamp={moment(time)}
|
||||
color="gray"
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Box, Center, Col, LoadingSpinner, Text } from '@tlon/indigo-react';
|
||||
import { Box, Center, Col, LoadingSpinner, Text, Icon } from '@tlon/indigo-react';
|
||||
import {
|
||||
IndexedNotification,
|
||||
IndexedNotification,
|
||||
|
||||
JoinRequests, Notifications,
|
||||
JoinRequests, Notifications,
|
||||
|
||||
Timebox
|
||||
Timebox
|
||||
} from '@urbit/api';
|
||||
import { BigInteger } from 'big-integer';
|
||||
import _ from 'lodash';
|
||||
@ -14,6 +14,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { getNotificationKey } from '~/logic/lib/hark';
|
||||
import { useLazyScroll } from '~/logic/lib/useLazyScroll';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import { daToUnix, MOMENT_CALENDAR_DATE } from '~/logic/lib/util';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import { Invites } from './invites';
|
||||
@ -57,6 +58,8 @@ export default function Inbox(props: {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const runtimeLag = useLaunchState(state => state.runtimeLag);
|
||||
|
||||
const ready = useHarkState(
|
||||
s => Object.keys(s.unreads.graph).length > 0
|
||||
);
|
||||
@ -113,7 +116,15 @@ export default function Inbox(props: {
|
||||
);
|
||||
|
||||
return (
|
||||
<Col p="1" ref={scrollRef} position="relative" height="100%" overflowY="auto" overflowX="hidden">
|
||||
<Col p={1} ref={scrollRef} position="relative" height="100%" overflowY="auto" overflowX="hidden">
|
||||
{runtimeLag && (
|
||||
<Box bg="yellow" borderRadius={2} p={2} m={2}>
|
||||
<Icon verticalAlign="middle" mr={2} icon="Tutorial" />
|
||||
<Text verticalAlign="middle">
|
||||
Update your binary to continue receiving updates.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Invites pendingJoin={props.pendingJoin} api={api} />
|
||||
{[...notificationsByDayMap.keys()].sort().reverse().map((day, index) => {
|
||||
const timeboxes = notificationsByDayMap.get(day)!;
|
||||
@ -129,15 +140,15 @@ export default function Inbox(props: {
|
||||
);
|
||||
})}
|
||||
{isDone ? (
|
||||
<Center mt="2" borderTop={notifications.length !== 0 ? 1 : 0} borderTopColor="lightGray" width="100%" height="96px">
|
||||
<Text gray fontSize="1">No more notifications</Text>
|
||||
<Center mt={2} borderTop={notifications.length !== 0 ? 1 : 0} borderTopColor="lightGray" width="100%" height="96px">
|
||||
<Text gray fontSize={1}>No more notifications</Text>
|
||||
</Center>
|
||||
) : isLoading ? (
|
||||
<Center mt="2" borderTop={notifications.length !== 0 ? 1 : 0} borderTopColor="lightGray" width="100%" height="96px">
|
||||
<Center mt={2} borderTop={notifications.length !== 0 ? 1 : 0} borderTopColor="lightGray" width="100%" height="96px">
|
||||
<LoadingSpinner />
|
||||
</Center>
|
||||
) : (
|
||||
<Box mt="2" height="96px" />
|
||||
<Box mt={2} height="96px" />
|
||||
)}
|
||||
|
||||
</Col>
|
||||
@ -172,7 +183,7 @@ function DaySection({
|
||||
{_.map(timeboxes.sort(sortTimeboxes), ([date, nots], i: number) =>
|
||||
_.map(nots.sort(sortIndexedNotification), (not, j: number) => (
|
||||
<Notification
|
||||
key={getNotificationKey(time, not)}
|
||||
key={getNotificationKey(date, not)}
|
||||
api={api}
|
||||
notification={not}
|
||||
archived={archive}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Box, Row, SegmentedProgressBar, Text } from '@tlon/indigo-react';
|
||||
import {
|
||||
joinError, joinProgress,
|
||||
joinError, joinProgress,
|
||||
|
||||
JoinRequest
|
||||
JoinRequest
|
||||
} from '@urbit/api';
|
||||
import React, { useCallback } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -34,8 +34,8 @@ export function JoiningStatus(props: JoiningStatusProps) {
|
||||
<Row
|
||||
display={['flex-column', 'flex']}
|
||||
alignItems="center"
|
||||
px="4"
|
||||
gapX="4"
|
||||
px={4}
|
||||
gapX={4}
|
||||
>
|
||||
<Box flexGrow={1} maxWidth="400px">
|
||||
<SegmentedProgressBar current={current + 1} segments={3} />
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { Metadata } from '@urbit/api';
|
||||
import React from 'react';
|
||||
import { Header } from './header';
|
||||
import { MetadataBody, NotificationProps } from './types';
|
||||
|
||||
function getInvolvedUsers(body: MetadataBody) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function getDescription(body: MetadataBody) {
|
||||
function getDescription(body: { metadata: Metadata}) {
|
||||
const b = body.metadata;
|
||||
if ('new' in b) {
|
||||
return 'created';
|
||||
@ -24,12 +20,12 @@ function getDescription(body: MetadataBody) {
|
||||
}
|
||||
}
|
||||
|
||||
export function MetadataNotification(props: NotificationProps<'metadata'>) {
|
||||
export function MetadataNotification(props: any) {
|
||||
const { unread } = props;
|
||||
const description = getDescription(unread.unreads[0].body);
|
||||
|
||||
return (
|
||||
<Box p="2">
|
||||
<Box p={2}>
|
||||
<Header
|
||||
authors={[]}
|
||||
description={description}
|
||||
|
@ -22,7 +22,7 @@ import { SwipeMenu } from '~/views/components/SwipeMenu';
|
||||
import { GraphNotification } from './graph';
|
||||
import { GroupNotification } from './group';
|
||||
|
||||
interface NotificationProps {
|
||||
export interface NotificationProps {
|
||||
notification: IndexedNotification;
|
||||
time: BigInteger;
|
||||
api: GlobalApi;
|
||||
@ -40,7 +40,7 @@ function getMuted(
|
||||
if (!('graph' in notification.contents)) {
|
||||
throw new Error();
|
||||
}
|
||||
const parent = getParentIndex(index.graph, notification.contents.graph);
|
||||
const parent = getParentIndex(idxNotif.index.graph, notification.contents.graph);
|
||||
|
||||
return (
|
||||
_.findIndex(
|
||||
@ -102,7 +102,7 @@ export function NotificationWrapper(props: {
|
||||
menuWidth={100}
|
||||
disabled={!isMobile}
|
||||
menu={
|
||||
<Button onClick={onArchive} ml="2" height="100%" width="92px" primary destructive>
|
||||
<Button onClick={onArchive} ml={2} height="100%" width="92px" primary destructive>
|
||||
Remove
|
||||
</Button>
|
||||
}
|
||||
@ -125,7 +125,7 @@ export function NotificationWrapper(props: {
|
||||
{children}
|
||||
<Row
|
||||
alignItems="flex-start"
|
||||
gapX="2"
|
||||
gapX={2}
|
||||
gridArea="actions"
|
||||
justifyContent="flex-end"
|
||||
opacity={[0, hovering ? 1 : 0]}
|
||||
@ -147,7 +147,7 @@ export function NotificationWrapper(props: {
|
||||
}
|
||||
|
||||
export function Notification(props: NotificationProps) {
|
||||
const { notification, associations, archived } = props;
|
||||
const { notification, archived } = props;
|
||||
const { read, contents, time } = notification.notification;
|
||||
|
||||
const wrapperProps = {
|
||||
|
@ -23,7 +23,7 @@ const HeaderLink = React.forwardRef((
|
||||
|
||||
return (
|
||||
<Link to={to}>
|
||||
<Text ref={ref} px="2" {...textProps} gray={!active} />
|
||||
<Text ref={ref} px={2} {...textProps} gray={!active} />
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
@ -67,21 +67,21 @@ export default function NotificationsScreen(props: any): ReactElement {
|
||||
<Body>
|
||||
<Col overflowY="hidden" height="100%">
|
||||
<Row
|
||||
p="3"
|
||||
p={3}
|
||||
alignItems="center"
|
||||
height="48px"
|
||||
justifyContent="space-between"
|
||||
width="100%"
|
||||
borderBottom="1"
|
||||
borderBottom={1}
|
||||
borderBottomColor="lightGray"
|
||||
>
|
||||
|
||||
<Text fontWeight="bold" fontSize="2" lineHeight="1" ref={anchorRef}>
|
||||
<Text fontWeight="bold" fontSize={2} lineHeight={1} ref={anchorRef}>
|
||||
Notifications
|
||||
</Text>
|
||||
<Row
|
||||
justifyContent="space-between"
|
||||
gapX="3"
|
||||
gapX={3}
|
||||
>
|
||||
<StatelessAsyncAction
|
||||
overflow="hidden"
|
||||
@ -93,7 +93,7 @@ export default function NotificationsScreen(props: any): ReactElement {
|
||||
</StatelessAsyncAction>
|
||||
<Link to="/~settings#notifications">
|
||||
<Box>
|
||||
<Icon lineHeight="1" icon="Adjust" />
|
||||
<Icon lineHeight={1} icon="Adjust" />
|
||||
</Box>
|
||||
</Link>
|
||||
</Row>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Anchor, Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
|
||||
import { Association, GraphNode, Group, Post } from '@urbit/api';
|
||||
import { Association, GraphConfig, GraphNode, Group, Post, ReferenceContent, TextContent, UrlContent } from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import React from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -38,7 +38,7 @@ function TranscludedLinkNode(props: {
|
||||
|
||||
switch (idx.length) {
|
||||
case 1:
|
||||
const [{ text }, link] = node.post.contents;
|
||||
const [{ text }, link] = node.post.contents as [TextContent, UrlContent | ReferenceContent];
|
||||
if('reference' in link) {
|
||||
const permalink = referenceToPermalink(link).link;
|
||||
return <PermalinkEmbed transcluded={transcluded + 1} api={api} link={permalink} association={assoc} />;
|
||||
@ -76,6 +76,7 @@ function TranscludedLinkNode(props: {
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<TranscludedComment
|
||||
@ -170,7 +171,7 @@ function TranscludedPublishNode(props: {
|
||||
?.get(bigInt.one)
|
||||
?.children?.peekLargest()?.[1]!;
|
||||
return (
|
||||
<Col color="black" gapY="2">
|
||||
<Col color="black" gapY={2}>
|
||||
<Author
|
||||
pl='12px'
|
||||
pt='12px'
|
||||
@ -182,7 +183,7 @@ function TranscludedPublishNode(props: {
|
||||
group={group}
|
||||
/>
|
||||
<Text pl='44px' fontSize="2" fontWeight="medium">
|
||||
{post.post.contents[0]?.text}
|
||||
{(post.post.contents[0] as TextContent)?.text}
|
||||
</Text>
|
||||
<Box pl="44px" pr='3'>
|
||||
<NotePreviewContent
|
||||
@ -267,7 +268,7 @@ export function TranscludedNode(props: {
|
||||
api: GlobalApi;
|
||||
showOurContact?: boolean;
|
||||
}) {
|
||||
const { node, showOurContact, assoc, transcluded } = props;
|
||||
const { node, showOurContact, assoc, transcluded, api } = props;
|
||||
const group = useGroupForAssoc(assoc)!;
|
||||
|
||||
if (
|
||||
@ -287,23 +288,19 @@ export function TranscludedNode(props: {
|
||||
);
|
||||
}
|
||||
|
||||
switch (assoc.metadata.config.graph) {
|
||||
switch ((assoc.metadata.config as GraphConfig).graph) {
|
||||
case 'chat':
|
||||
return (
|
||||
<Row width="100%" flexShrink={0} flexGrow={1} flexWrap="wrap">
|
||||
<ChatMessage
|
||||
width="100%"
|
||||
renderSigil
|
||||
transcluded={transcluded + 1}
|
||||
containerClass="items-top cf hide-child"
|
||||
className="items-top cf hide-child"
|
||||
association={assoc}
|
||||
group={group}
|
||||
groups={{}}
|
||||
msg={node.post}
|
||||
fontSize="0"
|
||||
ml="0"
|
||||
mr="0"
|
||||
fontSize={0}
|
||||
showOurContact={showOurContact}
|
||||
api={api}
|
||||
mt='0'
|
||||
/>
|
||||
</Row>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Association } from '@urbit/api';
|
||||
import { Association, GraphConfig } from '@urbit/api';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Redirect, Route, Switch
|
||||
@ -82,7 +82,7 @@ function GroupRoutes(props: { group: string; url: string }) {
|
||||
return <Redirect
|
||||
to={toQuery(
|
||||
{ auto: 'y', redir: location.pathname },
|
||||
`${groupUrl}/join/${association.metadata.config.graph}${path}`
|
||||
`${groupUrl}/join/${(association.metadata.config as GraphConfig).graph}${path}`
|
||||
)}
|
||||
/>;
|
||||
}
|
||||
|
@ -1,27 +1,25 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useHistory, useLocation, Link } from 'react-router-dom';
|
||||
import {
|
||||
BaseAnchor, Box,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Center, Col, Icon, Row, Text
|
||||
} from "@tlon/indigo-react";
|
||||
import { Association, GraphNode, resourceFromPath } from '@urbit/api';
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import {
|
||||
getPermalinkForGraph, GraphPermalink as IGraphPermalink, parsePermalink
|
||||
} from '~/logic/lib/permalinks';
|
||||
import {
|
||||
Action,
|
||||
Box,
|
||||
Text,
|
||||
BaseAnchor,
|
||||
Row,
|
||||
Icon,
|
||||
Col,
|
||||
Center
|
||||
} from "@tlon/indigo-react";
|
||||
import { GroupLink } from "~/views/components/GroupLink";
|
||||
import { getModuleIcon } from "~/logic/lib/util";
|
||||
import useMetadataState from "~/logic/state/metadata";
|
||||
import { useVirtualResizeProp } from "~/logic/lib/virtualContext";
|
||||
import useGraphState from "~/logic/state/graph";
|
||||
import { GraphNodeContent } from "../notifications/graph";
|
||||
import useMetadataState from "~/logic/state/metadata";
|
||||
import { GroupLink } from "~/views/components/GroupLink";
|
||||
import { TranscludedNode } from "./TranscludedNode";
|
||||
import {useVirtualResizeProp} from "~/logic/lib/virtualContext";
|
||||
|
||||
function GroupPermalink(props: { group: string; api: GlobalApi }) {
|
||||
const { group, api } = props;
|
||||
@ -29,9 +27,9 @@ function GroupPermalink(props: { group: string; api: GlobalApi }) {
|
||||
<GroupLink
|
||||
resource={group}
|
||||
api={api}
|
||||
pl="2"
|
||||
border="1"
|
||||
borderRadius="2"
|
||||
pl={2}
|
||||
border={1}
|
||||
borderRadius={2}
|
||||
borderColor="lightGray"
|
||||
/>
|
||||
);
|
||||
@ -112,7 +110,7 @@ function GraphPermalink(
|
||||
maxWidth={full ? null : "500px"}
|
||||
border={full ? null : "1"}
|
||||
borderColor="lightGray"
|
||||
borderRadius="2"
|
||||
borderRadius={2}
|
||||
cursor="pointer"
|
||||
onClick={(e) => {
|
||||
navigate(e);
|
||||
@ -140,7 +138,7 @@ function GraphPermalink(
|
||||
<PermalinkDetails
|
||||
known
|
||||
showTransclusion={showTransclusion}
|
||||
icon={getModuleIcon(association.metadata.config.graph)}
|
||||
icon={getModuleIcon((association.metadata.config as GraphConfig).graph as GraphModule)}
|
||||
title={association.metadata.title}
|
||||
permalink={permalink}
|
||||
/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Association, Group } from '@urbit/api';
|
||||
import { Association, GraphConfig, Group } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
@ -8,7 +8,7 @@ export function getGraphPermalink(
|
||||
group: Group,
|
||||
index: string
|
||||
) {
|
||||
const mod = assoc.metadata.config.graph;
|
||||
const mod = (assoc.metadata.config as GraphConfig).graph;
|
||||
const groupPath = group.hidden
|
||||
? '/~landscape/home'
|
||||
: `/~landscape${assoc.group}`;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
Button, Col, ManagedCheckboxField as Checkbox, ManagedForm as Form,
|
||||
ManagedTextInputField as Input,
|
||||
Button, Col, ManagedCheckboxField as Checkbox, ManagedForm as Form,
|
||||
ManagedTextInputField as Input,
|
||||
|
||||
Row, Text
|
||||
Row, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Formik } from 'formik';
|
||||
import _ from 'lodash';
|
||||
@ -18,9 +18,9 @@ import { ColorInput } from '~/views/components/ColorInput';
|
||||
import GroupSearch from '~/views/components/GroupSearch';
|
||||
import { ImageInput } from '~/views/components/ImageInput';
|
||||
import {
|
||||
ProfileControls, ProfileHeader,
|
||||
ProfileControls, ProfileHeader,
|
||||
|
||||
ProfileImages, ProfileStatus
|
||||
ProfileImages, ProfileStatus
|
||||
} from './Profile';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
@ -61,7 +61,7 @@ export function ProfileHeaderImageEdit(props: any): ReactElement {
|
||||
<ImageInput id='cover' marginTop='-8px' width='288px' />
|
||||
) : (
|
||||
<Row>
|
||||
<Button mr='2' onClick={() => setEditCover(true)}>
|
||||
<Button mr={2} onClick={() => setEditCover(true)}>
|
||||
Replace Header
|
||||
</Button>
|
||||
<Button onClick={e => handleClear(e)}>
|
||||
@ -90,11 +90,12 @@ export function EditProfile(props: any): ReactElement {
|
||||
|
||||
const onSubmit = async (values: any, actions: any) => {
|
||||
try {
|
||||
await Object.keys(values).reduce((acc, key) => {
|
||||
Object.keys(values).forEach((key) => {
|
||||
const newValue = key !== 'color' ? values[key] : uxToHex(values[key]);
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === 'isPublic') {
|
||||
return acc.then(() => api.contacts.setPublic(newValue));
|
||||
api.contacts.setPublic(newValue)
|
||||
return;
|
||||
} else if (key === 'groups') {
|
||||
const toRemove: string[] = _.difference(
|
||||
contact?.groups || [],
|
||||
@ -104,24 +105,18 @@ export function EditProfile(props: any): ReactElement {
|
||||
newValue,
|
||||
contact?.groups || []
|
||||
);
|
||||
const promises: Promise<any>[] = [];
|
||||
promises.concat(
|
||||
toRemove.map(e =>
|
||||
toRemove.forEach(e =>
|
||||
api.contacts.edit(ship, { 'remove-group': resourceFromPath(e) })
|
||||
)
|
||||
);
|
||||
promises.concat(
|
||||
toAdd.map(e =>
|
||||
)
|
||||
toAdd.forEach(e =>
|
||||
api.contacts.edit(ship, { 'add-group': resourceFromPath(e) })
|
||||
)
|
||||
);
|
||||
return acc.then(() => Promise.all(promises));
|
||||
)
|
||||
} else if (key !== 'last-updated' && key !== 'isPublic') {
|
||||
return acc.then(() => api.contacts.edit(ship, { [key]: newValue }));
|
||||
api.contacts.edit(ship, { [key]: newValue });
|
||||
return;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}, Promise.resolve());
|
||||
});
|
||||
// actions.setStatus({ success: null });
|
||||
history.push(`/~profile/${ship}`);
|
||||
} catch (e) {
|
||||
@ -148,16 +143,16 @@ export function EditProfile(props: any): ReactElement {
|
||||
cursor='pointer'
|
||||
fontWeight='500'
|
||||
color='blue'
|
||||
pl='0'
|
||||
pr='0'
|
||||
border='0'
|
||||
pl={0}
|
||||
pr={0}
|
||||
border={0}
|
||||
style={{ appearance: 'none', background: 'transparent' }}
|
||||
>
|
||||
Save Edits
|
||||
</Button>
|
||||
<Text
|
||||
py='2'
|
||||
ml='3'
|
||||
py={2}
|
||||
ml={3}
|
||||
fontWeight='500'
|
||||
cursor='pointer'
|
||||
onClick={() => {
|
||||
|
@ -16,7 +16,7 @@ export function ProfileHeader(props: any): ReactElement {
|
||||
<Box
|
||||
border='1px solid'
|
||||
borderColor='lightGray'
|
||||
borderRadius='3'
|
||||
borderRadius={3}
|
||||
overflow='hidden'
|
||||
marginBottom='calc(64px + 2rem)'
|
||||
>
|
||||
@ -27,7 +27,7 @@ export function ProfileHeader(props: any): ReactElement {
|
||||
|
||||
export function ProfileImages(props: any): ReactElement {
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const { contact, hideCover, ship } = { ...props };
|
||||
const { contact, hideCover, ship } = props;
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
|
||||
|
||||
const anchorRef = useRef<HTMLDivElement>(null);
|
||||
@ -76,7 +76,7 @@ export function ProfileImages(props: any): ReactElement {
|
||||
<Box
|
||||
height='128px'
|
||||
width='128px'
|
||||
borderRadius='3'
|
||||
borderRadius={3}
|
||||
overflow='hidden'
|
||||
position='absolute'
|
||||
left='50%'
|
||||
@ -91,18 +91,18 @@ export function ProfileImages(props: any): ReactElement {
|
||||
|
||||
export function ProfileControls(props: any): ReactElement {
|
||||
return (
|
||||
<Row alignItems='center' justifyContent='space-between' px='3'>
|
||||
<Row alignItems='center' justifyContent='space-between' px={3}>
|
||||
{props.children}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileStatus(props: any): ReactElement {
|
||||
const { contact } = { ...props };
|
||||
const { contact } = props;
|
||||
return (
|
||||
<RichText
|
||||
mb='0'
|
||||
py='2'
|
||||
mb={0}
|
||||
py={2}
|
||||
disableRemoteContent
|
||||
maxWidth='18rem'
|
||||
overflowX='hidden'
|
||||
@ -120,14 +120,14 @@ export function ProfileStatus(props: any): ReactElement {
|
||||
}
|
||||
|
||||
export function ProfileActions(props: any): ReactElement {
|
||||
const { ship, isPublic, contact, api } = { ...props };
|
||||
const { ship, isPublic, contact, api } = props;
|
||||
const history = useHistory();
|
||||
return (
|
||||
<Row>
|
||||
{ship === `~${window.ship}` ? (
|
||||
<>
|
||||
<Text
|
||||
py='2'
|
||||
py={2}
|
||||
cursor='pointer'
|
||||
fontWeight='500'
|
||||
onClick={() => {
|
||||
@ -145,8 +145,8 @@ export function ProfileActions(props: any): ReactElement {
|
||||
</Text>
|
||||
<SetStatusBarModal
|
||||
isControl
|
||||
py='2'
|
||||
ml='3'
|
||||
py={2}
|
||||
ml={3}
|
||||
api={api}
|
||||
ship={`~${window.ship}`}
|
||||
contact={contact}
|
||||
@ -155,7 +155,7 @@ export function ProfileActions(props: any): ReactElement {
|
||||
) : (
|
||||
<>
|
||||
<Text
|
||||
py='2'
|
||||
py={2}
|
||||
cursor='pointer'
|
||||
fontWeight='500'
|
||||
onClick={() => history.push(`/~landscape/dm/${ship.substring(1)}`)}
|
||||
|
@ -45,15 +45,15 @@ export function ViewProfile(props: any): ReactElement {
|
||||
</Text>
|
||||
</Center>
|
||||
</Row>
|
||||
<Col pb={2} mt='3' alignItems='center' justifyContent='center' width='100%'>
|
||||
<Col pb={2} mt={3} alignItems='center' justifyContent='center' width='100%'>
|
||||
<Center flexDirection='column' maxWidth='32rem'>
|
||||
<RichText width='100%' disableRemoteContent>
|
||||
<RichText api={props.api} width='100%' disableRemoteContent>
|
||||
{contact?.bio ? contact.bio : ''}
|
||||
</RichText>
|
||||
</Center>
|
||||
</Col>
|
||||
{(contact?.groups || []).length > 0 && (
|
||||
<Col gapY='3' mb='3' mt='6' alignItems='flex-start'>
|
||||
<Col gapY={3} mb={3} mt={6} alignItems='flex-start'>
|
||||
<Text gray>Pinned Groups</Text>
|
||||
<Col>
|
||||
{contact?.groups.slice().sort(lengthOrder).map(g => (
|
||||
|
@ -36,7 +36,7 @@ export default function ProfileScreen(props: any) {
|
||||
border={1}
|
||||
borderColor='lightGray'
|
||||
overflowY='auto'
|
||||
flexGrow
|
||||
flexGrow={1}
|
||||
>
|
||||
<Box>
|
||||
<Profile
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { GraphNode } from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import { FormikHelpers } from 'formik';
|
||||
import _ from 'lodash';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { RouteComponentProps, useLocation } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { editPost, getLatestRevision } from '~/logic/lib/publish';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { PostForm, PostFormSchema } from './NoteForm';
|
||||
|
||||
interface EditPostProps {
|
||||
ship: string;
|
||||
noteId: number;
|
||||
noteId: bigInt.BigInteger;
|
||||
note: GraphNode;
|
||||
api: GlobalApi;
|
||||
book: string;
|
||||
|
@ -93,7 +93,6 @@ export function MarkdownEditor(
|
||||
|
||||
return (
|
||||
<Box
|
||||
height="100%"
|
||||
position="relative"
|
||||
className="publish"
|
||||
p={1}
|
||||
@ -111,10 +110,10 @@ export function MarkdownEditor(
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={handleChange}
|
||||
onDragLeave={(editor, e) => bind.onDragLeave(e)}
|
||||
onDragOver={(editor, e) => bind.onDragOver(e)}
|
||||
onDrop={(editor, e) => bind.onDrop(e)}
|
||||
onDragEnter={(editor, e) => bind.onDragEnter(e)}
|
||||
onDragLeave={(editor, e: DragEvent) => bind.onDragLeave(e)}
|
||||
onDragOver={(editor, e: DragEvent) => bind.onDragOver(e)}
|
||||
onDrop={(editor, e: DragEvent) => bind.onDrop(e)}
|
||||
onDragEnter={(editor, e: DragEvent) => bind.onDragEnter(e)}
|
||||
/>
|
||||
{dragging && <SubmitDragger />}
|
||||
</Box>
|
||||
|
@ -36,7 +36,7 @@ export const MarkdownField = ({
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
<ErrorLabel mt="2" hasError={Boolean(error && touched)}>
|
||||
<ErrorLabel mt={2} hasError={Boolean(error && touched)}>
|
||||
{error}
|
||||
</ErrorLabel>
|
||||
</Box>
|
||||
|
@ -1,18 +1,18 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Text, Col, Anchor, Row, Action } from '@tlon/indigo-react';
|
||||
import { Action, Anchor, Box, Col, Row, Text } from '@tlon/indigo-react';
|
||||
import { Association, Graph, GraphNode, Group } from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { Contacts, GraphNode, Graph, Association, Unreads, Group, Post } from '@urbit/api';
|
||||
import { getPermalinkForGraph } from '~/logic/lib/permalinks';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
import { getComments, getLatestRevision } from '~/logic/lib/publish';
|
||||
import { useCopy } from '~/logic/lib/useCopy';
|
||||
import { useQuery } from '~/logic/lib/useQuery';
|
||||
import Author from '~/views/components/Author';
|
||||
import { Comments } from '~/views/components/Comments';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { GraphContent } from '~/views/landscape/components/Graph/GraphContent';
|
||||
import { NoteNavigation } from './NoteNavigation';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
@ -117,7 +117,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
date={post?.['time-sent']}
|
||||
group={group}
|
||||
>
|
||||
<Row px="2" gapX="2" alignItems="flex-end" height="14px">
|
||||
<Row px={2} gapX={2} alignItems="flex-end" height="14px">
|
||||
<Action bg="white" onClick={doCopy}>{copyDisplay}</Action>
|
||||
{adminLinks}
|
||||
</Row>
|
||||
@ -128,8 +128,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
<NoteNavigation
|
||||
notebook={notebook}
|
||||
noteId={noteId}
|
||||
ship={props.ship}
|
||||
book={props.book}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
<Comments
|
||||
ship={ship}
|
||||
|
@ -29,7 +29,7 @@ function NavigationItem(props: {
|
||||
<Timestamp
|
||||
stamp={moment(props.date)}
|
||||
time={false}
|
||||
fontSize="1"
|
||||
fontSize={1}
|
||||
justifyContent={props.prev ? 'flex-start' : 'flex-end'}
|
||||
/>
|
||||
</Link>
|
||||
@ -48,12 +48,12 @@ function getAdjacentId(
|
||||
return target?.[0] || null;
|
||||
}
|
||||
|
||||
function makeNoteUrl(noteId: number) {
|
||||
function makeNoteUrl(noteId: BigInteger) {
|
||||
return noteId.toString();
|
||||
}
|
||||
|
||||
interface NoteNavigationProps {
|
||||
noteId: number;
|
||||
noteId: BigInteger;
|
||||
notebook: Graph;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ import ReactMarkdown from 'react-markdown';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
getComments,
|
||||
getLatestRevision,
|
||||
getSnippet
|
||||
getComments,
|
||||
getLatestRevision,
|
||||
getSnippet
|
||||
} from '~/logic/lib/publish';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import Author from '~/views/components/Author';
|
||||
@ -37,7 +37,7 @@ export function NotePreviewContent({ snippet }) {
|
||||
style={{ backgroundSize: 'cover',
|
||||
backgroundPosition: 'center' }}
|
||||
>
|
||||
<Image src={props.src} opacity="0" maxHeight="300px" />
|
||||
<Image src={props.src} opacity={0} maxHeight="300px" />
|
||||
</Box>
|
||||
),
|
||||
paragraph: props => (
|
||||
@ -91,7 +91,7 @@ export function NotePreview(props: NotePreviewProps) {
|
||||
borderRadius={2}
|
||||
alignItems='flex-start'
|
||||
overflow='hidden'
|
||||
p='2'
|
||||
p={2}
|
||||
>
|
||||
<WrappedBox mb={2}><Text bold>{title}</Text></WrappedBox>
|
||||
<WrappedBox>
|
||||
@ -101,7 +101,7 @@ export function NotePreview(props: NotePreviewProps) {
|
||||
</WrappedBox>
|
||||
</Col>
|
||||
</Link>
|
||||
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||
<Row minWidth={0} flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
|
||||
<Author
|
||||
showImage
|
||||
ship={post?.author}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Association, Graph, GraphNode, Group } from '@urbit/api';
|
||||
import bigInt from 'big-integer';
|
||||
import React from 'react';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -9,7 +10,7 @@ interface NoteRoutesProps {
|
||||
ship: string;
|
||||
book: string;
|
||||
note: GraphNode;
|
||||
noteId: number;
|
||||
noteId: bigInt.BigInteger;
|
||||
notebook: Graph;
|
||||
api: GlobalApi;
|
||||
association: Association;
|
||||
|
@ -40,7 +40,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
|
||||
}
|
||||
|
||||
return (
|
||||
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
|
||||
<Col gapY={4} pt={4} mx="auto" px={3} maxWidth="768px">
|
||||
<Row justifyContent="space-between">
|
||||
<Box>
|
||||
<Text display='block'>{association.metadata?.title}</Text>
|
||||
@ -50,7 +50,7 @@ export function Notebook(props: NotebookProps & RouteComponentProps): ReactEleme
|
||||
</Text>
|
||||
</Box>
|
||||
</Row>
|
||||
<Box borderBottom="1" borderBottomColor="lightGray" />
|
||||
<Box borderBottom={1} borderBottomColor="lightGray" />
|
||||
<NotebookPosts
|
||||
graph={graph}
|
||||
host={ship}
|
||||
|
@ -38,7 +38,7 @@ export const Writers = (props: WritersProps): ReactElement => {
|
||||
return (
|
||||
<Box maxWidth='512px'>
|
||||
<Text display='block'>Writers</Text>
|
||||
<Text display='block' mt='2' gray>Add additional writers to this notebook</Text>
|
||||
<Text display='block' mt={2} gray>Add additional writers to this notebook</Text>
|
||||
<Formik
|
||||
initialValues={{ ships: [] }}
|
||||
onSubmit={onSubmit}
|
||||
@ -49,16 +49,16 @@ export const Writers = (props: WritersProps): ReactElement => {
|
||||
label=""
|
||||
maxLength={undefined}
|
||||
/>
|
||||
<AsyncButton width='100%' mt='3' primary>
|
||||
<AsyncButton width='100%' mt={3} primary>
|
||||
Submit
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
</Formik>
|
||||
{writers.length > 0 ? <>
|
||||
<Text display='block' mt='2'>Current writers:</Text>
|
||||
<Text mt='2' display='block' mono>{writers}</Text>
|
||||
<Text display='block' mt={2}>Current writers:</Text>
|
||||
<Text mt={2} display='block' mono>{writers}</Text>
|
||||
</> :
|
||||
<Text display='block' mt='2'>
|
||||
<Text display='block' mt={2}>
|
||||
All group members can write to this channel
|
||||
</Text>
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export function BackButton(props: {}) {
|
||||
<Link to='/~settings'>
|
||||
<Text
|
||||
display={['block', 'none']}
|
||||
fontSize='2'
|
||||
fontSize={2}
|
||||
fontWeight='medium'
|
||||
p={4}
|
||||
pb={0}
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
|
||||
ManagedRadioButtonField as Radio, Row, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import {useField} from 'formik';
|
||||
import React, { ReactElement } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { ColorInput } from '~/views/components/ColorInput';
|
||||
@ -10,11 +11,7 @@ import { ImageInput } from '~/views/components/ImageInput';
|
||||
|
||||
export type BgType = 'none' | 'url' | 'color';
|
||||
|
||||
export function BackgroundPicker({
|
||||
bgType,
|
||||
bgUrl,
|
||||
api
|
||||
}: {
|
||||
export function BackgroundPicker({ api }: {
|
||||
bgType: BgType;
|
||||
bgUrl?: string;
|
||||
api: GlobalApi;
|
||||
@ -32,27 +29,26 @@ export function BackgroundPicker({
|
||||
<Label>Landscape Background</Label>
|
||||
<Row flexWrap="wrap" {...rowSpace}>
|
||||
<Col {...colProps}>
|
||||
<Radio mb="1" name="bgType" label="Image" id="url" />
|
||||
<Text ml="5" gray>Set an image background</Text>
|
||||
<Radio mb={1} name="bgType" label="Image" id="url" />
|
||||
<Text ml={5} gray>Set an image background</Text>
|
||||
<ImageInput
|
||||
ml="5"
|
||||
ml={5}
|
||||
api={api}
|
||||
id="bgUrl"
|
||||
placeholder="Drop or upload a file, or paste a link here"
|
||||
name="bgUrl"
|
||||
url={bgUrl || ''}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowSpace}>
|
||||
<Col {...colProps}>
|
||||
<Radio mb="1" label="Color" id="color" name="bgType" />
|
||||
<Text ml="5" gray>Set a hex-based background</Text>
|
||||
<ColorInput placeholder="FFFFFF" ml="5" id="bgColor" />
|
||||
<Radio mb={1} label="Color" id="color" name="bgType" />
|
||||
<Text ml={5} gray>Set a hex-based background</Text>
|
||||
<ColorInput placeholder="FFFFFF" ml={5} id="bgColor" />
|
||||
</Col>
|
||||
</Row>
|
||||
<Radio
|
||||
my="3"
|
||||
my={3}
|
||||
caption="Your home screen will simply render as its respective day/night mode color"
|
||||
name="bgType"
|
||||
label="None"
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {
|
||||
Box,
|
||||
Button, ManagedForm as Form, ManagedTextInputField as Input,
|
||||
Box,
|
||||
Button, ManagedForm as Form, ManagedTextInputField as Input,
|
||||
|
||||
Menu,
|
||||
MenuButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
|
||||
MenuItem, MenuList,
|
||||
MenuItem, MenuList,
|
||||
|
||||
Row, Text
|
||||
Row, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Formik, FormikHelpers } from 'formik';
|
||||
import React, { ReactElement, useCallback, useState } from 'react';
|
||||
@ -95,12 +95,12 @@ export function BucketList({
|
||||
{adding && (
|
||||
<Input
|
||||
placeholder="Enter your new bucket"
|
||||
mt="2"
|
||||
mt={2}
|
||||
label="New Bucket"
|
||||
id="newBucket"
|
||||
/>
|
||||
)}
|
||||
<Row gapX="3" mt="3">
|
||||
<Row gapX={3} mt={3}>
|
||||
<Button type="button" onClick={() => setAdding(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
@ -1,14 +1,16 @@
|
||||
import {
|
||||
Col, ManagedToggleSwitchField as Toggle,
|
||||
Col, ManagedToggleSwitchField as Toggle,
|
||||
|
||||
Text
|
||||
Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import useSettingsState, { selectSettingsState } from '~/logic/state/settings';
|
||||
import useSettingsState, { selectSettingsState, SettingsState } from '~/logic/state/settings';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import { BackButton } from './BackButton';
|
||||
import _ from 'lodash';
|
||||
import {FormikOnBlur} from '~/views/components/FormikOnBlur';
|
||||
|
||||
interface FormSchema {
|
||||
hideAvatars: boolean;
|
||||
@ -22,61 +24,43 @@ interface FormSchema {
|
||||
videoShown: boolean;
|
||||
}
|
||||
|
||||
const settingsSel = selectSettingsState(['calm', 'remoteContentPolicy']);
|
||||
const settingsSel = (s: SettingsState): FormSchema => ({
|
||||
hideAvatars: s.calm.hideAvatars,
|
||||
hideNicknames: s.calm.hideAvatars,
|
||||
hideUnreads: s.calm.hideUnreads,
|
||||
hideGroups: s.calm.hideGroups,
|
||||
hideUtilities: s.calm.hideUtilities,
|
||||
imageShown: !s.remoteContentPolicy.imageShown,
|
||||
videoShown: !s.remoteContentPolicy.videoShown,
|
||||
oembedShown: !s.remoteContentPolicy.oembedShown,
|
||||
audioShown: !s.remoteContentPolicy.audioShown
|
||||
});
|
||||
|
||||
|
||||
export function CalmPrefs(props: {
|
||||
api: GlobalApi;
|
||||
}) {
|
||||
const { api } = props;
|
||||
const {
|
||||
calm: {
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
hideUnreads,
|
||||
hideGroups,
|
||||
hideUtilities
|
||||
},
|
||||
remoteContentPolicy: {
|
||||
imageShown,
|
||||
videoShown,
|
||||
oembedShown,
|
||||
audioShown
|
||||
}
|
||||
} = useSettingsState(settingsSel);
|
||||
|
||||
const initialValues: FormSchema = {
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
hideUnreads,
|
||||
hideGroups,
|
||||
hideUtilities,
|
||||
imageShown: !imageShown,
|
||||
videoShown: !videoShown,
|
||||
oembedShown: !oembedShown,
|
||||
audioShown: !audioShown
|
||||
};
|
||||
const initialValues = useSettingsState(settingsSel);
|
||||
|
||||
const onSubmit = useCallback(async (v: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
||||
await Promise.all([
|
||||
api.settings.putEntry('calm', 'hideAvatars', v.hideAvatars),
|
||||
api.settings.putEntry('calm', 'hideNicknames', v.hideNicknames),
|
||||
api.settings.putEntry('calm', 'hideUnreads', v.hideUnreads),
|
||||
api.settings.putEntry('calm', 'hideGroups', v.hideGroups),
|
||||
api.settings.putEntry('calm', 'hideUtilities', v.hideUtilities),
|
||||
api.settings.putEntry('remoteContentPolicy', 'imageShown', !v.imageShown),
|
||||
api.settings.putEntry('remoteContentPolicy', 'videoShown', !v.videoShown),
|
||||
api.settings.putEntry('remoteContentPolicy', 'audioShown', !v.audioShown),
|
||||
api.settings.putEntry('remoteContentPolicy', 'oembedShown', !v.oembedShown)
|
||||
]);
|
||||
let promises: Promise<any>[] = [];
|
||||
_.forEach(v, (bool, key) => {
|
||||
const bucket = ['imageShown', 'videoShown', 'audioShown', 'oembedShown'].includes(key) ? 'remoteContentPolicy' : 'calm';
|
||||
if(initialValues[key] !== bool) {
|
||||
promises.push(api.settings.putEntry(bucket, key, bool));
|
||||
}
|
||||
})
|
||||
await Promise.all(promises);
|
||||
actions.setStatus({ success: null });
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<Form>
|
||||
<BackButton />
|
||||
<Col borderBottom="1" borderBottomColor="washedGray" p="5" pt="4" gapY="5">
|
||||
<Col gapY="1" mt="0">
|
||||
<Col borderBottom={1} borderBottomColor="washedGray" p={5} pt={4} gapY={5}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text color="black" fontSize={2} fontWeight="medium">
|
||||
CalmEngine
|
||||
</Text>
|
||||
@ -132,12 +116,8 @@ export function CalmPrefs(props: {
|
||||
id="oembedShown"
|
||||
caption="Embedded content may contain scripts that can track you"
|
||||
/>
|
||||
|
||||
<AsyncButton primary width="fit-content" type="submit">
|
||||
Save
|
||||
</AsyncButton>
|
||||
</Col>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FormikOnBlur>
|
||||
);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ const StoreDebugger = (props: StoreDebuggerProps) => {
|
||||
backgroundColor='white'
|
||||
color='black'
|
||||
border='1px solid transparent'
|
||||
borderRadius='2'
|
||||
borderRadius={2}
|
||||
fontSize={1}
|
||||
placeholder="Drill Down"
|
||||
width="100%"
|
||||
@ -65,7 +65,7 @@ const StoreDebugger = (props: StoreDebuggerProps) => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Text mono p='1' borderRadius='1' display='block' overflow='auto' backgroundColor='washedGray' style={{ whiteSpace: 'pre', wordWrap: 'break-word' }}>{text}</Text>
|
||||
<Text mono p={1} borderRadius={1} display='block' overflow='auto' backgroundColor='washedGray' style={{ whiteSpace: 'pre', wordWrap: 'break-word' }}>{text}</Text>
|
||||
</Box>}
|
||||
</Box>
|
||||
);
|
||||
@ -75,8 +75,8 @@ const DebugPane = () => {
|
||||
return (
|
||||
<>
|
||||
<BackButton />
|
||||
<Col borderBottom="1" borderBottomColor="washedGray" p="5" pt="4" gapY="5">
|
||||
<Col gapY="1" mt="0">
|
||||
<Col borderBottom={1} borderBottomColor="washedGray" p={5} pt={4} gapY={5}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text color="black" fontSize={2} fontWeight="medium">
|
||||
Debug Menu
|
||||
</Text>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {
|
||||
Col,
|
||||
Col,
|
||||
|
||||
Label,
|
||||
ManagedRadioButtonField as Radio, Text
|
||||
Label,
|
||||
ManagedRadioButtonField as Radio, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Form, Formik } from 'formik';
|
||||
import React from 'react';
|
||||
@ -11,6 +11,7 @@ import GlobalApi from '~/logic/api/global';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import useSettingsState, { selectSettingsState } from '~/logic/state/settings';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import {FormikOnBlur} from '~/views/components/FormikOnBlur';
|
||||
import { BackButton } from './BackButton';
|
||||
import { BackgroundPicker, BgType } from './BackgroundPicker';
|
||||
|
||||
@ -58,7 +59,7 @@ export default function DisplayForm(props: DisplayFormProps) {
|
||||
const bgType = backgroundType || 'none';
|
||||
|
||||
return (
|
||||
<Formik
|
||||
<FormikOnBlur
|
||||
validationSchema={formSchema}
|
||||
initialValues={
|
||||
{
|
||||
@ -86,11 +87,10 @@ export default function DisplayForm(props: DisplayFormProps) {
|
||||
actions.setStatus({ success: null });
|
||||
}}
|
||||
>
|
||||
{props => (
|
||||
<Form>
|
||||
<BackButton />
|
||||
<Col p="5" pt="4" gapY="5">
|
||||
<Col gapY="1" mt="0">
|
||||
<Col p={5} pt={4} gapY={5}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text color="black" fontSize={2} fontWeight="medium">
|
||||
Display Preferences
|
||||
</Text>
|
||||
@ -98,10 +98,7 @@ export default function DisplayForm(props: DisplayFormProps) {
|
||||
Customize visual interfaces across your Landscape
|
||||
</Text>
|
||||
</Col>
|
||||
<BackgroundPicker
|
||||
bgType={props.values.bgType}
|
||||
bgUrl={props.values.bgUrl}
|
||||
api={api}
|
||||
<BackgroundPicker api={api}
|
||||
/>
|
||||
<Label>Theme</Label>
|
||||
<Radio name="theme" id="light" label="Light" />
|
||||
@ -112,7 +109,6 @@ export default function DisplayForm(props: DisplayFormProps) {
|
||||
</AsyncButton>
|
||||
</Col>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FormikOnBlur>
|
||||
);
|
||||
}
|
||||
|
@ -3,14 +3,15 @@ import {
|
||||
|
||||
Center, Col, Icon,
|
||||
|
||||
StatelessToggleSwitchField, Text
|
||||
ToggleSwitch, Text,
|
||||
StatelessToggleSwitchField
|
||||
} from '@tlon/indigo-react';
|
||||
import { Association, resourceFromPath } from '@urbit/api';
|
||||
import { Association, GraphConfig, resourceFromPath } from '@urbit/api';
|
||||
import { useField } from 'formik';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { isWatching } from '~/logic/lib/hark';
|
||||
import { getModuleIcon } from '~/logic/lib/util';
|
||||
import { getModuleIcon, GraphModule } from '~/logic/lib/util';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState, { useGraphsForGroup } from '~/logic/state/metadata';
|
||||
@ -20,7 +21,7 @@ export function GroupChannelPicker(props: {}) {
|
||||
const associations = useMetadataState(s => s.associations);
|
||||
|
||||
return (
|
||||
<Col gapY="3">
|
||||
<Col gapY={3}>
|
||||
{_.map(associations.groups, (assoc: Association, group: string) => (
|
||||
<GroupWithChannels key={group} association={assoc} />
|
||||
))}
|
||||
@ -62,7 +63,7 @@ function GroupWithChannels(props: { association: Association }) {
|
||||
display="grid"
|
||||
gridTemplateColumns="24px 24px 1fr 24px 24px"
|
||||
gridTemplateRows="auto"
|
||||
gridGap="2"
|
||||
gridGap={2}
|
||||
gridTemplateAreas="'arrow icon title graphToggle groupToggle'"
|
||||
>
|
||||
{Object.keys(joinedGroupGraphs).length > 0 && (
|
||||
@ -100,7 +101,7 @@ function Channel(props: { association: Association }) {
|
||||
return isWatching(config, association.resource);
|
||||
});
|
||||
|
||||
const [{ value }, meta, { setValue }] = useField(
|
||||
const [{ value }, meta, { setValue, setTouched }] = useField(
|
||||
`graph["${association.resource}"]`
|
||||
);
|
||||
|
||||
@ -108,22 +109,24 @@ function Channel(props: { association: Association }) {
|
||||
setValue(watching);
|
||||
}, [watching]);
|
||||
|
||||
const onChange = () => {
|
||||
const onClick = () => {
|
||||
setValue(!value);
|
||||
};
|
||||
setTouched(true);
|
||||
}
|
||||
|
||||
const icon = getModuleIcon(metadata.config?.graph);
|
||||
|
||||
const icon = getModuleIcon((metadata.config as GraphConfig)?.graph as GraphModule);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Center gridColumn="2">
|
||||
<Center gridColumn={2}>
|
||||
<Icon icon={icon} />
|
||||
</Center>
|
||||
<Box gridColumn="3">
|
||||
<Box gridColumn={3}>
|
||||
<Text> {metadata.title}</Text>
|
||||
</Box>
|
||||
<Box gridColumn="4">
|
||||
<StatelessToggleSwitchField selected={value} onChange={onChange} />
|
||||
<Box gridColumn={4}>
|
||||
<StatelessToggleSwitchField selected={value} onClick={onClick} />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Col,
|
||||
Col,
|
||||
|
||||
ManagedCheckboxField, Text
|
||||
ManagedCheckboxField, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Form, useField, useFormikContext } from 'formik';
|
||||
import _ from 'lodash';
|
||||
@ -9,8 +9,8 @@ import React from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import useSettingsState, { selectSettingsState } from '~/logic/state/settings';
|
||||
import {
|
||||
LeapCategories,
|
||||
leapCategories
|
||||
LeapCategories,
|
||||
leapCategories
|
||||
} from '~/types';
|
||||
import { FormikOnBlur } from '~/views/components/FormikOnBlur';
|
||||
import { ShuffleFields } from '~/views/components/ShuffleFields';
|
||||
@ -50,7 +50,6 @@ export function LeapSettings(props: { api: GlobalApi; }) {
|
||||
const { leap, set: setSettingsState } = useSettingsState(settingsSel);
|
||||
const categories = leap.categories as LeapCategories[];
|
||||
const missing = _.difference(leapCategories, categories);
|
||||
console.log(categories);
|
||||
|
||||
const initialValues = {
|
||||
categories: [
|
||||
@ -73,9 +72,9 @@ export function LeapSettings(props: { api: GlobalApi; }) {
|
||||
return (
|
||||
<>
|
||||
<BackButton />
|
||||
<Col p="5" pt="4" gapY="5">
|
||||
<Col gapY="1" mt="0">
|
||||
<Text fontSize="2" fontWeight="medium">
|
||||
<Col p={5} pt={4} gapY={5}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text fontSize={2} fontWeight="medium">
|
||||
Leap
|
||||
</Text>
|
||||
<Text gray>
|
||||
@ -84,7 +83,7 @@ export function LeapSettings(props: { api: GlobalApi; }) {
|
||||
</Col>
|
||||
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<Form>
|
||||
<Col gapY="4">
|
||||
<Col gapY={4}>
|
||||
<Text fontWeight="medium">
|
||||
Customize default Leap sections
|
||||
</Text>
|
||||
|
@ -1,15 +1,20 @@
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
|
||||
|
||||
|
||||
|
||||
ManagedToggleSwitchField as Toggle, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import _ from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { isWatching } from '~/logic/lib/hark';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import {FormikOnBlur} from '~/views/components/FormikOnBlur';
|
||||
import { BackButton } from './BackButton';
|
||||
import { GroupChannelPicker } from './GroupChannelPicker';
|
||||
|
||||
@ -69,12 +74,14 @@ export function NotificationPreferences(props: {
|
||||
}
|
||||
}, [api, graphConfig, dnd]);
|
||||
|
||||
const [notificationsAllowed, setNotificationsAllowed] = useState(Notification.permission !== 'default');
|
||||
|
||||
return (
|
||||
<>
|
||||
<BackButton />
|
||||
<Col p="5" pt="4" gapY="5">
|
||||
<Col gapY="1" mt="0">
|
||||
<Text fontSize="2" fontWeight="medium">
|
||||
<Col p={5} pt={4} gapY={5}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text fontSize={2} fontWeight="medium">
|
||||
Notification Preferences
|
||||
</Text>
|
||||
<Text gray>
|
||||
@ -82,9 +89,17 @@ export function NotificationPreferences(props: {
|
||||
messaging
|
||||
</Text>
|
||||
</Col>
|
||||
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<FormikOnBlur initialValues={initialValues} onSubmit={onSubmit}>
|
||||
<Form>
|
||||
<Col gapY="4">
|
||||
{notificationsAllowed
|
||||
? null
|
||||
: <Button alignSelf='flex-start' onClick={() => {
|
||||
Notification.requestPermission().then(() => {
|
||||
setNotificationsAllowed(Notification.permission !== 'default');
|
||||
});
|
||||
}}>Allow Browser Notifications</Button>
|
||||
}
|
||||
<Toggle
|
||||
label="Do not disturb"
|
||||
id="dnd"
|
||||
@ -100,7 +115,7 @@ export function NotificationPreferences(props: {
|
||||
id="mentions"
|
||||
caption="Notify me if someone mentions my @p in a channel I've joined"
|
||||
/>
|
||||
<Col gapY="3">
|
||||
<Col gapY={3}>
|
||||
<Text lineHeight="tall">
|
||||
Activity
|
||||
</Text>
|
||||
@ -109,12 +124,9 @@ export function NotificationPreferences(props: {
|
||||
</Text>
|
||||
<GroupChannelPicker />
|
||||
</Col>
|
||||
<AsyncButton primary width="fit-content">
|
||||
Save
|
||||
</AsyncButton>
|
||||
</Col>
|
||||
</Form>
|
||||
</Formik>
|
||||
</FormikOnBlur>
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Anchor, Col, ManagedForm as Form, ManagedTextInputField as Input,
|
||||
Anchor, Col, ManagedForm as Form, ManagedTextInputField as Input,
|
||||
|
||||
Text
|
||||
Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Formik, FormikHelpers } from 'formik';
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
@ -47,7 +47,7 @@ export default function S3Form(props: S3FormProps): ReactElement {
|
||||
return (
|
||||
<>
|
||||
<BackButton />
|
||||
<Col p='5' pt='4' borderBottom='1' borderBottomColor='washedGray'>
|
||||
<Col p={5} pt={4} borderBottom={1} borderBottomColor='washedGray'>
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
@ -61,8 +61,8 @@ export default function S3Form(props: S3FormProps): ReactElement {
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form>
|
||||
<Col maxWidth='600px' gapY='5'>
|
||||
<Col gapY='1' mt='0'>
|
||||
<Col maxWidth='600px' gapY={5}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text color='black' fontSize={2} fontWeight='medium'>
|
||||
S3 Storage Setup
|
||||
</Text>
|
||||
@ -72,8 +72,8 @@ export default function S3Form(props: S3FormProps): ReactElement {
|
||||
<Anchor
|
||||
target='_blank'
|
||||
style={{ textDecoration: 'none' }}
|
||||
borderBottom='1'
|
||||
ml='1'
|
||||
borderBottom={1}
|
||||
ml={1}
|
||||
href='https://urbit.org/using/os/s3/'
|
||||
>
|
||||
Learn more
|
||||
@ -94,8 +94,8 @@ export default function S3Form(props: S3FormProps): ReactElement {
|
||||
</Form>
|
||||
</Formik>
|
||||
</Col>
|
||||
<Col maxWidth='600px' p='5' gapY='4'>
|
||||
<Col gapY='1'>
|
||||
<Col maxWidth='600px' p={5} gapY={4}>
|
||||
<Col gapY={1}>
|
||||
<Text color='black' mb={4} fontSize={2} fontWeight='medium'>
|
||||
S3 Buckets
|
||||
</Text>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
StatelessCheckboxField, Text
|
||||
Button,
|
||||
Col,
|
||||
StatelessCheckboxField, Text
|
||||
} from '@tlon/indigo-react';
|
||||
import React, { useState } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -16,8 +16,8 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) {
|
||||
return (
|
||||
<>
|
||||
<BackButton />
|
||||
<Col gapY="5" p="5" pt="4">
|
||||
<Col gapY="1" mt="0">
|
||||
<Col gapY={5} p={5} pt={4}>
|
||||
<Col gapY={1} mt={0}>
|
||||
<Text fontSize={2} fontWeight="medium">
|
||||
Security Preferences
|
||||
</Text>
|
||||
@ -25,17 +25,17 @@ export default function SecuritySettings({ api }: SecuritySettingsProps) {
|
||||
Manage sessions, login credentials and Landscape access
|
||||
</Text>
|
||||
</Col>
|
||||
<Col gapY="1">
|
||||
<Col gapY={1}>
|
||||
<Text color="black">
|
||||
Log out of this session
|
||||
</Text>
|
||||
<Text mb="3" gray>
|
||||
<Text mb={3} gray>
|
||||
{allSessions
|
||||
? 'You will be logged out of all browsers that have currently logged into your Urbit.'
|
||||
: 'You will be logged out of your Urbit on this browser.'}
|
||||
</Text>
|
||||
<StatelessCheckboxField
|
||||
mb="3"
|
||||
mb={3}
|
||||
selected={allSessions}
|
||||
onChange={() => setAllSessions(s => !s)}
|
||||
>
|
||||
|
@ -10,14 +10,14 @@ export function SettingsItem(props: {
|
||||
const { to, title, description } = props;
|
||||
return (
|
||||
<Link to={`/~settings/${to}`}>
|
||||
<Row alignItems="center" gapX="3">
|
||||
<Row alignItems="center" gapX={3}>
|
||||
<Box
|
||||
borderRadius="2"
|
||||
borderRadius={2}
|
||||
backgroundColor="blue"
|
||||
width="64px"
|
||||
height="64px"
|
||||
/>
|
||||
<Col gapY="2">
|
||||
<Col gapY={2}>
|
||||
<Text>{title}</Text>
|
||||
<Text gray>{description}</Text>
|
||||
</Col>
|
||||
@ -28,9 +28,9 @@ export function SettingsItem(props: {
|
||||
|
||||
export default function Settings(props: {}) {
|
||||
return (
|
||||
<Col gapY="5" p="5">
|
||||
<Col gapY="1">
|
||||
<Text fontSize="2">System Preferences</Text>
|
||||
<Col gapY={5} p={5}>
|
||||
<Col gapY={1}>
|
||||
<Text fontSize={2}>System Preferences</Text>
|
||||
<Text gray>Configure and customize Landscape</Text>
|
||||
</Col>
|
||||
<Box
|
||||
@ -38,7 +38,7 @@ export default function Settings(props: {}) {
|
||||
width="100%"
|
||||
height="100%"
|
||||
gridTemplateColumns={['100%', '1fr 1fr']}
|
||||
gridGap="3"
|
||||
gridGap={3}
|
||||
>
|
||||
<SettingsItem
|
||||
to="notifications"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user