mirror of
https://github.com/urbit/shrub.git
synced 2025-01-05 02:57:18 +03:00
Merge branch 'master' into next/landscape
This commit is contained in:
commit
318cb9f00e
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
@ -39,7 +39,6 @@ on:
|
|||||||
- 'pkg/docker-image/**'
|
- 'pkg/docker-image/**'
|
||||||
- 'pkg/ent/**'
|
- 'pkg/ent/**'
|
||||||
- 'pkg/ge-additions/**'
|
- 'pkg/ge-additions/**'
|
||||||
- 'pkg/hs/**'
|
|
||||||
- 'pkg/libaes_siv/**'
|
- 'pkg/libaes_siv/**'
|
||||||
- 'pkg/urbit/**'
|
- 'pkg/urbit/**'
|
||||||
- 'bin/**'
|
- 'bin/**'
|
||||||
@ -50,7 +49,6 @@ on:
|
|||||||
- 'pkg/docker-image/**'
|
- 'pkg/docker-image/**'
|
||||||
- 'pkg/ent/**'
|
- 'pkg/ent/**'
|
||||||
- 'pkg/ge-additions/**'
|
- 'pkg/ge-additions/**'
|
||||||
- 'pkg/hs/**'
|
|
||||||
- 'pkg/libaes_siv/**'
|
- 'pkg/libaes_siv/**'
|
||||||
- 'pkg/urbit/**'
|
- 'pkg/urbit/**'
|
||||||
- 'bin/**'
|
- 'bin/**'
|
||||||
@ -74,12 +72,12 @@ jobs:
|
|||||||
# for the docker build. We don't want in on Mac, where it isn't but
|
# for the docker build. We don't want in on Mac, where it isn't but
|
||||||
# it breaks the nix install. The two `if` clauses should be mutually
|
# it breaks the nix install. The two `if` clauses should be mutually
|
||||||
# exclusive
|
# exclusive
|
||||||
- uses: cachix/install-nix-action@v13
|
- uses: cachix/install-nix-action@v16
|
||||||
with:
|
with:
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system-features = nixos-test benchmark big-parallel kvm
|
system-features = nixos-test benchmark big-parallel kvm
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
- uses: cachix/install-nix-action@v13
|
- uses: cachix/install-nix-action@v16
|
||||||
if: ${{ matrix.os != 'ubuntu-latest' }}
|
if: ${{ matrix.os != 'ubuntu-latest' }}
|
||||||
|
|
||||||
- uses: cachix/cachix-action@v10
|
- uses: cachix/cachix-action@v10
|
||||||
@ -95,28 +93,6 @@ jobs:
|
|||||||
- if: ${{ matrix.os == 'ubuntu-latest' }}
|
- if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
run: nix-build -A docker-image
|
run: nix-build -A docker-image
|
||||||
|
|
||||||
haskell:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { os: ubuntu-latest }
|
|
||||||
- { os: macos-latest }
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: cachix/install-nix-action@v13
|
|
||||||
- uses: cachix/cachix-action@v10
|
|
||||||
with:
|
|
||||||
name: ares
|
|
||||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
|
|
||||||
- run: nix-build -A hs.urbit-king.components.exes.urbit-king --arg enableStatic true
|
|
||||||
- run: nix-build -A hs-checks
|
|
||||||
- run: nix-build shell.nix
|
|
||||||
|
|
||||||
mingw:
|
mingw:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
defaults:
|
defaults:
|
||||||
|
2
.github/workflows/release-docker.yml
vendored
2
.github/workflows/release-docker.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: cachix/install-nix-action@v13
|
- uses: cachix/install-nix-action@v16
|
||||||
with:
|
with:
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
system-features = nixos-test benchmark big-parallel kvm
|
system-features = nixos-test benchmark big-parallel kvm
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: cachix/install-nix-action@v13
|
- uses: cachix/install-nix-action@v16
|
||||||
- uses: cachix/cachix-action@v10
|
- uses: cachix/cachix-action@v10
|
||||||
with:
|
with:
|
||||||
name: ${{ secrets.CACHIX_NAME }}
|
name: ${{ secrets.CACHIX_NAME }}
|
||||||
|
@ -9,8 +9,12 @@ interface, see its [contribution guidelines][interface].
|
|||||||
|
|
||||||
For information on Arvo's maintainers, see [pkg/arvo][main].
|
For information on Arvo's maintainers, see [pkg/arvo][main].
|
||||||
|
|
||||||
|
For more extensive information on Urbit development, such as how to set up an
|
||||||
|
environment and how to submit a grant, see the [developer documentation][dev].
|
||||||
|
|
||||||
[start]: https://urbit.org/using/install
|
[start]: https://urbit.org/using/install
|
||||||
[interface]: /pkg/interface/CONTRIBUTING.md
|
[interface]: /pkg/interface/CONTRIBUTING.md
|
||||||
|
[dev]: https://urbit.org/docs/development
|
||||||
|
|
||||||
## Fake ships
|
## Fake ships
|
||||||
|
|
||||||
@ -55,8 +59,9 @@ urbit -F zod -B "bin/solid.pill" -A "pkg/arvo"
|
|||||||
|
|
||||||
The canonical source tree is the `master` branch of
|
The canonical source tree is the `master` branch of
|
||||||
[https://github.com/urbit/urbit][repo]. You should typically branch off of
|
[https://github.com/urbit/urbit][repo]. You should typically branch off of
|
||||||
`master` when commencing new work; similarly, when we pull in your
|
`master` when commencing new work. Most pull requests should be merging into
|
||||||
contribution, we'll do so by merging it to `master`.
|
one of the `next/*` branches, depending on what part of the system the pull
|
||||||
|
request is targeting.
|
||||||
|
|
||||||
Since we use GitHub, we request you contribute via a GitHub pull request. Tag
|
Since we use GitHub, we request you contribute via a GitHub pull request. Tag
|
||||||
the [maintainer][main] for the component. If you have a question for the
|
the [maintainer][main] for the component. If you have a question for the
|
||||||
@ -294,5 +299,5 @@ Questions or other communications about contributing to Urbit can go to
|
|||||||
[repo]: https://github.com/urbit/urbit
|
[repo]: https://github.com/urbit/urbit
|
||||||
[reba]: https://git-rebase.io/
|
[reba]: https://git-rebase.io/
|
||||||
[issu]: https://github.com/urbit/urbit/issues
|
[issu]: https://github.com/urbit/urbit/issues
|
||||||
[hoon]: https://urbit.org/docs/learn/hoon/style/
|
[hoon]: https://urbit.org/docs/hoon/reference/style
|
||||||
[main]: https://github.com/urbit/urbit/tree/master/pkg/arvo#maintainers
|
[main]: https://github.com/urbit/urbit/tree/master/pkg/arvo#maintainers
|
||||||
|
120
MAINTAINERS.md
120
MAINTAINERS.md
@ -29,7 +29,7 @@ released.
|
|||||||
### Release branches
|
### Release branches
|
||||||
|
|
||||||
Release branches are code that is ready to release. All release branch names
|
Release branches are code that is ready to release. All release branch names
|
||||||
should start with `release/`.
|
should start with `next/`.
|
||||||
|
|
||||||
All code must be reviewed before being pushed to a release branch. Thus,
|
All code must be reviewed before being pushed to a release branch. Thus,
|
||||||
feature branches should be PR'd against a release branch, not master.
|
feature branches should be PR'd against a release branch, not master.
|
||||||
@ -41,57 +41,55 @@ be released together -- unless one of the underlying commits is separately put
|
|||||||
on a release branch.
|
on a release branch.
|
||||||
|
|
||||||
Here's a worked example. The rule is to make however many branches are useful,
|
Here's a worked example. The rule is to make however many branches are useful,
|
||||||
and no more. This example is not prescriptive, the developers making the
|
and no more. This example is not prescriptive; the developers making the
|
||||||
changes may add, remove, or rename branches in this flow at will.
|
changes may add, remove, or rename branches in this flow at will.
|
||||||
|
|
||||||
Suppose you (plural, the dev community at large) complete some work in a
|
Suppose you (plural, the dev community at large) complete some work in a
|
||||||
userspace app, and you put it in `release/next-userspace`. Separately, you make
|
userspace app, and you put it in `next/landscape`. Separately, you make a small
|
||||||
a small JS change. If you PR it to `release/next-userspace`, then it will only
|
JS change. If you PR it to `next/landscape`, then it will only be released at
|
||||||
be released at the same time as the app changes. Maybe this is fine, or maybe
|
the same time as the app changes. Maybe this is fine, or maybe you want this
|
||||||
you want this change to go out quickly, and the change in
|
change to go out quickly, and the change in `next/landscape` is relatively
|
||||||
`release/next-userspace` is relatively risky, so you don't want to push it out
|
risky, so you don't want to push it out on Friday afternoon. In this case, put
|
||||||
on Friday afternoon. In this case, put the change in another release branch,
|
the change in another release branch, say `next/js`. Now either can be released
|
||||||
say `release/next-js`. Now either can be released independently.
|
independently.
|
||||||
|
|
||||||
Suppose you do further work that you want to PR to `release/next-userspace`, but
|
Suppose you do further work that you want to PR to `next/landscape`, but it
|
||||||
it depends on your fixes in `release/next-js`. Simply merge `release/next-js`
|
depends on your fixes in `next/js`. Simply merge `next/js` into either your
|
||||||
into either your feature branch or `release/next-userspace` and PR your finished
|
feature branch or `next/landscape` and PR your finished work to
|
||||||
work to `release/next-userspace`. Now there is a one-way coupling:
|
`next/landscape`. Now there is a one-way coupling: `next/landscape` contains
|
||||||
`release/next-userspace` contains `release/next-js`, so releasing it will
|
`next/js`, so releasing it will implicitly release `next/js`. However, you can
|
||||||
implicitly release `release/next-js`. However, you can still release
|
still release `next/js` independently.
|
||||||
`release/next-js` independently.
|
|
||||||
|
|
||||||
This scheme extends to other branches, like `release/next-kernel` or
|
This scheme extends to other branches, like `next/base` or `next/os1.1` or
|
||||||
`release/os1.1` or `release/ford-fusion`. Some branches may be long-lived and
|
`next/ford-fusion`. Some branches may be long-lived and represent simply the
|
||||||
represent simply the "next" release of something, while others will have a
|
"next" release of something, while others will have a definite lifetime that
|
||||||
definite lifetime that corresponds to development of a particular feature or
|
corresponds to development of a particular feature or numbered release.
|
||||||
numbered release.
|
|
||||||
|
|
||||||
Since they are "done", release branches should be considered "public", in the
|
Since they are "done", release branches should be considered "public", in the
|
||||||
sense that others may depend on them at will. Thus, never rebase a release
|
sense that others may depend on them at will. Thus, never rebase a release
|
||||||
branch.
|
branch.
|
||||||
|
|
||||||
When cutting a new release, you can filter branches with `git branch --list
|
When cutting a new release, you can filter branches with `git branch --list
|
||||||
'release/*'` or by typing "release/" in the branch filter on Github. This will
|
'next/*'` or by typing "next/" in the branch filter on Github. This will give
|
||||||
give you the list of branches which have passed review and may be merged to
|
you the list of branches which have passed review and may be merged to master
|
||||||
master and released. When choosing which branches to release, make sure you
|
and released. When choosing which branches to release, make sure you understand
|
||||||
understand the risks of releasing them immediately. If merging these produces
|
the risks of releasing them immediately. If merging these produces nontrivial
|
||||||
nontrivial conflicts, consider asking the developers on those branches to merge
|
conflicts, consider asking the developers on those branches to merge between
|
||||||
between themselves. In many cases a developer can do this directly, but if it's
|
themselves. In many cases a developer can do this directly, but if it's
|
||||||
sufficiently nontrivial, this may be a reviewed PR of one release branch into
|
sufficiently nontrivial, this may be a reviewed PR of one release branch into
|
||||||
another.
|
another.
|
||||||
|
|
||||||
### Non-OTAable release branches
|
#### Standard release branches
|
||||||
|
|
||||||
In some cases, work is completed which cannot be OTA'd as written. For example,
|
While you can always create non-standard release branches to stage for a
|
||||||
the code may lack state adapters, or it may not properly handle outstanding
|
particular release, most changes should go through the following:
|
||||||
subscriptions. It could also be code which is planned to be released only upon
|
|
||||||
a breach (network-wide or rolling).
|
|
||||||
|
|
||||||
In this case, the code may be PR'd to a `na-release/` branch. All rules are the
|
- next/base -- changes to the %base desk in pkg/arvo
|
||||||
same as for release branches, except that the code does not need to apply
|
- next/garden -- changes to the %garden desk
|
||||||
cleanly to an existing ship. If you later write state adapter or otherwise make
|
- next/landscape -- changes to the %landscape desk
|
||||||
it OTAable, then you may PR it to a release branch.
|
- next/bitcoin -- changes to the %bitcoin desk
|
||||||
|
- next/webterm -- changes to the %webterm desk
|
||||||
|
- next/vere -- changes to the runtime
|
||||||
|
|
||||||
### Other cases
|
### Other cases
|
||||||
|
|
||||||
@ -163,9 +161,10 @@ If you're making a Vere release, just play it safe and update all the pills.
|
|||||||
|
|
||||||
For an Urbit OS release, after all the merge commits, make a release with the
|
For an Urbit OS release, after all the merge commits, make a release with the
|
||||||
commit message "release: urbit-os-v1.0.xx". This commit should have up-to-date
|
commit message "release: urbit-os-v1.0.xx". This commit should have up-to-date
|
||||||
artifacts from pkg/interface and a new solid pill. If neither the pill nor the
|
artifacts from pkg/interface and a new version number in the desk.docket-0 of
|
||||||
JS need to be updated (e.g if the pill was already updated in the previous merge
|
any desk which changed. If neither the pill nor the JS need to be updated (e.g
|
||||||
commit), consider making the release commit with --allow-empty.
|
if the pill was already updated in the previous merge commit), consider making
|
||||||
|
the release commit with --allow-empty.
|
||||||
|
|
||||||
If anything in `pkg/interface` has changed, ensure it has been built and
|
If anything in `pkg/interface` has changed, ensure it has been built and
|
||||||
deployed properly. You'll want to do this before making a pill, since you want
|
deployed properly. You'll want to do this before making a pill, since you want
|
||||||
@ -191,21 +190,23 @@ What you should do here depends on the type of release being made.
|
|||||||
|
|
||||||
First, for Urbit OS releases:
|
First, for Urbit OS releases:
|
||||||
|
|
||||||
If it's a very trivial hotfix that you know isn't going to break
|
If it's a very trivial hotfix that you know isn't going to break anything, tag
|
||||||
anything, tag it as `urbit-os-vx.y.z`. Here 'x' refers to the product version
|
it as `urbit-os-vx.y`. Here 'x' is the major version and 'y' is an OTA patch
|
||||||
(e.g. OS1, OS2..), 'y' to the continuity era in that version, and 'z' to an
|
counter. Change `urbit-os` to e.g. `landscape` or another desk if that's what you're
|
||||||
OTA patch counter. So for a hotfix version, you'll just want to increment 'z'.
|
releasing. If you're releasing changes to more than one desk, add a separate
|
||||||
|
tag for each desk (but only make one announcment email/post, with all of the
|
||||||
|
desks listed).
|
||||||
|
|
||||||
Use an annotated tag, i.e.
|
Use an annotated tag, i.e.
|
||||||
|
|
||||||
```
|
```
|
||||||
git tag -a urbit-os-vx.y.z
|
git tag -a urbit-os-vx.y
|
||||||
```
|
```
|
||||||
|
|
||||||
The tag format should look something like this:
|
The tag format should look something like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
urbit-os-vx.y.z
|
urbit-os-vx.y
|
||||||
|
|
||||||
This release will be pushed to the network as an over-the-air update.
|
This release will be pushed to the network as an over-the-air update.
|
||||||
|
|
||||||
@ -236,17 +237,17 @@ If the commit descriptions are too poor to easily do this, then again, yell at
|
|||||||
your fellow contributors to make them better in the future.
|
your fellow contributors to make them better in the future.
|
||||||
|
|
||||||
If it's *not* a trivial hotfix, you should probably make any number of release
|
If it's *not* a trivial hotfix, you should probably make any number of release
|
||||||
candidate tags (e.g. `urbit-os-vx.y.z.rc1`, `urbit-os-vx.y.z.rc2`, ..), test
|
candidate tags (e.g. `urbit-os-vx.y.rc1`, `urbit-os-vx.y.rc2`, ..), test
|
||||||
them, and after you confirm one of them is good, tag the release as
|
them, and after you confirm one of them is good, tag the release as
|
||||||
`urbit-os-vx.y.z`.
|
`urbit-os-vx.y`.
|
||||||
|
|
||||||
For Vere releases:
|
For Vere releases:
|
||||||
|
|
||||||
Tag the release as `urbit-vx.y.z`. The tag format should look something like
|
Tag the release as `urbit-vx.y`. The tag format should look something like
|
||||||
this:
|
this:
|
||||||
|
|
||||||
```
|
```
|
||||||
urbit-vx.y.z
|
urbit-vx.y
|
||||||
|
|
||||||
Note that this Vere release will by default boot fresh ships using an Urbit OS
|
Note that this Vere release will by default boot fresh ships using an Urbit OS
|
||||||
va.b.c pill.
|
va.b.c pill.
|
||||||
@ -254,10 +255,10 @@ va.b.c pill.
|
|||||||
Release binaries:
|
Release binaries:
|
||||||
|
|
||||||
(linux64)
|
(linux64)
|
||||||
https://bootstrap.urbit.org/urbit-vx.y.z-linux64.tgz
|
https://bootstrap.urbit.org/urbit-vx.y-linux64.tgz
|
||||||
|
|
||||||
(macOS)
|
(macOS)
|
||||||
https://bootstrap.urbit.org/urbit-vx.y.z-darwin.tgz
|
https://bootstrap.urbit.org/urbit-vx.y-darwin.tgz
|
||||||
|
|
||||||
Release notes:
|
Release notes:
|
||||||
|
|
||||||
@ -295,10 +296,10 @@ and stars to the rest of the network.
|
|||||||
For consistency, I create a release tarball and then rsync the files in.
|
For consistency, I create a release tarball and then rsync the files in.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ wget https://github.com/urbit/urbit/archive/urbit-os-vx.y.z.tar.gz
|
$ wget https://github.com/urbit/urbit/archive/urbit-os-vx.y.tar.gz
|
||||||
$ tar xzf urbit-os-vx.y.z.tar.gz
|
$ tar xzf urbit-os-vx.y.tar.gz
|
||||||
$ herb zod -p hood -d "+hood/mount /=home="
|
$ herb zod -p hood -d "+hood/mount /=home="
|
||||||
$ rsync -zr --delete urbit-urbit-os-vx.y.z/pkg/arvo/ zod/home
|
$ rsync -zr --delete urbit-urbit-os-vx.y/pkg/arvo/ zod/home
|
||||||
$ herb zod -p hood -d "+hood/commit %home"
|
$ herb zod -p hood -d "+hood/commit %home"
|
||||||
$ herb zod -p hood -d "+hood/merge %kids our %home"
|
$ herb zod -p hood -d "+hood/merge %kids our %home"
|
||||||
```
|
```
|
||||||
@ -306,16 +307,11 @@ $ herb zod -p hood -d "+hood/merge %kids our %home"
|
|||||||
For Vere updates, this means simply shutting down each desired ship, installing
|
For Vere updates, this means simply shutting down each desired ship, installing
|
||||||
the new binary, and restarting the pier with it.
|
the new binary, and restarting the pier with it.
|
||||||
|
|
||||||
#### Continuous deployment
|
|
||||||
|
|
||||||
A subset of release branches are deployed continuously to the network. Thus far
|
|
||||||
this only includes `release/next-userspace`, which deploys livenet-compatible
|
|
||||||
changes to select QA ships. Any push to master will automatically
|
|
||||||
merge master into `release/next-userspace` to keep the streams at parity.
|
|
||||||
|
|
||||||
### Announce the update
|
### Announce the update
|
||||||
|
|
||||||
Post an announcement to urbit-dev. The tag annotation, basically, is fine here
|
Post an announcement to urbit-dev. The tag annotation, basically, is fine here
|
||||||
-- I usually add the %base hash (for Urbit OS releases) and the release binary
|
-- I usually add the %cz hash (for Urbit OS releases) and the release binary
|
||||||
URLs (for Vere releases). Check the urbit-dev archives for examples of these
|
URLs (for Vere releases). Check the urbit-dev archives for examples of these
|
||||||
announcements.
|
announcements.
|
||||||
|
|
||||||
|
Post the same announcement to the group feed of Urbit Community.
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6f48518fe49584a6532a20018f4ac4eae3817b25d85d60536a99643eb5d65b2b
|
oid sha256:e660fba934c5b80eeda64037a1f28c71eff4b2ea0bd28809b91432ca3d5ef08a
|
||||||
size 22872573
|
size 23052691
|
||||||
|
31
default.nix
31
default.nix
@ -14,10 +14,6 @@
|
|||||||
$ nix-build -A urbit --argstr crossSystem x86_64-unknown-linux-musl \
|
$ nix-build -A urbit --argstr crossSystem x86_64-unknown-linux-musl \
|
||||||
--arg enableStatic true
|
--arg enableStatic true
|
||||||
|
|
||||||
Static urbit-king binary:
|
|
||||||
|
|
||||||
$ nix-build -A hs.urbit-king.components.exes.urbit-king --arg enableStatic true
|
|
||||||
|
|
||||||
Static release tarball:
|
Static release tarball:
|
||||||
|
|
||||||
$ nix-build -A tarball --arg enableStatic true
|
$ nix-build -A tarball --arg enableStatic true
|
||||||
@ -28,15 +24,6 @@
|
|||||||
$ nix-build -A brass.build
|
$ nix-build -A brass.build
|
||||||
$ nix-build -A solid.build
|
$ nix-build -A solid.build
|
||||||
|
|
||||||
Run the king-haskell checks (.tests are _build_ the test code, .checks _runs_):
|
|
||||||
|
|
||||||
$ nix-build -A hs.urbit-king.checks.urbit-king-tests
|
|
||||||
|
|
||||||
Build a specific Haskell package from ./pkg/hs:
|
|
||||||
|
|
||||||
$ nix-build -A hs.urbit-noun.components.library
|
|
||||||
$ nix-build -A hs.urbit-atom.components.benchmarks.urbit-atom-bench
|
|
||||||
$ nix-build -A hs.urbit-atom.components.tests.urbit-atom-tests
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
# The build system where packages will be _built_.
|
# The build system where packages will be _built_.
|
||||||
@ -52,7 +39,7 @@
|
|||||||
# Overlays to apply to the last package set in cross compilation.
|
# Overlays to apply to the last package set in cross compilation.
|
||||||
, crossOverlays ? [ ]
|
, crossOverlays ? [ ]
|
||||||
# Whether to use pkgs.pkgsStatic.* to obtain statically linked package
|
# Whether to use pkgs.pkgsStatic.* to obtain statically linked package
|
||||||
# dependencies - ie. when building fully-static libraries or executables.
|
# dependencies - ie. when building fully-static libraries or executables.
|
||||||
, enableStatic ? false }:
|
, enableStatic ? false }:
|
||||||
|
|
||||||
let
|
let
|
||||||
@ -76,7 +63,7 @@ let
|
|||||||
|
|
||||||
# Enrich the global package set with our local functions and packages.
|
# Enrich the global package set with our local functions and packages.
|
||||||
# Cross vs static build dependencies can be selectively overridden for
|
# Cross vs static build dependencies can be selectively overridden for
|
||||||
# inputs like python and haskell-nix
|
# inputs like python etc.
|
||||||
callPackage =
|
callPackage =
|
||||||
pkgsNative.lib.callPackageWith (pkgsStatic // libLocal // pkgsLocal);
|
pkgsNative.lib.callPackageWith (pkgsStatic // libLocal // pkgsLocal);
|
||||||
|
|
||||||
@ -113,22 +100,12 @@ let
|
|||||||
urcrypt = callPackage ./nix/pkgs/urcrypt { inherit enableStatic; };
|
urcrypt = callPackage ./nix/pkgs/urcrypt { inherit enableStatic; };
|
||||||
|
|
||||||
docker-image = callPackage ./nix/pkgs/docker-image { };
|
docker-image = callPackage ./nix/pkgs/docker-image { };
|
||||||
|
|
||||||
hs = callPackage ./nix/pkgs/hs {
|
|
||||||
inherit enableStatic;
|
|
||||||
inherit (pkgsCross) haskell-nix;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Additional top-level packages and attributes exposed for convenience.
|
# Additional top-level packages and attributes exposed for convenience.
|
||||||
pkgsExtra = with pkgsLocal; rec {
|
pkgsExtra = with pkgsLocal; rec {
|
||||||
# Expose packages with local customisations (like patches) for dev access.
|
# Expose packages with local customisations (like patches) for dev access.
|
||||||
inherit (pkgsCross) libsigsegv;
|
inherit (pkgsStatic) libsigsegv lmdb;
|
||||||
|
|
||||||
# Collect haskell check (aka "run the tests") attributes so we can run every
|
|
||||||
# test for our local haskell packages, similar to the urbit-tests attribute.
|
|
||||||
hs-checks = (pkgsNative.recurseIntoAttrs
|
|
||||||
(libLocal.collectHaskellComponents pkgsLocal.hs)).checks;
|
|
||||||
|
|
||||||
urbit-debug = urbit.override { enableDebug = true; };
|
urbit-debug = urbit.override { enableDebug = true; };
|
||||||
urbit-tests = libLocal.testFakeShip {
|
urbit-tests = libLocal.testFakeShip {
|
||||||
@ -145,14 +122,12 @@ let
|
|||||||
# Create a .tgz of the primary binaries.
|
# Create a .tgz of the primary binaries.
|
||||||
tarball = let
|
tarball = let
|
||||||
name = "urbit-v${urbit.version}-${urbit.system}";
|
name = "urbit-v${urbit.version}-${urbit.system}";
|
||||||
urbit-king = hs.urbit-king.components.exes.urbit-king;
|
|
||||||
in libLocal.makeReleaseTarball {
|
in libLocal.makeReleaseTarball {
|
||||||
inherit name;
|
inherit name;
|
||||||
|
|
||||||
contents = {
|
contents = {
|
||||||
"${name}/urbit" = "${urbit}/bin/urbit";
|
"${name}/urbit" = "${urbit}/bin/urbit";
|
||||||
"${name}/urbit-worker" = "${urbit}/bin/urbit-worker";
|
"${name}/urbit-worker" = "${urbit}/bin/urbit-worker";
|
||||||
"${name}/urbit-king" = "${urbit-king}/bin/urbit-king";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,42 +13,19 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
sourcesFinal = import ./sources.nix { inherit pkgs; } // sources;
|
finalSources = import ./sources.nix { } // sources;
|
||||||
|
|
||||||
haskellNix = import sourcesFinal."haskell.nix" {
|
pkgs = import finalSources.nixpkgs {
|
||||||
sourcesOverride = {
|
inherit system config crossSystem crossOverlays;
|
||||||
hackage = sourcesFinal."hackage.nix";
|
|
||||||
stackage = sourcesFinal."stackage.nix";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
configFinal = haskellNix.config // config;
|
overlays = [
|
||||||
|
# Make prev.sources available to subsequent overlays.
|
||||||
overlaysFinal = haskellNix.overlays ++ [
|
(_final: _prev: { sources = finalSources; })
|
||||||
(_final: prev: {
|
# General unguarded (native) overrides for nixpkgs.
|
||||||
# Add top-level .sources attribute for other overlays to access sources.
|
(import ./overlays/native.nix)
|
||||||
sources = sourcesFinal;
|
# Specific overrides guarded by the host platform.
|
||||||
|
(import ./overlays/musl.nix)
|
||||||
# Additional non-convential package/exe mappings for shellFor.tools.
|
];
|
||||||
haskell-nix = prev.haskell-nix // {
|
|
||||||
toolPackageName = prev.haskell-nix.toolPackageName // {
|
|
||||||
shellcheck = "ShellCheck";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
# General unguarded (native) overrides for nixpkgs.
|
|
||||||
(import ./overlays/native.nix)
|
|
||||||
|
|
||||||
# Specific overrides guarded by the host platform.
|
|
||||||
(import ./overlays/musl.nix)
|
|
||||||
] ++ overlays;
|
|
||||||
|
|
||||||
pkgs = import sourcesFinal.nixpkgs {
|
|
||||||
inherit system crossSystem crossOverlays;
|
|
||||||
|
|
||||||
config = configFinal;
|
|
||||||
overlays = overlaysFinal;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
in pkgs // {
|
in pkgs // {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Functions that are expected run on the native (non-cross) system.
|
# Functions that are expected run on the native (non-cross) system.
|
||||||
|
|
||||||
{ lib, recurseIntoAttrs, haskell-nix, callPackage }:
|
{ callPackage }:
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
bootFakeShip = callPackage ./boot-fake-ship.nix { };
|
bootFakeShip = callPackage ./boot-fake-ship.nix { };
|
||||||
@ -10,28 +10,4 @@ rec {
|
|||||||
fetchGitHubLFS = callPackage ./fetch-github-lfs.nix { };
|
fetchGitHubLFS = callPackage ./fetch-github-lfs.nix { };
|
||||||
|
|
||||||
makeReleaseTarball = callPackage ./make-release-tarball.nix { };
|
makeReleaseTarball = callPackage ./make-release-tarball.nix { };
|
||||||
|
|
||||||
collectHaskellComponents = project:
|
|
||||||
let
|
|
||||||
|
|
||||||
# These functions pull out from the Haskell project either all the
|
|
||||||
# components of a particular type, or all the checks.
|
|
||||||
|
|
||||||
pkgs = haskell-nix.haskellLib.selectProjectPackages project;
|
|
||||||
|
|
||||||
collectChecks = _:
|
|
||||||
recurseIntoAttrs (builtins.mapAttrs (_: p: p.checks) pkgs);
|
|
||||||
|
|
||||||
collectComponents = type:
|
|
||||||
haskell-nix.haskellLib.collectComponents' type pkgs;
|
|
||||||
|
|
||||||
# Recompute the Haskell package set sliced by component type
|
|
||||||
in builtins.mapAttrs (type: f: f type) {
|
|
||||||
# These names must match the subcomponent: components.<name>.<...>
|
|
||||||
"library" = collectComponents;
|
|
||||||
"tests" = collectComponents;
|
|
||||||
"benchmarks" = collectComponents;
|
|
||||||
"exes" = collectComponents;
|
|
||||||
"checks" = collectChecks;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ let
|
|||||||
"''${curl[@]}" -s --output "$out" "$href"
|
"''${curl[@]}" -s --output "$out" "$href"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
impureEnvVars = stdenvNoCC.lib.fetchers.proxyImpureEnvVars;
|
impureEnvVars = lib.fetchers.proxyImpureEnvVars;
|
||||||
|
|
||||||
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
|
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
|
||||||
|
@ -23,6 +23,4 @@ in prev.lib.optionalAttrs isMusl {
|
|||||||
rhash = overrideStdenv prev.rhash;
|
rhash = overrideStdenv prev.rhash;
|
||||||
|
|
||||||
numactl = overrideStdenv prev.numactl;
|
numactl = overrideStdenv prev.numactl;
|
||||||
|
|
||||||
lmdb = overrideStdenv prev.lmdb;
|
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,7 @@ in {
|
|||||||
version = final.sources.h2o.rev;
|
version = final.sources.h2o.rev;
|
||||||
src = final.sources.h2o;
|
src = final.sources.h2o;
|
||||||
outputs = [ "out" "dev" "lib" ];
|
outputs = [ "out" "dev" "lib" ];
|
||||||
});
|
meta.platforms = prev.lib.platforms.linux ++ prev.lib.platforms.darwin;
|
||||||
|
|
||||||
secp256k1 = prev.secp256k1.overrideAttrs (_attrs: {
|
|
||||||
version = final.sources.secp256k1.rev;
|
|
||||||
src = final.sources.secp256k1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
libsigsegv = prev.libsigsegv.overrideAttrs (attrs: {
|
libsigsegv = prev.libsigsegv.overrideAttrs (attrs: {
|
||||||
@ -23,7 +19,7 @@ in {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
curlMinimal = prev.curl.override {
|
curlUrbit = prev.curlMinimal.override {
|
||||||
http2Support = false;
|
http2Support = false;
|
||||||
scpSupport = false;
|
scpSupport = false;
|
||||||
gssSupport = false;
|
gssSupport = false;
|
||||||
|
@ -16,7 +16,7 @@ let
|
|||||||
in {
|
in {
|
||||||
gmp = enableStatic prev.gmp;
|
gmp = enableStatic prev.gmp;
|
||||||
|
|
||||||
curlMinimal = enableStatic prev.curlMinimal;
|
curlUrbit = enableStatic prev.curlUrbit;
|
||||||
|
|
||||||
libuv = enableStatic prev.libuv;
|
libuv = enableStatic prev.libuv;
|
||||||
|
|
||||||
@ -26,12 +26,8 @@ in {
|
|||||||
|
|
||||||
lmdb = prev.lmdb.overrideAttrs (old:
|
lmdb = prev.lmdb.overrideAttrs (old:
|
||||||
configureFlags old // {
|
configureFlags old // {
|
||||||
# Why remove the so version? It's easier than preventing it from being
|
postPatch = ''
|
||||||
# built with lmdb's custom Makefiles, and it can't exist in the output
|
sed '/^ILIBS\t/s/liblmdb\$(SOEXT)//' -i Makefile
|
||||||
# because otherwise the linker will preferentially choose the .so over
|
|
||||||
# the .a.
|
|
||||||
postInstall = ''
|
|
||||||
rm $out/lib/liblmdb.so
|
|
||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ stdenvNoCC.mkDerivation {
|
|||||||
phases = [ "installPhase" "fixupPhase" ];
|
phases = [ "installPhase" "fixupPhase" ];
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp $src $out/bin/herb
|
cp $src $out/bin/herb
|
||||||
chmod +x $out/bin/herb
|
chmod +x $out/bin/herb
|
||||||
'';
|
'';
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
{ lib, stdenv, darwin, haskell-nix, lmdb, gmp, zlib, libffi, brass
|
|
||||||
, enableStatic ? stdenv.hostPlatform.isStatic }:
|
|
||||||
|
|
||||||
haskell-nix.stackProject {
|
|
||||||
compiler-nix-name = "ghc884";
|
|
||||||
index-state = "2020-09-24T00:00:00Z";
|
|
||||||
|
|
||||||
# This is incredibly difficult to get right, almost everything goes wrong.
|
|
||||||
# See: https://github.com/input-output-hk/haskell.nix/issues/496
|
|
||||||
src = haskell-nix.haskellLib.cleanSourceWith {
|
|
||||||
# Otherwise this depends on the name in the parent directory, which
|
|
||||||
# reduces caching, and is particularly bad on Hercules.
|
|
||||||
# See: https://github.com/hercules-ci/support/issues/40
|
|
||||||
name = "urbit-hs";
|
|
||||||
src = ../../../pkg/hs;
|
|
||||||
};
|
|
||||||
|
|
||||||
modules = [{
|
|
||||||
# This corresponds to the set of packages (boot libs) that ship with GHC.
|
|
||||||
# We declare them yere to ensure any dependency gets them from GHC itself
|
|
||||||
# rather than trying to re-install them into the package database.
|
|
||||||
nonReinstallablePkgs = [
|
|
||||||
"Cabal"
|
|
||||||
"Win32"
|
|
||||||
"array"
|
|
||||||
"base"
|
|
||||||
"binary"
|
|
||||||
"bytestring"
|
|
||||||
"containers"
|
|
||||||
"deepseq"
|
|
||||||
"directory"
|
|
||||||
"filepath"
|
|
||||||
"ghc"
|
|
||||||
"ghc-boot"
|
|
||||||
"ghc-boot-th"
|
|
||||||
"ghc-compact"
|
|
||||||
"ghc-heap"
|
|
||||||
"ghc-prim"
|
|
||||||
"ghci"
|
|
||||||
"ghcjs-prim"
|
|
||||||
"ghcjs-th"
|
|
||||||
"haskeline"
|
|
||||||
"hpc"
|
|
||||||
"integer-gmp"
|
|
||||||
"integer-simple"
|
|
||||||
"mtl"
|
|
||||||
"parsec"
|
|
||||||
"pretty"
|
|
||||||
"process"
|
|
||||||
"rts"
|
|
||||||
"stm"
|
|
||||||
"template-haskell"
|
|
||||||
"terminfo"
|
|
||||||
"text"
|
|
||||||
"time"
|
|
||||||
"transformers"
|
|
||||||
"unix"
|
|
||||||
"xhtml"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Override various project-local flags and build configuration.
|
|
||||||
packages = {
|
|
||||||
urbit-king.components.exes.urbit-king = {
|
|
||||||
enableStatic = enableStatic;
|
|
||||||
enableShared = !enableStatic;
|
|
||||||
|
|
||||||
configureFlags = lib.optionals enableStatic [
|
|
||||||
"--ghc-option=-optl=-L${lmdb}/lib"
|
|
||||||
"--ghc-option=-optl=-L${gmp}/lib"
|
|
||||||
"--ghc-option=-optl=-L${libffi}/lib"
|
|
||||||
"--ghc-option=-optl=-L${zlib}/lib"
|
|
||||||
] ++ lib.optionals (enableStatic && stdenv.isDarwin)
|
|
||||||
[ "--ghc-option=-optl=-L${darwin.libiconv}/lib" ];
|
|
||||||
|
|
||||||
postInstall = lib.optionalString (enableStatic && stdenv.isDarwin) ''
|
|
||||||
find "$out/bin" -type f -exec \
|
|
||||||
install_name_tool -change \
|
|
||||||
${stdenv.cc.libc}/lib/libSystem.B.dylib \
|
|
||||||
/usr/lib/libSystem.B.dylib {} \;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
urbit-king.components.tests.urbit-king-tests.testFlags =
|
|
||||||
[ "--brass-pill=${brass.lfs}" ];
|
|
||||||
|
|
||||||
lmdb.components.library.libs = lib.mkForce [ lmdb ];
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
{ lib, stdenv, coreutils, pkgconfig # build/env
|
{ lib, stdenv, coreutils, pkgconfig # build/env
|
||||||
, cacert, ca-bundle, ivory # codegen
|
, cacert, ca-bundle, ivory # codegen
|
||||||
, curlMinimal, ent, gmp, h2o, libsigsegv, libuv, lmdb # libs
|
, curlUrbit, ent, gmp, h2o, libsigsegv, libuv, lmdb # libs
|
||||||
, murmur3, openssl, softfloat3, urcrypt, zlib #
|
, murmur3, openssl, softfloat3, urcrypt, zlib #
|
||||||
, enableStatic ? stdenv.hostPlatform.isStatic # opts
|
, enableStatic ? stdenv.hostPlatform.isStatic # opts
|
||||||
, enableDebug ? false
|
, enableDebug ? false
|
||||||
@ -25,7 +25,7 @@ in stdenv.mkDerivation {
|
|||||||
buildInputs = [
|
buildInputs = [
|
||||||
cacert
|
cacert
|
||||||
ca-bundle
|
ca-bundle
|
||||||
curlMinimal
|
curlUrbit
|
||||||
ent
|
ent
|
||||||
gmp
|
gmp
|
||||||
h2o
|
h2o
|
||||||
|
@ -31,6 +31,24 @@
|
|||||||
"url": "https://github.com/LMDB/lmdb/archive/48a7fed59a8aae623deff415dda27097198ca0c1.tar.gz",
|
"url": "https://github.com/LMDB/lmdb/archive/48a7fed59a8aae623deff415dda27097198ca0c1.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
},
|
||||||
|
"secp256k1": {
|
||||||
|
"branch": "master",
|
||||||
|
"description": "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1.",
|
||||||
|
"homepage": null,
|
||||||
|
"owner": "bitcoin-core",
|
||||||
|
"pmnsh": {
|
||||||
|
"include": "include",
|
||||||
|
"lib": ".libs",
|
||||||
|
"make": "libsecp256k1.la",
|
||||||
|
"prepare": "./autogen.sh && ./configure --disable-shared --enable-benchmark=no --enable-exhaustive-tests=no --enable-experimental --enable-module-ecdh --enable-module-recovery --enable-module-schnorrsig --enable-tests=yes CFLAGS=-DSECP256K1_API="
|
||||||
|
},
|
||||||
|
"repo": "secp256k1",
|
||||||
|
"rev": "7973576f6e3ab27d036a09397152b124d747f4ae",
|
||||||
|
"sha256": "0vjk55dv0mkph4k6bqgkykmxn05ngzvhc4rzjnvn33xzi8dzlvah",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/bitcoin-core/secp256k1/archive/7973576f6e3ab27d036a09397152b124d747f4ae.tar.gz",
|
||||||
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
|
},
|
||||||
"uv": {
|
"uv": {
|
||||||
"branch": "v1.x",
|
"branch": "v1.x",
|
||||||
"description": "Cross-platform asynchronous I/O",
|
"description": "Cross-platform asynchronous I/O",
|
||||||
|
104
nix/sources.json
104
nix/sources.json
@ -3,17 +3,17 @@
|
|||||||
"branch": "master",
|
"branch": "master",
|
||||||
"description": "H2O - the optimized HTTP/1, HTTP/2, HTTP/3 server",
|
"description": "H2O - the optimized HTTP/1, HTTP/2, HTTP/3 server",
|
||||||
"homepage": "https://h2o.examp1e.net",
|
"homepage": "https://h2o.examp1e.net",
|
||||||
"pmnsh": {
|
|
||||||
"include": "include",
|
|
||||||
"prepare": "cmake .",
|
|
||||||
"make": "libh2o",
|
|
||||||
"compat": {
|
|
||||||
"mingw": {
|
|
||||||
"prepare": "cmake -G\"MSYS Makefiles\" -DCMAKE_INSTALL_PREFIX=. ."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"owner": "h2o",
|
"owner": "h2o",
|
||||||
|
"pmnsh": {
|
||||||
|
"compat": {
|
||||||
|
"mingw": {
|
||||||
|
"prepare": "cmake -G\"MSYS Makefiles\" -DCMAKE_INSTALL_PREFIX=. ."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": "include",
|
||||||
|
"make": "libh2o",
|
||||||
|
"prepare": "cmake ."
|
||||||
|
},
|
||||||
"repo": "h2o",
|
"repo": "h2o",
|
||||||
"rev": "v2.2.6",
|
"rev": "v2.2.6",
|
||||||
"sha256": "0qni676wqvxx0sl0pw9j0ph7zf2krrzqc1zwj73mgpdnsr8rsib7",
|
"sha256": "0qni676wqvxx0sl0pw9j0ph7zf2krrzqc1zwj73mgpdnsr8rsib7",
|
||||||
@ -21,47 +21,23 @@
|
|||||||
"url": "https://github.com/h2o/h2o/archive/v2.2.6.tar.gz",
|
"url": "https://github.com/h2o/h2o/archive/v2.2.6.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
},
|
||||||
"hackage.nix": {
|
|
||||||
"branch": "master",
|
|
||||||
"description": "Automatically generated Nix expressions for Hackage",
|
|
||||||
"homepage": "",
|
|
||||||
"owner": "input-output-hk",
|
|
||||||
"repo": "hackage.nix",
|
|
||||||
"rev": "ed4d2759c9e6ca8133a4170f99fabdd76f30f51a",
|
|
||||||
"sha256": "1n5fk8zsxnbca96zk4ikh74iz3lzh35m302q65zk1rx3nmy4027d",
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/input-output-hk/hackage.nix/archive/ed4d2759c9e6ca8133a4170f99fabdd76f30f51a.tar.gz",
|
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
|
||||||
},
|
|
||||||
"haskell.nix": {
|
|
||||||
"branch": "master",
|
|
||||||
"description": "Alternative Haskell Infrastructure for Nixpkgs",
|
|
||||||
"homepage": "https://input-output-hk.github.io/haskell.nix",
|
|
||||||
"owner": "input-output-hk",
|
|
||||||
"repo": "haskell.nix",
|
|
||||||
"rev": "bbb34dcdf7b90d478002f91713531f418ddf1b53",
|
|
||||||
"sha256": "1qq397j8vnlp5npk8r675fzjfimg74fcvrkxcdgx7vj48315bh2w",
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/input-output-hk/haskell.nix/archive/bbb34dcdf7b90d478002f91713531f418ddf1b53.tar.gz",
|
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
|
||||||
},
|
|
||||||
"libaes_siv": {
|
"libaes_siv": {
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"description": null,
|
"description": null,
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
|
"owner": "dfoxfranke",
|
||||||
"pmnsh": {
|
"pmnsh": {
|
||||||
"compat": {
|
"compat": {
|
||||||
"m1brew": {
|
"m1brew": {
|
||||||
"prepare": "cmake .",
|
"make": "install CFLAGS=$(pkg-config --cflags openssl)",
|
||||||
"make": "install CFLAGS=$(pkg-config --cflags openssl)"
|
"prepare": "cmake ."
|
||||||
},
|
},
|
||||||
"mingw": {
|
"mingw": {
|
||||||
"prepare": "cmake -G\"MSYS Makefiles\" -DDISABLE_DOCS:BOOL=ON .",
|
"make": "aes_siv_static",
|
||||||
"make": "aes_siv_static"
|
"prepare": "cmake -G\"MSYS Makefiles\" -DDISABLE_DOCS:BOOL=ON ."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"owner":"dfoxfranke",
|
|
||||||
"repo": "libaes_siv",
|
"repo": "libaes_siv",
|
||||||
"rev": "9681279cfaa6e6399bb7ca3afbbc27fc2e19df4b",
|
"rev": "9681279cfaa6e6399bb7ca3afbbc27fc2e19df4b",
|
||||||
"sha256": "1g4wy0m5wpqx7z6nillppkh5zki9fkx9rdw149qcxh7mc5vlszzi",
|
"sha256": "1g4wy0m5wpqx7z6nillppkh5zki9fkx9rdw149qcxh7mc5vlszzi",
|
||||||
@ -73,10 +49,10 @@
|
|||||||
"branch": "master",
|
"branch": "master",
|
||||||
"description": null,
|
"description": null,
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
|
"owner": "urbit",
|
||||||
"pmnsh": {
|
"pmnsh": {
|
||||||
"make": "static"
|
"make": "static"
|
||||||
},
|
},
|
||||||
"owner": "urbit",
|
|
||||||
"repo": "murmur3",
|
"repo": "murmur3",
|
||||||
"rev": "71a75d57ca4e7ca0f7fc2fd84abd93595b0624ca",
|
"rev": "71a75d57ca4e7ca0f7fc2fd84abd93595b0624ca",
|
||||||
"sha256": "0k7jq2nb4ad9ajkr6wc4w2yy2f2hkwm3nkbj2pklqgwsg6flxzwg",
|
"sha256": "0k7jq2nb4ad9ajkr6wc4w2yy2f2hkwm3nkbj2pklqgwsg6flxzwg",
|
||||||
@ -97,23 +73,23 @@
|
|||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"branch": "master",
|
"branch": "nixos-21.11",
|
||||||
"description": "Nix Packages collection",
|
"description": "Nix Packages collection",
|
||||||
"homepage": null,
|
"homepage": "",
|
||||||
"owner": "nixos",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "166ab9d237409c4b74b1f8ca31476ead35e8fe53",
|
"rev": "573095944e7c1d58d30fc679c81af63668b54056",
|
||||||
"sha256": "13i43kvbkdl3dh8b986j6mxbn355mqjhcxrd8cni8zfx1z0wrscr",
|
"sha256": "07s5cwhskqvy82b4rld9b14ljc0013pig23i3jx3l3f957rk95pg",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/nixos/nixpkgs/archive/166ab9d237409c4b74b1f8ca31476ead35e8fe53.tar.gz",
|
"url": "https://github.com/NixOS/nixpkgs/archive/573095944e7c1d58d30fc679c81af63668b54056.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
},
|
||||||
"softfloat3": {
|
"softfloat3": {
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"description": null,
|
"description": null,
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
|
"owner": "urbit",
|
||||||
"pmnsh": {
|
"pmnsh": {
|
||||||
"include": "source/include",
|
|
||||||
"compat": {
|
"compat": {
|
||||||
"m1brew": {
|
"m1brew": {
|
||||||
"lib": "build/template-FAST_INT64",
|
"lib": "build/template-FAST_INT64",
|
||||||
@ -123,44 +99,14 @@
|
|||||||
"lib": "build/Win64-MinGW-w64",
|
"lib": "build/Win64-MinGW-w64",
|
||||||
"make": "-C build/Win64-MinGW-w64 libsoftfloat3.a"
|
"make": "-C build/Win64-MinGW-w64 libsoftfloat3.a"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"include": "source/include"
|
||||||
},
|
},
|
||||||
"owner": "urbit",
|
|
||||||
"repo": "berkeley-softfloat-3",
|
"repo": "berkeley-softfloat-3",
|
||||||
"rev": "ec4c7e31b32e07aad80e52f65ff46ac6d6aad986",
|
"rev": "ec4c7e31b32e07aad80e52f65ff46ac6d6aad986",
|
||||||
"sha256": "1lz4bazbf7lns1xh8aam19c814a4n4czq5xsq5rmi9sgqw910339",
|
"sha256": "1lz4bazbf7lns1xh8aam19c814a4n4czq5xsq5rmi9sgqw910339",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/urbit/berkeley-softfloat-3/archive/ec4c7e31b32e07aad80e52f65ff46ac6d6aad986.tar.gz",
|
"url": "https://github.com/urbit/berkeley-softfloat-3/archive/ec4c7e31b32e07aad80e52f65ff46ac6d6aad986.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
|
||||||
"secp256k1": {
|
|
||||||
"branch": "master",
|
|
||||||
"description": "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1.",
|
|
||||||
"homepage": null,
|
|
||||||
"pmnsh": {
|
|
||||||
"include": "include",
|
|
||||||
"lib": ".libs",
|
|
||||||
"prepare": "./autogen.sh && ./configure --disable-shared --enable-module-recovery CFLAGS=-DSECP256K1_API=",
|
|
||||||
"make": "libsecp256k1.la"
|
|
||||||
},
|
|
||||||
"owner": "bitcoin-core",
|
|
||||||
"repo": "secp256k1",
|
|
||||||
"rev": "26de4dfeb1f1436dae1fcf17f57bdaa43540f940",
|
|
||||||
"sha256": "03i3nv8d3ci7q9y98q11rrp3rvwdqc0hc0ss0pr6xckybvizsmbb",
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/bitcoin-core/secp256k1/archive/26de4dfeb1f1436dae1fcf17f57bdaa43540f940.tar.gz",
|
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
|
||||||
},
|
|
||||||
"stackage.nix": {
|
|
||||||
"branch": "master",
|
|
||||||
"description": "Automatically generated Nix expressions of Stackage snapshots",
|
|
||||||
"homepage": "",
|
|
||||||
"owner": "input-output-hk",
|
|
||||||
"repo": "stackage.nix",
|
|
||||||
"rev": "08312f475f4f5f3b6578e7a78dc501de6fea8792",
|
|
||||||
"sha256": "15j1l6616kfv7351jxwgb9kj6y8227fcm87nxwabmbn1q6a8q2kf",
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/input-output-hk/stackage.nix/archive/08312f475f4f5f3b6578e7a78dc501de6fea8792.tar.gz",
|
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
210
nix/sources.nix
210
nix/sources.nix
@ -6,149 +6,169 @@ let
|
|||||||
# The fetchers. fetch_<type> fetches specs of type <type>.
|
# The fetchers. fetch_<type> fetches specs of type <type>.
|
||||||
#
|
#
|
||||||
|
|
||||||
fetch_file = pkgs: spec:
|
fetch_file = pkgs: name: spec:
|
||||||
if spec.builtin or true then
|
let
|
||||||
builtins_fetchurl { inherit (spec) url sha256; }
|
name' = sanitizeName name + "-src";
|
||||||
else
|
in
|
||||||
pkgs.fetchurl { inherit (spec) url sha256; };
|
if spec.builtin or true then
|
||||||
|
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
|
||||||
|
else
|
||||||
|
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
|
||||||
|
|
||||||
fetch_tarball = pkgs: name: spec:
|
fetch_tarball = pkgs: name: spec:
|
||||||
let
|
let
|
||||||
ok = str: !builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str);
|
name' = sanitizeName name + "-src";
|
||||||
# sanitize the name, though nix will still fail if name starts with period
|
in
|
||||||
name' = stringAsChars (x: if !ok x then "-" else x) "${name}-src";
|
if spec.builtin or true then
|
||||||
in if spec.builtin or true then
|
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
|
||||||
builtins_fetchTarball {
|
else
|
||||||
name = name';
|
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
|
||||||
inherit (spec) url sha256;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
pkgs.fetchzip {
|
|
||||||
name = name';
|
|
||||||
inherit (spec) url sha256;
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch_git = spec:
|
fetch_git = name: spec:
|
||||||
builtins.fetchGit {
|
let
|
||||||
url = spec.repo;
|
ref =
|
||||||
inherit (spec) rev ref;
|
if spec ? ref then spec.ref else
|
||||||
};
|
if spec ? branch then "refs/heads/${spec.branch}" else
|
||||||
|
if spec ? tag then "refs/tags/${spec.tag}" else
|
||||||
|
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
|
||||||
|
in
|
||||||
|
builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
|
||||||
|
|
||||||
fetch_local = spec: spec.path;
|
fetch_local = spec: spec.path;
|
||||||
|
|
||||||
fetch_builtin-tarball = name:
|
fetch_builtin-tarball = name: throw
|
||||||
throw ''
|
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
|
||||||
[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
|
$ niv modify ${name} -a type=tarball -a builtin=true'';
|
||||||
$ niv modify ${name} -a type=tarball -a builtin=true'';
|
|
||||||
|
|
||||||
fetch_builtin-url = name:
|
fetch_builtin-url = name: throw
|
||||||
throw ''
|
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
|
||||||
[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
|
$ niv modify ${name} -a type=file -a builtin=true'';
|
||||||
$ niv modify ${name} -a type=file -a builtin=true'';
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Various helpers
|
# Various helpers
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
|
||||||
|
sanitizeName = name:
|
||||||
|
(
|
||||||
|
concatMapStrings (s: if builtins.isList s then "-" else s)
|
||||||
|
(
|
||||||
|
builtins.split "[^[:alnum:]+._?=-]+"
|
||||||
|
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
# The set of packages used when specs are fetched using non-builtins.
|
# The set of packages used when specs are fetched using non-builtins.
|
||||||
mkPkgs = sources:
|
mkPkgs = sources: system:
|
||||||
let
|
let
|
||||||
sourcesNixpkgs =
|
sourcesNixpkgs =
|
||||||
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; })
|
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
|
||||||
{ };
|
|
||||||
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
|
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
|
||||||
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
|
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
|
||||||
in if builtins.hasAttr "nixpkgs" sources then
|
in
|
||||||
sourcesNixpkgs
|
if builtins.hasAttr "nixpkgs" sources
|
||||||
else if hasNixpkgsPath && !hasThisAsNixpkgsPath then
|
then sourcesNixpkgs
|
||||||
import <nixpkgs> { }
|
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
|
||||||
else
|
import <nixpkgs> {}
|
||||||
abort ''
|
else
|
||||||
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
|
abort
|
||||||
add a package called "nixpkgs" to your sources.json.
|
''
|
||||||
'';
|
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
|
||||||
|
add a package called "nixpkgs" to your sources.json.
|
||||||
|
'';
|
||||||
|
|
||||||
# The actual fetching function.
|
# The actual fetching function.
|
||||||
fetch = pkgs: name: spec:
|
fetch = pkgs: name: spec:
|
||||||
|
|
||||||
if !builtins.hasAttr "type" spec then
|
if ! builtins.hasAttr "type" spec then
|
||||||
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
|
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
|
||||||
else if spec.type == "file" then
|
else if spec.type == "file" then fetch_file pkgs name spec
|
||||||
fetch_file pkgs spec
|
else if spec.type == "tarball" then fetch_tarball pkgs name spec
|
||||||
else if spec.type == "tarball" then
|
else if spec.type == "git" then fetch_git name spec
|
||||||
fetch_tarball pkgs name spec
|
else if spec.type == "local" then fetch_local spec
|
||||||
else if spec.type == "git" then
|
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
|
||||||
fetch_git spec
|
else if spec.type == "builtin-url" then fetch_builtin-url name
|
||||||
else if spec.type == "local" then
|
|
||||||
fetch_local spec
|
|
||||||
else if spec.type == "builtin-tarball" then
|
|
||||||
fetch_builtin-tarball name
|
|
||||||
else if spec.type == "builtin-url" then
|
|
||||||
fetch_builtin-url name
|
|
||||||
else
|
else
|
||||||
abort
|
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
|
||||||
"ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
|
|
||||||
|
# If the environment variable NIV_OVERRIDE_${name} is set, then use
|
||||||
|
# the path directly as opposed to the fetched source.
|
||||||
|
replace = name: drv:
|
||||||
|
let
|
||||||
|
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
|
||||||
|
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
|
||||||
|
in
|
||||||
|
if ersatz == "" then drv else
|
||||||
|
# this turns the string into an actual Nix path (for both absolute and
|
||||||
|
# relative paths)
|
||||||
|
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
|
||||||
|
|
||||||
# Ports of functions for older nix versions
|
# Ports of functions for older nix versions
|
||||||
|
|
||||||
# a Nix version of mapAttrs if the built-in doesn't exist
|
# a Nix version of mapAttrs if the built-in doesn't exist
|
||||||
mapAttrs = builtins.mapAttrs or (f: set:
|
mapAttrs = builtins.mapAttrs or (
|
||||||
with builtins;
|
f: set: with builtins;
|
||||||
listToAttrs (map (attr: {
|
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
|
||||||
name = attr;
|
);
|
||||||
value = f attr set.${attr};
|
|
||||||
}) (attrNames set)));
|
|
||||||
|
|
||||||
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
|
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
|
||||||
range = first: last:
|
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
|
||||||
if first > last then
|
|
||||||
[ ]
|
|
||||||
else
|
|
||||||
builtins.genList (n: first + n) (last - first + 1);
|
|
||||||
|
|
||||||
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
|
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
|
||||||
stringToCharacters = s:
|
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
|
||||||
map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
|
|
||||||
|
|
||||||
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
|
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
|
||||||
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
|
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
|
||||||
|
concatMapStrings = f: list: concatStrings (map f list);
|
||||||
concatStrings = builtins.concatStringsSep "";
|
concatStrings = builtins.concatStringsSep "";
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
|
||||||
|
optionalAttrs = cond: as: if cond then as else {};
|
||||||
|
|
||||||
# fetchTarball version that is compatible between all the versions of Nix
|
# fetchTarball version that is compatible between all the versions of Nix
|
||||||
builtins_fetchTarball = { url, name, sha256 }@attrs:
|
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
|
||||||
let inherit (builtins) lessThan nixVersion fetchTarball;
|
let
|
||||||
in if lessThan nixVersion "1.12" then
|
inherit (builtins) lessThan nixVersion fetchTarball;
|
||||||
fetchTarball { inherit name url; }
|
in
|
||||||
else
|
if lessThan nixVersion "1.12" then
|
||||||
fetchTarball attrs;
|
fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
|
||||||
|
else
|
||||||
|
fetchTarball attrs;
|
||||||
|
|
||||||
# fetchurl version that is compatible between all the versions of Nix
|
# fetchurl version that is compatible between all the versions of Nix
|
||||||
builtins_fetchurl = { url, sha256 }@attrs:
|
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
|
||||||
let inherit (builtins) lessThan nixVersion fetchurl;
|
let
|
||||||
in if lessThan nixVersion "1.12" then
|
inherit (builtins) lessThan nixVersion fetchurl;
|
||||||
fetchurl { inherit url; }
|
in
|
||||||
else
|
if lessThan nixVersion "1.12" then
|
||||||
fetchurl attrs;
|
fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
|
||||||
|
else
|
||||||
|
fetchurl attrs;
|
||||||
|
|
||||||
# Create the final "sources" from the config
|
# Create the final "sources" from the config
|
||||||
mkSources = config:
|
mkSources = config:
|
||||||
mapAttrs (name: spec:
|
mapAttrs (
|
||||||
if builtins.hasAttr "outPath" spec then
|
name: spec:
|
||||||
abort
|
if builtins.hasAttr "outPath" spec
|
||||||
"The values in sources.json should not have an 'outPath' attribute"
|
then abort
|
||||||
else
|
"The values in sources.json should not have an 'outPath' attribute"
|
||||||
spec // { outPath = fetch config.pkgs name spec; }) config.sources;
|
else
|
||||||
|
spec // { outPath = replace name (fetch config.pkgs name spec); }
|
||||||
|
) config.sources;
|
||||||
|
|
||||||
# The "config" used by the fetchers
|
# The "config" used by the fetchers
|
||||||
mkConfig = { sourcesFile ? ./sources.json
|
mkConfig =
|
||||||
, sources ? builtins.fromJSON (builtins.readFile sourcesFile)
|
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
|
||||||
, pkgs ? mkPkgs sources }: rec {
|
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
|
||||||
|
, system ? builtins.currentSystem
|
||||||
|
, pkgs ? mkPkgs sources system
|
||||||
|
}: rec {
|
||||||
# The sources, i.e. the attribute set of spec name to spec
|
# The sources, i.e. the attribute set of spec name to spec
|
||||||
inherit sources;
|
inherit sources;
|
||||||
|
|
||||||
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
|
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
};
|
};
|
||||||
in mkSources (mkConfig { }) // {
|
|
||||||
__functor = _: settings: mkSources (mkConfig settings);
|
in
|
||||||
}
|
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
|
||||||
|
@ -32,7 +32,8 @@
|
|||||||
++ on-watch
|
++ on-watch
|
||||||
|= =path
|
|= =path
|
||||||
^- (quip card:agent:gall _this)
|
^- (quip card:agent:gall _this)
|
||||||
?> ?=([%session @ ~] path)
|
?> =(our src):bowl
|
||||||
|
?> ?=([%session @ %view ~] path)
|
||||||
:_ this
|
:_ this
|
||||||
:: scry prompt and cursor position out of dill for initial response
|
:: scry prompt and cursor position out of dill for initial response
|
||||||
::
|
::
|
||||||
@ -57,12 +58,13 @@
|
|||||||
:_ this
|
:_ this
|
||||||
%+ turn p.sign-arvo
|
%+ turn p.sign-arvo
|
||||||
|= =blit:dill
|
|= =blit:dill
|
||||||
[%give %fact [%session %$ ~]~ %blit !>(blit)]
|
[%give %fact [%session %$ %view ~]~ %blit !>(blit)]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ on-poke
|
++ on-poke
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
^- (quip card:agent:gall _this)
|
^- (quip card:agent:gall _this)
|
||||||
|
?> =(our src):bowl
|
||||||
?. ?=(%belt mark)
|
?. ?=(%belt mark)
|
||||||
~| [%unexpected-mark mark]
|
~| [%unexpected-mark mark]
|
||||||
!!
|
!!
|
||||||
|
@ -1,466 +0,0 @@
|
|||||||
:: azimuth: constants and utilities
|
|
||||||
::
|
|
||||||
/+ ethereum
|
|
||||||
::
|
|
||||||
=> => [azimuth-types ethereum-types .]
|
|
||||||
|%
|
|
||||||
+$ complete-ship
|
|
||||||
$: state=point
|
|
||||||
history=(list diff-point) ::TODO maybe block/event nr? :: newest first
|
|
||||||
keys=(map life pass)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ fleet (map @p complete-ship)
|
|
||||||
::
|
|
||||||
++ eth-type
|
|
||||||
|%
|
|
||||||
++ point
|
|
||||||
:~ [%bytes-n 32] :: encryptionKey
|
|
||||||
[%bytes-n 32] :: authenticationKey
|
|
||||||
%bool :: hasSponsor
|
|
||||||
%bool :: active
|
|
||||||
%bool :: escapeRequested
|
|
||||||
%uint :: sponsor
|
|
||||||
%uint :: escapeRequestedTo
|
|
||||||
%uint :: cryptoSuiteVersion
|
|
||||||
%uint :: keyRevisionNumber
|
|
||||||
%uint :: continuityNumber
|
|
||||||
==
|
|
||||||
++ deed
|
|
||||||
:~ %address :: owner
|
|
||||||
%address :: managementProxy
|
|
||||||
%address :: spawnProxy
|
|
||||||
%address :: votingProxy
|
|
||||||
%address :: transferProxy
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ eth-noun
|
|
||||||
|%
|
|
||||||
+$ point
|
|
||||||
$: encryption-key=octs
|
|
||||||
authentication-key=octs
|
|
||||||
has-sponsor=?
|
|
||||||
active=?
|
|
||||||
escape-requested=?
|
|
||||||
sponsor=@ud
|
|
||||||
escape-to=@ud
|
|
||||||
crypto-suite=@ud
|
|
||||||
key-revision=@ud
|
|
||||||
continuity-number=@ud
|
|
||||||
==
|
|
||||||
+$ deed
|
|
||||||
$: owner=address
|
|
||||||
management-proxy=address
|
|
||||||
spawn-proxy=address
|
|
||||||
voting-proxy=address
|
|
||||||
transfer-proxy=address
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ function
|
|
||||||
|%
|
|
||||||
++ azimuth
|
|
||||||
$% [%points who=@p]
|
|
||||||
[%rights who=@p]
|
|
||||||
[%get-spawned who=@p]
|
|
||||||
[%dns-domains ind=@ud]
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
|
||||||
:: # diffs
|
|
||||||
::
|
|
||||||
++ update
|
|
||||||
$% [%full ships=(map ship point) dns=dnses heard=events]
|
|
||||||
[%difs dis=(list (pair event-id diff-azimuth))]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
:: # constants
|
|
||||||
::
|
|
||||||
:: contract addresses
|
|
||||||
++ contracts mainnet-contracts
|
|
||||||
++ mainnet-contracts
|
|
||||||
|%
|
|
||||||
:: azimuth: data contract
|
|
||||||
::
|
|
||||||
++ azimuth
|
|
||||||
0x223c.067f.8cf2.8ae1.73ee.5caf.ea60.ca44.c335.fecb
|
|
||||||
::
|
|
||||||
++ ecliptic
|
|
||||||
0xa5b6.109a.d2d3.5191.b3bc.32c0.0e45.26be.56fe.321f
|
|
||||||
::
|
|
||||||
++ linear-star-release
|
|
||||||
0x86cd.9cd0.992f.0423.1751.e376.1de4.5cec.ea5d.1801
|
|
||||||
::
|
|
||||||
++ conditional-star-release
|
|
||||||
0x8c24.1098.c3d3.498f.e126.1421.633f.d579.86d7.4aea
|
|
||||||
::
|
|
||||||
++ delegated-sending
|
|
||||||
0xf790.8ab1.f1e3.52f8.3c5e.bc75.051c.0565.aeae.a5fb
|
|
||||||
::
|
|
||||||
++ naive
|
|
||||||
0xeb70.029c.fb3c.53c7.78ea.f68c.d28d.e725.390a.1fe9
|
|
||||||
::
|
|
||||||
:: launch: block number of azimuth deploy
|
|
||||||
::
|
|
||||||
++ launch 6.784.800
|
|
||||||
::
|
|
||||||
:: public: block number of azimuth becoming independent
|
|
||||||
::
|
|
||||||
++ public 7.033.765
|
|
||||||
::
|
|
||||||
++ chain-id 1
|
|
||||||
--
|
|
||||||
::
|
|
||||||
:: Testnet contract addresses
|
|
||||||
::
|
|
||||||
++ ropsten-contracts
|
|
||||||
|%
|
|
||||||
++ azimuth
|
|
||||||
0x308a.b6a6.024c.f198.b57e.008d.0ac9.ad02.1988.6579
|
|
||||||
::
|
|
||||||
++ ecliptic
|
|
||||||
0x8b9f.86a2.8921.d9c7.05b3.113a.755f.b979.e1bd.1bce
|
|
||||||
::
|
|
||||||
++ linear-star-release
|
|
||||||
0x1f8e.dd03.1ee4.1474.0aed.b39b.84fb.8f2f.66ca.422f
|
|
||||||
::
|
|
||||||
++ conditional-star-release
|
|
||||||
0x0
|
|
||||||
::
|
|
||||||
++ delegated-sending
|
|
||||||
0x3e8c.a510.354b.c2fd.bbd6.1502.52d9.3105.c9c2.7bbe
|
|
||||||
::
|
|
||||||
++ naive
|
|
||||||
0xe7cf.4b83.06d3.11ba.ca15.585f.e3f0.7cd0.441c.21d1
|
|
||||||
::
|
|
||||||
++ launch 4.601.630
|
|
||||||
++ public launch
|
|
||||||
++ chain-id 3
|
|
||||||
--
|
|
||||||
::
|
|
||||||
:: Local contract addresses
|
|
||||||
::
|
|
||||||
:: These addresses are only reproducible if you use the deploy
|
|
||||||
:: script in bridge
|
|
||||||
::
|
|
||||||
++ local-contracts
|
|
||||||
|%
|
|
||||||
++ ecliptic
|
|
||||||
0x56db.68f2.9203.ff44.a803.faa2.404a.44ec.bb7a.7480
|
|
||||||
++ azimuth
|
|
||||||
0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381
|
|
||||||
++ delegated-sending
|
|
||||||
0xb71c.0b6c.ee1b.cae5.6dfe.95cd.9d3e.41dd.d7ea.fc43
|
|
||||||
++ linear-star-release
|
|
||||||
0x3c3.dc12.be65.8158.d1d7.f9e6.6e08.ec40.99c5.68e4
|
|
||||||
++ conditional-star-release
|
|
||||||
0x35eb.3b10.2d9c.1b69.ac14.69c1.b1fe.1799.850c.d3eb
|
|
||||||
++ naive
|
|
||||||
0x6bb8.8a9b.bd82.be7a.997f.eb01.929c.6ec7.8988.fe12
|
|
||||||
++ launch 0
|
|
||||||
++ public 0
|
|
||||||
++ chain-id 1.337
|
|
||||||
--
|
|
||||||
::
|
|
||||||
:: ++ azimuth 0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381 :: local bridge
|
|
||||||
:: hashes of ship event signatures
|
|
||||||
++ azimuth-events
|
|
||||||
|%
|
|
||||||
::
|
|
||||||
:: OwnerChanged(uint32,address)
|
|
||||||
++ owner-changed
|
|
||||||
0x16d0.f539.d49c.6cad.822b.767a.9445.bfb1.
|
|
||||||
cf7e.a6f2.a6c2.b120.a7ea.4cc7.660d.8fda
|
|
||||||
::
|
|
||||||
:: Activated(uint32)
|
|
||||||
++ activated
|
|
||||||
0xe74c.0380.9d07.69e1.b1f7.06cc.8414.258c.
|
|
||||||
d1f3.b6fe.020c.d15d.0165.c210.ba50.3a0f
|
|
||||||
::
|
|
||||||
:: Spawned(uint32,uint32)
|
|
||||||
++ spawned
|
|
||||||
0xb2d3.a6e7.a339.f5c8.ff96.265e.2f03.a010.
|
|
||||||
a854.1070.f374.4a24.7090.9644.1508.1546
|
|
||||||
::
|
|
||||||
:: EscapeRequested(uint32,uint32)
|
|
||||||
++ escape-requested
|
|
||||||
0xb4d4.850b.8f21.8218.141c.5665.cba3.79e5.
|
|
||||||
3e9b.b015.b51e.8d93.4be7.0210.aead.874a
|
|
||||||
::
|
|
||||||
:: EscapeCanceled(uint32,uint32)
|
|
||||||
++ escape-canceled
|
|
||||||
0xd653.bb0e.0bb7.ce83.93e6.24d9.8fbf.17cd.
|
|
||||||
a590.2c83.28ed.0cd0.9988.f368.90d9.932a
|
|
||||||
::
|
|
||||||
:: EscapeAccepted(uint32,uint32)
|
|
||||||
++ escape-accepted
|
|
||||||
0x7e44.7c9b.1bda.4b17.4b07.96e1.00bf.7f34.
|
|
||||||
ebf3.6dbb.7fe6.6549.0b1b.fce6.246a.9da5
|
|
||||||
::
|
|
||||||
:: LostSponsor(uint32,uint32)
|
|
||||||
++ lost-sponsor
|
|
||||||
0xd770.4f9a.2519.3dbd.0b0c.b4a8.09fe.ffff.
|
|
||||||
a7f1.9d1a.ae88.17a7.1346.c194.4482.10d5
|
|
||||||
::
|
|
||||||
:: ChangedKeys(uint32,bytes32,bytes32,uint32,uint32)
|
|
||||||
++ changed-keys
|
|
||||||
0xaa10.e7a0.117d.4323.f1d9.9d63.0ec1.69be.
|
|
||||||
bb3a.988e.8957.70e3.5198.7e01.ff54.23d5
|
|
||||||
::
|
|
||||||
:: BrokeContinuity(uint32,uint32)
|
|
||||||
++ broke-continuity
|
|
||||||
0x2929.4799.f1c2.1a37.ef83.8e15.f79d.d91b.
|
|
||||||
cee2.df99.d63c.d1c1.8ac9.68b1.2951.4e6e
|
|
||||||
::
|
|
||||||
:: ChangedSpawnProxy(uint32,address)
|
|
||||||
++ changed-spawn-proxy
|
|
||||||
0x9027.36af.7b3c.efe1.0d9e.840a.ed0d.687e.
|
|
||||||
35c8.4095.122b.2505.1a20.ead8.866f.006d
|
|
||||||
::
|
|
||||||
:: ChangedTransferProxy(uint32,address)
|
|
||||||
++ changed-transfer-proxy
|
|
||||||
0xcfe3.69b7.197e.7f0c.f067.93ae.2472.a9b1.
|
|
||||||
3583.fecb.ed2f.78df.a14d.1f10.796b.847c
|
|
||||||
::
|
|
||||||
:: ChangedManagementProxy(uint32,address)
|
|
||||||
++ changed-management-proxy
|
|
||||||
0xab9c.9327.cffd.2acc.168f.afed.be06.139f.
|
|
||||||
5f55.cb84.c761.df05.e051.1c25.1e2e.e9bf
|
|
||||||
::
|
|
||||||
:: ChangedVotingProxy(uint32,address)
|
|
||||||
++ changed-voting-proxy
|
|
||||||
0xcbd6.269e.c714.57f2.c7b1.a227.74f2.46f6.
|
|
||||||
c5a2.eae3.795e.d730.0db5.1768.0c61.c805
|
|
||||||
::
|
|
||||||
:: ChangedDns(string,string,string)
|
|
||||||
++ changed-dns
|
|
||||||
0xfafd.04ad.e1da.ae2e.1fdb.0fc1.cc6a.899f.
|
|
||||||
d424.063e.d5c9.2120.e67e.0730.53b9.4898
|
|
||||||
--
|
|
||||||
--
|
|
||||||
::
|
|
||||||
:: logic
|
|
||||||
::
|
|
||||||
|%
|
|
||||||
++ pass-from-eth
|
|
||||||
|= [enc=octs aut=octs sut=@ud]
|
|
||||||
^- pass
|
|
||||||
%^ cat 3 'b'
|
|
||||||
?. &(=(1 sut) =(p.enc 32) =(p.aut 32))
|
|
||||||
(cat 8 0 0)
|
|
||||||
(cat 8 q.aut q.enc)
|
|
||||||
::
|
|
||||||
++ point-from-eth
|
|
||||||
|= [who=@p point:eth-noun deed:eth-noun]
|
|
||||||
^- point
|
|
||||||
::
|
|
||||||
:: ownership
|
|
||||||
::
|
|
||||||
:+ :* owner
|
|
||||||
management-proxy
|
|
||||||
voting-proxy
|
|
||||||
transfer-proxy
|
|
||||||
==
|
|
||||||
::
|
|
||||||
:: network state
|
|
||||||
::
|
|
||||||
?. active ~
|
|
||||||
:- ~
|
|
||||||
:* key-revision
|
|
||||||
::
|
|
||||||
(pass-from-eth encryption-key authentication-key crypto-suite)
|
|
||||||
::
|
|
||||||
continuity-number
|
|
||||||
::
|
|
||||||
[has-sponsor `@p`sponsor]
|
|
||||||
::
|
|
||||||
?. escape-requested ~
|
|
||||||
``@p`escape-to
|
|
||||||
==
|
|
||||||
::
|
|
||||||
:: spawn state
|
|
||||||
::
|
|
||||||
?. ?=(?(%czar %king) (clan:title who)) ~
|
|
||||||
:- ~
|
|
||||||
:* spawn-proxy
|
|
||||||
~ ::TODO call getSpawned to fill this
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ event-log-to-point-diff
|
|
||||||
=, azimuth-events
|
|
||||||
=, abi:ethereum
|
|
||||||
|= log=event-log:rpc:ethereum
|
|
||||||
^- (unit (pair ship diff-point))
|
|
||||||
~? ?=(~ mined.log) %processing-unmined-event
|
|
||||||
::
|
|
||||||
?: =(i.topics.log owner-changed)
|
|
||||||
=/ [who=@ wer=address]
|
|
||||||
(decode-topics t.topics.log ~[%uint %address])
|
|
||||||
`[who %owner wer]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log activated)
|
|
||||||
=/ who=@
|
|
||||||
(decode-topics t.topics.log ~[%uint])
|
|
||||||
`[who %activated who]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log spawned)
|
|
||||||
=/ [pre=@ who=@]
|
|
||||||
(decode-topics t.topics.log ~[%uint %uint])
|
|
||||||
`[pre %spawned who]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log escape-requested)
|
|
||||||
=/ [who=@ wer=@]
|
|
||||||
(decode-topics t.topics.log ~[%uint %uint])
|
|
||||||
`[who %escape `wer]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log escape-canceled)
|
|
||||||
=/ who=@ (decode-topics t.topics.log ~[%uint])
|
|
||||||
`[who %escape ~]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log escape-accepted)
|
|
||||||
=/ [who=@ wer=@]
|
|
||||||
(decode-topics t.topics.log ~[%uint %uint])
|
|
||||||
`[who %sponsor & wer]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log lost-sponsor)
|
|
||||||
=/ [who=@ pos=@]
|
|
||||||
(decode-topics t.topics.log ~[%uint %uint])
|
|
||||||
`[who %sponsor | pos]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log changed-keys)
|
|
||||||
=/ who=@ (decode-topics t.topics.log ~[%uint])
|
|
||||||
=/ [enc=octs aut=octs sut=@ud rev=@ud]
|
|
||||||
%+ decode-results data.log
|
|
||||||
~[[%bytes-n 32] [%bytes-n 32] %uint %uint]
|
|
||||||
`[who %keys rev (pass-from-eth enc aut sut)]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log broke-continuity)
|
|
||||||
=/ who=@ (decode-topics t.topics.log ~[%uint])
|
|
||||||
=/ num=@ (decode-results data.log ~[%uint])
|
|
||||||
`[who %continuity num]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log changed-management-proxy)
|
|
||||||
=/ [who=@ sox=address]
|
|
||||||
(decode-topics t.topics.log ~[%uint %address])
|
|
||||||
`[who %management-proxy sox]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log changed-voting-proxy)
|
|
||||||
=/ [who=@ tox=address]
|
|
||||||
(decode-topics t.topics.log ~[%uint %address])
|
|
||||||
`[who %voting-proxy tox]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log changed-spawn-proxy)
|
|
||||||
=/ [who=@ sox=address]
|
|
||||||
(decode-topics t.topics.log ~[%uint %address])
|
|
||||||
`[who %spawn-proxy sox]
|
|
||||||
::
|
|
||||||
?: =(i.topics.log changed-transfer-proxy)
|
|
||||||
=/ [who=@ tox=address]
|
|
||||||
(decode-topics t.topics.log ~[%uint %address])
|
|
||||||
`[who %transfer-proxy tox]
|
|
||||||
::
|
|
||||||
:: warn about unimplemented events, but ignore
|
|
||||||
:: the ones we know are harmless.
|
|
||||||
~? ?! .= i.topics.log
|
|
||||||
:: OwnershipTransferred(address,address)
|
|
||||||
0x8be0.079c.5316.5914.1344.cd1f.d0a4.f284.
|
|
||||||
1949.7f97.22a3.daaf.e3b4.186f.6b64.57e0
|
|
||||||
[%unimplemented-event i.topics.log]
|
|
||||||
~
|
|
||||||
::
|
|
||||||
++ apply-point-diff
|
|
||||||
|= [pot=point dif=diff-point]
|
|
||||||
^- point
|
|
||||||
?- -.dif
|
|
||||||
%full new.dif
|
|
||||||
::
|
|
||||||
%activated
|
|
||||||
%_ pot
|
|
||||||
net `[0 0 0 &^(^sein:title who.dif) ~]
|
|
||||||
kid ?. ?=(?(%czar %king) (clan:title who.dif)) ~
|
|
||||||
`[0x0 ~]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
:: ownership
|
|
||||||
::
|
|
||||||
%owner pot(owner.own new.dif)
|
|
||||||
%transfer-proxy pot(transfer-proxy.own new.dif)
|
|
||||||
%management-proxy pot(management-proxy.own new.dif)
|
|
||||||
%voting-proxy pot(voting-proxy.own new.dif)
|
|
||||||
::
|
|
||||||
:: networking
|
|
||||||
::
|
|
||||||
?(%keys %continuity %sponsor %escape)
|
|
||||||
?> ?=(^ net.pot)
|
|
||||||
?- -.dif
|
|
||||||
%keys
|
|
||||||
pot(life.u.net life.dif, pass.u.net pass.dif)
|
|
||||||
::
|
|
||||||
%sponsor
|
|
||||||
%= pot
|
|
||||||
sponsor.u.net new.dif
|
|
||||||
escape.u.net ?:(has.new.dif ~ escape.u.net.pot)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
%continuity pot(continuity-number.u.net new.dif)
|
|
||||||
%escape pot(escape.u.net new.dif)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
:: spawning
|
|
||||||
::
|
|
||||||
?(%spawned %spawn-proxy)
|
|
||||||
?> ?=(^ kid.pot)
|
|
||||||
?- -.dif
|
|
||||||
%spawned
|
|
||||||
=- pot(spawned.u.kid -)
|
|
||||||
(~(put in spawned.u.kid.pot) who.dif)
|
|
||||||
::
|
|
||||||
%spawn-proxy pot(spawn-proxy.u.kid new.dif)
|
|
||||||
==
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ parse-id
|
|
||||||
|= id=@t
|
|
||||||
^- azimuth:function
|
|
||||||
|^
|
|
||||||
~| id
|
|
||||||
%+ rash id
|
|
||||||
;~ pose
|
|
||||||
(function %points 'points' shipname)
|
|
||||||
(function %get-spawned 'getSpawned' shipname)
|
|
||||||
(function %dns-domains 'dnsDomains' dem:ag)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ function
|
|
||||||
|* [tag=@tas fun=@t rul=rule]
|
|
||||||
;~(plug (cold tag (jest fun)) (ifix [pal par] rul))
|
|
||||||
::
|
|
||||||
++ shipname
|
|
||||||
;~(pfix sig fed:ag)
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ function-to-call
|
|
||||||
|%
|
|
||||||
++ azimuth
|
|
||||||
|= cal=azimuth:function
|
|
||||||
^- [id=@t dat=call-data:rpc:ethereum]
|
|
||||||
?- -.cal
|
|
||||||
%points
|
|
||||||
:- (crip "points({(scow %p who.cal)})")
|
|
||||||
['points(uint32)' ~[uint+`@`who.cal]]
|
|
||||||
::
|
|
||||||
%rights
|
|
||||||
:- (crip "rights({(scow %p who.cal)})")
|
|
||||||
['rights(uint32)' ~[uint+`@`who.cal]]
|
|
||||||
::
|
|
||||||
%get-spawned
|
|
||||||
:- (crip "getSpawned({(scow %p who.cal)})")
|
|
||||||
['getSpawned(uint32)' ~[uint+`@`who.cal]]
|
|
||||||
::
|
|
||||||
%dns-domains
|
|
||||||
:- (crip "dnsDomains({(scow %ud ind.cal)})")
|
|
||||||
['dnsDomains(uint256)' ~[uint+ind.cal]]
|
|
||||||
==
|
|
||||||
--
|
|
||||||
--
|
|
1
pkg/arvo/lib/azimuth.hoon
Symbolic link
1
pkg/arvo/lib/azimuth.hoon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../base-dev/lib/azimuth.hoon
|
@ -1,18 +0,0 @@
|
|||||||
::
|
|
||||||
:::: /hoon/atom/mar
|
|
||||||
::
|
|
||||||
/? 310
|
|
||||||
::
|
|
||||||
:::: A minimal atom mark
|
|
||||||
::
|
|
||||||
=, mimes:html
|
|
||||||
|_ ato=@
|
|
||||||
++ grab |%
|
|
||||||
++ noun @
|
|
||||||
++ mime |=([* p=octs] q.p)
|
|
||||||
--
|
|
||||||
++ grow |%
|
|
||||||
++ mime [/application/x-urb-unknown (as-octs ato)]
|
|
||||||
--
|
|
||||||
++ grad %mime
|
|
||||||
--
|
|
1
pkg/arvo/mar/atom.hoon
Symbolic link
1
pkg/arvo/mar/atom.hoon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../base-dev/mar/atom.hoon
|
@ -87,7 +87,7 @@
|
|||||||
0x223c.067f.8cf2.8ae1.73ee.5caf.ea60.ca44.c335.fecb
|
0x223c.067f.8cf2.8ae1.73ee.5caf.ea60.ca44.c335.fecb
|
||||||
::
|
::
|
||||||
++ ecliptic
|
++ ecliptic
|
||||||
0x6ac0.7b7c.4601.b5ce.11de.8dfe.6335.b871.c7c4.dd4d
|
0xa5b6.109a.d2d3.5191.b3bc.32c0.0e45.26be.56fe.321f
|
||||||
::
|
::
|
||||||
++ linear-star-release
|
++ linear-star-release
|
||||||
0x86cd.9cd0.992f.0423.1751.e376.1de4.5cec.ea5d.1801
|
0x86cd.9cd0.992f.0423.1751.e376.1de4.5cec.ea5d.1801
|
||||||
@ -98,6 +98,9 @@
|
|||||||
++ delegated-sending
|
++ delegated-sending
|
||||||
0xf790.8ab1.f1e3.52f8.3c5e.bc75.051c.0565.aeae.a5fb
|
0xf790.8ab1.f1e3.52f8.3c5e.bc75.051c.0565.aeae.a5fb
|
||||||
::
|
::
|
||||||
|
++ naive
|
||||||
|
0xeb70.029c.fb3c.53c7.78ea.f68c.d28d.e725.390a.1fe9
|
||||||
|
::
|
||||||
:: launch: block number of azimuth deploy
|
:: launch: block number of azimuth deploy
|
||||||
::
|
::
|
||||||
++ launch 6.784.800
|
++ launch 6.784.800
|
||||||
@ -105,6 +108,8 @@
|
|||||||
:: public: block number of azimuth becoming independent
|
:: public: block number of azimuth becoming independent
|
||||||
::
|
::
|
||||||
++ public 7.033.765
|
++ public 7.033.765
|
||||||
|
::
|
||||||
|
++ chain-id 1
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
:: Testnet contract addresses
|
:: Testnet contract addresses
|
||||||
@ -126,8 +131,12 @@
|
|||||||
++ delegated-sending
|
++ delegated-sending
|
||||||
0x3e8c.a510.354b.c2fd.bbd6.1502.52d9.3105.c9c2.7bbe
|
0x3e8c.a510.354b.c2fd.bbd6.1502.52d9.3105.c9c2.7bbe
|
||||||
::
|
::
|
||||||
|
++ naive
|
||||||
|
0xe7cf.4b83.06d3.11ba.ca15.585f.e3f0.7cd0.441c.21d1
|
||||||
|
::
|
||||||
++ launch 4.601.630
|
++ launch 4.601.630
|
||||||
++ public launch
|
++ public launch
|
||||||
|
++ chain-id 3
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
:: Local contract addresses
|
:: Local contract addresses
|
||||||
@ -137,9 +146,9 @@
|
|||||||
::
|
::
|
||||||
++ local-contracts
|
++ local-contracts
|
||||||
|%
|
|%
|
||||||
++ ecliptic
|
++ ecliptic
|
||||||
0x56db.68f2.9203.ff44.a803.faa2.404a.44ec.bb7a.7480
|
0x56db.68f2.9203.ff44.a803.faa2.404a.44ec.bb7a.7480
|
||||||
++ azimuth
|
++ azimuth
|
||||||
0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381
|
0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381
|
||||||
++ delegated-sending
|
++ delegated-sending
|
||||||
0xb71c.0b6c.ee1b.cae5.6dfe.95cd.9d3e.41dd.d7ea.fc43
|
0xb71c.0b6c.ee1b.cae5.6dfe.95cd.9d3e.41dd.d7ea.fc43
|
||||||
@ -147,8 +156,11 @@
|
|||||||
0x3c3.dc12.be65.8158.d1d7.f9e6.6e08.ec40.99c5.68e4
|
0x3c3.dc12.be65.8158.d1d7.f9e6.6e08.ec40.99c5.68e4
|
||||||
++ conditional-star-release
|
++ conditional-star-release
|
||||||
0x35eb.3b10.2d9c.1b69.ac14.69c1.b1fe.1799.850c.d3eb
|
0x35eb.3b10.2d9c.1b69.ac14.69c1.b1fe.1799.850c.d3eb
|
||||||
|
++ naive
|
||||||
|
0x6bb8.8a9b.bd82.be7a.997f.eb01.929c.6ec7.8988.fe12
|
||||||
++ launch 0
|
++ launch 0
|
||||||
++ public 0
|
++ public 0
|
||||||
|
++ chain-id 1.337
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
:: ++ azimuth 0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381 :: local bridge
|
:: ++ azimuth 0x863d.9c2e.5c4c.1335.96cf.ac29.d552.55f0.d0f8.6381 :: local bridge
|
||||||
|
18
pkg/base-dev/mar/atom.hoon
Normal file
18
pkg/base-dev/mar/atom.hoon
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
::
|
||||||
|
:::: /hoon/atom/mar
|
||||||
|
::
|
||||||
|
/? 310
|
||||||
|
::
|
||||||
|
:::: A minimal atom mark
|
||||||
|
::
|
||||||
|
=, mimes:html
|
||||||
|
|_ ato=@
|
||||||
|
++ grab |%
|
||||||
|
++ noun @
|
||||||
|
++ mime |=([* p=octs] q.p)
|
||||||
|
--
|
||||||
|
++ grow |%
|
||||||
|
++ mime [/application/x-urb-unknown (as-octs ato)]
|
||||||
|
--
|
||||||
|
++ grad %mime
|
||||||
|
--
|
@ -1 +0,0 @@
|
|||||||
[%zuse 420]
|
|
233
pkg/garden-dev/lib/hark-store.hoon
Normal file
233
pkg/garden-dev/lib/hark-store.hoon
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
/- sur=hark-store
|
||||||
|
^?
|
||||||
|
=, sur
|
||||||
|
=< [. sur]
|
||||||
|
|%
|
||||||
|
|
||||||
|
++ enjs
|
||||||
|
=, enjs:format
|
||||||
|
|%
|
||||||
|
++ update
|
||||||
|
|= upd=^update
|
||||||
|
^- json
|
||||||
|
%+ frond -.upd
|
||||||
|
?+ -.upd a+~
|
||||||
|
%added (notification +.upd)
|
||||||
|
%add-note (add-note +.upd)
|
||||||
|
%timebox (timebox +.upd)
|
||||||
|
%more (more +.upd)
|
||||||
|
%read-each (read-each +.upd)
|
||||||
|
%read-count (place +.upd)
|
||||||
|
%unread-each (read-each +.upd)
|
||||||
|
%unread-count (unread-count +.upd)
|
||||||
|
%saw-place (saw-place +.upd)
|
||||||
|
%all-stats (all-stats +.upd)
|
||||||
|
%del-place (place +.upd)
|
||||||
|
::%read-note (index +.upd)
|
||||||
|
::%note-read (note-read +.upd)
|
||||||
|
%archived (archived +.upd)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ add-note
|
||||||
|
|= [bi=^bin bo=^body]
|
||||||
|
%- pairs
|
||||||
|
:~ bin+(bin bi)
|
||||||
|
body+(body bo)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ saw-place
|
||||||
|
|= [p=^place t=(unit ^time)]
|
||||||
|
%- pairs
|
||||||
|
:~ place+(place p)
|
||||||
|
time+?~(t ~ (time u.t))
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ archived
|
||||||
|
|= [t=^time l=^lid n=^notification]
|
||||||
|
%- pairs
|
||||||
|
:~ lid+(lid l)
|
||||||
|
time+s+(scot %ud t)
|
||||||
|
notification+(notification n)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ note-read
|
||||||
|
|= *
|
||||||
|
(pairs ~)
|
||||||
|
::
|
||||||
|
++ all-stats
|
||||||
|
|= places=(map ^place ^stats)
|
||||||
|
^- json
|
||||||
|
:- %a
|
||||||
|
^- (list json)
|
||||||
|
%+ turn ~(tap by places)
|
||||||
|
|= [p=^place s=^stats]
|
||||||
|
%- pairs
|
||||||
|
:~ stats+(stats s)
|
||||||
|
place+(place p)
|
||||||
|
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ stats
|
||||||
|
|= s=^stats
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ each+a+(turn ~(tap in each.s) (cork spat (lead %s)))
|
||||||
|
last+(time last.s)
|
||||||
|
count+(numb count.s)
|
||||||
|
==
|
||||||
|
++ more
|
||||||
|
|= upds=(list ^update)
|
||||||
|
^- json
|
||||||
|
a+(turn upds update)
|
||||||
|
::
|
||||||
|
++ place
|
||||||
|
|= =^place
|
||||||
|
%- pairs
|
||||||
|
:~ desk+s+desk.place
|
||||||
|
path+s+(spat path.place)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ bin
|
||||||
|
|= =^bin
|
||||||
|
%- pairs
|
||||||
|
:~ place+(place place.bin)
|
||||||
|
path+s+(spat path.bin)
|
||||||
|
==
|
||||||
|
++ notification
|
||||||
|
|= ^notification
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ time+(time date)
|
||||||
|
bin+(^bin bin)
|
||||||
|
body+(bodies body)
|
||||||
|
==
|
||||||
|
++ bodies
|
||||||
|
|= bs=(list ^body)
|
||||||
|
^- json
|
||||||
|
a+(turn bs body)
|
||||||
|
::
|
||||||
|
++ contents
|
||||||
|
|= cs=(list ^content)
|
||||||
|
^- json
|
||||||
|
a+(turn cs content)
|
||||||
|
::
|
||||||
|
++ content
|
||||||
|
|= c=^content
|
||||||
|
^- json
|
||||||
|
%+ frond -.c
|
||||||
|
?- -.c
|
||||||
|
%ship s+(scot %p ship.c)
|
||||||
|
%text s+cord.c
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ body
|
||||||
|
|= ^body
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ title+(contents title)
|
||||||
|
content+(contents content)
|
||||||
|
time+(^time time)
|
||||||
|
link+s+(spat link)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ binned-notification
|
||||||
|
|= [=^bin =^notification]
|
||||||
|
%- pairs
|
||||||
|
:~ bin+(^bin bin)
|
||||||
|
notification+(^notification notification)
|
||||||
|
==
|
||||||
|
++ lid
|
||||||
|
|= l=^lid
|
||||||
|
^- json
|
||||||
|
%+ frond -.l
|
||||||
|
?- -.l
|
||||||
|
?(%seen %unseen) ~
|
||||||
|
%archive s+(scot %ud time.l)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ timebox
|
||||||
|
|= [li=^lid l=(list ^notification)]
|
||||||
|
^- json
|
||||||
|
%- pairs
|
||||||
|
:~ lid+(lid li)
|
||||||
|
notifications+a+(turn l notification)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ read-each
|
||||||
|
|= [p=^place pax=^path]
|
||||||
|
%- pairs
|
||||||
|
:~ place+(place p)
|
||||||
|
path+(path pax)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ unread-count
|
||||||
|
|= [p=^place inc=? count=@ud]
|
||||||
|
%- pairs
|
||||||
|
:~ place+(place p)
|
||||||
|
inc+b+inc
|
||||||
|
count+(numb count)
|
||||||
|
==
|
||||||
|
--
|
||||||
|
++ dejs
|
||||||
|
=, dejs:format
|
||||||
|
|%
|
||||||
|
:: TODO: fix +stab
|
||||||
|
::
|
||||||
|
++ pa
|
||||||
|
|= j=json
|
||||||
|
^- path
|
||||||
|
?> ?=(%s -.j)
|
||||||
|
?: =('/' p.j) /
|
||||||
|
(stab p.j)
|
||||||
|
::
|
||||||
|
++ place
|
||||||
|
%- ot
|
||||||
|
:~ desk+so
|
||||||
|
path+pa
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ bin
|
||||||
|
%- ot
|
||||||
|
:~ path+pa
|
||||||
|
place+place
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ read-each
|
||||||
|
%- ot
|
||||||
|
:~ place+place
|
||||||
|
path+pa
|
||||||
|
==
|
||||||
|
::
|
||||||
|
:: parse date as @ud
|
||||||
|
:: TODO: move to zuse
|
||||||
|
++ sd
|
||||||
|
|= jon=json
|
||||||
|
^- @da
|
||||||
|
?> ?=(%s -.jon)
|
||||||
|
`@da`(rash p.jon dem:ag)
|
||||||
|
::
|
||||||
|
++ lid
|
||||||
|
%- of
|
||||||
|
:~ archive+sd
|
||||||
|
unseen+ul
|
||||||
|
seen+ul
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ archive
|
||||||
|
%- ot
|
||||||
|
:~ lid+lid
|
||||||
|
bin+bin
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ action
|
||||||
|
^- $-(json ^action)
|
||||||
|
%- of
|
||||||
|
:~ archive-all+ul
|
||||||
|
archive+archive
|
||||||
|
opened+ul
|
||||||
|
read-count+place
|
||||||
|
read-each+read-each
|
||||||
|
read-note+bin
|
||||||
|
==
|
||||||
|
--
|
||||||
|
--
|
1
pkg/garden/lib/hark-store.hoon
Symbolic link
1
pkg/garden/lib/hark-store.hoon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../garden-dev/lib/hark-store.hoon
|
@ -1,235 +0,0 @@
|
|||||||
/- sur=hark-store
|
|
||||||
^?
|
|
||||||
=, sur
|
|
||||||
=< [. sur]
|
|
||||||
|%
|
|
||||||
|
|
||||||
++ enjs
|
|
||||||
=, enjs:format
|
|
||||||
|%
|
|
||||||
++ update
|
|
||||||
|= upd=^update
|
|
||||||
^- json
|
|
||||||
|^
|
|
||||||
%+ frond -.upd
|
|
||||||
?+ -.upd a+~
|
|
||||||
%added (notification +.upd)
|
|
||||||
%add-note (add-note +.upd)
|
|
||||||
%timebox (timebox +.upd)
|
|
||||||
%more (more +.upd)
|
|
||||||
%read-each (read-each +.upd)
|
|
||||||
%read-count (place +.upd)
|
|
||||||
%unread-each (read-each +.upd)
|
|
||||||
%unread-count (unread-count +.upd)
|
|
||||||
%saw-place (saw-place +.upd)
|
|
||||||
%all-stats (all-stats +.upd)
|
|
||||||
%del-place (place +.upd)
|
|
||||||
::%read-note (index +.upd)
|
|
||||||
::%note-read (note-read +.upd)
|
|
||||||
%archived (archived +.upd)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ add-note
|
|
||||||
|= [bi=^bin bo=^body]
|
|
||||||
%- pairs
|
|
||||||
:~ bin+(bin bi)
|
|
||||||
body+(body bo)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ saw-place
|
|
||||||
|= [p=^place t=(unit ^time)]
|
|
||||||
%- pairs
|
|
||||||
:~ place+(place p)
|
|
||||||
time+?~(t ~ (time u.t))
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ archived
|
|
||||||
|= [t=^time l=^lid n=^notification]
|
|
||||||
%- pairs
|
|
||||||
:~ lid+(lid l)
|
|
||||||
time+s+(scot %ud t)
|
|
||||||
notification+(notification n)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ note-read
|
|
||||||
|= *
|
|
||||||
(pairs ~)
|
|
||||||
::
|
|
||||||
++ all-stats
|
|
||||||
|= places=(map ^place ^stats)
|
|
||||||
^- json
|
|
||||||
:- %a
|
|
||||||
^- (list json)
|
|
||||||
%+ turn ~(tap by places)
|
|
||||||
|= [p=^place s=^stats]
|
|
||||||
%- pairs
|
|
||||||
:~ stats+(stats s)
|
|
||||||
place+(place p)
|
|
||||||
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ stats
|
|
||||||
|= s=^stats
|
|
||||||
^- json
|
|
||||||
%- pairs
|
|
||||||
:~ each+a+(turn ~(tap in each.s) (cork spat (lead %s)))
|
|
||||||
last+(time last.s)
|
|
||||||
count+(numb count.s)
|
|
||||||
==
|
|
||||||
++ more
|
|
||||||
|= upds=(list ^update)
|
|
||||||
^- json
|
|
||||||
a+(turn upds update)
|
|
||||||
::
|
|
||||||
++ place
|
|
||||||
|= =^place
|
|
||||||
%- pairs
|
|
||||||
:~ desk+s+desk.place
|
|
||||||
path+s+(spat path.place)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ bin
|
|
||||||
|= =^bin
|
|
||||||
%- pairs
|
|
||||||
:~ place+(place place.bin)
|
|
||||||
path+s+(spat path.bin)
|
|
||||||
==
|
|
||||||
++ notification
|
|
||||||
|= ^notification
|
|
||||||
^- json
|
|
||||||
%- pairs
|
|
||||||
:~ time+(time date)
|
|
||||||
bin+(^bin bin)
|
|
||||||
body+(bodies body)
|
|
||||||
==
|
|
||||||
++ bodies
|
|
||||||
|= bs=(list ^body)
|
|
||||||
^- json
|
|
||||||
a+(turn bs body)
|
|
||||||
::
|
|
||||||
++ contents
|
|
||||||
|= cs=(list ^content)
|
|
||||||
^- json
|
|
||||||
a+(turn cs content)
|
|
||||||
::
|
|
||||||
++ content
|
|
||||||
|= c=^content
|
|
||||||
^- json
|
|
||||||
%+ frond -.c
|
|
||||||
?- -.c
|
|
||||||
%ship s+(scot %p ship.c)
|
|
||||||
%text s+cord.c
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ body
|
|
||||||
|= ^body
|
|
||||||
^- json
|
|
||||||
%- pairs
|
|
||||||
:~ title+(contents title)
|
|
||||||
content+(contents content)
|
|
||||||
time+(^time time)
|
|
||||||
link+s+(spat link)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ binned-notification
|
|
||||||
|= [=^bin =^notification]
|
|
||||||
%- pairs
|
|
||||||
:~ bin+(^bin bin)
|
|
||||||
notification+(^notification notification)
|
|
||||||
==
|
|
||||||
++ lid
|
|
||||||
|= l=^lid
|
|
||||||
^- json
|
|
||||||
%+ frond -.l
|
|
||||||
?- -.l
|
|
||||||
?(%seen %unseen) ~
|
|
||||||
%archive s+(scot %ud time.l)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ timebox
|
|
||||||
|= [li=^lid l=(list ^notification)]
|
|
||||||
^- json
|
|
||||||
%- pairs
|
|
||||||
:~ lid+(lid li)
|
|
||||||
notifications+a+(turn l notification)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ read-each
|
|
||||||
|= [p=^place pax=^path]
|
|
||||||
%- pairs
|
|
||||||
:~ place+(place p)
|
|
||||||
path+(path pax)
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ unread-count
|
|
||||||
|= [p=^place inc=? count=@ud]
|
|
||||||
%- pairs
|
|
||||||
:~ place+(place p)
|
|
||||||
inc+b+inc
|
|
||||||
count+(numb count)
|
|
||||||
==
|
|
||||||
--
|
|
||||||
--
|
|
||||||
++ dejs
|
|
||||||
=, dejs:format
|
|
||||||
|%
|
|
||||||
:: TODO: fix +stab
|
|
||||||
::
|
|
||||||
++ pa
|
|
||||||
|= j=json
|
|
||||||
^- path
|
|
||||||
?> ?=(%s -.j)
|
|
||||||
?: =('/' p.j) /
|
|
||||||
(stab p.j)
|
|
||||||
::
|
|
||||||
++ place
|
|
||||||
%- ot
|
|
||||||
:~ desk+so
|
|
||||||
path+pa
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ bin
|
|
||||||
%- ot
|
|
||||||
:~ path+pa
|
|
||||||
place+place
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ read-each
|
|
||||||
%- ot
|
|
||||||
:~ place+place
|
|
||||||
path+pa
|
|
||||||
==
|
|
||||||
::
|
|
||||||
:: parse date as @ud
|
|
||||||
:: TODO: move to zuse
|
|
||||||
++ sd
|
|
||||||
|= jon=json
|
|
||||||
^- @da
|
|
||||||
?> ?=(%s -.jon)
|
|
||||||
`@da`(rash p.jon dem:ag)
|
|
||||||
::
|
|
||||||
++ lid
|
|
||||||
%- of
|
|
||||||
:~ archive+sd
|
|
||||||
unseen+ul
|
|
||||||
seen+ul
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ archive
|
|
||||||
%- ot
|
|
||||||
:~ lid+lid
|
|
||||||
bin+bin
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ action
|
|
||||||
^- $-(json ^action)
|
|
||||||
%- of
|
|
||||||
:~ archive-all+ul
|
|
||||||
archive+archive
|
|
||||||
opened+ul
|
|
||||||
read-count+place
|
|
||||||
read-each+read-each
|
|
||||||
read-note+bin
|
|
||||||
==
|
|
||||||
--
|
|
||||||
--
|
|
@ -1 +0,0 @@
|
|||||||
import ../../shell.nix
|
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
|
addons: ['@storybook/addon-links', '@storybook/addon-essentials', 'storybook-addon-designs'],
|
||||||
webpackFinal: (config) => {
|
webpackFinal: (config) => {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.(j|t)sx?$/,
|
test: /\.(j|t)sx?$/,
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import dark from '@tlon/indigo-dark';
|
import dark from "@tlon/indigo-dark";
|
||||||
import light from '@tlon/indigo-light';
|
import light from "@tlon/indigo-light";
|
||||||
import { Reset } from '@tlon/indigo-react';
|
import { Reset } from "@tlon/indigo-react";
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { ThemeProvider } from 'styled-components';
|
import { ThemeProvider } from "styled-components";
|
||||||
import useGraphState from '~/logic/state/graph';
|
import useGraphState from "~/logic/state/graph";
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
import useGroupState from "~/logic/state/group";
|
||||||
import useContactState from '~/logic/state/contact';
|
import useMetadataState from "~/logic/state/metadata";
|
||||||
import '~/views/landscape/css/custom.css';
|
import useContactState from "~/logic/state/contact";
|
||||||
import '~/views/css/fonts.css';
|
import "~/views/landscape/css/custom.css";
|
||||||
import '~/views/apps/chat/css/custom.css';
|
import "~/views/css/fonts.css";
|
||||||
import '~/views/css/indigo-static.css';
|
import "~/views/apps/chat/css/custom.css";
|
||||||
|
import "~/views/css/indigo-static.css";
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
controls: {
|
controls: {
|
||||||
matchers: {
|
matchers: {
|
||||||
color: /(background|color)$/i,
|
color: /(background|color)$/i,
|
||||||
@ -22,170 +23,197 @@ export const parameters = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const groupPreview = {
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index",
|
||||||
|
channels: {
|
||||||
|
"/ship/~darrux-landes/index-weekly": {
|
||||||
|
metadata: {
|
||||||
|
preview: false,
|
||||||
|
vip: "",
|
||||||
|
title: "Index Weekly",
|
||||||
|
description: "A weekly roundup of content from around the network",
|
||||||
|
creator: "~bollug-worlus",
|
||||||
|
picture: "",
|
||||||
|
hidden: false,
|
||||||
|
config: {
|
||||||
|
graph: "publish",
|
||||||
|
},
|
||||||
|
"date-created": "~2020.4.6..21.53.30..dc68",
|
||||||
|
color: "0x0",
|
||||||
|
},
|
||||||
|
"app-name": "graph",
|
||||||
|
resource: "/ship/~bollug-worlus/index-weekly",
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
members: 1237,
|
||||||
|
"channel-count": 3,
|
||||||
|
metadata: {
|
||||||
|
preview: false,
|
||||||
|
vip: "",
|
||||||
|
title: "Urbit Index",
|
||||||
|
description: "A weekly roundup of content form around the network",
|
||||||
|
creator: "~bollug-worlus",
|
||||||
|
picture: "",
|
||||||
|
hidden: false,
|
||||||
|
config: {
|
||||||
|
group: null,
|
||||||
|
},
|
||||||
|
"date-created": "~2020.4.6..21.53.30..dc68",
|
||||||
|
color: "0x0",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupPending = (progress) => ({
|
||||||
|
hidden: false,
|
||||||
|
started: Date.now() - 3600,
|
||||||
|
ship: "~bollug-worlus",
|
||||||
|
progress,
|
||||||
|
shareContact: false,
|
||||||
|
autojoin: false,
|
||||||
|
app: "groups",
|
||||||
|
invite: [],
|
||||||
|
});
|
||||||
|
|
||||||
export const globalTypes = {
|
export const globalTypes = {
|
||||||
theme: {
|
theme: {
|
||||||
name: 'Theme',
|
name: "Theme",
|
||||||
description: 'Global Theme for components',
|
description: "Global Theme for components",
|
||||||
defaultValue: 'light',
|
defaultValue: "light",
|
||||||
toolbar: {
|
toolbar: {
|
||||||
icon: 'circlehollow',
|
icon: "circlehollow",
|
||||||
items: ['light', 'dark'],
|
items: ["light", "dark"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decorators = [
|
export const decorators = [
|
||||||
(Story, context) => {
|
(Story, context) => {
|
||||||
window.ship = 'sampel-palnet';
|
window.ship = "sampel-palnet";
|
||||||
const theme = context.globals.theme === 'light' ? light : dark;
|
const theme = context.globals.theme === "light" ? light : dark;
|
||||||
|
|
||||||
useContactState.setState({
|
useContactState.setState({
|
||||||
contacts: {
|
contacts: {
|
||||||
'~ridlur-figbud': {
|
"~ridlur-figbud": {
|
||||||
status: 'please like and subscribe',
|
status: "please like and subscribe",
|
||||||
'last-updated': 1616609090555,
|
"last-updated": 1616609090555,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
cover: null,
|
cover: null,
|
||||||
bio: '',
|
bio: "",
|
||||||
nickname: 'Gav',
|
nickname: "Gav",
|
||||||
color: '0x26.3e0f',
|
color: "0x26.3e0f",
|
||||||
groups: [],
|
groups: [],
|
||||||
},
|
},
|
||||||
'~sampel-palnet': {
|
"~sampel-palnet": {
|
||||||
status: 'A test status',
|
status: "A test status",
|
||||||
'last-updated': 1616609090555,
|
"last-updated": 1616609090555,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
cover: null,
|
cover: null,
|
||||||
bio: '',
|
bio: "",
|
||||||
nickname: 'You',
|
nickname: "You",
|
||||||
color: '0x26.3e0f',
|
color: "0x26.3e0f",
|
||||||
groups: [],
|
groups: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useGroupState.setState({
|
||||||
|
pendingJoin: {
|
||||||
|
"/ship/~bollug-worlus/urbit-index-start": groupPending("start"),
|
||||||
|
"/ship/~bollug-worlus/urbit-index-metadata": groupPending("metadata"),
|
||||||
|
"/ship/~bollug-worlus/urbit-index-done": groupPending("done"),
|
||||||
|
"/ship/~bollug-worlus/urbit-index-error": groupPending("no-perms"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useMetadataState.setState({
|
useMetadataState.setState({
|
||||||
associations: {
|
associations: {
|
||||||
groups: {
|
groups: {
|
||||||
'/ship/~bitbet-bolbel/urbit-community': {
|
"/ship/~bitbet-bolbel/urbit-community": {
|
||||||
metadata: {
|
metadata: {
|
||||||
preview: false,
|
preview: false,
|
||||||
vip: '',
|
vip: "",
|
||||||
title: 'Urbit Community',
|
title: "Urbit Community",
|
||||||
description: 'World hub, help desk, meet and greet, etc.',
|
description: "World hub, help desk, meet and greet, etc.",
|
||||||
creator: '~bitbet-bolbel',
|
creator: "~bitbet-bolbel",
|
||||||
picture:
|
picture:
|
||||||
'https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.4.02..21.52.41-UC.png',
|
"https://fabled-faster.nyc3.digitaloceanspaces.com/fabled-faster/2021.4.02..21.52.41-UC.png",
|
||||||
hidden: false,
|
hidden: false,
|
||||||
config: {
|
config: {
|
||||||
group: {
|
group: {
|
||||||
'app-name': 'graph',
|
"app-name": "graph",
|
||||||
resource: '/ship/~bitbet-bolbel/urbit-community-5.963',
|
resource: "/ship/~bitbet-bolbel/urbit-community-5.963",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'date-created': '~2020.6.25..21.39.35..2fd2',
|
"date-created": "~2020.6.25..21.39.35..2fd2",
|
||||||
color: '0x8f.9c9d',
|
color: "0x8f.9c9d",
|
||||||
},
|
},
|
||||||
'app-name': 'groups',
|
"app-name": "groups",
|
||||||
resource: '/ship/~bitbet-bolbel/urbit-community',
|
resource: "/ship/~bitbet-bolbel/urbit-community",
|
||||||
group: '/ship/~bitbet-bolbel/urbit-community',
|
group: "/ship/~bitbet-bolbel/urbit-community",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
graph: {
|
graph: {
|
||||||
'/ship/~bitbet-bolbel/links': {
|
"/ship/~bitbet-bolbel/links": {
|
||||||
metadata: {
|
metadata: {
|
||||||
preview: false,
|
preview: false,
|
||||||
vip: '',
|
vip: "",
|
||||||
title: 'Link Collection',
|
title: "Link Collection",
|
||||||
description: '',
|
description: "",
|
||||||
creator: '~darrux-landes',
|
creator: "~darrux-landes",
|
||||||
picture: '',
|
picture: "",
|
||||||
hidden: false,
|
hidden: false,
|
||||||
config: {
|
config: {
|
||||||
graph: 'link',
|
graph: "link",
|
||||||
},
|
},
|
||||||
'date-created': '~2020.4.6..21.53.30..dc68',
|
"date-created": "~2020.4.6..21.53.30..dc68",
|
||||||
color: '0x0',
|
color: "0x0",
|
||||||
},
|
},
|
||||||
'app-name': 'graph',
|
"app-name": "graph",
|
||||||
resource: '/ship/~bitbet-bolbel/links',
|
resource: "/ship/~bitbet-bolbel/links",
|
||||||
group: '/ship/~bitbet-bolbel/urbit-community',
|
group: "/ship/~bitbet-bolbel/urbit-community",
|
||||||
},
|
},
|
||||||
'/ship/~darrux-landes/development': {
|
"/ship/~darrux-landes/development": {
|
||||||
metadata: {
|
metadata: {
|
||||||
preview: false,
|
preview: false,
|
||||||
vip: '',
|
vip: "",
|
||||||
title: 'Development',
|
title: "Development",
|
||||||
description:
|
description:
|
||||||
'Urbit Development Mailing List: https://groups.google.com/a/urbit.org/forum/#!forum/dev',
|
"Urbit Development Mailing List: https://groups.google.com/a/urbit.org/forum/#!forum/dev",
|
||||||
creator: '~darrux-landes',
|
creator: "~darrux-landes",
|
||||||
picture: '',
|
picture: "",
|
||||||
hidden: false,
|
hidden: false,
|
||||||
config: {
|
config: {
|
||||||
graph: 'chat',
|
graph: "chat",
|
||||||
},
|
},
|
||||||
'date-created': '~2020.4.6..21.53.30..dc68',
|
"date-created": "~2020.4.6..21.53.30..dc68",
|
||||||
color: '0x0',
|
color: "0x0",
|
||||||
},
|
},
|
||||||
'app-name': 'graph',
|
"app-name": "graph",
|
||||||
resource: '/ship/~darrux-landes/development',
|
resource: "/ship/~darrux-landes/development",
|
||||||
group: '/ship/~bitbet-bolbel/urbit-community',
|
group: "/ship/~bitbet-bolbel/urbit-community",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
previews: {
|
previews: {
|
||||||
'/ship/~bollug-worlus/urbit-index': {
|
"/ship/~bollug-worlus/urbit-index": groupPreview,
|
||||||
group: '/ship/~bollug-worlus/urbit-index',
|
"/ship/~bollug-worlus/urbit-index-start": groupPreview,
|
||||||
channels: {
|
"/ship/~bollug-worlus/urbit-index-metadata": groupPreview,
|
||||||
'/ship/~darrux-landes/index-weekly': {
|
"/ship/~bollug-worlus/urbit-index-done": groupPreview,
|
||||||
metadata: {
|
"/ship/~bollug-worlus/urbit-index-error": groupPreview,
|
||||||
preview: false,
|
|
||||||
vip: '',
|
|
||||||
title: 'Index Weekly',
|
|
||||||
description: '',
|
|
||||||
creator: '~bollug-worlus',
|
|
||||||
picture: '',
|
|
||||||
hidden: false,
|
|
||||||
config: {
|
|
||||||
graph: 'publish',
|
|
||||||
},
|
|
||||||
'date-created': '~2020.4.6..21.53.30..dc68',
|
|
||||||
color: '0x0',
|
|
||||||
},
|
|
||||||
'app-name': 'graph',
|
|
||||||
resource: '/ship/~bollug-worlus/index-weekly',
|
|
||||||
group: '/ship/~bollug-worlus/urbit-index',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
members: 1237,
|
|
||||||
metadata: {
|
|
||||||
preview: false,
|
|
||||||
vip: '',
|
|
||||||
title: 'Urbit Index',
|
|
||||||
description: '',
|
|
||||||
creator: '~bollug-worlus',
|
|
||||||
picture: '',
|
|
||||||
hidden: false,
|
|
||||||
config: {
|
|
||||||
group: null,
|
|
||||||
},
|
|
||||||
'date-created': '~2020.4.6..21.53.30..dc68',
|
|
||||||
color: '0x0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useContactState.setState({
|
useContactState.setState({
|
||||||
contacts: {
|
contacts: {
|
||||||
'~sampel-palnet': {
|
"~sampel-palnet": {
|
||||||
status: 'Just urbiting',
|
status: "Just urbiting",
|
||||||
'last-updated': 1621511447583,
|
"last-updated": 1621511447583,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
cover: null,
|
cover: null,
|
||||||
bio: 'An urbit user',
|
bio: "An urbit user",
|
||||||
nickname: 'Sample Planet',
|
nickname: "Sample Planet",
|
||||||
color: '0xee.5432',
|
color: "0xee.5432",
|
||||||
groups: [],
|
groups: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -193,27 +221,27 @@ export const decorators = [
|
|||||||
|
|
||||||
useGraphState.setState({
|
useGraphState.setState({
|
||||||
looseNodes: {
|
looseNodes: {
|
||||||
'darrux-landes/development': {
|
"darrux-landes/development": {
|
||||||
'/170141184505059416342852185329797955584': {
|
"/170141184505059416342852185329797955584": {
|
||||||
post: {
|
post: {
|
||||||
index: '/170141184505059416342852185329797955584',
|
index: "/170141184505059416342852185329797955584",
|
||||||
author: 'sipfyn-pidmex',
|
author: "sipfyn-pidmex",
|
||||||
'time-sent': 1621275183241,
|
"time-sent": 1621275183241,
|
||||||
signatures: [
|
signatures: [
|
||||||
{
|
{
|
||||||
signature:
|
signature:
|
||||||
'0x3.9e41.4f04.3cac.786e.30c1.f4cc.8db3.9a78.0401.d16f.6301.94d0.a08a.0695.5008.02bf.0e07.a7a9.3d87.85f7.6334.e598.4ed3.5dee.58a7.cbd3.30e6.d65b.1fc9.ac62.162a.daf0.ff14.9cca.4a93.8177.0755.7b74.9d52.c0a6.b27f.9001',
|
"0x3.9e41.4f04.3cac.786e.30c1.f4cc.8db3.9a78.0401.d16f.6301.94d0.a08a.0695.5008.02bf.0e07.a7a9.3d87.85f7.6334.e598.4ed3.5dee.58a7.cbd3.30e6.d65b.1fc9.ac62.162a.daf0.ff14.9cca.4a93.8177.0755.7b74.9d52.c0a6.b27f.9001",
|
||||||
life: 2,
|
life: 2,
|
||||||
ship: 'sipfyn-pidmex',
|
ship: "sipfyn-pidmex",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
contents: [
|
contents: [
|
||||||
{
|
{
|
||||||
text:
|
text:
|
||||||
'is there a way to get a bunt of a specific instantance of a tagged union? i.e. if you have `$%([%a =atom] [%b =cell])`, can you get a bunt of specifically subtype `%a`?',
|
"is there a way to get a bunt of a specific instantance of a tagged union? i.e. if you have `$%([%a =atom] [%b =cell])`, can you get a bunt of specifically subtype `%a`?",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
hash: '0xe790.53c1.0f2b.1e1b.8c30.7d33.236c.e69e',
|
hash: "0xe790.53c1.0f2b.1e1b.8c30.7d33.236c.e69e",
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
root: {},
|
root: {},
|
||||||
|
129293
pkg/interface/package-lock.json
generated
129293
pkg/interface/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@
|
|||||||
"@react-spring/web": "^9.1.1",
|
"@react-spring/web": "^9.1.1",
|
||||||
"@tlon/indigo-dark": "^1.0.6",
|
"@tlon/indigo-dark": "^1.0.6",
|
||||||
"@tlon/indigo-light": "^1.0.7",
|
"@tlon/indigo-light": "^1.0.7",
|
||||||
"@tlon/indigo-react": "^1.2.23",
|
"@tlon/indigo-react": "^1.2.27",
|
||||||
"@tlon/sigil-js": "^1.4.3",
|
"@tlon/sigil-js": "^1.4.3",
|
||||||
"@urbit/api": "^2.1.0",
|
"@urbit/api": "^2.1.0",
|
||||||
"@urbit/http-api": "^2.1.0",
|
"@urbit/http-api": "^2.1.0",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Urbit from '@urbit/http-api';
|
import Urbit from '@urbit/http-api';
|
||||||
const api = new Urbit('', '', (window as any).desk);
|
const api = new Urbit('', '', (window as any).desk);
|
||||||
api.ship = window.ship;
|
api.ship = window.ship;
|
||||||
// api.verbose = true;
|
api.verbose = true;
|
||||||
// @ts-ignore TODO window typings
|
// @ts-ignore TODO window typings
|
||||||
window.api = api;
|
window.api = api;
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import useMetadataState from '../state/metadata';
|
import useMetadataState from '../state/metadata';
|
||||||
import ob from 'urbit-ob';
|
import ob from 'urbit-ob';
|
||||||
|
import useInviteState from '../state/invite';
|
||||||
|
import {resourceAsPath} from '../../../../npm/api/dist';
|
||||||
|
|
||||||
function getGroupResourceRedirect(key: string) {
|
function getGroupResourceRedirect(key: string) {
|
||||||
const association = useMetadataState.getState().associations.graph[`/ship/${key}`];
|
const association = useMetadataState.getState().associations.graph[`/ship/${key}`];
|
||||||
@ -67,7 +69,9 @@ function getGraphRedirect(link: string) {
|
|||||||
|
|
||||||
function getInviteRedirect(link: string) {
|
function getInviteRedirect(link: string) {
|
||||||
const [,,app,uid] = link.split('/');
|
const [,,app,uid] = link.split('/');
|
||||||
return `/invites/${app}/${uid}`;
|
const invite = useInviteState.getState().invites[app][uid];
|
||||||
|
if(!invite) { return ''; }
|
||||||
|
return { search: `?join-kind=${app}&join-path=${encodeURIComponent(resourceAsPath(invite.resource))}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDmRedirect(link: string) {
|
function getDmRedirect(link: string) {
|
||||||
|
@ -56,7 +56,7 @@ const commandIndex = function (currentGroup, groups, associations) {
|
|||||||
if (canAdd) {
|
if (canAdd) {
|
||||||
commands.push(result('Channel: Create', `/~landscape${workspace}/new`, 'Groups', null));
|
commands.push(result('Channel: Create', `/~landscape${workspace}/new`, 'Groups', null));
|
||||||
}
|
}
|
||||||
commands.push(result('Groups: Join', '/~landscape/join', 'Groups', null));
|
commands.push(result('Groups: Join', '?join-kind=group', 'Groups', null));
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,7 @@ export const isUrl = (str) => {
|
|||||||
|
|
||||||
const raceRegexes = (str) => {
|
const raceRegexes = (str) => {
|
||||||
let link = str.match(URL_REGEX);
|
let link = str.match(URL_REGEX);
|
||||||
while(link?.[1]?.endsWith('(')) {
|
while(link?.[1]?.endsWith('(') || link?.[1].endsWith('[')) {
|
||||||
const resumePos = link[1].length + link[2].length;
|
const resumePos = link[1].length + link[2].length;
|
||||||
const resume = str.slice(resumePos);
|
const resume = str.slice(resumePos);
|
||||||
link = resume.match(URL_REGEX);
|
link = resume.match(URL_REGEX);
|
||||||
|
@ -8,6 +8,7 @@ type InviteState = State & BaseState<State>;
|
|||||||
const initial = (json: InviteUpdate, state: InviteState): InviteState => {
|
const initial = (json: InviteUpdate, state: InviteState): InviteState => {
|
||||||
const data = _.get(json, 'initial', false);
|
const data = _.get(json, 'initial', false);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
state.loaded = true;
|
||||||
state.invites = data;
|
state.invites = data;
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Association, Group, hideGroup, JoinRequests } from '@urbit/api';
|
import { Association, Group, JoinRequests, abortJoin } from '@urbit/api';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { reduce } from '../reducers/group-update';
|
import { reduce } from '../reducers/group-update';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@ -15,7 +15,8 @@ export interface GroupState {
|
|||||||
[group: string]: Group;
|
[group: string]: Group;
|
||||||
};
|
};
|
||||||
pendingJoin: JoinRequests;
|
pendingJoin: JoinRequests;
|
||||||
hidePending: (group: string) => Promise<void>;
|
abortJoin: (group: string) => Promise<void>;
|
||||||
|
doneJoin: (group: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore investigate zustand types
|
// @ts-ignore investigate zustand types
|
||||||
@ -24,12 +25,21 @@ const useGroupState = createState<GroupState>(
|
|||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
groups: {},
|
groups: {},
|
||||||
pendingJoin: {},
|
pendingJoin: {},
|
||||||
hidePending: async (group) => {
|
abortJoin: async (group) => {
|
||||||
get().set((draft) => {
|
get().set((draft) => {
|
||||||
delete draft.pendingJoin[group];
|
delete draft.pendingJoin[group];
|
||||||
});
|
});
|
||||||
await api.poke(hideGroup(group));
|
await api.poke(abortJoin(group));
|
||||||
}
|
},
|
||||||
|
doneJoin: async (group) => {
|
||||||
|
get().set((draft) => {
|
||||||
|
delete draft.pendingJoin[group];
|
||||||
|
});
|
||||||
|
await api.poke({ app: 'group-view', mark: 'group-view-action', json: {
|
||||||
|
done: group
|
||||||
|
}});
|
||||||
|
},
|
||||||
|
|
||||||
}),
|
}),
|
||||||
['groups'],
|
['groups'],
|
||||||
[
|
[
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Invites } from '@urbit/api';
|
import { deSig, Invite, Invites } from '@urbit/api';
|
||||||
import { reduce } from '../reducers/invite-update';
|
import { reduce } from '../reducers/invite-update';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
@ -9,14 +9,16 @@ import {
|
|||||||
|
|
||||||
export interface InviteState {
|
export interface InviteState {
|
||||||
invites: Invites;
|
invites: Invites;
|
||||||
|
loaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useInviteState = createState<InviteState>(
|
const useInviteState = createState<InviteState>(
|
||||||
'Invite',
|
'Invite',
|
||||||
{
|
{
|
||||||
invites: {}
|
invites: {},
|
||||||
|
loaded: false
|
||||||
},
|
},
|
||||||
['invites'],
|
['invites', 'loaded'],
|
||||||
[
|
[
|
||||||
(set, get) =>
|
(set, get) =>
|
||||||
createSubscription('invite-store', '/all', (e) => {
|
createSubscription('invite-store', '/all', (e) => {
|
||||||
@ -29,3 +31,18 @@ const useInviteState = createState<InviteState>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default useInviteState;
|
export default useInviteState;
|
||||||
|
|
||||||
|
interface InviteWithUid extends Invite {
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInviteForResource(app: string, ship: string, name: string) {
|
||||||
|
const { invites } = useInviteState();
|
||||||
|
const matches = Object.entries(invites?.[app] || {})
|
||||||
|
.reduce((acc, [uid, invite]) => {
|
||||||
|
const isMatch = (invite.resource.ship === deSig(ship)
|
||||||
|
&& invite.resource.name === name)
|
||||||
|
return isMatch ? [{ uid, ...invite}, ...acc] : acc;
|
||||||
|
}, [] as InviteWithUid[])
|
||||||
|
return matches?.[0];
|
||||||
|
}
|
||||||
|
22
pkg/interface/src/stories/Join.stories.tsx
Normal file
22
pkg/interface/src/stories/Join.stories.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Story, Meta } from "@storybook/react";
|
||||||
|
import { Box } from "@tlon/indigo-react";
|
||||||
|
|
||||||
|
import { JoinPrompt, JoinPromptProps } from "~/views/landscape/components/Join/Join";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Join/Prompt",
|
||||||
|
component: JoinPrompt,
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story<JoinPromptProps> = (args) => (
|
||||||
|
<Box backgroundColor="white" p="2" width="fit-content">
|
||||||
|
<JoinPrompt {...args} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Prompt = Template.bind({});
|
||||||
|
|
||||||
|
Prompt.args = {
|
||||||
|
kind: 'groups',
|
||||||
|
}
|
80
pkg/interface/src/stories/Join/Form.stories.tsx
Normal file
80
pkg/interface/src/stories/Join/Form.stories.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Story, Meta } from "@storybook/react";
|
||||||
|
import { Box } from "@tlon/indigo-react";
|
||||||
|
|
||||||
|
import { Join, JoinProps } from "~/views/landscape/components/Join/Join";
|
||||||
|
import { withDesign } from "storybook-addon-designs";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Join/Form",
|
||||||
|
component: Join,
|
||||||
|
decorators: [withDesign],
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story<JoinProps> = (args) => (
|
||||||
|
<Box backgroundColor="white" p="2" width="fit-content">
|
||||||
|
<Join {...args} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Prompt = Template.bind({});
|
||||||
|
|
||||||
|
Prompt.args = {
|
||||||
|
desc: {
|
||||||
|
kind: "groups",
|
||||||
|
group: "/ship/~bitbet-bolbel/urbit-community",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithPreview = Template.bind({});
|
||||||
|
|
||||||
|
WithPreview.args = {
|
||||||
|
desc: {
|
||||||
|
kind: "groups",
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
WithPreview.parameters = {
|
||||||
|
design: {
|
||||||
|
type: "figma",
|
||||||
|
url:
|
||||||
|
"https://www.figma.com/file/VxNYyFRnj8ZnqWG54VbVRM/Landscape-Baikal?node-id=1795%3A27718",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProgressStart = Template.bind({});
|
||||||
|
|
||||||
|
ProgressStart.args = {
|
||||||
|
desc: {
|
||||||
|
kind: "groups",
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index-start",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProgressMetadata = Template.bind({});
|
||||||
|
|
||||||
|
ProgressMetadata.args = {
|
||||||
|
desc: {
|
||||||
|
kind: "groups",
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index-metadata",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Finished = Template.bind({});
|
||||||
|
|
||||||
|
Finished.args = {
|
||||||
|
desc: {
|
||||||
|
kind: "groups",
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index-done",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Error = Template.bind({});
|
||||||
|
|
||||||
|
Error.args = {
|
||||||
|
desc: {
|
||||||
|
kind: "groups",
|
||||||
|
group: "/ship/~bollug-worlus/urbit-index-error",
|
||||||
|
},
|
||||||
|
};
|
0
pkg/interface/src/stories/Join/Progress.stories.tsx
Normal file
0
pkg/interface/src/stories/Join/Progress.stories.tsx
Normal file
@ -27,6 +27,17 @@ import './css/indigo-static.css';
|
|||||||
import { Content } from './landscape/components/Content';
|
import { Content } from './landscape/components/Content';
|
||||||
import './landscape/css/custom.css';
|
import './landscape/css/custom.css';
|
||||||
import { bootstrapApi } from '~/logic/api/bootstrap';
|
import { bootstrapApi } from '~/logic/api/bootstrap';
|
||||||
|
import { uxToHex } from '@urbit/api/dist';
|
||||||
|
|
||||||
|
function ensureValidHex(color) {
|
||||||
|
if (!color)
|
||||||
|
return '#000000';
|
||||||
|
|
||||||
|
const isUx = color.startsWith('0x');
|
||||||
|
const parsedColor = isUx ? uxToHex(color) : color;
|
||||||
|
|
||||||
|
return parsedColor.startsWith('#') ? parsedColor : `#${parsedColor}`;
|
||||||
|
}
|
||||||
|
|
||||||
const Root = withState(styled.div`
|
const Root = withState(styled.div`
|
||||||
font-family: ${p => p.theme.fonts.sans};
|
font-family: ${p => p.theme.fonts.sans};
|
||||||
@ -38,7 +49,7 @@ const Root = withState(styled.div`
|
|||||||
background-image: url('${p.display.background}');
|
background-image: url('${p.display.background}');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
` : p.display.backgroundType === 'color' ? `
|
` : p.display.backgroundType === 'color' ? `
|
||||||
background-color: ${p.display.background};
|
background-color: ${ensureValidHex(p.display.background)};
|
||||||
` : `background-color: ${p.theme.colors.white};`
|
` : `background-color: ${p.theme.colors.white};`
|
||||||
}
|
}
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
/* eslint-disable max-lines-per-function */
|
/* eslint-disable max-lines-per-function */
|
||||||
import { Box, Icon, Row, Text } from '@tlon/indigo-react';
|
import { Box, Icon, Row, Text, Button } from "@tlon/indigo-react";
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement } from "react";
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from "react-helmet";
|
||||||
import { Route } from 'react-router-dom';
|
import { Route, useHistory } from "react-router-dom";
|
||||||
import styled from 'styled-components';
|
import styled from "styled-components";
|
||||||
import useHarkState from '~/logic/state/hark';
|
import useHarkState from "~/logic/state/hark";
|
||||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
import useSettingsState, { selectCalmState } from "~/logic/state/settings";
|
||||||
import { JoinGroup } from '~/views/landscape/components/JoinGroup';
|
import Groups from "./components/Groups";
|
||||||
import { NewGroup } from '~/views/landscape/components/NewGroup';
|
import { NewGroup } from "~/views/landscape/components/NewGroup";
|
||||||
import Groups from './components/Groups';
|
import ModalButton from "./components/ModalButton";
|
||||||
import ModalButton from './components/ModalButton';
|
import Tiles from "./components/tiles";
|
||||||
import Tiles from './components/tiles';
|
import Tile from "./components/tiles/tile";
|
||||||
import Tile from './components/tiles/tile';
|
import "./css/custom.css";
|
||||||
import { Invite } from './components/Invite';
|
import { Join, JoinRoute } from "~/views/landscape/components/Join/Join";
|
||||||
import './css/custom.css';
|
|
||||||
|
|
||||||
const ScrollbarLessBox = styled(Box)`
|
const ScrollbarLessBox = styled(Box)`
|
||||||
scrollbar-width: none !important;
|
scrollbar-width: none !important;
|
||||||
@ -28,73 +27,83 @@ interface LaunchAppProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||||
const notificationsCount = useHarkState(state => state.notificationsCount);
|
const notificationsCount = useHarkState((state) => state.notificationsCount);
|
||||||
const calmState = useSettingsState(selectCalmState);
|
const calmState = useSettingsState(selectCalmState);
|
||||||
const { hideUtilities, hideGroups } = calmState;
|
const { hideUtilities, hideGroups } = calmState;
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet defer={false}>
|
<Helmet defer={false}>
|
||||||
<title>{ notificationsCount ? `(${String(notificationsCount) }) `: '' }Groups</title>
|
<title>
|
||||||
|
{notificationsCount ? `(${String(notificationsCount)}) ` : ""}Groups
|
||||||
|
</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Route path="/invites/:app/:uid">
|
<Route path="/join/:ship/:name">
|
||||||
<Invite />
|
<JoinRoute modal />
|
||||||
</Route>
|
</Route>
|
||||||
<ScrollbarLessBox height='100%' overflowY='scroll' display="flex" flexDirection="column">
|
<ScrollbarLessBox
|
||||||
|
height="100%"
|
||||||
|
overflowY="scroll"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
mx={2}
|
mx={2}
|
||||||
display='grid'
|
display="grid"
|
||||||
gridTemplateColumns='repeat(auto-fill, minmax(128px, 1fr))'
|
gridTemplateColumns="repeat(auto-fill, minmax(128px, 1fr))"
|
||||||
gridGap={3}
|
gridGap={3}
|
||||||
p={2}
|
p={2}
|
||||||
pt={0}
|
pt={0}
|
||||||
>
|
>
|
||||||
{!hideUtilities && <>
|
{!hideUtilities && (
|
||||||
<Tile
|
<>
|
||||||
bg="white"
|
<Tile
|
||||||
color="scales.black20"
|
bg="white"
|
||||||
to="/~landscape/home"
|
color="scales.black20"
|
||||||
p={0}
|
to="/~landscape/home"
|
||||||
>
|
p={0}
|
||||||
<Box
|
>
|
||||||
p={2}
|
<Box
|
||||||
height='100%'
|
p={2}
|
||||||
width='100%'
|
height="100%"
|
||||||
bg='scales.black20'
|
width="100%"
|
||||||
border={1}
|
bg="scales.black20"
|
||||||
borderColor="lightGray"
|
border={1}
|
||||||
>
|
borderColor="lightGray"
|
||||||
<Row alignItems='center'>
|
>
|
||||||
<Icon
|
<Row alignItems="center">
|
||||||
color="black"
|
<Icon color="black" icon="Home" />
|
||||||
icon="Home"
|
<Text ml={2} mt="1px" color="black">
|
||||||
/>
|
My Channels
|
||||||
<Text ml={2} mt='1px' color="black">My Channels</Text>
|
</Text>
|
||||||
</Row>
|
</Row>
|
||||||
</Box>
|
</Box>
|
||||||
</Tile>
|
</Tile>
|
||||||
<Tiles />
|
<Tiles />
|
||||||
<ModalButton
|
<ModalButton
|
||||||
icon="Plus"
|
icon="Plus"
|
||||||
bg="washedGray"
|
bg="white"
|
||||||
color="black"
|
color="black"
|
||||||
text="New Group"
|
text="New Group"
|
||||||
style={{ gridColumnStart: 1 }}
|
style={{ gridColumnStart: 1 }}
|
||||||
>
|
>
|
||||||
<NewGroup />
|
<NewGroup />
|
||||||
</ModalButton>
|
</ModalButton>
|
||||||
<ModalButton
|
<Button
|
||||||
icon="BootNode"
|
border={0}
|
||||||
bg="washedGray"
|
p={0}
|
||||||
color="black"
|
borderRadius={2}
|
||||||
text="Join Group"
|
onClick={() => history.push({ search: "?join-kind=group" })}
|
||||||
>
|
>
|
||||||
{dismiss => <JoinGroup dismiss={dismiss} />}
|
<Row backgroundColor="white" gapX="2" p={2} height="100%" width="100%" alignItems="center">
|
||||||
</ModalButton>
|
<Icon icon="BootNode" />
|
||||||
</>}
|
<Text fontWeight="medium" whiteSpace="nowrap">Join Group</Text>
|
||||||
{!hideGroups &&
|
</Row>
|
||||||
(<Groups />)
|
</Button>
|
||||||
}
|
</>
|
||||||
|
)}
|
||||||
|
{!hideGroups && <Groups />}
|
||||||
</Box>
|
</Box>
|
||||||
</ScrollbarLessBox>
|
</ScrollbarLessBox>
|
||||||
</>
|
</>
|
||||||
|
@ -1,16 +1,27 @@
|
|||||||
import { Box, Col, Text } from '@tlon/indigo-react';
|
import { Box, Col, Text } from "@tlon/indigo-react";
|
||||||
import { Association, Associations, Unreads } from '@urbit/api';
|
import {
|
||||||
import f from 'lodash/fp';
|
Association,
|
||||||
import React from 'react';
|
Associations,
|
||||||
import { getNotificationCount } from '~/logic/lib/hark';
|
resourceAsPath,
|
||||||
import { alphabeticalOrder } from '~/logic/lib/util';
|
resourceFromPath,
|
||||||
import useGroupState from '~/logic/state/group';
|
Unreads,
|
||||||
import useHarkState, { selHarkGraph } from '~/logic/state/hark';
|
} from "@urbit/api";
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
import f from "lodash/fp";
|
||||||
|
import _ from "lodash";
|
||||||
|
import React from "react";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { getNotificationCount } from "~/logic/lib/hark";
|
||||||
|
import { alphabeticalOrder } from "~/logic/lib/util";
|
||||||
|
import useGroupState from "~/logic/state/group";
|
||||||
|
import useHarkState, { selHarkGraph } from "~/logic/state/hark";
|
||||||
|
import useInviteState from "~/logic/state/invite";
|
||||||
|
import useMetadataState, { usePreview } from "~/logic/state/metadata";
|
||||||
import useSettingsState, {
|
import useSettingsState, {
|
||||||
selectCalmState
|
selectCalmState,
|
||||||
} from '~/logic/state/settings';
|
SettingsState,
|
||||||
import Tile from '../components/tiles/tile';
|
} from "~/logic/state/settings";
|
||||||
|
import Tile from "../components/tiles/tile";
|
||||||
|
import { useQuery } from "~/logic/lib/useQuery";
|
||||||
|
|
||||||
const sortGroupsAlph = (a: Association, b: Association) =>
|
const sortGroupsAlph = (a: Association, b: Association) =>
|
||||||
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||||
@ -25,7 +36,7 @@ const getGraphUnreads = (associations: Associations) => {
|
|||||||
return (path: string) =>
|
return (path: string) =>
|
||||||
f.flow(
|
f.flow(
|
||||||
f.pickBy((a: Association) => a.group === path),
|
f.pickBy((a: Association) => a.group === path),
|
||||||
f.map('resource'),
|
f.map("resource"),
|
||||||
f.map(selUnread),
|
f.map(selUnread),
|
||||||
f.reduce(f.add, 0)
|
f.reduce(f.add, 0)
|
||||||
)(associations.graph);
|
)(associations.graph);
|
||||||
@ -37,18 +48,18 @@ const getGraphNotifications = (
|
|||||||
) => (path: string) =>
|
) => (path: string) =>
|
||||||
f.flow(
|
f.flow(
|
||||||
f.pickBy((a: Association) => a.group === path),
|
f.pickBy((a: Association) => a.group === path),
|
||||||
f.map('resource'),
|
f.map("resource"),
|
||||||
f.map(rid => getNotificationCount(unreads, rid)),
|
f.map((rid) => getNotificationCount(unreads, rid)),
|
||||||
f.reduce(f.add, 0)
|
f.reduce(f.add, 0)
|
||||||
)(associations.graph);
|
)(associations.graph);
|
||||||
|
|
||||||
export default function Groups(props: Parameters<typeof Box>[0]) {
|
export default function Groups(props: Parameters<typeof Box>[0]) {
|
||||||
const unreads = useHarkState(state => state.unreads);
|
const unreads = useHarkState((state) => state.unreads);
|
||||||
const groupState = useGroupState(state => state.groups);
|
const groupState = useGroupState((state) => state.groups);
|
||||||
const associations = useMetadataState(state => state.associations);
|
const associations = useMetadataState((state) => state.associations);
|
||||||
|
|
||||||
const groups = Object.values(associations?.groups || {})
|
const groups = Object.values(associations?.groups || {})
|
||||||
.filter(e => e?.group in groupState)
|
.filter((e) => e?.group in groupState)
|
||||||
.sort(sortGroupsAlph);
|
.sort(sortGroupsAlph);
|
||||||
const graphUnreads = getGraphUnreads(associations || ({} as Associations));
|
const graphUnreads = getGraphUnreads(associations || ({} as Associations));
|
||||||
const graphNotifications = getGraphNotifications(
|
const graphNotifications = getGraphNotifications(
|
||||||
@ -56,6 +67,22 @@ export default function Groups(props: Parameters<typeof Box>[0]) {
|
|||||||
unreads
|
unreads
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const joining = useGroupState((s) =>
|
||||||
|
_.omit(
|
||||||
|
_.pickBy(s.pendingJoin || {}, req => req.app === 'groups' && req.progress != 'abort'),
|
||||||
|
groups.map((g) => g.group)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const invites = useInviteState(
|
||||||
|
(s) =>
|
||||||
|
Object.values(s.invites?.["groups"] || {}).map((inv) =>
|
||||||
|
resourceAsPath(inv.resource)
|
||||||
|
) || []
|
||||||
|
);
|
||||||
|
const pending = _.union(invites, Object.keys(joining)).filter(group => {
|
||||||
|
return !(group in (groupState?.groups || {})) && !(group in (associations.groups || {}))
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{groups.map((group, index) => {
|
{groups.map((group, index) => {
|
||||||
@ -73,10 +100,60 @@ export default function Groups(props: Parameters<typeof Box>[0]) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{pending.map((group, idx) => (
|
||||||
|
<PendingGroup
|
||||||
|
key={group}
|
||||||
|
path={group}
|
||||||
|
first={idx === 0 && groups.length === 0}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PendingGroupProps {
|
||||||
|
path: string;
|
||||||
|
first?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PendingGroup(props: PendingGroupProps) {
|
||||||
|
const { path, first } = props;
|
||||||
|
const history = useHistory();
|
||||||
|
const { preview, error } = usePreview(path);
|
||||||
|
const title = preview?.metadata?.title || path;
|
||||||
|
const { toQuery } = useQuery();
|
||||||
|
const onClick = () => {
|
||||||
|
const { ship, name } = resourceFromPath(path);
|
||||||
|
history.push(toQuery({ "join-kind": "groups", "join-path": path }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const joining = useGroupState((s) => s.pendingJoin[path]?.progress);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tile gridColumnStart={first ? 1 : undefined}>
|
||||||
|
<Col
|
||||||
|
onClick={onClick}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Text gray>{title}</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{!joining ? (
|
||||||
|
<Text color="blue">Invited</Text>
|
||||||
|
) : joining !== "done" ? (
|
||||||
|
<Text gray>Joining...</Text>
|
||||||
|
) : (
|
||||||
|
<Text color="blue">Recently joined</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Col>
|
||||||
|
</Tile>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface GroupProps {
|
interface GroupProps {
|
||||||
path: string;
|
path: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -87,6 +164,7 @@ interface GroupProps {
|
|||||||
function Group(props: GroupProps) {
|
function Group(props: GroupProps) {
|
||||||
const { path, title, unreads, updates, first = false } = props;
|
const { path, title, unreads, updates, first = false } = props;
|
||||||
const { hideUnreads } = useSettingsState(selectCalmState);
|
const { hideUnreads } = useSettingsState(selectCalmState);
|
||||||
|
const request = useGroupState((s) => s.pendingJoin[path]);
|
||||||
return (
|
return (
|
||||||
<Tile
|
<Tile
|
||||||
position="relative"
|
position="relative"
|
||||||
@ -97,9 +175,10 @@ function Group(props: GroupProps) {
|
|||||||
<Text>{title}</Text>
|
<Text>{title}</Text>
|
||||||
{!hideUnreads && (
|
{!hideUnreads && (
|
||||||
<Col>
|
<Col>
|
||||||
|
{!!request ? <Text color="blue">New group</Text> : null}
|
||||||
{updates > 0 && (
|
{updates > 0 && (
|
||||||
<Text mt={1} color="blue">
|
<Text mt={1} color="blue">
|
||||||
{updates} update{updates !== 1 && 's'}{' '}
|
{updates} update{updates !== 1 && "s"}{" "}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{unreads > 0 && <Text color="lightGray">{unreads}</Text>}
|
{unreads > 0 && <Text color="lightGray">{unreads}</Text>}
|
||||||
|
@ -108,7 +108,6 @@ function useInviteAccept(resource: string, app?: string, uid?: string) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await airlock.poke(join(ship, name));
|
|
||||||
await waiter((p) => {
|
await waiter((p) => {
|
||||||
return (
|
return (
|
||||||
(resource in p.groups &&
|
(resource in p.groups &&
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Row, SegmentedProgressBar, Text } from '@tlon/indigo-react';
|
import { Box, Row, SegmentedProgressBar, Text } from '@tlon/indigo-react';
|
||||||
import { joinError, joinProgress, JoinRequest, hideGroup } from '@urbit/api';
|
import { joinError, joinProgress, JoinRequest } from '@urbit/api';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
|
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
|
||||||
import airlock from '~/logic/api';
|
import airlock from '~/logic/api';
|
||||||
@ -24,10 +24,8 @@ export function JoiningStatus(props: JoiningStatusProps) {
|
|||||||
const desc = description?.[current] || '';
|
const desc = description?.[current] || '';
|
||||||
const isError = joinError.indexOf(status.progress as any) !== -1;
|
const isError = joinError.indexOf(status.progress as any) !== -1;
|
||||||
const onHide = useCallback(
|
const onHide = useCallback(
|
||||||
async () => {
|
async () => { },
|
||||||
await airlock.poke(hideGroup(resource));
|
[]
|
||||||
},
|
|
||||||
[resource]
|
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
|
@ -115,7 +115,12 @@ function GraphPermalink(
|
|||||||
})();
|
})();
|
||||||
}, [pending, graph, index]);
|
}, [pending, graph, index]);
|
||||||
const showTransclusion = Boolean(association && node && transcluded < 1);
|
const showTransclusion = Boolean(association && node && transcluded < 1);
|
||||||
const permalink = getPermalinkForGraph(group, graph, index);
|
const permalink = (() => {
|
||||||
|
const link = `/perma${getPermalinkForGraph(group, graph, index).slice(16)}`;
|
||||||
|
return (!association && !loading)
|
||||||
|
? { search: `?join-kind=group&join-path=${encodeURIComponent(group)}&redir=${encodeURIComponent(link)}` }
|
||||||
|
: link
|
||||||
|
})();
|
||||||
|
|
||||||
const [nodeGroupHost, nodeGroupName] = association?.group.split('/').slice(-2) ?? ['Unknown', 'Unknown'];
|
const [nodeGroupHost, nodeGroupName] = association?.group.split('/').slice(-2) ?? ['Unknown', 'Unknown'];
|
||||||
const [nodeChannelHost, nodeChannelName] = association?.resource
|
const [nodeChannelHost, nodeChannelName] = association?.resource
|
||||||
@ -140,7 +145,7 @@ function GraphPermalink(
|
|||||||
return (
|
return (
|
||||||
<Col
|
<Col
|
||||||
as={Link}
|
as={Link}
|
||||||
to={`/perma${permalink.slice(16)}`}
|
to={permalink}
|
||||||
width="100%"
|
width="100%"
|
||||||
bg="white"
|
bg="white"
|
||||||
maxWidth={full ? null : '500px'}
|
maxWidth={full ? null : '500px'}
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
import { Form } from 'formik';
|
import { Form } from 'formik';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { uxToHex } from '~/logic/lib/util';
|
|
||||||
import useSettingsState, { SettingsState } from '~/logic/state/settings';
|
import useSettingsState, { SettingsState } from '~/logic/state/settings';
|
||||||
import { FormikOnBlur } from '~/views/components/FormikOnBlur';
|
import { FormikOnBlur } from '~/views/components/FormikOnBlur';
|
||||||
import { BackButton } from './BackButton';
|
import { BackButton } from './BackButton';
|
||||||
@ -54,18 +53,28 @@ export default function DisplayForm() {
|
|||||||
|
|
||||||
const onSubmit = useCallback(async (values) => {
|
const onSubmit = useCallback(async (values) => {
|
||||||
const { putEntry } = useSettingsState.getState();
|
const { putEntry } = useSettingsState.getState();
|
||||||
putEntry('display', 'backgroundType', values.bgType);
|
const { bgType, bgColor, bgUrl, theme } = initialValues;
|
||||||
putEntry(
|
|
||||||
'display',
|
if (bgType !== values.bgType) {
|
||||||
'background',
|
putEntry('display', 'backgroundType', values.bgType);
|
||||||
values.bgType === 'color'
|
}
|
||||||
? `#${uxToHex(values.bgColor || '0x0')}`
|
|
||||||
: values.bgType === 'url'
|
if (bgColor !== values.bgColor || bgUrl !== values.bgUrl) {
|
||||||
? values.bgUrl || ''
|
putEntry(
|
||||||
: false
|
'display',
|
||||||
);
|
'background',
|
||||||
putEntry('display', 'theme', values.theme);
|
values.bgType === 'color'
|
||||||
}, []);
|
? values.bgColor
|
||||||
|
: values.bgType === 'url'
|
||||||
|
? values.bgUrl || ''
|
||||||
|
: ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theme !== values.theme) {
|
||||||
|
putEntry('display', 'theme', values.theme);
|
||||||
|
}
|
||||||
|
}, [initialValues]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormikOnBlur
|
<FormikOnBlur
|
||||||
|
@ -21,6 +21,7 @@ export interface AuthorProps {
|
|||||||
lineHeight?: string | number;
|
lineHeight?: string | number;
|
||||||
isRelativeTime?: boolean;
|
isRelativeTime?: boolean;
|
||||||
dontShowTime?: boolean;
|
dontShowTime?: boolean;
|
||||||
|
gray?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line max-lines-per-function
|
// eslint-disable-next-line max-lines-per-function
|
||||||
@ -35,6 +36,7 @@ function Author(props: AuthorProps & PropFunc<typeof Box>): ReactElement {
|
|||||||
isRelativeTime,
|
isRelativeTime,
|
||||||
dontShowTime,
|
dontShowTime,
|
||||||
lineHeight = 'tall',
|
lineHeight = 'tall',
|
||||||
|
gray = false,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@ -88,7 +90,7 @@ function Author(props: AuthorProps & PropFunc<typeof Box>): ReactElement {
|
|||||||
<Box display='flex' alignItems='baseline'>
|
<Box display='flex' alignItems='baseline'>
|
||||||
<Text
|
<Text
|
||||||
ml={showImage ? 2 : 0}
|
ml={showImage ? 2 : 0}
|
||||||
color='black'
|
color={gray ? 'gray': 'black'}
|
||||||
fontSize='1'
|
fontSize='1'
|
||||||
cursor='pointer'
|
cursor='pointer'
|
||||||
lineHeight={lineHeight}
|
lineHeight={lineHeight}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
Box, Col,
|
Box, Col,
|
||||||
|
|
||||||
ErrorLabel, Label,
|
ErrorLabel, Label,
|
||||||
Row,
|
Row,
|
||||||
|
|
||||||
StatelessTextInput as Input
|
StatelessTextInput as Input
|
||||||
} from '@tlon/indigo-react';
|
} from '@tlon/indigo-react';
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
import React, { FormEvent, useState, useEffect } from 'react';
|
import React, { useState, useEffect, ChangeEvent, useMemo } from 'react';
|
||||||
import { hexToUx } from '~/logic/lib/util';
|
import { uxToHex, hexToUx } from '@urbit/api';
|
||||||
import { uxToHex } from '@urbit/api';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export type ColorInputProps = Parameters<typeof Col>[0] & {
|
export type ColorInputProps = Parameters<typeof Col>[0] & {
|
||||||
id: string;
|
id: string;
|
||||||
@ -20,37 +19,57 @@ export type ColorInputProps = Parameters<typeof Col>[0] & {
|
|||||||
|
|
||||||
const COLOR_REGEX = /^(\d|[a-f]|[A-F]){6}$/;
|
const COLOR_REGEX = /^(\d|[a-f]|[A-F]){6}$/;
|
||||||
|
|
||||||
function padHex(hex: string) {
|
function isValidHex(color: string): boolean {
|
||||||
if(hex.length === 0) {
|
return COLOR_REGEX.test(color);
|
||||||
return '000000';
|
|
||||||
}
|
|
||||||
const repeat = 6 / hex.length;
|
|
||||||
if(Math.floor(repeat) === repeat) {
|
|
||||||
return hex.repeat(repeat);
|
|
||||||
}
|
|
||||||
if(hex.length < 6) {
|
|
||||||
return hex.slice(0,3).repeat(2);
|
|
||||||
}
|
|
||||||
return hex.slice(0,6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseIncomingColor(value: string): string {
|
||||||
|
if (!value)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
const isUx = value.startsWith('0x');
|
||||||
|
return isUx ? uxToHex(value) : value.replace('#', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClickInput = styled(Input)`
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
export function ColorInput(props: ColorInputProps) {
|
export function ColorInput(props: ColorInputProps) {
|
||||||
const { id, placeholder, label, caption, disabled, ...rest } = props;
|
const { id, placeholder, label, caption, disabled, ...rest } = props;
|
||||||
const [{ value, onBlur }, meta, { setValue, setTouched }] = useField(id);
|
const [{ value }, meta, { setValue, setTouched }] = useField(id);
|
||||||
const [field, setField] = useState(uxToHex(value));
|
const [field, setField] = useState(parseIncomingColor(value));
|
||||||
|
|
||||||
useEffect(() => {
|
const update = (value: string) => {
|
||||||
const newValue = hexToUx(padHex(field));
|
const normalizedValue = value.trim().replace(/[^a-f\d]/gi, '').slice(0,6);
|
||||||
|
setField(normalizedValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onText = (e: ChangeEvent<HTMLInputElement>) => update(e.target.value);
|
||||||
|
|
||||||
|
const pickerChange = useMemo(() => _.debounce(update, 300), []);
|
||||||
|
|
||||||
|
const updateField = useMemo(() => _.debounce((field: string) => {
|
||||||
|
const newValue = hexToUx(field);
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
setTouched(true);
|
setTouched(true);
|
||||||
|
}, 100), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isValidHex(field)) {
|
||||||
|
updateField(field);
|
||||||
|
}
|
||||||
}, [field]);
|
}, [field]);
|
||||||
|
|
||||||
const onChange = (e: FormEvent<HTMLInputElement>) => {
|
useEffect(() => {
|
||||||
const { value: newValue } = e.target as HTMLInputElement;
|
const parsedColor = parseIncomingColor(value);
|
||||||
setField(newValue.slice(1));
|
|
||||||
};
|
if (parsedColor !== field) {
|
||||||
const hex = uxToHex(value);
|
update(parsedColor);
|
||||||
const isValid = COLOR_REGEX.test(hex);
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const isValid = isValidHex(field);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display='flex' flexDirection='column' {...rest}>
|
<Box display='flex' flexDirection='column' {...rest}>
|
||||||
@ -60,13 +79,13 @@ export function ColorInput(props: ColorInputProps) {
|
|||||||
{caption}
|
{caption}
|
||||||
</Label>
|
</Label>
|
||||||
) : null}
|
) : null}
|
||||||
<Row mt={2} alignItems='flex-end'>
|
<Row mt={2} alignItems='flex-end' maxWidth="120px">
|
||||||
<Input
|
<Input
|
||||||
id={id}
|
id={id}
|
||||||
borderTopRightRadius={0}
|
borderTopRightRadius={0}
|
||||||
borderBottomRightRadius={0}
|
borderBottomRightRadius={0}
|
||||||
onBlur={onBlur}
|
onBlur={onText}
|
||||||
onChange={onChange}
|
onChange={onText}
|
||||||
value={field}
|
value={field}
|
||||||
disabled={disabled || false}
|
disabled={disabled || false}
|
||||||
borderRight={0}
|
borderRight={0}
|
||||||
@ -76,21 +95,21 @@ export function ColorInput(props: ColorInputProps) {
|
|||||||
borderBottomRightRadius={1}
|
borderBottomRightRadius={1}
|
||||||
borderTopRightRadius={1}
|
borderTopRightRadius={1}
|
||||||
border={1}
|
border={1}
|
||||||
borderLeft={0}
|
|
||||||
borderColor='lightGray'
|
borderColor='lightGray'
|
||||||
width='32px'
|
width='32px'
|
||||||
alignSelf='stretch'
|
alignSelf='stretch'
|
||||||
bg={isValid ? `#${hex}` : 'transparent'}
|
bg={isValid ? `#${field}` : 'transparent'}
|
||||||
>
|
>
|
||||||
<Input
|
<ClickInput
|
||||||
width='100%'
|
width='100%'
|
||||||
height='100%'
|
height='100%'
|
||||||
alignSelf='stretch'
|
alignSelf='stretch'
|
||||||
disabled={disabled || false}
|
disabled={disabled || false}
|
||||||
type='color'
|
type='color'
|
||||||
|
value={`#${isValid ? field : uxToHex(value)}`}
|
||||||
opacity={0}
|
opacity={0}
|
||||||
overflow='hidden'
|
overflow='hidden'
|
||||||
onChange={onChange}
|
onChange={(e: ChangeEvent<HTMLInputElement>) => pickerChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
|
import { Box, Col, Icon, Row, Text } from '@tlon/indigo-react';
|
||||||
import React, { ReactElement, useCallback } from 'react';
|
import React, { ReactElement, useCallback } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useModal } from '~/logic/lib/useModal';
|
|
||||||
import useMetadataState, { usePreview } from '~/logic/state/metadata';
|
import useMetadataState, { usePreview } from '~/logic/state/metadata';
|
||||||
import { PropFunc } from '~/types';
|
import { PropFunc } from '~/types';
|
||||||
import { JoinGroup } from '../landscape/components/JoinGroup';
|
|
||||||
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
||||||
|
|
||||||
type GroupLinkProps = {
|
type GroupLinkProps = {
|
||||||
@ -22,61 +20,44 @@ export function GroupLink({
|
|||||||
useCallback(s => resource in s.associations.groups, [resource])
|
useCallback(s => resource in s.associations.groups, [resource])
|
||||||
);
|
);
|
||||||
|
|
||||||
const { modal, showModal } = useModal({
|
const { preview } = usePreview(resource);
|
||||||
modal: <JoinGroup autojoin={name} />
|
|
||||||
});
|
|
||||||
|
|
||||||
const { preview } = usePreview(resource);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Row
|
||||||
{modal}
|
{...rest}
|
||||||
<Row
|
as={Link}
|
||||||
{...rest}
|
to={joined ? `/~landscape/ship/${name}` : { search: `?join-kind=groups&join-path=/ship/${name}`}}
|
||||||
as={Link}
|
flexShrink={1}
|
||||||
to={joined ? `/~landscape/ship/${name}` : `/perma/group/${name}`}
|
alignItems="center"
|
||||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
width="100%"
|
||||||
e.stopPropagation();
|
maxWidth="500px"
|
||||||
|
py={2}
|
||||||
if (e.metaKey || e.ctrlKey) {
|
pr={2}
|
||||||
return;
|
cursor='pointer'
|
||||||
}
|
backgroundColor='white'
|
||||||
|
borderColor={borderColor}
|
||||||
e.preventDefault();
|
opacity={preview ? '1' : '0.6'}
|
||||||
showModal();
|
>
|
||||||
}}
|
<MetadataIcon height={6} width={6} metadata={preview ? preview.metadata : { color: '0x0' , picture: '' }} />
|
||||||
flexShrink={1}
|
<Col>
|
||||||
alignItems="center"
|
<Text ml={2} fontWeight="medium" mono={!preview}>
|
||||||
width="100%"
|
{preview ? preview.metadata.title : name}
|
||||||
maxWidth="500px"
|
</Text>
|
||||||
py={2}
|
<Box pt='1' ml='2' display='flex' alignItems='center'>
|
||||||
pr={2}
|
{preview ?
|
||||||
cursor='pointer'
|
<>
|
||||||
backgroundColor='white'
|
<Box display='flex' alignItems='center'>
|
||||||
borderColor={borderColor}
|
<Icon icon='Users' color='gray' mr='1' />
|
||||||
opacity={preview ? '1' : '0.6'}
|
<Text fontSize='0'color='gray' >
|
||||||
>
|
{preview.members}
|
||||||
<MetadataIcon height={6} width={6} metadata={preview ? preview.metadata : { color: '0x0' , picture: '' }} />
|
{' '}
|
||||||
<Col>
|
{preview.members > 1 ? 'peers' : 'peer'}
|
||||||
<Text ml={2} fontWeight="medium" mono={!preview}>
|
</Text>
|
||||||
{preview ? preview.metadata.title : name}
|
</Box>
|
||||||
</Text>
|
</>
|
||||||
<Box pt='1' ml='2' display='flex' alignItems='center'>
|
: <Text fontSize='0'>Fetching member count</Text>}
|
||||||
{preview ?
|
</Box>
|
||||||
<>
|
</Col>
|
||||||
<Box display='flex' alignItems='center'>
|
</Row>
|
||||||
<Icon icon='Users' color='gray' mr='1' />
|
|
||||||
<Text fontSize='0'color='gray' >
|
|
||||||
{preview.members}
|
|
||||||
{' '}
|
|
||||||
{preview.members > 1 ? 'peers' : 'peer'}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
: <Text fontSize='0'>Fetching member count</Text>}
|
|
||||||
</Box>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ import { Box, Icon, LoadingSpinner, Row, Text } from '@tlon/indigo-react';
|
|||||||
import {
|
import {
|
||||||
accept,
|
accept,
|
||||||
decline,
|
decline,
|
||||||
hideGroup,
|
|
||||||
Invite,
|
Invite,
|
||||||
join,
|
|
||||||
joinProgress,
|
joinProgress,
|
||||||
joinResult,
|
joinResult,
|
||||||
JoinRequest,
|
JoinRequest,
|
||||||
@ -170,7 +168,6 @@ export function useInviteAccept(resource: string, app?: string, uid?: string) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await airlock.poke(join(ship, name));
|
|
||||||
await airlock.poke(accept(app, uid));
|
await airlock.poke(accept(app, uid));
|
||||||
await waiter((p) => {
|
await waiter((p) => {
|
||||||
return (
|
return (
|
||||||
@ -218,9 +215,6 @@ function InviteActions(props: {
|
|||||||
await airlock.poke(decline(app, uid));
|
await airlock.poke(decline(app, uid));
|
||||||
}, [app, uid]);
|
}, [app, uid]);
|
||||||
|
|
||||||
const hideJoin = useCallback(async () => {
|
|
||||||
await airlock.poke(hideGroup(resource));
|
|
||||||
}, [resource]);
|
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
return (
|
return (
|
||||||
@ -228,7 +222,7 @@ function InviteActions(props: {
|
|||||||
<StatelessAsyncButton
|
<StatelessAsyncButton
|
||||||
height={4}
|
height={4}
|
||||||
backgroundColor="white"
|
backgroundColor="white"
|
||||||
onClick={hideJoin}
|
onClick={async () => {}}
|
||||||
>
|
>
|
||||||
{[...joinResult].includes(status?.progress as any)
|
{[...joinResult].includes(status?.progress as any)
|
||||||
? 'Dismiss'
|
? 'Dismiss'
|
||||||
@ -289,7 +283,6 @@ export function GroupInvite(props: GroupInviteProps): ReactElement {
|
|||||||
if (status?.progress === 'done') {
|
if (status?.progress === 'done') {
|
||||||
const redir = inviteUrl(app !== 'groups', resource, graphAssoc?.metadata);
|
const redir = inviteUrl(app !== 'groups', resource, graphAssoc?.metadata);
|
||||||
if (redir) {
|
if (redir) {
|
||||||
airlock.poke(hideGroup(resource));
|
|
||||||
history.push(redir);
|
history.push(redir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import { Dropdown } from './Dropdown';
|
|||||||
import { ProfileStatus } from './ProfileStatus';
|
import { ProfileStatus } from './ProfileStatus';
|
||||||
import ReconnectButton from './ReconnectButton';
|
import ReconnectButton from './ReconnectButton';
|
||||||
import { StatusBarItem } from './StatusBarItem';
|
import { StatusBarItem } from './StatusBarItem';
|
||||||
import { StatusBarJoins } from './StatusBarJoins';
|
|
||||||
import useHarkState from '~/logic/state/hark';
|
import useHarkState from '~/logic/state/hark';
|
||||||
|
|
||||||
const localSel = selectLocalState(['toggleOmnibox']);
|
const localSel = selectLocalState(['toggleOmnibox']);
|
||||||
@ -83,7 +82,6 @@ const StatusBar = (props) => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</StatusBarItem>
|
</StatusBarItem>
|
||||||
<StatusBarJoins />
|
|
||||||
<ReconnectButton />
|
<ReconnectButton />
|
||||||
</Row>
|
</Row>
|
||||||
<Row justifyContent='flex-end'>
|
<Row justifyContent='flex-end'>
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
import { LoadingSpinner, Button } from '@tlon/indigo-react';
|
|
||||||
import React from 'react';
|
|
||||||
import { Box, Row, Col, Text } from '@tlon/indigo-react';
|
|
||||||
import { PropFunc } from '~/types';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { StatusBarItem } from './StatusBarItem';
|
|
||||||
import useGroupState from '~/logic/state/group';
|
|
||||||
import { JoinRequest, joinProgress } from '@urbit/api';
|
|
||||||
import { usePreview } from '~/logic/state/metadata';
|
|
||||||
import { Dropdown } from './Dropdown';
|
|
||||||
import { MetadataIcon } from '../landscape/components/MetadataIcon';
|
|
||||||
|
|
||||||
function Elbow(
|
|
||||||
props: { size?: number; color?: string } & PropFunc<typeof Box>
|
|
||||||
) {
|
|
||||||
const { size = 12, color = 'lightGray', ...rest } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
{...rest}
|
|
||||||
overflow="hidden"
|
|
||||||
width={size}
|
|
||||||
height={size}
|
|
||||||
position="relative"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
border="2px solid"
|
|
||||||
borderRadius={3}
|
|
||||||
borderColor={color}
|
|
||||||
position="absolute"
|
|
||||||
left="0px"
|
|
||||||
bottom="0px"
|
|
||||||
width={size * 2}
|
|
||||||
height={size * 2}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StatusBarJoins() {
|
|
||||||
const pendingJoin = useGroupState(s => s.pendingJoin);
|
|
||||||
if (
|
|
||||||
Object.keys(_.omitBy(pendingJoin, j => j.hidden || j.progress === 'done'))
|
|
||||||
.length === 0
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dropdown
|
|
||||||
dropWidth="325px"
|
|
||||||
options={
|
|
||||||
<Col
|
|
||||||
left="0px"
|
|
||||||
top="120%"
|
|
||||||
position="absolute"
|
|
||||||
zIndex={10}
|
|
||||||
alignItems="flex-start"
|
|
||||||
p="2"
|
|
||||||
gapY="3"
|
|
||||||
border="1"
|
|
||||||
borderColor="lightGray"
|
|
||||||
borderRadius="1"
|
|
||||||
backgroundColor="white"
|
|
||||||
>
|
|
||||||
{Object.keys(pendingJoin).map(g => (
|
|
||||||
<JoinStatus key={g} group={g} join={pendingJoin[g]} />
|
|
||||||
))}
|
|
||||||
</Col>
|
|
||||||
}
|
|
||||||
alignX="left"
|
|
||||||
alignY="bottom"
|
|
||||||
>
|
|
||||||
<StatusBarItem mr="2" width="32px" flexShrink={0} border={0}>
|
|
||||||
<LoadingSpinner foreground="black" />
|
|
||||||
</StatusBarItem>
|
|
||||||
</Dropdown>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const description: string[] = [
|
|
||||||
'Contacting host...',
|
|
||||||
'Retrieving data...',
|
|
||||||
'Finished join',
|
|
||||||
'Unable to join, you do not have the correct permissions',
|
|
||||||
'Internal error, please file an issue'
|
|
||||||
];
|
|
||||||
|
|
||||||
export function JoinStatus({
|
|
||||||
group,
|
|
||||||
join
|
|
||||||
}: {
|
|
||||||
group: string;
|
|
||||||
join: JoinRequest;
|
|
||||||
}) {
|
|
||||||
const { preview } = usePreview(group);
|
|
||||||
const current = join && joinProgress.indexOf(join.progress);
|
|
||||||
const desc = _.isNumber(current) && description[current];
|
|
||||||
const onHide = () => {
|
|
||||||
useGroupState.getState().hidePending(group);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Row alignItems="center" gapX="3">
|
|
||||||
<Col gapY="2">
|
|
||||||
<Row alignItems="center" gapX="2">
|
|
||||||
{preview ? (
|
|
||||||
<MetadataIcon height={4} width={4} metadata={preview.metadata} />
|
|
||||||
) : null}
|
|
||||||
<Text>{preview?.metadata.title || group.slice(6)}</Text>
|
|
||||||
</Row>
|
|
||||||
<Row ml="2" alignItems="center" gapX="2">
|
|
||||||
<Elbow />
|
|
||||||
<Text>{desc}</Text>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Button onClick={onHide}>Hide</Button>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
@ -167,8 +167,15 @@ export function Omnibox(props: OmniboxProps): ReactElement {
|
|||||||
if(shift && app === 'profile') {
|
if(shift && app === 'profile') {
|
||||||
// TODO: hacky, fix
|
// TODO: hacky, fix
|
||||||
link = link.replace('~profile', '~landscape/messages/dm');
|
link = link.replace('~profile', '~landscape/messages/dm');
|
||||||
|
}
|
||||||
|
if(link.startsWith('?')) {
|
||||||
|
history.push({
|
||||||
|
search: link
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
history.push(link);
|
||||||
|
|
||||||
}
|
}
|
||||||
history.push(link);
|
|
||||||
} else {
|
} else {
|
||||||
window.location.href = link;
|
window.location.href = link;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ export function ChannelDetails(props: ChannelDetailsProps) {
|
|||||||
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||||
<Form style={{ display: 'contents' }}>
|
<Form style={{ display: 'contents' }}>
|
||||||
<FormGroupChild id="details" />
|
<FormGroupChild id="details" />
|
||||||
<Col mx={4} mb={4} flexShrink={0} gapY={4}>
|
<Col mx={4} mb={4} flexShrink={0} gapY={4} maxWidth="512px">
|
||||||
<Col mb={3}>
|
<Col mb={3}>
|
||||||
<Text id="details" fontSize={2} fontWeight="bold">
|
<Text id="details" fontSize={2} fontWeight="bold">
|
||||||
Channel Details
|
Channel Details
|
||||||
|
@ -15,6 +15,8 @@ import { useShortcut } from '~/logic/state/settings';
|
|||||||
import Landscape from '~/views/landscape/index';
|
import Landscape from '~/views/landscape/index';
|
||||||
import GraphApp from '../../apps/graph/App';
|
import GraphApp from '../../apps/graph/App';
|
||||||
import { getNotificationRedirect } from '~/logic/lib/notificationRedirects';
|
import { getNotificationRedirect } from '~/logic/lib/notificationRedirects';
|
||||||
|
import {JoinRoute} from './Join/Join';
|
||||||
|
import useInviteState from '~/logic/state/invite';
|
||||||
|
|
||||||
export const Container = styled(Box)`
|
export const Container = styled(Box)`
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@ -27,16 +29,20 @@ export const Content = (props) => {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const mdLoaded = useMetadataState(s => s.loaded);
|
const mdLoaded = useMetadataState(s => s.loaded);
|
||||||
|
const inviteLoaded = useInviteState(s => s.loaded);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const query = new URLSearchParams(location.search);
|
const query = new URLSearchParams(location.search);
|
||||||
if(mdLoaded && query.has('grid-note')) {
|
if(!(mdLoaded && inviteLoaded)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(query.has('grid-note')) {
|
||||||
history.push(getNotificationRedirect(query.get('grid-note')!));
|
history.push(getNotificationRedirect(query.get('grid-note')!));
|
||||||
} else if(mdLoaded && query.has('grid-link')) {
|
} else if(query.has('grid-link')) {
|
||||||
const link = decodeURIComponent(query.get('grid-link')!);
|
const link = decodeURIComponent(query.get('grid-link')!);
|
||||||
history.push(`/perma${link}`);
|
history.push(`/perma${link}`);
|
||||||
}
|
}
|
||||||
}, [location.search, mdLoaded]);
|
}, [location.search, mdLoaded, inviteLoaded]);
|
||||||
|
|
||||||
useShortcut('navForward', useCallback((e) => {
|
useShortcut('navForward', useCallback((e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -68,11 +74,11 @@ export const Content = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
<JoinRoute />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={['/', '/invites/:app/:uid']}
|
path="/" render={p => (
|
||||||
render={p => (
|
|
||||||
<LaunchApp
|
<LaunchApp
|
||||||
location={p.location}
|
location={p.location}
|
||||||
match={p.match}
|
match={p.match}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Col, Row, Text, Icon } from '@tlon/indigo-react';
|
import { Col, Row, Text, Icon } from "@tlon/indigo-react";
|
||||||
import { Metadata } from '@urbit/api';
|
import { Metadata } from "@urbit/api";
|
||||||
import React, { ReactElement, ReactNode } from 'react';
|
import React, { ReactElement, ReactNode } from "react";
|
||||||
import { PropFunc, IconRef } from '~/types';
|
import { PropFunc, IconRef } from "~/types";
|
||||||
import { MetadataIcon } from './MetadataIcon';
|
import { MetadataIcon } from "./MetadataIcon";
|
||||||
import { useCopy } from '~/logic/lib/useCopy';
|
import { useCopy } from "~/logic/lib/useCopy";
|
||||||
interface GroupSummaryProps {
|
interface GroupSummaryProps {
|
||||||
metadata: Metadata;
|
metadata: Metadata;
|
||||||
memberCount: number;
|
memberCount: number;
|
||||||
@ -28,11 +28,12 @@ export function GroupSummary(
|
|||||||
} = props;
|
} = props;
|
||||||
const { doCopy, copyDisplay } = useCopy(
|
const { doCopy, copyDisplay } = useCopy(
|
||||||
`web+urbitgraph://group${resource?.slice(5)}`,
|
`web+urbitgraph://group${resource?.slice(5)}`,
|
||||||
'Copy',
|
"Copy",
|
||||||
'Checkmark'
|
"Checkmark"
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col {...rest} gapY={4} maxWidth={['100%', '288px']}>
|
<Col gapY={4} maxWidth={["100%", "288px"]} {...rest}>
|
||||||
<Row gapX={2} width="100%">
|
<Row gapX={2} width="100%">
|
||||||
<MetadataIcon
|
<MetadataIcon
|
||||||
width="40px"
|
width="40px"
|
||||||
@ -53,9 +54,9 @@ export function GroupSummary(
|
|||||||
{props?.AllowCopy && (
|
{props?.AllowCopy && (
|
||||||
<Icon
|
<Icon
|
||||||
color="gray"
|
color="gray"
|
||||||
icon={props?.locked ? 'Locked' : (copyDisplay as IconRef)}
|
icon={props?.locked ? "Locked" : (copyDisplay as IconRef)}
|
||||||
onClick={!props?.locked ? doCopy : null}
|
onClick={!props?.locked ? doCopy : null}
|
||||||
cursor={props?.locked ? 'default' : 'pointer'}
|
cursor={props?.locked ? "default" : "pointer"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
@ -69,8 +70,8 @@ export function GroupSummary(
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row width="100%">
|
{metadata.description.length > 0 && (
|
||||||
{metadata.description && (
|
<Row width="100%">
|
||||||
<Text
|
<Text
|
||||||
gray
|
gray
|
||||||
width="100%"
|
width="100%"
|
||||||
@ -80,8 +81,8 @@ export function GroupSummary(
|
|||||||
>
|
>
|
||||||
{metadata.description}
|
{metadata.description}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
</Row>
|
||||||
</Row>
|
)}
|
||||||
{children}
|
{children}
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ import { readGroup } from '@urbit/api';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
import { Box } from '@tlon/indigo-react';
|
||||||
import {
|
import {
|
||||||
Route,
|
Route,
|
||||||
RouteComponentProps, Switch
|
RouteComponentProps, Switch
|
||||||
@ -25,7 +26,7 @@ import { NewChannel } from './NewChannel';
|
|||||||
import { PopoverRoutes } from './PopoverRoutes';
|
import { PopoverRoutes } from './PopoverRoutes';
|
||||||
import { Resource } from './Resource';
|
import { Resource } from './Resource';
|
||||||
import { Skeleton } from './Skeleton';
|
import { Skeleton } from './Skeleton';
|
||||||
import airlock from '~/logic/api';
|
import {Join, JoinRoute} from './Join/Join';
|
||||||
|
|
||||||
interface GroupsPaneProps {
|
interface GroupsPaneProps {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@ -59,6 +60,13 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
if (workspace.type !== 'group') {
|
if (workspace.type !== 'group') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { pendingJoin, doneJoin } = useGroupState.getState();
|
||||||
|
const group = getGroupFromWorkspace(workspace)!;
|
||||||
|
if(group in pendingJoin) {
|
||||||
|
doneJoin(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setRecentGroups(gs => _.uniq([workspace.group, ...gs]));
|
setRecentGroups(gs => _.uniq([workspace.group, ...gs]));
|
||||||
};
|
};
|
||||||
@ -175,7 +183,31 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={relativePath('/pending/:ship/:name')}
|
||||||
|
render={(routeProps) => {
|
||||||
|
const { ship, name } = routeProps.match.params as Record<string, string>;
|
||||||
|
const desc = {
|
||||||
|
group: `/ship/${ship}/${name}`,
|
||||||
|
kind: 'graph' as const
|
||||||
|
};
|
||||||
|
return (<Skeleton
|
||||||
|
mobileHide
|
||||||
|
recentGroups={recentGroups}
|
||||||
|
{...props}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
>
|
||||||
|
<Box width="100%">
|
||||||
|
<Join desc={desc} />
|
||||||
|
</Box>
|
||||||
|
</Skeleton>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path={relativePath('/new')}
|
path={relativePath('/new')}
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
|
377
pkg/interface/src/views/landscape/components/Join/Join.tsx
Normal file
377
pkg/interface/src/views/landscape/components/Join/Join.tsx
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
import {
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
Text,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ManagedTextInputField,
|
||||||
|
ManagedCheckboxField,
|
||||||
|
ContinuousProgressBar,
|
||||||
|
} from "@tlon/indigo-react";
|
||||||
|
import { Formik, Form } from "formik";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useHistory, useLocation, useParams } from "react-router-dom";
|
||||||
|
import useGroupState from "~/logic/state/group";
|
||||||
|
import useInviteState, { useInviteForResource } from "~/logic/state/invite";
|
||||||
|
import useMetadataState, { usePreview } from "~/logic/state/metadata";
|
||||||
|
import { decline, Invite } from "@urbit/api";
|
||||||
|
import { join, JoinRequest } from "@urbit/api/groups";
|
||||||
|
import airlock from "~/logic/api";
|
||||||
|
import { joinError, joinResult, joinLoad, JoinProgress } from "@urbit/api";
|
||||||
|
import { useQuery } from "~/logic/lib/useQuery";
|
||||||
|
import { JoinKind, JoinDesc, JoinSkeleton } from "./Skeleton";
|
||||||
|
|
||||||
|
interface InviteWithUid extends Invite {
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormSchema {
|
||||||
|
autojoin: boolean;
|
||||||
|
shareContact: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
autojoin: false,
|
||||||
|
shareContact: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function JoinForm(props: {
|
||||||
|
desc: JoinDesc;
|
||||||
|
dismiss: () => void;
|
||||||
|
invite?: InviteWithUid;
|
||||||
|
}) {
|
||||||
|
const { desc, dismiss, invite } = props;
|
||||||
|
const onSubmit = (values: FormSchema) => {
|
||||||
|
const [, , ship, name] = desc.group.split("/");
|
||||||
|
airlock.poke(
|
||||||
|
join(ship, name, desc.kind, values.autojoin, values.shareContact)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDecline = () => {
|
||||||
|
airlock.poke(decline(desc.kind, invite.uid));
|
||||||
|
dismiss();
|
||||||
|
};
|
||||||
|
const isGroups = desc.kind === "groups";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||||
|
<Form>
|
||||||
|
<Col p="4" gapY="4">
|
||||||
|
{isGroups ? (
|
||||||
|
<ManagedCheckboxField id="autojoin" label="Join all channels" />
|
||||||
|
) : null}
|
||||||
|
<ManagedCheckboxField id="shareContact" label="Share identity" />
|
||||||
|
<Row justifyContent="space-between" width="100%">
|
||||||
|
<Button onClick={dismiss}>Dismiss</Button>
|
||||||
|
<Row gapX="2">
|
||||||
|
{!invite ? null : (
|
||||||
|
<Button onClick={onDecline} destructive type="button">
|
||||||
|
Decline
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button primary type="submit">
|
||||||
|
{!invite ? "Join Group" : "Accept"}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const REQUEST: JoinDesc = {
|
||||||
|
group: "/ship/~bitbet-bolbel/urbit-community",
|
||||||
|
kind: "groups",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function JoinInitial(props: {
|
||||||
|
invite?: InviteWithUid;
|
||||||
|
desc: JoinDesc;
|
||||||
|
modal: boolean;
|
||||||
|
dismiss: () => void;
|
||||||
|
}) {
|
||||||
|
const { desc, dismiss, modal, invite } = props;
|
||||||
|
const title = (() => {
|
||||||
|
const name = desc.kind === "graph" ? "Group Chat" : "Group";
|
||||||
|
if (invite) {
|
||||||
|
return `You've been invited to a ${name}`;
|
||||||
|
} else {
|
||||||
|
return `You're joining a ${name}`;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return (
|
||||||
|
<JoinSkeleton modal={modal} desc={desc} title={title}>
|
||||||
|
<JoinForm invite={invite} dismiss={dismiss} desc={desc} />
|
||||||
|
</JoinSkeleton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function JoinLoading(props: {
|
||||||
|
desc: JoinDesc;
|
||||||
|
modal: boolean;
|
||||||
|
request: JoinRequest;
|
||||||
|
dismiss: () => void;
|
||||||
|
finished: string;
|
||||||
|
}) {
|
||||||
|
const { desc, request, dismiss, modal, finished } = props;
|
||||||
|
const history = useHistory();
|
||||||
|
useEffect(() => {
|
||||||
|
if (desc.kind === "graph" && request.progress === "done") {
|
||||||
|
history.push(finished);
|
||||||
|
}
|
||||||
|
}, [request]);
|
||||||
|
const name = desc.kind === "graph" ? "Group Chat" : "Group";
|
||||||
|
const title = `Joining ${name}, please wait`;
|
||||||
|
const onCancel = () => {
|
||||||
|
useGroupState.getState().abortJoin(desc.group);
|
||||||
|
dismiss();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<JoinSkeleton modal={modal} desc={desc} title={title}>
|
||||||
|
<Col maxWidth="512px" p="4" gapY="4">
|
||||||
|
{joinLoad.indexOf(request.progress as any) !== -1 ? (
|
||||||
|
<JoinProgressIndicator progress={request.progress} />
|
||||||
|
) : null}
|
||||||
|
<Box>
|
||||||
|
<Text>
|
||||||
|
If join seems to take a while, the host of the {name} may be
|
||||||
|
offline, or the connection between you both may be unstable.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Row gapX="2">
|
||||||
|
<Button onClick={dismiss}>Dismiss</Button>
|
||||||
|
<Button destructive onClick={onCancel}>
|
||||||
|
Cancel Join
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</JoinSkeleton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function JoinError(props: {
|
||||||
|
desc: JoinDesc;
|
||||||
|
request: JoinRequest;
|
||||||
|
modal: boolean;
|
||||||
|
}) {
|
||||||
|
const { desc, request, modal } = props;
|
||||||
|
const { preview } = usePreview(desc.group);
|
||||||
|
const group = preview?.metadata?.title ?? desc.group;
|
||||||
|
const title = `Joining ${group} failed`;
|
||||||
|
const explanation =
|
||||||
|
request.progress === "no-perms"
|
||||||
|
? "You do not have the correct permissions"
|
||||||
|
: "An unexpected error occurred";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JoinSkeleton modal={modal} title={title} desc={desc}>
|
||||||
|
<Col p="4" gapY="4">
|
||||||
|
<Text fontWeight="medium">{explanation}</Text>
|
||||||
|
<Row>
|
||||||
|
<Button>Dismiss</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</JoinSkeleton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JoinProps {
|
||||||
|
desc: JoinDesc;
|
||||||
|
redir?: string;
|
||||||
|
modal?: boolean;
|
||||||
|
dismiss?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Join(props: JoinProps) {
|
||||||
|
const { desc, modal, dismiss, redir } = props;
|
||||||
|
const { group, kind } = desc;
|
||||||
|
const [, , ship, name] = group.split("/");
|
||||||
|
const graph = kind === "graph";
|
||||||
|
const finishedPath = !!redir
|
||||||
|
? redir
|
||||||
|
: graph
|
||||||
|
? `/~landscape/messages/resource/chat/${ship}/${name}`
|
||||||
|
: `/~landscape/ship/${ship}/${name}`;
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
const joinRequest = useGroupState((s) => s.pendingJoin[group]);
|
||||||
|
const invite = useInviteForResource(kind, ship, name);
|
||||||
|
|
||||||
|
const isDone = joinRequest && joinRequest.progress === "done";
|
||||||
|
const isErrored =
|
||||||
|
joinRequest && joinError.includes(joinRequest.progress as any);
|
||||||
|
const isLoading =
|
||||||
|
joinRequest && joinLoad.includes(joinRequest.progress as any);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDone && desc.kind == "graph") {
|
||||||
|
history.push(finishedPath);
|
||||||
|
}
|
||||||
|
}, [isDone, desc]);
|
||||||
|
|
||||||
|
return isDone ? (
|
||||||
|
<JoinDone
|
||||||
|
dismiss={dismiss}
|
||||||
|
modal={modal}
|
||||||
|
desc={desc}
|
||||||
|
finished={finishedPath}
|
||||||
|
/>
|
||||||
|
) : isLoading ? (
|
||||||
|
<JoinLoading
|
||||||
|
modal={modal}
|
||||||
|
dismiss={dismiss}
|
||||||
|
desc={desc}
|
||||||
|
request={joinRequest}
|
||||||
|
finished={finishedPath}
|
||||||
|
/>
|
||||||
|
) : isErrored ? (
|
||||||
|
<JoinError modal={modal} desc={desc} request={joinRequest} />
|
||||||
|
) : (
|
||||||
|
<JoinInitial modal={modal} dismiss={dismiss} desc={desc} invite={invite} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PromptFormProps {
|
||||||
|
kind: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PromptFormSchema {
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
export interface JoinPromptProps {
|
||||||
|
kind: string;
|
||||||
|
dismiss?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JoinPrompt(props: JoinPromptProps) {
|
||||||
|
const { kind, dismiss } = props;
|
||||||
|
const { query, appendQuery } = useQuery();
|
||||||
|
const history = useHistory();
|
||||||
|
const initialValues = {
|
||||||
|
link: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async ({ link }: PromptFormSchema) => {
|
||||||
|
const path = `/ship/${link}`;
|
||||||
|
history.push({
|
||||||
|
search: appendQuery({ "join-path": path }),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JoinSkeleton modal body={<Text>a</Text>} title="Join a Group">
|
||||||
|
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||||
|
<Form>
|
||||||
|
<Col p="4" gapY="4">
|
||||||
|
<ManagedTextInputField
|
||||||
|
label="Invite Link"
|
||||||
|
id="link"
|
||||||
|
caption="Enter either a web+urbitgraph:// link or an identifier in the form ~sampel-palnet/group"
|
||||||
|
/>
|
||||||
|
<Row gapX="2">
|
||||||
|
{!!dismiss ? (
|
||||||
|
<Button type="button" onClick={dismiss}>
|
||||||
|
Dismiss
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
<Button type="submit" primary>
|
||||||
|
Join
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</JoinSkeleton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function JoinProgressIndicator(props: { progress: JoinProgress }) {
|
||||||
|
const { progress } = props;
|
||||||
|
const percentage =
|
||||||
|
progress === "done" ? 100 : (joinLoad.indexOf(progress as any) + 1) * 25;
|
||||||
|
|
||||||
|
const description = (() => {
|
||||||
|
switch (progress) {
|
||||||
|
case "start":
|
||||||
|
return "Connecting to host";
|
||||||
|
case "added":
|
||||||
|
return "Retrieving members";
|
||||||
|
case "metadata":
|
||||||
|
return "Retrieving channels";
|
||||||
|
case "done":
|
||||||
|
return "Finished";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col gapY="2">
|
||||||
|
<Text color="lightGray">{description}</Text>
|
||||||
|
<ContinuousProgressBar percentage={percentage} />
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JoinDoneProps {
|
||||||
|
desc: JoinDesc;
|
||||||
|
modal: boolean;
|
||||||
|
finished: string;
|
||||||
|
dismiss: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JoinDone(props: JoinDoneProps) {
|
||||||
|
const { desc, modal, finished, dismiss } = props;
|
||||||
|
const { preview, error } = usePreview(desc.group);
|
||||||
|
const name = desc.kind === "groups" ? "Group" : "Group Chat";
|
||||||
|
const title = `Joined ${name} successfully`;
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const onView = () => {
|
||||||
|
history.push(finished);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JoinSkeleton title={title} modal={modal} desc={desc}>
|
||||||
|
<Col p="4" gapY="4">
|
||||||
|
<JoinProgressIndicator progress="done" />
|
||||||
|
<Row gapX="2">
|
||||||
|
<Button onClick={dismiss}>Dismiss</Button>
|
||||||
|
<Button onClick={onView} primary>
|
||||||
|
View Group
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</JoinSkeleton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JoinRoute(props: { graph?: boolean; modal?: boolean }) {
|
||||||
|
const { modal = false, graph = false } = props;
|
||||||
|
const { query } = useQuery();
|
||||||
|
const history = useHistory();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const kind = query.get("join-kind");
|
||||||
|
const path = query.get("join-path");
|
||||||
|
const redir = query.get("redir");
|
||||||
|
if (!kind) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const desc: JoinDesc = path
|
||||||
|
? {
|
||||||
|
group: path,
|
||||||
|
kind: graph ? "graph" : "groups",
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const dismiss = () => {
|
||||||
|
history.push(pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
return desc ? (
|
||||||
|
<Join desc={desc} modal dismiss={dismiss} redir={redir} />
|
||||||
|
) : (
|
||||||
|
<JoinPrompt kind={kind} dismiss={dismiss} />
|
||||||
|
);
|
||||||
|
}
|
139
pkg/interface/src/views/landscape/components/Join/Skeleton.tsx
Normal file
139
pkg/interface/src/views/landscape/components/Join/Skeleton.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
Text,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ManagedTextInputField,
|
||||||
|
ManagedCheckboxField,
|
||||||
|
ContinuousProgressBar,
|
||||||
|
} from "@tlon/indigo-react";
|
||||||
|
import { ModalOverlay } from "~/views/components/ModalOverlay";
|
||||||
|
import Author from "~/views/components/Author";
|
||||||
|
import { GroupSummary } from "../GroupSummary";
|
||||||
|
|
||||||
|
import { resourceFromPath } from "~/logic/lib/group";
|
||||||
|
|
||||||
|
import useMetadataState, { usePreview } from "~/logic/state/metadata";
|
||||||
|
import useInviteState, { useInviteForResource } from "~/logic/state/invite";
|
||||||
|
import {useHistory} from "react-router-dom";
|
||||||
|
|
||||||
|
const SUMMARY_HEIGHT = "96px";
|
||||||
|
|
||||||
|
export type JoinKind = "graph" | "groups";
|
||||||
|
|
||||||
|
export interface JoinDesc {
|
||||||
|
group: string;
|
||||||
|
kind: JoinKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JoinSkeletonProps {
|
||||||
|
title: string;
|
||||||
|
desc?: JoinDesc;
|
||||||
|
modal: boolean;
|
||||||
|
children: JSX.Element;
|
||||||
|
onJoin?: () => void;
|
||||||
|
body?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JoinSkeleton(props: JoinSkeletonProps) {
|
||||||
|
const { title, body, children, onJoin, desc, modal } = props;
|
||||||
|
const history = useHistory();
|
||||||
|
const dismiss = () => {
|
||||||
|
history.push({ search: '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const inner = (
|
||||||
|
<Col
|
||||||
|
width={modal ? ["90vw", "384px"] : undefined}
|
||||||
|
borderRadius="2"
|
||||||
|
backgroundColor="white"
|
||||||
|
>
|
||||||
|
<Col
|
||||||
|
gapY="4"
|
||||||
|
p="4"
|
||||||
|
borderRadius="2"
|
||||||
|
backgroundColor="washedGray"
|
||||||
|
justifyContent="space-between"
|
||||||
|
flexGrow={1}
|
||||||
|
>
|
||||||
|
<Box maxWidth="512px">
|
||||||
|
<Text fontWeight="medium" fontSize="2">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
{!!desc ? <JoinBody desc={desc} /> : null}
|
||||||
|
</Col>
|
||||||
|
{children}
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
return modal ? (
|
||||||
|
<ModalOverlay dismiss={dismiss}>{inner}</ModalOverlay>
|
||||||
|
) : (
|
||||||
|
inner
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JoinBody(props: { desc: JoinDesc }) {
|
||||||
|
const { desc } = props;
|
||||||
|
const { group, kind } = desc || {};
|
||||||
|
const { preview, error } = usePreview(group);
|
||||||
|
const { ship, name } = resourceFromPath(group);
|
||||||
|
|
||||||
|
const invite = useInviteForResource(kind, ship, name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!desc ? "Prompt invite link" : null}
|
||||||
|
{preview ? (
|
||||||
|
<GroupSummary
|
||||||
|
memberCount={preview.members}
|
||||||
|
channelCount={preview["channel-count"]}
|
||||||
|
metadata={preview.metadata}
|
||||||
|
height={SUMMARY_HEIGHT}
|
||||||
|
width="100%"
|
||||||
|
maxWidth="100%"
|
||||||
|
overflow="hidden"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FallbackSummary path={group} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{invite ? (
|
||||||
|
<Col gapY="2">
|
||||||
|
<Box>
|
||||||
|
<Text>
|
||||||
|
<Text mono>{invite.ship}</Text> <Text gray>invited you</Text>
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
{invite.text?.length > 0 ? (
|
||||||
|
<Box>
|
||||||
|
<Text>"{invite.text}"</Text>
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</Col>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FallbackSummary(props: { path: string }) {
|
||||||
|
const { path } = props;
|
||||||
|
const [, , ship, name] = path.split("/");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
height={SUMMARY_HEIGHT}
|
||||||
|
width="100%"
|
||||||
|
overflow="hidden"
|
||||||
|
alignItems="center"
|
||||||
|
gapX="0"
|
||||||
|
>
|
||||||
|
<Author gray fullNotIcon size={40} showImage ship={ship} dontShowTime />
|
||||||
|
<Text mono whiteSpace="nowrap" overflow="hidden" textOverflow="ellipsis">
|
||||||
|
/{name}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
@ -1,233 +0,0 @@
|
|||||||
import {
|
|
||||||
Box, Col,
|
|
||||||
Icon,
|
|
||||||
ManagedTextInputField as Input, Row,
|
|
||||||
Text,
|
|
||||||
Button
|
|
||||||
} from '@tlon/indigo-react';
|
|
||||||
import { join, MetadataUpdatePreview } from '@urbit/api';
|
|
||||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import urbitOb from 'urbit-ob';
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
import { useQuery } from '~/logic/lib/useQuery';
|
|
||||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
|
||||||
import { getModuleIcon } from '~/logic/lib/util';
|
|
||||||
import useGroupState from '~/logic/state/group';
|
|
||||||
import useMetadataState from '~/logic/state/metadata';
|
|
||||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
|
||||||
import { FormError } from '~/views/components/FormError';
|
|
||||||
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
|
|
||||||
import { GroupSummary } from './GroupSummary';
|
|
||||||
import airlock from '~/logic/api';
|
|
||||||
|
|
||||||
const formSchema = Yup.object({
|
|
||||||
group: Yup.string()
|
|
||||||
.required('Must provide group to join')
|
|
||||||
.test('is-valid', 'Invalid group', (group: string | null | undefined) => {
|
|
||||||
if (!group) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const [patp, name] = group.split('/');
|
|
||||||
return urbitOb.isValidPatp(patp) && name.length > 0;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
interface FormSchema {
|
|
||||||
group: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface JoinGroupProps {
|
|
||||||
autojoin?: string;
|
|
||||||
dismiss?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Autojoin(props: { autojoin: string | null }) {
|
|
||||||
const { submitForm } = useFormikContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.autojoin) {
|
|
||||||
submitForm();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JoinGroup(props: JoinGroupProps): ReactElement {
|
|
||||||
const { autojoin, dismiss } = props;
|
|
||||||
const { associations, getPreview } = useMetadataState();
|
|
||||||
const [timedOut, setTimedOut] = useState(false);
|
|
||||||
const groups = useGroupState(state => state.groups);
|
|
||||||
const history = useHistory();
|
|
||||||
const initialValues: FormSchema = {
|
|
||||||
group: autojoin || ''
|
|
||||||
};
|
|
||||||
const [preview, setPreview] = useState<
|
|
||||||
MetadataUpdatePreview | string | null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
const waiter = useWaitForProps({ associations, groups }, _.isString(preview) ? 1 : 30000);
|
|
||||||
|
|
||||||
const { query } = useQuery();
|
|
||||||
|
|
||||||
const onConfirm = useCallback(async (group: string) => {
|
|
||||||
const [,,ship,name] = group.split('/');
|
|
||||||
if (group in groups) {
|
|
||||||
return history.push(`/~landscape${group}`);
|
|
||||||
}
|
|
||||||
await airlock.poke(join(ship, name));
|
|
||||||
try {
|
|
||||||
await waiter((p) => {
|
|
||||||
return group in p.groups &&
|
|
||||||
(group in (p.associations?.graph ?? {})
|
|
||||||
|| group in (p.associations?.groups ?? {}));
|
|
||||||
});
|
|
||||||
|
|
||||||
if(query.has('redir')) {
|
|
||||||
const redir = query.get('redir')!;
|
|
||||||
history.push(redir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groups?.[group]?.hidden) {
|
|
||||||
const { metadata } = associations.graph[group];
|
|
||||||
if (metadata?.config && 'graph' in metadata.config) {
|
|
||||||
history.push(`/~landscape/home/resource/${metadata.config.graph}${group}`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
history.push(`/~landscape${group}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setTimedOut(true);
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}, [waiter, history, associations, groups]);
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
|
||||||
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
|
||||||
const [ship, name] = values.group.split('/');
|
|
||||||
const path = `/ship/${ship}/${name}`;
|
|
||||||
if (path in groups) {
|
|
||||||
return history.push(`/~landscape${path}`);
|
|
||||||
}
|
|
||||||
// skip if it's unmanaged
|
|
||||||
try {
|
|
||||||
const prev = await getPreview(path);
|
|
||||||
actions.setStatus({ success: null });
|
|
||||||
setPreview(prev);
|
|
||||||
} catch (e) {
|
|
||||||
if (e === 'no-permissions') {
|
|
||||||
actions.setStatus({
|
|
||||||
error:
|
|
||||||
'Unable to join group, you do not have the correct permissions'
|
|
||||||
});
|
|
||||||
} else if (e === 'offline') {
|
|
||||||
setPreview(path);
|
|
||||||
} else {
|
|
||||||
actions.setStatus({ error: 'Unknown error' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[waiter, history, onConfirm]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col p={3}>
|
|
||||||
<Box mb={3}>
|
|
||||||
<Text fontSize={2} fontWeight="bold">
|
|
||||||
Join a Group
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
{ timedOut ? (
|
|
||||||
<Col width="100%" gapY={4}>
|
|
||||||
<Text>The host is not responding. You will receive a notification when the join requests succeeds
|
|
||||||
</Text>
|
|
||||||
<Button primary onClick={dismiss}>
|
|
||||||
Dismiss
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</Col>
|
|
||||||
) : _.isString(preview) ? (
|
|
||||||
|
|
||||||
<Col width="100%" gapY={4}>
|
|
||||||
<Text>The host appears to be offline. Join anyway?</Text>
|
|
||||||
<StatelessAsyncButton
|
|
||||||
primary
|
|
||||||
name="join"
|
|
||||||
onClick={() => onConfirm(preview)}
|
|
||||||
>
|
|
||||||
Join anyway
|
|
||||||
</StatelessAsyncButton>
|
|
||||||
</Col>
|
|
||||||
) : preview ? (
|
|
||||||
<>
|
|
||||||
<GroupSummary
|
|
||||||
metadata={preview.metadata}
|
|
||||||
memberCount={preview?.members}
|
|
||||||
channelCount={preview?.['channel-count']}
|
|
||||||
>
|
|
||||||
{ Object.keys(preview.channels).length > 0 && (
|
|
||||||
<Col
|
|
||||||
gapY={2}
|
|
||||||
p={2}
|
|
||||||
borderRadius={2}
|
|
||||||
border={1}
|
|
||||||
borderColor="washedGray"
|
|
||||||
bg="washedBlue"
|
|
||||||
maxHeight="300px"
|
|
||||||
overflowY="auto"
|
|
||||||
>
|
|
||||||
<Text gray fontSize={1}>
|
|
||||||
Channels
|
|
||||||
</Text>
|
|
||||||
<Box width="100%" flexShrink={0}>
|
|
||||||
{Object.values(preview.channels).map(({ metadata }: any, i) => (
|
|
||||||
<Row key={i} width="100%">
|
|
||||||
<Icon
|
|
||||||
mr={2}
|
|
||||||
color="blue"
|
|
||||||
icon={getModuleIcon(metadata?.config?.graph) as any}
|
|
||||||
/>
|
|
||||||
<Text color="blue">{metadata.title} </Text>
|
|
||||||
</Row>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
</GroupSummary>
|
|
||||||
<StatelessAsyncButton
|
|
||||||
marginTop={3}
|
|
||||||
primary
|
|
||||||
name="join"
|
|
||||||
onClick={() => onConfirm(preview.group)}
|
|
||||||
>
|
|
||||||
Join {preview.metadata.title}
|
|
||||||
</StatelessAsyncButton>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Col width="100%" gapY={4}>
|
|
||||||
<Formik
|
|
||||||
validationSchema={formSchema}
|
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
<Form style={{ display: 'contents' }}>
|
|
||||||
<Autojoin autojoin={autojoin ?? null} />
|
|
||||||
<Input
|
|
||||||
id="group"
|
|
||||||
label="Group"
|
|
||||||
caption="What group are you joining?"
|
|
||||||
placeholder="~sampel-palnet/test-group"
|
|
||||||
/>
|
|
||||||
<AsyncButton mt={4}>Join Group</AsyncButton>
|
|
||||||
<FormError mt={4} />
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
}
|
|
@ -13,11 +13,12 @@ import Dot from '~/views/components/Dot';
|
|||||||
import { useHarkDm, useHarkStat } from '~/logic/state/hark';
|
import { useHarkDm, useHarkStat } from '~/logic/state/hark';
|
||||||
import useSettingsState from '~/logic/state/settings';
|
import useSettingsState from '~/logic/state/settings';
|
||||||
import useGraphState from '~/logic/state/graph';
|
import useGraphState from '~/logic/state/graph';
|
||||||
|
import {usePreview} from '~/logic/state/metadata';
|
||||||
|
|
||||||
function useAssociationStatus(resource: string) {
|
function useAssociationStatus(resource: string) {
|
||||||
const [, , ship, name] = resource.split('/');
|
const [, , ship, name] = resource.split("/");
|
||||||
const graphKey = `${deSig(ship)}/${name}`;
|
const graphKey = `${deSig(ship)}/${name}`;
|
||||||
const isSubscribed = useGraphState(s => s.graphKeys.has(graphKey));
|
const isSubscribed = useGraphState((s) => s.graphKeys.has(graphKey));
|
||||||
const stats = useHarkStat(`/graph/~${graphKey}`);
|
const stats = useHarkStat(`/graph/~${graphKey}`);
|
||||||
const { count, each } = stats;
|
const { count, each } = stats;
|
||||||
const hasNotifications = false;
|
const hasNotifications = false;
|
||||||
@ -43,6 +44,7 @@ function SidebarItemBase(props: {
|
|||||||
title: string | ReactNode;
|
title: string | ReactNode;
|
||||||
mono?: boolean;
|
mono?: boolean;
|
||||||
pending?: boolean;
|
pending?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
@ -53,22 +55,24 @@ function SidebarItemBase(props: {
|
|||||||
hasUnread,
|
hasUnread,
|
||||||
isSynced = false,
|
isSynced = false,
|
||||||
mono = false,
|
mono = false,
|
||||||
pending = false
|
pending = false,
|
||||||
|
onClick
|
||||||
} = props;
|
} = props;
|
||||||
const color = isSynced
|
const color = isSynced
|
||||||
? hasUnread || hasNotification
|
? hasUnread || hasNotification
|
||||||
? 'black'
|
? "black"
|
||||||
: 'gray'
|
: "gray"
|
||||||
: 'lightGray';
|
: "lightGray";
|
||||||
|
|
||||||
const fontWeight = hasUnread || hasNotification ? '500' : 'normal';
|
const fontWeight = hasUnread || hasNotification ? "500" : "normal";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverBoxLink
|
<HoverBoxLink
|
||||||
// ref={anchorRef}
|
// ref={anchorRef}
|
||||||
to={to}
|
to={to}
|
||||||
bg={pending ? 'lightBlue' : 'white'}
|
onClick={onClick}
|
||||||
bgActive={pending ? 'washedBlue' : 'washedGray'}
|
bg={pending ? "lightBlue" : "white"}
|
||||||
|
bgActive={pending ? "washedBlue" : "washedGray"}
|
||||||
width="100%"
|
width="100%"
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
@ -108,7 +112,7 @@ function SidebarItemBase(props: {
|
|||||||
mono={mono}
|
mono={mono}
|
||||||
color={color}
|
color={color}
|
||||||
fontWeight={fontWeight}
|
fontWeight={fontWeight}
|
||||||
style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}
|
style={{ textOverflow: "ellipsis", whiteSpace: "pre" }}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
@ -118,156 +122,201 @@ function SidebarItemBase(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarDmItem = React.memo((props: {
|
export const SidebarPendingItem = (props: {
|
||||||
ship: string;
|
path: string;
|
||||||
selected?: boolean;
|
|
||||||
workspace: Workspace;
|
|
||||||
pending?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { ship, selected = false, pending = false } = props;
|
|
||||||
const contact = useContact(ship);
|
|
||||||
const { hideAvatars, hideNicknames } = useSettingsState(s => s.calm);
|
|
||||||
const title =
|
|
||||||
!hideNicknames && contact?.nickname
|
|
||||||
? contact?.nickname
|
|
||||||
: cite(ship) ?? ship;
|
|
||||||
const { count, each } = useHarkDm(ship);
|
|
||||||
const unreads = count + each.length;
|
|
||||||
const img =
|
|
||||||
contact?.avatar && !hideAvatars ? (
|
|
||||||
<BaseImage
|
|
||||||
referrerPolicy="no-referrer"
|
|
||||||
src={contact.avatar}
|
|
||||||
width="16px"
|
|
||||||
height="16px"
|
|
||||||
borderRadius={2}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Sigil
|
|
||||||
ship={ship}
|
|
||||||
color={`#${uxToHex(contact?.color || '0x0')}`}
|
|
||||||
icon
|
|
||||||
padding={2}
|
|
||||||
size={16}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarItemBase
|
|
||||||
selected={selected}
|
|
||||||
hasNotification={false}
|
|
||||||
hasUnread={(unreads as number) > 0}
|
|
||||||
to={`/~landscape/messages/dm/${ship}`}
|
|
||||||
title={title}
|
|
||||||
mono={hideAvatars || !contact?.nickname}
|
|
||||||
isSynced
|
|
||||||
pending={pending}
|
|
||||||
>
|
|
||||||
{img}
|
|
||||||
</SidebarItemBase>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line max-lines-per-function
|
|
||||||
export const SidebarAssociationItem = React.memo((props: {
|
|
||||||
hideUnjoined: boolean;
|
|
||||||
association: Association;
|
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
workspace: Workspace;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { association, selected } = props;
|
const { path, selected } = props;
|
||||||
const title = getItemTitle(association) || '';
|
const { preview, error } = usePreview(path);
|
||||||
const appName = association?.['app-name'];
|
const color = `#${uxToHex(preview?.metadata?.color || "0x0")}`;
|
||||||
let mod: string = appName;
|
const title = preview?.metadata?.title || path;
|
||||||
if (association?.metadata?.config && 'graph' in association.metadata.config) {
|
const to = `/~landscape/messages/pending/${path.slice(6)}`;
|
||||||
mod = association.metadata.config.graph ;
|
|
||||||
}
|
|
||||||
const rid = association?.resource;
|
|
||||||
const groupPath = association?.group;
|
|
||||||
const group = useGroupState(state => state.groups[groupPath]);
|
|
||||||
const { hideNicknames } = useSettingsState(s => s.calm);
|
|
||||||
const contacts = useContactState(s => s.contacts);
|
|
||||||
const isUnmanaged = group?.hidden || false;
|
|
||||||
const DM = isUnmanaged && props.workspace?.type === 'messages';
|
|
||||||
const itemStatus = useAssociationStatus(rid);
|
|
||||||
const hasNotification = itemStatus === 'notification';
|
|
||||||
const hasUnread = itemStatus === 'unread';
|
|
||||||
const isSynced = itemStatus !== 'unsubscribed';
|
|
||||||
let baseUrl = `/~landscape${groupPath}`;
|
|
||||||
|
|
||||||
if (DM) {
|
|
||||||
baseUrl = '/~landscape/messages';
|
|
||||||
} else if (isUnmanaged) {
|
|
||||||
baseUrl = '/~landscape/home';
|
|
||||||
}
|
|
||||||
|
|
||||||
const to = isSynced
|
|
||||||
? `${baseUrl}/resource/${mod}${rid}`
|
|
||||||
: `${baseUrl}/join/${mod}${rid}`;
|
|
||||||
|
|
||||||
if (props.hideUnjoined && !isSynced) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const participantNames = (str: string) => {
|
|
||||||
const color = isSynced
|
|
||||||
? hasUnread || hasNotification
|
|
||||||
? 'black'
|
|
||||||
: 'gray'
|
|
||||||
: 'lightGray';
|
|
||||||
if (_.includes(str, ',') && _.startsWith(str, '~')) {
|
|
||||||
const names = _.split(str, ', ');
|
|
||||||
return names.map((name, idx) => {
|
|
||||||
if (urbitOb.isValidPatp(name)) {
|
|
||||||
if (contacts[name]?.nickname && !hideNicknames)
|
|
||||||
return (
|
|
||||||
<Text key={name} bold={hasUnread} color={color}>
|
|
||||||
{contacts[name]?.nickname}
|
|
||||||
{idx + 1 != names.length ? ', ' : null}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Text key={name} mono bold={hasUnread} color={color}>
|
|
||||||
{name}
|
|
||||||
<Text color={color}>{idx + 1 != names.length ? ', ' : null}</Text>
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarItemBase
|
<SidebarItemBase
|
||||||
to={to}
|
to={to}
|
||||||
|
title={title}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
hasUnread={hasUnread}
|
hasNotification={false}
|
||||||
isSynced={isSynced}
|
hasUnread={false}
|
||||||
title={
|
pending
|
||||||
DM && !urbitOb.isValidPatp(title) ? participantNames(title) : title
|
|
||||||
}
|
|
||||||
hasNotification={hasNotification}
|
|
||||||
>
|
>
|
||||||
{DM ? (
|
<Box
|
||||||
<Box
|
flexShrink={0}
|
||||||
flexShrink={0}
|
height={16}
|
||||||
height={16}
|
width={16}
|
||||||
width={16}
|
borderRadius={2}
|
||||||
borderRadius={2}
|
backgroundColor={color}
|
||||||
backgroundColor={
|
/>
|
||||||
`#${uxToHex(props?.association?.metadata?.color)}` || '#000000'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Icon
|
|
||||||
display="block"
|
|
||||||
color={isSynced ? 'black' : 'lightGray'}
|
|
||||||
icon={getModuleIcon(mod as any)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SidebarItemBase>
|
</SidebarItemBase>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
export const SidebarDmItem = React.memo(
|
||||||
|
(props: {
|
||||||
|
ship: string;
|
||||||
|
selected?: boolean;
|
||||||
|
workspace: Workspace;
|
||||||
|
pending?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { ship, selected = false, pending = false } = props;
|
||||||
|
const contact = useContact(ship);
|
||||||
|
const { hideAvatars, hideNicknames } = useSettingsState((s) => s.calm);
|
||||||
|
const title =
|
||||||
|
!hideNicknames && contact?.nickname
|
||||||
|
? contact?.nickname
|
||||||
|
: cite(ship) ?? ship;
|
||||||
|
const { count, each } = useHarkDm(ship);
|
||||||
|
const unreads = count + each.length;
|
||||||
|
const img =
|
||||||
|
contact?.avatar && !hideAvatars ? (
|
||||||
|
<BaseImage
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
src={contact.avatar}
|
||||||
|
width="16px"
|
||||||
|
height="16px"
|
||||||
|
borderRadius={2}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Sigil
|
||||||
|
ship={ship}
|
||||||
|
color={`#${uxToHex(contact?.color || "0x0")}`}
|
||||||
|
icon
|
||||||
|
padding={2}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarItemBase
|
||||||
|
selected={selected}
|
||||||
|
hasNotification={false}
|
||||||
|
hasUnread={(unreads as number) > 0}
|
||||||
|
to={`/~landscape/messages/dm/${ship}`}
|
||||||
|
title={title}
|
||||||
|
mono={hideAvatars || !contact?.nickname}
|
||||||
|
isSynced
|
||||||
|
pending={pending}
|
||||||
|
>
|
||||||
|
{img}
|
||||||
|
</SidebarItemBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line max-lines-per-function
|
||||||
|
export const SidebarAssociationItem = React.memo(
|
||||||
|
(props: {
|
||||||
|
hideUnjoined: boolean;
|
||||||
|
association: Association;
|
||||||
|
selected: boolean;
|
||||||
|
workspace: Workspace;
|
||||||
|
}) => {
|
||||||
|
const { association, selected } = props;
|
||||||
|
const title = association ? getItemTitle(association) || "" : "";
|
||||||
|
const appName = association?.["app-name"];
|
||||||
|
let mod: string = appName;
|
||||||
|
if (
|
||||||
|
association?.metadata?.config &&
|
||||||
|
"graph" in association.metadata.config
|
||||||
|
) {
|
||||||
|
mod = association.metadata.config.graph;
|
||||||
|
}
|
||||||
|
const pending = useGroupState(s => association.group in s.pendingJoin);
|
||||||
|
console.log(pending);
|
||||||
|
const rid = association?.resource;
|
||||||
|
const { hideNicknames } = useSettingsState((s) => s.calm);
|
||||||
|
const contacts = useContactState((s) => s.contacts);
|
||||||
|
const group = useGroupState(s => association ? s.groups[association.group] : undefined);
|
||||||
|
const isUnmanaged = group?.hidden || false;
|
||||||
|
const DM = isUnmanaged && props.workspace?.type === "messages";
|
||||||
|
const itemStatus = useAssociationStatus(rid);
|
||||||
|
const hasNotification = itemStatus === "notification";
|
||||||
|
const hasUnread = itemStatus === "unread";
|
||||||
|
const isSynced = itemStatus !== "unsubscribed";
|
||||||
|
let baseUrl = `/~landscape${association.group}`;
|
||||||
|
|
||||||
|
if (DM) {
|
||||||
|
baseUrl = "/~landscape/messages";
|
||||||
|
} else if (isUnmanaged) {
|
||||||
|
baseUrl = "/~landscape/home";
|
||||||
|
}
|
||||||
|
|
||||||
|
const to = isSynced
|
||||||
|
? `${baseUrl}/resource/${mod}${rid}`
|
||||||
|
: `${baseUrl}/join/${mod}${rid}`;
|
||||||
|
|
||||||
|
const onClick = pending ? () => {
|
||||||
|
useGroupState.getState().doneJoin(rid);
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
if (props.hideUnjoined && !isSynced) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const participantNames = (str: string) => {
|
||||||
|
const color = isSynced
|
||||||
|
? hasUnread || hasNotification
|
||||||
|
? "black"
|
||||||
|
: "gray"
|
||||||
|
: "lightGray";
|
||||||
|
if (_.includes(str, ",") && _.startsWith(str, "~")) {
|
||||||
|
const names = _.split(str, ", ");
|
||||||
|
return names.map((name, idx) => {
|
||||||
|
if (urbitOb.isValidPatp(name)) {
|
||||||
|
if (contacts[name]?.nickname && !hideNicknames)
|
||||||
|
return (
|
||||||
|
<Text key={name} bold={hasUnread} color={color}>
|
||||||
|
{contacts[name]?.nickname}
|
||||||
|
{idx + 1 != names.length ? ", " : null}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Text key={name} mono bold={hasUnread} color={color}>
|
||||||
|
{name}
|
||||||
|
<Text color={color}>
|
||||||
|
{idx + 1 != names.length ? ", " : null}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarItemBase
|
||||||
|
to={to}
|
||||||
|
selected={selected}
|
||||||
|
hasUnread={hasUnread}
|
||||||
|
isSynced={isSynced}
|
||||||
|
title={
|
||||||
|
DM && !urbitOb.isValidPatp(title) ? participantNames(title) : title
|
||||||
|
}
|
||||||
|
hasNotification={hasNotification}
|
||||||
|
pending={pending}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{DM ? (
|
||||||
|
<Box
|
||||||
|
flexShrink={0}
|
||||||
|
height={16}
|
||||||
|
width={16}
|
||||||
|
borderRadius={2}
|
||||||
|
backgroundColor={
|
||||||
|
`#${uxToHex(props?.association?.metadata?.color)}` || "#000000"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
display="block"
|
||||||
|
color={isSynced ? "black" : "lightGray"}
|
||||||
|
icon={getModuleIcon(mod as any)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</SidebarItemBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -3,7 +3,7 @@ import { Associations, Graph, Unreads } from '@urbit/api';
|
|||||||
import { patp, patp2dec } from 'urbit-ob';
|
import { patp, patp2dec } from 'urbit-ob';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { SidebarAssociationItem, SidebarDmItem } from './SidebarItem';
|
import { SidebarAssociationItem, SidebarDmItem, SidebarPendingItem } from './SidebarItem';
|
||||||
import useGraphState, { useInbox } from '~/logic/state/graph';
|
import useGraphState, { useInbox } from '~/logic/state/graph';
|
||||||
import useHarkState from '~/logic/state/hark';
|
import useHarkState from '~/logic/state/hark';
|
||||||
import { alphabeticalOrder, getResourcePath, modulo } from '~/logic/lib/util';
|
import { alphabeticalOrder, getResourcePath, modulo } from '~/logic/lib/util';
|
||||||
@ -12,8 +12,10 @@ import { Workspace } from '~/types/workspace';
|
|||||||
import useMetadataState from '~/logic/state/metadata';
|
import useMetadataState from '~/logic/state/metadata';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { useShortcut } from '~/logic/state/settings';
|
import { useShortcut } from '~/logic/state/settings';
|
||||||
|
import useGroupState from '~/logic/state/group';
|
||||||
|
import useInviteState from '~/logic/state/invite';
|
||||||
|
|
||||||
function sidebarSort(unreads: Unreads, pending: Set<string>): Record<SidebarSort, (a: string, b: string) => number> {
|
function sidebarSort(unreads: Unreads, pending: string[]): Record<SidebarSort, (a: string, b: string) => number> {
|
||||||
const { associations } = useMetadataState.getState();
|
const { associations } = useMetadataState.getState();
|
||||||
const alphabetical = (a: string, b: string) => {
|
const alphabetical = (a: string, b: string) => {
|
||||||
const aAssoc = associations[a];
|
const aAssoc = associations[a];
|
||||||
@ -25,8 +27,8 @@ function sidebarSort(unreads: Unreads, pending: Set<string>): Record<SidebarSort
|
|||||||
};
|
};
|
||||||
|
|
||||||
const lastUpdated = (a: string, b: string) => {
|
const lastUpdated = (a: string, b: string) => {
|
||||||
const aPend = pending.has(a.slice(1));
|
const aPend = pending.includes(a);
|
||||||
const bPend = pending.has(b.slice(1));
|
const bPend = pending.includes(b);
|
||||||
if(aPend && !bPend) {
|
if(aPend && !bPend) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -50,7 +52,7 @@ function sidebarSort(unreads: Unreads, pending: Set<string>): Record<SidebarSort
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItems(associations: Associations, workspace: Workspace, inbox: Graph, pending: Set<string>) {
|
function getItems(associations: Associations, workspace: Workspace, inbox: Graph, pending: string[]) {
|
||||||
const filtered = Object.keys(associations.graph).filter((a) => {
|
const filtered = Object.keys(associations.graph).filter((a) => {
|
||||||
const assoc = associations.graph[a];
|
const assoc = associations.graph[a];
|
||||||
if(!('graph' in assoc.metadata.config)) {
|
if(!('graph' in assoc.metadata.config)) {
|
||||||
@ -84,9 +86,9 @@ function getItems(associations: Associations, workspace: Workspace, inbox: Graph
|
|||||||
: inbox.keys().map(x => patp(x.toString()));
|
: inbox.keys().map(x => patp(x.toString()));
|
||||||
const pend = workspace.type !== 'messages'
|
const pend = workspace.type !== 'messages'
|
||||||
? []
|
? []
|
||||||
: Array.from(pending).map(s => `~${s}`);
|
: pending
|
||||||
|
|
||||||
return [...filtered, ..._.union(direct, pend)];
|
return _.union(direct, pend, filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SidebarList(props: {
|
export function SidebarList(props: {
|
||||||
@ -98,9 +100,18 @@ export function SidebarList(props: {
|
|||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { selected, config, workspace } = props;
|
const { selected, config, workspace } = props;
|
||||||
const associations = useMetadataState(state => state.associations);
|
const associations = useMetadataState(state => state.associations);
|
||||||
|
const groups = useGroupState(s => s.groups);
|
||||||
const inbox = useInbox();
|
const inbox = useInbox();
|
||||||
const graphKeys = useGraphState(s => s.graphKeys);
|
const graphKeys = useGraphState(s => s.graphKeys);
|
||||||
const pending = useGraphState(s => s.pendingDms);
|
const pendingDms = useGraphState(s => [...s.pendingDms].map(s => `~${s}`));
|
||||||
|
const pendingGroupChats = useGroupState(s => _.pickBy(s.pendingJoin, (req, rid) => !(rid in groups) && req.app === 'graph'));
|
||||||
|
const inviteGroupChats = useInviteState(
|
||||||
|
s => Object.values(s.invites?.['graph'] || {})
|
||||||
|
.map(inv => {
|
||||||
|
return `/ship/~${inv.resource.ship}/${inv.resource.name}`
|
||||||
|
}).filter(group => !(group in groups))
|
||||||
|
);
|
||||||
|
const pending = [...pendingDms, ...Object.keys(pendingGroupChats), ...inviteGroupChats];
|
||||||
const unreads = useHarkState(s => s.unreads);
|
const unreads = useHarkState(s => s.unreads);
|
||||||
|
|
||||||
const ordered = getItems(associations, workspace, inbox, pending)
|
const ordered = getItems(associations, workspace, inbox, pending)
|
||||||
@ -118,10 +129,16 @@ export function SidebarList(props: {
|
|||||||
if(newChannel.startsWith('~')) {
|
if(newChannel.startsWith('~')) {
|
||||||
path = `/~landscape/messages/dm/${newChannel}`;
|
path = `/~landscape/messages/dm/${newChannel}`;
|
||||||
} else {
|
} else {
|
||||||
const { metadata, resource } = associations.graph[ordered[newIdx]];
|
const association = associations.graph[ordered[newIdx]];
|
||||||
const joined = graphKeys.has(resource.slice(7));
|
if(!association) {
|
||||||
if ('graph' in metadata.config) {
|
path = `/~landscape/messages`
|
||||||
path = getResourcePath(workspace, resource, joined, metadata.config.graph);
|
return;
|
||||||
|
} else {
|
||||||
|
const { metadata, resource } = association;
|
||||||
|
const joined = graphKeys.has(resource.slice(7));
|
||||||
|
if ('graph' in metadata.config) {
|
||||||
|
path = getResourcePath(workspace, resource, joined, metadata.config.graph);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
history.push(path);
|
history.push(path);
|
||||||
@ -140,7 +157,22 @@ export function SidebarList(props: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ordered.map((pathOrShip) => {
|
{ordered.map((pathOrShip) => {
|
||||||
return pathOrShip.startsWith('/') ? (
|
return pathOrShip.startsWith('~') ? (
|
||||||
|
<SidebarDmItem
|
||||||
|
key={pathOrShip}
|
||||||
|
ship={pathOrShip}
|
||||||
|
workspace={workspace}
|
||||||
|
selected={pathOrShip === selected}
|
||||||
|
pending={pending.includes(pathOrShip)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
) : pending.includes(pathOrShip) ? (
|
||||||
|
<SidebarPendingItem
|
||||||
|
key={pathOrShip}
|
||||||
|
path={pathOrShip}
|
||||||
|
selected={pathOrShip === selected}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<SidebarAssociationItem
|
<SidebarAssociationItem
|
||||||
key={pathOrShip}
|
key={pathOrShip}
|
||||||
selected={pathOrShip === selected}
|
selected={pathOrShip === selected}
|
||||||
@ -148,16 +180,7 @@ export function SidebarList(props: {
|
|||||||
hideUnjoined={config.hideUnjoined}
|
hideUnjoined={config.hideUnjoined}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
/>
|
/>
|
||||||
) : (
|
) ;
|
||||||
<SidebarDmItem
|
|
||||||
key={pathOrShip}
|
|
||||||
ship={pathOrShip}
|
|
||||||
workspace={workspace}
|
|
||||||
selected={pathOrShip === selected}
|
|
||||||
pending={pending.has(pathOrShip.slice(1))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,6 @@ import useHarkState from '~/logic/state/hark';
|
|||||||
import { Workspace } from '~/types/workspace';
|
import { Workspace } from '~/types/workspace';
|
||||||
import { Body } from '../components/Body';
|
import { Body } from '../components/Body';
|
||||||
import { GroupsPane } from './components/GroupsPane';
|
import { GroupsPane } from './components/GroupsPane';
|
||||||
import { JoinGroup } from './components/JoinGroup';
|
|
||||||
import { NewGroup } from './components/NewGroup';
|
import { NewGroup } from './components/NewGroup';
|
||||||
import './css/custom.css';
|
import './css/custom.css';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@ -75,22 +74,6 @@ export default function Landscape() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Body>
|
</Body>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/~landscape/join/:ship?/:name?"
|
|
||||||
render={(routeProps) => {
|
|
||||||
const { ship, name } = routeProps.match.params;
|
|
||||||
const autojoin = ship && name ? `${ship}/${name}` : undefined;
|
|
||||||
return (
|
|
||||||
<Body>
|
|
||||||
<Box maxWidth="300px">
|
|
||||||
<JoinGroup
|
|
||||||
autojoin={autojoin}
|
|
||||||
{...routeProps}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Body>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
8
pkg/interface/webterm/api.ts
Normal file
8
pkg/interface/webterm/api.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Urbit from '@urbit/http-api';
|
||||||
|
const api = new Urbit('', '', (window as any).desk);
|
||||||
|
api.ship = window.ship;
|
||||||
|
// api.verbose = true;
|
||||||
|
// @ts-ignore TODO window typings
|
||||||
|
window.api = api;
|
||||||
|
|
||||||
|
export default api;
|
@ -1,50 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default class Api {
|
|
||||||
ship: any;
|
|
||||||
channel: any;
|
|
||||||
bindPaths: any[];
|
|
||||||
constructor(ship, channel) {
|
|
||||||
this.ship = ship;
|
|
||||||
this.channel = channel;
|
|
||||||
this.bindPaths = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
bind(path, method, ship = this.ship, appl = 'herm', success, fail) {
|
|
||||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
|
||||||
|
|
||||||
(window as any).subscriptionId = this.channel.subscribe(ship, appl, path,
|
|
||||||
(err) => {
|
|
||||||
fail(err);
|
|
||||||
},
|
|
||||||
(event) => {
|
|
||||||
success({
|
|
||||||
data: event,
|
|
||||||
from: {
|
|
||||||
ship,
|
|
||||||
path
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
fail(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
belt(belt) {
|
|
||||||
return this.action('herm', 'belt', belt);
|
|
||||||
}
|
|
||||||
|
|
||||||
action(appl, mark, data) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.channel.poke(window.ship, appl, mark, data,
|
|
||||||
(json) => {
|
|
||||||
resolve(json);
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +1,471 @@
|
|||||||
import { Box, Col } from '@tlon/indigo-react';
|
/* eslint-disable max-lines */
|
||||||
import React, { Component } from 'react';
|
import React, {
|
||||||
import dark from '@tlon/indigo-dark';
|
useEffect,
|
||||||
import light from '@tlon/indigo-light';
|
useRef,
|
||||||
|
useCallback
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import useTermState from './state';
|
||||||
|
import { useDark } from './join';
|
||||||
|
import api from './api';
|
||||||
|
|
||||||
|
import { Terminal, ITerminalOptions, ITheme } from 'xterm';
|
||||||
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
|
import { Box, Col, Reset, _dark, _light } from '@tlon/indigo-react';
|
||||||
|
|
||||||
|
import 'xterm/css/xterm.css';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Belt, Blit, Stye, Stub, Tint, Deco,
|
||||||
|
pokeTask, pokeBelt
|
||||||
|
} from '@urbit/api/term';
|
||||||
|
|
||||||
|
import bel from './lib/bel';
|
||||||
import { ThemeProvider } from 'styled-components';
|
import { ThemeProvider } from 'styled-components';
|
||||||
import Api from './api';
|
|
||||||
import { History } from './components/history';
|
|
||||||
import { Input } from './components/input';
|
|
||||||
import './css/custom.css';
|
|
||||||
import Store from './store';
|
|
||||||
import Subscription from './subscription';
|
|
||||||
import Channel from './lib/channel';
|
|
||||||
|
|
||||||
class TermApp extends Component<any, any> {
|
type TermAppProps = {
|
||||||
store: Store;
|
ship: string;
|
||||||
api: any;
|
}
|
||||||
subscription: any;
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.store = new Store();
|
|
||||||
this.store.setStateHandler(this.setState.bind(this));
|
|
||||||
|
|
||||||
this.state = this.store.state;
|
const makeTheme = (dark: boolean): ITheme => {
|
||||||
|
let fg, bg: string;
|
||||||
|
if (dark) {
|
||||||
|
fg = 'white';
|
||||||
|
bg = 'rgb(26,26,26)';
|
||||||
|
} else {
|
||||||
|
fg = 'black';
|
||||||
|
bg = 'white';
|
||||||
|
}
|
||||||
|
// TODO indigo colors.
|
||||||
|
// we can't pluck these from ThemeContext because they have transparency.
|
||||||
|
// technically xterm supports transparency, but it degrades performance.
|
||||||
|
return {
|
||||||
|
foreground: fg,
|
||||||
|
background: bg,
|
||||||
|
brightBlack: '#7f7f7f', // NOTE slogs
|
||||||
|
cursor: fg
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const termConfig: ITerminalOptions = {
|
||||||
|
logLevel: 'warn',
|
||||||
|
//
|
||||||
|
convertEol: true,
|
||||||
|
//
|
||||||
|
rows: 24,
|
||||||
|
cols: 80,
|
||||||
|
scrollback: 10000,
|
||||||
|
//
|
||||||
|
fontFamily: '"Source Code Pro", "Roboto mono", "Courier New", monospace',
|
||||||
|
fontWeight: 400,
|
||||||
|
// NOTE theme colors configured dynamically
|
||||||
|
//
|
||||||
|
bellStyle: 'sound',
|
||||||
|
bellSound: bel,
|
||||||
|
//
|
||||||
|
// allows text selection by holding modifier (option, or shift)
|
||||||
|
macOptionClickForcesSelection: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const csi = (cmd: string, ...args: number[]) => {
|
||||||
|
return '\x1b[' + args.join(';') + cmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tint = (t: Tint) => {
|
||||||
|
switch (t) {
|
||||||
|
case null: return '9';
|
||||||
|
case 'k': return '0';
|
||||||
|
case 'r': return '1';
|
||||||
|
case 'g': return '2';
|
||||||
|
case 'y': return '3';
|
||||||
|
case 'b': return '4';
|
||||||
|
case 'm': return '5';
|
||||||
|
case 'c': return '6';
|
||||||
|
case 'w': return '7';
|
||||||
|
default: return `8;2;${t.r%256};${t.g%256};${t.b%256}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stye = (s: Stye) => {
|
||||||
|
let out = '';
|
||||||
|
|
||||||
|
// text decorations
|
||||||
|
//
|
||||||
|
if (s.deco.length > 0) {
|
||||||
|
out += s.deco.reduce((decs: number[], deco: Deco) => {
|
||||||
|
/* eslint-disable max-statements-per-line */
|
||||||
|
switch (deco) {
|
||||||
|
case null: decs.push(0); return decs;
|
||||||
|
case 'br': decs.push(1); return decs;
|
||||||
|
case 'un': decs.push(4); return decs;
|
||||||
|
case 'bl': decs.push(5); return decs;
|
||||||
|
default: console.log('weird deco', deco); return decs;
|
||||||
|
}
|
||||||
|
}, []).join(';');
|
||||||
}
|
}
|
||||||
|
|
||||||
resetControllers() {
|
// background color
|
||||||
this.api = null;
|
//
|
||||||
this.subscription = null;
|
if (s.back !== null) {
|
||||||
|
if (out !== '') {
|
||||||
|
out += ';';
|
||||||
|
}
|
||||||
|
out += '4';
|
||||||
|
out += tint(s.back);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
// foreground color
|
||||||
this.resetControllers();
|
//
|
||||||
// eslint-disable-next-line new-cap
|
if (s.fore !== null) {
|
||||||
const channel = new Channel();
|
if (out !== '') {
|
||||||
this.api = new Api(window.ship, channel);
|
out += ';';
|
||||||
this.store.api = this.api;
|
}
|
||||||
|
out += '3';
|
||||||
this.subscription = new Subscription(this.store, this.api, channel);
|
out += tint(s.fore);
|
||||||
this.subscription.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
if (out === '') {
|
||||||
this.subscription.delete();
|
return out;
|
||||||
this.store.clear();
|
}
|
||||||
this.resetControllers();
|
return '\x1b[' + out + 'm';
|
||||||
|
};
|
||||||
|
|
||||||
|
const showBlit = (term: Terminal, blit: Blit) => {
|
||||||
|
let out = '';
|
||||||
|
|
||||||
|
if ('bel' in blit) {
|
||||||
|
out += '\x07';
|
||||||
|
} else if ('clr' in blit) {
|
||||||
|
term.clear();
|
||||||
|
out += csi('u');
|
||||||
|
} else if ('hop' in blit) {
|
||||||
|
if (typeof blit.hop === 'number') {
|
||||||
|
out += csi('H', term.rows, blit.hop + 1);
|
||||||
|
} else {
|
||||||
|
out += csi('H', term.rows - blit.hop.r, blit.hop.c + 1);
|
||||||
|
}
|
||||||
|
out += csi('s'); // save cursor position
|
||||||
|
} else if ('put' in blit) {
|
||||||
|
out += blit.put.join('');
|
||||||
|
out += csi('u');
|
||||||
|
} else if ('klr' in blit) {
|
||||||
|
//TODO remove for new backend
|
||||||
|
{
|
||||||
|
out += csi('H', term.rows, 1);
|
||||||
|
out += csi('K');
|
||||||
|
}
|
||||||
|
out += blit.klr.reduce((lin: string, p: Stub) => {
|
||||||
|
lin += stye(p.stye);
|
||||||
|
lin += p.text.join('');
|
||||||
|
lin += csi('m', 0);
|
||||||
|
return lin;
|
||||||
|
}, '');
|
||||||
|
out += csi('u');
|
||||||
|
} else if ('nel' in blit) {
|
||||||
|
out += '\n';
|
||||||
|
} else if ('sag' in blit || 'sav' in blit) {
|
||||||
|
const sav = ('sag' in blit) ? blit.sag : blit.sav;
|
||||||
|
const name = sav.path.split('/').slice(-2).join('.');
|
||||||
|
const buff = Buffer.from(sav.file, 'base64');
|
||||||
|
const blob = new Blob([buff], { type: 'application/octet-stream' });
|
||||||
|
saveAs(blob, name);
|
||||||
|
} else if ('url' in blit) {
|
||||||
|
window.open(blit.url);
|
||||||
|
} else if ('wyp' in blit) {
|
||||||
|
out += '\r' + csi('K');
|
||||||
|
out += csi('u');
|
||||||
|
//
|
||||||
|
//TODO remove for new backend
|
||||||
|
} else if ('lin' in blit) {
|
||||||
|
out += csi('H', term.rows, 1);
|
||||||
|
out += csi('K');
|
||||||
|
out += blit.lin.join('');
|
||||||
|
} else if ('mor' in blit) {
|
||||||
|
out += '\n';
|
||||||
|
} else {
|
||||||
|
console.log('weird blit', blit);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTheme() {
|
term.write(out);
|
||||||
const { props } = this;
|
};
|
||||||
return ((props.dark && props?.display?.theme == 'auto') ||
|
|
||||||
props?.display?.theme == 'dark'
|
|
||||||
) ? dark : light;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
// NOTE should generally only be passed the default terminal session
|
||||||
const theme = this.getTheme();
|
const showSlog = (term: Terminal, slog: string) => {
|
||||||
return (
|
// set scroll region to exclude the bottom line,
|
||||||
<ThemeProvider theme={theme}>
|
// scroll up one line,
|
||||||
|
// move cursor to start of the newly created whitespace,
|
||||||
|
// set text to grey,
|
||||||
|
// print the slog,
|
||||||
|
// restore color, scroll region, and cursor.
|
||||||
|
//
|
||||||
|
term.write(csi('r', 1, term.rows - 1)
|
||||||
|
+ csi('S', 1)
|
||||||
|
+ csi('H', term.rows - 1, 1)
|
||||||
|
+ csi('m', 90)
|
||||||
|
+ slog
|
||||||
|
+ csi('m', 0)
|
||||||
|
+ csi('r')
|
||||||
|
+ csi('u'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const readInput = (term: Terminal, e: string): Belt[] => {
|
||||||
|
const belts: Belt[] = [];
|
||||||
|
let strap = '';
|
||||||
|
|
||||||
|
while (e.length > 0) {
|
||||||
|
let c = e.charCodeAt(0);
|
||||||
|
|
||||||
|
// text input
|
||||||
|
//
|
||||||
|
if (c >= 32 && c !== 127) {
|
||||||
|
strap += e[0];
|
||||||
|
e = e.slice(1);
|
||||||
|
continue;
|
||||||
|
} else if ('' !== strap) {
|
||||||
|
belts.push({ txt: strap.split('') });
|
||||||
|
strap = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// special keys/characters
|
||||||
|
//
|
||||||
|
if (0 === c) {
|
||||||
|
term.write('\x07'); // bel
|
||||||
|
} else if (8 === c || 127 === c) {
|
||||||
|
belts.push({ bac: null });
|
||||||
|
} else if (13 === c) {
|
||||||
|
belts.push({ ret: null });
|
||||||
|
} else if (c <= 26) {
|
||||||
|
let k = String.fromCharCode(96 + c);
|
||||||
|
//NOTE prevent remote shut-downs
|
||||||
|
if ('d' !== k) {
|
||||||
|
belts.push({ ctl: k });
|
||||||
|
//TODO for new backend
|
||||||
|
// belts.push({ mod: { mod: 'ctl', key: k } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape sequences
|
||||||
|
//
|
||||||
|
if (27 === c) { // ESC
|
||||||
|
e = e.slice(1);
|
||||||
|
c = e.charCodeAt(0);
|
||||||
|
if (91 === c || 79 === c) { // [ or O
|
||||||
|
e = e.slice(1);
|
||||||
|
c = e.charCodeAt(0);
|
||||||
|
/* eslint-disable max-statements-per-line */
|
||||||
|
switch (c) {
|
||||||
|
case 65: belts.push({ aro: 'u' }); break;
|
||||||
|
case 66: belts.push({ aro: 'd' }); break;
|
||||||
|
case 67: belts.push({ aro: 'r' }); break;
|
||||||
|
case 68: belts.push({ aro: 'l' }); break;
|
||||||
|
//
|
||||||
|
case 77: {
|
||||||
|
const m = e.charCodeAt(1) - 31;
|
||||||
|
if (1 === m) {
|
||||||
|
const c = e.charCodeAt(2) - 32;
|
||||||
|
const r = e.charCodeAt(3) - 32;
|
||||||
|
//TODO re-enable for new backend
|
||||||
|
// belts.push({ hit: { r: term.rows - r, c: c - 1 } });
|
||||||
|
}
|
||||||
|
e = e.slice(3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
default: term.write('\x07'); break; // bel
|
||||||
|
}
|
||||||
|
} else if (c >= 97 && c <= 122) { // a <= c <= z
|
||||||
|
belts.push({ mod: { mod: 'met', key: e[0] } });
|
||||||
|
} else if (c === 46) { // .
|
||||||
|
belts.push({ mod: { mod: 'met', key: '.' } });
|
||||||
|
} else if (c === 8 || c === 127) {
|
||||||
|
belts.push({ mod: { mod: 'met', key: { bac: null } } });
|
||||||
|
} else {
|
||||||
|
term.write('\x07'); break; // bel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e = e.slice(1);
|
||||||
|
}
|
||||||
|
if ('' !== strap) {
|
||||||
|
belts.push({ txt: strap.split('') });
|
||||||
|
strap = '';
|
||||||
|
}
|
||||||
|
return belts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TermApp(props: TermAppProps) {
|
||||||
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
// TODO allow switching of selected
|
||||||
|
const { sessions, selected, slogstream, set } = useTermState();
|
||||||
|
const session = sessions[selected];
|
||||||
|
const dark = useDark();
|
||||||
|
|
||||||
|
const setupSlog = useCallback(() => {
|
||||||
|
console.log('slog: setting up...');
|
||||||
|
let available = false;
|
||||||
|
const slog = new EventSource('/~_~/slog', { withCredentials: true });
|
||||||
|
|
||||||
|
slog.onopen = (e) => {
|
||||||
|
console.log('slog: opened stream');
|
||||||
|
available = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
slog.onmessage = (e) => {
|
||||||
|
const session = useTermState.getState().sessions[''];
|
||||||
|
if (!session) {
|
||||||
|
console.log('default session mia!', 'slog:', slog);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showSlog(session.term, e.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
slog.onerror = (e) => {
|
||||||
|
console.error('slog: eventsource error:', e);
|
||||||
|
if (available) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (slog.readyState !== EventSource.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('slog: reconnecting...');
|
||||||
|
setupSlog();
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
state.slogstream = slog;
|
||||||
|
});
|
||||||
|
}, [sessions]);
|
||||||
|
|
||||||
|
const onInput = useCallback((ses: string, e: string) => {
|
||||||
|
const term = useTermState.getState().sessions[ses].term;
|
||||||
|
const belts = readInput(term, e);
|
||||||
|
belts.map((b) => { // NOTE passing api.poke(pokeBelt makes `this` undefined!
|
||||||
|
//TODO pokeBelt(ses, b);
|
||||||
|
api.poke({
|
||||||
|
app: 'herm',
|
||||||
|
mark: 'belt',
|
||||||
|
json: b
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [sessions]);
|
||||||
|
|
||||||
|
const onResize = useCallback(() => {
|
||||||
|
// TODO debounce, if it ever becomes a problem
|
||||||
|
session?.fit.fit();
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
// on-init, open slogstream
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (!slogstream) {
|
||||||
|
setupSlog();
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
return () => {
|
||||||
|
// TODO clean up subs?
|
||||||
|
window.removeEventListener('resize', onResize);
|
||||||
|
};
|
||||||
|
}, [onResize, setupSlog]);
|
||||||
|
|
||||||
|
// on dark mode change, change terminals' theme
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
const theme = makeTheme(dark);
|
||||||
|
for (const ses in sessions) {
|
||||||
|
sessions[ses].term.setOption('theme', theme);
|
||||||
|
}
|
||||||
|
if (container.current) {
|
||||||
|
container.current.style.backgroundColor = theme.background || '';
|
||||||
|
}
|
||||||
|
}, [dark, sessions]);
|
||||||
|
|
||||||
|
// on selected change, maybe setup the term, or put it into the container
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
let ses = session;
|
||||||
|
// initialize terminal
|
||||||
|
//
|
||||||
|
if (!ses) {
|
||||||
|
// set up terminal
|
||||||
|
//
|
||||||
|
const term = new Terminal(termConfig);
|
||||||
|
term.setOption('theme', makeTheme(dark));
|
||||||
|
const fit = new FitAddon();
|
||||||
|
term.loadAddon(fit);
|
||||||
|
|
||||||
|
// start mouse reporting
|
||||||
|
//
|
||||||
|
term.write(csi('?9h'));
|
||||||
|
|
||||||
|
// set up event handlers
|
||||||
|
//
|
||||||
|
term.onData(e => onInput(selected, e));
|
||||||
|
term.onBinary(e => onInput(selected, e));
|
||||||
|
term.onResize((e) => {
|
||||||
|
//TODO re-enable once new backend lands
|
||||||
|
// api.poke(pokeTask(selected, { blew: { w: e.cols, h: e.rows } }));
|
||||||
|
});
|
||||||
|
|
||||||
|
ses = { term, fit };
|
||||||
|
|
||||||
|
// open subscription
|
||||||
|
//
|
||||||
|
api.subscribe({ app: 'herm', path: '/session/'+selected+'/view',
|
||||||
|
event: (e) => {
|
||||||
|
const ses = useTermState.getState().sessions[selected];
|
||||||
|
if (!ses) {
|
||||||
|
console.log('on blit: no such session', selected, sessions, useTermState.getState().sessions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showBlit(ses.term, e);
|
||||||
|
},
|
||||||
|
quit: () => { // quit
|
||||||
|
// TODO show user a message
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.current && !container.current.contains(ses.term.element || null)) {
|
||||||
|
ses.term.open(container.current);
|
||||||
|
ses.fit.fit();
|
||||||
|
ses.term.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
|
state.sessions[selected] = ses;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// TODO unload term from container
|
||||||
|
// but term.dispose is too powerful? maybe just empty the container?
|
||||||
|
};
|
||||||
|
}, [set, session, container]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ThemeProvider theme={dark ? _dark : _light}>
|
||||||
|
<Reset />
|
||||||
<Box
|
<Box
|
||||||
width='100%'
|
width='100%'
|
||||||
height='100%'
|
height='100%'
|
||||||
p={['0','3']}
|
bg='white'
|
||||||
style={{ boxSizing: 'border-box' }}
|
fontFamily='mono'
|
||||||
|
overflow='hidden'
|
||||||
>
|
>
|
||||||
<Col
|
<Col
|
||||||
p={3}
|
|
||||||
backgroundColor='white'
|
|
||||||
width='100%'
|
width='100%'
|
||||||
height='100%'
|
height='100%'
|
||||||
minHeight={0}
|
minHeight='0'
|
||||||
minWidth={0}
|
px={['0','2']}
|
||||||
color='lightGray'
|
pb={['0','2']}
|
||||||
borderRadius={2}
|
ref={container}
|
||||||
border={['0','1']}
|
|
||||||
cursor='text'
|
|
||||||
style={{ boxSizing: 'border-box' }}
|
|
||||||
>
|
>
|
||||||
{/* @ts-ignore declare props in later pass */}
|
|
||||||
<History log={this.state.lines.slice(0, -1)} />
|
|
||||||
<Input
|
|
||||||
ship={this.props.ship}
|
|
||||||
cursor={this.state.cursor}
|
|
||||||
api={this.api}
|
|
||||||
store={this.store}
|
|
||||||
line={this.state.lines.slice(-1)[0]}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Box>
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
</>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TermApp;
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import { Box } from '@tlon/indigo-react';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Line from './line';
|
|
||||||
|
|
||||||
export class History extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
height='100%'
|
|
||||||
minHeight={0}
|
|
||||||
minWidth={0}
|
|
||||||
display='flex'
|
|
||||||
flexDirection='column-reverse'
|
|
||||||
overflowY='scroll'
|
|
||||||
style={{ resize: 'none' }}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
mt='auto'
|
|
||||||
>
|
|
||||||
{/* @ts-ignore declare props in later pass */}
|
|
||||||
{this.props.log.map((line, i) => {
|
|
||||||
// @ts-ignore react memo not passing props
|
|
||||||
return <Line key={i} line={line} />;
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default History;
|
|
@ -1,128 +0,0 @@
|
|||||||
import { BaseInput, Box, Row } from '@tlon/indigo-react';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
|
||||||
export class Input extends Component<any, {}> {
|
|
||||||
inputRef: React.RefObject<unknown>;
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {};
|
|
||||||
this.keyPress = this.keyPress.bind(this);
|
|
||||||
this.paste = this.paste.bind(this);
|
|
||||||
this.click = this.click.bind(this);
|
|
||||||
this.inputRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
if (
|
|
||||||
document.activeElement == this.inputRef.current
|
|
||||||
) {
|
|
||||||
// @ts-ignore ref type issues
|
|
||||||
this.inputRef.current.focus();
|
|
||||||
// @ts-ignore ref type issues
|
|
||||||
this.inputRef.current.setSelectionRange(this.props.cursor, this.props.cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyPress(e) {
|
|
||||||
const key = e.key;
|
|
||||||
// let paste and leap events pass
|
|
||||||
if ((e.getModifierState('Control') || e.getModifierState('Meta'))
|
|
||||||
&& (e.key === 'v' || e.key === '/')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let belt = null;
|
|
||||||
if (key === 'ArrowLeft')
|
|
||||||
belt = { aro: 'l' };
|
|
||||||
else if (key === 'ArrowRight')
|
|
||||||
belt = { aro: 'r' };
|
|
||||||
else if (key === 'ArrowUp')
|
|
||||||
belt = { aro: 'u' };
|
|
||||||
else if (key === 'ArrowDown')
|
|
||||||
belt = { aro: 'd' };
|
|
||||||
else if (key === 'Backspace')
|
|
||||||
belt = { bac: null };
|
|
||||||
else if (key === 'Delete')
|
|
||||||
belt = { del: null };
|
|
||||||
else if (key === 'Tab')
|
|
||||||
belt = { ctl: 'i' };
|
|
||||||
else if (key === 'Enter')
|
|
||||||
belt = { ret: null };
|
|
||||||
else if (key.length === 1)
|
|
||||||
belt = { txt: [key] };
|
|
||||||
else
|
|
||||||
belt = null;
|
|
||||||
|
|
||||||
if (belt && e.getModifierState('Control')) {
|
|
||||||
if (belt.txt !== undefined)
|
|
||||||
belt = { ctl: belt.txt[0] };
|
|
||||||
} else
|
|
||||||
if (belt &&
|
|
||||||
(e.getModifierState('Meta') || e.getModifierState('Alt'))) {
|
|
||||||
if (belt.bac !== undefined)
|
|
||||||
belt = { met: 'bac' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (belt !== null) {
|
|
||||||
this.props.api.belt(belt);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
paste(e) {
|
|
||||||
const clipboardData = e.clipboardData || (window as any).clipboardData;
|
|
||||||
const clipboardText = clipboardData.getData('Text');
|
|
||||||
this.props.api.belt({ txt: [...clipboardText] });
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
click(e) {
|
|
||||||
// prevent desynced cursor movement
|
|
||||||
e.preventDefault();
|
|
||||||
e.target.setSelectionRange(this.props.cursor, this.props.cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const line = this.props.line;
|
|
||||||
let prompt = 'connecting...';
|
|
||||||
if (line) {
|
|
||||||
if (line.lin) {
|
|
||||||
prompt = line.lin.join('');
|
|
||||||
} else if (line.klr) {
|
|
||||||
// TODO render prompt style
|
|
||||||
prompt = line.klr.reduce((l, p) => (l + p.text.join('')), '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Row flexGrow={1} position='relative'>
|
|
||||||
<Box flexShrink={0} width='100%' color='black' fontSize={0}>
|
|
||||||
<BaseInput
|
|
||||||
autoFocus
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
color='lightGray'
|
|
||||||
minHeight={0}
|
|
||||||
display='inline-block'
|
|
||||||
width='100%'
|
|
||||||
spellCheck="false"
|
|
||||||
tabindex={0}
|
|
||||||
wrap="off"
|
|
||||||
fontFamily="mono"
|
|
||||||
id="term"
|
|
||||||
cursor={this.props.cursor}
|
|
||||||
onKeyDown={this.keyPress}
|
|
||||||
onClick={this.click}
|
|
||||||
onPaste={this.paste}
|
|
||||||
// @ts-ignore indigo-react doesn't let us pass refs
|
|
||||||
ref={this.inputRef}
|
|
||||||
defaultValue="connecting..."
|
|
||||||
value={prompt}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Input;
|
|
@ -1,66 +0,0 @@
|
|||||||
import { Text } from '@tlon/indigo-react';
|
|
||||||
import React from 'react';
|
|
||||||
// @ts-ignore line isn't in props?
|
|
||||||
export default React.memo(({ line }) => {
|
|
||||||
// line body to jsx
|
|
||||||
// NOTE lines are lists of characters that might span multiple codepoints
|
|
||||||
//
|
|
||||||
let text = '';
|
|
||||||
if (line.lin) {
|
|
||||||
text = line.lin.join('');
|
|
||||||
} else if (line.klr) {
|
|
||||||
text = line.klr.map((part, i) => {
|
|
||||||
const prop = part.stye.deco.reduce((prop, deco) => {
|
|
||||||
switch (deco) {
|
|
||||||
case null: return prop;
|
|
||||||
case 'br': return { bold: true, ...prop };
|
|
||||||
case 'bl': return { className: 'blink', ...prop };
|
|
||||||
case 'un': return { style: { textDecoration: 'underline' }, ...prop };
|
|
||||||
default: console.log('weird deco', deco); return prop;
|
|
||||||
}
|
|
||||||
}, {});
|
|
||||||
switch (part.stye.fore) {
|
|
||||||
case null: break;
|
|
||||||
case 'r': prop.color = 'red'; break;
|
|
||||||
case 'g': prop.color = 'green'; break;
|
|
||||||
case 'b': prop.color = 'blue'; break;
|
|
||||||
case 'c': prop.color = 'cyan'; break;
|
|
||||||
case 'm': prop.color = 'purple'; break;
|
|
||||||
case 'y': prop.color = 'yellow'; break;
|
|
||||||
case 'k': prop.color = 'black'; break;
|
|
||||||
case 'w': prop.color = 'white'; break;
|
|
||||||
default: prop.color = '#' + part.stye.fore;
|
|
||||||
}
|
|
||||||
switch (part.stye.back) {
|
|
||||||
case null: break;
|
|
||||||
case 'r': prop.backgroundColor = 'red'; break;
|
|
||||||
case 'g': prop.backgroundColor = 'green'; break;
|
|
||||||
case 'b': prop.backgroundColor = 'blue'; break;
|
|
||||||
case 'c': prop.backgroundColor = 'cyan'; break;
|
|
||||||
case 'm': prop.backgroundColor = 'purple'; break;
|
|
||||||
case 'y': prop.backgroundColor = 'yellow'; break;
|
|
||||||
case 'k': prop.backgroundColor = 'black'; break;
|
|
||||||
case 'w': prop.backgroundColor = 'white'; break;
|
|
||||||
default: prop.backgroundColor = '#' + part.stye.back;
|
|
||||||
}
|
|
||||||
if (Object.keys(prop).length === 0) {
|
|
||||||
return part.text;
|
|
||||||
} else {
|
|
||||||
return (<Text mono fontSize='inherit' key={i} {...prop}>
|
|
||||||
{part.text.join('')}
|
|
||||||
</Text>);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// render line
|
|
||||||
//
|
|
||||||
return (
|
|
||||||
<Text mono display='flex'
|
|
||||||
fontSize={0}
|
|
||||||
style={{ overflowWrap: 'break-word', whiteSpace: 'pre-wrap' }}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1,23 +0,0 @@
|
|||||||
body, #root {
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input#term {
|
|
||||||
background-color: inherit;
|
|
||||||
color: inherit;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blink {
|
|
||||||
animation: 4s ease-in-out infinite opacity_blink;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes opacity_blink {
|
|
||||||
0% { opacity: 0; }
|
|
||||||
10% { opacity: 1; }
|
|
||||||
80% { opacity: 1; }
|
|
||||||
90% { opacity: 0; }
|
|
||||||
100% { opacity: 0; }
|
|
||||||
}
|
|
@ -8,8 +8,8 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-touch-fullscreen" content="yes" />
|
<meta name="apple-touch-fullscreen" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
<link rel="apple-touch-icon" href="/~landscape/img/touch_icon.png">
|
<!--<link rel="apple-touch-icon" href="/~landscape/img/touch_icon.png">
|
||||||
<link rel="icon" type="image/png" href="/~landscape/img/Favicon.png">
|
<link rel="icon" type="image/png" href="/~landscape/img/Favicon.png">-->
|
||||||
<link rel="manifest"
|
<link rel="manifest"
|
||||||
href='data:application/manifest+json,{
|
href='data:application/manifest+json,{
|
||||||
"name": "Terminal",
|
"name": "Terminal",
|
||||||
@ -18,6 +18,16 @@
|
|||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "%23FFFFFF",
|
"background_color": "%23FFFFFF",
|
||||||
"theme_color": "%23000000"}' />
|
"theme_color": "%23000000"}' />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body, #root {
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
24
pkg/interface/webterm/join.ts
Normal file
24
pkg/interface/webterm/join.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTheme } from './settings';
|
||||||
|
import useTermState from './state';
|
||||||
|
|
||||||
|
export function useDark() {
|
||||||
|
const [osDark, setOsDark] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const themeWatcher = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const update = (e: MediaQueryListEvent) => {
|
||||||
|
setOsDark(e.matches);
|
||||||
|
};
|
||||||
|
setOsDark(themeWatcher.matches);
|
||||||
|
themeWatcher.addListener(update);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
themeWatcher.removeListener(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const theme = useTermState(s => s.theme);
|
||||||
|
return theme === 'dark' || (osDark && theme === 'auto');
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
1
pkg/interface/webterm/lib/bel.ts
Normal file
1
pkg/interface/webterm/lib/bel.ts
Normal file
File diff suppressed because one or more lines are too long
@ -1,290 +0,0 @@
|
|||||||
export default class Channel {
|
|
||||||
constructor() {
|
|
||||||
this.init();
|
|
||||||
this.deleteOnUnload();
|
|
||||||
|
|
||||||
// a way to handle channel errors
|
|
||||||
//
|
|
||||||
//
|
|
||||||
this.onChannelError = (err) => {
|
|
||||||
console.error('event source error: ', err);
|
|
||||||
};
|
|
||||||
this.onChannelOpen = (e) => {
|
|
||||||
console.log('open', e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.debounceInterval = 500;
|
|
||||||
// unique identifier: current time and random number
|
|
||||||
//
|
|
||||||
this.uid =
|
|
||||||
new Date().getTime().toString() +
|
|
||||||
"-" +
|
|
||||||
Math.random().toString(16).slice(-6);
|
|
||||||
|
|
||||||
this.requestId = 1;
|
|
||||||
|
|
||||||
// the currently connected EventSource
|
|
||||||
//
|
|
||||||
this.eventSource = null;
|
|
||||||
|
|
||||||
// the id of the last EventSource event we received
|
|
||||||
//
|
|
||||||
this.lastEventId = 0;
|
|
||||||
|
|
||||||
// this last event id acknowledgment sent to the server
|
|
||||||
//
|
|
||||||
this.lastAcknowledgedEventId = 0;
|
|
||||||
|
|
||||||
// a registry of requestId to successFunc/failureFunc
|
|
||||||
//
|
|
||||||
// These functions are registered during a +poke and are executed
|
|
||||||
// in the onServerEvent()/onServerError() callbacks. Only one of
|
|
||||||
// the functions will be called, and the outstanding poke will be
|
|
||||||
// removed after calling the success or failure function.
|
|
||||||
//
|
|
||||||
|
|
||||||
this.outstandingPokes = new Map();
|
|
||||||
|
|
||||||
// a registry of requestId to subscription functions.
|
|
||||||
//
|
|
||||||
// These functions are registered during a +subscribe and are
|
|
||||||
// executed in the onServerEvent()/onServerError() callbacks. The
|
|
||||||
// event function will be called whenever a new piece of data on this
|
|
||||||
// subscription is available, which may be 0, 1, or many times. The
|
|
||||||
// disconnect function may be called exactly once.
|
|
||||||
//
|
|
||||||
this.outstandingSubscriptions = new Map();
|
|
||||||
|
|
||||||
this.outstandingJSON = [];
|
|
||||||
|
|
||||||
this.debounceTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetDebounceTimer() {
|
|
||||||
if (this.debounceTimer) {
|
|
||||||
clearTimeout(this.debounceTimer);
|
|
||||||
this.debounceTimer = null;
|
|
||||||
}
|
|
||||||
this.debounceTimer = setTimeout(() => {
|
|
||||||
this.sendJSONToChannel();
|
|
||||||
}, this.debounceInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnChannelError(onError = (err) => {}) {
|
|
||||||
this.onChannelError = onError;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnChannelOpen(onOpen = (e) => {}) {
|
|
||||||
this.onChannelOpen = onOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteOnUnload() {
|
|
||||||
window.addEventListener("beforeunload", (event) => {
|
|
||||||
this.delete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
clearQueue() {
|
|
||||||
clearTimeout(this.debounceTimer);
|
|
||||||
this.debounceTimer = null;
|
|
||||||
this.sendJSONToChannel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// sends a poke to an app on an urbit ship
|
|
||||||
//
|
|
||||||
poke(ship, app, mark, json, successFunc, failureFunc) {
|
|
||||||
let id = this.nextId();
|
|
||||||
this.outstandingPokes.set(
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
success: successFunc,
|
|
||||||
fail: failureFunc
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const j = {
|
|
||||||
id,
|
|
||||||
action: "poke",
|
|
||||||
ship,
|
|
||||||
app,
|
|
||||||
mark,
|
|
||||||
json
|
|
||||||
};
|
|
||||||
|
|
||||||
this.sendJSONToChannel(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribes to a path on an specific app and ship.
|
|
||||||
//
|
|
||||||
// Returns a subscription id, which is the same as the same internal id
|
|
||||||
// passed to your Urbit.
|
|
||||||
subscribe(
|
|
||||||
ship,
|
|
||||||
app,
|
|
||||||
path,
|
|
||||||
connectionErrFunc = () => {},
|
|
||||||
eventFunc = () => {},
|
|
||||||
quitFunc = () => {},
|
|
||||||
subAckFunc = () => {},
|
|
||||||
) {
|
|
||||||
let id = this.nextId();
|
|
||||||
this.outstandingSubscriptions.set(
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
err: connectionErrFunc,
|
|
||||||
event: eventFunc,
|
|
||||||
quit: quitFunc,
|
|
||||||
subAck: subAckFunc
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const json = {
|
|
||||||
id,
|
|
||||||
action: "subscribe",
|
|
||||||
ship,
|
|
||||||
app,
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetDebounceTimer();
|
|
||||||
|
|
||||||
this.outstandingJSON.push(json);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// quit the channel
|
|
||||||
//
|
|
||||||
delete() {
|
|
||||||
let id = this.nextId();
|
|
||||||
clearInterval(this.ackTimer);
|
|
||||||
navigator.sendBeacon(this.channelURL(), JSON.stringify([{
|
|
||||||
id,
|
|
||||||
action: "delete"
|
|
||||||
}]));
|
|
||||||
if (this.eventSource) {
|
|
||||||
this.eventSource.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsubscribe to a specific subscription
|
|
||||||
//
|
|
||||||
unsubscribe(subscription) {
|
|
||||||
let id = this.nextId();
|
|
||||||
this.sendJSONToChannel({
|
|
||||||
id,
|
|
||||||
action: "unsubscribe",
|
|
||||||
subscription
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// sends a JSON command command to the server.
|
|
||||||
//
|
|
||||||
sendJSONToChannel(j) {
|
|
||||||
let req = new XMLHttpRequest();
|
|
||||||
req.open("PUT", this.channelURL());
|
|
||||||
req.setRequestHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
if (this.lastEventId == this.lastAcknowledgedEventId) {
|
|
||||||
if (j) {
|
|
||||||
this.outstandingJSON.push(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.outstandingJSON.length > 0) {
|
|
||||||
let x = JSON.stringify(this.outstandingJSON);
|
|
||||||
req.send(x);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we add an acknowledgment to clear the server side queue
|
|
||||||
//
|
|
||||||
// The server side puts messages it sends us in a queue until we
|
|
||||||
// acknowledge that we received it.
|
|
||||||
//
|
|
||||||
let payload = [
|
|
||||||
...this.outstandingJSON,
|
|
||||||
{action: "ack", "event-id": this.lastEventId}
|
|
||||||
];
|
|
||||||
if (j) {
|
|
||||||
payload.push(j)
|
|
||||||
}
|
|
||||||
let x = JSON.stringify(payload);
|
|
||||||
req.send(x);
|
|
||||||
|
|
||||||
this.lastAcknowledgedEventId = this.lastEventId;
|
|
||||||
}
|
|
||||||
this.outstandingJSON = [];
|
|
||||||
|
|
||||||
this.connectIfDisconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
// connects to the EventSource if we are not currently connected
|
|
||||||
//
|
|
||||||
connectIfDisconnected() {
|
|
||||||
if (this.eventSource) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.eventSource = new EventSource(this.channelURL(), {withCredentials:true});
|
|
||||||
this.eventSource.onmessage = e => {
|
|
||||||
this.lastEventId = parseInt(e.lastEventId, 10);
|
|
||||||
|
|
||||||
let obj = JSON.parse(e.data);
|
|
||||||
let pokeFuncs = this.outstandingPokes.get(obj.id);
|
|
||||||
let subFuncs = this.outstandingSubscriptions.get(obj.id);
|
|
||||||
|
|
||||||
if (obj.response == "poke" && !!pokeFuncs) {
|
|
||||||
let funcs = pokeFuncs;
|
|
||||||
if (obj.hasOwnProperty("ok")) {
|
|
||||||
funcs["success"]();
|
|
||||||
} else if (obj.hasOwnProperty("err")) {
|
|
||||||
funcs["fail"](obj.err);
|
|
||||||
} else {
|
|
||||||
console.error("Invalid poke response: ", obj);
|
|
||||||
}
|
|
||||||
this.outstandingPokes.delete(obj.id);
|
|
||||||
|
|
||||||
} else if (obj.response == "subscribe" ||
|
|
||||||
(obj.response == "poke" && !!subFuncs)) {
|
|
||||||
let funcs = subFuncs;
|
|
||||||
|
|
||||||
if (obj.hasOwnProperty("err")) {
|
|
||||||
funcs["err"](obj.err);
|
|
||||||
this.outstandingSubscriptions.delete(obj.id);
|
|
||||||
} else if (obj.hasOwnProperty("ok")) {
|
|
||||||
funcs["subAck"](obj);
|
|
||||||
}
|
|
||||||
} else if (obj.response == "diff") {
|
|
||||||
// ensure we ack before channel clogs
|
|
||||||
if((this.lastEventId - this.lastAcknowledgedEventId) > 30) {
|
|
||||||
this.clearQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
let funcs = subFuncs;
|
|
||||||
funcs["event"](obj.json);
|
|
||||||
} else if (obj.response == "quit") {
|
|
||||||
let funcs = subFuncs;
|
|
||||||
funcs["quit"](obj);
|
|
||||||
this.outstandingSubscriptions.delete(obj.id);
|
|
||||||
} else {
|
|
||||||
console.log("Unrecognized response: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.eventSource.onopen = this.onChannelOpen;
|
|
||||||
|
|
||||||
this.eventSource.onerror = e => {
|
|
||||||
this.delete();
|
|
||||||
this.init();
|
|
||||||
this.onChannelError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channelURL() {
|
|
||||||
return "/~/channel/" + this.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextId() {
|
|
||||||
return this.requestId++;
|
|
||||||
}
|
|
||||||
}
|
|
29092
pkg/interface/webterm/package-lock.json
generated
29092
pkg/interface/webterm/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,63 +1,23 @@
|
|||||||
{
|
{
|
||||||
"name": "interface",
|
"name": "webterm",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@reach/disclosure": "^0.10.5",
|
|
||||||
"@reach/menu-button": "^0.10.5",
|
"@reach/menu-button": "^0.10.5",
|
||||||
"@reach/tabs": "^0.10.5",
|
|
||||||
"@react-spring/web": "^9.1.1",
|
|
||||||
"@tlon/indigo-dark": "^1.0.6",
|
|
||||||
"@tlon/indigo-light": "^1.0.7",
|
|
||||||
"@tlon/indigo-react": "^1.2.23",
|
"@tlon/indigo-react": "^1.2.23",
|
||||||
"@tlon/sigil-js": "^1.4.3",
|
|
||||||
"@urbit/api": "^1.1.1",
|
"@urbit/api": "^1.1.1",
|
||||||
"@urbit/http-api": "^1.2.1",
|
"@urbit/http-api": "^1.2.1",
|
||||||
"any-ascii": "^0.1.7",
|
|
||||||
"aws-sdk": "^2.830.0",
|
|
||||||
"big-integer": "^1.6.48",
|
|
||||||
"classnames": "^2.2.6",
|
|
||||||
"codemirror": "^5.59.2",
|
|
||||||
"css-loader": "^3.6.0",
|
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"formik": "^2.1.5",
|
|
||||||
"immer": "^9.0.2",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"moment": "^2.29.1",
|
|
||||||
"mousetrap": "^1.6.5",
|
|
||||||
"mousetrap-global-bind": "^1.1.0",
|
|
||||||
"normalize-wheel": "1.0.1",
|
|
||||||
"oembed-parser": "^1.4.5",
|
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"querystring": "^0.2.0",
|
|
||||||
"react": "^16.14.0",
|
"react": "^16.14.0",
|
||||||
"react-codemirror2": "^6.0.1",
|
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^16.14.0",
|
||||||
"react-helmet": "^6.1.0",
|
|
||||||
"react-markdown": "^4.3.1",
|
|
||||||
"react-oembed-container": "^1.0.0",
|
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-use-gesture": "^9.1.3",
|
|
||||||
"react-virtuoso": "^0.20.3",
|
|
||||||
"react-visibility-sensor": "^5.1.1",
|
|
||||||
"remark": "^12.0.0",
|
|
||||||
"remark-breaks": "^2.0.2",
|
|
||||||
"remark-disable-tokenizers": "1.1.0",
|
|
||||||
"stacktrace-js": "^2.0.2",
|
|
||||||
"style-loader": "^1.3.0",
|
|
||||||
"styled-components": "^5.1.1",
|
"styled-components": "^5.1.1",
|
||||||
"styled-system": "^5.1.5",
|
"styled-system": "^5.1.5",
|
||||||
"suncalc": "^1.8.0",
|
"xterm": "^4.15.0",
|
||||||
"unist-util-visit": "^3.0.0",
|
"xterm-addon-fit": "^0.5.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.5.0"
|
"zustand": "^3.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -69,17 +29,11 @@
|
|||||||
"@babel/preset-env": "^7.12.11",
|
"@babel/preset-env": "^7.12.11",
|
||||||
"@babel/preset-react": "^7.12.10",
|
"@babel/preset-react": "^7.12.10",
|
||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@storybook/addon-actions": "^6.2.9",
|
|
||||||
"@storybook/addon-essentials": "^6.2.9",
|
|
||||||
"@storybook/addon-links": "^6.2.9",
|
|
||||||
"@storybook/react": "^6.2.9",
|
|
||||||
"@types/lodash": "^4.14.168",
|
|
||||||
"@types/react": "^16.14.2",
|
"@types/react": "^16.14.2",
|
||||||
"@types/react-dom": "^16.9.10",
|
"@types/react-dom": "^16.9.10",
|
||||||
"@types/react-router-dom": "^5.1.7",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/styled-components": "^5.1.7",
|
"@types/styled-components": "^5.1.7",
|
||||||
"@types/styled-system": "^5.1.10",
|
"@types/styled-system": "^5.1.10",
|
||||||
"@types/yup": "^0.29.11",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||||
"@typescript-eslint/parser": "^4.24.0",
|
"@typescript-eslint/parser": "^4.24.0",
|
||||||
"@urbit/eslint-config": "^1.0.0",
|
"@urbit/eslint-config": "^1.0.0",
|
||||||
@ -87,9 +41,7 @@
|
|||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
|
||||||
"babel-plugin-root-import": "^6.6.0",
|
"babel-plugin-root-import": "^6.6.0",
|
||||||
"chromatic": "^5.8.3",
|
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^7.26.0",
|
"eslint": "^7.26.0",
|
||||||
@ -99,13 +51,7 @@
|
|||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"loki": "^0.28.1",
|
|
||||||
"moment-locales-webpack-plugin": "^1.2.0",
|
|
||||||
"react-hot-loader": "^4.13.0",
|
"react-hot-loader": "^4.13.0",
|
||||||
"sass": "^1.32.5",
|
|
||||||
"sass-loader": "^8.0.2",
|
|
||||||
"storybook-addon-designs": "^6.0.0",
|
|
||||||
"ts-mdast": "^1.0.0",
|
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.2.4",
|
||||||
"webpack": "^4.46.0",
|
"webpack": "^4.46.0",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^3.3.12",
|
||||||
@ -121,9 +67,6 @@
|
|||||||
"start": "webpack-dev-server --config config/webpack.dev.js",
|
"start": "webpack-dev-server --config config/webpack.dev.js",
|
||||||
"test": "tsc && jest",
|
"test": "tsc && jest",
|
||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
"storybook": "start-storybook -p 6006",
|
|
||||||
"build-storybook": "build-storybook",
|
|
||||||
"chromatic": "chromatic --exit-zero-on-changes",
|
|
||||||
"hook-lint": "eslint --cache --fix"
|
"hook-lint": "eslint --cache --fix"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
26
pkg/interface/webterm/state.ts
Normal file
26
pkg/interface/webterm/state.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import create from 'zustand';
|
||||||
|
import produce from 'immer';
|
||||||
|
|
||||||
|
type Session = { term: Terminal, fit: FitAddon };
|
||||||
|
type Sessions = { [id: string]: Session; }
|
||||||
|
|
||||||
|
export interface TermState {
|
||||||
|
sessions: Sessions,
|
||||||
|
selected: string,
|
||||||
|
slogstream: null | EventSource,
|
||||||
|
theme: 'auto' | 'light' | 'dark'
|
||||||
|
};
|
||||||
|
|
||||||
|
const useTermState = create<TermState>((set, get) => ({
|
||||||
|
sessions: {} as Sessions,
|
||||||
|
selected: '', // empty string is default session
|
||||||
|
slogstream: null,
|
||||||
|
theme: 'auto',
|
||||||
|
set: (f: (draft: TermState) => void) => {
|
||||||
|
set(produce(f));
|
||||||
|
}
|
||||||
|
} as TermState));
|
||||||
|
|
||||||
|
export default useTermState;
|
@ -1,94 +0,0 @@
|
|||||||
import { saveAs } from 'file-saver';
|
|
||||||
import bel from './lib/bel';
|
|
||||||
|
|
||||||
export default class Store {
|
|
||||||
state: any;
|
|
||||||
api: any;
|
|
||||||
setState: any;
|
|
||||||
constructor() {
|
|
||||||
this.state = this.initialState();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialState() {
|
|
||||||
return {
|
|
||||||
lines: [''],
|
|
||||||
cursor: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.setState(this.initialState());
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent(data) {
|
|
||||||
// process slogs
|
|
||||||
//
|
|
||||||
if (data.slog) {
|
|
||||||
this.state.lines.splice(this.state.lines.length-1, 0, { lin: [data.slog] });
|
|
||||||
this.setState({ lines: this.state.lines });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// process blits
|
|
||||||
//
|
|
||||||
const blit = data.data;
|
|
||||||
switch (Object.keys(blit)[0]) {
|
|
||||||
case 'bel':
|
|
||||||
bel.play();
|
|
||||||
break;
|
|
||||||
case 'clr':
|
|
||||||
this.state.lines = this.state.lines.slice(-1);
|
|
||||||
this.setState({ lines: this.state.lines });
|
|
||||||
break;
|
|
||||||
case 'hop':
|
|
||||||
// since lines are lists of characters that might span multiple
|
|
||||||
// codepoints, we need to calculate the byte-wise cursor position
|
|
||||||
// to avoid incorrect cursor rendering.
|
|
||||||
//
|
|
||||||
const line = this.state.lines[this.state.lines.length - 1];
|
|
||||||
let hops;
|
|
||||||
if (line.lin) {
|
|
||||||
hops = line.lin.slice(0, blit.hop);
|
|
||||||
} else if (line.klr) {
|
|
||||||
hops = line.klr.reduce((h, p) => {
|
|
||||||
if (h.length >= blit.hop)
|
|
||||||
return h;
|
|
||||||
return [...h, ...p.text.slice(0, blit.hop - h.length)];
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
this.setState({ cursor: hops.join('').length });
|
|
||||||
break;
|
|
||||||
case 'lin':
|
|
||||||
this.state.lines[this.state.lines.length - 1] = blit;
|
|
||||||
this.setState({ lines: this.state.lines });
|
|
||||||
break;
|
|
||||||
case 'klr':
|
|
||||||
this.state.lines[this.state.lines.length - 1] = blit;
|
|
||||||
this.setState({ lines: this.state.lines });
|
|
||||||
break;
|
|
||||||
case 'mor':
|
|
||||||
this.state.lines.push('');
|
|
||||||
this.setState({ lines: this.state.lines });
|
|
||||||
break;
|
|
||||||
case 'sag':
|
|
||||||
blit.sav = blit.sag;
|
|
||||||
break;
|
|
||||||
case 'sav':
|
|
||||||
const name = blit.sav.path.split('/').slice(-2).join('.');
|
|
||||||
const buff = new Buffer(blit.sav.file, 'base64');
|
|
||||||
const blob = new Blob([buff], { type: 'application/octet-stream' });
|
|
||||||
saveAs(blob, name);
|
|
||||||
break;
|
|
||||||
case 'url':
|
|
||||||
// TODO too invasive? just print as <a>?
|
|
||||||
window.open(blit.url);
|
|
||||||
break;
|
|
||||||
default: console.log('weird blit', blit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setStateHandler(setState) {
|
|
||||||
this.setState = setState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
|||||||
export default class Subscription {
|
|
||||||
store: any;
|
|
||||||
api: any;
|
|
||||||
channel: any;
|
|
||||||
firstRoundComplete: boolean;
|
|
||||||
constructor(store, api, channel) {
|
|
||||||
this.store = store;
|
|
||||||
this.api = api;
|
|
||||||
this.channel = channel;
|
|
||||||
|
|
||||||
this.channel.setOnChannelError(this.onChannelError.bind(this));
|
|
||||||
this.firstRoundComplete = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
if (this.api.ship) {
|
|
||||||
this.firstRound();
|
|
||||||
} else {
|
|
||||||
console.error('~~~ ERROR: Must set api.ship before operation ~~~');
|
|
||||||
}
|
|
||||||
this.setupSlog();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupSlog() {
|
|
||||||
let available = false;
|
|
||||||
const slog = new EventSource('/~_~/slog', { withCredentials: true });
|
|
||||||
|
|
||||||
slog.onopen = (e) => {
|
|
||||||
console.log('slog: opened stream');
|
|
||||||
available = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
slog.onmessage = (e) => {
|
|
||||||
this.handleEvent({ slog: e.data });
|
|
||||||
};
|
|
||||||
|
|
||||||
slog.onerror = (e) => {
|
|
||||||
console.error('slog: eventsource error:', e);
|
|
||||||
if (available) {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
if (slog.readyState !== EventSource.CLOSED)
|
|
||||||
return;
|
|
||||||
console.log('slog: reconnecting...');
|
|
||||||
this.setupSlog();
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
delete() {
|
|
||||||
this.channel.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
onChannelError(err) {
|
|
||||||
console.error('event source error: ', err);
|
|
||||||
console.log('initiating new channel');
|
|
||||||
this.firstRoundComplete = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.store.handleEvent({
|
|
||||||
data: { clear : true }
|
|
||||||
});
|
|
||||||
|
|
||||||
this.start();
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(path, app) {
|
|
||||||
this.api.bind(path, 'PUT', this.api.ship, app,
|
|
||||||
this.handleEvent.bind(this),
|
|
||||||
(err) => {
|
|
||||||
console.log(err);
|
|
||||||
this.subscribe(path, app);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.subscribe(path, app);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
firstRound() {
|
|
||||||
this.subscribe('/session/', 'herm');
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent(diff) {
|
|
||||||
this.store.handleEvent(diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +1,34 @@
|
|||||||
/- view-sur=group-view, group-store, *group, metadata=metadata-store, hark=hark-store
|
/- view-sur=group-view, group-store, *group, metadata=metadata-store, hark=hark-store
|
||||||
|
/- inv=invite-store
|
||||||
/+ default-agent, agentio, mdl=metadata,
|
/+ default-agent, agentio, mdl=metadata,
|
||||||
resource, dbug, grpl=group, conl=contact, verb
|
resource, dbug, grpl=group, conl=contact, verb
|
||||||
|%
|
|%
|
||||||
++ card card:agent:gall
|
++ card card:agent:gall
|
||||||
::
|
::
|
||||||
+$ base-state-0
|
|
||||||
joining=(map rid=resource [=ship =progress:view])
|
|
||||||
::
|
::
|
||||||
+$ base-state-1
|
|
||||||
joining=(map rid=resource request:view)
|
|
||||||
::
|
::
|
||||||
+$ state-zero
|
+$ state-zero
|
||||||
[%0 base-state-0]
|
[%0 *]
|
||||||
::
|
::
|
||||||
+$ state-one
|
+$ state-one
|
||||||
[%1 base-state-0]
|
[%1 *]
|
||||||
::
|
::
|
||||||
+$ state-two
|
+$ state-two
|
||||||
[%2 base-state-1]
|
[%2 *]
|
||||||
|
::
|
||||||
|
+$ state-three
|
||||||
|
[%3 joining=(map rid=resource request:view)]
|
||||||
::
|
::
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-zero
|
$% state-zero
|
||||||
state-one
|
state-one
|
||||||
state-two
|
state-two
|
||||||
|
state-three
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ view view-sur
|
++ view view-sur
|
||||||
--
|
--
|
||||||
=| state-two
|
=| state-three
|
||||||
=* state -
|
=* state -
|
||||||
::
|
::
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
@ -48,29 +49,10 @@
|
|||||||
|= =vase
|
|= =vase
|
||||||
=+ !<(old=versioned-state vase)
|
=+ !<(old=versioned-state vase)
|
||||||
=| cards=(list card)
|
=| cards=(list card)
|
||||||
|^
|
|-
|
||||||
?- -.old
|
?: ?=(%3 -.old)
|
||||||
%2 [cards this(state old)]
|
[cards this(state old)]
|
||||||
%1 $(-.old %2, +.old (base-state-to-1 +.old))
|
$(old *state-three)
|
||||||
%0 $(-.old %1, cards :_(cards (poke-self:pass:io noun+!>(%cleanup))))
|
|
||||||
==
|
|
||||||
::
|
|
||||||
++ base-state-to-1
|
|
||||||
|= base-state-0
|
|
||||||
%- ~(gas by *(map resource request:view))
|
|
||||||
(turn ~(tap by joining) request-to-1)
|
|
||||||
::
|
|
||||||
++ request-to-1
|
|
||||||
|= [rid=resource =ship =progress:view]
|
|
||||||
^- [resource request:view]
|
|
||||||
:- rid
|
|
||||||
%* . *request:view
|
|
||||||
started now.bowl
|
|
||||||
hidden %.n
|
|
||||||
ship ship
|
|
||||||
progress progress
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
::
|
||||||
++ on-poke
|
++ on-poke
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
@ -84,8 +66,9 @@
|
|||||||
=+ !<(=action:view vase)
|
=+ !<(=action:view vase)
|
||||||
=^ cards state
|
=^ cards state
|
||||||
?+ -.action !!
|
?+ -.action !!
|
||||||
%join jn-abet:(jn-start:join:gc +.action)
|
%join jn-abet:(jn-start:join:gc +.action)
|
||||||
%hide (hide:gc +.action)
|
%abort jn-abet:(jn-abort:join:gc +.action)
|
||||||
|
%done jn-abet:(jn-done:join:gc +.action)
|
||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
@ -106,7 +89,7 @@
|
|||||||
++ on-agent
|
++ on-agent
|
||||||
|= [=wire =sign:agent:gall]
|
|= [=wire =sign:agent:gall]
|
||||||
=^ cards state
|
=^ cards state
|
||||||
?+ wire `state
|
?+ wire (on-agent:def:gc wire sign)
|
||||||
[%join %ship @ @ *]
|
[%join %ship @ @ *]
|
||||||
=/ rid
|
=/ rid
|
||||||
(de-path:resource t.wire)
|
(de-path:resource t.wire)
|
||||||
@ -115,7 +98,18 @@
|
|||||||
==
|
==
|
||||||
[cards this]
|
[cards this]
|
||||||
::
|
::
|
||||||
++ on-arvo on-arvo:def
|
++ on-arvo
|
||||||
|
|= [=wire sign=sign-arvo]
|
||||||
|
=^ cards state
|
||||||
|
?+ wire (on-arvo:def:gc wire sign)
|
||||||
|
[%breach ~]
|
||||||
|
?> ?=([%jael %public-keys *] sign)
|
||||||
|
?. ?=(%breach -.public-keys-result.sign)
|
||||||
|
`state
|
||||||
|
(breach who.public-keys-result.sign)
|
||||||
|
==
|
||||||
|
[cards this]
|
||||||
|
::
|
||||||
++ on-leave on-leave:def
|
++ on-leave on-leave:def
|
||||||
++ on-fail on-fail:def
|
++ on-fail on-fail:def
|
||||||
--
|
--
|
||||||
@ -124,6 +118,7 @@
|
|||||||
++ grp ~(. grpl bowl)
|
++ grp ~(. grpl bowl)
|
||||||
++ io ~(. agentio bowl)
|
++ io ~(. agentio bowl)
|
||||||
++ con ~(. conl bowl)
|
++ con ~(. conl bowl)
|
||||||
|
++ def ~(. (default-agent state %|) bowl)
|
||||||
++ hide
|
++ hide
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
^- (quip card _state)
|
^- (quip card _state)
|
||||||
@ -133,7 +128,28 @@
|
|||||||
:_ state
|
:_ state
|
||||||
(fact:io group-view-update+!>(`update:view`[%initial joining]) /all ~)^~
|
(fact:io group-view-update+!>(`update:view`[%initial joining]) /all ~)^~
|
||||||
:- (fact:io group-view-update+!>([%hide rid]) /all ~)^~
|
:- (fact:io group-view-update+!>([%hide rid]) /all ~)^~
|
||||||
state(joining (~(put by joining) rid request(hidden %.y)))
|
state(joining (~(put by joining) rid request))
|
||||||
|
::
|
||||||
|
++ is-tracking
|
||||||
|
|= her=ship
|
||||||
|
^- ?
|
||||||
|
%+ lien ~(tap in ~(key by joining))
|
||||||
|
|=([him=ship name=term] =(her him))
|
||||||
|
::
|
||||||
|
++ breach
|
||||||
|
|= who=ship
|
||||||
|
^- (quip card _state)
|
||||||
|
=/ requests=(list [rid=resource =request:view])
|
||||||
|
~(tap by joining)
|
||||||
|
=| cards=(list card)
|
||||||
|
|- ^- (quip card _state)
|
||||||
|
?~ requests
|
||||||
|
[cards state]
|
||||||
|
?. =(entity.rid.i.requests who)
|
||||||
|
$(requests t.requests)
|
||||||
|
=^ crds state
|
||||||
|
jn-abet:jn-breach:(jn-abed:join rid.i.requests)
|
||||||
|
[(welp cards crds) state]
|
||||||
::
|
::
|
||||||
++ has-joined
|
++ has-joined
|
||||||
|= rid=resource
|
|= rid=resource
|
||||||
@ -170,12 +186,54 @@
|
|||||||
(emit (fact:io cage /all tx+(en-path:resource rid) ~))
|
(emit (fact:io cage /all tx+(en-path:resource rid) ~))
|
||||||
group-view-update+!>([%progress rid progress])
|
group-view-update+!>([%progress rid progress])
|
||||||
::
|
::
|
||||||
++ watch-md
|
++ pass
|
||||||
(emit (watch-our:(jn-pass-io /md) %metadata-store /updates))
|
|%
|
||||||
::
|
++ pull-action pull-hook-action+!>([%add ship rid])
|
||||||
++ watch-groups
|
::
|
||||||
(emit (watch-our:(jn-pass-io /groups) %group-store /groups))
|
++ watch-md (watch-our:(jn-pass-io /md) %metadata-store /updates)
|
||||||
::
|
++ watch-groups (watch-our:(jn-pass-io /groups) %group-store /groups)
|
||||||
|
++ watch-md-nacks (watch-our:(jn-pass-io /md-nacks) %metadata-pull-hook /nack)
|
||||||
|
++ watch-grp-nacks (watch-our:(jn-pass-io /grp-nacks) %group-pull-hook /nack)
|
||||||
|
::
|
||||||
|
++ add-us
|
||||||
|
%+ poke:(jn-pass-io /add)
|
||||||
|
[ship %group-push-hook]
|
||||||
|
group-update-0+!>([%add-members rid (silt our.bowl ~)])
|
||||||
|
::
|
||||||
|
++ del-us
|
||||||
|
%+ poke:pass:io [ship %group-push-hook]
|
||||||
|
group-update-0+!>([%remove-members rid (silt our.bowl ~)])
|
||||||
|
::
|
||||||
|
++ remove-pull-groups
|
||||||
|
(poke-our:pass:io %group-pull-hook pull-hook-action+!>([%remove rid]))
|
||||||
|
::
|
||||||
|
++ pull-groups
|
||||||
|
(poke-our:(jn-pass-io /poke) %group-pull-hook pull-action)
|
||||||
|
++ pull-md
|
||||||
|
(poke-our:(jn-pass-io /poke) %metadata-pull-hook pull-action)
|
||||||
|
++ pull-co
|
||||||
|
(poke-our:(jn-pass-io /poke) %contact-pull-hook pull-action)
|
||||||
|
::
|
||||||
|
++ allow-co
|
||||||
|
%+ poke-our:(jn-pass-io /poke) %contact-store
|
||||||
|
contact-update-0+!>([%allow %group rid])
|
||||||
|
::
|
||||||
|
++ share-co
|
||||||
|
%+ poke:(jn-pass-io /poke)
|
||||||
|
[entity.rid %contact-push-hook]
|
||||||
|
[%contact-share !>([%share our.bowl])]
|
||||||
|
::
|
||||||
|
++ pull-gra
|
||||||
|
|= gr=resource
|
||||||
|
(poke-our:(jn-pass-io /poke) %graph-pull-hook pull-hook-action+!>([%add entity .]:gr))
|
||||||
|
::
|
||||||
|
++ retry
|
||||||
|
(poke-self:pass:io group-view-action+!>([%join rid ship]))
|
||||||
|
++ watch-breach
|
||||||
|
(~(arvo pass:io /breach) %j %public-keys (silt ship ~))
|
||||||
|
++ leave-breach
|
||||||
|
(~(arvo pass:io /breach) %j %nuke (silt ship ~))
|
||||||
|
--
|
||||||
++ jn-pass-io
|
++ jn-pass-io
|
||||||
|= pax=path
|
|= pax=path
|
||||||
~(. pass:io (welp join+(en-path:resource rid) pax))
|
~(. pass:io (welp join+(en-path:resource rid) pax))
|
||||||
@ -191,12 +249,14 @@
|
|||||||
[(flop cards) state]
|
[(flop cards) state]
|
||||||
::
|
::
|
||||||
++ jn-start
|
++ jn-start
|
||||||
|= [rid=resource =^ship]
|
|= [rid=resource =^ship =app:view share-co=? autojoin=?]
|
||||||
^+ jn-core
|
^+ jn-core
|
||||||
?> ?= $@(~ [~ %done])
|
?> ?= $@(~ [~ ?(%done %abort)])
|
||||||
(bind (~(get by joining) rid) |=(request:view progress))
|
(bind (~(get by joining) rid) |=(request:view progress))
|
||||||
|
=/ =request:view
|
||||||
|
[now.bowl ship %start app share-co autojoin (get-invites app rid)]
|
||||||
=. joining
|
=. joining
|
||||||
(~(put by joining) rid [%.n now.bowl ship %start])
|
(~(put by joining) rid request)
|
||||||
=. jn-core
|
=. jn-core
|
||||||
(jn-abed rid)
|
(jn-abed rid)
|
||||||
=. jn-core
|
=. jn-core
|
||||||
@ -205,14 +265,80 @@
|
|||||||
group-view-update+!>([%started rid (~(got by joining) rid)])
|
group-view-update+!>([%started rid (~(got by joining) rid)])
|
||||||
~[/all]
|
~[/all]
|
||||||
?< ~|("already joined {<rid>}" (has-joined rid))
|
?< ~|("already joined {<rid>}" (has-joined rid))
|
||||||
=. jn-core
|
=. jn-core (emit add-us:pass)
|
||||||
%- emit
|
|
||||||
%+ poke:(jn-pass-io /add)
|
|
||||||
[ship %group-push-hook]
|
|
||||||
group-update-0+!>([%add-members rid (silt our.bowl ~)])
|
|
||||||
=. jn-core (tx-progress %start)
|
=. jn-core (tx-progress %start)
|
||||||
=> watch-md
|
=? jn-core !(is-tracking ship)
|
||||||
watch-groups
|
(emit watch-breach:pass)
|
||||||
|
=> (emit watch-md:pass)
|
||||||
|
=> (emit watch-groups:pass)
|
||||||
|
=> (emit watch-grp-nacks:pass)
|
||||||
|
=> (emit watch-md-nacks:pass)
|
||||||
|
(emit watch-breach:pass)
|
||||||
|
::
|
||||||
|
++ jn-breach
|
||||||
|
=/ =request:view (~(got by joining) rid)
|
||||||
|
?. ?=(%start progress.request)
|
||||||
|
:: no action required, subscriptions are sane across breaches
|
||||||
|
jn-core
|
||||||
|
(emit add-us:pass)
|
||||||
|
::
|
||||||
|
++ jn-abort
|
||||||
|
|= r=resource
|
||||||
|
^+ jn-core
|
||||||
|
=. jn-core (jn-abed r)
|
||||||
|
(cleanup:rollback %abort)
|
||||||
|
::
|
||||||
|
++ jn-done
|
||||||
|
|= r=resource
|
||||||
|
=. joining (~(del by joining) r)
|
||||||
|
jn-core
|
||||||
|
::
|
||||||
|
++ rollback
|
||||||
|
|^
|
||||||
|
=/ =request:view (~(got by joining) rid)
|
||||||
|
?+ progress.request ~|(cannot-rollback/progress.request !!)
|
||||||
|
%start start
|
||||||
|
%added added
|
||||||
|
%metadata metadata
|
||||||
|
==
|
||||||
|
++ start jn-core
|
||||||
|
++ added (emit del-us:pass)
|
||||||
|
++ metadata (emit:added remove-pull-groups:pass)
|
||||||
|
--
|
||||||
|
::
|
||||||
|
++ get-invites
|
||||||
|
|= [=app:view rid=resource]
|
||||||
|
^- (set uid:view)
|
||||||
|
=+ .^(invit=(unit invitatory:inv) %gx (scry:io %invite-store /invitatory/[app]/noun))
|
||||||
|
?~ invit ~
|
||||||
|
%- ~(gas in *(set uid:view))
|
||||||
|
%+ murn ~(tap by u.invit)
|
||||||
|
|= [=uid:view =invite:inv]
|
||||||
|
?. =(rid resource.invite) ~
|
||||||
|
`uid
|
||||||
|
::
|
||||||
|
++ cleanup
|
||||||
|
|= =progress:view
|
||||||
|
=. jn-core
|
||||||
|
(tx-progress progress)
|
||||||
|
=. jn-core
|
||||||
|
(emit (leave-our:(jn-pass-io /groups) %group-store))
|
||||||
|
=. jn-core
|
||||||
|
(emit (leave-our:(jn-pass-io /md) %metadata-store))
|
||||||
|
=. jn-core
|
||||||
|
(emit (leave-our:(jn-pass-io /md-nacks) %metadata-pull-hook))
|
||||||
|
=. jn-core
|
||||||
|
(emit (leave-our:(jn-pass-io /grp-nacks) %group-pull-hook))
|
||||||
|
=/ =request:view (~(got by joining) rid)
|
||||||
|
=. jn-core
|
||||||
|
%- emit-many
|
||||||
|
%+ turn ~(tap in invite.request)
|
||||||
|
|= =uid:view
|
||||||
|
%+ poke-our:pass:io %invite-store
|
||||||
|
=- invite-action+!>(-)
|
||||||
|
^- action:inv
|
||||||
|
[%accept `@tas`app.request uid]
|
||||||
|
jn-core
|
||||||
::
|
::
|
||||||
++ jn-agent
|
++ jn-agent
|
||||||
|= [=wire =sign:agent:gall]
|
|= [=wire =sign:agent:gall]
|
||||||
@ -225,34 +351,16 @@
|
|||||||
(cleanup %no-perms)
|
(cleanup %no-perms)
|
||||||
=. jn-core
|
=. jn-core
|
||||||
(tx-progress %added)
|
(tx-progress %added)
|
||||||
%- emit
|
(emit pull-groups:pass)
|
||||||
%+ poke-our:(jn-pass-io /pull-groups) %group-pull-hook
|
|
||||||
pull-hook-action+!>([%add ship rid])
|
|
||||||
::
|
|
||||||
%pull-groups
|
|
||||||
?> ?=(%poke-ack -.sign)
|
|
||||||
(ack +.sign)
|
|
||||||
::
|
::
|
||||||
%groups
|
%groups
|
||||||
?+ -.sign !!
|
?+ -.sign !!
|
||||||
%fact (groups-fact +.sign)
|
%fact (groups-fact +.sign)
|
||||||
%watch-ack (ack +.sign)
|
%watch-ack (ack +.sign)
|
||||||
%kick watch-groups
|
%kick (emit watch-groups:pass)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
%pull-md
|
%poke
|
||||||
?> ?=(%poke-ack -.sign)
|
|
||||||
(ack +.sign)
|
|
||||||
::
|
|
||||||
%pull-co
|
|
||||||
?> ?=(%poke-ack -.sign)
|
|
||||||
(ack +.sign)
|
|
||||||
::
|
|
||||||
%share-co
|
|
||||||
?> ?=(%poke-ack -.sign)
|
|
||||||
(ack +.sign)
|
|
||||||
::
|
|
||||||
%push-co
|
|
||||||
?> ?=(%poke-ack -.sign)
|
?> ?=(%poke-ack -.sign)
|
||||||
(ack +.sign)
|
(ack +.sign)
|
||||||
::
|
::
|
||||||
@ -260,13 +368,38 @@
|
|||||||
?+ -.sign !!
|
?+ -.sign !!
|
||||||
%fact (md-fact +.sign)
|
%fact (md-fact +.sign)
|
||||||
%watch-ack (ack +.sign)
|
%watch-ack (ack +.sign)
|
||||||
%kick watch-md
|
%kick (emit watch-md:pass)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
%pull-graphs
|
%pull-graphs
|
||||||
?> ?=(%poke-ack -.sign)
|
?> ?=(%poke-ack -.sign)
|
||||||
%- cleanup
|
%- cleanup
|
||||||
?^(p.sign %strange %done)
|
?^(p.sign %strange %done)
|
||||||
|
::
|
||||||
|
%md-nacks
|
||||||
|
?+ -.sign !!
|
||||||
|
%watch-ack (ack +.sign)
|
||||||
|
%kick (emit watch-md-nacks:pass)
|
||||||
|
::
|
||||||
|
%fact
|
||||||
|
?. =(%resource p.cage.sign) jn-core
|
||||||
|
=+ !<(nack=resource q.cage.sign)
|
||||||
|
?. =(nack rid) jn-core
|
||||||
|
(cleanup %strange)
|
||||||
|
==
|
||||||
|
::
|
||||||
|
%grp-nacks
|
||||||
|
?+ -.sign !!
|
||||||
|
%watch-ack (ack +.sign)
|
||||||
|
%kick (emit watch-grp-nacks:pass)
|
||||||
|
::
|
||||||
|
%fact
|
||||||
|
?. =(%resource p.cage.sign) jn-core
|
||||||
|
=+ !<(nack=resource q.cage.sign)
|
||||||
|
?. =(nack rid) jn-core
|
||||||
|
(cleanup %strange)
|
||||||
|
==
|
||||||
|
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ groups-fact
|
++ groups-fact
|
||||||
@ -274,19 +407,15 @@
|
|||||||
?. ?=(%group-update-0 p.cage) jn-core
|
?. ?=(%group-update-0 p.cage) jn-core
|
||||||
=+ !<(=update:group-store q.cage)
|
=+ !<(=update:group-store q.cage)
|
||||||
?. ?=(%initial-group -.update) jn-core
|
?. ?=(%initial-group -.update) jn-core
|
||||||
|
=/ =request:view (~(got by joining) rid)
|
||||||
?. =(rid resource.update) jn-core
|
?. =(rid resource.update) jn-core
|
||||||
%- emit-many
|
=. jn-core (emit pull-md:pass)
|
||||||
=/ cag=^cage pull-hook-action+!>([%add [entity .]:rid])
|
=. jn-core (emit pull-co:pass)
|
||||||
%- zing
|
?. |(share-co.request scry-is-public:con)
|
||||||
:~ [(poke-our:(jn-pass-io /pull-md) %metadata-pull-hook cag)]~
|
jn-core
|
||||||
[(poke-our:(jn-pass-io /pull-co) %contact-pull-hook cag)]~
|
?: scry-is-public:con (emit share-co:pass)
|
||||||
::
|
=. jn-core (emit allow-co:pass)
|
||||||
?. scry-is-public:con ~
|
(emit share-co:pass)
|
||||||
:_ ~
|
|
||||||
%+ poke:(jn-pass-io /share-co)
|
|
||||||
[entity.rid %contact-push-hook]
|
|
||||||
[%contact-share !>([%share our.bowl])]
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
++ md-fact
|
++ md-fact
|
||||||
|= [=mark =vase]
|
|= [=mark =vase]
|
||||||
@ -294,32 +423,40 @@
|
|||||||
=+ !<(=update:metadata vase)
|
=+ !<(=update:metadata vase)
|
||||||
?. ?=(%initial-group -.update) jn-core
|
?. ?=(%initial-group -.update) jn-core
|
||||||
?. =(group.update rid) jn-core
|
?. =(group.update rid) jn-core
|
||||||
|
|^ ^+ jn-core
|
||||||
|
=/ =request:view (~(got by joining) rid)
|
||||||
|
=/ feed feed-rid
|
||||||
=. jn-core (cleanup %done)
|
=. jn-core (cleanup %done)
|
||||||
?. hidden:(need (scry-group:grp rid))
|
=/ hidden hidden:(need (scry-group:grp rid))
|
||||||
|
=? jn-core ?&(!hidden ?=(^ feed))
|
||||||
|
%- emit
|
||||||
|
(pull-gra:pass (need feed))
|
||||||
|
=? jn-core |(hidden autojoin.request)
|
||||||
|
%- emit-many
|
||||||
|
(turn graphs pull-gra:pass)
|
||||||
|
jn-core
|
||||||
|
::
|
||||||
|
++ feed-rid
|
||||||
|
^- (unit resource)
|
||||||
=/ list-md=(list [=md-resource:metadata =association:metadata])
|
=/ list-md=(list [=md-resource:metadata =association:metadata])
|
||||||
%+ skim ~(tap by associations.update)
|
%+ skim ~(tap by associations.update)
|
||||||
|= [=md-resource:metadata =association:metadata]
|
|= [=md-resource:metadata =association:metadata]
|
||||||
=(app-name.md-resource %groups)
|
=(app-name.md-resource %groups)
|
||||||
?> ?=(^ list-md)
|
?~ list-md ~
|
||||||
=* metadatum metadatum.association.i.list-md
|
=* metadatum metadatum.association.i.list-md
|
||||||
?. ?& ?=(%group -.config.metadatum)
|
?. ?& ?=(%group -.config.metadatum)
|
||||||
?=(^ feed.config.metadatum)
|
?=([~ ~ *] feed.config.metadatum)
|
||||||
?=(^ u.feed.config.metadatum)
|
|
||||||
==
|
==
|
||||||
jn-core
|
~
|
||||||
=* feed resource.u.u.feed.config.metadatum
|
`resource.u.u.feed.config.metadatum
|
||||||
%- emit
|
::
|
||||||
%+ poke-our:(jn-pass-io /pull-feed) %graph-pull-hook
|
++ graphs
|
||||||
pull-hook-action+!>([%add [entity .]:feed])
|
^- (list resource)
|
||||||
%- emit-many
|
%+ murn ~(tap by associations.update)
|
||||||
%+ murn ~(tap by associations.update)
|
|= [=md-resource:metadata =association:metadata]
|
||||||
|= [=md-resource:metadata =association:metadata]
|
?. =(app-name.md-resource %graph) ~
|
||||||
^- (unit card)
|
`resource.md-resource
|
||||||
?. =(app-name.md-resource %graph) ~
|
--
|
||||||
=* rid resource.md-resource
|
|
||||||
:- ~
|
|
||||||
%+ poke-our:(jn-pass-io /pull-graph) %graph-pull-hook
|
|
||||||
pull-hook-action+!>([%add [entity .]:rid])
|
|
||||||
::
|
::
|
||||||
++ ack
|
++ ack
|
||||||
|= err=(unit tang)
|
|= err=(unit tang)
|
||||||
@ -327,66 +464,6 @@
|
|||||||
%- (slog u.err)
|
%- (slog u.err)
|
||||||
(cleanup %strange)
|
(cleanup %strange)
|
||||||
::
|
::
|
||||||
++ notify
|
|
||||||
%- emit
|
|
||||||
%+ poke-our:(jn-pass-io /hark) %hark-store
|
|
||||||
=- hark-action+!>(-)
|
|
||||||
^- action:hark
|
|
||||||
|^
|
|
||||||
[%add-note bin body]
|
|
||||||
++ bin
|
|
||||||
^- bin:hark
|
|
||||||
[/ [q.byk.bowl /join/(scot %p entity.rid)/[name.rid]]]
|
|
||||||
++ title
|
|
||||||
|= [name=@t rest=@t]
|
|
||||||
text/(rap 3 'Joining group: "' name '" ' rest ~)
|
|
||||||
++ body
|
|
||||||
^- body:hark
|
|
||||||
=/ =request:view (~(got by joining) rid)
|
|
||||||
?> ?=(final:view progress.request)
|
|
||||||
=/ name (rap 3 (scot %p entity.rid) '/' name.rid ~)
|
|
||||||
?- progress.request
|
|
||||||
::
|
|
||||||
%done
|
|
||||||
=/ =metadatum:metadata (need (peek-metadatum:met %groups rid))
|
|
||||||
:* ~[(title title.metadatum 'succeeded')]
|
|
||||||
~
|
|
||||||
now.bowl
|
|
||||||
/
|
|
||||||
/groups/(scot %p entity.rid)/[name.rid]
|
|
||||||
==
|
|
||||||
::
|
|
||||||
%strange
|
|
||||||
:* ~[(title name 'errored unexpectedly')]
|
|
||||||
~
|
|
||||||
now.bowl
|
|
||||||
/
|
|
||||||
/
|
|
||||||
==
|
|
||||||
::
|
|
||||||
%no-perms
|
|
||||||
:* ~[(title name 'failed, you are not permitted to join the group')]
|
|
||||||
~
|
|
||||||
now.bowl
|
|
||||||
/
|
|
||||||
/
|
|
||||||
==
|
|
||||||
==
|
|
||||||
--
|
|
||||||
::
|
|
||||||
++ cleanup
|
|
||||||
|= =progress:view
|
|
||||||
=. jn-core
|
|
||||||
(tx-progress progress)
|
|
||||||
=. jn-core
|
|
||||||
(emit (leave-our:(jn-pass-io /groups) %group-store))
|
|
||||||
=. jn-core
|
|
||||||
(emit (leave-our:(jn-pass-io /md) %metadata-store))
|
|
||||||
=/ =request:view (~(got by joining) rid)
|
|
||||||
=? jn-core (lte (sub now.bowl started.request) ~s30)
|
|
||||||
notify
|
|
||||||
=. joining (~(del by joining) rid)
|
|
||||||
jn-core
|
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
|
@ -38,13 +38,13 @@
|
|||||||
=/ act=action !<(action vase)
|
=/ act=action !<(action vase)
|
||||||
?+ -.act ~
|
?+ -.act ~
|
||||||
%invites
|
%invites
|
||||||
?. (team:title [our src]:bowl) ~
|
?. =,(bowl =(our src)) ~
|
||||||
:: outgoing. we must be inviting other ships. send them each an invite
|
:: outgoing. we must be inviting other ships. send them each an invite
|
||||||
::
|
::
|
||||||
%+ turn ~(tap in recipients.invites.act)
|
%+ turn ~(tap in recipients.invites.act)
|
||||||
|= recipient=ship
|
|= recipient=ship
|
||||||
^- card
|
^- card
|
||||||
?< (team:title our.bowl recipient)
|
?< =,(bowl =(our recipient))
|
||||||
%+ invite-hook-poke recipient
|
%+ invite-hook-poke recipient
|
||||||
:^ %invite term.act uid.act
|
:^ %invite term.act uid.act
|
||||||
^- invite
|
^- invite
|
||||||
@ -56,10 +56,10 @@
|
|||||||
==
|
==
|
||||||
::
|
::
|
||||||
%invite
|
%invite
|
||||||
?: (team:title [our src]:bowl)
|
?: =,(bowl =(our src))
|
||||||
:: outgoing. we must be inviting another ship. send them the invite.
|
:: outgoing. we must be inviting another ship. send them the invite.
|
||||||
::
|
::
|
||||||
?< (team:title our.bowl recipient.invite.act)
|
?< =(our.bowl recipient.invite.act)
|
||||||
[(invite-hook-poke recipient.invite.act act)]~
|
[(invite-hook-poke recipient.invite.act act)]~
|
||||||
:: else incoming. ensure invitatory exists and invite is not a duplicate.
|
:: else incoming. ensure invitatory exists and invite is not a duplicate.
|
||||||
::
|
::
|
||||||
|
@ -196,7 +196,7 @@
|
|||||||
^- (unit (unit cage))
|
^- (unit (unit cage))
|
||||||
?+ path (on-peek:def path)
|
?+ path (on-peek:def path)
|
||||||
[%x %all ~]
|
[%x %all ~]
|
||||||
``noun+!>(invites)
|
``invite-update+!>([%initial invites])
|
||||||
::
|
::
|
||||||
[%x %invitatory @ ~]
|
[%x %invitatory @ ~]
|
||||||
:^ ~ ~ %noun
|
:^ ~ ~ %noun
|
||||||
|
@ -296,19 +296,21 @@
|
|||||||
++ on-watch
|
++ on-watch
|
||||||
|= =path
|
|= =path
|
||||||
?> (team:title [our src]:bowl)
|
?> (team:title [our src]:bowl)
|
||||||
?. ?=([%preview @ @ @ ~] path)
|
?+ path (on-watch:def path)
|
||||||
(on-watch:def path)
|
::
|
||||||
=/ rid=resource
|
[%preview @ @ @ ~]
|
||||||
(de-path:resource t.path)
|
=/ rid=resource
|
||||||
=/ prev=(unit group-preview:metadata)
|
(de-path:resource t.path)
|
||||||
?^ (peek-metadatum:met %groups rid)
|
=/ prev=(unit group-preview:metadata)
|
||||||
(some (get-preview:met rid))
|
?^ (peek-metadatum:met %groups rid)
|
||||||
(~(get by previews) rid)
|
(some (get-preview:met rid))
|
||||||
?~ prev
|
(~(get by previews) rid)
|
||||||
:_ this(pending (~(put in pending) rid))
|
?~ prev
|
||||||
(get-preview rid)^~
|
:_ this(pending (~(put in pending) rid))
|
||||||
:_ this
|
(get-preview rid)^~
|
||||||
(fact-init:io metadata-hook-update+!>([%preview u.prev]))^~
|
:_ this
|
||||||
|
(fact-init:io metadata-hook-update+!>([%preview u.prev]))^~
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-leave on-leave:def
|
++ on-leave on-leave:def
|
||||||
++ on-peek on-peek:def
|
++ on-peek on-peek:def
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
$: =provider-state
|
$: =provider-state
|
||||||
=client-state
|
=client-state
|
||||||
==
|
==
|
||||||
|
+$ base-state-2
|
||||||
|
$: notifications=(map uid notification)
|
||||||
|
base-state-0
|
||||||
|
==
|
||||||
::
|
::
|
||||||
+$ state-0
|
+$ state-0
|
||||||
[%0 base-state-0]
|
[%0 base-state-0]
|
||||||
@ -29,14 +33,18 @@
|
|||||||
+$ state-1
|
+$ state-1
|
||||||
[%1 base-state-0]
|
[%1 base-state-0]
|
||||||
::
|
::
|
||||||
|
+$ state-2
|
||||||
|
[%2 base-state-2]
|
||||||
|
::
|
||||||
+$ versioned-state
|
+$ versioned-state
|
||||||
$% state-0
|
$% state-0
|
||||||
state-1
|
state-1
|
||||||
|
state-2
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
--
|
--
|
||||||
::
|
::
|
||||||
=| state-1
|
=| state-2
|
||||||
=* state -
|
=* state -
|
||||||
::
|
::
|
||||||
%- agent:dbug
|
%- agent:dbug
|
||||||
@ -53,7 +61,9 @@
|
|||||||
::
|
::
|
||||||
++ on-init
|
++ on-init
|
||||||
:_ this
|
:_ this
|
||||||
[(~(watch-our pass:io /hark) %hark-store /notes)]~
|
:~ (~(watch-our pass:io /hark/notes) %hark-store /notes)
|
||||||
|
(~(watch-our pass:io /hark/updates) %hark-store /updates)
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-save !>(state)
|
++ on-save !>(state)
|
||||||
++ on-load
|
++ on-load
|
||||||
@ -63,17 +73,22 @@
|
|||||||
=| cards=(list card)
|
=| cards=(list card)
|
||||||
|-
|
|-
|
||||||
?- -.old
|
?- -.old
|
||||||
%1 [(flop cards) this]
|
|
||||||
::
|
::
|
||||||
%0
|
%2
|
||||||
%_ $
|
=/ upd=wire /hark/updates
|
||||||
-.old %1
|
=/ not=wire /hark/notes
|
||||||
::
|
=/ =dock [our.bowl %hark-store]
|
||||||
cards
|
=? cards !(~(has by wex.bowl) [upd dock]) :: rewatch updates
|
||||||
%+ welp cards
|
:_(cards [%pass upd %agent dock %watch /updates])
|
||||||
:~ (~(leave-our pass:io /hark) %hark-store)
|
=? cards !(~(has by wex.bowl) [not dock]) :: rewatch notes
|
||||||
(~(watch-our pass:io /hark) %hark-store /notes)
|
:_(cards [%pass not %agent dock %watch /notes])
|
||||||
==
|
=. notifications.old ~
|
||||||
|
[(flop cards) this(state old)]
|
||||||
|
::
|
||||||
|
?(%0 %1)
|
||||||
|
%_ $
|
||||||
|
-.old %2
|
||||||
|
+.old [~ +.old]
|
||||||
==
|
==
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -203,7 +218,17 @@
|
|||||||
^- (quip card _this)
|
^- (quip card _this)
|
||||||
`this
|
`this
|
||||||
::
|
::
|
||||||
++ on-peek on-peek:def
|
++ on-peek
|
||||||
|
|= =path
|
||||||
|
^- (unit (unit cage))
|
||||||
|
=/ =(pole knot) path
|
||||||
|
?+ pole [~ ~]
|
||||||
|
::
|
||||||
|
[%x %note uid=@t ~]
|
||||||
|
=/ =uid (slav %ux uid.pole)
|
||||||
|
=/ note=notification (~(got by notifications) uid)
|
||||||
|
``hark-note+!>(note)
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-agent
|
++ on-agent
|
||||||
|= [=wire =sign:agent:gall]
|
|= [=wire =sign:agent:gall]
|
||||||
@ -212,22 +237,20 @@
|
|||||||
::
|
::
|
||||||
:: subscription from client to their own hark-store
|
:: subscription from client to their own hark-store
|
||||||
::
|
::
|
||||||
[%hark ~]
|
[%hark @ ~]
|
||||||
?+ -.sign (on-agent:def wire sign)
|
?+ -.sign (on-agent:def wire sign)
|
||||||
%fact
|
%fact
|
||||||
:_ this
|
|
||||||
?. ?=(%hark-update p.cage.sign)
|
?. ?=(%hark-update p.cage.sign)
|
||||||
~
|
`this
|
||||||
=+ !<(hark-update=update:hark-store q.cage.sign)
|
=+ !<(hark-update=update:hark-store q.cage.sign)
|
||||||
?~ not=(filter-notifications:do hark-update) ~
|
=^ upds notifications
|
||||||
:: only send the last one, since hark accumulates notifcations
|
(filter-notifications:do hark-update)
|
||||||
=/ =update [%notification u.not]
|
:_ this
|
||||||
=/ card=(unit card) ~ ::(fact-all:io %notify-update !>(update))
|
(murn upds |=(=update (fact-all:io %notify-update !>(update))))
|
||||||
(drop card)
|
|
||||||
::
|
::
|
||||||
%kick
|
%kick
|
||||||
:_ this
|
:_ this
|
||||||
[%pass /hark %agent [our.bowl %hark-store] %watch /updates]~
|
[%pass wire %agent [our.bowl %hark-store] %watch t.wire]~
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
:: subscription from provider to client
|
:: subscription from provider to client
|
||||||
@ -240,13 +263,10 @@
|
|||||||
?> ?=(%notify-update p.cage.sign)
|
?> ?=(%notify-update p.cage.sign)
|
||||||
=+ !<(=update q.cage.sign)
|
=+ !<(=update q.cage.sign)
|
||||||
:_ this
|
:_ this
|
||||||
?- -.update
|
=/ entry=(unit provider-entry) (~(get by provider-state) service)
|
||||||
%notification
|
?~ entry
|
||||||
=/ entry=(unit provider-entry) (~(get by provider-state) service)
|
~
|
||||||
?~ entry
|
[(send-notification:do u.entry who update)]~
|
||||||
~
|
|
||||||
[(send-notification:do u.entry who notification.update)]~
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
%kick
|
%kick
|
||||||
:_ this
|
:_ this
|
||||||
@ -289,11 +309,20 @@
|
|||||||
=. clients.u.entry (~(put by clients.u.entry) who `sid)
|
=. clients.u.entry (~(put by clients.u.entry) who `sid)
|
||||||
this(provider-state (~(put by provider-state) service u.entry))
|
this(provider-state (~(put by provider-state) service u.entry))
|
||||||
::
|
::
|
||||||
[%remove-binding *]
|
[%remove-binding *] `this
|
||||||
`this
|
|
||||||
::
|
::
|
||||||
[%send-notification *]
|
[%send-notification *]
|
||||||
`this
|
?> ?=(%iris -.sign-arvo)
|
||||||
|
?> ?=(%http-response +<.sign-arvo)
|
||||||
|
=* res client-response.sign-arvo
|
||||||
|
?> ?=(%finished -.res)
|
||||||
|
%. `this
|
||||||
|
=* status status-code.response-header.res
|
||||||
|
?: =(200 status) same
|
||||||
|
%+ slog
|
||||||
|
leaf/"Error sending notfication, status: {(scow %ud status)}"
|
||||||
|
?~ full-file.res ~
|
||||||
|
~[leaf/(trip `@t`q.data.u.full-file.res)]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ on-fail on-fail:def
|
++ on-fail on-fail:def
|
||||||
@ -302,27 +331,69 @@
|
|||||||
+* gra ~(. graphlib bowl)
|
+* gra ~(. graphlib bowl)
|
||||||
::
|
::
|
||||||
++ filter-notifications
|
++ filter-notifications
|
||||||
|= =update:hark-store
|
|= upd=update:hark-store
|
||||||
^- (unit notification)
|
^- (quip update _notifications)
|
||||||
?. ?=(%add-note -.update) ~
|
?+ -.upd `notifications
|
||||||
=* place place.bin.update
|
::
|
||||||
?. ?=(%landscape desk.place) ~
|
%more
|
||||||
?. ?=([%graph *] path.place) ~
|
=| upds=(list update)
|
||||||
=/ link=path link.body.update
|
|-
|
||||||
?. ?=([@ @ @ *] link) ~
|
?~ more.upd [upds notifications]
|
||||||
?~ ship=(slaw %p i.t.link) ~
|
=^ us notifications
|
||||||
=* name i.t.t.link
|
(filter-notifications i.more.upd)
|
||||||
=/ =resource:resource [u.ship name]
|
$(upds (welp upds us), more.upd t.more.upd)
|
||||||
=/ =index:graph-store
|
::
|
||||||
(turn t.t.t.link (curr rash dim:ag))
|
%read-count
|
||||||
`[resource index]
|
=/ uids ~(tap in (uids-for-place place.upd))
|
||||||
|
=| upds=(list update)
|
||||||
|
|-
|
||||||
|
?~ uids
|
||||||
|
[upds notifications]
|
||||||
|
%_ $
|
||||||
|
notifications (~(del by notifications) i.uids)
|
||||||
|
upds :_(upds [i.uids %dismiss])
|
||||||
|
uids t.uids
|
||||||
|
==
|
||||||
|
::
|
||||||
|
%add-note
|
||||||
|
=/ note=notification +.upd
|
||||||
|
?. (should-notify note) `notifications
|
||||||
|
=/ =uid (shas %notify-uid eny.bowl)
|
||||||
|
:_ (~(put by notifications) uid note)
|
||||||
|
[uid %notify]~
|
||||||
|
==
|
||||||
|
::
|
||||||
|
++ should-notify
|
||||||
|
|= note=notification
|
||||||
|
^- ?
|
||||||
|
?. ?=([%graph @ @ *] path.place.bin.note)
|
||||||
|
|
|
||||||
|
=/ s=(unit ship) (slaw %p i.t.path.place.bin.note)
|
||||||
|
?~ s |
|
||||||
|
=/ =resource:resource
|
||||||
|
[u.s i.t.t.path.place.bin.note]
|
||||||
|
?& ?=(%landscape desk.place.bin.note)
|
||||||
|
?| ?=([%graph-validator-dm *] link.body.note)
|
||||||
|
?& (group-is-hidden resource)
|
||||||
|
?=([%graph-validator-chat *] link.body.note)
|
||||||
|
==
|
||||||
|
== ==
|
||||||
|
::
|
||||||
|
++ uids-for-place
|
||||||
|
|= =place:hark
|
||||||
|
%- ~(gas in *(set uid))
|
||||||
|
%+ murn ~(tap by notifications)
|
||||||
|
|= [=uid =notification]
|
||||||
|
^- (unit ^uid)
|
||||||
|
?. =(place.bin.notification place) ~
|
||||||
|
`uid
|
||||||
::
|
::
|
||||||
++ group-is-hidden
|
++ group-is-hidden
|
||||||
|= =resource:resource
|
|= =resource:resource
|
||||||
^- (unit ?)
|
^- ?
|
||||||
=/ grp=(unit group:group) (~(scry-group group bowl) resource)
|
=/ grp=(unit group:group) (~(scry-group group bowl) resource)
|
||||||
?~ grp ~
|
?~ grp |
|
||||||
`hidden.u.grp
|
hidden.u.grp
|
||||||
::
|
::
|
||||||
++ is-whitelisted
|
++ is-whitelisted
|
||||||
|= [who=@p entry=provider-entry]
|
|= [who=@p entry=provider-entry]
|
||||||
@ -357,6 +428,12 @@
|
|||||||
++ post-form
|
++ post-form
|
||||||
|= [=wire url=@t auth=@t params=(list [@t @t])]
|
|= [=wire url=@t auth=@t params=(list [@t @t])]
|
||||||
^- card
|
^- card
|
||||||
|
=/ esc=$-(@t @t)
|
||||||
|
|=(t=@t (crip (en-urlt:html (trip t))))
|
||||||
|
=. params
|
||||||
|
%+ turn params
|
||||||
|
|= [p=@t q=@t]
|
||||||
|
[(esc p) (esc q)]
|
||||||
=/ data
|
=/ data
|
||||||
%+ roll
|
%+ roll
|
||||||
%+ sort params
|
%+ sort params
|
||||||
@ -385,16 +462,12 @@
|
|||||||
(rap 3 out '&' p '=' q ~)
|
(rap 3 out '&' p '=' q ~)
|
||||||
::
|
::
|
||||||
++ send-notification
|
++ send-notification
|
||||||
|= [entry=provider-entry who=@p =notification]
|
|= [entry=provider-entry who=@p =update]
|
||||||
^- card
|
^- card
|
||||||
=/ params=(list [@t @t])
|
=/ params=(list [@t @t])
|
||||||
:~ identity+(rsh [3 1] (scot %p who))
|
:~ identity+(rsh [3 1] (scot %p who))
|
||||||
ship+(rsh [3 1] (scot %p entity.resource.notification))
|
action+`@t`action.update
|
||||||
graph+name.resource.notification
|
uid+(scot %ux uid.update)
|
||||||
:- %node
|
|
||||||
%+ roll index.notification
|
|
||||||
|= [in=@ out=@t]
|
|
||||||
(rap 3 out '/' (scot %ud in) ~)
|
|
||||||
==
|
==
|
||||||
%: post-form
|
%: post-form
|
||||||
/send-notification/(scot %uv (sham eny.bowl))
|
/send-notification/(scot %uv (sham eny.bowl))
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
:~ title+'Groups'
|
:~ title+'Groups'
|
||||||
info+'A suite of applications to communicate on Urbit'
|
info+'A suite of applications to communicate on Urbit'
|
||||||
color+0xee.5432
|
color+0xee.5432
|
||||||
glob-http+['https://bootstrap.urbit.org/glob-0v2.fn2uu.2iu5q.ddmsu.01br7.8uaft.glob' 0v2.fn2uu.2iu5q.ddmsu.01br7.8uaft]
|
glob-http+['https://bootstrap.urbit.org/glob-0v3.m2nd4.9tg9d.vs9ls.9rj6u.7lqhg.glob' 0v3.m2nd4.9tg9d.vs9ls.9rj6u.7lqhg]
|
||||||
|
|
||||||
base+'landscape'
|
base+'landscape'
|
||||||
version+[1 0 2]
|
version+[1 0 4]
|
||||||
website+'https://tlon.io'
|
website+'https://tlon.io'
|
||||||
license+'MIT'
|
license+'MIT'
|
||||||
==
|
==
|
||||||
|
@ -10,5 +10,5 @@
|
|||||||
=/ who (scot %p ship)
|
=/ who (scot %p ship)
|
||||||
::
|
::
|
||||||
.^ update:graph-store
|
.^ update:graph-store
|
||||||
/gx/[our]/graph-store/[wen]/archive/[who]/[graph]/graph-update
|
/gx/[our]/graph-store/[wen]/archive/[who]/[graph]/graph-update-3
|
||||||
==
|
==
|
||||||
|
@ -13,9 +13,10 @@
|
|||||||
:~ create+create
|
:~ create+create
|
||||||
remove+remove
|
remove+remove
|
||||||
join+join
|
join+join
|
||||||
|
abort+dejs-path:resource
|
||||||
leave+leave
|
leave+leave
|
||||||
invite+invite
|
invite+invite
|
||||||
hide+dejs-path:resource
|
done+dejs-path:resource
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ create
|
++ create
|
||||||
@ -34,6 +35,9 @@
|
|||||||
%- ot
|
%- ot
|
||||||
:~ resource+dejs:resource
|
:~ resource+dejs:resource
|
||||||
ship+(su ;~(pfix sig fed:ag))
|
ship+(su ;~(pfix sig fed:ag))
|
||||||
|
app+(su (perk %groups %graph ~))
|
||||||
|
'shareContact'^bo
|
||||||
|
autojoin+bo
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ invite
|
++ invite
|
||||||
@ -74,10 +78,13 @@
|
|||||||
++ request
|
++ request
|
||||||
|= req=^request
|
|= req=^request
|
||||||
%- pairs
|
%- pairs
|
||||||
:~ hidden+b+hidden.req
|
:~ started+(time started.req)
|
||||||
started+(time started.req)
|
|
||||||
ship+(ship ship.req)
|
ship+(ship ship.req)
|
||||||
progress+s+progress.req
|
progress+s+progress.req
|
||||||
|
'shareContact'^b+share-co.req
|
||||||
|
autojoin+b+autojoin.req
|
||||||
|
app+s+`@t`app.req
|
||||||
|
invite+a+(turn ~(tap in invite.req) (cork (cury scot %ux) (lead %s)))
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
++ initial
|
++ initial
|
||||||
|
1
pkg/landscape/lib/hark-store.hoon
Symbolic link
1
pkg/landscape/lib/hark-store.hoon
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../garden-dev/lib/hark-store.hoon
|
@ -13,7 +13,8 @@
|
|||||||
=/ members
|
=/ members
|
||||||
~(wyt in (members:grp rid))
|
~(wyt in (members:grp rid))
|
||||||
=/ =metadatum:store
|
=/ =metadatum:store
|
||||||
(need (peek-metadatum %groups rid))
|
?^ met=(peek-metadatum %groups rid) u.met
|
||||||
|
(need (peek-metadatum %graph rid))
|
||||||
[rid channels members channel-count metadatum]
|
[rid channels members channel-count metadatum]
|
||||||
::
|
::
|
||||||
++ channels
|
++ channels
|
||||||
|
@ -319,12 +319,18 @@
|
|||||||
|= =path
|
|= =path
|
||||||
^- [(list card:agent:gall) agent:gall]
|
^- [(list card:agent:gall) agent:gall]
|
||||||
?> (team:title our.bowl src.bowl)
|
?> (team:title our.bowl src.bowl)
|
||||||
?. ?=([%tracking ~] path)
|
?+ path
|
||||||
|
:: forward by default
|
||||||
=^ cards pull-hook
|
=^ cards pull-hook
|
||||||
(on-watch:og path)
|
(on-watch:og path)
|
||||||
[cards this]
|
[cards this]
|
||||||
:_ this
|
::
|
||||||
~[give-update]
|
[%nack ~] `this
|
||||||
|
::
|
||||||
|
[%tracking ~]
|
||||||
|
:_ this
|
||||||
|
~[give-update]
|
||||||
|
==
|
||||||
::
|
::
|
||||||
++ on-agent
|
++ on-agent
|
||||||
|= [=wire =sign:agent:gall]
|
|= [=wire =sign:agent:gall]
|
||||||
@ -455,7 +461,8 @@
|
|||||||
|= tan=(unit tang)
|
|= tan=(unit tang)
|
||||||
?~ tan tr-core
|
?~ tan tr-core
|
||||||
?. versioned
|
?. versioned
|
||||||
(tr-ap-og:tr-cleanup |.((on-pull-nack:og rid u.tan)))
|
%- tr-ap-og:tr-cleanup:tr-give-nack
|
||||||
|
|.((on-pull-nack:og rid u.tan))
|
||||||
%- (slog leaf+"versioned nack for {<rid>} in {<dap.bowl>}" u.tan)
|
%- (slog leaf+"versioned nack for {<rid>} in {<dap.bowl>}" u.tan)
|
||||||
=/ pax
|
=/ pax
|
||||||
(kick-mule:virt rid |.((on-pull-kick:og rid)))
|
(kick-mule:virt rid |.((on-pull-kick:og rid)))
|
||||||
@ -569,6 +576,9 @@
|
|||||||
:: +| %subscription: subscription cards
|
:: +| %subscription: subscription cards
|
||||||
::
|
::
|
||||||
::
|
::
|
||||||
|
++ tr-give-nack
|
||||||
|
(tr-emit (fact:io resource+!>(rid) /nack ~))
|
||||||
|
::
|
||||||
++ tr-ver-wire
|
++ tr-ver-wire
|
||||||
(make-wire /version)
|
(make-wire /version)
|
||||||
::
|
::
|
||||||
|
20
pkg/landscape/mar/hark-note.hoon
Normal file
20
pkg/landscape/mar/hark-note.hoon
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/+ *hark-store
|
||||||
|
|%
|
||||||
|
+$ note [=bin =body]
|
||||||
|
--
|
||||||
|
|_ not=note
|
||||||
|
++ grad %noun
|
||||||
|
++ grow
|
||||||
|
|%
|
||||||
|
++ noun not
|
||||||
|
++ json
|
||||||
|
%- pairs:enjs:format
|
||||||
|
:~ bin+(bin:enjs bin.not)
|
||||||
|
body+(body:enjs body.not)
|
||||||
|
==
|
||||||
|
--
|
||||||
|
++ grab
|
||||||
|
|%
|
||||||
|
++ noun note
|
||||||
|
--
|
||||||
|
--
|
@ -1,12 +1,27 @@
|
|||||||
/- *resource, *group
|
/- *resource, *group
|
||||||
^?
|
^?
|
||||||
|%
|
|%
|
||||||
|
+$ app ?(%graph %groups)
|
||||||
|
+$ uid @uvH
|
||||||
|
::
|
||||||
|
:: $request: State of a join request
|
||||||
|
::
|
||||||
|
:: .started: Time request first sent
|
||||||
|
:: .ship: Host of group
|
||||||
|
:: .progress: Progress of request
|
||||||
|
:: .share-co: Automatically share contact?
|
||||||
|
:: .autojoin: Automatically join graphs
|
||||||
|
:: .app: Whether we're joining a group or a graph
|
||||||
|
:: .invite: Associated invites
|
||||||
::
|
::
|
||||||
+$ request
|
+$ request
|
||||||
$: hidden=?
|
$: started=time
|
||||||
started=time
|
|
||||||
=ship
|
=ship
|
||||||
=progress
|
=progress
|
||||||
|
=app
|
||||||
|
share-co=?
|
||||||
|
autojoin=?
|
||||||
|
invite=(set uid)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
+$ action
|
+$ action
|
||||||
@ -14,20 +29,38 @@
|
|||||||
[%create name=term =policy title=@t description=@t]
|
[%create name=term =policy title=@t description=@t]
|
||||||
[%remove =resource]
|
[%remove =resource]
|
||||||
:: client side
|
:: client side
|
||||||
[%join =resource =ship]
|
$: %join
|
||||||
|
=resource
|
||||||
|
=ship
|
||||||
|
=app
|
||||||
|
share-contact=?
|
||||||
|
autojoin=?
|
||||||
|
==
|
||||||
|
[%abort =resource]
|
||||||
[%leave =resource]
|
[%leave =resource]
|
||||||
::
|
::
|
||||||
[%invite =resource ships=(set ship) description=@t]
|
[%invite =resource ships=(set ship) description=@t]
|
||||||
:: pending ops
|
:: pending ops
|
||||||
[%hide =resource]
|
[%done =resource]
|
||||||
==
|
==
|
||||||
|
:: $progress: state of a join request
|
||||||
::
|
::
|
||||||
|
:: %start: Waiting on add poke to succeed
|
||||||
|
:: %added: Waiting on groups
|
||||||
|
:: %metadata: Waiting on metadata
|
||||||
|
:: final: Join request succeeded/errors
|
||||||
+$ progress
|
+$ progress
|
||||||
?(%start %added final)
|
?(%start %added %metadata final)
|
||||||
|
::
|
||||||
|
:: $final: resolution of a join request
|
||||||
|
::
|
||||||
|
:: %no-perms: Failed, did not have permissions
|
||||||
|
:: %abort: Join request manually aborted
|
||||||
|
:: %strange: Failed unexpectedly
|
||||||
|
:: %done: Succeeded
|
||||||
::
|
::
|
||||||
+$ final
|
+$ final
|
||||||
?(%no-perms %strange %done)
|
?(%no-perms %abort %strange %done)
|
||||||
::
|
::
|
||||||
+$ update
|
+$ update
|
||||||
$% [%initial initial=(map resource request)]
|
$% [%initial initial=(map resource request)]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/- resource, graph-store
|
/- hark=hark-store, resource, graph-store
|
||||||
|%
|
|%
|
||||||
+$ provider-action
|
+$ provider-action
|
||||||
$% [%add service=term notify=@t binding=@t auth-token=@t =whitelist]
|
$% [%add service=term notify=@t binding=@t auth-token=@t =whitelist]
|
||||||
@ -12,10 +12,10 @@
|
|||||||
[%remove-provider who=@p service=term]
|
[%remove-provider who=@p service=term]
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
+$ uid @uvH
|
||||||
|
::
|
||||||
+$ notification
|
+$ notification
|
||||||
$: =resource:resource
|
[=bin:hark =body:hark]
|
||||||
=index:graph-store
|
|
||||||
==
|
|
||||||
::
|
::
|
||||||
+$ whitelist
|
+$ whitelist
|
||||||
$: public=?
|
$: public=?
|
||||||
@ -24,7 +24,8 @@
|
|||||||
groups=(set resource:resource)
|
groups=(set resource:resource)
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
|
+$ action ?(%notify %dismiss)
|
||||||
|
::
|
||||||
+$ update
|
+$ update
|
||||||
$% [%notification =notification]
|
[=uid =action]
|
||||||
==
|
|
||||||
--
|
--
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user