mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 05:22:27 +03:00
Merge remote-tracking branch 'origin/master' into release/next-sys
This commit is contained in:
commit
6e3d1680a7
27
.github/workflows/merge-master.yml
vendored
Normal file
27
.github/workflows/merge-master.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: merge
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
jobs:
|
||||
merge-to-next-js:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Merge master to release/next-js"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: devmasx/merge-branch@v1.3.1
|
||||
with:
|
||||
type: now
|
||||
target_branch: release/next-js
|
||||
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}
|
||||
|
||||
merge-to-group-timer:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Merge master to ops/group-timer"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: devmasx/merge-branch@v1.3.1
|
||||
with:
|
||||
type: now
|
||||
target_branch: ops/group-timer
|
||||
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}
|
@ -1,17 +1,17 @@
|
||||
name: merge
|
||||
name: ops-merge
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'release/*'
|
||||
jobs:
|
||||
merge-to-next-js:
|
||||
merge-release-to-ops:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Merge master to release/next-js"
|
||||
name: "Merge to ops-tlon"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: devmasx/merge-branch@v1.3.1
|
||||
with:
|
||||
type: now
|
||||
target_branch: release/next-js
|
||||
target_branch: ops-tlon
|
||||
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}
|
||||
|
20
.github/workflows/ops-group-timer.yml
vendored
Normal file
20
.github/workflows/ops-group-timer.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: group-timer
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'ops/group-timer'
|
||||
jobs:
|
||||
glob:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Create and deploy a glob to ~difmex-passed"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
- uses: ./.github/actions/glob
|
||||
with:
|
||||
ship: 'difmex-passed'
|
||||
credentials: ${{ secrets.JANEWAY_SERVICE_KEY }}
|
||||
ssh-sec-key: ${{ secrets.JANEWAY_SSH_SEC_KEY }}
|
||||
ssh-pub-key: ${{ secrets.JANEWAY_SSH_PUB_KEY }}
|
||||
|
60
.github/workflows/publish-npm-packages.yml
vendored
Normal file
60
.github/workflows/publish-npm-packages.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
name: publish-npm-packages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
jobs:
|
||||
publish-api:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Publish '@urbit/api' if a new version is available"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: 'npm install'
|
||||
working-directory: 'pkg/npm/api'
|
||||
- uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
check-version: true
|
||||
package: './pkg/npm/api/package.json'
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish-http-api:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Publish '@urbit/http-api' if a new version is available"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: 'npm install'
|
||||
working-directory: 'pkg/npm/http-api'
|
||||
- uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
check-version: true
|
||||
package: './pkg/npm/http-api/package.json'
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish-eslint-config:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Publish '@urbit/eslint-config' if a new version is available"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
- run: 'npm install'
|
||||
working-directory: 'pkg/npm/eslint-config'
|
||||
- uses: JS-DevTools/npm-publish@v1
|
||||
with:
|
||||
check-version: true
|
||||
package: './pkg/npm/eslint-config/package.json'
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:65ad594ab389394b97fcd8bb58f25b88ec283c4142983858c1a2c3b01e14215c
|
||||
size 8841004
|
||||
oid sha256:d284136bd8c203cd9f6951f0294aaef0cac08b3e455539846cdc401126c319a7
|
||||
size 10967330
|
||||
|
@ -240,7 +240,12 @@
|
||||
=/ mime-type=@t (rsh 3 (crip <p.u.data>))
|
||||
:: Should maybe inspect to see how long cache should hold
|
||||
::
|
||||
[[200 ['content-type' mime-type] max-1-da:gen ~] `q.u.data]
|
||||
=/ headers
|
||||
:~ content-type+mime-type
|
||||
max-1-da:gen
|
||||
'Service-Worker-Allowed'^'/'
|
||||
==
|
||||
[[200 headers] `q.u.data]
|
||||
==
|
||||
::
|
||||
++ lowercase
|
||||
|
@ -5,7 +5,7 @@
|
||||
/- glob
|
||||
/+ default-agent, verb, dbug
|
||||
|%
|
||||
++ hash 0v5.pmklm.qttdl.n8bs0.0tnc4.gg633
|
||||
++ hash 0v5.ip41o.9jcdb.4jb1f.sd508.fdssj
|
||||
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
|
||||
+$ all-states
|
||||
$% state-0
|
||||
@ -105,12 +105,15 @@
|
||||
(cat 3 js-name '.js')
|
||||
=+ .^(js=@t %cx :(weld home /app/landscape/js/bundle /[js-name]/js))
|
||||
=+ .^(map=@t %cx :(weld home /app/landscape/js/bundle /[map-name]/map))
|
||||
=+ .^(sw=@t %cx :(weld home /app/landscape/js/bundle /serviceworker/js))
|
||||
=+ !<(=js=mime (js-tube !>(js)))
|
||||
=+ !<(=sw=mime (js-tube !>(sw)))
|
||||
=+ !<(=map=mime (map-tube !>(map)))
|
||||
=/ =glob:glob
|
||||
%- ~(gas by *glob:glob)
|
||||
:~ /[js-name]/js^js-mime
|
||||
/[map-name]/map^map-mime
|
||||
/serviceworker/js^sw-mime
|
||||
==
|
||||
=/ =path /(cat 3 'glob-' (scot %uv (sham glob)))/glob
|
||||
[%pass /make %agent [our.bowl %hood] %poke %drum-put !>([path (jam glob)])]~
|
||||
|
@ -216,6 +216,8 @@
|
||||
%- some
|
||||
%+ levy ~(tap by nodes)
|
||||
|= [=index:store =node:store]
|
||||
?. =(author.post.node src.bowl)
|
||||
%.n
|
||||
=/ =permissions:store
|
||||
%^ add-mark resource vip
|
||||
(node-to-indexed-post node)
|
||||
|
@ -279,7 +279,6 @@
|
||||
%+ turn
|
||||
~(tap by unreads-count)
|
||||
|= [=stats-index:store count=@ud]
|
||||
?> ?=(%graph -.stats-index)
|
||||
:* stats-index
|
||||
~(wyt in (~(gut by by-index) stats-index ~))
|
||||
[%count count]
|
||||
@ -297,10 +296,27 @@
|
||||
(~(gut by last-seen) stats-index *time)
|
||||
==
|
||||
::
|
||||
++ give-group-unreads
|
||||
^- (list [stats-index:store stats:store])
|
||||
%+ murn ~(tap by by-index)
|
||||
|= [=stats-index:store nots=(set [time index:store])]
|
||||
?. ?=(%group -.stats-index)
|
||||
~
|
||||
:- ~
|
||||
:* stats-index
|
||||
~(wyt in nots)
|
||||
[%count 0]
|
||||
*time
|
||||
==
|
||||
::
|
||||
++ give-unreads
|
||||
^- update:store
|
||||
:- %unreads
|
||||
(weld give-each-unreads give-since-unreads)
|
||||
;: weld
|
||||
give-each-unreads
|
||||
give-since-unreads
|
||||
give-group-unreads
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-peek
|
||||
@ -751,6 +767,8 @@
|
||||
++ inflate-cache
|
||||
|= state-4
|
||||
^+ +.state
|
||||
=. +.state
|
||||
*cache
|
||||
=/ nots=(list [p=@da =timebox:store])
|
||||
(tap:orm notifications)
|
||||
|- =* outer $
|
||||
|
BIN
pkg/arvo/app/landscape/fonts/sourcecodepro-bold.woff2
Normal file
BIN
pkg/arvo/app/landscape/fonts/sourcecodepro-bold.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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.ee4aea08fefb9c47dcfd.js"></script>
|
||||
<script src="/~landscape/js/bundle/index.01423cd5af57c2f23adc.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,13 +4,12 @@
|
||||
+$ card card:agent:gall
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
state-1
|
||||
==
|
||||
+$ state-0
|
||||
$: %0
|
||||
=settings
|
||||
==
|
||||
+$ state-0 [%0 settings=settings-0]
|
||||
+$ state-1 [%1 =settings]
|
||||
--
|
||||
=| state-0
|
||||
=| state-1
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -32,8 +31,10 @@
|
||||
|= =old=vase
|
||||
^- (quip card _this)
|
||||
=/ old !<(versioned-state old-vase)
|
||||
|-
|
||||
?- -.old
|
||||
%0 [~ this(state old)]
|
||||
%0 $(old [%1 +.old])
|
||||
%1 [~ this(state old)]
|
||||
==
|
||||
::
|
||||
++ on-poke
|
||||
|
@ -40,7 +40,7 @@
|
||||
=. mar-ok.state %.y
|
||||
=+ .^(paz=(list path) ct+(en-beam now-beak /mar))
|
||||
|- ^+ [fex this]
|
||||
?~ paz [fex this]
|
||||
?~ paz [(flop fex) this]
|
||||
=/ xap=path (flop i.paz)
|
||||
?. ?=([%hoon *] xap)
|
||||
$(paz t.paz)
|
||||
@ -63,7 +63,7 @@
|
||||
?> =(~ app.state)
|
||||
=. app-ok.state %.y
|
||||
=+ .^(app-arch=arch cy+(en-beam now-beak /app))
|
||||
=/ daz ~(tap in ~(key by dir.app-arch))
|
||||
=/ daz (sort ~(tap in ~(key by dir.app-arch)) |=((pair) !(aor p q)))
|
||||
|- ^+ [fex this]
|
||||
?~ daz [fex this]
|
||||
=/ dap-pax=path /app/[i.daz]/hoon
|
||||
@ -86,7 +86,7 @@
|
||||
=. gen-ok.state %.y
|
||||
=+ .^(paz=(list path) ct+(en-beam now-beak /gen))
|
||||
|- ^+ [fex this]
|
||||
?~ paz [fex this]
|
||||
?~ paz [(flop fex) this]
|
||||
=/ xap=path (flop i.paz)
|
||||
?. ?=([%hoon *] xap)
|
||||
$(paz t.paz)
|
||||
@ -106,11 +106,18 @@
|
||||
++ on-peek on-peek:def
|
||||
++ on-agent on-agent:def
|
||||
++ on-arvo
|
||||
=> |%
|
||||
++ report
|
||||
|* [=path ok=?]
|
||||
=/ =tank leaf+"{?:(ok "built " "FAILED")} {(spud path)}"
|
||||
~>(%slog.[0 tank] same)
|
||||
--
|
||||
::
|
||||
|= [=wire =sign-arvo]
|
||||
^- [(list card) _this]
|
||||
?. ?=([%build *] wire)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
?. ?=(%writ +<.sign-arvo)
|
||||
?. ?& ?=([%build *] wire)
|
||||
?=([%clay %writ *] sign-arvo)
|
||||
==
|
||||
(on-arvo:def wire sign-arvo)
|
||||
=/ =path t.wire
|
||||
?+ path ~|(path+path !!)
|
||||
@ -118,41 +125,29 @@
|
||||
=/ ok
|
||||
?~ p.sign-arvo |
|
||||
(~(nest ut -:!>(*agent:gall)) | -:!<(vase q.r.u.p.sign-arvo))
|
||||
~& ?: ok
|
||||
agent-built+path
|
||||
agent-failed+path
|
||||
%- (report path ok)
|
||||
=? app-ok.state !ok %.n
|
||||
=. app.state (~(del in app.state) path)
|
||||
~? =(~ app.state)
|
||||
?: app-ok.state
|
||||
%all-agents-built
|
||||
%some-agents-failed
|
||||
?:(app-ok.state %all-agents-built %some-agents-failed)
|
||||
[~ this]
|
||||
::
|
||||
[%mar *]
|
||||
=/ ok ?=(^ p.sign-arvo)
|
||||
~& ?: ok
|
||||
mark-built+path
|
||||
mark-failed+path
|
||||
%- (report path ok)
|
||||
=? mar-ok.state !ok %.n
|
||||
=. mar.state (~(del in mar.state) path)
|
||||
~? =(~ mar.state)
|
||||
?: mar-ok.state
|
||||
%all-marks-built
|
||||
%some-marks-failed
|
||||
?:(mar-ok.state %all-marks-built %some-marks-failed)
|
||||
[~ this]
|
||||
::
|
||||
[%gen *]
|
||||
=/ ok ?=(^ p.sign-arvo)
|
||||
~& ?: ok
|
||||
generator-built+path
|
||||
generator-failed+path
|
||||
%- (report path ok)
|
||||
=? gen-ok.state !ok %.n
|
||||
=. gen.state (~(del in gen.state) path)
|
||||
~? =(~ gen.state)
|
||||
?: gen-ok.state
|
||||
%all-generators-built
|
||||
%some-generators-failed
|
||||
?:(gen-ok.state %all-generators-built %some-generators-failed)
|
||||
[~ this]
|
||||
==
|
||||
++ on-fail on-fail:def
|
||||
|
@ -34,10 +34,8 @@
|
||||
=/ groups=(list [local=? resource:re members=@ud])
|
||||
%+ murn
|
||||
%~ tap in
|
||||
%~ key by
|
||||
dir:(scry arch %y %group-store /groups)
|
||||
|= i=@ta
|
||||
=/ r=resource:re (de-path:re (stab i))
|
||||
(scry (set resource:re) %y %group-store /groups)
|
||||
|= r=resource:re
|
||||
=/ g=(unit group:gr)
|
||||
%+ scry (unit group:gr)
|
||||
[%x %group-store [%groups (snoc (en-path:re r) %noun)]]
|
||||
@ -59,10 +57,10 @@
|
||||
%~ tap by
|
||||
%+ scry associations:md
|
||||
[%x %metadata-store [%group (snoc (en-path:re r) %noun)]]
|
||||
|= [[* m=md-resource:md] metadata:md]
|
||||
|= [m=md-resource:md association:md]
|
||||
::NOTE we only count graphs for now
|
||||
?. &(=(%graph app-name.m) =(our creator)) ~
|
||||
`[module (de-path:re app-path.m)]
|
||||
?. &(=(%graph app-name.m) =(our creator.metadatum)) ~
|
||||
`[module.metadatum resource.m]
|
||||
:: count activity per channel
|
||||
::
|
||||
=/ activity=(list [resource:re members=@ud (list [resource:re mod=term week=@ud authors=@ud])])
|
||||
|
@ -13,9 +13,7 @@
|
||||
=/ members
|
||||
~(wyt in (members:grp rid))
|
||||
=/ =metadatum:store
|
||||
%- need
|
||||
%+ mate (peek-metadatum %groups rid)
|
||||
(peek-metadatum %graph rid)
|
||||
(need (peek-metadatum %groups rid))
|
||||
[rid channels members channel-count metadatum]
|
||||
::
|
||||
++ channels
|
||||
|
@ -50,7 +50,7 @@
|
||||
%- pairs
|
||||
:~ bucket-key+s+b
|
||||
entry-key+s+k
|
||||
value+(val v)
|
||||
value+(value v)
|
||||
==
|
||||
::
|
||||
++ del-entry
|
||||
@ -68,6 +68,7 @@
|
||||
%s val
|
||||
%b val
|
||||
%n (numb p.val)
|
||||
%a [%a (turn p.val value)]
|
||||
==
|
||||
::
|
||||
++ bucket
|
||||
@ -105,7 +106,7 @@
|
||||
%- ot
|
||||
:~ bucket-key+so
|
||||
entry-key+so
|
||||
value+val
|
||||
value+value
|
||||
==
|
||||
::
|
||||
++ del-entry
|
||||
@ -121,6 +122,7 @@
|
||||
%s jon
|
||||
%b jon
|
||||
%n [%n (rash p.jon dem)]
|
||||
%a [%a (turn p.jon value)]
|
||||
==
|
||||
::
|
||||
++ bucket
|
||||
|
@ -442,19 +442,19 @@
|
||||
;< ~ bind:m (send-request (hiss-to-request:html hiss))
|
||||
take-maybe-sigh
|
||||
::
|
||||
:: +build-fail: build the source file at the specified $beam
|
||||
:: +build-file: build the source file at the specified $beam
|
||||
::
|
||||
++ build-file
|
||||
|= [[=ship =desk =case] =spur]
|
||||
=* arg +<
|
||||
=/ m (strand ,vase)
|
||||
=/ m (strand ,(unit vase))
|
||||
^- form:m
|
||||
;< =riot:clay bind:m
|
||||
(warp ship desk ~ %sing %a case spur)
|
||||
?~ riot
|
||||
(strand-fail %build-file >arg< ~)
|
||||
(pure:m ~)
|
||||
?> =(%vase p.r.u.riot)
|
||||
(pure:m !<(vase q.r.u.riot))
|
||||
(pure:m (some !<(vase q.r.u.riot)))
|
||||
:: +build-mark: build a mark definition to a $dais
|
||||
::
|
||||
++ build-mark
|
||||
|
@ -1,11 +1,21 @@
|
||||
|%
|
||||
+$ settings-0 (map key bucket-0)
|
||||
+$ bucket-0 (map key val-0)
|
||||
+$ val-0
|
||||
$% [%s p=@t]
|
||||
[%b p=?]
|
||||
[%n p=@]
|
||||
==
|
||||
::
|
||||
+$ settings (map key bucket)
|
||||
+$ bucket (map key val)
|
||||
+$ key term
|
||||
+$ val
|
||||
$~ [%n 0]
|
||||
$% [%s p=@t]
|
||||
[%b p=?]
|
||||
[%n p=@]
|
||||
[%a p=(list val)]
|
||||
==
|
||||
+$ event
|
||||
$% [%put-bucket =key =bucket]
|
||||
|
@ -1217,7 +1217,7 @@
|
||||
on-hear-forward
|
||||
::
|
||||
?: ?& ?=(%pawn (clan:title sndr.packet))
|
||||
!(~(has by peers.ames-state) sndr.packet)
|
||||
!?=([~ %known *] (~(get by peers.ames-state) sndr.packet))
|
||||
==
|
||||
on-hear-open
|
||||
on-hear-shut
|
||||
@ -1289,14 +1289,9 @@
|
||||
|= [=lane =packet dud=(unit goof)]
|
||||
^+ event-core
|
||||
=/ sndr-state (~(get by peers.ames-state) sndr.packet)
|
||||
:: if we don't know them, maybe enqueue a jael %public-keys request
|
||||
::
|
||||
:: Ignore encrypted packets from alien comets.
|
||||
:: TODO: maybe crash?
|
||||
:: if we don't know them, ask jael for their keys and enqueue
|
||||
::
|
||||
?. ?=([~ %known *] sndr-state)
|
||||
?: =(%pawn (clan:title sndr.packet))
|
||||
event-core
|
||||
(enqueue-alien-todo sndr.packet |=(alien-agenda +<))
|
||||
:: decrypt packet contents using symmetric-key.channel
|
||||
::
|
||||
|
@ -6,6 +6,9 @@
|
||||
=/ m (strand ,vase)
|
||||
^- form:m
|
||||
=+ !<([~ pax=path] arg)
|
||||
?^ bem=(de-beam pax)
|
||||
(build-file:strandio u.bem)
|
||||
(strand-fail:strand %path-not-beam >pax< ~)
|
||||
?~ bem=(de-beam pax)
|
||||
(strand-fail:strand %path-not-beam >pax< ~)
|
||||
;< vax=(unit vase) bind:m (build-file:strandio u.bem)
|
||||
?^ vax
|
||||
(pure:m u.vax)
|
||||
(strand-fail:strand %build-file >u.bem< ~)
|
||||
|
@ -92,8 +92,12 @@
|
||||
?. =(%hoon (rear p)) ~
|
||||
(some [[-.i.bez p] ~])
|
||||
loop(bez t.bez, fiz (~(gas in fiz) foz))
|
||||
~| bad-test-beam+i.bez
|
||||
=/ tex=term =-(?>(((sane %tas) -) -) (rear s.i.bez))
|
||||
::
|
||||
:: XX this logic appears to be vestigial
|
||||
::
|
||||
=/ tex=term
|
||||
~| bad-test-beam+i.bez
|
||||
=-(?>(((sane %tas) -) -) (rear s.i.bez))
|
||||
=/ xup=path (snip s.i.bez)
|
||||
;< hov=? bind:m (check-for-file:strandio i.bez(s (snoc xup %hoon)))
|
||||
?. hov
|
||||
@ -107,16 +111,23 @@
|
||||
=/ paz=(list path)
|
||||
(tail !<([~ (list path)] arg))
|
||||
=/ bez=(list beam)
|
||||
(turn paz |=(p=path (need (de-beam p))))
|
||||
(turn paz |=(p=path ~|([%test-not-beam p] (need (de-beam p)))))
|
||||
;< fiz=(set [=beam test=(unit term)]) bind:m (find-test-files bez)
|
||||
=> .(fiz (sort ~(tap in fiz) aor))
|
||||
=| test-arms=(map path (list test-arm))
|
||||
=| build-ok=?
|
||||
|- ^- form:m
|
||||
=* gather-tests $
|
||||
?^ fiz
|
||||
~> %slog.0^leaf+"test: building {(spud s.beam.i.fiz)}"
|
||||
;< cor=vase bind:m (build-file:strandio beam.i.fiz)
|
||||
=/ arms=(list test-arm) (get-test-arms cor)
|
||||
;< cor=(unit vase) bind:m (build-file:strandio beam.i.fiz)
|
||||
?~ cor
|
||||
~> %slog.0^leaf+"FAILED {(spud s.beam.i.fiz)} (build)"
|
||||
gather-tests(fiz t.fiz, build-ok |)
|
||||
~> %slog.0^leaf+"built {(spud s.beam.i.fiz)}"
|
||||
=/ arms=(list test-arm) (get-test-arms u.cor)
|
||||
::
|
||||
:: XX this logic appears to be vestigial
|
||||
::
|
||||
=? arms ?=(^ test.i.fiz)
|
||||
|- ^+ arms
|
||||
?~ arms ~|(no-test-arm+i.fiz !!)
|
||||
@ -127,7 +138,7 @@
|
||||
gather-tests(fiz t.fiz)
|
||||
%- pure:m !> ^= ok
|
||||
%+ roll (resolve-test-paths test-arms)
|
||||
|= [[=path =test-func] ok=_`?`%&]
|
||||
|= [[=path =test-func] ok=_build-ok]
|
||||
^+ ok
|
||||
=/ res (run-test path test-func)
|
||||
%- (slog (flop tang.res))
|
||||
|
@ -327,7 +327,7 @@
|
||||
:: echo "hello" | openssl dgst -sha256 -sign private.pem | base64
|
||||
%+ expect-eq
|
||||
!> exp2b64
|
||||
!> (en:base64 (met 3 sig) (swp 3 sig))
|
||||
!> (en:base64:mimes:html (met 3 sig) (swp 3 sig))
|
||||
==
|
||||
::
|
||||
++ test-csr
|
||||
|
@ -1,186 +1,3 @@
|
||||
const env = {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
};
|
||||
|
||||
const rules = {
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
"arrow-parens": [
|
||||
"error",
|
||||
"as-needed",
|
||||
{
|
||||
"requireForBlockBody": true
|
||||
}
|
||||
],
|
||||
"arrow-spacing": "error",
|
||||
"block-spacing": ["error", "always"],
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"camelcase": [
|
||||
"error",
|
||||
{
|
||||
"properties": "never"
|
||||
}
|
||||
],
|
||||
"comma-dangle": ["error", "never"],
|
||||
"eol-last": ["error", "always"],
|
||||
"func-name-matching": "error",
|
||||
"indent": [
|
||||
"off",
|
||||
2,
|
||||
{
|
||||
"ArrayExpression": "off",
|
||||
"SwitchCase": 1,
|
||||
"CallExpression": {
|
||||
"arguments": "off"
|
||||
},
|
||||
"FunctionDeclaration": {
|
||||
"parameters": "off"
|
||||
},
|
||||
"FunctionExpression": {
|
||||
"parameters": "off"
|
||||
},
|
||||
"MemberExpression": "off",
|
||||
"ObjectExpression": "off",
|
||||
"ImportDeclaration": "off"
|
||||
}
|
||||
],
|
||||
"handle-callback-err": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"max-lines": [
|
||||
"error",
|
||||
{
|
||||
"max": 300,
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
],
|
||||
"max-lines-per-function": [
|
||||
"warn",
|
||||
{
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
],
|
||||
"max-statements-per-line": [
|
||||
"error",
|
||||
{
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"new-cap": [
|
||||
"error",
|
||||
{
|
||||
"newIsCap": true,
|
||||
"capIsNew": false
|
||||
}
|
||||
],
|
||||
"new-parens": "error",
|
||||
"no-buffer-constructor": "error",
|
||||
"no-console": "off",
|
||||
"no-extra-semi": "off",
|
||||
"no-fallthrough": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-multi-assign": "error",
|
||||
"no-multiple-empty-lines": [
|
||||
"error",
|
||||
{
|
||||
"max": 1
|
||||
}
|
||||
],
|
||||
"no-nested-ternary": "error",
|
||||
"no-param-reassign": "off",
|
||||
"no-return-assign": "error",
|
||||
"no-return-await": "off",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-tabs": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"ignoreRestSiblings": false
|
||||
}
|
||||
],
|
||||
"no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
"functions": false,
|
||||
"classes": false
|
||||
}
|
||||
],
|
||||
"no-useless-escape": "off",
|
||||
"no-var": "error",
|
||||
"nonblock-statement-body-position": ["error", "below"],
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"padded-blocks": ["error", "never"],
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{
|
||||
"destructuring": "all",
|
||||
"ignoreReadBeforeAssign": true
|
||||
}
|
||||
],
|
||||
"prefer-template": "off",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"exceptions": ["!"]
|
||||
}
|
||||
],
|
||||
"space-before-blocks": "error",
|
||||
"unicode-bom": ["error", "never"],
|
||||
"valid-jsdoc": "error",
|
||||
"wrap-iife": ["error", "inside"],
|
||||
"react/jsx-closing-bracket-location": 1,
|
||||
"react/jsx-tag-spacing": 1,
|
||||
"react/jsx-max-props-per-line": ["error", { "maximum": 2, "when": "multiline" }],
|
||||
"react/prop-types": 0
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
"env": env,
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"eslint:recommended",
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "^16.5.2"
|
||||
}
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 10,
|
||||
"requireConfigFile": false,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"root": true,
|
||||
"rules": rules,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.ts", "**/*.tsx"],
|
||||
"env": env,
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": { "jsx": true },
|
||||
"ecmaVersion": 10,
|
||||
"requireConfigFile": false,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": rules
|
||||
}
|
||||
]
|
||||
};
|
||||
extends: "@urbit"
|
||||
};
|
@ -64,6 +64,12 @@ if(urbitrc.URL) {
|
||||
return '/index.js'
|
||||
}
|
||||
},
|
||||
'/~landscape/js/serviceworker.js': {
|
||||
target: 'http://localhost:9000',
|
||||
pathRewrite: (req, path) => {
|
||||
return '/serviceworker.js'
|
||||
}
|
||||
},
|
||||
'**': {
|
||||
changeOrigin: true,
|
||||
target: urbitrc.URL,
|
||||
@ -78,7 +84,8 @@ if(urbitrc.URL) {
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: {
|
||||
app: './src/index.js'
|
||||
app: './src/index.js',
|
||||
serviceworker: './src/serviceworker.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
@ -135,10 +142,13 @@ module.exports = {
|
||||
],
|
||||
watch: true,
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
chunkFilename: 'index.js',
|
||||
filename: (pathData) => {
|
||||
return pathData.chunk.name === 'app' ? 'index.js' : '[name].js';
|
||||
},
|
||||
chunkFilename: '[name].js',
|
||||
path: path.resolve(__dirname, '../dist'),
|
||||
publicPath: '/'
|
||||
publicPath: '/',
|
||||
globalObject: 'this'
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
|
@ -3,11 +3,15 @@ const path = require('path');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const MomentLocalesPlugin = require('moment-locales-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const GIT_DESC = execSync('git describe --always', { encoding: 'utf8' }).trim();
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
app: './src/index.js'
|
||||
app: './src/index.js',
|
||||
serviceworker: './src/serviceworker.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
@ -56,20 +60,22 @@ module.exports = {
|
||||
new CleanWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.LANDSCAPE_STREAM': JSON.stringify(process.env.LANDSCAPE_STREAM),
|
||||
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(process.env.LANDSCAPE_SHORTHASH),
|
||||
'process.env.LANDSCAPE_SHORTHASH': JSON.stringify(GIT_DESC),
|
||||
'process.env.TUTORIAL_HOST': JSON.stringify('~hastuc-dibtux'),
|
||||
'process.env.TUTORIAL_GROUP': JSON.stringify('beginner-island'),
|
||||
'process.env.TUTORIAL_CHAT': JSON.stringify('chat-8401'),
|
||||
'process.env.TUTORIAL_BOOK': JSON.stringify('notebook-9148'),
|
||||
'process.env.TUTORIAL_LINKS': JSON.stringify('links-4353'),
|
||||
}),
|
||||
})
|
||||
// new HtmlWebpackPlugin({
|
||||
// title: 'Hot Module Replacement',
|
||||
// template: './public/index.html',
|
||||
// }),
|
||||
],
|
||||
output: {
|
||||
filename: 'index.[contenthash].js',
|
||||
filename: (pathData) => {
|
||||
return pathData.chunk.name === 'app' ? 'index.[contenthash].js' : '[name].js';
|
||||
},
|
||||
path: path.resolve(__dirname, '../../arvo/app/landscape/js/bundle'),
|
||||
publicPath: '/'
|
||||
},
|
||||
|
978
pkg/interface/package-lock.json
generated
978
pkg/interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@
|
||||
"@tlon/indigo-light": "^1.0.6",
|
||||
"@tlon/indigo-react": "1.2.17",
|
||||
"@tlon/sigil-js": "^1.4.3",
|
||||
"@urbit/api": "file:../npm/api",
|
||||
"aws-sdk": "^2.830.0",
|
||||
"big-integer": "^1.6.48",
|
||||
"classnames": "^2.2.6",
|
||||
@ -45,6 +46,10 @@
|
||||
"styled-system": "^5.1.5",
|
||||
"suncalc": "^1.8.0",
|
||||
"urbit-ob": "^5.0.1",
|
||||
"workbox-core": "^6.0.2",
|
||||
"workbox-precaching": "^6.0.2",
|
||||
"workbox-recipes": "^6.0.2",
|
||||
"workbox-routing": "^6.0.2",
|
||||
"yup": "^0.29.3",
|
||||
"zustand": "^3.3.1"
|
||||
},
|
||||
@ -64,15 +69,15 @@
|
||||
"@types/styled-components": "^5.1.7",
|
||||
"@types/styled-system": "^5.1.10",
|
||||
"@types/yup": "^0.29.11",
|
||||
"@typescript-eslint/eslint-plugin": "^3.10.1",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||
"@urbit/eslint-config": "file:../npm/eslint-config",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"babel-plugin-root-import": "^6.6.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^4.5.1",
|
||||
@ -86,7 +91,7 @@
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ./src/**/*.{js,ts,tsx}",
|
||||
"lint": "eslint ./src/**/*.{ts,tsx}",
|
||||
"lint-file": "eslint",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import './register-sw';
|
||||
|
||||
import App from './views/App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
@ -1,6 +1,5 @@
|
||||
import _ from "lodash";
|
||||
import { uuid } from "../lib/util";
|
||||
import { Patp, Path } from "~/types/noun";
|
||||
import _ from 'lodash';
|
||||
import { Patp, Path } from '@urbit/api';
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
export default class BaseApi<S extends object = {}> {
|
||||
@ -26,8 +25,8 @@ export default class BaseApi<S extends object = {}> {
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path,
|
||||
},
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
@ -50,8 +49,12 @@ export default class BaseApi<S extends object = {}> {
|
||||
appl,
|
||||
mark,
|
||||
data,
|
||||
(json) => { resolve(json); },
|
||||
(err) => { reject(err); }
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -69,5 +72,4 @@ export default class BaseApi<S extends object = {}> {
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path, Enc } from '~/types/noun';
|
||||
import { Contact, ContactEdit } from '~/types/contact-update';
|
||||
import { GroupPolicy, Resource } from '~/types/group-update';
|
||||
import { Patp } from '@urbit/api';
|
||||
import { ContactEdit } from '@urbit/api/contacts';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
add(ship: Patp, contact: any) {
|
||||
@ -31,7 +30,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
ship,
|
||||
'edit-field': editField,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -62,7 +61,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
return this.action(
|
||||
'contact-push-hook',
|
||||
'contact-share',
|
||||
{ share: recipient },
|
||||
{ share: recipient }
|
||||
);
|
||||
}
|
||||
|
||||
@ -85,7 +84,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('contact-store', 'contact-update', action)
|
||||
return this.action('contact-store', 'contact-update', action);
|
||||
}
|
||||
|
||||
private viewAction(threadName: string, action: any) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Patp } from '~/types/noun';
|
||||
import { Patp } from '@urbit/api';
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import GlobalStore from '../store/store';
|
||||
@ -11,7 +11,7 @@ import LaunchApi from './launch';
|
||||
import GraphApi from './graph';
|
||||
import S3Api from './s3';
|
||||
import GcpApi from './gcp';
|
||||
import {HarkApi} from './hark';
|
||||
import { HarkApi } from './hark';
|
||||
import SettingsApi from './settings';
|
||||
|
||||
export default class GlobalApi extends BaseApi<StoreState> {
|
||||
|
@ -1,14 +1,14 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path, PatpNoSig } from '~/types/noun';
|
||||
import { Patp, Path } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import {makeResource, resourceFromPath} from '../lib/group';
|
||||
import {GroupPolicy, Enc, Post, NodeMap, Content, Resource} from '~/types';
|
||||
import { makeResource, resourceFromPath } from '../lib/group';
|
||||
import { GroupPolicy, Enc, Post, Content } from '@urbit/api';
|
||||
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util';
|
||||
|
||||
export const createBlankNodeWithChildPost = (
|
||||
parentIndex: string = '',
|
||||
childIndex: string = '',
|
||||
parentIndex = '',
|
||||
childIndex = '',
|
||||
contents: Content[]
|
||||
) => {
|
||||
const date = unixToDa(Date.now()).toString();
|
||||
@ -37,11 +37,11 @@ export const createBlankNodeWithChildPost = (
|
||||
signatures: []
|
||||
},
|
||||
children: childGraph
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function markPending(nodes: any) {
|
||||
_.forEach(nodes, node => {
|
||||
_.forEach(nodes, (node) => {
|
||||
node.post.author = deSig(node.post.author);
|
||||
node.post.pending = true;
|
||||
markPending(node.children || {});
|
||||
@ -50,8 +50,8 @@ function markPending(nodes: any) {
|
||||
|
||||
export const createPost = (
|
||||
contents: Content[],
|
||||
parentIndex: string = '',
|
||||
childIndex:string = 'DATE_PLACEHOLDER'
|
||||
parentIndex = '',
|
||||
childIndex = 'DATE_PLACEHOLDER'
|
||||
) => {
|
||||
if (childIndex === 'DATE_PLACEHOLDER') {
|
||||
childIndex = unixToDa(Date.now()).toString();
|
||||
@ -80,11 +80,10 @@ function moduleToMark(mod: string): string | undefined {
|
||||
}
|
||||
|
||||
export default class GraphApi extends BaseApi<StoreState> {
|
||||
|
||||
joiningGraphs = new Set<string>();
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('graph-store', 'graph-update', action)
|
||||
return this.action('graph-store', 'graph-update', action);
|
||||
}
|
||||
|
||||
private viewAction(threadName: string, action: any) {
|
||||
@ -106,12 +105,12 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
const resource = makeResource(`~${window.ship}`, name);
|
||||
|
||||
return this.viewAction('graph-create', {
|
||||
"create": {
|
||||
'create': {
|
||||
resource,
|
||||
title,
|
||||
description,
|
||||
associated,
|
||||
"module": mod,
|
||||
'module': mod,
|
||||
mark: moduleToMark(mod)
|
||||
}
|
||||
});
|
||||
@ -127,12 +126,12 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
const resource = makeResource(`~${window.ship}`, name);
|
||||
|
||||
return this.viewAction('graph-create', {
|
||||
"create": {
|
||||
'create': {
|
||||
resource,
|
||||
title,
|
||||
description,
|
||||
associated: { policy },
|
||||
"module": mod,
|
||||
'module': mod,
|
||||
mark: moduleToMark(mod)
|
||||
}
|
||||
});
|
||||
@ -148,9 +147,9 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
return this.viewAction('graph-join', {
|
||||
join: {
|
||||
resource,
|
||||
ship,
|
||||
ship
|
||||
}
|
||||
}).then(res => {
|
||||
}).then((res) => {
|
||||
this.joiningGraphs.delete(rid);
|
||||
return res;
|
||||
});
|
||||
@ -159,7 +158,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
deleteGraph(name: string) {
|
||||
const resource = makeResource(`~${window.ship}`, name);
|
||||
return this.viewAction('graph-delete', {
|
||||
"delete": {
|
||||
'delete': {
|
||||
resource
|
||||
}
|
||||
});
|
||||
@ -168,7 +167,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
leaveGraph(ship: Patp, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
return this.viewAction('graph-leave', {
|
||||
"leave": {
|
||||
'leave': {
|
||||
resource
|
||||
}
|
||||
});
|
||||
@ -203,7 +202,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
addPost(ship: Patp, name: string, post: Post) {
|
||||
let nodes = {};
|
||||
const nodes = {};
|
||||
nodes[post.index] = {
|
||||
post,
|
||||
children: null
|
||||
@ -212,7 +211,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
addNode(ship: Patp, name: string, node: Object) {
|
||||
let nodes = {};
|
||||
const nodes = {};
|
||||
nodes[node.post.index] = node;
|
||||
|
||||
return this.addNodes(ship, name, nodes);
|
||||
@ -300,7 +299,6 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
|
||||
getGraphSubset(ship: string, resource: string, start: string, end: string) {
|
||||
return this.scry<any>(
|
||||
'graph-store',
|
||||
|
@ -1,14 +1,14 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp, Enc } from '~/types/noun';
|
||||
import { Path, Patp, Enc } from '@urbit/api';
|
||||
import {
|
||||
GroupAction,
|
||||
GroupPolicy,
|
||||
Resource,
|
||||
Tag,
|
||||
GroupPolicyDiff,
|
||||
} from '~/types/group-update';
|
||||
import {makeResource} from '../lib/group';
|
||||
GroupPolicyDiff
|
||||
} from '@urbit/api/groups';
|
||||
import { makeResource } from '../lib/group';
|
||||
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
remove(resource: Resource, ships: Patp[]) {
|
||||
@ -38,7 +38,7 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
join(ship: string, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
|
||||
return this.viewAction({ join: { resource, ship }});
|
||||
return this.viewAction({ join: { resource, ship } });
|
||||
}
|
||||
|
||||
create(name: string, policy: Enc<GroupPolicy>, title: string, description: string) {
|
||||
@ -76,7 +76,6 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
description
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private proxyAction(action: GroupAction) {
|
||||
@ -93,6 +92,5 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
|
||||
private viewAction(action: any) {
|
||||
return this.action('group-view', 'group-view-action', action);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,23 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { dateToDa, decToUd } from "../lib/util";
|
||||
import {NotifIndex, IndexedNotification, Association, GraphNotifDescription} from "~/types";
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { dateToDa, decToUd } from '../lib/util';
|
||||
import { NotifIndex, IndexedNotification, Association, GraphNotifDescription } from '@urbit/api';
|
||||
import { BigInteger } from 'big-integer';
|
||||
import {getParentIndex} from "../lib/notification";
|
||||
import { getParentIndex } from '../lib/notification';
|
||||
|
||||
export class HarkApi extends BaseApi<StoreState> {
|
||||
private harkAction(action: any): Promise<any> {
|
||||
return this.action("hark-store", "hark-action", action);
|
||||
return this.action('hark-store', 'hark-action', action);
|
||||
}
|
||||
|
||||
private graphHookAction(action: any) {
|
||||
return this.action("hark-graph-hook", "hark-graph-hook-action", action);
|
||||
return this.action('hark-graph-hook', 'hark-graph-hook-action', action);
|
||||
}
|
||||
|
||||
private groupHookAction(action: any) {
|
||||
return this.action("hark-group-hook", "hark-group-hook-action", action);
|
||||
return this.action('hark-group-hook', 'hark-group-hook-action', action);
|
||||
}
|
||||
|
||||
|
||||
private actOnNotification(frond: string, intTime: BigInteger, index: NotifIndex) {
|
||||
const time = decToUd(intTime.toString());
|
||||
return this.harkAction({
|
||||
@ -74,12 +73,10 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
module: association.metadata.module,
|
||||
description,
|
||||
index: parent
|
||||
} },
|
||||
} }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
markEachAsRead(association: Association, parent: string, child: string, description: GraphNotifDescription, mod: string) {
|
||||
return this.harkAction({
|
||||
'read-each': {
|
||||
@ -116,7 +113,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
mute(notif: IndexedNotification) {
|
||||
if('graph' in notif.index && 'graph' in notif.notification.contents) {
|
||||
const { index } = notif;
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph)
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph);
|
||||
if(!parentIndex) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -132,7 +129,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
unmute(notif: IndexedNotification) {
|
||||
if('graph' in notif.index && 'graph' in notif.notification.contents) {
|
||||
const { index } = notif;
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph)
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph);
|
||||
if(!parentIndex) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -147,7 +144,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
ignoreGroup(group: string) {
|
||||
return this.groupHookAction({
|
||||
ignore: group
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
ignoreGraph(graph: string, index: string) {
|
||||
@ -156,13 +153,13 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
graph,
|
||||
index
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
listenGroup(group: string) {
|
||||
return this.groupHookAction({
|
||||
listen: group
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
listenGraph(graph: string, index: string) {
|
||||
@ -171,7 +168,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
graph,
|
||||
index
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async getMore(): Promise<boolean> {
|
||||
@ -183,16 +180,16 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
|
||||
async getSubset(offset:number, count:number, isArchive: boolean) {
|
||||
const where = isArchive ? 'archive' : 'inbox';
|
||||
const data = await this.scry("hark-store", `/recent/${where}/${offset}/${count}`);
|
||||
const data = await this.scry('hark-store', `/recent/${where}/${offset}/${count}`);
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
async getTimeSubset(start?: Date, end?: Date) {
|
||||
const s = start ? dateToDa(start) : "-";
|
||||
const e = end ? dateToDa(end) : "-";
|
||||
const result = await this.scry("hark-hook", `/recent/${s}/${e}`);
|
||||
const s = start ? dateToDa(start) : '-';
|
||||
const e = end ? dateToDa(end) : '-';
|
||||
const result = await this.scry('hark-hook', `/recent/${s}/${e}`);
|
||||
this.store.handleEvent({
|
||||
data: result,
|
||||
data: result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { Serial, Path } from "~/types/noun";
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Serial, Path } from '@urbit/api';
|
||||
|
||||
export default class InviteApi extends BaseApi<StoreState> {
|
||||
accept(app: string, uid: Serial) {
|
||||
|
@ -2,7 +2,7 @@ import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
export default class LaunchApi extends BaseApi<StoreState> {
|
||||
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
|
||||
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) {
|
||||
return this.launchAction({ add: { name, tile } });
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export default class LaunchApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
changeIsShown(name: string, isShown = true) {
|
||||
return this.launchAction({ 'change-is-shown': { name, isShown }});
|
||||
return this.launchAction({ 'change-is-shown': { name, isShown } });
|
||||
}
|
||||
|
||||
weather(location: string) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
export default class LocalApi extends BaseApi<StoreState> {
|
||||
getBaseHash() {
|
||||
this.scry<string>('file-server', '/clay/base/hash').then(baseHash => {
|
||||
this.scry<string>('file-server', '/clay/base/hash').then((baseHash) => {
|
||||
this.store.handleEvent({ data: { local: { baseHash } } });
|
||||
});
|
||||
}
|
||||
@ -11,5 +11,4 @@ export default class LocalApi extends BaseApi<StoreState> {
|
||||
dehydrate() {
|
||||
this.store.dehydrate();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '~/types';
|
||||
import {uxToHex} from '../lib/util';
|
||||
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '@urbit/api';
|
||||
import { uxToHex } from '../lib/util';
|
||||
|
||||
export default class MetadataApi extends BaseApi<StoreState> {
|
||||
|
||||
|
||||
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
|
||||
const creator = `~${this.ship}`;
|
||||
return this.metadataAction({
|
||||
@ -44,9 +42,9 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
update(association: Association, newMetadata: Partial<Metadata>) {
|
||||
const metadata = {...association.metadata, ...newMetadata };
|
||||
const metadata = { ...association.metadata, ...newMetadata };
|
||||
metadata.color = uxToHex(metadata.color);
|
||||
return this.metadataAction({
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
group: association.group,
|
||||
resource: {
|
||||
@ -69,10 +67,10 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
}
|
||||
done = true;
|
||||
tempChannel.delete();
|
||||
reject(new Error("offline"))
|
||||
reject(new Error('offline'));
|
||||
}, 15000);
|
||||
|
||||
tempChannel.subscribe(window.ship, "metadata-pull-hook", `/preview${group}`,
|
||||
tempChannel.subscribe(window.ship, 'metadata-pull-hook', `/preview${group}`,
|
||||
(err) => {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
@ -88,24 +86,22 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
} else {
|
||||
done = true;
|
||||
tempChannel.delete();
|
||||
reject(new Error("no-permissions"));
|
||||
reject(new Error('no-permissions'));
|
||||
}
|
||||
},
|
||||
(quit) => {
|
||||
tempChannel.delete();
|
||||
if(!done) {
|
||||
reject(new Error("offline"))
|
||||
reject(new Error('offline'));
|
||||
}
|
||||
},
|
||||
(a) => {
|
||||
console.log(a);
|
||||
}
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private metadataAction(data) {
|
||||
return this.action('metadata-push-hook', 'metadata-update', data);
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import {S3Update} from '../../types/s3-update';
|
||||
|
||||
import { S3Update } from '../../types/s3-update';
|
||||
|
||||
export default class S3Api extends BaseApi<StoreState> {
|
||||
|
||||
setCurrentBucket(bucket: string) {
|
||||
return this.s3Action({ 'set-current-bucket': bucket });
|
||||
}
|
||||
@ -32,6 +30,5 @@ export default class S3Api extends BaseApi<StoreState> {
|
||||
private s3Action(data: any) {
|
||||
return this.action('s3-store', 's3-action', data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import {
|
||||
SettingsUpdate,
|
||||
SettingsData,
|
||||
Key,
|
||||
import { Key,
|
||||
Value,
|
||||
Bucket,
|
||||
} from '~/types/settings';
|
||||
|
||||
Bucket
|
||||
} from '@urbit/api/settings';
|
||||
|
||||
export default class SettingsApi extends BaseApi<StoreState> {
|
||||
private storeAction(action: SettingsEvent): Promise<any> {
|
||||
@ -16,59 +12,59 @@ export default class SettingsApi extends BaseApi<StoreState> {
|
||||
|
||||
putBucket(key: Key, bucket: Bucket) {
|
||||
this.storeAction({
|
||||
"put-bucket": {
|
||||
"bucket-key": key,
|
||||
"bucket": bucket,
|
||||
'put-bucket': {
|
||||
'bucket-key': key,
|
||||
'bucket': bucket
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delBucket(key: Key) {
|
||||
this.storeAction({
|
||||
"del-bucket": {
|
||||
"bucket-key": key,
|
||||
'del-bucket': {
|
||||
'bucket-key': key
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
putEntry(buc: Key, key: Key, val: Value) {
|
||||
return this.storeAction({
|
||||
"put-entry": {
|
||||
"bucket-key": buc,
|
||||
"entry-key": key,
|
||||
"value": val,
|
||||
'put-entry': {
|
||||
'bucket-key': buc,
|
||||
'entry-key': key,
|
||||
'value': val
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delEntry(buc: Key, key: Key) {
|
||||
this.storeAction({
|
||||
"put-entry": {
|
||||
"bucket-key": buc,
|
||||
"entry-key": key,
|
||||
'put-entry': {
|
||||
'bucket-key': buc,
|
||||
'entry-key': key
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
const data = await this.scry("settings-store", "/all");
|
||||
this.store.handleEvent({data: {"settings-data": data.all}});
|
||||
const data = await this.scry('settings-store', '/all');
|
||||
this.store.handleEvent({ data: { 'settings-data': data.all } });
|
||||
}
|
||||
|
||||
async getBucket(bucket: Key) {
|
||||
const data = await this.scry('settings-store', `/bucket/${bucket}`);
|
||||
this.store.handleEvent({data: {"settings-data": {
|
||||
"bucket-key": bucket,
|
||||
"bucket": data.bucket,
|
||||
}}});
|
||||
this.store.handleEvent({ data: { 'settings-data': {
|
||||
'bucket-key': bucket,
|
||||
'bucket': data.bucket
|
||||
} } });
|
||||
}
|
||||
|
||||
async getEntry(bucket: Key, entry: Key) {
|
||||
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
|
||||
this.store.handleEvent({data: {"settings-data": {
|
||||
"bucket-key": bucket,
|
||||
"entry-key": entry,
|
||||
"entry": data.entry,
|
||||
}}});
|
||||
this.store.handleEvent({ data: { 'settings-data': {
|
||||
'bucket-key': bucket,
|
||||
'entry-key': entry,
|
||||
'entry': data.entry
|
||||
} } });
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
interface NonemptyNode<V> {
|
||||
n: [BigInteger, V];
|
||||
@ -14,7 +14,7 @@ type MapNode<V> = NonemptyNode<V> | null;
|
||||
*/
|
||||
export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
private root: MapNode<V> = null;
|
||||
size: number = 0;
|
||||
size = 0;
|
||||
|
||||
constructor(initial: [BigInteger, V][] = []) {
|
||||
initial.forEach(([key, val]) => {
|
||||
@ -48,13 +48,12 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
* Put an item by a key
|
||||
*/
|
||||
set(key: BigInteger, value: V): void {
|
||||
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if (!node) {
|
||||
return {
|
||||
n: [key, value],
|
||||
l: null,
|
||||
r: null,
|
||||
r: null
|
||||
};
|
||||
}
|
||||
const [k] = node.n;
|
||||
@ -62,22 +61,22 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
this.size--;
|
||||
return {
|
||||
...node,
|
||||
n: [k, value],
|
||||
n: [k, value]
|
||||
};
|
||||
}
|
||||
if (key.gt(k)) {
|
||||
const l = inner(node.l);
|
||||
if (!l) {
|
||||
throw new Error("invariant violation");
|
||||
throw new Error('invariant violation');
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
l,
|
||||
l
|
||||
};
|
||||
}
|
||||
const r = inner(node.r);
|
||||
if (!r) {
|
||||
throw new Error("invariant violation");
|
||||
throw new Error('invariant violation');
|
||||
}
|
||||
|
||||
return { ...node, r };
|
||||
@ -133,8 +132,8 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
bool,
|
||||
{
|
||||
...node,
|
||||
l,
|
||||
},
|
||||
l
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@ -143,8 +142,8 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
bool,
|
||||
{
|
||||
...node,
|
||||
r,
|
||||
},
|
||||
r
|
||||
}
|
||||
];
|
||||
};
|
||||
const [ret, newRoot] = inner(this.root);
|
||||
@ -165,12 +164,12 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
}
|
||||
return {
|
||||
...node.l,
|
||||
r: inner(node.r),
|
||||
r: inner(node.r)
|
||||
};
|
||||
};
|
||||
return inner(nod);
|
||||
}
|
||||
|
||||
|
||||
peekLargest(): [BigInteger, V] | undefined {
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if(!node) {
|
||||
@ -180,7 +179,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
return inner(node.l);
|
||||
}
|
||||
return node.n;
|
||||
}
|
||||
};
|
||||
return inner(this.root);
|
||||
}
|
||||
|
||||
@ -193,7 +192,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
return inner(node.r);
|
||||
}
|
||||
return node.n;
|
||||
}
|
||||
};
|
||||
return inner(this.root);
|
||||
}
|
||||
|
||||
@ -208,7 +207,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[BigInteger, V]> {
|
||||
let result: [BigInteger, V][] = [];
|
||||
const result: [BigInteger, V][] = [];
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if (!node) {
|
||||
return;
|
||||
@ -227,7 +226,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
return { value: result[idx++], done: false };
|
||||
}
|
||||
return { done: true, value: null };
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
export class OrderedMap<V> extends Map<number, V>
|
||||
implements Iterable<[number, V]> {
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[number, V]> {
|
||||
const sorted = Array.from(super[Symbol.iterator]()).sort(
|
||||
([a], [b]) => b - a
|
||||
@ -15,7 +14,7 @@ export class OrderedMap<V> extends Map<number, V>
|
||||
} else {
|
||||
return { done: true, value: null };
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
export function max(a: BigInteger, b: BigInteger) {
|
||||
return a.gt(b) ? a : b;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from "lodash";
|
||||
import { roleTags, RoleTags, Group, Resource } from "~/types/group-update";
|
||||
import { PatpNoSig, Path } from "~/types/noun";
|
||||
import {deSig} from "./util";
|
||||
import _ from 'lodash';
|
||||
import { roleTags, RoleTags, Group, Resource } from '@urbit/api/groups';
|
||||
import { PatpNoSig, Path } from '@urbit/api';
|
||||
import { deSig } from './util';
|
||||
|
||||
export function roleForShip(
|
||||
group: Group,
|
||||
@ -14,7 +14,7 @@ export function roleForShip(
|
||||
}
|
||||
|
||||
export function resourceFromPath(path: Path): Resource {
|
||||
const [, , ship, name] = path.split("/");
|
||||
const [, , ship, name] = path.split('/');
|
||||
return { ship, name };
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ export function makeResource(ship: string, name: string) {
|
||||
export function isWriter(group: Group, resource: string) {
|
||||
const writers: Set<string> | undefined = _.get(
|
||||
group,
|
||||
["tags", "graph", resource, "writers"],
|
||||
['tags', 'graph', resource, 'writers'],
|
||||
undefined
|
||||
);
|
||||
const admins = group?.tags?.role?.admin ?? new Set();
|
||||
@ -36,18 +36,18 @@ export function isWriter(group: Group, resource: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function isChannelAdmin(group: Group, resource: string, ship: string = `~${window.ship}`) {
|
||||
export function isChannelAdmin(group: Group, resource: string, ship = `~${window.ship}`) {
|
||||
const role = roleForShip(group, ship.slice(1));
|
||||
|
||||
return (
|
||||
isHost(resource, ship) ||
|
||||
role === "admin" ||
|
||||
role === "moderator"
|
||||
role === 'admin' ||
|
||||
role === 'moderator'
|
||||
);
|
||||
}
|
||||
|
||||
export function isHost(resource: string, ship: string = `~${window.ship}`) {
|
||||
const [, , host] = resource.split("/");
|
||||
export function isHost(resource: string, ship = `~${window.ship}`) {
|
||||
const [, , host] = resource.split('/');
|
||||
|
||||
return ship === host;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import f from "lodash/fp";
|
||||
import { Unreads } from "~/types";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import f from 'lodash/fp';
|
||||
import { Unreads } from '@urbit/api';
|
||||
|
||||
export function getLastSeen(
|
||||
unreads: Unreads,
|
||||
@ -8,10 +8,10 @@ export function getLastSeen(
|
||||
index: string
|
||||
): BigInteger | undefined {
|
||||
const lastSeenIdx = unreads.graph?.[path]?.[index]?.unreads;
|
||||
if (!(typeof lastSeenIdx === "string")) {
|
||||
if (!(typeof lastSeenIdx === 'string')) {
|
||||
return bigInt.zero;
|
||||
}
|
||||
return f.flow(f.split("/"), f.last, (x) => (!!x ? bigInt(x) : undefined))(
|
||||
return f.flow(f.split('/'), f.last, x => (x ? bigInt(x) : undefined))(
|
||||
lastSeenIdx
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { GraphNotifIndex, GraphNotificationContents } from "~/types";
|
||||
import { GraphNotifIndex, GraphNotificationContents } from '@urbit/api';
|
||||
|
||||
export function getParentIndex(
|
||||
idx: GraphNotifIndex,
|
||||
contents: GraphNotificationContents
|
||||
) {
|
||||
const origIndex = contents[0].index.slice(1).split("/");
|
||||
const ret = (i: string[]) => `/${i.join("/")}`;
|
||||
const origIndex = contents[0].index.slice(1).split('/');
|
||||
const ret = (i: string[]) => `/${i.join('/')}`;
|
||||
switch (idx.description) {
|
||||
case "link":
|
||||
return "/";
|
||||
case "comment":
|
||||
case 'link':
|
||||
return '/';
|
||||
case 'comment':
|
||||
return ret(origIndex.slice(0, 1));
|
||||
case "note":
|
||||
return "/";
|
||||
case "mention":
|
||||
case 'note':
|
||||
return '/';
|
||||
case 'mention':
|
||||
return undefined;
|
||||
default:
|
||||
return undefined;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Post, GraphNode } from "~/types";
|
||||
import { Post, GraphNode } from '@urbit/api';
|
||||
|
||||
export const buntPost = (): Post => ({
|
||||
author: '',
|
||||
@ -10,7 +10,7 @@ export const buntPost = (): Post => ({
|
||||
});
|
||||
|
||||
export function makeNodeMap(posts: Post[]): Record<string, GraphNode> {
|
||||
let nodes = {};
|
||||
const nodes = {};
|
||||
posts.forEach((p) => {
|
||||
nodes[p.index] = { children: { empty: null }, post: p };
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Post, GraphNode, TextContent, Graph, NodeMap } from "~/types";
|
||||
import { Post, GraphNode, TextContent, Graph, NodeMap } from '@urbit/api';
|
||||
import { buntPost } from '~/logic/lib/post';
|
||||
import { unixToDa } from "~/logic/lib/util";
|
||||
import {BigIntOrderedMap} from "./BigIntOrderedMap";
|
||||
import bigInt, {BigInteger} from 'big-integer';
|
||||
import { unixToDa } from '~/logic/lib/util';
|
||||
import { BigIntOrderedMap } from './BigIntOrderedMap';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
export function newPost(
|
||||
title: string,
|
||||
@ -12,20 +12,20 @@ export function newPost(
|
||||
const nowDa = unixToDa(now);
|
||||
const root: Post = {
|
||||
author: `~${window.ship}`,
|
||||
index: "/" + nowDa.toString(),
|
||||
"time-sent": now,
|
||||
index: '/' + nowDa.toString(),
|
||||
'time-sent': now,
|
||||
contents: [],
|
||||
hash: null,
|
||||
signatures: [],
|
||||
signatures: []
|
||||
};
|
||||
|
||||
const revContainer: Post = { ...root, index: root.index + "/1" };
|
||||
const commentsContainer = { ...root, index: root.index + "/2" };
|
||||
const revContainer: Post = { ...root, index: root.index + '/1' };
|
||||
const commentsContainer = { ...root, index: root.index + '/2' };
|
||||
|
||||
const firstRevision: Post = {
|
||||
...revContainer,
|
||||
index: revContainer.index + "/1",
|
||||
contents: [{ text: title }, { text: body }],
|
||||
index: revContainer.index + '/1',
|
||||
contents: [{ text: title }, { text: body }]
|
||||
};
|
||||
|
||||
const nodes = {
|
||||
@ -37,16 +37,16 @@ export function newPost(
|
||||
children: {
|
||||
1: {
|
||||
post: firstRevision,
|
||||
children: null,
|
||||
},
|
||||
},
|
||||
children: null
|
||||
}
|
||||
}
|
||||
},
|
||||
2: {
|
||||
post: commentsContainer,
|
||||
children: null
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [nowDa, nodes];
|
||||
@ -57,15 +57,15 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s
|
||||
const newRev: Post = {
|
||||
author: `~${window.ship}`,
|
||||
index: `/${noteId.toString()}/1/${rev}`,
|
||||
"time-sent": now,
|
||||
'time-sent': now,
|
||||
contents: [{ text: title }, { text: body }],
|
||||
hash: null,
|
||||
signatures: [],
|
||||
signatures: []
|
||||
};
|
||||
const nodes = {
|
||||
[newRev.index]: {
|
||||
post: newRev,
|
||||
children: null
|
||||
children: null
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,7 +74,7 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s
|
||||
|
||||
export function getLatestRevision(node: GraphNode): [number, string, string, Post] {
|
||||
const revs = node.children.get(bigInt(1));
|
||||
const empty = [1, "", "", buntPost()] as [number, string, string, Post];
|
||||
const empty = [1, '', '', buntPost()] as [number, string, string, Post];
|
||||
if(!revs) {
|
||||
return empty;
|
||||
}
|
||||
@ -98,17 +98,16 @@ export function getLatestCommentRevision(node: GraphNode): [number, Post] {
|
||||
return [revNum.toJSNumber(), rev.post];
|
||||
}
|
||||
|
||||
|
||||
export function getComments(node: GraphNode): GraphNode {
|
||||
const comments = node.children.get(bigInt(2));
|
||||
if(!comments) {
|
||||
return { post: buntPost(), children: new BigIntOrderedMap() }
|
||||
return { post: buntPost(), children: new BigIntOrderedMap() };
|
||||
}
|
||||
return comments;
|
||||
}
|
||||
|
||||
export function getSnippet(body: string) {
|
||||
const start = body.slice(0, body.indexOf('\n', 2));
|
||||
return (start === body || start.startsWith("![")) ? start : `${start}...`;
|
||||
return (start === body || start.startsWith('![')) ? start : `${start}...`;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import _ from "lodash";
|
||||
import _ from 'lodash';
|
||||
|
||||
export const alignY = ["top", "bottom"] as const;
|
||||
export const alignY = ['top', 'bottom'] as const;
|
||||
export type AlignY = typeof alignY[number];
|
||||
export const alignX = ["left", "right"] as const;
|
||||
export const alignX = ['left', 'right'] as const;
|
||||
export type AlignX = typeof alignX[number];
|
||||
|
||||
export function getRelativePosition(
|
||||
relativeTo: HTMLElement | null,
|
||||
alignX: AlignX | AlignX[],
|
||||
alignY: AlignY | AlignY[],
|
||||
offsetX: number = 0,
|
||||
offsetY: number = 0
|
||||
offsetX = 0,
|
||||
offsetY = 0
|
||||
) {
|
||||
const rect = relativeTo?.getBoundingClientRect();
|
||||
if (!rect) {
|
||||
@ -20,7 +20,7 @@ export function getRelativePosition(
|
||||
top: rect.top - offsetY,
|
||||
left: rect.left - offsetX,
|
||||
bottom: document.documentElement.clientHeight - rect.bottom - offsetY,
|
||||
right: document.documentElement.clientWidth - rect.right - offsetX,
|
||||
right: document.documentElement.clientWidth - rect.right - offsetX
|
||||
};
|
||||
const alignXArr = _.isArray(alignX) ? alignX : [alignX];
|
||||
const alignYArr = _.isArray(alignY) ? alignY : [alignY];
|
||||
@ -34,7 +34,7 @@ export function getRelativePosition(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
)
|
||||
}),
|
||||
{}
|
||||
),
|
||||
@ -46,10 +46,10 @@ export function getRelativePosition(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
)
|
||||
}),
|
||||
{}
|
||||
),
|
||||
)
|
||||
} as Record<AlignY | AlignX, string[]>;
|
||||
}
|
||||
|
||||
|
@ -52,16 +52,13 @@ const tokenizeMessage = (text) => {
|
||||
}
|
||||
messages.push({ url: str });
|
||||
message = [];
|
||||
} else if (urbitOb.isValidPatp(str.replace(/[^a-z\-\~]/g, '')) && !isInCodeBlock) {
|
||||
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
|
||||
if (message.length > 0) {
|
||||
// If we're in the middle of a message, add it to the stack and reset
|
||||
messages.push({ text: message.join(' ') });
|
||||
message = [];
|
||||
}
|
||||
messages.push({ mention: str.replace(/[^a-z\-\~]/g, '') });
|
||||
if (str.replace(/[a-z\-\~]/g, '').length > 0) {
|
||||
messages.push({ text: str.replace(/[a-z\-\~]/g, '') });
|
||||
}
|
||||
messages.push({ mention: str });
|
||||
message = [];
|
||||
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { TutorialProgress, Associations } from "~/types";
|
||||
import { AlignX, AlignY } from "~/logic/lib/relativePosition";
|
||||
import { Direction } from "~/views/components/Triangle";
|
||||
import { TutorialProgress, Associations } from '@urbit/api';
|
||||
import { AlignX, AlignY } from '~/logic/lib/relativePosition';
|
||||
import { Direction } from '~/views/components/Triangle';
|
||||
|
||||
export const MODAL_WIDTH = 256;
|
||||
export const MODAL_HEIGHT = 256;
|
||||
@ -43,7 +43,7 @@ export const getTrianglePosition = (dir: Direction) => {
|
||||
return {
|
||||
top: midY,
|
||||
left: '-32px'
|
||||
}
|
||||
};
|
||||
case 'North':
|
||||
return {
|
||||
top: '-32px',
|
||||
@ -55,117 +55,117 @@ export const getTrianglePosition = (dir: Direction) => {
|
||||
left: midX
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const progressDetails: Record<TutorialProgress, StepDetail> = {
|
||||
hidden: {} as any,
|
||||
exit: {} as any,
|
||||
done: {
|
||||
title: "End",
|
||||
title: 'End',
|
||||
description:
|
||||
"This tutorial is finished. Would you like to leave Beginner Island?",
|
||||
url: "/",
|
||||
alignX: "right",
|
||||
alignY: "top",
|
||||
'This tutorial is finished. Would you like to leave Beginner Island?',
|
||||
url: '/',
|
||||
alignX: 'right',
|
||||
alignY: 'top',
|
||||
offsetX: MODAL_WIDTH + 8,
|
||||
offsetY: 0,
|
||||
offsetY: 0
|
||||
},
|
||||
start: {
|
||||
title: "New Group added",
|
||||
title: 'New Group added',
|
||||
description:
|
||||
"We just added you to the Beginner island group to show you around. This group is public, but other groups can be private",
|
||||
url: "/",
|
||||
alignX: "right",
|
||||
alignY: "top",
|
||||
arrow: "West",
|
||||
'We just added you to the Beginner island group to show you around. This group is public, but other groups can be private',
|
||||
url: '/',
|
||||
alignX: 'right',
|
||||
alignY: 'top',
|
||||
arrow: 'West',
|
||||
offsetX: MODAL_WIDTH + 24,
|
||||
offsetY: 64,
|
||||
offsetY: 64
|
||||
},
|
||||
"group-desc": {
|
||||
title: "What's a group",
|
||||
'group-desc': {
|
||||
title: 'What\'s a group',
|
||||
description:
|
||||
"A group contains members and tends to be centered around a topic or multiple topics.",
|
||||
'A group contains members and tends to be centered around a topic or multiple topics.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
|
||||
alignX: "left",
|
||||
alignY: "top",
|
||||
arrow: "East",
|
||||
alignX: 'left',
|
||||
alignY: 'top',
|
||||
arrow: 'East',
|
||||
offsetX: MODAL_WIDTH + 24,
|
||||
offsetY: MODAL_HEIGHT / 2 - 8,
|
||||
offsetY: MODAL_HEIGHT / 2 - 8
|
||||
},
|
||||
channels: {
|
||||
title: "Channels",
|
||||
title: 'Channels',
|
||||
description:
|
||||
"Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!",
|
||||
'Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "West",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'West',
|
||||
offsetX: MODAL_WIDTH + 24,
|
||||
offsetY: -8,
|
||||
offsetY: -8
|
||||
},
|
||||
chat: {
|
||||
title: "Chat",
|
||||
title: 'Chat',
|
||||
description:
|
||||
"Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen",
|
||||
'Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/chat/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}`,
|
||||
alignY: "top",
|
||||
arrow: "North",
|
||||
alignX: "right",
|
||||
alignY: 'top',
|
||||
arrow: 'North',
|
||||
alignX: 'right',
|
||||
offsetY: -56,
|
||||
offsetX: -8,
|
||||
offsetX: -8
|
||||
},
|
||||
link: {
|
||||
title: "Collection",
|
||||
title: 'Collection',
|
||||
description:
|
||||
"A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.",
|
||||
'A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/link/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'North',
|
||||
offsetX: -8,
|
||||
offsetY: -56,
|
||||
offsetY: -56
|
||||
},
|
||||
publish: {
|
||||
title: "Notebook",
|
||||
title: 'Notebook',
|
||||
description:
|
||||
"Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.",
|
||||
'Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/publish/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'North',
|
||||
offsetX: -8,
|
||||
offsetY: -56,
|
||||
offsetY: -56
|
||||
},
|
||||
notifications: {
|
||||
title: "Notifications",
|
||||
description: "You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.",
|
||||
title: 'Notifications',
|
||||
description: 'You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.',
|
||||
url: '/~notifications',
|
||||
alignY: "top",
|
||||
alignX: "left",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'left',
|
||||
arrow: 'North',
|
||||
offsetX: (MODAL_WIDTH / 2) - 16,
|
||||
offsetY: -48,
|
||||
offsetY: -48
|
||||
},
|
||||
profile: {
|
||||
title: "Profile",
|
||||
title: 'Profile',
|
||||
description:
|
||||
"Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.",
|
||||
'Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.',
|
||||
url: `/~profile/~${window.ship}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "South",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'South',
|
||||
offsetX: -300 + MODAL_WIDTH / 2,
|
||||
offsetY: -120 + MODAL_HEIGHT / 2,
|
||||
offsetY: -120 + MODAL_HEIGHT / 2
|
||||
},
|
||||
leap: {
|
||||
title: "Leap",
|
||||
title: 'Leap',
|
||||
description:
|
||||
"Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.",
|
||||
'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.',
|
||||
url: `/~profile/~${window.ship}`,
|
||||
alignY: "top",
|
||||
alignX: "left",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'left',
|
||||
arrow: 'North',
|
||||
offsetX: 0.3 *MODAL_HEIGHT,
|
||||
offsetY: -48,
|
||||
},
|
||||
offsetY: -48
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useMemo, useEffect } from "react";
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
|
||||
function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
|
||||
const files: File[] = [];
|
||||
@ -8,8 +8,8 @@ function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
|
||||
}
|
||||
if (e.dataTransfer?.items) {
|
||||
Array.from(e.dataTransfer.items || [])
|
||||
.filter((i) => i.kind === 'file')
|
||||
.forEach(f => {
|
||||
.filter(i => i.kind === 'file')
|
||||
.forEach((f) => {
|
||||
valid = true; // Valid if file exists, but on DragOver, won't reveal its contents for security
|
||||
const data = f.getAsFile();
|
||||
if (data) {
|
||||
@ -89,14 +89,14 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
|
||||
document.body.addEventListener('mouseout', mouseleave);
|
||||
return () => {
|
||||
document.body.removeEventListener('mouseout', mouseleave);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const bind = {
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
onDragEnter,
|
||||
onDragEnter
|
||||
};
|
||||
|
||||
return { bind, dragging };
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
|
||||
export function useDropdown<C>(
|
||||
candidates: C[],
|
||||
@ -12,10 +12,10 @@ export function useDropdown<C>(
|
||||
(s: string) => {
|
||||
const exactMatch = isExact(s);
|
||||
const exact = exactMatch ? [exactMatch] : [];
|
||||
const opts = [...new Set([...exact, ...candidates.filter((c) => searchPred(s, c))])];
|
||||
const opts = [...new Set([...exact, ...candidates.filter(c => searchPred(s, c))])];
|
||||
setOptions(opts);
|
||||
if (selected) {
|
||||
const idx = opts.findIndex((c) => key(c) === key(selected));
|
||||
const idx = opts.findIndex(c => key(c) === key(selected));
|
||||
if (idx < 0) {
|
||||
setSelected(undefined);
|
||||
}
|
||||
@ -29,9 +29,11 @@ export function useDropdown<C>(
|
||||
const select = (idx: number) => {
|
||||
setSelected(options[idx]);
|
||||
};
|
||||
if(!selected) { select(0); return false; }
|
||||
if(!selected) {
|
||||
select(0); return false;
|
||||
}
|
||||
|
||||
const idx = options.findIndex((c) => key(c) === key(selected));
|
||||
const idx = options.findIndex(c => key(c) === key(selected));
|
||||
if (
|
||||
idx === -1 ||
|
||||
(options.length - 1 <= idx && !backward)
|
||||
@ -55,6 +57,6 @@ export function useDropdown<C>(
|
||||
back,
|
||||
search,
|
||||
selected,
|
||||
options,
|
||||
options
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import {useLocation} from "react-router-dom";
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export function useHashLink() {
|
||||
const location = useLocation();
|
||||
@ -10,8 +9,5 @@ export function useHashLink() {
|
||||
return;
|
||||
}
|
||||
document.querySelector(location.hash)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
}, [location.hash]);
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, RefObject, useRef, useState } from "react";
|
||||
import _ from "lodash";
|
||||
import usePreviousValue from "./usePreviousValue";
|
||||
import { useEffect, RefObject, useRef, useState } from 'react';
|
||||
import _ from 'lodash';
|
||||
import usePreviousValue from './usePreviousValue';
|
||||
|
||||
export function distanceToBottom(el: HTMLElement) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = el;
|
||||
@ -40,7 +40,6 @@ export function useLazyScroll(
|
||||
}
|
||||
}, [count]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
@ -54,13 +53,12 @@ export function useLazyScroll(
|
||||
loadUntil(el);
|
||||
};
|
||||
|
||||
ref.current.addEventListener("scroll", onScroll, { passive: true });
|
||||
ref.current.addEventListener('scroll', onScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
ref.current?.removeEventListener("scroll", onScroll);
|
||||
ref.current?.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
}, [ref?.current, count]);
|
||||
|
||||
|
||||
return { isDone, isLoading };
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
|
||||
function retrieve<T>(key: string, initial: T): T {
|
||||
const s = localStorage.getItem(key);
|
||||
@ -25,7 +25,7 @@ export function useLocalStorageState<T>(key: string, initial: T) {
|
||||
|
||||
const setState = useCallback(
|
||||
(s: SetState<T>) => {
|
||||
const updated = typeof s === "function" ? s(state) : s;
|
||||
const updated = typeof s === 'function' ? s(state) : s;
|
||||
_setState(updated);
|
||||
localStorage.setItem(key, JSON.stringify(updated));
|
||||
},
|
||||
|
@ -5,15 +5,15 @@ import React, {
|
||||
SyntheticEvent,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
useRef
|
||||
} from 'react';
|
||||
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import { useOutsideClick } from "./useOutsideClick";
|
||||
import { ModalOverlay } from "~/views/components/ModalOverlay";
|
||||
import {Portal} from "~/views/components/Portal";
|
||||
import {ModalPortal} from "~/views/components/ModalPortal";
|
||||
import {PropFunc} from "~/types";
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { useOutsideClick } from './useOutsideClick';
|
||||
import { ModalOverlay } from '~/views/components/ModalOverlay';
|
||||
import { Portal } from '~/views/components/Portal';
|
||||
import { ModalPortal } from '~/views/components/ModalPortal';
|
||||
import { PropFunc } from '@urbit/api';
|
||||
|
||||
type ModalFunc = (dismiss: () => void) => JSX.Element;
|
||||
interface UseModalProps {
|
||||
@ -42,7 +42,7 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
() =>
|
||||
!modalShown
|
||||
? null
|
||||
: typeof modal === "function"
|
||||
: typeof modal === 'function'
|
||||
? modal(dismiss)
|
||||
: modal,
|
||||
[modalShown, modal, dismiss]
|
||||
@ -59,7 +59,7 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
bg="white"
|
||||
borderRadius={2}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
borderColor={['washedGray', 'washedGray']}
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
@ -76,6 +76,6 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
|
||||
return {
|
||||
showModal,
|
||||
modal: modalComponent,
|
||||
modal: modalComponent
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useEffect, RefObject } from "react";
|
||||
import { useEffect, RefObject } from 'react';
|
||||
|
||||
export function useOutsideClick(
|
||||
ref: RefObject<HTMLElement | null | undefined>,
|
||||
onClick: () => void,
|
||||
onClick: () => void
|
||||
) {
|
||||
useEffect(() => {
|
||||
function handleClick(event: MouseEvent) {
|
||||
@ -16,17 +16,16 @@ export function useOutsideClick(
|
||||
}
|
||||
|
||||
function handleKeyDown(ev) {
|
||||
if(ev.key === "Escape") {
|
||||
if(ev.key === 'Escape') {
|
||||
onClick();
|
||||
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener('mousedown', handleClick);
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClick);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
document.removeEventListener('mousedown', handleClick);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [ref.current, onClick]);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { useRef } from "react";
|
||||
import { Primitive } from "~/types";
|
||||
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { Primitive } from '@urbit/api';
|
||||
|
||||
export default function usePreviousValue<T extends Primitive>(value: T): T {
|
||||
const prev = useRef<T | null>(null);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useMemo, useCallback } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function useQuery() {
|
||||
@ -25,6 +25,6 @@ export function useQuery() {
|
||||
|
||||
return {
|
||||
query,
|
||||
appendQuery,
|
||||
appendQuery
|
||||
};
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { MouseEvent, useCallback, useState, useEffect } from "react";
|
||||
export type AsyncClickableState = "waiting" | "error" | "loading" | "success";
|
||||
import { MouseEvent, useCallback, useState, useEffect } from 'react';
|
||||
export type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success';
|
||||
|
||||
export function useStatelessAsyncClickable(
|
||||
onClick: (e: MouseEvent) => Promise<void>,
|
||||
name: string
|
||||
) {
|
||||
const [state, setState] = useState<ButtonState>("waiting");
|
||||
const [state, setState] = useState<ButtonState>('waiting');
|
||||
const handleClick = useCallback(
|
||||
async (e: MouseEvent) => {
|
||||
try {
|
||||
setState("loading");
|
||||
setState('loading');
|
||||
await onClick(e);
|
||||
setState("success");
|
||||
setState('success');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setState("error");
|
||||
setState('error');
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setState("waiting");
|
||||
setState('waiting');
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
@ -26,7 +26,7 @@ export function useStatelessAsyncClickable(
|
||||
|
||||
// When name changes, reset button
|
||||
useEffect(() => {
|
||||
setState("waiting");
|
||||
setState('waiting');
|
||||
}, [name]);
|
||||
|
||||
return { buttonState: state, onClick: handleClick };
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {useCallback, useMemo, useEffect, useRef, useState} from 'react';
|
||||
import { useCallback, useMemo, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
GcpState,
|
||||
S3State,
|
||||
StorageState
|
||||
} from '../../types';
|
||||
import S3 from "aws-sdk/clients/s3";
|
||||
import S3 from 'aws-sdk/clients/s3';
|
||||
import GcpClient from './GcpClient';
|
||||
import {StorageClient, StorageAcl} from './StorageClient';
|
||||
import {dateToDa, deSig} from "./util";
|
||||
import { StorageClient, StorageAcl } from './StorageClient';
|
||||
import { dateToDa, deSig } from './util';
|
||||
|
||||
|
||||
export interface IuseStorage {
|
||||
@ -47,14 +47,14 @@ const useStorage = ({gcp, s3}: StorageState,
|
||||
() =>
|
||||
((gcp.token || (s3.credentials && s3.credentials.accessKeyId &&
|
||||
s3.credentials.secretAccessKey)) &&
|
||||
s3.configuration.currentBucket !== "") || false,
|
||||
s3.configuration.currentBucket !== '') || false,
|
||||
[s3.credentials, gcp.token, s3.configuration.currentBucket]
|
||||
);
|
||||
|
||||
const upload = useCallback(
|
||||
async (file: File, bucket: string) => {
|
||||
if (client.current === null) {
|
||||
throw new Error("Storage not ready");
|
||||
throw new Error('Storage not ready');
|
||||
}
|
||||
|
||||
const fileParts = file.name.split('.');
|
||||
@ -67,7 +67,7 @@ const useStorage = ({gcp, s3}: StorageState,
|
||||
Key: `${window.ship}/${timestamp}-${fileName}.${fileExtension}`,
|
||||
Body: file,
|
||||
ACL: StorageAcl.PublicRead,
|
||||
ContentType: file.type,
|
||||
ContentType: file.type
|
||||
};
|
||||
|
||||
setUploading(true);
|
||||
@ -83,7 +83,7 @@ const useStorage = ({gcp, s3}: StorageState,
|
||||
|
||||
const uploadDefault = useCallback(async (file: File) => {
|
||||
if (s3.configuration.currentBucket === '') {
|
||||
throw new Error("current bucket not set");
|
||||
throw new Error('current bucket not set');
|
||||
}
|
||||
return upload(file, s3.configuration.currentBucket);
|
||||
}, [s3, upload]);
|
||||
@ -103,10 +103,10 @@ const useStorage = ({gcp, s3}: StorageState,
|
||||
}
|
||||
uploadDefault(files[0]).then(resolve);
|
||||
document.body.removeChild(fileSelector);
|
||||
})
|
||||
});
|
||||
document.body.appendChild(fileSelector);
|
||||
fileSelector.click();
|
||||
})
|
||||
});
|
||||
},
|
||||
[uploadDefault]
|
||||
);
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
|
||||
export function useWaitForProps<P>(props: P, timeout: number = 0) {
|
||||
export function useWaitForProps<P>(props: P, timeout = 0) {
|
||||
const [resolve, setResolve] = useState<() => void>(() => () => {});
|
||||
const [ready, setReady] = useState<(p: P) => boolean | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof ready === "function" && ready(props)) {
|
||||
if (typeof ready === 'function' && ready(props)) {
|
||||
resolve();
|
||||
}
|
||||
}, [props, ready, resolve]);
|
||||
@ -26,7 +25,7 @@ export function useWaitForProps<P>(props: P, timeout: number = 0) {
|
||||
setResolve(() => resolve);
|
||||
if(timeout > 0) {
|
||||
setTimeout(() => {
|
||||
reject(new Error("Timed out"));
|
||||
reject(new Error('Timed out'));
|
||||
}, timeout);
|
||||
}
|
||||
});
|
||||
|
@ -1,27 +1,27 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import _ from "lodash";
|
||||
import f, { memoize } from "lodash/fp";
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import { Contact } from '~/types';
|
||||
import _ from 'lodash';
|
||||
import f, { memoize } from 'lodash/fp';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import { Contact } from '@urbit/api';
|
||||
import useLocalState from '../state/local';
|
||||
|
||||
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
|
||||
|
||||
export const MOMENT_CALENDAR_DATE = {
|
||||
sameDay: "[Today]",
|
||||
nextDay: "[Tomorrow]",
|
||||
nextWeek: "dddd",
|
||||
lastDay: "[Yesterday]",
|
||||
lastWeek: "[Last] dddd",
|
||||
sameElse: "~YYYY.M.D",
|
||||
sameDay: '[Today]',
|
||||
nextDay: '[Tomorrow]',
|
||||
nextWeek: 'dddd',
|
||||
lastDay: '[Yesterday]',
|
||||
lastWeek: '[Last] dddd',
|
||||
sameElse: '~YYYY.M.D'
|
||||
};
|
||||
|
||||
export const getModuleIcon = (mod: string) => {
|
||||
if (mod === "link") {
|
||||
return "Collection";
|
||||
if (mod === 'link') {
|
||||
return 'Collection';
|
||||
}
|
||||
return _.capitalize(mod);
|
||||
}
|
||||
};
|
||||
|
||||
export function wait(ms: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -37,8 +37,8 @@ export function parentPath(path: string) {
|
||||
return _.dropRight(path.split('/'), 1).join('/');
|
||||
}
|
||||
|
||||
const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud` ~1970.1.1
|
||||
const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1
|
||||
const DA_UNIX_EPOCH = bigInt('170141184475152167957503069145530368000'); // `@ud` ~1970.1.1
|
||||
const DA_SECOND = bigInt('18446744073709551616'); // `@ud` ~s1
|
||||
export function daToUnix(da: BigInteger) {
|
||||
// ported from +time:enjs:format in hoon.hoon
|
||||
const offset = DA_SECOND.divide(bigInt(2000));
|
||||
@ -59,20 +59,20 @@ export function makePatDa(patda: string) {
|
||||
}
|
||||
|
||||
export function udToDec(ud: string): string {
|
||||
return ud.replace(/\./g, "");
|
||||
return ud.replace(/\./g, '');
|
||||
}
|
||||
|
||||
export function decToUd(str: string): string {
|
||||
return _.trimStart(
|
||||
f.flow(
|
||||
f.split(""),
|
||||
f.split(''),
|
||||
f.reverse,
|
||||
f.chunk(3),
|
||||
f.map(f.flow(f.reverse, f.join(""))),
|
||||
f.map(f.flow(f.reverse, f.join(''))),
|
||||
f.reverse,
|
||||
f.join(".")
|
||||
f.join('.')
|
||||
)(str),
|
||||
"0."
|
||||
'0.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,12 +86,12 @@ export function clamp(x: number, min: number, max: number) {
|
||||
// color is a #000000 color
|
||||
export function adjustHex(color: string, amount: number): string {
|
||||
return f.flow(
|
||||
f.split(""),
|
||||
f.split(''),
|
||||
f.chunk(2), // get RGB channels
|
||||
f.map((c) => parseInt(c.join(""), 16)), // as hex
|
||||
f.map((c) => clamp(c + amount, 0, 255).toString(16)), // adjust
|
||||
f.join(""),
|
||||
(res) => `#${res}` //format
|
||||
f.map(c => parseInt(c.join(''), 16)), // as hex
|
||||
f.map(c => clamp(c + amount, 0, 255).toString(16)), // adjust
|
||||
f.join(''),
|
||||
res => `#${res}` // format
|
||||
)(color.slice(1));
|
||||
}
|
||||
|
||||
@ -101,12 +101,12 @@ export function resourceAsPath(resource: any) {
|
||||
}
|
||||
|
||||
export function uuid() {
|
||||
let str = "0v";
|
||||
str += Math.ceil(Math.random() * 8) + ".";
|
||||
let str = '0v';
|
||||
str += Math.ceil(Math.random() * 8) + '.';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random() * 10000000).toString(32);
|
||||
_str = ("00000" + _str).substr(-5, 5);
|
||||
str += _str + ".";
|
||||
_str = ('00000' + _str).substr(-5, 5);
|
||||
str += _str + '.';
|
||||
}
|
||||
|
||||
return str.slice(0, -1);
|
||||
@ -120,11 +120,11 @@ export function uuid() {
|
||||
*/
|
||||
export function daToDate(st: string) {
|
||||
const dub = function (n: string) {
|
||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
||||
return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString();
|
||||
};
|
||||
const da = st.split("..");
|
||||
const bigEnd = da[0].split(".");
|
||||
const lilEnd = da[1].split(".");
|
||||
const da = st.split('..');
|
||||
const bigEnd = da[0].split('.');
|
||||
const lilEnd = da[1].split('.');
|
||||
const ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(
|
||||
lilEnd[0]
|
||||
)}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
@ -138,9 +138,9 @@ export function daToDate(st: string) {
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
*/
|
||||
|
||||
export function dateToDa(d: Date, mil: boolean = false) {
|
||||
export function dateToDa(d: Date, mil = false) {
|
||||
const fil = function (n: number) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
return n >= 10 ? n : '0' + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
@ -149,7 +149,7 @@ export function dateToDa(d: Date, mil: boolean = false) {
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
`${mil ? '..0000' : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,16 +157,16 @@ export function deSig(ship: string) {
|
||||
if (!ship) {
|
||||
return null;
|
||||
}
|
||||
return ship.replace("~", "");
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
|
||||
export function uxToHex(ux: string) {
|
||||
if (ux.length > 2 && ux.substr(0, 2) === "0x") {
|
||||
const value = ux.substr(2).replace(".", "").padStart(6, "0");
|
||||
if (ux.length > 2 && ux.substr(0, 2) === '0x') {
|
||||
const value = ux.substr(2).replace('.', '').padStart(6, '0');
|
||||
return value;
|
||||
}
|
||||
|
||||
const value = ux.replace(".", "").padStart(6, "0");
|
||||
const value = ux.replace('.', '').padStart(6, '0');
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -187,13 +187,13 @@ export function writeText(str: string) {
|
||||
|
||||
let success = false;
|
||||
function listener(e) {
|
||||
e.clipboardData.setData("text/plain", str);
|
||||
e.clipboardData.setData('text/plain', str);
|
||||
e.preventDefault();
|
||||
success = true;
|
||||
}
|
||||
document.addEventListener("copy", listener);
|
||||
document.execCommand("copy");
|
||||
document.removeEventListener("copy", listener);
|
||||
document.addEventListener('copy', listener);
|
||||
document.execCommand('copy');
|
||||
document.removeEventListener('copy', listener);
|
||||
|
||||
document?.getSelection()?.removeAllRanges();
|
||||
|
||||
@ -206,21 +206,21 @@ export function writeText(str: string) {
|
||||
// trim patps to match dojo, chat-cli
|
||||
export function cite(ship: string) {
|
||||
let patp = ship,
|
||||
shortened = "";
|
||||
if (patp === null || patp === "") {
|
||||
shortened = '';
|
||||
if (patp === null || patp === '') {
|
||||
return null;
|
||||
}
|
||||
if (patp.startsWith("~")) {
|
||||
if (patp.startsWith('~')) {
|
||||
patp = patp.substr(1);
|
||||
}
|
||||
// comet
|
||||
if (patp.length === 56) {
|
||||
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
|
||||
shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56);
|
||||
return shortened;
|
||||
}
|
||||
// moon
|
||||
if (patp.length === 27) {
|
||||
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
|
||||
shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27);
|
||||
return shortened;
|
||||
}
|
||||
return `~${patp}`;
|
||||
@ -232,7 +232,6 @@ export function alphabeticalOrder(a: string, b: string) {
|
||||
|
||||
export function lengthOrder(a: string, b: string) {
|
||||
return b.length - a.length;
|
||||
|
||||
}
|
||||
|
||||
// TODO: deprecated
|
||||
@ -244,13 +243,13 @@ export function alphabetiseAssociations(associations: any) {
|
||||
let bName = b.substr(1);
|
||||
if (associations[a].metadata && associations[a].metadata.title) {
|
||||
aName =
|
||||
associations[a].metadata.title !== ""
|
||||
associations[a].metadata.title !== ''
|
||||
? associations[a].metadata.title
|
||||
: a.substr(1);
|
||||
}
|
||||
if (associations[b].metadata && associations[b].metadata.title) {
|
||||
bName =
|
||||
associations[b].metadata.title !== ""
|
||||
associations[b].metadata.title !== ''
|
||||
? associations[b].metadata.title
|
||||
: b.substr(1);
|
||||
}
|
||||
@ -266,41 +265,42 @@ export function alphabetiseAssociations(associations: any) {
|
||||
// for example, 'some Chars!' becomes '~.some.~43.hars~21.'
|
||||
//
|
||||
export function stringToTa(str: string) {
|
||||
let out = "";
|
||||
let out = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
let add = "";
|
||||
let add = '';
|
||||
switch (char) {
|
||||
case " ":
|
||||
add = ".";
|
||||
case ' ':
|
||||
add = '.';
|
||||
break;
|
||||
case ".":
|
||||
add = "~.";
|
||||
case '.':
|
||||
add = '~.';
|
||||
break;
|
||||
case "~":
|
||||
add = "~~";
|
||||
case '~':
|
||||
add = '~~';
|
||||
break;
|
||||
default:
|
||||
const charCode = str.charCodeAt(i);
|
||||
if (
|
||||
(charCode >= 97 && charCode <= 122) || // a-z
|
||||
(charCode >= 48 && charCode <= 57) || // 0-9
|
||||
char === "-"
|
||||
char === '-'
|
||||
) {
|
||||
add = char;
|
||||
} else {
|
||||
// TODO behavior for unicode doesn't match +wood's,
|
||||
// but we can probably get away with that for now.
|
||||
add = "~" + charCode.toString(16) + ".";
|
||||
add = '~' + charCode.toString(16) + '.';
|
||||
}
|
||||
}
|
||||
out = out + add;
|
||||
}
|
||||
return "~." + out;
|
||||
return '~.' + out;
|
||||
}
|
||||
|
||||
export function amOwnerOfGroup(groupPath: string) {
|
||||
if (!groupPath) return false;
|
||||
if (!groupPath)
|
||||
return false;
|
||||
const groupOwner = /(\/~)?\/~([a-z-]{3,})\/.*/.exec(groupPath)?.[2];
|
||||
return window.ship === groupOwner;
|
||||
}
|
||||
@ -308,18 +308,18 @@ export function amOwnerOfGroup(groupPath: string) {
|
||||
export function getContactDetails(contact: any) {
|
||||
const member = !contact;
|
||||
contact = contact || {
|
||||
nickname: "",
|
||||
nickname: '',
|
||||
avatar: null,
|
||||
color: "0x0",
|
||||
color: '0x0'
|
||||
};
|
||||
const nickname = contact.nickname || "";
|
||||
const color = uxToHex(contact.color || "0x0");
|
||||
const nickname = contact.nickname || '';
|
||||
const color = uxToHex(contact.color || '0x0');
|
||||
const avatar = contact.avatar || null;
|
||||
return { nickname, color, member, avatar };
|
||||
}
|
||||
|
||||
export function stringToSymbol(str: string) {
|
||||
let result = "";
|
||||
let result = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const n = str.charCodeAt(i);
|
||||
if ((n >= 97 && n <= 122) || (n >= 48 && n <= 57)) {
|
||||
@ -327,19 +327,17 @@ export function stringToSymbol(str: string) {
|
||||
} else if (n >= 65 && n <= 90) {
|
||||
result += String.fromCharCode(n + 32);
|
||||
} else {
|
||||
result += "-";
|
||||
result += '-';
|
||||
}
|
||||
}
|
||||
result = result.replace(/^[\-\d]+|\-+/g, "-");
|
||||
result = result.replace(/^\-+|\-+$/g, "");
|
||||
if (result === "") {
|
||||
result = result.replace(/^[\-\d]+|\-+/g, '-');
|
||||
result = result.replace(/^\-+|\-+$/g, '');
|
||||
if (result === '') {
|
||||
return dateToDa(new Date());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formats a numbers as a `@ud` inserting dot where needed
|
||||
*/
|
||||
@ -351,23 +349,24 @@ export function numToUd(num: number) {
|
||||
f.reverse,
|
||||
f.map(s => s.join('')),
|
||||
f.join('.')
|
||||
)(num.toString())
|
||||
)(num.toString());
|
||||
}
|
||||
|
||||
export function usePreventWindowUnload(shouldPreventDefault: boolean, message = "You have unsaved changes. Are you sure you want to exit?") {
|
||||
export function usePreventWindowUnload(shouldPreventDefault: boolean, message = 'You have unsaved changes. Are you sure you want to exit?') {
|
||||
useEffect(() => {
|
||||
if (!shouldPreventDefault) return;
|
||||
const handleBeforeUnload = event => {
|
||||
if (!shouldPreventDefault)
|
||||
return;
|
||||
const handleBeforeUnload = (event) => {
|
||||
event.preventDefault();
|
||||
return message;
|
||||
}
|
||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||
};
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
window.onbeforeunload = handleBeforeUnload;
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
// @ts-ignore
|
||||
window.onbeforeunload = undefined;
|
||||
}
|
||||
};
|
||||
}, [shouldPreventDefault]);
|
||||
}
|
||||
|
||||
@ -378,7 +377,7 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
|
||||
// Hide is an optional second parameter for when this function is used in class components
|
||||
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
|
||||
const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames);
|
||||
return !!(contact && contact.nickname && !hideNicknames);
|
||||
return Boolean(contact && contact.nickname && !hideNicknames);
|
||||
}
|
||||
|
||||
interface useHoveringInterface {
|
||||
@ -406,7 +405,6 @@ export function getItemTitle(association: Association) {
|
||||
return cite(`~${name.slice(4)}`);
|
||||
}
|
||||
return cite(ship);
|
||||
|
||||
}
|
||||
return association.metadata.title || association.resource
|
||||
};
|
||||
return association.metadata.title || association.resource;
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
import { Associations, Workspace } from "~/types";
|
||||
import { Associations, Workspace } from '@urbit/api';
|
||||
|
||||
export function getTitleFromWorkspace(
|
||||
associations: Associations,
|
||||
workspace: Workspace
|
||||
) {
|
||||
switch (workspace.type) {
|
||||
case "home":
|
||||
return "My Channels";
|
||||
case "messages":
|
||||
return "Messages";
|
||||
case "group":
|
||||
case 'home':
|
||||
return 'My Channels';
|
||||
case 'messages':
|
||||
return 'Messages';
|
||||
case 'group':
|
||||
const association = associations.groups[workspace.group];
|
||||
return association?.metadata?.title || "";
|
||||
return association?.metadata?.title || '';
|
||||
}
|
||||
}
|
||||
|
||||
export function getGroupFromWorkspace(
|
||||
workspace: Workspace
|
||||
): string | undefined {
|
||||
if (workspace.type === "group") {
|
||||
if (workspace.type === 'group') {
|
||||
return workspace.group;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { ContactUpdate } from '~/types/contact-update';
|
||||
import {resourceAsPath} from '../lib/util';
|
||||
import { ContactUpdate } from '@urbit/api/contacts';
|
||||
import { resourceAsPath } from '../lib/util';
|
||||
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
||||
@ -78,4 +78,3 @@ const setPublic = (json: ContactUpdate, state: S) => {
|
||||
state.isContactPublic = data;
|
||||
};
|
||||
|
||||
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
OpenPolicyDiff,
|
||||
OpenPolicy,
|
||||
InvitePolicyDiff,
|
||||
InvitePolicy,
|
||||
} from '~/types/group-update';
|
||||
import { Enc, PatpNoSig } from '~/types/noun';
|
||||
InvitePolicy
|
||||
} from '@urbit/api/groups';
|
||||
import { Enc, PatpNoSig } from '@urbit/api';
|
||||
import { resourceAsPath } from '../lib/util';
|
||||
|
||||
type GroupState = Pick<StoreState, 'groups' | 'groupKeys'>;
|
||||
@ -23,7 +23,7 @@ function decodeGroup(group: Enc<Group>): Group {
|
||||
...group,
|
||||
members,
|
||||
tags: decodeTags(group.tags),
|
||||
policy: decodePolicy(group.policy),
|
||||
policy: decodePolicy(group.policy)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
@ -35,7 +35,7 @@ function decodePolicy(policy: Enc<GroupPolicy>): GroupPolicy {
|
||||
} else {
|
||||
const { open } = policy;
|
||||
return {
|
||||
open: { banned: new Set(open.banned), banRanks: new Set(open.banRanks) },
|
||||
open: { banned: new Set(open.banned), banRanks: new Set(open.banRanks) }
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -61,6 +61,7 @@ export default class GroupReducer<S extends GroupState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json.groupUpdate;
|
||||
if (data) {
|
||||
console.log(data);
|
||||
this.initial(data, state);
|
||||
this.addMembers(data, state);
|
||||
this.addTag(data, state);
|
||||
@ -98,7 +99,7 @@ export default class GroupReducer<S extends GroupState> {
|
||||
members: new Set(),
|
||||
tags: { role: { admin: new Set([window.ship]) } },
|
||||
policy: decodePolicy(policy),
|
||||
hidden,
|
||||
hidden
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -116,6 +117,12 @@ export default class GroupReducer<S extends GroupState> {
|
||||
const resourcePath = resourceAsPath(resource);
|
||||
for (const member of ships) {
|
||||
state.groups[resourcePath].members.add(member);
|
||||
if (
|
||||
'invite' in state.groups[resourcePath].policy &&
|
||||
state.groups[resourcePath].policy.invite.pending.has(member)
|
||||
) {
|
||||
state.groups[resourcePath].policy.invite.pending.delete(member)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,7 +196,6 @@ export default class GroupReducer<S extends GroupState> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private inviteChangePolicy(diff: InvitePolicyDiff, policy: InvitePolicy) {
|
||||
if ('addInvites' in diff) {
|
||||
const { addInvites } = diff;
|
||||
|
@ -1,25 +1,24 @@
|
||||
import { resourceAsPath } from "~/logic/lib/util";
|
||||
|
||||
import { resourceAsPath } from '~/logic/lib/util';
|
||||
|
||||
const initial = (json: any, state: any) => {
|
||||
const data = json.initial;
|
||||
if(data) {
|
||||
state.pendingJoin = data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const progress = (json: any, state: any) => {
|
||||
const data = json.progress;
|
||||
if(data) {
|
||||
const { progress, resource } = data;
|
||||
state.pendingJoin = {...state.pendingJoin, [resource]: progress };
|
||||
state.pendingJoin = { ...state.pendingJoin, [resource]: progress };
|
||||
if(progress === 'done') {
|
||||
setTimeout(() => {
|
||||
delete state.pendingJoin[resource];
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const GroupViewReducer = (json: any, state: any) => {
|
||||
const data = json['group-view-update'];
|
||||
@ -27,4 +26,4 @@ export const GroupViewReducer = (json: any, state: any) => {
|
||||
progress(data, state);
|
||||
initial(data, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -3,22 +3,21 @@ import {
|
||||
NotifIndex,
|
||||
NotificationGraphConfig,
|
||||
GroupNotificationsConfig,
|
||||
UnreadStats,
|
||||
} from "~/types";
|
||||
import { makePatDa } from "~/logic/lib/util";
|
||||
import _ from "lodash";
|
||||
import {StoreState} from "../store/type";
|
||||
UnreadStats
|
||||
} from '@urbit/api';
|
||||
import { makePatDa } from '~/logic/lib/util';
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
||||
|
||||
type HarkState = Pick<StoreState, "notifications" | "notificationsGraphConfig" | "notificationsGroupConfig" | "unreads" >;
|
||||
|
||||
type HarkState = Pick<StoreState, 'notifications' | 'notificationsGraphConfig' | 'notificationsGroupConfig' | 'unreads' >;
|
||||
|
||||
export const HarkReducer = (json: any, state: HarkState) => {
|
||||
const data = _.get(json, "harkUpdate", false);
|
||||
const data = _.get(json, 'harkUpdate', false);
|
||||
if (data) {
|
||||
reduce(data, state);
|
||||
}
|
||||
const graphHookData = _.get(json, "hark-graph-hook-update", false);
|
||||
const graphHookData = _.get(json, 'hark-graph-hook-update', false);
|
||||
if (graphHookData) {
|
||||
graphInitial(graphHookData, state);
|
||||
graphIgnore(graphHookData, state);
|
||||
@ -26,7 +25,7 @@ export const HarkReducer = (json: any, state: HarkState) => {
|
||||
graphWatchSelf(graphHookData, state);
|
||||
graphMentions(graphHookData, state);
|
||||
}
|
||||
const groupHookData = _.get(json, "hark-group-hook-update", false);
|
||||
const groupHookData = _.get(json, 'hark-group-hook-update', false);
|
||||
if (groupHookData) {
|
||||
groupInitial(groupHookData, state);
|
||||
groupListen(groupHookData, state);
|
||||
@ -35,31 +34,31 @@ export const HarkReducer = (json: any, state: HarkState) => {
|
||||
};
|
||||
|
||||
function groupInitial(json: any, state: HarkState) {
|
||||
const data = _.get(json, "initial", false);
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.notificationsGroupConfig = data;
|
||||
}
|
||||
}
|
||||
|
||||
function graphInitial(json: any, state: HarkState) {
|
||||
const data = _.get(json, "initial", false);
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.notificationsGraphConfig = data;
|
||||
}
|
||||
}
|
||||
|
||||
function graphListen(json: any, state: HarkState) {
|
||||
const data = _.get(json, "listen", false);
|
||||
const data = _.get(json, 'listen', false);
|
||||
if (data) {
|
||||
state.notificationsGraphConfig.watching = [
|
||||
...state.notificationsGraphConfig.watching,
|
||||
data,
|
||||
data
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function graphIgnore(json: any, state: HarkState) {
|
||||
const data = _.get(json, "ignore", false);
|
||||
const data = _.get(json, 'ignore', false);
|
||||
if (data) {
|
||||
state.notificationsGraphConfig.watching = state.notificationsGraphConfig.watching.filter(
|
||||
({ graph, index }) => !(graph === data.graph && index === data.index)
|
||||
@ -68,30 +67,30 @@ function graphIgnore(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function groupListen(json: any, state: HarkState) {
|
||||
const data = _.get(json, "listen", false);
|
||||
const data = _.get(json, 'listen', false);
|
||||
if (data) {
|
||||
state.notificationsGroupConfig = [...state.notificationsGroupConfig, data];
|
||||
}
|
||||
}
|
||||
|
||||
function groupIgnore(json: any, state: HarkState) {
|
||||
const data = _.get(json, "ignore", false);
|
||||
const data = _.get(json, 'ignore', false);
|
||||
if (data) {
|
||||
state.notificationsGroupConfig = state.notificationsGroupConfig.filter(
|
||||
(n) => n !== data
|
||||
n => n !== data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function graphMentions(json: any, state: HarkState) {
|
||||
const data = _.get(json, "set-mentions", undefined);
|
||||
const data = _.get(json, 'set-mentions', undefined);
|
||||
if (!_.isUndefined(data)) {
|
||||
state.notificationsGraphConfig.mentions = data;
|
||||
}
|
||||
}
|
||||
|
||||
function graphWatchSelf(json: any, state: HarkState) {
|
||||
const data = _.get(json, "set-watch-on-self", undefined);
|
||||
const data = _.get(json, 'set-watch-on-self', undefined);
|
||||
if (!_.isUndefined(data)) {
|
||||
state.notificationsGraphConfig.watchOnSelf = data;
|
||||
}
|
||||
@ -139,14 +138,14 @@ function seenIndex(json: any, state: HarkState) {
|
||||
function readEach(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'read-each');
|
||||
if(data) {
|
||||
updateUnreads(state, data.index, u => u.delete(data.target))
|
||||
updateUnreads(state, data.index, u => u.delete(data.target));
|
||||
}
|
||||
}
|
||||
|
||||
function readSince(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'read-count');
|
||||
if(data) {
|
||||
updateUnreadCount(state, data, () => 0)
|
||||
updateUnreadCount(state, data, () => 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,7 +159,7 @@ function unreadSince(json: any, state: HarkState) {
|
||||
function unreadEach(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'unread-each');
|
||||
if(data) {
|
||||
updateUnreads(state, data.index, us => us.add(data.target))
|
||||
updateUnreads(state, data.index, us => us.add(data.target));
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,15 +182,15 @@ function unreads(json: any, state: HarkState) {
|
||||
}
|
||||
}
|
||||
|
||||
function clearState(state){
|
||||
let initialState = {
|
||||
function clearState(state) {
|
||||
const initialState = {
|
||||
notifications: new BigIntOrderedMap<Timebox>(),
|
||||
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
||||
notificationsGroupConfig: [],
|
||||
notificationsGraphConfig: {
|
||||
watchOnSelf: false,
|
||||
mentions: false,
|
||||
watching: [],
|
||||
watching: []
|
||||
},
|
||||
unreads: {
|
||||
graph: {},
|
||||
@ -200,7 +199,7 @@ function clearState(state){
|
||||
notificationsCount: 0
|
||||
};
|
||||
|
||||
Object.keys(initialState).forEach(key => {
|
||||
Object.keys(initialState).forEach((key) => {
|
||||
state[key] = initialState[key];
|
||||
});
|
||||
}
|
||||
@ -211,7 +210,7 @@ function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: numbe
|
||||
}
|
||||
const property = [index.graph.graph, index.graph.index, 'unreads'];
|
||||
const curr = _.get(state.unreads.graph, property, 0);
|
||||
const newCount = count(curr)
|
||||
const newCount = count(curr);
|
||||
_.set(state.unreads.graph, property, newCount);
|
||||
}
|
||||
|
||||
@ -226,7 +225,6 @@ function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>)
|
||||
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
|
||||
}
|
||||
|
||||
|
||||
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'notifications' | 'unreads' | 'last', f: (x: number) => number) {
|
||||
if(statField === 'notifications') {
|
||||
state.notificationsCount = f(state.notificationsCount);
|
||||
@ -235,19 +233,19 @@ function updateNotificationStats(state: HarkState, index: NotifIndex, statField:
|
||||
const curr = _.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));
|
||||
} else if('group' in index) {
|
||||
const curr = _.get(state.unreads.group, [index.group.group, statField], 0);
|
||||
_.set(state.unreads.group, [index.group.group, statField], f(curr));
|
||||
const curr = _.get(state.unreads.group, [index.group, statField], 0);
|
||||
_.set(state.unreads.group, [index.group, statField], f(curr));
|
||||
}
|
||||
}
|
||||
|
||||
function added(json: any, state: HarkState) {
|
||||
const data = _.get(json, "added", false);
|
||||
const data = _.get(json, 'added', false);
|
||||
if (data) {
|
||||
const { index, notification } = data;
|
||||
const time = makePatDa(data.time);
|
||||
const timebox = state.notifications.get(time) || [];
|
||||
|
||||
const arrIdx = timebox.findIndex((idxNotif) =>
|
||||
const arrIdx = timebox.findIndex(idxNotif =>
|
||||
notifIdxEqual(index, idxNotif.index)
|
||||
);
|
||||
if (arrIdx !== -1) {
|
||||
@ -264,14 +262,14 @@ function added(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
const dnd = (json: any, state: HarkState) => {
|
||||
const data = _.get(json, "set-dnd", undefined);
|
||||
const data = _.get(json, 'set-dnd', undefined);
|
||||
if (!_.isUndefined(data)) {
|
||||
state.doNotDisturb = data;
|
||||
}
|
||||
};
|
||||
|
||||
const timebox = (json: any, state: HarkState) => {
|
||||
const data = _.get(json, "timebox", false);
|
||||
const data = _.get(json, 'timebox', false);
|
||||
if (data) {
|
||||
const time = makePatDa(data.time);
|
||||
if (!data.archive) {
|
||||
@ -281,21 +279,21 @@ const timebox = (json: any, state: HarkState) => {
|
||||
};
|
||||
|
||||
function more(json: any, state: HarkState) {
|
||||
const data = _.get(json, "more", false);
|
||||
const data = _.get(json, 'more', false);
|
||||
if (data) {
|
||||
_.forEach(data, (d) => reduce(d, state));
|
||||
_.forEach(data, d => reduce(d, state));
|
||||
}
|
||||
}
|
||||
|
||||
function notifIdxEqual(a: NotifIndex, b: NotifIndex) {
|
||||
if ("graph" in a && "graph" in b) {
|
||||
if ('graph' in a && 'graph' in b) {
|
||||
return (
|
||||
a.graph.graph === b.graph.graph &&
|
||||
a.graph.group === b.graph.group &&
|
||||
a.graph.module === b.graph.module &&
|
||||
a.graph.description === b.graph.description
|
||||
);
|
||||
} else if ("group" in a && "group" in b) {
|
||||
} else if ('group' in a && 'group' in b) {
|
||||
return (
|
||||
a.group.group === b.group.group &&
|
||||
a.group.description === b.group.description
|
||||
@ -313,14 +311,14 @@ function setRead(
|
||||
const patDa = makePatDa(time);
|
||||
const timebox = state.notifications.get(patDa);
|
||||
if (_.isNull(timebox)) {
|
||||
console.warn("Modifying nonexistent timebox");
|
||||
console.warn('Modifying nonexistent timebox');
|
||||
return;
|
||||
}
|
||||
const arrIdx = timebox.findIndex((idxNotif) =>
|
||||
const arrIdx = timebox.findIndex(idxNotif =>
|
||||
notifIdxEqual(index, idxNotif.index)
|
||||
);
|
||||
if (arrIdx === -1) {
|
||||
console.warn("Modifying nonexistent index");
|
||||
console.warn('Modifying nonexistent index');
|
||||
return;
|
||||
}
|
||||
timebox[arrIdx].notification.read = read;
|
||||
@ -328,7 +326,7 @@ function setRead(
|
||||
}
|
||||
|
||||
function read(json: any, state: HarkState) {
|
||||
const data = _.get(json, "read-note", false);
|
||||
const data = _.get(json, 'read-note', false);
|
||||
if (data) {
|
||||
const { time, index } = data;
|
||||
updateNotificationStats(state, index, 'notifications', x => x-1);
|
||||
@ -337,7 +335,7 @@ function read(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function unread(json: any, state: HarkState) {
|
||||
const data = _.get(json, "unread-note", false);
|
||||
const data = _.get(json, 'unread-note', false);
|
||||
if (data) {
|
||||
const { time, index } = data;
|
||||
updateNotificationStats(state, index, 'notifications', x => x+1);
|
||||
@ -346,16 +344,16 @@ function unread(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function archive(json: any, state: HarkState) {
|
||||
const data = _.get(json, "archive", false);
|
||||
const data = _.get(json, 'archive', false);
|
||||
if (data) {
|
||||
const { index } = data;
|
||||
const time = makePatDa(data.time);
|
||||
const timebox = state.notifications.get(time);
|
||||
if (!timebox) {
|
||||
console.warn("Modifying nonexistent timebox");
|
||||
console.warn('Modifying nonexistent timebox');
|
||||
return;
|
||||
}
|
||||
const [archived, unarchived] = _.partition(timebox, (idxNotif) =>
|
||||
const [archived, unarchived] = _.partition(timebox, idxNotif =>
|
||||
notifIdxEqual(index, idxNotif.index)
|
||||
);
|
||||
if(unarchived.length === 0) {
|
||||
@ -365,6 +363,6 @@ function archive(json: any, state: HarkState) {
|
||||
state.notifications.set(time, unarchived);
|
||||
}
|
||||
const newlyRead = archived.filter(x => !x.notification.read).length;
|
||||
updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead);
|
||||
updateNotificationStats(state, index, 'notifications', x => x - newlyRead);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { InviteUpdate } from '~/types/invite-update';
|
||||
|
||||
type InviteState = Pick<StoreState, "invites">;
|
||||
import { InviteUpdate } from '@urbit/api/invite';
|
||||
|
||||
type InviteState = Pick<StoreState, 'invites'>;
|
||||
|
||||
export default class InviteReducer<S extends InviteState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
|
@ -51,12 +51,11 @@ export default class LaunchReducer<S extends LaunchState> {
|
||||
changeIsShown(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeIsShown', false);
|
||||
if (data) {
|
||||
let tile = state.launch.tiles[data.name];
|
||||
const tile = state.launch.tiles[data.name];
|
||||
console.log(tile);
|
||||
if (tile) {
|
||||
tile.isShown = data.isShown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ import _ from 'lodash';
|
||||
|
||||
import { StoreState } from '../../store/type';
|
||||
|
||||
import { MetadataUpdate } from '~/types/metadata-update';
|
||||
import { MetadataUpdate } from '@urbit/api/metadata';
|
||||
import { Cage } from '~/types/cage';
|
||||
|
||||
type MetadataState = Pick<StoreState, 'associations'>;
|
||||
|
||||
export default class MetadataReducer<S extends MetadataState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
let data = json['metadata-update']
|
||||
const data = json['metadata-update'];
|
||||
if (data) {
|
||||
console.log(data);
|
||||
this.associations(data, state);
|
||||
@ -29,13 +29,13 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
associations(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'associations', false);
|
||||
const data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
const metadata = state.associations;
|
||||
Object.keys(data).forEach((key) => {
|
||||
let val = data[key];
|
||||
let appName = val['app-name'];
|
||||
let rid = val.resource;
|
||||
const val = data[key];
|
||||
const appName = val['app-name'];
|
||||
const rid = val.resource;
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
@ -50,11 +50,11 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
add(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'add', false);
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let appName = data['app-name'];
|
||||
let appPath = data.resource;
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const appPath = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
@ -69,11 +69,11 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
update(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'update-metadata', false);
|
||||
const data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let appName = data['app-name'];
|
||||
let rid = data.resource;
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
@ -88,11 +88,11 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
remove(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let appName = data['app-name'];
|
||||
let rid = data.resource;
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (appName in metadata && rid in metadata[appName]) {
|
||||
delete metadata[appName][rid];
|
||||
|
@ -1,21 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import {
|
||||
SettingsUpdate,
|
||||
} from '~/types/settings';
|
||||
SettingsUpdate
|
||||
} from '@urbit/api/settings';
|
||||
|
||||
type SettingsState = Pick<StoreState, 'settings'>;
|
||||
|
||||
export default class SettingsReducer<S extends SettingsState>{
|
||||
export default class SettingsReducer<S extends SettingsState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
let data = json["settings-event"];
|
||||
let data = json['settings-event'];
|
||||
if (data) {
|
||||
this.putBucket(data, state);
|
||||
this.delBucket(data, state);
|
||||
this.putEntry(data, state);
|
||||
this.delEntry(data, state);
|
||||
}
|
||||
data = json["settings-data"];
|
||||
data = json['settings-data'];
|
||||
if (data) {
|
||||
this.getAll(data, state);
|
||||
this.getBucket(data, state);
|
||||
@ -26,31 +26,31 @@ export default class SettingsReducer<S extends SettingsState>{
|
||||
putBucket(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'put-bucket', false);
|
||||
if (data) {
|
||||
state.settings[data["bucket-key"]] = data.bucket;
|
||||
state.settings[data['bucket-key']] = data.bucket;
|
||||
}
|
||||
}
|
||||
|
||||
delBucket(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'del-bucket', false);
|
||||
if (data) {
|
||||
delete state.settings[data["bucket-key"]];
|
||||
delete state.settings[data['bucket-key']];
|
||||
}
|
||||
}
|
||||
|
||||
putEntry(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'put-entry', false);
|
||||
if (data) {
|
||||
if (!state.settings[data["bucket-key"]]) {
|
||||
state.settings[data["bucket-key"]] = {};
|
||||
if (!state.settings[data['bucket-key']]) {
|
||||
state.settings[data['bucket-key']] = {};
|
||||
}
|
||||
state.settings[data["bucket-key"]][data["entry-key"]] = data.value;
|
||||
state.settings[data['bucket-key']][data['entry-key']] = data.value;
|
||||
}
|
||||
}
|
||||
|
||||
delEntry(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'del-entry', false);
|
||||
if (data) {
|
||||
delete state.settings[data["bucket-key"]][data["entry-key"]];
|
||||
delete state.settings[data['bucket-key']][data['entry-key']];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { ReactNode } from 'react';
|
||||
import f from 'lodash/fp';
|
||||
import create, { State } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import produce from 'immer';
|
||||
import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from "~/types/local-update";
|
||||
|
||||
import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update';
|
||||
|
||||
export interface LocalState extends State {
|
||||
hideAvatars: boolean;
|
||||
@ -22,8 +21,8 @@ export interface LocalState extends State {
|
||||
suspendedFocus?: HTMLElement;
|
||||
toggleOmnibox: () => void;
|
||||
set: (fn: (state: LocalState) => void) => void
|
||||
};
|
||||
export const selectLocalState =
|
||||
}
|
||||
export const selectLocalState =
|
||||
<K extends keyof LocalState>(keys: K[]) => f.pick<LocalState, K>(keys);
|
||||
|
||||
const useLocalState = create<LocalState>(persist((set, get) => ({
|
||||
@ -33,21 +32,21 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
|
||||
hideNicknames: false,
|
||||
tutorialProgress: 'hidden',
|
||||
tutorialRef: null,
|
||||
setTutorialRef: (el: HTMLElement | null) => set(produce(state => {
|
||||
setTutorialRef: (el: HTMLElement | null) => set(produce((state) => {
|
||||
state.tutorialRef = el;
|
||||
})),
|
||||
hideTutorial: () => set(produce(state => {
|
||||
hideTutorial: () => set(produce((state) => {
|
||||
state.tutorialProgress = 'hidden';
|
||||
state.tutorialRef = null;
|
||||
})),
|
||||
nextTutStep: () => set(produce(state => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress)
|
||||
nextTutStep: () => set(produce((state) => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress);
|
||||
if(currIdx < tutorialProgress.length) {
|
||||
state.tutorialProgress = tutorialProgress[currIdx + 1];
|
||||
}
|
||||
})),
|
||||
prevTutStep: () => set(produce(state => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress)
|
||||
prevTutStep: () => set(produce((state) => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress);
|
||||
if(currIdx > 0) {
|
||||
state.tutorialProgress = tutorialProgress[currIdx - 1];
|
||||
}
|
||||
@ -56,11 +55,11 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
|
||||
imageShown: true,
|
||||
audioShown: true,
|
||||
videoShown: true,
|
||||
oembedShown: true,
|
||||
oembedShown: true
|
||||
},
|
||||
omniboxShown: false,
|
||||
suspendedFocus: undefined,
|
||||
toggleOmnibox: () => set(produce(state => {
|
||||
toggleOmnibox: () => set(produce((state) => {
|
||||
state.omniboxShown = !state.omniboxShown;
|
||||
if (typeof state.suspendedFocus?.focus === 'function') {
|
||||
state.suspendedFocus.focus();
|
||||
@ -86,7 +85,7 @@ function withLocalState<P, S extends keyof LocalState>(Component: any, stateMemb
|
||||
(object, key) => ({ ...object, [key]: state[key] }), {}
|
||||
)
|
||||
): useLocalState();
|
||||
return <Component ref={ref} {...localState} {...props} />
|
||||
return <Component ref={ref} {...localState} {...props} />;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ export default class BaseStore<S extends object> {
|
||||
|
||||
clear() {
|
||||
this.handleEvent({
|
||||
data: { clear: true },
|
||||
data: { clear: true }
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export default class BaseStore<S extends object> {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("clear" in json && json.clear) {
|
||||
if ('clear' in json && json.clear) {
|
||||
this.setState(this.initialState());
|
||||
return;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import MetadataReducer from '../reducers/metadata-update';
|
||||
import LocalReducer from '../reducers/local';
|
||||
|
||||
import { StoreState } from './type';
|
||||
import { Timebox } from '~/types';
|
||||
import { Timebox } from '@urbit/api';
|
||||
import { Cage } from '~/types/cage';
|
||||
import S3Reducer from '../reducers/s3-update';
|
||||
import { GraphReducer } from '../reducers/graph-update';
|
||||
@ -17,10 +17,9 @@ import LaunchReducer from '../reducers/launch-update';
|
||||
import ConnectionReducer from '../reducers/connection';
|
||||
import SettingsReducer from '../reducers/settings-update';
|
||||
import GcpReducer from '../reducers/gcp-reducer';
|
||||
import {OrderedMap} from '../lib/OrderedMap';
|
||||
import { OrderedMap } from '../lib/OrderedMap';
|
||||
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
||||
import {GroupViewReducer} from '../reducers/group-view';
|
||||
|
||||
import { GroupViewReducer } from '../reducers/group-view';
|
||||
|
||||
export default class GlobalStore extends BaseStore<StoreState> {
|
||||
inviteReducer = new InviteReducer();
|
||||
@ -60,7 +59,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
invites: {},
|
||||
associations: {
|
||||
groups: {},
|
||||
graph: {},
|
||||
graph: {}
|
||||
},
|
||||
groups: {},
|
||||
groupKeys: new Set(),
|
||||
@ -69,7 +68,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
launch: {
|
||||
firstTime: false,
|
||||
tileOrdering: [],
|
||||
tiles: {},
|
||||
tiles: {}
|
||||
},
|
||||
weather: {},
|
||||
userLocation: null,
|
||||
@ -92,7 +91,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
notificationsGraphConfig: {
|
||||
watchOnSelf: false,
|
||||
mentions: false,
|
||||
watching: [],
|
||||
watching: []
|
||||
},
|
||||
unreads: {
|
||||
graph: {},
|
||||
@ -100,7 +99,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
},
|
||||
notificationsCount: 0,
|
||||
settings: {},
|
||||
pendingJoin: {},
|
||||
pendingJoin: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { Path } from '~/types/noun';
|
||||
import { Invites } from '~/types/invite-update';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
import { Rolodex } from '~/types/contact-update';
|
||||
import { Groups } from '~/types/group-update';
|
||||
import { Path } from '@urbit/api';
|
||||
import { Invites } from '@urbit/api/invite';
|
||||
import { Associations } from '@urbit/api/metadata';
|
||||
import { Rolodex } from '@urbit/api/contacts';
|
||||
import { Groups } from '@urbit/api/groups';
|
||||
import { StorageState } from '~/types/storage-state';
|
||||
import { LaunchState, WeatherState } from '~/types/launch-update';
|
||||
import { ConnectionStatus } from '~/types/connection';
|
||||
import {Graphs} from '~/types/graph-update';
|
||||
import { Graphs } from '@urbit/api/graph';
|
||||
import {
|
||||
Notifications,
|
||||
NotificationGraphConfig,
|
||||
NotificationGraphConfig,
|
||||
GroupNotificationsConfig,
|
||||
Unreads,
|
||||
JoinRequests,
|
||||
Patp
|
||||
} from "~/types";
|
||||
} from '@urbit/api';
|
||||
|
||||
export interface StoreState {
|
||||
// local state
|
||||
@ -35,7 +35,6 @@ export interface StoreState {
|
||||
graphs: Graphs;
|
||||
graphKeys: Set<string>;
|
||||
|
||||
|
||||
// App specific states
|
||||
// launch state
|
||||
launch: LaunchState;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BaseStore from "../store/base";
|
||||
import BaseApi from "../api/base";
|
||||
import { Path } from "~/types/noun";
|
||||
import BaseStore from '../store/base';
|
||||
import BaseApi from '../api/base';
|
||||
import { Path } from '@urbit/api';
|
||||
|
||||
export default class BaseSubscription<S extends object> {
|
||||
private errorCount = 0;
|
||||
@ -19,24 +19,24 @@ export default class BaseSubscription<S extends object> {
|
||||
|
||||
// Exists to allow subclasses to hook
|
||||
restart() {
|
||||
this.handleEvent({ data: { connection: 'reconnecting' }});
|
||||
this.handleEvent({ data: { connection: 'reconnecting' } });
|
||||
this.start();
|
||||
}
|
||||
|
||||
onChannelOpen(e: any) {
|
||||
this.errorCount = 0;
|
||||
this.handleEvent({ data: { connection: 'connected' }});
|
||||
this.handleEvent({ data: { connection: 'connected' } });
|
||||
}
|
||||
|
||||
onChannelError(err) {
|
||||
console.error('event source error: ', err);
|
||||
this.errorCount++;
|
||||
if(this.errorCount >= 5) {
|
||||
console.error("bailing out, too many retries");
|
||||
this.handleEvent({ data: { connection: 'disconnected' }});
|
||||
console.error('bailing out, too many retries');
|
||||
this.handleEvent({ data: { connection: 'disconnected' } });
|
||||
return;
|
||||
}
|
||||
this.handleEvent({ data: { connection: 'reconnecting' }});
|
||||
this.handleEvent({ data: { connection: 'reconnecting' } });
|
||||
setTimeout(() => {
|
||||
this.restart();
|
||||
}, Math.pow(2,this.errorCount - 1) * 750);
|
||||
|
@ -1,9 +1,8 @@
|
||||
import BaseSubscription from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '~/types/noun';
|
||||
import { Path } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
/**
|
||||
* Path to subscribe on and app to subscribe to
|
||||
*/
|
||||
@ -68,7 +67,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
||||
}
|
||||
|
||||
stopApp(app: AppName) {
|
||||
this.openSubscriptions[app].map(id => this.unsubscribe(id))
|
||||
this.openSubscriptions[app].map(id => this.unsubscribe(id));
|
||||
this.openSubscriptions[app] = [];
|
||||
}
|
||||
}
|
||||
|
10
pkg/interface/src/register-sw.js
Normal file
10
pkg/interface/src/register-sw.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/~landscape/js/bundle/serviceworker.js", {
|
||||
scope: "/",
|
||||
}).then(reg => {
|
||||
});
|
||||
});
|
||||
}
|
82
pkg/interface/src/serviceworker.js
Normal file
82
pkg/interface/src/serviceworker.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { registerRoute } from 'workbox-routing';
|
||||
import {
|
||||
NetworkFirst,
|
||||
StaleWhileRevalidate,
|
||||
CacheFirst,
|
||||
} from 'workbox-strategies';
|
||||
|
||||
// Used for filtering matches based on status code, header, or both
|
||||
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
|
||||
// Used to limit entries in cache, remove entries after a certain period of time
|
||||
import { ExpirationPlugin } from 'workbox-expiration';
|
||||
|
||||
|
||||
// generate a different sw for every build, to bust cache properly
|
||||
const hash = process.env.LANDSCAPE_SHORTHASH;
|
||||
|
||||
self.addEventListener("install", ev => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', ev => {
|
||||
ev.waitUntil(clients.claim());
|
||||
});
|
||||
|
||||
// Cache page navigations (html) with a Network First strategy
|
||||
registerRoute(
|
||||
// Check to see if the request is a navigation to a new page
|
||||
({ request }) => request.mode === 'navigate',
|
||||
// Use a Network First caching strategy
|
||||
new NetworkFirst({
|
||||
// Put all cached files in a cache named 'pages'
|
||||
cacheName: 'pages',
|
||||
plugins: [
|
||||
// Ensure that only requests that result in a 200 status are cached
|
||||
new CacheableResponsePlugin({
|
||||
statuses: [200],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
|
||||
registerRoute(
|
||||
// Check to see if the request's destination is style for stylesheets, script for JavaScript, or worker for web worker
|
||||
({ request }) =>
|
||||
request.destination === 'style' ||
|
||||
request.destination === 'script' ||
|
||||
request.destination === 'worker',
|
||||
// Use a Stale While Revalidate caching strategy
|
||||
new StaleWhileRevalidate({
|
||||
// Put all cached files in a cache named 'assets'
|
||||
cacheName: 'assets',
|
||||
plugins: [
|
||||
// Ensure that only requests that result in a 200 status are cached
|
||||
new CacheableResponsePlugin({
|
||||
statuses: [200],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
// Cache images with a Cache First strategy
|
||||
registerRoute(
|
||||
// Check to see if the request's destination is style for an image
|
||||
({ request }) => request.destination === 'image',
|
||||
// Use a Cache First caching strategy
|
||||
new CacheFirst({
|
||||
// Put all cached files in a cache named 'images'
|
||||
cacheName: 'images',
|
||||
plugins: [
|
||||
// Ensure that only requests that result in a 200 status are cached
|
||||
new CacheableResponsePlugin({
|
||||
statuses: [200],
|
||||
}),
|
||||
// Don't cache more than 50 items, and expire them after 30 days
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 50,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 Days
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
@ -1,21 +1,17 @@
|
||||
import { ContactUpdate } from "./contact-update";
|
||||
import { InviteUpdate } from "./invite-update";
|
||||
import { LocalUpdate } from "./local-update";
|
||||
import { MetadataUpdate } from "./metadata-update";
|
||||
import { GroupUpdate } from "./group-update";
|
||||
import { LaunchUpdate, WeatherState } from "./launch-update";
|
||||
import { ConnectionStatus } from "./connection";
|
||||
import { SettingsUpdate } from "./settings";
|
||||
import { LocalUpdate } from './local-update';
|
||||
import { LaunchUpdate, WeatherState } from './launch-update';
|
||||
import { ConnectionStatus } from './connection';
|
||||
import { ContactUpdate, GroupUpdate, InviteUpdate, MetadataUpdate } from '@urbit/api';
|
||||
import { SettingsUpdate } from '@urbit/api/settings';
|
||||
|
||||
interface MarksToTypes {
|
||||
readonly json: any;
|
||||
readonly "contact-update": ContactUpdate;
|
||||
readonly "invite-update": InviteUpdate;
|
||||
readonly "metadata-update": MetadataUpdate;
|
||||
readonly 'contact-update': ContactUpdate;
|
||||
readonly 'invite-update': InviteUpdate;
|
||||
readonly 'metadata-update': MetadataUpdate;
|
||||
readonly groupUpdate: GroupUpdate;
|
||||
readonly "launch-update": LaunchUpdate;
|
||||
readonly "link-listen-update": LinkListenUpdate;
|
||||
readonly "settings-event": SettingsUpdate;
|
||||
readonly 'launch-update': LaunchUpdate;
|
||||
readonly 'settings-event': SettingsUpdate;
|
||||
// not really marks but w/e
|
||||
readonly 'local': LocalUpdate;
|
||||
readonly 'weather': WeatherState | {};
|
||||
|
@ -1,2 +1 @@
|
||||
|
||||
export type ConnectionStatus = 'reconnecting' | 'disconnected' | 'connected';
|
||||
|
@ -1,85 +0,0 @@
|
||||
import { Path, Patp } from "./noun";
|
||||
|
||||
export type ContactUpdate =
|
||||
| ContactUpdateCreate
|
||||
| ContactUpdateDelete
|
||||
| ContactUpdateAdd
|
||||
| ContactUpdateRemove
|
||||
| ContactUpdateEdit
|
||||
| ContactUpdateInitial
|
||||
| ContactUpdateContacts;
|
||||
|
||||
interface ContactUpdateCreate {
|
||||
create: Path;
|
||||
}
|
||||
|
||||
interface ContactUpdateDelete {
|
||||
delete: Path;
|
||||
}
|
||||
|
||||
interface ContactUpdateAdd {
|
||||
add: {
|
||||
path: Path;
|
||||
ship: Patp;
|
||||
contact: Contact;
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactUpdateRemove {
|
||||
remove: {
|
||||
path: Path;
|
||||
ship: Patp;
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactUpdateEdit {
|
||||
edit: {
|
||||
path: Path;
|
||||
ship: Patp;
|
||||
"edit-field": ContactEdit;
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactUpdateInitial {
|
||||
initial: Rolodex;
|
||||
}
|
||||
|
||||
interface ContactUpdateContacts {
|
||||
contacts: {
|
||||
path: Path;
|
||||
contacts: Contacts;
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type ContactAvatar = ContactAvatarUrl | ContactAvatarOcts;
|
||||
|
||||
export type Rolodex = {
|
||||
[p in Path]: Contacts;
|
||||
};
|
||||
|
||||
export type Contacts = {
|
||||
[p in Patp]: Contact;
|
||||
};
|
||||
|
||||
interface ContactAvatarUrl {
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface ContactAvatarOcts {
|
||||
octs: string;
|
||||
}
|
||||
export interface Contact {
|
||||
nickname: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
website: string;
|
||||
notes: string;
|
||||
color: string;
|
||||
avatar: string | null;
|
||||
}
|
||||
|
||||
export type ContactEdit = {
|
||||
[k in keyof Contact]: Contact[k];
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { PatpNoSig } from "./noun";
|
||||
import { PatpNoSig } from '@urbit/api';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -1,16 +1,8 @@
|
||||
export * from './cage';
|
||||
export * from './connection';
|
||||
export * from './contact-update';
|
||||
export * from './global';
|
||||
export * from './group-update';
|
||||
export * from './group-view';
|
||||
export * from './graph-update';
|
||||
export * from './hark-update';
|
||||
export * from './invite-update';
|
||||
export * from './launch-update';
|
||||
export * from './local-update';
|
||||
export * from './metadata-update';
|
||||
export * from './noun';
|
||||
export * from './storage-state';
|
||||
export * from './gcp-state';
|
||||
export * from './s3-update';
|
||||
|
@ -1,11 +1,9 @@
|
||||
|
||||
export type LaunchUpdate =
|
||||
LaunchUpdateInitial
|
||||
| LaunchUpdateFirstTime
|
||||
| LaunchUpdateOrder
|
||||
| LaunchUpdateIsShown;
|
||||
|
||||
|
||||
interface LaunchUpdateInitial {
|
||||
initial: LaunchState;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
export interface S3Credentials {
|
||||
export interface S3Credentials {
|
||||
endpoint: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
@ -51,7 +49,6 @@ interface S3UpdateSecretAccessKey {
|
||||
setSecretAccessKey: string;
|
||||
}
|
||||
|
||||
|
||||
export type S3Update =
|
||||
S3UpdateCredentials
|
||||
| S3UpdateConfiguration
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Icon } from "@tlon/indigo-react";
|
||||
import { Icon } from '@tlon/indigo-react';
|
||||
export type PropFunc<T extends (...args: any[]) => any> = Parameters<T>[0];
|
||||
|
||||
export type Primitive = string | number | undefined | symbol | null | boolean;
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
interface GroupWorkspace {
|
||||
type: 'group';
|
||||
group: string;
|
||||
|
@ -3,7 +3,7 @@ import { RouteComponentProps } from 'react-router-dom';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Association } from '~/types/metadata-update';
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import { useFileDrag } from '~/logic/lib/useDrag';
|
||||
import ChatWindow from './components/ChatWindow';
|
||||
@ -113,7 +113,6 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
} else {
|
||||
setShowBanner(false);
|
||||
}
|
||||
|
||||
} else {
|
||||
const groupShared = await props.api.contacts.fetchIsAllowed(
|
||||
`~${window.ship}`,
|
||||
@ -126,14 +125,13 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
|
||||
setHasLoadedAllowed(true);
|
||||
})();
|
||||
|
||||
}, [groupPath]);
|
||||
}, [groupPath, group]);
|
||||
|
||||
if(!graph) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
var modifiedContacts = { ...contacts };
|
||||
const modifiedContacts = { ...contacts };
|
||||
delete modifiedContacts[`~${window.ship}`];
|
||||
|
||||
return (
|
||||
@ -147,7 +145,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
setShowBanner={setShowBanner}
|
||||
group={group}
|
||||
groupPath={groupPath}
|
||||
/>
|
||||
/>
|
||||
{dragging && <SubmitDragger />}
|
||||
<ChatWindow
|
||||
history={props.history}
|
||||
|
@ -7,7 +7,8 @@ import { createPost } from '~/logic/api/graph';
|
||||
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Envelope } from '~/types/chat-update';
|
||||
import { StorageState, Contacts, Content } from '~/types';
|
||||
import { StorageState } from '~/types';
|
||||
import { Contacts, Content } from '@urbit/api';
|
||||
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
|
||||
import withStorage from '~/views/components/withStorage';
|
||||
import { withLocalState } from '~/logic/state/local';
|
||||
@ -15,8 +16,8 @@ import { withLocalState } from '~/logic/state/local';
|
||||
type ChatInputProps = IuseStorage & {
|
||||
api: GlobalApi;
|
||||
numMsgs: number;
|
||||
station: any;
|
||||
ourContact: any;
|
||||
station: unknown;
|
||||
ourContact: unknown;
|
||||
envelopes: Envelope[];
|
||||
contacts: Contacts;
|
||||
onUnmount(msg: string): void;
|
||||
@ -67,14 +68,14 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
inCodeMode: false
|
||||
}, async () => {
|
||||
const output = await props.api.graph.eval(text);
|
||||
const contents: Content[] = [{ code: { output, expression: text }}];
|
||||
const contents: Content[] = [{ code: { output, expression: text } }];
|
||||
const post = createPost(contents);
|
||||
props.api.graph.addPost(ship, name, post);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const post = createPost(tokenizeMessage((text)))
|
||||
const post = createPost(tokenizeMessage((text)));
|
||||
|
||||
props.deleteMessage();
|
||||
|
||||
@ -110,7 +111,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
if (!this.props.canUpload) {
|
||||
return;
|
||||
}
|
||||
Array.from(files).forEach(file => {
|
||||
Array.from(files).forEach((file) => {
|
||||
this.props.uploadDefault(file)
|
||||
.then(this.uploadSuccess)
|
||||
.catch(this.uploadError);
|
||||
@ -178,7 +179,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
width="16"
|
||||
height="16"
|
||||
onClick={() => this.props.promptUpload().then(this.uploadSuccess)}
|
||||
/>
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</Box>
|
||||
@ -200,4 +201,4 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default withLocalState(withStorage(ChatInput, {accept: 'image/*'}), ['hideAvatars']);
|
||||
export default withLocalState(withStorage(ChatInput, { accept: 'image/*' }), ['hideAvatars']);
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
@ -17,44 +18,65 @@ import {
|
||||
useShowNickname,
|
||||
useHovering
|
||||
} from '~/logic/lib/util';
|
||||
import { Group, Association, Contacts, Post, Groups, Associations } from '~/types';
|
||||
import {
|
||||
Group,
|
||||
Association,
|
||||
Contacts,
|
||||
Post,
|
||||
Groups,
|
||||
Associations
|
||||
} from '~/types';
|
||||
import TextContent from './content/text';
|
||||
import CodeContent from './content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
import { Mention } from '~/views/components/MentionText';
|
||||
import styled from 'styled-components';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import Timestamp from '~/views/components/Timestamp';
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||
<Row
|
||||
flexShrink={0}
|
||||
ref={ref}
|
||||
color='blue'
|
||||
alignItems='center'
|
||||
fontSize='0'
|
||||
position='absolute'
|
||||
width='100%'
|
||||
py='2'
|
||||
>
|
||||
<Rule borderColor='blue' display={['none', 'block']} m='0' width='2rem' />
|
||||
<Text flexShrink='0' display='block' zIndex='2' mx='4' color='blue'>
|
||||
New messages below
|
||||
</Text>
|
||||
<Rule borderColor='blue' flexGrow='1' m='0' />
|
||||
<Rule style={{ width: 'calc(50% - 48px)' }} borderColor='blue' m='0' />
|
||||
</Row>
|
||||
));
|
||||
interface DayBreakProps {
|
||||
when: string;
|
||||
shimTop?: boolean;
|
||||
}
|
||||
|
||||
export const DayBreak = ({ when }) => (
|
||||
<Row pb='3' alignItems='center' justifyContent='center' width='100%'>
|
||||
<Text gray>
|
||||
export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
|
||||
<Row
|
||||
px={2}
|
||||
height={5}
|
||||
mb={2}
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
mt={shimTop ? '-8px' : '0'}
|
||||
>
|
||||
<Rule borderColor='lightGray' />
|
||||
<Text gray flexShrink='0' fontSize={0} px={2}>
|
||||
{moment(when).calendar(null, { sameElse: DATESTAMP_FORMAT })}
|
||||
</Text>
|
||||
<Rule borderColor='lightGray' />
|
||||
</Row>
|
||||
);
|
||||
|
||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||
<Row
|
||||
position='absolute'
|
||||
ref={ref}
|
||||
px={2}
|
||||
mt={2}
|
||||
height={5}
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
width='100%'
|
||||
>
|
||||
<Rule borderColor='lightBlue' />
|
||||
<Text color='blue' fontSize={0} flexShrink='0' px={2}>
|
||||
New messages below
|
||||
</Text>
|
||||
<Rule borderColor='lightBlue' />
|
||||
</Row>
|
||||
));
|
||||
|
||||
interface ChatMessageProps {
|
||||
measure(element): void;
|
||||
msg: Post;
|
||||
@ -66,13 +88,14 @@ interface ChatMessageProps {
|
||||
contacts: Contacts;
|
||||
className?: string;
|
||||
isPending: boolean;
|
||||
style?: any;
|
||||
style?: unknown;
|
||||
scrollWindow: HTMLDivElement;
|
||||
isLastMessage?: boolean;
|
||||
unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
history: any;
|
||||
api: any;
|
||||
history: unknown;
|
||||
api: GlobalApi;
|
||||
highlighted?: boolean;
|
||||
renderSigil?: boolean;
|
||||
}
|
||||
|
||||
export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
@ -113,21 +136,26 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
associations
|
||||
} = this.props;
|
||||
|
||||
const renderSigil = Boolean(
|
||||
(nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1
|
||||
);
|
||||
let { renderSigil } = this.props;
|
||||
|
||||
if (renderSigil === undefined) {
|
||||
renderSigil = Boolean(
|
||||
(nextMsg && msg.author !== nextMsg.author) ||
|
||||
!nextMsg ||
|
||||
msg.number === 1
|
||||
);
|
||||
}
|
||||
|
||||
const dayBreak =
|
||||
nextMsg &&
|
||||
new Date(msg['time-sent']).getDate() !==
|
||||
new Date(nextMsg['time-sent']).getDate();
|
||||
|
||||
const containerClass = `${
|
||||
renderSigil ? 'cf pl2 lh-copy' : 'items-top cf hide-child'
|
||||
} ${isPending ? 'o-40' : ''} ${className}`;
|
||||
const containerClass = `${isPending ? 'o-40' : ''} ${className}`;
|
||||
|
||||
const timestamp = moment
|
||||
.unix(msg['time-sent'] / 1000)
|
||||
.format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
||||
.format(renderSigil ? 'h:mm A' : 'h:mm');
|
||||
|
||||
const reboundMeasure = (event) => {
|
||||
return measure(this.divRef.current);
|
||||
@ -149,7 +177,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
highlighted,
|
||||
fontSize,
|
||||
associations,
|
||||
groups,
|
||||
groups
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
@ -158,34 +186,24 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
|
||||
return (
|
||||
<Box
|
||||
bg={highlighted ? 'washedBlue' : 'white'}
|
||||
flexShrink={0}
|
||||
width='100%'
|
||||
display='flex'
|
||||
flexWrap='wrap'
|
||||
pt={this.props.pt ? this.props.pt : renderSigil ? 3 : 0}
|
||||
pr={3}
|
||||
pb={isLastMessage ? 3 : 0}
|
||||
ref={this.divRef}
|
||||
pt={renderSigil ? 2 : 0}
|
||||
pb={isLastMessage ? 4 : 2}
|
||||
className={containerClass}
|
||||
style={style}
|
||||
mb={1}
|
||||
position='relative'
|
||||
>
|
||||
{dayBreak && !isLastRead ? <DayBreak when={msg['time-sent']} /> : null}
|
||||
{dayBreak && !isLastRead ? (
|
||||
<DayBreak when={msg['time-sent']} shimTop={renderSigil} />
|
||||
) : null}
|
||||
{renderSigil ? (
|
||||
<MessageWithSigil {...messageProps} />
|
||||
<>
|
||||
<MessageAuthor pb={'2px'} {...messageProps} />
|
||||
<Message pl={5} pr={4} {...messageProps} />
|
||||
</>
|
||||
) : (
|
||||
<MessageWithoutSigil {...messageProps} />
|
||||
<Message pl={5} pr={4} timestampHover {...messageProps} />
|
||||
)}
|
||||
<Box
|
||||
flexShrink={0}
|
||||
fontSize={0}
|
||||
position='relative'
|
||||
width='100%'
|
||||
overflow='visible'
|
||||
style={unreadContainerStyle}
|
||||
>
|
||||
<Box style={unreadContainerStyle}>
|
||||
{isLastRead ? (
|
||||
<UnreadMarker
|
||||
dayBreak={dayBreak}
|
||||
@ -199,44 +217,28 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
}
|
||||
}
|
||||
|
||||
interface MessageProps {
|
||||
msg: Post;
|
||||
timestamp: string;
|
||||
group: Group;
|
||||
association: Association;
|
||||
contacts: Contacts;
|
||||
containerClass: string;
|
||||
isPending: boolean;
|
||||
style: any;
|
||||
measure(element): void;
|
||||
scrollWindow: HTMLDivElement;
|
||||
associations: Associations;
|
||||
groups: Groups;
|
||||
}
|
||||
|
||||
export const MessageWithSigil = (props) => {
|
||||
const {
|
||||
msg,
|
||||
timestamp,
|
||||
contacts,
|
||||
association,
|
||||
associations,
|
||||
groups,
|
||||
group,
|
||||
measure,
|
||||
api,
|
||||
history,
|
||||
scrollWindow,
|
||||
fontSize
|
||||
} = props;
|
||||
|
||||
export const MessageAuthor = ({
|
||||
timestamp,
|
||||
contacts,
|
||||
msg,
|
||||
measure,
|
||||
group,
|
||||
api,
|
||||
associations,
|
||||
groups,
|
||||
history,
|
||||
scrollWindow,
|
||||
...rest
|
||||
}) => {
|
||||
const dark = useLocalState((state) => state.dark);
|
||||
|
||||
const datestamp = moment
|
||||
.unix(msg['time-sent'] / 1000)
|
||||
.format(DATESTAMP_FORMAT);
|
||||
const contact = `~${msg.author}` in contacts ? contacts[`~${msg.author}`] : false;
|
||||
const contact =
|
||||
`~${msg.author}` in contacts ? contacts[`~${msg.author}`] : false;
|
||||
const showNickname = useShowNickname(contact);
|
||||
const { hideAvatars } = useLocalState(({ hideAvatars }) => ({ hideAvatars }));
|
||||
const shipName = showNickname ? contact.nickname : cite(msg.author);
|
||||
const copyNotice = 'Copied';
|
||||
const color = contact
|
||||
@ -270,10 +272,10 @@ export const MessageWithSigil = (props) => {
|
||||
};
|
||||
const timer = setTimeout(() => resetDisplay(), 800);
|
||||
return () => clearTimeout(timer);
|
||||
}, [displayName]);
|
||||
}, [shipName, displayName]);
|
||||
|
||||
const img =
|
||||
contact && contact.avatar !== null ? (
|
||||
contact?.avatar && !hideAvatars ? (
|
||||
<BaseImage
|
||||
display='inline-block'
|
||||
src={contact.avatar}
|
||||
@ -290,21 +292,21 @@ export const MessageWithSigil = (props) => {
|
||||
padding={2}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display='flex' alignItems='center' {...rest}>
|
||||
<Box
|
||||
onClick={() => {
|
||||
setShowOverlay(true);
|
||||
}}
|
||||
className='fl v-top pt1'
|
||||
height={16}
|
||||
pr={3}
|
||||
pr={2}
|
||||
pl={2}
|
||||
cursor='pointer'
|
||||
position='relative'
|
||||
>
|
||||
{showOverlay && (
|
||||
<OverlaySigil
|
||||
cursor='auto'
|
||||
ship={msg.author}
|
||||
contact={contact}
|
||||
color={`#${uxToHex(contact?.color ?? '0x0')}`}
|
||||
@ -329,11 +331,11 @@ export const MessageWithSigil = (props) => {
|
||||
>
|
||||
<Text
|
||||
fontSize={0}
|
||||
mr={3}
|
||||
mr={2}
|
||||
flexShrink={0}
|
||||
mono={nameMono}
|
||||
fontWeight={nameMono ? '400' : '500'}
|
||||
className={'mw5 db truncate pointer'}
|
||||
cursor='pointer'
|
||||
onClick={() => {
|
||||
writeText(`~${msg.author}`);
|
||||
showCopyNotice();
|
||||
@ -342,48 +344,25 @@ export const MessageWithSigil = (props) => {
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
<Text flexShrink={0} fontSize={0} gray mono>
|
||||
<Text flexShrink={0} fontSize={0} gray>
|
||||
{timestamp}
|
||||
</Text>
|
||||
<Text
|
||||
flexShrink={0}
|
||||
fontSize={0}
|
||||
gray
|
||||
mono
|
||||
ml={2}
|
||||
display={['none', hovering ? 'block' : 'none']}
|
||||
>
|
||||
{datestamp}
|
||||
</Text>
|
||||
</Box>
|
||||
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
|
||||
{msg.contents.map((c, i) => (
|
||||
<MessageContent
|
||||
key={i}
|
||||
contacts={contacts}
|
||||
content={c}
|
||||
measure={measure}
|
||||
scrollWindow={scrollWindow}
|
||||
fontSize={fontSize}
|
||||
group={group}
|
||||
api={api}
|
||||
associations={associations}
|
||||
groups={groups}
|
||||
/>
|
||||
))}
|
||||
</ContentBox>
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ContentBox = styled(Box)`
|
||||
& > :first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MessageWithoutSigil = ({
|
||||
export const Message = ({
|
||||
timestamp,
|
||||
contacts,
|
||||
msg,
|
||||
@ -392,119 +371,97 @@ export const MessageWithoutSigil = ({
|
||||
api,
|
||||
associations,
|
||||
groups,
|
||||
scrollWindow
|
||||
scrollWindow,
|
||||
timestampHover,
|
||||
...rest
|
||||
}) => {
|
||||
const { hovering, bind } = useHovering();
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
flexShrink={0}
|
||||
mono
|
||||
gray
|
||||
display={hovering ? 'block' : 'none'}
|
||||
pt='2px'
|
||||
lineHeight='tall'
|
||||
fontSize={0}
|
||||
position='absolute'
|
||||
left={1}
|
||||
>
|
||||
{timestamp}
|
||||
</Text>
|
||||
<ContentBox
|
||||
flexShrink={0}
|
||||
fontSize='14px'
|
||||
className='clamp-message'
|
||||
style={{ flexGrow: 1 }}
|
||||
{...bind}
|
||||
pl={6}
|
||||
>
|
||||
{msg.contents.map((c, i) => (
|
||||
<MessageContent
|
||||
key={i}
|
||||
contacts={contacts}
|
||||
content={c}
|
||||
group={group}
|
||||
measure={measure}
|
||||
scrollWindow={scrollWindow}
|
||||
groups={groups}
|
||||
associations={associations}
|
||||
api={api}
|
||||
/>
|
||||
))}
|
||||
</ContentBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const MessageContent = ({
|
||||
content,
|
||||
contacts,
|
||||
api,
|
||||
associations,
|
||||
groups,
|
||||
measure,
|
||||
scrollWindow,
|
||||
fontSize,
|
||||
group
|
||||
}) => {
|
||||
if ('code' in content) {
|
||||
return <CodeContent content={content} />;
|
||||
} else if ('url' in content) {
|
||||
return (
|
||||
<Box
|
||||
mx='2px'
|
||||
flexShrink={0}
|
||||
fontSize={fontSize ? fontSize : '14px'}
|
||||
lineHeight='tall'
|
||||
color='black'
|
||||
>
|
||||
<RemoteContent
|
||||
url={content.url}
|
||||
onLoad={measure}
|
||||
imageProps={{
|
||||
style: {
|
||||
maxWidth: 'min(100%,18rem)',
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
videoProps={{
|
||||
style: {
|
||||
maxWidth: '18rem',
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
textProps={{
|
||||
style: {
|
||||
fontSize: 'inherit',
|
||||
borderBottom: '1px solid',
|
||||
textDecoration: 'none'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box position='relative' {...rest}>
|
||||
{timestampHover ? (
|
||||
<Text
|
||||
display={hovering ? 'block' : 'none'}
|
||||
position='absolute'
|
||||
left='0'
|
||||
top='3px'
|
||||
fontSize={0}
|
||||
gray
|
||||
>
|
||||
{timestamp}
|
||||
</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Box {...bind}>
|
||||
{msg.contents.map((content, i) => {
|
||||
switch (Object.keys(content)[0]) {
|
||||
case 'text':
|
||||
return (
|
||||
<TextContent
|
||||
associations={associations}
|
||||
groups={groups}
|
||||
measure={measure}
|
||||
api={api}
|
||||
fontSize={1}
|
||||
lineHeight={'20px'}
|
||||
content={content}
|
||||
/>
|
||||
);
|
||||
case 'code':
|
||||
return <CodeContent content={content} />;
|
||||
case 'url':
|
||||
return (
|
||||
<Box
|
||||
flexShrink={0}
|
||||
fontSize={1}
|
||||
lineHeight='20px'
|
||||
color='black'
|
||||
>
|
||||
<RemoteContent
|
||||
url={content.url}
|
||||
onLoad={measure}
|
||||
imageProps={{
|
||||
style: {
|
||||
maxWidth: 'min(100%,18rem)',
|
||||
display: 'inline-block',
|
||||
marginTop: '0.5rem'
|
||||
}
|
||||
}}
|
||||
videoProps={{
|
||||
style: {
|
||||
maxWidth: '18rem',
|
||||
display: 'block',
|
||||
marginTop: '0.5rem'
|
||||
}
|
||||
}}
|
||||
textProps={{
|
||||
style: {
|
||||
fontSize: 'inherit',
|
||||
borderBottom: '1px solid',
|
||||
textDecoration: 'none'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
case 'mention':
|
||||
const first = (i) => (i === 0);
|
||||
return (
|
||||
<Mention
|
||||
first={first(i)}
|
||||
group={group}
|
||||
scrollWindow={scrollWindow}
|
||||
ship={content.mention}
|
||||
contact={contacts?.[`~${content.mention}`]}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
} else if ('text' in content) {
|
||||
return (
|
||||
<TextContent
|
||||
associations={associations}
|
||||
groups={groups}
|
||||
measure={measure}
|
||||
api={api}
|
||||
fontSize={fontSize}
|
||||
content={content}
|
||||
/>);
|
||||
} else if ('mention' in content) {
|
||||
return (
|
||||
<Mention
|
||||
group={group}
|
||||
scrollWindow={scrollWindow}
|
||||
ship={content.mention}
|
||||
contact={contacts?.[`~${content.mention}`]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const MessagePlaceholder = ({
|
||||
|
@ -1,22 +1,25 @@
|
||||
import React, { Component } from "react";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import _ from "lodash";
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import {
|
||||
Patp,
|
||||
Contacts,
|
||||
Association,
|
||||
Associations,
|
||||
Group,
|
||||
Groups,
|
||||
Graph
|
||||
} from '@urbit/api';
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Patp, Path } from "~/types/noun";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import { Association, Associations } from "~/types/metadata-update";
|
||||
import { Group, Groups } from "~/types/group-update";
|
||||
import { Envelope, IMessage } from "~/types/chat-update";
|
||||
import { Graph } from "~/types";
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
|
||||
import VirtualScroller from "~/views/components/VirtualScroller";
|
||||
import VirtualScroller from '~/views/components/VirtualScroller';
|
||||
|
||||
import ChatMessage, { MessagePlaceholder } from './ChatMessage';
|
||||
import { UnreadNotice } from "./unread-notice";
|
||||
import { UnreadNotice } from './unread-notice';
|
||||
|
||||
const INITIAL_LOAD = 20;
|
||||
const DEFAULT_BACKLOG_SIZE = 100;
|
||||
@ -38,7 +41,7 @@ type ChatWindowProps = RouteComponentProps<{
|
||||
scrollTo?: number;
|
||||
associations: Associations;
|
||||
groups: Groups;
|
||||
}
|
||||
};
|
||||
|
||||
interface ChatWindowState {
|
||||
fetchPending: boolean;
|
||||
@ -47,7 +50,10 @@ interface ChatWindowState {
|
||||
unreadIndex: BigInteger;
|
||||
}
|
||||
|
||||
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
||||
export default class ChatWindow extends Component<
|
||||
ChatWindowProps,
|
||||
ChatWindowState
|
||||
> {
|
||||
private virtualList: VirtualScroller | null;
|
||||
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
private prevSize = 0;
|
||||
@ -66,8 +72,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
unreadIndex: bigInt.zero
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.dismissUnread = this.dismissUnread.bind(this);
|
||||
this.scrollToUnread = this.scrollToUnread.bind(this);
|
||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
||||
@ -86,7 +90,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
window.addEventListener('blur', this.handleWindowBlur);
|
||||
window.addEventListener('focus', this.handleWindowFocus);
|
||||
setTimeout(() => {
|
||||
if(this.props.scrollTo) {
|
||||
if (this.props.scrollTo) {
|
||||
this.scrollToUnread();
|
||||
}
|
||||
|
||||
@ -102,7 +106,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
calculateUnreadIndex() {
|
||||
const { graph, unreadCount } = this.props;
|
||||
const unreadIndex = graph.keys()[unreadCount];
|
||||
if(!unreadIndex || unreadCount === 0) {
|
||||
if (!unreadIndex || unreadCount === 0) {
|
||||
this.setState({
|
||||
unreadIndex: bigInt.zero
|
||||
});
|
||||
@ -110,7 +114,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
}
|
||||
this.setState({
|
||||
unreadIndex
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
handleWindowBlur() {
|
||||
@ -135,9 +139,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.calculateUnreadIndex();
|
||||
}
|
||||
|
||||
|
||||
if(this.prevSize !== graph.size) {
|
||||
if(this.state.unreadIndex.eq(bigInt.zero)) {
|
||||
if (this.prevSize !== graph.size) {
|
||||
if (this.state.unreadIndex.eq(bigInt.zero)) {
|
||||
this.calculateUnreadIndex();
|
||||
this.scrollToUnread();
|
||||
}
|
||||
@ -161,7 +164,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
scrollToUnread() {
|
||||
const { unreadIndex } = this.state;
|
||||
if(unreadIndex.eq(bigInt.zero)) {
|
||||
if (unreadIndex.eq(bigInt.zero)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -179,32 +182,36 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
async fetchMessages(newer: boolean, force = false): Promise<void> {
|
||||
const { api, station, graph } = this.props;
|
||||
|
||||
if ( this.state.fetchPending && !force) {
|
||||
return new Promise((resolve, reject) => {});
|
||||
if (this.state.fetchPending && !force) {
|
||||
return new Promise((resolve, reject) => {});
|
||||
}
|
||||
|
||||
this.setState({ fetchPending: true });
|
||||
|
||||
const [,, ship, name] = station.split('/');
|
||||
const [, , ship, name] = station.split('/');
|
||||
const currSize = graph.size;
|
||||
if(newer && !this.loadedNewest) {
|
||||
if (newer && !this.loadedNewest) {
|
||||
const [index] = graph.peekLargest()!;
|
||||
await api.graph.getYoungerSiblings(ship,name, 20, `/${index.toString()}`)
|
||||
if(currSize === graph.size) {
|
||||
await api.graph.getYoungerSiblings(
|
||||
ship,
|
||||
name,
|
||||
20,
|
||||
`/${index.toString()}`
|
||||
);
|
||||
if (currSize === graph.size) {
|
||||
console.log('loaded all newest');
|
||||
this.loadedNewest = true;
|
||||
}
|
||||
} else if(!newer && !this.loadedOldest) {
|
||||
} else if (!newer && !this.loadedOldest) {
|
||||
const [index] = graph.peekSmallest()!;
|
||||
await api.graph.getOlderSiblings(ship,name, 20, `/${index.toString()}`)
|
||||
await api.graph.getOlderSiblings(ship, name, 20, `/${index.toString()}`);
|
||||
this.calculateUnreadIndex();
|
||||
if(currSize === graph.size) {
|
||||
if (currSize === graph.size) {
|
||||
console.log('loaded all oldest');
|
||||
this.loadedOldest = true;
|
||||
}
|
||||
}
|
||||
this.setState({ fetchPending: false });
|
||||
|
||||
}
|
||||
|
||||
onScroll({ scrollTop, scrollHeight, windowHeight }) {
|
||||
@ -222,8 +229,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
if (!parent) return;
|
||||
const { scrollTop, scrollHeight, offsetHeight } = this.virtualList.window;
|
||||
if (
|
||||
(scrollHeight - parent.offsetTop > scrollTop)
|
||||
&& (scrollHeight - parent.offsetTop < scrollTop + offsetHeight)
|
||||
scrollHeight - parent.offsetTop > scrollTop &&
|
||||
scrollHeight - parent.offsetTop < scrollTop + offsetHeight
|
||||
) {
|
||||
this.dismissUnread();
|
||||
}
|
||||
@ -245,25 +252,39 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
} = this.props;
|
||||
|
||||
const unreadMarkerRef = this.unreadMarkerRef;
|
||||
|
||||
|
||||
const messageProps = { association, group, contacts, unreadMarkerRef, history, api, groups, associations };
|
||||
|
||||
const messageProps = {
|
||||
association,
|
||||
group,
|
||||
contacts,
|
||||
unreadMarkerRef,
|
||||
history,
|
||||
api,
|
||||
groups,
|
||||
associations
|
||||
};
|
||||
const keys = graph.keys().reverse();
|
||||
const unreadIndex = graph.keys()[this.props.unreadCount];
|
||||
const unreadMsg = unreadIndex && graph.get(unreadIndex);
|
||||
|
||||
return (
|
||||
<Col height='100%' overflow='hidden' position="relative">
|
||||
<Col height='100%' overflow='hidden' position='relative'>
|
||||
<UnreadNotice
|
||||
unreadCount={unreadCount}
|
||||
unreadMsg={unreadCount === 1 && unreadMsg && unreadMsg?.post.author === window.ship ? false : unreadMsg}
|
||||
unreadMsg={
|
||||
unreadCount === 1 &&
|
||||
unreadMsg &&
|
||||
unreadMsg?.post.author === window.ship
|
||||
? false
|
||||
: unreadMsg
|
||||
}
|
||||
dismissUnread={this.dismissUnread}
|
||||
onClick={this.scrollToUnread}
|
||||
/>
|
||||
<VirtualScroller
|
||||
ref={list => {this.virtualList = list}}
|
||||
origin="bottom"
|
||||
ref={(list) => {
|
||||
this.virtualList = list;
|
||||
}}
|
||||
origin='bottom'
|
||||
style={{ height: '100%' }}
|
||||
onStartReached={() => {
|
||||
this.setState({ idle: false });
|
||||
@ -276,18 +297,33 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
const msg = graph.get(index)?.post;
|
||||
if (!msg) return null;
|
||||
if (!this.state.initialized) {
|
||||
return <MessagePlaceholder key={index.toString()} height="64px" index={index} />;
|
||||
return (
|
||||
<MessagePlaceholder
|
||||
key={index.toString()}
|
||||
height='64px'
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
|
||||
const isLastMessage = index.eq(graph.peekLargest()?.[0] ?? bigInt.zero);
|
||||
const isLastMessage = index.eq(
|
||||
graph.peekLargest()?.[0] ?? bigInt.zero
|
||||
);
|
||||
const highlighted = bigInt(this.props.scrollTo || -1).eq(index);
|
||||
const graphIdx = keys.findIndex(idx => idx.eq(index));
|
||||
const prevIdx = keys[graphIdx+1];
|
||||
const nextIdx = keys[graphIdx-1];
|
||||
|
||||
|
||||
const graphIdx = keys.findIndex((idx) => idx.eq(index));
|
||||
const prevIdx = keys[graphIdx + 1];
|
||||
const nextIdx = keys[graphIdx - 1];
|
||||
const isLastRead: boolean = this.state.unreadIndex.eq(index);
|
||||
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
||||
const props = {
|
||||
measure,
|
||||
highlighted,
|
||||
scrollWindow,
|
||||
isPending,
|
||||
isLastRead,
|
||||
isLastMessage,
|
||||
msg,
|
||||
...messageProps
|
||||
};
|
||||
return (
|
||||
<ChatMessage
|
||||
key={index.toString()}
|
||||
@ -305,4 +341,3 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user