on main help sign in
frontend support for search
74331e0John Hyde
desk/app/astrolabe.hoon+4−1
@@ -145,6 +145,9 @@
145 =? state ?=([%tx [* * * %spawn ship *] *] diff)
145 =? state ?=([%tx [* * * %spawn ship *] *] diff)
146 %- put-in-opoints
146 %- put-in-opoints
147 ship.tx.raw-tx.diff
147 ship.tx.raw-tx.diff
148+ =? state ?=([%point @ %owner *] diff)
149+ %- put-in-opoints
150+ ship.diff
148 `state
151 `state
149::
152::
150++ is-spawn-tx
153++ is-spawn-tx
@@ -248,7 +251,7 @@
248 ?: (gth pain.spoints 2.820) ~|(%search-predicted-too-broad !!)
251 ?: (gth pain.spoints 2.820) ~|(%search-predicted-too-broad !!)
249 =/ results
252 =/ results
250 %-
253 %-
251− (run-wildcard-search (list @q) search-full points.spoints *page-info)
254+ (run-wildcard-search (list @q) search-full points.spoints [0 0])
252 |= [agg=(list @q) result=@q]
255 |= [agg=(list @q) result=@q]
253 [result agg]
256 [result agg]
254 ~& "returning {<(lent agg.results)>} of {<count.results>} results"
257 ~& "returning {<(lent agg.results)>} of {<count.results>} results"
desk/lib/astrolabe.hoon+17−7
@@ -162,11 +162,17 @@
162 ?: |(=(i.search i.cand) =(i.search '*'))
162 ?: |(=(i.search i.cand) =(i.search '*'))
163 $(search t.search, cand t.cand)
163 $(search t.search, cand t.cand)
164 %.n
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))
165++ syl-matches
172++ syl-matches
166 |= [=syl cand=@]
173 |= [=syl cand=@]
167 :: todo check cand < 256
174 :: todo check cand < 256
168− =/ tof ?: =(%prefix pfsf.syl) tos:po tod:po
169− (tape-matches tape.syl (trip (tof cand)))
175+ (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
170++ matches-search
176++ matches-search
171 |= [search=search-syls cand-atom=@]
177 |= [search=search-syls cand-atom=@]
172 =/ cand (rip 3 cand-atom)
178 =/ cand (rip 3 cand-atom)
@@ -175,11 +181,15 @@
175 ?~ search =(cand ~)
181 ?~ search =(cand ~)
176 :: =? cand =(cand ~)
182 :: =? cand =(cand ~)
177 :: `(lest @)`~[0]
183 :: `(lest @)`~[0]
184+ ?~ cand
185+ ?& (syl-equals i.search 0)
186+ $(search t.search)
187+ ==
178− =/ nncand ^- (lest @)
188+ :: =/ nncand ^- (lest @)
179− ?~ cand ~[0] cand
189+ :: ?~ cand ~[0] cand
180− ?: (syl-matches i.search i.nncand)
190+ ?& (syl-matches i.search i.cand)
181− $(search t.search, cand t.nncand)
191+ $(search t.search, cand t.cand)
182− %.n
192+ ==
183::
193::
184++ opts-in-range
194++ opts-in-range
185 |= [lb=@ rb=@ opts=(list @)]
195 |= [lb=@ rb=@ opts=(list @)]
ui/package-lock.json+3−4
@@ -13,6 +13,7 @@
13 "@urbit/api": "^2.1.0",
13 "@urbit/api": "^2.1.0",
14 "@urbit/http-api": "^2.1.0",
14 "@urbit/http-api": "^2.1.0",
15 "bn.js": "^5.2.0",
15 "bn.js": "^5.2.0",
16+ "lodash": "^4.17.21",
16 "svelte-copy-to-clipboard": "^0.2.5",
17 "svelte-copy-to-clipboard": "^0.2.5",
17 "tippy.js": "^6.3.7",
18 "tippy.js": "^6.3.7",
18 "urbit-ob": "^5.0.1"
19 "urbit-ob": "^5.0.1"
@@ -1606,8 +1607,7 @@
1606 "node_modules/lodash": {
1607 "node_modules/lodash": {
1607 "version": "4.17.21",
1608 "version": "4.17.21",
1608 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
1609 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
1609− "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
1610+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
1610− "dev": true
1611 },
1611 },
1612 "node_modules/lodash.chunk": {
1612 "node_modules/lodash.chunk": {
1613 "version": "4.2.0",
1613 "version": "4.2.0",
@@ -4049,8 +4049,7 @@
4049 "lodash": {
4049 "lodash": {
4050 "version": "4.17.21",
4050 "version": "4.17.21",
4051 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
4051 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
4052− "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
4052+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
4053− "dev": true
4054 },
4053 },
4055 "lodash.chunk": {
4054 "lodash.chunk": {
4056 "version": "4.2.0",
4055 "version": "4.2.0",
ui/package.json+1
@@ -37,6 +37,7 @@
37 "@urbit/api": "^2.1.0",
37 "@urbit/api": "^2.1.0",
38 "@urbit/http-api": "^2.1.0",
38 "@urbit/http-api": "^2.1.0",
39 "bn.js": "^5.2.0",
39 "bn.js": "^5.2.0",
40+ "lodash": "^4.17.21",
40 "svelte-copy-to-clipboard": "^0.2.5",
41 "svelte-copy-to-clipboard": "^0.2.5",
41 "tippy.js": "^6.3.7",
42 "tippy.js": "^6.3.7",
42 "urbit-ob": "^5.0.1"
43 "urbit-ob": "^5.0.1"
ui/src/App.svelte+2−1
@@ -11,6 +11,7 @@
11 '/help/:path?': HelpPage,
11 '/help/:path?': HelpPage,
12 // '/:patp?': ShipViewPage,
12 // '/:patp?': ShipViewPage,
13 '/ship/:patp?': ShipViewPage,
13 '/ship/:patp?': ShipViewPage,
14+ '/search/:patp?': ShipViewPage,
14 '*': ShipViewPage,
15 '*': ShipViewPage,
15 }
16 }
16</script>
17</script>
@@ -18,4 +19,4 @@
18<main class="h-full overflow-y-auto">
19<main class="h-full overflow-y-auto">
19 <HelpModal />
20 <HelpModal />
20 <Router {routes} />
21 <Router {routes} />
21−</main>
22+</main>
ui/src/components/SearchResults.svelte+79−55
@@ -1,45 +1,65 @@
1<script lang="ts">
1<script lang="ts">
2+
2− import { Groups, Rolodex } from '@urbit/api';
3+ import type { Patp, Rolodex } from '@urbit/api';
3 import { searchPoints } from '../lib/api';
4 import { searchPoints } from '../lib/api';
4 import store from '../stores/store';
5 import store from '../stores/store';
5− import ShipListing from './ShipListing.svelte';
6+ import ShipListings from './ShipListings.svelte';
6− import SearchResultNavButtons from './SearchResultNavButtons.svelte';
7+
7− import ShipView from './ShipView.svelte';
8 export let query: RegExp;
8 export let query: RegExp;
9 export let search: string;
9 export let search: string;
10 // export let patp: string;
10 // export let patp: string;
11− let contactsList = [];
11+ let combinedSearchResults = [];
12+ let searchedPoints: Patp[] = [];
12 let selectedShipIndex: number;
13 let selectedShipIndex: number;
13
14
14 $: searchedPointsP = searchPoints(search)
15 $: searchedPointsP = searchPoints(search)
16+ $: {
17+ searchedPointsP.then((points) => {
18+ searchedPoints = points;
19+ });
20+ }
21+
15− // $: {
22+ $: {
16− // // if (!patp) {
23+ // if (!patp) {
17− // store.search(query)
24+ store.search(query)
18− // // }
25+ // }
19− // // if (searchQuery) {
26+ // if (searchQuery) {
20− // // store.search(searchQuery)
27+ // store.search(searchQuery)
21− // // } else {
28+ // } else {
22− // // store.reset();
29+ // store.reset();
23− // // }
30+ // }
24− // }
31+ }
25− // $: {
32+ $: {
33+ const contactSearchResults = JSON.parse(JSON.stringify($store.contactSearchResults));
34+ const contacts = [];
35+ const nonContacts = [];
36+ searchedPoints.forEach(patp => {
37+ const contact = contactSearchResults[patp];
38+ if (contact) {
39+ contacts.push({ ...contact, patp });
40+ delete contactSearchResults[patp];
41+ } else {
42+ nonContacts.push({ patp });
43+ }
44+ });
26− // contactsList = Object.entries($store?.contacts).map(([patp, ship]) => {
45+ const nonAzimuthContacts = Object.entries(contactSearchResults).map(([patp, ship]) => {
27− // return {
46+ return {
28− // ...ship,
47+ ...ship,
29− // patp,
48+ patp,
30− // };
49+ };
31− // });
50+ });
32− // if (contactsList.length === 1) {
51+ combinedSearchResults = [...contacts, ...nonAzimuthContacts, ...nonContacts];
52+ if (combinedSearchResults.length === 1) {
33− // selectedShipIndex = 0;
53+ selectedShipIndex = 0;
34− // } else {
54+ } else {
35− // selectedShipIndex = null;
55+ selectedShipIndex = null;
36− // }
56+ }
37− // }
57+ }
38− // $: selectedShip = contactsList[selectedShipIndex]
58+ $: selectedShip = combinedSearchResults[selectedShipIndex]
39
59
40− // function updateIndex(e) {
60+ function updateIndex(e) {
41− // selectedShipIndex = e.detail.index;
61+ selectedShipIndex = e.detail.index;
42− // }
62+ }
43</script>
63</script>
44
64
45<!-- should actually switch on whether field is empty -->
65<!-- should actually switch on whether field is empty -->
@@ -51,32 +71,36 @@
51{/if} -->
71{/if} -->
52<!-- {#if patp}
72<!-- {#if patp}
53 <ShipView {patp} />
73 <ShipView {patp} />
54−{:else if selectedShip}
74+{:else if selectedShip}-->
55− {#if contactsList.length > 1}
75+ <!-- {#if contactsList.length > 1}
56 <SearchResultNavButtons
76 <SearchResultNavButtons
57 index={selectedShipIndex}
77 index={selectedShipIndex}
58 totalResults={contactsList.length}
78 totalResults={contactsList.length}
59 on:updateIndex={updateIndex}
79 on:updateIndex={updateIndex}
60 />
80 />
61− {/if}
81+ {/if} -->
62− <ShipView patp={selectedShip.patp} />
82+<!-- <ShipView patp={selectedShip.patp} />
63{:else} -->
83{:else} -->
64−{query}
84+<div class="max-w-md w-full rounded-lg bg-white overflow-hidden">
65−{search}
66−{#await searchedPointsP}
85+ {#await searchedPointsP}
67−Loading...
86+ <p class="p-8 text-center">
68−{:then { points }}
87+ Searching all spawned points...
69− <div class="max-w-md w-full rounded-lg bg-white">
88+ </p>
70− <!-- {#each contactsList as contact, i (contact.patp)}
89+ {:catch}
90+ <p class="p-8 text-center">
91+ Your search is too broad, please be more specific.
92+ </p>
93+ {/await}
94+ {#if combinedSearchResults.length === 0}
95+ <p class="p-8 text-center">
96+ No spawned ships match your search.
97+ </p>
98+ {:else}
99+ <ShipListings
100+ ships={combinedSearchResults}
71− <ShipListing ship={contact} on:click={() => selectedShipIndex = i}/>
101+ on:click={({ detail: i }) => selectedShipIndex = i}
72− {/each} -->
102+ linkToShips differentiateContacts
73− {#each points as patp, i (patp)}
103+ />
74− <ShipListing ship={{ patp }} linkToShip />
104+ {/if}
75− {/each}
76− </div>
105+</div>
77−{:catch}
78− <div class="max-w-md w-full rounded-lg bg-white">
79− Search Too Broad
80− </div>
81−{/await}
82−<!-- {/if} -->
106+ <!-- {/if} -->
ui/src/components/ShipListing.svelte+17−11
@@ -6,8 +6,12 @@
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
10
10 $: patp = normalizeId(ship.patp);
11 $: patp = normalizeId(ship.patp);
12+ $: borderClass = differentiateContacts ? (
13+ 'border-l-8 ' + (Object.keys(ship).length > 1 ? 'border-gold' : 'border-gray-300')
14+ ) : '';
11
15
12 function handleClick() {
16 function handleClick() {
13 if (linkToShip) {
17 if (linkToShip) {
@@ -16,15 +20,17 @@
16 }
20 }
17</script>
21</script>
18
22
19−<div class="flex flex-wrap p-4 border-b last:border-none items-center hover:bg-gray-300 cursor-pointer"
23+<div class="hover:bg-gray-200 {borderClass} cursor-pointer"
20− on:click={handleClick}
24+on:click={handleClick}
21− on:click
25+on:click
22− >
26+>
27+ <div class="flex flex-wrap p-4 items-center">
23− <Sigil {patp} size={48} />
28+ <Sigil {patp} size={48} />
24− <!-- {#if ship.avatar}
29+ {#if ship.avatar}
25− <img src={ship.avatar} class="h-12 w-12 mr-4" />
30+ <img src={ship.avatar} class="h-12 w-12 mr-4" />
26− {/if} -->
31+ {/if}
27− <p class="ml-4 break-words" >
32+ <p class="ml-4 break-words">
28− {patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
33+ {patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
29− </p>
34+ </p>
35+ </div>
30</div>
36</div>
ui/src/components/ShipListings.sveltenew file+19
@@ -0,0 +1,19 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import ShipListing from './ShipListing.svelte';
4+ const dispatch = createEventDispatcher();
5+
6+ export let ships = [];
7+ export let linkToShips = false;
8+ export let differentiateContacts = false;
9+
10+ function onClick(index) {
11+ dispatch('click', index);
12+ }
13+</script>
14+
15+<div class="divide-y">
16+ {#each ships as ship, i (ship.patp)}
17+ <ShipListing {ship} on:click={() => onClick(i)} linkToShip={linkToShips} {differentiateContacts} />
18+ {/each}
19+</div>
ui/src/components/ShipView.svelte+11−15
@@ -10,7 +10,7 @@
10 import EthAddressLink from './EthAddressLink.svelte';
10 import EthAddressLink from './EthAddressLink.svelte';
11 import CollapsibleContent from './CollapsibleContent.svelte';
11 import CollapsibleContent from './CollapsibleContent.svelte';
12 import ShipChain from './ShipChain.svelte';
12 import ShipChain from './ShipChain.svelte';
13− import ShipListing from './ShipListing.svelte';
13+ import ShipListings from './ShipListings.svelte';
14
14
15 export let patp: string;
15 export let patp: string;
16
16
@@ -103,7 +103,7 @@
103 $: {
103 $: {
104 spawnedPoints = [];
104 spawnedPoints = [];
105 if (rawSpawnedPoints?.points) {
105 if (rawSpawnedPoints?.points) {
106− spawnedPoints = rawSpawnedPoints?.points.map(patp => normalizeId(patp));
106+ spawnedPoints = rawSpawnedPoints?.points.map(patp => ({ patp: normalizeId(patp) }));
107 }
107 }
108 }
108 }
109 $: {
109 $: {
@@ -232,16 +232,11 @@
232 {:then}
232 {:then}
233 {#if shipClass !== 'galaxy'}
233 {#if shipClass !== 'galaxy'}
234 <p>
234 <p>
235− {#if pointInfo.sponsor && shipClass !== 'galaxy'}
236− <!-- Sponsor:
237− <ShipLink patp={pointInfo.sponsor}></ShipLink> -->
238−
239− {#if sponsorChain.length > 0}
235+ {#if sponsorChain && sponsorChain.length > 0}
240− <p>
236+ <p>
241− {sponsorChain.length === 1 ? 'Sponsor:' : 'Sponsor Chain:'}
237+ {sponsorChain.length === 1 ? 'Sponsor:' : 'Sponsor Chain:'}
242− <ShipChain shipChain={sponsorChain}></ShipChain>
238+ <ShipChain shipChain={sponsorChain}></ShipChain>
243− </p>
239+ </p>
244− {/if}
245 {:else}
240 {:else}
246 Unsponsored
241 Unsponsored
247 {/if}
242 {/if}
@@ -337,9 +332,10 @@
337 Loading {spawnableClassPlural}...
332 Loading {spawnableClassPlural}...
338 {:then}
333 {:then}
339 <!-- TODO: pagination -->
334 <!-- TODO: pagination -->
340− {#each spawnedPoints as patp, i}
335+ <ShipListings
341− <ShipListing ship={{ patp }} linkToShip />
336+ ships={spawnedPoints}
342− {/each}
337+ linkToShips
338+ />
343 {/await}
339 {/await}
344 </div>
340 </div>
345 {/if}
341 {/if}
ui/src/lib/api.ts+63−3
@@ -1,6 +1,14 @@
1import UrbitApi from '@urbit/http-api';
1import UrbitApi from '@urbit/http-api';
2+import type { ContactUpdate, Patp, Group, Contact, Rolodex } from '@urbit/api';
3+import _get from 'lodash/get';
4+import { compose } from 'lodash/fp';
5+
6+import { normalizeId } from './id';
7+
2console.log(`Initializing Urbit API at ${Date()}`);
8console.log(`Initializing Urbit API at ${Date()}`);
3−const api: UrbitApi = new UrbitApi('');
9+const api: UrbitApi = new UrbitApi('', '', window.desk);
10+api.ship = window.ship;
11+// api.connect();
4
12
5function scry<T = any>(path: string): Promise<T> {
13function scry<T = any>(path: string): Promise<T> {
6 return api.scry<T>({ app: 'astrolabe', path });
14 return api.scry<T>({ app: 'astrolabe', path });
@@ -14,14 +22,65 @@ function getSpawnedPoints(patp: string): Promise<any> {
14 return scry<any>(`/point/${patp}/spawned`);
22 return scry<any>(`/point/${patp}/spawned`);
15}
23}
16
24
17−function searchPoints(search: string): Promise<any> {
25+async function searchPoints(search: string): Promise<Patp[]> {
18− return scry<any>(`/search/${search}`);
26+ const { points } = await scry<any>(`/search/${search}`);
27+ return points.map(normalizeId);
19}
28}
20
29
21function getDoc(path: string): Promise<string> {
30function getDoc(path: string): Promise<string> {
22 return scry<string>(`/doc/${path}`);
31 return scry<string>(`/doc/${path}`);
23}
32}
24
33
34+function initialContacts(json: ContactUpdate, contacts: Rolodex): Rolodex {
35+ const data = _get(json, 'initial', false);
36+ if (data) {
37+ contacts = data.rolodex;
38+ }
39+ return contacts;
40+};
41+
42+function addContact(json: ContactUpdate, contacts: Rolodex): Rolodex {
43+ const data = _get(json, 'add', false);
44+ if (data) {
45+ contacts[data.ship] = data.contact;
46+ }
47+ return contacts;
48+};
49+
50+function removeContact(json: ContactUpdate, contacts: Rolodex): Rolodex {
51+ const data = _get(json, 'remove', false);
52+ if (
53+ data &&
54+ (data.ship in contacts)
55+ ) {
56+ delete contacts[data.ship];
57+ }
58+ return contacts;
59+};
60+
61+function handleContactUpdateEvent(updateContacts) {
62+ return ({ 'contact-update': contactUpdate }) => {
63+ console.log(`received contact-update: ${Object.keys(contactUpdate)}`)
64+ const reducers = [initialContacts, addContact, removeContact];
65+ const reducer = compose(reducers.map(r => sta => r(contactUpdate, sta)));
66+ updateContacts(reducer);
67+ };
68+}
69+
70+function subscribeToContacts(e: (data: any) => void): Promise<any> {
71+ return api.subscribe({
72+ app: 'contact-store',
73+ path: '/all',
74+ event: handleContactUpdateEvent(e),
75+ err: () => {
76+ console.error(`Subscription to contact-store/all just get "err"`);
77+ },
78+ quit: () => {
79+ console.error(`Subscription to contact-store/all just get "quit"`)
80+ }
81+ })
82+}
83+
25export {
84export {
26 api,
85 api,
27 scry,
86 scry,
@@ -29,4 +88,5 @@ export {
29 getSpawnedPoints,
88 getSpawnedPoints,
30 searchPoints,
89 searchPoints,
31 getDoc,
90 getDoc,
91+ subscribeToContacts,
32};
92};
ui/src/main.ts+3
@@ -1,5 +1,8 @@
1import App from './App.svelte'
1import App from './App.svelte'
2
2
3+window.desk = window.desk || 'astrolabe';
4+window.ship = window.ship || 'zod';
5+
3const app = new App({
6const app = new App({
4 target: document.getElementById('app'),
7 target: document.getElementById('app'),
5})
8})
ui/src/routes/ShipViewPage.svelte+12−4
@@ -24,11 +24,19 @@
24 if (paramChangedMoreRecently) {
24 if (paramChangedMoreRecently) {
25 search = params.patp;
25 search = params.patp;
26 } else {
26 } else {
27− if (analysis.patpIsValid && analysis.search === search) {
27+ if (analysis.search === search) {
28− if (analysis.patp === search) {
28+ if (!analysis.patpIsValid) {
29− push(linkToShip(analysis.patp));
29+ if (search) {
30+ push(`/search/${search}`);
31+ } else {
32+ push('/');
33+ }
30 } else {
34 } else {
35+ if (analysis.patp === search) {
36+ push(linkToShip(analysis.patp));
37+ } else {
31− push(linkToShip(analysis.patp, { search }));
38+ push(linkToShip(analysis.patp, { search }));
39+ }
32 }
40 }
33 }
41 }
34 }
42 }
ui/src/stores/store.ts+19−4
@@ -1,6 +1,7 @@
1import { writable } from 'svelte/store';
1import { writable } from 'svelte/store';
2
2
3−import { api } from '../lib/api';
3+import type { ContactUpdate, Patp, Group, Contact, Rolodex } from '@urbit/api';
4+import { subscribeToContacts } from '../lib/api';
4import type { StoreState, Contacts } from '../types/store';
5import type { StoreState, Contacts } from '../types/store';
5import { filterObject } from '../lib/utils';
6import { filterObject } from '../lib/utils';
6import fakeContacts from '../fake_data/fake-contacts';
7import fakeContacts from '../fake_data/fake-contacts';
@@ -9,6 +10,7 @@ const initStore: StoreState = {
9 // chats: [],
10 // chats: [],
10 // groups: {},
11 // groups: {},
11 contacts: {},
12 contacts: {},
13+ contactSearchResults: {},
12 connection: 'disconnected',
14 connection: 'disconnected',
13 ship: window.ship,
15 ship: window.ship,
14};
16};
@@ -21,16 +23,16 @@ function search(idQuery: RegExp) {
21 if (!idQuery) {
23 if (!idQuery) {
22 return {
24 return {
23 ...$store,
25 ...$store,
24− contacts: fakeContacts,
26+ contactSearchResults: $store.contacts,
25 };
27 };
26 }
28 }
27 // const filteredContacts = filterObject($store.contacts, (id, contact) => {
29 // const filteredContacts = filterObject($store.contacts, (id, contact) => {
28− const filteredContacts = filterObject(fakeContacts, (id) => {
30+ const filteredContacts = filterObject($store.contacts, (id) => {
29 return idQuery.test(id);
31 return idQuery.test(id);
30 }) as Contacts;
32 }) as Contacts;
31 return {
33 return {
32 ...$store,
34 ...$store,
33− contacts: filteredContacts,
35+ contactSearchResults: filteredContacts,
34 }
36 }
35 });
37 });
36}
38}
@@ -39,5 +41,18 @@ function reset() {
39 set(initStore);
41 set(initStore);
40}
42}
41
43
44+function updateContacts(callback: (contacts: Rolodex) => Rolodex): void {
45+ update($store => {
46+ return {
47+ ...$store,
48+ contacts: callback($store.contacts),
49+ };
50+ });
51+}
52+
53+// setTimeout(() => {
54+ subscribeToContacts(updateContacts);
55+// }, 1000);
56+
42
57
43export default { subscribe, search, reset };
58export default { subscribe, search, reset };
ui/src/styles/tailwind-output.css+33−4
@@ -720,6 +720,11 @@ Ensure the default browser behavior of the `hidden` attribute.
720 margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
720 margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
721 margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
721 margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
722}
722}
723+.divide-y > :not([hidden]) ~ :not([hidden]) {
724+ --tw-divide-y-reverse: 0;
725+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
726+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
727+}
723.justify-self-start {
728.justify-self-start {
724 justify-self: start;
729 justify-self: start;
725}
730}
@@ -729,6 +734,9 @@ Ensure the default browser behavior of the `hidden` attribute.
729.overflow-auto {
734.overflow-auto {
730 overflow: auto;
735 overflow: auto;
731}
736}
737+.overflow-hidden {
738+ overflow: hidden;
739+}
732.overflow-scroll {
740.overflow-scroll {
733 overflow: scroll;
741 overflow: scroll;
734}
742}
@@ -762,12 +770,21 @@ Ensure the default browser behavior of the `hidden` attribute.
762.border-4 {
770.border-4 {
763 border-width: 4px;
771 border-width: 4px;
764}
772}
773+.border-8 {
774+ border-width: 8px;
775+}
765.border-b {
776.border-b {
766 border-bottom-width: 1px;
777 border-bottom-width: 1px;
767}
778}
768.border-t {
779.border-t {
769 border-top-width: 1px;
780 border-top-width: 1px;
770}
781}
782+.border-l {
783+ border-left-width: 1px;
784+}
785+.border-l-8 {
786+ border-left-width: 8px;
787+}
771.border-white {
788.border-white {
772 --tw-border-opacity: 1;
789 --tw-border-opacity: 1;
773 border-color: rgb(255 255 255 / var(--tw-border-opacity));
790 border-color: rgb(255 255 255 / var(--tw-border-opacity));
@@ -784,6 +801,14 @@ Ensure the default browser behavior of the `hidden` attribute.
784 --tw-border-opacity: 1;
801 --tw-border-opacity: 1;
785 border-color: rgb(252 211 77 / var(--tw-border-opacity));
802 border-color: rgb(252 211 77 / var(--tw-border-opacity));
786}
803}
804+.border-gray-300 {
805+ --tw-border-opacity: 1;
806+ border-color: rgb(209 213 219 / var(--tw-border-opacity));
807+}
808+.border-gray-400 {
809+ --tw-border-opacity: 1;
810+ border-color: rgb(156 163 175 / var(--tw-border-opacity));
811+}
787.bg-gold {
812.bg-gold {
788 --tw-bg-opacity: 1;
813 --tw-bg-opacity: 1;
789 background-color: rgb(252 211 77 / var(--tw-bg-opacity));
814 background-color: rgb(252 211 77 / var(--tw-bg-opacity));
@@ -1068,6 +1093,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1068 --tw-bg-opacity: 1;
1093 --tw-bg-opacity: 1;
1069 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1094 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1070}
1095}
1096+.hover\:bg-gray-400:hover {
1097+ --tw-bg-opacity: 1;
1098+ background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1099+}
1100+.hover\:bg-gray-200:hover {
1101+ --tw-bg-opacity: 1;
1102+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1103+}
1071.hover\:opacity-40:hover {
1104.hover\:opacity-40:hover {
1072 opacity: 0.4;
1105 opacity: 0.4;
1073}
1106}
@@ -1106,10 +1139,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1106 max-width: 42rem;
1139 max-width: 42rem;
1107 }
1140 }
1108
1141
1109− .md\:max-w-lg {
1110− max-width: 32rem;
1111− }
1112−
1113 .md\:flex-row {
1142 .md\:flex-row {
1114 flex-direction: row;
1143 flex-direction: row;
1115 }
1144 }
ui/src/types/store.ts+5−2
@@ -1,5 +1,5 @@
1import type { ConnectionStatus } from './connection';
1import type { ConnectionStatus } from './connection';
2−import type { Patp, Group, Contact } from '@urbit/api';
2+import type { Patp, Group, Contact, Rolodex } from '@urbit/api';
3export interface Contacts {
3export interface Contacts {
4 [contact: string]: Contact;
4 [contact: string]: Contact;
5};
5};
@@ -12,7 +12,10 @@ export interface StoreState {
12 // groups: {
12 // groups: {
13 // [group: string]: Group;
13 // [group: string]: Group;
14 // };
14 // };
15− contacts: Contacts;
15+ contacts: Rolodex;
16+ contactSearchResults: Rolodex;
17+ // contacts: Contacts;
18+ // contactSearchResults: Contacts;
16 // chats: [];
19 // chats: [];
17 connection: ConnectionStatus;
20 connection: ConnectionStatus;
18 subscription?: unknown;
21 subscription?: unknown;