on main help sign in
Merge pull request #7 from johnhyde/sigil-search

Sigil Search

commit504a7a86b0dd6875e0de8c2bc4e981ed4412e757 authorJohn Hyde <johnhydefour@gmail.com> dateFri 30 Sep 2022, 03:38 (4 years ago) parent46ff0d6994efea
94 files changed+2824−463
.gitignore+1 .vscode/tasks.json+13 README.md+32−25 base-desk/lib/bip/b173.hoon+1−1 base-desk/lib/bip/b174.hoon+2 base-desk/lib/cram.hoon−1 base-desk/lib/ethereum.hoon+19−1 base-desk/lib/hark-store.hoon+22 base-desk/lib/ph/io.hoon+14−2 base-desk/lib/ph/util.hoon+1−1 base-desk/lib/pill.hoon+3−3 base-desk/lib/ring.hoon+11−10 base-desk/lib/story.hoon+112 base-desk/lib/strandio.hoon+3−3 base-desk/lib/test.hoon+1−1 base-desk/mar/css.hoon+21 base-desk/mar/story-diff.hoon+16 base-desk/mar/story.hoon+70 base-desk/mar/thread-done.hoon+11 base-desk/mar/thread-fail.hoon+11 base-desk/mar/udon.hoon−1 base-desk/sur/aquarium.hoon+10−1 base-desk/sur/bitcoin.hoon+1−1 base-desk/sur/hood.hoon+4−14 base-desk/sur/spider.hoon+7 base-desk/sur/story.hoon+9 bin/commit.sh+24 bin/get_glob.sh+19 bin/install.sh+82 desk/app/astrolabe.hoon+21−5 desk/desk.docket-0+2−2 desk/doc/usr/sigil.udon+4 desk/lib/astrolabe.hoon+9 desk/lib/search.hoon+47−5 desk/mar/ico.hoon+12 desk/mar/webmanifest.hoon+12 install.sh−44 ui/.env+3 ui/package-lock.json+133−30 ui/package.json+18−6 ui/src/App.svelte+3−1 ui/src/actions/sigma.ts−73 ui/src/actions/symbolPartClick.ts+27 ui/src/actions/tooltip.ts+1−1 ui/src/assets/up-arrow.svg+1 ui/src/assets/x.svg+1 ui/src/components/ChartLink.svelte+1−1 ui/src/components/EthAddressLink.svelte+2−2 ui/src/components/HomeScreenTiles.svelte+3−3 ui/src/components/SearchBar.svelte−47 ui/src/components/ShipLink.svelte+2−2 ui/src/components/ShipListing.svelte+3−3 ui/src/components/ShipListings.svelte+1−1 ui/src/components/ShipView.svelte+3−3 ui/src/components/Sigil.svelte+34−10 ui/src/components/StarChart.svelte+4−4 ui/src/components/Tooltip.svelte+1−2 ui/src/components/docs/Doc.svelte+3−3 ui/src/components/docs/DocContent.svelte+3−3 ui/src/components/docs/DocLink.svelte+1−1 ui/src/components/HelpModal.svelte → ui/src/components/docs/HelpModal.svelte+2−2 ui/src/components/search/SearchBar.svelte+125 ui/src/components/SearchResultNavButtons.svelte → ui/src/components/search/SearchResultNavButtons.svelte ui/src/components/SearchResults.svelte → ui/src/components/search/SearchResults.svelte+18−9 ui/src/components/SearchResultsNavButtons.svelte → ui/src/components/search/SearchResultsNavButtons.svelte ui/src/components/SearchSettings.svelte → ui/src/components/search/SearchSettings.svelte+3−3 ui/src/components/search/sigil/ClanSelect.svelte+36 ui/src/components/search/sigil/ClanSelectOption.svelte+17 ui/src/components/search/sigil/GeonSelector.svelte+30 ui/src/components/search/sigil/MiniFakeSigil.svelte+25 ui/src/components/search/sigil/SigilInput.svelte+92 ui/src/components/search/sigil/Symbol.svelte+79 ui/src/components/search/sigil/SymbolInput.svelte+105 ui/src/components/search/sigil/SymbolInputModeSelector.svelte+129 ui/src/components/search/sigil/SymbolInputModeSelectorButton.svelte+33 ui/src/components/search/sigil/SymbolInputNavButtons.svelte+25 ui/src/lib/api.ts+2−2 ui/src/lib/colors.ts+3 ui/src/lib/id.ts+8−4 ui/src/lib/link.ts+5−1 ui/src/lib/sigil.ts+375 ui/src/lib/utils.ts+4 ui/src/routes/HelpPage.svelte+2−2 ui/src/routes/Home.svelte+69−43 ui/src/routes/SigilPage.svelte+58 ui/src/stores/docs.ts+1−2 ui/src/stores/searchStores.ts+34−10 ui/src/stores/store.ts+17−9 ui/src/styles/tailwind-output.css+467−55 ui/src/types/sigil.ts+148 ui/src/types/store.ts+5−1 ui/svelte.config.js+2−2 ui/tsconfig.json+9 ui/vite.config.ts+21−1
.gitignore+1
@@ -1,3 +1,4 @@
11# OS X
22.DS_Store
33extras
4+globs
.vscode/tasks.jsonnew file+13
@@ -0,0 +1,13 @@
1+{
2+ "version": "2.0.0",
3+ "tasks": [
4+ {
5+ "type": "npm",
6+ "script": "commit",
7+ "path": "ui",
8+ "problemMatcher": [],
9+ "label": "npm: commit - ui",
10+ "detail": "dotenv -c local npm run _commit"
11+ }
12+ ]
13+}
README.md+32−25
@@ -21,47 +21,54 @@ Astrolabe is built primarily using [Svelte], [Typescript], and [Tailwind CSS]. [
2121
2222To get started using Astrolabe first you need to run `npm install` inside the `ui` directory.
2323
24−To develop you'll need a running ship to point to. To do so you first need to add a `.env.local` file to the `ui` directory. This file will not be committed. Adding `VITE_SHIP_URL={URL}` where **{URL}** is the URL of the ship you would like to point to, will allow you to run `npm run dev`. This will proxy all requests to the ship except for those powering the interface, allowing you to see live data.
24+To develop you'll need a running ship to point to. If it's running somewhere other than `http://localhost:8081`, make a copy of `.env` in the `ui` directory and name it `.env.local`. This file will not be committed. Change `VITE_SHIP_URL` to the URL of the ship you would like to point to.
25+
26+Once your URL is correctly configured, you can run `npm run dev`. This will proxy all requests to the ship except for those powering the interface, allowing you to see live data.
2527
2628Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
2729
28−### Deploying with `glob-ames` glob
30+# Deploying
2931
30321. Spin up a comet or distribution ship.
31332. Mount an `%astrolabe` desk. In Dojo:
3234 1. `|merge %astrolabe our %base` to create a new desk.
33− 2. `|mount %work` to mount it.
35+ 2. `|mount %astrolabe` to mount it.
36+ 3. Set `ASTROLABE_DESK` in `.env.local` if the path to your desk is different.
37+3. Create and upload `glob-http`, if applicable (see below)
34383. Install the desk
35− 1. From the root of the project: `./install.sh path/to/pier/astrolabe`
39+ 1. From the `ui` folder: `npm run install:desk`
3640 2. From the distribution ship's dojo: `:treaty|unpublish %astrolabe`
3741 2. From middleman or distribution ship's dojo: `|commit %astrolabe`
3842 3. (if using a comet as a middleman) `|public %astrolabe`
3943 4. (if using a comet as a middleman) from the actual distribution ship: `|install ~comet-name %astrolabe` or `|resume %astrolabe`
4044 5. (if using a comet as a middleman) from the actual distribution ship: `|pause %astrolabe`
41−4. Build the UI with `npm run build` in the `ui` directory which will bundle all the code and assets into the `dist/` folder.
42−5. Upload the `dist/` folder as a glob to your distribution ship. Example url: https://distribution.urbit-ship.com/docket/upload
43−6. Publish the desk from dojo: `:treaty|publish %astrolabe`
44−
45−### Deploying with `glob-http` glob
45+4. Build UI and upload `glob-ames`
46+5. Publish the desk from dojo: `:treaty|publish %astrolabe`
4647
47−To deploy, run `npm run build` in the `ui` directory which will bundle all the code and assets into the `dist/` folder. This can then be made into a glob by doing the following:
48+## Deploying with `glob-ames` glob
49+Initial Setup:
50+1. Put the line `glob-ames+[~dister-midlev-mindyr 0v0]` in `desk.docket-0` if it's not there, where `~dister-midlevi-mindyr` is the `@p` of the distribution ship.
4851
49−1. Create or launch an urbit using the -F flag
50−2. On that urbit, if you don't already have a desk to run from, run `|merge %work our %base` to create a new desk and mount it with `|mount %work`.
51−3. Now the `%work` desk is accessible through the host OS's filesystem as a directory of that urbit's pier ie `~/zod/work`.
52−4. From the `ui` directory you can run `rsync -avL --delete dist/ ~/zod/work/astrolabe` where `~/zod` is your fake urbit's pier.
53−5. Once completed you can then run `|commit %work` on your urbit and you should see your files logged back out from the dojo.
54−6. Now run `=dir /=garden` to switch to the garden desk directory
55−7. You can now run `-make-glob %work /astrolabe` which will take the folder where you just added files and create a glob which can be thought of as a sort of bundle. It will be output to `~/zod/.urb/put`.
56−8. If you navigate to `~/zod/.urb/put` you should see a file that looks like this `glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob`. The characters between `glob-` and `.glob` are a hash of the glob's contents.
57−9. Now that we have the glob it can be uploaded to any publicly available HTTP endpoint that can serve files. This allows the glob to distributed over HTTP.
58−10. Once you've uploaded the glob, you should then update the corresponding entry in the docket file at `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
59−
60−```hoon
61− glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
62−```
52+Every Time:
53+4. Build the UI with `npm run build` in the `ui` directory which will bundle all the code and assets into the `dist/` folder.
54+5. Upload the `dist/` folder as a glob to your distribution ship. Example url: https://distribution.urbit-ship.com/docket/upload
6355
64−11. This can now be safely committed and deployed.
56+## Deploying with `glob-http` glob
57+Initial Setup:
58+1. Spin up a globber ship (comet or fakezod) (or just reuse your dev ship for this).
59+2. Mount an `%globber` desk. In Dojo:
60+ 1. `|merge %globber our %base` to create a new desk.
61+ 2. `|mount %globber` to mount it.
62+ 3. Set `GLOBBER_DESK` in `.env.local` if the path to your globber desk is different.
63+
64+Every Time:
65+3. From the `ui` directory, run `npm run release` to build the ui, make the glob, and copy it to the `globs` folder. The command will also print out the glob hash/id.
66+4. Copy the string printed by the last step, and paste it into `desk.docket-0`.
67+5. Upload the glob in `/globs/` to any publicly available HTTP endpoint that can serve files. This allows the glob to be distributed over HTTP.
68+6. Once you've uploaded the glob, paste the url into `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
69+ ```hoon
70+ glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
71+ ```
6572
6673[svelte]: https://svelte.dev/
6774[typescript]: https://www.typescriptlang.org/
base-desk/lib/bip/b173.hoon+1−1
@@ -11,7 +11,7 @@
1111|%
1212++ prefixes
1313 ^- (map network tape)
14− (my [[%main "bc"] [%testnet "tb"] ~])
14+ (my [[%main "bc"] [%testnet "tb"] [%regtest "bcrt"] ~])
1515++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
1616+$ raw-decoded [hrp=tape data=(list @) checksum=(list @)]
1717:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js
base-desk/lib/bip/b174.hoon+2
@@ -56,6 +56,8 @@
5656 1^0x0
5757 %testnet
5858 1^0x1
59+ %regtest
60+ 1^0x1
5961 ==
6062 :- (cat:byt ~[1^typ pubkey.h])
6163 %- cat:byt
base-desk/lib/cram.hoon−1
@@ -31,7 +31,6 @@
3131 =- (weld - $(p.gen t.p.gen))
3232 ?^ -.i.p.gen $(gen [%xray i.p.gen])
3333 ~| [%shut-tuna -.i.p.gen]
34− ~| [%shut-slamon -.p.i.p.gen]
3534 ?+ -.i.p.gen !!
3635 %manx ?>(?=(%xray -.p.i.p.gen) $(gen p.i.p.gen))
3736 %marl ?>(?=(%mcts -.p.i.p.gen) $(gen p.i.p.gen))
base-desk/lib/ethereum.hoon+19−1
@@ -948,6 +948,21 @@
948948 %- prefix-hex
949949 (render-hex-bytes 20 `@`a)
950950::
951+++ address-to-checksum
952+ |= a=address
953+ ^- tape
954+ =/ hexed (render-hex-bytes 20 `@`a)
955+ =/ hash (keccak-256:keccak:crypto (as-octs:mimes:html (crip hexed)))
956+ =| ret=tape
957+ =/ pos 63
958+ |-
959+ ?~ hexed (prefix-hex (flop ret))
960+ =/ char i.hexed
961+ ?: (lth char 58) $(pos (dec pos), ret [char ret], hexed t.hexed)
962+ =/ nib (cut 2 [pos 1] hash)
963+ ?: (lth 7 nib) $(pos (dec pos), ret [(sub char 32) ret], hexed t.hexed)
964+ $(pos (dec pos), ret [char ret], hexed t.hexed)
965+::
951966++ transaction-to-hex
952967 |= h=@
953968 ^- tape
@@ -978,5 +993,8 @@
978993::
979994++ hex-to-num
980995 |= a=@t
981− (rash (rsh [3 2] a) hex)
996+ ~| %non-hex-cord
997+ ?> =((end [3 2] a) '0x')
998+ =< ?<(=(0 p) q) %- need
999+ (de:base16:mimes:html (rsh [3 2] a))
9821000--
base-desk/lib/hark-store.hoon+22
@@ -171,6 +171,7 @@
171171++ dejs
172172 =, dejs:format
173173 |%
174+ ++ ship (su ;~(pfix sig fed:ag))
174175 :: TODO: fix +stab
175176 ::
176177 ++ pa
@@ -218,6 +219,26 @@
218219 :~ lid+lid
219220 bin+bin
220221 ==
222+ ++ content
223+ %- of
224+ :~ text+so
225+ ship+ship
226+ ==
227+ ::
228+ ++ body
229+ %- ot
230+ :~ title+(ar content)
231+ content+(ar content)
232+ time+di
233+ binned+pa
234+ link+pa
235+ ==
236+ ::
237+ ++ add-note
238+ %- ot
239+ :~ bin+bin
240+ body+body
241+ ==
221242 ::
222243 ++ action
223244 ^- $-(json ^action)
@@ -228,6 +249,7 @@
228249 read-count+place
229250 read-each+read-each
230251 read-note+bin
252+ add-note+add-note
231253 ==
232254 --
233255--
base-desk/lib/ph/io.hoon+14−2
@@ -1,5 +1,5 @@
11/- *aquarium, spider
2−/+ libstrand=strand, *strandio, util=ph-util
2+/+ libstrand=strand, *strandio, util=ph-util, aqua-azimuth
33=, strand=strand:libstrand
44|%
55++ send-events
@@ -127,7 +127,7 @@
127127 ;< ~ bind:m (send-azimuth-action %breach who)
128128 |- ^- form:m
129129 =* loop $
130− ;< ~ bind:m (sleep ~s1)
130+ ;< ~ bind:m (sleep ~s10)
131131 ;< =bowl:spider bind:m get-bowl
132132 =/ aqua-pax
133133 :- %i
@@ -137,6 +137,18 @@
137137 (pure:m ~)
138138 loop
139139::
140+++ init-moon ::NOTE real moon always have the same keys
141+ |= [moon=ship fake=?]
142+ ?> ?=(%earl (clan:title moon))
143+ ?: fake (init-ship moon &)
144+ =/ m (strand ,~)
145+ ^- form:m
146+ ;< ~ bind:m
147+ %+ dojo (^sein:title moon)
148+ =/ =pass pub:ex:(get-keys:aqua-azimuth moon 1)
149+ "|moon {(scow %p moon)}, =public-key {(scow %uw pass)}"
150+ (init-ship moon |)
151+::
140152++ init-ship
141153 |= [=ship fake=?]
142154 =/ m (strand ,~)
base-desk/lib/ph/util.hoon+1−1
@@ -59,7 +59,7 @@
5959::
6060:: Checks whether the given event is a dojo output blit containing the
6161:: given tape
62−::
62+::TODO should be rename -dill-output
6363++ is-dojo-output
6464 |= [who=ship her=ship uf=unix-effect what=tape]
6565 ?& =(who her)
base-desk/lib/pill.hoon+3−3
@@ -61,10 +61,10 @@
6161 =; hav ~& user-files+(lent hav)
6262 =/ =yuki:clay
6363 :- *(list tako:clay)
64− %- ~(gas by *(map path (each page:clay lobe:clay)))
65− (turn hav |=([=path =page:clay] [path &+page]))
64+ %- ~(gas by *(map path (each page lobe:clay)))
65+ (turn hav |=([=path =page] [path &+page]))
6666 [/c/sync [%park des &+yuki *rang:clay]]
67− =| hav=(list [path page:clay])
67+ =| hav=(list [path page])
6868 |- ^+ hav
6969 ?~ sal ~
7070 =. hav $(sal t.sal)
base-desk/lib/ring.hoon+11−10
@@ -349,15 +349,16 @@
349349 |= [our=@p now=@da ship=@p =life]
350350 ^- @udpoint
351351 ::
352− =/ d=[=^life =pass]
352+ =/ d=[=^life =pass *]
353353 =/ scry-path=path
354− :~ %k
354+ :~ %j
355355 (scot %p our)
356+ %deed
356357 (scot %da now)
357358 (scot %p ship)
358359 (scot %ud life)
359360 ==
360− .^([^life pass] scry-path)
361+ .^([^life pass *] scry-path)
361362 :: we have the deed which has pass, which is several numbers +cat-ed
362363 :: together; pull out the keys
363364 ::
@@ -376,15 +377,15 @@
376377 ?~ invited
377378 [participants keys]
378379 ::
379− =/ =life
380− .^(life k+/(scot %p our)/life/(scot %da now)/(scot %p i.invited))
380+ =/ lyfe=(unit @ud)
381+ .^((unit @ud) j+/(scot %p our)/lyfe/(scot %da now)/(scot %p i.invited))
381382 ::
382− ?: =(life 0)
383+ ?~ lyfe
383384 $(invited t.invited)
384385 ::
385− =/ pubkey=@udpoint (ship-life-to-pubid our now i.invited life)
386+ =/ pubkey=@udpoint (ship-life-to-pubid our now i.invited u.lyfe)
386387 ::
387− =. participants (~(put in participants) [i.invited life])
388+ =. participants (~(put in participants) [i.invited u.lyfe])
388389 =. keys (~(put in keys) pubkey)
389390 ::
390391 $(invited t.invited)
@@ -436,11 +437,11 @@
436437 :: get our ships' current life
437438 ::
438439 =/ our-life=life
439− .^(life %k /(scot %p our)/life/(scot %da now)/(scot %p our))
440+ .^(life %j /(scot %p our)/life/(scot %da now)/(scot %p our))
440441 :: get our ships' secret keyfile ring
441442 ::
442443 =/ secret-ring=ring
443− .^(ring %k /(scot %p our)/vein/(scot %da now)/(scot %ud our-life))
444+ .^(ring %j /(scot %p our)/vein/(scot %da now)/(scot %ud our-life))
444445 :: fetch the encoded auth seed from the ring
445446 ::
446447 =/ secret-auth-seed=@
base-desk/lib/story.hoonnew file+112
@@ -0,0 +1,112 @@
1+/- *story
2+!:
3+^?
4+|%
5+:: XX generalize move to hoon.hoon
6+++ dif-ju
7+ |= [a=story b=story]
8+ ^- story
9+ :: if 0 is the empty set,
10+ :: a \ 0 = a
11+ :: 0 \ b = 0 :: anything in 0 but not in b is by definition 0
12+ ::
13+ ?: =(~ a) ~
14+ :: uno := (a-b) + (merged items in both a and b) + (b-a)
15+ :: ret := (a-b) + (merged items in both a and b)
16+ :: ret = (~(int by a) uno) :: preserve only the entries whose keys are in a
17+ =/ uno=story
18+ %- (~(uno by a) b)
19+ |= [k=tako:clay proses-a=proses proses-b=proses]
20+ ^- proses
21+ (~(dif in proses-a) proses-b)
22+ ::
23+ =/ ret=story (~(int by a) uno)
24+ :: normalizing step, remove any keys with null sets,
25+ :: which can occur if proses-a == proses-b above
26+ %- ~(gas by *story)
27+ (skip ~(tap by ret) |=([k=* v=proses] ?=(~ v)))
28+::
29+++ uni-ju
30+ |= [a=story b=story]
31+ ^- story
32+ :: 0 + b = b
33+ ?: =(~ a) b
34+ %- (~(uno by a) b)
35+ |= [k=tako:clay proses-a=proses proses-b=proses]
36+ ^- proses
37+ (~(uni in proses-a) proses-b)
38+::
39+:: Canonical textual representation
40+::
41+++ tako-to-text
42+ |= [=tako:clay]
43+ ^- tape
44+ "commit: {<`@uv`tako>}\0a"
45+::
46+++ proses-to-text
47+ |= [=proses]
48+ ^- tape
49+ =/ proses-list=(list prose) ~(tap in proses)
50+ ?: ?=(~ proses-list) ""
51+ ?: ?=([prose ~] proses-list)
52+ (prose-to-text i.proses-list)
53+ %- tail
54+ %^ spin `(list prose)`t.proses-list
55+ (prose-to-text i.proses-list)
56+ |= [prz=prose state=tape]
57+ ^- [prose tape]
58+ :- prz
59+ ;: welp
60+ state
61+ "|||"
62+ "\0a"
63+ (prose-to-text prz)
64+ ==
65+::
66+++ prose-to-text
67+ |= prz=prose
68+ =/ [title=@t body=@t] prz
69+ ^- tape
70+ ;: welp
71+ "{(trip title)}"
72+ "\0a\0a"
73+ "{(trip body)}"
74+ "\0a"
75+ ==
76+::
77+:: Parsers
78+::
79+++ parse-commit-hash
80+ ;~ sfix
81+ ;~ pfix (jest 'commit: ')
82+ (cook @uv ;~(pfix (jest '0v') viz:ag))
83+ ==
84+ ::
85+ (just '\0a')
86+ ==
87+::
88+++ parse-title
89+ ;~ sfix
90+ (cook crip (star prn))
91+ (jest '\0a\0a')
92+ ==
93+::
94+++ parse-body
95+ %+ cook of-wain:format
96+ %- star
97+ ;~ less
98+ ;~(pose (jest '|||\0a') (jest '---\0a'))
99+ (cook crip ;~(sfix (star prn) (just '\0a')))
100+ ==
101+::
102+++ parse-prose ;~(plug parse-title parse-body)
103+++ parse-rest-proses (star ;~(pfix (jest '|||\0a') parse-prose))
104+++ parse-proses (cook silt ;~(plug parse-prose parse-rest-proses))
105+++ parse-chapter ;~(plug parse-commit-hash parse-proses)
106+++ parse-story
107+ (cook ~(gas by *story) (star ;~(sfix parse-chapter (jest '---\0a'))))
108+::
109+:: N.B: If conflicting messages are written individually,
110+:: instead of under the same commit, we will overwrite previous entries
111+:: with later ones due to the nature of gas:by.
112+--
base-desk/lib/strandio.hoon+3−3
@@ -734,7 +734,7 @@
734734 ;< =bowl:spider bind:m get-bowl
735735 =/ tid
736736 (scot %ta (cat 3 (cat 3 'strand_' file) (scot %uv (sham file eny.bowl))))
737− =/ poke-vase !>([`tid.bowl `tid beak file args])
737+ =/ poke-vase !>(`start-args:spider`[`tid.bowl `tid beak file args])
738738 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
739739 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
740740 (pure:m tid)
@@ -748,7 +748,7 @@
748748 ^- form:m
749749 ;< =bowl:spider bind:m get-bowl
750750 =/ tid (scot %ta (cat 3 'strand_' (scot %uv (sham file eny.bowl))))
751− =/ poke-vase !>([`tid.bowl `tid file args])
751+ =/ poke-vase !>(`start-args:spider`[`tid.bowl `tid byk.bowl file args])
752752 ;< ~ bind:m (watch-our /awaiting/[tid] %spider /thread-result/[tid])
753753 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
754754 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
@@ -756,6 +756,6 @@
756756 ;< ~ bind:m (take-kick /awaiting/[tid])
757757 ?+ p.cage ~|([%strange-thread-result p.cage file tid] !!)
758758 %thread-done (pure:m %& q.cage)
759− %thread-fail (pure:m %| !<([term tang] q.cage))
759+ %thread-fail (pure:m %| ;;([term tang] q.q.cage))
760760 ==
761761--
base-desk/lib/test.hoon+1−1
@@ -13,7 +13,7 @@
1313 %+ weld result
1414 ^- tang
1515 :~ [%palm [": " ~ ~ ~] [leaf+"expected" (sell expected) ~]]
16− [%palm [": " ~ ~ ~] [leaf+"actual" (sell actual) ~]]
16+ [%palm [": " ~ ~ ~] [leaf+"actual " (sell actual) ~]]
1717 ==
1818 ::
1919 =? result !(~(nest ut p.actual) | p.expected)
base-desk/mar/css.hoonnew file+21
@@ -0,0 +1,21 @@
1+::
2+:::: /hoon/css/mar
3+ ::
4+/? 310
5+=, eyre
6+=, mimes:html
7+|_ mud=@t
8+++ grow :: convert to
9+ |% ++ mime [/text/css (as-octs mud)] :: convert to %mime
10+ ++ elem ;style :: convert to %hymn
11+ ;- (trip mud)
12+ ==
13+ ++ hymn ;html:(head:"{elem}" body)
14+ --
15+++ grab
16+ |% :: convert from
17+ ++ mime |=([p=mite q=octs] (@t q.q))
18+ ++ noun @t :: clam from %noun
19+ --
20+++ grad %mime
21+--
base-desk/mar/story-diff.hoonnew file+16
@@ -0,0 +1,16 @@
1+::
2+::::
3+ ::
4+/- *story
5+|_ =story-diff
6+::
7+++ grad %noun
8+++ grow
9+ |%
10+ ++ noun story-diff
11+ --
12+++ grab :: convert from
13+ |%
14+ ++ noun ^story-diff :: make from %noun
15+ --
16+--
base-desk/mar/story.hoonnew file+70
@@ -0,0 +1,70 @@
1+/- *story
2+/+ *story
3+|_ tale=story
4+++ grad
5+ |%
6+ ++ form %story-diff
7+ ++ diff
8+ |= tory=story
9+ ^- story-diff
10+ :: Given new story (tory), old story (tale), compute the diff
11+ :: additions = new - old
12+ :: deletions = old - new
13+ [(dif-ju tory tale) (dif-ju tale tory)]
14+ ++ pact
15+ |= dif=story-diff
16+ :: Compute the new story after applying dif to tale.
17+ ::
18+ ^- story
19+ =. tale (uni-ju tale additions.dif)
20+ =. tale (dif-ju tale deletions.dif)
21+ tale
22+ ++ join
23+ |= [ali=story-diff bob=story-diff]
24+ ^- (unit story-diff)
25+ =/ joined-additions (uni-ju additions.ali additions.bob)
26+ =/ joined-deletions (uni-ju deletions.ali deletions.bob)
27+ ::
28+ :: In a true join, we'd do a set intersection on the keys.
29+ :: If they're not equal, we have a conflict.
30+ :: In this case, we'd produce null and kick the flow to +mash
31+ ::
32+ %- some
33+ [joined-additions joined-deletions]
34+ ++ mash
35+ :: called by meld, force merge, annotating conflicts
36+ |= $: [als=ship ald=desk ali=story-diff]
37+ [bos=ship bod=desk bob=story-diff]
38+ ==
39+ ^- story-diff
40+ (need (join ali bob)) :: XX temporary, only because join doesn't fail
41+ --
42+::
43+++ grow :: convert to
44+ |% ::
45+ ++ mime :: to %mime
46+ [/text/x-urb-story (as-octs:mimes:html (of-wain:format txt))]
47+ ++ txt
48+ ^- wain
49+ %- snoc :_ '' :: ensures terminating newline is present
50+ %+ murn ~(tap by tale)
51+ |= [[=tako:clay =proses]]
52+ ^- (unit cord)
53+ ?~ proses ~
54+ %- some
55+ %- crip
56+ ;: welp
57+ (tako-to-text tako)
58+ (proses-to-text proses)
59+ "---"
60+ ==
61+ --
62+++ grab
63+ |% :: convert from
64+ ++ noun story :: clam from %noun
65+ ++ mime :: retrieve from %mime
66+ |= [p=mite q=octs]
67+ =/ story-text `@t`q.q
68+ `story`(rash story-text parse-story)
69+ --
70+--
base-desk/mar/thread-done.hoonnew file+11
@@ -0,0 +1,11 @@
1+|_ res=*
2+++ grab
3+ |%
4+ ++ noun *
5+ --
6+++ grow
7+ |%
8+ ++ noun res
9+ --
10+++ grad %noun
11+--
base-desk/mar/thread-fail.hoonnew file+11
@@ -0,0 +1,11 @@
1+|_ err=*
2+++ grab
3+ |%
4+ ++ noun (pair term tang)
5+ --
6+++ grow
7+ |%
8+ ++ noun err
9+ --
10+++ grad %noun
11+--
base-desk/mar/udon.hoon−1
@@ -28,5 +28,4 @@
2828 ++ txt of-wain:format
2929 --
3030++ grad %txt
31−++ garb /down
3231--
base-desk/sur/aquarium.hoon+10−1
@@ -24,7 +24,16 @@
2424 aqua-event
2525 ==
2626::
27−+$ unix-event unix-event:pill-lib
27++$ unix-event ::NOTE like unix-event:pill-lib but for all tasks
28+ %+ pair wire
29+ $% [%wack p=@]
30+ [%what p=(list (pair path (cask)))]
31+ [%whom p=ship]
32+ [%boot ? $%($>(%fake task:jael) $>(%dawn task:jael))]
33+ [%wyrd p=vere]
34+ [%verb p=(unit ?)]
35+ task-arvo
36+ ==
2837+$ pill pill:pill-lib
2938::
3039+$ aqua-event
base-desk/sur/bitcoin.hoon+1−1
@@ -4,7 +4,7 @@
44:: chyg: whether account is (non-)change. 0 or 1
55:: bytc: "btc-byts" with dat cast to @ux
66|%
7−+$ network ?(%main %testnet)
7++$ network ?(%main %testnet %regtest)
88+$ hexb [wid=@ dat=@ux] :: hex byts
99+$ bits [wid=@ dat=@ub]
1010+$ xpub @ta
base-desk/sur/hood.hoon+4−14
@@ -130,13 +130,8 @@
130130 =/ tak (scot %uv (~(got by hit.dom) let.dom))
131131 =/ yak .^(yaki cs/~[her syd yon %yaki tak])
132132 =/ lob (scot %uv (~(got by q.yak) /sys/kelvin))
133− =/ bob .^(blob cs/~[her syd yon %blob lob])
134− ::
135− ;; weft
136− ?- -.bob
137− %direct q.q.bob
138− %delta q.r.bob
139− ==
133+ =/ peg .^(page cs/~[her syd yon %blob lob])
134+ ;;(weft q.peg)
140135:: +read-kelvin-local: read /sys/kelvin from a local desk
141136::
142137++ read-kelvin-local
@@ -164,13 +159,8 @@
164159 =/ fil (~(get by q.yak) /desk/bill)
165160 ?~ fil ~
166161 =/ lob (scot %uv u.fil)
167− =/ bob .^(blob cs/~[her syd yon %blob lob])
168− ::
169− ;; (list dude)
170− ?- -.bob
171− %direct q.q.bob
172− %delta q.r.bob
173− ==
162+ =/ peg .^(page cs/~[her syd yon %blob lob])
163+ ;;((list dude) q.peg)
174164:: +read-bill: read contents of /desk/bill manifest
175165::
176166++ read-bill
base-desk/sur/spider.hoon+7
@@ -11,4 +11,11 @@
1111 %nonexistent :: 404
1212 %offline :: 504
1313 ==
14++$ start-args
15+ $: parent=(unit tid)
16+ use=(unit tid)
17+ =beak
18+ file=term
19+ =vase
20+ ==
1421--
base-desk/sur/story.hoonnew file+9
@@ -0,0 +1,9 @@
1+^?
2+|%
3++$ prose [title=@t body=@t]
4++$ proses (set prose)
5++$ story (jug tako:clay prose) :: set len > 1 means conflicting messages have been assigned
6++$ chapter [tako:clay proses] :: prose entry type
7++$ cash $%([%tako p=tako:clay] case) :: used in generators to accept a tako directly as well
8++$ story-diff [additions=story deletions=story]
9+--
bin/commit.shnew file+24
@@ -0,0 +1,24 @@
1+#!/bin/bash
2+
3+usage() { printf "Usage: $0 URBIT_DESK_DIRECTORY\n" 1>&2; exit 1; }
4+
5+cdir=$(dirname $0)
6+
7+if [ $# -eq 0 ]; then
8+ usage
9+ exit 2
10+fi
11+DESK_DIR=${@: -1}
12+DESK_DIR="${DESK_DIR/#\~/$HOME}"
13+PIER=$(dirname $DESK_DIR)
14+DESK=$(basename $DESK_DIR)
15+
16+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
17+
18+lensa() {
19+ curl -s \
20+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
21+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
22+}
23+
24+lensa 'hood' "+hood/commit %$DESK"
bin/get_glob.shnew file+19
@@ -0,0 +1,19 @@
1+#!/bin/bash
2+
3+usage() { printf "Usage: $0 URBIT_GLOBBER_DESK_DIRECTORY" 1>&2; exit 1; }
4+
5+cdir=$(dirname $0)
6+
7+if [ $# -eq 0 ]; then
8+ usage
9+ exit 2
10+fi
11+DESK="${1//\~/$HOME}"
12+GLOBS="$DESK/../.urb/put"
13+GLOB=$(ls -t $GLOBS | head -1)
14+TARGET_GLOBS="$cdir"/../globs/
15+mkdir -p $TARGET_GLOBS
16+cp "$GLOBS/$GLOB" $TARGET_GLOBS/
17+HASH="${GLOB//glob-/}"
18+HASH="${HASH//.glob/}"
19+echo $HASH
bin/install.shnew file+82
@@ -0,0 +1,82 @@
1+#!/bin/bash
2+#
3+# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
4+# by ~timluc-miptev
5+#
6+# Modified HEAVILY
7+#
8+usage() { printf "Usage: $0 [-w] [-g] URBIT_DESK_DIRECTORY\n(-w: flag to watch and live copy code)\n(-g: install built ui to globber desk)\n" 1>&2; exit 1; }
9+
10+cdir=$(dirname $0)
11+
12+if [ $# -eq 0 ]; then
13+ usage
14+ exit 2
15+fi
16+DESK_DIR=${@: -1}
17+DESK_DIR="${DESK_DIR/#\~/$HOME}"
18+PIER=$(dirname $DESK_DIR)
19+DESK=$(basename $DESK_DIR)
20+EXCLUDE_FILE="${cdir}"/../config/ignore_files_on_install.txt
21+
22+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
23+
24+lensd() {
25+ curl -s \
26+ --data "{\"source\":{\"dojo\":\"$1\"},\"sink\":{\"stdout\":null}}" \
27+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
28+}
29+
30+lensa() {
31+ curl -s \
32+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
33+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
34+}
35+
36+sync() {
37+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK_DIR/
38+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK_DIR/
39+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK_DIR/
40+ if [ "$GLOBBER" ]; then
41+ rsync -r --copy-links "${cdir}"/../ui/dist/* $DESK_DIR/astrolabe
42+ fi
43+}
44+
45+
46+while getopts "wg" opt; do
47+ case ${opt} in
48+ w) WATCH_MODE="true"
49+ ;;
50+ g) GLOBBER="true"
51+ ;;
52+ *) usage
53+ ;;
54+ esac
55+done
56+
57+if [ -z "$WATCH_MODE" ]; then
58+ echo "Installed %astrolabe to ${DESK_DIR}"
59+ rm -r $DESK_DIR/*
60+ sync
61+ lensa 'hood' "+hood/commit %$DESK"
62+ if [ "$GLOBBER" ]; then
63+ lensd '-garden!make-glob %globber /astrolabe'
64+ GLOBS="$PIER/.urb/put"
65+ GLOB=$(ls -t $GLOBS | head -1)
66+ TARGET_GLOBS="$cdir"/../globs/
67+ mkdir -p $TARGET_GLOBS
68+ cp "$GLOBS/$GLOB" $TARGET_GLOBS/
69+ echo "copied from $GLOBS to $TARGET_GLOBS"
70+ HASH="${GLOB//glob-/}"
71+ HASH="${HASH//.glob/}"
72+ echo $HASH
73+ fi
74+else
75+ echo "Watching for changes to copy to ${DESK_DIR}..."
76+ rm -r $DESK_DIR/*
77+ while [ 0 ]
78+ do
79+ sleep 0.8
80+ sync
81+ done
82+fi
desk/app/astrolabe.hoon+21−5
@@ -67,10 +67,15 @@
6767 =/ =ship `@p`(slav %p &3.path)
6868 =/ arg ?~(t.t.t.t.path %all i.t.t.t.t.path)
6969 ``astrolabe-point-set+!>((get-spawned-points:hc ship arg))
70− [%x %search @ ?(~ [%narrow ~])]
71− =/ =search-text (trip &3.path)
70+ [%x %search %patp @ ?(~ [%narrow ~])]
71+ =/ =search-text (trip &4.path)
72+ ?~ search-text ~|(%search-predicted-too-broad !!)
7273 =/ expand-search =(t.t.t.path ~)
73− ``astrolabe-point-set+!>((search-opoints:hc search-text expand-search))
74+ ``astrolabe-point-set+!>((search-opoints-patp:hc search-text expand-search))
75+ [%x %search %sigil @ ~]
76+ =/ =search-text (trip &4.path)
77+ ?~ search-text ~|(%search-predicted-too-broad !!)
78+ ``astrolabe-point-set+!>((search-opoints-sigil:hc search-text))
7479 [%x %peers ~]
7580 =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
7681 ``astrolabe-point-set+!>((turn ~(tap in peers) head))
@@ -262,14 +267,25 @@
262267 |= [agg=@ud [spawn=ship *]]
263268 +(agg)
264269::
265−++ search-opoints
270+++ search-opoints-patp
266271 |= [=search-text expand-search=?]
267272 ^- (list ship)
268273 =/ search-fulls
269274 ?: expand-search
270275 (search-text-to-fulls search-text)
271276 ~[(search-text-to-full search-text)]
272− :: ~& (turn search-fulls |=(=search-full search-syls.search-full))
277+ (search-opoints search-fulls)
278+::
279+++ search-opoints-sigil
280+ |= [=search-text]
281+ ^- (list ship)
282+ =/ search-fulls
283+ ~[(sigil-search-text-to-full search-text)]
284+ (search-opoints search-fulls)
285+::
286+++ search-opoints
287+ |= [search-fulls=(list search-full)]
288+ ^- (list ship)
273289 =/ mr-searches (maybe-reverse-searches search-fulls 1.000)
274290 :: ~& (turn mr-searches |=(=mr-search pain.mr-search))
275291 :: ~& mr-searches
desk/desk.docket-0+2−2
@@ -2,9 +2,9 @@
22 info+'Your guide to the Urbit constellations'
33 color+0x1f.243c
44 image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
5− glob-ames+[~dister-midlev-mindyr 0v0]
5+ glob-http+['https://s3.us-east-2.amazonaws.com/warkgnall.com/urbit/astrolabe/globs/glob-0v6c4an.v6o54.h391h.bas4f.pl6gv.glob' 0v6c4an.v6o54.h391h.bas4f.pl6gv]
66 base+'astrolabe'
7− version+[0 2 2]
7+ version+[0 3 0]
88 website+'https://github.com/johnhyde/astrolabe'
99 license+'MIT'
1010==
desk/doc/usr/sigil.udonnew file+4
@@ -0,0 +1,4 @@
1+;>
2+# What is a Sigil?
3+
4+TODO: write this doc
desk/lib/astrolabe.hoon+9
@@ -54,6 +54,15 @@
5454 .= linear-star-release:contracts:azimuth
5555 address.owner.own.p
5656::
57+++ split
58+ |* [nedl=(list) hstk=(list)]
59+ |- ^- (list _hstk)
60+ =/ brk (find nedl hstk)
61+ ?~ brk ~[hstk]
62+ =/ next (add u.brk (lent nedl))
63+ :- (scag u.brk hstk)
64+ $(hstk (slag next hstk))
65+::
5766++ smel
5867 |%
5968 ++ ship
desk/lib/search.hoon+47−5
@@ -12,7 +12,7 @@
1212++ prefixes ^~ (trip-syls sis:po)
1313++ suffixes ^~ (trip-syls dex:po)
1414++ full-opts ^~ (gulf .~zod .~fes)
15−++ galaxies ^~ `(list @p)``(list @)`full-opts
15+++ galaxies ^~ `(list @p)`full-opts
1616++ sanitize-search-text
1717 |= =search-text
1818 ^- tape
@@ -129,6 +129,25 @@
129129 ?: =(~[~ ~ ~ ~] search-opts.full)
130130 ~
131131 (some full)
132+::
133+++ sigil-search-text-to-full
134+ |= text=tape
135+ ^- search-full
136+ :- ~
137+ ^- search-opts
138+ %- flop
139+ %+ turn
140+ (split "_" text)
141+ |= ints-tape=tape
142+ ^- search-syl-opts
143+ ?: =(ints-tape "any")
144+ full-opts
145+ %+ turn
146+ (split "." ints-tape)
147+ |= =tape
148+ :: ~& tape
149+ `@q`(slav %ud (crip tape))
150+::
132151++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
133152 |* lst=(list)
134153 ^- _lst
@@ -221,7 +240,10 @@
221240 |= [=syl cand=@]
222241 :: todo check cand < 256
223242 (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
224−++ matches-search
243+++ syl-opts-match
244+ |= [opts=search-syl-opts cand=@]
245+ !=(~ (find ~[cand] opts))
246+++ matches-search-syls
225247 |= [search=search-syls cand-atom=@]
226248 =/ cand (rip 3 cand-atom)
227249 |-
@@ -240,6 +262,28 @@
240262 $(search t.search, cand t.cand)
241263 ==
242264::
265+++ matches-search-opts
266+ |= [search=search-opts cand-atom=@]
267+ =/ cand (rip 3 cand-atom)
268+ |-
269+ ^- ?
270+ ?~ search =(cand ~)
271+ ?~ cand
272+ ?& (syl-opts-match i.search 0)
273+ $(search t.search)
274+ ==
275+ :: =/ nncand ^- (lest @)
276+ :: ?~ cand ~[0] cand
277+ ?& (syl-opts-match i.search i.cand)
278+ $(search t.search, cand t.cand)
279+ ==
280+::
281+++ matches-search
282+ |= [search=search-full cand-atom=@]
283+ ?~ search-syls.search
284+ (matches-search-opts search-opts.search cand-atom)
285+ (matches-search-syls search-syls.search cand-atom)
286+::
243287++ opts-in-range
244288 |= [lb=@ rb=@ opts=(list @)]
245289 ^- search-syl-opts
@@ -282,8 +326,6 @@
282326 |* [agg=mold search=search-full points=opoints =page-info]
283327 |= [res=[count=@ =agg] f=$-([agg @q] agg)]
284328 ^- [count=@ =agg]
285− =/ s-syls search-syls.search
286− :: ~& s-syls
287329 =/ s-opts search-opts.search
288330 =+ [llb=.~zod rrb=`@q`(sub (bex 32) 1)]
289331 :: =< +
@@ -302,7 +344,7 @@
302344 =/ right-view (range-contains-search rlb rrb s-opts)
303345 =? res contains.left-view
304346 $(points l.points, rrb lrb, s-opts opts.left-view)
305− =? res (matches-search s-syls n)
347+ =? res (matches-search search n)
306348 =/ fits (fits-on-page count.res page-info)
307349 =. count.res +(count.res)
308350 =? agg.res fits (f agg.res n)
desk/mar/ico.hoonnew file+12
@@ -0,0 +1,12 @@
1+|_ dat=@
2+++ grow
3+ |%
4+ ++ mime [/image/x-icon (as-octs:mimes:html dat)]
5+ --
6+++ grab
7+ |%
8+ ++ mime |=([p=mite q=octs] q.q)
9+ ++ noun @
10+ --
11+++ grad %mime
12+--
desk/mar/webmanifest.hoonnew file+12
@@ -0,0 +1,12 @@
1+|_ dat=@
2+++ grow
3+ |%
4+ ++ mime [/application/'manifest+json' (as-octs:mimes:html dat)]
5+ --
6+++ grab
7+ |%
8+ ++ mime |=([p=mite q=octs] q.q)
9+ ++ noun @
10+ --
11+++ grad %mime
12+--
install.shdeleted−44
@@ -1,44 +0,0 @@
1−#!/bin/bash
2−#
3−# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
4−# by ~timluc-miptev
5−#
6−usage() { printf "Usage: $0 [-w] URBIT_DESK_DIRECTORY \n(-w: flag to watch and live copy code)\n" 1>&2; exit 1; }
7−
8−if [ $# -eq 0 ]; then
9− usage
10− exit 2
11−fi
12−DESK=$1
13−EXCLUDE_FILE=config/ignore_files_on_install.txt
14−
15−while getopts "w" opt; do
16− case ${opt} in
17− w) WATCH_MODE="true"
18− DESK=$2
19− ;;
20− *) usage
21− ;;
22− esac
23−done
24−
25−if [ -z "$WATCH_MODE" ]; then
26− echo "Installed %astrolabe"
27− rm -r $DESK/*
28− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./base-desk/* $DESK/
29− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./docs-dev-desk/* $DESK/
30− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./desk/* $DESK/
31−
32−else
33− echo "Watching for changes to copy to ${DESK}..."
34− rm -r $DESK/*
35− while [ 0 ]
36− do
37− sleep 0.8
38− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./base-desk/* $DESK/
39− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./docs-dev-desk/* $DESK/
40− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./desk/* $DESK/
41−
42− done
43−fi
44−ls
ui/.envnew file+3
@@ -0,0 +1,3 @@
1+VITE_SHIP_URL=http://localhost:8081
2+ASTROLABE_DESK=~/urbit/zod/astrolabe
3+GLOBBER_DESK=~/urbit/zod/globber
ui/package-lock.json+133−30
@@ -1,13 +1,14 @@
11{
22 "name": "urbit-astrolabe",
3− "version": "0.0.0",
3+ "version": "0.3.0",
44 "lockfileVersion": 2,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "urbit-astrolabe",
9− "version": "0.0.0",
9+ "version": "0.3.0",
1010 "dependencies": {
11+ "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
1112 "@tlon/phonemes-js": "^1.0.0",
1213 "@tlon/sigil-js": "^1.4.5",
1314 "@urbit/api": "^2.1.0",
@@ -27,6 +28,7 @@
2728 "autoprefixer": "^10.4.2",
2829 "concurrently": "^7.0.0",
2930 "cross-env": "^7.0.3",
31+ "dotenv-cli": "^6.0.0",
3032 "postcss-cli": "^9.1.0",
3133 "sass": "^1.50.0",
3234 "svelte": "^3.48.0",
@@ -39,6 +41,28 @@
3941 "vite": "^2.7.2"
4042 }
4143 },
44+ "../../sigil-js": {
45+ "name": "@johnhyde/sigil-js",
46+ "version": "1.4.6",
47+ "extraneous": true,
48+ "license": "MIT",
49+ "dependencies": {
50+ "invariant": "^2.2.4",
51+ "svgson": "^4.0.0",
52+ "transformation-matrix": "2.1.1"
53+ },
54+ "devDependencies": {
55+ "@types/invariant": "^2.2.31",
56+ "@types/react": "^17.0.20",
57+ "husky": "^4.2.3",
58+ "prettier": "^1.19.1",
59+ "tsdx": "^0.14.1",
60+ "typescript": "^3.8.3"
61+ },
62+ "peerDependencies": {
63+ "react": "^16 || ^17"
64+ }
65+ },
4266 "node_modules/@babel/code-frame": {
4367 "version": "7.16.7",
4468 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@@ -147,6 +171,19 @@
147171 "node": ">=6.9.0"
148172 }
149173 },
174+ "node_modules/@johnhyde/sigil-js": {
175+ "version": "1.4.6",
176+ "resolved": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
177+ "license": "MIT",
178+ "dependencies": {
179+ "invariant": "^2.2.4",
180+ "svgson": "^4.0.0",
181+ "transformation-matrix": "2.1.1"
182+ },
183+ "peerDependencies": {
184+ "react": "^16 || ^17"
185+ }
186+ },
150187 "node_modules/@microsoft/fetch-event-source": {
151188 "version": "2.0.1",
152189 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -775,7 +812,7 @@
775812 "node_modules/deep-rename-keys": {
776813 "version": "0.2.1",
777814 "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
778− "integrity": "sha1-7eeFN9emaivmFRfir5Vtf1ij8dg=",
815+ "integrity": "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==",
779816 "dependencies": {
780817 "kind-of": "^3.0.2",
781818 "rename-keys": "^1.1.2"
@@ -849,6 +886,39 @@
849886 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
850887 "dev": true
851888 },
889+ "node_modules/dotenv": {
890+ "version": "16.0.1",
891+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
892+ "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
893+ "dev": true,
894+ "engines": {
895+ "node": ">=12"
896+ }
897+ },
898+ "node_modules/dotenv-cli": {
899+ "version": "6.0.0",
900+ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-6.0.0.tgz",
901+ "integrity": "sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==",
902+ "dev": true,
903+ "dependencies": {
904+ "cross-spawn": "^7.0.3",
905+ "dotenv": "^16.0.0",
906+ "dotenv-expand": "^8.0.1",
907+ "minimist": "^1.2.5"
908+ },
909+ "bin": {
910+ "dotenv": "cli.js"
911+ }
912+ },
913+ "node_modules/dotenv-expand": {
914+ "version": "8.0.3",
915+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
916+ "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==",
917+ "dev": true,
918+ "engines": {
919+ "node": ">=12"
920+ }
921+ },
852922 "node_modules/electron-to-chromium": {
853923 "version": "1.4.38",
854924 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
@@ -1153,7 +1223,7 @@
11531223 "node_modules/eventemitter3": {
11541224 "version": "2.0.3",
11551225 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
1156− "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
1226+ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
11571227 },
11581228 "node_modules/events": {
11591229 "version": "3.3.0",
@@ -1277,7 +1347,7 @@
12771347 "node_modules/get-value": {
12781348 "version": "2.0.6",
12791349 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
1280− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
1350+ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
12811351 "engines": {
12821352 "node": ">=0.10.0"
12831353 }
@@ -1390,7 +1460,7 @@
13901460 "node_modules/has-value": {
13911461 "version": "0.3.1",
13921462 "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
1393− "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
1463+ "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
13941464 "dependencies": {
13951465 "get-value": "^2.0.3",
13961466 "has-values": "^0.1.4",
@@ -1403,7 +1473,7 @@
14031473 "node_modules/has-value/node_modules/isobject": {
14041474 "version": "2.1.0",
14051475 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
1406− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
1476+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
14071477 "dependencies": {
14081478 "isarray": "1.0.0"
14091479 },
@@ -1414,7 +1484,7 @@
14141484 "node_modules/has-values": {
14151485 "version": "0.1.4",
14161486 "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
1417− "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
1487+ "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==",
14181488 "engines": {
14191489 "node": ">=0.10.0"
14201490 }
@@ -1571,7 +1641,7 @@
15711641 "node_modules/isarray": {
15721642 "version": "1.0.0",
15731643 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
1574− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
1644+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
15751645 },
15761646 "node_modules/isexe": {
15771647 "version": "2.0.0",
@@ -1582,7 +1652,7 @@
15821652 "node_modules/isobject": {
15831653 "version": "3.0.1",
15841654 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
1585− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
1655+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
15861656 "engines": {
15871657 "node": ">=0.10.0"
15881658 }
@@ -1613,7 +1683,7 @@
16131683 "node_modules/kind-of": {
16141684 "version": "3.2.2",
16151685 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
1616− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
1686+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
16171687 "dependencies": {
16181688 "is-buffer": "^1.1.5"
16191689 },
@@ -1795,7 +1865,7 @@
17951865 "node_modules/object-assign": {
17961866 "version": "4.1.1",
17971867 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1798− "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
1868+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
17991869 "peer": true,
18001870 "engines": {
18011871 "node": ">=0.10.0"
@@ -1818,7 +1888,7 @@
18181888 "node_modules/omit-deep": {
18191889 "version": "0.3.0",
18201890 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
1821− "integrity": "sha1-IcivNJm8rdKWUaIyy8rLxSRF6+w=",
1891+ "integrity": "sha512-Lbl/Ma59sss2b15DpnWnGmECBRL8cRl/PjPbPMVW+Y8zIQzRrwMaI65Oy6HvxyhYeILVKBJb2LWeG81bj5zbMg==",
18221892 "dependencies": {
18231893 "is-plain-object": "^2.0.1",
18241894 "unset-value": "^0.1.1"
@@ -2765,7 +2835,7 @@
27652835 "node_modules/unset-value": {
27662836 "version": "0.1.2",
27672837 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
2768− "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
2838+ "integrity": "sha512-yhv5I4TsldLdE3UcVQn0hD2T5sNCPv4+qm/CTUpRKIpwthYRIipsAPdsrNpOI79hPQa0rTTeW22Fq6JWRcTgNg==",
27692839 "dependencies": {
27702840 "has-value": "^0.3.1",
27712841 "isobject": "^3.0.0"
@@ -2900,7 +2970,7 @@
29002970 "node_modules/xml-lexer": {
29012971 "version": "0.2.2",
29022972 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
2903− "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
2973+ "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
29042974 "dependencies": {
29052975 "eventemitter3": "^2.0.0"
29062976 }
@@ -2908,7 +2978,7 @@
29082978 "node_modules/xml-reader": {
29092979 "version": "2.4.3",
29102980 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
2911− "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
2981+ "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
29122982 "dependencies": {
29132983 "eventemitter3": "^2.0.0",
29142984 "xml-lexer": "^0.2.2"
@@ -3056,6 +3126,15 @@
30563126 "regenerator-runtime": "^0.13.4"
30573127 }
30583128 },
3129+ "@johnhyde/sigil-js": {
3130+ "version": "git+ssh://git@github.com/johnhyde/sigil-js.git#35f71ac00b54dd56d010b6fccfa48c6f75c48ee8",
3131+ "from": "@johnhyde/sigil-js@git+ssh://git@github.com:johnhyde/sigil-js.git",
3132+ "requires": {
3133+ "invariant": "^2.2.4",
3134+ "svgson": "^4.0.0",
3135+ "transformation-matrix": "2.1.1"
3136+ }
3137+ },
30593138 "@microsoft/fetch-event-source": {
30603139 "version": "2.0.1",
30613140 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -3512,7 +3591,7 @@
35123591 "deep-rename-keys": {
35133592 "version": "0.2.1",
35143593 "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
3515− "integrity": "sha1-7eeFN9emaivmFRfir5Vtf1ij8dg=",
3594+ "integrity": "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==",
35163595 "requires": {
35173596 "kind-of": "^3.0.2",
35183597 "rename-keys": "^1.1.2"
@@ -3568,6 +3647,30 @@
35683647 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
35693648 "dev": true
35703649 },
3650+ "dotenv": {
3651+ "version": "16.0.1",
3652+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
3653+ "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
3654+ "dev": true
3655+ },
3656+ "dotenv-cli": {
3657+ "version": "6.0.0",
3658+ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-6.0.0.tgz",
3659+ "integrity": "sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==",
3660+ "dev": true,
3661+ "requires": {
3662+ "cross-spawn": "^7.0.3",
3663+ "dotenv": "^16.0.0",
3664+ "dotenv-expand": "^8.0.1",
3665+ "minimist": "^1.2.5"
3666+ }
3667+ },
3668+ "dotenv-expand": {
3669+ "version": "8.0.3",
3670+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
3671+ "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==",
3672+ "dev": true
3673+ },
35713674 "electron-to-chromium": {
35723675 "version": "1.4.38",
35733676 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
@@ -3760,7 +3863,7 @@
37603863 "eventemitter3": {
37613864 "version": "2.0.3",
37623865 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
3763− "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
3866+ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
37643867 },
37653868 "events": {
37663869 "version": "3.3.0",
@@ -3849,7 +3952,7 @@
38493952 "get-value": {
38503953 "version": "2.0.6",
38513954 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
3852− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
3955+ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="
38533956 },
38543957 "glob": {
38553958 "version": "7.2.0",
@@ -3933,7 +4036,7 @@
39334036 "has-value": {
39344037 "version": "0.3.1",
39354038 "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
3936− "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
4039+ "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
39374040 "requires": {
39384041 "get-value": "^2.0.3",
39394042 "has-values": "^0.1.4",
@@ -3943,7 +4046,7 @@
39434046 "isobject": {
39444047 "version": "2.1.0",
39454048 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
3946− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
4049+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
39474050 "requires": {
39484051 "isarray": "1.0.0"
39494052 }
@@ -3953,7 +4056,7 @@
39534056 "has-values": {
39544057 "version": "0.1.4",
39554058 "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
3956− "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
4059+ "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ=="
39574060 },
39584061 "ignore": {
39594062 "version": "5.2.0",
@@ -4073,7 +4176,7 @@
40734176 "isarray": {
40744177 "version": "1.0.0",
40754178 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
4076− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
4179+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
40774180 },
40784181 "isexe": {
40794182 "version": "2.0.0",
@@ -4084,7 +4187,7 @@
40844187 "isobject": {
40854188 "version": "3.0.1",
40864189 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
4087− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
4190+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
40884191 },
40894192 "js-tokens": {
40904193 "version": "4.0.0",
@@ -4110,7 +4213,7 @@
41104213 "kind-of": {
41114214 "version": "3.2.2",
41124215 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
4113− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
4216+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
41144217 "requires": {
41154218 "is-buffer": "^1.1.5"
41164219 }
@@ -4250,7 +4353,7 @@
42504353 "object-assign": {
42514354 "version": "4.1.1",
42524355 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
4253− "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
4356+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
42544357 "peer": true
42554358 },
42564359 "object-hash": {
@@ -4267,7 +4370,7 @@
42674370 "omit-deep": {
42684371 "version": "0.3.0",
42694372 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
4270− "integrity": "sha1-IcivNJm8rdKWUaIyy8rLxSRF6+w=",
4373+ "integrity": "sha512-Lbl/Ma59sss2b15DpnWnGmECBRL8cRl/PjPbPMVW+Y8zIQzRrwMaI65Oy6HvxyhYeILVKBJb2LWeG81bj5zbMg==",
42714374 "requires": {
42724375 "is-plain-object": "^2.0.1",
42734376 "unset-value": "^0.1.1"
@@ -4899,7 +5002,7 @@
48995002 "unset-value": {
49005003 "version": "0.1.2",
49015004 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
4902− "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
5005+ "integrity": "sha512-yhv5I4TsldLdE3UcVQn0hD2T5sNCPv4+qm/CTUpRKIpwthYRIipsAPdsrNpOI79hPQa0rTTeW22Fq6JWRcTgNg==",
49035006 "requires": {
49045007 "has-value": "^0.3.1",
49055008 "isobject": "^3.0.0"
@@ -4986,7 +5089,7 @@
49865089 "xml-lexer": {
49875090 "version": "0.2.2",
49885091 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
4989− "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
5092+ "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
49905093 "requires": {
49915094 "eventemitter3": "^2.0.0"
49925095 }
@@ -4994,7 +5097,7 @@
49945097 "xml-reader": {
49955098 "version": "2.4.3",
49965099 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
4997− "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
5100+ "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
49985101 "requires": {
49995102 "eventemitter3": "^2.0.0",
50005103 "xml-lexer": "^0.2.2"
ui/package.json+18−6
@@ -1,17 +1,27 @@
11{
22 "name": "urbit-astrolabe",
3− "version": "0.0.0",
43 "type": "module",
4+ "version": "0.3.0",
55 "scripts": {
6− "dev:only": "vite --port 4000 --host",
7− "build:only": "vite build",
6+ "dev:vite": "vite --port 4000 --host",
7+ "build:vite": "vite build",
88 "tailwind:watch": "cross-env TAILWIND_MODE=watch cross-env NODE_ENV=development postcss src/styles/tailwind.css -o src/styles/tailwind-output.css -w",
99 "tailwind:build": "cross-env TAILWIND_MODE=build cross-env NODE_ENV=production postcss src/styles/tailwind.css -o src/styles/tailwind-output.css",
10− "dev": "concurrently \"npm run dev:only\" \"npm run tailwind:watch\"",
11− "build": "npm run tailwind:build && npm run build:only",
10+ "dev": "concurrently \"npm run dev:vite\" \"npm run tailwind:watch\"",
11+ "build": "npm run tailwind:build && npm run build:vite",
1212 "preview": "vite preview",
1313 "check": "svelte-check --tsconfig ./tsconfig.json",
14− "release": "npm run build && ../install.sh "
14+ "_install:glob": "../bin/install.sh -g $GLOBBER_DESK",
15+ "_install:desk": "../bin/install.sh $ASTROLABE_DESK",
16+ "_install:desk:watch": "../bin/install.sh -w $ASTROLABE_DESK",
17+ "install:glob": "dotenv -c local npm run _install:glob",
18+ "install:desk": "dotenv -c local npm run _install:desk",
19+ "install:desk:watch": "dotenv -c local npm run _install:desk:watch",
20+ "release": "npm run build && npm run install:glob",
21+ "_get-glob": "../bin/get_glob.sh $GLOBBER_DESK",
22+ "get-glob": "dotenv -c local npm run _get-glob",
23+ "_commit": "../bin/commit.sh $ASTROLABE_DESK",
24+ "commit": "dotenv -c local npm run _commit"
1525 },
1626 "devDependencies": {
1727 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
@@ -20,6 +30,7 @@
2030 "autoprefixer": "^10.4.2",
2131 "concurrently": "^7.0.0",
2232 "cross-env": "^7.0.3",
33+ "dotenv-cli": "^6.0.0",
2334 "postcss-cli": "^9.1.0",
2435 "sass": "^1.50.0",
2536 "svelte": "^3.48.0",
@@ -32,6 +43,7 @@
3243 "vite": "^2.7.2"
3344 },
3445 "dependencies": {
46+ "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
3547 "@tlon/phonemes-js": "^1.0.0",
3648 "@tlon/sigil-js": "^1.4.5",
3749 "@urbit/api": "^2.1.0",
ui/src/App.svelte+3−1
@@ -1,8 +1,9 @@
11<script lang="ts">
22 import Router from 'svelte-spa-router';
3+ import SigilPage from './routes/SigilPage.svelte';
34 import HelpPage from './routes/HelpPage.svelte';
45 import Home from './routes/Home.svelte';
5− import HelpModal from './components/HelpModal.svelte';
6+ import HelpModal from '@/docs/HelpModal.svelte';
67 import "./styles/styles.scss";
78
89 // const routes = {
@@ -14,6 +15,7 @@
1415 // }
1516
1617 const routes = {
18+ '/sigil': SigilPage,
1719 '/help/:path?': HelpPage,
1820 // '/:patp?': Home,
1921 // '/ship/:patp?': Home,
ui/src/actions/sigma.tsdeleted−73
@@ -1,73 +0,0 @@
1−import tippy from 'tippy.js';
2−// import DocLink from '../components/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something, like:
3−// const app = new DocLink({
4−// target: document.getElementBySelector('.tippy')
5−// })
6−
7−const defaultParams = {
8− theme: 'astrolabe',
9− delay: [500, 0],
10−};
11−
12−function getParams(paramsOrContent:any = {}, overrides = {}) {
13− let params = paramsOrContent;
14− if (typeof paramsOrContent === 'string') {
15− params = { content: paramsOrContent };
16− }
17− return {
18− ...defaultParams,
19− ...params,
20− ...overrides,
21− };
22−}
23−
24−export default function tooltip(node, paramsOrContent: any = {}) {
25− const onTrigger = () => {
26− console.log('triggered');
27− node.dispatchEvent(new CustomEvent("tooltipTriggered"));
28− };
29− const onUntrigger = () => {
30− console.log('untriggered');
31− node.dispatchEvent(new CustomEvent("tooltipUntriggered"));
32− };
33− const onClickOutside = (_, e) => {
34− e.stopPropagation();
35− };
36− const listeners = {
37− onTrigger,
38− onUntrigger,
39− onClickOutside,
40− }
41−
42− const params = getParams(paramsOrContent, listeners);
43− const { content } = params;
44−
45− // Let's make sure the "aria-label" attribute
46− // is set so our element is accessible:
47− // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute
48− if (!node.getAttribute("aria-label")) node.setAttribute("aria-label", content);
49−
50−
51− // Support any of the Tippy props by forwarding all "params":
52− // https://atomiks.github.io/tippyjs/v6/all-props/
53− const tip: any = tippy(node, params);
54− const hideTip = (e) => {
55− if (tip.state.isShown) {
56− tip.hide();
57− // e.stopPropagation();
58− }
59− }
60− node.addEventListener('click', hideTip);
61−
62−
63− return {
64− // If the props change, let's update the Tippy instance:
65− update: (newParams) => tip.setProps(getParams(newParams)),
66−
67− // Clean up the Tippy instance on unmount:
68− destroy: () => {
69− node.removeEventListener('click', hideTip);
70− tip.destroy();
71− }
72− };
73−};
ui/src/actions/symbolPartClick.tsnew file+27
@@ -0,0 +1,27 @@
1+function listenOnSvgElements(node, onPathChange) {
2+ const parts = node.querySelectorAll('path, circle, line');
3+ for (let part of parts) {
4+ if (part.getAttribute('dataisgeon') === 'true') continue;
5+ if (onPathChange) {
6+ part.onclick = () => {
7+ onPathChange(part.getAttribute('part-key'));
8+ };
9+ } else {
10+ part.onclick = null;
11+ }
12+ }
13+}
14+
15+export default function svgClick(node: HTMLElement, { onPartClick, enabled = true, svgString }) {
16+ if (enabled) listenOnSvgElements(node, onPartClick);
17+
18+ return {
19+ update: ({ onPartClick, enabled = true, svgString }) => {
20+ if (enabled) {
21+ listenOnSvgElements(node, onPartClick);
22+ } else {
23+ listenOnSvgElements(node, undefined);
24+ }
25+ },
26+ };
27+}
ui/src/actions/tooltip.ts+1−1
@@ -1,5 +1,5 @@
11import tippy from 'tippy.js';
2−// import DocLink from '../components/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something, like:
2+// import DocLink from '@/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something, like:
33// const app = new DocLink({
44// target: document.getElementBySelector('.tippy')
55// })
ui/src/assets/up-arrow.svgnew file+1
@@ -0,0 +1 @@
1+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g data-name="Layer 2"><g data-name="arrow-ios-upward"><rect width="24" height="24" transform="rotate(180 12 12)" opacity="0"/><path d="M18 15a1 1 0 0 1-.64-.23L12 10.29l-5.37 4.32a1 1 0 0 1-1.41-.15 1 1 0 0 1 .15-1.41l6-4.83a1 1 0 0 1 1.27 0l6 5a1 1 0 0 1 .13 1.41A1 1 0 0 1 18 15z"/></g></g></svg>
ui/src/assets/x.svgnew file+1
@@ -0,0 +1 @@
1+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g data-name="Layer 2"><g data-name="close"><rect width="24" height="24" transform="rotate(180 12 12)" opacity="0"/><path d="M13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29-4.3 4.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l4.29-4.3 4.29 4.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"/></g></g></svg>
ui/src/components/ChartLink.svelte+1−1
@@ -1,6 +1,6 @@
11<script lang="ts">
22 import { link } from 'svelte-spa-router'
3− import { linkToChart } from '../lib/link';
3+ import { linkToChart } from 'lib/link';
44
55 export let patp = null;
66</script>
ui/src/components/EthAddressLink.svelte+2−2
@@ -1,6 +1,6 @@
11<script lang="ts">
2− import CopyToClipboard from "svelte-copy-to-clipboard";
3− import tooltip from "../actions/tooltip";
2+ import CopyToClipboard from 'svelte-copy-to-clipboard';
3+ import tooltip from 'actions/tooltip';
44
55 let overrideText = null;
66
ui/src/components/HomeScreenTiles.svelte+3−3
@@ -17,21 +17,21 @@
1717</script>
1818
1919<div class="pt-10 max-w-md w-full flex flex-wrap gap-5 justify-center">
20− <div class="rounded-lg bg-white flex-grow max-w-xs">
20+ <div class="rounded-lg bg-white grow max-w-xs">
2121 <DocLink modal path="astrolabe">
2222 <div class="p-5 text-xl text-center">
2323 About Astrolabe
2424 </div>
2525 </DocLink>
2626 </div>
27− <div class="rounded-lg bg-white flex-grow max-w-xs">
27+ <div class="rounded-lg bg-white grow max-w-xs">
2828 <DocLink modal path="">
2929 <div class="p-5 text-xl text-center">
3030 About Urbit IDs
3131 </div>
3232 </DocLink>
3333 </div>
34− <a class="rounded-lg bg-navy border border-gold flex-grow max-w-s" use:link href="/chart">
34+ <a class="rounded-lg bg-navy border border-gold grow max-w-s" use:link href="/chart">
3535 <p class="p-5 text-xl text-center text-white">
3636 <br/>
3737 Star Chart
ui/src/components/SearchBar.sveltedeleted−47
@@ -1,47 +0,0 @@
1−<script lang="ts">
2−
3− import { SearchAnalysis } from '../lib/id';
4−
5− export let patp: string = undefined;
6− export let searchQuery: RegExp = /~der/;
7− export let analysis: SearchAnalysis = new SearchAnalysis();
8− export let search = '';
9− export let pauseAnalysis: boolean = false;
10− let debouncedSearch = search;
11−
12− export let placeholderText = "Search by @p or #. '*' matches any character";
13−
14− $: {
15− if (!pauseAnalysis) {
16− analysis = new SearchAnalysis(debouncedSearch);
17− if (analysis.queryIsValid) {
18− searchQuery = analysis.query;
19− } else {
20− searchQuery = undefined;
21− }
22− if (analysis.patpIsValid) {
23− patp = analysis.patp;
24− } else {
25− patp = undefined;
26− }
27− }
28− }
29−
30− let timer;
31− $: {
32− clearTimeout(timer);
33− timer = setTimeout(() => {
34− debouncedSearch = search;
35− }, 300);
36− }
37−</script>
38−
39−<div class="relative max-w-md rounded-full w-full">
40− <input type="text" class="p-4 py-2 w-full rounded-full"
41− spellcheck="false"
42− placeholder={placeholderText}
43− bind:value={search}
44− autofocus
45− />
46− <button class="absolute right-0 w-10 h-10 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"></button>
47−</div>
ui/src/components/ShipLink.svelte+2−2
@@ -1,7 +1,7 @@
11<script lang="ts">
22 import { link } from 'svelte-spa-router'
3− import { cite, normalizeId } from '../lib/id';
4− import { linkToShip } from '../lib/link';
3+ import { cite, normalizeId } from 'lib/id';
4+ import { linkToShip } from 'lib/link';
55 export let patp: string;
66 $: normalizedId = normalizeId(patp);
77</script>
ui/src/components/ShipListing.svelte+3−3
@@ -2,8 +2,8 @@
22
33 import Sigil from "./Sigil.svelte";
44 import { push } from 'svelte-spa-router'
5− import { normalizeId } from '../lib/id';
6− import { linkToShip as generateLinkToShip } from '../lib/link';
5+ import { normalizeId } from 'lib/id';
6+ import { linkToShip as generateLinkToShip } from 'lib/link';
77 export let ship: any;
88 export let linkToShip: boolean = false;
99 export let differentiateContacts = false;
@@ -25,7 +25,7 @@ on:click={handleClick}
2525on:click
2626>
2727 <div class="flex flex-wrap p-4 items-center">
28− <Sigil {patp} size={48} />
28+ <Sigil {patp} size={48} useNew />
2929 <!-- {#if ship.avatar}
3030 <img src={ship.avatar} class="h-12 w-12 mr-4" />
3131 {/if} -->
ui/src/components/ShipListings.svelte+1−1
@@ -1,6 +1,6 @@
11<script lang="ts">
22 import { createEventDispatcher } from 'svelte';
3− import ShipListing from './ShipListing.svelte';
3+ import ShipListing from '@/ShipListing.svelte';
44 const dispatch = createEventDispatcher();
55
66 export let ships = [];
ui/src/components/ShipView.svelte+3−3
@@ -1,8 +1,8 @@
11<script lang="ts">
22 import { clan, sein, patp2dec } from 'urbit-ob';
33
4− import { cite, normalizeId } from '../lib/id';
5− import { getPoint, getSpawnedPoints } from '../lib/api';
4+ import { cite, normalizeId } from 'lib/id';
5+ import { getPoint, getSpawnedPoints } from 'lib/api';
66 import Sigil from "./Sigil.svelte";
77 import GoldBadge from './GoldBadge.svelte';
88 import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
@@ -181,7 +181,7 @@
181181<div class="p-8 max-w-md w-full rounded-lg bg-white flex flex-col md:max-w-2xl">
182182 <div class="flex flex-col items-center space-y-4 md:flex-row md:items-start md:space-x-8 md:space-y-0">
183183 <div class="w-full max-w-[256px] md:min-w-[256px] flex-shrink-0">
184− <Sigil patp={patp} size={256} />
184+ <Sigil patp={patp} size={256} useNew />
185185 </div>
186186 <div class="grow w-full min-w-0">
187187 <h2 class="text-2xl text-center">
ui/src/components/Sigil.svelte+34−10
@@ -1,11 +1,17 @@
11<script lang="ts">
22 import { clan } from 'urbit-ob';
3− import { sigil, stringRenderer } from '@tlon/sigil-js';
4− import moonPng from '../assets/moon.png';
5− import cometSvg from '../assets/comet.svg';
3+ import { sigil as sigil1, stringRenderer as sr1 } from '@tlon/sigil-js';
4+ import { sigil as sigil2, stringRenderer as sr2 } from '@johnhyde/sigil-js';
5+ import { sigilScalingFunction } from 'lib/sigil';
6+ import moonPng from 'assets/moon.png';
7+ import cometSvg from 'assets/comet.svg';
68
79 export let patp: string;
810 export let size: number;
11+ export let useNew: boolean = false;
12+ export let fgColor: string = 'white';
13+ export let bgColor: string = 'black';
14+ export let altBgColor: string = undefined;
915 let svgString: any;
1016 let imgSrc: any;
1117
@@ -13,13 +19,31 @@
1319 $: displaySigil = ['galaxy', 'star', 'planet'].includes(shipClass);
1420 $: {
1521 if (displaySigil) {
16− const svgAST = sigil({
17− patp: patp,
18− size,
19− colors: ['black', 'white'],
20− });
22+ let sigil = sigil1;
23+ let sr = sr1;
24+ let svgAST;
25+ if (useNew) {
26+ sigil = sigil2;
27+ sr = sr2;
28+ svgAST = sigil({
29+ patp: patp,
30+ size,
31+ colors: [bgColor, fgColor],
32+ autoScaleStrokes: true,
33+ strokeScalingFunctionV2: sigilScalingFunction,
34+ });
35+ } else {
36+ svgAST = sigil({
37+ patp: patp,
38+ size,
39+ colors: [bgColor, fgColor],
40+ });
41+ }
2142 svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
22− svgString = stringRenderer(svgAST);
43+ svgString = sr(svgAST);
44+ if (altBgColor) {
45+ svgString = svgString.replace(/rect fill="[^"]+"/, `rect fill="${altBgColor}"`);
46+ }
2347 } else if (shipClass === 'moon') {
2448 imgSrc = moonPng;
2549 } else {
@@ -28,7 +52,7 @@
2852 }
2953</script>
3054
31−<div class="flex-grow" style:max-width="{size}px">
55+<div class="grow" style:max-width="{size}px">
3256 {#if displaySigil}
3357 {@html svgString}
3458 {:else}
ui/src/components/StarChart.svelte+4−4
@@ -8,10 +8,10 @@
88
99 import { clan } from 'urbit-ob';
1010 import DocLink from './docs/DocLink.svelte';
11− import { cite, normalizeId } from '../lib/id';
12− import { parseChartData, populateGraph } from '../lib/graph';
13− import { getChartData } from '../lib/api';
14− import { linkToShip } from '../lib/link';
11+ import { cite, normalizeId } from 'lib/id';
12+ import { parseChartData, populateGraph } from 'lib/graph';
13+ import { getChartData } from 'lib/api';
14+ import { linkToShip } from 'lib/link';
1515
1616 import InlineSelect from './buttons/InlineSelect.svelte';
1717
ui/src/components/Tooltip.svelte+1−2
@@ -1,6 +1,5 @@
11<script lang="ts">
2− import { tick } from "svelte";
3− import tooltip from "../actions/tooltip";
2+ import tooltip from 'actions/tooltip';
43 export let text: string = '';
54 let className: string = '';
65 export { className as class };
ui/src/components/docs/Doc.svelte+3−3
@@ -2,8 +2,8 @@
22 import { createEventDispatcher } from 'svelte';
33 import DocContent from './DocContent.svelte';
44 import DocHome from './DocHome.svelte';
5− import Hamburger from '../buttons/Hamburger.svelte';
6− import CloseButton from '../buttons/CloseButton.svelte';
5+ import Hamburger from '@/buttons/Hamburger.svelte';
6+ import CloseButton from '@/buttons/CloseButton.svelte';
77
88 export let path: string;
99
@@ -26,7 +26,7 @@
2626 {:else}
2727 <div class="w-7 h-7" />
2828 {/if}
29− <div class="flex-grow text-center text-xl">
29+ <div class="grow text-center text-xl">
3030 Help
3131 </div>
3232 <CloseButton on:click={close} />
ui/src/components/docs/DocContent.svelte+3−3
@@ -2,10 +2,10 @@
22
33 import { createEventDispatcher } from 'svelte';
44
5− import docLinks from "../../actions/docLinks";
6− import docs from "../../stores/docs";
5+ import docLinks from "actions/docLinks";
6+ import docs from "stores/docs";
77
8− import LoadingSpinner from "../LoadingSpinner.svelte";
8+ import LoadingSpinner from "@/LoadingSpinner.svelte";
99
1010 export let path:string;
1111
ui/src/components/docs/DocLink.svelte+1−1
@@ -1,6 +1,6 @@
11<script lang="ts">
22 import { createEventDispatcher } from 'svelte';
3− import docs from '../../stores/docs';
3+ import docs from 'stores/docs';
44
55 export let path:string;
66 export let modal:boolean = false;
ui/src/components/HelpModal.svelte → ui/src/components/docs/HelpModal.svelterenamed+2−2
@@ -1,6 +1,6 @@
11<script lang="ts">
2− import docs from '../stores/docs';
3− import Doc from './docs/Doc.svelte';
2+ import docs from 'stores/docs';
3+ import Doc from './Doc.svelte';
44
55 function close() {
66 docs.hide()
ui/src/components/search/SearchBar.sveltenew file+125
@@ -0,0 +1,125 @@
1+<script lang="ts">
2+ import SigilInput from './sigil/SigilInput.svelte';
3+ import Symbol from './sigil/Symbol.svelte';
4+
5+ import docs from 'stores/docs';
6+ import { SearchAnalysis } from 'lib/id';
7+ import { SigilQuery } from 'lib/sigil';
8+
9+ export let patp: string = undefined;
10+ export let analysis: SearchAnalysis = new SearchAnalysis();
11+ export let search = '';
12+ export let sigilQuery: SigilQuery = new SigilQuery('planet');
13+ sigilQuery.urlString = search;
14+ export let searchMode: ('patp' | 'sigil') = 'patp';
15+ export let placeholderText = "Search by @p or #. '*' matches any character";
16+
17+ $: showSigilInput = searchMode == 'sigil';
18+
19+ let bouncySearch: string = search;
20+ let debouncedSearch: string = bouncySearch;
21+
22+ let timer;
23+ $: {
24+ if (!showSigilInput){
25+ if (bouncySearch !== search) {
26+ clearTimeout(timer);
27+ timer = setTimeout(() => {
28+ search = bouncySearch;
29+ }, 300);
30+ }
31+ }
32+ }
33+
34+ function updateSigilUrlString() {
35+ if (showSigilInput) {
36+ if (search !== sigilQuery.urlString) {
37+ sigilQuery.urlString = search;
38+ }
39+ }
40+ }
41+
42+ function updateSigilSearch() {
43+ if (showSigilInput) {
44+ if (sigilQuery.isNotEmpty) {
45+ search = sigilQuery.urlString;
46+ } else {
47+ search = '';
48+ }
49+ }
50+ }
51+
52+ $: search, updateSigilUrlString();
53+ $: sigilQuery.urlString, updateSigilSearch();
54+
55+ $: {
56+ if (showSigilInput) {
57+ if (sigilQuery.isDefinitive) {
58+ patp = sigilQuery.patp;
59+ } else {
60+ patp = undefined;
61+ }
62+ } else {
63+ analysis = new SearchAnalysis(search);
64+ if (analysis.patpIsValid) {
65+ patp = analysis.patp;
66+ } else {
67+ patp = undefined;
68+ }
69+ bouncySearch = search = analysis.search;
70+ }
71+ }
72+
73+ function openSigilInput() {
74+ searchMode = 'sigil';
75+ }
76+
77+ function closeSigilInput() {
78+ searchMode = 'patp';
79+ bouncySearch = patp || analysis.search;
80+ debouncedSearch = bouncySearch;
81+ search = debouncedSearch;
82+ }
83+
84+ function showSigilDoc() {
85+ docs.show('sigil');
86+ }
87+
88+ const buttonClasses = 'rounded-full bg-black text-white text-sm transition-opacity opacity-50 hover:opacity-80';
89+</script>
90+
91+<div class="relative max-w-md w-full">
92+ {#if showSigilInput}
93+ <div class="rounded-2xl bg-white overflow-hidden">
94+ <SigilInput bind:sigilQuery></SigilInput>
95+ </div>
96+ <button
97+ on:click={showSigilDoc}
98+ class="absolute top-2 left-2 w-6 h-6 {buttonClasses}"
99+ >
100+ ?
101+ </button>
102+ <button
103+ on:click={closeSigilInput}
104+ class="absolute top-2 right-2 w-20 h-6 {buttonClasses}"
105+ >
106+ patp or #
107+ </button>
108+ {:else}
109+ <input type="text" class="p-4 py-2 w-full rounded-full"
110+ spellcheck="false"
111+ placeholder={placeholderText}
112+ on:input={(e) => bouncySearch = e.target.value}
113+ value={search}
114+ autofocus
115+ />
116+ <button
117+ on:click={openSigilInput}
118+ class="absolute top-1 right-1 w-8 h-8 {buttonClasses}"
119+ >
120+ <Symbol components={'gci.lvf.lhf.lbhb.atrf4.al3.cbl2lf.cbl2mb'.split('.')} size={32}
121+ altBgColor="transparent"
122+ />
123+ </button>
124+ {/if}
125+</div>
ui/src/components/SearchResultNavButtons.svelte → ui/src/components/search/SearchResultNavButtons.svelterenamed

The content is the same.

ui/src/components/SearchResults.svelte → ui/src/components/search/SearchResults.svelterenamed+18−9
@@ -1,15 +1,18 @@
11<script lang="ts">
22
33 import type { Patp, Rolodex } from '@urbit/api';
4− import { searchPoints } from '../lib/api';
5− import { searchedContacts } from '../stores/searchStores';
6− import ShipListings from './ShipListings.svelte';
4+ import { searchPoints } from 'lib/api';
5+ import type { SigilQuery } from 'lib/sigil';
6+ import { searchedContacts } from 'stores/searchStores';
7+ import ShipListings from '@/ShipListings.svelte';
78 import SearchSettings from './SearchSettings.svelte';
89 import SearchResultsNavButtons from './SearchResultsNavButtons.svelte';
910
10− export let query: RegExp;
11− export let search: string;
12− // export let patp: string;
11+ export let regexQuery: RegExp;
12+ export let sigilQuery: SigilQuery;
13+ export let patpSearch: string;
14+ export let searchMode: ('patp' | 'sigil') = 'patp';
15+
1316 let combinedSearchResults = [];
1417 let searchedPointsP: Promise<any>;
1518 let searchedPoints: Patp[] = [];
@@ -25,14 +28,19 @@
2528 $: {
2629 clearTimeout(timer);
2730 timer = setTimeout(() => {
28− searchedContacts.search(query);
31+ if (searchMode == 'patp') {
32+ searchedContacts.searchPatp(regexQuery);
33+ } else if (searchMode == 'sigil') {
34+ searchedContacts.searchSigil(sigilQuery.querySyls);
35+ }
2936 }, 1000);
3037 }
38+
3139 function doSearch() {
3240 const searchSnap = search;
3341 searchStatus = 'prog';
3442 console.log(`starting search for "${searchSnap}"`);
35− searchedPointsP = searchPoints(searchSnap);
43+ searchedPointsP = searchPoints(searchSnap, searchMode);
3644 searchedPointsP.then((points) => {
3745 if (search === searchSnap) {
3846 searchedPoints = points;
@@ -48,7 +56,8 @@
4856 }
4957 });
5058 }
51− $: search, doSearch();
59+ $: search = (searchMode === 'sigil') ? sigilQuery.string : patpSearch;
60+ $: [search], doSearch();
5261
5362 $: {
5463 const contactSearchResults: Rolodex = JSON.parse(JSON.stringify($searchedContacts));
ui/src/components/SearchResultsNavButtons.svelte → ui/src/components/search/SearchResultsNavButtons.svelterenamed

The content is the same.

ui/src/components/SearchSettings.svelte → ui/src/components/search/SearchSettings.svelterenamed+3−3
@@ -1,8 +1,8 @@
11<script lang="ts">
2− import ToggleButton from './buttons/ToggleButton.svelte';
2+ import ToggleButton from '../buttons/ToggleButton.svelte';
33
4− import { searchSettings } from '../stores/searchStores';
5− import { toggleStoreKey } from '../lib/utils';
4+ import { searchSettings } from 'stores/searchStores';
5+ import { toggleStoreKey } from 'lib/utils';
66
77 function toggleMoons() {
88 toggleStoreKey(searchSettings, 'includeMoons');
ui/src/components/search/sigil/ClanSelect.sveltenew file+36
@@ -0,0 +1,36 @@
1+<script lang="ts">
2+ import ClanSelectOption from './ClanSelectOption.svelte';
3+
4+ import { capitalize } from 'lib/utils';
5+ import { CLANS, Clan } from 'types/sigil';
6+
7+ export let clan: Clan = 'planet';
8+
9+ let dropdownOpen = false;
10+
11+ function selectClan(itemClan: typeof clan) {
12+ clan = itemClan;
13+ dropdownOpen = false;
14+ }
15+</script>
16+
17+<div class="text-xl text-center bg-white">
18+ <p
19+ on:click={() => dropdownOpen = true}
20+ class=" py-2"
21+ >
22+ {capitalize(clan)}
23+ <span class="font-mono">▼</span>
24+ </p>
25+ {#if dropdownOpen}
26+ <div class="absolute z-20 top-1 w-full flex flex-col mx-auto divide-y bg-white rounded-2xl border-b">
27+ <h3 class="py-2" on:click={() => dropdownOpen = false}>
28+ Select Sigil Type
29+ </h3>
30+ {#each CLANS as itemClan}
31+ <ClanSelectOption clan={itemClan} on:click={() => selectClan(itemClan)} />
32+ {/each}
33+ </div>
34+ {/if}
35+</div>
36+
ui/src/components/search/sigil/ClanSelectOption.sveltenew file+17
@@ -0,0 +1,17 @@
1+<script lang="ts">
2+ import { capitalize } from 'lib/utils';
3+ import MiniFakeSigil from './MiniFakeSigil.svelte';
4+
5+ export let sigilSearch = {};
6+ export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
7+
8+ let dropdownOpen = true;
9+</script>
10+
11+<div class="flex items-center justify-center gap-2 py-2" on:click>
12+ <h3>
13+ {capitalize(clan)}:
14+ </h3>
15+ <MiniFakeSigil {clan} />
16+</div>
17+
ui/src/components/search/sigil/GeonSelector.sveltenew file+30
@@ -0,0 +1,30 @@
1+<script lang="ts">
2+ import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
3+
4+ import { searchSettings } from 'stores/searchStores';
5+ import type { SymbolQuery } from 'lib/sigil';
6+ import { GEONS } from 'types/sigil';
7+ import { arePartsPlausible } from 'lib/sigil';
8+
9+ export let symbolQuery: SymbolQuery;
10+ export let popMode: Function;
11+
12+ function setGeon(geon) {
13+ symbolQuery.geon = geon;
14+ popMode();
15+ }
16+
17+ $: componentsSansGeon = symbolQuery.components.filter(c => c[0] !== 'g');
18+</script>
19+
20+{#each GEONS as geon}
21+ {@const plausible = $searchSettings.allowFictionalSigils ||
22+ arePartsPlausible(componentsSansGeon, symbolQuery.symbolType, [geon])
23+ }
24+ <SymbolInputModeSelectorButton components={[geon]}
25+ focused={geon === symbolQuery.geon}
26+ enabled={plausible}
27+ on:click={() => setGeon(geon)}
28+ />
29+{/each}
30+
ui/src/components/search/sigil/MiniFakeSigil.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import { sigilFromSymbols, stringRenderer as sr, symbolFromDef } from '@johnhyde/sigil-js';
3+
4+ export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
5+
6+ let size = 32;
7+ let squareDef = ['gci'];
8+ let square = symbolFromDef(squareDef);
9+ let sigilProps = {
10+ size,
11+ renderer: sr,
12+ colors: ['white', 'black'],
13+ icon: true,
14+ }
15+ let sigils = {
16+ galaxy: sigilFromSymbols([square], sigilProps),
17+ star: sigilFromSymbols([square, square], sigilProps),
18+ planet: sigilFromSymbols([square, square, square, square], sigilProps),
19+ }
20+ $: sigilWidth = (clan === 'galaxy' ? size / 2 : size);
21+</script>
22+
23+<div style:width="{sigilWidth}px">
24+ {@html sigils[clan]}
25+</div>
ui/src/components/search/sigil/SigilInput.sveltenew file+92
@@ -0,0 +1,92 @@
1+<script lang="ts">
2+ import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
3+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
4+ import SymbolInput from './SymbolInput.svelte';
5+ import ClanSelect from './ClanSelect.svelte';
6+ import ToggleButton from '@/buttons/ToggleButton.svelte';
7+
8+ import type { SigilQuery } from 'lib/sigil';
9+ import { searchSettings } from 'stores/searchStores';
10+ import { toggleStoreKey } from 'lib/utils';
11+
12+ export let sigilQuery: SigilQuery;
13+ let focusedSymbolIndex: number = undefined;
14+ let inputComponents: string[] = [];
15+
16+ $: gridClasses = (sigilQuery.clan === 'galaxy' ? 'max-w-[10rem] grid items-center' : 'max-w-[18rem] grid grid-cols-2 items-center');
17+
18+ function unfocusSymbol() {
19+ focusedSymbolIndex = undefined;
20+ }
21+ $: focusedSymbol = sigilQuery.activeSymbols[focusedSymbolIndex];
22+ $: {
23+ const numActiveSymbols = sigilQuery.activeSymbols.length;
24+ if (focusedSymbolIndex >= numActiveSymbols) {
25+ focusedSymbolIndex = numActiveSymbols - 1;
26+ }
27+ }
28+
29+ function toggleAllowFictional() {
30+ toggleStoreKey(searchSettings, 'allowFictionalSigils');
31+ }
32+
33+ function clear() {
34+ sigilQuery = sigilQuery.clearSymbols();
35+ }
36+
37+ $: {
38+ sigilQuery.allowFictional = $searchSettings.allowFictionalSigils;
39+ }
40+</script>
41+
42+<div>
43+ <ClanSelect bind:clan={sigilQuery.clan} />
44+ <div class="flex flex-col xs:flex-row">
45+ <div class="grow-[2] w-full xs:m-0 xs:max-w-[18rem]" on:click={unfocusSymbol}>
46+ <div class="min-h-[14rem] mx-auto p-4 {gridClasses} xs:rounded-bl-2xl">
47+ {#each sigilQuery.activeSymbols as symbol, index}
48+ <SymbolInput
49+ bind:symbolQuery={symbol}
50+ {focusedSymbolIndex}
51+ {index}
52+ clan={sigilQuery.clan}
53+ {inputComponents}
54+ on:click={() => focusedSymbolIndex = index}
55+ />
56+ {/each}
57+ </div>
58+ </div>
59+ <div class="px-4 py-2 border-t grow rounded-b-2xl xs:border-l xs:border-t-0 xs:rounded-bl-none">
60+ {#if focusedSymbol}
61+ <SymbolInputModeSelector
62+ bind:symbolQuery={focusedSymbol}
63+ bind:inputComponents
64+ unfocusSymbol={unfocusSymbol}
65+ />
66+ {:else}
67+ <SymbolInputNavButtons onClear={sigilQuery.isNotEmpty ? clear : null} />
68+ <p class="mt-2">
69+ Allow Sigils which don't exist:
70+ <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
71+ </p>
72+ {/if}
73+ </div>
74+ </div>
75+ <!-- {#each sigilQuery.activeSymbols as symbol}
76+ {@const plausibleSyllables = symbol.plausibleSyllables}
77+ {@const plausibleParts = symbol.plausibleParts}
78+ <p>
79+ {#if plausibleSyllables.length < 20 && plausibleSyllables.length > 0}
80+ {plausibleSyllables.join(', ')}:
81+ {#if plausibleParts.length < 20 && plausibleParts.length > 0}
82+ {plausibleParts.join(', ')}
83+ {:else}
84+ {plausibleParts.length}
85+ {/if}
86+ {:else}
87+ {plausibleSyllables.length}
88+ {/if}
89+ </p>
90+ {/each} -->
91+ <!-- {sigilQuery.string} -->
92+</div>
ui/src/components/search/sigil/Symbol.sveltenew file+79
@@ -0,0 +1,79 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import { sigilFromSymbols, stringRenderer as sr, symbolFromDef } from '@johnhyde/sigil-js';
4+ import { sigilScalingFunction } from 'lib/sigil';
5+ import symbolPartClick from 'actions/symbolPartClick';
6+
7+ export let components: string[];
8+ export let size: number;
9+ export let fgColor: string = 'black';
10+ export let bgColor: string = 'white';
11+ export let altBgColor: string = undefined;
12+ export let hoverColor: string = '#8f8';
13+ export let opacity: number = 1;
14+ export let inverted: boolean = false;
15+ export let interactive: boolean = false;
16+ export let strokeWidthFactor: number = 1;
17+ let svgString: any;
18+
19+ const dispatch = createEventDispatcher();
20+
21+ $: {
22+ const symbol = symbolFromDef(components);
23+ let colors = [bgColor, fgColor];
24+ let tempAltBgColor = altBgColor;
25+ if (inverted) {
26+ colors = [fgColor, bgColor];
27+ if (!tempAltBgColor) tempAltBgColor = bgColor;
28+ }
29+ const svgAST = sigilFromSymbols(
30+ [symbol],
31+ {
32+ size,
33+ colors,
34+ margin: false,
35+ autoScaleStrokes: true,
36+ strokeScalingFunctionV2: (s) => {
37+ return strokeWidthFactor * sigilScalingFunction(s);
38+ },
39+ },
40+ );
41+ svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
42+ svgString = sr(svgAST);
43+ if (tempAltBgColor) {
44+ svgString = svgString.replace(/rect fill="[^"]+"/, `rect fill="${tempAltBgColor}"`);
45+ }
46+ }
47+
48+ function onPartClick(partId: string) {
49+ dispatch('partClick', partId);
50+ }
51+</script>
52+
53+<style lang="scss" global>
54+ .interactive {
55+ svg {
56+ pointer-events: none;
57+ }
58+
59+ path, circle, line {
60+ pointer-events: auto;
61+
62+ &:not([dataisgeon="true"]):hover {
63+ stroke: var(--hoverColor);
64+ }
65+ }
66+
67+ circle:not([dataisgeon="true"]):not([fill="none"]):hover {
68+ fill: var(--hoverColor);
69+ }
70+ }
71+</style>
72+
73+<div class="aspect-square" class:interactive style:max-width="{size}px"
74+ style="--hoverColor: {hoverColor}; opacity: {opacity};"
75+ on:click
76+ use:symbolPartClick={{ onPartClick, enabled: interactive, svgString }}
77+>
78+ {@html svgString}
79+</div>
ui/src/components/search/sigil/SymbolInput.sveltenew file+105
@@ -0,0 +1,105 @@
1+<script lang="ts">
2+ import Symbol from './Symbol.svelte';
3+ import without from 'lodash/without';
4+
5+ import { searchSettings } from 'stores/searchStores';
6+ import type { SymbolQuery } from 'lib/sigil';
7+ import { filterPartsByGeon } from 'lib/sigil';
8+
9+ export let symbolQuery: SymbolQuery;
10+ export let focusedSymbolIndex: number;
11+ export let index: number = 0;
12+ export let clan: string = 'galaxy';
13+ export let inputComponents: string[] = [];
14+ export let fgColor = 'black';
15+ export let bgColor = 'white';
16+ let size = 128;
17+
18+ let translateClass;
19+ $: {
20+ translateClass = 'translate-x-0 translate-y-0';
21+ if (focusedSymbolIndex !== undefined) {
22+ if (clan === 'star') {
23+ translateClass = index ? 'origin-right' : 'origin-left';
24+ } else if (clan === 'planet') {
25+ let cornerIndex = index;
26+ if (!focused) cornerIndex = focusedSymbolIndex;
27+ switch (cornerIndex) {
28+ case 0: translateClass = 'translate-x-1/4 translate-y-1/4'; break;
29+ case 1: translateClass = '-translate-x-1/4 translate-y-1/4'; break;
30+ case 2: translateClass = 'translate-x-1/4 -translate-y-1/4'; break;
31+ case 3:
32+ default: translateClass = '-translate-x-1/4 -translate-y-1/4';
33+ }
34+ }
35+ }
36+ }
37+ $: scaleClass = 'scale-150 ';
38+
39+ $: focused = index === focusedSymbolIndex;
40+ $: focusedClasses = (
41+ focused ? `${scaleClass} z-10`
42+ : (focusedSymbolIndex !== undefined ? 'scale-50' : '' )
43+ );
44+
45+ function onPartDelete({ detail: partId }) {
46+ symbolQuery = symbolQuery.removePart(partId);
47+ }
48+
49+ function onPartClick({ detail: partId }) {
50+ symbolQuery = symbolQuery.addPart(partId);
51+ }
52+
53+ let filteredInputComponents = [];
54+ let inputContainsStrokes;
55+ $: {
56+ if (inputComponents) {
57+ filteredInputComponents = without(inputComponents, ...symbolQuery.components);
58+ } else {
59+ filteredInputComponents = [];
60+ }
61+ filteredInputComponents = filterPartsByGeon(filteredInputComponents, symbolQuery.geon);
62+ }
63+ $: {
64+ inputContainsStrokes = undefined !== filteredInputComponents.find((c) => {
65+ return c[0] === 'l' || c[0] === 'a' || c[0] === 'b';
66+ });
67+ }
68+ $: plausible = $searchSettings.allowFictionalSigils || symbolQuery.isPlausible;
69+</script>
70+
71+<div class="aspect-square relative flex transition {translateClass} {focusedClasses}"
72+ style:max-width="{size}px"
73+ on:click|stopPropagation
74+>
75+ {#if symbolQuery.components.length > 0}
76+ <Symbol components={symbolQuery.components} {size}
77+ inverted={!symbolQuery.geon}
78+ interactive={focused}
79+ strokeWidthFactor={focused ? 2 : 1}
80+ fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? 'black' : '#333')}
81+ bgColor="white"
82+ altBgColor="transparent"
83+ hoverColor="red"
84+ on:partClick={onPartDelete}
85+ />
86+ {:else}
87+ <div class="border-[1.5px] border-white grow bg-white">
88+ <div class="rounded-md border-4 border-gray-500 h-full"></div>
89+ </div>
90+ {/if}
91+ {#if focused && filteredInputComponents.length}
92+ <div class="absolute inset-0 pointer-events-none">
93+ <Symbol components={filteredInputComponents} {size}
94+ strokeWidthFactor={inputContainsStrokes ? 2.5 : 1}
95+ fgColor={symbolQuery.geon ? bgColor : fgColor}
96+ bgColor={!symbolQuery.geon ? bgColor : fgColor}
97+ altBgColor="transparent"
98+ opacity={0.5}
99+ inverted
100+ interactive
101+ on:partClick={onPartClick}
102+ />
103+ </div>
104+ {/if}
105+</div>
ui/src/components/search/sigil/SymbolInputModeSelector.sveltenew file+129
@@ -0,0 +1,129 @@
1+<script lang="ts">
2+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
3+
4+ import GeonSelector from './GeonSelector.svelte';
5+ import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
6+
7+ import { searchSettings } from 'stores/searchStores';
8+ import type { PartType, ModeDef, ModeDefs } from 'types/sigil';
9+ import type { SymbolQuery } from 'lib/sigil';
10+ import { MODE_DEFS } from 'types/sigil';
11+ import { filterPartsByGeon, getDeepParts } from 'lib/sigil';
12+
13+ export let symbolQuery: SymbolQuery = null;
14+ export let inputComponents: string[] = [];
15+ export let unfocusSymbol: Function = () => {};
16+
17+ let modes: string[] = [];
18+ let modeDef: ModeDef = undefined;
19+ let subModeDefs: ModeDefs = MODE_DEFS;
20+
21+ function showGeonSelector() {
22+ if (!symbolQuery.components.length && modes.length == 0) {
23+ modes = ['geon'];
24+ }
25+ }
26+
27+ $: symbolQuery.components, showGeonSelector();
28+
29+ function pushMode(mode: string) {
30+ if (modeDef?.modes) {
31+ modes = [...modes, mode];
32+ } else {
33+ modes = [...modes.slice(0, -1), mode];
34+ }
35+ }
36+
37+ function popMode() {
38+ if (modes.length) {
39+ if (modeDef?.modes) {
40+ modes = modes.slice(0, -1);
41+ } else {
42+ modes = modes.slice(0, -2);
43+ }
44+ } else {
45+ unfocusSymbol();
46+ }
47+ }
48+
49+ $: {
50+ modeDef = undefined;
51+ subModeDefs = MODE_DEFS;
52+ for (let mode of modes) {
53+ if (modeDef && modeDef.modes) {
54+ subModeDefs = modeDef.modes;
55+ }
56+ if (subModeDefs[mode]) {
57+ modeDef = subModeDefs[mode];
58+ } else {
59+ console.error(`Unknown mode: ${mode} of ${modes} in defs:`, subModeDefs);
60+ }
61+ }
62+
63+ if (modeDef && modeDef.modes) {
64+ subModeDefs = modeDef.modes;
65+ }
66+
67+ if (modeDef?.parts?.length && !modeDef.modes) {
68+ inputComponents = modeDef.parts;
69+ if (!$searchSettings.allowFictionalSigils) {
70+ inputComponents = symbolQuery.filterPlausibleParts(inputComponents);
71+ }
72+ } else {
73+ inputComponents = [];
74+ }
75+ }
76+
77+ $: plausibleModes = Object.entries(subModeDefs).map(([modeName, def]) => {
78+ const deepParts = getDeepParts(def);
79+ const plausible = $searchSettings.allowFictionalSigils || symbolQuery.arePartsPlausible(deepParts);
80+ if (!plausible && modeName !== 'geon') return null;
81+ const parts = filterPartsByGeon(def.displayParts || def.parts, symbolQuery.geon);
82+ return { parts, modeName };
83+ }).filter(mode => mode !== null);
84+
85+ $: {
86+ const currentMode = modes.slice(-1)[0];
87+ if (currentMode != 'geon') {
88+ if (plausibleModes.length === 0) {
89+ popMode();
90+ } else if (plausibleModes.length >= 1) {
91+ const leafMode = modeDef && !(modeDef.modes);
92+ const plausibleModeNames = plausibleModes.map(({ modeName }) => modeName);
93+ if ((modes.length == 1 && modeDef.modes) || (leafMode && !plausibleModeNames.includes(currentMode))) {
94+ pushMode(plausibleModes[0].modeName);
95+ }
96+ }
97+ }
98+ }
99+
100+ function clear() {
101+ if (modes.length) {
102+ symbolQuery = symbolQuery.clearPartType(modes[0] as PartType);
103+ } else {
104+ symbolQuery = symbolQuery.clear();
105+ }
106+ }
107+</script>
108+
109+<style lang="scss">
110+ .grid-cols-custom {
111+ grid-template-columns: repeat(auto-fill, 48px);
112+ }
113+</style>
114+
115+<div class="mx-auto flex justify-center xs:items-center xs:flex-col">
116+ <SymbolInputNavButtons onDone={popMode} onClear={clear} />
117+ <div class="max-w-[15rem] h-full grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
118+ {#if modes[0] === 'geon'}
119+ <GeonSelector bind:symbolQuery {popMode} />
120+ {:else}
121+ {#each plausibleModes as { modeName, parts }}
122+ <SymbolInputModeSelectorButton components={parts} focused={modes.slice(-1)[0] === modeName}
123+ on:click={() => pushMode(modeName)}
124+ />
125+ {/each}
126+ {/if}
127+ </div>
128+</div>
129+
ui/src/components/search/sigil/SymbolInputModeSelectorButton.sveltenew file+33
@@ -0,0 +1,33 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import Symbol from './Symbol.svelte';
4+
5+ export let components: string[] = [];
6+ export let focused: boolean = false;
7+ export let enabled: boolean = true;
8+ export let padding: number = undefined;
9+
10+ const dispatch = createEventDispatcher();
11+ let size = 48;
12+
13+ $: focusedClasses = focused ? 'border-2 border-gold-s1 p-[3px]' : 'border border-gray-500 p-[4px]';
14+ $: enabledClasses = enabled ? '' : 'opacity-50';
15+ $: includesGeon = components.find(c => c[0] === 'g') !== undefined;
16+ $: defaultPadding = includesGeon ? 0 : 0;
17+ $: actualPadding = padding !== undefined ? padding : defaultPadding;
18+
19+ function onClick(e) {
20+ if (enabled) dispatch('click', e);
21+ }
22+</script>
23+<div
24+class="bg-white rounded-md w-[48px] {focusedClasses} {enabledClasses}"
25+on:click={onClick}
26+>
27+<div class:cursor-pointer={enabled}
28+ style:padding="{actualPadding}px"
29+>
30+ <Symbol {components} {size} inverted={!includesGeon} />
31+</div>
32+</div>
33+
ui/src/components/search/sigil/SymbolInputNavButtons.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import upArrow from 'assets/up-arrow.svg';
3+ import xIcon from 'assets/x.svg';
4+
5+ export let onDone: () => void = null;
6+ export let onClear: () => void = null;
7+
8+ let buttonClasses = 'cursor-pointer border border-gray-500 rounded-md aspect-square w-[48px]';
9+</script>
10+
11+<style lang="scss">
12+ .grid-cols-custom {
13+ grid-template-columns: repeat(auto-fill, 48px);
14+ }
15+</style>
16+
17+<div class="grid gap-1 self-start justify-center mr-1 xs:mr-0 xs:mb-1 xs:self-stretch {(onDone && onClear) ? 'grid-cols-custom' : ''}">
18+ {#if onDone}
19+ <img src={upArrow} alt="done" class={buttonClasses} on:click={onDone}/>
20+ {/if}
21+ {#if onClear}
22+ <img src={xIcon} alt="clear" class={buttonClasses} on:click={onClear}/>
23+ {/if}
24+ </div>
25+
ui/src/lib/api.ts+2−2
@@ -26,8 +26,8 @@ export function getPeers(): Promise<any> {
2626 return scry<any>(`/peers`);
2727}
2828
29−export async function searchPoints(search: string): Promise<Patp[]> {
30− const { points } = await scry<any>(`/search/${search}`);
29+export async function searchPoints(search: string, mode: ('patp' | 'sigil')): Promise<Patp[]> {
30+ const { points } = await scry<any>(`/search/${mode}/${search}`);
3131 return points.map(normalizeId);
3232}
3333
ui/src/lib/colors.ts+3
@@ -1,5 +1,8 @@
1+export const ORANGE_GOLD = '#f7d392';
12export const GOLD = '#fcd34d';
23export const GOLDT2 = '#fddc71';
4+export const GOLDS1 = '#e3be45';
5+export const GOLDS2 = '#caa93e';
36export const WHITE = '#FFF';
47export const LGREY = '#BBB';
58export const DGREY = '#777';
ui/src/lib/id.ts+8−4
@@ -435,14 +435,18 @@ function cite(ship: string) {
435435 return `~${patp}`;
436436}
437437
438−function splitIdIntoWords(str: string): string[] {
439− return str.split(/(?:--|[-_^])/);
438+export function splitIdIntoWords(str: string): string[] {
439+ return desig(str).split(/(?:--|[-_^])/);
440+}
441+
442+export function splitIdIntoSyls(str: string): string[] {
443+ return splitIdIntoWords(str).map(word => [word.slice(0,3), word.slice(3,6)]).flat();
440444}
441445
442446function padWithWild(search: string) {
443447 let words = search.split(/[_^]/);
444− words[0] = '*'.repeat(6 - words[0].length) + words[0];
445− words[1] += '*'.repeat(6 - words[1].length);
448+ words[0] = '*'.repeat(Math.max(0, 6 - words[0].length)) + words[0];
449+ words[1] += '*'.repeat(Math.max(0, 6 - words[1].length));
446450 return words.join(search.match(/([_^])/)[1]);
447451}
448452
ui/src/lib/link.ts+5−1
@@ -4,9 +4,13 @@ export function linkToShip(patp: string, queryParams = {}): string {
44 return `/ship/${patp}` + buildQuerystring(queryParams);
55}
66
7+export function linkToSearch(search: string, queryParams = {}): string {
8+ return `/search/${search}` + buildQuerystring(queryParams);
9+}
10+
711export function linkToChart(patp: string): string {
812 if (patp) {
9− return `/chart/${patp}`;
13+ return `/chart/${patp}`;
1014 }
1115 return `/chart`;
1216}
ui/src/lib/sigil.tsnew file+375
@@ -0,0 +1,375 @@
1+import intersection from 'lodash/intersection';
2+import without from 'lodash/without';
3+import uniq from 'lodash/uniq';
4+import { partParents, symbolDefs } from '@johnhyde/sigil-js';
5+
6+import type { Clan, Geon, ModeDef, PartType, SymbolType } from 'types/sigil';
7+import { PART_TYPES } from 'types/sigil';
8+import { prefixes, suffixes } from 'lib/id';
9+
10+export function sigilScalingFunction(size: number): number {
11+ return 3.5 * Math.sqrt(51 / size);
12+}
13+
14+export class SigilQuery {
15+ clan: Clan;
16+ symbols: SymbolQuery[];
17+ allowFictional: boolean = false;
18+
19+ constructor(clan: Clan = 'planet', symbols = []) {
20+ this.clan = clan;
21+ this.symbols = symbols;
22+ this.padSymbols();
23+ }
24+
25+ padSymbols() {
26+ if (this.symbols.length === 1) {
27+ this.symbols.unshift(new SymbolQuery(this));
28+ }
29+ while (this.symbols.length < 4) {
30+ this.symbols.push(new SymbolQuery(this));
31+ }
32+ this.symbols = this.symbols.slice(0, 4);
33+ this.symbols.forEach((symbol, i) => {
34+ if (i % 2 === 0) {
35+ symbol.symbolType = 'prefix';
36+ // symbol.components = ['gci', 'lhf'];
37+ } else {
38+ symbol.symbolType = 'suffix';
39+ // symbol.components = ['gsq', 'atrf4'];
40+ }
41+ });
42+ this.symbols = this.symbols;
43+ }
44+
45+ clearSymbols() {
46+ this.symbols = [];
47+ this.padSymbols();
48+ return this;
49+ }
50+
51+ get activeSymbols() {
52+ if (this.clan === 'galaxy')
53+ return [this.symbols[1]];
54+ if (this.clan === 'star')
55+ return this.symbols.slice(0, 2);
56+ return this.symbols;
57+ }
58+
59+ get isPlausible() {
60+ return this.activeSymbols.every(symbol => symbol.isPlausible);
61+ }
62+
63+ get queryParts(): string[][] {
64+ return this.activeSymbols.map(symbol => symbol.components);
65+ }
66+
67+ get querySyls(): string[][] {
68+ return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
69+ }
70+
71+ get queryInts(): number[][] {
72+ return this.activeSymbols.map(symbol => symbol.plausibleInts);
73+ }
74+
75+ get isDefinitive() {
76+ return this.querySyls.every(syls => syls.length === 1);
77+ }
78+
79+ get isNotEmpty() {
80+ return this.activeSymbols.some(symbol => symbol.components.length > 0);
81+ }
82+
83+ get isWorthSearching() {
84+ if (!this.isNotEmpty) return false; // 🙃
85+ if (!this.isPlausible) return false;
86+ return true;
87+ }
88+
89+ get string(): string {
90+ return this.queryInts.map((int) => int.length == 256 ? 'any' : int.join('.')).join('_');
91+ }
92+
93+ get urlString(): string {
94+ return this.queryParts.map(parts => parts.join('.')).join('_');
95+ }
96+
97+ set urlString(str: string) {
98+ if (!str) return;
99+ const queryParts = str.split('_').map(sym => sym.split('.').filter(part => part !== ''));
100+ switch (queryParts.length) {
101+ case 1:
102+ this.clan = 'galaxy';
103+ break;
104+ case 2:
105+ this.clan = 'star';
106+ break;
107+ case 4:
108+ this.clan = 'planet';
109+ break;
110+ default:
111+ return;
112+ }
113+ let symbols;
114+ try {
115+ symbols = queryParts.map((parts) => {
116+ let symbol = new SymbolQuery(this, parts);
117+ if (symbol.components.length !== parts.length) {
118+ throw new Error(`Invalid component ids: ${without(parts, symbol.components)}`);
119+ }
120+ });
121+ } catch (e) {
122+ console.error("Couldn't load in sigil query text", e);
123+ this.clan = 'planet';
124+ symbols = [];
125+ }
126+ this.padSymbols();
127+ }
128+
129+ get patp(): string {
130+ if (!this.isDefinitive) return '';
131+ const querySyls = this.querySyls.flat();
132+ let patp = '~' + querySyls[0];
133+ if (querySyls.length > 1) {
134+ patp += querySyls[1];
135+ if (querySyls.length > 2) {
136+ patp += '-' + querySyls[2] + querySyls[3];
137+ }
138+ }
139+ return patp;
140+ }
141+
142+ setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
143+ if (activeSymbolIndex >= this.activeSymbols.length)
144+ throw new Error(`Symbol index ${activeSymbolIndex} out of range for clan: ${this.clan}`);
145+ this.activeSymbols[activeSymbolIndex] = symbol;
146+ }
147+}
148+
149+const equivalentParts = {
150+ lfhb: 'lfhbm',
151+ lfhbm: 'lfhb',
152+ atlf2: 'atlf2l',
153+ atlf2l: 'atlf2',
154+ atlf4: 'atlf4l',
155+ atlf4l: 'atlf4',
156+ atlf6: 'atlf6l',
157+ atlf6l: 'atlf6',
158+}
159+
160+function addEquivalentParts(parts: string[]): string[] {
161+ const moreParts = parts.map(partId => equivalentParts[partId]).filter(part => part);
162+ return [...parts, ...moreParts];
163+}
164+
165+function getSyllablesBySymbolType(symbolType: SymbolType): string[] {
166+ if (symbolType === 'prefix') {
167+ return prefixes;
168+ } else if (symbolType === 'suffix') {
169+ return suffixes;
170+ } else {
171+ return [...prefixes, ...suffixes];
172+ }
173+}
174+
175+export function getPlausibleSyllables(parts: string[], symbolType: SymbolType): string[] {
176+ let plausibleSyllables = getSyllablesBySymbolType(symbolType);
177+ const possibilitiesByPart = parts.map((partId) => {
178+ let parents = partParents[partId];
179+ const otherPart = equivalentParts[partId];
180+ if (otherPart) {
181+ parents = [...parents, ...partParents[otherPart]];
182+ }
183+ return parents;
184+ });
185+ plausibleSyllables = intersection(plausibleSyllables, ...possibilitiesByPart);
186+ return plausibleSyllables;
187+}
188+
189+function convertSyllableToInt(syl: string, symbolType: SymbolType): number {
190+ let syls = getSyllablesBySymbolType(symbolType);
191+ return syls.findIndex(s => s === syl) % 256;
192+}
193+
194+export function getPlausibleNewParts(parts: string[], symbolType: SymbolType): string[] {
195+ const syllables: string[] = getPlausibleSyllables(parts, symbolType);
196+ let plausibleParts = uniq(syllables.map((syllable) => symbolDefs[syllable]).flat());
197+ plausibleParts = addEquivalentParts(plausibleParts);
198+ return without(plausibleParts, ...parts);
199+}
200+
201+export function filterPlausibleParts(parts: string[], symbolType: SymbolType, newParts: string[]): string[] {
202+ const plausibleParts = getPlausibleNewParts(parts, symbolType);
203+ return intersection(newParts, plausibleParts);
204+}
205+
206+export function arePartsPlausible(parts: string[], symbolType: SymbolType, newParts: string[]): boolean {
207+ return filterPlausibleParts(parts, symbolType, newParts).length > 0;
208+}
209+
210+function hasComplement(partId: string): boolean {
211+ const secondToLastChar = partId.slice(-2)[0];
212+ return partId[0] === 'c' && (secondToLastChar === 'm' || secondToLastChar === 'l');
213+}
214+
215+function getComplementaryCircleId(circleId: string): string {
216+ let newId = circleId.replace(/m(?=.$)/, 'l');
217+ if (newId === circleId) {
218+ newId = circleId.replace(/l(?=.$)/, 'm');
219+ }
220+ circleId = newId;
221+ circleId = circleId.replace(/f$/, 'b');
222+ if (newId === circleId) {
223+ circleId = circleId.replace(/b$/, 'f');
224+ }
225+ return circleId;
226+}
227+
228+function getPartTypeIndex(partId: string): number {
229+ return PART_TYPES.findIndex(p => p[0] === partId[0]);
230+}
231+
232+export function filterPartsByGeon(parts: string[], geon: Geon): string[] {
233+ if (['gq1', 'gbs'].includes(geon)) {
234+ parts = without(parts, 'ctr2mb', 'ctr2lf', 'atrf2', 'atra2', 'atrb2');
235+ } else {
236+ parts = without(parts, 'ctr3mf', 'ctr3lb');
237+ }
238+ if (['gq2', 'gfs'].includes(geon)) {
239+ parts = without(parts, 'ctl3mb', 'ctl3lf', 'ctl2mb', 'ctl2lf', 'atlf2', 'atla2', 'atlb2');
240+ } else {
241+ parts = without(parts, 'ctl3mf', 'ctl3lb');
242+ }
243+ if (['gq3', 'gbs'].includes(geon)) {
244+ parts = without(parts, 'cbl3mb', 'cbl3lf', 'cbl2mb', 'cbl2lf', 'ablf2', 'ablb2');
245+ } else {
246+ parts = without(parts, 'cbl3mf', 'cbl3lb');
247+ }
248+ if (['gq4', 'gfs'].includes(geon)) {
249+ parts = without(parts, 'cbr2mb', 'cbr2lf', 'abrf2', 'abrb2', 'arb2');
250+ }
251+ return parts;
252+}
253+
254+export function filterFakeParts(parts: string[], symbolType: SymbolType): string[] {
255+ let plausibleSyllables = getSyllablesBySymbolType(symbolType);
256+ return parts.filter(part => plausibleSyllables.includes(part));
257+}
258+
259+export class SymbolQuery {
260+ sigilQuery?: SigilQuery;
261+ symbolType: SymbolType;
262+ components: string[];
263+
264+ constructor(sigilQuery: SigilQuery = undefined, components = [], symbolType: SymbolType = 'suffix') {
265+ this.sigilQuery = sigilQuery;
266+ this.components = components;
267+ this.symbolType = symbolType;
268+ this.cleanupParts();
269+ }
270+
271+ set geon(geon: Geon) {
272+ this.components = this.components.filter(c => c[0] !== 'g');
273+ if (geon)
274+ this.components = [geon, ...this.components];
275+ this.cleanupParts();
276+ }
277+
278+ get geon(): Geon {
279+ return this.components.find(c => c[0] === 'g');
280+ }
281+
282+ get allowFictional() {
283+ if (this.sigilQuery) return this.sigilQuery.allowFictional;
284+ return true;
285+ }
286+
287+ get plausibleSyllables() {
288+ return getPlausibleSyllables(this.components, this.symbolType);
289+ }
290+
291+ get plausibleInts() {
292+ return this.plausibleSyllables.map(syl => convertSyllableToInt(syl, this.symbolType));
293+ }
294+
295+ get isPlausible() {
296+ return this.plausibleSyllables.length > 0;
297+ }
298+
299+ get plausibleParts() {
300+ return getPlausibleNewParts(this.components, this.symbolType);
301+ }
302+
303+ get isPerfectMatch() {
304+ const syls = this.plausibleSyllables;
305+ if (syls.length === 1) return true;
306+ const compLength = this.components.length;
307+ return syls.map(syl => symbolDefs[syl].length).some(length => length === compLength);
308+ }
309+
310+ filterPlausibleParts(parts: string[]): string[] {
311+ return filterPlausibleParts(this.components, this.symbolType, parts);
312+ }
313+
314+ arePartsPlausible(parts: string[]): boolean {
315+ return arePartsPlausible(this.components, this.symbolType, parts);
316+ }
317+
318+ cleanupParts() {
319+ this.components = uniq(this.components);
320+ this.components = filterPartsByGeon(this.components, this.geon);
321+ this.components = filterFakeParts(this.components, this.symbolType);
322+ // if (!this.allowFictional) {
323+ // this.components = this.plausibleParts;
324+ // }
325+ this.components = this.components.sort((c1, c2) => {
326+ let i1 = getPartTypeIndex(c1);
327+ let i2 = getPartTypeIndex(c2);
328+ let diff = i1 - i2;
329+ if (diff !== 0) return diff;
330+ if (c1[0] === 'c') {
331+ return c1.localeCompare(c2);
332+ } else {
333+ return c2.localeCompare(c1);
334+ }
335+ });
336+ }
337+
338+ clearPartType(partType: PartType) {
339+ this.components = this.components.filter(c => c[0] !== partType[0]);
340+ return this;
341+ }
342+
343+ clear() {
344+ this.components = [];
345+ return this;
346+ }
347+
348+ addPart(partId: string) {
349+ this.components = [...this.components, partId];
350+ if (hasComplement(partId)) {
351+ const otherPartId = getComplementaryCircleId(partId);
352+ this.components = [...this.components, otherPartId];
353+ }
354+ this.cleanupParts();
355+ return this;
356+ }
357+
358+ removePart(partId: string) {
359+ this.components = without(this.components, partId);
360+ if (hasComplement(partId)) {
361+ const otherPartId = getComplementaryCircleId(partId);
362+ this.components = without(this.components, otherPartId);
363+ }
364+ this.cleanupParts();
365+ return this;
366+ }
367+}
368+
369+export function getDeepParts(def: ModeDef): string[] {
370+ if (def.modes) {
371+ return uniq(Object.values(def.modes).map(getDeepParts).flat());
372+ } else {
373+ return def.parts || [];
374+ }
375+}
ui/src/lib/utils.ts+4
@@ -41,3 +41,7 @@ export function toggleStoreKey(store, key) {
4141 [key]: !store[key],
4242 }));
4343}
44+
45+export function capitalize(string: string): string {
46+ return string.charAt(0).toUpperCase() + string.slice(1);
47+}
ui/src/routes/HelpPage.svelte+2−2
@@ -1,8 +1,8 @@
11<script lang="ts">
22 import { onMount } from 'svelte';
33 import { push } from 'svelte-spa-router';
4− import docs from '../stores/docs';
5− import Doc from '../components/docs/Doc.svelte';
4+ import docs from 'stores/docs';
5+ import Doc from '@/docs/Doc.svelte';
66 export let params: any = {};
77 $: path = params.path || '';
88
ui/src/routes/Home.svelte+69−43
@@ -1,35 +1,43 @@
11<script lang="ts">
2− import Router from 'svelte-spa-router';
3− import { link, push, replace, location, querystring } from 'svelte-spa-router'
4− import pageName from '../stores/pageName';
5− import { SearchAnalysis } from '../lib/id';
6− import { linkToShip, linkToChart } from '../lib/link';
7− import SearchBar from "../components/SearchBar.svelte";
8− import SearchResults from "../components/SearchResults.svelte";
9− import ShipView from '../components/ShipView.svelte';
10− import StarChart from '../components/StarChart.svelte';
11− import ValidationProblems from '../components/ValidationProblems.svelte';
12− import HomeScreenTiles from '../components/HomeScreenTiles.svelte';
2+ import { push, replace, location, querystring } from 'svelte-spa-router'
3+ import pageName from 'stores/pageName';
4+ import { SearchAnalysis } from 'lib/id';
5+ import { SigilQuery } from 'lib/sigil';
6+ import { buildQuerystring } from 'lib/utils';
7+ import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
8+ import SearchBar from "@/search/SearchBar.svelte";
9+ import SearchResults from "@/search/SearchResults.svelte";
10+ import ShipView from '@/ShipView.svelte';
11+ import StarChart from '@/StarChart.svelte';
12+ import ValidationProblems from '@/ValidationProblems.svelte';
13+ import HomeScreenTiles from '@/HomeScreenTiles.svelte';
1314 export let params: any = {};
1415 let analysis: SearchAnalysis = new SearchAnalysis();
16+ let sigilQuery: SigilQuery = new SigilQuery('planet');
1517 let navigating = false;
16−
17− let search: string = (new URLSearchParams($querystring)).get('search') || params.arg || '';
18+
19+ let searchParams = new URLSearchParams($querystring);
20+ function getUrlSearchMode(searchParams) {
21+ return (searchParams.get('mode') == 'sigil') ? 'sigil' : 'patp';
22+ }
23+ let searchMode: ('patp' | 'sigil') = getUrlSearchMode(searchParams);
24+ let search: string = searchParams.get('search') || params.arg || '';
25+ let patp: string = undefined;
1826 let urlSearch = search;
19− // let urlChanged;
20− let urlChangedSinceAnalysisPatp = false;
27+ let urlSearchMode = searchMode;
28+ let urlChangedSinceShipQuery = false;
2129 $: {
22− const potentialUrlSearch = (new URLSearchParams($querystring)).get('search') || params.arg || '';
30+ const searchParams = new URLSearchParams($querystring);
31+ const potentialUrlSearch = searchParams.get('search') || params.arg || '';
2332 if (($location + $querystring).includes(potentialUrlSearch)) {
2433 urlSearch = potentialUrlSearch;
25− // urlChanged = Date.now();
26− urlChangedSinceAnalysisPatp = true;
34+ urlSearchMode = getUrlSearchMode(searchParams);
35+ urlChangedSinceShipQuery = true;
2736 navigating = false;
2837 }
2938 }
3039 $: {
31− // analysisPatpChanged = (analysis.patp || true) && Date.now();
32− urlChangedSinceAnalysisPatp = (analysis.patp || true) && false;
40+ urlChangedSinceShipQuery = (search || true) && searchMode && false;
3341 }
3442 function nav(path, addToHistory = false) {
3543 (addToHistory ? push : replace)(path);
@@ -37,24 +45,28 @@
3745 function navHome(addToHistory = false) {
3846 params.mode = null;
3947 params.arg = null;
40− nav('/', addToHistory);
48+ if (searchMode == 'patp') {
49+ nav('/', addToHistory);
50+ } else {
51+ nav('/' + buildQuerystring({ mode: searchMode }), addToHistory);
52+ }
4153 }
4254 function navToShipView(addToHistory = false) {
4355 params.mode = 'ship';
44− params.arg = analysis.patp;
45− if (analysis.patp === search) {
46− nav(linkToShip(analysis.patp), addToHistory);
56+ params.arg = patp;
57+ if (patp === search) {
58+ nav(linkToShip(patp), addToHistory);
4759 } else {
48− nav(linkToShip(analysis.patp, { search }), addToHistory);
60+ nav(linkToShip(patp, { search, mode: searchMode }), addToHistory);
4961 }
5062 }
5163 function navToSearch(addToHistory = false) {
5264 if (!search) {
5365 navHome(addToHistory);
54− } else if (analysis.queryIsValid) {
66+ } else {
5567 params.mode = 'search';
5668 params.arg = search;
57− nav(`/search/${search}`, addToHistory);
69+ nav(linkToSearch(search, { mode: searchMode }), addToHistory);
5870 }
5971 }
6072 function navToChart(addToHistory = true) {
@@ -75,26 +87,27 @@
7587 navHome(addToHistory);
7688 }
7789 }
78− // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
7990 function autoNav() {
80− if (search !== urlSearch) {
81− if (urlChangedSinceAnalysisPatp) {
91+ if ((search !== urlSearch || searchMode !== urlSearchMode)) {
92+ if (urlChangedSinceShipQuery) {
8293 search = urlSearch;
83− } else if (analysis.search === search) {
94+ searchMode = urlSearchMode;
95+ } else {
8496 navigating = true;
8597 if (params.mode === 'chart') {
8698 navToChart(analysis.patpIsValid);
8799 } else {
88− if (analysis.patpIsValid) {
100+ if (patp) {
89101 navToShipView(true);
90102 } else {
91− navToSearch(params.mode === 'ship');
103+ const fromShipMode = params.mode === 'ship';
104+ navToSearch(fromShipMode);
92105 }
93106 }
94107 }
95108 }
96109 }
97− $: urlSearch, analysis, autoNav();
110+ $: urlSearch, urlSearchMode, search, searchMode, autoNav();
98111 $: {
99112 let properMode = params.mode;
100113 if (properMode === 'chart') {
@@ -136,18 +149,31 @@
136149</script>
137150
138151<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} />
140− <!-- <p class="bg-white">navigating</p> -->
152+ <SearchBar bind:analysis bind:sigilQuery bind:search bind:searchMode bind:patp {placeholderText} />
153+ <!-- <div class="bg-white">
154+ <p>
155+ Search: {search}
156+ </p>
157+ <p>
158+ URL Search: {urlSearch}
159+ </p>
160+ <p>
161+ Search mode: {searchMode}
162+ </p>
163+ <p>
164+ Patp: {patp}
165+ </p>
166+ </div> -->
141167 {#if params.mode == "chart"}
142− <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
143− {:else if analysis.patpIsValid}
144− <ShipView patp={analysis.patp} />
145− {:else if (analysis.queryIsValid)}
146− <SearchResults query={analysis.query} search={analysis.search} />
147− {:else if (analysis.search.length > 0)}
168+ <StarChart patp={patp || null} />
169+ {:else if patp}
170+ <ShipView {patp} />
171+ {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode === 'sigil' && sigilQuery.isWorthSearching)}
172+ <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery}
173+ patpSearch={analysis.search} {searchMode} />
174+ {:else if (analysis.search.length > 0 && analysis.queryProblems.length > 0)}
148175 <ValidationProblems problems={analysis.queryProblems} />
149176 {:else}
150177 <HomeScreenTiles />
151178 {/if}
152− <!-- {/if} -->
153179</div>
ui/src/routes/SigilPage.sveltenew file+58
@@ -0,0 +1,58 @@
1+<script lang="ts">
2+ import { prefixes, suffixes } from 'lib/id';
3+ import Sigil from "@/Sigil.svelte";
4+
5+ let index: number = undefined;
6+ let altBgColor = 'red';
7+
8+ let phonemes = prefixes.map(p => p + 'zod').concat(suffixes).sort().map(p => '~' + p);
9+
10+ function into(i) {
11+ index = i;
12+ }
13+
14+ function out() {
15+ index = undefined;
16+ }
17+
18+ function forward() {
19+ index = index + 1;
20+ index = Math.min(511, index);
21+ }
22+
23+ function back() {
24+ index = index - 1;
25+ index = Math.max(0, index);
26+ }
27+</script>
28+
29+<div class="bg-white m-4">
30+ {#if index == undefined}
31+ <div class="flex flex-wrap">
32+ {#each phonemes as phoneme, i}
33+ <div class="w-[128px] m-2" on:click={() => into(i)}>
34+ <h2>
35+ {phoneme}
36+ </h2>
37+ <div class="flex">
38+ <Sigil patp={phoneme} size={64} {altBgColor} />
39+ <Sigil patp={phoneme} size={64} {altBgColor} useNew />
40+ </div>
41+ </div>
42+ {/each}
43+ </div>
44+ {:else}
45+ <div class="flex gap-2">
46+ <button class="p-4 border" on:click={back}>&lt;</button>
47+ <button class="p-4 border" on:click={out}>^</button>
48+ <button class="p-4 border" on:click={forward}>&gt;</button>
49+ </div>
50+ <h2>
51+ {phonemes[index]}
52+ </h2>
53+ <div class="flex w-full">
54+ <Sigil patp={phonemes[index]} size={400} {altBgColor} />
55+ <Sigil patp={phonemes[index]} size={400} {altBgColor} useNew />
56+ </div>
57+ {/if}
58+</div>
ui/src/stores/docs.ts+1−2
@@ -1,8 +1,7 @@
11import { writable } from 'svelte/store';
22
3−import { getDoc } from '../lib/api';
3+import { getDoc } from 'lib/api';
44const docs = {};
5−// import type { AppState, Contacts } from '../types/store';
65
76const initStore: any = {
87 show: false,
ui/src/stores/searchStores.ts+34−10
@@ -2,21 +2,31 @@ import { writable, derived } from 'svelte/store';
22
33import type { Contact, Rolodex } from '@urbit/api';
44import store from './store';
5−import type { StoreState, SearchedContactsState } from '../types/store';
6−import { SearchSettings } from '../types/store';
7−import { filterObject } from '../lib/utils';
5+import type { StoreState, SearchedContactsState } from 'types/store';
6+import { SearchSettings } from 'types/store';
7+import { splitIdIntoSyls } from 'lib/id';
8+import { filterObject } from 'lib/utils';
89
910const searchSettings = writable(new SearchSettings());
1011
1112const searchedContacts = derived([store, searchSettings], deriveSearchedContacts);
12−searchedContacts.search = store.search;
13+searchedContacts.searchPatp = store.searchPatp;
14+searchedContacts.searchSigil = store.searchSigil;
1315
1416function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsState {
15− if (!$store.query) {
16− return {};
17+ let idMatches;
18+ if ($store.searchMode === 'patp') {
19+ if (!$store.patpQuery) {
20+ return {};
21+ }
22+ idMatches = idMatchesRegex;
23+ } else {
24+ if (!$store.sigilQuery) {
25+ return {};
26+ }
27+ idMatches = idMatchesSyls;
1728 }
18− function idMatchesSearch(id) {
19− if (!$store.query.test(id)) return false;
29+ function isIdAllowed(id) {
2030 if (!$searchSettings.includeMoons && id.length > 14 && id.length <= 28) {
2131 return false;
2232 }
@@ -25,9 +35,23 @@ function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsStat
2535 }
2636 return true;
2737 }
28− const filteredContacts = filterObject($store.contacts, idMatchesSearch) as Rolodex;
38+ function idMatchesSyls(id) {
39+ if (!isIdAllowed(id)) return false;
40+ const idSyls = splitIdIntoSyls(id);
41+ if (idSyls.length !== $store.sigilQuery.length) return false;
42+ return idSyls.every((syl, index) => {
43+ const symbolSyls = $store.sigilQuery[index];
44+ if (symbolSyls.length == 256) return true;
45+ return symbolSyls.includes(syl);
46+ });
47+ }
48+ function idMatchesRegex(id) {
49+ if (!$store.patpQuery.test(id)) return false;
50+ return isIdAllowed(id);
51+ }
52+ const filteredContacts = filterObject($store.contacts, idMatches) as Rolodex;
2953 $store.peers.forEach((id) => {
30− if (idMatchesSearch(id) && !filteredContacts[id]) {
54+ if (idMatches(id) && !filteredContacts[id]) {
3155 filteredContacts[id] = {
3256 nickname: null, bio: null, status: null, color: null, avatar: null,
3357 cover: null, groups: [], 'last-updated': 0,
ui/src/stores/store.ts+17−9
@@ -1,15 +1,17 @@
11import { writable } from 'svelte/store';
22
33import type { Rolodex } from '@urbit/api';
4−import { subscribeToContacts, getPeers } from '../lib/api';
5−import type { StoreState } from '../types/store';
6−import { normalizeId } from '../lib/id';
7−import { setStoreKey } from '../lib/utils';
4+import { subscribeToContacts, getPeers } from 'lib/api';
5+import type { StoreState } from 'types/store';
6+import { normalizeId } from 'lib/id';
7+import { setStoreKey } from 'lib/utils';
88
99const initStore: StoreState = {
1010 contacts: {},
11− peers: {},
12− query: null,
11+ peers: [],
12+ patpQuery: null,
13+ sigilQuery: null,
14+ searchMode: 'patp',
1315 connection: 'disconnected',
1416 ship: window.ship,
1517};
@@ -17,8 +19,14 @@ const initStore: StoreState = {
1719const store = writable(initStore);
1820const { subscribe, update, set } = store;
1921
20−function search(idQuery: RegExp) {
21− setStoreKey(store, 'query', idQuery);
22+function searchPatp(patpQuery: RegExp) {
23+ setStoreKey(store, 'patpQuery', patpQuery);
24+ setStoreKey(store, 'searchMode', 'patp');
25+}
26+
27+function searchSigil(syls: string[][]) {
28+ setStoreKey(store, 'sigilQuery', syls);
29+ setStoreKey(store, 'searchMode', 'sigil');
2230}
2331
2432function reset() {
@@ -40,4 +48,4 @@ getPeers().then(({ points }) => {
4048 setStoreKey(store, 'peers', points.map(normalizeId));
4149});
4250
43−export default { subscribe, set, search, reset };
51+export default { subscribe, set, searchPatp, searchSigil, reset };
ui/src/styles/tailwind-output.css+467−55
@@ -532,36 +532,67 @@ Ensure the default browser behavior of the `hidden` attribute.
532532.-bottom-3 {
533533 bottom: -0.75rem;
534534}
535−.z-10 {
536− z-index: 10;
535+.top-1 {
536+ top: 0.25rem;
537+}
538+.right-1 {
539+ right: 0.25rem;
540+}
541+.top-2 {
542+ top: 0.5rem;
543+}
544+.right-2 {
545+ right: 0.5rem;
546+}
547+.right-4 {
548+ right: 1rem;
549+}
550+.right-3 {
551+ right: 0.75rem;
552+}
553+.left-1 {
554+ left: 0.25rem;
555+}
556+.left-2 {
557+ left: 0.5rem;
537558}
538559.z-20 {
539560 z-index: 20;
540561}
562+.z-10 {
563+ z-index: 10;
564+}
541565.z-50 {
542566 z-index: 50;
543567}
568+.col-span-3 {
569+ grid-column: span 3 / span 3;
570+}
571+.col-span-2 {
572+ grid-column: span 2 / span 2;
573+}
544574.m-4 {
545575 margin: 1rem;
546576}
577+.m-2 {
578+ margin: 0.5rem;
579+}
547580.m-8 {
548581 margin: 2rem;
549582}
550583.-m-2 {
551584 margin: -0.5rem;
552585}
553−.mx-4 {
554− margin-left: 1rem;
555− margin-right: 1rem;
586+.m-auto {
587+ margin: auto;
588+}
589+.m-1 {
590+ margin: 0.25rem;
556591}
557592.mx-auto {
558593 margin-left: auto;
559594 margin-right: auto;
560595}
561−.mx-8 {
562− margin-left: 2rem;
563− margin-right: 2rem;
564−}
565596.my-1\.5 {
566597 margin-top: 0.375rem;
567598 margin-bottom: 0.375rem;
@@ -574,6 +605,14 @@ Ensure the default browser behavior of the `hidden` attribute.
574605 margin-top: 0.75rem;
575606 margin-bottom: 0.75rem;
576607}
608+.mx-4 {
609+ margin-left: 1rem;
610+ margin-right: 1rem;
611+}
612+.mx-8 {
613+ margin-left: 2rem;
614+ margin-right: 2rem;
615+}
577616.my-4 {
578617 margin-top: 1rem;
579618 margin-bottom: 1rem;
@@ -603,9 +642,6 @@ Ensure the default browser behavior of the `hidden` attribute.
603642.ml-1 {
604643 margin-left: 0.25rem;
605644}
606−.-mt-2 {
607− margin-top: -0.5rem;
608−}
609645.mr-4 {
610646 margin-right: 1rem;
611647}
@@ -621,6 +657,9 @@ Ensure the default browser behavior of the `hidden` attribute.
621657.ml-8 {
622658 margin-left: 2rem;
623659}
660+.-mt-2 {
661+ margin-top: -0.5rem;
662+}
624663.-mt-1 {
625664 margin-top: -0.25rem;
626665}
@@ -654,6 +693,15 @@ Ensure the default browser behavior of the `hidden` attribute.
654693.mr-3 {
655694 margin-right: 0.75rem;
656695}
696+.ml-auto {
697+ margin-left: auto;
698+}
699+.mb-1 {
700+ margin-bottom: 0.25rem;
701+}
702+.mr-1 {
703+ margin-right: 0.25rem;
704+}
657705.-mt-0\.5 {
658706 margin-top: -0.125rem;
659707}
@@ -681,21 +729,15 @@ Ensure the default browser behavior of the `hidden` attribute.
681729.hidden {
682730 display: none;
683731}
732+.aspect-square {
733+ aspect-ratio: 1 / 1;
734+}
684735.h-full {
685736 height: 100%;
686737}
687738.h-5 {
688739 height: 1.25rem;
689740}
690−.h-10 {
691− height: 2.5rem;
692−}
693−.h-6 {
694− height: 1.5rem;
695−}
696−.h-8 {
697− height: 2rem;
698−}
699741.h-12 {
700742 height: 3rem;
701743}
@@ -705,6 +747,15 @@ Ensure the default browser behavior of the `hidden` attribute.
705747.h-\[100vh\] {
706748 height: 100vh;
707749}
750+.h-10 {
751+ height: 2.5rem;
752+}
753+.h-6 {
754+ height: 1.5rem;
755+}
756+.h-8 {
757+ height: 2rem;
758+}
708759.h-2 {
709760 height: 0.5rem;
710761}
@@ -720,6 +771,24 @@ Ensure the default browser behavior of the `hidden` attribute.
720771.h-24 {
721772 height: 6rem;
722773}
774+.h-20 {
775+ height: 5rem;
776+}
777+.h-48 {
778+ height: 12rem;
779+}
780+.h-64 {
781+ height: 16rem;
782+}
783+.h-72 {
784+ height: 18rem;
785+}
786+.h-16 {
787+ height: 4rem;
788+}
789+.h-auto {
790+ height: auto;
791+}
723792.min-h-\[20px\] {
724793 min-height: 20px;
725794}
@@ -729,24 +798,30 @@ Ensure the default browser behavior of the `hidden` attribute.
729798.min-h-0 {
730799 min-height: 0px;
731800}
801+.min-h-\[14rem\] {
802+ min-height: 14rem;
803+}
732804.w-full {
733805 width: 100%;
734806}
735807.w-5 {
736808 width: 1.25rem;
737809}
738−.w-10 {
739− width: 2.5rem;
740−}
741810.w-12 {
742811 width: 3rem;
743812}
744813.w-3 {
745814 width: 0.75rem;
746815}
816+.w-\[128px\] {
817+ width: 128px;
818+}
747819.w-\[100vw\] {
748820 width: 100vw;
749821}
822+.w-10 {
823+ width: 2.5rem;
824+}
750825.w-7 {
751826 width: 1.75rem;
752827}
@@ -759,6 +834,33 @@ Ensure the default browser behavior of the `hidden` attribute.
759834.w-6 {
760835 width: 1.5rem;
761836}
837+.w-48 {
838+ width: 12rem;
839+}
840+.w-24 {
841+ width: 6rem;
842+}
843+.w-20 {
844+ width: 5rem;
845+}
846+.w-8 {
847+ width: 2rem;
848+}
849+.w-64 {
850+ width: 16rem;
851+}
852+.w-72 {
853+ width: 18rem;
854+}
855+.w-16 {
856+ width: 4rem;
857+}
858+.w-\[48px\] {
859+ width: 48px;
860+}
861+.w-40 {
862+ width: 10rem;
863+}
762864.min-w-0 {
763865 min-width: 0px;
764866}
@@ -780,6 +882,15 @@ Ensure the default browser behavior of the `hidden` attribute.
780882.max-w-2xl {
781883 max-width: 42rem;
782884}
885+.max-w-\[10rem\] {
886+ max-width: 10rem;
887+}
888+.max-w-\[18rem\] {
889+ max-width: 18rem;
890+}
891+.max-w-\[15rem\] {
892+ max-width: 15rem;
893+}
783894.flex-shrink-0 {
784895 flex-shrink: 0;
785896}
@@ -795,9 +906,54 @@ Ensure the default browser behavior of the `hidden` attribute.
795906.grow {
796907 flex-grow: 1;
797908}
909+.grow-\[2\] {
910+ flex-grow: 2;
911+}
798912.border-collapse {
799913 border-collapse: collapse;
800914}
915+.origin-bottom-right {
916+ transform-origin: bottom right;
917+}
918+.origin-left {
919+ transform-origin: left;
920+}
921+.origin-right {
922+ transform-origin: right;
923+}
924+.origin-top-left {
925+ transform-origin: top left;
926+}
927+.origin-top-right {
928+ transform-origin: top right;
929+}
930+.origin-bottom-left {
931+ transform-origin: bottom left;
932+}
933+.translate-x-0 {
934+ --tw-translate-x: 0px;
935+ 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));
936+}
937+.translate-y-0 {
938+ --tw-translate-y: 0px;
939+ 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));
940+}
941+.translate-x-1\/4 {
942+ --tw-translate-x: 25%;
943+ 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));
944+}
945+.translate-y-1\/4 {
946+ --tw-translate-y: 25%;
947+ 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));
948+}
949+.-translate-x-1\/4 {
950+ --tw-translate-x: -25%;
951+ 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));
952+}
953+.-translate-y-1\/4 {
954+ --tw-translate-y: -25%;
955+ 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));
956+}
801957.rotate-45 {
802958 --tw-rotate: 45deg;
803959 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));
@@ -806,6 +962,16 @@ Ensure the default browser behavior of the `hidden` attribute.
806962 --tw-rotate: -45deg;
807963 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));
808964}
965+.scale-150 {
966+ --tw-scale-x: 1.5;
967+ --tw-scale-y: 1.5;
968+ 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));
969+}
970+.scale-50 {
971+ --tw-scale-x: .5;
972+ --tw-scale-y: .5;
973+ 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));
974+}
809975.transform {
810976 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));
811977}
@@ -831,21 +997,71 @@ Ensure the default browser behavior of the `hidden` attribute.
831997.resize {
832998 resize: both;
833999}
1000+.auto-cols-min {
1001+ grid-auto-columns: -webkit-min-content;
1002+ grid-auto-columns: min-content;
1003+}
1004+.auto-cols-fr {
1005+ grid-auto-columns: minmax(0, 1fr);
1006+}
1007+.grid-flow-row {
1008+ grid-auto-flow: row;
1009+}
1010+.grid-flow-col {
1011+ grid-auto-flow: column;
1012+}
1013+.grid-flow-col-dense {
1014+ grid-auto-flow: column dense;
1015+}
1016+.auto-rows-min {
1017+ grid-auto-rows: -webkit-min-content;
1018+ grid-auto-rows: min-content;
1019+}
8341020.grid-cols-3 {
8351021 grid-template-columns: repeat(3, minmax(0, 1fr));
8361022}
1023+.grid-cols-2 {
1024+ grid-template-columns: repeat(2, minmax(0, 1fr));
1025+}
1026+.grid-cols-1 {
1027+ grid-template-columns: repeat(1, minmax(0, 1fr));
1028+}
1029+.grid-cols-4 {
1030+ grid-template-columns: repeat(4, minmax(0, 1fr));
1031+}
1032+.grid-cols-6 {
1033+ grid-template-columns: repeat(6, minmax(0, 1fr));
1034+}
1035+.grid-rows-2 {
1036+ grid-template-rows: repeat(2, minmax(0, 1fr));
1037+}
1038+.grid-rows-none {
1039+ grid-template-rows: none;
1040+}
8371041.flex-col {
8381042 flex-direction: column;
8391043}
8401044.flex-wrap {
8411045 flex-wrap: wrap;
8421046}
1047+.place-content-center {
1048+ place-content: center;
1049+}
1050+.place-items-stretch {
1051+ place-items: stretch;
1052+}
8431053.content-center {
8441054 align-content: center;
8451055}
1056+.items-start {
1057+ align-items: flex-start;
1058+}
8461059.items-center {
8471060 align-items: center;
8481061}
1062+.justify-end {
1063+ justify-content: flex-end;
1064+}
8491065.justify-center {
8501066 justify-content: center;
8511067}
@@ -867,14 +1083,14 @@ Ensure the default browser behavior of the `hidden` attribute.
8671083.gap-2 {
8681084 gap: 0.5rem;
8691085}
870−.gap-x-2 {
871− -moz-column-gap: 0.5rem;
872− column-gap: 0.5rem;
873−}
8741086.gap-x-1 {
8751087 -moz-column-gap: 0.25rem;
8761088 column-gap: 0.25rem;
8771089}
1090+.gap-x-2 {
1091+ -moz-column-gap: 0.5rem;
1092+ column-gap: 0.5rem;
1093+}
8781094.gap-x-3 {
8791095 -moz-column-gap: 0.75rem;
8801096 column-gap: 0.75rem;
@@ -922,6 +1138,15 @@ Ensure the default browser behavior of the `hidden` attribute.
9221138 --tw-divide-opacity: 1;
9231139 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
9241140}
1141+.place-self-center {
1142+ place-self: center;
1143+}
1144+.self-start {
1145+ align-self: flex-start;
1146+}
1147+.self-center {
1148+ align-self: center;
1149+}
9251150.justify-self-start {
9261151 justify-self: start;
9271152}
@@ -964,6 +1189,26 @@ Ensure the default browser behavior of the `hidden` attribute.
9641189.rounded-sm {
9651190 border-radius: 0.125rem;
9661191}
1192+.rounded-2xl {
1193+ border-radius: 1rem;
1194+}
1195+.rounded-3xl {
1196+ border-radius: 1.5rem;
1197+}
1198+.rounded-b-2xl {
1199+ border-bottom-right-radius: 1rem;
1200+ border-bottom-left-radius: 1rem;
1201+}
1202+.rounded-t-2xl {
1203+ border-top-left-radius: 1rem;
1204+ border-top-right-radius: 1rem;
1205+}
1206+.rounded-br-2xl {
1207+ border-bottom-right-radius: 1rem;
1208+}
1209+.rounded-bl-none {
1210+ border-bottom-left-radius: 0px;
1211+}
9671212.border {
9681213 border-width: 1px;
9691214}
@@ -973,6 +1218,15 @@ Ensure the default browser behavior of the `hidden` attribute.
9731218.border-8 {
9741219 border-width: 8px;
9751220}
1221+.border-2 {
1222+ border-width: 2px;
1223+}
1224+.border-0 {
1225+ border-width: 0px;
1226+}
1227+.border-\[1\.5px\] {
1228+ border-width: 1.5px;
1229+}
9761230.border-l-8 {
9771231 border-left-width: 8px;
9781232}
@@ -992,14 +1246,14 @@ Ensure the default browser behavior of the `hidden` attribute.
9921246 --tw-border-opacity: 1;
9931247 border-color: rgb(252 211 77 / var(--tw-border-opacity));
9941248}
995−.border-navy {
996− --tw-border-opacity: 1;
997− border-color: rgb(31 36 60 / var(--tw-border-opacity));
998−}
9991249.border-gray-300 {
10001250 --tw-border-opacity: 1;
10011251 border-color: rgb(209 213 219 / var(--tw-border-opacity));
10021252}
1253+.border-navy {
1254+ --tw-border-opacity: 1;
1255+ border-color: rgb(31 36 60 / var(--tw-border-opacity));
1256+}
10031257.border-white {
10041258 --tw-border-opacity: 1;
10051259 border-color: rgb(255 255 255 / var(--tw-border-opacity));
@@ -1012,6 +1266,30 @@ Ensure the default browser behavior of the `hidden` attribute.
10121266 --tw-border-opacity: 1;
10131267 border-color: rgb(156 163 175 / var(--tw-border-opacity));
10141268}
1269+.border-red-500 {
1270+ --tw-border-opacity: 1;
1271+ border-color: rgb(239 68 68 / var(--tw-border-opacity));
1272+}
1273+.border-gold-s5 {
1274+ --tw-border-opacity: 1;
1275+ border-color: rgb(126 106 39 / var(--tw-border-opacity));
1276+}
1277+.border-gold-s2 {
1278+ --tw-border-opacity: 1;
1279+ border-color: rgb(202 169 62 / var(--tw-border-opacity));
1280+}
1281+.border-gold-s1 {
1282+ --tw-border-opacity: 1;
1283+ border-color: rgb(227 190 69 / var(--tw-border-opacity));
1284+}
1285+.border-gray-800 {
1286+ --tw-border-opacity: 1;
1287+ border-color: rgb(31 41 55 / var(--tw-border-opacity));
1288+}
1289+.border-gray-500 {
1290+ --tw-border-opacity: 1;
1291+ border-color: rgb(107 114 128 / var(--tw-border-opacity));
1292+}
10151293.bg-navy {
10161294 --tw-bg-opacity: 1;
10171295 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
@@ -1024,10 +1302,6 @@ Ensure the default browser behavior of the `hidden` attribute.
10241302 --tw-bg-opacity: 1;
10251303 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
10261304}
1027−.bg-black {
1028− --tw-bg-opacity: 1;
1029− background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1030−}
10311305.bg-l1-blue {
10321306 --tw-bg-opacity: 1;
10331307 background-color: rgb(23 146 199 / var(--tw-bg-opacity));
@@ -1048,6 +1322,10 @@ Ensure the default browser behavior of the `hidden` attribute.
10481322 --tw-bg-opacity: 1;
10491323 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
10501324}
1325+.bg-black {
1326+ --tw-bg-opacity: 1;
1327+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1328+}
10511329.bg-amber-300 {
10521330 --tw-bg-opacity: 1;
10531331 background-color: rgb(252 211 77 / var(--tw-bg-opacity));
@@ -1116,6 +1394,29 @@ Ensure the default browser behavior of the `hidden` attribute.
11161394 --tw-bg-opacity: 1;
11171395 background-color: rgb(227 190 69 / var(--tw-bg-opacity));
11181396}
1397+.bg-blue-800 {
1398+ --tw-bg-opacity: 1;
1399+ background-color: rgb(30 64 175 / var(--tw-bg-opacity));
1400+}
1401+.bg-green-900 {
1402+ --tw-bg-opacity: 1;
1403+ background-color: rgb(20 83 45 / var(--tw-bg-opacity));
1404+}
1405+.bg-green-100 {
1406+ --tw-bg-opacity: 1;
1407+ background-color: rgb(220 252 231 / var(--tw-bg-opacity));
1408+}
1409+.bg-blue-200 {
1410+ --tw-bg-opacity: 1;
1411+ background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1412+}
1413+.bg-gray-500 {
1414+ --tw-bg-opacity: 1;
1415+ background-color: rgb(107 114 128 / var(--tw-bg-opacity));
1416+}
1417+.fill-transparent {
1418+ fill: transparent;
1419+}
11191420.p-5 {
11201421 padding: 1.25rem;
11211422}
@@ -1137,6 +1438,15 @@ Ensure the default browser behavior of the `hidden` attribute.
11371438.p-1 {
11381439 padding: 0.25rem;
11391440}
1441+.p-0 {
1442+ padding: 0px;
1443+}
1444+.p-\[3px\] {
1445+ padding: 3px;
1446+}
1447+.p-\[4px\] {
1448+ padding: 4px;
1449+}
11401450.px-2 {
11411451 padding-left: 0.5rem;
11421452 padding-right: 0.5rem;
@@ -1153,6 +1463,18 @@ Ensure the default browser behavior of the `hidden` attribute.
11531463 padding-top: 0px;
11541464 padding-bottom: 0px;
11551465}
1466+.px-4 {
1467+ padding-left: 1rem;
1468+ padding-right: 1rem;
1469+}
1470+.px-0\.5 {
1471+ padding-left: 0.125rem;
1472+ padding-right: 0.125rem;
1473+}
1474+.px-0 {
1475+ padding-left: 0px;
1476+ padding-right: 0px;
1477+}
11561478.py-4 {
11571479 padding-top: 1rem;
11581480 padding-bottom: 1rem;
@@ -1165,10 +1487,6 @@ Ensure the default browser behavior of the `hidden` attribute.
11651487 padding-left: 1.5rem;
11661488 padding-right: 1.5rem;
11671489}
1168−.px-4 {
1169− padding-left: 1rem;
1170− padding-right: 1rem;
1171−}
11721490.py-8 {
11731491 padding-top: 2rem;
11741492 padding-bottom: 2rem;
@@ -1177,14 +1495,6 @@ Ensure the default browser behavior of the `hidden` attribute.
11771495 padding-left: 2rem;
11781496 padding-right: 2rem;
11791497}
1180−.px-0\.5 {
1181− padding-left: 0.125rem;
1182− padding-right: 0.125rem;
1183−}
1184−.px-0 {
1185− padding-left: 0px;
1186− padding-right: 0px;
1187−}
11881498.px-3 {
11891499 padding-left: 0.75rem;
11901500 padding-right: 0.75rem;
@@ -1272,6 +1582,9 @@ Ensure the default browser behavior of the `hidden` attribute.
12721582.font-bold {
12731583 font-weight: 700;
12741584}
1585+.capitalize {
1586+ text-transform: capitalize;
1587+}
12751588.leading-4 {
12761589 line-height: 1rem;
12771590}
@@ -1336,21 +1649,46 @@ Ensure the default browser behavior of the `hidden` attribute.
13361649.underline-offset-2 {
13371650 text-underline-offset: 2px;
13381651}
1339−.opacity-50 {
1340− opacity: 0.5;
1341−}
13421652.opacity-25 {
13431653 opacity: 0.25;
13441654}
13451655.opacity-75 {
13461656 opacity: 0.75;
13471657}
1658+.opacity-50 {
1659+ opacity: 0.5;
1660+}
13481661.opacity-10 {
13491662 opacity: 0.1;
13501663}
1664+.opacity-20 {
1665+ opacity: 0.2;
1666+}
1667+.opacity-30 {
1668+ opacity: 0.3;
1669+}
1670+.opacity-40 {
1671+ opacity: 0.4;
1672+}
1673+.opacity-5 {
1674+ opacity: 0.05;
1675+}
13511676.outline {
13521677 outline-style: solid;
13531678}
1679+.ring {
1680+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1681+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1682+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1683+}
1684+.drop-shadow {
1685+ --tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06));
1686+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1687+}
1688+.sepia {
1689+ --tw-sepia: sepia(100%);
1690+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1691+}
13541692.filter {
13551693 filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
13561694}
@@ -1359,10 +1697,30 @@ Ensure the default browser behavior of the `hidden` attribute.
13591697 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
13601698 transition-duration: 150ms;
13611699}
1700+.transition-transform {
1701+ transition-property: transform;
1702+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1703+ transition-duration: 150ms;
1704+}
1705+.transition-all {
1706+ transition-property: all;
1707+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1708+ transition-duration: 150ms;
1709+}
1710+.transition {
1711+ transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
1712+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
1713+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
1714+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1715+ transition-duration: 150ms;
1716+}
13621717.hover\:bg-gray-200:hover {
13631718 --tw-bg-opacity: 1;
13641719 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
13651720}
1721+.hover\:opacity-80:hover {
1722+ opacity: 0.8;
1723+}
13661724.hover\:opacity-40:hover {
13671725 opacity: 0.4;
13681726}
@@ -1400,6 +1758,60 @@ Ensure the default browser behavior of the `hidden` attribute.
14001758 padding-bottom: 2rem;
14011759 }
14021760}
1761+@media (min-width: 475px) {
1762+
1763+ .xs\:m-0 {
1764+ margin: 0px;
1765+ }
1766+
1767+ .xs\:mr-0 {
1768+ margin-right: 0px;
1769+ }
1770+
1771+ .xs\:mb-1 {
1772+ margin-bottom: 0.25rem;
1773+ }
1774+
1775+ .xs\:max-w-\[18rem\] {
1776+ max-width: 18rem;
1777+ }
1778+
1779+ .xs\:max-w-\[8rem\] {
1780+ max-width: 8rem;
1781+ }
1782+
1783+ .xs\:flex-row {
1784+ flex-direction: row;
1785+ }
1786+
1787+ .xs\:flex-col {
1788+ flex-direction: column;
1789+ }
1790+
1791+ .xs\:items-center {
1792+ align-items: center;
1793+ }
1794+
1795+ .xs\:self-stretch {
1796+ align-self: stretch;
1797+ }
1798+
1799+ .xs\:rounded-bl-2xl {
1800+ border-bottom-left-radius: 1rem;
1801+ }
1802+
1803+ .xs\:rounded-bl-none {
1804+ border-bottom-left-radius: 0px;
1805+ }
1806+
1807+ .xs\:border-l {
1808+ border-left-width: 1px;
1809+ }
1810+
1811+ .xs\:border-t-0 {
1812+ border-top-width: 0px;
1813+ }
1814+}
14031815@media (min-width: 768px) {
14041816
14051817 .md\:mx-auto {
@@ -1411,14 +1823,14 @@ Ensure the default browser behavior of the `hidden` attribute.
14111823 min-width: 256px;
14121824 }
14131825
1414− .md\:max-w-xl {
1415− max-width: 36rem;
1416− }
1417−
14181826 .md\:max-w-2xl {
14191827 max-width: 42rem;
14201828 }
14211829
1830+ .md\:max-w-xl {
1831+ max-width: 36rem;
1832+ }
1833+
14221834 .md\:flex-row {
14231835 flex-direction: row;
14241836 }
ui/src/types/sigil.tsnew file+148
@@ -0,0 +1,148 @@
1+export const CLANS = ['galaxy', 'star', 'planet'] as const;
2+export type Clan = typeof CLANS[number];
3+
4+export const PART_TYPES = ['geon', 'line', 'arc', 'bezier', 'circle', 'donut'] as const;
5+export type PartType = typeof PART_TYPES[number];
6+
7+export const GEONS = ['gci', 'gsq', 'ghl', 'ghr', 'ght', 'ghb', 'gq1', 'gq2', 'gq3', 'gq4', 'gfs', 'gbs'];
8+export type Geon = typeof GEONS[number];
9+
10+export type SymbolType = ('prefix' | 'suffix');
11+
12+export interface ModeDef {
13+ modes?: ModeDefs;
14+ displayParts?: string[];
15+ parts?: string[];
16+}
17+
18+export interface ModeDefs {
19+ [mode: string]: ModeDef
20+}
21+
22+
23+export const MODE_DEFS: ModeDefs = {
24+ geon: {
25+ modes: {},
26+ displayParts: ['ghb'],
27+ },
28+ line: {
29+ modes: {
30+ mainFulls: {
31+ parts: ['lvf', 'lhf', 'lff', 'lbf'],
32+ },
33+ halvesFulls: {
34+ parts: ['lvfl', 'lvfr', 'lhft', 'lhfb', 'lfht', 'lfhb', 'lbht', 'lbhb'],
35+ },
36+ forwards: {
37+ parts: ['lff', 'lfft', 'lffb'],
38+ },
39+ backwards: {
40+ parts: ['lbf', 'lbft'],
41+ },
42+ weirds: {
43+ parts: ['lbhtf', 'lbrf', 'lfhm'],
44+ }
45+ },
46+ parts: ['lvf', 'lvfl', 'lvfr', 'lhf', 'lhft', 'lhfb', 'lff', 'lbf'],
47+ },
48+ arcCorner: {
49+ modes: {
50+ trFulls: {
51+ parts: ['atrf8', 'atrf6', 'atrf4', 'atrf2'],
52+ },
53+ trHalves: {
54+ parts: [/*'atra8',*/ 'atra6', 'atra4', 'atra2', 'atrb8', 'atrb6', 'atrb4', 'atrb2'],
55+ displayParts: ['atrb8', 'atrb6', 'atrb4', 'atrb2'],
56+ },
57+ tlFulls: {
58+ parts: ['atlf8', 'atlf6', 'atlf4', 'atlf2'],
59+ },
60+ tlHalves: {
61+ parts: ['atla8', 'atla6', 'atla4', 'atla2', 'atlb8', 'atlb6', 'atlb4', 'atlb2'],
62+ displayParts: ['atlb8', 'atlb6', 'atlb4', 'atlb2'],
63+ },
64+ blFulls: {
65+ parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
66+ },
67+ blHalves: {
68+ parts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
69+ displayParts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
70+ },
71+ brFulls: {
72+ parts: ['abrf8', 'abrf6', 'abrf4', 'abrf2'],
73+ },
74+ brHalves: {
75+ parts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
76+ displayParts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
77+ },
78+ },
79+ parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
80+ },
81+ arc: {
82+ modes: {
83+ rings: {
84+ parts: ['am081', 'am082', 'am083', 'am08s'],
85+ },
86+ diagonals: {
87+ parts: ['am741', 'am742', 'am743', 'am422', 'am423'],
88+ },
89+ topsBottoms: {
90+ parts: ['ab2', 'ab3', 'ab4', 'at1', 'at3', 'at4'],
91+ },
92+ leftsRights: {
93+ parts: ['al2', 'al3', 'al4', 'ar1', 'ar2', 'ar4'/*, 'arb2'*/],
94+ },
95+ rights: {
96+ parts: ['am642', 'am643'],
97+ },
98+ middles: {
99+ parts: ['am222', 'am223', 'am622', 'am623'],
100+ },
101+ middless: {
102+ parts: ['am142', 'am443'],
103+ },
104+ topRights: {
105+ parts: ['am021', 'am022', 'am023', 'am242', 'arb2'],
106+ },
107+ },
108+ parts: ['am222', 'am223', 'ar4', 'ar2', 'ar1'],
109+ },
110+ circle: {
111+ modes: {
112+ mediums: {
113+ parts: [
114+ 'cmmb',
115+ 'ctl3mb', 'cbl3mb',
116+ 'ct2mb', 'cb2mb', 'cl2mb', 'cr2mb', 'ctl2mb', 'ctr2mb', 'cbl2mb', 'cbr2mb',
117+ 'ctl1mb', 'ctr1mb', 'cbl1mb', 'cbr1mb',
118+ 'cbl3mf', 'ctr3mf', 'ctl3mf',
119+ ],
120+ displayParts: [
121+ 'cmmb',
122+ 'ctl3mb', 'cbl3mb',
123+ 'ct2mb', 'cb2mb', 'cl2mb', 'cr2mb', 'ctl2mb', 'ctr2mb', 'cbl2mb', 'cbr2mb',
124+ 'ctl1mb', 'ctr1mb', 'cbl1mb', 'cbr1mb',
125+ 'ctr3mf',
126+ ],
127+ },
128+ sides: {
129+ parts: ['cl1mb', 'cr1mb', 'cl3mb', 'cr3mb'],
130+ },
131+ smalls: {
132+ parts: ['cr1sb', 'cr2sb'],
133+ },
134+ },
135+ displayParts: [
136+ 'cbr3mb', 'ctl3mb',
137+ 'cmmb',
138+ 'ct2mb', 'cl2mb', 'cb2mb', 'cr2mb',
139+ ],
140+ },
141+ donut: {
142+ parts: ['dbl', 'dl', 'dm', 'dtl', 'dtr'],
143+ displayParts: ['dbl', /*'dl',*/ 'dm', 'dtl', 'dtr'],
144+ },
145+ bezier: {
146+ parts: ['b1l1', 'b1l2', 'b1l3', 'b4t2', 'b4t3'],
147+ },
148+};
ui/src/types/store.ts+5−1
@@ -14,7 +14,9 @@ export interface StoreState {
1414 // };
1515 contacts: Rolodex;
1616 peers: Patp[];
17− query: RegExp;
17+ patpQuery: RegExp;
18+ sigilQuery: string[][];
19+ searchMode: ('patp' | 'sigil');
1820 // contacts: Contacts;
1921 // contactSearchResults: Contacts;
2022 // chats: [];
@@ -27,4 +29,6 @@ export type SearchedContactsState = Rolodex;
2729export class SearchSettings {
2830 includeMoons: boolean = true;
2931 includeComets: boolean = false;
32+
33+ allowFictionalSigils: boolean = false;
3034}
ui/svelte.config.js+2−2
@@ -1,4 +1,4 @@
1−import sveltePreprocess from 'svelte-preprocess'
1+import sveltePreprocess from 'svelte-preprocess';
22
33export default {
44 // Consult https://github.com/sveltejs/svelte-preprocess
@@ -10,4 +10,4 @@ export default {
1010
1111 handler(warning)
1212 },
13−}
13+};
ui/tsconfig.json+9
@@ -7,6 +7,15 @@
77 "module": "esnext",
88 "resolveJsonModule": true,
99 "baseUrl": ".",
10+ "paths": {
11+ "~/*": ["src/*"],
12+ "@/*": ["src/components/*"],
13+ "lib/*": ["src/lib/*"],
14+ "stores/*": ["src/stores/*"],
15+ "assets/*": ["src/assets/*"],
16+ "actions/*": ["src/actions/*"],
17+ "types/*": ["src/types/*"],
18+ },
1019 /**
1120 * Typecheck JS in `.svelte` and `.js` files by default.
1221 * Disable checkJs if you'd like to use dynamic types in JS.
ui/vite.config.ts+21−1
@@ -1,6 +1,7 @@
11import { loadEnv, defineConfig } from 'vite';
22import { svelte } from '@sveltejs/vite-plugin-svelte'
33import { urbitPlugin } from '@urbit/vite-plugin-urbit';
4+import path from 'path';
45
56// https://vitejs.dev/config/
67export default ({ mode }) => {
@@ -9,6 +10,25 @@ export default ({ mode }) => {
910 console.log(SHIP_URL);
1011
1112 return defineConfig({
12− plugins: [urbitPlugin({ base: 'astrolabe', target: SHIP_URL, secure: false }), svelte()]
13+ plugins: [
14+ urbitPlugin({ base: 'astrolabe', target: SHIP_URL, secure: false }),
15+ svelte(),
16+ ],
17+ resolve: {
18+ alias: {
19+ '~': path.resolve('./src'),
20+ '@': path.resolve('./src/components'),
21+ lib: path.resolve('./src/lib'),
22+ stores: path.resolve('./src/stores'),
23+ assets: path.resolve('./src/assets'),
24+ actions: path.resolve('./src/actions'),
25+ types: path.resolve('./src/types'),
26+ },
27+ },
28+ server: {
29+ fs: {
30+ strict: false,
31+ },
32+ },
1333 });
1434};