on main help sign in
Merge pull request #7 from johnhyde/sigil-search
504a7a8John Hyde
.gitignore+1
@@ -1,3 +1,4 @@
1# OS X
1# OS X
2.DS_Store
2.DS_Store
3extras
3extras
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]. [
21
21
22To get started using Astrolabe first you need to run `npm install` inside the `ui` directory.
22To get started using Astrolabe first you need to run `npm install` inside the `ui` directory.
23
23
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.
25
27
26Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
28Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
27
29
28−### Deploying with `glob-ames` glob
30+# Deploying
29
31
301. Spin up a comet or distribution ship.
321. Spin up a comet or distribution ship.
312. Mount an `%astrolabe` desk. In Dojo:
332. Mount an `%astrolabe` desk. In Dojo:
32 1. `|merge %astrolabe our %base` to create a new desk.
34 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)
343. Install the desk
383. 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`
36 2. From the distribution ship's dojo: `:treaty|unpublish %astrolabe`
40 2. From the distribution ship's dojo: `:treaty|unpublish %astrolabe`
37 2. From middleman or distribution ship's dojo: `|commit %astrolabe`
41 2. From middleman or distribution ship's dojo: `|commit %astrolabe`
38 3. (if using a comet as a middleman) `|public %astrolabe`
42 3. (if using a comet as a middleman) `|public %astrolabe`
39 4. (if using a comet as a middleman) from the actual distribution ship: `|install ~comet-name %astrolabe` or `|resume %astrolabe`
43 4. (if using a comet as a middleman) from the actual distribution ship: `|install ~comet-name %astrolabe` or `|resume %astrolabe`
40 5. (if using a comet as a middleman) from the actual distribution ship: `|pause %astrolabe`
44 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.
45+4. Build UI and upload `glob-ames`
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`
46+5. Publish the desk from dojo: `:treaty|publish %astrolabe`
44−
45−### Deploying with `glob-http` glob
46
47
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.
48
51
49−1. Create or launch an urbit using the -F flag
52+Every Time:
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`.
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.
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`.
54+5. Upload the `dist/` folder as a glob to your distribution ship. Example url: https://distribution.urbit-ship.com/docket/upload
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−```
63
55
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+ ```
65
72
66[svelte]: https://svelte.dev/
73[svelte]: https://svelte.dev/
67[typescript]: https://www.typescriptlang.org/
74[typescript]: https://www.typescriptlang.org/
base-desk/lib/bip/b173.hoon+1−1
@@ -11,7 +11,7 @@
11|%
11|%
12++ prefixes
12++ prefixes
13 ^- (map network tape)
13 ^- (map network tape)
14− (my [[%main "bc"] [%testnet "tb"] ~])
14+ (my [[%main "bc"] [%testnet "tb"] [%regtest "bcrt"] ~])
15++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
15++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
16+$ raw-decoded [hrp=tape data=(list @) checksum=(list @)]
16+$ raw-decoded [hrp=tape data=(list @) checksum=(list @)]
17:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js
17:: 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 @@
56 1^0x0
56 1^0x0
57 %testnet
57 %testnet
58 1^0x1
58 1^0x1
59+ %regtest
60+ 1^0x1
59 ==
61 ==
60 :- (cat:byt ~[1^typ pubkey.h])
62 :- (cat:byt ~[1^typ pubkey.h])
61 %- cat:byt
63 %- cat:byt
base-desk/lib/cram.hoon−1
@@ -31,7 +31,6 @@
31 =- (weld - $(p.gen t.p.gen))
31 =- (weld - $(p.gen t.p.gen))
32 ?^ -.i.p.gen $(gen [%xray i.p.gen])
32 ?^ -.i.p.gen $(gen [%xray i.p.gen])
33 ~| [%shut-tuna -.i.p.gen]
33 ~| [%shut-tuna -.i.p.gen]
34− ~| [%shut-slamon -.p.i.p.gen]
35 ?+ -.i.p.gen !!
34 ?+ -.i.p.gen !!
36 %manx ?>(?=(%xray -.p.i.p.gen) $(gen p.i.p.gen))
35 %manx ?>(?=(%xray -.p.i.p.gen) $(gen p.i.p.gen))
37 %marl ?>(?=(%mcts -.p.i.p.gen) $(gen p.i.p.gen))
36 %marl ?>(?=(%mcts -.p.i.p.gen) $(gen p.i.p.gen))
base-desk/lib/ethereum.hoon+19−1
@@ -948,6 +948,21 @@
948 %- prefix-hex
948 %- prefix-hex
949 (render-hex-bytes 20 `@`a)
949 (render-hex-bytes 20 `@`a)
950::
950::
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+::
951++ transaction-to-hex
966++ transaction-to-hex
952 |= h=@
967 |= h=@
953 ^- tape
968 ^- tape
@@ -978,5 +993,8 @@
978::
993::
979++ hex-to-num
994++ hex-to-num
980 |= a=@t
995 |= 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))
982--
1000--
base-desk/lib/hark-store.hoon+22
@@ -171,6 +171,7 @@
171++ dejs
171++ dejs
172 =, dejs:format
172 =, dejs:format
173 |%
173 |%
174+ ++ ship (su ;~(pfix sig fed:ag))
174 :: TODO: fix +stab
175 :: TODO: fix +stab
175 ::
176 ::
176 ++ pa
177 ++ pa
@@ -218,6 +219,26 @@
218 :~ lid+lid
219 :~ lid+lid
219 bin+bin
220 bin+bin
220 ==
221 ==
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+ ==
221 ::
242 ::
222 ++ action
243 ++ action
223 ^- $-(json ^action)
244 ^- $-(json ^action)
@@ -228,6 +249,7 @@
228 read-count+place
249 read-count+place
229 read-each+read-each
250 read-each+read-each
230 read-note+bin
251 read-note+bin
252+ add-note+add-note
231 ==
253 ==
232 --
254 --
233--
255--
base-desk/lib/ph/io.hoon+14−2
@@ -1,5 +1,5 @@
1/- *aquarium, spider
1/- *aquarium, spider
2−/+ libstrand=strand, *strandio, util=ph-util
2+/+ libstrand=strand, *strandio, util=ph-util, aqua-azimuth
3=, strand=strand:libstrand
3=, strand=strand:libstrand
4|%
4|%
5++ send-events
5++ send-events
@@ -127,7 +127,7 @@
127 ;< ~ bind:m (send-azimuth-action %breach who)
127 ;< ~ bind:m (send-azimuth-action %breach who)
128 |- ^- form:m
128 |- ^- form:m
129 =* loop $
129 =* loop $
130− ;< ~ bind:m (sleep ~s1)
130+ ;< ~ bind:m (sleep ~s10)
131 ;< =bowl:spider bind:m get-bowl
131 ;< =bowl:spider bind:m get-bowl
132 =/ aqua-pax
132 =/ aqua-pax
133 :- %i
133 :- %i
@@ -137,6 +137,18 @@
137 (pure:m ~)
137 (pure:m ~)
138 loop
138 loop
139::
139::
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+::
140++ init-ship
152++ init-ship
141 |= [=ship fake=?]
153 |= [=ship fake=?]
142 =/ m (strand ,~)
154 =/ m (strand ,~)
base-desk/lib/ph/util.hoon+1−1
@@ -59,7 +59,7 @@
59::
59::
60:: Checks whether the given event is a dojo output blit containing the
60:: Checks whether the given event is a dojo output blit containing the
61:: given tape
61:: given tape
62−::
62+::TODO should be rename -dill-output
63++ is-dojo-output
63++ is-dojo-output
64 |= [who=ship her=ship uf=unix-effect what=tape]
64 |= [who=ship her=ship uf=unix-effect what=tape]
65 ?& =(who her)
65 ?& =(who her)
base-desk/lib/pill.hoon+3−3
@@ -61,10 +61,10 @@
61 =; hav ~& user-files+(lent hav)
61 =; hav ~& user-files+(lent hav)
62 =/ =yuki:clay
62 =/ =yuki:clay
63 :- *(list tako:clay)
63 :- *(list tako:clay)
64− %- ~(gas by *(map path (each page:clay lobe:clay)))
64+ %- ~(gas by *(map path (each page lobe:clay)))
65− (turn hav |=([=path =page:clay] [path &+page]))
65+ (turn hav |=([=path =page] [path &+page]))
66 [/c/sync [%park des &+yuki *rang:clay]]
66 [/c/sync [%park des &+yuki *rang:clay]]
67− =| hav=(list [path page:clay])
67+ =| hav=(list [path page])
68 |- ^+ hav
68 |- ^+ hav
69 ?~ sal ~
69 ?~ sal ~
70 =. hav $(sal t.sal)
70 =. hav $(sal t.sal)
base-desk/lib/ring.hoon+11−10
@@ -349,15 +349,16 @@
349 |= [our=@p now=@da ship=@p =life]
349 |= [our=@p now=@da ship=@p =life]
350 ^- @udpoint
350 ^- @udpoint
351 ::
351 ::
352− =/ d=[=^life =pass]
352+ =/ d=[=^life =pass *]
353 =/ scry-path=path
353 =/ scry-path=path
354− :~ %k
354+ :~ %j
355 (scot %p our)
355 (scot %p our)
356+ %deed
356 (scot %da now)
357 (scot %da now)
357 (scot %p ship)
358 (scot %p ship)
358 (scot %ud life)
359 (scot %ud life)
359 ==
360 ==
360− .^([^life pass] scry-path)
361+ .^([^life pass *] scry-path)
361 :: we have the deed which has pass, which is several numbers +cat-ed
362 :: we have the deed which has pass, which is several numbers +cat-ed
362 :: together; pull out the keys
363 :: together; pull out the keys
363 ::
364 ::
@@ -376,15 +377,15 @@
376 ?~ invited
377 ?~ invited
377 [participants keys]
378 [participants keys]
378 ::
379 ::
379− =/ =life
380+ =/ lyfe=(unit @ud)
380− .^(life k+/(scot %p our)/life/(scot %da now)/(scot %p i.invited))
381+ .^((unit @ud) j+/(scot %p our)/lyfe/(scot %da now)/(scot %p i.invited))
381 ::
382 ::
382− ?: =(life 0)
383+ ?~ lyfe
383 $(invited t.invited)
384 $(invited t.invited)
384 ::
385 ::
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)
386 ::
387 ::
387− =. participants (~(put in participants) [i.invited life])
388+ =. participants (~(put in participants) [i.invited u.lyfe])
388 =. keys (~(put in keys) pubkey)
389 =. keys (~(put in keys) pubkey)
389 ::
390 ::
390 $(invited t.invited)
391 $(invited t.invited)
@@ -436,11 +437,11 @@
436 :: get our ships' current life
437 :: get our ships' current life
437 ::
438 ::
438 =/ our-life=life
439 =/ 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))
440 :: get our ships' secret keyfile ring
441 :: get our ships' secret keyfile ring
441 ::
442 ::
442 =/ secret-ring=ring
443 =/ 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))
444 :: fetch the encoded auth seed from the ring
445 :: fetch the encoded auth seed from the ring
445 ::
446 ::
446 =/ secret-auth-seed=@
447 =/ 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 @@
734 ;< =bowl:spider bind:m get-bowl
734 ;< =bowl:spider bind:m get-bowl
735 =/ tid
735 =/ tid
736 (scot %ta (cat 3 (cat 3 'strand_' file) (scot %uv (sham file eny.bowl))))
736 (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])
738 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
738 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
739 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
739 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
740 (pure:m tid)
740 (pure:m tid)
@@ -748,7 +748,7 @@
748 ^- form:m
748 ^- form:m
749 ;< =bowl:spider bind:m get-bowl
749 ;< =bowl:spider bind:m get-bowl
750 =/ tid (scot %ta (cat 3 'strand_' (scot %uv (sham file eny.bowl))))
750 =/ 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])
752 ;< ~ bind:m (watch-our /awaiting/[tid] %spider /thread-result/[tid])
752 ;< ~ bind:m (watch-our /awaiting/[tid] %spider /thread-result/[tid])
753 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
753 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
754 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
754 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
@@ -756,6 +756,6 @@
756 ;< ~ bind:m (take-kick /awaiting/[tid])
756 ;< ~ bind:m (take-kick /awaiting/[tid])
757 ?+ p.cage ~|([%strange-thread-result p.cage file tid] !!)
757 ?+ p.cage ~|([%strange-thread-result p.cage file tid] !!)
758 %thread-done (pure:m %& q.cage)
758 %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))
760 ==
760 ==
761--
761--
base-desk/lib/test.hoon+1−1
@@ -13,7 +13,7 @@
13 %+ weld result
13 %+ weld result
14 ^- tang
14 ^- tang
15 :~ [%palm [": " ~ ~ ~] [leaf+"expected" (sell expected) ~]]
15 :~ [%palm [": " ~ ~ ~] [leaf+"expected" (sell expected) ~]]
16− [%palm [": " ~ ~ ~] [leaf+"actual" (sell actual) ~]]
16+ [%palm [": " ~ ~ ~] [leaf+"actual " (sell actual) ~]]
17 ==
17 ==
18 ::
18 ::
19 =? result !(~(nest ut p.actual) | p.expected)
19 =? 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 @@
28 ++ txt of-wain:format
28 ++ txt of-wain:format
29 --
29 --
30++ grad %txt
30++ grad %txt
31−++ garb /down
32--
31--
base-desk/sur/aquarium.hoon+10−1
@@ -24,7 +24,16 @@
24 aqua-event
24 aqua-event
25 ==
25 ==
26::
26::
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+ ==
28+$ pill pill:pill-lib
37+$ pill pill:pill-lib
29::
38::
30+$ aqua-event
39+$ aqua-event
base-desk/sur/bitcoin.hoon+1−1
@@ -4,7 +4,7 @@
4:: chyg: whether account is (non-)change. 0 or 1
4:: chyg: whether account is (non-)change. 0 or 1
5:: bytc: "btc-byts" with dat cast to @ux
5:: bytc: "btc-byts" with dat cast to @ux
6|%
6|%
7−+$ network ?(%main %testnet)
7++$ network ?(%main %testnet %regtest)
8+$ hexb [wid=@ dat=@ux] :: hex byts
8+$ hexb [wid=@ dat=@ux] :: hex byts
9+$ bits [wid=@ dat=@ub]
9+$ bits [wid=@ dat=@ub]
10+$ xpub @ta
10+$ xpub @ta
base-desk/sur/hood.hoon+4−14
@@ -130,13 +130,8 @@
130 =/ tak (scot %uv (~(got by hit.dom) let.dom))
130 =/ tak (scot %uv (~(got by hit.dom) let.dom))
131 =/ yak .^(yaki cs/~[her syd yon %yaki tak])
131 =/ yak .^(yaki cs/~[her syd yon %yaki tak])
132 =/ lob (scot %uv (~(got by q.yak) /sys/kelvin))
132 =/ lob (scot %uv (~(got by q.yak) /sys/kelvin))
133− =/ bob .^(blob cs/~[her syd yon %blob lob])
133+ =/ peg .^(page cs/~[her syd yon %blob lob])
134− ::
135− ;; weft
134+ ;;(weft q.peg)
136− ?- -.bob
137− %direct q.q.bob
138− %delta q.r.bob
139− ==
140:: +read-kelvin-local: read /sys/kelvin from a local desk
135:: +read-kelvin-local: read /sys/kelvin from a local desk
141::
136::
142++ read-kelvin-local
137++ read-kelvin-local
@@ -164,13 +159,8 @@
164 =/ fil (~(get by q.yak) /desk/bill)
159 =/ fil (~(get by q.yak) /desk/bill)
165 ?~ fil ~
160 ?~ fil ~
166 =/ lob (scot %uv u.fil)
161 =/ lob (scot %uv u.fil)
167− =/ bob .^(blob cs/~[her syd yon %blob lob])
162+ =/ peg .^(page cs/~[her syd yon %blob lob])
168− ::
169− ;; (list dude)
163+ ;;((list dude) q.peg)
170− ?- -.bob
171− %direct q.q.bob
172− %delta q.r.bob
173− ==
174:: +read-bill: read contents of /desk/bill manifest
164:: +read-bill: read contents of /desk/bill manifest
175::
165::
176++ read-bill
166++ read-bill
base-desk/sur/spider.hoon+7
@@ -11,4 +11,11 @@
11 %nonexistent :: 404
11 %nonexistent :: 404
12 %offline :: 504
12 %offline :: 504
13 ==
13 ==
14++$ start-args
15+ $: parent=(unit tid)
16+ use=(unit tid)
17+ =beak
18+ file=term
19+ =vase
20+ ==
14--
21--
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 @@
67 =/ =ship `@p`(slav %p &3.path)
67 =/ =ship `@p`(slav %p &3.path)
68 =/ arg ?~(t.t.t.t.path %all i.t.t.t.t.path)
68 =/ arg ?~(t.t.t.t.path %all i.t.t.t.t.path)
69 ``astrolabe-point-set+!>((get-spawned-points:hc ship arg))
69 ``astrolabe-point-set+!>((get-spawned-points:hc ship arg))
70− [%x %search @ ?(~ [%narrow ~])]
70+ [%x %search %patp @ ?(~ [%narrow ~])]
71− =/ =search-text (trip &3.path)
71+ =/ =search-text (trip &4.path)
72+ ?~ search-text ~|(%search-predicted-too-broad !!)
72 =/ expand-search =(t.t.t.path ~)
73 =/ 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))
74 [%x %peers ~]
79 [%x %peers ~]
75 =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
80 =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
76 ``astrolabe-point-set+!>((turn ~(tap in peers) head))
81 ``astrolabe-point-set+!>((turn ~(tap in peers) head))
@@ -262,14 +267,25 @@
262 |= [agg=@ud [spawn=ship *]]
267 |= [agg=@ud [spawn=ship *]]
263 +(agg)
268 +(agg)
264::
269::
265−++ search-opoints
270+++ search-opoints-patp
266 |= [=search-text expand-search=?]
271 |= [=search-text expand-search=?]
267 ^- (list ship)
272 ^- (list ship)
268 =/ search-fulls
273 =/ search-fulls
269 ?: expand-search
274 ?: expand-search
270 (search-text-to-fulls search-text)
275 (search-text-to-fulls search-text)
271 ~[(search-text-to-full search-text)]
276 ~[(search-text-to-full search-text)]
277+ (search-opoints search-fulls)
278+::
279+++ search-opoints-sigil
280+ |= [=search-text]
281+ ^- (list ship)
282+ =/ search-fulls
272− :: ~& (turn search-fulls |=(=search-full search-syls.search-full))
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)
273 =/ mr-searches (maybe-reverse-searches search-fulls 1.000)
289 =/ mr-searches (maybe-reverse-searches search-fulls 1.000)
274 :: ~& (turn mr-searches |=(=mr-search pain.mr-search))
290 :: ~& (turn mr-searches |=(=mr-search pain.mr-search))
275 :: ~& mr-searches
291 :: ~& mr-searches
desk/desk.docket-0+2−2
@@ -2,9 +2,9 @@
2 info+'Your guide to the Urbit constellations'
2 info+'Your guide to the Urbit constellations'
3 color+0x1f.243c
3 color+0x1f.243c
4 image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
4 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]
6 base+'astrolabe'
6 base+'astrolabe'
7− version+[0 2 2]
7+ version+[0 3 0]
8 website+'https://github.com/johnhyde/astrolabe'
8 website+'https://github.com/johnhyde/astrolabe'
9 license+'MIT'
9 license+'MIT'
10==
10==
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 @@
54 .= linear-star-release:contracts:azimuth
54 .= linear-star-release:contracts:azimuth
55 address.owner.own.p
55 address.owner.own.p
56::
56::
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+::
57++ smel
66++ smel
58 |%
67 |%
59 ++ ship
68 ++ ship
desk/lib/search.hoon+47−5
@@ -12,7 +12,7 @@
12++ prefixes ^~ (trip-syls sis:po)
12++ prefixes ^~ (trip-syls sis:po)
13++ suffixes ^~ (trip-syls dex:po)
13++ suffixes ^~ (trip-syls dex:po)
14++ full-opts ^~ (gulf .~zod .~fes)
14++ full-opts ^~ (gulf .~zod .~fes)
15−++ galaxies ^~ `(list @p)``(list @)`full-opts
15+++ galaxies ^~ `(list @p)`full-opts
16++ sanitize-search-text
16++ sanitize-search-text
17 |= =search-text
17 |= =search-text
18 ^- tape
18 ^- tape
@@ -129,6 +129,25 @@
129 ?: =(~[~ ~ ~ ~] search-opts.full)
129 ?: =(~[~ ~ ~ ~] search-opts.full)
130 ~
130 ~
131 (some full)
131 (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+::
132++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
151++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
133 |* lst=(list)
152 |* lst=(list)
134 ^- _lst
153 ^- _lst
@@ -221,7 +240,10 @@
221 |= [=syl cand=@]
240 |= [=syl cand=@]
222 :: todo check cand < 256
241 :: todo check cand < 256
223 (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
242 (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
243+++ syl-opts-match
244+ |= [opts=search-syl-opts cand=@]
245+ !=(~ (find ~[cand] opts))
224−++ matches-search
246+++ matches-search-syls
225 |= [search=search-syls cand-atom=@]
247 |= [search=search-syls cand-atom=@]
226 =/ cand (rip 3 cand-atom)
248 =/ cand (rip 3 cand-atom)
227 |-
249 |-
@@ -240,6 +262,28 @@
240 $(search t.search, cand t.cand)
262 $(search t.search, cand t.cand)
241 ==
263 ==
242::
264::
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+::
243++ opts-in-range
287++ opts-in-range
244 |= [lb=@ rb=@ opts=(list @)]
288 |= [lb=@ rb=@ opts=(list @)]
245 ^- search-syl-opts
289 ^- search-syl-opts
@@ -282,8 +326,6 @@
282 |* [agg=mold search=search-full points=opoints =page-info]
326 |* [agg=mold search=search-full points=opoints =page-info]
283 |= [res=[count=@ =agg] f=$-([agg @q] agg)]
327 |= [res=[count=@ =agg] f=$-([agg @q] agg)]
284 ^- [count=@ =agg]
328 ^- [count=@ =agg]
285− =/ s-syls search-syls.search
286− :: ~& s-syls
287 =/ s-opts search-opts.search
329 =/ s-opts search-opts.search
288 =+ [llb=.~zod rrb=`@q`(sub (bex 32) 1)]
330 =+ [llb=.~zod rrb=`@q`(sub (bex 32) 1)]
289 :: =< +
331 :: =< +
@@ -302,7 +344,7 @@
302 =/ right-view (range-contains-search rlb rrb s-opts)
344 =/ right-view (range-contains-search rlb rrb s-opts)
303 =? res contains.left-view
345 =? res contains.left-view
304 $(points l.points, rrb lrb, s-opts opts.left-view)
346 $(points l.points, rrb lrb, s-opts opts.left-view)
305− =? res (matches-search s-syls n)
347+ =? res (matches-search search n)
306 =/ fits (fits-on-page count.res page-info)
348 =/ fits (fits-on-page count.res page-info)
307 =. count.res +(count.res)
349 =. count.res +(count.res)
308 =? agg.res fits (f agg.res n)
350 =? 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 @@
1{
1{
2 "name": "urbit-astrolabe",
2 "name": "urbit-astrolabe",
3− "version": "0.0.0",
3+ "version": "0.3.0",
4 "lockfileVersion": 2,
4 "lockfileVersion": 2,
5 "requires": true,
5 "requires": true,
6 "packages": {
6 "packages": {
7 "": {
7 "": {
8 "name": "urbit-astrolabe",
8 "name": "urbit-astrolabe",
9− "version": "0.0.0",
9+ "version": "0.3.0",
10 "dependencies": {
10 "dependencies": {
11+ "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
11 "@tlon/phonemes-js": "^1.0.0",
12 "@tlon/phonemes-js": "^1.0.0",
12 "@tlon/sigil-js": "^1.4.5",
13 "@tlon/sigil-js": "^1.4.5",
13 "@urbit/api": "^2.1.0",
14 "@urbit/api": "^2.1.0",
@@ -27,6 +28,7 @@
27 "autoprefixer": "^10.4.2",
28 "autoprefixer": "^10.4.2",
28 "concurrently": "^7.0.0",
29 "concurrently": "^7.0.0",
29 "cross-env": "^7.0.3",
30 "cross-env": "^7.0.3",
31+ "dotenv-cli": "^6.0.0",
30 "postcss-cli": "^9.1.0",
32 "postcss-cli": "^9.1.0",
31 "sass": "^1.50.0",
33 "sass": "^1.50.0",
32 "svelte": "^3.48.0",
34 "svelte": "^3.48.0",
@@ -39,6 +41,28 @@
39 "vite": "^2.7.2"
41 "vite": "^2.7.2"
40 }
42 }
41 },
43 },
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+ },
42 "node_modules/@babel/code-frame": {
66 "node_modules/@babel/code-frame": {
43 "version": "7.16.7",
67 "version": "7.16.7",
44 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
68 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@@ -147,6 +171,19 @@
147 "node": ">=6.9.0"
171 "node": ">=6.9.0"
148 }
172 }
149 },
173 },
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+ },
150 "node_modules/@microsoft/fetch-event-source": {
187 "node_modules/@microsoft/fetch-event-source": {
151 "version": "2.0.1",
188 "version": "2.0.1",
152 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
189 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -775,7 +812,7 @@
775 "node_modules/deep-rename-keys": {
812 "node_modules/deep-rename-keys": {
776 "version": "0.2.1",
813 "version": "0.2.1",
777 "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
814 "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==",
779 "dependencies": {
816 "dependencies": {
780 "kind-of": "^3.0.2",
817 "kind-of": "^3.0.2",
781 "rename-keys": "^1.1.2"
818 "rename-keys": "^1.1.2"
@@ -849,6 +886,39 @@
849 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
886 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
850 "dev": true
887 "dev": true
851 },
888 },
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+ },
852 "node_modules/electron-to-chromium": {
922 "node_modules/electron-to-chromium": {
853 "version": "1.4.38",
923 "version": "1.4.38",
854 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
924 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
@@ -1153,7 +1223,7 @@
1153 "node_modules/eventemitter3": {
1223 "node_modules/eventemitter3": {
1154 "version": "2.0.3",
1224 "version": "2.0.3",
1155 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
1225 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
1156− "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
1226+ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
1157 },
1227 },
1158 "node_modules/events": {
1228 "node_modules/events": {
1159 "version": "3.3.0",
1229 "version": "3.3.0",
@@ -1277,7 +1347,7 @@
1277 "node_modules/get-value": {
1347 "node_modules/get-value": {
1278 "version": "2.0.6",
1348 "version": "2.0.6",
1279 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
1349 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
1280− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
1350+ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
1281 "engines": {
1351 "engines": {
1282 "node": ">=0.10.0"
1352 "node": ">=0.10.0"
1283 }
1353 }
@@ -1390,7 +1460,7 @@
1390 "node_modules/has-value": {
1460 "node_modules/has-value": {
1391 "version": "0.3.1",
1461 "version": "0.3.1",
1392 "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
1462 "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==",
1394 "dependencies": {
1464 "dependencies": {
1395 "get-value": "^2.0.3",
1465 "get-value": "^2.0.3",
1396 "has-values": "^0.1.4",
1466 "has-values": "^0.1.4",
@@ -1403,7 +1473,7 @@
1403 "node_modules/has-value/node_modules/isobject": {
1473 "node_modules/has-value/node_modules/isobject": {
1404 "version": "2.1.0",
1474 "version": "2.1.0",
1405 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
1475 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
1406− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
1476+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
1407 "dependencies": {
1477 "dependencies": {
1408 "isarray": "1.0.0"
1478 "isarray": "1.0.0"
1409 },
1479 },
@@ -1414,7 +1484,7 @@
1414 "node_modules/has-values": {
1484 "node_modules/has-values": {
1415 "version": "0.1.4",
1485 "version": "0.1.4",
1416 "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
1486 "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==",
1418 "engines": {
1488 "engines": {
1419 "node": ">=0.10.0"
1489 "node": ">=0.10.0"
1420 }
1490 }
@@ -1571,7 +1641,7 @@
1571 "node_modules/isarray": {
1641 "node_modules/isarray": {
1572 "version": "1.0.0",
1642 "version": "1.0.0",
1573 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
1643 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
1574− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
1644+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
1575 },
1645 },
1576 "node_modules/isexe": {
1646 "node_modules/isexe": {
1577 "version": "2.0.0",
1647 "version": "2.0.0",
@@ -1582,7 +1652,7 @@
1582 "node_modules/isobject": {
1652 "node_modules/isobject": {
1583 "version": "3.0.1",
1653 "version": "3.0.1",
1584 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
1654 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
1585− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
1655+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
1586 "engines": {
1656 "engines": {
1587 "node": ">=0.10.0"
1657 "node": ">=0.10.0"
1588 }
1658 }
@@ -1613,7 +1683,7 @@
1613 "node_modules/kind-of": {
1683 "node_modules/kind-of": {
1614 "version": "3.2.2",
1684 "version": "3.2.2",
1615 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
1685 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
1616− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
1686+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
1617 "dependencies": {
1687 "dependencies": {
1618 "is-buffer": "^1.1.5"
1688 "is-buffer": "^1.1.5"
1619 },
1689 },
@@ -1795,7 +1865,7 @@
1795 "node_modules/object-assign": {
1865 "node_modules/object-assign": {
1796 "version": "4.1.1",
1866 "version": "4.1.1",
1797 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1867 "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==",
1799 "peer": true,
1869 "peer": true,
1800 "engines": {
1870 "engines": {
1801 "node": ">=0.10.0"
1871 "node": ">=0.10.0"
@@ -1818,7 +1888,7 @@
1818 "node_modules/omit-deep": {
1888 "node_modules/omit-deep": {
1819 "version": "0.3.0",
1889 "version": "0.3.0",
1820 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
1890 "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==",
1822 "dependencies": {
1892 "dependencies": {
1823 "is-plain-object": "^2.0.1",
1893 "is-plain-object": "^2.0.1",
1824 "unset-value": "^0.1.1"
1894 "unset-value": "^0.1.1"
@@ -2765,7 +2835,7 @@
2765 "node_modules/unset-value": {
2835 "node_modules/unset-value": {
2766 "version": "0.1.2",
2836 "version": "0.1.2",
2767 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
2837 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
2768− "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
2838+ "integrity": "sha512-yhv5I4TsldLdE3UcVQn0hD2T5sNCPv4+qm/CTUpRKIpwthYRIipsAPdsrNpOI79hPQa0rTTeW22Fq6JWRcTgNg==",
2769 "dependencies": {
2839 "dependencies": {
2770 "has-value": "^0.3.1",
2840 "has-value": "^0.3.1",
2771 "isobject": "^3.0.0"
2841 "isobject": "^3.0.0"
@@ -2900,7 +2970,7 @@
2900 "node_modules/xml-lexer": {
2970 "node_modules/xml-lexer": {
2901 "version": "0.2.2",
2971 "version": "0.2.2",
2902 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
2972 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
2903− "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
2973+ "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
2904 "dependencies": {
2974 "dependencies": {
2905 "eventemitter3": "^2.0.0"
2975 "eventemitter3": "^2.0.0"
2906 }
2976 }
@@ -2908,7 +2978,7 @@
2908 "node_modules/xml-reader": {
2978 "node_modules/xml-reader": {
2909 "version": "2.4.3",
2979 "version": "2.4.3",
2910 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
2980 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
2911− "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
2981+ "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
2912 "dependencies": {
2982 "dependencies": {
2913 "eventemitter3": "^2.0.0",
2983 "eventemitter3": "^2.0.0",
2914 "xml-lexer": "^0.2.2"
2984 "xml-lexer": "^0.2.2"
@@ -3056,6 +3126,15 @@
3056 "regenerator-runtime": "^0.13.4"
3126 "regenerator-runtime": "^0.13.4"
3057 }
3127 }
3058 },
3128 },
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+ },
3059 "@microsoft/fetch-event-source": {
3138 "@microsoft/fetch-event-source": {
3060 "version": "2.0.1",
3139 "version": "2.0.1",
3061 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
3140 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -3512,7 +3591,7 @@
3512 "deep-rename-keys": {
3591 "deep-rename-keys": {
3513 "version": "0.2.1",
3592 "version": "0.2.1",
3514 "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
3593 "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==",
3516 "requires": {
3595 "requires": {
3517 "kind-of": "^3.0.2",
3596 "kind-of": "^3.0.2",
3518 "rename-keys": "^1.1.2"
3597 "rename-keys": "^1.1.2"
@@ -3568,6 +3647,30 @@
3568 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
3647 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
3569 "dev": true
3648 "dev": true
3570 },
3649 },
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+ },
3571 "electron-to-chromium": {
3674 "electron-to-chromium": {
3572 "version": "1.4.38",
3675 "version": "1.4.38",
3573 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
3676 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
@@ -3760,7 +3863,7 @@
3760 "eventemitter3": {
3863 "eventemitter3": {
3761 "version": "2.0.3",
3864 "version": "2.0.3",
3762 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
3865 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
3763− "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
3866+ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
3764 },
3867 },
3765 "events": {
3868 "events": {
3766 "version": "3.3.0",
3869 "version": "3.3.0",
@@ -3849,7 +3952,7 @@
3849 "get-value": {
3952 "get-value": {
3850 "version": "2.0.6",
3953 "version": "2.0.6",
3851 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
3954 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
3852− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
3955+ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="
3853 },
3956 },
3854 "glob": {
3957 "glob": {
3855 "version": "7.2.0",
3958 "version": "7.2.0",
@@ -3933,7 +4036,7 @@
3933 "has-value": {
4036 "has-value": {
3934 "version": "0.3.1",
4037 "version": "0.3.1",
3935 "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
4038 "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==",
3937 "requires": {
4040 "requires": {
3938 "get-value": "^2.0.3",
4041 "get-value": "^2.0.3",
3939 "has-values": "^0.1.4",
4042 "has-values": "^0.1.4",
@@ -3943,7 +4046,7 @@
3943 "isobject": {
4046 "isobject": {
3944 "version": "2.1.0",
4047 "version": "2.1.0",
3945 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
4048 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
3946− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
4049+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
3947 "requires": {
4050 "requires": {
3948 "isarray": "1.0.0"
4051 "isarray": "1.0.0"
3949 }
4052 }
@@ -3953,7 +4056,7 @@
3953 "has-values": {
4056 "has-values": {
3954 "version": "0.1.4",
4057 "version": "0.1.4",
3955 "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
4058 "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=="
3957 },
4060 },
3958 "ignore": {
4061 "ignore": {
3959 "version": "5.2.0",
4062 "version": "5.2.0",
@@ -4073,7 +4176,7 @@
4073 "isarray": {
4176 "isarray": {
4074 "version": "1.0.0",
4177 "version": "1.0.0",
4075 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
4178 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
4076− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
4179+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
4077 },
4180 },
4078 "isexe": {
4181 "isexe": {
4079 "version": "2.0.0",
4182 "version": "2.0.0",
@@ -4084,7 +4187,7 @@
4084 "isobject": {
4187 "isobject": {
4085 "version": "3.0.1",
4188 "version": "3.0.1",
4086 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
4189 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
4087− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
4190+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
4088 },
4191 },
4089 "js-tokens": {
4192 "js-tokens": {
4090 "version": "4.0.0",
4193 "version": "4.0.0",
@@ -4110,7 +4213,7 @@
4110 "kind-of": {
4213 "kind-of": {
4111 "version": "3.2.2",
4214 "version": "3.2.2",
4112 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
4215 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
4113− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
4216+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
4114 "requires": {
4217 "requires": {
4115 "is-buffer": "^1.1.5"
4218 "is-buffer": "^1.1.5"
4116 }
4219 }
@@ -4250,7 +4353,7 @@
4250 "object-assign": {
4353 "object-assign": {
4251 "version": "4.1.1",
4354 "version": "4.1.1",
4252 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
4355 "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==",
4254 "peer": true
4357 "peer": true
4255 },
4358 },
4256 "object-hash": {
4359 "object-hash": {
@@ -4267,7 +4370,7 @@
4267 "omit-deep": {
4370 "omit-deep": {
4268 "version": "0.3.0",
4371 "version": "0.3.0",
4269 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
4372 "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==",
4271 "requires": {
4374 "requires": {
4272 "is-plain-object": "^2.0.1",
4375 "is-plain-object": "^2.0.1",
4273 "unset-value": "^0.1.1"
4376 "unset-value": "^0.1.1"
@@ -4899,7 +5002,7 @@
4899 "unset-value": {
5002 "unset-value": {
4900 "version": "0.1.2",
5003 "version": "0.1.2",
4901 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
5004 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
4902− "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
5005+ "integrity": "sha512-yhv5I4TsldLdE3UcVQn0hD2T5sNCPv4+qm/CTUpRKIpwthYRIipsAPdsrNpOI79hPQa0rTTeW22Fq6JWRcTgNg==",
4903 "requires": {
5006 "requires": {
4904 "has-value": "^0.3.1",
5007 "has-value": "^0.3.1",
4905 "isobject": "^3.0.0"
5008 "isobject": "^3.0.0"
@@ -4986,7 +5089,7 @@
4986 "xml-lexer": {
5089 "xml-lexer": {
4987 "version": "0.2.2",
5090 "version": "0.2.2",
4988 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
5091 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
4989− "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
5092+ "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
4990 "requires": {
5093 "requires": {
4991 "eventemitter3": "^2.0.0"
5094 "eventemitter3": "^2.0.0"
4992 }
5095 }
@@ -4994,7 +5097,7 @@
4994 "xml-reader": {
5097 "xml-reader": {
4995 "version": "2.4.3",
5098 "version": "2.4.3",
4996 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
5099 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
4997− "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
5100+ "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
4998 "requires": {
5101 "requires": {
4999 "eventemitter3": "^2.0.0",
5102 "eventemitter3": "^2.0.0",
5000 "xml-lexer": "^0.2.2"
5103 "xml-lexer": "^0.2.2"
ui/package.json+18−6
@@ -1,17 +1,27 @@
1{
1{
2 "name": "urbit-astrolabe",
2 "name": "urbit-astrolabe",
3− "version": "0.0.0",
4 "type": "module",
3 "type": "module",
4+ "version": "0.3.0",
5 "scripts": {
5 "scripts": {
6− "dev:only": "vite --port 4000 --host",
6+ "dev:vite": "vite --port 4000 --host",
7− "build:only": "vite build",
7+ "build:vite": "vite build",
8 "tailwind:watch": "cross-env TAILWIND_MODE=watch cross-env NODE_ENV=development postcss src/styles/tailwind.css -o src/styles/tailwind-output.css -w",
8 "tailwind:watch": "cross-env TAILWIND_MODE=watch cross-env NODE_ENV=development postcss src/styles/tailwind.css -o src/styles/tailwind-output.css -w",
9 "tailwind:build": "cross-env TAILWIND_MODE=build cross-env NODE_ENV=production postcss src/styles/tailwind.css -o src/styles/tailwind-output.css",
9 "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\"",
10+ "dev": "concurrently \"npm run dev:vite\" \"npm run tailwind:watch\"",
11− "build": "npm run tailwind:build && npm run build:only",
11+ "build": "npm run tailwind:build && npm run build:vite",
12 "preview": "vite preview",
12 "preview": "vite preview",
13 "check": "svelte-check --tsconfig ./tsconfig.json",
13 "check": "svelte-check --tsconfig ./tsconfig.json",
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",
14− "release": "npm run build && ../install.sh "
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"
15 },
25 },
16 "devDependencies": {
26 "devDependencies": {
17 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
27 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
@@ -20,6 +30,7 @@
20 "autoprefixer": "^10.4.2",
30 "autoprefixer": "^10.4.2",
21 "concurrently": "^7.0.0",
31 "concurrently": "^7.0.0",
22 "cross-env": "^7.0.3",
32 "cross-env": "^7.0.3",
33+ "dotenv-cli": "^6.0.0",
23 "postcss-cli": "^9.1.0",
34 "postcss-cli": "^9.1.0",
24 "sass": "^1.50.0",
35 "sass": "^1.50.0",
25 "svelte": "^3.48.0",
36 "svelte": "^3.48.0",
@@ -32,6 +43,7 @@
32 "vite": "^2.7.2"
43 "vite": "^2.7.2"
33 },
44 },
34 "dependencies": {
45 "dependencies": {
46+ "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
35 "@tlon/phonemes-js": "^1.0.0",
47 "@tlon/phonemes-js": "^1.0.0",
36 "@tlon/sigil-js": "^1.4.5",
48 "@tlon/sigil-js": "^1.4.5",
37 "@urbit/api": "^2.1.0",
49 "@urbit/api": "^2.1.0",
ui/src/App.svelte+3−1
@@ -1,8 +1,9 @@
1<script lang="ts">
1<script lang="ts">
2 import Router from 'svelte-spa-router';
2 import Router from 'svelte-spa-router';
3+ import SigilPage from './routes/SigilPage.svelte';
3 import HelpPage from './routes/HelpPage.svelte';
4 import HelpPage from './routes/HelpPage.svelte';
4 import Home from './routes/Home.svelte';
5 import Home from './routes/Home.svelte';
5− import HelpModal from './components/HelpModal.svelte';
6+ import HelpModal from '@/docs/HelpModal.svelte';
6 import "./styles/styles.scss";
7 import "./styles/styles.scss";
7
8
8 // const routes = {
9 // const routes = {
@@ -14,6 +15,7 @@
14 // }
15 // }
15
16
16 const routes = {
17 const routes = {
18+ '/sigil': SigilPage,
17 '/help/:path?': HelpPage,
19 '/help/:path?': HelpPage,
18 // '/:patp?': Home,
20 // '/:patp?': Home,
19 // '/ship/:patp?': Home,
21 // '/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 @@
1import tippy from 'tippy.js';
1import 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:
3// const app = new DocLink({
3// const app = new DocLink({
4// target: document.getElementBySelector('.tippy')
4// target: document.getElementBySelector('.tippy')
5// })
5// })
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 @@
1<script lang="ts">
1<script lang="ts">
2 import { link } from 'svelte-spa-router'
2 import { link } from 'svelte-spa-router'
3− import { linkToChart } from '../lib/link';
3+ import { linkToChart } from 'lib/link';
4
4
5 export let patp = null;
5 export let patp = null;
6</script>
6</script>
ui/src/components/EthAddressLink.svelte+2−2
@@ -1,6 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2− import CopyToClipboard from "svelte-copy-to-clipboard";
2+ import CopyToClipboard from 'svelte-copy-to-clipboard';
3− import tooltip from "../actions/tooltip";
3+ import tooltip from 'actions/tooltip';
4
4
5 let overrideText = null;
5 let overrideText = null;
6
6
ui/src/components/HomeScreenTiles.svelte+3−3
@@ -17,21 +17,21 @@
17</script>
17</script>
18
18
19<div class="pt-10 max-w-md w-full flex flex-wrap gap-5 justify-center">
19<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">
21 <DocLink modal path="astrolabe">
21 <DocLink modal path="astrolabe">
22 <div class="p-5 text-xl text-center">
22 <div class="p-5 text-xl text-center">
23 About Astrolabe
23 About Astrolabe
24 </div>
24 </div>
25 </DocLink>
25 </DocLink>
26 </div>
26 </div>
27− <div class="rounded-lg bg-white flex-grow max-w-xs">
27+ <div class="rounded-lg bg-white grow max-w-xs">
28 <DocLink modal path="">
28 <DocLink modal path="">
29 <div class="p-5 text-xl text-center">
29 <div class="p-5 text-xl text-center">
30 About Urbit IDs
30 About Urbit IDs
31 </div>
31 </div>
32 </DocLink>
32 </DocLink>
33 </div>
33 </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">
35 <p class="p-5 text-xl text-center text-white">
35 <p class="p-5 text-xl text-center text-white">
36 <br/>
36 <br/>
37 Star Chart
37 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 @@
1<script lang="ts">
1<script lang="ts">
2 import { link } from 'svelte-spa-router'
2 import { link } from 'svelte-spa-router'
3− import { cite, normalizeId } from '../lib/id';
3+ import { cite, normalizeId } from 'lib/id';
4− import { linkToShip } from '../lib/link';
4+ import { linkToShip } from 'lib/link';
5 export let patp: string;
5 export let patp: string;
6 $: normalizedId = normalizeId(patp);
6 $: normalizedId = normalizeId(patp);
7</script>
7</script>
ui/src/components/ShipListing.svelte+3−3
@@ -2,8 +2,8 @@
2
2
3 import Sigil from "./Sigil.svelte";
3 import Sigil from "./Sigil.svelte";
4 import { push } from 'svelte-spa-router'
4 import { push } from 'svelte-spa-router'
5− import { normalizeId } from '../lib/id';
5+ import { normalizeId } from 'lib/id';
6− import { linkToShip as generateLinkToShip } from '../lib/link';
6+ import { linkToShip as generateLinkToShip } from 'lib/link';
7 export let ship: any;
7 export let ship: any;
8 export let linkToShip: boolean = false;
8 export let linkToShip: boolean = false;
9 export let differentiateContacts = false;
9 export let differentiateContacts = false;
@@ -25,7 +25,7 @@ on:click={handleClick}
25on:click
25on:click
26>
26>
27 <div class="flex flex-wrap p-4 items-center">
27 <div class="flex flex-wrap p-4 items-center">
28− <Sigil {patp} size={48} />
28+ <Sigil {patp} size={48} useNew />
29 <!-- {#if ship.avatar}
29 <!-- {#if ship.avatar}
30 <img src={ship.avatar} class="h-12 w-12 mr-4" />
30 <img src={ship.avatar} class="h-12 w-12 mr-4" />
31 {/if} -->
31 {/if} -->
ui/src/components/ShipListings.svelte+1−1
@@ -1,6 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2 import { createEventDispatcher } from 'svelte';
2 import { createEventDispatcher } from 'svelte';
3− import ShipListing from './ShipListing.svelte';
3+ import ShipListing from '@/ShipListing.svelte';
4 const dispatch = createEventDispatcher();
4 const dispatch = createEventDispatcher();
5
5
6 export let ships = [];
6 export let ships = [];
ui/src/components/ShipView.svelte+3−3
@@ -1,8 +1,8 @@
1<script lang="ts">
1<script lang="ts">
2 import { clan, sein, patp2dec } from 'urbit-ob';
2 import { clan, sein, patp2dec } from 'urbit-ob';
3
3
4− import { cite, normalizeId } from '../lib/id';
4+ import { cite, normalizeId } from 'lib/id';
5− import { getPoint, getSpawnedPoints } from '../lib/api';
5+ import { getPoint, getSpawnedPoints } from 'lib/api';
6 import Sigil from "./Sigil.svelte";
6 import Sigil from "./Sigil.svelte";
7 import GoldBadge from './GoldBadge.svelte';
7 import GoldBadge from './GoldBadge.svelte';
8 import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
8 import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
@@ -181,7 +181,7 @@
181<div class="p-8 max-w-md w-full rounded-lg bg-white flex flex-col md:max-w-2xl">
181<div class="p-8 max-w-md w-full rounded-lg bg-white flex flex-col md:max-w-2xl">
182 <div class="flex flex-col items-center space-y-4 md:flex-row md:items-start md:space-x-8 md:space-y-0">
182 <div class="flex flex-col items-center space-y-4 md:flex-row md:items-start md:space-x-8 md:space-y-0">
183 <div class="w-full max-w-[256px] md:min-w-[256px] flex-shrink-0">
183 <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 />
185 </div>
185 </div>
186 <div class="grow w-full min-w-0">
186 <div class="grow w-full min-w-0">
187 <h2 class="text-2xl text-center">
187 <h2 class="text-2xl text-center">
ui/src/components/Sigil.svelte+34−10
@@ -1,11 +1,17 @@
1<script lang="ts">
1<script lang="ts">
2 import { clan } from 'urbit-ob';
2 import { clan } from 'urbit-ob';
3− import { sigil, stringRenderer } from '@tlon/sigil-js';
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';
4− import moonPng from '../assets/moon.png';
6+ import moonPng from 'assets/moon.png';
5− import cometSvg from '../assets/comet.svg';
7+ import cometSvg from 'assets/comet.svg';
6
8
7 export let patp: string;
9 export let patp: string;
8 export let size: number;
10 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;
9 let svgString: any;
15 let svgString: any;
10 let imgSrc: any;
16 let imgSrc: any;
11
17
@@ -13,13 +19,31 @@
13 $: displaySigil = ['galaxy', 'star', 'planet'].includes(shipClass);
19 $: displaySigil = ['galaxy', 'star', 'planet'].includes(shipClass);
14 $: {
20 $: {
15 if (displaySigil) {
21 if (displaySigil) {
22+ let sigil = sigil1;
23+ let sr = sr1;
16− const svgAST = sigil({
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({
17− patp: patp,
37+ patp: patp,
18− size,
38+ size,
19− colors: ['black', 'white'],
39+ colors: [bgColor, fgColor],
20− });
40+ });
41+ }
21 svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
42 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+ }
23 } else if (shipClass === 'moon') {
47 } else if (shipClass === 'moon') {
24 imgSrc = moonPng;
48 imgSrc = moonPng;
25 } else {
49 } else {
@@ -28,7 +52,7 @@
28 }
52 }
29</script>
53</script>
30
54
31−<div class="flex-grow" style:max-width="{size}px">
55+<div class="grow" style:max-width="{size}px">
32 {#if displaySigil}
56 {#if displaySigil}
33 {@html svgString}
57 {@html svgString}
34 {:else}
58 {:else}
ui/src/components/StarChart.svelte+4−4
@@ -8,10 +8,10 @@
8
8
9 import { clan } from 'urbit-ob';
9 import { clan } from 'urbit-ob';
10 import DocLink from './docs/DocLink.svelte';
10 import DocLink from './docs/DocLink.svelte';
11− import { cite, normalizeId } from '../lib/id';
11+ import { cite, normalizeId } from 'lib/id';
12− import { parseChartData, populateGraph } from '../lib/graph';
12+ import { parseChartData, populateGraph } from 'lib/graph';
13− import { getChartData } from '../lib/api';
13+ import { getChartData } from 'lib/api';
14− import { linkToShip } from '../lib/link';
14+ import { linkToShip } from 'lib/link';
15
15
16 import InlineSelect from './buttons/InlineSelect.svelte';
16 import InlineSelect from './buttons/InlineSelect.svelte';
17
17
ui/src/components/Tooltip.svelte+1−2
@@ -1,6 +1,5 @@
1<script lang="ts">
1<script lang="ts">
2− import { tick } from "svelte";
3− import tooltip from "../actions/tooltip";
2+ import tooltip from 'actions/tooltip';
4 export let text: string = '';
3 export let text: string = '';
5 let className: string = '';
4 let className: string = '';
6 export { className as class };
5 export { className as class };
ui/src/components/docs/Doc.svelte+3−3
@@ -2,8 +2,8 @@
2 import { createEventDispatcher } from 'svelte';
2 import { createEventDispatcher } from 'svelte';
3 import DocContent from './DocContent.svelte';
3 import DocContent from './DocContent.svelte';
4 import DocHome from './DocHome.svelte';
4 import DocHome from './DocHome.svelte';
5− import Hamburger from '../buttons/Hamburger.svelte';
5+ import Hamburger from '@/buttons/Hamburger.svelte';
6− import CloseButton from '../buttons/CloseButton.svelte';
6+ import CloseButton from '@/buttons/CloseButton.svelte';
7
7
8 export let path: string;
8 export let path: string;
9
9
@@ -26,7 +26,7 @@
26 {:else}
26 {:else}
27 <div class="w-7 h-7" />
27 <div class="w-7 h-7" />
28 {/if}
28 {/if}
29− <div class="flex-grow text-center text-xl">
29+ <div class="grow text-center text-xl">
30 Help
30 Help
31 </div>
31 </div>
32 <CloseButton on:click={close} />
32 <CloseButton on:click={close} />
ui/src/components/docs/DocContent.svelte+3−3
@@ -2,10 +2,10 @@
2
2
3 import { createEventDispatcher } from 'svelte';
3 import { createEventDispatcher } from 'svelte';
4
4
5− import docLinks from "../../actions/docLinks";
5+ import docLinks from "actions/docLinks";
6− import docs from "../../stores/docs";
6+ import docs from "stores/docs";
7
7
8− import LoadingSpinner from "../LoadingSpinner.svelte";
8+ import LoadingSpinner from "@/LoadingSpinner.svelte";
9
9
10 export let path:string;
10 export let path:string;
11
11
ui/src/components/docs/DocLink.svelte+1−1
@@ -1,6 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2 import { createEventDispatcher } from 'svelte';
2 import { createEventDispatcher } from 'svelte';
3− import docs from '../../stores/docs';
3+ import docs from 'stores/docs';
4
4
5 export let path:string;
5 export let path:string;
6 export let modal:boolean = false;
6 export let modal:boolean = false;
ui/src/components/HelpModal.svelte → ui/src/components/docs/HelpModal.svelterenamed+2−2
@@ -1,6 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2− import docs from '../stores/docs';
2+ import docs from 'stores/docs';
3− import Doc from './docs/Doc.svelte';
3+ import Doc from './Doc.svelte';
4
4
5 function close() {
5 function close() {
6 docs.hide()
6 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 @@
1<script lang="ts">
1<script lang="ts">
2
2
3 import type { Patp, Rolodex } from '@urbit/api';
3 import type { Patp, Rolodex } from '@urbit/api';
4− import { searchPoints } from '../lib/api';
4+ import { searchPoints } from 'lib/api';
5+ import type { SigilQuery } from 'lib/sigil';
5− import { searchedContacts } from '../stores/searchStores';
6+ import { searchedContacts } from 'stores/searchStores';
6− import ShipListings from './ShipListings.svelte';
7+ import ShipListings from '@/ShipListings.svelte';
7 import SearchSettings from './SearchSettings.svelte';
8 import SearchSettings from './SearchSettings.svelte';
8 import SearchResultsNavButtons from './SearchResultsNavButtons.svelte';
9 import SearchResultsNavButtons from './SearchResultsNavButtons.svelte';
9
10
10− export let query: RegExp;
11+ export let regexQuery: RegExp;
11− export let search: string;
12+ export let sigilQuery: SigilQuery;
13+ export let patpSearch: string;
12− // export let patp: string;
14+ export let searchMode: ('patp' | 'sigil') = 'patp';
15+
13 let combinedSearchResults = [];
16 let combinedSearchResults = [];
14 let searchedPointsP: Promise<any>;
17 let searchedPointsP: Promise<any>;
15 let searchedPoints: Patp[] = [];
18 let searchedPoints: Patp[] = [];
@@ -25,14 +28,19 @@
25 $: {
28 $: {
26 clearTimeout(timer);
29 clearTimeout(timer);
27 timer = setTimeout(() => {
30 timer = setTimeout(() => {
31+ if (searchMode == 'patp') {
32+ searchedContacts.searchPatp(regexQuery);
33+ } else if (searchMode == 'sigil') {
28− searchedContacts.search(query);
34+ searchedContacts.searchSigil(sigilQuery.querySyls);
35+ }
29 }, 1000);
36 }, 1000);
30 }
37 }
38+
31 function doSearch() {
39 function doSearch() {
32 const searchSnap = search;
40 const searchSnap = search;
33 searchStatus = 'prog';
41 searchStatus = 'prog';
34 console.log(`starting search for "${searchSnap}"`);
42 console.log(`starting search for "${searchSnap}"`);
35− searchedPointsP = searchPoints(searchSnap);
43+ searchedPointsP = searchPoints(searchSnap, searchMode);
36 searchedPointsP.then((points) => {
44 searchedPointsP.then((points) => {
37 if (search === searchSnap) {
45 if (search === searchSnap) {
38 searchedPoints = points;
46 searchedPoints = points;
@@ -48,7 +56,8 @@
48 }
56 }
49 });
57 });
50 }
58 }
59+ $: search = (searchMode === 'sigil') ? sigilQuery.string : patpSearch;
51− $: search, doSearch();
60+ $: [search], doSearch();
52
61
53 $: {
62 $: {
54 const contactSearchResults: Rolodex = JSON.parse(JSON.stringify($searchedContacts));
63 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 @@
1<script lang="ts">
1<script lang="ts">
2− import ToggleButton from './buttons/ToggleButton.svelte';
2+ import ToggleButton from '../buttons/ToggleButton.svelte';
3
3
4− import { searchSettings } from '../stores/searchStores';
4+ import { searchSettings } from 'stores/searchStores';
5− import { toggleStoreKey } from '../lib/utils';
5+ import { toggleStoreKey } from 'lib/utils';
6
6
7 function toggleMoons() {
7 function toggleMoons() {
8 toggleStoreKey(searchSettings, 'includeMoons');
8 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> {
26 return scry<any>(`/peers`);
26 return scry<any>(`/peers`);
27}
27}
28
28
29−export async function searchPoints(search: string): Promise<Patp[]> {
29+export async function searchPoints(search: string, mode: ('patp' | 'sigil')): Promise<Patp[]> {
30− const { points } = await scry<any>(`/search/${search}`);
30+ const { points } = await scry<any>(`/search/${mode}/${search}`);
31 return points.map(normalizeId);
31 return points.map(normalizeId);
32}
32}
33
33
ui/src/lib/colors.ts+3
@@ -1,5 +1,8 @@
1+export const ORANGE_GOLD = '#f7d392';
1export const GOLD = '#fcd34d';
2export const GOLD = '#fcd34d';
2export const GOLDT2 = '#fddc71';
3export const GOLDT2 = '#fddc71';
4+export const GOLDS1 = '#e3be45';
5+export const GOLDS2 = '#caa93e';
3export const WHITE = '#FFF';
6export const WHITE = '#FFF';
4export const LGREY = '#BBB';
7export const LGREY = '#BBB';
5export const DGREY = '#777';
8export const DGREY = '#777';
ui/src/lib/id.ts+8−4
@@ -435,14 +435,18 @@ function cite(ship: string) {
435 return `~${patp}`;
435 return `~${patp}`;
436}
436}
437
437
438−function splitIdIntoWords(str: string): string[] {
438+export function splitIdIntoWords(str: string): string[] {
439− return str.split(/(?:--|[-_^])/);
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();
440}
444}
441
445
442function padWithWild(search: string) {
446function padWithWild(search: string) {
443 let words = search.split(/[_^]/);
447 let words = search.split(/[_^]/);
444− words[0] = '*'.repeat(6 - words[0].length) + words[0];
448+ words[0] = '*'.repeat(Math.max(0, 6 - words[0].length)) + words[0];
445− words[1] += '*'.repeat(6 - words[1].length);
449+ words[1] += '*'.repeat(Math.max(0, 6 - words[1].length));
446 return words.join(search.match(/([_^])/)[1]);
450 return words.join(search.match(/([_^])/)[1]);
447}
451}
448
452
ui/src/lib/link.ts+5−1
@@ -4,9 +4,13 @@ export function linkToShip(patp: string, queryParams = {}): string {
4 return `/ship/${patp}` + buildQuerystring(queryParams);
4 return `/ship/${patp}` + buildQuerystring(queryParams);
5}
5}
6
6
7+export function linkToSearch(search: string, queryParams = {}): string {
8+ return `/search/${search}` + buildQuerystring(queryParams);
9+}
10+
7export function linkToChart(patp: string): string {
11export function linkToChart(patp: string): string {
8 if (patp) {
12 if (patp) {
9− return `/chart/${patp}`;
13+ return `/chart/${patp}`;
10 }
14 }
11 return `/chart`;
15 return `/chart`;
12}
16}
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) {
41 [key]: !store[key],
41 [key]: !store[key],
42 }));
42 }));
43}
43}
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 @@
1<script lang="ts">
1<script lang="ts">
2 import { onMount } from 'svelte';
2 import { onMount } from 'svelte';
3 import { push } from 'svelte-spa-router';
3 import { push } from 'svelte-spa-router';
4− import docs from '../stores/docs';
4+ import docs from 'stores/docs';
5− import Doc from '../components/docs/Doc.svelte';
5+ import Doc from '@/docs/Doc.svelte';
6 export let params: any = {};
6 export let params: any = {};
7 $: path = params.path || '';
7 $: path = params.path || '';
8
8
ui/src/routes/Home.svelte+69−43
@@ -1,35 +1,43 @@
1<script lang="ts">
1<script lang="ts">
2− import Router from 'svelte-spa-router';
3− import { link, push, replace, location, querystring } from 'svelte-spa-router'
2+ import { push, replace, location, querystring } from 'svelte-spa-router'
4− import pageName from '../stores/pageName';
3+ import pageName from 'stores/pageName';
5− import { SearchAnalysis } from '../lib/id';
4+ import { SearchAnalysis } from 'lib/id';
5+ import { SigilQuery } from 'lib/sigil';
6+ import { buildQuerystring } from 'lib/utils';
6− import { linkToShip, linkToChart } from '../lib/link';
7+ import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
7− import SearchBar from "../components/SearchBar.svelte";
8+ import SearchBar from "@/search/SearchBar.svelte";
8− import SearchResults from "../components/SearchResults.svelte";
9+ import SearchResults from "@/search/SearchResults.svelte";
9− import ShipView from '../components/ShipView.svelte';
10+ import ShipView from '@/ShipView.svelte';
10− import StarChart from '../components/StarChart.svelte';
11+ import StarChart from '@/StarChart.svelte';
11− import ValidationProblems from '../components/ValidationProblems.svelte';
12+ import ValidationProblems from '@/ValidationProblems.svelte';
12− import HomeScreenTiles from '../components/HomeScreenTiles.svelte';
13+ import HomeScreenTiles from '@/HomeScreenTiles.svelte';
13 export let params: any = {};
14 export let params: any = {};
14 let analysis: SearchAnalysis = new SearchAnalysis();
15 let analysis: SearchAnalysis = new SearchAnalysis();
16+ let sigilQuery: SigilQuery = new SigilQuery('planet');
15 let navigating = false;
17 let navigating = false;
16−
18+
17− let search: string = (new URLSearchParams($querystring)).get('search') || params.arg || '';
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;
18 let urlSearch = search;
26 let urlSearch = search;
19− // let urlChanged;
27+ let urlSearchMode = searchMode;
20− let urlChangedSinceAnalysisPatp = false;
28+ let urlChangedSinceShipQuery = false;
21 $: {
29 $: {
22− const potentialUrlSearch = (new URLSearchParams($querystring)).get('search') || params.arg || '';
30+ const searchParams = new URLSearchParams($querystring);
31+ const potentialUrlSearch = searchParams.get('search') || params.arg || '';
23 if (($location + $querystring).includes(potentialUrlSearch)) {
32 if (($location + $querystring).includes(potentialUrlSearch)) {
24 urlSearch = potentialUrlSearch;
33 urlSearch = potentialUrlSearch;
25− // urlChanged = Date.now();
34+ urlSearchMode = getUrlSearchMode(searchParams);
26− urlChangedSinceAnalysisPatp = true;
35+ urlChangedSinceShipQuery = true;
27 navigating = false;
36 navigating = false;
28 }
37 }
29 }
38 }
30 $: {
39 $: {
31− // analysisPatpChanged = (analysis.patp || true) && Date.now();
40+ urlChangedSinceShipQuery = (search || true) && searchMode && false;
32− urlChangedSinceAnalysisPatp = (analysis.patp || true) && false;
33 }
41 }
34 function nav(path, addToHistory = false) {
42 function nav(path, addToHistory = false) {
35 (addToHistory ? push : replace)(path);
43 (addToHistory ? push : replace)(path);
@@ -37,24 +45,28 @@
37 function navHome(addToHistory = false) {
45 function navHome(addToHistory = false) {
38 params.mode = null;
46 params.mode = null;
39 params.arg = null;
47 params.arg = null;
48+ if (searchMode == 'patp') {
40− nav('/', addToHistory);
49+ nav('/', addToHistory);
50+ } else {
51+ nav('/' + buildQuerystring({ mode: searchMode }), addToHistory);
52+ }
41 }
53 }
42 function navToShipView(addToHistory = false) {
54 function navToShipView(addToHistory = false) {
43 params.mode = 'ship';
55 params.mode = 'ship';
44− params.arg = analysis.patp;
56+ params.arg = patp;
45− if (analysis.patp === search) {
57+ if (patp === search) {
46− nav(linkToShip(analysis.patp), addToHistory);
58+ nav(linkToShip(patp), addToHistory);
47 } else {
59 } else {
48− nav(linkToShip(analysis.patp, { search }), addToHistory);
60+ nav(linkToShip(patp, { search, mode: searchMode }), addToHistory);
49 }
61 }
50 }
62 }
51 function navToSearch(addToHistory = false) {
63 function navToSearch(addToHistory = false) {
52 if (!search) {
64 if (!search) {
53 navHome(addToHistory);
65 navHome(addToHistory);
54− } else if (analysis.queryIsValid) {
66+ } else {
55 params.mode = 'search';
67 params.mode = 'search';
56 params.arg = search;
68 params.arg = search;
57− nav(`/search/${search}`, addToHistory);
69+ nav(linkToSearch(search, { mode: searchMode }), addToHistory);
58 }
70 }
59 }
71 }
60 function navToChart(addToHistory = true) {
72 function navToChart(addToHistory = true) {
@@ -75,26 +87,27 @@
75 navHome(addToHistory);
87 navHome(addToHistory);
76 }
88 }
77 }
89 }
78− // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
79 function autoNav() {
90 function autoNav() {
80− if (search !== urlSearch) {
91+ if ((search !== urlSearch || searchMode !== urlSearchMode)) {
81− if (urlChangedSinceAnalysisPatp) {
92+ if (urlChangedSinceShipQuery) {
82 search = urlSearch;
93 search = urlSearch;
83− } else if (analysis.search === search) {
94+ searchMode = urlSearchMode;
95+ } else {
84 navigating = true;
96 navigating = true;
85 if (params.mode === 'chart') {
97 if (params.mode === 'chart') {
86 navToChart(analysis.patpIsValid);
98 navToChart(analysis.patpIsValid);
87 } else {
99 } else {
88− if (analysis.patpIsValid) {
100+ if (patp) {
89 navToShipView(true);
101 navToShipView(true);
90 } else {
102 } else {
91− navToSearch(params.mode === 'ship');
103+ const fromShipMode = params.mode === 'ship';
104+ navToSearch(fromShipMode);
92 }
105 }
93 }
106 }
94 }
107 }
95 }
108 }
96 }
109 }
97− $: urlSearch, analysis, autoNav();
110+ $: urlSearch, urlSearchMode, search, searchMode, autoNav();
98 $: {
111 $: {
99 let properMode = params.mode;
112 let properMode = params.mode;
100 if (properMode === 'chart') {
113 if (properMode === 'chart') {
@@ -136,18 +149,31 @@
136</script>
149</script>
137
150
138<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
151<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} />
152+ <SearchBar bind:analysis bind:sigilQuery bind:search bind:searchMode bind:patp {placeholderText} />
140− <!-- <p class="bg-white">navigating</p> -->
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> -->
141 {#if params.mode == "chart"}
167 {#if params.mode == "chart"}
142− <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
168+ <StarChart patp={patp || null} />
143− {:else if analysis.patpIsValid}
169+ {:else if patp}
144− <ShipView patp={analysis.patp} />
170+ <ShipView {patp} />
145− {:else if (analysis.queryIsValid)}
171+ {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode === 'sigil' && sigilQuery.isWorthSearching)}
146− <SearchResults query={analysis.query} search={analysis.search} />
172+ <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery}
173+ patpSearch={analysis.search} {searchMode} />
147− {:else if (analysis.search.length > 0)}
174+ {:else if (analysis.search.length > 0 && analysis.queryProblems.length > 0)}
148 <ValidationProblems problems={analysis.queryProblems} />
175 <ValidationProblems problems={analysis.queryProblems} />
149 {:else}
176 {:else}
150 <HomeScreenTiles />
177 <HomeScreenTiles />
151 {/if}
178 {/if}
152− <!-- {/if} -->
153</div>
179</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 @@
1import { writable } from 'svelte/store';
1import { writable } from 'svelte/store';
2
2
3−import { getDoc } from '../lib/api';
3+import { getDoc } from 'lib/api';
4const docs = {};
4const docs = {};
5−// import type { AppState, Contacts } from '../types/store';
6
5
7const initStore: any = {
6const initStore: any = {
8 show: false,
7 show: false,
ui/src/stores/searchStores.ts+34−10
@@ -2,21 +2,31 @@ import { writable, derived } from 'svelte/store';
2
2
3import type { Contact, Rolodex } from '@urbit/api';
3import type { Contact, Rolodex } from '@urbit/api';
4import store from './store';
4import store from './store';
5−import type { StoreState, SearchedContactsState } from '../types/store';
5+import type { StoreState, SearchedContactsState } from 'types/store';
6−import { SearchSettings } from '../types/store';
6+import { SearchSettings } from 'types/store';
7+import { splitIdIntoSyls } from 'lib/id';
7−import { filterObject } from '../lib/utils';
8+import { filterObject } from 'lib/utils';
8
9
9const searchSettings = writable(new SearchSettings());
10const searchSettings = writable(new SearchSettings());
10
11
11const searchedContacts = derived([store, searchSettings], deriveSearchedContacts);
12const searchedContacts = derived([store, searchSettings], deriveSearchedContacts);
12−searchedContacts.search = store.search;
13+searchedContacts.searchPatp = store.searchPatp;
14+searchedContacts.searchSigil = store.searchSigil;
13
15
14function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsState {
16function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsState {
17+ let idMatches;
18+ if ($store.searchMode === 'patp') {
15− if (!$store.query) {
19+ if (!$store.patpQuery) {
20+ return {};
21+ }
22+ idMatches = idMatchesRegex;
23+ } else {
24+ if (!$store.sigilQuery) {
16− return {};
25+ return {};
26+ }
27+ idMatches = idMatchesSyls;
17 }
28 }
18− function idMatchesSearch(id) {
29+ function isIdAllowed(id) {
19− if (!$store.query.test(id)) return false;
20 if (!$searchSettings.includeMoons && id.length > 14 && id.length <= 28) {
30 if (!$searchSettings.includeMoons && id.length > 14 && id.length <= 28) {
21 return false;
31 return false;
22 }
32 }
@@ -25,9 +35,23 @@ function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsStat
25 }
35 }
26 return true;
36 return true;
27 }
37 }
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+ }
28− const filteredContacts = filterObject($store.contacts, idMatchesSearch) as Rolodex;
52+ const filteredContacts = filterObject($store.contacts, idMatches) as Rolodex;
29 $store.peers.forEach((id) => {
53 $store.peers.forEach((id) => {
30− if (idMatchesSearch(id) && !filteredContacts[id]) {
54+ if (idMatches(id) && !filteredContacts[id]) {
31 filteredContacts[id] = {
55 filteredContacts[id] = {
32 nickname: null, bio: null, status: null, color: null, avatar: null,
56 nickname: null, bio: null, status: null, color: null, avatar: null,
33 cover: null, groups: [], 'last-updated': 0,
57 cover: null, groups: [], 'last-updated': 0,
ui/src/stores/store.ts+17−9
@@ -1,15 +1,17 @@
1import { writable } from 'svelte/store';
1import { writable } from 'svelte/store';
2
2
3import type { Rolodex } from '@urbit/api';
3import type { Rolodex } from '@urbit/api';
4−import { subscribeToContacts, getPeers } from '../lib/api';
4+import { subscribeToContacts, getPeers } from 'lib/api';
5−import type { StoreState } from '../types/store';
5+import type { StoreState } from 'types/store';
6−import { normalizeId } from '../lib/id';
6+import { normalizeId } from 'lib/id';
7−import { setStoreKey } from '../lib/utils';
7+import { setStoreKey } from 'lib/utils';
8
8
9const initStore: StoreState = {
9const initStore: StoreState = {
10 contacts: {},
10 contacts: {},
11− peers: {},
11+ peers: [],
12− query: null,
12+ patpQuery: null,
13+ sigilQuery: null,
14+ searchMode: 'patp',
13 connection: 'disconnected',
15 connection: 'disconnected',
14 ship: window.ship,
16 ship: window.ship,
15};
17};
@@ -17,8 +19,14 @@ const initStore: StoreState = {
17const store = writable(initStore);
19const store = writable(initStore);
18const { subscribe, update, set } = store;
20const { subscribe, update, set } = store;
19
21
20−function search(idQuery: RegExp) {
22+function searchPatp(patpQuery: RegExp) {
21− setStoreKey(store, 'query', idQuery);
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');
22}
30}
23
31
24function reset() {
32function reset() {
@@ -40,4 +48,4 @@ getPeers().then(({ points }) => {
40 setStoreKey(store, 'peers', points.map(normalizeId));
48 setStoreKey(store, 'peers', points.map(normalizeId));
41});
49});
42
50
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.
532.-bottom-3 {
532.-bottom-3 {
533 bottom: -0.75rem;
533 bottom: -0.75rem;
534}
534}
535−.z-10 {
535+.top-1 {
536− z-index: 10;
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;
537}
558}
538.z-20 {
559.z-20 {
539 z-index: 20;
560 z-index: 20;
540}
561}
562+.z-10 {
563+ z-index: 10;
564+}
541.z-50 {
565.z-50 {
542 z-index: 50;
566 z-index: 50;
543}
567}
568+.col-span-3 {
569+ grid-column: span 3 / span 3;
570+}
571+.col-span-2 {
572+ grid-column: span 2 / span 2;
573+}
544.m-4 {
574.m-4 {
545 margin: 1rem;
575 margin: 1rem;
546}
576}
577+.m-2 {
578+ margin: 0.5rem;
579+}
547.m-8 {
580.m-8 {
548 margin: 2rem;
581 margin: 2rem;
549}
582}
550.-m-2 {
583.-m-2 {
551 margin: -0.5rem;
584 margin: -0.5rem;
552}
585}
553−.mx-4 {
586+.m-auto {
554− margin-left: 1rem;
587+ margin: auto;
588+}
589+.m-1 {
555− margin-right: 1rem;
590+ margin: 0.25rem;
556}
591}
557.mx-auto {
592.mx-auto {
558 margin-left: auto;
593 margin-left: auto;
559 margin-right: auto;
594 margin-right: auto;
560}
595}
561−.mx-8 {
562− margin-left: 2rem;
563− margin-right: 2rem;
564−}
565.my-1\.5 {
596.my-1\.5 {
566 margin-top: 0.375rem;
597 margin-top: 0.375rem;
567 margin-bottom: 0.375rem;
598 margin-bottom: 0.375rem;
@@ -574,6 +605,14 @@ Ensure the default browser behavior of the `hidden` attribute.
574 margin-top: 0.75rem;
605 margin-top: 0.75rem;
575 margin-bottom: 0.75rem;
606 margin-bottom: 0.75rem;
576}
607}
608+.mx-4 {
609+ margin-left: 1rem;
610+ margin-right: 1rem;
611+}
612+.mx-8 {
613+ margin-left: 2rem;
614+ margin-right: 2rem;
615+}
577.my-4 {
616.my-4 {
578 margin-top: 1rem;
617 margin-top: 1rem;
579 margin-bottom: 1rem;
618 margin-bottom: 1rem;
@@ -603,9 +642,6 @@ Ensure the default browser behavior of the `hidden` attribute.
603.ml-1 {
642.ml-1 {
604 margin-left: 0.25rem;
643 margin-left: 0.25rem;
605}
644}
606−.-mt-2 {
607− margin-top: -0.5rem;
608−}
609.mr-4 {
645.mr-4 {
610 margin-right: 1rem;
646 margin-right: 1rem;
611}
647}
@@ -621,6 +657,9 @@ Ensure the default browser behavior of the `hidden` attribute.
621.ml-8 {
657.ml-8 {
622 margin-left: 2rem;
658 margin-left: 2rem;
623}
659}
660+.-mt-2 {
661+ margin-top: -0.5rem;
662+}
624.-mt-1 {
663.-mt-1 {
625 margin-top: -0.25rem;
664 margin-top: -0.25rem;
626}
665}
@@ -654,6 +693,15 @@ Ensure the default browser behavior of the `hidden` attribute.
654.mr-3 {
693.mr-3 {
655 margin-right: 0.75rem;
694 margin-right: 0.75rem;
656}
695}
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+}
657.-mt-0\.5 {
705.-mt-0\.5 {
658 margin-top: -0.125rem;
706 margin-top: -0.125rem;
659}
707}
@@ -681,21 +729,15 @@ Ensure the default browser behavior of the `hidden` attribute.
681.hidden {
729.hidden {
682 display: none;
730 display: none;
683}
731}
732+.aspect-square {
733+ aspect-ratio: 1 / 1;
734+}
684.h-full {
735.h-full {
685 height: 100%;
736 height: 100%;
686}
737}
687.h-5 {
738.h-5 {
688 height: 1.25rem;
739 height: 1.25rem;
689}
740}
690−.h-10 {
691− height: 2.5rem;
692−}
693−.h-6 {
694− height: 1.5rem;
695−}
696−.h-8 {
697− height: 2rem;
698−}
699.h-12 {
741.h-12 {
700 height: 3rem;
742 height: 3rem;
701}
743}
@@ -705,6 +747,15 @@ Ensure the default browser behavior of the `hidden` attribute.
705.h-\[100vh\] {
747.h-\[100vh\] {
706 height: 100vh;
748 height: 100vh;
707}
749}
750+.h-10 {
751+ height: 2.5rem;
752+}
753+.h-6 {
754+ height: 1.5rem;
755+}
756+.h-8 {
757+ height: 2rem;
758+}
708.h-2 {
759.h-2 {
709 height: 0.5rem;
760 height: 0.5rem;
710}
761}
@@ -720,6 +771,24 @@ Ensure the default browser behavior of the `hidden` attribute.
720.h-24 {
771.h-24 {
721 height: 6rem;
772 height: 6rem;
722}
773}
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+}
723.min-h-\[20px\] {
792.min-h-\[20px\] {
724 min-height: 20px;
793 min-height: 20px;
725}
794}
@@ -729,24 +798,30 @@ Ensure the default browser behavior of the `hidden` attribute.
729.min-h-0 {
798.min-h-0 {
730 min-height: 0px;
799 min-height: 0px;
731}
800}
801+.min-h-\[14rem\] {
802+ min-height: 14rem;
803+}
732.w-full {
804.w-full {
733 width: 100%;
805 width: 100%;
734}
806}
735.w-5 {
807.w-5 {
736 width: 1.25rem;
808 width: 1.25rem;
737}
809}
738−.w-10 {
739− width: 2.5rem;
740−}
741.w-12 {
810.w-12 {
742 width: 3rem;
811 width: 3rem;
743}
812}
744.w-3 {
813.w-3 {
745 width: 0.75rem;
814 width: 0.75rem;
746}
815}
816+.w-\[128px\] {
817+ width: 128px;
818+}
747.w-\[100vw\] {
819.w-\[100vw\] {
748 width: 100vw;
820 width: 100vw;
749}
821}
822+.w-10 {
823+ width: 2.5rem;
824+}
750.w-7 {
825.w-7 {
751 width: 1.75rem;
826 width: 1.75rem;
752}
827}
@@ -759,6 +834,33 @@ Ensure the default browser behavior of the `hidden` attribute.
759.w-6 {
834.w-6 {
760 width: 1.5rem;
835 width: 1.5rem;
761}
836}
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+}
762.min-w-0 {
864.min-w-0 {
763 min-width: 0px;
865 min-width: 0px;
764}
866}
@@ -780,6 +882,15 @@ Ensure the default browser behavior of the `hidden` attribute.
780.max-w-2xl {
882.max-w-2xl {
781 max-width: 42rem;
883 max-width: 42rem;
782}
884}
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+}
783.flex-shrink-0 {
894.flex-shrink-0 {
784 flex-shrink: 0;
895 flex-shrink: 0;
785}
896}
@@ -795,9 +906,54 @@ Ensure the default browser behavior of the `hidden` attribute.
795.grow {
906.grow {
796 flex-grow: 1;
907 flex-grow: 1;
797}
908}
909+.grow-\[2\] {
910+ flex-grow: 2;
911+}
798.border-collapse {
912.border-collapse {
799 border-collapse: collapse;
913 border-collapse: collapse;
800}
914}
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+}
801.rotate-45 {
957.rotate-45 {
802 --tw-rotate: 45deg;
958 --tw-rotate: 45deg;
803 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));
959 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.
806 --tw-rotate: -45deg;
962 --tw-rotate: -45deg;
807 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));
963 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));
808}
964}
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+}
809.transform {
975.transform {
810 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));
976 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));
811}
977}
@@ -831,21 +997,71 @@ Ensure the default browser behavior of the `hidden` attribute.
831.resize {
997.resize {
832 resize: both;
998 resize: both;
833}
999}
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+}
834.grid-cols-3 {
1020.grid-cols-3 {
835 grid-template-columns: repeat(3, minmax(0, 1fr));
1021 grid-template-columns: repeat(3, minmax(0, 1fr));
836}
1022}
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+}
837.flex-col {
1041.flex-col {
838 flex-direction: column;
1042 flex-direction: column;
839}
1043}
840.flex-wrap {
1044.flex-wrap {
841 flex-wrap: wrap;
1045 flex-wrap: wrap;
842}
1046}
1047+.place-content-center {
1048+ place-content: center;
1049+}
1050+.place-items-stretch {
1051+ place-items: stretch;
1052+}
843.content-center {
1053.content-center {
844 align-content: center;
1054 align-content: center;
845}
1055}
1056+.items-start {
1057+ align-items: flex-start;
1058+}
846.items-center {
1059.items-center {
847 align-items: center;
1060 align-items: center;
848}
1061}
1062+.justify-end {
1063+ justify-content: flex-end;
1064+}
849.justify-center {
1065.justify-center {
850 justify-content: center;
1066 justify-content: center;
851}
1067}
@@ -867,14 +1083,14 @@ Ensure the default browser behavior of the `hidden` attribute.
867.gap-2 {
1083.gap-2 {
868 gap: 0.5rem;
1084 gap: 0.5rem;
869}
1085}
870−.gap-x-2 {
871− -moz-column-gap: 0.5rem;
872− column-gap: 0.5rem;
873−}
874.gap-x-1 {
1086.gap-x-1 {
875 -moz-column-gap: 0.25rem;
1087 -moz-column-gap: 0.25rem;
876 column-gap: 0.25rem;
1088 column-gap: 0.25rem;
877}
1089}
1090+.gap-x-2 {
1091+ -moz-column-gap: 0.5rem;
1092+ column-gap: 0.5rem;
1093+}
878.gap-x-3 {
1094.gap-x-3 {
879 -moz-column-gap: 0.75rem;
1095 -moz-column-gap: 0.75rem;
880 column-gap: 0.75rem;
1096 column-gap: 0.75rem;
@@ -922,6 +1138,15 @@ Ensure the default browser behavior of the `hidden` attribute.
922 --tw-divide-opacity: 1;
1138 --tw-divide-opacity: 1;
923 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
1139 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
924}
1140}
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+}
925.justify-self-start {
1150.justify-self-start {
926 justify-self: start;
1151 justify-self: start;
927}
1152}
@@ -964,6 +1189,26 @@ Ensure the default browser behavior of the `hidden` attribute.
964.rounded-sm {
1189.rounded-sm {
965 border-radius: 0.125rem;
1190 border-radius: 0.125rem;
966}
1191}
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+}
967.border {
1212.border {
968 border-width: 1px;
1213 border-width: 1px;
969}
1214}
@@ -973,6 +1218,15 @@ Ensure the default browser behavior of the `hidden` attribute.
973.border-8 {
1218.border-8 {
974 border-width: 8px;
1219 border-width: 8px;
975}
1220}
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+}
976.border-l-8 {
1230.border-l-8 {
977 border-left-width: 8px;
1231 border-left-width: 8px;
978}
1232}
@@ -992,14 +1246,14 @@ Ensure the default browser behavior of the `hidden` attribute.
992 --tw-border-opacity: 1;
1246 --tw-border-opacity: 1;
993 border-color: rgb(252 211 77 / var(--tw-border-opacity));
1247 border-color: rgb(252 211 77 / var(--tw-border-opacity));
994}
1248}
995−.border-navy {
996− --tw-border-opacity: 1;
997− border-color: rgb(31 36 60 / var(--tw-border-opacity));
998−}
999.border-gray-300 {
1249.border-gray-300 {
1000 --tw-border-opacity: 1;
1250 --tw-border-opacity: 1;
1001 border-color: rgb(209 213 219 / var(--tw-border-opacity));
1251 border-color: rgb(209 213 219 / var(--tw-border-opacity));
1002}
1252}
1253+.border-navy {
1254+ --tw-border-opacity: 1;
1255+ border-color: rgb(31 36 60 / var(--tw-border-opacity));
1256+}
1003.border-white {
1257.border-white {
1004 --tw-border-opacity: 1;
1258 --tw-border-opacity: 1;
1005 border-color: rgb(255 255 255 / var(--tw-border-opacity));
1259 border-color: rgb(255 255 255 / var(--tw-border-opacity));
@@ -1012,6 +1266,30 @@ Ensure the default browser behavior of the `hidden` attribute.
1012 --tw-border-opacity: 1;
1266 --tw-border-opacity: 1;
1013 border-color: rgb(156 163 175 / var(--tw-border-opacity));
1267 border-color: rgb(156 163 175 / var(--tw-border-opacity));
1014}
1268}
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+}
1015.bg-navy {
1293.bg-navy {
1016 --tw-bg-opacity: 1;
1294 --tw-bg-opacity: 1;
1017 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1295 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
@@ -1024,10 +1302,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1024 --tw-bg-opacity: 1;
1302 --tw-bg-opacity: 1;
1025 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1303 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1026}
1304}
1027−.bg-black {
1028− --tw-bg-opacity: 1;
1029− background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1030−}
1031.bg-l1-blue {
1305.bg-l1-blue {
1032 --tw-bg-opacity: 1;
1306 --tw-bg-opacity: 1;
1033 background-color: rgb(23 146 199 / var(--tw-bg-opacity));
1307 background-color: rgb(23 146 199 / var(--tw-bg-opacity));
@@ -1048,6 +1322,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1048 --tw-bg-opacity: 1;
1322 --tw-bg-opacity: 1;
1049 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1323 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1050}
1324}
1325+.bg-black {
1326+ --tw-bg-opacity: 1;
1327+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1328+}
1051.bg-amber-300 {
1329.bg-amber-300 {
1052 --tw-bg-opacity: 1;
1330 --tw-bg-opacity: 1;
1053 background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1331 background-color: rgb(252 211 77 / var(--tw-bg-opacity));
@@ -1116,6 +1394,29 @@ Ensure the default browser behavior of the `hidden` attribute.
1116 --tw-bg-opacity: 1;
1394 --tw-bg-opacity: 1;
1117 background-color: rgb(227 190 69 / var(--tw-bg-opacity));
1395 background-color: rgb(227 190 69 / var(--tw-bg-opacity));
1118}
1396}
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+}
1119.p-5 {
1420.p-5 {
1120 padding: 1.25rem;
1421 padding: 1.25rem;
1121}
1422}
@@ -1137,6 +1438,15 @@ Ensure the default browser behavior of the `hidden` attribute.
1137.p-1 {
1438.p-1 {
1138 padding: 0.25rem;
1439 padding: 0.25rem;
1139}
1440}
1441+.p-0 {
1442+ padding: 0px;
1443+}
1444+.p-\[3px\] {
1445+ padding: 3px;
1446+}
1447+.p-\[4px\] {
1448+ padding: 4px;
1449+}
1140.px-2 {
1450.px-2 {
1141 padding-left: 0.5rem;
1451 padding-left: 0.5rem;
1142 padding-right: 0.5rem;
1452 padding-right: 0.5rem;
@@ -1153,6 +1463,18 @@ Ensure the default browser behavior of the `hidden` attribute.
1153 padding-top: 0px;
1463 padding-top: 0px;
1154 padding-bottom: 0px;
1464 padding-bottom: 0px;
1155}
1465}
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+}
1156.py-4 {
1478.py-4 {
1157 padding-top: 1rem;
1479 padding-top: 1rem;
1158 padding-bottom: 1rem;
1480 padding-bottom: 1rem;
@@ -1165,10 +1487,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1165 padding-left: 1.5rem;
1487 padding-left: 1.5rem;
1166 padding-right: 1.5rem;
1488 padding-right: 1.5rem;
1167}
1489}
1168−.px-4 {
1169− padding-left: 1rem;
1170− padding-right: 1rem;
1171−}
1172.py-8 {
1490.py-8 {
1173 padding-top: 2rem;
1491 padding-top: 2rem;
1174 padding-bottom: 2rem;
1492 padding-bottom: 2rem;
@@ -1177,14 +1495,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1177 padding-left: 2rem;
1495 padding-left: 2rem;
1178 padding-right: 2rem;
1496 padding-right: 2rem;
1179}
1497}
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−}
1188.px-3 {
1498.px-3 {
1189 padding-left: 0.75rem;
1499 padding-left: 0.75rem;
1190 padding-right: 0.75rem;
1500 padding-right: 0.75rem;
@@ -1272,6 +1582,9 @@ Ensure the default browser behavior of the `hidden` attribute.
1272.font-bold {
1582.font-bold {
1273 font-weight: 700;
1583 font-weight: 700;
1274}
1584}
1585+.capitalize {
1586+ text-transform: capitalize;
1587+}
1275.leading-4 {
1588.leading-4 {
1276 line-height: 1rem;
1589 line-height: 1rem;
1277}
1590}
@@ -1336,21 +1649,46 @@ Ensure the default browser behavior of the `hidden` attribute.
1336.underline-offset-2 {
1649.underline-offset-2 {
1337 text-underline-offset: 2px;
1650 text-underline-offset: 2px;
1338}
1651}
1339−.opacity-50 {
1340− opacity: 0.5;
1341−}
1342.opacity-25 {
1652.opacity-25 {
1343 opacity: 0.25;
1653 opacity: 0.25;
1344}
1654}
1345.opacity-75 {
1655.opacity-75 {
1346 opacity: 0.75;
1656 opacity: 0.75;
1347}
1657}
1658+.opacity-50 {
1659+ opacity: 0.5;
1660+}
1348.opacity-10 {
1661.opacity-10 {
1349 opacity: 0.1;
1662 opacity: 0.1;
1350}
1663}
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+}
1351.outline {
1676.outline {
1352 outline-style: solid;
1677 outline-style: solid;
1353}
1678}
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+}
1354.filter {
1692.filter {
1355 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);
1693 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);
1356}
1694}
@@ -1359,10 +1697,30 @@ Ensure the default browser behavior of the `hidden` attribute.
1359 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1697 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1360 transition-duration: 150ms;
1698 transition-duration: 150ms;
1361}
1699}
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+}
1362.hover\:bg-gray-200:hover {
1717.hover\:bg-gray-200:hover {
1363 --tw-bg-opacity: 1;
1718 --tw-bg-opacity: 1;
1364 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1719 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1365}
1720}
1721+.hover\:opacity-80:hover {
1722+ opacity: 0.8;
1723+}
1366.hover\:opacity-40:hover {
1724.hover\:opacity-40:hover {
1367 opacity: 0.4;
1725 opacity: 0.4;
1368}
1726}
@@ -1400,6 +1758,60 @@ Ensure the default browser behavior of the `hidden` attribute.
1400 padding-bottom: 2rem;
1758 padding-bottom: 2rem;
1401 }
1759 }
1402}
1760}
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+}
1403@media (min-width: 768px) {
1815@media (min-width: 768px) {
1404
1816
1405 .md\:mx-auto {
1817 .md\:mx-auto {
@@ -1411,14 +1823,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1411 min-width: 256px;
1823 min-width: 256px;
1412 }
1824 }
1413
1825
1414− .md\:max-w-xl {
1415− max-width: 36rem;
1416− }
1417−
1418 .md\:max-w-2xl {
1826 .md\:max-w-2xl {
1419 max-width: 42rem;
1827 max-width: 42rem;
1420 }
1828 }
1421
1829
1830+ .md\:max-w-xl {
1831+ max-width: 36rem;
1832+ }
1833+
1422 .md\:flex-row {
1834 .md\:flex-row {
1423 flex-direction: row;
1835 flex-direction: row;
1424 }
1836 }
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 {
14 // };
14 // };
15 contacts: Rolodex;
15 contacts: Rolodex;
16 peers: Patp[];
16 peers: Patp[];
17− query: RegExp;
17+ patpQuery: RegExp;
18+ sigilQuery: string[][];
19+ searchMode: ('patp' | 'sigil');
18 // contacts: Contacts;
20 // contacts: Contacts;
19 // contactSearchResults: Contacts;
21 // contactSearchResults: Contacts;
20 // chats: [];
22 // chats: [];
@@ -27,4 +29,6 @@ export type SearchedContactsState = Rolodex;
27export class SearchSettings {
29export class SearchSettings {
28 includeMoons: boolean = true;
30 includeMoons: boolean = true;
29 includeComets: boolean = false;
31 includeComets: boolean = false;
32+
33+ allowFictionalSigils: boolean = false;
30}
34}
ui/svelte.config.js+2−2
@@ -1,4 +1,4 @@
1−import sveltePreprocess from 'svelte-preprocess'
1+import sveltePreprocess from 'svelte-preprocess';
2
2
3export default {
3export default {
4 // Consult https://github.com/sveltejs/svelte-preprocess
4 // Consult https://github.com/sveltejs/svelte-preprocess
@@ -10,4 +10,4 @@ export default {
10
10
11 handler(warning)
11 handler(warning)
12 },
12 },
13−}
13+};
ui/tsconfig.json+9
@@ -7,6 +7,15 @@
7 "module": "esnext",
7 "module": "esnext",
8 "resolveJsonModule": true,
8 "resolveJsonModule": true,
9 "baseUrl": ".",
9 "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+ },
10 /**
19 /**
11 * Typecheck JS in `.svelte` and `.js` files by default.
20 * Typecheck JS in `.svelte` and `.js` files by default.
12 * Disable checkJs if you'd like to use dynamic types in JS.
21 * Disable checkJs if you'd like to use dynamic types in JS.
ui/vite.config.ts+21−1
@@ -1,6 +1,7 @@
1import { loadEnv, defineConfig } from 'vite';
1import { loadEnv, defineConfig } from 'vite';
2import { svelte } from '@sveltejs/vite-plugin-svelte'
2import { svelte } from '@sveltejs/vite-plugin-svelte'
3import { urbitPlugin } from '@urbit/vite-plugin-urbit';
3import { urbitPlugin } from '@urbit/vite-plugin-urbit';
4+import path from 'path';
4
5
5// https://vitejs.dev/config/
6// https://vitejs.dev/config/
6export default ({ mode }) => {
7export default ({ mode }) => {
@@ -9,6 +10,25 @@ export default ({ mode }) => {
9 console.log(SHIP_URL);
10 console.log(SHIP_URL);
10
11
11 return defineConfig({
12 return defineConfig({
13+ plugins: [
12− plugins: [urbitPlugin({ base: 'astrolabe', target: SHIP_URL, secure: false }), svelte()]
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+ },
13 });
33 });
14};
34};