on main help sign in
getting sigil search closer
c233148John Hyde
desk/app/astrolabe.hoon+3
@@ -69,6 +69,9 @@
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 @ ?(~ [%narrow ~])]
71 =/ =search-text (trip &3.path)
71 =/ =search-text (trip &3.path)
72+ ?~ search-text ~|(%search-predicted-too-broad !!)
73+ ~& "search text"
74+ ~& search-text
72 =/ expand-search =(t.t.t.path ~)
75 =/ expand-search =(t.t.t.path ~)
73 ``astrolabe-point-set+!>((search-opoints:hc search-text expand-search))
76 ``astrolabe-point-set+!>((search-opoints:hc search-text expand-search))
74 [%x %peers ~]
77 [%x %peers ~]
ui/src/components/search/SearchBar.svelte+3−3
@@ -10,11 +10,11 @@
10 export let analysis: SearchAnalysis = new SearchAnalysis();
10 export let analysis: SearchAnalysis = new SearchAnalysis();
11 export let search = '';
11 export let search = '';
12 export let sigilQuery: SigilQuery = new SigilQuery('planet');
12 export let sigilQuery: SigilQuery = new SigilQuery('planet');
13+ export let showSigilInput: boolean = false;
13 export let pauseAnalysis: boolean = false;
14 export let pauseAnalysis: boolean = false;
14 export let placeholderText = "Search by @p or #. '*' matches any character";
15 export let placeholderText = "Search by @p or #. '*' matches any character";
15
16
16 let debouncedSearch = search;
17 let debouncedSearch = search;
17− let showSigilInput: boolean = false;
18
18
19 $: {
19 $: {
20 if (!pauseAnalysis) {
20 if (!pauseAnalysis) {
@@ -34,12 +34,12 @@
34
34
35 $: {
35 $: {
36 if (showSigilInput && sigilQuery.isDefinitive) {
36 if (showSigilInput && sigilQuery.isDefinitive) {
37− const querySyls = sigilQuery.query.flat();
37+ const querySyls = sigilQuery.querySyls.flat();
38 search = '~' + querySyls[0];
38 search = '~' + querySyls[0];
39 if (querySyls.length > 1) {
39 if (querySyls.length > 1) {
40 search += querySyls[1];
40 search += querySyls[1];
41 if (querySyls.length > 2) {
41 if (querySyls.length > 2) {
42− search += '-' + querySyls[2] + '-' + querySyls[3];
42+ search += '-' + querySyls[2] + querySyls[3];
43 }
43 }
44 }
44 }
45 }
45 }
ui/src/components/search/SearchResults.svelte+20−4
@@ -2,13 +2,16 @@
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;
12+ export let sigilQuery: SigilQuery;
11 export let search: string;
13 export let search: string;
14+ export let searchMode: ('patp' | 'sigil') = 'patp';
12 // export let patp: string;
15 // export let patp: string;
13 let combinedSearchResults = [];
16 let combinedSearchResults = [];
14 let searchedPointsP: Promise<any>;
17 let searchedPointsP: Promise<any>;
@@ -25,14 +28,27 @@
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+ // let otherTimer;
39+ // $: {
40+ // clearTimeout(otherTimer);
41+ // if (searchMode === 'sigil') {
42+ // otherTimer = setTimeout(() => {
43+ // searchedContacts.search(regexQuery, searchMode);
44+ // }, 1000);
45+ // }
46+ // }
31 function doSearch() {
47 function doSearch() {
32 const searchSnap = search;
48 const searchSnap = search;
33 searchStatus = 'prog';
49 searchStatus = 'prog';
34 console.log(`starting search for "${searchSnap}"`);
50 console.log(`starting search for "${searchSnap}"`);
35− searchedPointsP = searchPoints(searchSnap);
51+ searchedPointsP = searchPoints(searchSnap, searchMode);
36 searchedPointsP.then((points) => {
52 searchedPointsP.then((points) => {
37 if (search === searchSnap) {
53 if (search === searchSnap) {
38 searchedPoints = points;
54 searchedPoints = points;
@@ -48,7 +64,7 @@
48 }
64 }
49 });
65 });
50 }
66 }
51− $: search, doSearch();
67+ $: [search, sigilQuery], doSearch();
52
68
53 $: {
69 $: {
54 const contactSearchResults: Rolodex = JSON.parse(JSON.stringify($searchedContacts));
70 const contactSearchResults: Rolodex = JSON.parse(JSON.stringify($searchedContacts));
ui/src/components/search/sigil/SigilInput.svelte+14−14
@@ -68,21 +68,21 @@ import { sigil } from '@tlon/sigil-js';
68 {/if}
68 {/if}
69 </div>
69 </div>
70 </div>
70 </div>
71− {#each sigilQuery.activeSymbols as symbol}
71+ <!-- {#each sigilQuery.activeSymbols as symbol}
72− {@const plausibleSyllables = symbol.plausibleSyllables}
72+ {@const plausibleSyllables = symbol.plausibleSyllables}
73− {@const plausibleParts = symbol.plausibleParts}
73+ {@const plausibleParts = symbol.plausibleParts}
74− <p>
74+ <p>
75− {#if plausibleSyllables.length < 20 && plausibleSyllables.length > 0}
75+ {#if plausibleSyllables.length < 20 && plausibleSyllables.length > 0}
76− {plausibleSyllables.join(', ')}:
76+ {plausibleSyllables.join(', ')}:
77− {#if plausibleParts.length < 20 && plausibleParts.length > 0}
77+ {#if plausibleParts.length < 20 && plausibleParts.length > 0}
78− {plausibleParts.join(', ')}
78+ {plausibleParts.join(', ')}
79− {:else}
80− {plausibleParts.length}
81− {/if}
82 {:else}
79 {:else}
83− {plausibleSyllables.length}
80+ {plausibleParts.length}
84 {/if}
81 {/if}
82+ {:else}
83+ {plausibleSyllables.length}
84+ {/if}
85− </p>
85+ </p>
86− {/each}
86+ {/each} -->
87 <!-- {sigilQuery.string} -->
87 <!-- {sigilQuery.string} -->
88</div>
88</div>
ui/src/components/search/sigil/SymbolInput.svelte+9−3
@@ -6,6 +6,7 @@
6 import type { PartType } from 'types/sigil';
6 import type { PartType } from 'types/sigil';
7 import type { SymbolQuery } from 'lib/sigil';
7 import type { SymbolQuery } from 'lib/sigil';
8 import { filterPartsByGeon } from 'lib/sigil';
8 import { filterPartsByGeon } from 'lib/sigil';
9+ import * as colors from 'lib/colors';
9
10
10 export let symbolQuery: SymbolQuery;
11 export let symbolQuery: SymbolQuery;
11 // export let inputMode: PartType;
12 // export let inputMode: PartType;
@@ -13,6 +14,8 @@
13 export let index: number = 0;
14 export let index: number = 0;
14 export let clan: string = 'galaxy';
15 export let clan: string = 'galaxy';
15 export let inputComponents: string[] = [];
16 export let inputComponents: string[] = [];
17+ export let fgColor = 'black';
18+ export let bgColor = 'white';
16 let size = 128;
19 let size = 128;
17
20
18 let translateClass;
21 let translateClass;
@@ -72,11 +75,14 @@
72 style:max-width="{size}px"
75 style:max-width="{size}px"
73 on:click|stopPropagation
76 on:click|stopPropagation
74>
77>
78+<!-- fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? colors.GOLDS1 : 'black')} -->
75 {#if symbolQuery.components.length > 0}
79 {#if symbolQuery.components.length > 0}
76 <Symbol components={symbolQuery.components} {size}
80 <Symbol components={symbolQuery.components} {size}
77 inverted={!symbolQuery.geon}
81 inverted={!symbolQuery.geon}
78 interactive={focused}
82 interactive={focused}
79− fgColor={plausible ? 'black' : 'orange'}
83+ fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? 'black' : '#333')}
84+ bgColor="white"
85+ altBgColor="transparent"
80 hoverColor="red"
86 hoverColor="red"
81 on:partClick={onPartDelete}
87 on:partClick={onPartDelete}
82 />
88 />
@@ -89,8 +95,8 @@
89 <div class="absolute inset-0 pointer-events-none">
95 <div class="absolute inset-0 pointer-events-none">
90 <Symbol components={filteredInputComponents} {size}
96 <Symbol components={filteredInputComponents} {size}
91 strokeWidthFactor={inputContainsStrokes ? 2.5 : 1}
97 strokeWidthFactor={inputContainsStrokes ? 2.5 : 1}
92− fgColor={symbolQuery.geon ? "white" : "black"}
98+ fgColor={symbolQuery.geon ? bgColor : fgColor}
93− bgColor={!symbolQuery.geon ? "white" : "black"}
99+ bgColor={!symbolQuery.geon ? bgColor : fgColor}
94 altBgColor="transparent"
100 altBgColor="transparent"
95 opacity={0.5}
101 opacity={0.5}
96 inverted
102 inverted
ui/src/components/search/sigil/SymbolInputModeSelector.svelte+35−18
@@ -16,16 +16,17 @@
16 export let unfocusSymbol: Function;
16 export let unfocusSymbol: Function;
17
17
18 let modes: string[] = [];
18 let modes: string[] = [];
19+ // let subMode: string = undefined;
19 let modeDef: ModeDef = undefined;
20 let modeDef: ModeDef = undefined;
20 let subModeDefs: ModeDefs = MODE_DEFS;
21 let subModeDefs: ModeDefs = MODE_DEFS;
21
22
22 let buttonClasses = 'border border-gray-500 rounded-md aspect-square w-[48px]';
23 let buttonClasses = 'border border-gray-500 rounded-md aspect-square w-[48px]';
23
24
24− $: {
25+ // $: {
25− if (!symbolQuery.geon) {
26+ // if (!symbolQuery.geon) {
26− modes = ['geon'];
27+ // modes = ['geon'];
27− }
28+ // }
28− }
29+ // }
29
30
30 function pushMode(mode: string) {
31 function pushMode(mode: string) {
31 if (modeDef?.modes) {
32 if (modeDef?.modes) {
@@ -75,6 +76,29 @@
75 }
76 }
76 }
77 }
77
78
79+ $: plausibleModes = Object.entries(subModeDefs).map(([modeName, def]) => {
80+ const deepParts = getDeepParts(def);
81+ const plausible = $searchSettings.allowFictionalSigils || symbolQuery.arePartsPlausible(deepParts);
82+ if (!plausible && modeName !== 'geon') return null;
83+ const parts = filterPartsByGeon(def.displayParts || def.parts, symbolQuery.geon);
84+ return { parts, modeName };
85+ }).filter(mode => mode !== null);
86+
87+ $: {
88+ const currentMode = modes.slice(-1)[0];
89+ if (currentMode != 'geon') {
90+ if (plausibleModes.length === 0) {
91+ popMode();
92+ } else if (plausibleModes.length >= 1) {
93+ const leafMode = modeDef && !(modeDef.modes);
94+ const plausibleModeNames = plausibleModes.map(({ modeName }) => modeName);
95+ if ((modes.length == 1 && modeDef.modes) || (leafMode && !plausibleModeNames.includes(currentMode))) {
96+ pushMode(plausibleModes[0].modeName);
97+ }
98+ }
99+ }
100+ }
101+
78 function clear() {
102 function clear() {
79 if (modes.length) {
103 if (modes.length) {
80 symbolQuery = symbolQuery.clearPartType(modes[0] as PartType);
104 symbolQuery = symbolQuery.clearPartType(modes[0] as PartType);
@@ -90,26 +114,19 @@
90 }
114 }
91</style>
115</style>
92
116
93−<div class="mx-auto flex items-center justify-center xs:flex-col">
117+<div class="mx-auto flex justify-center xs:items-center xs:flex-col">
94 <div class="grid grid-cols-custom gap-1 self-start justify-center mr-1 xs:mr-0 xs:mb-1 xs:self-stretch">
118 <div class="grid grid-cols-custom gap-1 self-start justify-center mr-1 xs:mr-0 xs:mb-1 xs:self-stretch">
95 <img src={upArrow} alt="done" class={buttonClasses} on:click={popMode}/>
119 <img src={upArrow} alt="done" class={buttonClasses} on:click={popMode}/>
96 <img src={xIcon} alt="done" class={buttonClasses} on:click={clear}/>
120 <img src={xIcon} alt="done" class={buttonClasses} on:click={clear}/>
97 </div>
121 </div>
98− <div class="max-w-[15rem] grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
122+ <div class="max-w-[15rem] h-full grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
99 {#if modes[0] === 'geon'}
123 {#if modes[0] === 'geon'}
100 <GeonSelector bind:symbolQuery {popMode} />
124 <GeonSelector bind:symbolQuery {popMode} />
101 {:else}
125 {:else}
102− {#each Object.entries(subModeDefs) as [modeName, def]}
126+ {#each plausibleModes as { modeName, parts }}
103− {@const parts = filterPartsByGeon(def.displayParts || def.parts, symbolQuery.geon)}
104− {@const deepParts = getDeepParts(def)}
105− {@const plausible = $searchSettings.allowFictionalSigils || symbolQuery.arePartsPlausible(deepParts)}
106− {#if plausible || modeName === 'geon'}
107− <SymbolInputModeSelectorButton components={parts} focused={modes.slice(-1)[0] === modeName}
127+ <SymbolInputModeSelectorButton components={parts} focused={modes.slice(-1)[0] === modeName}
108− on:click={() => pushMode(modeName)}
128+ on:click={() => pushMode(modeName)}
109− />
129+ />
110− <!-- {:else}
111− {deepParts.join()} -->
112− {/if}
113 {/each}
130 {/each}
114 {/if}
131 {/if}
115 </div>
132 </div>
ui/src/lib/api.ts+1−1
@@ -26,7 +26,7 @@ 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/${search}`);
31 return points.map(normalizeId);
31 return points.map(normalizeId);
32}
32}
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+6−2
@@ -435,8 +435,12 @@ 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) {
ui/src/lib/sigil.ts+39−4
@@ -53,16 +53,20 @@ export class SigilQuery {
53 return this.activeSymbols.every(symbol => !symbol.isPlausible);
53 return this.activeSymbols.every(symbol => !symbol.isPlausible);
54 }
54 }
55
55
56− get query(): string[][] {
56+ get querySyls(): string[][] {
57 return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
57 return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
58 }
58 }
59
59
60 get isDefinitive() {
60 get isDefinitive() {
61− return this.query.every(syls => syls.length === 1);
61+ return this.querySyls.every(syls => syls.length === 1);
62+ }
63+
64+ get isNotEmpty() {
65+ return this.activeSymbols.some(symbol => symbol.components.length > 0);
62 }
66 }
63
67
64 get string(): string {
68 get string(): string {
65− return this.symbols.map((symbol) => symbol.components.join()).join(' | ')
69+ return this.symbols.map((symbol) => symbol.components.join(',')).join('|')
66 }
70 }
67
71
68 setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
72 setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
@@ -72,6 +76,22 @@ export class SigilQuery {
72 }
76 }
73}
77}
74
78
79+const equivalentParts = {
80+ lfhb: 'lfhbm',
81+ lfhbm: 'lfhb',
82+ atlf2: 'atlf2l',
83+ atlf2l: 'atlf2',
84+ atlf4: 'atlf4l',
85+ atlf4l: 'atlf4',
86+ atlf6: 'atlf6l',
87+ atlf6l: 'atlf6',
88+}
89+
90+function addEquivalentParts(parts: string[]): string[] {
91+ const moreParts = parts.map(partId => equivalentParts[partId]).filter(part => part);
92+ return [...parts, ...moreParts];
93+}
94+
75export function getPlausibleSyllables(parts: string[], symbolType: SymbolType): string[] {
95export function getPlausibleSyllables(parts: string[], symbolType: SymbolType): string[] {
76 let plausibleSyllables: string[];
96 let plausibleSyllables: string[];
77 if (symbolType === 'prefix') {
97 if (symbolType === 'prefix') {
@@ -81,7 +101,14 @@ export function getPlausibleSyllables(parts: string[], symbolType: SymbolType):
81 } else {
101 } else {
82 plausibleSyllables = [...prefixes, ...suffixes];
102 plausibleSyllables = [...prefixes, ...suffixes];
83 }
103 }
84− const possibilitiesByPart = parts.map((partId) => partParents[partId]);
104+ const possibilitiesByPart = parts.map((partId) => {
105+ let parents = partParents[partId];
106+ const otherPart = equivalentParts[partId];
107+ if (otherPart) {
108+ parents = [...parents, ...partParents[otherPart]];
109+ }
110+ return parents;
111+ });
85 plausibleSyllables = intersection(plausibleSyllables, ...possibilitiesByPart);
112 plausibleSyllables = intersection(plausibleSyllables, ...possibilitiesByPart);
86 return plausibleSyllables;
113 return plausibleSyllables;
87}
114}
@@ -89,6 +116,7 @@ export function getPlausibleSyllables(parts: string[], symbolType: SymbolType):
89export function getPlausibleNewParts(parts: string[], symbolType: SymbolType): string[] {
116export function getPlausibleNewParts(parts: string[], symbolType: SymbolType): string[] {
90 const syllables: string[] = getPlausibleSyllables(parts, symbolType);
117 const syllables: string[] = getPlausibleSyllables(parts, symbolType);
91 let plausibleParts = uniq(syllables.map((syllable) => symbolDefs[syllable]).flat());
118 let plausibleParts = uniq(syllables.map((syllable) => symbolDefs[syllable]).flat());
119+ plausibleParts = addEquivalentParts(plausibleParts);
92 return without(plausibleParts, ...parts);
120 return without(plausibleParts, ...parts);
93}
121}
94
122
@@ -185,6 +213,13 @@ export class SymbolQuery {
185 return getPlausibleNewParts(this.components, this.symbolType);
213 return getPlausibleNewParts(this.components, this.symbolType);
186 }
214 }
187
215
216+ get isPerfectMatch() {
217+ const syls = this.plausibleSyllables;
218+ if (syls.length === 1) return true;
219+ const compLength = this.components.length;
220+ return syls.map(syl => symbolDefs[syl].length).some(length => length === compLength);
221+ }
222+
188 filterPlausibleParts(parts: string[]): string[] {
223 filterPlausibleParts(parts: string[]): string[] {
189 return filterPlausibleParts(this.components, this.symbolType, parts);
224 return filterPlausibleParts(this.components, this.symbolType, parts);
190 }
225 }
ui/src/routes/Home.svelte+8−3
@@ -3,6 +3,7 @@
3 import { link, push, replace, location, querystring } from 'svelte-spa-router'
3 import { link, push, replace, location, querystring } from 'svelte-spa-router'
4 import pageName from 'stores/pageName';
4 import pageName from 'stores/pageName';
5 import { SearchAnalysis } from 'lib/id';
5 import { SearchAnalysis } from 'lib/id';
6+ import { SigilQuery } from 'lib/sigil';
6 import { linkToShip, linkToChart } from 'lib/link';
7 import { linkToShip, linkToChart } from 'lib/link';
7 import SearchBar from "@/search/SearchBar.svelte";
8 import SearchBar from "@/search/SearchBar.svelte";
8 import SearchResults from "@/search/SearchResults.svelte";
9 import SearchResults from "@/search/SearchResults.svelte";
@@ -12,6 +13,9 @@
12 import HomeScreenTiles from '@/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');
17+ let showSigilInput: boolean = false;
18+ $: searchMode = showSigilInput ? 'sigil' : 'patp';
15 let navigating = false;
19 let navigating = false;
16
20
17 let search: string = (new URLSearchParams($querystring)).get('search') || params.arg || '';
21 let search: string = (new URLSearchParams($querystring)).get('search') || params.arg || '';
@@ -136,14 +140,15 @@
136</script>
140</script>
137
141
138<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
142<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} />
143+ <SearchBar bind:analysis bind:sigilQuery bind:search bind:showSigilInput {placeholderText} />
140 <!-- <p class="bg-white">navigating</p> -->
144 <!-- <p class="bg-white">navigating</p> -->
141 {#if params.mode == "chart"}
145 {#if params.mode == "chart"}
142 <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
146 <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
143 {:else if analysis.patpIsValid}
147 {:else if analysis.patpIsValid}
144 <ShipView patp={analysis.patp} />
148 <ShipView patp={analysis.patp} />
145− {:else if (analysis.queryIsValid)}
149+ {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode == 'sigil' && sigilQuery.isNotEmpty)}
146− <SearchResults query={analysis.query} search={analysis.search} />
150+ <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery} search={analysis.search}
151+ {searchMode} />
147 {:else if (analysis.search.length > 0)}
152 {:else if (analysis.search.length > 0)}
148 <ValidationProblems problems={analysis.queryProblems} />
153 <ValidationProblems problems={analysis.queryProblems} />
149 {:else}
154 {:else}
ui/src/stores/searchStores.ts+31−7
@@ -4,19 +4,29 @@ import type { Contact, Rolodex } from '@urbit/api';
4import store from './store';
4import store from './store';
5import type { StoreState, SearchedContactsState } from 'types/store';
5import type { StoreState, SearchedContactsState } from 'types/store';
6import { SearchSettings } from 'types/store';
6import { SearchSettings } from 'types/store';
7+import { splitIdIntoSyls } from 'lib/id';
7import { filterObject } from 'lib/utils';
8import { 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+11−4
@@ -9,7 +9,8 @@ import { setStoreKey } from 'lib/utils';
9const initStore: StoreState = {
9const initStore: StoreState = {
10 contacts: {},
10 contacts: {},
11 peers: {},
11 peers: {},
12− query: null,
12+ patpQuery: null,
13+ sigilQuery: null,
13 connection: 'disconnected',
14 connection: 'disconnected',
14 ship: window.ship,
15 ship: window.ship,
15};
16};
@@ -17,8 +18,14 @@ const initStore: StoreState = {
17const store = writable(initStore);
18const store = writable(initStore);
18const { subscribe, update, set } = store;
19const { subscribe, update, set } = store;
19
20
20−function search(idQuery: RegExp) {
21+function searchPatp(patpQuery: RegExp) {
21− setStoreKey(store, 'query', idQuery);
22+ setStoreKey(store, 'patpQuery', patpQuery);
23+ setStoreKey(store, 'searchMode', 'patp');
24+}
25+
26+function searchSigil(syls: string[][]) {
27+ setStoreKey(store, 'sigilQuery', syls);
28+ setStoreKey(store, 'searchMode', 'sigil');
22}
29}
23
30
24function reset() {
31function reset() {
@@ -40,4 +47,4 @@ getPeers().then(({ points }) => {
40 setStoreKey(store, 'peers', points.map(normalizeId));
47 setStoreKey(store, 'peers', points.map(normalizeId));
41});
48});
42
49
43−export default { subscribe, set, search, reset };
50+export default { subscribe, set, searchPatp, searchSigil, reset };
ui/src/styles/tailwind-output.css+18−65
@@ -690,15 +690,15 @@ Ensure the default browser behavior of the `hidden` attribute.
690.ml-auto {
690.ml-auto {
691 margin-left: auto;
691 margin-left: auto;
692}
692}
693−.-mt-0\.5 {
694− margin-top: -0.125rem;
695−}
696.mb-1 {
693.mb-1 {
697 margin-bottom: 0.25rem;
694 margin-bottom: 0.25rem;
698}
695}
699.mr-1 {
696.mr-1 {
700 margin-right: 0.25rem;
697 margin-right: 0.25rem;
701}
698}
699+.-mt-0\.5 {
700+ margin-top: -0.125rem;
701+}
702.block {
702.block {
703 display: block;
703 display: block;
704}
704}
@@ -849,15 +849,9 @@ Ensure the default browser behavior of the `hidden` attribute.
849.w-16 {
849.w-16 {
850 width: 4rem;
850 width: 4rem;
851}
851}
852−.w-\[50\%\] {
853− width: 50%;
854−}
855.w-\[48px\] {
852.w-\[48px\] {
856 width: 48px;
853 width: 48px;
857}
854}
858−.w-\[\] {
859− width: ;
860−}
861.min-w-0 {
855.min-w-0 {
862 min-width: 0px;
856 min-width: 0px;
863}
857}
@@ -879,20 +873,11 @@ Ensure the default browser behavior of the `hidden` attribute.
879.max-w-2xl {
873.max-w-2xl {
880 max-width: 42rem;
874 max-width: 42rem;
881}
875}
882−.max-w-\[18rem\] {
883− max-width: 18rem;
884−}
885.max-w-\[10rem\] {
876.max-w-\[10rem\] {
886 max-width: 10rem;
877 max-width: 10rem;
887}
878}
888−.max-w-\[21rem\] {
879+.max-w-\[18rem\] {
889− max-width: 21rem;
880+ max-width: 18rem;
890−}
891−.max-w-\[20rem\] {
892− max-width: 20rem;
893−}
894−.max-w-\[19rem\] {
895− max-width: 19rem;
896}
881}
897.max-w-\[15rem\] {
882.max-w-\[15rem\] {
898 max-width: 15rem;
883 max-width: 15rem;
@@ -936,14 +921,6 @@ Ensure the default browser behavior of the `hidden` attribute.
936.origin-bottom-left {
921.origin-bottom-left {
937 transform-origin: bottom left;
922 transform-origin: bottom left;
938}
923}
939−.translate-x-1\/4 {
940− --tw-translate-x: 25%;
941− 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));
942−}
943−.translate-y-1\/4 {
944− --tw-translate-y: 25%;
945− 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));
946−}
947.translate-x-0 {
924.translate-x-0 {
948 --tw-translate-x: 0px;
925 --tw-translate-x: 0px;
949 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));
926 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,6 +929,14 @@ Ensure the default browser behavior of the `hidden` attribute.
952 --tw-translate-y: 0px;
929 --tw-translate-y: 0px;
953 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));
930 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));
954}
931}
932+.translate-x-1\/4 {
933+ --tw-translate-x: 25%;
934+ 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));
935+}
936+.translate-y-1\/4 {
937+ --tw-translate-y: 25%;
938+ 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));
939+}
955.-translate-x-1\/4 {
940.-translate-x-1\/4 {
956 --tw-translate-x: -25%;
941 --tw-translate-x: -25%;
957 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));
942 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));
@@ -1227,12 +1212,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1227.border-2 {
1212.border-2 {
1228 border-width: 2px;
1213 border-width: 2px;
1229}
1214}
1230−.border-\[1\.5px\] {
1231− border-width: 1.5px;
1232−}
1233.border-0 {
1215.border-0 {
1234 border-width: 0px;
1216 border-width: 0px;
1235}
1217}
1218+.border-\[1\.5px\] {
1219+ border-width: 1.5px;
1220+}
1236.border-l-8 {
1221.border-l-8 {
1237 border-left-width: 8px;
1222 border-left-width: 8px;
1238}
1223}
@@ -1447,24 +1432,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1447.p-0 {
1432.p-0 {
1448 padding: 0px;
1433 padding: 0px;
1449}
1434}
1450−.p-0\.5 {
1451− padding: 0.125rem;
1452−}
1453.p-\[3px\] {
1435.p-\[3px\] {
1454 padding: 3px;
1436 padding: 3px;
1455}
1437}
1456.p-\[4px\] {
1438.p-\[4px\] {
1457 padding: 4px;
1439 padding: 4px;
1458}
1440}
1459−.p-\[2\.5px\] {
1460− padding: 2.5px;
1461−}
1462−.p-\[2\.75px\] {
1463− padding: 2.75px;
1464−}
1465−.p-\[2px\] {
1466− padding: 2px;
1467−}
1468.px-2 {
1441.px-2 {
1469 padding-left: 0.5rem;
1442 padding-left: 0.5rem;
1470 padding-right: 0.5rem;
1443 padding-right: 0.5rem;
@@ -1783,26 +1756,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1783 max-width: 8rem;
1756 max-width: 8rem;
1784 }
1757 }
1785
1758
1786− .xs\:grid-flow-row {
1787− grid-auto-flow: row;
1788− }
1789−
1790− .xs\:grid-flow-col {
1791− grid-auto-flow: column;
1792− }
1793−
1794− .xs\:grid-flow-row-dense {
1795− grid-auto-flow: row dense;
1796− }
1797−
1798− .xs\:grid-cols-2 {
1799− grid-template-columns: repeat(2, minmax(0, 1fr));
1800− }
1801−
1802− .xs\:grid-rows-3 {
1803− grid-template-rows: repeat(3, minmax(0, 1fr));
1804− }
1805−
1806 .xs\:flex-row {
1759 .xs\:flex-row {
1807 flex-direction: row;
1760 flex-direction: row;
1808 }
1761 }
@@ -1811,8 +1764,8 @@ Ensure the default browser behavior of the `hidden` attribute.
1811 flex-direction: column;
1764 flex-direction: column;
1812 }
1765 }
1813
1766
1814− .xs\:items-stretch {
1767+ .xs\:items-center {
1815− align-items: stretch;
1768+ align-items: center;
1816 }
1769 }
1817
1770
1818 .xs\:self-stretch {
1771 .xs\:self-stretch {
ui/src/types/store.ts+3−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: [];