on main help sign in
improve star chart and update docs
commit46ff0d6e1f4608021170e4822a130c9493c28657 authorJohn Hyde <johnhydefour@gmail.com> dateWed 13 Jul 2022, 17:13 (4 years ago) parenta64ee97
24 files changed+681−237
desk/app/astrolabe.hoon+69−42 desk/desk.docket-0+1−1 desk/doc/usr/astrolabe.udon+17−1 desk/doc/usr/star.udon+7 desk/lib/astrolabe.hoon+53−51 desk/mar/astrolabe/chart-data.hoon−14 desk/sur/astrolabe.hoon+3−21 ui/src/components/ChartLink.svelte+11 ui/src/components/HelpModal.svelte+2−2 ui/src/components/LoadingSpinner.svelte+8 ui/src/components/SearchBar.svelte+1−1 ui/src/components/ShipView.svelte+12−1 ui/src/components/StarChart.svelte+98−28 ui/src/components/buttons/InlineSelect.svelte+16 ui/src/components/buttons/ToggleButton.svelte+4−3 ui/src/components/docs/DocContent.svelte+3−1 ui/src/lib/api.ts+4−2 ui/src/lib/colors.ts+4 ui/src/lib/graph.ts+187−41 ui/src/lib/id.ts+2 ui/src/lib/link.ts+7 ui/src/routes/Home.svelte+20−7 ui/src/styles/tailwind-output.css+151−21 ui/tailwind.config.cjs+1
desk/app/astrolabe.hoon+69−42
@@ -1,26 +1,35 @@
11/- *astrolabe
2−/+ *astrolabe, *search, naive, default-agent, dbug, agentio
2+/+ *astrolabe, *search, *mip, naive, default-agent, dbug, agentio
33/$ udon-to-docu %udon %docu
44|%
55+$ versioned-state
66 $% state-0
77 state-1
8+ state-2
89 ==
910+$ state-0
1011 $: %0
1112 spa=spawned
1213 ==
14++$ fropoints-1
15+ $: fopoints=opoints :: forward ordered points
16+ ropoints=opoints :: reversed ordered points, e.g. .~palnet-sampel
17+ ==
1318+$ state-1
1419 $: %1
15− fopoints=opoints :: forward ordered points
16− ropoints=opoints :: reversed ordered points, e.g. .~palnet-sampel
20+ fropoints-1
21+ ==
22++$ state-2
23+ $: %2
24+ spam=spawn-mip
25+ fropoints-1
1726 ==
1827::
1928+$ spawned (jug ship ship)
2029+$ card card:agent:gall
2130--
2231%- agent:dbug
23−=| state-1
32+=| state-2
2433=* state -
2534^- agent:gall
2635=<
@@ -39,11 +48,11 @@
3948 |= old-state=vase
4049 ^- (quip card _this)
4150 =/ old !<(versioned-state old-state)
42− =^ cards-0 old
43− ?. ?=(%0 -.old) `old
51+ =^ cards-01 old
52+ ?. ?=(?(%0 %1) -.old) `old
4453 (on-naive-state:hc get-nas:hc)
45− ?> ?=(%1 -.old)
46− [cards-0 this(state old)]
54+ ?> ?=(%2 -.old)
55+ [cards-01 this(state old)]
4756++ on-poke on-poke:def
4857++ on-watch on-watch:def
4958++ on-leave on-leave:def
@@ -66,7 +75,7 @@
6675 =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
6776 ``astrolabe-point-set+!>((turn ~(tap in peers) head))
6877 [%x %chart-data ~]
69− ``astrolabe-chart-data+!>(get-chart-data:hc)
78+ ``json+!>(get-chart-data:hc)
7079 [%x %doc ^]
7180 =/ doc-path=^path t.t.path
7281 =/ doc (read-doc:hc doc-path)
@@ -120,7 +129,7 @@
120129++ scrio ~(scry agentio bowl)
121130++ az-events-card [%pass /azimuth-events %agent [our.bowl %azimuth] %watch /event]
122131++ get-nas .^(^state:naive %gx (scrio %azimuth /nas/noun))
123−++ put-in-opoints
132+++ put-in-fropoints
124133 |= =ship
125134 ^- _state
126135 =. fopoints
@@ -128,6 +137,28 @@
128137 =. ropoints
129138 (put:ors ropoints (switch-words (to-q ship)) ~)
130139 state
140+::
141+++ put-in-spawn-mip
142+ |= [child=ship nas=^state:naive]
143+ ^- _state
144+ =/ =npoint (get-npoint-from-nas child nas)
145+ =. spam
146+ =/ parent (^sein:title child)
147+ ?: =(parent child) spam
148+ ?: (is-npoint-locked npoint) spam
149+ =/ pdata (pdata:smel (pmeta:smel child npoint) ~)
150+ =* spo sponsor.net.npoint
151+ =? spam &(has.spo !=(parent who.spo))
152+ %^ ~(put bi-meta spam)
153+ who.spo
154+ child
155+ pdata
156+ %^ ~(put bi-meta spam)
157+ parent
158+ child
159+ pdata
160+ state
161+::
131162++ on-naive-state
132163 |= nas=^state:naive
133164 ^- (quip card _state)
@@ -137,7 +168,8 @@
137168 ^- _state
138169 ?~ points state
139170 =/ =ship -.n.points
140− =. state (put-in-opoints ship)
171+ =. state (put-in-fropoints ship)
172+ =. state (put-in-spawn-mip ship nas)
141173 $(points l.points, state $(points r.points))
142174::
143175++ on-naive-diff
@@ -150,11 +182,15 @@
150182 :: %point " ship: {<ship.diff>} - {<+>-.diff>}"
151183 :: ==
152184 =? state ?=([%tx [* * * %spawn ship *] *] diff)
153− %- put-in-opoints
185+ %- put-in-fropoints
154186 ship.tx.raw-tx.diff
155187 =? state ?=([%point @ %owner *] diff)
156− %- put-in-opoints
188+ %- put-in-fropoints
157189 ship.diff
190+ =? state ?=([%point @ *] diff)
191+ %+ put-in-spawn-mip
192+ ship.diff
193+ get-nas
158194 `state
159195::
160196++ is-spawn-tx
@@ -181,6 +217,10 @@
181217 (sponsor-chain ship unpoint)
182218 (probable-dominion ship unpoint)
183219 ==
220+++ get-npoint-from-nas
221+ |= [=ship nas=^state:naive]
222+ ^- npoint
223+ (need (get:orm:naive points.nas ship))
184224::
185225++ run-spawned-points
186226 |* [agg=mold targ=ship nas=^state:naive filter=spawned-filter]
@@ -265,37 +305,24 @@
265305 $(ship parent, unpoint parent-point)
266306::
267307++ get-chart-data
268− ^- chart-data
308+ ^- json
269309 =/ nas get-nas
270− :: =/ galaxies (gulf 0 30) :: todo remove
271− %+ turn galaxies
272− |= =ship
273− =/ point (need (get:orm:naive points.nas ship))
274− (get-point-chart-data nas ship point)
275−::
276−++ get-point-chart-data
277− |= [nas=^state:naive =ship =npoint]
278− ^- point-chart-data
279− =, sponsor.net.npoint
280− =/ desc-has
281− ?. has %none
282− ?: =(who (^sein:title ship))
283− %same
284− %diff
285− :- :* ship
286− dominion.npoint
287− [desc-has who]
288− ==
289− (get-spawned-chart-data nas ship)
310+ =/ main-data
311+ ^- tape
312+ %- ~(rep by spam)
313+ |= [[=ship kids=(map ship tape)] agg=tape]
314+ ^- tape
315+ %+ weld agg
316+ %+ pdata:smel (ship:smel ship)
317+ (flop ~(val by kids))
318+ =/ gal-data
319+ %+ pdata:smel "base"
320+ %+ turn galaxies
321+ |= gal=ship
322+ =/ npoint (get-npoint-from-nas gal nas)
323+ (pdata:smel (pmeta:smel gal npoint) ~)
324+ s+(crip :(weld "[" gal-data main-data "]"))
290325::
291−++ get-spawned-chart-data
292− |= [nas=^state:naive =ship]
293− ^- chart-data
294− :: [*point-meta ~]~
295− %- (run-spawned-points chart-data ship nas %unlocked)
296− |= [agg=chart-data [spawn=^ship spawn-point=npoint]]
297− :_ agg
298− (get-point-chart-data nas spawn spawn-point)
299326++ read-doc
300327 |= =path
301328 =/ doc-path=^path :(weld /doc/usr path /udon)
desk/desk.docket-0+1−1
@@ -4,7 +4,7 @@
44 image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
55 glob-ames+[~dister-midlev-mindyr 0v0]
66 base+'astrolabe'
7− version+[0 1 4]
7+ version+[0 2 2]
88 website+'https://github.com/johnhyde/astrolabe'
99 license+'MIT'
1010==
desk/doc/usr/astrolabe.udon+17−1
@@ -26,10 +26,26 @@ if you search for `***hec`, it will search for all stars under `~hec`,
2626and all planets that match `~***hec-******`, but planets matching `~******-***hec`
2727will be ignored to prevent the search from taking too long.
2828
29+## The Star Chart
30+
31+Click and drag to move your view, use the mouse wheel to zoom in and out. Click on a dot
32+to see the full Ship View. Enter full `@p`s in the search bar to find a specific point.
33+
34+Please note that stars [locked in the Linear Release Contract](/~/scry/docs/usr/astrolabe/star.html)
35+are not shown in the Star Chart, as they are not yet a part of the network (though they've technically
36+been spawned).
37+
38+The Sponsors/Parents toggle lets you switch between two different views of the network.
39+The default Sponsors view shows each planet under its sponsoring star, and each
40+star under its sponsoring galaxy. Planets and Stars without sponsors are not
41+present in this view.
42+The Parent view simply shows each point under the point that spawned it. All spawned points
43+are shown in this view, with the exception of locked stars.
44+In both views, all galaxies are shown for completeness.
45+
2946## Upcoming Features
3047
3148In addition to improving the search feature over time, Astrolabe will one day feature:
32−- a Star Chart, where you can visually see the layout of the network and explore freely.
3349- Sigil Search, with which you can select the shapes of a sigil and search for ships
3450 that match.
3551- Integrations with Groups, `%pals`, `%gora`, and whatever else seems fun and/or useful.
desk/doc/usr/star.udon+7
@@ -12,3 +12,10 @@ Stars can also choose to de-sponsor planets which abuse the network.
1212
1313Stars are NFTs, but they can be exchanged 1:1 for WSTR at [star.market](https://star.market/),
1414and you can trade fractional amounts of WSTR for ETH [on Uniswap V3](https://info.uniswap.org/#/pools/0x7f4a0822a6e3c085df795a8d78acbcd36f210ebf).
15+
16+## Locked stars and the Linear Release Contract
17+
18+To prevent galaxies from spawning too many stars all at once, many galaxies' stars were locked
19+in an Ethereum contract, to be slowly released (at a linear rate) over time.
20+Stars which are locked in this way will show the contract address as their owner,
21+and any stars held by this contract are considered locked, even if they are ready to be released.
desk/lib/astrolabe.hoon+53−51
@@ -1,5 +1,5 @@
11/- sur=astrolabe
2−/+ azimuth
2+/+ azimuth, naive, mip
33=< [sur .]
44=, sur
55|%
@@ -25,8 +25,20 @@
2525 ^- ?
2626 (rev-f lte a b)
2727::
28−+$ opoints (tree [@ @]) :: ordered points [ship ~]
28++$ opoints (tree [@ @]) :: ordered points [ship ~]
2929++ ors ((on @q @) rpor)
30+++ or-meta ((on @p tape) por:naive)
31+++ bi-meta
32+ =| a=(map * (map))
33+ |@
34+ ++ put
35+ |* [b=* c=* d=*]
36+ %+ ~(put by a) b
37+ %+ put:or-meta
38+ (~(gut by a) b ~)
39+ [c d]
40+ --
41+::
3042+$ page-info [size=_100 page=_0]
3143++ fits-on-page
3244 |= [i=@ =page-info]
@@ -37,27 +49,50 @@
3749 =/ page-end (add page-start size)
3850 &((gte i page-start) (lth i page-end))
3951::
40−++ palor :: @p alphabetical order
41− ~/ %por
42− |= [a=@p b=@p]
43− ^- ?
44− ?: =(a b) &
45− =| i=@
46− |-
47− ?: =(i 2)
48− :: second two bytes
49− (lte a b)
50− :: first two bytes
51− =+ [c=(end 3 a) d=(end 3 b)]
52− ?: =(c d)
53− $(a (rsh 3 a), b (rsh 3 b), i +(i))
54− (lth c d)
55−::
5652++ is-npoint-locked
5753 |= p=npoint
5854 .= linear-star-release:contracts:azimuth
5955 address.owner.own.p
6056::
57+++ smel
58+ |%
59+ ++ ship
60+ |= =^ship
61+ ^- tape
62+ (slag 1 (scow %p ship))
63+ ++ dom
64+ |= =dominion:naive
65+ ?- dominion
66+ %l1 ""
67+ %l2 "t"
68+ %spawn "s"
69+ ==
70+ ++ spo
71+ |= [self=^ship has=? spo=^ship]
72+ ?. has "~"
73+ =/ parent (^sein:title self)
74+ ?: =(spo parent)
75+ ""
76+ :(weld ":" (ship spo) "!" (ship parent))
77+ ++ pmeta
78+ |= [self=^ship point=npoint]
79+ ^- tape
80+ =/ self-str (ship self)
81+ =/ extras (weld (dom dominion.point) (spo self sponsor.net.point))
82+ ?~ extras self-str
83+ :(weld self-str ":" extras)
84+ ++ spa
85+ |= children=(list tape)
86+ =/ chil-str=tape (zing children)
87+ :: =/ chil-str=tape (reel children |=([a=tape b=tape] (weld a b)))
88+ ?~ chil-str ";"
89+ :(weld "[" chil-str "]")
90+ ++ pdata
91+ |= [pmeta=tape children=(list tape)]
92+ ^- tape
93+ (weld pmeta (spa children))
94+ --
95+::
6196++ nu :: parse number as hex
6297 |= jon=json
6398 ?> ?=([%s *] jon)
@@ -143,39 +178,6 @@
143178 ~
144179 ?: =('.' i.b) $(b t.b)
145180 [i.b $(b t.b)]
146− ++ point-meta
147− |= m=^point-meta
148− ^- (list [@tas json])
149− =/ main-pairs=(list [@tas json])
150− :~
151− p+(ship ship.m)
152− :: spa-count+(numb spa-count.m)
153− :: [%d s+dom.m]
154− ==
155− =? main-pairs !=(dom.m %l1)
156− :_ main-pairs
157− d+s+dom.m
158− ?: =(has.spo.m %same) main-pairs
159− :_ main-pairs
160− :- %s
161− ?: =(has.spo.m %none) ~
162− (ship who.spo.m)
163− ::
164− ++ point-chart-data
165− |= d=^point-chart-data
166− ^- json
167− =/ point-meta-pairs (point-meta -.d)
168− %- pairs
169− ?~ spa.d point-meta-pairs
170− %+ snoc point-meta-pairs
171− c+(chart-data spa.d)
172− :: spa+a+~
173− ::
174− ++ chart-data
175− |= d=^chart-data
176− ^- json
177− :- %a
178− (turn d point-chart-data)
179181 --
180182::
181183++ dejs
desk/mar/astrolabe/chart-data.hoondeleted−14
@@ -1,14 +0,0 @@
1−/+ *astrolabe
2−|_ data=chart-data
3−++ grab
4− |%
5− ++ noun chart-data
6− :: ++ json point:dejs
7− --
8−++ grow
9− |%
10− ++ noun data
11− ++ json (chart-data:enjs data)
12− --
13−++ grad %noun
14−--
desk/sur/astrolabe.hoon+3−21
@@ -1,4 +1,4 @@
1−/+ naive
1+/+ naive, *mip
22|%
33+$ npoint point:naive
44++ unpoint (unit npoint)
@@ -11,24 +11,6 @@
1111 ==
1212+$ spawned-filter ?(%all %locked %unlocked)
1313::
14−+$ point-meta
15− $: =ship
16− :: spa-count=@ud
17− dom=dominion:naive
18− spo=[has=?(%none %same %diff) who=ship]
19− :: moons=(list ship)
20− ==
21−:: +$ point-chart-data
22−:: $% [%czar point-meta spa=chart-data]
23−:: [%king point-meta spa=chart-data]
24−:: [%duke point-meta]
25−:: ==
26−+$ point-chart-data [point-meta spa=chart-data]
27−:: +$ star-chart-data [self=point planets=(list point)]
28−:: +$ galaxy-chart-data stars=(list star-chart-data)
29−++ chart-data
30− |-
31− $@ ~
32− (list [point-meta spa=$])
33−
14++$ spawn-mip (mip ship ship tape)
15+::
3416--
ui/src/components/ChartLink.sveltenew file+11
@@ -0,0 +1,11 @@
1+<script lang="ts">
2+ import { link } from 'svelte-spa-router'
3+ import { linkToChart } from '../lib/link';
4+
5+ export let patp = null;
6+</script>
7+
8+<a href={linkToChart(patp)} use:link
9+ class="bg-navy text-white px-2 py-1 rounded no-underline">
10+ <slot/>
11+</a>
ui/src/components/HelpModal.svelte+2−2
@@ -12,8 +12,8 @@
1212</script>
1313
1414{#if $docs.show}
15− <div class="absolute w-full h-full bg-navy opacity-50 cursor-pointer z-10" on:click/>
16− <div class="absolute w-full h-full z-10 overflow-y-auto" on:click={close}>
15+ <div class="absolute w-full h-full bg-navy opacity-50 cursor-pointer z-50" on:click/>
16+ <div class="absolute w-full h-full z-50 overflow-y-auto" on:click={close}>
1717 <div class="mx-4 2xs:mx-8 md:max-w-xl md:mx-auto">
1818 <div class="py-4 2xs:py-8">
1919 <div on:click|stopPropagation>
ui/src/components/LoadingSpinner.sveltenew file+8
@@ -0,0 +1,8 @@
1+<script lang="ts">
2+ export let spinnerClass = "text-white";
3+</script>
4+
5+<svg class="animate-spin h-5 w-5 mx-auto {spinnerClass}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
6+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
7+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
8+</svg>
ui/src/components/SearchBar.svelte+1−1
@@ -9,7 +9,7 @@
99 export let pauseAnalysis: boolean = false;
1010 let debouncedSearch = search;
1111
12− const placeholderText = "Search by @p or #. '*' matches any character";
12+ export let placeholderText = "Search by @p or #. '*' matches any character";
1313
1414 $: {
1515 if (!pauseAnalysis) {
ui/src/components/ShipView.svelte+12−1
@@ -7,10 +7,12 @@
77 import GoldBadge from './GoldBadge.svelte';
88 import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
99 import ShipLink from './ShipLink.svelte';
10+ import ChartLink from './ChartLink.svelte';
1011 import EthAddressLink from './EthAddressLink.svelte';
1112 import CollapsibleContent from './CollapsibleContent.svelte';
1213 import ShipChain from './ShipChain.svelte';
1314 import ShipListings from './ShipListings.svelte';
15+ import LoadingSpinner from './LoadingSpinner.svelte';
1416
1517 export let patp: string;
1618
@@ -109,6 +111,7 @@
109111 $: {
110112 pointInfo = {};
111113 let spawnStatus: any,
114+ spawnedUnlocked: boolean = false,
112115 layer: any,
113116 life: number,
114117 rift: number,
@@ -138,8 +141,10 @@
138141 spawnStatus = spawnStatusOptions.locked;
139142 } else if (keysSet) {
140143 spawnStatus = spawnStatusOptions.spawned;
144+ spawnedUnlocked = true;
141145 } else {
142146 spawnStatus = spawnStatusOptions.spawnedNoKeys;
147+ spawnedUnlocked = true;
143148 }
144149 // const { sponsor } = npoint.net;
145150 // if (sponsor.has) {
@@ -164,6 +169,7 @@
164169 ...pointInfo,
165170 layer,
166171 spawnStatus,
172+ spawnedUnlocked,
167173 keysSet,
168174 life,
169175 rift,
@@ -216,6 +222,11 @@
216222 <ShipLink patp={parentChain.at(-1)}></ShipLink>
217223 {/if}
218224 </div>
225+ {#if pointInfo?.spawnedUnlocked}
226+ <div class="w-full text-center my-3">
227+ <ChartLink {patp}>View in Star Chart ↗</ChartLink>
228+ </div>
229+ {/if}
219230 <div class="mt-4 w-full break-words">
220231 {#if parentChain.length > 0}
221232 <p>
@@ -228,7 +239,7 @@
228239 <b slot="title">Azimuth Details</b>
229240 <div slot="content">
230241 {#await rawPointInfoPromise}
231− Loading Azimuth info...
242+ <LoadingSpinner spinnerClass="text-black" />
232243 {:then}
233244 {#if shipClass !== 'galaxy'}
234245 <p>
ui/src/components/StarChart.svelte+98−28
@@ -1,23 +1,35 @@
11<script lang="ts">
2+ import LoadingSpinner from './LoadingSpinner.svelte';
3+
24 import { push } from 'svelte-spa-router'
35
46 import Sigma from 'sigma';
57 import Graph from 'graphology';
68
79 import { clan } from 'urbit-ob';
10+ import DocLink from './docs/DocLink.svelte';
811 import { cite, normalizeId } from '../lib/id';
9− import { populateGraph } from '../lib/graph';
12+ import { parseChartData, populateGraph } from '../lib/graph';
1013 import { getChartData } from '../lib/api';
1114 import { linkToShip } from '../lib/link';
1215
16+ import InlineSelect from './buttons/InlineSelect.svelte';
17+
1318 export let patp: string;
1419 let s;
1520 let g = new Graph();
21+ let highlightedPatp;
22+
23+ let firstRender = true;
24+ let mouseDown = false;
25+ let isDragging = false;
1626
1727 function sigma(container: HTMLElement, params: any = {}) {
1828 try {
1929 window['s'] = s = new Sigma(g, container, {
2030 renderLabels: false,
31+ // hideEdgesOnMove: true,
32+ allowInvalidContainer: true,
2133 // labelColor: { color: WHITE },
2234 labelFont: 'monospace',
2335 labelRenderedSizeThreshold: 9,
@@ -25,9 +37,32 @@
2537 maxCameraRatio: 10,
2638 });
2739
28− s.on('downNode', ({ node }) => {
40+ s.on('clickNode', ({ node }) => {
2941 push(linkToShip(node));
3042 });
43+ s.on('afterRender', () => {
44+ if (firstRender) {
45+ firstRender = false;
46+ searchFunc();
47+ }
48+ });
49+ const mc = s.getMouseCaptor();
50+ mc.on("mousedown", () => {
51+ mouseDown = true;
52+ });
53+ mc.on('mousemove', () => {
54+ if (mouseDown) {
55+ isDragging = true;
56+ }
57+ });
58+ mc.on('mouseup', () => {
59+ if (!isDragging) {
60+ clearHighlight();
61+ }
62+ mouseDown = false;
63+ isDragging = false;
64+ })
65+
3166 } catch(e) {
3267 console.error('error in initializing sigma:', e);
3368 if (!params.retried) {
@@ -53,60 +88,70 @@
5388 rawChartData = { error };
5489 });
5590 }
56− $: rawChartData, populateGraph(g, rawChartData), s?.scheduleRefresh(), searchFunc();
91+ $: parsedChartData = parseChartData(rawChartData);
92+ $: [parsedChartData, bySponsor], populateGraph(g, parsedChartData, bySponsor, () => {
93+ searchFunc();
94+ });
95+
96+ function clearHighlight() {
97+ const id = highlightedPatp;
98+ if (id) {
99+ setTimeout(() => {
100+ s.graph.removeNodeAttribute(id, 'highlighted');
101+ if (id == highlightedPatp)
102+ highlightedPatp = null;
103+ });
104+ }
105+ }
57106
58− let highlightedPatp;
59107 function searchFunc() {
108+ // console.log('searching for ' + patp);
60109 if (!s) return;
61− if (highlightedPatp) {
62− s.graph.removeNodeAttribute(highlightedPatp, 'highlighted');
63− highlightedPatp = null;
64− }
110+ if (highlightedPatp !== patp) clearHighlight();
65111 if (!patp) return;
66112 let point = s.getNodeDisplayData(patp);
67113 if (!point) return;
68114 s.graph.setNodeAttribute(patp, 'highlighted', true);
69115 highlightedPatp = patp;
70116
71− let ratio = 0.05;
117+ let ratio = 0.03;
72118 if (clan(patp) === 'star') {
73− ratio = 0.035;
119+ ratio = 0.008;
74120 } else if (clan(patp) === 'planet') {
75− ratio = 0.0025;
121+ ratio = 0.0005;
76122 }
77123
78124 s.camera.animate({
79125 x: point.x,
80126 y: point.y,
81127 ratio: ratio,
82− // ratio: 1,
83128 angle: 0
84129 }, {
85130 duration: 500,
86131 });
87− s.scheduleRefresh();
88132 };
89133
90− $: s, patp, searchFunc();
134+ $: [s, patp], searchFunc();
91135
92136 let helpBoxExpanded = false;
93137 let helpBoxClasses = '';
94138 $: {
95139 helpBoxClasses = helpBoxExpanded ?
96− "" :
97− "absolute border-r";
98− // refreshGraph();
99− if (s) {
100− s.resize();
101− s.scheduleRefresh();
102− }
140+ "w-full" :
141+ "border-r";
103142 }
143+ let bySponsor = true;
104144</script>
105145
106−<div class="border border-gold bg-navy w-full grow flex flex-col">
107− <div class="flex flex-wrap {helpBoxClasses} border-b border-gold bg-navy text-white z-10 cursor-pointer justify-center px-2 py-1 gap-4"
146+<div class="border border-gold bg-navy w-full grow flex flex-col relative">
147+ <div class="flex flex-wrap absolute {helpBoxClasses} border-b border-gold bg-navy text-white z-20 cursor-pointer justify-items-center px-2 py-1 gap-4"
108148 on:click={() => helpBoxExpanded = !helpBoxExpanded }>
109149 {#if helpBoxExpanded}
150+ <DocLink modal path="astrolabe">
151+ <div class="text-center justify-self-start">
152+ About Astrolabe
153+ </div>
154+ </DocLink>
110155 <div class="flex flex-wrap items-center gap-2">
111156 <div class="w-3 h-3 rounded-full bg-white"></div>
112157 Galaxy
@@ -117,18 +162,43 @@
117162 </div>
118163 <div class="flex flex-wrap items-center gap-2">
119164 <div class="w-3 h-3 rounded-full bg-l1-blue"></div>
120− L1 Planet
165+ Planet (L1)
121166 </div>
122167 <div class="flex flex-wrap items-center gap-2">
123168 <div class="w-3 h-3 rounded-full bg-l2-green"></div>
124− L2 Planet
169+ Planet (L2)
125170 </div>
171+ {#if s.nodeDataCache['~' + window.ship]}
172+ <div class="flex flex-wrap items-center gap-2">
173+ <div class="w-3 h-3 rounded-full bg-you"></div>
174+ You
175+ </div>
176+ {/if}
126177 {:else}
127− Help ({patp})
178+ ?
128179 {/if}
129180 </div>
130− <div class="grow flex">
131− <div class="w-full" use:sigma>
181+ <div class="flex flex-wrap absolute right-0 border-l border-b border-gold bg-navy text-white z-10 cursor-pointer justify-center px-2 py-1 gap-4"
182+ on:click={() => push('/') }>
183+ X
184+ </div>
185+ <div class="flex flex-col absolute -bottom-2 inset-x-0 text-white z-10 2xs:-bottom-3.5">
186+ <!-- <div class="ml-8 px-4 py-1 flex gap-x-1 items-center text-white"> -->
187+ <!-- <span>Arrange by</span> -->
188+ <InlineSelect bind:value={bySponsor}
189+ options={[
190+ ['Sponsors', true],
191+ ['Parents', false]
192+ ]}
193+ ></InlineSelect>
194+ <!-- </div> -->
195+ </div>
196+ {#await chartDataPromise}
197+ <div class="flex items-center justify-center absolute inset-0">
198+ <LoadingSpinner/>
132199 </div>
200+ {/await}
201+ <div class="grow flex">
202+ <div class="w-full min-h-[20px]" use:sigma></div>
133203 </div>
134204</div>
ui/src/components/buttons/InlineSelect.sveltenew file+16
@@ -0,0 +1,16 @@
1+<script lang="ts">
2+ export let options: [string, any][];
3+ export let value: any;
4+ export let borderClass = "border-gold";
5+ export let onClass = "bg-navy";
6+ export let offClass = "bg-white";
7+</script>
8+
9+<div class="flex mx-auto border {borderClass} rounded divide-x divide-gold bg-navy">
10+ {#each options as option}
11+ <div class="px-1.5 cursor-pointer" class:text-gray-400={value !== option[1]} on:click={() => value = option[1]}>
12+ {option[0]}
13+ </div>
14+ {/each}
15+</div>
16+
ui/src/components/buttons/ToggleButton.svelte+4−3
@@ -1,10 +1,11 @@
11<script lang="ts">
22 export let on: boolean = true;
3+ export let borderClass = "border-navy";
4+ export let onClass = "bg-navy";
5+ export let offClass = "bg-white";
36</script>
47
58<button on:click
6− class="w-4 h-4 rounded-full border border-navy"
7− class:bg-white={!on}
8− class:bg-navy={on}
9+ class={`w-4 h-4 rounded-full border ${borderClass} ${on ? onClass : offClass}`}
910/>
1011
ui/src/components/docs/DocContent.svelte+3−1
@@ -4,6 +4,8 @@
44
55 import docLinks from "../../actions/docLinks";
66 import docs from "../../stores/docs";
7+
8+ import LoadingSpinner from "../LoadingSpinner.svelte";
79
810 export let path:string;
911
@@ -22,7 +24,7 @@
2224
2325<div class="doc">
2426 {#await docs.getDoc(path)}
25− <h3>Loading page...</h3>
27+ <LoadingSpinner spinnerClass="text-black" />
2628 {:then docHtml}
2729 <div use:docLinks={navigate}>
2830 {@html convertLinks(docHtml)}
ui/src/lib/api.ts+4−2
@@ -31,10 +31,12 @@ export async function searchPoints(search: string): Promise<Patp[]> {
3131 return points.map(normalizeId);
3232}
3333
34−export function getChartData(): Promise<any> {
35− return scry<any>(`/chart-data`);
34+export function getChartData(old = false): Promise<any> {
35+ return scry<any>('/chart-data' + (old ? '-old' : ''));
3636}
3737
38+window['gcd'] = getChartData;
39+
3840export function getDoc(path: string): Promise<string> {
3941 return scry<string>(`/doc/${path}`);
4042}
ui/src/lib/colors.ts+4
@@ -5,3 +5,7 @@ export const LGREY = '#BBB';
55export const DGREY = '#777';
66export const BLUE = '#1792c7';
77export const GREEN = '#17c76a';
8+export const YOU_ARE_HERE = '#802';
9+export const YOU = '#b01';
10+export const YOUR_STAR = '#d71';
11+export const YOUR_GAL = '#fb1';
ui/src/lib/graph.ts+187−41
@@ -1,8 +1,139 @@
11import Graph from 'graphology';
2−import { clan, sein, patp2dec, patp } from 'urbit-ob';
32import * as colors from './colors';
43
4+const example = '[0[3216291:t;9219219;4608:s[156546;1612196;219621:~;321965149:4608;]1280[4289594624;4278191360;426672256;4260168960;4260824320;4246668544;4246144256;4253746432;4235396352;4230415616;4225697024;4227007744;4219667712;4222616832;4224648448;4214490368;4215997696;4219143424;4237296896;]]]'
5+
6+export interface ChartPoint {
7+ patp: string,
8+ dominion: ('l1' | 'l2' | 'spawn'),
9+ sponsor?: string,
10+ parent?: string,
11+ children?: ChartPoint[],
12+ sponsees?: ChartPoint[],
13+}
14+
15+function parseMetaString(metaString: string): ChartPoint {
16+ let idString, subMetaString = '';
17+ const colIndex = metaString.indexOf(':');
18+ if (colIndex === -1) {
19+ idString = metaString;
20+ } else {
21+ idString = metaString.slice(0, colIndex);
22+ subMetaString = metaString.slice(colIndex + 1);
23+ }
24+ const meta: ChartPoint = {
25+ patp: '~' + idString,
26+ dominion: 'l1',
27+ }
28+ if (subMetaString) {
29+ const firstChar = subMetaString[0];
30+ if (firstChar === 't') {
31+ meta.dominion = 'l2';
32+ subMetaString = subMetaString.slice(1);
33+ } else if (firstChar === 's') {
34+ meta.dominion = 'spawn';
35+ subMetaString = subMetaString.slice(1);
36+ }
37+ if (subMetaString) {
38+ if (subMetaString[0] === '~') {
39+ meta.sponsor = null;
40+ } else {
41+ let [sponsor, parent] = subMetaString.slice(1).split('!');
42+ meta.sponsor = '~' + sponsor;
43+ meta.parent = '~' + parent;
44+ }
45+ }
46+ }
47+ return meta;
48+}
49+
50+function extractMeta(data: string): [ChartPoint, string] {
51+ const index = data.search(/[[;]/);
52+ if (index === -1) {
53+ throw new Error(`No point meta present in ${data}`);
54+ }
55+ const metaString = data.slice(0, index);
56+ data = data.slice(index);
57+ return [parseMetaString(metaString), data];
58+}
59+
60+function extractChildren(data: string, sup: string = undefined): [ChartPoint[], ChartPoint[], string] {
61+ if (data[0] === ';') return [[], [], data.slice(1)];
62+ if (data[0] !== '[') throw new Error('Invalid children in data: ' + data.slice(0, 20));
63+
64+ let endFound = false;
65+ let counter = 0;
66+ data = data.slice(1);
67+ const children = [];
68+ const sponsees = [];
69+ while (!endFound && data.length && counter < 20000) {
70+ if (data[0] === ']') {
71+ endFound = true;
72+ data = data.slice(1);
73+ } else {
74+ let sub;
75+ [sub, data] = extractChartPoint(data);
76+ if (!sub.parent || sub.parent === sup)
77+ children.push(sub);
78+ if (sub.sponsor === undefined || sub.sponsor === sup)
79+ sponsees.push(sub);
80+ }
81+ counter++;
82+ }
83+ return [children, sponsees, data];
84+}
85+
86+function extractChartPoint(data: string): [ChartPoint, string] {
87+ let point, children, sponsees;
88+ [point, data] = extractMeta(data);
89+ [children, sponsees, data] = extractChildren(data, point.patp);
90+ point.children = children;
91+ point.sponsees = sponsees;
92+ return [point, data];
93+}
94+
95+function linkChartData(points: ChartPoint[], store: any): ChartPoint[] {
96+ if (!points) return points;
97+ return points.map(point => {
98+ if (!store[point.patp]) return point;
99+ if (!point.children?.length) {
100+ point.children = linkChartData(store[point.patp].children, store);
101+ }
102+ if (!point.sponsees?.length) {
103+ point.sponsees = linkChartData(store[point.patp].sponsees, store);
104+ }
105+ return point;
106+ });
107+}
108+
109+function _parseChartData(str: string): ChartPoint[] {
110+ if (!str) return [];
111+ let [baseChildren, _] = extractChildren(str);
112+ let store = {};
113+ baseChildren.forEach(child => {
114+ store[child.patp] = {
115+ children: child.children,
116+ sponsees: child.sponsees,
117+ };
118+ });
119+
120+ return linkChartData(store['~base'].children, store);
121+}
122+
123+export function parseChartData(data: string): ChartPoint[] {
124+ let start = Date.now();
125+ let parsed = _parseChartData(data);
126+ // console.log(`finished parsing chart data to js object in ${Date.now() - start} ms`);
127+ return parsed;
128+}
129+
5130const circle = 2 * Math.PI;
131+function chordLength(radius, radians) {
132+ return 2 * radius * Math.sin(radians / 2);
133+}
134+function distancePolar(r1, th1, r2, th2) {
135+ return Math.sqrt((r1 * r1) + (r2 * r2) - (2 * r1 * r2 * Math.cos(th2 - th1)));
136+}
6137export function arrangePointsInSpirals(
7138 numPoints,
8139 {
@@ -10,18 +141,22 @@ export function arrangePointsInSpirals(
10141 startX = 0, startY = 0,
11142 maxArms = 6, maxPerArm = 45,
12143 makeSingleArmCircle = false,
13− }): { coords: any[], numArms: number } {
144+ }): { coords: any[], numArms: number, minDistanceBetween: number } {
14145 const numSmallEnoughArms = Math.ceil(numPoints / maxPerArm);
15146 let eRootNumArms = Math.max(1, Math.floor(Math.pow(numPoints, 1 / Math.E)));
16147 const numArms = Math.max(numSmallEnoughArms, eRootNumArms);
17148 const singleArmCircle = makeSingleArmCircle && numArms === 1;
18149 const numPerArm = numPoints / numArms;
19− // todo refine theta range, and rename maxPerArm (misnomer)
20150 let armThetaRange = numPerArm * Math.min(Math.PI / 12, 0.25 * circle / numArms);
21151 if (singleArmCircle) {
22152 minDistance = maxDistance;
23− armThetaRange = 2 * Math.PI;
153+ armThetaRange = circle;
24154 }
155+ const distanceRange = maxDistance - minDistance;
156+ const pointWeight = 1 / numPerArm;
157+ let minDistanceBetween = distancePolar(minDistance, 0,
158+ minDistance + distanceRange * pointWeight, armThetaRange * pointWeight);
159+ if (numPoints <= 3) minDistanceBetween = maxDistance * 0.75;
25160 const coords = [];
26161 let lastArm = null;
27162 let indexInArm = null;
@@ -34,15 +169,11 @@ export function arrangePointsInSpirals(
34169 indexInArm = 0;
35170 }
36171 const interArmProgress = arm / numArms;
37− // (i - (numarms * i/numpoints) * (numpoints/numarms)) / numperarm
38− // const intraArmProgress = (i - arm * numPerArm) / numPerArm;
39172 const beginningOfNextArm = Math.floor(numPerArm * (arm + 1));
40173 const numInThisArm = beginningOfNextArm - (i - indexInArm);
41174 const intraArmProgress = indexInArm / numInThisArm;
42− // const intraArmProgress = (numArms * overallProgress) % 1;
43175 const theta = circle * interArmProgress + armThetaRange * intraArmProgress;
44− let baseLine = maxDistance - minDistance;
45− let line = (minDistance + baseLine * intraArmProgress);
176+ let line = (minDistance + distanceRange * intraArmProgress);
46177 let x = 0 + startX + line * Math.cos(theta);
47178 let y = 0 + startY + line * Math.sin(theta);
48179 coords.push({
@@ -54,17 +185,19 @@ export function arrangePointsInSpirals(
54185 return {
55186 coords,
56187 numArms,
188+ minDistanceBetween,
57189 };
58190}
59191
60−export
61−
62−function populateGraph(g: Graph, rawChartData: any[]) {
63− if (!(rawChartData instanceof Array)) return;
192+export function populateGraph(g: Graph, chartData: ChartPoint[], bySponsor: boolean, callback?): Graph {
193+ if (!(chartData instanceof Array)) return;
64194 if (!g) {
65195 g = new Graph();
196+ } else {
197+ g.clear();
66198 }
67199
200+ let subsKey = bySponsor ? 'sponsees' : 'children';
68201 let sponsorEdges = [];
69202
70203 let offsetX = 0;
@@ -74,34 +207,35 @@ function populateGraph(g: Graph, rawChartData: any[]) {
74207 let b = 9000000;
75208 let theta = 0;
76209
77− rawChartData.forEach((galaxy, gi) => {
210+ chartData.forEach((galaxy, gi) => {
78211 offsetX = 0 + (a + b * theta) * Math.cos(theta);
79212 offsetY = 0 + (a + b * theta) * Math.sin(theta);
80213
81214 theta = 0.15 * (gi+1) * (1 - .15 * (gi / 256));
82− const gPatp = '~' + galaxy.p;
215+ const gPatp = galaxy.patp;
216+ const itsYou = gPatp === '~' + window.ship;
83217 g.addNode(gPatp, {
84− // id: gPatp,
85218 label: gPatp,
86219 size: 3,
87− color: colors.WHITE,
88− // color: colors.'#fcd34d',
220+ color: itsYou ? colors.YOU : colors.WHITE,
89221 x: offsetX,
90222 y: offsetY,
91223 zIndex: 10,
92224 });
93225
94− const stars = galaxy.c || [];
226+ const stars = galaxy[subsKey] || [];
95227 const numStars = stars.length;
96228 const maxStarDistance = 60000 * 128;
229+ const minStarDistance = maxStarDistance / 4;
97230 const starSize = 1.2 / Math.pow(numStars, 1 / 7);
98231 const {
99232 coords: starCoords,
100233 numArms: numStarArms,
234+ minDistanceBetween: minDistanceBetweenStars,
101235 } = arrangePointsInSpirals(numStars,
102236 {
103237 maxDistance: maxStarDistance,
104− minDistance: maxStarDistance / 4,
238+ minDistance: minStarDistance,
105239 startX: offsetX,
106240 startY: offsetY,
107241 makeSingleArmCircle: true,
@@ -109,15 +243,19 @@ function populateGraph(g: Graph, rawChartData: any[]) {
109243 let lastStarArm = null;
110244 let lastStarPatp = null;
111245 stars.forEach((star, si) => {
112− const sPatp = '~' + star.p;
246+ const sPatp = star.patp;
113247
114248 const [starX, starY] = starCoords[si].coords;
115249 const arm = starCoords[si].arm;
250+
251+ const itsYou = sPatp === '~' + window.ship;
252+ if (itsYou) {
253+ g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
254+ }
116255 g.addNode(sPatp, {
117256 label: sPatp,
118257 size: starSize,
119− // color: colors.arm % 2 == 0 ? LGREY : DGREY,
120− color: colors.GOLDT2,
258+ color: itsYou ? colors.YOU : colors.GOLDT2,
121259 x: starX,
122260 y: starY,
123261 zIndex: 8,
@@ -137,24 +275,27 @@ function populateGraph(g: Graph, rawChartData: any[]) {
137275 }
138276 lastStarArm = arm;
139277 lastStarPatp = sPatp;
140− if (star.s) {
141− sponsorEdges.push([sPatp, '~' + star.s, {
278+ if (star.sponsor) {
279+ sponsorEdges.push([sPatp, star.sponsor, {
142280 color: colors.GREEN,
143281 size: 0.2,
144282 zIndex: 11,
145283 }]);
146284 }
147285
148− const planets = star.c || [];
286+ const planets = star[subsKey] || [];
149287 const numPlanets = planets.length;
150− const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
288+ // const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
289+ const maxPlanetDistance = minDistanceBetweenStars * 0.45;
290+ const minPlanetDistance = maxPlanetDistance / 4;
151291 const {
152292 coords: planetCoords,
153293 numArms: numPlanetArms,
294+ minDistanceBetween: minDistanceBetweenPlanets,
154295 } = arrangePointsInSpirals(numPlanets,
155296 {
156297 maxDistance: maxPlanetDistance,
157− minDistance: maxPlanetDistance / 4,
298+ minDistance: minPlanetDistance,
158299 startX: starX,
159300 startY: starY,
160301 maxArms: 30,
@@ -163,18 +304,20 @@ function populateGraph(g: Graph, rawChartData: any[]) {
163304 let lastPlanetArm = null;
164305 let lastPlanetPatp = null;
165306 planets.forEach((planet, pi) => {
166− const pPatp = '~' + planet.p;
167− // const circle = 2 * Math.PI;
168− // let line = 16000 * 64;
169− // let planetX = 0 + starX + Math.cos(circle * pi / numPlanets) * line;
170− // let planetY = 0 + starY + Math.sin(circle * pi / numPlanets) * line;
171−
307+ const pPatp = planet.patp;
172308 const [planetX, planetY] = planetCoords[pi].coords;
173309 const arm = planetCoords[pi].arm;
310+ const planetSize = Math.sqrt(Math.min(minPlanetDistance, minDistanceBetweenPlanets)) / 1300;
311+ const planetEdgeSize = planetSize / 3;
312+ const itsYou = pPatp === '~' + window.ship;
313+ if (itsYou) {
314+ g.setNodeAttribute(sPatp, 'color', colors.YOUR_STAR);
315+ g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
316+ }
174317 g.addNode(pPatp, {
175318 label: pPatp,
176− size: .15,
177− color: planet.d == 'l2' ? colors.GREEN : colors.BLUE,
319+ size: planetSize,
320+ color: itsYou ? colors.YOU : (planet.dominion == 'l2' ? colors.GREEN : colors.BLUE),
178321 x: planetX,
179322 y: planetY,
180323 zIndex: 6,
@@ -182,22 +325,22 @@ function populateGraph(g: Graph, rawChartData: any[]) {
182325 if (lastPlanetArm == arm) {
183326 g.addEdge(pPatp, lastPlanetPatp, {
184327 color: colors.DGREY,
185− size: 0.05,
328+ size: planetEdgeSize,
186329 zIndex: 5,
187330 });
188331 } else {
189332 if (numPlanetArms == 1) {
190333 g.addEdge(pPatp, sPatp, {
191334 color: colors.DGREY,
192− size: 0.05,
335+ size: planetEdgeSize,
193336 zIndex: 5,
194337 });
195338 }
196339 }
197− if (planet.s) {
198− sponsorEdges.push([pPatp, '~' + planet.s, {
340+ if (planet.sponsor) {
341+ sponsorEdges.push([pPatp, planet.sponsor, {
199342 color: colors.GREEN,
200− size: 0.05,
343+ size: planetEdgeSize,
201344 zIndex: 11,
202345 }]);
203346 }
@@ -209,5 +352,8 @@ function populateGraph(g: Graph, rawChartData: any[]) {
209352 sponsorEdges.forEach(([point, sponsor, attrs]) => {
210353 // g.addEdge(point, sponsor, attrs);
211354 });
355+ if (callback) {
356+ setTimeout(callback, 0);
357+ }
212358 return g;
213359}
ui/src/lib/id.ts+2
@@ -498,6 +498,8 @@ function analyzeSearch(search: string): SearchAnalysis {
498498}
499499
500500export {
501+ prefixes,
502+ suffixes,
501503 SearchAnalysis,
502504 normalizeId,
503505 normalizeIdAndDesig,
ui/src/lib/link.ts+7
@@ -3,3 +3,10 @@ import { buildQuerystring } from './utils';
33export function linkToShip(patp: string, queryParams = {}): string {
44 return `/ship/${patp}` + buildQuerystring(queryParams);
55}
6+
7+export function linkToChart(patp: string): string {
8+ if (patp) {
9+ return `/chart/${patp}`;
10+ }
11+ return `/chart`;
12+}
ui/src/routes/Home.svelte+20−7
@@ -3,7 +3,7 @@
33 import { link, push, replace, location, querystring } from 'svelte-spa-router'
44 import pageName from '../stores/pageName';
55 import { SearchAnalysis } from '../lib/id';
6− import { linkToShip } from '../lib/link';
6+ import { linkToShip, linkToChart } from '../lib/link';
77 import SearchBar from "../components/SearchBar.svelte";
88 import SearchResults from "../components/SearchResults.svelte";
99 import ShipView from '../components/ShipView.svelte';
@@ -57,6 +57,11 @@
5757 nav(`/search/${search}`, addToHistory);
5858 }
5959 }
60+ function navToChart(addToHistory = true) {
61+ params.mode = 'chart';
62+ params.arg = search;
63+ nav(linkToChart(search), addToHistory);
64+ }
6065
6166 function navToMode(mode, addToHistory = false) {
6267 switch (mode) {
@@ -72,15 +77,19 @@
7277 }
7378 // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
7479 function autoNav() {
75− if (params.mode !== 'chart' && search !== urlSearch) {
80+ if (search !== urlSearch) {
7681 if (urlChangedSinceAnalysisPatp) {
7782 search = urlSearch;
7883 } else if (analysis.search === search) {
7984 navigating = true;
80− if (analysis.patpIsValid) {
81− navToShipView(true);
85+ if (params.mode === 'chart') {
86+ navToChart(analysis.patpIsValid);
8287 } else {
83− navToSearch(params.mode === 'ship');
88+ if (analysis.patpIsValid) {
89+ navToShipView(true);
90+ } else {
91+ navToSearch(params.mode === 'ship');
92+ }
8493 }
8594 }
8695 }
@@ -102,8 +111,10 @@
102111 }
103112 }
104113
114+ let placeholderText;
105115 $: {
106116 if (analysis.search === urlSearch) {
117+ placeholderText = undefined;
107118 switch (params.mode) {
108119 case 'ship':
109120 $pageName = params.arg;
@@ -113,6 +124,8 @@
113124 break;
114125 case 'chart':
115126 $pageName = 'Star Chart';
127+ placeholderText = 'Search by @p or #.';
128+ break;
116129 default:
117130 $pageName = null;
118131 }
@@ -122,8 +135,8 @@
122135
123136</script>
124137
125−<div class="p-4 min-h-full flex flex-col items-center space-y-8 2xs:p-8">
126− <SearchBar bind:analysis bind:search />
138+<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
139+ <SearchBar bind:analysis bind:search {placeholderText} />
127140 <!-- <p class="bg-white">navigating</p> -->
128141 {#if params.mode == "chart"}
129142 <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
ui/src/styles/tailwind-output.css+151−21
@@ -487,18 +487,60 @@ Ensure the default browser behavior of the `hidden` attribute.
487487.relative {
488488 position: relative;
489489}
490+.inset-0 {
491+ top: 0px;
492+ right: 0px;
493+ bottom: 0px;
494+ left: 0px;
495+}
496+.inset-x-0 {
497+ left: 0px;
498+ right: 0px;
499+}
500+.inset-x-auto {
501+ left: auto;
502+ right: auto;
503+}
504+.inset-x-full {
505+ left: 100%;
506+ right: 100%;
507+}
490508.right-0 {
491509 right: 0px;
492510}
511+.-bottom-2 {
512+ bottom: -0.5rem;
513+}
493514.top-0 {
494515 top: 0px;
495516}
517+.bottom-0 {
518+ bottom: 0px;
519+}
520+.-bottom-12 {
521+ bottom: -3rem;
522+}
523+.-bottom-8 {
524+ bottom: -2rem;
525+}
526+.-bottom-4 {
527+ bottom: -1rem;
528+}
529+.-bottom-5 {
530+ bottom: -1.25rem;
531+}
532+.-bottom-3 {
533+ bottom: -0.75rem;
534+}
496535.z-10 {
497536 z-index: 10;
498537}
499538.z-20 {
500539 z-index: 20;
501540}
541+.z-50 {
542+ z-index: 50;
543+}
502544.m-4 {
503545 margin: 1rem;
504546}
@@ -512,6 +554,10 @@ Ensure the default browser behavior of the `hidden` attribute.
512554 margin-left: 1rem;
513555 margin-right: 1rem;
514556}
557+.mx-auto {
558+ margin-left: auto;
559+ margin-right: auto;
560+}
515561.mx-8 {
516562 margin-left: 2rem;
517563 margin-right: 2rem;
@@ -524,9 +570,9 @@ Ensure the default browser behavior of the `hidden` attribute.
524570 margin-top: 0.25rem;
525571 margin-bottom: 0.25rem;
526572}
527−.mx-auto {
528− margin-left: auto;
529− margin-right: auto;
573+.my-3 {
574+ margin-top: 0.75rem;
575+ margin-bottom: 0.75rem;
530576}
531577.my-4 {
532578 margin-top: 1rem;
@@ -544,6 +590,10 @@ Ensure the default browser behavior of the `hidden` attribute.
544590 margin-top: 0px;
545591 margin-bottom: 0px;
546592}
593+.my-auto {
594+ margin-top: auto;
595+ margin-bottom: auto;
596+}
547597.mt-2 {
548598 margin-top: 0.5rem;
549599}
@@ -568,6 +618,9 @@ Ensure the default browser behavior of the `hidden` attribute.
568618.mt-5 {
569619 margin-top: 1.25rem;
570620}
621+.ml-8 {
622+ margin-left: 2rem;
623+}
571624.-mt-1 {
572625 margin-top: -0.25rem;
573626}
@@ -595,6 +648,12 @@ Ensure the default browser behavior of the `hidden` attribute.
595648.mt-20 {
596649 margin-top: 5rem;
597650}
651+.-ml-1 {
652+ margin-left: -0.25rem;
653+}
654+.mr-3 {
655+ margin-right: 0.75rem;
656+}
598657.-mt-0\.5 {
599658 margin-top: -0.125rem;
600659}
@@ -625,6 +684,9 @@ Ensure the default browser behavior of the `hidden` attribute.
625684.h-full {
626685 height: 100%;
627686}
687+.h-5 {
688+ height: 1.25rem;
689+}
628690.h-10 {
629691 height: 2.5rem;
630692}
@@ -658,6 +720,9 @@ Ensure the default browser behavior of the `hidden` attribute.
658720.h-24 {
659721 height: 6rem;
660722}
723+.min-h-\[20px\] {
724+ min-height: 20px;
725+}
661726.min-h-full {
662727 min-height: 100%;
663728}
@@ -667,6 +732,9 @@ Ensure the default browser behavior of the `hidden` attribute.
667732.w-full {
668733 width: 100%;
669734}
735+.w-5 {
736+ width: 1.25rem;
737+}
670738.w-10 {
671739 width: 2.5rem;
672740}
@@ -741,6 +809,22 @@ Ensure the default browser behavior of the `hidden` attribute.
741809.transform {
742810 transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
743811}
812+@-webkit-keyframes spin {
813+
814+ to {
815+ transform: rotate(360deg);
816+ }
817+}
818+@keyframes spin {
819+
820+ to {
821+ transform: rotate(360deg);
822+ }
823+}
824+.animate-spin {
825+ -webkit-animation: spin 1s linear infinite;
826+ animation: spin 1s linear infinite;
827+}
744828.cursor-pointer {
745829 cursor: pointer;
746830}
@@ -765,6 +849,9 @@ Ensure the default browser behavior of the `hidden` attribute.
765849.justify-center {
766850 justify-content: center;
767851}
852+.justify-items-center {
853+ justify-items: center;
854+}
768855.gap-5 {
769856 gap: 1.25rem;
770857}
@@ -784,6 +871,10 @@ Ensure the default browser behavior of the `hidden` attribute.
784871 -moz-column-gap: 0.5rem;
785872 column-gap: 0.5rem;
786873}
874+.gap-x-1 {
875+ -moz-column-gap: 0.25rem;
876+ column-gap: 0.25rem;
877+}
787878.gap-x-3 {
788879 -moz-column-gap: 0.75rem;
789880 column-gap: 0.75rem;
@@ -827,6 +918,10 @@ Ensure the default browser behavior of the `hidden` attribute.
827918 border-right-width: calc(1px * var(--tw-divide-x-reverse));
828919 border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
829920}
921+.divide-gold > :not([hidden]) ~ :not([hidden]) {
922+ --tw-divide-opacity: 1;
923+ border-color: rgb(252 211 77 / var(--tw-divide-opacity));
924+}
830925.justify-self-start {
831926 justify-self: start;
832927}
@@ -851,6 +946,9 @@ Ensure the default browser behavior of the `hidden` attribute.
851946.break-all {
852947 word-break: break-all;
853948}
949+.rounded {
950+ border-radius: 0.25rem;
951+}
854952.rounded-md {
855953 border-radius: 0.375rem;
856954}
@@ -860,12 +958,12 @@ Ensure the default browser behavior of the `hidden` attribute.
860958.rounded-full {
861959 border-radius: 9999px;
862960}
863−.rounded {
864− border-radius: 0.25rem;
865−}
866961.rounded-xl {
867962 border-radius: 0.75rem;
868963}
964+.rounded-sm {
965+ border-radius: 0.125rem;
966+}
869967.border {
870968 border-width: 1px;
871969}
@@ -881,15 +979,15 @@ Ensure the default browser behavior of the `hidden` attribute.
881979.border-t {
882980 border-top-width: 1px;
883981}
982+.border-r {
983+ border-right-width: 1px;
984+}
884985.border-b {
885986 border-bottom-width: 1px;
886987}
887988.border-l {
888989 border-left-width: 1px;
889990}
890−.border-r {
891− border-right-width: 1px;
892−}
893991.border-gold {
894992 --tw-border-opacity: 1;
895993 border-color: rgb(252 211 77 / var(--tw-border-opacity));
@@ -914,14 +1012,14 @@ Ensure the default browser behavior of the `hidden` attribute.
9141012 --tw-border-opacity: 1;
9151013 border-color: rgb(156 163 175 / var(--tw-border-opacity));
9161014}
917−.bg-gold {
918− --tw-bg-opacity: 1;
919− background-color: rgb(252 211 77 / var(--tw-bg-opacity));
920−}
9211015.bg-navy {
9221016 --tw-bg-opacity: 1;
9231017 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
9241018}
1019+.bg-gold {
1020+ --tw-bg-opacity: 1;
1021+ background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1022+}
9251023.bg-white {
9261024 --tw-bg-opacity: 1;
9271025 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@@ -938,6 +1036,10 @@ Ensure the default browser behavior of the `hidden` attribute.
9381036 --tw-bg-opacity: 1;
9391037 background-color: rgb(23 199 106 / var(--tw-bg-opacity));
9401038}
1039+.bg-you {
1040+ --tw-bg-opacity: 1;
1041+ background-color: rgb(187 0 17 / var(--tw-bg-opacity));
1042+}
9411043.bg-orange-gold {
9421044 --tw-bg-opacity: 1;
9431045 background-color: rgb(247 211 146 / var(--tw-bg-opacity));
@@ -1035,14 +1137,18 @@ Ensure the default browser behavior of the `hidden` attribute.
10351137.p-1 {
10361138 padding: 0.25rem;
10371139}
1038−.py-0\.5 {
1039− padding-top: 0.125rem;
1040− padding-bottom: 0.125rem;
1041−}
10421140.px-2 {
10431141 padding-left: 0.5rem;
10441142 padding-right: 0.5rem;
10451143}
1144+.py-1 {
1145+ padding-top: 0.25rem;
1146+ padding-bottom: 0.25rem;
1147+}
1148+.py-0\.5 {
1149+ padding-top: 0.125rem;
1150+ padding-bottom: 0.125rem;
1151+}
10461152.py-0 {
10471153 padding-top: 0px;
10481154 padding-bottom: 0px;
@@ -1071,10 +1177,6 @@ Ensure the default browser behavior of the `hidden` attribute.
10711177 padding-left: 2rem;
10721178 padding-right: 2rem;
10731179}
1074−.py-1 {
1075− padding-top: 0.25rem;
1076− padding-bottom: 0.25rem;
1077−}
10781180.px-0\.5 {
10791181 padding-left: 0.125rem;
10801182 padding-right: 0.125rem;
@@ -1091,6 +1193,10 @@ Ensure the default browser behavior of the `hidden` attribute.
10911193 padding-left: 0.25rem;
10921194 padding-right: 0.25rem;
10931195}
1196+.px-1\.5 {
1197+ padding-left: 0.375rem;
1198+ padding-right: 0.375rem;
1199+}
10941200.pt-10 {
10951201 padding-top: 2.5rem;
10961202}
@@ -1183,6 +1289,10 @@ Ensure the default browser behavior of the `hidden` attribute.
11831289 --tw-text-opacity: 1;
11841290 color: rgb(55 65 81 / var(--tw-text-opacity));
11851291}
1292+.text-black {
1293+ --tw-text-opacity: 1;
1294+ color: rgb(0 0 0 / var(--tw-text-opacity));
1295+}
11861296.text-green-600 {
11871297 --tw-text-opacity: 1;
11881298 color: rgb(22 163 74 / var(--tw-text-opacity));
@@ -1229,6 +1339,12 @@ Ensure the default browser behavior of the `hidden` attribute.
12291339.opacity-50 {
12301340 opacity: 0.5;
12311341}
1342+.opacity-25 {
1343+ opacity: 0.25;
1344+}
1345+.opacity-75 {
1346+ opacity: 0.75;
1347+}
12321348.opacity-10 {
12331349 opacity: 0.1;
12341350}
@@ -1252,6 +1368,14 @@ Ensure the default browser behavior of the `hidden` attribute.
12521368}
12531369@media (min-width: 360px) {
12541370
1371+ .\32xs\:-bottom-3\.5 {
1372+ bottom: -0.875rem;
1373+ }
1374+
1375+ .\32xs\:-bottom-3 {
1376+ bottom: -0.75rem;
1377+ }
1378+
12551379 .\32xs\:m-8 {
12561380 margin: 2rem;
12571381 }
@@ -1261,6 +1385,12 @@ Ensure the default browser behavior of the `hidden` attribute.
12611385 margin-right: 2rem;
12621386 }
12631387
1388+ .\32xs\:space-y-8 > :not([hidden]) ~ :not([hidden]) {
1389+ --tw-space-y-reverse: 0;
1390+ margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
1391+ margin-bottom: calc(2rem * var(--tw-space-y-reverse));
1392+ }
1393+
12641394 .\32xs\:p-8 {
12651395 padding: 2rem;
12661396 }
ui/tailwind.config.cjs+1
@@ -14,6 +14,7 @@ module.exports = {
1414 'orange-gold': '#f7d392',
1515 'l1-blue': '#1792c7',
1616 'l2-green': '#17c76a',
17+ 'you': '#b01',
1718 'gold-s1': '#e3be45',
1819 'gold-s2': '#caa93e',
1920 'gold-s3': '#b09436',