on main help sign in
make sigil search better!
47686e0John Hyde
.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:
62 3. Set `GLOBBER_DESK` in `.env.local` if the path to your globber desk is different.
62 3. Set `GLOBBER_DESK` in `.env.local` if the path to your globber desk is different.
63
63
64Every Time:
64Every Time:
65−4. From the `ui` directory, run `npm run release` to build the ui and copy it to your globber desk
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−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`.
66+4. 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.
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.
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:
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:
74 ```hoon
69 ```hoon
75 glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
70 glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
76 ```
71 ```
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 @@
3# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
3# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
4# by ~timluc-miptev
4# by ~timluc-miptev
5#
5#
6+# Modified HEAVILY
7+#
6usage() { 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; }
8usage() { 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; }
7
9
8cdir=$(dirname $0)
10cdir=$(dirname $0)
@@ -11,9 +13,36 @@ if [ $# -eq 0 ]; then
11 usage
13 usage
12 exit 2
14 exit 2
13fi
15fi
14−DESK=${@: -1}
16+DESK_DIR=${@: -1}
17+DESK_DIR="${DESK_DIR/#\~/$HOME}"
18+PIER=$(dirname $DESK_DIR)
19+DESK=$(basename $DESK_DIR)
15EXCLUDE_FILE="${cdir}"/../config/ignore_files_on_install.txt
20EXCLUDE_FILE="${cdir}"/../config/ignore_files_on_install.txt
16
21
22+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
23+
24+lensd() {
25+ curl -s \
26+ --data "{\"source\":{\"dojo\":\"$1\"},\"sink\":{\"stdout\":null}}" \
27+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
28+}
29+
30+lensa() {
31+ curl -s \
32+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
33+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
34+}
35+
36+sync() {
37+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK_DIR/
38+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK_DIR/
39+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK_DIR/
40+ if [ "$GLOBBER" ]; then
41+ rsync -r --copy-links "${cdir}"/../ui/dist/* $DESK_DIR/astrolabe
42+ fi
43+}
44+
45+
17while getopts "wg" opt; do
46while getopts "wg" opt; do
18 case ${opt} in
47 case ${opt} in
19 w) WATCH_MODE="true"
48 w) WATCH_MODE="true"
@@ -24,28 +53,30 @@ while getopts "wg" opt; do
24 ;;
53 ;;
25 esac
54 esac
26done
55done
27−DESK="${DESK//\~/$HOME}"
56+
28if [ -z "$WATCH_MODE" ]; then
57if [ -z "$WATCH_MODE" ]; then
29− echo "Installed %astrolabe to ${DESK}"
58+ echo "Installed %astrolabe to ${DESK_DIR}"
30− rm -r $DESK/*
59+ rm -r $DESK_DIR/*
31− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK/
60+ sync
32− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK/
61+ lensa 'hood' "+hood/commit %$DESK"
33− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK/
34 if [ "$GLOBBER" ]; then
62 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
36 fi
73 fi
37else
74else
38− echo "Watching for changes to copy to ${DESK}..."
75+ echo "Watching for changes to copy to ${DESK_DIR}..."
39− rm -r $DESK/*
76+ rm -r $DESK_DIR/*
40 while [ 0 ]
77 while [ 0 ]
41 do
78 do
42 sleep 0.8
79 sleep 0.8
43− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK/
80+ sync
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
50 done
81 done
51fi
82fi
desk/lib/search.hoon+1−1
@@ -145,7 +145,7 @@
145 %+ turn
145 %+ turn
146 (split "." ints-tape)
146 (split "." ints-tape)
147 |= =tape
147 |= =tape
148− ~& tape
148+ :: ~& tape
149 `@q`(slav %ud (crip tape))
149 `@q`(slav %ud (crip tape))
150::
150::
151++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
151++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
ui/package.json+3−1
@@ -19,7 +19,9 @@
19 "install:desk:watch": "dotenv -c local npm run _install:desk:watch",
19 "install:desk:watch": "dotenv -c local npm run _install:desk:watch",
20 "release": "npm run build && npm run install:glob",
20 "release": "npm run build && npm run install:glob",
21 "_get-glob": "../bin/get_glob.sh $GLOBBER_DESK",
21 "_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"
23 },
25 },
24 "devDependencies": {
26 "devDependencies": {
25 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
27 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
ui/src/components/search/SearchBar.svelte+63−37
@@ -1,64 +1,83 @@
1<script lang="ts">
1<script lang="ts">
2 import SigilInput from './sigil/SigilInput.svelte';
2 import SigilInput from './sigil/SigilInput.svelte';
3−
3+ import Symbol from './sigil/Symbol.svelte';
4
4
5 import { SearchAnalysis } from 'lib/id';
5 import { SearchAnalysis } from 'lib/id';
6 import { SigilQuery } from 'lib/sigil';
6 import { SigilQuery } from 'lib/sigil';
7
7
8 export let patp: string = undefined;
8 export let patp: string = undefined;
9− export let searchQuery: RegExp = /~der/;
10 export let analysis: SearchAnalysis = new SearchAnalysis();
9 export let analysis: SearchAnalysis = new SearchAnalysis();
11 export let search = '';
10 export let search = '';
12 export let sigilQuery: SigilQuery = new SigilQuery('planet');
11 export let sigilQuery: SigilQuery = new SigilQuery('planet');
13− export let showSigilInput: boolean = false;
12+ sigilQuery.urlString = search;
14− export let pauseAnalysis: boolean = false;
13+ export let searchMode: ('patp' | 'sigil') = 'patp';
15 export let placeholderText = "Search by @p or #. '*' matches any character";
14 export let placeholderText = "Search by @p or #. '*' matches any character";
15+
16+ $: showSigilInput = searchMode == 'sigil';
16
17
17− let debouncedSearch = search;
18+ let bouncySearch: string = search;
19+ let debouncedSearch: string = bouncySearch;
18
20
21+ let timer;
19 $: {
22 $: {
20− if (!pauseAnalysis) {
23+ if (!showSigilInput){
21− analysis = new SearchAnalysis(debouncedSearch);
24+ if (bouncySearch !== search) {
22− if (analysis.queryIsValid) {
25+ clearTimeout(timer);
23− searchQuery = analysis.query;
26+ timer = setTimeout(() => {
24− } else {
27+ search = bouncySearch;
25− searchQuery = undefined;
28+ }, 300);
26 }
29 }
27− if (analysis.patpIsValid) {
30+ }
28− patp = analysis.patp;
31+ }
29− } else {
32+
30− patp = undefined;
33+ function updateSigilUrlString() {
34+ if (showSigilInput) {
35+ if (search !== sigilQuery.urlString) {
36+ sigilQuery.urlString = search;
31 }
37 }
32 }
38 }
33 }
39 }
34
40
35− $: {
41+ function updateSigilSearch() {
36− if (showSigilInput && sigilQuery.isDefinitive) {
42+ if (showSigilInput) {
37− const querySyls = sigilQuery.querySyls.flat();
43+ if (sigilQuery.isNotEmpty) {
38− search = '~' + querySyls[0];
44+ search = sigilQuery.urlString;
39− if (querySyls.length > 1) {
45+ } else {
40− search += querySyls[1];
46+ search = '';
41− if (querySyls.length > 2) {
42− search += '-' + querySyls[2] + querySyls[3];
43− }
44 }
47 }
45 }
48 }
46 }
49 }
47
50
48− let timer;
51+ $: search, updateSigilUrlString();
52+ $: sigilQuery.urlString, updateSigilSearch();
53+
49 $: {
54 $: {
50− clearTimeout(timer);
55+ if (showSigilInput) {
51− timer = setTimeout(() => {
56+ if (sigilQuery.isDefinitive) {
52− debouncedSearch = search;
57+ patp = sigilQuery.patp;
53− }, 300);
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+ }
54− }
70+ }
55
71
56 function openSigilInput() {
72 function openSigilInput() {
57− showSigilInput = true;
73+ searchMode = 'sigil';
58 }
74 }
59
75
60 function closeSigilInput() {
76 function closeSigilInput() {
61− showSigilInput = false;
77+ searchMode = 'patp';
78+ bouncySearch = patp || analysis.search;
79+ debouncedSearch = bouncySearch;
80+ search = debouncedSearch;
62 }
81 }
63</script>
82</script>
64
83
@@ -69,18 +88,25 @@
69 </div>
88 </div>
70 <button
89 <button
71 on:click={closeSigilInput}
90 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"
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"
73− />
92+ >
93+ patp or #
94+ </button>
74 {:else}
95 {:else}
75 <input type="text" class="p-4 py-2 w-full rounded-full"
96 <input type="text" class="p-4 py-2 w-full rounded-full"
76 spellcheck="false"
97 spellcheck="false"
77 placeholder={placeholderText}
98 placeholder={placeholderText}
99+ on:input={(e) => bouncySearch = e.target.value}
78− bind:value={search}
100+ value={search}
79 autofocus
101 autofocus
80 />
102 />
81 <button
103 <button
82 on:click={openSigilInput}
104 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"
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"
84− />
109+ />
110+ </button>
85 {/if}
111 {/if}
86</div>
112</div>
ui/src/components/search/sigil/SigilInput.svelte+12−4
@@ -1,5 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2 import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
2 import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
3+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
3 import SymbolInput from './SymbolInput.svelte';
4 import SymbolInput from './SymbolInput.svelte';
4 import ClanSelect from './ClanSelect.svelte';
5 import ClanSelect from './ClanSelect.svelte';
5 import ToggleButton from '@/buttons/ToggleButton.svelte';
6 import ToggleButton from '@/buttons/ToggleButton.svelte';
@@ -33,6 +34,10 @@ import { sigil } from '@tlon/sigil-js';
33 toggleStoreKey(searchSettings, 'allowFictionalSigils');
34 toggleStoreKey(searchSettings, 'allowFictionalSigils');
34 }
35 }
35
36
37+ function clear() {
38+ sigilQuery = sigilQuery.clearSymbols();
39+ }
40+
36 $: {
41 $: {
37 sigilQuery.allowFictional = $searchSettings.allowFictionalSigils;
42 sigilQuery.allowFictional = $searchSettings.allowFictionalSigils;
38 }
43 }
@@ -55,16 +60,19 @@ import { sigil } from '@tlon/sigil-js';
55 {/each}
60 {/each}
56 </div>
61 </div>
57 </div>
62 </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">
59 {#if focusedSymbol}
64 {#if focusedSymbol}
60 <SymbolInputModeSelector
65 <SymbolInputModeSelector
61 bind:symbolQuery={focusedSymbol}
66 bind:symbolQuery={focusedSymbol}
62 bind:inputComponents
67 bind:inputComponents
63 unfocusSymbol={unfocusSymbol}
68 unfocusSymbol={unfocusSymbol}
64 />
69 />
65− {:else}
70+ {:else}
71+ <SymbolInputNavButtons onClear={sigilQuery.isNotEmpty ? clear : null} />
72+ <p class="mt-2">
66− Allow Sigils which don't exist:
73+ Allow Sigils which don't exist:
67− <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
74+ <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
75+ </p>
68 {/if}
76 {/if}
69 </div>
77 </div>
70 </div>
78 </div>
ui/src/components/search/sigil/SymbolInput.svelte+15−13
@@ -20,19 +20,20 @@
20
20
21 let translateClass;
21 let translateClass;
22 $: {
22 $: {
23+ translateClass = 'translate-x-0 translate-y-0';
23− if (focusedSymbolIndex === undefined) {
24+ if (focusedSymbolIndex !== undefined) {
24− translateClass = 'translate-x-0 translate-y-0';
25− } else if (clan === 'star') {
25+ if (clan === 'star') {
26− translateClass = index ? 'origin-right' : 'origin-left';
26+ translateClass = index ? 'origin-right' : 'origin-left';
27− } else if (clan === 'planet') {
27+ } else if (clan === 'planet') {
28− let cornerIndex = index;
28+ let cornerIndex = index;
29− if (!focused) cornerIndex = focusedSymbolIndex;
29+ if (!focused) cornerIndex = focusedSymbolIndex;
30− switch (cornerIndex) {
30+ switch (cornerIndex) {
31− case 0: translateClass = 'translate-x-1/4 translate-y-1/4'; break;
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;
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;
33+ case 2: translateClass = 'translate-x-1/4 -translate-y-1/4'; break;
34− case 3:
34+ case 3:
35− default: translateClass = '-translate-x-1/4 -translate-y-1/4';
35+ default: translateClass = '-translate-x-1/4 -translate-y-1/4';
36+ }
36 }
37 }
37 }
38 }
38 }
39 }
@@ -80,6 +81,7 @@
80 <Symbol components={symbolQuery.components} {size}
81 <Symbol components={symbolQuery.components} {size}
81 inverted={!symbolQuery.geon}
82 inverted={!symbolQuery.geon}
82 interactive={focused}
83 interactive={focused}
84+ strokeWidthFactor={focused ? 2 : 1}
83 fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? 'black' : '#333')}
85 fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? 'black' : '#333')}
84 bgColor="white"
86 bgColor="white"
85 altBgColor="transparent"
87 altBgColor="transparent"
ui/src/components/search/sigil/SymbolInputModeSelector.svelte+12−17
@@ -1,4 +1,6 @@
1<script lang="ts">
1<script lang="ts">
2+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
3+
2 import GeonSelector from './GeonSelector.svelte';
4 import GeonSelector from './GeonSelector.svelte';
3 import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
5 import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
4
6
@@ -8,25 +10,21 @@
8 import { MODE_DEFS } from 'types/sigil';
10 import { MODE_DEFS } from 'types/sigil';
9 import { filterPartsByGeon, getDeepParts } from 'lib/sigil';
11 import { filterPartsByGeon, getDeepParts } from 'lib/sigil';
10
12
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;
15 export let inputComponents: string[] = [];
14 export let inputComponents: string[] = [];
16− export let unfocusSymbol: Function;
15+ export let unfocusSymbol: Function = () => {};
17
16
18 let modes: string[] = [];
17 let modes: string[] = [];
19− // let subMode: string = undefined;
20 let modeDef: ModeDef = undefined;
18 let modeDef: ModeDef = undefined;
21 let subModeDefs: ModeDefs = MODE_DEFS;
19 let subModeDefs: ModeDefs = MODE_DEFS;
22
20
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+ }
24
26
25− // $: {
26− // if (!symbolQuery.geon) {
27+ $: symbolQuery.components, showGeonSelector();
27− // modes = ['geon'];
28− // }
29− // }
30
28
31 function pushMode(mode: string) {
29 function pushMode(mode: string) {
32 if (modeDef?.modes) {
30 if (modeDef?.modes) {
@@ -115,11 +113,8 @@
115</style>
113</style>
116
114
117<div class="mx-auto flex justify-center xs:items-center xs:flex-col">
115<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">
116+ <SymbolInputNavButtons onDone={popMode} onClear={clear} />
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]">
117+ <div class="max-w-[15rem] h-full grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
123 {#if modes[0] === 'geon'}
118 {#if modes[0] === 'geon'}
124 <GeonSelector bind:symbolQuery {popMode} />
119 <GeonSelector bind:symbolQuery {popMode} />
125 {:else}
120 {: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[] {
445
445
446function padWithWild(search: string) {
446function padWithWild(search: string) {
447 let words = search.split(/[_^]/);
447 let words = search.split(/[_^]/);
448− words[0] = '*'.repeat(6 - words[0].length) + words[0];
448+ words[0] = '*'.repeat(Math.max(0, 6 - words[0].length)) + words[0];
449− words[1] += '*'.repeat(6 - words[1].length);
449+ words[1] += '*'.repeat(Math.max(0, 6 - words[1].length));
450 return words.join(search.match(/([_^])/)[1]);
450 return words.join(search.match(/([_^])/)[1]);
451}
451}
452
452
ui/src/lib/link.ts+5−1
@@ -4,9 +4,13 @@ export function linkToShip(patp: string, queryParams = {}): string {
4 return `/ship/${patp}` + buildQuerystring(queryParams);
4 return `/ship/${patp}` + buildQuerystring(queryParams);
5}
5}
6
6
7+export function linkToSearch(search: string, queryParams = {}): string {
8+ return `/search/${search}` + buildQuerystring(queryParams);
9+}
10+
7export function linkToChart(patp: string): string {
11export function linkToChart(patp: string): string {
8 if (patp) {
12 if (patp) {
9− return `/chart/${patp}`;
13+ return `/chart/${patp}`;
10 }
14 }
11 return `/chart`;
15 return `/chart`;
12}
16}
ui/src/lib/sigil.ts+61−1
@@ -42,6 +42,12 @@ export class SigilQuery {
42 this.symbols = this.symbols;
42 this.symbols = this.symbols;
43 }
43 }
44
44
45+ clearSymbols() {
46+ this.symbols = [];
47+ this.padSymbols();
48+ return this;
49+ }
50+
45 get activeSymbols() {
51 get activeSymbols() {
46 if (this.clan === 'galaxy')
52 if (this.clan === 'galaxy')
47 return [this.symbols[1]];
53 return [this.symbols[1]];
@@ -54,6 +60,10 @@ export class SigilQuery {
54 return this.activeSymbols.every(symbol => symbol.isPlausible);
60 return this.activeSymbols.every(symbol => symbol.isPlausible);
55 }
61 }
56
62
63+ get queryParts(): string[][] {
64+ return this.activeSymbols.map(symbol => symbol.components);
65+ }
66+
57 get querySyls(): string[][] {
67 get querySyls(): string[][] {
58 return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
68 return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
59 }
69 }
@@ -73,12 +83,56 @@ export class SigilQuery {
73 get isWorthSearching() {
83 get isWorthSearching() {
74 if (!this.isNotEmpty) return false; // 🙃
84 if (!this.isNotEmpty) return false; // 🙃
75 if (!this.isPlausible) return false;
85 if (!this.isPlausible) return false;
76− return 100000 > this.querySyls.reduce((acc, syls) => acc * (syls.length || 256), 1);
86+ return true;
77 }
87 }
78
88
79 get string(): string {
89 get string(): string {
80 return this.queryInts.map((int) => int.length == 256 ? 'any' : int.join('.')).join('_');
90 return this.queryInts.map((int) => int.length == 256 ? 'any' : int.join('.')).join('_');
81 }
91 }
92+
93+ get urlString(): string {
94+ return this.queryParts.map(parts => parts.join('.')).join('_');
95+ }
96+
97+ set urlString(str: string) {
98+ if (!str) return;
99+ const queryParts = str.split('_').map(sym => sym.split('.').filter(part => part !== ''));
100+ switch (queryParts.length) {
101+ case 1:
102+ this.clan = 'galaxy';
103+ break;
104+ case 2:
105+ this.clan = 'star';
106+ break;
107+ case 4:
108+ this.clan = 'planet';
109+ break;
110+ default:
111+ return;
112+ }
113+ 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+ }
82
136
83 setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
137 setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
84 if (activeSymbolIndex >= this.activeSymbols.length)
138 if (activeSymbolIndex >= this.activeSymbols.length)
@@ -192,6 +246,11 @@ export function filterPartsByGeon(parts: string[], geon: Geon): string[] {
192 return parts;
246 return parts;
193}
247}
194
248
249+export function filterFakeParts(parts: string[], symbolType: SymbolType): string[] {
250+ let plausibleSyllables = getSyllablesBySymbolType(symbolType);
251+ return parts.filter(part => !plausibleSyllables.includes(part));
252+}
253+
195export class SymbolQuery {
254export class SymbolQuery {
196 sigilQuery?: SigilQuery;
255 sigilQuery?: SigilQuery;
197 symbolType: SymbolType;
256 symbolType: SymbolType;
@@ -254,6 +313,7 @@ export class SymbolQuery {
254 cleanupParts() {
313 cleanupParts() {
255 this.components = uniq(this.components);
314 this.components = uniq(this.components);
256 this.components = filterPartsByGeon(this.components, this.geon);
315 this.components = filterPartsByGeon(this.components, this.geon);
316+ this.components = filterFakeParts(this.components, this.symbolType);
257 // if (!this.allowFictional) {
317 // if (!this.allowFictional) {
258 // this.components = this.plausibleParts;
318 // this.components = this.plausibleParts;
259 // }
319 // }
ui/src/routes/Home.svelte+57−36
@@ -1,10 +1,10 @@
1<script lang="ts">
1<script lang="ts">
2− import Router from 'svelte-spa-router';
3− import { link, push, replace, location, querystring } from 'svelte-spa-router'
2+ import { push, replace, location, querystring } from 'svelte-spa-router'
4 import pageName from 'stores/pageName';
3 import pageName from 'stores/pageName';
5 import { SearchAnalysis } from 'lib/id';
4 import { SearchAnalysis } from 'lib/id';
6 import { SigilQuery } from 'lib/sigil';
5 import { SigilQuery } from 'lib/sigil';
6+ import { buildQuerystring } from 'lib/utils';
7− import { linkToShip, linkToChart } from 'lib/link';
7+ import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
8 import SearchBar from "@/search/SearchBar.svelte";
8 import SearchBar from "@/search/SearchBar.svelte";
9 import SearchResults from "@/search/SearchResults.svelte";
9 import SearchResults from "@/search/SearchResults.svelte";
10 import ShipView from '@/ShipView.svelte';
10 import ShipView from '@/ShipView.svelte';
@@ -14,26 +14,30 @@
14 export let params: any = {};
14 export let params: any = {};
15 let analysis: SearchAnalysis = new SearchAnalysis();
15 let analysis: SearchAnalysis = new SearchAnalysis();
16 let sigilQuery: SigilQuery = new SigilQuery('planet');
16 let sigilQuery: SigilQuery = new SigilQuery('planet');
17− let showSigilInput: boolean = false;
18− $: searchMode = showSigilInput ? 'sigil' : 'patp';
19 let navigating = false;
17 let navigating = false;
20−
18+
21− let search: string = (new URLSearchParams($querystring)).get('search') || params.arg || '';
19+ let searchParams = new URLSearchParams($querystring);
20+ function getUrlSearchMode(searchParams) {
21+ return (searchParams.get('mode') == 'sigil') ? 'sigil' : 'patp';
22+ }
23+ let searchMode: ('patp' | 'sigil') = getUrlSearchMode(searchParams);
24+ let search: string = searchParams.get('search') || params.arg || '';
25+ let patp: string = undefined;
22 let urlSearch = search;
26 let urlSearch = search;
23− // let urlChanged;
27+ let urlSearchMode = searchMode;
24− let urlChangedSinceAnalysisPatp = false;
28+ let urlChangedSinceShipQuery = false;
25 $: {
29 $: {
26− const potentialUrlSearch = (new URLSearchParams($querystring)).get('search') || params.arg || '';
30+ const searchParams = new URLSearchParams($querystring);
31+ const potentialUrlSearch = searchParams.get('search') || params.arg || '';
27 if (($location + $querystring).includes(potentialUrlSearch)) {
32 if (($location + $querystring).includes(potentialUrlSearch)) {
28 urlSearch = potentialUrlSearch;
33 urlSearch = potentialUrlSearch;
29− // urlChanged = Date.now();
34+ urlSearchMode = getUrlSearchMode(searchParams);
30− urlChangedSinceAnalysisPatp = true;
35+ urlChangedSinceShipQuery = true;
31 navigating = false;
36 navigating = false;
32 }
37 }
33 }
38 }
34 $: {
39 $: {
35− // analysisPatpChanged = (analysis.patp || true) && Date.now();
40+ urlChangedSinceShipQuery = (search || true) && searchMode && false;
36− urlChangedSinceAnalysisPatp = (analysis.patp || true) && false;
37 }
41 }
38 function nav(path, addToHistory = false) {
42 function nav(path, addToHistory = false) {
39 (addToHistory ? push : replace)(path);
43 (addToHistory ? push : replace)(path);
@@ -41,24 +45,28 @@
41 function navHome(addToHistory = false) {
45 function navHome(addToHistory = false) {
42 params.mode = null;
46 params.mode = null;
43 params.arg = null;
47 params.arg = null;
48+ if (searchMode == 'patp') {
44− nav('/', addToHistory);
49+ nav('/', addToHistory);
50+ } else {
51+ nav('/' + buildQuerystring({ mode: searchMode }), addToHistory);
52+ }
45 }
53 }
46 function navToShipView(addToHistory = false) {
54 function navToShipView(addToHistory = false) {
47 params.mode = 'ship';
55 params.mode = 'ship';
48− params.arg = analysis.patp;
56+ params.arg = patp;
49− if (analysis.patp === search) {
57+ if (patp === search) {
50− nav(linkToShip(analysis.patp), addToHistory);
58+ nav(linkToShip(patp), addToHistory);
51 } else {
59 } else {
52− nav(linkToShip(analysis.patp, { search }), addToHistory);
60+ nav(linkToShip(patp, { search, mode: searchMode }), addToHistory);
53 }
61 }
54 }
62 }
55 function navToSearch(addToHistory = false) {
63 function navToSearch(addToHistory = false) {
56 if (!search) {
64 if (!search) {
57 navHome(addToHistory);
65 navHome(addToHistory);
58− } else if (analysis.queryIsValid) {
66+ } else {
59 params.mode = 'search';
67 params.mode = 'search';
60 params.arg = search;
68 params.arg = search;
61− nav(`/search/${search}`, addToHistory);
69+ nav(linkToSearch(search, { mode: searchMode }), addToHistory);
62 }
70 }
63 }
71 }
64 function navToChart(addToHistory = true) {
72 function navToChart(addToHistory = true) {
@@ -79,26 +87,27 @@
79 navHome(addToHistory);
87 navHome(addToHistory);
80 }
88 }
81 }
89 }
82− // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
83 function autoNav() {
90 function autoNav() {
84− if (search !== urlSearch) {
91+ if ((search !== urlSearch || searchMode !== urlSearchMode)) {
85− if (urlChangedSinceAnalysisPatp) {
92+ if (urlChangedSinceShipQuery) {
86 search = urlSearch;
93 search = urlSearch;
87− } else if (analysis.search === search) {
94+ searchMode = urlSearchMode;
95+ } else {
88 navigating = true;
96 navigating = true;
89 if (params.mode === 'chart') {
97 if (params.mode === 'chart') {
90 navToChart(analysis.patpIsValid);
98 navToChart(analysis.patpIsValid);
91 } else {
99 } else {
92− if (analysis.patpIsValid) {
100+ if (patp) {
93 navToShipView(true);
101 navToShipView(true);
94 } else {
102 } else {
95− navToSearch(params.mode === 'ship');
103+ const fromShipMode = params.mode === 'ship';
104+ navToSearch(fromShipMode);
96 }
105 }
97 }
106 }
98 }
107 }
99 }
108 }
100 }
109 }
101− $: urlSearch, analysis, autoNav();
110+ $: urlSearch, urlSearchMode, search, searchMode, autoNav();
102 $: {
111 $: {
103 let properMode = params.mode;
112 let properMode = params.mode;
104 if (properMode === 'chart') {
113 if (properMode === 'chart') {
@@ -140,19 +149,31 @@
140</script>
149</script>
141
150
142<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
151<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
143− <SearchBar bind:analysis bind:sigilQuery bind:search bind:showSigilInput {placeholderText} />
152+ <SearchBar bind:analysis bind:sigilQuery bind:search bind:searchMode bind:patp {placeholderText} />
144− <!-- <p class="bg-white">navigating</p> -->
153+ <!-- <div class="bg-white">
154+ <p>
155+ Search: {search}
156+ </p>
157+ <p>
158+ URL Search: {urlSearch}
159+ </p>
160+ <p>
161+ Search mode: {searchMode}
162+ </p>
163+ <p>
164+ Patp: {patp}
165+ </p>
166+ </div> -->
145 {#if params.mode == "chart"}
167 {#if params.mode == "chart"}
146− <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
168+ <StarChart patp={patp || null} />
147− {:else if analysis.patpIsValid}
169+ {:else if patp}
148− <ShipView patp={analysis.patp} />
170+ <ShipView {patp} />
149− {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode == 'sigil' && sigilQuery.isWorthSearching)}
171+ {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode === 'sigil' && sigilQuery.isWorthSearching)}
150 <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery}
172 <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery}
151 patpSearch={analysis.search} {searchMode} />
173 patpSearch={analysis.search} {searchMode} />
152− {:else if (analysis.search.length > 0)}
174+ {:else if (analysis.search.length > 0 && analysis.queryProblems.length > 0)}
153 <ValidationProblems problems={analysis.queryProblems} />
175 <ValidationProblems problems={analysis.queryProblems} />
154 {:else}
176 {:else}
155 <HomeScreenTiles />
177 <HomeScreenTiles />
156 {/if}
178 {/if}
157− <!-- {/if} -->
158</div>
179</div>
ui/src/styles/tailwind-output.css+14−2
@@ -1652,6 +1652,18 @@ Ensure the default browser behavior of the `hidden` attribute.
1652.opacity-10 {
1652.opacity-10 {
1653 opacity: 0.1;
1653 opacity: 0.1;
1654}
1654}
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+}
1655.outline {
1667.outline {
1656 outline-style: solid;
1668 outline-style: solid;
1657}
1669}
@@ -1697,8 +1709,8 @@ Ensure the default browser behavior of the `hidden` attribute.
1697 --tw-bg-opacity: 1;
1709 --tw-bg-opacity: 1;
1698 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1710 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1699}
1711}
1700−.hover\:opacity-40:hover {
1712+.hover\:opacity-80:hover {
1701− opacity: 0.4;
1713+ opacity: 0.8;
1702}
1714}
1703@media (min-width: 360px) {
1715@media (min-width: 360px) {
1704
1716