Merge branch 'release/next-js' into lf/infinite-link-scroll

This commit is contained in:
Matilde Park 2021-01-20 18:05:03 -05:00
commit 3418b0f6e6
72 changed files with 684 additions and 1001 deletions

View File

@ -1,4 +1,4 @@
FROM jaredtobin/janeway:v0.13.1
FROM jaredtobin/janeway:v0.13.3
COPY entrypoint.sh /entrypoint.sh
EXPOSE 22/tcp
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -10,13 +10,7 @@ chmod 600 service-account
chmod 600 id_ssh
chmod 600 id_ssh.pub
LANDSCAPE_STREAM="development"
export LANDSCAPE_STREAM
LANDSCAPE_SHORTHASH="${GITHUB_SHA:0:7}"
export LANDSCAPE_SHORTHASH
janeway release glob --no-pill \
janeway release glob --dev --no-pill \
--credentials service-account \
--ssh-key id_ssh \
--do-it-live \

View File

@ -47,10 +47,22 @@ jobs:
steps:
- uses: actions/checkout@v2
# We only want the extra nix config on linux, where it is necessary
# 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
# exclusive
- uses: cachix/install-nix-action@v12
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
if: ${{ matrix.os == 'ubuntu-latest' }}
- uses: cachix/install-nix-action@v12
if: ${{ matrix.os != 'ubuntu-latest' }}
- uses: cachix/cachix-action@v8
with:
name: ares
name: ${{ secrets.CACHIX_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix-build -A urbit --arg enableStatic true
@ -58,6 +70,9 @@ jobs:
- if: ${{ matrix.os == 'ubuntu-latest' }}
run: nix-build -A urbit-tests
- if: ${{ matrix.os == 'ubuntu-latest' }}
run: nix-build -A docker-image
haskell:
strategy:
fail-fast: false
@ -73,7 +88,7 @@ jobs:
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: ares
name: ${{ secrets.CACHIX_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix-build -A hs.urbit-king.components.exes.urbit-king --arg enableStatic true

View File

@ -13,5 +13,5 @@ jobs:
with:
type: now
target_branch: release/next-js
github_token: ${{ github.token }}
github_token: ${{ secrets.JANEWAY_BOT_TOKEN }}

51
.github/workflows/release-docker.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: release-docker
on:
release: null
push:
tags: ['urbit-v*']
jobs:
upload:
strategy:
matrix:
include:
- { os: ubuntu-latest, system: x86_64-linux }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v12
with:
extra_nix_config: |
system-features = nixos-test benchmark big-parallel kvm
- uses: cachix/cachix-action@v8
with:
name: ${{ secrets.CACHIX_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- uses: docker/docker-login-action@v1.8.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: christian-korneck/update-container-description-action@v1
env:
DOCKER_USER: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_PASS: ${{ secrets.DOCKERHUB_TOKEN }}
with:
destination_container_repo: ${{ secrets.DOCKERHUB_USERNAME }}/urbit
provider: dockerhub
short_description: 'Urbit: a clean-slate OS and network for the 21st century'
readme_file: 'pkg/docker-image/README.md'
- run: |
version="$(cat ./pkg/urbit/version)"
image="$(nix-build -A docker-image)"
imageName="$(nix-instantiate --eval -A docker-image.imageName | cut -d'"' -f2)"
imageTag="$(nix-instantiate --eval -A docker-image.imageTag | cut -d'"' -f2)"
# Load the image from the nix-built tarball
docker load -i $image
docker tag "$imageName:$imageTag" ${{secrets.DOCKERHUB_USERNAME }}/urbit:v$version
docker tag "$imageName:$imageTag" ${{secrets.DOCKERHUB_USERNAME }}/urbit:latest
docker push ${{secrets.DOCKERHUB_USERNAME }}/urbit:v$version
docker push ${{secrets.DOCKERHUB_USERNAME }}/urbit:latest

View File

@ -20,7 +20,7 @@ jobs:
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: ares
name: ${{ secrets.CACHIX_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- uses: google-github-actions/setup-gcloud@v0.2.0

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c512d0c3da3ce7f0ac25babcbbceba04a9e62975e9d92aec56ab00e1c2fc6224
size 8618963
oid sha256:288b3ab68e2e3946dbf0e0de05c2907c351ec97fcc08134e558569eab4121c94
size 8809816

View File

@ -115,6 +115,8 @@ let
urbit = callPackage ./nix/pkgs/urbit { inherit enableStatic; };
docker-image = callPackage ./nix/pkgs/docker-image { };
hs = callPackage ./nix/pkgs/hs {
inherit enableStatic;
inherit (pkgsCross) haskell-nix;
@ -158,6 +160,8 @@ let
};
};
inherit (pkgsNative) skopeo;
# A convenience function for constructing a shell.nix for any of the
# pkgsLocal derivations by automatically propagating any dependencies
# to the nix-shell.

View File

@ -0,0 +1,68 @@
{ urbit, libcap, coreutils, bashInteractive, dockerTools, writeScriptBin, amesPort ? 34343 }:
let
startUrbit = writeScriptBin "start-urbit" ''
#!${bashInteractive}/bin/bash
set -eu
# If the container is not started with the `-i` flag
# then STDIN will be closed and we need to start
# Urbit/vere with the `-t` flag.
ttyflag=""
if [ ! -t 0 ]; then
echo "Running with no STDIN"
ttyflag="-t"
fi
# Check if there is a keyfile, if so boot a ship with its name, and then remove the key
if [ -e *.key ]; then
# Get the name of the key
keynames="*.key"
keys=( $keynames )
keyname=''${keys[0]}
mv $keyname /tmp
# Boot urbit with the key, exit when done booting
urbit $ttyflag -w $(basename $keyname .key) -k /tmp/$keyname -c $(basename $keyname .key) -p ${toString amesPort} -x
# Remove the keyfile for security
rm /tmp/$keyname
rm *.key || true
elif [ -e *.comet ]; then
cometnames="*.comet"
comets=( $cometnames )
cometname=''${comets[0]}
rm *.comet
urbit $ttyflag -c $(basename $cometname .comet) -p ${toString amesPort} -x
fi
# Find the first directory and start urbit with the ship therein
dirnames="*/"
dirs=( $dirnames )
dirname=''${dirnames[0]}
urbit $ttyflag -p ${toString amesPort} $dirname
'';
in dockerTools.buildImage {
name = "urbit";
tag = "v${urbit.version}";
contents = [ bashInteractive urbit startUrbit coreutils ];
runAsRoot = ''
#!${bashInteractive}
mkdir -p /urbit
mkdir -p /tmp
${libcap}/bin/setcap 'cap_net_bind_service=+ep' /bin/urbit
'';
config = {
Cmd = [ "/bin/start-urbit" ];
Env = [ "PATH=/bin" ];
WorkingDir = "/urbit";
Volumes = {
"/urbit" = {};
};
Expose = [ "80/tcp" "${toString amesPort}/udp" ];
};
}

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v5.sekq0.8skge.ekt62.i73ig.g5sep
++ hash 0v1.4u9gp.rs1fi.ki7ok.ib4cp.mgdvs
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -92,27 +92,6 @@
%run-updates (is-allowed resource.q.update bowl %.y)
==
::
++ resource-for-update
|= =vase
^- (unit resource:res)
=/ =update:store !<(update:store vase)
?- -.q.update
%add-graph `resource.q.update
%remove-graph `resource.q.update
%add-nodes `resource.q.update
%remove-nodes `resource.q.update
%add-signatures `resource.uid.q.update
%remove-signatures `resource.uid.q.update
%archive-graph `resource.q.update
%unarchive-graph ~
%add-tag ~
%remove-tag ~
%keys ~
%tags ~
%tag-queries ~
%run-updates `resource.q.update
==
::
++ initial-watch
|= [=path =resource:res]
^- vase

View File

@ -230,7 +230,7 @@
?> ?=(%0 -.update)
=? p.update =(p.update *time) now.bowl
?- -.q.update
%add-graph (add-graph +.q.update)
%add-graph (add-graph p.update +.q.update)
%remove-graph (remove-graph +.q.update)
%add-nodes (add-nodes p.update +.q.update)
%remove-nodes (remove-nodes p.update +.q.update)
@ -247,7 +247,8 @@
==
::
++ add-graph
|= $: =resource:store
|= $: =time
=resource:store
=graph:store
mark=(unit mark:store)
overwrite=?
@ -258,9 +259,13 @@
!(~(has by graphs) resource)
== ==
?> (validate-graph graph mark)
=/ =logged-update:store
[%0 time %add-graph resource graph mark overwrite]
=/ =update-log:store
(gas:orm-log ~ [time logged-update] ~)
:_ %_ state
graphs (~(put by graphs) resource [graph mark])
update-logs (~(put by update-logs) resource (gas:orm-log ~ ~))
update-logs (~(put by update-logs) resource update-log)
archive (~(del by archive) resource)
::
validators
@ -643,6 +648,7 @@
%- graph-update
^- update:store
?- -.q.update
%add-graph update(resource.q resource)
%add-nodes update(resource.q resource)
%remove-nodes update(resource.q resource)
%add-signatures update(resource.uid.q resource)

View File

@ -81,15 +81,6 @@
==
--
::
++ resource-for-update
|= =vase
^- (unit resource)
=/ =update:store
!<(update:store vase)
?: ?=(%initial -.update)
~
`resource.update
::
++ take-update
|= =vase
^- [(list card) agent]

View File

@ -9,11 +9,17 @@
+$ card card:agent:gall
+$ versioned-state
$% state-0
state-1
==
::
+$ state-0
$: %0
watching=(set [resource index:post])
[%0 base-state-0]
::
+$ state-1
[%1 base-state-0]
::
+$ base-state-0
$: watching=(set [resource index:post])
mentions=_&
watch-on-self=_&
==
@ -36,7 +42,7 @@
::
--
::
=| state-0
=| state-1
=* state -
::
=<
@ -57,13 +63,25 @@
::
++ on-save !>(state)
++ on-load
|= old=vase
|= =vase
^- (quip card _this)
:_ this(state !<(state-0 old))
=+ !<(old=versioned-state vase)
=| cards=(list card)
|-
?: ?=(%0 -.old)
%_ $
-.old %1
::
cards
:_ cards
[%pass / %agent [our dap]:bowl %poke noun+!>(%rewatch-dms)]
==
:_ this(state old)
=. cards (flop cards)
%+ welp
?: (~(has by wex.bowl) [/graph our.bowl %graph-store])
~
~[watch-graph:ha]
cards
[watch-graph:ha cards]
%+ turn
^- (list mark)
:~ %graph-validator-chat
@ -103,9 +121,23 @@
?+ mark (on-poke:def mark vase)
%hark-graph-hook-action
(hark-graph-hook-action !<(action:hook vase))
%noun
(poke-noun !<(* vase))
==
[cards this]
::
++ poke-noun
|= non=*
?> ?=(%rewatch-dms non)
=/ graphs=(list resource)
~(tap in get-keys:gra)
:- ~
%_ state
watching
%- ~(gas in watching)
(murn graphs |=(rid=resource ?:((should-watch:ha rid) `[rid ~] ~)))
==
::
++ hark-graph-hook-action
|= =action:hook
^- (quip card _state)
@ -223,23 +255,14 @@
++ add-graph
|= rid=resource
^- (quip card _state)
=/ group-rid=(unit resource)
(group-from-app-resource:met %graph rid)
?~ group-rid
~& no-group+rid
`state
=/ is-hidden=?
!(is-managed:grp u.group-rid)
=/ should-watch
|(is-hidden &(watch-on-self =(our.bowl entity.rid)))
?. should-watch
`state
=/ graph=graph:graph-store :: graph in subscription is bunted
(get-graph-mop:gra rid)
=/ node=(unit node:graph-store)
(bind (peek:orm:graph-store graph) |=([@ =node:graph-store] node))
=^ cards state
(check-nodes (drop node) rid)
?. (should-watch:ha rid)
[cards state]
:_ state(watching (~(put in watching) [rid ~]))
(weld cards (give:ha ~[/updates] %listen [rid ~]))
::
@ -277,6 +300,9 @@
--
::
|_ =bowl:gall
+* met ~(. metadata bowl)
grp ~(. grouplib bowl)
gra ~(. graph bowl)
::
++ get-conversion
|= rid=resource
@ -314,6 +340,16 @@
%.y
$(contents t.contents)
::
++ should-watch
|= rid=resource
^- ?
=/ group-rid=(unit resource)
(group-from-app-resource:met %graph rid)
?~ group-rid %.n
?| !(is-managed:grp u.group-rid)
&(watch-on-self =(our.bowl entity.rid))
==
::
++ handle-update
|_ $: rid=resource :: input
updates=(list node:graph-store)

View File

@ -49,7 +49,7 @@
:: $cache: useful to have precalculated, but can be derived from state
:: albeit expensively
+$ cache
$: by-index=(jug stats-index:store @da)
$: by-index=(jug stats-index:store [time=@da =index:store])
~
==
::
@ -228,6 +228,7 @@
[%count count]
(~(gut by last-seen) stats-index *time)
==
::
++ give-each-unreads
^- (list [stats-index:store stats:store])
%+ turn
@ -477,19 +478,16 @@
::
++ read-index-each
|= [=stats-index:store ref=index:graph-store]
%+ read-index stats-index
%- read-indices
%+ skim
~(tap ^in (~(get ju by-index) stats-index))
|= time=@da
|= [time=@da =index:store]
=/ =timebox:store
(gut-orm notifications time)
%+ roll
~(tap ^in timebox)
|= [[=index:store not=notification:store] out=?]
?: out out
?. (stats-index-is-index:store stats-index index) out
?. ?=(%graph -.index) out
?. ?=(%graph -.contents.not) out
=/ not=notification:store
(~(got by timebox) index)
?. ?=(%graph -.index) %.n
?. ?=(%graph -.contents.not) %.n
(lien list.contents.not |=(p=post:post =(index.p ref)))
::
++ read-each
@ -517,35 +515,18 @@
++ read-count
|= =stats-index:store
=. unreads-count (~(put by unreads-count) stats-index 0)
=/ times=(list @da)
=/ times=(list [@da index:store])
~(tap ^in (~(get ju by-index) stats-index))
(give:(read-index stats-index times) %read-count stats-index)
(give:(read-indices times) %read-count stats-index)
::
++ read-index
|= [=stats-index:store times=(list @da)]
++ read-indices
|= times=(list [time=@da =index:store])
|-
?~ times poke-core
=/ core
(read-stats-index i.times stats-index)
(read-note i.times)
$(poke-core core, times t.times)
::
++ read-stats-index
|= [time=@da =stats-index:store]
=/ keys
~(tap ^in ~(key by (gut-orm notifications time)))
|- ^+ poke-core
?~ keys
poke-core
?. (stats-index-is-index:store stats-index i.keys)
$(keys t.keys)
=/ =notification:store
(~(got by (gut-orm notifications time)) i.keys)
?: read.notification
$(keys t.keys)
=/ core
(read-note time i.keys)
$(poke-core core, keys t.keys)
::
++ seen-index
|= [time=@da =stats-index:store]
=/ new-time=@da
@ -570,7 +551,7 @@
=. last-seen
((dif-map-by-key ,@da) last-seen indices)
=. by-index
((dif-map-by-key ,(set @da)) by-index indices)
((dif-map-by-key ,(set [@da =index:store])) by-index indices)
poke-core
::
++ get-stats-indices
@ -603,10 +584,10 @@
~(tap ^in set)
|-
?~ indices poke-core
=/ times=(list @da)
=/ times=(list [time=@da =index:store])
~(tap ^in (~(get ju by-index) i.indices))
=. poke-core
(read-index i.indices times)
(read-indices times)
$(indices t.indices)
--
::
@ -694,7 +675,7 @@
%_ +.state
::
by-index
%. [(to-stats-index:store index) time]
%. [(to-stats-index:store index) time index]
?: read
~(del ju by-index)
~(put ju by-index)

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.6056641fbc32a19ee0fd.js"></script>
<script src="/~landscape/js/bundle/index.86c6e416c338a305e1e9.js"></script>
</body>
</html>

View File

@ -63,7 +63,7 @@ class Channel {
}
resetDebounceTimer() {
if(this.debounceTimer) {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
this.debounceTimer = null;
}
@ -182,19 +182,19 @@ class Channel {
// sends a JSON command command to the server.
//
sendJSONToChannel(j) {
if(!j && this.outstandingJSON.length === 0) {
return;
}
let req = new XMLHttpRequest();
req.open("PUT", this.channelURL());
req.setRequestHeader("Content-Type", "application/json");
if (this.lastEventId == this.lastAcknowledgedEventId) {
if(j) {
if (j) {
this.outstandingJSON.push(j);
}
let x = JSON.stringify(this.outstandingJSON);
req.send(x);
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
//
@ -203,15 +203,15 @@ class Channel {
//
let payload = [
...this.outstandingJSON,
{action: "ack", "event-id": parseInt(this.lastEventId)}
{action: "ack", "event-id": this.lastEventId}
];
if(j) {
if (j) {
payload.push(j)
}
let x = JSON.stringify(payload);
req.send(x);
this.lastEventId = this.lastAcknowledgedEventId;
this.lastAcknowledgedEventId = this.lastEventId;
}
this.outstandingJSON = [];
@ -227,7 +227,7 @@ class Channel {
this.eventSource = new EventSource(this.channelURL(), {withCredentials:true});
this.eventSource.onmessage = e => {
this.lastEventId = e.lastEventId;
this.lastEventId = parseInt(e.lastEventId, 10);
let obj = JSON.parse(e.data);
let pokeFuncs = this.outstandingPokes.get(obj.id);

View File

@ -285,7 +285,7 @@
(on-agent:og wire sign)
[cards this]
:_ this
~[(update-store:hc q.cage.sign)]
~[(update-store:hc rid q.cage.sign)]
==
++ on-leave
|= =path
@ -424,15 +424,24 @@
/helper/pull-hook
wire
::
++ get-conversion
.^ tube:clay
%cc (scot %p our.bowl) %home (scot %da now.bowl)
/[update-mark.config]/resource
==
::
++ give-update
^- card
[%give %fact ~[/tracking] %pull-hook-update !>(tracking)]
::
++ update-store
|= =vase
|= [wire-rid=resource =vase]
^- card
=/ =wire
(make-wire /store)
=+ !<(rid=resource (get-conversion vase))
?> =(src.bowl (~(got by tracking) rid))
?> =(wire-rid rid)
[%pass wire %agent [our.bowl store-name.config] %poke update-mark.config vase]
--
--

View File

@ -67,16 +67,6 @@
|* =config
$_ ^|
|_ bowl:gall
::
:: +resource-for-update: get affected resource from an update
::
:: Given a vase of the update, the mark of which is
:: update-mark.config, produce the affected resource, if any.
::
++ resource-for-update
|~ vase
*(unit resource)
::
:: +take-update: handle update from store
::
:: Given an update from the store, do other things after proxying
@ -175,9 +165,11 @@
|^
?- -.old
%1
=. cards
:_(cards (build-mark:hc %sing))
=^ og-cards push-hook
(on-load:og inner-state.old)
[(weld cards og-cards) this(state old)]
[(weld (flop cards) og-cards) this(state old)]
::
%0
%_ $
@ -274,11 +266,18 @@
=^ cards push-hook
(on-leave:og path)
[cards this]
::
++ on-arvo
|= [=wire =sign-arvo]
=^ cards push-hook
(on-arvo:og wire sign-arvo)
[cards this]
?. ?=([%helper %push-hook @ *] wire)
=^ cards push-hook
(on-arvo:og wire sign-arvo)
[cards this]
?. ?=(%resource-conversion i.t.t.wire)
(on-arvo:def wire sign-arvo)
:_ this
~[(build-mark:hc %next)]
::
++ on-fail
|= [=term =tang]
=^ cards push-hook
@ -368,7 +367,7 @@
|= =vase
^- (list card:agent:gall)
=/ rid=(unit resource)
(resource-for-update:og vase)
(resource-for-update vase)
?~ rid ~
=/ prefix=path
resource+(en-path:resource u.rid)
@ -385,7 +384,7 @@
|= =vase
^- (list card:agent:gall)
=/ rid=(unit resource)
(resource-for-update:og vase)
(resource-for-update vase)
?~ rid ~
=/ =path
resource+(en-path:resource u.rid)
@ -394,5 +393,30 @@
=/ dap=term
?:(=(our.bowl entity.u.rid) store-name.config dap.bowl)
[%pass wire %agent [entity.u.rid dap] %poke update-mark.config vase]~
::
++ get-conversion
.^ tube:clay
%cc (scot %p our.bowl) %home (scot %da now.bowl)
/[update-mark.config]/resource
==
::
++ resource-for-update
|= update=vase
=/ =tube:clay
get-conversion
%+ bind
(mole |.((tube update)))
|=(=vase !<(resource vase))
::
++ build-mark
|= rav=?(%sing %next)
^- card
=/ =wire
(make-wire /resource-conversion)
=/ =mood:clay
[%c da+now.bowl /[update-mark.config]/resource]
=/ =rave:clay
?:(?=(%next rav) [rav mood] [rav mood])
[%pass wire %arvo %c %warp our.bowl [%home `rave]]
--
--

View File

@ -7,6 +7,13 @@
|%
++ noun upd
++ json (update:enjs upd)
++ resource
?+ -.q.upd !!
?(%run-updates %add-nodes %remove-nodes %add-graph) resource.q.upd
?(%remove-graph %archive-graph %unarchive-graph) resource.q.upd
?(%add-tag %remove-tag) resource.q.upd
?(%add-signatures %remove-signatures) resource.uid.q.upd
==
++ mime [/application/x-urb-graph-update (as-octs (jam upd))]
--
::

View File

@ -4,9 +4,13 @@
++ grow
|%
++ noun upd
++ resource
?< ?=(%initial -.upd)
resource.upd
::
++ json
%+ frond:enjs:format 'groupUpdate'
(update:enjs upd)
%+ frond:enjs:format 'groupUpdate'
(update:enjs upd)
--
++ grab
|%

View File

@ -0,0 +1,14 @@
/+ resource
|_ rid=resource
++ grad %noun
++ grow
|%
++ noun rid
++ json (enjs:resource rid)
--
++ grab
|%
++ noun resource
++ json dejs:resource
--
--

View File

@ -34,7 +34,8 @@
==
::
+$ logged-update-0
$% [%add-nodes =resource nodes=(map index node)]
$% [%add-graph =resource =graph mark=(unit mark) overwrite=?]
[%add-nodes =resource nodes=(map index node)]
[%remove-nodes =resource indices=(set index)]
[%add-signatures =uid =signatures]
[%remove-signatures =uid =signatures]
@ -42,7 +43,6 @@
::
+$ update-0
$% logged-update-0
[%add-graph =resource =graph mark=(unit mark) overwrite=?]
[%remove-graph =resource]
::
[%add-tag =term =resource]

View File

@ -0,0 +1,37 @@
# Official Urbit Docker Image
This is the official Docker image for [Urbit](https://urbit.org).
Urbit is a clean-slate OS and network for the 21st century.
## Using
To use this image, you should mount a volume with a keyfile, comet file, or existing pier at `/urbit`, and map ports
as described below.
### Volume Mount
This image expects a volume mounted at `/urbit`. This volume should initially contain one of
- A keyfile `<shipname>.key` for a galaxy, star, planet, or moon. See the setup instructions for Urbit for information on [obtaining a keyfile](https://urbit.org/using/install/).
* e.g. `sampel-palnet.key` for the planet `sampel-palnet`.
- An empty file with the extension `.comet`. This will cause Urbit to boot a [comet](https://urbit.org/docs/glossary/comet/) in a pier named for the `.comet` file (less the extension).
* e.g. starting with an empty file `my-urbit-bot.comet` will result in Urbit booting a comet into the pier
`my-urbit-bot` under your volume.
- An existing pier as a directory `<shipname>`. You can migrate an existing ship to a new docker container in this way by placing its pier under the volume.
* e.g. if your ship is `sampel-palnet` then you likely have a directory `sampel-palnet` whose path you pass to `./urbit` when starting. [Move your pier](https://urbit.org/using/operations/using-your-ship/#moving-your-pier) directory to the volume and then start the container.
The first two options result in Urbit attempting to boot either the ship named by the name of the keyfile, or a comet. In both cases, after that boot is successful, the `.key` or `.comet` file will be removed from the volume and the pier will take its place.
In consequence, it is safe to remove the container and start a new container which mounts the same volume, e.g. to upgrade the version of the urbit binary by running a later container version. It is also possible to stop the container and then move the pier away e.g. to a location where you will run it directly with the Urbit binary.
### Ports
The image includes `EXPOSE` directives for TCP port 80 and UDP port 34343. Port `80` is used for Urbit's HTTP interface for both [Landscape](https://urbit.org/docs/glossary/landscape/) and for [API calls](https://urbit.org/using/integrating-api/) to the ship. Port `34343` is used by [Ames](https://urbit.org/docs/glossary/ames/) for ship-to-ship communication.
You can either pass the `-P` flag to docker to map ports directly to the corresponding ports on the host, or map them individually with `-p` flags. For local testing the latter is often convenient, for instance to remap port 80 to an unprivileged port.
## Extending
You likely do not want to extend this image. External applications which interact with Urbit do so primarily via an HTTP API, which should be exposed as described above. For containerized applications using Urbit, it is more appropriate to use a container orchestration service such as Docker Compose or Kubernetes to run Urbit alongside other containers which will interface with its API.
## Development
The docker image is built by a Nix derivation in the [`nix/pkgs/docker-image/default.nix`](https://github.com/urbit/urbit/tree/master/nix/pkgs/docker-image/default.nix) file under the Urbit git repository.

View File

@ -96,7 +96,7 @@ module.exports = {
]
}
},
exclude: /node_modules/
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
},
{
test: /\.css$/i,

View File

@ -26,7 +26,7 @@ module.exports = {
]
}
},
exclude: /node_modules/
exclude: /node_modules\/(?!(@tlon\/indigo-dark|@tlon\/indigo-light)\/).*/
},
{
test: /\.css$/i,

View File

@ -1687,15 +1687,20 @@
"@styled-system/css": "^5.1.5"
}
},
"@tlon/indigo-dark": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@tlon/indigo-dark/-/indigo-dark-1.0.6.tgz",
"integrity": "sha512-/c+3/aC+gSnLHiLwTdje7pYS84ZAR3zyMJhp2mT9BIPtk7ek/EGsrrugZjVJxeKXqy+mQpFD5TXktgAEh0Ko1A=="
},
"@tlon/indigo-light": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tlon/indigo-light/-/indigo-light-1.0.3.tgz",
"integrity": "sha512-3OPSdf9cejP/TSzWXuBaYbzLtAfBzQnc75SlPLkoPfwpxnv1Bvy9hiWngLY0WnKRR6lMOldnkYQCCuNWeDibYQ=="
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@tlon/indigo-light/-/indigo-light-1.0.6.tgz",
"integrity": "sha512-kBzJueOoGDVF2knGt+Kf5ylvil6+V1qn8/RqAj1S6wUTnfUfAMRzDp4LQI2MxLI8Is0OG3XCErVSOUImU6R3lg=="
},
"@tlon/indigo-react": {
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.15.tgz",
"integrity": "sha512-h9umWEzYZwyb53ujWoCQCJQwY9RUuoDaf6189+0LH3C7y9fybJe6vzbW6g2cUVH8dXA2EZkedS5nriYR0IpQbw==",
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@tlon/indigo-react/-/indigo-react-1.2.16.tgz",
"integrity": "sha512-9bQ43cXiJGOsrihwy8+MBfG4WroKucZJOm4whfSjsNFCHorjS+5Y/6nWl2hEwHo068XONFmD7xlDE1QBMTk+pA==",
"requires": {
"@reach/menu-button": "^0.10.5",
"react": "^16.13.1",
@ -1703,16 +1708,16 @@
},
"dependencies": {
"tslib": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A=="
}
}
},
"@tlon/sigil-js": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@tlon/sigil-js/-/sigil-js-1.4.2.tgz",
"integrity": "sha512-meb0q0kf4S34oTKDulRMfVU6Wq/9lSOALeQil4EWttL72Lae9Fznsm+ix3tgT69g1xUpjeZIB+vqGOtAFhZX3g==",
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@tlon/sigil-js/-/sigil-js-1.4.3.tgz",
"integrity": "sha512-IaJUvAgXRmPFj5JA/MDfd+b+RFDhGdiMLfzJZKuFIQyl3Dl/3cC9HdDLCYSoK4GBTu3gZqoqi6wxZl5Xia/cSw==",
"requires": {
"invariant": "^2.2.4",
"svgson": "^4.0.0",
@ -8132,6 +8137,11 @@
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
"dev": true
},
"remark-breaks": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-2.0.1.tgz",
"integrity": "sha512-CZKI8xdPUnvMqPxYEIBBUg8C0B0kyn14lkW0abzhfh/P71YRIxCC3wvBh6AejQL602OxF6kNRl1x4HAZA07JyQ=="
},
"remark-disable-tokenizers": {
"version": "1.0.24",
"resolved": "https://registry.npmjs.org/remark-disable-tokenizers/-/remark-disable-tokenizers-1.0.24.tgz",
@ -10128,8 +10138,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -10150,14 +10159,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -10172,20 +10179,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -10302,8 +10306,7 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -10315,7 +10318,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -10330,7 +10332,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -10338,14 +10339,12 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -10364,7 +10363,6 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -10426,8 +10424,7 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"npm-packlist": {
"version": "1.4.8",
@ -10455,8 +10452,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -10468,7 +10464,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -10546,8 +10541,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -10583,7 +10577,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -10603,7 +10596,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -10647,14 +10639,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -11135,8 +11125,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -11157,14 +11146,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -11179,20 +11166,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -11309,8 +11293,7 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -11322,7 +11305,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -11337,7 +11319,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -11345,14 +11326,12 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -11371,7 +11350,6 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -11433,8 +11411,7 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"npm-packlist": {
"version": "1.4.8",
@ -11462,8 +11439,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -11475,7 +11451,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -11553,8 +11528,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -11590,7 +11564,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -11610,7 +11583,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -11654,14 +11626,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},

View File

@ -8,9 +8,10 @@
"@reach/disclosure": "^0.10.5",
"@reach/menu-button": "^0.10.5",
"@reach/tabs": "^0.10.5",
"@tlon/indigo-light": "^1.0.3",
"@tlon/indigo-react": "1.2.15",
"@tlon/sigil-js": "^1.4.2",
"@tlon/indigo-dark": "^1.0.6",
"@tlon/indigo-light": "^1.0.6",
"@tlon/indigo-react": "1.2.16",
"@tlon/sigil-js": "^1.4.3",
"aws-sdk": "^2.726.0",
"big-integer": "^1.6.48",
"classnames": "^2.2.6",
@ -36,6 +37,7 @@
"react-router-dom": "^5.0.0",
"react-virtuoso": "^0.20.0",
"react-visibility-sensor": "^5.1.1",
"remark-breaks": "^2.0.1",
"remark-disable-tokenizers": "^1.0.24",
"style-loader": "^1.2.1",
"styled-components": "^5.1.0",

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import _ from "lodash";
import f, { memoize } from "lodash/fp";
import bigInt, { BigInteger } from "big-integer";
@ -13,7 +13,7 @@ export const MOMENT_CALENDAR_DATE = {
nextWeek: "dddd",
lastDay: "[Yesterday]",
lastWeek: "[Last] dddd",
sameElse: "DD/MM/YYYY",
sameElse: "~YYYY.M.D",
};
export function appIsGraph(app: string) {
@ -361,4 +361,13 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames);
return !!(contact && contact.nickname && !hideNicknames);
}
export function useHovering() {
const [hovering, setHovering] = useState(false);
const bind = {
onMouseEnter: () => setHovering(true),
onMouseLeave: () => setHovering(false)
};
return { hovering, bind };
}

View File

@ -386,5 +386,7 @@ function archive(json: any, state: HarkState) {
notifIdxEqual(index, idxNotif.index)
);
state.notifications.set(time, unarchived);
const newlyRead = archived.filter(x => !x.notification.read).length;
updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead);
}
}

View File

@ -31,7 +31,7 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
suspendedFocus: undefined,
toggleOmnibox: () => set(produce(state => {
state.omniboxShown = !state.omniboxShown;
if (state.suspendedFocus) {
if (typeof state.suspendedFocus?.focus === 'function') {
state.suspendedFocus.focus();
state.suspendedFocus = undefined;
} else {

View File

@ -11,8 +11,8 @@ import 'mousetrap-global-bind';
import './css/indigo-static.css';
import './css/fonts.css';
import light from './themes/light';
import dark from './themes/old-dark';
import light from '@tlon/indigo-light';
import dark from '@tlon/indigo-dark';
import { Text, Anchor, Row } from '@tlon/indigo-react';
@ -40,7 +40,7 @@ const Root = styled.div`
background-size: cover;
` : p.background?.type === 'color' ? `
background-color: ${p.background.color};
` : ''
` : `background-color: ${p.theme.colors.white};`
}
display: flex;
flex-flow: column nowrap;

View File

@ -231,8 +231,8 @@ export const MessageWithSigil = (props) => {
}}
title={`~${msg.author}`}
>{name}</Text>
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
<Text flexShrink='0' fontSize='0' gray mono className="v-mid">{timestamp}</Text>
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
</Box>
<ContentBox flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
{msg.contents.map(c =>
@ -259,7 +259,7 @@ const ContentBox = styled(Box)`
export const MessageWithoutSigil = ({ timestamp, contacts, msg, measure, group }) => (
<>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child" fontSize='0'>{timestamp}</Text>
<ContentBox flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
{msg.contents.map((c, i) => (
<MessageContent

View File

@ -193,6 +193,8 @@ export default class ChatEditor extends Component {
alignItems='center'
flexGrow='1'
height='100%'
paddingTop={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
paddingBottom={MOBILE_BROWSER_REGEX.test(navigator.userAgent) ? '16px' : '0'}
maxHeight='224px'
width='calc(100% - 88px)'
className={inCodeMode ? 'chat code' : 'chat'}
@ -201,7 +203,7 @@ export default class ChatEditor extends Component {
{MOBILE_BROWSER_REGEX.test(navigator.userAgent)
? <MobileBox
data-value={this.state.message}
fontSize="14px"
fontSize="1"
lineHeight="tall"
onClick={event => {
if (this.editor) {
@ -211,7 +213,7 @@ export default class ChatEditor extends Component {
>
<BaseTextArea
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
fontSize="14px"
fontSize="1"
lineHeight="tall"
rows="1"
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}

View File

@ -12,6 +12,7 @@ export default class CodeContent extends Component {
(
<Text
display='block'
fontSize='0'
mono
p='1'
my='0'
@ -37,6 +38,7 @@ export default class CodeContent extends Component {
overflow='auto'
maxHeight='10em'
maxWidth='100%'
fontSize='0'
style={{ whiteSpace: 'pre' }}
>
{content.code.expression}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
import RemarkBreaks from 'remark-breaks';
import urbitOb from 'urbit-ob';
import { Text } from '@tlon/indigo-react';
@ -26,10 +27,10 @@ const DISABLED_INLINE_TOKENS = [
const renderers = {
inlineCode: ({language, value}) => {
return <Text mono p='1' backgroundColor='washedGray' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
return <Text mono p='1' backgroundColor='washedGray' fontSize='0' style={{ whiteSpace: 'preWrap'}}>{value}</Text>
},
paragraph: ({ children }) => {
return (<Text fontSize="14px">{children}</Text>);
return (<Text fontSize="1">{children}</Text>);
},
code: ({language, value}) => {
return <Text
@ -38,6 +39,7 @@ const renderers = {
display='block'
borderRadius='1'
mono
fontSize='0'
backgroundColor='washedGray'
overflowX='auto'
style={{ whiteSpace: 'pre'}}>
@ -66,6 +68,7 @@ const MessageMarkdown = React.memo(props => (
return true;
}}
plugins={[[
RemarkBreaks,
RemarkDisableTokenizers,
{ block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }
]]} />

View File

@ -277,9 +277,6 @@ pre.CodeMirror-placeholder.CodeMirror-line-like { color: var(--gray); }
/* dark */
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-black-d {
background-color: black;
}

View File

@ -78,7 +78,7 @@ export default function LaunchApp(props) {
color="black"
icon="Mail"
/>
<Text ml="1" mt='1px' color="black">DMs + Drafts</Text>
<Text ml="2" mt='1px' color="black">DMs + Drafts</Text>
</Row>
</Box>
</Tile>
@ -102,7 +102,7 @@ export default function LaunchApp(props) {
icon="CreateGroup"
bg="green"
color="#fff"
text="Create a Group"
text="Create Group"
>
<NewGroup {...props} />
</ModalButton>

View File

@ -20,8 +20,8 @@ export default class BasicTile extends React.PureComponent {
size='12px'
display='inline-block'
verticalAlign='top'
pt='5px'
pr='2px'
mt='5px'
mr='2'
/>
: null
}{props.title}

View File

@ -171,7 +171,7 @@ export default class WeatherTile extends React.Component {
onClick={() => this.setState({ manualEntry: !this.state.manualEntry })}
>
<Box>
<Icon icon='Weather' display='inline-block' verticalAlign='top' pt='3px' pr='2px' />
<Icon icon='Weather' display='inline-block' verticalAlign='top' mt='3px' mr='2' />
<Text>Weather</Text>
</Box>
<Text style={{ cursor: 'pointer' }}>
@ -217,15 +217,14 @@ export default class WeatherTile extends React.Component {
title={`${locationName} Weather`}
>
<Text>
<Icon icon='Weather' display='inline' style={{ position: 'relative', top: '.3em' }} />
Weather
<Icon icon='Weather' display='inline' mr='2' style={{ position: 'relative', top: '.3em' }} />
<Text
cursor='pointer'
onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry })
}
>
->
Weather ->
</Text>
</Text>
@ -268,7 +267,7 @@ export default class WeatherTile extends React.Component {
flexDirection="column"
justifyContent="flex-start"
>
<Text><Icon icon='Weather' color='black' display='inline' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
<Text><Icon icon='Weather' color='black' display='inline' mr='2' style={{ position: 'relative', top: '.3em' }} /> Weather</Text>
<Text width='100%' display='flex' flexDirection='column' mt={1}>
Loading, please check again later...
</Text>

View File

@ -40,12 +40,12 @@ button {
/* stolen from indigo-react reset.css
* TODO: remove and add reset.css properly
*/
@keyframes loadingSpinnerRotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
@ -53,9 +53,6 @@ button {
/* dark */
@media all and (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-gray0-d {
background-color: #333;
}

View File

@ -130,9 +130,9 @@ export const LinkItem = (props: LinkItemProps) => {
</Anchor>
</Text>
</Box>
<Row minWidth='0' flexShrink={0} width="100%" justifyContent="space-between" py={3} bg="white">
<Author
showImage
contacts={contacts}
@ -150,9 +150,9 @@ export const LinkItem = (props: LinkItemProps) => {
</Box>
</Link>
</Box>
<Dropdown
width="200px"
dropWidth="200px"
alignX="right"
alignY="top"
options={
@ -170,7 +170,7 @@ export const LinkItem = (props: LinkItemProps) => {
>
<Icon ml="2" display="block" icon="Ellipsis" color="gray" />
</Dropdown>
</Row>
</Box>);
};

View File

@ -132,7 +132,6 @@ const LinkSubmit = (props: LinkSubmitProps) => {
position="absolute"
px={2}
pt={2}
fontSize={0}
style={{ pointerEvents: 'none' }}
>{canUpload
? <>
@ -180,7 +179,6 @@ const LinkSubmit = (props: LinkSubmitProps) => {
type="url"
pl={2}
width="100%"
fontSize={0}
py={2}
color="black"
backgroundColor="transparent"
@ -198,8 +196,8 @@ const LinkSubmit = (props: LinkSubmitProps) => {
pl={2}
backgroundColor="transparent"
width="100%"
fontSize={0}
color="black"
fontSize={1}
style={{
resize: 'none',
height: 40

View File

@ -1,105 +0,0 @@
import React, { useCallback } from "react";
import _ from "lodash";
import { Link } from "react-router-dom";
import GlobalApi from "~/logic/api/global";
import {
Rolodex,
Associations,
ChatNotifIndex,
ChatNotificationContents,
Groups,
} from "~/types";
import { BigInteger } from "big-integer";
import { Box, Col } from "@tlon/indigo-react";
import { Header } from "./header";
import { pluralize } from "~/logic/lib/util";
import ChatMessage from "../chat/components/ChatMessage";
function describeNotification(mention: boolean, lent: number) {
const msg = pluralize("message", lent !== 1);
if (mention) {
return `mentioned you in ${msg} in`;
}
return `sent ${msg} in`;
}
export function ChatNotification(props: {
index: ChatNotifIndex;
contents: ChatNotificationContents;
archived: boolean;
read: boolean;
time: number;
timebox: BigInteger;
associations: Associations;
contacts: Rolodex;
groups: Groups;
api: GlobalApi;
}) {
const { index, contents, read, time, api, timebox } = props;
const authors = _.map(contents, "author");
const { chat, mention } = index;
const association = props?.associations?.chat?.[chat];
const groupPath = association?.["group-path"];
const appPath = index?.chat;
const group = props?.groups?.[groupPath];
const desc = describeNotification(mention, contents.length);
const groupContacts = props.contacts[groupPath] || {};
const onClick = useCallback(() => {
if (props.archived) {
return;
}
const func = read ? "unread" : "read";
return api.hark[func](timebox, { chat: index });
}, [api, timebox, index, read]);
return (
<Col onClick={onClick} flexGrow="1" p="2">
<Header
chat
associations={props.associations}
read={read}
archived={props.archived}
time={time}
authors={authors}
moduleIcon="Chat"
channel={chat}
contacts={props.contacts}
group={groupPath}
description={desc}
/>
<Col pb="3" pl="5">
{_.map(_.take(contents, 5), (content, idx) => {
let workspace = groupPath;
if (workspace === undefined || group?.hidden) {
workspace = '/home';
}
const to = `/~landscape${workspace}/resource/chat${appPath}?msg=${content.number}`;
return (
<Link key={idx} to={to}>
<ChatMessage
measure={() => {}}
msg={content}
isLastRead={false}
group={group}
contacts={groupContacts}
fontSize='0'
pt='2'
/>
</Link>
);
})}
{contents.length > 5 && (
<Box ml="4" mt="3" mb="2" color="gray" fontSize="14px">
and {contents.length - 5} other message
{contents.length > 6 ? "s" : ""}
</Box>
)}
</Col>
</Col>
);
}

View File

@ -90,6 +90,8 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
content={contents}
group={group}
contacts={contacts}
fontSize='14px'
lineHeight="tall"
/>
} else if (idx[1] === "1") {
const [{ text: header }, { text: body }] = contents;
@ -164,13 +166,14 @@ const GraphNode = ({
group,
read,
onRead,
showContact = false,
remoteContentPolicy
}) => {
const { contents } = post;
author = deSig(author);
const history = useHistory();
const img = (
const img = showContact ? (
<Sigil
ship={`~${author}`}
size={16}
@ -178,7 +181,7 @@ const GraphNode = ({
color={`#000000`}
classes="mix-blend-diff"
/>
);
) : <Box style={{ width: '16px' }}></Box>;
const groupContacts = contacts[groupPath] ?? {};
@ -192,10 +195,10 @@ const GraphNode = ({
}, [read, onRead]);
return (
<Row onClick={onClick} gapX="2" pt="2">
<Row onClick={onClick} gapX="2" pt={showContact ? 2 : 0}>
<Col>{img}</Col>
<Col flexGrow={1} alignItems="flex-start">
<Row
{showContact && <Row
mb="2"
height="16px"
alignItems="center"
@ -208,8 +211,8 @@ const GraphNode = ({
<Text ml="2" gray>
{moment(time).format("HH:mm")}
</Text>
</Row>
<Row width="100%" p="1">
</Row>}
<Row width="100%" p="1" flexDirection="column">
<GraphNodeContent
contacts={groupContacts}
post={post}
@ -244,16 +247,15 @@ export function GraphNotification(props: {
const desc = describeNotification(index.description, contents.length !== 1);
const onClick = useCallback(() => {
if (props.archived) {
if (props.archived || read) {
return;
}
const func = read ? "unread" : "read";
return api.hark[func](timebox, { graph: index });
return api.hark["read"](timebox, { graph: index });
}, [api, timebox, index, read]);
return (
<Col flexGrow={1} width="100%" p="2">
<>
<Header
onClick={onClick}
archived={props.archived}
@ -267,7 +269,7 @@ return (
description={desc}
associations={props.associations}
/>
<Col flexGrow={1} width="100%" pl="5">
<Box flexGrow={1} width="100%" pl={5} gridArea="main">
{_.map(contents, (content, idx) => (
<GraphNode
post={content}
@ -282,9 +284,10 @@ return (
groupPath={group}
read={read}
onRead={onClick}
showContact={idx === 0}
/>
))}
</Col>
</Col>
</Box>
</>
);
}

View File

@ -1,5 +1,5 @@
import React from "react";
import { Text as NormalText, Row, Icon, Rule } from "@tlon/indigo-react";
import { Text as NormalText, Row, Icon, Rule, Box } from "@tlon/indigo-react";
import f from "lodash/fp";
import _ from "lodash";
import moment from "moment";
@ -71,12 +71,13 @@ export function Header(props: {
channel;
return (
<Row onClick={props.onClick} p="2" flexWrap="wrap" gapX="1" alignItems="center">
<Row onClick={props.onClick} p="2" flexWrap="wrap" alignItems="center" gridArea="header">
{!props.archived && (
<Icon
display="block"
mr="1"
icon={read ? "Circle" : "Bullet"}
opacity={read ? 0 : 1}
mr={2}
icon="Bullet"
color="blue"
/>
)}
@ -84,13 +85,13 @@ export function Header(props: {
{authorDesc}
</Text>
<Text mr="1">{description}</Text>
{!!moduleIcon && <Icon icon={moduleIcon as any} />}
{!!channel && <Text fontWeight="500">{channelTitle}</Text>}
<Rule vertical height="12px" />
{!!moduleIcon && <Icon icon={moduleIcon as any} mr={1} />}
{!!channel && <Text fontWeight="500" mr={1}>{channelTitle}</Text>}
<Rule vertical height="12px" mr={1} />
{groupTitle &&
<>
<Text fontWeight="500">{groupTitle}</Text>
<Rule vertical height="12px"/>
<Text fontWeight="500" mr={1}>{groupTitle}</Text>
<Rule vertical height="12px" mr={1} />
</>
}
<Text fontWeight="regular" color="lightGray">

View File

@ -61,20 +61,36 @@ export default function Inbox(props: {
};
}, []);
const [newNotifications, ...notifications] =
const notifications =
Array.from(props.showArchive ? props.archive : props.notifications) || [];
const calendar = {
...MOMENT_CALENDAR_DATE, sameDay: function (now) {
if (this.subtract(6, 'hours').isBefore(now)) {
return "[Earlier Today]";
} else {
return MOMENT_CALENDAR_DATE.sameDay;
}
}
};
const notificationsByDay = f.flow(
let notificationsByDay = f.flow(
f.map<DatedTimebox>(([date, nots]) => [
date,
nots.filter(filterNotification(associations, props.filter)),
]),
f.groupBy<DatedTimebox>(([date]) =>
moment(daToUnix(date)).format("DDMMYYYY")
),
f.values,
f.reverse
f.groupBy<DatedTimebox>(([date]) => {
date = moment(daToUnix(date));
if (moment().subtract(6, 'hours').isBefore(date)) {
return 'latest';
} else {
return date.format("YYYYMMDD");
}
}),
)(notifications);
notificationsByDay = new Map(Object.keys(notificationsByDay).sort().reverse().map(timebox => {
return [timebox, notificationsByDay[timebox]];
}));
useEffect(() => {
api.hark.getMore(props.showArchive);
@ -133,38 +149,24 @@ export default function Inbox(props: {
<Col zIndex={4} gapY={2} bg="white" top="0px" position="sticky">
{inviteItems(invites, api)}
</Col>
{newNotifications && (
<DaySection
latest
timeboxes={[newNotifications]}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
api={api}
/>
)}
{_.map(
notificationsByDay,
(timeboxes, idx) =>
timeboxes.length > 0 && (
<DaySection
key={idx}
timeboxes={timeboxes}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
api={api}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
/>
)
)}
{[...notificationsByDay.keys()].map((day, index) => {
const timeboxes = notificationsByDay.get(day);
return timeboxes.length > 0 && (
<DaySection
key={day}
label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
timeboxes={timeboxes}
contacts={props.contacts}
archive={!!props.showArchive}
associations={props.associations}
api={api}
groups={props.groups}
graphConfig={props.notificationsGraphConfig}
groupConfig={props.notificationsGroupConfig}
chatConfig={props.notificationsChatConfig}
/>
);
})}
</Col>
);
}
@ -181,21 +183,18 @@ function sortIndexedNotification(
}
function DaySection({
label,
contacts,
groups,
archive,
timeboxes,
latest = false,
associations,
api,
groupConfig,
graphConfig,
chatConfig,
remoteContentPolicy
}) {
const calendar = latest
? MOMENT_CALENDAR_DATE
: { ...MOMENT_CALENDAR_DATE, sameDay: "[Earlier Today]" };
const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
if (lent === 0 || timeboxes.length === 0) {
return null;
@ -206,7 +205,7 @@ function DaySection({
<Box position="sticky" zIndex="3" top="-1px" bg="white">
<Box p="2" bg="scales.black05">
<Text>
{moment(daToUnix(timeboxes[0][0])).calendar(null, calendar)}
{label}
</Text>
</Box>
</Box>

View File

@ -1,5 +1,5 @@
import React, { ReactNode, useCallback, useMemo } from "react";
import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react";
import React, { ReactNode, useCallback, useMemo, useState } from "react";
import { Row, Box } from "@tlon/indigo-react";
import _ from "lodash";
import {
GraphNotificationContents,
@ -7,7 +7,6 @@ import {
GroupNotificationContents,
NotificationGraphConfig,
GroupNotificationsConfig,
NotifIndex,
Groups,
Associations,
Contacts,
@ -17,8 +16,8 @@ import { getParentIndex } from "~/logic/lib/notification";
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
import { GroupNotification } from "./group";
import { GraphNotification } from "./graph";
import { ChatNotification } from "./chat";
import { BigInteger } from "big-integer";
import { useHovering } from "~/logic/lib/util";
interface NotificationProps {
notification: IndexedNotification;
@ -55,9 +54,6 @@ function getMuted(
if ("group" in index) {
return _.findIndex(groups || [], (g) => g === index.group.group) === -1;
}
if ("chat" in index) {
return _.findIndex(chat || [], (c) => c === index.chat.chat) === -1;
}
return false;
}
@ -89,11 +85,21 @@ function NotificationWrapper(props: {
return api.hark[func](notif);
}, [notif, api, isMuted]);
const { hovering, bind } = useHovering();
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
return (
<Row width="100%" flexShrink={0} alignItems="top" justifyContent="space-between">
<Box
width="100%"
display="grid"
gridTemplateColumns="1fr 200px"
gridTemplateRows="auto"
gridTemplateAreas="'header actions' 'main main'"
pb={2}
{...bind}
>
{children}
<Row gapX="2" p="2" pt='3' alignItems="top">
<Row gapX="2" p="2" pt='3' gridArea="actions" justifyContent="flex-end" opacity={[1, hovering ? 1 : 0]}>
<StatelessAsyncAction name={changeMuteDesc} onClick={onChangeMute} backgroundColor="transparent">
{changeMuteDesc}
</StatelessAsyncAction>
@ -103,7 +109,7 @@ function NotificationWrapper(props: {
</StatelessAsyncAction>
)}
</Row>
</Row>
</Box>
);
}
@ -166,26 +172,6 @@ export function Notification(props: NotificationProps) {
</Wrapper>
);
}
if ("chat" in notification.index) {
const index = notification.index.chat;
const c: ChatNotificationContents = (contents as any).chat;
return (
<Wrapper>
<ChatNotification
api={props.api}
index={index}
contents={c}
contacts={props.contacts}
read={read}
archived={archived}
groups={props.groups}
timebox={props.time}
time={time}
associations={associations}
/>
</Wrapper>
);
}
return null;
}

View File

@ -33,7 +33,7 @@ const SidebarItem = ({ children, view, current }) => {
backgroundColor={selected ? "washedGray" : "white"}
>
<Icon mr={2} display="inline-block" icon={icon(view)} color='black' />
<Text color='black' fontSize={0}>
<Text color='black'>
{children}
</Text>
</Row>
@ -76,7 +76,7 @@ export default function ProfileScreen(props: any) {
height="100%"
width="100%"
display="grid"
gridTemplateColumns={["100%", "200px 1fr"]}
gridTemplateColumns={["100%", "250px 1fr"]}
gridTemplateRows={["48px 1fr", "1fr"]}
borderRadius={1}
bg="white"
@ -95,8 +95,8 @@ export default function ProfileScreen(props: any) {
bg={sigilColor}
borderRadius={8}
my={4}
height={128}
width={128}
height={160}
width={160}
display="flex"
justifyContent="center"
alignItems="center"

View File

@ -10,6 +10,7 @@ import CodeMirror from "codemirror";
import "codemirror/mode/markdown/markdown";
import "codemirror/addon/display/placeholder";
import "codemirror/addon/edit/continuelist";
import "codemirror/lib/codemirror.css";
import { Box } from "@tlon/indigo-react";
@ -54,6 +55,7 @@ export function MarkdownEditor(
scrollbarStyle: "native",
// cursorHeight: 0.85,
placeholder: placeholder || "",
extraKeys: { 'Enter': 'newlineAndIndentContinueMarkdownList' }
};
const editor: React.RefObject<any> = useRef();

View File

@ -67,7 +67,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
color="red"
ml={2}
onClick={deletePost}
css={{ cursor: "pointer" }}
style={{ cursor: "pointer" }}
>
Delete
</Text>

View File

@ -61,9 +61,9 @@ export function NotePreview(props: NotePreviewProps) {
overflow='hidden'
p='2'
>
<WrappedBox mb={2}><Text bold fontSize='0'>{title}</Text></WrappedBox>
<WrappedBox mb={2}><Text bold>{title}</Text></WrappedBox>
<WrappedBox>
<Text fontSize='14px'>
<Text fontSize='14px' lineHeight='tall'>
<ReactMarkdown
unwrapDisallowed
allowedTypes={['text', 'root', 'break', 'paragraph', 'image']}

View File

@ -1,10 +1,9 @@
import React from "react";
import { Link, RouteComponentProps } from "react-router-dom";
import { RouteComponentProps } from "react-router-dom";
import { NotebookPosts } from "./NotebookPosts";
import { Box, Button, Text, Row, Col } from "@tlon/indigo-react";
import { Col } from "@tlon/indigo-react";
import GlobalApi from "~/logic/api/global";
import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads } from "~/types";
import { useShowNickname } from "~/logic/lib/util";
interface NotebookProps {
api: GlobalApi;
@ -30,44 +29,14 @@ export function Notebook(props: NotebookProps & RouteComponentProps) {
association,
graph
} = props;
const { metadata } = association;
const group = groups[association?.['group-path']];
if (!group) {
return null; // Waiting on groups to populate
}
const relativePath = (p: string) => props.baseUrl + p;
const contact = notebookContacts?.[ship];
const isOwn = `~${window.ship}` === ship;
let isWriter = true;
if (group.tags?.publish?.[`writers-${book}`]) {
isWriter = isOwn || group.tags?.publish?.[`writers-${book}`]?.has(window.ship);
}
const showNickname = useShowNickname(contact);
return (
<Col gapY="4" pt={4} mx="auto" px={3} maxWidth="768px">
<Row justifyContent="space-between">
<Box>
<Text display='block'>{metadata?.title}</Text>
<Text color="lightGray">by </Text>
<Text fontFamily={showNickname ? 'sans' : 'mono'}>
{showNickname ? contact?.nickname : ship}
</Text>
</Box>
{isWriter && (
<Link to={relativePath('/new')}>
<Button primary style={{ cursor: 'pointer' }}>
New Post
</Button>
</Link>
)}
</Row>
<Box borderBottom="1" borderBottomColor="washedGray" />
<NotebookPosts
graph={graph}
host={ship}

View File

@ -206,9 +206,6 @@
}
@media all and (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-black-d {
background-color: black;
}

View File

@ -52,13 +52,14 @@ export default function Author(props: AuthorProps) {
<Box
ml={showImage ? 2 : 0}
color="black"
fontSize='1'
lineHeight='tall'
fontFamily={showNickname ? "sans" : "mono"}
fontWeight={showNickname ? '500' : '400'}
>
{name}
</Box>
<Box ml={2} color={props.unread ? "blue" : "gray"}>
<Box fontSize='1' ml={2} color={props.unread ? "blue" : "gray"}>
{dateFmt}
</Box>
{props.children}

View File

@ -21,6 +21,7 @@ interface DropdownProps {
alignY: AlignY | AlignY[];
alignX: AlignX | AlignX[];
width?: string;
dropWidth?: string;
}
const ClickBox = styled(Box)`
@ -111,14 +112,14 @@ export function Dropdown(props: DropdownProps) {
});
return (
<Box flexShrink={1} position={open ? "relative" : "static"} minWidth='0'>
<Box flexShrink={1} position={open ? "relative" : "static"} minWidth='0' width={props?.width ? props.width : 'auto'}>
<ClickBox width='100%' ref={anchorRef} onClick={onOpen}>
{children}
</ClickBox>
{open && (
<Portal>
<DropdownOptions
width={props.width || "max-content"}
width={props?.dropWidth || "max-content"}
{...coords}
ref={dropdownRef}
>

View File

@ -19,10 +19,10 @@ interface MentionTextProps {
group: Group;
}
export function MentionText(props: MentionTextProps) {
const { content, contacts, contact, group } = props;
const { content, contacts, contact, group, ...rest } = props;
return (
<RichText contacts={contacts} contact={contact} group={group}>
<RichText contacts={contacts} contact={contact} group={group} {...rest}>
{content.reduce((accum, c) => {
if ("text" in c) {
return accum + c.text;
@ -44,7 +44,7 @@ export function Mention(props: {
const { contacts, ship } = props;
let { contact } = props;
contact = (contact?.nickname) ? contact : contacts?.[ship];
contact = (contact?.color) ? contact : contacts?.[ship];
const showNickname = useShowNickname(contact);

View File

@ -129,7 +129,7 @@ class ProfileOverlay extends PureComponent<ProfileOverlayProps, {}> {
)}
<Text mono gray>{cite(`~${ship}`)}</Text>
{!isOwn && (
<Button mt={2} width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
<Button mt={2} fontSize='0' width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
Send Message
</Button>
)}

View File

@ -27,19 +27,17 @@ const RichText = React.memo(({ disableRemoteContent, ...props }) => (
{...props}
renderers={{
link: (linkProps) => {
if (disableRemoteContent) {
linkProps.remoteContentPolicy = {
imageShown: false,
audioShown: false,
videoShown: false,
oembedShown: false
};
}
const remoteContentPolicy = disableRemoteContent ? {
imageShown: false,
audioShown: false,
videoShown: false,
oembedShown: false
} : null;
if (hasProvider(linkProps.href)) {
return <RemoteContent className="mw-100" url={linkProps.href} />;
}
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' {...linkProps}>{linkProps.children}</BaseAnchor>;
return <BaseAnchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' remoteContentPolicy={remoteContentPolicy} {...linkProps}>{linkProps.children}</BaseAnchor>;
},
linkReference: (linkProps) => {
const linkText = String(linkProps.children[0].props.children);

View File

@ -5,6 +5,7 @@ import ReconnectButton from './ReconnectButton';
import { StatusBarItem } from './StatusBarItem';
import { Sigil } from '~/logic/lib/sigil';
import useLocalState from '~/logic/state/local';
import { cite } from '~/logic/lib/util';
const StatusBar = (props) => {
const invites = [].concat(...Object.values(props.invites).map(obj => Object.values(obj)));
@ -21,7 +22,7 @@ const StatusBar = (props) => {
pb='3'
>
<Row collapse>
<Button borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
<Button width="32px" borderColor='washedGray' mr='2' px='2' onClick={() => props.history.push('/')} {...props}>
<Icon icon='Spaces' color='black'/>
</Button>
@ -59,9 +60,8 @@ const StatusBar = (props) => {
>
<Text color='#000000'>Submit <Text color='#000000' display={['none', 'inline']}>an</Text> issue</Text>
</StatusBarItem>
<StatusBarItem px={'2'} flexShrink='0' onClick={() => props.history.push('/~profile')}>
<StatusBarItem width={['32px', 'auto']} px={'2'} flexShrink='0' onClick={() => props.history.push('/~profile')}>
<Sigil ship={props.ship} size={16} color='black' classes='mix-blend-diff' icon />
<Text ml={2} display={["none", "inline"]} fontFamily="mono">~{props.ship}</Text>
</StatusBarItem>
</Row>
</Box>

View File

@ -22,7 +22,7 @@ export class OmniboxInput extends Component {
border='1px solid transparent'
borderRadius='2'
maxWidth='calc(600px - 1.15rem)'
fontSize='0'
fontSize='1'
style={{ boxSizing: 'border-box' }}
placeholder='Search...'
onKeyDown={props.control}

View File

@ -48,7 +48,7 @@ export function ChannelMenu(props: ChannelMenuProps) {
const isOurs = ship.slice(1) === window.ship;
const isMuted =
const isMuted =
props.graphNotificationConfig.watching.findIndex(
(a) => a.graph === appPath && a.index === "/"
) === -1;
@ -102,7 +102,7 @@ export function ChannelMenu(props: ChannelMenuProps) {
</ChannelMenuItem>
<ChannelMenuItem bottom icon="Gear" color="black">
<Link to={`${baseUrl}/settings`}>
<Box fontSize={0} p="2">
<Box fontSize={1} p="2">
Channel Settings
</Box>
</Link>
@ -119,9 +119,9 @@ export function ChannelMenu(props: ChannelMenuProps) {
}
alignX="right"
alignY="top"
width="250px"
dropWidth="250px"
>
<Icon display="block" icon="Menu" color="gray" />
<Icon display="block" icon="Menu" color="gray" pr='2' />
</Dropdown>
);
}

View File

@ -1,21 +1,18 @@
import React from "react";
import React from 'react';
import {
Center,
Box,
Col,
Row,
Text,
IconButton,
Button,
Icon,
} from "@tlon/indigo-react";
import { uxToHex } from "~/logic/lib/util";
import { Link } from "react-router-dom";
Icon
} from '@tlon/indigo-react';
import { uxToHex } from '~/logic/lib/util';
import { Link } from 'react-router-dom';
import { Association, Associations } from "~/types/metadata-update";
import { Dropdown } from "~/views/components/Dropdown";
import { Workspace } from "~/types";
import { getTitleFromWorkspace } from "~/logic/lib/workspace";
import { Associations } from '~/types/metadata-update';
import { Dropdown } from '~/views/components/Dropdown';
import { Workspace } from '~/types';
import { getTitleFromWorkspace } from '~/logic/lib/workspace';
const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
<Link to={to}>
@ -47,7 +44,7 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
return (e in associations?.contacts);
}).slice(1, 5).map((g) => {
const assoc = associations.contacts[g];
const color = uxToHex(assoc?.metadata?.color || "0x0");
const color = uxToHex(assoc?.metadata?.color || '0x0');
return (
<Link key={g} style={{ minWidth: 0 }} to={`/~landscape${g}`}>
<Row px={1} pb={2} alignItems="center">
@ -60,7 +57,7 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
bg={`#${color}`}
mr={2}
display="block"
flexShrink='0'
flexShrink={0}
/>
<Text verticalAlign='top' maxWidth='100%' overflow='hidden' display='inline-block' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{assoc?.metadata?.title}</Text>
</Row>
@ -76,22 +73,20 @@ export function GroupSwitcher(props: {
workspace: Workspace;
baseUrl: string;
recentGroups: string[];
isAdmin: any;
}) {
const { associations, workspace, isAdmin } = props;
const title = getTitleFromWorkspace(associations, workspace);
const navTo = (to: string) => `${props.baseUrl}${to}`;
return (
<Box zIndex="2" position="sticky" top="0px" p={2}>
<Box height='48px' backgroundColor="white" zIndex="2" position="sticky" top="0px" py={3} pl='3' borderBottom='1px solid' borderColor='washedGray'>
<Col
justifyContent="center"
bg="white"
borderRadius={1}
border={1}
borderColor="washedGray"
>
<Row alignItems="center" justifyContent="space-between">
<Row justifyContent="space-between">
<Dropdown
width="231px"
width="100%"
dropWidth="231px"
alignY="top"
options={
<Col
@ -134,9 +129,9 @@ export function GroupSwitcher(props: {
<Icon mr="2" color="gray" icon="Plus" />
<Text> Join Group</Text>
</GroupSwitcherItem>
{workspace.type === "group" && (
{workspace.type === 'group' && (
<>
<GroupSwitcherItem to={navTo("/popover/participants")}>
<GroupSwitcherItem to={navTo('/popover/participants')}>
<Icon
mr={2}
color="gray"
@ -144,7 +139,7 @@ export function GroupSwitcher(props: {
/>
<Text> Participants</Text>
</GroupSwitcherItem>
<GroupSwitcherItem to={navTo("/popover/settings")}>
<GroupSwitcherItem to={navTo('/popover/settings')}>
<Icon
mr={2}
color="gray"
@ -152,7 +147,7 @@ export function GroupSwitcher(props: {
/>
<Text> Group Settings</Text>
</GroupSwitcherItem>
{isAdmin && (<GroupSwitcherItem bottom to={navTo("/invites")}>
{isAdmin && (<GroupSwitcherItem bottom to={navTo('/invites')}>
<Icon
mr={2}
color="blue"
@ -165,25 +160,25 @@ export function GroupSwitcher(props: {
</Col>
}
>
<Row p={2} alignItems="center" width='100%' minWidth='0'>
<Row alignItems="center" mr={1} flex='1' width='100%' minWidth='0'>
<Text overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre'}}>{title}</Text>
<Icon size='12px' ml='1' mt="0px" display="inline-block" icon="ChevronSouth" />
<Row width='100%' minWidth='0' flexShrink={0}>
<Row justifyContent="space-between" mr={1} flexShrink={0} width='100%' minWidth='0'>
<Text lineHeight="1.1" fontSize='2' fontWeight="700" overflow='hidden' display='inline-block' flexShrink='1' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{title}</Text>
</Row>
</Row>
</Dropdown>
<Row pr={1} justifyContent="flex-end" alignItems="center">
{(workspace.type === "group") && (
<Row pr='3' verticalAlign="middle">
{(workspace.type === 'group') && (
<>
{isAdmin && (<Link to={navTo("/invites")}>
{isAdmin && (<Link to={navTo('/invites')}>
<Icon
display="block"
display="inline-block"
color='blue'
icon="Users"
ml='12px'
/>
</Link>)}
<Link to={navTo("/popover/settings")}>
<Icon color='gray' display="block" m={1} icon="Gear" />
<Link to={navTo('/popover/settings')}>
<Icon color='gray' display="inline-block" ml={'12px'} icon="Gear" />
</Link>
</>
)}

View File

@ -85,7 +85,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
moduleType
);
}
if (!group) {
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
}
@ -99,11 +99,11 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
actions.setStatus({ error: 'Channel creation failed' });
}
};
return (
<Col overflowY="auto" p={3}>
<Box pb='3' display={['block', 'none']} onClick={() => history.push(props.baseUrl)}>
{'<- Back'}
<Text fontSize='0' bold>{'<- Back'}</Text>
</Box>
<Box fontWeight="bold" mb={4} color="black">
New Channel

View File

@ -80,7 +80,7 @@ export function PopoverRoutes(
<Box
display="grid"
gridTemplateRows={["32px 1fr", "100%"]}
gridTemplateColumns={["100%", "150px 1fr"]}
gridTemplateColumns={["100%", "250px 1fr"]}
height="100%"
width="100%"
>

View File

@ -52,6 +52,7 @@ export function Resource(props: ResourceProps) {
return (
<ResourceSkeleton
baseUrl={props.baseUrl}
groupTags={props.groups?.[selectedGroup]?.tags}
{...skelProps}
>
<ChannelSettings
@ -72,6 +73,7 @@ export function Resource(props: ResourceProps) {
notificationsGraphConfig={props.notificationsGraphConfig}
notificationsChatConfig={props.notificationsChatConfig}
baseUrl={props.baseUrl}
groupTags={props.groups?.[selectedGroup]?.tags}
{...skelProps}
atRoot
>

View File

@ -16,7 +16,7 @@ import { ChannelMenu } from "./ChannelMenu";
import { NotificationGraphConfig } from "~/types";
const TruncatedBox = styled(Box)`
white-space: nowrap;
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
`;
@ -29,19 +29,33 @@ type ResourceSkeletonProps = {
children: ReactNode;
atRoot?: boolean;
title?: string;
groupTags?: any;
};
export function ResourceSkeleton(props: ResourceSkeletonProps) {
const { association, api, baseUrl, children, atRoot } = props;
const { association, api, baseUrl, children, atRoot, groupTags } = props;
const app = association?.metadata?.module || association["app-name"];
const appPath = association["app-path"];
const workspace =
baseUrl === "/~landscape/home" ? "/home" : association["group-path"];
const title = props.title || association?.metadata?.title;
const [, , ship, resource] = appPath.split("/");
const resourcePath = (p: string) => baseUrl + `/resource/${app}/ship/${ship}/${resource}` + p;
const isOwn = `~${window.ship}` === ship;
let isWriter = (app === 'publish') ? true : false;
if (groupTags?.publish?.[`writers-${resource}`]) {
isWriter = isOwn || groupTags?.publish?.[`writers-${resource}`]?.has(window.ship);
}
return (
<Col width="100%" height="100%" overflowY="hidden">
<Box
flexShrink="0"
height='48px'
py="2"
px="2"
display="flex"
@ -54,6 +68,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
borderRight={1}
borderRightColor="gray"
pr={3}
fontSize='1'
mr={3}
my="1"
display={["block", "none"]}
@ -70,15 +85,15 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
{atRoot && (
<>
<Box pr={1} mr={2}>
<Text display="inline-block" verticalAlign="middle">
<Box px={1} mr={2}>
<Text fontSize='2' fontWeight='700' display="inline-block" verticalAlign="middle" textOverflow="ellipsis" overflow="hidden" whiteSpace="pre">
{title}
</Text>
</Box>
<TruncatedBox
display={["none", "block"]}
maxWidth="60%"
verticalAlign="middle"
maxWidth='60%'
flexShrink={1}
title={association?.metadata?.description}
color="gray"
@ -93,6 +108,11 @@ export function ResourceSkeleton(props: ResourceSkeletonProps) {
</RichText>
</TruncatedBox>
<Box flexGrow={1} />
{isWriter && (
<Link to={resourcePath('/new')} style={{ flexShrink: '0' }}>
<Text bold pr='3' color='blue'>+ New Post</Text>
</Link>
)}
<ChannelMenu
graphNotificationConfig={props.notificationsGraphConfig}
chatNotificationConfig={props.notificationsChatConfig}

View File

@ -1,26 +1,24 @@
import React, { ReactNode } from "react";
import React, { ReactNode } from 'react';
import styled from 'styled-components';
import {
Box,
Col,
} from "@tlon/indigo-react";
import { Link } from "react-router-dom";
Col
} from '@tlon/indigo-react';
import GlobalApi from "~/logic/api/global";
import { GroupSwitcher } from "../GroupSwitcher";
import GlobalApi from '~/logic/api/global';
import { GroupSwitcher } from '../GroupSwitcher';
import {
Associations,
Workspace,
Groups,
Invites,
Rolodex,
} from "~/types";
import { SidebarListHeader } from "./SidebarListHeader";
import { useLocalStorageState } from "~/logic/lib/useLocalStorageState";
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
Rolodex
} from '~/types';
import { SidebarListHeader } from './SidebarListHeader';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import { SidebarAppConfigs } from './types';
import { SidebarList } from "./SidebarList";
import { roleForShip } from "~/logic/lib/group";
import { SidebarList } from './SidebarList';
import { roleForShip } from '~/logic/lib/group';
const ScrollbarLessCol = styled(Col)`
scrollbar-width: none !important;
@ -30,7 +28,6 @@ const ScrollbarLessCol = styled(Col)`
}
`;
interface SidebarProps {
contacts: Rolodex;
children: ReactNode;
@ -48,38 +45,24 @@ interface SidebarProps {
workspace: Workspace;
}
// Magic spacer that because firefox doesn't correctly calculate
// position: sticky on a flex child
// remove when https://bugzilla.mozilla.org/show_bug.cgi?id=1488080
// is fixed
const SidebarStickySpacer = styled(Box)`
height: 0px;
flex-grow: 1;
@-moz-document url-prefix() {
& {
height: ${p => p.theme.space[6] }px;
}
}
`;
export function Sidebar(props: SidebarProps) {
const { invites, api, associations, selected, apps, workspace } = props;
const { associations, selected, workspace } = props;
const groupPath = getGroupFromWorkspace(workspace);
const display = props.mobileHide ? ["none", "flex"] : "flex";
const display = props.mobileHide ? ['none', 'flex'] : 'flex';
if (!associations) {
return null;
}
const [config, setConfig] = useLocalStorageState<SidebarListConfig>(
`group-config:${groupPath || "home"}`,
`group-config:${groupPath || 'home'}`,
{
sortBy: "lastUpdated",
hideUnjoined: false,
sortBy: 'lastUpdated',
hideUnjoined: false
}
);
const role = props.groups?.[groupPath] ? roleForShip(props.groups[groupPath], window.ship) : undefined;
const isAdmin = (role === "admin") || (workspace?.type === 'home');
const isAdmin = (role === 'admin') || (workspace?.type === 'home');
return (
<ScrollbarLessCol
@ -107,8 +90,9 @@ export function Sidebar(props: SidebarProps) {
groups={props.groups}
initialValues={config}
handleSubmit={setConfig}
selected={selected || ""}
workspace={workspace} />
selected={selected || ''}
workspace={workspace}
/>
<SidebarList
config={config}
associations={associations}
@ -118,31 +102,6 @@ export function Sidebar(props: SidebarProps) {
apps={props.apps}
baseUrl={props.baseUrl}
/>
<SidebarStickySpacer flexShrink={0} />
<Box
flexShrink="0"
display={isAdmin ? "flex" : "none"}
justifyContent="center"
position="sticky"
bottom={"8px"}
width="100%"
height="fit-content"
py="2"
>
<Link
to={!!groupPath ? `/~landscape${groupPath}/new` : `/~landscape/home/new`}
>
<Box
bg="white"
p={2}
borderRadius={1}
border={1}
borderColor="lightGray"
>
+ New Channel
</Box>
</Link>
</Box>
</ScrollbarLessCol>
);
}

View File

@ -93,8 +93,8 @@ export function SidebarItem(props: {
justifyContent="space-between"
alignItems="center"
py={1}
pl={2}
pr={2}
pl={3}
pr={3}
selected={selected}
>
<Row width='100%' alignItems="center" flex='1 auto' minWidth='0'>
@ -105,7 +105,7 @@ export function SidebarItem(props: {
/>
<Box width='100%' flexShrink={2} ml={2} display='flex' overflow='hidden'>
<Text
lineHeight="short"
lineHeight="tall"
display='inline-block'
flex='1'
overflow='hidden'

View File

@ -14,7 +14,8 @@ import { Dropdown } from "~/views/components/Dropdown";
import { FormikHelpers } from "formik";
import { SidebarListConfig, Workspace } from "./types";
import { Link, useHistory } from 'react-router-dom';
import {ShipSearch} from "~/views/components/ShipSearch";
import { getGroupFromWorkspace } from "~/logic/lib/workspace";
import { roleForShip } from "~/logic/lib/group";
import {Groups, Rolodex} from "~/types";
export function SidebarListHeader(props: {
@ -36,42 +37,51 @@ export function SidebarListHeader(props: {
[props.handleSubmit]
);
const groupPath = getGroupFromWorkspace(props.workspace);
const role = props.groups?.[groupPath] ? roleForShip(props.groups[groupPath], window.ship) : undefined;
const isAdmin = (role === "admin") || (props.workspace?.type === 'home');
return (
<Row
flexShrink="0"
alignItems="center"
justifyContent="space-between"
py={2}
pr={2}
pl={2}
px={3}
height='48px'
>
<Box flexShrink='0'>
<Text bold>
<Text>
{props.initialValues.hideUnjoined ? "Joined Channels" : "All Channels"}
</Text>
</Box>
<Box
width='100%'
textAlign='right'
mr='2'
display={(props.workspace?.type === 'home') ? 'inline-block' : 'none'}
display='flex'
>
<Link to={`${props.baseUrl}/invites`}>
<Link
style={{
display: isAdmin ? "inline-block" : "none" }}
to={
!!groupPath ? `/~landscape${groupPath}/new` : `/~landscape/home/new`}>
<Icon icon="Plus" color="gray" pr='2'/>
</Link>
<Link to={`${props.baseUrl}/invites`}
style={{ display: (props.workspace?.type === 'home') ? 'inline-block' : 'none'}}>
<Text
display='inline-block'
verticalAlign='middle'
py='1px'
px='3px'
mr='2'
backgroundColor='washedBlue'
color='blue'
borderRadius='1'>
+ DM
</Text>
</Link>
</Box>
<Dropdown
flexShrink='0'
width="200px"
width="auto"
alignY="top"
alignX={["right", "left"]}
options={
@ -102,6 +112,7 @@ export function SidebarListHeader(props: {
>
<Icon color="gray" icon="Adjust" />
</Dropdown>
</Box>
</Row>
);
}

View File

@ -1,171 +0,0 @@
import baseStyled, { ThemedStyledInterface } from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const theme = {
colors: {
white: base.white,
black: base.black,
darkGray: scales.black80,
gray: scales.black60,
lightGray: scales.black30,
washedGray: scales.black10,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red10,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
16, // 4
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export type Theme = typeof theme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
export default theme;

View File

@ -1,186 +0,0 @@
import baseStyled, { ThemedStyledInterface } from "styled-components";
const base = {
white: "rgba(255,255,255,1)",
black: "rgba(0,0,0,1)",
red: "rgba(255,65,54,1)",
yellow: "rgba(255,199,0,1)",
green: "rgba(0,159,101,1)",
blue: "rgba(0,142,255,1)",
};
const scales = {
white05: "rgba(255,255,255,0.05)",
white10: "rgba(255,255,255,0.1)",
white20: "rgba(255,255,255,0.2)",
white30: "rgba(255,255,255,0.3)",
white40: "rgba(255,255,255,0.4)",
white50: "rgba(255,255,255,0.5)",
white60: "rgba(255,255,255,0.6)",
white70: "rgba(255,255,255,0.7)",
white80: "rgba(255,255,255,0.8)",
white90: "rgba(255,255,255,0.9)",
white100: "rgba(255,255,255,1)",
black05: "rgba(0,0,0,0.05)",
black10: "rgba(0,0,0,0.1)",
black20: "rgba(0,0,0,0.2)",
black30: "rgba(0,0,0,0.3)",
black40: "rgba(0,0,0,0.4)",
black50: "rgba(0,0,0,0.5)",
black60: "rgba(0,0,0,0.6)",
black70: "rgba(0,0,0,0.7)",
black80: "rgba(0,0,0,0.8)",
black90: "rgba(0,0,0,0.9)",
black100: "rgba(0,0,0,1)",
red05: "rgba(255,65,54,0.05)",
red10: "rgba(255,65,54,0.1)",
red20: "rgba(255,65,54,0.2)",
red30: "rgba(255,65,54,0.3)",
red40: "rgba(255,65,54,0.4)",
red50: "rgba(255,65,54,0.5)",
red60: "rgba(255,65,54,0.6)",
red70: "rgba(255,65,54,0.7)",
red80: "rgba(255,65,54,0.8)",
red90: "rgba(255,65,54,0.9)",
red100: "rgba(255,65,54,1)",
yellow05: "rgba(255,199,0,0.05)",
yellow10: "rgba(255,199,0,0.1)",
yellow20: "rgba(255,199,0,0.2)",
yellow30: "rgba(255,199,0,0.3)",
yellow40: "rgba(255,199,0,0.4)",
yellow50: "rgba(255,199,0,0.5)",
yellow60: "rgba(255,199,0,0.6)",
yellow70: "rgba(255,199,0,0.7)",
yellow80: "rgba(255,199,0,0.8)",
yellow90: "rgba(255,199,0,0.9)",
yellow100: "rgba(255,199,0,1)",
green05: "rgba(0,159,101,0.05)",
green10: "rgba(0,159,101,0.1)",
green20: "rgba(0,159,101,0.2)",
green30: "rgba(0,159,101,0.3)",
green40: "rgba(0,159,101,0.4)",
green50: "rgba(0,159,101,0.5)",
green60: "rgba(0,159,101,0.6)",
green70: "rgba(0,159,101,0.7)",
green80: "rgba(0,159,101,0.8)",
green90: "rgba(0,159,101,0.9)",
green100: "rgba(0,159,101,1)",
blue05: "rgba(0,142,255,0.05)",
blue10: "rgba(0,142,255,0.1)",
blue20: "rgba(0,142,255,0.2)",
blue30: "rgba(0,142,255,0.3)",
blue40: "rgba(0,142,255,0.4)",
blue50: "rgba(0,142,255,0.5)",
blue60: "rgba(0,142,255,0.6)",
blue70: "rgba(0,142,255,0.7)",
blue80: "rgba(0,142,255,0.8)",
blue90: "rgba(0,142,255,0.9)",
blue100: "rgba(0,142,255,1)",
};
const util = {
cyan: "#00FFFF",
magenta: "#FF00FF",
yellow: "#FFFF00",
black: "#000000",
gray0: "#333333"
};
const theme = {
colors: {
white: util.gray0,
black: base.white,
darkGray: scales.white80,
gray: scales.white60,
lightGray: scales.white30,
washedGray: scales.white05,
red: base.red,
lightRed: scales.red30,
washedRed: scales.red05,
yellow: base.yellow,
lightYellow: scales.yellow30,
washedYellow: scales.yellow10,
green: base.green,
lightGreen: scales.green30,
washedGreen: scales.green10,
blue: base.blue,
lightBlue: scales.blue30,
washedBlue: scales.blue10,
none: "rgba(0,0,0,0)",
scales: scales,
util: util,
},
fonts: {
sans: `"Inter", "Inter UI", -apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', Arial, sans-serif`,
mono: `"Source Code Pro", "Roboto mono", "Courier New", monospace`,
},
// font-size
fontSizes: [
12, // 0
16, // 1
24, // 2
32, // 3
48, // 4
64, // 5
],
// font-weight
fontWeights: {
thin: 300,
regular: 400,
semibold: 500,
bold: 600,
},
// line-height
lineHeights: {
min: 1.2,
short: 1.333333,
regular: 1.5,
tall: 1.666666,
},
// border, border-top, border-right, border-bottom, border-left
borders: ["none", "1px solid"],
// margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, grid-gap, grid-column-gap, grid-row-gap
space: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// border-radius
radii: [
0, // 0
2, // 1
4, // 2
8, // 3
],
// width, height, min-width, max-width, min-height, max-height
sizes: [
0, // 0
4, // 1
8, // 2
16, // 3
24, // 4
32, // 5
48, // 6
64, // 7
96, // 8
],
// z-index
zIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
breakpoints: ["550px", "750px", "960px"],
};
export type Theme = typeof theme;
export const styled = baseStyled as ThemedStyledInterface<Theme>;
export default theme;