on main help sign in
move stuff around, fix bugs, and, uh, add apps integration and known moons
commit4e38358d95776679f3cfa0ec140df57186c35c35 authorJohn Hyde <johnhydefour@gmail.com> dateWed 7 Dec 2022, 11:41 (4 years ago) parent7e2bd15
30 files changed+500−202
base-desk/mar/kelvin.hoon+16−6 ui/package-lock.json−40 ui/package.json−1 ui/src/components/ShipListing.svelte+1−1 ui/src/components/StarChart.svelte+1−1 ui/src/components/ChartLink.svelte → ui/src/components/common/ChartLink.svelte ui/src/components/EthAddressLink.svelte → ui/src/components/common/EthAddressLink.svelte ui/src/components/GoldBadge.svelte → ui/src/components/common/GoldBadge.svelte ui/src/components/Legil.svelte → ui/src/components/common/Legil.svelte ui/src/components/LoadingSpinner.svelte → ui/src/components/common/LoadingSpinner.svelte ui/src/components/ShipLink.svelte → ui/src/components/common/ShipLink.svelte ui/src/components/Sigil.svelte → ui/src/components/common/Sigil.svelte ui/src/components/Tooltip.svelte → ui/src/components/common/Tooltip.svelte ui/src/components/TooltipAndDocLink.svelte → ui/src/components/common/TooltipAndDocLink.svelte+1−1 ui/src/components/docs/DocContent.svelte+1−1 ui/src/components/extensions/AppIcon.svelte+61 ui/src/components/extensions/AppsShipViewExtension.svelte+56 ui/src/components/groups/GroupsShipViewExtension.svelte → ui/src/components/extensions/GroupsShipViewExtension.svelte+7−5 ui/src/components/pals/PalTagger.svelte+1−1 ui/src/components/ShipChain.svelte → ui/src/components/ship_view/ShipChain.svelte+1−1 ui/src/components/ship_view/ShipChildrenTabs.svelte+105 ui/src/components/ShipView.svelte → ui/src/components/ship_view/ShipView.svelte+16−59 ui/src/lib/apps.ts+75 ui/src/lib/id.ts+19−1 ui/src/routes/Home.svelte+1−1 ui/src/routes/SigilPage.svelte+4−4 ui/src/stores/searchStores.ts+5−7 ui/src/stores/store.ts+15−3 ui/src/styles/tailwind-output.css+99−69 ui/src/types/store.ts+15
base-desk/mar/kelvin.hoon+16−6
@@ -1,18 +1,28 @@
1−=/ weft ,[lal=@tas num=@ud] :: TODO remove after merge
2−|_ kel=weft
1+|_ kal=waft:clay
32++ grow
43 |%
54 ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
6− ++ noun kel
7− ++ hoon (crip "{<[lal num]:kel>}\0a")
5+ ++ noun kal
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+ ::
818 ++ txt (to-wain:format hoon)
919 --
1020++ grab
1121 |%
12− ++ noun weft
22+ ++ noun waft:clay
1323 ++ mime
1424 |= [=mite len=@ud tex=@]
15− !<(weft (slap !>(~) (ream tex)))
25+ (cord-to-waft:clay tex)
1626 --
1727++ grad %noun
1828--
ui/package-lock.json−40
@@ -8,7 +8,6 @@
88 "name": "urbit-astrolabe",
99 "version": "0.3.0",
1010 "dependencies": {
11− "@johnhyde/legil-js": "file:../../legil-js",
1211 "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
1312 "@tlon/phonemes-js": "^1.0.0",
1413 "@tlon/sigil-js": "^1.4.5",
@@ -43,27 +42,6 @@
4342 "vite": "^2.7.2"
4443 }
4544 },
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− },
6745 "../../sigil-js": {
6846 "name": "@johnhyde/sigil-js",
6947 "version": "1.4.6",
@@ -194,10 +172,6 @@
194172 "node": ">=6.9.0"
195173 }
196174 },
197− "node_modules/@johnhyde/legil-js": {
198− "resolved": "../../legil-js",
199− "link": true
200− },
201175 "node_modules/@johnhyde/sigil-js": {
202176 "version": "1.4.6",
203177 "resolved": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
@@ -3180,20 +3154,6 @@
31803154 "regenerator-runtime": "^0.13.4"
31813155 }
31823156 },
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− },
31973157 "@johnhyde/sigil-js": {
31983158 "version": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
31993159 "from": "@johnhyde/sigil-js@github:johnhyde/sigil-js",
ui/package.json−1
@@ -43,7 +43,6 @@
4343 "vite": "^2.7.2"
4444 },
4545 "dependencies": {
46− "@johnhyde/legil-js": "file:../../legil-js",
4746 "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
4847 "@tlon/phonemes-js": "^1.0.0",
4948 "@tlon/sigil-js": "^1.4.5",
ui/src/components/ShipListing.svelte+1−1
@@ -1,6 +1,6 @@
11<script lang="ts">
22
3− import Sigil from "./Sigil.svelte";
3+ import Sigil from "@/common/Sigil.svelte";
44 import PalStatusIndicator from '@/pals/PalStatusIndicator.svelte';
55 import { push } from 'svelte-spa-router'
66 import { normalizeId } from 'lib/id';
ui/src/components/StarChart.svelte+1−1
@@ -1,5 +1,5 @@
11<script lang="ts">
2− import LoadingSpinner from './LoadingSpinner.svelte';
2+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
33
44 import { push } from 'svelte-spa-router'
55
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 @@
11<script lang="ts">
22 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
44 export let text: string = '';
55 export let doc: string = '';
66 let className: string = '';
ui/src/components/docs/DocContent.svelte+1−1
@@ -5,7 +5,7 @@
55 import docLinks from "actions/docLinks";
66 import docs from "stores/docs";
77
8− import LoadingSpinner from "@/LoadingSpinner.svelte";
8+ import LoadingSpinner from "@/common/LoadingSpinner.svelte";
99
1010 export let path:string;
1111
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 @@
33 import { uxToHex } from 'lib/utils';
44 import { normalizeId } from 'lib/id';
55 import store from 'stores/store';
6− import Sigil from "@/Sigil.svelte";
6+ import Sigil from "@/common/Sigil.svelte";
77 import ExtensionWindow from '@/ExtensionWindow.svelte';
88
99 export let patp: string;
@@ -16,7 +16,7 @@
1616 <ExtensionWindow name="Groups">
1717 <svelte:fragment slot="top-bar">
1818 {#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">
2020 {contact.nickname}
2121 </div>
2222 {/if}
@@ -37,9 +37,11 @@
3737 <Sigil patp={patp} size={128} bgColor={color} useNew />
3838 {/if}
3939 </div>
40− <div class="text-sm p-3 bg-white rounded-tl-lg rounded-br-lg whitespace-pre-line">
41− <SvelteMarkdown source={contact.bio} />
42− </div>
40+ {#if contact.bio}
41+ <div class="text-sm p-3 bg-white rounded-tl-lg rounded-br-lg whitespace-pre-line">
42+ <SvelteMarkdown source={contact.bio} />
43+ </div>
44+ {/if}
4345 </div>
4446 <!-- <p class="whitespace-pre-line">
4547 {contact.bio}
ui/src/components/pals/PalTagger.svelte+1−1
@@ -8,7 +8,7 @@
88
99 let newTagText = '';
1010 $: 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, '_'));
1212 $: newTagsAreValid = newTags.every(tagIsValid);
1313 $: validationClasses = newTagsAreValid ? '' : 'border-red-500 bg-red-100'
1414
ui/src/components/ShipChain.svelte → ui/src/components/ship_view/ShipChain.svelterenamed+1−1
@@ -1,5 +1,5 @@
11<script lang="ts">
2− import ShipLink from './ShipLink.svelte';
2+ import ShipLink from '@/common/ShipLink.svelte';
33
44 export let shipChain: string[];
55</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 @@
11<script lang="ts">
2−
3−
4−
52 import { clan, patp2dec } from 'urbit-ob';
63
74 import { cite, normalizeId } from 'lib/id';
85 import { getPoint, getSpawnedPoints } from 'lib/api';
96 import { generateParentChain, processPointInfo } from 'lib/ship';
10− import Sigil from "./Sigil.svelte";
11− import GoldBadge from './GoldBadge.svelte';
12− import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
13− import ShipLink from './ShipLink.svelte';
14− import ChartLink from './ChartLink.svelte';
15− import EthAddressLink from './EthAddressLink.svelte';
16− import CollapsibleContent from './CollapsibleContent.svelte';
7+ import Sigil from '@/common/Sigil.svelte';
8+ import GoldBadge from '@/common/GoldBadge.svelte';
9+ import TooltipAndDocLink from '@/common/TooltipAndDocLink.svelte';
10+ import ShipLink from '@/common/ShipLink.svelte';
11+ import ChartLink from '@/common/ChartLink.svelte';
12+ import EthAddressLink from '@/common/EthAddressLink.svelte';
13+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
14+ import CollapsibleContent from '@/CollapsibleContent.svelte';
1715 import ShipChain from './ShipChain.svelte';
18− import ShipListings from './ShipListings.svelte';
19− import LoadingSpinner from './LoadingSpinner.svelte';;
20− import GroupsShipViewExtension from './groups/GroupsShipViewExtension.svelte';
21− import PalsShipViewExtension from './pals/PalsShipViewExtension.svelte';
16+ import ShipChildrenTabs from './ShipChildrenTabs.svelte';
17+ import GroupsShipViewExtension from '@/extensions/GroupsShipViewExtension.svelte';
18+ import AppsShipViewExtension from '@/extensions/AppsShipViewExtension.svelte';
19+ import PalsShipViewExtension from '@/pals/PalsShipViewExtension.svelte';
2220
2321 export let patp: string;
2422
@@ -26,14 +24,9 @@
2624 let rawPointInfoPromise: any;
2725 let rawPointInfo: any = null;
2826 let pointInfo: any = {};
29− let rawSpawnedPointsPromise: any;
30− let rawSpawnedPoints: any = null;
31− let spawnedPoints: string[] = [];
3227
3328 $: shipClass = clan(patp);
3429 $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
35− $: canSpawnPoints = ['galaxy', 'star'].includes(shipClass);
36− $: spawnableClassPlural = !canSpawnPoints ? '' : (shipClass === 'galaxy' ? 'Stars' : 'Planets');
3730 $: {
3831 parentChain = generateParentChain(patp);
3932 }
@@ -48,23 +41,6 @@
4841 });
4942 }
5043 }
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− }
6844 $: {
6945 pointInfo = {};
7046 if (azPoint) {
@@ -87,9 +63,6 @@
8763 {cite(patp)}
8864 </TooltipAndDocLink>
8965 </h2>
90− <!-- {#if (nickname)}
91− <h3 class="text-lg text-center text-gray-800">{nickname}</h3>
92− {/if} -->
9366 {#if (!azPoint)}
9467 <h3 class="text-lg text-center text-gray-700">{patp}</h3>
9568 {/if}
@@ -229,27 +202,11 @@
229202 <div class="flex flex-wrap shrink min-h-0 h-fit -m-1 mt-2 justify-evenly justify-items-center">
230203 <GroupsShipViewExtension {patp} />
231204 <PalsShipViewExtension {patp} />
205+ <AppsShipViewExtension {patp} />
232206 </div>
233− {#if canSpawnPoints}
234− <div class="mt-5 p-3 border-t">
235− {#await rawPointInfoPromise then}
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}
207+ {#if azPoint}
208+ <div class="mt-5 border-t">
209+ <ShipChildrenTabs {patp} />
253210 </div>
254211 {/if}
255212</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 @@
11import BN from 'bn.js';
22import 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';
44import { isStringNaN, listStrings } from './utils';
55
66// 340282366920938463463374607431768211455
@@ -501,6 +501,21 @@ function analyzeSearch(search: string): SearchAnalysis {
501501 return new SearchAnalysis(search);
502502}
503503
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+
504519export {
505520 prefixes,
506521 suffixes,
@@ -510,4 +525,7 @@ export {
510525 cite,
511526 convertSearchTextToRegex,
512527 analyzeSearch,
528+ isMoonLength,
529+ isCometLength,
530+ filterIdsForChildMoons,
513531};
ui/src/routes/Home.svelte+1−1
@@ -7,7 +7,7 @@
77 import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
88 import SearchBar from "@/search/SearchBar.svelte";
99 import SearchResults from "@/search/SearchResults.svelte";
10− import ShipView from '@/ShipView.svelte';
10+ import ShipView from '@/ship_view/ShipView.svelte';
1111 import StarChart from '@/StarChart.svelte';
1212 import ValidationProblems from '@/ValidationProblems.svelte';
1313 import HomeScreenTiles from '@/HomeScreenTiles.svelte';
ui/src/routes/SigilPage.svelte+4−4
@@ -1,7 +1,7 @@
11<script lang="ts">
22 import { prefixes, suffixes } from 'lib/id';
3− import Sigil from "@/Sigil.svelte";
4− import Legil from "@/Legil.svelte";
3+ import Sigil from "@/common/Sigil.svelte";
4+ // import Legil from "@/common/Legil.svelte";
55
66 let index: number = undefined;
77 let altBgColor = 'red';
@@ -38,7 +38,7 @@
3838 <div class="flex">
3939 <Sigil patp={phoneme} size={64} {altBgColor} />
4040 <Sigil patp={phoneme} size={64} {altBgColor} useNew />
41− <Legil patp={phoneme} size={64} {altBgColor} />
41+ <!-- <Legil patp={phoneme} size={64} {altBgColor} /> -->
4242 </div>
4343 </div>
4444 {/each}
@@ -55,7 +55,7 @@
5555 <div class="flex w-full">
5656 <Sigil patp={phonemes[index]} size={400} {altBgColor} />
5757 <Sigil patp={phonemes[index]} size={400} {altBgColor} useNew />
58− <Legil patp={phonemes[index]} size={400} {altBgColor} />
58+ <!-- <Legil patp={phonemes[index]} size={400} {altBgColor} /> -->
5959 </div>
6060 {/if}
6161</div>
ui/src/stores/searchStores.ts+5−7
@@ -3,8 +3,9 @@ import { writable, derived } from 'svelte/store';
33import type { Contact, Rolodex } from '@urbit/api';
44import store from './store';
55import type { StoreState, SearchedContactsState } from 'types/store';
6+import { createEmptyContact } from 'types/store';
67import { SearchSettings } from 'types/store';
7−import { splitIdIntoSyls } from 'lib/id';
8+import { splitIdIntoSyls, isMoonLength, isCometLength } from 'lib/id';
89import { filterObject } from 'lib/utils';
910
1011const searchSettings = writable(new SearchSettings());
@@ -27,10 +28,10 @@ function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsStat
2728 idMatches = idMatchesSyls;
2829 }
2930 function isIdAllowed(id) {
30− if (!$searchSettings.includeMoons && id.length > 14 && id.length <= 28) {
31+ if (!$searchSettings.includeMoons && isMoonLength(id)) {
3132 return false;
3233 }
33− if (!$searchSettings.includeComets && id.length > 28) {
34+ if (!$searchSettings.includeComets && isCometLength(id)) {
3435 return false;
3536 }
3637 return true;
@@ -52,10 +53,7 @@ function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsStat
5253 const filteredContacts = filterObject($store.contacts, idMatches) as Rolodex;
5354 $store.peers.forEach((id) => {
5455 if (idMatches(id) && !filteredContacts[id]) {
55− filteredContacts[id] = {
56− nickname: null, bio: null, status: null, color: null, avatar: null,
57− cover: null, groups: [], 'last-updated': 0,
58− } as Contact
56+ filteredContacts[id] = createEmptyContact();
5957 }
6058 })
6159 return filteredContacts;
ui/src/stores/store.ts+15−3
@@ -5,7 +5,8 @@ import { subscribeToContacts, getPeers } from 'lib/api';
55import type { StoreState } from 'types/store';
66import { normalizeId } from 'lib/id';
77import { setStoreKey } from 'lib/utils';
8−import { getPals, getPalStatus, getPal } from 'lib/pals';
8+import { getPals } from 'lib/pals';
9+import { getKnownApps } from 'lib/apps';
910
1011const initStore: StoreState = {
1112 contacts: {},
@@ -16,6 +17,10 @@ const initStore: StoreState = {
1617 mutuals: {},
1718 },
1819 palsInstalled: false,
20+ apps: {
21+ allies: {},
22+ set: new Set(),
23+ },
1924 patpQuery: null,
2025 sigilQuery: null,
2126 searchMode: 'patp',
@@ -56,11 +61,18 @@ getPeers().then(({ points }) => {
5661});
5762
5863function refreshPals() {
59− getPals().then((pals) => {
64+ return getPals().then((pals) => {
6065 setStoreKey(store, 'pals', pals);
6166 setStoreKey(store, 'palsInstalled', true);
6267 });
6368}
6469refreshPals();
6570
66−export default { subscribe, set, searchPatp, searchSigil, refreshPals, reset };
71+function refreshKnownApps() {
72+ return getKnownApps().then((apps) => {
73+ setStoreKey(store, 'apps', apps);
74+ });
75+}
76+refreshKnownApps();
77+
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.
571571.col-span-2 {
572572 grid-column: span 2 / span 2;
573573}
574−.-m-1 {
575− margin: -0.25rem;
576−}
577574.m-4 {
578575 margin: 1rem;
579576}
580577.m-2 {
581578 margin: 0.5rem;
582579}
580+.-m-1 {
581+ margin: -0.25rem;
582+}
583583.m-8 {
584584 margin: 2rem;
585585}
@@ -596,10 +596,6 @@ Ensure the default browser behavior of the `hidden` attribute.
596596 margin-left: auto;
597597 margin-right: auto;
598598}
599−.my-1\.5 {
600− margin-top: 0.375rem;
601− margin-bottom: 0.375rem;
602−}
603599.my-1 {
604600 margin-top: 0.25rem;
605601 margin-bottom: 0.25rem;
@@ -640,30 +636,38 @@ Ensure the default browser behavior of the `hidden` attribute.
640636 margin-left: -0.25rem;
641637 margin-right: -0.25rem;
642638}
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+}
643647.mt-2 {
644648 margin-top: 0.5rem;
645649}
646650.ml-\[22px\] {
647651 margin-left: 22px;
648652}
649−.ml-1 {
650− margin-left: 0.25rem;
651−}
652653.mr-4 {
653654 margin-right: 1rem;
654655}
655656.mr-1 {
656657 margin-right: 0.25rem;
657658}
659+.ml-8 {
660+ margin-left: 2rem;
661+}
662+.ml-1 {
663+ margin-left: 0.25rem;
664+}
658665.mt-4 {
659666 margin-top: 1rem;
660667}
661668.mt-5 {
662669 margin-top: 1.25rem;
663670}
664−.ml-8 {
665− margin-left: 2rem;
666−}
667671.ml-4 {
668672 margin-left: 1rem;
669673}
@@ -757,23 +761,23 @@ Ensure the default browser behavior of the `hidden` attribute.
757761.h-full {
758762 height: 100%;
759763}
760−.h-5 {
761− height: 1.25rem;
762−}
763764.h-12 {
764765 height: 3rem;
765766}
766−.h-fit {
767− height: -webkit-fit-content;
768− height: -moz-fit-content;
769− height: fit-content;
770−}
771767.h-3 {
772768 height: 0.75rem;
773769}
774770.h-\[100vh\] {
775771 height: 100vh;
776772}
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+}
777781.h-10 {
778782 height: 2.5rem;
779783}
@@ -830,15 +834,15 @@ Ensure the default browser behavior of the `hidden` attribute.
830834.h-60 {
831835 height: 15rem;
832836}
833−.min-h-0 {
834− min-height: 0px;
835−}
836837.min-h-\[20px\] {
837838 min-height: 20px;
838839}
839840.min-h-full {
840841 min-height: 100%;
841842}
843+.min-h-0 {
844+ min-height: 0px;
845+}
842846.min-h-\[14rem\] {
843847 min-height: 14rem;
844848}
@@ -850,9 +854,6 @@ Ensure the default browser behavior of the `hidden` attribute.
850854 width: -moz-min-content;
851855 width: min-content;
852856}
853−.w-5 {
854− width: 1.25rem;
855−}
856857.w-12 {
857858 width: 3rem;
858859}
@@ -865,6 +866,9 @@ Ensure the default browser behavior of the `hidden` attribute.
865866.w-\[100vw\] {
866867 width: 100vw;
867868}
869+.w-5 {
870+ width: 1.25rem;
871+}
868872.w-10 {
869873 width: 2.5rem;
870874}
@@ -929,9 +933,6 @@ Ensure the default browser behavior of the `hidden` attribute.
929933.max-w-xs {
930934 max-width: 20rem;
931935}
932−.max-w-\[256px\] {
933− max-width: 256px;
934−}
935936.max-w-sm {
936937 max-width: 24rem;
937938}
@@ -950,6 +951,9 @@ Ensure the default browser behavior of the `hidden` attribute.
950951.max-w-\[128px\] {
951952 max-width: 128px;
952953}
954+.max-w-\[256px\] {
955+ max-width: 256px;
956+}
953957.max-w-\[10rem\] {
954958 max-width: 10rem;
955959}
@@ -1075,6 +1079,10 @@ Ensure the default browser behavior of the `hidden` attribute.
10751079.auto-cols-fr {
10761080 grid-auto-columns: minmax(0, 1fr);
10771081}
1082+.auto-cols-max {
1083+ grid-auto-columns: -webkit-max-content;
1084+ grid-auto-columns: max-content;
1085+}
10781086.grid-flow-row {
10791087 grid-auto-flow: row;
10801088}
@@ -1091,6 +1099,13 @@ Ensure the default browser behavior of the `hidden` attribute.
10911099 grid-auto-rows: -webkit-min-content;
10921100 grid-auto-rows: min-content;
10931101}
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+}
10941109.grid-cols-3 {
10951110 grid-template-columns: repeat(3, minmax(0, 1fr));
10961111}
@@ -1157,21 +1172,21 @@ Ensure the default browser behavior of the `hidden` attribute.
11571172.gap-5 {
11581173 gap: 1.25rem;
11591174}
1160−.gap-1\.5 {
1161− gap: 0.375rem;
1162−}
1163−.gap-1 {
1164− gap: 0.25rem;
1165−}
11661175.gap-4 {
11671176 gap: 1rem;
11681177}
11691178.gap-2 {
11701179 gap: 0.5rem;
11711180}
1181+.gap-1 {
1182+ gap: 0.25rem;
1183+}
11721184.gap-3 {
11731185 gap: 0.75rem;
11741186}
1187+.gap-1\.5 {
1188+ gap: 0.375rem;
1189+}
11751190.gap-x-4 {
11761191 -moz-column-gap: 1rem;
11771192 column-gap: 1rem;
@@ -1299,9 +1314,6 @@ Ensure the default browser behavior of the `hidden` attribute.
12991314.break-all {
13001315 word-break: break-all;
13011316}
1302−.rounded {
1303− border-radius: 0.25rem;
1304−}
13051317.rounded-md {
13061318 border-radius: 0.375rem;
13071319}
@@ -1311,6 +1323,9 @@ Ensure the default browser behavior of the `hidden` attribute.
13111323.rounded-full {
13121324 border-radius: 9999px;
13131325}
1326+.rounded {
1327+ border-radius: 0.25rem;
1328+}
13141329.rounded-xl {
13151330 border-radius: 0.75rem;
13161331}
@@ -1331,6 +1346,10 @@ Ensure the default browser behavior of the `hidden` attribute.
13311346 border-top-left-radius: 1rem;
13321347 border-top-right-radius: 1rem;
13331348}
1349+.rounded-t-md {
1350+ border-top-left-radius: 0.375rem;
1351+ border-top-right-radius: 0.375rem;
1352+}
13341353.rounded-br-2xl {
13351354 border-bottom-right-radius: 1rem;
13361355}
@@ -1364,9 +1383,6 @@ Ensure the default browser behavior of the `hidden` attribute.
13641383.border-l-8 {
13651384 border-left-width: 8px;
13661385}
1367−.border-t {
1368− border-top-width: 1px;
1369−}
13701386.border-r {
13711387 border-right-width: 1px;
13721388}
@@ -1376,6 +1392,12 @@ Ensure the default browser behavior of the `hidden` attribute.
13761392.border-l {
13771393 border-left-width: 1px;
13781394}
1395+.border-t {
1396+ border-top-width: 1px;
1397+}
1398+.border-b-0 {
1399+ border-bottom-width: 0px;
1400+}
13791401.border-none {
13801402 border-style: none;
13811403}
@@ -1431,22 +1453,26 @@ Ensure the default browser behavior of the `hidden` attribute.
14311453 --tw-border-opacity: 1;
14321454 border-color: rgb(75 85 99 / var(--tw-border-opacity));
14331455}
1434−.bg-navy {
1435− --tw-bg-opacity: 1;
1436− background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1456+.border-gray-200 {
1457+ --tw-border-opacity: 1;
1458+ border-color: rgb(229 231 235 / var(--tw-border-opacity));
14371459}
14381460.bg-gray-400 {
14391461 --tw-bg-opacity: 1;
14401462 background-color: rgb(156 163 175 / var(--tw-bg-opacity));
14411463}
1442−.bg-gold {
1443− --tw-bg-opacity: 1;
1444− background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1445−}
14461464.bg-white {
14471465 --tw-bg-opacity: 1;
14481466 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
14491467}
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+}
14501476.bg-l1-blue {
14511477 --tw-bg-opacity: 1;
14521478 background-color: rgb(23 146 199 / var(--tw-bg-opacity));
@@ -1563,6 +1589,10 @@ Ensure the default browser behavior of the `hidden` attribute.
15631589 --tw-bg-opacity: 1;
15641590 background-color: rgb(254 226 226 / var(--tw-bg-opacity));
15651591}
1592+.bg-gray-600 {
1593+ --tw-bg-opacity: 1;
1594+ background-color: rgb(75 85 99 / var(--tw-bg-opacity));
1595+}
15661596.fill-transparent {
15671597 fill: transparent;
15681598}
@@ -1608,14 +1638,6 @@ Ensure the default browser behavior of the `hidden` attribute.
16081638 padding-top: 0.25rem;
16091639 padding-bottom: 0.25rem;
16101640}
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−}
16191641.px-4 {
16201642 padding-left: 1rem;
16211643 padding-right: 1rem;
@@ -1628,6 +1650,10 @@ Ensure the default browser behavior of the `hidden` attribute.
16281650 padding-left: 0px;
16291651 padding-right: 0px;
16301652}
1653+.py-0 {
1654+ padding-top: 0px;
1655+ padding-bottom: 0px;
1656+}
16311657.py-4 {
16321658 padding-top: 1rem;
16331659 padding-bottom: 1rem;
@@ -1660,6 +1686,10 @@ Ensure the default browser behavior of the `hidden` attribute.
16601686 padding-left: 0.375rem;
16611687 padding-right: 0.375rem;
16621688}
1689+.py-0\.5 {
1690+ padding-top: 0.125rem;
1691+ padding-bottom: 0.125rem;
1692+}
16631693.pt-10 {
16641694 padding-top: 2.5rem;
16651695}
@@ -1708,10 +1738,6 @@ Ensure the default browser behavior of the `hidden` attribute.
17081738.font-mono {
17091739 font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
17101740}
1711−.text-3xl {
1712− font-size: 1.875rem;
1713− line-height: 2.25rem;
1714−}
17151741.text-sm {
17161742 font-size: 0.875rem;
17171743 line-height: 1.25rem;
@@ -1728,6 +1754,10 @@ Ensure the default browser behavior of the `hidden` attribute.
17281754 font-size: 1.125rem;
17291755 line-height: 1.75rem;
17301756}
1757+.text-3xl {
1758+ font-size: 1.875rem;
1759+ line-height: 2.25rem;
1760+}
17311761.text-base {
17321762 font-size: 1rem;
17331763 line-height: 1.5rem;
@@ -1747,14 +1777,14 @@ Ensure the default browser behavior of the `hidden` attribute.
17471777.leading-3 {
17481778 line-height: .75rem;
17491779}
1750−.text-white {
1751− --tw-text-opacity: 1;
1752− color: rgb(255 255 255 / var(--tw-text-opacity));
1753−}
17541780.text-gray-600 {
17551781 --tw-text-opacity: 1;
17561782 color: rgb(75 85 99 / var(--tw-text-opacity));
17571783}
1784+.text-white {
1785+ --tw-text-opacity: 1;
1786+ color: rgb(255 255 255 / var(--tw-text-opacity));
1787+}
17581788.text-gray-800 {
17591789 --tw-text-opacity: 1;
17601790 color: rgb(31 41 55 / var(--tw-text-opacity));
@@ -1981,14 +2011,14 @@ Ensure the default browser behavior of the `hidden` attribute.
19812011 min-width: 256px;
19822012 }
19832013
1984− .md\:max-w-2xl {
1985− max-width: 42rem;
1986− }
1987−
19882014 .md\:max-w-xl {
19892015 max-width: 36rem;
19902016 }
19912017
2018+ .md\:max-w-2xl {
2019+ max-width: 42rem;
2020+ }
2021+
19922022 .md\:flex-row {
19932023 flex-direction: row;
19942024 }
ui/src/types/store.ts+15
@@ -1,5 +1,12 @@
11import type { ConnectionStatus } from './connection';
22import 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+}
310export interface Contacts {
411 [contact: string]: Contact;
512};
@@ -34,6 +41,13 @@ export interface PalInfo extends Pal {
3441 status: PalStatus;
3542}
3643
44+export interface Apps {
45+ allies: {
46+ [ally: string]: string[],
47+ },
48+ set: Set<string>,
49+}
50+
3751export interface StoreState {
3852 // Urbit
3953 // groups: {
@@ -43,6 +57,7 @@ export interface StoreState {
4357 peers: Patp[];
4458 pals: Pals;
4559 palsInstalled: boolean;
60+ apps: Apps,
4661 patpQuery: RegExp;
4762 sigilQuery: string[][];
4863 searchMode: ('patp' | 'sigil');