Merge remote-tracking branch 'origin/dist' into m/dist-ames-glob-fixes
@ -512,10 +512,10 @@
|
||||
++ pause
|
||||
|= lac=desk
|
||||
^+ vats
|
||||
=. vats (abed lac)
|
||||
?. is-tracking
|
||||
~> %slog.0^leaf/"kiln: {<lac>} already paused, ignoring"
|
||||
vats
|
||||
=. vats (abed lac)
|
||||
~> %slog.0^leaf/"kiln: {<lac>} pausing updates"
|
||||
=/ rel ral
|
||||
=. rail.rak `rel(paused &, aeon 0)
|
||||
|
@ -11,7 +11,7 @@
|
||||
?- -.diff
|
||||
%block (block +.diff)
|
||||
?(%merge-sunk %merge-fail) (desk-arak-err +.diff)
|
||||
?(%reset %merge %suspend %revive) (desk-arak +.diff)
|
||||
?(%reset %commit %suspend %revive) (desk-arak +.diff)
|
||||
==
|
||||
::
|
||||
++ block
|
||||
|
13
pkg/base-dev/mar/ship.hoon
Normal file
@ -0,0 +1,13 @@
|
||||
|_ s=ship
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun s
|
||||
++ json s+(scot %p s)
|
||||
--
|
||||
++ grab
|
||||
|%
|
||||
++ noun ship
|
||||
++ json (su:dejs:format ;~(pfix sig fed:ag))
|
||||
--
|
||||
--
|
@ -67,7 +67,11 @@
|
||||
++ report-vat
|
||||
|= [our=ship now=@da vat]
|
||||
^- tank
|
||||
=+ .^(=weft %cx /(scot %p our)/[desk]/(scot %da now)/sys/kelvin)
|
||||
=/ kel-path
|
||||
/(scot %p our)/[desk]/(scot %da now)/sys/kelvin
|
||||
?. .^(? %cu kel-path)
|
||||
leaf+"bad desk: {<desk>}"
|
||||
=+ .^(=weft %cx kel-path)
|
||||
:+ %rose ["" "{<desk>}" "::"]
|
||||
^- tang
|
||||
=/ meb (mergebase-hashes our desk now arak)
|
||||
@ -81,7 +85,7 @@
|
||||
:~ leaf/"/sys/kelvin: {<[lal num]:weft>}"
|
||||
leaf/"base hash: {?.(=(1 (lent meb)) <meb> <(head meb)>)}"
|
||||
leaf/"%cz hash: {<hash>}"
|
||||
leaf/"updates: {sat}"
|
||||
leaf/"updates: {poz}"
|
||||
leaf/"source ship: {?~(rail.arak <~> <ship.u.rail.arak>)}"
|
||||
leaf/"source desk: {?~(rail.arak <~> <desk.u.rail.arak>)}"
|
||||
leaf/"source aeon: {?~(rail.arak <~> <aeon.u.rail.arak>)}"
|
||||
|
@ -2,7 +2,7 @@
|
||||
title+'Bitcoin'
|
||||
info+'BTC wallet for Urbit. Testing'
|
||||
color+0xf9.8e40
|
||||
glob-http+'https://bootstrap.urbit.org/glob-0v4.ghaim.of1as.9ucee.uj93f.a9nbs.glob'
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v4.ghaim.of1as.9ucee.uj93f.a9nbs.glob' 0v4.ghaim.of1as.9ucee.uj93f.a9nbs]
|
||||
image+'https://urbit.ewr1.vultrobjects.com/hastuc-dibtux/2021.8.24..02.57.38-bitcoin.svg'
|
||||
base+'bitcoin'
|
||||
version+[0 0 1]
|
||||
|
@ -572,15 +572,17 @@
|
||||
?: =(suffix /desk/js)
|
||||
%- inline-js-response
|
||||
(rap 3 'window.desk = "' u.des '";' ~)
|
||||
=/ requested
|
||||
?: (~(has by glob) suffix) suffix
|
||||
/index/html
|
||||
=/ data=mime
|
||||
(~(gut by glob) suffix (~(got by glob) /index/html))
|
||||
(~(got by glob) requested)
|
||||
=/ mime-type=@t (rsh 3 (crip <p.data>))
|
||||
=; headers
|
||||
[[200 headers] `q.data]
|
||||
:~ content-type+mime-type
|
||||
max-1-wk:gen
|
||||
'service-worker-allowed'^'/'
|
||||
==
|
||||
:- content-type+mime-type
|
||||
?: =(/index/html requested) ~
|
||||
~[max-1-wk:gen]
|
||||
--
|
||||
::
|
||||
++ get-light-charge
|
||||
|
@ -1,6 +1,9 @@
|
||||
/- docket, *treaty
|
||||
/+ default-agent, agentio, verb, dbug
|
||||
|%
|
||||
:: TODO: update before livenet deploy
|
||||
++ default-ally ~zod
|
||||
::
|
||||
+$ card card:agent:gall
|
||||
+$ state-0
|
||||
$: treaties=(map [=ship =desk] treaty)
|
||||
@ -23,9 +26,8 @@
|
||||
pass pass:io
|
||||
cc ~(. +> bowl)
|
||||
++ on-init
|
||||
=/ sponsor=ship (sein:title [our now our]:bowl)
|
||||
?: =(our.bowl sponsor) `this
|
||||
(on-poke %ally-update-0 !>([%add sponsor]))
|
||||
?: =(our.bowl default-ally) `this
|
||||
(on-poke %ally-update-0 !>([%add default-ally]))
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= =vase
|
||||
@ -120,8 +122,9 @@
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
?+ path (on-peek:def path)
|
||||
[%x %alliance ~] ``(alliance-update:cg:ca %ini entente)
|
||||
[%x %allies ~] ``(ally-update:cg:ca %ini allies)
|
||||
[%x %alliance ~] ``(alliance-update:cg:ca %ini entente)
|
||||
[%x %default-ally ~] ``ship+!>(default-ally)
|
||||
[%x %allies ~] ``(ally-update:cg:ca %ini allies)
|
||||
::
|
||||
[%x %treaties @ ~]
|
||||
=/ =ship (slav %p i.t.t.path)
|
||||
|
@ -1,7 +1,8 @@
|
||||
:~ title+'Garden'
|
||||
info+'An app launcher for Urbit.'
|
||||
info+'An app launcher for Urbit. '
|
||||
color+0xee.5432
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v2.dne76.9hibl.1o442.h6l11.cn7os.glob' 0v2.dne76.9hibl.1o442.h6l11.cn7os]
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v7.1jqf3.5gao8.67i4k.64c49.i6kr8.glob' 0v7.1jqf3.5gao8.67i4k.64c49.i6kr8]
|
||||
::glob-ames+~zod^0v0
|
||||
base+'grid'
|
||||
version+[0 0 1]
|
||||
|
1
pkg/garden/mar/ship.hoon
Symbolic link
@ -0,0 +1 @@
|
||||
../../base-dev/mar/ship.hoon
|
@ -2,14 +2,17 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/src/assets/favicon.png" />
|
||||
<link rel="icon" href="/src/assets/favicon.svg" sizes="any" type="image/svg+xml" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Landscape • Home</title>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="/src/assets/favicon.svg" sizes="any" type="image/svg+xml" />
|
||||
<link rel="mask-icon" href="/src/assets/safari-pinned-tab.svg" color="#000000" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/src/assets/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/src/assets/manifest.json" />
|
||||
<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:wght@400;600&display=swap"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter&family=Source+Code+Pro:wght@400;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
14091
pkg/grid/package-lock.json
generated
@ -49,7 +49,8 @@ const AppRoutes = () => {
|
||||
useEffect(() => {
|
||||
window.name = 'grid';
|
||||
|
||||
const { fetchAllies, fetchCharges } = useDocketState.getState();
|
||||
const { fetchDefaultAlly, fetchAllies, fetchCharges } = useDocketState.getState();
|
||||
fetchDefaultAlly();
|
||||
fetchCharges();
|
||||
fetchAllies();
|
||||
const { fetchVats, fetchLag } = useKilnState.getState();
|
||||
|
BIN
pkg/grid/src/assets/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
pkg/grid/src/assets/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
pkg/grid/src/assets/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.0 KiB |
@ -1,11 +1,12 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="513" height="513" viewBox="0 0 513 513" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.icon-color { fill: #000000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
rect { fill: #ffffff; }
|
||||
.icon-color { fill: #ffffff; }
|
||||
}
|
||||
</style>
|
||||
<rect x="51" y="51" width="173" height="173" rx="24" fill="#444444"/>
|
||||
<rect x="51" y="288" width="173" height="173" rx="24" fill="#444444"/>
|
||||
<rect x="288" y="51" width="173" height="173" rx="24" fill="#444444"/>
|
||||
<rect x="288" y="288" width="173" height="173" rx="24" fill="#444444"/>
|
||||
<path d="M247.634 122.758C247.634 147.519 238.15 162.538 219.182 171.468C198.239 181.21 184.804 197.853 178.876 220.584C172.159 245.751 153.981 260.364 129.481 259.552C109.723 259.146 94.7075 249.404 86.014 231.138C79.2963 216.931 70.6028 204.753 56.7723 197.041C50.4497 193.388 43.732 190.952 36.6192 188.923C16.4661 183.24 1.84525 166.597 0.264615 145.896C-1.71118 121.135 7.37747 103.274 27.5305 93.1266C49.6594 82.5728 63.49 65.1184 69.4173 41.1695C75.3447 16.8145 95.8929 2.60752 122.369 4.6371C137.385 5.85484 148.844 13.5672 157.538 25.7447C163.465 34.2689 168.602 43.199 174.529 51.3173C181.642 61.4652 191.126 67.9598 202.586 72.019C208.118 74.0486 214.045 76.0781 219.182 78.9195C238.15 88.6615 247.634 104.492 247.634 122.758Z" class="icon-color" />
|
||||
<path d="M512.01 355.757C511.615 372.806 503.712 387.824 486.325 396.349C463.406 407.714 449.18 425.981 442.067 450.335C435.745 471.037 419.543 484.026 398.6 485.65C377.657 487.274 359.084 476.72 350.391 457.236C340.512 434.505 323.915 420.704 300.601 414.209C258.714 403.249 254.762 350.48 279.262 328.561C283.609 324.908 288.351 321.254 293.488 318.819C314.036 308.671 327.076 292.029 333.399 270.109C338.931 250.219 350.786 236.824 370.939 232.765C393.858 228.3 415.197 235.2 426.261 259.555C436.535 282.287 453.132 295.682 476.446 302.582C500.551 309.889 512.01 326.937 512.01 355.757Z" class="icon-color" />
|
||||
<path d="M463.786 51.3199C462.6 58.2204 461.415 65.121 459.439 71.6156C453.117 93.1291 454.697 113.425 466.157 132.909C476.431 150.363 476.036 168.629 465.762 185.678C455.883 202.32 440.472 210.033 421.899 210.033C417.157 210.033 412.415 208.815 408.069 207.191C384.359 199.073 361.44 200.697 339.706 214.498C310.069 233.17 271.739 206.379 268.973 178.371C268.182 169.441 268.182 160.511 271.344 152.393C280.037 130.067 276.481 109.366 266.997 89.07C262.65 80.1398 259.094 70.8038 259.094 60.6559C259.489 27.3709 290.311 3.42188 321.529 11.9461C331.408 14.7875 341.287 18.4407 351.956 18.4407C364.206 18.4407 374.875 14.3816 385.94 9.10469C393.052 5.45146 400.956 2.20414 408.859 0.580478C434.544 -3.88459 461.02 18.0348 462.996 44.8252C462.996 47.2607 463.391 49.2903 463.391 51.7258C463.391 51.3199 463.786 51.3199 463.786 51.3199Z" class="icon-color" />
|
||||
<path d="M235.757 342.773C234.571 349.268 233.781 356.168 231.805 362.663C225.483 384.176 227.063 404.878 238.523 424.362C257.095 456.429 237.337 498.239 197.822 501.486C192.684 501.892 187.152 500.674 182.015 499.05C157.515 490.526 134.201 491.744 112.072 505.951C82.8304 524.217 44.8952 497.833 41.7339 470.636C40.9436 461.706 40.5485 453.182 43.7097 444.658C52.4032 421.521 48.8468 399.601 38.1775 378.088C33.0405 367.94 30.2743 356.98 31.4598 345.209C33.8308 322.883 53.9839 302.587 76.1127 300.964C84.4111 300.558 92.3142 301.37 99.8222 304.211C120.766 312.329 140.524 309.488 159.886 298.528C174.507 290.004 190.314 286.757 206.12 294.469C224.692 304.617 234.966 320.448 235.757 342.773Z" class="icon-color" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 474 B After Width: | Height: | Size: 3.1 KiB |
14
pkg/grid/src/assets/manifest.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "urbit",
|
||||
"short_name": "urbit",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/apps/grid/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
54
pkg/grid/src/assets/safari-pinned-tab.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M5565 6989 c-16 -5 -39 -11 -50 -13 -54 -12 -116 -37 -230 -91 -219
|
||||
-105 -335 -135 -495 -131 -63 2 -126 7 -140 10 -14 4 -36 9 -50 12 -14 3 -79
|
||||
21 -145 41 -205 61 -361 54 -525 -23 -239 -113 -392 -356 -392 -625 0 -110 25
|
||||
-201 107 -389 85 -197 116 -320 121 -480 3 -137 -12 -231 -60 -370 -15 -41
|
||||
-30 -97 -33 -125 -8 -58 -8 -190 0 -255 27 -220 225 -440 475 -527 67 -24 92
|
||||
-27 197 -27 109 0 127 3 189 28 37 16 100 47 140 70 174 103 316 141 521 140
|
||||
146 0 208 -10 362 -58 171 -54 291 -54 449 -1 294 99 498 442 448 755 -4 25
|
||||
-9 50 -10 55 -2 6 -7 24 -10 40 -4 17 -36 86 -71 153 -60 117 -113 253 -128
|
||||
332 -4 19 -8 91 -10 160 -4 126 8 227 40 336 24 82 65 283 60 297 -2 6 -6 43
|
||||
-10 81 -8 97 -22 150 -62 231 -44 90 -71 126 -148 200 -123 117 -255 175 -415
|
||||
180 -52 2 -108 -1 -125 -6z"/>
|
||||
<path d="M1445 6923 c-38 -8 -106 -31 -150 -52 -187 -90 -284 -214 -354 -455
|
||||
-59 -201 -123 -321 -243 -451 -81 -88 -154 -143 -295 -222 -64 -35 -135 -79
|
||||
-157 -96 -100 -76 -187 -210 -223 -347 -21 -80 -24 -308 -4 -390 22 -91 84
|
||||
-209 146 -277 102 -112 188 -163 385 -228 307 -100 451 -229 620 -550 61 -115
|
||||
94 -161 164 -228 52 -50 156 -117 181 -117 8 0 16 -4 19 -9 16 -26 274 -53
|
||||
356 -37 14 3 37 7 53 10 71 12 165 55 237 109 122 90 201 216 265 420 51 165
|
||||
96 258 172 359 86 114 212 216 358 290 147 75 226 139 292 238 36 53 80 162
|
||||
89 216 3 22 8 42 10 46 3 4 7 53 9 109 10 221 -44 374 -180 512 -67 68 -167
|
||||
136 -253 173 -31 14 -66 29 -77 33 -11 5 -47 19 -80 31 -164 59 -294 148 -377
|
||||
257 -25 32 -97 137 -160 233 -131 199 -185 263 -272 324 -147 103 -335 138
|
||||
-531 99z"/>
|
||||
<path d="M5070 3824 c-177 -38 -296 -111 -399 -245 -36 -46 -88 -160 -122
|
||||
-264 -103 -321 -254 -508 -534 -660 -184 -100 -292 -208 -352 -350 -113 -268
|
||||
-56 -593 138 -785 81 -80 154 -122 300 -171 152 -51 233 -88 322 -147 139 -93
|
||||
274 -254 346 -415 83 -185 208 -309 376 -372 84 -32 139 -40 260 -39 145 1
|
||||
248 34 370 117 121 82 205 205 266 388 113 340 278 537 601 714 145 80 234
|
||||
172 290 300 86 195 64 503 -49 694 -50 85 -141 172 -224 216 -37 19 -120 53
|
||||
-184 76 -197 67 -303 129 -425 246 -92 87 -158 179 -230 323 -102 202 -207
|
||||
301 -380 360 -82 28 -272 36 -370 14z"/>
|
||||
<path d="M2395 3017 c-76 -22 -110 -37 -270 -119 -72 -38 -195 -82 -252 -93
|
||||
-127 -22 -184 -24 -293 -10 -86 11 -114 17 -179 40 -132 47 -193 58 -317 57
|
||||
-103 0 -176 -18 -268 -64 -142 -72 -258 -190 -325 -331 -95 -200 -84 -408 34
|
||||
-666 95 -209 136 -366 136 -528 1 -142 -12 -213 -67 -380 -22 -66 -27 -97 -27
|
||||
-198 -1 -66 2 -142 6 -169 29 -183 195 -384 394 -476 186 -87 369 -86 533 2
|
||||
41 23 102 55 135 73 73 39 196 80 290 95 69 12 250 15 283 6 9 -3 38 -7 64
|
||||
-11 26 -3 87 -17 135 -31 48 -14 113 -32 143 -41 211 -56 483 46 643 242 48
|
||||
58 120 194 131 245 3 14 8 36 12 50 13 50 13 225 0 285 -19 81 -35 123 -94
|
||||
240 -96 188 -122 287 -127 468 -2 115 7 188 40 317 52 202 68 337 50 423 -3
|
||||
15 -7 39 -10 54 -8 38 -40 125 -58 159 -8 16 -24 45 -33 64 -28 53 -150 172
|
||||
-229 222 -106 68 -172 89 -295 94 -88 3 -118 0 -185 -19z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
@ -68,6 +68,8 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
||||
}, 1250);
|
||||
}, []);
|
||||
|
||||
const installing = installStatus === 'installing';
|
||||
|
||||
if (!docket) {
|
||||
// TODO: maybe replace spinner with skeletons
|
||||
return (
|
||||
@ -87,15 +89,15 @@ export const AppInfo: FC<AppInfoProps> = ({ docket, vat, className }) => {
|
||||
as="a"
|
||||
href={getAppHref(docket.href)}
|
||||
target={docket.desk || '_blank'}
|
||||
onClick={() => addRecentApp(docket)}
|
||||
onClick={() => addRecentApp(docket.desk)}
|
||||
>
|
||||
Open App
|
||||
</PillButton>
|
||||
)}
|
||||
{installStatus !== 'installed' && (
|
||||
<Dialog>
|
||||
<DialogTrigger as={PillButton} variant="alt-primary">
|
||||
{installStatus === 'installing' ? (
|
||||
<DialogTrigger as={PillButton} disabled={installing} variant="alt-primary">
|
||||
{installing ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span className="sr-only">Installing...</span>
|
||||
|
@ -52,7 +52,7 @@ export const AppList = <T extends DocketWithDesk>({
|
||||
size={size}
|
||||
selected={selected(app)}
|
||||
onClick={(e) => {
|
||||
addRecentApp(app);
|
||||
addRecentApp(app.desk);
|
||||
onClick?.(e, app);
|
||||
}}
|
||||
/>
|
||||
|
@ -4,6 +4,7 @@ import { sigil, reactRenderer } from '@tlon/sigil-js';
|
||||
import { deSig, Contact } from '@urbit/api';
|
||||
import { darken, lighten, parseToHsla } from 'color2k';
|
||||
import { useCurrentTheme } from '../state/local';
|
||||
import { normalizeUrbitColor } from '../state/util';
|
||||
|
||||
export type AvatarSizes = 'xs' | 'small' | 'default';
|
||||
|
||||
@ -66,7 +67,7 @@ export const Avatar = ({ size, className, ...ship }: AvatarProps) => {
|
||||
const currentTheme = useCurrentTheme();
|
||||
const { shipName, color, avatar } = { ...emptyContact, ...ship };
|
||||
const { classes, size: sigilSize } = sizeMap[size];
|
||||
const adjustedColor = themeAdjustColor(color, currentTheme);
|
||||
const adjustedColor = themeAdjustColor(normalizeUrbitColor(color), currentTheme);
|
||||
const foregroundColor = foregroundFromBackground(adjustedColor);
|
||||
const sigilElement = useMemo(() => {
|
||||
if (shipName.match(/[_^]/)) {
|
||||
|
@ -8,10 +8,18 @@ import { Toggle } from './Toggle';
|
||||
type SettingsProps = {
|
||||
name: string;
|
||||
on: boolean;
|
||||
disabled?: boolean;
|
||||
toggle: (open: boolean) => Promise<void>;
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const Setting: FC<SettingsProps> = ({ name, on, toggle, className, children }) => {
|
||||
export const Setting: FC<SettingsProps> = ({
|
||||
name,
|
||||
on,
|
||||
disabled = false,
|
||||
toggle,
|
||||
className,
|
||||
children
|
||||
}) => {
|
||||
const { status, call } = useAsyncCall(toggle);
|
||||
const id = slugify(name);
|
||||
|
||||
@ -26,6 +34,8 @@ export const Setting: FC<SettingsProps> = ({ name, on, toggle, className, childr
|
||||
pressed={on}
|
||||
onPressedChange={call}
|
||||
className="flex-none self-start text-blue-400"
|
||||
disabled={disabled}
|
||||
loading={status === 'loading'}
|
||||
/>
|
||||
<div className="flex-1 flex flex-col justify-center space-y-6">{children}</div>
|
||||
</div>
|
||||
|
@ -6,13 +6,17 @@ import type * as Polymorphic from '@radix-ui/react-polymorphic';
|
||||
type ToggleComponent = Polymorphic.ForwardRefComponent<
|
||||
Polymorphic.IntrinsicElement<typeof RadixToggle.Root>,
|
||||
Polymorphic.OwnProps<typeof RadixToggle.Root> & {
|
||||
loading?: boolean;
|
||||
toggleClass?: string;
|
||||
knobClass?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const Toggle = React.forwardRef(
|
||||
({ defaultPressed, pressed, onPressedChange, disabled, className, toggleClass }, ref) => {
|
||||
(
|
||||
{ defaultPressed, pressed, onPressedChange, disabled, className, toggleClass, loading = false },
|
||||
ref
|
||||
) => {
|
||||
const [on, setOn] = useState(defaultPressed);
|
||||
const isControlled = !!onPressedChange;
|
||||
const proxyPressed = isControlled ? pressed : on;
|
||||
@ -24,7 +28,7 @@ export const Toggle = React.forwardRef(
|
||||
className={classNames('default-ring rounded-full', className)}
|
||||
pressed={proxyPressed}
|
||||
onPressedChange={proxyOnPressedChange}
|
||||
disabled={disabled}
|
||||
disabled={disabled || loading}
|
||||
ref={ref}
|
||||
>
|
||||
<svg
|
||||
|
@ -25,8 +25,8 @@ function SystemPreferencesSection({
|
||||
<Link
|
||||
to={url}
|
||||
className={classNames(
|
||||
'flex items-center px-2 py-2 hover:text-black hover:bg-gray-100 rounded-xl',
|
||||
active && 'text-black bg-gray-100'
|
||||
'flex items-center px-2 py-2 hover:text-black hover:bg-gray-50 rounded-xl',
|
||||
active && 'text-black bg-gray-50'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@ -63,7 +63,7 @@ export const SystemPreferences = (props: RouteComponentProps<{ submenu: string }
|
||||
<div className="flex h-full overflow-y-auto">
|
||||
<aside className="flex-none self-start min-w-60 py-8 font-semibold border-r-2 border-gray-50">
|
||||
<nav className="px-6">
|
||||
<ul>
|
||||
<ul className="space-y-1">
|
||||
<SystemPreferencesSection
|
||||
url={subUrl('notifications')}
|
||||
active={matchSub('notifications')}
|
||||
@ -86,7 +86,7 @@ export const SystemPreferences = (props: RouteComponentProps<{ submenu: string }
|
||||
</nav>
|
||||
<hr className="my-4 border-t-2 border-gray-50" />
|
||||
<nav className="px-6">
|
||||
<ul>
|
||||
<ul className="space-y-1">
|
||||
{Object.values(charges).map((charge) => (
|
||||
<SystemPreferencesSection
|
||||
key={charge.desk}
|
||||
|
@ -8,6 +8,7 @@ import { ShipName } from '../../components/ShipName';
|
||||
import { DeskLink } from '../../components/DeskLink';
|
||||
import { useHarkStore } from '../../state/hark';
|
||||
import { DocketImage } from '../../components/DocketImage';
|
||||
import { Button } from '../../components/Button';
|
||||
|
||||
interface BasicNotificationProps {
|
||||
notification: Notification;
|
||||
@ -45,14 +46,20 @@ export const BasicNotification = ({ notification, lid }: BasicNotificationProps)
|
||||
useHarkStore.getState().archiveNote(notification.bin, lid);
|
||||
};
|
||||
|
||||
const archiveNoFollow = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
archive();
|
||||
};
|
||||
|
||||
return (
|
||||
<DeskLink
|
||||
onClick={archive}
|
||||
to={`?grid-note=${encodeURIComponent(first.link)}`}
|
||||
desk={desk}
|
||||
className={cn(
|
||||
'text-black rounded-xl',
|
||||
'unseen' in lid ? 'bg-blue-100' : 'bg-gray-100',
|
||||
'text-black rounded-xl group',
|
||||
'unseen' in lid ? 'bg-blue-100' : 'bg-gray-50',
|
||||
large ? 'note-grid-no-content' : 'note-grid-content'
|
||||
)}
|
||||
aria-labelledby={id}
|
||||
@ -64,6 +71,9 @@ export const BasicNotification = ({ notification, lid }: BasicNotificationProps)
|
||||
<h2 id={`${id}-title`} className="note-grid-head font-semibold text-gray-600">
|
||||
<NotificationText contents={first.title} />
|
||||
</h2>
|
||||
<div className="note-grid-actions hidden justify-center self-center group-hover:flex">
|
||||
<Button onClick={archiveNoFollow}>Archive</Button>
|
||||
</div>
|
||||
</header>
|
||||
{contents.length > 0 ? (
|
||||
<div className="note-grid-body space-y-2">
|
||||
|
@ -14,8 +14,8 @@ const cards: OnboardingCardProps[] = [
|
||||
href: '/leap/search/direct/apps/~zod/webterm'
|
||||
},
|
||||
{
|
||||
title: 'Groups',
|
||||
body: 'Install Groups, a suite of social software to communicate with other urbit users',
|
||||
title: 'Landscape',
|
||||
body: 'Install Landscape, a suite of social software to communicate with other urbit users',
|
||||
button: 'Install',
|
||||
color: '#D1DDD3',
|
||||
href: '/leap/search/direct/apps/~zod/landscape'
|
||||
@ -43,6 +43,16 @@ const cards: OnboardingCardProps[] = [
|
||||
// }
|
||||
];
|
||||
|
||||
if ('registerProtocolHandler' in window.navigator) {
|
||||
cards.push({
|
||||
title: 'Open Urbit-Native Links',
|
||||
body: 'Enable your Urbit to open links you find in the wild',
|
||||
button: 'Enable Link Handler',
|
||||
color: '#82A6CA',
|
||||
href: '/apps/grid/leap/system-preferences/interface'
|
||||
});
|
||||
}
|
||||
|
||||
interface OnboardingCardProps {
|
||||
title: string;
|
||||
button: string;
|
||||
|
@ -4,6 +4,8 @@ import { useProtocolHandling, setLocalState } from '../../state/local';
|
||||
|
||||
export function InterfacePrefs() {
|
||||
const protocolHandling = useProtocolHandling();
|
||||
const secure = window.location.protocol === 'https:';
|
||||
const linkHandlingAllowed = secure && !('registerProtocolHandler' in window.navigator);
|
||||
const toggleProtoHandling = async () => {
|
||||
if (!protocolHandling && window?.navigator?.registerProtocolHandler) {
|
||||
try {
|
||||
@ -33,8 +35,20 @@ export function InterfacePrefs() {
|
||||
return (
|
||||
<>
|
||||
<h2 className="h3 mb-7">Interface Settings</h2>
|
||||
<Setting on={protocolHandling} toggle={toggleProtoHandling} name="Handle Urbit links">
|
||||
<p>Automatically open urbit links with this urbit</p>
|
||||
<Setting
|
||||
on={protocolHandling}
|
||||
toggle={toggleProtoHandling}
|
||||
name="Handle Urbit links"
|
||||
disabled={!linkHandlingAllowed}
|
||||
>
|
||||
<p>
|
||||
Automatically open urbit links with this urbit
|
||||
{!linkHandlingAllowed && (
|
||||
<>
|
||||
, <strong className="text-orange-500">requires HTTPS</strong>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</Setting>
|
||||
</>
|
||||
);
|
||||
|
@ -9,7 +9,7 @@ const selDnd = (s: SettingsState) => s.display.doNotDisturb;
|
||||
async function toggleDnd() {
|
||||
const state = useSettingsState.getState();
|
||||
const curr = selDnd(state);
|
||||
if(curr) {
|
||||
if (curr) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
await state.putEntry('display', 'doNotDisturb', !curr);
|
||||
@ -24,12 +24,18 @@ async function toggleMentions() {
|
||||
export const NotificationPrefs = () => {
|
||||
const doNotDisturb = useSettingsState(selDnd);
|
||||
const mentions = useHarkStore(selMentions);
|
||||
const secure = window.location.protocol === 'https:';
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="h3 mb-7">Notifications</h2>
|
||||
<div className="space-y-3">
|
||||
<Setting on={doNotDisturb} toggle={toggleDnd} name="Do Not Disturb">
|
||||
<Setting
|
||||
on={doNotDisturb}
|
||||
toggle={toggleDnd}
|
||||
name="Do Not Disturb"
|
||||
disabled={doNotDisturb && !secure}
|
||||
>
|
||||
<p>
|
||||
Block visual desktop notifications whenever Urbit software produces an in-Landscape
|
||||
notification badge.
|
||||
@ -37,6 +43,11 @@ export const NotificationPrefs = () => {
|
||||
<p>
|
||||
Turning this "off" will prompt your browser to ask if you'd like to
|
||||
enable notifications
|
||||
{!secure && (
|
||||
<>
|
||||
, <strong className="text-orange-500">requires HTTPS</strong>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</Setting>
|
||||
<Setting on={mentions} toggle={toggleMentions} name="Mentions">
|
||||
|
@ -12,7 +12,7 @@ export const SystemUpdatePrefs = () => {
|
||||
_.pick(s, ['toggleOTAs', 'changeOTASource'])
|
||||
);
|
||||
const base = useVat('base');
|
||||
const otasEnabled = base && !(base.arak?.rail ?? true);
|
||||
const otasEnabled = base && !(base.arak?.rail?.paused ?? true);
|
||||
const otaSource = base && base.arak.rail!.ship!;
|
||||
|
||||
const toggleBase = useCallback((on: boolean) => toggleOTAs('base', on), [toggleOTAs]);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import produce from 'immer';
|
||||
import create from 'zustand';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { take } from 'lodash';
|
||||
import { MatchItem, useLeapStore } from '../Nav';
|
||||
import { providerMatch } from './Providers';
|
||||
import { AppList } from '../../components/AppList';
|
||||
@ -10,15 +10,16 @@ import { ProviderList } from '../../components/ProviderList';
|
||||
import { AppLink } from '../../components/AppLink';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import { ProviderLink } from '../../components/ProviderLink';
|
||||
import { DocketWithDesk, useCharges } from '../../state/docket';
|
||||
import useDocketState, { ChargesWithDesks, useCharges } from '../../state/docket';
|
||||
import { getAppHref } from '../../state/util';
|
||||
import useContactState from '../../state/contact';
|
||||
|
||||
export interface RecentsStore {
|
||||
recentApps: DocketWithDesk[];
|
||||
recentApps: string[];
|
||||
recentDevs: string[];
|
||||
addRecentApp: (app: DocketWithDesk) => void;
|
||||
addRecentApp: (desk: string) => void;
|
||||
addRecentDev: (ship: string) => void;
|
||||
removeRecentApp: (desk: string) => void;
|
||||
}
|
||||
|
||||
export const useRecentsStore = create<RecentsStore>(
|
||||
@ -26,15 +27,15 @@ export const useRecentsStore = create<RecentsStore>(
|
||||
(set) => ({
|
||||
recentApps: [],
|
||||
recentDevs: [],
|
||||
addRecentApp: (app) => {
|
||||
addRecentApp: (desk: string) => {
|
||||
set(
|
||||
produce((draft: RecentsStore) => {
|
||||
const hasApp = draft.recentApps.find((a) => a.desk === app.desk);
|
||||
const hasApp = draft.recentApps.find((testDesk) => testDesk === desk);
|
||||
if (!hasApp) {
|
||||
draft.recentApps.unshift(app);
|
||||
draft.recentApps.unshift(desk);
|
||||
}
|
||||
|
||||
draft.recentApps = take(draft.recentApps, 3);
|
||||
draft.recentApps = _.take(draft.recentApps, 3);
|
||||
})
|
||||
);
|
||||
},
|
||||
@ -46,7 +47,14 @@ export const useRecentsStore = create<RecentsStore>(
|
||||
draft.recentDevs.unshift(dev);
|
||||
}
|
||||
|
||||
draft.recentDevs = take(draft.recentDevs, 3);
|
||||
draft.recentDevs = _.take(draft.recentDevs, 3);
|
||||
})
|
||||
);
|
||||
},
|
||||
removeRecentApp: (desk: string) => {
|
||||
set(
|
||||
produce((draft: RecentsStore) => {
|
||||
_.remove(draft.recentApps, (test) => test === desk);
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -65,21 +73,28 @@ export function addRecentDev(dev: string) {
|
||||
return useRecentsStore.getState().addRecentDev(dev);
|
||||
}
|
||||
|
||||
export function addRecentApp(app: DocketWithDesk) {
|
||||
export function addRecentApp(app: string) {
|
||||
return useRecentsStore.getState().addRecentApp(app);
|
||||
}
|
||||
|
||||
function getApps(desks: string[], charges: ChargesWithDesks) {
|
||||
return desks.filter((desk) => desk in charges).map((desk) => charges[desk]);
|
||||
}
|
||||
|
||||
export const Home = () => {
|
||||
const selectedMatch = useLeapStore((state) => state.selectedMatch);
|
||||
const { recentApps, recentDevs } = useRecentsStore();
|
||||
const charges = useCharges();
|
||||
const groups = charges?.groups;
|
||||
const contacts = useContactState((s) => s.contacts);
|
||||
const zod = { shipName: '~zod', ...contacts['~zod'] };
|
||||
const defaultAlly = useDocketState((s) =>
|
||||
s.defaultAlly ? { shipName: s.defaultAlly, ...contacts[s.defaultAlly] } : null
|
||||
);
|
||||
const providerList = recentDevs.map((d) => ({ shipName: d, ...contacts[d] }));
|
||||
const apps = getApps(recentApps, charges);
|
||||
|
||||
useEffect(() => {
|
||||
const apps = recentApps.map((app) => ({
|
||||
const appMatches = apps.map((app) => ({
|
||||
url: getAppHref(app.href),
|
||||
openInNewTab: true,
|
||||
value: app.desk,
|
||||
@ -88,7 +103,7 @@ export const Home = () => {
|
||||
const devs = recentDevs.map(providerMatch);
|
||||
|
||||
useLeapStore.setState({
|
||||
matches: ([] as MatchItem[]).concat(apps, devs)
|
||||
matches: ([] as MatchItem[]).concat(appMatches, devs)
|
||||
});
|
||||
}, [recentApps, recentDevs]);
|
||||
|
||||
@ -97,26 +112,15 @@ export const Home = () => {
|
||||
<h2 id="recent-apps" className="mb-4 h4 text-gray-500">
|
||||
Recent Apps
|
||||
</h2>
|
||||
{recentApps.length === 0 && (
|
||||
{apps.length === 0 && (
|
||||
<div className="min-h-[150px] p-6 rounded-xl bg-gray-50">
|
||||
<p className="mb-4">Apps you use will be listed here, in the order you used them.</p>
|
||||
<p className="mb-6">You can click/tap/keyboard on a listed app to open it.</p>
|
||||
{groups && (
|
||||
<AppLink
|
||||
app={groups}
|
||||
size="small"
|
||||
onClick={() => addRecentApp({ ...groups, desk: 'groups' })}
|
||||
/>
|
||||
)}
|
||||
{groups && <AppLink app={groups} size="small" onClick={() => addRecentApp('groups')} />}
|
||||
</div>
|
||||
)}
|
||||
{recentApps.length > 0 && (
|
||||
<AppList
|
||||
apps={recentApps}
|
||||
labelledBy="recent-apps"
|
||||
matchAgainst={selectedMatch}
|
||||
size="small"
|
||||
/>
|
||||
{apps.length > 0 && (
|
||||
<AppList apps={apps} labelledBy="recent-apps" matchAgainst={selectedMatch} size="small" />
|
||||
)}
|
||||
<hr className="-mx-4 my-6 md:-mx-8 md:my-9 border-t-2 border-gray-50" />
|
||||
<h2 id="recent-devs" className="mb-4 h4 text-gray-500">
|
||||
@ -125,15 +129,15 @@ export const Home = () => {
|
||||
{recentDevs.length === 0 && (
|
||||
<div className="min-h-[150px] p-6 rounded-xl bg-gray-50">
|
||||
<p className="mb-4">Urbit app developers you search for will be listed here.</p>
|
||||
{zod && (
|
||||
{defaultAlly && (
|
||||
<>
|
||||
<p className="mb-6">
|
||||
Try out app discovery by visiting <ShipName name="~zod" /> below.
|
||||
Try out app discovery by visiting <ShipName name={defaultAlly.shipName} /> below.
|
||||
</p>
|
||||
<ProviderLink
|
||||
provider={zod}
|
||||
provider={defaultAlly}
|
||||
size="small"
|
||||
onClick={() => addRecentDev(zod.shipName)}
|
||||
onClick={() => addRecentDev(defaultAlly.shipName)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
scryAllies,
|
||||
scryAllyTreaties,
|
||||
scryCharges,
|
||||
scryDefaultAlly,
|
||||
Treaty,
|
||||
Docket,
|
||||
Treaties,
|
||||
@ -22,7 +23,7 @@ import {
|
||||
} from '@urbit/api';
|
||||
import api from './api';
|
||||
import { mockAllies, mockCharges, mockTreaties } from './mock-data';
|
||||
import { fakeRequest, useMockData } from './util';
|
||||
import { fakeRequest, normalizeUrbitColor, useMockData } from './util';
|
||||
|
||||
export interface ChargeWithDesk extends Charge {
|
||||
desk: string;
|
||||
@ -40,7 +41,9 @@ interface DocketState {
|
||||
charges: ChargesWithDesks;
|
||||
treaties: Treaties;
|
||||
allies: Allies;
|
||||
defaultAlly: string | null;
|
||||
fetchCharges: () => Promise<void>;
|
||||
fetchDefaultAlly: () => Promise<void>;
|
||||
requestTreaty: (ship: string, desk: string) => Promise<Treaty>;
|
||||
fetchAllies: () => Promise<Allies>;
|
||||
fetchAllyTreaties: (ally: string) => Promise<Treaties>;
|
||||
@ -50,6 +53,11 @@ interface DocketState {
|
||||
}
|
||||
|
||||
const useDocketState = create<DocketState>((set, get) => ({
|
||||
defaultAlly: useMockData ? '~zod' : null,
|
||||
fetchDefaultAlly: async () => {
|
||||
const defaultAlly = await api.scry<string>(scryDefaultAlly);
|
||||
set({ defaultAlly });
|
||||
},
|
||||
fetchCharges: async () => {
|
||||
const charg = useMockData
|
||||
? await fakeRequest(mockCharges)
|
||||
@ -100,8 +108,8 @@ const useDocketState = create<DocketState>((set, get) => ({
|
||||
if (!treaty) {
|
||||
throw new Error('Bad install');
|
||||
}
|
||||
set((state) => addCharge(state, desk, { ...treaty, chad: { install: null } }));
|
||||
if (useMockData) {
|
||||
set((state) => addCharge(state, desk, { ...treaty, chad: { install: null } }));
|
||||
await new Promise<void>((res) => setTimeout(() => res(), 10000));
|
||||
set((state) => addCharge(state, desk, { ...treaty, chad: { glob: null } }));
|
||||
}
|
||||
@ -147,14 +155,10 @@ const useDocketState = create<DocketState>((set, get) => ({
|
||||
}));
|
||||
|
||||
function normalizeDocket<T extends Docket>(docket: T, desk: string): T {
|
||||
const color = docket?.color?.startsWith('#')
|
||||
? docket.color
|
||||
: `#${docket.color.slice(2).replace('.', '')}`.toUpperCase();
|
||||
|
||||
return {
|
||||
...docket,
|
||||
desk,
|
||||
color
|
||||
color: normalizeUrbitColor(docket.color)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -84,9 +84,19 @@ export const useHarkStore = createState<HarkState>(
|
||||
await api.poke(archiveAll);
|
||||
},
|
||||
archiveNote: async (bin, lid) => {
|
||||
if (useMockData) {
|
||||
get().set((draft) => {
|
||||
const seen = 'seen' in lid ? 'seen' : 'unseen';
|
||||
const binId = harkBinToId(bin);
|
||||
delete draft[seen][binId];
|
||||
});
|
||||
return;
|
||||
}
|
||||
await api.poke(archive(bin, lid));
|
||||
},
|
||||
opened: async () => {
|
||||
reduceHark({ opened: null });
|
||||
|
||||
await api.poke(opened);
|
||||
},
|
||||
getMore: async () => {
|
||||
|
@ -51,31 +51,22 @@ const useKilnState = create<KilnState>((set, get) => ({
|
||||
await api.poke(kilnInstall(ship, '%kids', 'base'));
|
||||
},
|
||||
toggleOTAs: async (desk: string, on: boolean) => {
|
||||
if (useMockData) {
|
||||
await fakeRequest('');
|
||||
set(
|
||||
produce((draft: KilnState) => {
|
||||
const { arak } = draft.vats[desk];
|
||||
if (!arak.rail) {
|
||||
return;
|
||||
}
|
||||
set(
|
||||
produce((draft: KilnState) => {
|
||||
const { arak } = draft.vats[desk];
|
||||
if (!arak.rail) {
|
||||
return;
|
||||
}
|
||||
if (on) {
|
||||
arak.rail.paused = false;
|
||||
} else {
|
||||
arak.rail.paused = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (on) {
|
||||
arak.rail.paused = false;
|
||||
} else {
|
||||
arak.rail.paused = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await api.poke(on ? kilnResume(desk) : kilnPause(desk));
|
||||
|
||||
if (!on) {
|
||||
get().fetchVats(); // refresh vat state
|
||||
}
|
||||
await (useMockData ? fakeRequest('') : api.poke(on ? kilnResume(desk) : kilnPause(desk)));
|
||||
await get().fetchVats(); // refresh vat state
|
||||
},
|
||||
set: produce(set)
|
||||
}));
|
||||
|
@ -329,7 +329,6 @@ export const mockVat = (desk: string, blockers?: boolean): Vat => ({
|
||||
ud: 1
|
||||
},
|
||||
desk,
|
||||
paused: false,
|
||||
arak: {
|
||||
rein: {
|
||||
sub: [],
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DocketHref } from '@urbit/api/docket';
|
||||
import { hsla, parseToHsla } from 'color2k';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const useMockData = import.meta.env.MODE === 'mock';
|
||||
|
||||
@ -35,6 +36,16 @@ export function deSig(ship: string): string {
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
|
||||
export function normalizeUrbitColor(color: string): string {
|
||||
if (color.startsWith('#')) {
|
||||
return color;
|
||||
}
|
||||
|
||||
const colorString = color.slice(2).replace('.', '').toUpperCase();
|
||||
const lengthAdjustedColor = _.padEnd(colorString, 6, _.last(colorString));
|
||||
return `#${lengthAdjustedColor}`;
|
||||
}
|
||||
|
||||
export function getDarkColor(color: string): string {
|
||||
const hslaColor = parseToHsla(color);
|
||||
return hsla(hslaColor[0], hslaColor[1], 1 - hslaColor[2], 1);
|
||||
|
@ -1,13 +1,13 @@
|
||||
.note-grid-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5rem 1fr;
|
||||
grid-template-columns: 1.5rem 1fr 6rem;
|
||||
grid-template-rows: 1.5rem 1.5rem;
|
||||
grid-gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
grid-template-areas:
|
||||
'icon title'
|
||||
'arrow head'
|
||||
'. body';
|
||||
'icon title actions '
|
||||
'arrow head actions'
|
||||
'. body actions';
|
||||
}
|
||||
|
||||
.note-grid-no-content {
|
||||
@ -15,12 +15,12 @@
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
grid-template-rows: 1.75rem 1.75rem;
|
||||
grid-template-columns: 3.5rem 1fr;
|
||||
grid-template-columns: 3.5rem 1fr 6rem;
|
||||
grid-column-gap: 0.75rem;
|
||||
align-items: center;
|
||||
grid-template-areas:
|
||||
'icon title'
|
||||
'icon head';
|
||||
'icon title actions'
|
||||
'icon head actions';
|
||||
}
|
||||
.note-grid-title {
|
||||
grid-area: title;
|
||||
@ -42,4 +42,6 @@
|
||||
grid-area: head;
|
||||
}
|
||||
|
||||
|
||||
.note-grid-actions {
|
||||
grid-area: actions;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { Button } from '../components/Button';
|
||||
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
|
||||
import { useRecentsStore } from '../nav/search/Home';
|
||||
import useDocketState, { useCharges } from '../state/docket';
|
||||
|
||||
export const RemoveApp = () => {
|
||||
@ -14,6 +15,7 @@ export const RemoveApp = () => {
|
||||
// TODO: add optimistic updates
|
||||
const handleRemoveApp = useCallback(() => {
|
||||
uninstallDocket(desk);
|
||||
useRecentsStore.getState().removeRecentApp(desk);
|
||||
}, [desk]);
|
||||
|
||||
return (
|
||||
|
@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
|
||||
import { Redirect, useHistory, useParams } from 'react-router-dom';
|
||||
import { Button } from '../components/Button';
|
||||
import { Dialog, DialogClose, DialogContent } from '../components/Dialog';
|
||||
import { useRecentsStore } from '../nav/search/Home';
|
||||
import useDocketState, { useCharges } from '../state/docket';
|
||||
|
||||
export const SuspendApp = () => {
|
||||
@ -13,6 +14,7 @@ export const SuspendApp = () => {
|
||||
// TODO: add optimistic updates
|
||||
const handleSuspendApp = useCallback(() => {
|
||||
useDocketState.getState().toggleDocket(desk);
|
||||
useRecentsStore.getState().removeRecentApp(desk);
|
||||
}, [desk]);
|
||||
|
||||
if ('suspend' in charge.chad) {
|
||||
|
@ -33,8 +33,8 @@ export const Tile: FunctionComponent<TileProps> = ({ charge, desk }) => {
|
||||
!active && 'cursor-default'
|
||||
)}
|
||||
style={{ backgroundColor }}
|
||||
onClick={() => addRecentApp(charge)}
|
||||
onAuxClick={() => addRecentApp(charge)}
|
||||
onClick={() => addRecentApp(desk)}
|
||||
onAuxClick={() => addRecentApp(desk)}
|
||||
>
|
||||
<div>
|
||||
{loading ? (
|
||||
|
@ -13,20 +13,27 @@ function getMenuColor(color: string, darkBg: boolean): string {
|
||||
return bgAdjustedColor(satAdjustedColor, darkBg);
|
||||
}
|
||||
|
||||
// makes tiles look broken because they blend into BG
|
||||
function disallowWhiteTiles(color: string): string {
|
||||
const hslaColor = parseToHsla(color);
|
||||
return hslaColor[2] >= 0.95 ? darken(color, hslaColor[2] - 0.95) : color;
|
||||
}
|
||||
|
||||
export const useTileColor = (color: string) => {
|
||||
const theme = useCurrentTheme();
|
||||
const darkTheme = theme === 'dark';
|
||||
const tileColor = darkTheme ? getDarkColor(color) : color;
|
||||
const allowedColor = disallowWhiteTiles(color);
|
||||
const tileColor = darkTheme ? getDarkColor(allowedColor) : allowedColor;
|
||||
const darkBg = !readableColorIsBlack(tileColor);
|
||||
const lightText = darkBg !== darkTheme; // if not same, light text
|
||||
const suspendColor = darkTheme ? 'rgb(26,26,26)' : 'rgb(220,220,220)';
|
||||
|
||||
return {
|
||||
theme,
|
||||
tileColor: theme === 'dark' ? getDarkColor(color) : color,
|
||||
tileColor,
|
||||
menuColor: getMenuColor(tileColor, darkBg),
|
||||
suspendColor,
|
||||
suspendMenuColor: bgAdjustedColor(suspendColor, darkBg),
|
||||
suspendMenuColor: bgAdjustedColor(suspendColor, darkTheme),
|
||||
lightText
|
||||
};
|
||||
};
|
||||
|
@ -200,7 +200,8 @@ module.exports = {
|
||||
},
|
||||
variants: {
|
||||
extend: {
|
||||
opacity: ['hover-none']
|
||||
opacity: ['hover-none'],
|
||||
display: ['group-hover']
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
|
@ -26,6 +26,9 @@ if(urbitrc.URL) {
|
||||
devServer = {
|
||||
...devServer,
|
||||
index: 'index.html',
|
||||
// headers: {
|
||||
// 'Service-Worker-Allowed': '/'
|
||||
// },
|
||||
proxy: [
|
||||
{
|
||||
context: (path) => {
|
||||
|
@ -13,6 +13,7 @@ import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import { pluralize } from './util';
|
||||
import useMetadataState from '../state/metadata';
|
||||
import { emptyHarkStats } from '../state/hark';
|
||||
|
||||
export function getLastSeen(
|
||||
unreads: Unreads,
|
||||
@ -28,13 +29,16 @@ export function getLastSeen(
|
||||
);
|
||||
}
|
||||
|
||||
export function getHarkStats(unreads: Unreads, path: string) {
|
||||
return unreads?.[path] ?? emptyHarkStats();
|
||||
}
|
||||
|
||||
export function getUnreadCount(
|
||||
unreads: Unreads,
|
||||
path: string,
|
||||
index: string
|
||||
path: string
|
||||
): number {
|
||||
const graphUnreads = unreads.graph?.[path]?.[index]?.unreads ?? 0;
|
||||
return typeof graphUnreads === 'number' ? graphUnreads : graphUnreads.size;
|
||||
const { count, each } = getHarkStats(unreads, path);
|
||||
return count + each.length;
|
||||
}
|
||||
|
||||
export function getNotificationCount(unreads: Unreads, path: string): number {
|
||||
|
@ -88,7 +88,6 @@ const otherIndex = function(config) {
|
||||
const other = [];
|
||||
const idx = {
|
||||
mychannel: result('My Channels', '/~landscape/home', 'home', null),
|
||||
updates: result('Notifications', '/~notifications', 'inbox', null),
|
||||
profile: result('Profile', `/~profile/~${window.ship}`, 'profile', null),
|
||||
messages: result('Messages', '/~landscape/messages', 'messages', null),
|
||||
logout: result('Log Out', '/~/logout', 'logout', null)
|
||||
|
@ -121,7 +121,7 @@ const useHarkState = createState<HarkState>(
|
||||
]
|
||||
);
|
||||
|
||||
const emptyStats = () => ({
|
||||
export const emptyHarkStats = () => ({
|
||||
last: 0,
|
||||
count: 0,
|
||||
each: []
|
||||
@ -132,7 +132,7 @@ export function useHarkDm(ship: string) {
|
||||
useCallback(
|
||||
(s) => {
|
||||
const key = `/graph/~${window.ship}/dm-inbox/${patp2dec(ship)}`;
|
||||
return s.unreads[key] || emptyStats();
|
||||
return s.unreads[key] || emptyHarkStats();
|
||||
},
|
||||
[ship]
|
||||
)
|
||||
@ -141,15 +141,19 @@ export function useHarkDm(ship: string) {
|
||||
|
||||
export function useHarkStat(path: string) {
|
||||
return useHarkState(
|
||||
useCallback(s => s.unreads[path] || emptyStats(), [path])
|
||||
useCallback(s => s.unreads[path] || emptyHarkStats(), [path])
|
||||
);
|
||||
}
|
||||
|
||||
export function selHarkGraph(graph: string) {
|
||||
const [,, ship, name] = graph.split('/');
|
||||
const path = `/graph/${ship}/${name}`;
|
||||
return (s: HarkState) => (s.unreads[path] || emptyHarkStats());
|
||||
}
|
||||
|
||||
export function useHarkGraph(graph: string) {
|
||||
const [, ship, name] = useMemo(() => graph.split('/'), [graph]);
|
||||
return useHarkState(
|
||||
useCallback(s => s.unreads[`/graph/${ship}/${name}`], [ship, name])
|
||||
);
|
||||
const sel = useMemo(() => selHarkGraph(graph), [graph]);
|
||||
return useHarkState(sel);
|
||||
}
|
||||
|
||||
export function useHarkGraphIndex(graph: string, index: string) {
|
||||
|
@ -4,7 +4,6 @@ import {
|
||||
createSubscription,
|
||||
reduceStateN
|
||||
} from './base';
|
||||
import airlock from '~/logic/api';
|
||||
import { reduce } from '../reducers/launch-update';
|
||||
import _ from 'lodash';
|
||||
|
||||
@ -16,10 +15,6 @@ export interface LaunchState {
|
||||
};
|
||||
weather: WeatherState | null | Record<string, never> | boolean;
|
||||
userLocation: string | null;
|
||||
baseHash: string | null;
|
||||
runtimeLag: boolean;
|
||||
getRuntimeLag: () => Promise<void>;
|
||||
getBaseHash: () => Promise<void>;
|
||||
}
|
||||
|
||||
// @ts-ignore investigate zustand types
|
||||
@ -30,23 +25,7 @@ const useLaunchState = createState<LaunchState>(
|
||||
tileOrdering: [],
|
||||
tiles: {},
|
||||
weather: null,
|
||||
userLocation: null,
|
||||
baseHash: null,
|
||||
runtimeLag: false,
|
||||
getBaseHash: async () => {
|
||||
const baseHash = await airlock.scry({
|
||||
app: 'file-server',
|
||||
path: '/clay/base/hash'
|
||||
});
|
||||
set({ baseHash });
|
||||
},
|
||||
getRuntimeLag: async () => {
|
||||
const runtimeLag = await airlock.scry({
|
||||
app: 'launch',
|
||||
path: '/runtime-lag'
|
||||
});
|
||||
set({ runtimeLag });
|
||||
}
|
||||
userLocation: null
|
||||
}),
|
||||
['weather'],
|
||||
[
|
||||
|
@ -1,7 +1,7 @@
|
||||
if ('serviceWorker' in navigator && process.env.NODE_ENV !== 'development') {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/~landscape/js/bundle/serviceworker.js', {
|
||||
scope: '/'
|
||||
navigator.serviceWorker.register('/apps/landscape/serviceworker.js', {
|
||||
scope: '/apps/landscape'
|
||||
}).then((reg) => {
|
||||
});
|
||||
});
|
||||
|
@ -28,7 +28,6 @@ import './css/indigo-static.css';
|
||||
import { Content } from './landscape/components/Content';
|
||||
import './landscape/css/custom.css';
|
||||
import { bootstrapApi } from '~/logic/api/bootstrap';
|
||||
import useLaunchState from '../logic/state/launch';
|
||||
|
||||
const Root = withState(styled.div`
|
||||
font-family: ${p => p.theme.fonts.sans};
|
||||
@ -104,8 +103,6 @@ class App extends React.Component {
|
||||
this.updateMedium(this.mediumWatcher);
|
||||
this.updateLarge(this.largeWatcher);
|
||||
}, 500);
|
||||
this.props.getBaseHash();
|
||||
this.props.getRuntimeLag(); // TODO consider polling periodically
|
||||
this.props.getAll();
|
||||
gcpManager.start();
|
||||
Mousetrap.bindGlobal(['command+/', 'ctrl+/'], (e) => {
|
||||
@ -211,14 +208,12 @@ const selContacts = s => s.contacts[`~${window.ship}`];
|
||||
const selLocal = s => [s.set, s.omniboxShown, s.toggleOmnibox, s.dark];
|
||||
const selSettings = s => [s.display, s.getAll];
|
||||
const selGraph = s => s.getShallowChildren;
|
||||
const selLaunch = s => [s.getRuntimeLag, s.getBaseHash];
|
||||
|
||||
const WithApp = React.forwardRef((props, ref) => {
|
||||
const ourContact = useContactState(selContacts);
|
||||
const [display, getAll] = useSettingsState(selSettings, shallow);
|
||||
const [setLocal, omniboxShown, toggleOmnibox, dark] = useLocalState(selLocal);
|
||||
const getShallowChildren = useGraphState(selGraph);
|
||||
const [getRuntimeLag, getBaseHash] = useLaunchState(selLaunch, shallow);
|
||||
|
||||
return (
|
||||
<WarmApp
|
||||
@ -229,8 +224,6 @@ const WithApp = React.forwardRef((props, ref) => {
|
||||
set={setLocal}
|
||||
dark={dark}
|
||||
getShallowChildren={getShallowChildren}
|
||||
getRuntimeLag={getRuntimeLag}
|
||||
getBaseHash={getBaseHash}
|
||||
toggleOmnibox={toggleOmnibox}
|
||||
omniboxShown={omniboxShown}
|
||||
/>
|
||||
|
@ -3,23 +3,20 @@ import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react';
|
||||
import f from 'lodash/fp';
|
||||
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Route, useHistory } from 'react-router-dom';
|
||||
import { Route } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
hasTutorialGroup,
|
||||
|
||||
TUTORIAL_BOOK,
|
||||
TUTORIAL_CHAT, TUTORIAL_GROUP,
|
||||
TUTORIAL_HOST,
|
||||
|
||||
TUTORIAL_LINKS
|
||||
hasTutorialGroup,
|
||||
TUTORIAL_BOOK,
|
||||
TUTORIAL_CHAT,
|
||||
TUTORIAL_GROUP,
|
||||
TUTORIAL_HOST,
|
||||
TUTORIAL_LINKS
|
||||
} from '~/logic/lib/tutorialModal';
|
||||
import { useModal } from '~/logic/lib/useModal';
|
||||
import { useQuery } from '~/logic/lib/useQuery';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import { writeText } from '~/logic/lib/util';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
@ -53,8 +50,6 @@ interface LaunchAppProps {
|
||||
|
||||
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
const { connection } = props;
|
||||
const { baseHash, runtimeLag } = useLaunchState(state => state);
|
||||
const [hashText, setHashText] = useState(baseHash);
|
||||
const [exitingTut, setExitingTut] = useState(false);
|
||||
const seen = useSettingsState(s => s?.tutorial?.seen) ?? true;
|
||||
const associations = useMetadataState(s => s.associations);
|
||||
@ -67,35 +62,6 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
!hideGroups ? { hideGroups } = calmState : null;
|
||||
|
||||
const waiter = useWaitForProps({ ...props, associations });
|
||||
const hashBox = (
|
||||
<Box
|
||||
position="sticky"
|
||||
left={3}
|
||||
bottom={3}
|
||||
mt={3}
|
||||
backgroundColor="white"
|
||||
borderRadius={2}
|
||||
width="fit-content"
|
||||
fontSize={0}
|
||||
cursor="pointer"
|
||||
onClick={() => {
|
||||
writeText(baseHash);
|
||||
setHashText('copied');
|
||||
setTimeout(() => {
|
||||
setHashText(baseHash);
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
height="100%"
|
||||
backgroundColor={runtimeLag ? 'yellow' : 'washedGray'}
|
||||
p={2}
|
||||
width="fit-content"
|
||||
>
|
||||
<Text mono bold>{hashText || baseHash}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const { query } = useQuery();
|
||||
|
||||
@ -247,7 +213,6 @@ export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
|
||||
(<Groups />)
|
||||
}
|
||||
</Box>
|
||||
{hashBox}
|
||||
</ScrollbarLessBox>
|
||||
</>
|
||||
);
|
||||
|
@ -3,18 +3,16 @@ import { Association, Associations, Unreads } from '@urbit/api';
|
||||
import f from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import React, { useRef } from 'react';
|
||||
import { getNotificationCount, getUnreadCount } from '~/logic/lib/hark';
|
||||
import { getNotificationCount } from '~/logic/lib/hark';
|
||||
import { TUTORIAL_GROUP, TUTORIAL_GROUP_RESOURCE, TUTORIAL_HOST } from '~/logic/lib/tutorialModal';
|
||||
import { alphabeticalOrder } from '~/logic/lib/util';
|
||||
import useGroupState from '~/logic/state/group';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useHarkState, { selHarkGraph } from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import useSettingsState, { selectCalmState, SettingsState } from '~/logic/state/settings';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import Tile from '../components/tiles/tile';
|
||||
|
||||
interface GroupsProps {}
|
||||
|
||||
const sortGroupsAlph = (a: Association, b: Association) =>
|
||||
a.group === TUTORIAL_GROUP_RESOURCE
|
||||
? -1
|
||||
@ -22,13 +20,22 @@ const sortGroupsAlph = (a: Association, b: Association) =>
|
||||
? 1
|
||||
: alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||
|
||||
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
f.flow(
|
||||
f.pickBy((a: Association) => a.group === path),
|
||||
f.map('resource'),
|
||||
f.map(rid => getUnreadCount(unreads, rid, '/')),
|
||||
f.reduce(f.add, 0)
|
||||
)(associations.graph);
|
||||
const getGraphUnreads = (associations: Associations) => {
|
||||
const state = useHarkState.getState();
|
||||
const selUnread = (graph: string) => {
|
||||
const { count, each } = selHarkGraph(graph)(state);
|
||||
const result = count + each.length;
|
||||
console.log(graph, result);
|
||||
return result;
|
||||
};
|
||||
return (path: string) =>
|
||||
f.flow(
|
||||
f.pickBy((a: Association) => a.group === path),
|
||||
f.map('resource'),
|
||||
f.map(selUnread),
|
||||
f.reduce(f.add, 0)
|
||||
)(associations.graph);
|
||||
};
|
||||
|
||||
const getGraphNotifications = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
f.flow(
|
||||
@ -38,8 +45,7 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
|
||||
f.reduce(f.add, 0)
|
||||
)(associations.graph);
|
||||
|
||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const { inbox, ...boxProps } = props;
|
||||
export default function Groups(props: Parameters<typeof Box>[0]) {
|
||||
const unreads = useHarkState(state => state.unreads);
|
||||
const groupState = useGroupState(state => state.groups);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
@ -47,7 +53,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const groups = Object.values(associations?.groups || {})
|
||||
.filter(e => e?.group in groupState)
|
||||
.sort(sortGroupsAlph);
|
||||
const graphUnreads = getGraphUnreads(associations || {} as Associations, unreads);
|
||||
const graphUnreads = getGraphUnreads(associations || {} as Associations);
|
||||
const graphNotifications = getGraphNotifications(associations || {} as Associations, unreads);
|
||||
|
||||
return (
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import { WeatherState } from '~/types';
|
||||
import BasicTile from './tiles/basic';
|
||||
import ClockTile from './tiles/clock';
|
||||
import CustomTile from './tiles/custom';
|
||||
import WeatherTile from './tiles/weather';
|
||||
|
||||
const Tiles = (): ReactElement => {
|
||||
@ -16,16 +14,7 @@ const Tiles = (): ReactElement => {
|
||||
return tile.isShown;
|
||||
}).map((key) => {
|
||||
const tile = tileState[key];
|
||||
if ('basic' in tile.type) {
|
||||
const basic = tile.type.basic;
|
||||
return (
|
||||
<BasicTile
|
||||
key={key}
|
||||
title={basic.title}
|
||||
linkedUrl={basic.linkedUrl}
|
||||
/>
|
||||
);
|
||||
} else if ('custom' in tile.type) {
|
||||
if ('custom' in tile.type) {
|
||||
if (key === 'weather') {
|
||||
return (
|
||||
<WeatherTile key={key} />
|
||||
@ -35,14 +24,6 @@ const Tiles = (): ReactElement => {
|
||||
return (
|
||||
<ClockTile key={key} location={location} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<CustomTile
|
||||
key={key}
|
||||
tileImage={tile.type.custom.image}
|
||||
linkedUrl={tile.type.custom.linkedUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -9,7 +9,7 @@ import useLocalState from '~/logic/state/local';
|
||||
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
|
||||
import bigInt from 'big-integer';
|
||||
import airlock from '~/logic/api';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useHarkState, { selHarkGraph } from '~/logic/state/hark';
|
||||
import { BlockScroller } from '~/views/components/BlockScroller';
|
||||
|
||||
export interface LinkBlocksProps {
|
||||
@ -46,11 +46,13 @@ export function LinkBlocks(props: LinkBlocksProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unreads =
|
||||
useHarkState.getState().unreads.graph?.[association.resource]?.['/']
|
||||
?.unreads || new Set<string>();
|
||||
Array.from(unreads as Set<string>).forEach((u) => {
|
||||
airlock.poke(markEachAsRead(association.resource, '/', u));
|
||||
const unreads = selHarkGraph(association.resource)(useHarkState.getState());
|
||||
const [,,ship,name] = association.resource.split('/');
|
||||
unreads.each.forEach((u) => {
|
||||
airlock.poke(markEachAsRead({
|
||||
desk: (window as any).desk,
|
||||
path: `/graph/${ship}/${name}`
|
||||
}, u));
|
||||
});
|
||||
}, [association.resource]);
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Box, Center, Col, LoadingSpinner, Text, Icon } from '@tlon/indigo-react';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import {
|
||||
IndexedNotification,
|
||||
|
||||
JoinRequests, Notifications,
|
||||
|
||||
JoinRequests,
|
||||
Notifications,
|
||||
seen,
|
||||
|
||||
Timebox,
|
||||
unixToDa
|
||||
} from '@urbit/api';
|
||||
@ -13,10 +11,8 @@ import { BigInteger } from 'big-integer';
|
||||
import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { getNotificationKey } from '~/logic/lib/hark';
|
||||
import { useLazyScroll } from '~/logic/lib/useLazyScroll';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import { daToUnix } from '~/logic/lib/util';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import { Invites } from './invites';
|
||||
@ -59,8 +55,6 @@ export default function Inbox(props: {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const runtimeLag = useLaunchState(state => state.runtimeLag);
|
||||
|
||||
const ready = useHarkState(
|
||||
s => Object.keys(s.unreads.graph).length > 0
|
||||
);
|
||||
@ -99,14 +93,6 @@ export default function Inbox(props: {
|
||||
|
||||
return (
|
||||
<Col p={1} position="relative" height="100%" overflowY="auto" overflowX="hidden">
|
||||
{runtimeLag && (
|
||||
<Box bg="yellow" borderRadius={2} p={2} m={2}>
|
||||
<Icon verticalAlign="middle" mr={2} icon="Tutorial" />
|
||||
<Text verticalAlign="middle">
|
||||
Update your binary to continue receiving updates.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Invites pendingJoin={props.pendingJoin} />
|
||||
</Col>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BaseAnchor, Box, Button, Center, Col, H3, Icon, Image, Row, Text } from '@tlon/indigo-react';
|
||||
import { Association, GraphNode, resourceFromPath, GraphConfig } from '@urbit/api';
|
||||
import { BaseAnchor, Box, BoxProps, Button, Center, Col, H3, Icon, Image, Row, Text } from '@tlon/indigo-react';
|
||||
import { Association, GraphNode, resourceFromPath, GraphConfig, Treaty } from '@urbit/api';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
@ -196,8 +196,38 @@ const ClampedText = styled(Text)`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
type AppTileProps = Treaty & BoxProps;
|
||||
|
||||
export function AppTile({ color, image, ...props }: AppTileProps) {
|
||||
return (
|
||||
<Box
|
||||
position="relative"
|
||||
flex="none"
|
||||
height={['48px', '132px']}
|
||||
width={['48px', '132px']}
|
||||
marginRight={3}
|
||||
borderRadius={3}
|
||||
bg={color || 'gray'}
|
||||
{...props}
|
||||
>
|
||||
{image && (
|
||||
<Image
|
||||
src={image}
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function AppPermalink({ link, ship, desk }: Omit<IAppPermalink, 'type'>) {
|
||||
const treaty = useTreaty(ship, desk);
|
||||
const hasProtocolHandling = Boolean(window?.navigator?.registerProtocolHandler);
|
||||
const href = hasProtocolHandling ? link : `/apps/grid/perma?ext=${link}`;
|
||||
|
||||
useEffect(() => {
|
||||
if (!treaty) {
|
||||
@ -208,36 +238,23 @@ function AppPermalink({ link, ship, desk }: Omit<IAppPermalink, 'type'>) {
|
||||
return (
|
||||
<Row
|
||||
display="inline-flex"
|
||||
width="500px"
|
||||
width="100%"
|
||||
maxWidth="500px"
|
||||
padding={3}
|
||||
bg="washedGray"
|
||||
borderRadius={3}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
flex="none"
|
||||
height="132px"
|
||||
width="132px"
|
||||
marginRight={3}
|
||||
borderRadius={3}
|
||||
bg={treaty?.color || 'gray'}
|
||||
>
|
||||
{treaty?.image && (
|
||||
<Image
|
||||
src={treaty.image}
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{treaty && <AppTile display={['none', 'block']} {...treaty} />}
|
||||
<Col>
|
||||
<H3 color="black">{treaty?.title}</H3>
|
||||
{treaty?.ship && <Author ship={treaty?.ship} showImage dontShowTime={true} marginBottom={2} />}
|
||||
<Row flexDirection={['row', 'column']} alignItems={['center', 'start']} marginBottom={2}>
|
||||
{treaty && <AppTile display={['block', 'none']} {...treaty} />}
|
||||
<Col>
|
||||
<H3 color="black">{treaty?.title}</H3>
|
||||
{treaty?.ship && <Author ship={treaty?.ship} showImage dontShowTime={true} />}
|
||||
</Col>
|
||||
</Row>
|
||||
<ClampedText marginBottom={2} color="gray">{treaty?.info}</ClampedText>
|
||||
<Button as="a" href={link} primary alignSelf="start" display="inline-flex" marginTop="auto">Open App</Button>
|
||||
<Button as="a" href={href} primary alignSelf="start" display="inline-flex" marginTop="auto">Open App</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@ -19,7 +19,6 @@ import GroupSearch from '~/views/components/GroupSearch';
|
||||
import { ImageInput } from '~/views/components/ImageInput';
|
||||
import {
|
||||
ProfileControls, ProfileHeader,
|
||||
|
||||
ProfileImages, ProfileStatus
|
||||
} from './Profile';
|
||||
import airlock from '~/logic/api';
|
||||
@ -96,7 +95,7 @@ export function EditProfile(props: any): ReactElement {
|
||||
const newValue = key !== 'color' ? values[key] : uxToHex(values[key]);
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === 'isPublic') {
|
||||
airlock.poke(setPublic(true));
|
||||
airlock.poke(setPublic(newValue));
|
||||
return;
|
||||
} else if (key === 'groups') {
|
||||
const toRemove: string[] = _.difference(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, {
|
||||
MouseEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState
|
||||
} from 'react';
|
||||
@ -14,7 +15,8 @@ import {
|
||||
allSystemStyle,
|
||||
Icon,
|
||||
Row,
|
||||
Col
|
||||
Col,
|
||||
Text
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { TruncatedText } from '~/views/components/TruncatedText';
|
||||
@ -23,11 +25,13 @@ import { IconRef, PropFunc } from '~/types';
|
||||
import { system } from 'styled-system';
|
||||
import { Association, GraphConfig, ReferenceContent } from '@urbit/api';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import { AppPermalink, referenceToPermalink } from '~/logic/lib/permalinks';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
import { RemoteContentWrapper } from './wrapper';
|
||||
import { useEmbed } from '~/logic/state/embed';
|
||||
import { IS_SAFARI } from '~/logic/lib/platform';
|
||||
import useDocketState, { useTreaty } from '~/logic/state/docket';
|
||||
import { AppTile } from '~/views/apps/permalinks/embed';
|
||||
|
||||
interface RemoteContentEmbedProps {
|
||||
url: string;
|
||||
@ -217,11 +221,41 @@ export function RemoteContentPermalinkEmbed(props: {
|
||||
return <RemoteContentPermalinkEmbedGraph {...permalink} />;
|
||||
} else if (permalink.type === 'group') {
|
||||
return <RemoteContentPermalinkEmbedGroup {...permalink} />;
|
||||
} else if (permalink.type === 'app') {
|
||||
return <RemoteContentPermalinkEmbedApp {...permalink} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function RemoteContentPermalinkEmbedApp({ link, ship, desk }: Omit<AppPermalink, 'type'>) {
|
||||
const treaty = useTreaty(ship, desk);
|
||||
|
||||
useEffect(() => {
|
||||
if (!treaty) {
|
||||
useDocketState.getState().requestTreaty(ship, desk);
|
||||
}
|
||||
}, [treaty, ship, desk]);
|
||||
|
||||
return (
|
||||
<Col
|
||||
width="100%"
|
||||
height="100%"
|
||||
padding={3}
|
||||
borderRadius={3}
|
||||
justifyContent="space-around"
|
||||
alignItems="center"
|
||||
>
|
||||
{treaty && (
|
||||
<>
|
||||
<AppTile color="washedGray" marginRight={0} {...treaty} />
|
||||
<Row><Text fontSize="1" color="gray">App: {treaty.title}</Text></Row>
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
function RemoteContentPermalinkEmbedGroup(props: {
|
||||
group: string;
|
||||
link: string;
|
||||
|
@ -14,6 +14,7 @@ import { PostRepliesRoutes } from './Post/PostReplies';
|
||||
import PostTimeline from './Post/PostTimeline';
|
||||
import airlock from '~/logic/api';
|
||||
import { PostThreadRoutes } from './Post/PostThread';
|
||||
import { toHarkPlace } from '~/logic/lib/util';
|
||||
|
||||
function GroupFeed(props) {
|
||||
const {
|
||||
@ -52,7 +53,8 @@ function GroupFeed(props) {
|
||||
return;
|
||||
}
|
||||
getNewest(graphResource.ship, graphResource.name, 100);
|
||||
airlock.poke(markCountAsRead(graphPath));
|
||||
|
||||
airlock.poke(markCountAsRead(toHarkPlace(graphPath)));
|
||||
}, [graphPath]);
|
||||
|
||||
if (!graphPath) {
|
||||
|
@ -14,6 +14,7 @@ import PostFlatTimeline from './Post/PostFlatTimeline';
|
||||
import airlock from '~/logic/api';
|
||||
import { markCountAsRead } from '@urbit/api';
|
||||
import { PostRepliesRoutes } from './Post/PostReplies';
|
||||
import { toHarkPlace } from '~/logic/lib/util';
|
||||
|
||||
function GroupFlatFeed(props) {
|
||||
const {
|
||||
@ -43,7 +44,7 @@ function GroupFlatFeed(props) {
|
||||
return;
|
||||
}
|
||||
getDeepOlderThan(graphRid.ship, graphRid.name, 100);
|
||||
airlock.poke(markCountAsRead(graphPath));
|
||||
airlock.poke(markCountAsRead(toHarkPlace(graphPath)));
|
||||
}, [graphPath]);
|
||||
|
||||
if (!graphPath) {
|
||||
|
@ -104,8 +104,7 @@ export function JoinGroup(props: JoinGroupProps): ReactElement {
|
||||
history.push(`/~landscape${group}`);
|
||||
}
|
||||
} catch (e) {
|
||||
// drop them into inbox to show join request still pending
|
||||
history.push('/~notifications');
|
||||
console.error(e);
|
||||
}
|
||||
}, [waiter, history, associations, groups]);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
import { Associations, Graph } from '@urbit/api';
|
||||
import { patp, patp2dec } from 'urbit-ob';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { SidebarAssociationItem, SidebarDmItem } from './SidebarItem';
|
||||
import useGraphState, { useInbox } from '~/logic/state/graph';
|
||||
@ -86,7 +87,7 @@ function getItems(associations: Associations, workspace: Workspace, inbox: Graph
|
||||
? []
|
||||
: Array.from(pending).map(s => `~${s}`);
|
||||
|
||||
return [...filtered, ...direct, ...pend];
|
||||
return [...filtered, ..._.union(direct, pend)];
|
||||
}
|
||||
|
||||
export function SidebarList(props: {
|
||||
|
@ -193,7 +193,8 @@
|
||||
++ jn-start
|
||||
|= [rid=resource =^ship]
|
||||
^+ jn-core
|
||||
?< (~(has by joining) rid)
|
||||
?> ?= $@(~ [~ %done])
|
||||
(bind (~(get by joining) rid) |=(request:view progress))
|
||||
=. joining
|
||||
(~(put by joining) rid [%.n now.bowl ship %start])
|
||||
=. jn-core
|
||||
|
@ -1,7 +1,7 @@
|
||||
:~ title+'Landscape'
|
||||
info+'A suite of applications to communicate on Urbit'
|
||||
color+0xee.5432
|
||||
glob-http+'https://bootstrap.urbit.org/glob-0v3.55j15.2edo4.e891p.cgslf.iv6oo.glob'
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v1.vu4e7.efplp.stcdg.7hhds.0tp5v.glob' 0v1.vu4e7.efplp.stcdg.7hhds.0tp5v]
|
||||
base+'landscape'
|
||||
version+[1 3 5]
|
||||
website+'https://tlon.io'
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
import { Path, Patp, Poke, resourceAsPath, Scry } from "../lib";
|
||||
import { Patp, Poke, Scry } from '../lib';
|
||||
import {
|
||||
Contact,
|
||||
ContactUpdateAdd,
|
||||
@ -10,36 +10,36 @@ import {
|
||||
ContactUpdate,
|
||||
ContactUpdateAllowShips,
|
||||
ContactUpdateAllowGroup,
|
||||
ContactUpdateSetPublic,
|
||||
} from "./types";
|
||||
ContactUpdateSetPublic
|
||||
} from './types';
|
||||
|
||||
export const CONTACT_UPDATE_VERSION: number = 0;
|
||||
export const CONTACT_UPDATE_VERSION = 0;
|
||||
|
||||
const storeAction = <T extends ContactUpdate>(data: T, version: number = CONTACT_UPDATE_VERSION): Poke<T> => ({
|
||||
app: "contact-store",
|
||||
app: 'contact-store',
|
||||
mark: `contact-update-${version}`,
|
||||
json: data,
|
||||
json: data
|
||||
});
|
||||
|
||||
export { storeAction as contactStoreAction };
|
||||
|
||||
export const addContact = (ship: Patp, contact: Contact): Poke<ContactUpdateAdd> => {
|
||||
contact["last-updated"] = Date.now();
|
||||
contact['last-updated'] = Date.now();
|
||||
|
||||
return storeAction({
|
||||
add: { ship, contact },
|
||||
add: { ship, contact }
|
||||
});
|
||||
};
|
||||
|
||||
export const removeContact = (ship: Patp): Poke<ContactUpdateRemove> =>
|
||||
storeAction({
|
||||
remove: { ship },
|
||||
remove: { ship }
|
||||
});
|
||||
|
||||
export const share = (recipient: Patp, version: number = CONTACT_UPDATE_VERSION): Poke<ContactShare> => ({
|
||||
app: "contact-push-hook",
|
||||
mark: "contact-share",
|
||||
json: { share: recipient },
|
||||
app: 'contact-push-hook',
|
||||
mark: 'contact-share',
|
||||
json: { share: recipient }
|
||||
});
|
||||
|
||||
export const editContact = (
|
||||
@ -49,9 +49,9 @@ export const editContact = (
|
||||
storeAction({
|
||||
edit: {
|
||||
ship,
|
||||
"edit-field": editField,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
'edit-field': editField,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
|
||||
export const allowShips = (
|
||||
@ -67,7 +67,7 @@ export const allowGroup = (
|
||||
name: string
|
||||
): Poke<ContactUpdateAllowGroup> => storeAction({
|
||||
allow: {
|
||||
group: resourceAsPath({ ship, name })
|
||||
group: { ship, name }
|
||||
}
|
||||
});
|
||||
|
||||
@ -77,7 +77,7 @@ export const setPublic = (
|
||||
return storeAction({
|
||||
'set-public': setPublic
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const retrieve = (
|
||||
ship: string
|
||||
@ -93,7 +93,7 @@ export const retrieve = (
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchIsAllowed = (
|
||||
entity: string,
|
||||
@ -105,5 +105,5 @@ export const fetchIsAllowed = (
|
||||
return {
|
||||
app: 'contact-store',
|
||||
path: `/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Path, Patp } from "../lib";
|
||||
import { Resource } from "../groups";
|
||||
import { Path, Patp } from '../lib';
|
||||
import { Resource } from '../groups';
|
||||
|
||||
export type ContactUpdate =
|
||||
| ContactUpdateAdd
|
||||
@ -26,7 +26,7 @@ export interface ContactUpdateRemove {
|
||||
export interface ContactUpdateEdit {
|
||||
edit: {
|
||||
ship: Patp;
|
||||
"edit-field": ContactEditField;
|
||||
'edit-field': ContactEditField;
|
||||
timestamp: number;
|
||||
};
|
||||
}
|
||||
@ -39,7 +39,7 @@ export interface ContactUpdateAllowShips {
|
||||
|
||||
export interface ContactUpdateAllowGroup {
|
||||
allow: {
|
||||
group: Path;
|
||||
group: Resource;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ export interface Contact {
|
||||
|
||||
type ContactKeys = keyof Contact;
|
||||
|
||||
export type ContactEditFieldPrim = Exclude<ContactKeys, "groups" | "last-updated">;
|
||||
export type ContactEditFieldPrim = Exclude<ContactKeys, 'groups' | 'last-updated'>;
|
||||
|
||||
export type ContactEditField = Partial<Pick<Contact, ContactEditFieldPrim>> & {
|
||||
'add-group'?: Resource;
|
||||
|
@ -20,6 +20,11 @@ export const scryTreaties: Scry = {
|
||||
path: '/treaties'
|
||||
};
|
||||
|
||||
export const scryDefaultAlly: Scry = {
|
||||
app: 'treaty',
|
||||
path: '/default-ally'
|
||||
};
|
||||
|
||||
export const scryAllies: Scry = {
|
||||
app: 'treaty',
|
||||
path: '/allies'
|
||||
|
@ -143,10 +143,6 @@ export interface Vat {
|
||||
* .^(@uv %cz /=desk=)
|
||||
* ```
|
||||
*/
|
||||
/**
|
||||
* True if desk is no longer syncing from upstream
|
||||
*/
|
||||
paused: boolean;
|
||||
hash: string;
|
||||
/**
|
||||
* Current revision
|
||||
|
@ -1,7 +1,7 @@
|
||||
:~ title+'Web Terminal'
|
||||
info+'A web interface for dill, through herm.'
|
||||
color+0xff.ffff
|
||||
glob-http+'https://bootstrap.urbit.org/glob-0v4.8ui32.ui10d.t0v4d.n9g1s.1ftua.glob'
|
||||
color+0x2e.4347
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v4.8ui32.ui10d.t0v4d.n9g1s.1ftua.glob' 0v4.8ui32.ui10d.t0v4d.n9g1s.1ftua]
|
||||
base+'webterm'
|
||||
version+[0 0 1]
|
||||
website+'https://tlon.io'
|
||||
|