on main help sign in
make sigil search better!
commit47686e02438f014a52fb9b39aa6110ff7deb2902 authorJohn Hyde <johnhydefour@gmail.com> dateFri 30 Sep 2022, 02:00 (4 years ago) parent8458711
16 files changed+360−141
.vscode/tasks.json+13 README.md+4−9 bin/commit.sh+25 bin/install.sh+48−17 desk/lib/search.hoon+1−1 ui/package.json+3−1 ui/src/components/search/SearchBar.svelte+63−37 ui/src/components/search/sigil/SigilInput.svelte+12−4 ui/src/components/search/sigil/SymbolInput.svelte+15−13 ui/src/components/search/sigil/SymbolInputModeSelector.svelte+12−17 ui/src/components/search/sigil/SymbolInputNavButtons.svelte+25 ui/src/lib/id.ts+2−2 ui/src/lib/link.ts+5−1 ui/src/lib/sigil.ts+61−1 ui/src/routes/Home.svelte+57−36 ui/src/styles/tailwind-output.css+14−2
.vscode/tasks.jsonnew file+13
@@ -0,0 +1,13 @@
1+{
2+ "version": "2.0.0",
3+ "tasks": [
4+ {
5+ "type": "npm",
6+ "script": "commit",
7+ "path": "ui",
8+ "problemMatcher": [],
9+ "label": "npm: commit - ui",
10+ "detail": "dotenv -c local npm run _commit"
11+ }
12+ ]
13+}
README.md+4−9
@@ -62,15 +62,10 @@ Initial Setup:
6262 3. Set `GLOBBER_DESK` in `.env.local` if the path to your globber desk is different.
6363
6464Every Time:
65−4. From the `ui` directory, run `npm run release` to build the ui and copy it to your globber desk
66−5. From the globber ship: `|commit %globber` (create basic mark files (see `png.hoon`) for any files without mark files)
67−6. Now run `=dir /=garden=` to switch to the garden desk directory
68−7. `-make-glob %globber /astrolabe` which will take the folder where you just added files and create a glob which can be thought of as a sort of bundle. It will be output to `~/zod/.urb/put`.
69−8. `=dir` to return to base desk.
70−8. From the `ui` directory, run `npm run get-glob` to place the latest glob in the `globs` folder.
71−9. Copy the string printed by the last step, and paste it into `desk.docket-0`.
72−8. Upload the glob in `/globs/` to any publicly available HTTP endpoint that can serve files. This allows the glob to distributed over HTTP.
73−10. Once you've uploaded the glob, paste the url into `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
65+3. From the `ui` directory, run `npm run release` to build the ui, make the glob, and copy it to the `globs` folder. The command will also print out the glob hash/id.
66+4. Copy the string printed by the last step, and paste it into `desk.docket-0`.
67+5. Upload the glob in `/globs/` to any publicly available HTTP endpoint that can serve files. This allows the glob to be distributed over HTTP.
68+6. Once you've uploaded the glob, paste the url into `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
7469 ```hoon
7570 glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
7671 ```
bin/commit.shnew file+25
@@ -0,0 +1,25 @@
1+#!/bin/bash
2+
3+usage() { printf "Usage: $0 URBIT_DESK_DIRECTORY\n" 1>&2; exit 1; }
4+
5+cdir=$(dirname $0)
6+
7+if [ $# -eq 0 ]; then
8+ usage
9+ exit 2
10+fi
11+DESK_DIR=${@: -1}
12+DESK_DIR="${DESK_DIR/#\~/$HOME}"
13+PIER=$(dirname $DESK_DIR)
14+DESK=$(basename $DESK_DIR)
15+
16+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
17+
18+lensa() {
19+ curl -s \
20+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
21+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
22+}
23+
24+
25+lensa 'hood' "+hood/commit %$DESK"
bin/install.sh+48−17
@@ -3,6 +3,8 @@
33# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
44# by ~timluc-miptev
55#
6+# Modified HEAVILY
7+#
68usage() { printf "Usage: $0 [-w] [-g] URBIT_DESK_DIRECTORY\n(-w: flag to watch and live copy code)\n(-g: install built ui to globber desk)\n" 1>&2; exit 1; }
79
810cdir=$(dirname $0)
@@ -11,9 +13,36 @@ if [ $# -eq 0 ]; then
1113 usage
1214 exit 2
1315fi
14−DESK=${@: -1}
16+DESK_DIR=${@: -1}
17+DESK_DIR="${DESK_DIR/#\~/$HOME}"
18+PIER=$(dirname $DESK_DIR)
19+DESK=$(basename $DESK_DIR)
1520EXCLUDE_FILE="${cdir}"/../config/ignore_files_on_install.txt
1621
22+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
23+
24+lensd() {
25+ curl -s \
26+ --data "{\"source\":{\"dojo\":\"$1\"},\"sink\":{\"stdout\":null}}" \
27+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
28+}
29+
30+lensa() {
31+ curl -s \
32+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
33+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
34+}
35+
36+sync() {
37+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK_DIR/
38+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK_DIR/
39+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK_DIR/
40+ if [ "$GLOBBER" ]; then
41+ rsync -r --copy-links "${cdir}"/../ui/dist/* $DESK_DIR/astrolabe
42+ fi
43+}
44+
45+
1746while getopts "wg" opt; do
1847 case ${opt} in
1948 w) WATCH_MODE="true"
@@ -24,28 +53,30 @@ while getopts "wg" opt; do
2453 ;;
2554 esac
2655done
27−DESK="${DESK//\~/$HOME}"
56+
2857if [ -z "$WATCH_MODE" ]; then
29− echo "Installed %astrolabe to ${DESK}"
30− rm -r $DESK/*
31− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK/
32− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK/
33− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK/
58+ echo "Installed %astrolabe to ${DESK_DIR}"
59+ rm -r $DESK_DIR/*
60+ sync
61+ lensa 'hood' "+hood/commit %$DESK"
3462 if [ "$GLOBBER" ]; then
35− rsync -r --copy-links "${cdir}"/../ui/dist/* $DESK/astrolabe
63+ lensd '-garden!make-glob %globber /astrolabe'
64+ GLOBS="$PIER/.urb/put"
65+ GLOB=$(ls -t $GLOBS | head -1)
66+ TARGET_GLOBS="$cdir"/../globs/
67+ mkdir -p $TARGET_GLOBS
68+ cp "$GLOBS/$GLOB" $TARGET_GLOBS/
69+ echo "copied from $GLOBS to $TARGET_GLOBS"
70+ HASH="${GLOB//glob-/}"
71+ HASH="${HASH//.glob/}"
72+ echo $HASH
3673 fi
3774else
38− echo "Watching for changes to copy to ${DESK}..."
39− rm -r $DESK/*
75+ echo "Watching for changes to copy to ${DESK_DIR}..."
76+ rm -r $DESK_DIR/*
4077 while [ 0 ]
4178 do
4279 sleep 0.8
43− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK/
44− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK/
45− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK/
46− if [ "$GLOBBER" ]; then
47− echo "heheh"
48− rsync -r --copy-links "${cdir}"/../ui/dist/* $DESK/astrolabe
49− fi
80+ sync
5081 done
5182fi
desk/lib/search.hoon+1−1
@@ -145,7 +145,7 @@
145145 %+ turn
146146 (split "." ints-tape)
147147 |= =tape
148− ~& tape
148+ :: ~& tape
149149 `@q`(slav %ud (crip tape))
150150::
151151++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
ui/package.json+3−1
@@ -19,7 +19,9 @@
1919 "install:desk:watch": "dotenv -c local npm run _install:desk:watch",
2020 "release": "npm run build && npm run install:glob",
2121 "_get-glob": "../bin/get_glob.sh $GLOBBER_DESK",
22− "get-glob": "dotenv -c local npm run _get-glob"
22+ "get-glob": "dotenv -c local npm run _get-glob",
23+ "_commit": "../bin/commit.sh $ASTROLABE_DESK",
24+ "commit": "dotenv -c local npm run _commit"
2325 },
2426 "devDependencies": {
2527 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
ui/src/components/search/SearchBar.svelte+63−37
@@ -1,64 +1,83 @@
11<script lang="ts">
22 import SigilInput from './sigil/SigilInput.svelte';
3−
3+ import Symbol from './sigil/Symbol.svelte';
44
55 import { SearchAnalysis } from 'lib/id';
66 import { SigilQuery } from 'lib/sigil';
77
88 export let patp: string = undefined;
9− export let searchQuery: RegExp = /~der/;
109 export let analysis: SearchAnalysis = new SearchAnalysis();
1110 export let search = '';
1211 export let sigilQuery: SigilQuery = new SigilQuery('planet');
13− export let showSigilInput: boolean = false;
14− export let pauseAnalysis: boolean = false;
12+ sigilQuery.urlString = search;
13+ export let searchMode: ('patp' | 'sigil') = 'patp';
1514 export let placeholderText = "Search by @p or #. '*' matches any character";
15+
16+ $: showSigilInput = searchMode == 'sigil';
1617
17− let debouncedSearch = search;
18+ let bouncySearch: string = search;
19+ let debouncedSearch: string = bouncySearch;
1820
21+ let timer;
1922 $: {
20− if (!pauseAnalysis) {
21− analysis = new SearchAnalysis(debouncedSearch);
22− if (analysis.queryIsValid) {
23− searchQuery = analysis.query;
24− } else {
25− searchQuery = undefined;
23+ if (!showSigilInput){
24+ if (bouncySearch !== search) {
25+ clearTimeout(timer);
26+ timer = setTimeout(() => {
27+ search = bouncySearch;
28+ }, 300);
2629 }
27− if (analysis.patpIsValid) {
28− patp = analysis.patp;
29− } else {
30− patp = undefined;
30+ }
31+ }
32+
33+ function updateSigilUrlString() {
34+ if (showSigilInput) {
35+ if (search !== sigilQuery.urlString) {
36+ sigilQuery.urlString = search;
3137 }
3238 }
3339 }
3440
35− $: {
36− if (showSigilInput && sigilQuery.isDefinitive) {
37− const querySyls = sigilQuery.querySyls.flat();
38− search = '~' + querySyls[0];
39− if (querySyls.length > 1) {
40− search += querySyls[1];
41− if (querySyls.length > 2) {
42− search += '-' + querySyls[2] + querySyls[3];
43− }
41+ function updateSigilSearch() {
42+ if (showSigilInput) {
43+ if (sigilQuery.isNotEmpty) {
44+ search = sigilQuery.urlString;
45+ } else {
46+ search = '';
4447 }
4548 }
4649 }
4750
48− let timer;
51+ $: search, updateSigilUrlString();
52+ $: sigilQuery.urlString, updateSigilSearch();
53+
4954 $: {
50− clearTimeout(timer);
51− timer = setTimeout(() => {
52− debouncedSearch = search;
53− }, 300);
54− }
55+ if (showSigilInput) {
56+ if (sigilQuery.isDefinitive) {
57+ patp = sigilQuery.patp;
58+ } else {
59+ patp = undefined;
60+ }
61+ } else {
62+ analysis = new SearchAnalysis(search);
63+ if (analysis.patpIsValid) {
64+ patp = analysis.patp;
65+ } else {
66+ patp = undefined;
67+ }
68+ bouncySearch = search = analysis.search;
69+ }
70+ }
5571
5672 function openSigilInput() {
57− showSigilInput = true;
73+ searchMode = 'sigil';
5874 }
5975
6076 function closeSigilInput() {
61− showSigilInput = false;
77+ searchMode = 'patp';
78+ bouncySearch = patp || analysis.search;
79+ debouncedSearch = bouncySearch;
80+ search = debouncedSearch;
6281 }
6382</script>
6483
@@ -69,18 +88,25 @@
6988 </div>
7089 <button
7190 on:click={closeSigilInput}
72− class="absolute top-2 right-2 w-20 h-6 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"
73− />
91+ class="absolute top-2 right-2 w-20 h-6 rounded-full bg-black text-white text-sm transition-opacity opacity-50 hover:opacity-80"
92+ >
93+ patp or #
94+ </button>
7495 {:else}
7596 <input type="text" class="p-4 py-2 w-full rounded-full"
7697 spellcheck="false"
7798 placeholder={placeholderText}
78− bind:value={search}
99+ on:input={(e) => bouncySearch = e.target.value}
100+ value={search}
79101 autofocus
80102 />
81103 <button
82104 on:click={openSigilInput}
83− class="absolute top-1 right-1 w-8 h-8 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"
84− />
105+ class="absolute top-1 right-1 w-8 h-8 transition-opacity opacity-50 hover:opacity-80"
106+ >
107+ <Symbol components={'gci.lvf.lhf.lbhb.atrf4.al3.cbl2lf.cbl2mb'.split('.')} size={32}
108+ altBgColor="transparent"
109+ />
110+ </button>
85111 {/if}
86112</div>
ui/src/components/search/sigil/SigilInput.svelte+12−4
@@ -1,5 +1,6 @@
11<script lang="ts">
22 import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
3+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
34 import SymbolInput from './SymbolInput.svelte';
45 import ClanSelect from './ClanSelect.svelte';
56 import ToggleButton from '@/buttons/ToggleButton.svelte';
@@ -33,6 +34,10 @@ import { sigil } from '@tlon/sigil-js';
3334 toggleStoreKey(searchSettings, 'allowFictionalSigils');
3435 }
3536
37+ function clear() {
38+ sigilQuery = sigilQuery.clearSymbols();
39+ }
40+
3641 $: {
3742 sigilQuery.allowFictional = $searchSettings.allowFictionalSigils;
3843 }
@@ -55,16 +60,19 @@ import { sigil } from '@tlon/sigil-js';
5560 {/each}
5661 </div>
5762 </div>
58− <div class="p-2 border-t grow rounded-b-2xl xs:border-l xs:border-t-0 xs:rounded-bl-none">
63+ <div class="px-4 py-2 border-t grow rounded-b-2xl xs:border-l xs:border-t-0 xs:rounded-bl-none">
5964 {#if focusedSymbol}
6065 <SymbolInputModeSelector
6166 bind:symbolQuery={focusedSymbol}
6267 bind:inputComponents
6368 unfocusSymbol={unfocusSymbol}
6469 />
65− {:else}
66− Allow Sigils which don't exist:
67− <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
70+ {:else}
71+ <SymbolInputNavButtons onClear={sigilQuery.isNotEmpty ? clear : null} />
72+ <p class="mt-2">
73+ Allow Sigils which don't exist:
74+ <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
75+ </p>
6876 {/if}
6977 </div>
7078 </div>
ui/src/components/search/sigil/SymbolInput.svelte+15−13
@@ -20,19 +20,20 @@
2020
2121 let translateClass;
2222 $: {
23− if (focusedSymbolIndex === undefined) {
24− translateClass = 'translate-x-0 translate-y-0';
25− } else if (clan === 'star') {
26− translateClass = index ? 'origin-right' : 'origin-left';
27− } else if (clan === 'planet') {
28− let cornerIndex = index;
29− if (!focused) cornerIndex = focusedSymbolIndex;
30− switch (cornerIndex) {
31− case 0: translateClass = 'translate-x-1/4 translate-y-1/4'; break;
32− case 1: translateClass = '-translate-x-1/4 translate-y-1/4'; break;
33− case 2: translateClass = 'translate-x-1/4 -translate-y-1/4'; break;
34− case 3:
35− default: translateClass = '-translate-x-1/4 -translate-y-1/4';
23+ translateClass = 'translate-x-0 translate-y-0';
24+ if (focusedSymbolIndex !== undefined) {
25+ if (clan === 'star') {
26+ translateClass = index ? 'origin-right' : 'origin-left';
27+ } else if (clan === 'planet') {
28+ let cornerIndex = index;
29+ if (!focused) cornerIndex = focusedSymbolIndex;
30+ switch (cornerIndex) {
31+ case 0: translateClass = 'translate-x-1/4 translate-y-1/4'; break;
32+ case 1: translateClass = '-translate-x-1/4 translate-y-1/4'; break;
33+ case 2: translateClass = 'translate-x-1/4 -translate-y-1/4'; break;
34+ case 3:
35+ default: translateClass = '-translate-x-1/4 -translate-y-1/4';
36+ }
3637 }
3738 }
3839 }
@@ -80,6 +81,7 @@
8081 <Symbol components={symbolQuery.components} {size}
8182 inverted={!symbolQuery.geon}
8283 interactive={focused}
84+ strokeWidthFactor={focused ? 2 : 1}
8385 fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? 'black' : '#333')}
8486 bgColor="white"
8587 altBgColor="transparent"
ui/src/components/search/sigil/SymbolInputModeSelector.svelte+12−17
@@ -1,4 +1,6 @@
11<script lang="ts">
2+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
3+
24 import GeonSelector from './GeonSelector.svelte';
35 import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
46
@@ -8,25 +10,21 @@
810 import { MODE_DEFS } from 'types/sigil';
911 import { filterPartsByGeon, getDeepParts } from 'lib/sigil';
1012
11− import upArrow from 'assets/up-arrow.svg';
12− import xIcon from 'assets/x.svg';
13−
14− export let symbolQuery: SymbolQuery;
13+ export let symbolQuery: SymbolQuery = null;
1514 export let inputComponents: string[] = [];
16− export let unfocusSymbol: Function;
15+ export let unfocusSymbol: Function = () => {};
1716
1817 let modes: string[] = [];
19− // let subMode: string = undefined;
2018 let modeDef: ModeDef = undefined;
2119 let subModeDefs: ModeDefs = MODE_DEFS;
2220
23− let buttonClasses = 'border border-gray-500 rounded-md aspect-square w-[48px]';
21+ function showGeonSelector() {
22+ if (!symbolQuery.components.length && modes.length == 0) {
23+ modes = ['geon'];
24+ }
25+ }
2426
25− // $: {
26− // if (!symbolQuery.geon) {
27− // modes = ['geon'];
28− // }
29− // }
27+ $: symbolQuery.components, showGeonSelector();
3028
3129 function pushMode(mode: string) {
3230 if (modeDef?.modes) {
@@ -115,11 +113,8 @@
115113</style>
116114
117115<div class="mx-auto flex justify-center xs:items-center xs:flex-col">
118− <div class="grid grid-cols-custom gap-1 self-start justify-center mr-1 xs:mr-0 xs:mb-1 xs:self-stretch">
119− <img src={upArrow} alt="done" class={buttonClasses} on:click={popMode}/>
120− <img src={xIcon} alt="done" class={buttonClasses} on:click={clear}/>
121− </div>
122− <div class="max-w-[15rem] h-full grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
116+ <SymbolInputNavButtons onDone={popMode} onClear={clear} />
117+ <div class="max-w-[15rem] h-full grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
123118 {#if modes[0] === 'geon'}
124119 <GeonSelector bind:symbolQuery {popMode} />
125120 {:else}
ui/src/components/search/sigil/SymbolInputNavButtons.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import upArrow from 'assets/up-arrow.svg';
3+ import xIcon from 'assets/x.svg';
4+
5+ export let onDone: () => void = null;
6+ export let onClear: () => void = null;
7+
8+ let buttonClasses = 'cursor-pointer border border-gray-500 rounded-md aspect-square w-[48px]';
9+</script>
10+
11+<style lang="scss">
12+ .grid-cols-custom {
13+ grid-template-columns: repeat(auto-fill, 48px);
14+ }
15+</style>
16+
17+<div class="grid gap-1 self-start justify-center mr-1 xs:mr-0 xs:mb-1 xs:self-stretch {(onDone && onClear) ? 'grid-cols-custom' : ''}">
18+ {#if onDone}
19+ <img src={upArrow} alt="done" class={buttonClasses} on:click={onDone}/>
20+ {/if}
21+ {#if onClear}
22+ <img src={xIcon} alt="clear" class={buttonClasses} on:click={onClear}/>
23+ {/if}
24+ </div>
25+
ui/src/lib/id.ts+2−2
@@ -445,8 +445,8 @@ export function splitIdIntoSyls(str: string): string[] {
445445
446446function padWithWild(search: string) {
447447 let words = search.split(/[_^]/);
448− words[0] = '*'.repeat(6 - words[0].length) + words[0];
449− words[1] += '*'.repeat(6 - words[1].length);
448+ words[0] = '*'.repeat(Math.max(0, 6 - words[0].length)) + words[0];
449+ words[1] += '*'.repeat(Math.max(0, 6 - words[1].length));
450450 return words.join(search.match(/([_^])/)[1]);
451451}
452452
ui/src/lib/link.ts+5−1
@@ -4,9 +4,13 @@ export function linkToShip(patp: string, queryParams = {}): string {
44 return `/ship/${patp}` + buildQuerystring(queryParams);
55}
66
7+export function linkToSearch(search: string, queryParams = {}): string {
8+ return `/search/${search}` + buildQuerystring(queryParams);
9+}
10+
711export function linkToChart(patp: string): string {
812 if (patp) {
9− return `/chart/${patp}`;
13+ return `/chart/${patp}`;
1014 }
1115 return `/chart`;
1216}
ui/src/lib/sigil.ts+61−1
@@ -42,6 +42,12 @@ export class SigilQuery {
4242 this.symbols = this.symbols;
4343 }
4444
45+ clearSymbols() {
46+ this.symbols = [];
47+ this.padSymbols();
48+ return this;
49+ }
50+
4551 get activeSymbols() {
4652 if (this.clan === 'galaxy')
4753 return [this.symbols[1]];
@@ -54,6 +60,10 @@ export class SigilQuery {
5460 return this.activeSymbols.every(symbol => symbol.isPlausible);
5561 }
5662
63+ get queryParts(): string[][] {
64+ return this.activeSymbols.map(symbol => symbol.components);
65+ }
66+
5767 get querySyls(): string[][] {
5868 return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
5969 }
@@ -73,12 +83,56 @@ export class SigilQuery {
7383 get isWorthSearching() {
7484 if (!this.isNotEmpty) return false; // 🙃
7585 if (!this.isPlausible) return false;
76− return 100000 > this.querySyls.reduce((acc, syls) => acc * (syls.length || 256), 1);
86+ return true;
7787 }
7888
7989 get string(): string {
8090 return this.queryInts.map((int) => int.length == 256 ? 'any' : int.join('.')).join('_');
8191 }
92+
93+ get urlString(): string {
94+ return this.queryParts.map(parts => parts.join('.')).join('_');
95+ }
96+
97+ set urlString(str: string) {
98+ if (!str) return;
99+ const queryParts = str.split('_').map(sym => sym.split('.').filter(part => part !== ''));
100+ switch (queryParts.length) {
101+ case 1:
102+ this.clan = 'galaxy';
103+ break;
104+ case 2:
105+ this.clan = 'star';
106+ break;
107+ case 4:
108+ this.clan = 'planet';
109+ break;
110+ default:
111+ return;
112+ }
113+ try {
114+ this.symbols = queryParts.map((parts) => {
115+ return new SymbolQuery(this, parts);
116+ });
117+ } catch (e) {
118+ console.error("Couldn't load in sigil query text", e);
119+ this.symbols = [];
120+ }
121+ this.padSymbols();
122+ }
123+
124+ get patp(): string {
125+ if (!this.isDefinitive) return '';
126+ const querySyls = this.querySyls.flat();
127+ let patp = '~' + querySyls[0];
128+ if (querySyls.length > 1) {
129+ patp += querySyls[1];
130+ if (querySyls.length > 2) {
131+ patp += '-' + querySyls[2] + querySyls[3];
132+ }
133+ }
134+ return patp;
135+ }
82136
83137 setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
84138 if (activeSymbolIndex >= this.activeSymbols.length)
@@ -192,6 +246,11 @@ export function filterPartsByGeon(parts: string[], geon: Geon): string[] {
192246 return parts;
193247}
194248
249+export function filterFakeParts(parts: string[], symbolType: SymbolType): string[] {
250+ let plausibleSyllables = getSyllablesBySymbolType(symbolType);
251+ return parts.filter(part => !plausibleSyllables.includes(part));
252+}
253+
195254export class SymbolQuery {
196255 sigilQuery?: SigilQuery;
197256 symbolType: SymbolType;
@@ -254,6 +313,7 @@ export class SymbolQuery {
254313 cleanupParts() {
255314 this.components = uniq(this.components);
256315 this.components = filterPartsByGeon(this.components, this.geon);
316+ this.components = filterFakeParts(this.components, this.symbolType);
257317 // if (!this.allowFictional) {
258318 // this.components = this.plausibleParts;
259319 // }
ui/src/routes/Home.svelte+57−36
@@ -1,10 +1,10 @@
11<script lang="ts">
2− import Router from 'svelte-spa-router';
3− import { link, push, replace, location, querystring } from 'svelte-spa-router'
2+ import { push, replace, location, querystring } from 'svelte-spa-router'
43 import pageName from 'stores/pageName';
54 import { SearchAnalysis } from 'lib/id';
65 import { SigilQuery } from 'lib/sigil';
7− import { linkToShip, linkToChart } from 'lib/link';
6+ import { buildQuerystring } from 'lib/utils';
7+ import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
88 import SearchBar from "@/search/SearchBar.svelte";
99 import SearchResults from "@/search/SearchResults.svelte";
1010 import ShipView from '@/ShipView.svelte';
@@ -14,26 +14,30 @@
1414 export let params: any = {};
1515 let analysis: SearchAnalysis = new SearchAnalysis();
1616 let sigilQuery: SigilQuery = new SigilQuery('planet');
17− let showSigilInput: boolean = false;
18− $: searchMode = showSigilInput ? 'sigil' : 'patp';
1917 let navigating = false;
20−
21− let search: string = (new URLSearchParams($querystring)).get('search') || params.arg || '';
18+
19+ let searchParams = new URLSearchParams($querystring);
20+ function getUrlSearchMode(searchParams) {
21+ return (searchParams.get('mode') == 'sigil') ? 'sigil' : 'patp';
22+ }
23+ let searchMode: ('patp' | 'sigil') = getUrlSearchMode(searchParams);
24+ let search: string = searchParams.get('search') || params.arg || '';
25+ let patp: string = undefined;
2226 let urlSearch = search;
23− // let urlChanged;
24− let urlChangedSinceAnalysisPatp = false;
27+ let urlSearchMode = searchMode;
28+ let urlChangedSinceShipQuery = false;
2529 $: {
26− const potentialUrlSearch = (new URLSearchParams($querystring)).get('search') || params.arg || '';
30+ const searchParams = new URLSearchParams($querystring);
31+ const potentialUrlSearch = searchParams.get('search') || params.arg || '';
2732 if (($location + $querystring).includes(potentialUrlSearch)) {
2833 urlSearch = potentialUrlSearch;
29− // urlChanged = Date.now();
30− urlChangedSinceAnalysisPatp = true;
34+ urlSearchMode = getUrlSearchMode(searchParams);
35+ urlChangedSinceShipQuery = true;
3136 navigating = false;
3237 }
3338 }
3439 $: {
35− // analysisPatpChanged = (analysis.patp || true) && Date.now();
36− urlChangedSinceAnalysisPatp = (analysis.patp || true) && false;
40+ urlChangedSinceShipQuery = (search || true) && searchMode && false;
3741 }
3842 function nav(path, addToHistory = false) {
3943 (addToHistory ? push : replace)(path);
@@ -41,24 +45,28 @@
4145 function navHome(addToHistory = false) {
4246 params.mode = null;
4347 params.arg = null;
44− nav('/', addToHistory);
48+ if (searchMode == 'patp') {
49+ nav('/', addToHistory);
50+ } else {
51+ nav('/' + buildQuerystring({ mode: searchMode }), addToHistory);
52+ }
4553 }
4654 function navToShipView(addToHistory = false) {
4755 params.mode = 'ship';
48− params.arg = analysis.patp;
49− if (analysis.patp === search) {
50− nav(linkToShip(analysis.patp), addToHistory);
56+ params.arg = patp;
57+ if (patp === search) {
58+ nav(linkToShip(patp), addToHistory);
5159 } else {
52− nav(linkToShip(analysis.patp, { search }), addToHistory);
60+ nav(linkToShip(patp, { search, mode: searchMode }), addToHistory);
5361 }
5462 }
5563 function navToSearch(addToHistory = false) {
5664 if (!search) {
5765 navHome(addToHistory);
58− } else if (analysis.queryIsValid) {
66+ } else {
5967 params.mode = 'search';
6068 params.arg = search;
61− nav(`/search/${search}`, addToHistory);
69+ nav(linkToSearch(search, { mode: searchMode }), addToHistory);
6270 }
6371 }
6472 function navToChart(addToHistory = true) {
@@ -79,26 +87,27 @@
7987 navHome(addToHistory);
8088 }
8189 }
82− // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
8390 function autoNav() {
84− if (search !== urlSearch) {
85− if (urlChangedSinceAnalysisPatp) {
91+ if ((search !== urlSearch || searchMode !== urlSearchMode)) {
92+ if (urlChangedSinceShipQuery) {
8693 search = urlSearch;
87− } else if (analysis.search === search) {
94+ searchMode = urlSearchMode;
95+ } else {
8896 navigating = true;
8997 if (params.mode === 'chart') {
9098 navToChart(analysis.patpIsValid);
9199 } else {
92− if (analysis.patpIsValid) {
100+ if (patp) {
93101 navToShipView(true);
94102 } else {
95− navToSearch(params.mode === 'ship');
103+ const fromShipMode = params.mode === 'ship';
104+ navToSearch(fromShipMode);
96105 }
97106 }
98107 }
99108 }
100109 }
101− $: urlSearch, analysis, autoNav();
110+ $: urlSearch, urlSearchMode, search, searchMode, autoNav();
102111 $: {
103112 let properMode = params.mode;
104113 if (properMode === 'chart') {
@@ -140,19 +149,31 @@
140149</script>
141150
142151<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
143− <SearchBar bind:analysis bind:sigilQuery bind:search bind:showSigilInput {placeholderText} />
144− <!-- <p class="bg-white">navigating</p> -->
152+ <SearchBar bind:analysis bind:sigilQuery bind:search bind:searchMode bind:patp {placeholderText} />
153+ <!-- <div class="bg-white">
154+ <p>
155+ Search: {search}
156+ </p>
157+ <p>
158+ URL Search: {urlSearch}
159+ </p>
160+ <p>
161+ Search mode: {searchMode}
162+ </p>
163+ <p>
164+ Patp: {patp}
165+ </p>
166+ </div> -->
145167 {#if params.mode == "chart"}
146− <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
147− {:else if analysis.patpIsValid}
148− <ShipView patp={analysis.patp} />
149− {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode == 'sigil' && sigilQuery.isWorthSearching)}
168+ <StarChart patp={patp || null} />
169+ {:else if patp}
170+ <ShipView {patp} />
171+ {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode === 'sigil' && sigilQuery.isWorthSearching)}
150172 <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery}
151173 patpSearch={analysis.search} {searchMode} />
152− {:else if (analysis.search.length > 0)}
174+ {:else if (analysis.search.length > 0 && analysis.queryProblems.length > 0)}
153175 <ValidationProblems problems={analysis.queryProblems} />
154176 {:else}
155177 <HomeScreenTiles />
156178 {/if}
157− <!-- {/if} -->
158179</div>
ui/src/styles/tailwind-output.css+14−2
@@ -1652,6 +1652,18 @@ Ensure the default browser behavior of the `hidden` attribute.
16521652.opacity-10 {
16531653 opacity: 0.1;
16541654}
1655+.opacity-20 {
1656+ opacity: 0.2;
1657+}
1658+.opacity-30 {
1659+ opacity: 0.3;
1660+}
1661+.opacity-40 {
1662+ opacity: 0.4;
1663+}
1664+.opacity-5 {
1665+ opacity: 0.05;
1666+}
16551667.outline {
16561668 outline-style: solid;
16571669}
@@ -1697,8 +1709,8 @@ Ensure the default browser behavior of the `hidden` attribute.
16971709 --tw-bg-opacity: 1;
16981710 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
16991711}
1700−.hover\:opacity-40:hover {
1701− opacity: 0.4;
1712+.hover\:opacity-80:hover {
1713+ opacity: 0.8;
17021714}
17031715@media (min-width: 360px) {
17041716