on main help sign in
improving search and search ui
23a05e0John Hyde
desk/app/astrolabe.hoon+32−21
@@ -1,5 +1,5 @@
1/- *astrolabe
1/- *astrolabe
2−/+ *astrolabe, naive, default-agent, dbug, agentio
2+/+ *astrolabe, *search, naive, default-agent, dbug, agentio
3/$ udon-to-docu %udon %docu
3/$ udon-to-docu %udon %docu
4|%
4|%
5+$ versioned-state
5+$ versioned-state
@@ -57,9 +57,10 @@
57 [%x %point @ %spawned ~]
57 [%x %point @ %spawned ~]
58 =/ =ship `@p`(slav %p &3.path)
58 =/ =ship `@p`(slav %p &3.path)
59 ``astrolabe-point-set+!>((get-spawned-points:hc ship))
59 ``astrolabe-point-set+!>((get-spawned-points:hc ship))
60− [%x %search @ ~]
60+ [%x %search @ ?(~ [%narrow ~])]
61 =/ =search-text (trip &3.path)
61 =/ =search-text (trip &3.path)
62+ =/ expand-search =(t.t.t.path ~)
62− ``astrolabe-point-set+!>((search-opoints:hc search-text))
63+ ``astrolabe-point-set+!>((search-opoints:hc search-text expand-search))
63 [%x %doc ^]
64 [%x %doc ^]
64 =/ doc-path=^path t.t.path
65 =/ doc-path=^path t.t.path
65 =/ doc (read-doc:hc doc-path)
66 =/ doc (read-doc:hc doc-path)
@@ -209,12 +210,11 @@
209::
210::
210++ run-wildcard-search
211++ run-wildcard-search
211 |* [agg=mold search=search-full points=opoints =page-info]
212 |* [agg=mold search=search-full points=opoints =page-info]
212− |= f=$-([agg @q] agg)
213+ |= [res=[count=@ =agg] f=$-([agg @q] agg)]
213 ^- [count=@ =agg]
214 ^- [count=@ =agg]
214 =/ s-syls search-syls.search
215 =/ s-syls search-syls.search
215− ~& s-syls
216+ :: ~& s-syls
216 =/ s-opts search-opts.search
217 =/ s-opts search-opts.search
217− =| res=[count=@ =agg]
218 =+ [llb=.~zod rrb=`@q`(sub (bex 32) 1)]
218 =+ [llb=.~zod rrb=`@q`(sub (bex 32) 1)]
219 :: =< +
219 :: =< +
220 |-
220 |-
@@ -242,24 +242,35 @@
242 $(points r.points, llb rlb, s-opts opts.right-view)
242 $(points r.points, llb rlb, s-opts opts.right-view)
243 res
243 res
244::
244::
245+++ run-mr-search
246+ |= [search=mr-search res=[count=@ agg=opoints]]
247+ ^- _res
248+ =/ points ?: reversed.search ropoints fopoints
249+ =. res
250+ %+ (run-wildcard-search _agg.res search-full.search points [0 0])
251+ res
252+ |= [agg=_agg.res result=@q]
253+ =? result reversed.search
254+ (switch-words result)
255+ =. result (to-p result)
256+ (put:((on @ @) gte) agg result ~)
257+ ~& "returning {<~(wyt by agg.res)>} of {<count.res>} results"
258+ res
245++ search-opoints
259++ search-opoints
246− |= =search-text
260+ |= [=search-text expand-search=?]
247 ^- (list ship)
261 ^- (list ship)
262+ =/ search-fulls
263+ ?: expand-search
264+ (search-text-to-fulls search-text)
248− =/ =search-full (search-text-to-search-full search-text)
265+ ~[(search-text-to-full search-text)]
249− =^ spoints search-full
266+ ~& (turn search-fulls |=(=search-full search-syls.search-full))
250− (maybe-reverse-search search-full fopoints ropoints)
267+ =/ mr-searches (maybe-reverse-searches search-fulls 2.820)
268+ ~& (turn mr-searches |=(=mr-search pain.mr-search))
251− ?: (gth pain.spoints 2.820) ~|(%search-predicted-too-broad !!)
269+ ?~ mr-searches ~|(%search-predicted-too-broad !!)
252 =/ results
270 =/ results
253− %-
271+ %+ roll `(list mr-search)`mr-searches
254− (run-wildcard-search (list @q) search-full points.spoints [0 0])
272+ run-mr-search
255− |= [agg=(list @q) result=@q]
256− [result agg]
257− ~& "returning {<(lent agg.results)>} of {<count.results>} results"
258− =/ ships agg.results
273+ (turn ~(tap in agg.results) head)
259− =? ships reversed.spoints
260− (turn ships switch-words)
261− %- flop
262− (turn ships to-p)
263::
274::
264++ sponsor-chain
275++ sponsor-chain
265 |= [=ship =unpoint]
276 |= [=ship =unpoint]
desk/desk.docket-0+1−1
@@ -4,7 +4,7 @@
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+[~zod 0v0]
5 glob-ames+[~zod 0v0]
6 base+'astrolabe'
6 base+'astrolabe'
7− version+[0 0 4]
7+ version+[0 1 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/astrolabe.udon+5−2
@@ -1,8 +1,11 @@
1;>
1;>
2# Astrolabe
2# Astrolabe
3
3
4−Currently Astrolabe lets you type in `@p`s or Azimuth point numbers
4+Currently, Astrolabe lets you type in `@p`s or Azimuth point numbers
5+to look up ships on the network. You can search your contacts and
6+spawned ships with partial or wildcard searches, e.g. `sampel-***net`.
7+
5−to look up ships on the network. There's also Help Documentation if
8+There's also Help Documentation if
6you want to learn more about how Urbit IDs work.
9you want to learn more about how Urbit IDs work.
7
10
8## Info
11## Info
desk/lib/astrolabe.hoon+2−199
@@ -2,13 +2,6 @@
2=< [sur .]
2=< [sur .]
3=, sur
3=, sur
4|%
4|%
5−++ search-text-chars "*abcdefghilmnoprstuvwxyz"
6−++ sanitize-search-text
7− |= =search-text
8− ^- tape
9− %+ skim search-text
10− |= a=@t
11− !=(~ (find [a]~ search-text-chars))
12++ to-q fein:ob
5++ to-q fein:ob
13++ to-p fynd:ob
6++ to-p fynd:ob
14++ rev-q
7++ rev-q
@@ -31,19 +24,8 @@
31 ^- ?
24 ^- ?
32 (rev-f lte a b)
25 (rev-f lte a b)
33::
26::
34−+$ opoints (tree [ship @]) :: ordered points
27++$ opoints (tree [@ @]) :: ordered points [ship ~]
35−++ ors ((on ship @) rpor)
28+++ ors ((on @q @) rpor)
36−+$ pfsf ?(%prefix %suffix)
37−+$ syl [=tape =pfsf]
38−+$ search-text tape
39−+$ search-syls (list syl) :: in reverse order, e.g. ["net" "pal" "pel" "sam" ~]. length 1-4
40−+$ search-syl-opts (list @q)
41−+$ search-opts (list search-syl-opts) :: must have length 4
42−+$ search-full
43− $:
44− =search-syls
45− =search-opts
46− ==
47+$ page-info [size=_100 page=_0]
29+$ page-info [size=_100 page=_0]
48++ fits-on-page
30++ fits-on-page
49 |= [i=@ =page-info]
31 |= [i=@ =page-info]
@@ -53,185 +35,6 @@
53 =/ page-start (mul size page)
35 =/ page-start (mul size page)
54 =/ page-end (add page-start size)
36 =/ page-end (add page-start size)
55 &((gte i page-start) (lth i page-end))
37 &((gte i page-start) (lth i page-end))
56−++ search-syl-to-search-syl-opts
57− |= search=syl
58− ^- search-syl-opts
59− =/ syls=(list @t)
60− %+ rip [0 24] :: jet doesn't support rip [3 3] which is equivalent
61− ?: =(%prefix pfsf.search) sis:po dex:po
62− ~! syls
63− ~! search
64− %+ murn syls
65− |= cand=@t
66− ?. (tape-matches tape.search (trip cand)) ~
67− %- ?: =(%prefix pfsf.search) ins:po ind:po
68− cand
69−++ search-text-to-search-syls
70− |= search-text=tape
71− ^- search-syls
72− =. search-text (sanitize-search-text search-text)
73− =/ syl-tapes
74− %+ turn
75− %- flop
76− (rip [0 24] (crip search-text)) :: jet doesn't support rip [3 3] which is equivalent
77− trip
78− =| count=@
79− |-
80− ^- search-syls
81− =/ =pfsf
82− ?: =(0 (mod count 2)) %suffix %prefix
83− ?~ syl-tapes ~
84− :- [i.syl-tapes pfsf]
85− $(syl-tapes t.syl-tapes, count +(count))
86−
87−++ search-syls-to-search-opts
88− |= =search-syls
89− ^- search-opts
90− =/ opts
91− %+ turn
92− search-syls
93− |= =syl
94− (search-syl-to-search-syl-opts syl)
95− |-
96− ?: (gte (lent opts) 4)
97− opts
98− $(opts (snoc (search-opts opts) (search-syl-opts ~[.~zod])))
99−++ search-text-to-search-full
100− |= search-text=tape
101− ^- search-full
102− =/ =search-syls (search-text-to-search-syls search-text)
103− :- search-syls
104− (search-syls-to-search-opts search-syls)
105−::
106−++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
107− |* lst=(list)
108− ^- _lst
109− %+ weld
110− (slag 2 lst)
111− (scag 2 lst)
112−++ pad-search-syls
113− |= =search-syls
114− ^- ^search-syls
115− =/ padding (search-text-to-search-syls "dozzoddozzod")
116− %+ weld
117− search-syls
118− (slag (lent search-syls) padding)
119−++ reverse-search
120− |= search=search-full
121− ^- search-full
122− :- (reverse-list (pad-search-syls search-syls.search))
123− (reverse-list search-opts.search)
124−++ estimate-search-pain
125− |= search=search-full
126− =/ opts search-opts.search
127− =| acc=@
128− |-
129− ?~ opts acc
130− =/ new-acc
131− (add (mul acc 2) (lent i.opts))
132− $(acc new-acc, opts t.opts)
133−:: ++ estimate-search-pain
134−:: |= search=search-full
135−:: =/ max-pain (bex 32)
136−:: =/ opts search-opts.search
137−:: =| acc=@
138−:: |-
139−:: ?~ opts acc
140−:: =/ level-pain (div (mul max-pain (lent i.opts)) 256)
141−:: =/ new-acc
142−:: (add (mul acc 2) (lent i.opts))
143−:: $(acc new-acc, opts t.opts)
144−++ estimate-search-text-pain
145− |= =search-text
146− (estimate-search-pain (search-text-to-search-full search-text))
147−++ maybe-reverse-search
148− |= [search=search-full fopoints=opoints ropoints=opoints]
149− ^- [[points=opoints reversed=? pain=@] =search-full]
150− =/ search-pain (estimate-search-pain search)
151− =/ rsearch (reverse-search search)
152− =/ rsearch-pain (estimate-search-pain rsearch)
153− ?: (gth search-pain rsearch-pain)
154− [[ropoints %.y rsearch-pain] rsearch]
155− [[fopoints %.n search-pain] search]
156−++ tape-matches
157− |= [search=tape cand=tape]
158− ^- ?
159− |-
160− ?~ search %.y
161− ?~ cand %.n
162− ?: |(=(i.search i.cand) =(i.search '*'))
163− $(search t.search, cand t.cand)
164− %.n
165−++ tosd
166− |= [a=@ =pfsf]
167− %- ?:(=(%prefix pfsf) tos:po tod:po)
168− a
169−++ syl-equals
170− |= [=syl cand=@]
171− =((crip tape.syl) (tosd cand pfsf.syl))
172−++ syl-matches
173− |= [=syl cand=@]
174− :: todo check cand < 256
175− (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
176−++ matches-search
177− |= [search=search-syls cand-atom=@]
178− =/ cand (rip 3 cand-atom)
179− |-
180− ^- ?
181− ?~ search =(cand ~)
182− :: =? cand =(cand ~)
183− :: `(lest @)`~[0]
184− ?~ cand
185− ?& (syl-equals i.search 0)
186− $(search t.search)
187− ==
188− :: =/ nncand ^- (lest @)
189− :: ?~ cand ~[0] cand
190− ?& (syl-matches i.search i.cand)
191− $(search t.search, cand t.cand)
192− ==
193−::
194−++ opts-in-range
195− |= [lb=@ rb=@ opts=(list @)]
196− ^- search-syl-opts
197− %+ skim
198− opts
199− |= a=@
200− &((lte lb a) (gte rb a))
201−::
202−++ range-contains-search
203− |= [lb=@ rb=@ search=search-opts] :: includes lb and rb - [lb, rb]
204− ^- [opts=search-opts contains=?]
205− ?~ search `%.y
206− =/ s-0 i.search
207− ?~ s-0 `%.n
208− =/ lb-0 (end 3 lb)
209− =/ rb-0 (end 3 rb)
210− =/ cand-0 (opts-in-range lb-0 rb-0 s-0)
211− =/ cand-0-len (lent cand-0)
212− :: ~& "lb: {<`@q`lb>} rb: {<`@q`rb>} cand-len: {<cand-0-len>}"
213− ?: =(0 cand-0-len) `%.n
214− =. i.search cand-0
215− :_
216− ?: (gte cand-0-len 3) %.y :: entire valid galaxy is bounded
217− =/ lb-clean =(0 (rsh 3 lb)) :: lb is galaxy
218− =/ rb-clean =(0 (rsh 3 (add-d rb 1))) :: rb is last planet in galaxy
219− ?: &(=(2 cand-0-len) |(lb-clean rb-clean))
220− %.y :: two galaxies, and one is right on the edge (fully bounded)
221− |-
222− ^- ?
223− ?~ cand-0 %.n
224− =/ crb (add .~fipfes-fipzod i.cand-0)
225− =/ slb (rev-q (rev-f max lb i.cand-0))
226− =/ srb (rev-q (rev-f min rb crb))
227− =/ sub-contains +:^$(lb (rsh 3 slb), rb (rsh 3 srb), search t.search)
228− ?: sub-contains %.y
229− $(cand-0 t.cand-0)
230− search
231− :: check that first byte of lb and rb contain something that matches first of search
232− :: if contains full matching galaxy, return true
233− :: if contains no more than part of any galaxy, check those galaxies (1 or 2)
234− ::
235::
38::
236++ palor :: @p alphabetical order
39++ palor :: @p alphabetical order
237 ~/ %por
40 ~/ %por
desk/lib/search.hoonnew file+271
@@ -0,0 +1,271 @@
1+/- sur=astrolabe
2+/+ *astrolabe
3+=< [sur .]
4+=, sur
5+|%
6+++ search-text-chars "*abcdefghilmnoprstuvwxyz"
7+++ trip-syls
8+ |= syls=cord
9+ %+ turn
10+ (rip [0 24] syls)
11+ trip
12+++ prefixes ^~ (trip-syls sis:po)
13+++ suffixes ^~ (trip-syls dex:po)
14+++ full-opts ^~ (gulf .~zod .~fes)
15+++ sanitize-search-text
16+ |= =search-text
17+ ^- tape
18+ %+ skim search-text
19+ |= a=@t
20+ !=(~ (find [a]~ search-text-chars))
21++$ pfsf ?(%prefix %suffix)
22++$ syl [=tape =pfsf]
23++$ search-text tape
24++$ search-syls (list syl) :: in reverse order, e.g. ["net" "pal" "pel" "sam" ~]. length 1-4
25++$ search-syl-opts (list @q)
26++$ search-opts (list search-syl-opts) :: must have length 4
27++$ search-full
28+ $:
29+ =search-syls
30+ =search-opts
31+ ==
32++$ mr-search :: maybe reversed full search
33+ $:
34+ [reversed=? pain=@]
35+ =search-full
36+ ==
37+++ search-syl-to-search-syl-opts
38+ |= search=syl
39+ ^- search-syl-opts
40+ =/ syls
41+ ?: =(%prefix pfsf.search) prefixes suffixes
42+ ?: =(tape.search "***") full-opts
43+ %+ murn syls
44+ |= cand=tape
45+ ?. (tape-matches tape.search cand) ~
46+ %- ?: =(%prefix pfsf.search) ins:po ind:po
47+ (crip cand)
48+++ search-text-to-search-syls
49+ |= search-text=tape
50+ ^- search-syls
51+ =. search-text (sanitize-search-text search-text)
52+ =/ syl-tapes
53+ %+ turn
54+ %- flop
55+ (rip [0 24] (crip search-text)) :: jet doesn't support rip [3 3] which is equivalent
56+ trip
57+ =| count=@
58+ |-
59+ ^- search-syls
60+ =/ =pfsf
61+ ?: =(0 (mod count 2)) %suffix %prefix
62+ ?~ syl-tapes ~
63+ :- [i.syl-tapes pfsf]
64+ $(syl-tapes t.syl-tapes, count +(count))
65+::
66+++ search-syls-to-search-opts
67+ |= =search-syls
68+ ^- search-opts
69+ =/ opts
70+ %+ turn
71+ search-syls
72+ |= =syl
73+ (search-syl-to-search-syl-opts syl)
74+ ?: %+ lien opts
75+ |= syl-opts=search-syl-opts
76+ =(~ syl-opts)
77+ ~[~ ~ ~ ~]
78+ |-
79+ ?: (gte (lent opts) 4)
80+ opts
81+ $(opts (snoc (search-opts opts) (search-syl-opts ~[.~zod])))
82+::
83+++ search-text-to-full
84+ |= search-text=tape
85+ ^- search-full
86+ =/ =search-syls (search-text-to-search-syls search-text)
87+ :- search-syls
88+ (search-syls-to-search-opts search-syls)
89+::
90+++ expand-search-text-to-len
91+ |= [search=tape flen=@ud rotate=?]
92+ =. search (sanitize-search-text search)
93+ |-
94+ ^- (list tape)
95+ =+ slen=(lent search)
96+ ?: =(slen flen)
97+ ~[search]
98+ ?: (gth slen flen) ~
99+ :-
100+ (weld search (reap (sub flen slen) '*'))
101+ ?. rotate ~
102+ $(search (weld "*" search))
103+::
104+++ expand-search-text
105+ |= search=tape
106+ ^- (list tape)
107+ =. search (sanitize-search-text search)
108+ ?~ search ~
109+ =/ rotate
110+ ?& !=('~' i.search)
111+ !(levy (tape search) (cury test '*')) :: is all wild
112+ ==
113+ =/ expand (cury expand-search-text-to-len search)
114+ ;: weld
115+ (expand 3 rotate)
116+ (expand 6 rotate)
117+ (expand 12 rotate)
118+ ==
119+::
120+++ search-text-to-fulls
121+ |= search=tape
122+ ^- (list search-full)
123+ =/ texts (expand-search-text search)
124+ %+ murn
125+ texts
126+ |= text=tape
127+ =/ full (search-text-to-full text)
128+ ?: =(~[~ ~ ~ ~] search-opts.full)
129+ ~
130+ (some full)
131+++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
132+ |* lst=(list)
133+ ^- _lst
134+ %+ weld
135+ (slag 2 lst)
136+ (scag 2 lst)
137+++ pad-search-syls
138+ |= =search-syls
139+ ^- ^search-syls
140+ =/ padding (search-text-to-search-syls "dozzoddozzod")
141+ %+ weld
142+ search-syls
143+ (slag (lent search-syls) padding)
144+++ reverse-search
145+ |= search=search-full
146+ ^- search-full
147+ :- (reverse-list (pad-search-syls search-syls.search))
148+ (reverse-list search-opts.search)
149+++ estimate-search-pain
150+ |= search=search-full
151+ =/ opts search-opts.search
152+ =| acc=@
153+ |-
154+ ?~ opts acc
155+ =/ new-acc
156+ (add (mul acc 2) (lent i.opts))
157+ $(acc new-acc, opts t.opts)
158+:: ++ estimate-search-pain
159+:: |= search=search-full
160+:: =/ max-pain (bex 32)
161+:: =/ opts search-opts.search
162+:: =| acc=@
163+:: |-
164+:: ?~ opts acc
165+:: =/ level-pain (div (mul max-pain (lent i.opts)) 256)
166+:: =/ new-acc
167+:: (add (mul acc 2) (lent i.opts))
168+:: $(acc new-acc, opts t.opts)
169+++ estimate-search-text-pain
170+ |= =search-text
171+ (estimate-search-pain (search-text-to-full search-text))
172+++ maybe-reverse-search
173+ |= [search=search-full]
174+ ^- mr-search
175+ =/ search-pain (estimate-search-pain search)
176+ =/ rsearch (reverse-search search)
177+ =/ rsearch-pain (estimate-search-pain rsearch)
178+ ?: (gth search-pain rsearch-pain)
179+ [[%.y rsearch-pain] rsearch]
180+ [[%.n search-pain] search]
181+::
182+++ maybe-reverse-searches
183+ |= [searches=(list search-full) pain-thresh=@]
184+ =| pain=@
185+ |-
186+ ^- (list mr-search)
187+ ?~ searches ~
188+ =/ mr (maybe-reverse-search i.searches)
189+ =/ new-pain (add pain pain.mr)
190+ ?: (gth new-pain pain-thresh)
191+ $(searches t.searches)
192+ :- mr
193+ $(searches t.searches, pain new-pain)
194+::
195+++ tape-matches
196+ |= [search=tape cand=tape]
197+ ^- ?
198+ |-
199+ ?~ search %.y
200+ ?~ cand %.n
201+ ?: |(=(i.search i.cand) =(i.search '*'))
202+ $(search t.search, cand t.cand)
203+ %.n
204+++ tosd
205+ |= [a=@ =pfsf]
206+ %- ?:(=(%prefix pfsf) tos:po tod:po)
207+ a
208+++ syl-equals
209+ |= [=syl cand=@]
210+ =((crip tape.syl) (tosd cand pfsf.syl))
211+++ syl-matches
212+ |= [=syl cand=@]
213+ :: todo check cand < 256
214+ (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
215+++ matches-search
216+ |= [search=search-syls cand-atom=@]
217+ =/ cand (rip 3 cand-atom)
218+ |-
219+ ^- ?
220+ ?~ search =(cand ~)
221+ :: =? cand =(cand ~)
222+ :: `(lest @)`~[0]
223+ ?~ cand
224+ ?& (syl-equals i.search 0)
225+ $(search t.search)
226+ ==
227+ :: =/ nncand ^- (lest @)
228+ :: ?~ cand ~[0] cand
229+ ?& (syl-matches i.search i.cand)
230+ $(search t.search, cand t.cand)
231+ ==
232+::
233+++ opts-in-range
234+ |= [lb=@ rb=@ opts=(list @)]
235+ ^- search-syl-opts
236+ %+ skim
237+ opts
238+ |= a=@
239+ &((lte lb a) (gte rb a))
240+::
241+++ range-contains-search
242+ |= [lb=@ rb=@ search=search-opts] :: includes lb and rb - [lb, rb]
243+ ^- [opts=search-opts contains=?]
244+ ?~ search `%.y
245+ =/ s-0 i.search
246+ ?~ s-0 `%.n
247+ =/ lb-0 (end 3 lb)
248+ =/ rb-0 (end 3 rb)
249+ =/ cand-0 (opts-in-range lb-0 rb-0 s-0)
250+ =/ cand-0-len (lent cand-0)
251+ :: ~& "lb: {<`@q`lb>} rb: {<`@q`rb>} cand-len: {<cand-0-len>}"
252+ ?: =(0 cand-0-len) `%.n
253+ =. i.search cand-0
254+ :_
255+ ?: (gte cand-0-len 3) %.y :: entire valid galaxy is bounded
256+ =/ lb-clean =(0 (rsh 3 lb)) :: lb is galaxy
257+ =/ rb-clean =(0 (rsh 3 (add-d rb 1))) :: rb is last planet in galaxy
258+ ?: &(=(2 cand-0-len) |(lb-clean rb-clean))
259+ %.y :: two galaxies, and one is right on the edge (fully bounded)
260+ |-
261+ ^- ?
262+ ?~ cand-0 %.n
263+ =/ crb (add .~fipfes-fipzod i.cand-0)
264+ =/ slb (rev-q (rev-f max lb i.cand-0))
265+ =/ srb (rev-q (rev-f min rb crb))
266+ =/ sub-contains +:^$(lb (rsh 3 slb), rb (rsh 3 srb), search t.search)
267+ ?: sub-contains %.y
268+ $(cand-0 t.cand-0)
269+ search
270+::
271+--
ui/index.html−1
@@ -8,7 +8,6 @@
8 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
8 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
9 <link rel="manifest" href="/site.webmanifest">
9 <link rel="manifest" href="/site.webmanifest">
10 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11− <title>Astrolabe</title>
12 </head>
11 </head>
13 <body>
12 <body>
14 <div id="app" class="h-full"></div>
13 <div id="app" class="h-full"></div>
ui/src/App.svelte+17−8
@@ -1,22 +1,31 @@
1<script lang="ts">
1<script lang="ts">
2 import Router from 'svelte-spa-router';
2 import Router from 'svelte-spa-router';
3− import Home from './routes/Home.svelte';
4 import HelpPage from './routes/HelpPage.svelte';
3 import HelpPage from './routes/HelpPage.svelte';
5− import ShipViewPage from './routes/ShipViewPage.svelte';
4+ import Home from './routes/Home.svelte';
6 import HelpModal from './components/HelpModal.svelte';
5 import HelpModal from './components/HelpModal.svelte';
7 import "./styles/styles.scss";
6 import "./styles/styles.scss";
8
7
9 const routes = {
8 const routes = {
10− // '/': Home,
11 '/help/:path?': HelpPage,
9 '/help/:path?': HelpPage,
12− // '/:patp?': ShipViewPage,
10+ // '/:patp?': Home,
13− '/ship/:patp?': ShipViewPage,
11+ '/ship/:patp?': Home,
14− '/search/:patp?': ShipViewPage,
12+ '/search/:search?': Home,
15− '*': ShipViewPage,
13+ '*': Home,
14+ }
15+ import pageName from './stores/pageName';
16+
17+ $: title = $pageName ? `Astrolabe - ${$pageName}` : 'Astrolabe';
18+
19+ $: {
20+ document.title = title
16 }
21 }
17</script>
22</script>
18
23
19<main class="h-full overflow-y-auto">
24<main class="h-full overflow-y-auto">
20 <HelpModal />
25 <HelpModal />
21 <Router {routes} />
26 <Router {routes} />
22−</main>
27+</main>
28+
29+<svelte:head>
30+ <title>{title}</title>
31+</svelte:head>
ui/src/components/SearchBar.svelte+11−35
@@ -1,7 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2− import ValidationProblems from './ValidationProblems.svelte';
3
2
4− import { SearchAnalysis, analyzeSearch } from '../lib/id';
3+ import { SearchAnalysis } from '../lib/id';
5
4
6 export let patp: string = undefined;
5 export let patp: string = undefined;
7 export let searchQuery: RegExp = /~der/;
6 export let searchQuery: RegExp = /~der/;
@@ -9,11 +8,9 @@
9 export let search = '';
8 export let search = '';
10 let debouncedSearch = search;
9 let debouncedSearch = search;
11
10
12− // const placeholderText = "Enter a @p or non-negative integer. '*' is wildcard.";
11+ const placeholderText = "Search by @p or #. '*' is wildcard";
13− const placeholderText = "Enter a @p or non-negative integer";
14
12
15 $: {
13 $: {
16− // analysis.search = search;
17 analysis = new SearchAnalysis(debouncedSearch);
14 analysis = new SearchAnalysis(debouncedSearch);
18 if (analysis.queryIsValid) {
15 if (analysis.queryIsValid) {
19 searchQuery = analysis.query;
16 searchQuery = analysis.query;
@@ -36,33 +33,12 @@
36 }
33 }
37</script>
34</script>
38
35
39−<!-- <div class="flex justify-center"> -->
40− <div class="relative grow max-w-md rounded-full w-full">
36+<div class="relative grow max-w-md rounded-full w-full">
41− <input type="text" class="p-4 py-2 w-full rounded-full"
37+ <input type="text" class="p-4 py-2 w-full rounded-full"
42− spellcheck="false"
38+ spellcheck="false"
43− placeholder={placeholderText}
39+ placeholder={placeholderText}
44− bind:value={search}
40+ bind:value={search}
45− autofocus
41+ autofocus
46− />
42+ />
47− <button class="absolute right-0 w-10 h-10 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"></button>
43+ <button class="absolute right-0 w-10 h-10 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"></button>
48− </div>
49−<!-- </div> -->
50−<!-- <div class="bg-gray-300 p-2 rounded-lg">
51− <p>
52− text: {search}
53− </p>
54− <p>
55− query: {searchQuery}
56− </p>
57−</div> -->
58−<!-- {#if search !== '' && analysis && analysis.queryProblems.length > 0}
59− <div class="flex justify-center w-full">
60− <ValidationProblems type="Query Problems" problems={analysis.queryProblems}></ValidationProblems>
61− </div>
62−{/if} -->
63−
64−<!-- {#if search !== '' && analysis && analysis.patpProblems.length > 0}
65− <div class="flex justify-center">
66− <ValidationProblems type="ID Problems" problems={analysis.patpProblems}></ValidationProblems>
67− </div>
44+</div>
68−{/if} -->
ui/src/components/SearchResults.svelte+49−55
@@ -2,8 +2,10 @@
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 store from '../stores/store';
5+ import { searchedContacts } from '../stores/searchStores';
6 import ShipListings from './ShipListings.svelte';
6 import ShipListings from './ShipListings.svelte';
7+ import SearchSettings from './SearchSettings.svelte';
8+ import SearchResultsNavButtons from './SearchResultsNavButtons.svelte';
7
9
8 export let query: RegExp;
10 export let query: RegExp;
9 export let search: string;
11 export let search: string;
@@ -11,26 +13,25 @@
11 let combinedSearchResults = [];
13 let combinedSearchResults = [];
12 let searchedPoints: Patp[] = [];
14 let searchedPoints: Patp[] = [];
13 let selectedShipIndex: number;
15 let selectedShipIndex: number;
14−
16+ let page: number = 0;
17+ let pageSize: number = 50;
18+ let searchStatus: ('init' | 'prog' | 'done' | 'fail') = 'init';
19+ let notification: string = null;
20+ $: { searchedContacts.search(query) }
15− $: searchedPointsP = searchPoints(search)
21+ $: searchedPointsP = searchPoints(search);
16 $: {
22 $: {
23+ searchStatus = 'prog';
17 searchedPointsP.then((points) => {
24 searchedPointsP.then((points) => {
18 searchedPoints = points;
25 searchedPoints = points;
26+ searchStatus = 'done';
27+ }).catch((err) => {
28+ searchedPoints = [];
29+ searchStatus = 'fail';
19 });
30 });
20 }
31 }
21
32
22 $: {
33 $: {
23− // if (!patp) {
34+ const contactSearchResults: Rolodex = JSON.parse(JSON.stringify($searchedContacts));
24− store.search(query)
25− // }
26− // if (searchQuery) {
27− // store.search(searchQuery)
28− // } else {
29− // store.reset();
30− // }
31− }
32− $: {
33− const contactSearchResults = JSON.parse(JSON.stringify($store.contactSearchResults));
34 const contacts = [];
35 const contacts = [];
35 const nonContacts = [];
36 const nonContacts = [];
36 searchedPoints.forEach(patp => {
37 searchedPoints.forEach(patp => {
@@ -55,52 +56,45 @@
55 selectedShipIndex = null;
56 selectedShipIndex = null;
56 }
57 }
57 }
58 }
58− $: selectedShip = combinedSearchResults[selectedShipIndex]
59+ $: totalPages = Math.ceil(combinedSearchResults.length / pageSize);
60+ $: firstItemOnPage = page * pageSize
61+ $: pageResults = combinedSearchResults.slice(firstItemOnPage, (page + 1) * pageSize);
62+ $: itemRangeText = pageResults.length == combinedSearchResults.length ? 'all' :
63+ `${firstItemOnPage + 1}-${firstItemOnPage + pageResults.length}`;
59
64
60− function updateIndex(e) {
65+ $: {
61− selectedShipIndex = e.detail.index;
66+ if (searchStatus == 'prog') {
67+ notification = 'Searching all spawned points...';
68+ } else if (searchStatus == 'fail') {
69+ notification = 'Your search is too broad, please be more specific.';
70+ } else if (searchStatus == 'done' && combinedSearchResults.length === 0) {
71+ notification = 'No spawned ships match your search.'
72+ } else {
73+ notification = null;
74+ }
62 }
75 }
63</script>
76</script>
64
77
65−<!-- should actually switch on whether field is empty -->
78+<SearchSettings/>
66−<!-- searchQuery is undefined when query is invalid -->
79+{#if notification}
67−<!-- {#if searchQuery === undefined}
68− <p class="p-6 rounded-lg bg-white">
80+<div class="rounded-lg bg-white overflow-hidden">
69− Hello, ~{window.ship}, would you like to search?
81+ <p class="py-4 px-6 text-center">
82+ {notification}
70 </p>
83 </p>
84+</div>
71−{/if} -->
85+{/if}
72−<!-- {#if patp}
86+{#if combinedSearchResults.length > 0}
73− <ShipView {patp} />
74−{:else if selectedShip}-->
75− <!-- {#if contactsList.length > 1}
76− <SearchResultNavButtons
77− index={selectedShipIndex}
78− totalResults={contactsList.length}
79− on:updateIndex={updateIndex}
80− />
81− {/if} -->
82−<!-- <ShipView patp={selectedShip.patp} />
83−{:else} -->
84−<div class="max-w-md w-full rounded-lg bg-white overflow-hidden">
87+ <div class="max-w-md w-full rounded-lg bg-white overflow-hidden">
85− {#await searchedPointsP}
88+ <SearchResultsNavButtons {page} {pageSize} {totalPages}
86− <p class="p-8 text-center">
89+ on:updatePage={({ detail }) => page = detail.page}
87− Searching all spawned points...
88− </p>
89− {:catch}
90− <p class="p-8 text-center">
91− Your search is too broad, please be more specific.
92− </p>
90+ >
93− {/await}
94− {#if combinedSearchResults.length === 0}
95− <p class="p-8 text-center">
91+ <p class="px-4 py-8 text-center">
96− No spawned ships match your search.
92+ Showing {itemRangeText} of {combinedSearchResults.length} results
97− </p>
93+ </p>
98− {:else}
94+ </SearchResultsNavButtons>
99 <ShipListings
95 <ShipListings
100− ships={combinedSearchResults}
96+ ships={pageResults}
101− on:click={({ detail: i }) => selectedShipIndex = i}
102 linkToShips differentiateContacts
97 linkToShips differentiateContacts
103 />
98 />
104− {/if}
105−</div>
99+ </div>
106− <!-- {/if} -->
100+{/if}
ui/src/components/SearchResultsNavButtons.sveltenew file+54
@@ -0,0 +1,54 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ export let page: number;
4+ export let pageSize: number;
5+ export let totalPages: number;
6+ const dispatch = createEventDispatcher();
7+
8+ function updatePage(page: number) {
9+ dispatch('updatePage', { page });
10+ }
11+
12+ function visitPrevious() {
13+ updatePage(page - 1);
14+ }
15+
16+ function visitNext() {
17+ updatePage(page + 1);
18+ }
19+
20+ function seeList() {
21+ updatePage(null);
22+ }
23+</script>
24+
25+{#if totalPages > 1}
26+ <div class="flex items-center px-8 max-w-md w-full">
27+ <div class="justify-self-start h-6">
28+ <button
29+ on:click={visitPrevious}
30+ class="h-6 w-12 bg-gold"
31+ class:invisible={!(page > 0)}
32+ style="
33+ clip-path: polygon(25% 0%, 0% 50%, 25% 100%, 100% 100%, 75% 50%, 100% 0%);
34+ "
35+ >
36+ </button>
37+ </div>
38+ <div class="grow">
39+ <slot/>
40+ </div>
41+ <div class="justify-self-end h-6">
42+ <button
43+ on:click={visitNext}
44+ class="h-6 w-12 bg-gold"
45+ class:invisible={!(page < totalPages - 1)}
46+ style="
47+
48+ clip-path: polygon(75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%, 0% 0%);
49+ "
50+ >
51+ </button>
52+ </div>
53+ </div>
54+{/if}
ui/src/components/SearchSettings.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import ToggleButton from './buttons/ToggleButton.svelte';
3+
4+ import { searchSettings } from '../stores/searchStores';
5+ import { toggleStoreKey } from '../lib/utils';
6+
7+ function toggleMoons() {
8+ toggleStoreKey(searchSettings, 'includeMoons');
9+ }
10+
11+ function toggleComets() {
12+ toggleStoreKey(searchSettings, 'includeComets');
13+ }
14+</script>
15+
16+<div class="max-w-md rounded-lg bg-gold overflow-hidden flex flex-wrap items-center justify-center">
17+ <div class="px-4 py-2 flex gap-x-2 items-center border-navy">
18+ <span>Include Moons</span>
19+ <ToggleButton on:click={toggleMoons} on={$searchSettings.includeMoons}></ToggleButton>
20+ </div>
21+ <div class="px-4 py-2 flex gap-x-2 items-center border-navy">
22+ <span>Include Comets</span>
23+ <ToggleButton on:click={toggleComets} on={$searchSettings.includeComets}></ToggleButton>
24+ </div>
25+</div>
ui/src/components/ShipListing.svelte+2−2
@@ -26,9 +26,9 @@ on: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} />
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} -->
32 <p class="ml-4 break-words">
32 <p class="ml-4 break-words">
33 {patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
33 {patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
34 </p>
34 </p>
ui/src/components/buttons/ToggleButton.sveltenew file+10
@@ -0,0 +1,10 @@
1+<script lang="ts">
2+ export let on: boolean = true;
3+</script>
4+
5+<button on:click
6+ class="w-4 h-4 rounded-full border border-navy"
7+ class:bg-white={!on}
8+ class:bg-navy={on}
9+/>
10+
ui/src/components/docs/DocHome.svelte+1−1
@@ -9,7 +9,7 @@
9 }
9 }
10</script>
10</script>
11
11
12−<div class="doc">
12+<div class="doc w-auto mx-auto">
13 <h1 class="text-center">Table of Contents</h1>
13 <h1 class="text-center">Table of Contents</h1>
14 <DocLink path="urbit-id" on:navigate>
14 <DocLink path="urbit-id" on:navigate>
15 <h2>What is an Urbit ID?</h2>
15 <h2>What is an Urbit ID?</h2>
ui/src/lib/id.ts+1
@@ -448,6 +448,7 @@ function padWithWild(search: string) {
448
448
449function convertSearchTextToRegex(str: string, { checkStart = false, checkEnd = false } = {}): RegExp {
449function convertSearchTextToRegex(str: string, { checkStart = false, checkEnd = false } = {}): RegExp {
450 str = str.replace(/\*/g, '.');
450 str = str.replace(/\*/g, '.');
451+ if (str[0] === '~') checkStart = true;
451 if (checkStart) {
452 if (checkStart) {
452 str = '^' + str;
453 str = '^' + str;
453 }
454 }
ui/src/lib/utils.ts+16−10
@@ -1,19 +1,19 @@
1−function isStringNaN(value: string): boolean {
1+export function isStringNaN(value: string): boolean {
2 return window.isNaN(value as any) || Number.isNaN(parseInt(value)) || !(/^-?\d+$/.test(value));
2 return window.isNaN(value as any) || Number.isNaN(parseInt(value)) || !(/^-?\d+$/.test(value));
3}
3}
4
4
5−function listStrings(list: string[], includeAnd: boolean = false): string {
5+export function listStrings(list: string[], includeAnd: boolean = false): string {
6 if (includeAnd && list.length > 1) {
6 if (includeAnd && list.length > 1) {
7 return listStrings(list.slice(0, -1)) + ' and ' + listStrings(list.slice(-1));
7 return listStrings(list.slice(0, -1)) + ' and ' + listStrings(list.slice(-1));
8 }
8 }
9 return "'" + list.join("', '") + "'";
9 return "'" + list.join("', '") + "'";
10}
10}
11
11
12−function filterObject(obj, predicate) {
12+export function filterObject(obj, predicate) {
13 return Object.fromEntries(Object.entries(obj).filter(([key, value]) => predicate(key, value)));
13 return Object.fromEntries(Object.entries(obj).filter(([key, value]) => predicate(key, value)));
14}
14}
15
15
16−function buildQuerystring(data) {
16+export function buildQuerystring(data) {
17 if (typeof (data) === 'string') return data;
17 if (typeof (data) === 'string') return data;
18
18
19 var query = [];
19 var query = [];
@@ -26,12 +26,18 @@ function buildQuerystring(data) {
26
26
27 // Join each item in the array with a `&` and return the resulting string
27 // Join each item in the array with a `&` and return the resulting string
28 return query.length ? '?' + query.join('&') : '';
28 return query.length ? '?' + query.join('&') : '';
29+}
29
30
31+export function setStoreKey(store, key, value) {
32+ store.update((store) => ({
33+ ...store,
34+ [key]: value,
35+ }));
30}
36}
31
37
32−export {
38+export function toggleStoreKey(store, key) {
33− isStringNaN,
39+ store.update((store) => ({
34− listStrings,
40+ ...store,
35− filterObject,
41+ [key]: !store[key],
36− buildQuerystring,
37−};
42+ }));
43+}
ui/src/routes/Home.svelte+58−5
@@ -1,11 +1,64 @@
1<script lang="ts">
1<script lang="ts">
2+ import { link, push, replace, querystring } from 'svelte-spa-router'
3+ import pageName from '../stores/pageName';
4+ import { SearchAnalysis } from '../lib/id';
5+ import { linkToShip } from '../lib/link';
2 import SearchBar from "../components/SearchBar.svelte";
6 import SearchBar from "../components/SearchBar.svelte";
3 import SearchResults from "../components/SearchResults.svelte";
7 import SearchResults from "../components/SearchResults.svelte";
4− let patp: string;
8+ import ShipView from '../components/ShipView.svelte';
5− let searchQuery: RegExp;
9+ import ValidationProblems from '../components/ValidationProblems.svelte';
10+ import HomeScreenTiles from '../components/HomeScreenTiles.svelte';
11+ export let params: any = {};
12+ let analysis: SearchAnalysis = new SearchAnalysis();
13+
14+ const searchParams = new URLSearchParams($querystring);
15+ function getSearch() {
16+ return searchParams.get('search') || params.search || params.patp || '';
17+ }
18+ $: urlSearch = searchParams.get('search') || params.search || params.patp || '';
19+ let search: string = getSearch();
20+ $: urlChanged = (urlSearch || true) && Date.now();
21+ $: analysisPatpChanged = (analysis.patp || true) && Date.now();
22+ $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
23+ $: {
24+ if (search !== urlSearch) {
25+ if (urlChangedSinceAnalysisPatp) {
26+ search = urlSearch;
27+ } else if (analysis.search === search) {
28+ if (analysis.patpIsValid) {
29+ if (analysis.patp === search) {
30+ push(linkToShip(analysis.patp));
31+ } else {
32+ push(linkToShip(analysis.patp, { search }));
33+ }
34+ } else {
35+ const nav = params.patp !== undefined ? push : replace;
36+ if (!search) {
37+ nav('/');
38+ } else if (analysis.queryIsValid) {
39+ nav(`/search/${search}`);
40+ }
41+ }
42+ }
43+ }
44+ }
45+
46+ $: {
47+ $pageName = params.patp !== undefined ? params.patp : (search ? 'Search' : null);
48+ }
49+
50+
6</script>
51</script>
7
52
8−<div class="m-8 flex flex-col items-center space-y-8">
53+<div class="m-4 flex flex-col items-center space-y-8 2xs:m-8">
9− <SearchBar bind:patp bind:searchQuery />
54+ <SearchBar bind:analysis bind:search />
10− <SearchResults {patp} {searchQuery}/>
55+ {#if analysis.patpIsValid}
56+ <ShipView patp={analysis.patp} />
57+ {:else if (analysis.queryIsValid)}
58+ <SearchResults query={analysis.query} search={analysis.search} />
59+ {:else if (analysis.search.length > 0)}
60+ <ValidationProblems problems={analysis.queryProblems} />
61+ {:else}
62+ <HomeScreenTiles />
63+ {/if}
11</div>
64</div>
ui/src/routes/ShipViewPage.sveltedeleted−63
@@ -1,63 +0,0 @@
1−<script lang="ts">
2− import { link, push, querystring } from 'svelte-spa-router'
3− import docs from '../stores/docs';
4− import { SearchAnalysis } from '../lib/id';
5− import { linkToShip } from '../lib/link';
6− import SearchBar from "../components/SearchBar.svelte";
7− import SearchResults from "../components/SearchResults.svelte";
8− import ShipView from '../components/ShipView.svelte';
9− import ValidationProblems from '../components/ValidationProblems.svelte';
10− import HomeScreenTiles from '../components/HomeScreenTiles.svelte';
11− export let params: any = {};
12− let analysis: SearchAnalysis = new SearchAnalysis();
13−
14− const searchParams = new URLSearchParams($querystring);
15− let search: string = searchParams.get('search') || params.patp || '';
16− $: paramChanged = params.patp && Date.now();
17− $: analysisPatpChanged = analysis.patp && Date.now();
18− $: paramChangedMoreRecently = (paramChanged - analysisPatpChanged) > 1000;
19− // $: search = params.patp || '';
20− // $: patp = normalizeId(params.patp);
21− // $: analysis = analyzeSearch(params.patp);
22− $: {
23− if (params.patp !== analysis.patp) {
24− if (paramChangedMoreRecently) {
25− search = params.patp;
26− } else {
27− if (analysis.search === search) {
28− if (!analysis.patpIsValid) {
29− if (search) {
30− push(`/search/${search}`);
31− } else {
32− push('/');
33− }
34− } else {
35− if (analysis.patp === search) {
36− push(linkToShip(analysis.patp));
37− } else {
38− push(linkToShip(analysis.patp, { search }));
39− }
40− }
41− }
42− }
43− }
44− // if ()
45− }
46−
47−</script>
48−
49−<div class="m-4 flex flex-col items-center space-y-8 2xs:m-8">
50− <!-- {@debug analysis, search, params} -->
51− <!-- <a use:link={"/"} class="p-4 bg-white rounded-lg text-center">Go Home</a> -->
52− <!-- <SearchBar bind:searchText={search} bind:analysis /> -->
53− <SearchBar bind:analysis bind:search />
54− {#if analysis.patpIsValid}
55− <ShipView patp={analysis.patp} />
56− {:else if (analysis.queryIsValid)}
57− <SearchResults query={analysis.query} search={analysis.search} />
58− {:else if (analysis.search.length > 0)}
59− <ValidationProblems problems={analysis.queryProblems} />
60− {:else}
61− <HomeScreenTiles />
62− {/if}
63−</div>
ui/src/stores/pageName.tsnew file+12
@@ -0,0 +1,12 @@
1+import { writable } from 'svelte/store';
2+
3+const initial = null;
4+
5+const store = writable(initial);
6+const { subscribe, update, set } = store;
7+
8+function reset() {
9+ set(initial);
10+}
11+
12+export default { subscribe, update, set, reset };
ui/src/stores/searchStores.tsnew file+35
@@ -0,0 +1,35 @@
1+import { writable, derived } from 'svelte/store';
2+
3+import type { Rolodex } from '@urbit/api';
4+import store from './store';
5+import type { StoreState, SearchedContactsState } from '../types/store';
6+import { SearchSettings } from '../types/store';
7+import { filterObject } from '../lib/utils';
8+
9+const searchSettings = writable(new SearchSettings());
10+
11+const searchedContacts = derived([store, searchSettings], deriveSearchedContacts);
12+searchedContacts.search = store.search;
13+
14+function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsState {
15+ if (!$store.query) {
16+ return {};
17+ }
18+ const filteredContacts = filterObject($store.contacts, (id) => {
19+ if (!$store.query.test(id)) return false;
20+ if (!$searchSettings.includeMoons && id.length > 16 && id.length <= 32) {
21+ return false;
22+ }
23+ if (!$searchSettings.includeComets && id.length > 32) {
24+ return false;
25+ }
26+ return true;
27+ }) as Rolodex;
28+ return filteredContacts;
29+}
30+
31+export {
32+ // searchedContacts: { ...searchedContacts, search: store.search },
33+ searchedContacts,
34+ searchSettings,
35+};
ui/src/stores/store.ts+7−28
@@ -1,16 +1,13 @@
1import { writable } from 'svelte/store';
1import { writable } from 'svelte/store';
2
2
3−import type { ContactUpdate, Patp, Group, Contact, Rolodex } from '@urbit/api';
3+import type { Rolodex } from '@urbit/api';
4import { subscribeToContacts } from '../lib/api';
4import { subscribeToContacts } from '../lib/api';
5−import type { StoreState, Contacts } from '../types/store';
5+import type { StoreState } from '../types/store';
6−import { filterObject } from '../lib/utils';
6+import { setStoreKey } from '../lib/utils';
7−import fakeContacts from '../fake_data/fake-contacts';
8
7
9const initStore: StoreState = {
8const initStore: StoreState = {
10− // chats: [],
11− // groups: {},
12 contacts: {},
9 contacts: {},
13− contactSearchResults: {},
10+ query: null,
14 connection: 'disconnected',
11 connection: 'disconnected',
15 ship: window.ship,
12 ship: window.ship,
16};
13};
@@ -19,22 +16,7 @@ const store = writable(initStore);
19const { subscribe, update, set } = store;
16const { subscribe, update, set } = store;
20
17
21function search(idQuery: RegExp) {
18function search(idQuery: RegExp) {
22− update(($store: StoreState): StoreState => {
19+ setStoreKey(store, 'query', idQuery);
23− if (!idQuery) {
24− return {
25− ...$store,
26− contactSearchResults: $store.contacts,
27− };
28− }
29− // const filteredContacts = filterObject($store.contacts, (id, contact) => {
30− const filteredContacts = filterObject($store.contacts, (id) => {
31− return idQuery.test(id);
32− }) as Contacts;
33− return {
34− ...$store,
35− contactSearchResults: filteredContacts,
36− }
37− });
38}
20}
39
21
40function reset() {
22function reset() {
@@ -50,9 +32,6 @@ function updateContacts(callback: (contacts: Rolodex) => Rolodex): void {
50 });
32 });
51}
33}
52
34
53−// setTimeout(() => {
54− subscribeToContacts(updateContacts);
35+subscribeToContacts(updateContacts);
55−// }, 1000);
56−
57
36
58−export default { subscribe, search, reset };
37+export default { subscribe, set, search, reset };
ui/src/styles/docs.scss+3−1
@@ -23,7 +23,9 @@
23 @apply pl-5;
23 @apply pl-5;
24
24
25 li {
25 li {
26+ h1, h2, h3, h4 {
26− @apply py-1;
27+ @apply py-0;
28+ }
27
29
28 // & * {
30 // & * {
29 // @apply inline;
31 // @apply inline;
ui/src/styles/tailwind-output.css+77
@@ -433,6 +433,9 @@ Ensure the default browser behavior of the `hidden` attribute.
433.visible {
433.visible {
434 visibility: visible;
434 visibility: visible;
435}
435}
436+.invisible {
437+ visibility: hidden;
438+}
436.absolute {
439.absolute {
437 position: absolute;
440 position: absolute;
438}
441}
@@ -492,6 +495,10 @@ Ensure the default browser behavior of the `hidden` attribute.
492 margin-top: 1.25rem;
495 margin-top: 1.25rem;
493 margin-bottom: 1.25rem;
496 margin-bottom: 1.25rem;
494}
497}
498+.my-0 {
499+ margin-top: 0px;
500+ margin-bottom: 0px;
501+}
495.mt-2 {
502.mt-2 {
496 margin-top: 0.5rem;
503 margin-top: 0.5rem;
497}
504}
@@ -597,6 +604,9 @@ Ensure the default browser behavior of the `hidden` attribute.
597.h-7 {
604.h-7 {
598 height: 1.75rem;
605 height: 1.75rem;
599}
606}
607+.h-4 {
608+ height: 1rem;
609+}
600.w-full {
610.w-full {
601 width: 100%;
611 width: 100%;
602}
612}
@@ -612,6 +622,12 @@ Ensure the default browser behavior of the `hidden` attribute.
612.w-7 {
622.w-7 {
613 width: 1.75rem;
623 width: 1.75rem;
614}
624}
625+.w-auto {
626+ width: auto;
627+}
628+.w-4 {
629+ width: 1rem;
630+}
615.min-w-0 {
631.min-w-0 {
616 min-width: 0px;
632 min-width: 0px;
617}
633}
@@ -695,6 +711,18 @@ Ensure the default browser behavior of the `hidden` attribute.
695.gap-1 {
711.gap-1 {
696 gap: 0.25rem;
712 gap: 0.25rem;
697}
713}
714+.gap-x-3 {
715+ -moz-column-gap: 0.75rem;
716+ column-gap: 0.75rem;
717+}
718+.gap-x-2 {
719+ -moz-column-gap: 0.5rem;
720+ column-gap: 0.5rem;
721+}
722+.gap-x-4 {
723+ -moz-column-gap: 1rem;
724+ column-gap: 1rem;
725+}
698.space-y-4 > :not([hidden]) ~ :not([hidden]) {
726.space-y-4 > :not([hidden]) ~ :not([hidden]) {
699 --tw-space-y-reverse: 0;
727 --tw-space-y-reverse: 0;
700 margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
728 margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@@ -725,6 +753,11 @@ Ensure the default browser behavior of the `hidden` attribute.
725 border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
753 border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
726 border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
754 border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
727}
755}
756+.divide-x > :not([hidden]) ~ :not([hidden]) {
757+ --tw-divide-x-reverse: 0;
758+ border-right-width: calc(1px * var(--tw-divide-x-reverse));
759+ border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
760+}
728.justify-self-start {
761.justify-self-start {
729 justify-self: start;
762 justify-self: start;
730}
763}
@@ -873,6 +906,34 @@ Ensure the default browser behavior of the `hidden` attribute.
873 --tw-bg-opacity: 1;
906 --tw-bg-opacity: 1;
874 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
907 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
875}
908}
909+.bg-gold-t8 {
910+ --tw-bg-opacity: 1;
911+ background-color: rgb(254 246 219 / var(--tw-bg-opacity));
912+}
913+.bg-gold-s8 {
914+ --tw-bg-opacity: 1;
915+ background-color: rgb(50 42 15 / var(--tw-bg-opacity));
916+}
917+.bg-gold-s4 {
918+ --tw-bg-opacity: 1;
919+ background-color: rgb(151 127 46 / var(--tw-bg-opacity));
920+}
921+.bg-gold-t1 {
922+ --tw-bg-opacity: 1;
923+ background-color: rgb(252 215 95 / var(--tw-bg-opacity));
924+}
925+.bg-gold-t2 {
926+ --tw-bg-opacity: 1;
927+ background-color: rgb(253 220 113 / var(--tw-bg-opacity));
928+}
929+.bg-gold-s2 {
930+ --tw-bg-opacity: 1;
931+ background-color: rgb(202 169 62 / var(--tw-bg-opacity));
932+}
933+.bg-gold-s1 {
934+ --tw-bg-opacity: 1;
935+ background-color: rgb(227 190 69 / var(--tw-bg-opacity));
936+}
876.p-5 {
937.p-5 {
877 padding: 1.25rem;
938 padding: 1.25rem;
878}
939}
@@ -934,6 +995,22 @@ Ensure the default browser behavior of the `hidden` attribute.
934 padding-left: 0.25rem;
995 padding-left: 0.25rem;
935 padding-right: 0.25rem;
996 padding-right: 0.25rem;
936}
997}
998+.px-8 {
999+ padding-left: 2rem;
1000+ padding-right: 2rem;
1001+}
1002+.px-4 {
1003+ padding-left: 1rem;
1004+ padding-right: 1rem;
1005+}
1006+.py-8 {
1007+ padding-top: 2rem;
1008+ padding-bottom: 2rem;
1009+}
1010+.px-6 {
1011+ padding-left: 1.5rem;
1012+ padding-right: 1.5rem;
1013+}
937.pt-10 {
1014.pt-10 {
938 padding-top: 2.5rem;
1015 padding-top: 2.5rem;
939}
1016}
ui/src/types/store.ts+7−1
@@ -13,7 +13,7 @@ export interface StoreState {
13 // [group: string]: Group;
13 // [group: string]: Group;
14 // };
14 // };
15 contacts: Rolodex;
15 contacts: Rolodex;
16− contactSearchResults: Rolodex;
16+ query: RegExp;
17 // contacts: Contacts;
17 // contacts: Contacts;
18 // contactSearchResults: Contacts;
18 // contactSearchResults: Contacts;
19 // chats: [];
19 // chats: [];
@@ -21,3 +21,9 @@ export interface StoreState {
21 subscription?: unknown;
21 subscription?: unknown;
22 ship: Patp;
22 ship: Patp;
23}
23}
24+export type SearchedContactsState = Rolodex;
25+
26+export class SearchSettings {
27+ includeMoons: boolean = true;
28+ includeComets: boolean = false;
29+}