on main help sign in
move stuff around, fix bugs, and, uh, add apps integration and known moons
4e38358John Hyde
base-desk/mar/kelvin.hoon+16−6
@@ -1,18 +1,28 @@
1−=/ weft ,[lal=@tas num=@ud] :: TODO remove after merge
1+|_ kal=waft:clay
2−|_ kel=weft
3++ grow
2++ grow
4 |%
3 |%
5 ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
4 ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
6− ++ noun kel
5+ ++ noun kal
7− ++ hoon (crip "{<[lal num]:kel>}\0a")
6+ ++ hoon
7+ %+ rap 3
8+ %+ turn
9+ %+ sort
10+ ~(tap in (waft-to-wefts:clay kal))
11+ |= [a=weft b=weft]
12+ ?: =(lal.a lal.b)
13+ (gte num.a num.b)
14+ (gte lal.a lal.b)
15+ |= =weft
16+ (rap 3 '[%' (scot %tas lal.weft) ' ' (scot %ud num.weft) ']\0a' ~)
17+ ::
8 ++ txt (to-wain:format hoon)
18 ++ txt (to-wain:format hoon)
9 --
19 --
10++ grab
20++ grab
11 |%
21 |%
12− ++ noun weft
22+ ++ noun waft:clay
13 ++ mime
23 ++ mime
14 |= [=mite len=@ud tex=@]
24 |= [=mite len=@ud tex=@]
15− !<(weft (slap !>(~) (ream tex)))
25+ (cord-to-waft:clay tex)
16 --
26 --
17++ grad %noun
27++ grad %noun
18--
28--
ui/package-lock.json−40
@@ -8,7 +8,6 @@
8 "name": "urbit-astrolabe",
8 "name": "urbit-astrolabe",
9 "version": "0.3.0",
9 "version": "0.3.0",
10 "dependencies": {
10 "dependencies": {
11− "@johnhyde/legil-js": "file:../../legil-js",
12 "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
11 "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
13 "@tlon/phonemes-js": "^1.0.0",
12 "@tlon/phonemes-js": "^1.0.0",
14 "@tlon/sigil-js": "^1.4.5",
13 "@tlon/sigil-js": "^1.4.5",
@@ -43,27 +42,6 @@
43 "vite": "^2.7.2"
42 "vite": "^2.7.2"
44 }
43 }
45 },
44 },
46− "../../legil-js": {
47− "name": "@johnhyde/legil-js",
48− "version": "0.0.0",
49− "license": "MIT",
50− "dependencies": {
51− "invariant": "^2.2.4",
52− "svgson": "^4.0.0",
53− "transformation-matrix": "2.1.1"
54− },
55− "devDependencies": {
56− "@types/invariant": "^2.2.31",
57− "@types/react": "^17.0.20",
58− "husky": "^4.2.3",
59− "prettier": "^1.19.1",
60− "tsdx": "^0.14.1",
61− "typescript": "^3.8.3"
62− },
63− "peerDependencies": {
64− "react": "^16 || ^17"
65− }
66− },
67 "../../sigil-js": {
45 "../../sigil-js": {
68 "name": "@johnhyde/sigil-js",
46 "name": "@johnhyde/sigil-js",
69 "version": "1.4.6",
47 "version": "1.4.6",
@@ -194,10 +172,6 @@
194 "node": ">=6.9.0"
172 "node": ">=6.9.0"
195 }
173 }
196 },
174 },
197− "node_modules/@johnhyde/legil-js": {
198− "resolved": "../../legil-js",
199− "link": true
200− },
201 "node_modules/@johnhyde/sigil-js": {
175 "node_modules/@johnhyde/sigil-js": {
202 "version": "1.4.6",
176 "version": "1.4.6",
203 "resolved": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
177 "resolved": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
@@ -3180,20 +3154,6 @@
3180 "regenerator-runtime": "^0.13.4"
3154 "regenerator-runtime": "^0.13.4"
3181 }
3155 }
3182 },
3156 },
3183− "@johnhyde/legil-js": {
3184− "version": "file:../../legil-js",
3185− "requires": {
3186− "@types/invariant": "^2.2.31",
3187− "@types/react": "^17.0.20",
3188− "husky": "^4.2.3",
3189− "invariant": "^2.2.4",
3190− "prettier": "^1.19.1",
3191− "svgson": "^4.0.0",
3192− "transformation-matrix": "2.1.1",
3193− "tsdx": "^0.14.1",
3194− "typescript": "^3.8.3"
3195− }
3196− },
3197 "@johnhyde/sigil-js": {
3157 "@johnhyde/sigil-js": {
3198 "version": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
3158 "version": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
3199 "from": "@johnhyde/sigil-js@github:johnhyde/sigil-js",
3159 "from": "@johnhyde/sigil-js@github:johnhyde/sigil-js",
ui/package.json−1
@@ -43,7 +43,6 @@
43 "vite": "^2.7.2"
43 "vite": "^2.7.2"
44 },
44 },
45 "dependencies": {
45 "dependencies": {
46− "@johnhyde/legil-js": "file:../../legil-js",
47 "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
46 "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
48 "@tlon/phonemes-js": "^1.0.0",
47 "@tlon/phonemes-js": "^1.0.0",
49 "@tlon/sigil-js": "^1.4.5",
48 "@tlon/sigil-js": "^1.4.5",
ui/src/components/ShipListing.svelte+1−1
@@ -1,6 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2
2
3− import Sigil from "./Sigil.svelte";
3+ import Sigil from "@/common/Sigil.svelte";
4 import PalStatusIndicator from '@/pals/PalStatusIndicator.svelte';
4 import PalStatusIndicator from '@/pals/PalStatusIndicator.svelte';
5 import { push } from 'svelte-spa-router'
5 import { push } from 'svelte-spa-router'
6 import { normalizeId } from 'lib/id';
6 import { normalizeId } from 'lib/id';
ui/src/components/StarChart.svelte+1−1
@@ -1,5 +1,5 @@
1<script lang="ts">
1<script lang="ts">
2− import LoadingSpinner from './LoadingSpinner.svelte';
2+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
3
3
4 import { push } from 'svelte-spa-router'
4 import { push } from 'svelte-spa-router'
5
5
ui/src/components/ChartLink.svelte → ui/src/components/common/ChartLink.svelterenamed

The content is the same.

ui/src/components/EthAddressLink.svelte → ui/src/components/common/EthAddressLink.svelterenamed

The content is the same.

ui/src/components/GoldBadge.svelte → ui/src/components/common/GoldBadge.svelterenamed

The content is the same.

ui/src/components/Legil.svelte → ui/src/components/common/Legil.svelterenamed

The content is the same.

ui/src/components/LoadingSpinner.svelte → ui/src/components/common/LoadingSpinner.svelterenamed

The content is the same.

ui/src/components/ShipLink.svelte → ui/src/components/common/ShipLink.svelterenamed

The content is the same.

ui/src/components/Sigil.svelte → ui/src/components/common/Sigil.svelterenamed

The content is the same.

ui/src/components/Tooltip.svelte → ui/src/components/common/Tooltip.svelterenamed

The content is the same.

ui/src/components/TooltipAndDocLink.svelte → ui/src/components/common/TooltipAndDocLink.svelterenamed+1−1
@@ -1,6 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2 import Tooltip from "./Tooltip.svelte";
2 import Tooltip from "./Tooltip.svelte";
3− import DocLink from './docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something
3+ import DocLink from '@/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something
4 export let text: string = '';
4 export let text: string = '';
5 export let doc: string = '';
5 export let doc: string = '';
6 let className: string = '';
6 let className: string = '';
ui/src/components/docs/DocContent.svelte+1−1
@@ -5,7 +5,7 @@
5 import docLinks from "actions/docLinks";
5 import docLinks from "actions/docLinks";
6 import docs from "stores/docs";
6 import docs from "stores/docs";
7
7
8− import LoadingSpinner from "@/LoadingSpinner.svelte";
8+ import LoadingSpinner from "@/common/LoadingSpinner.svelte";
9
9
10 export let path:string;
10 export let path:string;
11
11
ui/src/components/extensions/AppIcon.sveltenew file+61
@@ -0,0 +1,61 @@
1+<script lang="ts">
2+ import CopyToClipboard from 'svelte-copy-to-clipboard';
3+ import { uxToHex } from 'lib/utils';
4+ import { getAppByPath } from 'lib/apps';
5+ import store from 'stores/store';
6+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
7+
8+ export let path: string;
9+
10+ $: desk = path.split('/')[1];
11+ $: appPromise = getAppByPath(path);
12+
13+ let overrideText = null;
14+
15+ const handleSuccessfullyCopied = (e) => {
16+ overrideText = 'Copied!';
17+ setTimeout(() => {
18+ overrideText = null;
19+ }, 1000);
20+ }
21+
22+ const handleFailedCopy = () => {
23+ alert('failed to copy :(');
24+ }
25+</script>
26+
27+<div class="w-20 text-sm text-center">
28+ <div class="w-20 h-20 border border-gray-300 bg-gray-600 text-white rounded-lg overflow-hidden">
29+ {#await appPromise}
30+ <div class="flex h-full items-center">
31+ <LoadingSpinner />
32+ </div>
33+ {:then app}
34+ <a style:background-color={uxToHex(app.color)} class="block h-full w-full text-sm"
35+ href="web+urbitgraph://{path}" target="_blank"
36+ >
37+ {#if app.image}
38+ <img src={app.image} alt="app icon"
39+ class="aspect-square object-cover w-full h-full"
40+ />
41+ {/if}
42+ </a>
43+ {:catch}
44+ {path}
45+ {/await}
46+ </div>
47+ <CopyToClipboard text={path} on:copy={handleSuccessfullyCopied} on:fail={handleFailedCopy} let:copy>
48+ <span on:click={copy}>
49+ {#if overrideText}
50+ {overrideText}
51+ {:else}
52+ {#await appPromise}
53+ {desk}
54+ {:then app}
55+ <!-- {desk} -->
56+ {app.title}
57+ {/await}
58+ {/if}
59+ </span>
60+ </CopyToClipboard>
61+</div>
ui/src/components/extensions/AppsShipViewExtension.sveltenew file+56
@@ -0,0 +1,56 @@
1+<script lang="ts">
2+
3+ import { sein } from 'urbit-ob';
4+ import uniq from 'lodash/uniq';
5+ import tooltip from 'actions/tooltip';
6+ import { normalizeId, filterIdsForChildMoons } from 'lib/id';
7+ import { filterObject } from 'lib/utils';
8+ import { addAllies, delAllies } from 'lib/apps';
9+ import store from 'stores/store';
10+ import ExtensionWindow from '@/ExtensionWindow.svelte';
11+ import AppIcon from './AppIcon.svelte';
12+
13+ export let patp: string;
14+ $: moons = filterIdsForChildMoons($store.peers, patp);
15+ $: possibleAllies = [patp, ...moons];
16+ $: knownApps = uniq(Object.values(filterObject($store.apps.allies, (ally) => {
17+ ally = normalizeId(ally);
18+ return patp === ally || sein(ally) === patp;
19+ })).flat());
20+ $: nonAllies = possibleAllies.filter((ally) => !$store.apps.allies[ally]);
21+ $: emptyAllies = possibleAllies.filter((ally) => {
22+ let apps = $store.apps.allies[ally];
23+ return apps && !apps.length;
24+ });
25+</script>
26+<ExtensionWindow name="Apps" halfWidth>
27+ <svelte:fragment slot="top-bar">
28+ <div class="grow m-2 flex justify-end align-center">
29+ {#if nonAllies.length > 0}
30+ <button class="bg-gray-200 ml-3 px-2 rounded"
31+ on:click={() => addAllies(nonAllies)}
32+ >
33+ Check for More
34+ </button>
35+ {/if}
36+ {#if emptyAllies.length > 0}
37+ <button class="bg-gray-200 ml-3 px-2 rounded"
38+ on:click={() => delAllies(emptyAllies)}
39+ >
40+ Stop Watching
41+ <span use:tooltip={emptyAllies.join(', ')}>
42+ {emptyAllies.length} Ship{emptyAllies.length > 1 ? 's': ''}
43+ </span>
44+ </button>
45+ {/if}
46+ </div>
47+ </svelte:fragment>
48+ {#if knownApps.length > 0}
49+ <div class="flex flex-wrap p-2 gap-2 justify-center">
50+ {#each knownApps as app}
51+ <AppIcon path={app} />
52+ {/each}
53+ </div>
54+ {/if}
55+</ExtensionWindow>
56+
ui/src/components/groups/GroupsShipViewExtension.svelte → ui/src/components/extensions/GroupsShipViewExtension.svelterenamed+7−5
@@ -3,7 +3,7 @@
3 import { uxToHex } from 'lib/utils';
3 import { uxToHex } from 'lib/utils';
4 import { normalizeId } from 'lib/id';
4 import { normalizeId } from 'lib/id';
5 import store from 'stores/store';
5 import store from 'stores/store';
6− import Sigil from "@/Sigil.svelte";
6+ import Sigil from "@/common/Sigil.svelte";
7 import ExtensionWindow from '@/ExtensionWindow.svelte';
7 import ExtensionWindow from '@/ExtensionWindow.svelte';
8
8
9 export let patp: string;
9 export let patp: string;
@@ -16,7 +16,7 @@
16 <ExtensionWindow name="Groups">
16 <ExtensionWindow name="Groups">
17 <svelte:fragment slot="top-bar">
17 <svelte:fragment slot="top-bar">
18 {#if contact.nickname}
18 {#if contact.nickname}
19− <div class="grow ml-1 my-1 flex justify-center align-center">
19+ <div class="grow m-1 flex justify-center align-center">
20 {contact.nickname}
20 {contact.nickname}
21 </div>
21 </div>
22 {/if}
22 {/if}
@@ -37,9 +37,11 @@
37 <Sigil patp={patp} size={128} bgColor={color} useNew />
37 <Sigil patp={patp} size={128} bgColor={color} useNew />
38 {/if}
38 {/if}
39 </div>
39 </div>
40+ {#if contact.bio}
40− <div class="text-sm p-3 bg-white rounded-tl-lg rounded-br-lg whitespace-pre-line">
41+ <div class="text-sm p-3 bg-white rounded-tl-lg rounded-br-lg whitespace-pre-line">
41− <SvelteMarkdown source={contact.bio} />
42+ <SvelteMarkdown source={contact.bio} />
42− </div>
43+ </div>
44+ {/if}
43 </div>
45 </div>
44 <!-- <p class="whitespace-pre-line">
46 <!-- <p class="whitespace-pre-line">
45 {contact.bio}
47 {contact.bio}
ui/src/components/pals/PalTagger.svelte+1−1
@@ -8,7 +8,7 @@
8
8
9 let newTagText = '';
9 let newTagText = '';
10 $: newTags = newTagText.split(',').map(t => t.trim());
10 $: newTags = newTagText.split(',').map(t => t.trim());
11− $: sanitizedNewTags = newTags.map(t => t.replaceAll(/[^-_.~a-z0-9]+/g, '_'));
11+ $: sanitizedNewTags = newTags.map(t => t.toLocaleLowerCase().replaceAll(/[^-_.~a-z0-9]+/g, '_'));
12 $: newTagsAreValid = newTags.every(tagIsValid);
12 $: newTagsAreValid = newTags.every(tagIsValid);
13 $: validationClasses = newTagsAreValid ? '' : 'border-red-500 bg-red-100'
13 $: validationClasses = newTagsAreValid ? '' : 'border-red-500 bg-red-100'
14
14
ui/src/components/ShipChain.svelte → ui/src/components/ship_view/ShipChain.svelterenamed+1−1
@@ -1,5 +1,5 @@
1<script lang="ts">
1<script lang="ts">
2− import ShipLink from './ShipLink.svelte';
2+ import ShipLink from '@/common/ShipLink.svelte';
3
3
4 export let shipChain: string[];
4 export let shipChain: string[];
5</script>
5</script>
ui/src/components/ship_view/ShipChildrenTabs.sveltenew file+105
@@ -0,0 +1,105 @@
1+<script lang="ts">
2+ import { clan } from 'urbit-ob';
3+
4+ import { normalizeId, filterIdsForChildMoons } from 'lib/id';
5+ import { getSpawnedPoints } from 'lib/api';
6+ import store from 'stores/store';
7+ import ShipListings from '@/ShipListings.svelte';
8+
9+ export let patp: string;
10+ export let spawnedCount: number = undefined;
11+
12+ let rawSpawnedPointsPromise: any;
13+ let rawSpawnedPoints: any = null;
14+ let spawnedPoints: string[] = [];
15+
16+ $: shipClass = clan(patp);
17+ $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
18+ $: canSpawnPoints = ['galaxy', 'star'].includes(shipClass);
19+ $: spawnableClassPlural = !canSpawnPoints ? '' : (shipClass === 'galaxy' ? 'Stars' : 'Planets');
20+ $: {
21+ rawSpawnedPoints = null;
22+ if (canSpawnPoints) {
23+ rawSpawnedPointsPromise = getSpawnedPoints(patp);
24+ rawSpawnedPointsPromise.then((info) => {
25+ rawSpawnedPoints = info;
26+ }).catch((error) => {
27+ rawSpawnedPoints = { error };
28+ });
29+ }
30+ }
31+ $: {
32+ spawnedPoints = [];
33+ if (rawSpawnedPoints?.points) {
34+ spawnedPoints = rawSpawnedPoints?.points.map(patpToShip);
35+ }
36+ }
37+ $: trueSpawnedCount = spawnedCount || spawnedPoints.length;
38+ $: spawnedTab = {
39+ name: 'Spawned',
40+ plural: 'Spawned ' + spawnableClassPlural + (trueSpawnedCount > 1 ? 's' : ''),
41+ promise: rawSpawnedPointsPromise,
42+ points: spawnedPoints,
43+ count: trueSpawnedCount,
44+ };
45+ $: moons = filterIdsForChildMoons($store.peers, patp).map(patpToShip);
46+ $: moonsTab = {
47+ name: 'Moons',
48+ plural: 'Known Moon' + (moons.length > 1 ? 's' : ''),
49+ promise: true,
50+ points: moons,
51+ count: moons.length,
52+ };
53+ let tabIndex = 0;
54+ let tabs = [];
55+ $: {
56+ tabs = [];
57+ if (spawnedCount !== undefined || spawnedPoints.length > 0) {
58+ tabs.push(spawnedTab);
59+ }
60+ if (azPoint) {
61+ tabs.push(moonsTab);
62+ }
63+ }
64+ $: tab = tabs[tabIndex];
65+
66+ function patpToShip(id: string) {
67+ return { patp: normalizeId(id) };
68+ }
69+</script>
70+
71+<div class="p-3">
72+ {#if tabs.length > 1}
73+ <div class="flex gap-x-1">
74+ {#each tabs as tab, i}
75+ <h3 on:click={() => tabIndex = i}
76+ class:bg-gray-200={tabIndex !== i}
77+ class="px-2 border border-gray-300 border-b-0 rounded-t-md cursor-pointer"
78+ >
79+ {tab.name}
80+ </h3>
81+ {/each}
82+ </div>
83+ {/if}
84+ {#if tab}
85+ <div class="mt-2">
86+ <h3 class="text-lg">
87+ {#if tab.count}
88+ {tab.count} {tab.plural}:
89+ {:else}
90+ No {tab.plural}
91+ {/if}
92+ </h3>
93+ {#await tab.promise}
94+ Loading {tab.plural}...
95+ {:then}
96+ <!-- TODO: pagination -->
97+ <ShipListings
98+ ships={tab.points}
99+ linkToShips
100+ />
101+ {/await}
102+ </div>
103+ {/if}
104+</div>
105+
ui/src/components/ShipView.svelte → ui/src/components/ship_view/ShipView.svelterenamed+16−59
@@ -1,24 +1,22 @@
1<script lang="ts">
1<script lang="ts">
2−
3−
4−
5 import { clan, patp2dec } from 'urbit-ob';
2 import { clan, patp2dec } from 'urbit-ob';
6
3
7 import { cite, normalizeId } from 'lib/id';
4 import { cite, normalizeId } from 'lib/id';
8 import { getPoint, getSpawnedPoints } from 'lib/api';
5 import { getPoint, getSpawnedPoints } from 'lib/api';
9 import { generateParentChain, processPointInfo } from 'lib/ship';
6 import { generateParentChain, processPointInfo } from 'lib/ship';
10− import Sigil from "./Sigil.svelte";
7+ import Sigil from '@/common/Sigil.svelte';
11− import GoldBadge from './GoldBadge.svelte';
8+ import GoldBadge from '@/common/GoldBadge.svelte';
12− import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
9+ import TooltipAndDocLink from '@/common/TooltipAndDocLink.svelte';
13− import ShipLink from './ShipLink.svelte';
10+ import ShipLink from '@/common/ShipLink.svelte';
14− import ChartLink from './ChartLink.svelte';
11+ import ChartLink from '@/common/ChartLink.svelte';
15− import EthAddressLink from './EthAddressLink.svelte';
12+ import EthAddressLink from '@/common/EthAddressLink.svelte';
13+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
16− import CollapsibleContent from './CollapsibleContent.svelte';
14+ import CollapsibleContent from '@/CollapsibleContent.svelte';
17 import ShipChain from './ShipChain.svelte';
15 import ShipChain from './ShipChain.svelte';
18− import ShipListings from './ShipListings.svelte';
16+ import ShipChildrenTabs from './ShipChildrenTabs.svelte';
19− import LoadingSpinner from './LoadingSpinner.svelte';;
17+ import GroupsShipViewExtension from '@/extensions/GroupsShipViewExtension.svelte';
20− import GroupsShipViewExtension from './groups/GroupsShipViewExtension.svelte';
18+ import AppsShipViewExtension from '@/extensions/AppsShipViewExtension.svelte';
21− import PalsShipViewExtension from './pals/PalsShipViewExtension.svelte';
19+ import PalsShipViewExtension from '@/pals/PalsShipViewExtension.svelte';
22
20
23 export let patp: string;
21 export let patp: string;
24
22
@@ -26,14 +24,9 @@
26 let rawPointInfoPromise: any;
24 let rawPointInfoPromise: any;
27 let rawPointInfo: any = null;
25 let rawPointInfo: any = null;
28 let pointInfo: any = {};
26 let pointInfo: any = {};
29− let rawSpawnedPointsPromise: any;
30− let rawSpawnedPoints: any = null;
31− let spawnedPoints: string[] = [];
32
27
33 $: shipClass = clan(patp);
28 $: shipClass = clan(patp);
34 $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
29 $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
35− $: canSpawnPoints = ['galaxy', 'star'].includes(shipClass);
36− $: spawnableClassPlural = !canSpawnPoints ? '' : (shipClass === 'galaxy' ? 'Stars' : 'Planets');
37 $: {
30 $: {
38 parentChain = generateParentChain(patp);
31 parentChain = generateParentChain(patp);
39 }
32 }
@@ -48,23 +41,6 @@
48 });
41 });
49 }
42 }
50 }
43 }
51− $: {
52− rawSpawnedPoints = null;
53− if (canSpawnPoints) {
54− rawSpawnedPointsPromise = getSpawnedPoints(patp);
55− rawSpawnedPointsPromise.then((info) => {
56− rawSpawnedPoints = info;
57− }).catch((error) => {
58− rawSpawnedPoints = { error };
59− });
60− }
61− }
62− $: {
63− spawnedPoints = [];
64− if (rawSpawnedPoints?.points) {
65− spawnedPoints = rawSpawnedPoints?.points.map(patp => ({ patp: normalizeId(patp) }));
66− }
67− }
68 $: {
44 $: {
69 pointInfo = {};
45 pointInfo = {};
70 if (azPoint) {
46 if (azPoint) {
@@ -87,9 +63,6 @@
87 {cite(patp)}
63 {cite(patp)}
88 </TooltipAndDocLink>
64 </TooltipAndDocLink>
89 </h2>
65 </h2>
90− <!-- {#if (nickname)}
91− <h3 class="text-lg text-center text-gray-800">{nickname}</h3>
92− {/if} -->
93 {#if (!azPoint)}
66 {#if (!azPoint)}
94 <h3 class="text-lg text-center text-gray-700">{patp}</h3>
67 <h3 class="text-lg text-center text-gray-700">{patp}</h3>
95 {/if}
68 {/if}
@@ -229,27 +202,11 @@
229 <div class="flex flex-wrap shrink min-h-0 h-fit -m-1 mt-2 justify-evenly justify-items-center">
202 <div class="flex flex-wrap shrink min-h-0 h-fit -m-1 mt-2 justify-evenly justify-items-center">
230 <GroupsShipViewExtension {patp} />
203 <GroupsShipViewExtension {patp} />
231 <PalsShipViewExtension {patp} />
204 <PalsShipViewExtension {patp} />
205+ <AppsShipViewExtension {patp} />
232 </div>
206 </div>
233− {#if canSpawnPoints}
207+ {#if azPoint}
234− <div class="mt-5 p-3 border-t">
208+ <div class="mt-5 border-t">
235− {#await rawPointInfoPromise then}
209+ <ShipChildrenTabs {patp} />
236− <h3 class="text-lg">
237− {#if pointInfo.spawnedCount}
238− {pointInfo.spawnedCount} Spawned {spawnableClassPlural}:
239− {:else}
240− No Spawned {spawnableClassPlural}
241− {/if}
242− </h3>
243− {/await}
244− {#await rawSpawnedPointsPromise}
245− Loading {spawnableClassPlural}...
246− {:then}
247− <!-- TODO: pagination -->
248− <ShipListings
249− ships={spawnedPoints}
250− linkToShips
251− />
252− {/await}
253 </div>
210 </div>
254 {/if}
211 {/if}
255</div>
212</div>
ui/src/lib/apps.tsnew file+75
@@ -0,0 +1,75 @@
1+import type { Apps } from 'types/store';
2+import store from 'stores/store';
3+import { api } from 'lib/api';
4+import { normalizeId } from 'lib/id';
5+
6+export function getProvidedApps(patp: string): Promise<any> {
7+ return api.scry<any>({ app: 'treaty', path: `/treaties/${patp}` });
8+}
9+
10+export function getKnownApps(): Promise<any> {
11+ return api.scry<any>({ app: 'treaty', path: `/allies` }).then(processAllies);
12+}
13+
14+export function getApp(ship: string, desk: string): Promise<any> {
15+ return api.scry<any>({ app: 'treaty', path: `/treaty/${normalizeId(ship)}/${desk}` });
16+}
17+
18+// @param path - ship/desk e.g. ~mister-dister-dozzod-dozzod/webterm
19+export function getAppByPath(path: string): Promise<any> {
20+ return api.scry<any>({ app: 'treaty', path: `/treaty/${path}` });
21+}
22+
23+export function getInstalledApps(): Promise<any> {
24+ return api.scry<any>({ app: 'docket', path: `/charges` });
25+}
26+
27+export async function sendAllyUpdate(cmd: string, ship: string) {
28+ let result = await api.poke({
29+ app: 'treaty',
30+ mark: 'ally-update-0',
31+ json: { [cmd]: normalizeId(ship) },
32+ });
33+ // store.refreshKnownApps();
34+ return result;
35+}
36+
37+export function addAlly(patp: string) {
38+ return sendAllyUpdate('add', patp);
39+}
40+
41+export function delAlly(patp: string) {
42+ return sendAllyUpdate('del', patp);
43+}
44+
45+export async function addAllies(ships: string[]) {
46+ let results = await Promise.allSettled(ships.map((ship: string) => {
47+ return addAlly(normalizeId(ship));
48+ }));
49+ results = results.filter(r => r.status === 'fulfilled').map((r) => {
50+ return (r as PromiseFulfilledResult<any>).value;
51+ });
52+ store.refreshKnownApps();
53+ return results;
54+}
55+
56+export async function delAllies(ships: string[], retry: number = 0) {
57+ let results = await Promise.allSettled(ships.map((ship: string) => {
58+ return delAlly(normalizeId(ship));
59+ }));
60+ results = results.filter(r => r.status === 'fulfilled').map((r) => {
61+ return (r as PromiseFulfilledResult<any>).value;
62+ });
63+ store.refreshKnownApps();
64+ return results;
65+}
66+
67+export function processAllies(rawPals: any): Apps {
68+ let allies = rawPals.ini;
69+ let paths = Object.values(allies).flat() as string[];
70+ let set = new Set(paths);
71+ return {
72+ allies,
73+ set,
74+ };
75+}
ui/src/lib/id.ts+19−1
@@ -1,6 +1,6 @@
1import BN from 'bn.js';
1import BN from 'bn.js';
2import type { Patp } from '@urbit/api'
2import type { Patp } from '@urbit/api'
3−import { hex2patp, patp2hex, patp as any2patp } from 'urbit-ob';
3+import { hex2patp, patp2hex, patp as any2patp, sein } from 'urbit-ob';
4import { isStringNaN, listStrings } from './utils';
4import { isStringNaN, listStrings } from './utils';
5
5
6// 340282366920938463463374607431768211455
6// 340282366920938463463374607431768211455
@@ -501,6 +501,21 @@ function analyzeSearch(search: string): SearchAnalysis {
501 return new SearchAnalysis(search);
501 return new SearchAnalysis(search);
502}
502}
503
503
504+function isMoonLength(patp: string): boolean {
505+ return patp.length > 14 && patp.length <= 28;
506+}
507+
508+function isCometLength(patp: string): boolean {
509+ return patp.length > 28;
510+}
511+
512+function filterIdsForChildMoons(ids: string[], parentId: string): string[] {
513+ return ids.filter((peerId) => {
514+ if (!isMoonLength(peerId)) return false;
515+ return sein(peerId) === normalizeId(parentId);
516+ })
517+}
518+
504export {
519export {
505 prefixes,
520 prefixes,
506 suffixes,
521 suffixes,
@@ -510,4 +525,7 @@ export {
510 cite,
525 cite,
511 convertSearchTextToRegex,
526 convertSearchTextToRegex,
512 analyzeSearch,
527 analyzeSearch,
528+ isMoonLength,
529+ isCometLength,
530+ filterIdsForChildMoons,
513};
531};
ui/src/routes/Home.svelte+1−1
@@ -7,7 +7,7 @@
7 import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
7 import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
8 import SearchBar from "@/search/SearchBar.svelte";
8 import SearchBar from "@/search/SearchBar.svelte";
9 import SearchResults from "@/search/SearchResults.svelte";
9 import SearchResults from "@/search/SearchResults.svelte";
10− import ShipView from '@/ShipView.svelte';
10+ import ShipView from '@/ship_view/ShipView.svelte';
11 import StarChart from '@/StarChart.svelte';
11 import StarChart from '@/StarChart.svelte';
12 import ValidationProblems from '@/ValidationProblems.svelte';
12 import ValidationProblems from '@/ValidationProblems.svelte';
13 import HomeScreenTiles from '@/HomeScreenTiles.svelte';
13 import HomeScreenTiles from '@/HomeScreenTiles.svelte';
ui/src/routes/SigilPage.svelte+4−4
@@ -1,7 +1,7 @@
1<script lang="ts">
1<script lang="ts">
2 import { prefixes, suffixes } from 'lib/id';
2 import { prefixes, suffixes } from 'lib/id';
3− import Sigil from "@/Sigil.svelte";
3+ import Sigil from "@/common/Sigil.svelte";
4− import Legil from "@/Legil.svelte";
4+ // import Legil from "@/common/Legil.svelte";
5
5
6 let index: number = undefined;
6 let index: number = undefined;
7 let altBgColor = 'red';
7 let altBgColor = 'red';
@@ -38,7 +38,7 @@
38 <div class="flex">
38 <div class="flex">
39 <Sigil patp={phoneme} size={64} {altBgColor} />
39 <Sigil patp={phoneme} size={64} {altBgColor} />
40 <Sigil patp={phoneme} size={64} {altBgColor} useNew />
40 <Sigil patp={phoneme} size={64} {altBgColor} useNew />
41− <Legil patp={phoneme} size={64} {altBgColor} />
41+ <!-- <Legil patp={phoneme} size={64} {altBgColor} /> -->
42 </div>
42 </div>
43 </div>
43 </div>
44 {/each}
44 {/each}
@@ -55,7 +55,7 @@
55 <div class="flex w-full">
55 <div class="flex w-full">
56 <Sigil patp={phonemes[index]} size={400} {altBgColor} />
56 <Sigil patp={phonemes[index]} size={400} {altBgColor} />
57 <Sigil patp={phonemes[index]} size={400} {altBgColor} useNew />
57 <Sigil patp={phonemes[index]} size={400} {altBgColor} useNew />
58− <Legil patp={phonemes[index]} size={400} {altBgColor} />
58+ <!-- <Legil patp={phonemes[index]} size={400} {altBgColor} /> -->
59 </div>
59 </div>
60 {/if}
60 {/if}
61</div>
61</div>
ui/src/stores/searchStores.ts+5−7
@@ -3,8 +3,9 @@ import { writable, derived } from 'svelte/store';
3import type { Contact, Rolodex } from '@urbit/api';
3import type { Contact, Rolodex } from '@urbit/api';
4import store from './store';
4import store from './store';
5import type { StoreState, SearchedContactsState } from 'types/store';
5import type { StoreState, SearchedContactsState } from 'types/store';
6+import { createEmptyContact } from 'types/store';
6import { SearchSettings } from 'types/store';
7import { SearchSettings } from 'types/store';
7−import { splitIdIntoSyls } from 'lib/id';
8+import { splitIdIntoSyls, isMoonLength, isCometLength } from 'lib/id';
8import { filterObject } from 'lib/utils';
9import { filterObject } from 'lib/utils';
9
10
10const searchSettings = writable(new SearchSettings());
11const searchSettings = writable(new SearchSettings());
@@ -27,10 +28,10 @@ function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsStat
27 idMatches = idMatchesSyls;
28 idMatches = idMatchesSyls;
28 }
29 }
29 function isIdAllowed(id) {
30 function isIdAllowed(id) {
30− if (!$searchSettings.includeMoons && id.length > 14 && id.length <= 28) {
31+ if (!$searchSettings.includeMoons && isMoonLength(id)) {
31 return false;
32 return false;
32 }
33 }
33− if (!$searchSettings.includeComets && id.length > 28) {
34+ if (!$searchSettings.includeComets && isCometLength(id)) {
34 return false;
35 return false;
35 }
36 }
36 return true;
37 return true;
@@ -52,10 +53,7 @@ function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsStat
52 const filteredContacts = filterObject($store.contacts, idMatches) as Rolodex;
53 const filteredContacts = filterObject($store.contacts, idMatches) as Rolodex;
53 $store.peers.forEach((id) => {
54 $store.peers.forEach((id) => {
54 if (idMatches(id) && !filteredContacts[id]) {
55 if (idMatches(id) && !filteredContacts[id]) {
55− filteredContacts[id] = {
56+ filteredContacts[id] = createEmptyContact();
56− nickname: null, bio: null, status: null, color: null, avatar: null,
57− cover: null, groups: [], 'last-updated': 0,
58− } as Contact
59 }
57 }
60 })
58 })
61 return filteredContacts;
59 return filteredContacts;
ui/src/stores/store.ts+15−3
@@ -5,7 +5,8 @@ import { subscribeToContacts, getPeers } from 'lib/api';
5import type { StoreState } from 'types/store';
5import type { StoreState } from 'types/store';
6import { normalizeId } from 'lib/id';
6import { normalizeId } from 'lib/id';
7import { setStoreKey } from 'lib/utils';
7import { setStoreKey } from 'lib/utils';
8−import { getPals, getPalStatus, getPal } from 'lib/pals';
8+import { getPals } from 'lib/pals';
9+import { getKnownApps } from 'lib/apps';
9
10
10const initStore: StoreState = {
11const initStore: StoreState = {
11 contacts: {},
12 contacts: {},
@@ -16,6 +17,10 @@ const initStore: StoreState = {
16 mutuals: {},
17 mutuals: {},
17 },
18 },
18 palsInstalled: false,
19 palsInstalled: false,
20+ apps: {
21+ allies: {},
22+ set: new Set(),
23+ },
19 patpQuery: null,
24 patpQuery: null,
20 sigilQuery: null,
25 sigilQuery: null,
21 searchMode: 'patp',
26 searchMode: 'patp',
@@ -56,11 +61,18 @@ getPeers().then(({ points }) => {
56});
61});
57
62
58function refreshPals() {
63function refreshPals() {
59− getPals().then((pals) => {
64+ return getPals().then((pals) => {
60 setStoreKey(store, 'pals', pals);
65 setStoreKey(store, 'pals', pals);
61 setStoreKey(store, 'palsInstalled', true);
66 setStoreKey(store, 'palsInstalled', true);
62 });
67 });
63}
68}
64refreshPals();
69refreshPals();
65
70
71+function refreshKnownApps() {
72+ return getKnownApps().then((apps) => {
73+ setStoreKey(store, 'apps', apps);
74+ });
75+}
76+refreshKnownApps();
77+
66−export default { subscribe, set, searchPatp, searchSigil, refreshPals, reset };
78+export default { subscribe, set, searchPatp, searchSigil, refreshPals, refreshKnownApps, reset };
ui/src/styles/tailwind-output.css+99−69
@@ -571,15 +571,15 @@ Ensure the default browser behavior of the `hidden` attribute.
571.col-span-2 {
571.col-span-2 {
572 grid-column: span 2 / span 2;
572 grid-column: span 2 / span 2;
573}
573}
574−.-m-1 {
575− margin: -0.25rem;
576−}
577.m-4 {
574.m-4 {
578 margin: 1rem;
575 margin: 1rem;
579}
576}
580.m-2 {
577.m-2 {
581 margin: 0.5rem;
578 margin: 0.5rem;
582}
579}
580+.-m-1 {
581+ margin: -0.25rem;
582+}
583.m-8 {
583.m-8 {
584 margin: 2rem;
584 margin: 2rem;
585}
585}
@@ -596,10 +596,6 @@ Ensure the default browser behavior of the `hidden` attribute.
596 margin-left: auto;
596 margin-left: auto;
597 margin-right: auto;
597 margin-right: auto;
598}
598}
599−.my-1\.5 {
600− margin-top: 0.375rem;
601− margin-bottom: 0.375rem;
602−}
603.my-1 {
599.my-1 {
604 margin-top: 0.25rem;
600 margin-top: 0.25rem;
605 margin-bottom: 0.25rem;
601 margin-bottom: 0.25rem;
@@ -640,30 +636,38 @@ Ensure the default browser behavior of the `hidden` attribute.
640 margin-left: -0.25rem;
636 margin-left: -0.25rem;
641 margin-right: -0.25rem;
637 margin-right: -0.25rem;
642}
638}
639+.mx-1 {
640+ margin-left: 0.25rem;
641+ margin-right: 0.25rem;
642+}
643+.my-1\.5 {
644+ margin-top: 0.375rem;
645+ margin-bottom: 0.375rem;
646+}
643.mt-2 {
647.mt-2 {
644 margin-top: 0.5rem;
648 margin-top: 0.5rem;
645}
649}
646.ml-\[22px\] {
650.ml-\[22px\] {
647 margin-left: 22px;
651 margin-left: 22px;
648}
652}
649−.ml-1 {
650− margin-left: 0.25rem;
651−}
652.mr-4 {
653.mr-4 {
653 margin-right: 1rem;
654 margin-right: 1rem;
654}
655}
655.mr-1 {
656.mr-1 {
656 margin-right: 0.25rem;
657 margin-right: 0.25rem;
657}
658}
659+.ml-8 {
660+ margin-left: 2rem;
661+}
662+.ml-1 {
663+ margin-left: 0.25rem;
664+}
658.mt-4 {
665.mt-4 {
659 margin-top: 1rem;
666 margin-top: 1rem;
660}
667}
661.mt-5 {
668.mt-5 {
662 margin-top: 1.25rem;
669 margin-top: 1.25rem;
663}
670}
664−.ml-8 {
665− margin-left: 2rem;
666−}
667.ml-4 {
671.ml-4 {
668 margin-left: 1rem;
672 margin-left: 1rem;
669}
673}
@@ -757,23 +761,23 @@ Ensure the default browser behavior of the `hidden` attribute.
757.h-full {
761.h-full {
758 height: 100%;
762 height: 100%;
759}
763}
760−.h-5 {
761− height: 1.25rem;
762−}
763.h-12 {
764.h-12 {
764 height: 3rem;
765 height: 3rem;
765}
766}
766−.h-fit {
767− height: -webkit-fit-content;
768− height: -moz-fit-content;
769− height: fit-content;
770−}
771.h-3 {
767.h-3 {
772 height: 0.75rem;
768 height: 0.75rem;
773}
769}
774.h-\[100vh\] {
770.h-\[100vh\] {
775 height: 100vh;
771 height: 100vh;
776}
772}
773+.h-5 {
774+ height: 1.25rem;
775+}
776+.h-fit {
777+ height: -webkit-fit-content;
778+ height: -moz-fit-content;
779+ height: fit-content;
780+}
777.h-10 {
781.h-10 {
778 height: 2.5rem;
782 height: 2.5rem;
779}
783}
@@ -830,15 +834,15 @@ Ensure the default browser behavior of the `hidden` attribute.
830.h-60 {
834.h-60 {
831 height: 15rem;
835 height: 15rem;
832}
836}
833−.min-h-0 {
834− min-height: 0px;
835−}
836.min-h-\[20px\] {
837.min-h-\[20px\] {
837 min-height: 20px;
838 min-height: 20px;
838}
839}
839.min-h-full {
840.min-h-full {
840 min-height: 100%;
841 min-height: 100%;
841}
842}
843+.min-h-0 {
844+ min-height: 0px;
845+}
842.min-h-\[14rem\] {
846.min-h-\[14rem\] {
843 min-height: 14rem;
847 min-height: 14rem;
844}
848}
@@ -850,9 +854,6 @@ Ensure the default browser behavior of the `hidden` attribute.
850 width: -moz-min-content;
854 width: -moz-min-content;
851 width: min-content;
855 width: min-content;
852}
856}
853−.w-5 {
854− width: 1.25rem;
855−}
856.w-12 {
857.w-12 {
857 width: 3rem;
858 width: 3rem;
858}
859}
@@ -865,6 +866,9 @@ Ensure the default browser behavior of the `hidden` attribute.
865.w-\[100vw\] {
866.w-\[100vw\] {
866 width: 100vw;
867 width: 100vw;
867}
868}
869+.w-5 {
870+ width: 1.25rem;
871+}
868.w-10 {
872.w-10 {
869 width: 2.5rem;
873 width: 2.5rem;
870}
874}
@@ -929,9 +933,6 @@ Ensure the default browser behavior of the `hidden` attribute.
929.max-w-xs {
933.max-w-xs {
930 max-width: 20rem;
934 max-width: 20rem;
931}
935}
932−.max-w-\[256px\] {
933− max-width: 256px;
934−}
935.max-w-sm {
936.max-w-sm {
936 max-width: 24rem;
937 max-width: 24rem;
937}
938}
@@ -950,6 +951,9 @@ Ensure the default browser behavior of the `hidden` attribute.
950.max-w-\[128px\] {
951.max-w-\[128px\] {
951 max-width: 128px;
952 max-width: 128px;
952}
953}
954+.max-w-\[256px\] {
955+ max-width: 256px;
956+}
953.max-w-\[10rem\] {
957.max-w-\[10rem\] {
954 max-width: 10rem;
958 max-width: 10rem;
955}
959}
@@ -1075,6 +1079,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1075.auto-cols-fr {
1079.auto-cols-fr {
1076 grid-auto-columns: minmax(0, 1fr);
1080 grid-auto-columns: minmax(0, 1fr);
1077}
1081}
1082+.auto-cols-max {
1083+ grid-auto-columns: -webkit-max-content;
1084+ grid-auto-columns: max-content;
1085+}
1078.grid-flow-row {
1086.grid-flow-row {
1079 grid-auto-flow: row;
1087 grid-auto-flow: row;
1080}
1088}
@@ -1091,6 +1099,13 @@ Ensure the default browser behavior of the `hidden` attribute.
1091 grid-auto-rows: -webkit-min-content;
1099 grid-auto-rows: -webkit-min-content;
1092 grid-auto-rows: min-content;
1100 grid-auto-rows: min-content;
1093}
1101}
1102+.auto-rows-auto {
1103+ grid-auto-rows: auto;
1104+}
1105+.auto-rows-max {
1106+ grid-auto-rows: -webkit-max-content;
1107+ grid-auto-rows: max-content;
1108+}
1094.grid-cols-3 {
1109.grid-cols-3 {
1095 grid-template-columns: repeat(3, minmax(0, 1fr));
1110 grid-template-columns: repeat(3, minmax(0, 1fr));
1096}
1111}
@@ -1157,21 +1172,21 @@ Ensure the default browser behavior of the `hidden` attribute.
1157.gap-5 {
1172.gap-5 {
1158 gap: 1.25rem;
1173 gap: 1.25rem;
1159}
1174}
1160−.gap-1\.5 {
1161− gap: 0.375rem;
1162−}
1163−.gap-1 {
1164− gap: 0.25rem;
1165−}
1166.gap-4 {
1175.gap-4 {
1167 gap: 1rem;
1176 gap: 1rem;
1168}
1177}
1169.gap-2 {
1178.gap-2 {
1170 gap: 0.5rem;
1179 gap: 0.5rem;
1171}
1180}
1181+.gap-1 {
1182+ gap: 0.25rem;
1183+}
1172.gap-3 {
1184.gap-3 {
1173 gap: 0.75rem;
1185 gap: 0.75rem;
1174}
1186}
1187+.gap-1\.5 {
1188+ gap: 0.375rem;
1189+}
1175.gap-x-4 {
1190.gap-x-4 {
1176 -moz-column-gap: 1rem;
1191 -moz-column-gap: 1rem;
1177 column-gap: 1rem;
1192 column-gap: 1rem;
@@ -1299,9 +1314,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1299.break-all {
1314.break-all {
1300 word-break: break-all;
1315 word-break: break-all;
1301}
1316}
1302−.rounded {
1303− border-radius: 0.25rem;
1304−}
1305.rounded-md {
1317.rounded-md {
1306 border-radius: 0.375rem;
1318 border-radius: 0.375rem;
1307}
1319}
@@ -1311,6 +1323,9 @@ Ensure the default browser behavior of the `hidden` attribute.
1311.rounded-full {
1323.rounded-full {
1312 border-radius: 9999px;
1324 border-radius: 9999px;
1313}
1325}
1326+.rounded {
1327+ border-radius: 0.25rem;
1328+}
1314.rounded-xl {
1329.rounded-xl {
1315 border-radius: 0.75rem;
1330 border-radius: 0.75rem;
1316}
1331}
@@ -1331,6 +1346,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1331 border-top-left-radius: 1rem;
1346 border-top-left-radius: 1rem;
1332 border-top-right-radius: 1rem;
1347 border-top-right-radius: 1rem;
1333}
1348}
1349+.rounded-t-md {
1350+ border-top-left-radius: 0.375rem;
1351+ border-top-right-radius: 0.375rem;
1352+}
1334.rounded-br-2xl {
1353.rounded-br-2xl {
1335 border-bottom-right-radius: 1rem;
1354 border-bottom-right-radius: 1rem;
1336}
1355}
@@ -1364,9 +1383,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1364.border-l-8 {
1383.border-l-8 {
1365 border-left-width: 8px;
1384 border-left-width: 8px;
1366}
1385}
1367−.border-t {
1368− border-top-width: 1px;
1369−}
1370.border-r {
1386.border-r {
1371 border-right-width: 1px;
1387 border-right-width: 1px;
1372}
1388}
@@ -1376,6 +1392,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1376.border-l {
1392.border-l {
1377 border-left-width: 1px;
1393 border-left-width: 1px;
1378}
1394}
1395+.border-t {
1396+ border-top-width: 1px;
1397+}
1398+.border-b-0 {
1399+ border-bottom-width: 0px;
1400+}
1379.border-none {
1401.border-none {
1380 border-style: none;
1402 border-style: none;
1381}
1403}
@@ -1431,22 +1453,26 @@ Ensure the default browser behavior of the `hidden` attribute.
1431 --tw-border-opacity: 1;
1453 --tw-border-opacity: 1;
1432 border-color: rgb(75 85 99 / var(--tw-border-opacity));
1454 border-color: rgb(75 85 99 / var(--tw-border-opacity));
1433}
1455}
1434−.bg-navy {
1456+.border-gray-200 {
1435− --tw-bg-opacity: 1;
1457+ --tw-border-opacity: 1;
1436− background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1458+ border-color: rgb(229 231 235 / var(--tw-border-opacity));
1437}
1459}
1438.bg-gray-400 {
1460.bg-gray-400 {
1439 --tw-bg-opacity: 1;
1461 --tw-bg-opacity: 1;
1440 background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1462 background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1441}
1463}
1442−.bg-gold {
1443− --tw-bg-opacity: 1;
1444− background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1445−}
1446.bg-white {
1464.bg-white {
1447 --tw-bg-opacity: 1;
1465 --tw-bg-opacity: 1;
1448 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1466 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1449}
1467}
1468+.bg-navy {
1469+ --tw-bg-opacity: 1;
1470+ background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1471+}
1472+.bg-gold {
1473+ --tw-bg-opacity: 1;
1474+ background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1475+}
1450.bg-l1-blue {
1476.bg-l1-blue {
1451 --tw-bg-opacity: 1;
1477 --tw-bg-opacity: 1;
1452 background-color: rgb(23 146 199 / var(--tw-bg-opacity));
1478 background-color: rgb(23 146 199 / var(--tw-bg-opacity));
@@ -1563,6 +1589,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1563 --tw-bg-opacity: 1;
1589 --tw-bg-opacity: 1;
1564 background-color: rgb(254 226 226 / var(--tw-bg-opacity));
1590 background-color: rgb(254 226 226 / var(--tw-bg-opacity));
1565}
1591}
1592+.bg-gray-600 {
1593+ --tw-bg-opacity: 1;
1594+ background-color: rgb(75 85 99 / var(--tw-bg-opacity));
1595+}
1566.fill-transparent {
1596.fill-transparent {
1567 fill: transparent;
1597 fill: transparent;
1568}
1598}
@@ -1608,14 +1638,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1608 padding-top: 0.25rem;
1638 padding-top: 0.25rem;
1609 padding-bottom: 0.25rem;
1639 padding-bottom: 0.25rem;
1610}
1640}
1611−.py-0\.5 {
1612− padding-top: 0.125rem;
1613− padding-bottom: 0.125rem;
1614−}
1615−.py-0 {
1616− padding-top: 0px;
1617− padding-bottom: 0px;
1618−}
1619.px-4 {
1641.px-4 {
1620 padding-left: 1rem;
1642 padding-left: 1rem;
1621 padding-right: 1rem;
1643 padding-right: 1rem;
@@ -1628,6 +1650,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1628 padding-left: 0px;
1650 padding-left: 0px;
1629 padding-right: 0px;
1651 padding-right: 0px;
1630}
1652}
1653+.py-0 {
1654+ padding-top: 0px;
1655+ padding-bottom: 0px;
1656+}
1631.py-4 {
1657.py-4 {
1632 padding-top: 1rem;
1658 padding-top: 1rem;
1633 padding-bottom: 1rem;
1659 padding-bottom: 1rem;
@@ -1660,6 +1686,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1660 padding-left: 0.375rem;
1686 padding-left: 0.375rem;
1661 padding-right: 0.375rem;
1687 padding-right: 0.375rem;
1662}
1688}
1689+.py-0\.5 {
1690+ padding-top: 0.125rem;
1691+ padding-bottom: 0.125rem;
1692+}
1663.pt-10 {
1693.pt-10 {
1664 padding-top: 2.5rem;
1694 padding-top: 2.5rem;
1665}
1695}
@@ -1708,10 +1738,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1708.font-mono {
1738.font-mono {
1709 font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1739 font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1710}
1740}
1711−.text-3xl {
1712− font-size: 1.875rem;
1713− line-height: 2.25rem;
1714−}
1715.text-sm {
1741.text-sm {
1716 font-size: 0.875rem;
1742 font-size: 0.875rem;
1717 line-height: 1.25rem;
1743 line-height: 1.25rem;
@@ -1728,6 +1754,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1728 font-size: 1.125rem;
1754 font-size: 1.125rem;
1729 line-height: 1.75rem;
1755 line-height: 1.75rem;
1730}
1756}
1757+.text-3xl {
1758+ font-size: 1.875rem;
1759+ line-height: 2.25rem;
1760+}
1731.text-base {
1761.text-base {
1732 font-size: 1rem;
1762 font-size: 1rem;
1733 line-height: 1.5rem;
1763 line-height: 1.5rem;
@@ -1747,14 +1777,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1747.leading-3 {
1777.leading-3 {
1748 line-height: .75rem;
1778 line-height: .75rem;
1749}
1779}
1750−.text-white {
1751− --tw-text-opacity: 1;
1752− color: rgb(255 255 255 / var(--tw-text-opacity));
1753−}
1754.text-gray-600 {
1780.text-gray-600 {
1755 --tw-text-opacity: 1;
1781 --tw-text-opacity: 1;
1756 color: rgb(75 85 99 / var(--tw-text-opacity));
1782 color: rgb(75 85 99 / var(--tw-text-opacity));
1757}
1783}
1784+.text-white {
1785+ --tw-text-opacity: 1;
1786+ color: rgb(255 255 255 / var(--tw-text-opacity));
1787+}
1758.text-gray-800 {
1788.text-gray-800 {
1759 --tw-text-opacity: 1;
1789 --tw-text-opacity: 1;
1760 color: rgb(31 41 55 / var(--tw-text-opacity));
1790 color: rgb(31 41 55 / var(--tw-text-opacity));
@@ -1981,14 +2011,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1981 min-width: 256px;
2011 min-width: 256px;
1982 }
2012 }
1983
2013
1984− .md\:max-w-2xl {
1985− max-width: 42rem;
1986− }
1987−
1988 .md\:max-w-xl {
2014 .md\:max-w-xl {
1989 max-width: 36rem;
2015 max-width: 36rem;
1990 }
2016 }
1991
2017
2018+ .md\:max-w-2xl {
2019+ max-width: 42rem;
2020+ }
2021+
1992 .md\:flex-row {
2022 .md\:flex-row {
1993 flex-direction: row;
2023 flex-direction: row;
1994 }
2024 }
ui/src/types/store.ts+15
@@ -1,5 +1,12 @@
1import type { ConnectionStatus } from './connection';
1import type { ConnectionStatus } from './connection';
2import type { Patp, Group, Contact, Rolodex } from '@urbit/api';
2import type { Patp, Group, Contact, Rolodex } from '@urbit/api';
3+
4+export function createEmptyContact(): Contact {
5+ return {
6+ nickname: null, bio: null, status: null, color: null, avatar: null,
7+ cover: null, groups: [], 'last-updated': 0,
8+ };
9+}
3export interface Contacts {
10export interface Contacts {
4 [contact: string]: Contact;
11 [contact: string]: Contact;
5};
12};
@@ -34,6 +41,13 @@ export interface PalInfo extends Pal {
34 status: PalStatus;
41 status: PalStatus;
35}
42}
36
43
44+export interface Apps {
45+ allies: {
46+ [ally: string]: string[],
47+ },
48+ set: Set<string>,
49+}
50+
37export interface StoreState {
51export interface StoreState {
38 // Urbit
52 // Urbit
39 // groups: {
53 // groups: {
@@ -43,6 +57,7 @@ export interface StoreState {
43 peers: Patp[];
57 peers: Patp[];
44 pals: Pals;
58 pals: Pals;
45 palsInstalled: boolean;
59 palsInstalled: boolean;
60+ apps: Apps,
46 patpQuery: RegExp;
61 patpQuery: RegExp;
47 sigilQuery: string[][];
62 sigilQuery: string[][];
48 searchMode: ('patp' | 'sigil');
63 searchMode: ('patp' | 'sigil');