on main help sign in
sigil entry very nearly "complete"
67ad836John Hyde
ui/src/actions/symbolPartClick.ts+13−4
@@ -1,12 +1,17 @@
1const docLinkRegex = /\/~\/scry\/docs\/usr\/astrolabe\/([-~._\w\d]+).html/;
1const docLinkRegex = /\/~\/scry\/docs\/usr\/astrolabe\/([-~._\w\d]+).html/;
2
2
3+
3function listenOnSvgElements(node, onPathChange) {
4function listenOnSvgElements(node, onPathChange) {
4 const parts = node.querySelectorAll('path, circle, line');
5 const parts = node.querySelectorAll('path, circle, line');
5 for (let part of parts) {
6 for (let part of parts) {
6 if (part.getAttribute('dataisgeon') === 'true') continue;
7 if (part.getAttribute('dataisgeon') === 'true') continue;
7− part.addEventListener('click', () => {
8+ if (onPathChange) {
9+ part.onclick = () => {
8− onPathChange(part.getAttribute('part-key'));
10+ onPathChange(part.getAttribute('part-key'));
9− });
11+ };
12+ } else {
13+ part.onclick = null;
14+ }
10 }
15 }
11}
16}
12
17
@@ -22,7 +27,11 @@ export default function svgClick(node: HTMLElement, { onPartClick, enabled = tru
22
27
23 return {
28 return {
24 update: ({ onPartClick, enabled = true, svgString }) => {
29 update: ({ onPartClick, enabled = true, svgString }) => {
30+ if (enabled) {
25− if (enabled) listenOnSvgElements(node, onPartClick);
31+ listenOnSvgElements(node, onPartClick);
32+ } else {
33+ listenOnSvgElements(node, undefined);
34+ }
26 },
35 },
27 };
36 };
28}
37}
ui/src/assets/up-arrow.svgnew file+1
@@ -0,0 +1 @@
1+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g data-name="Layer 2"><g data-name="arrow-ios-upward"><rect width="24" height="24" transform="rotate(180 12 12)" opacity="0"/><path d="M18 15a1 1 0 0 1-.64-.23L12 10.29l-5.37 4.32a1 1 0 0 1-1.41-.15 1 1 0 0 1 .15-1.41l6-4.83a1 1 0 0 1 1.27 0l6 5a1 1 0 0 1 .13 1.41A1 1 0 0 1 18 15z"/></g></g></svg>
ui/src/assets/x.svgnew file+1
@@ -0,0 +1 @@
1+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g data-name="Layer 2"><g data-name="close"><rect width="24" height="24" transform="rotate(180 12 12)" opacity="0"/><path d="M13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29-4.3 4.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l4.29-4.3 4.29 4.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"/></g></g></svg>
ui/src/components/Sigil.svelte+2−2
@@ -3,8 +3,8 @@
3 import { sigil as sigil1, stringRenderer as sr1 } from '@tlon/sigil-js';
3 import { sigil as sigil1, stringRenderer as sr1 } from '@tlon/sigil-js';
4 import { sigil as sigil2, stringRenderer as sr2 } from '@johnhyde/sigil-js';
4 import { sigil as sigil2, stringRenderer as sr2 } from '@johnhyde/sigil-js';
5 import { sigilScalingFunction } from 'lib/sigil';
5 import { sigilScalingFunction } from 'lib/sigil';
6− import moonPng from '../assets/moon.png';
6+ import moonPng from 'assets/moon.png';
7− import cometSvg from '../assets/comet.svg';
7+ import cometSvg from 'assets/comet.svg';
8
8
9 export let patp: string;
9 export let patp: string;
10 export let size: number;
10 export let size: number;
ui/src/components/search/sigil/ClanSelect.svelte+1−1
@@ -28,7 +28,7 @@
28 <span class="font-mono">▼</span>
28 <span class="font-mono">▼</span>
29 </p>
29 </p>
30 {#if dropdownOpen}
30 {#if dropdownOpen}
31− <div class="absolute z-10 top-1 w-full flex flex-col mx-auto divide-y bg-white rounded-2xl border-b">
31+ <div class="absolute z-20 top-1 w-full flex flex-col mx-auto divide-y bg-white rounded-2xl border-b">
32 <h3 class="py-2" on:click={() => dropdownOpen = false}>
32 <h3 class="py-2" on:click={() => dropdownOpen = false}>
33 Select Sigil Type
33 Select Sigil Type
34 </h3>
34 </h3>
ui/src/components/search/sigil/GeonSelector.svelte+14−13
@@ -1,29 +1,30 @@
1<script lang="ts">
1<script lang="ts">
2− import Symbol from './Symbol.svelte';
2+ import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
3
3
4+ import { searchSettings } from 'stores/searchStores';
4− import type { PartType, SymbolQuery } from 'types/sigil';
5+ import type { SymbolQuery } from 'lib/sigil';
5 import { GEONS } from 'types/sigil';
6 import { GEONS } from 'types/sigil';
7+ import { arePartsPlausible } from 'lib/sigil';
6
8
7 export let symbolQuery: SymbolQuery;
9 export let symbolQuery: SymbolQuery;
8 export let popMode: Function;
10 export let popMode: Function;
9
11
10− let size = 48;
11−
12 function setGeon(geon) {
12 function setGeon(geon) {
13 symbolQuery.geon = geon;
13 symbolQuery.geon = geon;
14 popMode();
14 popMode();
15 }
15 }
16+
17+ $: componentsSansGeon = symbolQuery.components.filter(c => c[0] !== 'g');
16</script>
18</script>
17
19
18−<!-- <div on:click={() => inputMode = undefined}>
19− done
20−</div>
21−<div on:click={() => setGeon(undefined)}>
22− clear
23−</div> -->
24{#each GEONS as geon}
20{#each GEONS as geon}
21+ {@const plausible = $searchSettings.allowFictionalSigils ||
22+ arePartsPlausible(componentsSansGeon, symbolQuery.symbolType, [geon])
23+ }
24+ <SymbolInputModeSelectorButton components={[geon]}
25+ focused={geon === symbolQuery.geon}
26+ enabled={plausible}
25− <div style:width="{size}px" on:click={() => setGeon(geon)}>
27+ on:click={() => setGeon(geon)}
26− <Symbol components={[geon]} {size} />
28+ />
27− </div>
28{/each}
29{/each}
29
30
ui/src/components/search/sigil/SigilInput.svelte+46−15
@@ -2,19 +2,25 @@
2 import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
2 import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
3 import SymbolInput from './SymbolInput.svelte';
3 import SymbolInput from './SymbolInput.svelte';
4 import ClanSelect from './ClanSelect.svelte';
4 import ClanSelect from './ClanSelect.svelte';
5+ import ToggleButton from '@/buttons/ToggleButton.svelte';
5
6
6− import type { PartType } from 'types/sigil';
7 import type { SigilQuery } from 'lib/sigil';
7 import type { SigilQuery } from 'lib/sigil';
8+ import { searchSettings } from 'stores/searchStores';
9+ import { toggleStoreKey } from 'lib/utils';
10+import { sigil } from '@tlon/sigil-js';
8
11
9 export let sigilQuery: SigilQuery;
12 export let sigilQuery: SigilQuery;
10 // export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
13 // export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
11− let focusedSymbolIndex: number = 0;
14+ let focusedSymbolIndex: number = undefined;
12 let inputComponents: string[] = [];
15 let inputComponents: string[] = [];
13
16
14 // let symbols: SymbolQuery = [];
17 // let symbols: SymbolQuery = [];
15
18
16− $: gridClasses = (sigilQuery.clan === 'galaxy' ? 'w-[50%]' : 'grid grid-cols-2 items-center');
19+ $: gridClasses = (sigilQuery.clan === 'galaxy' ? 'max-w-[10rem] grid items-center' : 'grid grid-cols-2 items-center');
17
20
21+ function unfocusSymbol() {
22+ focusedSymbolIndex = undefined;
23+ }
18 $: focusedSymbol = sigilQuery.activeSymbols[focusedSymbolIndex];
24 $: focusedSymbol = sigilQuery.activeSymbols[focusedSymbolIndex];
19 $: {
25 $: {
20 const numActiveSymbols = sigilQuery.activeSymbols.length;
26 const numActiveSymbols = sigilQuery.activeSymbols.length;
@@ -22,36 +28,61 @@
22 focusedSymbolIndex = numActiveSymbols - 1;
28 focusedSymbolIndex = numActiveSymbols - 1;
23 }
29 }
24 }
30 }
25− // function updateSigilQuery() {
31+
26− // sigilQuery.setSymbol(focusedSymbol, focusedSymbolIndex);
32+ function toggleAllowFictional() {
33+ toggleStoreKey(searchSettings, 'allowFictionalSigils');
34+ }
35+
36+ $: {
37+ sigilQuery.allowFictional = $searchSettings.allowFictionalSigils;
27− // }
38+ }
28− // $: focusedSymbol, updateSigilQuery();
29</script>
39</script>
30
40
31<div class="">
41<div class="">
32 <ClanSelect bind:clan={sigilQuery.clan} />
42 <ClanSelect bind:clan={sigilQuery.clan} />
33 <div class="flex flex-col xs:flex-row">
43 <div class="flex flex-col xs:flex-row">
34− <div class="grow-[2] mx-auto w-full xs:m-0 xs:max-w-[18rem]">
44+ <div class="grow-[2] w-full xs:m-0 xs:max-w-[18rem]" on:click={unfocusSymbol}>
35− <div class={`max-w-[18rem] mx-auto p-4 ${gridClasses} xs:rounded-bl-2xl`}>
45+ <div class="max-w-[18rem] min-h-[14rem] mx-auto p-4 {gridClasses} xs:rounded-bl-2xl">
36 {#each sigilQuery.activeSymbols as symbol, index}
46 {#each sigilQuery.activeSymbols as symbol, index}
37 <SymbolInput
47 <SymbolInput
38 bind:symbolQuery={symbol}
48 bind:symbolQuery={symbol}
39− focused={index === focusedSymbolIndex}
49+ {focusedSymbolIndex}
50+ {index}
51+ clan={sigilQuery.clan}
40 {inputComponents}
52 {inputComponents}
41 on:click={() => focusedSymbolIndex = index}
53 on:click={() => focusedSymbolIndex = index}
42 />
54 />
43 {/each}
55 {/each}
44 </div>
56 </div>
45 </div>
57 </div>
46− {#if focusedSymbol}
47− <div class="bg-green-100 grow rounded-b-2xl xs:rounded-bl-none">
58+ <div class="p-2 border-t grow rounded-b-2xl xs:border-l xs:border-t-0 xs:rounded-bl-none">
59+ {#if focusedSymbol}
48 <SymbolInputModeSelector
60 <SymbolInputModeSelector
49 bind:symbolQuery={focusedSymbol}
61 bind:symbolQuery={focusedSymbol}
50 bind:inputComponents
62 bind:inputComponents
51− unfocusSymbol={() => focusedSymbolIndex = undefined}
63+ unfocusSymbol={unfocusSymbol}
52 />
64 />
65+ {:else}
66+ Allow Sigils which don't exist:
67+ <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
68+ {/if}
53− </div>
69+ </div>
54− {/if}
55 </div>
70 </div>
71+ {#each sigilQuery.activeSymbols as symbol}
72+ {@const plausibleSyllables = symbol.plausibleSyllables}
73+ {@const plausibleParts = symbol.plausibleParts}
74+ <p>
75+ {#if plausibleSyllables.length < 20 && plausibleSyllables.length > 0}
76+ {plausibleSyllables.join(', ')}:
77+ {#if plausibleParts.length < 20 && plausibleParts.length > 0}
78+ {plausibleParts.join(', ')}
79+ {:else}
80+ {plausibleParts.length}
81+ {/if}
82+ {:else}
83+ {plausibleSyllables.length}
84+ {/if}
85+ </p>
86+ {/each}
56 <!-- {sigilQuery.string} -->
87 <!-- {sigilQuery.string} -->
57</div>
88</div>
ui/src/components/search/sigil/Symbol.svelte+6−1
@@ -10,6 +10,7 @@
10 export let bgColor: string = 'white';
10 export let bgColor: string = 'white';
11 export let altBgColor: string = undefined;
11 export let altBgColor: string = undefined;
12 export let hoverColor: string = 'blue';
12 export let hoverColor: string = 'blue';
13+ export let opacity: number = 1;
13 export let inverted: boolean = false;
14 export let inverted: boolean = false;
14 export let interactive: boolean = false;
15 export let interactive: boolean = false;
15 export let strokeWidthFactor: number = 1;
16 export let strokeWidthFactor: number = 1;
@@ -62,11 +63,15 @@
62 stroke: var(--hoverColor);
63 stroke: var(--hoverColor);
63 }
64 }
64 }
65 }
66+
67+ circle:not([dataisgeon="true"]):not([fill="none"]):hover {
68+ fill: var(--hoverColor);
69+ }
65 }
70 }
66</style>
71</style>
67
72
68<div class="aspect-square" class:interactive style:max-width="{size}px"
73<div class="aspect-square" class:interactive style:max-width="{size}px"
69− style="--hoverColor: {hoverColor};"
74+ style="--hoverColor: {hoverColor}; opacity: {opacity};"
70 on:click
75 on:click
71 use:symbolPartClick={{ onPartClick, enabled: interactive, svgString }}
76 use:symbolPartClick={{ onPartClick, enabled: interactive, svgString }}
72>
77>
ui/src/components/search/sigil/SymbolInput.svelte+54−8
@@ -2,15 +2,45 @@
2 import Symbol from './Symbol.svelte';
2 import Symbol from './Symbol.svelte';
3 import without from 'lodash/without';
3 import without from 'lodash/without';
4
4
5− import type { PartType, SymbolQuery } from 'types/sigil';
5+ import { searchSettings } from 'stores/searchStores';
6+ import type { PartType } from 'types/sigil';
7+ import type { SymbolQuery } from 'lib/sigil';
8+ import { filterPartsByGeon } from 'lib/sigil';
6
9
7 export let symbolQuery: SymbolQuery;
10 export let symbolQuery: SymbolQuery;
8 // export let inputMode: PartType;
11 // export let inputMode: PartType;
9− export let focused: boolean = false;
12+ export let focusedSymbolIndex: number;
13+ export let index: number = 0;
14+ export let clan: string = 'galaxy';
10 export let inputComponents: string[] = [];
15 export let inputComponents: string[] = [];
11 let size = 128;
16 let size = 128;
12
17
18+ let translateClass;
19+ $: {
20+ if (focusedSymbolIndex === undefined) {
21+ translateClass = 'translate-x-0 translate-y-0';
22+ } else if (clan === 'star') {
23+ translateClass = index ? 'origin-right' : 'origin-left';
24+ } else if (clan === 'planet') {
25+ let cornerIndex = index;
26+ if (!focused) cornerIndex = focusedSymbolIndex;
27+ switch (cornerIndex) {
28+ case 0: translateClass = 'translate-x-1/4 translate-y-1/4'; break;
29+ case 1: translateClass = '-translate-x-1/4 translate-y-1/4'; break;
30+ case 2: translateClass = 'translate-x-1/4 -translate-y-1/4'; break;
31+ case 3:
32+ default: translateClass = '-translate-x-1/4 -translate-y-1/4';
33+ }
34+ }
35+ }
36+ $: scaleClass = 'scale-150 ';
37+
38+ $: focused = index === focusedSymbolIndex;
39+ $: focusedClasses = (
40+ focused ? `${scaleClass} z-10`
41+ : (focusedSymbolIndex !== undefined ? 'scale-50' : '' )
42+ );
13− $: focusedClasses = focused ? 'border border-gold-s1 border-2' : '';
43+ // $: focusedClasses = focused ? `border border-gold-s1 border-2` : '';
14
44
15 function onPartDelete({ detail: partId }) {
45 function onPartDelete({ detail: partId }) {
16 symbolQuery = symbolQuery.removePart(partId);
46 symbolQuery = symbolQuery.removePart(partId);
@@ -21,32 +51,48 @@
21 }
51 }
22
52
23 let filteredInputComponents = [];
53 let filteredInputComponents = [];
54+ let inputContainsStrokes;
24 $: {
55 $: {
25 if (inputComponents) {
56 if (inputComponents) {
26 filteredInputComponents = without(inputComponents, ...symbolQuery.components);
57 filteredInputComponents = without(inputComponents, ...symbolQuery.components);
27 } else {
58 } else {
28 filteredInputComponents = [];
59 filteredInputComponents = [];
29 }
60 }
61+ filteredInputComponents = filterPartsByGeon(filteredInputComponents, symbolQuery.geon);
30 }
62 }
63+ $: {
64+ inputContainsStrokes = undefined !== filteredInputComponents.find((c) => {
65+ return c[0] === 'l' || c[0] === 'a' || c[0] === 'b';
66+ });
67+ }
68+ $: plausible = $searchSettings.allowFictionalSigils || symbolQuery.isPlausible;
31</script>
69</script>
32
70
33−<div class="aspect-square relative flex {focusedClasses}" style:max-width="{size}px" on:click>
71+<div class="aspect-square relative flex transition {translateClass} {focusedClasses}"
72+ style:max-width="{size}px"
73+ on:click|stopPropagation
74+>
34 {#if symbolQuery.components.length > 0}
75 {#if symbolQuery.components.length > 0}
35 <Symbol components={symbolQuery.components} {size}
76 <Symbol components={symbolQuery.components} {size}
36 inverted={!symbolQuery.geon}
77 inverted={!symbolQuery.geon}
37 interactive={focused}
78 interactive={focused}
79+ fgColor={plausible ? 'black' : 'orange'}
38 hoverColor="red"
80 hoverColor="red"
39 on:partClick={onPartDelete}
81 on:partClick={onPartDelete}
40 />
82 />
41 {:else}
83 {:else}
84+ <div class="border-[1.5px] border-white grow bg-white">
42− <div class="m-1 rounded-sm border-4 border-gray-500 grow"></div>
85+ <div class="rounded-md border-4 border-gray-500 h-full"></div>
86+ </div>
43 {/if}
87 {/if}
44 {#if focused && filteredInputComponents.length}
88 {#if focused && filteredInputComponents.length}
45 <div class="absolute inset-0 pointer-events-none">
89 <div class="absolute inset-0 pointer-events-none">
46 <Symbol components={filteredInputComponents} {size}
90 <Symbol components={filteredInputComponents} {size}
47− strokeWidthFactor={2.5}
91+ strokeWidthFactor={inputContainsStrokes ? 2.5 : 1}
48− fgColor={symbolQuery.geon ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)"}
92+ fgColor={symbolQuery.geon ? "white" : "black"}
93+ bgColor={!symbolQuery.geon ? "white" : "black"}
49− bgColor="transparent"
94+ altBgColor="transparent"
95+ opacity={0.5}
50 inverted
96 inverted
51 interactive
97 interactive
52 on:partClick={onPartClick}
98 on:partClick={onPartClick}
ui/src/components/search/sigil/SymbolInputModeSelector.svelte+40−144
@@ -2,133 +2,24 @@
2 import GeonSelector from './GeonSelector.svelte';
2 import GeonSelector from './GeonSelector.svelte';
3 import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
3 import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
4
4
5− import Symbol from './Symbol.svelte';
5+ import { searchSettings } from 'stores/searchStores';
6+ import type { PartType, ModeDef, ModeDefs } from 'types/sigil';
7+ import type { SymbolQuery } from 'lib/sigil';
8+ import { MODE_DEFS } from 'types/sigil';
9+ import { filterPartsByGeon, getDeepParts } from 'lib/sigil';
6
10
7− import type { PartType, SymbolQuery } from 'types/sigil';
11+ import upArrow from 'assets/up-arrow.svg';
12+ import xIcon from 'assets/x.svg';
8
13
9 export let symbolQuery: SymbolQuery;
14 export let symbolQuery: SymbolQuery;
10− // export let inputMode: PartType;
11 export let inputComponents: string[] = [];
15 export let inputComponents: string[] = [];
12 export let unfocusSymbol: Function;
16 export let unfocusSymbol: Function;
13
17
14− interface ModeDefs {
15− [mode: string]: ModeDef
16− }
17− interface ModeDef {
18− modes?: ModeDefs;
19− displayParts?: string[];
20− parts?: string[];
21− }
22−
23− const modeDefs: ModeDefs = {
24− geon: {
25− modes: {},
26− displayParts: ['ghb'],
27− },
28− line: {
29− modes: {
30− mainFulls: {
31− parts: ['lvf', 'lhf', 'lff', 'lbf'],
32− },
33− halvesFulls: {
34− parts: ['lvfl', 'lvfr', 'lhft', 'lhfb', 'lfht', 'lfhb', 'lbht', 'lbhb'],
35− },
36− forwards: {
37− parts: ['lff', 'lfft', 'lffb'],
38− },
39− backwards: {
40− parts: ['lbf', 'lbft'],
41− },
42− weirds: {
43− parts: ['lbhtf', 'lbrf', 'lfhm'],
44− }
45− },
46− parts: ['lvf', 'lvfl', 'lvfr', 'lhf', 'lhft', 'lhfb', 'lff', 'lbf'],
47− },
48− arcCorner: {
49− modes: {
50− trFulls: {
51− parts: ['atrf8', 'atrf6', 'atrf4', 'atrf2'],
52− },
53− trHalves: {
54− parts: [/*'atra8',*/ 'atra6', 'atra4', 'atra2', 'atrb8', 'atrb6', 'atrb4', 'atrb2'],
55− displayParts: ['atrb8', 'atrb6', 'atrb4', 'atrb2'],
56− },
57− tlFulls: {
58− parts: ['atlf8', 'atlf6', 'atlf4', 'atlf2'],
59− },
60− tlHalves: {
61− parts: ['atla8', 'atla6', 'atla4', 'atla2', 'atlb8', 'atlb6', 'atlb4', 'atlb2'],
62− displayParts: ['atlb8', 'atlb6', 'atlb4', 'atlb2'],
63− },
64− blFulls: {
65− parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
66− },
67− blHalves: {
68− parts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
69− displayParts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
70− },
71− brFulls: {
72− parts: ['abrf8', 'abrf6', 'abrf4', 'abrf2'],
73− },
74− brHalves: {
75− parts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
76− displayParts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
77− },
78− },
79− parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
80− },
81− arc: {
82− modes: {
83− rings: {
84− parts: ['am081', 'am082', 'am083', 'am08s'],
85− },
86− diagonals: {
87− parts: ['am741', 'am742', 'am743', 'am422', 'am423'],
88− },
89− topsBottoms: {
90− parts: ['ab2', 'ab3', 'ab4', 'at1', 'at3', 'at4'],
91− },
92− leftsRights: {
93− parts: ['al2', 'al3', 'al4', 'ar1', 'ar2', 'ar4'/*, 'arb2'*/],
94− },
95− rights: {
96− parts: ['am642', 'am643'],
97− },
98− middles: {
99− parts: ['am222', 'am223', 'am622', 'am623'],
100− },
101− middless: {
102− parts: ['am142', 'am443'],
103− },
104− topRights: {
105− parts: ['am021', 'am022', 'am023', 'am242', 'arb2'],
106− },
107− // weirds: {
108− // parts: ['arb2'],
109− // },
110− },
111− parts: ['am222', 'am223', 'ar4', 'ar2', 'ar1'],
112− },
113− circle: {
114− parts: [
115− 'cbr3mb', 'ctl3mb',
116− 'cmmb',
117− 'ct2mb', 'cl2mb', 'cb2mb', 'cr2mb',
118− ],
119− },
120− donut: {
121− parts: ['dbl', /*'dl',*/ 'dm', 'dtl', 'dtr'],
122− },
123− bezier: {
124− parts: ['b1l1', 'b1l2', 'b1l3'],
125− },
126− };
127 let modes: string[] = [];
18 let modes: string[] = [];
128 let modeDef: ModeDef = undefined;
19 let modeDef: ModeDef = undefined;
129− let subModeDefs: ModeDefs = modeDefs;
20+ let subModeDefs: ModeDefs = MODE_DEFS;
130− let size = 48;
131
21
22+ let buttonClasses = 'border border-gray-500 rounded-md aspect-square w-[48px]';
132
23
133 $: {
24 $: {
134 if (!symbolQuery.geon) {
25 if (!symbolQuery.geon) {
@@ -158,7 +49,7 @@
158
49
159 $: {
50 $: {
160 modeDef = undefined;
51 modeDef = undefined;
161− subModeDefs = modeDefs;
52+ subModeDefs = MODE_DEFS;
162 for (let mode of modes) {
53 for (let mode of modes) {
163 if (modeDef && modeDef.modes) {
54 if (modeDef && modeDef.modes) {
164 subModeDefs = modeDef.modes;
55 subModeDefs = modeDef.modes;
@@ -176,6 +67,9 @@
176
67
177 if (modeDef?.parts?.length && !modeDef.modes) {
68 if (modeDef?.parts?.length && !modeDef.modes) {
178 inputComponents = modeDef.parts;
69 inputComponents = modeDef.parts;
70+ if (!$searchSettings.allowFictionalSigils) {
71+ inputComponents = symbolQuery.filterPlausibleParts(inputComponents);
72+ }
179 } else {
73 } else {
180 inputComponents = [];
74 inputComponents = [];
181 }
75 }
@@ -190,32 +84,34 @@
190 }
84 }
191</script>
85</script>
192
86
193−<div class="grid grid-rows-2 grid-flow-col xs:grid-cols-2 xs:grid-flow-row place-content-center justify-center">
87+<style lang="scss">
194− <div
88+ .grid-cols-custom {
195− class="aspect-square"
89+ grid-template-columns: repeat(auto-fill, 48px);
90+ }
91+</style>
92+
93+<div class="mx-auto flex items-center justify-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">
196− on:click={popMode}
95+ <img src={upArrow} alt="done" class={buttonClasses} on:click={popMode}/>
197− >
96+ <img src={xIcon} alt="done" class={buttonClasses} on:click={clear}/>
198− done
199 </div>
97 </div>
200− <div
98+ <div class="max-w-[15rem] grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
201− class="aspect-square"
99+ {#if modes[0] === 'geon'}
100+ <GeonSelector bind:symbolQuery {popMode} />
101+ {:else}
102+ {#each Object.entries(subModeDefs) as [modeName, def]}
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}
202− on:click={clear}
108+ on:click={() => pushMode(modeName)}
203− >
109+ />
204− clear
110+ <!-- {:else}
111+ {deepParts.join()} -->
112+ {/if}
113+ {/each}
114+ {/if}
205 </div>
115 </div>
206− {#if modes[0] === 'geon'}
207− <GeonSelector bind:symbolQuery {popMode} />
208− {:else}
209− <!-- {#each PART_TYPES as partType}
210− <SymbolInputModeSelectorButton {partType} focused={modes[0] === partType}
211− on:click={() => pushMode(partType)}
212− />
213− {/each} -->
214− {#each Object.entries(subModeDefs) as [modeName, def]}
215− <SymbolInputModeSelectorButton components={def.displayParts || def.parts} focused={modes.slice(-1)[0] === modeName}
216− on:click={() => pushMode(modeName)}
217− />
218− {/each}
219− {/if}
220</div>
116</div>
221
117
ui/src/components/search/sigil/SymbolInputModeSelectorButton.svelte+24−32
@@ -1,41 +1,33 @@
1<script lang="ts">
1<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
2 import Symbol from './Symbol.svelte';
3 import Symbol from './Symbol.svelte';
3
4
4− import type { PartType, SymbolQuery } from 'types/sigil';
5− import { PART_TYPES } from 'types/sigil';
6−
7− // export let partType: PartType;
8 export let components: string[] = [];
5 export let components: string[] = [];
9 export let focused: boolean = false;
6 export let focused: boolean = false;
10−
7+ export let enabled: boolean = true;
8+ export let padding: number = undefined;
9+
10+ const dispatch = createEventDispatcher();
11 let size = 48;
11 let size = 48;
12− $: focusedClasses = focused ? 'border border-gold-s1 border-2' : '';
13− $: includesGeon = components.find(c => c[0] === 'g') === undefined;
14−</script>
15
12
13+ $: focusedClasses = focused ? 'border-2 border-gold-s1 p-[3px]' : 'border border-gray-500 p-[4px]';
14+ $: enabledClasses = enabled ? '' : 'opacity-50';
15+ $: includesGeon = components.find(c => c[0] === 'g') !== undefined;
16+ $: defaultPadding = includesGeon ? 0 : 0;
17+ $: actualPadding = padding !== undefined ? padding : defaultPadding;
18+
19+ function onClick(e) {
20+ if (enabled) dispatch('click', e);
21+ }
22+</script>
23+<div
16−<div class="border-2 w-[48px] {focusedClasses}" on:click>
24+class="bg-white rounded-md w-[48px] {focusedClasses} {enabledClasses}"
17− <Symbol {components} {size} inverted={includesGeon} />
25+on:click={onClick}
18− <!-- {#if partType === 'geon'}
26+>
19− <Symbol components={['ghb']} {size} />
27+<div class:cursor-pointer={enabled}
20− {:else if partType === 'line'}
28+ style:padding="{actualPadding}px"
21− <Symbol components={['lvf', 'lvfl', 'lvfr', 'lhf', 'lhft', 'lhfb', 'lff', 'lbf']} {size} inverted />
29+>
22− {:else if partType === 'arc'}
23− <Symbol components={['abl202', 'abl402', 'abl602', 'abl802', 'ar124', 'ar224', 'ar424' ]} {size} inverted />
24− {:else if partType === 'ring'}
25− <Symbol components={['rm1', 'rm2', 'rm3']} {size} inverted />
26− {:else if partType === 'circle'}
27− <Symbol components={[
28− // 'ctr1mb', 'cbr1mb', 'cbl1mb', 'ctl1mb',
29− 'cbr3mb', 'ctl3mb',
30− 'cmmb',
31− 'ct2mb', 'cl2mb', 'cb2mb', 'cr2mb',
32− ]} {size} inverted />
33− {:else if partType === 'donut'}
34− <Symbol components={['dbl', /*'dl',*/ 'dm', 'dtl', 'dtr']} {size} inverted />
30+ <Symbol {components} {size} inverted={!includesGeon} />
35− {:else if partType === 'bezier'}
31+</div>
36− <Symbol components={['b1l1', 'b1l2', 'b1l3']} {size} inverted />
37− {:else}
38− not {partType}
39− {/if} -->
40</div>
32</div>
41
33
ui/src/lib/sigil.ts+197−8
@@ -1,9 +1,20 @@
1+import intersection from 'lodash/intersection';
2+import without from 'lodash/without';
3+import uniq from 'lodash/uniq';
4+import { partParents, symbolDefs } from '@johnhyde/sigil-js';
5+
1−import type { Clan } from 'types/sigil';
6+import type { Clan, Geon, ModeDef, PartType, SymbolType } from 'types/sigil';
2−import { SymbolQuery } from 'types/sigil';
7+import { PART_TYPES } from 'types/sigil';
8+import { prefixes, suffixes } from 'lib/id';
9+
10+export function sigilScalingFunction(size: number): number {
11+ return 3.5 * Math.sqrt(51 / size);
12+}
3
13
4export class SigilQuery {
14export class SigilQuery {
5 clan: Clan;
15 clan: Clan;
6 symbols: SymbolQuery[];
16 symbols: SymbolQuery[];
17+ allowFictional: boolean = false;
7
18
8 constructor(clan: Clan = 'planet', symbols = []) {
19 constructor(clan: Clan = 'planet', symbols = []) {
9 this.clan = clan;
20 this.clan = clan;
@@ -13,19 +24,18 @@ export class SigilQuery {
13
24
14 padSymbols() {
25 padSymbols() {
15 if (this.symbols.length === 1) {
26 if (this.symbols.length === 1) {
16− this.symbols.unshift(new SymbolQuery());
27+ this.symbols.unshift(new SymbolQuery(this));
17 }
28 }
18 while (this.symbols.length < 4) {
29 while (this.symbols.length < 4) {
19− this.symbols.push(new SymbolQuery());
30+ this.symbols.push(new SymbolQuery(this));
20 }
31 }
21 this.symbols = this.symbols.slice(0, 4);
32 this.symbols = this.symbols.slice(0, 4);
22 this.symbols.forEach((symbol, i) => {
33 this.symbols.forEach((symbol, i) => {
23 if (i % 2 === 0) {
34 if (i % 2 === 0) {
24 symbol.symbolType = 'prefix';
35 symbol.symbolType = 'prefix';
25− // symbol.components = ['gci', 'abr622']
26 } else {
36 } else {
27 symbol.symbolType = 'suffix';
37 symbol.symbolType = 'suffix';
28− symbol.components = ['gsq', 'ablf2']
38+ // symbol.components = ['gsq', 'ablf2']
29 }
39 }
30 });
40 });
31 this.symbols = this.symbols;
41 this.symbols = this.symbols;
@@ -39,6 +49,10 @@ export class SigilQuery {
39 return this.symbols;
49 return this.symbols;
40 }
50 }
41
51
52+ get isPlausible() {
53+ return this.activeSymbols.every(symbol => !symbol.isPlausible);
54+ }
55+
42 get string(): string {
56 get string(): string {
43 return this.symbols.map((symbol) => symbol.components.join()).join(' | ')
57 return this.symbols.map((symbol) => symbol.components.join()).join(' | ')
44 }
58 }
@@ -50,6 +64,181 @@ export class SigilQuery {
50 }
64 }
51}
65}
52
66
53−export function sigilScalingFunction(size: number): number {
67+export function getPlausibleSyllables(parts: string[], symbolType: SymbolType): string[] {
54− return 3.5 * Math.sqrt(51 / size);
68+ let plausibleSyllables: string[];
69+ if (symbolType === 'prefix') {
70+ plausibleSyllables = prefixes;
71+ } else if (symbolType === 'suffix') {
72+ plausibleSyllables = suffixes;
73+ } else {
74+ plausibleSyllables = [...prefixes, ...suffixes];
75+ }
76+ const possibilitiesByPart = parts.map((partId) => partParents[partId]);
77+ plausibleSyllables = intersection(plausibleSyllables, ...possibilitiesByPart);
78+ return plausibleSyllables;
79+}
80+
81+export function getPlausibleNewParts(parts: string[], symbolType: SymbolType): string[] {
82+ const syllables: string[] = getPlausibleSyllables(parts, symbolType);
83+ let plausibleParts = uniq(syllables.map((syllable) => symbolDefs[syllable]).flat());
84+ return without(plausibleParts, ...parts);
85+}
86+
87+export function filterPlausibleParts(parts: string[], symbolType: SymbolType, newParts: string[]): string[] {
88+ const plausibleParts = getPlausibleNewParts(parts, symbolType);
89+ return intersection(newParts, plausibleParts);
90+}
91+
92+export function arePartsPlausible(parts: string[], symbolType: SymbolType, newParts: string[]): boolean {
93+ return filterPlausibleParts(parts, symbolType, newParts).length > 0;
94+}
95+
96+function hasComplement(partId: string): boolean {
97+ const secondToLastChar = partId.slice(-2)[0];
98+ return partId[0] === 'c' && (secondToLastChar === 'm' || secondToLastChar === 'l');
99+}
100+
101+function getComplementaryCircleId(circleId: string): string {
102+ let newId = circleId.replace(/m(?=.$)/, 'l');
103+ if (newId === circleId) {
104+ newId = circleId.replace(/l(?=.$)/, 'm');
105+ }
106+ circleId = newId;
107+ circleId = circleId.replace(/f$/, 'b');
108+ if (newId === circleId) {
109+ circleId = circleId.replace(/b$/, 'f');
110+ }
111+ return circleId;
112+}
113+
114+function getPartTypeIndex(partId: string): number {
115+ return PART_TYPES.findIndex(p => p[0] === partId[0]);
116+}
117+
118+export function filterPartsByGeon(parts: string[], geon: Geon): string[] {
119+ if (['gq1', 'gbs'].includes(geon)) {
120+ parts = without(parts, 'ctr2mb', 'ctr2lf', 'atrf2', 'atra2', 'atrb2');
121+ } else {
122+ parts = without(parts, 'ctr3mf', 'ctr3lb');
123+ }
124+ if (['gq2', 'gfs'].includes(geon)) {
125+ parts = without(parts, 'ctl3mb', 'ctl3lf', 'ctl2mb', 'ctl2lf', 'atlf2', 'atla2', 'atlb2');
126+ } else {
127+ parts = without(parts, 'ctl3mf', 'ctl3lb');
128+ }
129+ if (['gq3', 'gbs'].includes(geon)) {
130+ parts = without(parts, 'cbl3mb', 'cbl3lf', 'cbl2mb', 'cbl2lf', 'ablf2', 'ablb2');
131+ } else {
132+ parts = without(parts, 'cbl3mf', 'cbl3lb');
133+ }
134+ if (['gq4', 'gfs'].includes(geon)) {
135+ parts = without(parts, 'cbr2mb', 'cbr2lf', 'abrf2', 'abrb2', 'arb2');
136+ }
137+ return parts;
138+}
139+
140+export class SymbolQuery {
141+ sigilQuery?: SigilQuery;
142+ symbolType: SymbolType;
143+ components: string[];
144+
145+ constructor(sigilQuery: SigilQuery = undefined, components = [], symbolType: SymbolType = 'suffix') {
146+ this.sigilQuery = sigilQuery;
147+ this.components = components;
148+ this.symbolType = symbolType;
149+ this.cleanupParts();
150+ }
151+
152+ set geon(geon: Geon) {
153+ this.components = this.components.filter(c => c[0] !== 'g');
154+ if (geon)
155+ this.components = [geon, ...this.components];
156+ this.cleanupParts();
157+ }
158+
159+ get geon(): Geon {
160+ return this.components.find(c => c[0] === 'g');
161+ }
162+
163+ get allowFictional() {
164+ if (this.sigilQuery) return this.sigilQuery.allowFictional;
165+ return true;
166+ }
167+
168+ get plausibleSyllables() {
169+ return getPlausibleSyllables(this.components, this.symbolType);
170+ }
171+
172+ get isPlausible() {
173+ return this.plausibleSyllables.length > 0;
174+ }
175+
176+ get plausibleParts() {
177+ return getPlausibleNewParts(this.components, this.symbolType);
178+ }
179+
180+ filterPlausibleParts(parts: string[]): string[] {
181+ return filterPlausibleParts(this.components, this.symbolType, parts);
182+ }
183+
184+ arePartsPlausible(parts: string[]): boolean {
185+ return arePartsPlausible(this.components, this.symbolType, parts);
186+ }
187+
188+ cleanupParts() {
189+ this.components = uniq(this.components);
190+ this.components = filterPartsByGeon(this.components, this.geon);
191+ // if (!this.allowFictional) {
192+ // this.components = this.plausibleParts;
193+ // }
194+ this.components = this.components.sort((c1, c2) => {
195+ let i1 = getPartTypeIndex(c1);
196+ let i2 = getPartTypeIndex(c2);
197+ let diff = i1 - i2;
198+ if (diff !== 0) return diff;
199+ if (c1[0] === 'c') {
200+ return c1.localeCompare(c2);
201+ } else {
202+ return c2.localeCompare(c1);
203+ }
204+ });
205+ }
206+
207+ clearPartType(partType: PartType) {
208+ this.components = this.components.filter(c => c[0] !== partType[0]);
209+ return this;
210+ }
211+
212+ clear() {
213+ this.components = [];
214+ return this;
215+ }
216+
217+ addPart(partId: string) {
218+ this.components = [...this.components, partId];
219+ if (hasComplement(partId)) {
220+ const otherPartId = getComplementaryCircleId(partId);
221+ this.components = [...this.components, otherPartId];
222+ }
223+ this.cleanupParts();
224+ return this;
225+ }
226+
227+ removePart(partId: string) {
228+ this.components = without(this.components, partId);
229+ if (hasComplement(partId)) {
230+ const otherPartId = getComplementaryCircleId(partId);
231+ this.components = without(this.components, otherPartId);
232+ }
233+ this.cleanupParts();
234+ return this;
235+ }
236+}
237+
238+export function getDeepParts(def: ModeDef): string[] {
239+ if (def.modes) {
240+ return uniq(Object.values(def.modes).map(getDeepParts).flat());
241+ } else {
242+ return def.parts || [];
243+ }
55}
244}
ui/src/styles/tailwind-output.css+207
@@ -559,6 +559,12 @@ Ensure the default browser behavior of the `hidden` attribute.
559.z-50 {
559.z-50 {
560 z-index: 50;
560 z-index: 50;
561}
561}
562+.col-span-3 {
563+ grid-column: span 3 / span 3;
564+}
565+.col-span-2 {
566+ grid-column: span 2 / span 2;
567+}
562.m-4 {
568.m-4 {
563 margin: 1rem;
569 margin: 1rem;
564}
570}
@@ -687,6 +693,12 @@ Ensure the default browser behavior of the `hidden` attribute.
687.-mt-0\.5 {
693.-mt-0\.5 {
688 margin-top: -0.125rem;
694 margin-top: -0.125rem;
689}
695}
696+.mb-1 {
697+ margin-bottom: 0.25rem;
698+}
699+.mr-1 {
700+ margin-right: 0.25rem;
701+}
690.block {
702.block {
691 display: block;
703 display: block;
692}
704}
@@ -780,6 +792,9 @@ Ensure the default browser behavior of the `hidden` attribute.
780.min-h-0 {
792.min-h-0 {
781 min-height: 0px;
793 min-height: 0px;
782}
794}
795+.min-h-\[14rem\] {
796+ min-height: 14rem;
797+}
783.w-full {
798.w-full {
784 width: 100%;
799 width: 100%;
785}
800}
@@ -840,6 +855,9 @@ Ensure the default browser behavior of the `hidden` attribute.
840.w-\[48px\] {
855.w-\[48px\] {
841 width: 48px;
856 width: 48px;
842}
857}
858+.w-\[\] {
859+ width: ;
860+}
843.min-w-0 {
861.min-w-0 {
844 min-width: 0px;
862 min-width: 0px;
845}
863}
@@ -864,6 +882,21 @@ Ensure the default browser behavior of the `hidden` attribute.
864.max-w-\[18rem\] {
882.max-w-\[18rem\] {
865 max-width: 18rem;
883 max-width: 18rem;
866}
884}
885+.max-w-\[10rem\] {
886+ max-width: 10rem;
887+}
888+.max-w-\[21rem\] {
889+ max-width: 21rem;
890+}
891+.max-w-\[20rem\] {
892+ max-width: 20rem;
893+}
894+.max-w-\[19rem\] {
895+ max-width: 19rem;
896+}
897+.max-w-\[15rem\] {
898+ max-width: 15rem;
899+}
867.flex-shrink-0 {
900.flex-shrink-0 {
868 flex-shrink: 0;
901 flex-shrink: 0;
869}
902}
@@ -885,6 +918,48 @@ Ensure the default browser behavior of the `hidden` attribute.
885.border-collapse {
918.border-collapse {
886 border-collapse: collapse;
919 border-collapse: collapse;
887}
920}
921+.origin-bottom-right {
922+ transform-origin: bottom right;
923+}
924+.origin-left {
925+ transform-origin: left;
926+}
927+.origin-right {
928+ transform-origin: right;
929+}
930+.origin-top-left {
931+ transform-origin: top left;
932+}
933+.origin-top-right {
934+ transform-origin: top right;
935+}
936+.origin-bottom-left {
937+ transform-origin: bottom left;
938+}
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 {
948+ --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));
950+}
951+.translate-y-0 {
952+ --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));
954+}
955+.-translate-x-1\/4 {
956+ --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));
958+}
959+.-translate-y-1\/4 {
960+ --tw-translate-y: -25%;
961+ 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));
962+}
888.rotate-45 {
963.rotate-45 {
889 --tw-rotate: 45deg;
964 --tw-rotate: 45deg;
890 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));
965 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));
@@ -893,6 +968,16 @@ Ensure the default browser behavior of the `hidden` attribute.
893 --tw-rotate: -45deg;
968 --tw-rotate: -45deg;
894 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));
969 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));
895}
970}
971+.scale-150 {
972+ --tw-scale-x: 1.5;
973+ --tw-scale-y: 1.5;
974+ 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));
975+}
976+.scale-50 {
977+ --tw-scale-x: .5;
978+ --tw-scale-y: .5;
979+ 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));
980+}
896.transform {
981.transform {
897 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));
982 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));
898}
983}
@@ -918,9 +1003,26 @@ Ensure the default browser behavior of the `hidden` attribute.
918.resize {
1003.resize {
919 resize: both;
1004 resize: both;
920}
1005}
1006+.auto-cols-min {
1007+ grid-auto-columns: -webkit-min-content;
1008+ grid-auto-columns: min-content;
1009+}
1010+.auto-cols-fr {
1011+ grid-auto-columns: minmax(0, 1fr);
1012+}
1013+.grid-flow-row {
1014+ grid-auto-flow: row;
1015+}
921.grid-flow-col {
1016.grid-flow-col {
922 grid-auto-flow: column;
1017 grid-auto-flow: column;
923}
1018}
1019+.grid-flow-col-dense {
1020+ grid-auto-flow: column dense;
1021+}
1022+.auto-rows-min {
1023+ grid-auto-rows: -webkit-min-content;
1024+ grid-auto-rows: min-content;
1025+}
924.grid-cols-3 {
1026.grid-cols-3 {
925 grid-template-columns: repeat(3, minmax(0, 1fr));
1027 grid-template-columns: repeat(3, minmax(0, 1fr));
926}
1028}
@@ -930,6 +1032,12 @@ Ensure the default browser behavior of the `hidden` attribute.
930.grid-cols-1 {
1032.grid-cols-1 {
931 grid-template-columns: repeat(1, minmax(0, 1fr));
1033 grid-template-columns: repeat(1, minmax(0, 1fr));
932}
1034}
1035+.grid-cols-4 {
1036+ grid-template-columns: repeat(4, minmax(0, 1fr));
1037+}
1038+.grid-cols-6 {
1039+ grid-template-columns: repeat(6, minmax(0, 1fr));
1040+}
933.grid-rows-2 {
1041.grid-rows-2 {
934 grid-template-rows: repeat(2, minmax(0, 1fr));
1042 grid-template-rows: repeat(2, minmax(0, 1fr));
935}
1043}
@@ -951,6 +1059,9 @@ Ensure the default browser behavior of the `hidden` attribute.
951.content-center {
1059.content-center {
952 align-content: center;
1060 align-content: center;
953}
1061}
1062+.items-start {
1063+ align-items: flex-start;
1064+}
954.items-center {
1065.items-center {
955 align-items: center;
1066 align-items: center;
956}
1067}
@@ -1033,6 +1144,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1033 --tw-divide-opacity: 1;
1144 --tw-divide-opacity: 1;
1034 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
1145 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
1035}
1146}
1147+.place-self-center {
1148+ place-self: center;
1149+}
1150+.self-start {
1151+ align-self: flex-start;
1152+}
1036.self-center {
1153.self-center {
1037 align-self: center;
1154 align-self: center;
1038}
1155}
@@ -1110,6 +1227,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1110.border-2 {
1227.border-2 {
1111 border-width: 2px;
1228 border-width: 2px;
1112}
1229}
1230+.border-\[1\.5px\] {
1231+ border-width: 1.5px;
1232+}
1233+.border-0 {
1234+ border-width: 0px;
1235+}
1113.border-l-8 {
1236.border-l-8 {
1114 border-left-width: 8px;
1237 border-left-width: 8px;
1115}
1238}
@@ -1293,6 +1416,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1293 --tw-bg-opacity: 1;
1416 --tw-bg-opacity: 1;
1294 background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1417 background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1295}
1418}
1419+.bg-gray-500 {
1420+ --tw-bg-opacity: 1;
1421+ background-color: rgb(107 114 128 / var(--tw-bg-opacity));
1422+}
1296.fill-transparent {
1423.fill-transparent {
1297 fill: transparent;
1424 fill: transparent;
1298}
1425}
@@ -1317,6 +1444,27 @@ Ensure the default browser behavior of the `hidden` attribute.
1317.p-1 {
1444.p-1 {
1318 padding: 0.25rem;
1445 padding: 0.25rem;
1319}
1446}
1447+.p-0 {
1448+ padding: 0px;
1449+}
1450+.p-0\.5 {
1451+ padding: 0.125rem;
1452+}
1453+.p-\[3px\] {
1454+ padding: 3px;
1455+}
1456+.p-\[4px\] {
1457+ padding: 4px;
1458+}
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+}
1320.px-2 {
1468.px-2 {
1321 padding-left: 0.5rem;
1469 padding-left: 0.5rem;
1322 padding-right: 0.5rem;
1470 padding-right: 0.5rem;
@@ -1539,6 +1687,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1539 --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1687 --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1540 box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1688 box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1541}
1689}
1690+.drop-shadow {
1691+ --tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06));
1692+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1693+}
1694+.sepia {
1695+ --tw-sepia: sepia(100%);
1696+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1697+}
1542.filter {
1698.filter {
1543 filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1699 filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1544}
1700}
@@ -1557,6 +1713,13 @@ Ensure the default browser behavior of the `hidden` attribute.
1557 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1713 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1558 transition-duration: 150ms;
1714 transition-duration: 150ms;
1559}
1715}
1716+.transition {
1717+ transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
1718+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
1719+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
1720+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1721+ transition-duration: 150ms;
1722+}
1560.hover\:bg-gray-200:hover {
1723.hover\:bg-gray-200:hover {
1561 --tw-bg-opacity: 1;
1724 --tw-bg-opacity: 1;
1562 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1725 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
@@ -1604,22 +1767,58 @@ Ensure the default browser behavior of the `hidden` attribute.
1604 margin: 0px;
1767 margin: 0px;
1605 }
1768 }
1606
1769
1770+ .xs\:mr-0 {
1771+ margin-right: 0px;
1772+ }
1773+
1774+ .xs\:mb-1 {
1775+ margin-bottom: 0.25rem;
1776+ }
1777+
1607 .xs\:max-w-\[18rem\] {
1778 .xs\:max-w-\[18rem\] {
1608 max-width: 18rem;
1779 max-width: 18rem;
1609 }
1780 }
1610
1781
1782+ .xs\:max-w-\[8rem\] {
1783+ max-width: 8rem;
1784+ }
1785+
1611 .xs\:grid-flow-row {
1786 .xs\:grid-flow-row {
1612 grid-auto-flow: row;
1787 grid-auto-flow: row;
1613 }
1788 }
1614
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+
1615 .xs\:grid-cols-2 {
1798 .xs\:grid-cols-2 {
1616 grid-template-columns: repeat(2, minmax(0, 1fr));
1799 grid-template-columns: repeat(2, minmax(0, 1fr));
1617 }
1800 }
1618
1801
1802+ .xs\:grid-rows-3 {
1803+ grid-template-rows: repeat(3, minmax(0, 1fr));
1804+ }
1805+
1619 .xs\:flex-row {
1806 .xs\:flex-row {
1620 flex-direction: row;
1807 flex-direction: row;
1621 }
1808 }
1622
1809
1810+ .xs\:flex-col {
1811+ flex-direction: column;
1812+ }
1813+
1814+ .xs\:items-stretch {
1815+ align-items: stretch;
1816+ }
1817+
1818+ .xs\:self-stretch {
1819+ align-self: stretch;
1820+ }
1821+
1623 .xs\:rounded-bl-2xl {
1822 .xs\:rounded-bl-2xl {
1624 border-bottom-left-radius: 1rem;
1823 border-bottom-left-radius: 1rem;
1625 }
1824 }
@@ -1627,6 +1826,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1627 .xs\:rounded-bl-none {
1826 .xs\:rounded-bl-none {
1628 border-bottom-left-radius: 0px;
1827 border-bottom-left-radius: 0px;
1629 }
1828 }
1829+
1830+ .xs\:border-l {
1831+ border-left-width: 1px;
1832+ }
1833+
1834+ .xs\:border-t-0 {
1835+ border-top-width: 0px;
1836+ }
1630}
1837}
1631@media (min-width: 768px) {
1838@media (min-width: 768px) {
1632
1839
ui/src/types/sigil.ts+132−80
@@ -1,10 +1,7 @@
1−import without from 'lodash/without';
2−import uniq from 'lodash/uniq';
3−
4export const CLANS = ['galaxy', 'star', 'planet'] as const;
1export const CLANS = ['galaxy', 'star', 'planet'] as const;
5export type Clan = typeof CLANS[number];
2export type Clan = typeof CLANS[number];
6
3
7−export const PART_TYPES = ['geon', 'line', 'arc', 'circle', 'donut', 'bezier'] as const;
4+export const PART_TYPES = ['geon', 'line', 'arc', 'bezier', 'circle', 'donut'] as const;
8export type PartType = typeof PART_TYPES[number];
5export type PartType = typeof PART_TYPES[number];
9
6
10export const GEONS = ['gci', 'gsq', 'ghl', 'ghr', 'ght', 'ghb', 'gq1', 'gq2', 'gq3', 'gq4', 'gfs', 'gbs'];
7export const GEONS = ['gci', 'gsq', 'ghl', 'ghr', 'ght', 'ghb', 'gq1', 'gq2', 'gq3', 'gq4', 'gfs', 'gbs'];
@@ -12,85 +9,140 @@ export type Geon = typeof GEONS[number];
12
9
13export type SymbolType = ('prefix' | 'suffix');
10export type SymbolType = ('prefix' | 'suffix');
14
11
15−function getComplementaryCircleId(circleId: string): string {
12+export interface ModeDef {
16− let newId = circleId.replace(/m(?=.$)/, 'l');
13+ modes?: ModeDefs;
17− if (newId === circleId) {
14+ displayParts?: string[];
18− newId = circleId.replace(/l(?=.$)/, 'm');
15+ parts?: string[];
19− }
20− circleId = newId;
21− circleId = circleId.replace(/f$/, 'b');
22− if (newId === circleId) {
23− circleId = circleId.replace(/b$/, 'f');
24− }
25− return circleId;
26}
16}
27
17
28−function getPartTypeIndex(partId: string): number {
18+export interface ModeDefs {
29− return PART_TYPES.findIndex(p => p[0] === partId[0]);
19+ [mode: string]: ModeDef
30}
20}
31
21
32−export class SymbolQuery {
33− symbolType: SymbolType;
34− components: string[];
35−
36− constructor(symbolType: SymbolType = 'suffix', components = []) {
37− this.symbolType = symbolType;
38− this.components = components;
39− this.cleanupParts();
40− }
41−
42− set geon(geon: Geon) {
43− this.components = this.components.filter(c => c[0] !== 'g');
44− if (geon)
45− this.components = [geon, ...this.components];
46− }
47
22
23+export const MODE_DEFS: ModeDefs = {
48− get geon(): Geon {
24+ geon: {
49− return this.components.find(c => c[0] === 'g');
25+ modes: {},
26+ displayParts: ['ghb'],
50− }
27+ },
51−
28+ line: {
52− cleanupParts() {
29+ modes: {
53− this.components = uniq(this.components);
30+ mainFulls: {
54− this.components = this.components.sort((c1, c2) => {
31+ parts: ['lvf', 'lhf', 'lff', 'lbf'],
55− let i1 = getPartTypeIndex(c1);
32+ },
56− let i2 = getPartTypeIndex(c2);
33+ halvesFulls: {
57− let diff = i1 - i2;
34+ parts: ['lvfl', 'lvfr', 'lhft', 'lhfb', 'lfht', 'lfhb', 'lbht', 'lbhb'],
58− if (diff !== 0) return diff;
35+ },
59− if (c1[0] === 'c') {
36+ forwards: {
60− return c1.localeCompare(c2);
37+ parts: ['lff', 'lfft', 'lffb'],
61− } else {
38+ },
62− return c2.localeCompare(c1);
39+ backwards: {
40+ parts: ['lbf', 'lbft'],
41+ },
42+ weirds: {
43+ parts: ['lbhtf', 'lbrf', 'lfhm'],
63 }
44 }
64− });
45+ },
46+ parts: ['lvf', 'lvfl', 'lvfr', 'lhf', 'lhft', 'lhfb', 'lff', 'lbf'],
65− }
47+ },
66−
48+ arcCorner: {
67− clearPartType(partType: PartType) {
49+ modes: {
68− this.components = this.components.filter(c => c[0] !== partType[0]);
50+ trFulls: {
69− return this;
51+ parts: ['atrf8', 'atrf6', 'atrf4', 'atrf2'],
52+ },
53+ trHalves: {
54+ parts: [/*'atra8',*/ 'atra6', 'atra4', 'atra2', 'atrb8', 'atrb6', 'atrb4', 'atrb2'],
55+ displayParts: ['atrb8', 'atrb6', 'atrb4', 'atrb2'],
56+ },
57+ tlFulls: {
58+ parts: ['atlf8', 'atlf6', 'atlf4', 'atlf2'],
59+ },
60+ tlHalves: {
61+ parts: ['atla8', 'atla6', 'atla4', 'atla2', 'atlb8', 'atlb6', 'atlb4', 'atlb2'],
62+ displayParts: ['atlb8', 'atlb6', 'atlb4', 'atlb2'],
63+ },
64+ blFulls: {
65+ parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
66+ },
67+ blHalves: {
68+ parts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
69+ displayParts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
70+ },
71+ brFulls: {
72+ parts: ['abrf8', 'abrf6', 'abrf4', 'abrf2'],
73+ },
74+ brHalves: {
75+ parts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
76+ displayParts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
77+ },
78+ },
79+ parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
70− }
80+ },
71−
81+ arc: {
72− clear() {
82+ modes: {
73− this.components = [];
83+ rings: {
74− return this;
84+ parts: ['am081', 'am082', 'am083', 'am08s'],
85+ },
86+ diagonals: {
87+ parts: ['am741', 'am742', 'am743', 'am422', 'am423'],
88+ },
89+ topsBottoms: {
90+ parts: ['ab2', 'ab3', 'ab4', 'at1', 'at3', 'at4'],
91+ },
92+ leftsRights: {
93+ parts: ['al2', 'al3', 'al4', 'ar1', 'ar2', 'ar4'/*, 'arb2'*/],
94+ },
95+ rights: {
96+ parts: ['am642', 'am643'],
97+ },
98+ middles: {
99+ parts: ['am222', 'am223', 'am622', 'am623'],
100+ },
101+ middless: {
102+ parts: ['am142', 'am443'],
103+ },
104+ topRights: {
105+ parts: ['am021', 'am022', 'am023', 'am242', 'arb2'],
106+ },
107+ },
108+ parts: ['am222', 'am223', 'ar4', 'ar2', 'ar1'],
109+ },
110+ circle: {
111+ modes: {
112+ mediums: {
113+ parts: [
114+ 'cmmb',
115+ 'ctl3mb', 'cbl3mb',
116+ 'ct2mb', 'cb2mb', 'cl2mb', 'cr2mb', 'ctl2mb', 'ctr2mb', 'cbl2mb', 'cbr2mb',
117+ 'ctl1mb', 'ctr1mb', 'cbl1mb', 'cbr1mb',
118+ 'cbl3mf', 'ctr3mf', 'ctl3mf',
119+ ],
120+ displayParts: [
121+ 'cmmb',
122+ 'ctl3mb', 'cbl3mb',
123+ 'ct2mb', 'cb2mb', 'cl2mb', 'cr2mb', 'ctl2mb', 'ctr2mb', 'cbl2mb', 'cbr2mb',
124+ 'ctl1mb', 'ctr1mb', 'cbl1mb', 'cbr1mb',
125+ 'ctr3mf',
126+ ],
127+ },
128+ sides: {
129+ parts: ['cl1mb', 'cr1mb', 'cl3mb', 'cr3mb'],
130+ },
131+ smalls: {
132+ parts: ['cr1sb', 'cr2sb'],
133+ },
134+ },
135+ displayParts: [
136+ 'cbr3mb', 'ctl3mb',
137+ 'cmmb',
138+ 'ct2mb', 'cl2mb', 'cb2mb', 'cr2mb',
139+ ],
75− }
140+ },
76−
141+ donut: {
77− addPart(partId: string) {
142+ parts: ['dbl', 'dl', 'dm', 'dtl', 'dtr'],
78− this.components = [...this.components, partId];
143+ displayParts: ['dbl', /*'dl',*/ 'dm', 'dtl', 'dtr'],
79− if (partId[0] === 'c') {
80− const otherPartId = getComplementaryCircleId(partId);
81− this.components = [...this.components, otherPartId];
82− }
83− this.cleanupParts();
84− return this;
85− }
144+ },
86−
145+ bezier: {
87− removePart(partId: string) {
146+ parts: ['b1l1', 'b1l2', 'b1l3', 'b4t2', 'b4t3'],
88− this.components = without(this.components, partId);
89− if (partId[0] === 'c') {
90− const otherPartId = getComplementaryCircleId(partId);
91− this.components = without(this.components, otherPartId);
92− }
93− this.cleanupParts();
94− return this;
95− }
147+ },
96−}
148+};
ui/src/types/store.ts+2
@@ -27,4 +27,6 @@ export type SearchedContactsState = Rolodex;
27export class SearchSettings {
27export class SearchSettings {
28 includeMoons: boolean = true;
28 includeMoons: boolean = true;
29 includeComets: boolean = false;
29 includeComets: boolean = false;
30+
31+ allowFictionalSigils: boolean = false;
30}
32}