|
4 | 4 | import Button from './shared/Button.svelte'; |
5 | 5 | import Checkbox from './shared/Checkbox.svelte'; |
6 | 6 | import Slider from './shared/Slider.svelte'; |
| 7 | + import Select from './shared/Select.svelte'; |
| 8 | + import Input from './shared/Input.svelte'; |
7 | 9 | import InfoTip from './shared/InfoTip.svelte'; |
8 | 10 | import { uiStore } from '../stores/ui.svelte'; |
9 | 11 | import { beamStore, isInsideBeam } from '../stores/beam.svelte'; |
|
890 | 892 | {:else} |
891 | 893 | <div class="setup-panel"> |
892 | 894 | <h4 class="section-header">Antenna</h4> |
893 | | - <div class="rot-row"> |
| 895 | + <div class="row"> |
894 | 896 | <label>Preset<InfoTip>Sets beam width, tolerance, and update rate for common antenna types.</InfoTip></label> |
895 | 897 | <div class="antenna-presets"> |
896 | 898 | {#each Object.entries(ANTENNA_PRESETS) as [key, p]} |
897 | 899 | <Button size="xs" active={activeAntennaPreset === key} onclick={() => applyAntennaPreset(key)}>{p.label}</Button> |
898 | 900 | {/each} |
899 | 901 | </div> |
900 | 902 | </div> |
901 | | - <div class="rot-row antenna-summary"> |
| 903 | + <div class="row antenna-summary"> |
902 | 904 | <span>Beam {beamStore.beamWidth}°</span> |
903 | 905 | <span>Tolerance {rotatorStore.tolerance}°</span> |
904 | 906 | <span>Rate {rateDisplay}</span> |
905 | 907 | </div> |
906 | 908 |
|
907 | 909 | <h4 class="section-header">Connection</h4> |
908 | | - <div class="rot-row"> |
| 910 | + <div class="row"> |
909 | 911 | <label>Mode</label> |
910 | 912 | <div class="rot-mode-btns"> |
911 | 913 | <Button size="xs" variant="ghost" active={rotatorStore.mode === 'serial'} |
|
916 | 918 | </div> |
917 | 919 |
|
918 | 920 | {#if rotatorStore.mode === 'serial'} |
919 | | - <div class="rot-row"> |
| 921 | + <div class="row"> |
920 | 922 | <label>Protocol</label> |
921 | | - <select class="rot-select" value={rotatorStore.serialProtocol} |
922 | | - onchange={(e) => rotatorStore.setSerialProtocol((e.currentTarget as HTMLSelectElement).value as any)}> |
| 923 | + <Select size="xs" value={rotatorStore.serialProtocol} |
| 924 | + onchange={(e) => rotatorStore.setSerialProtocol((e.target as HTMLSelectElement).value as any)}> |
923 | 925 | <option value="gs232">GS-232</option> |
924 | 926 | <option value="easycomm">EasyComm II</option> |
925 | | - </select> |
| 927 | + </Select> |
926 | 928 | </div> |
927 | | - <div class="rot-row"> |
| 929 | + <div class="row"> |
928 | 930 | <label>Baud</label> |
929 | | - <select class="rot-select" value={String(rotatorStore.baudRate)} |
930 | | - onchange={(e) => rotatorStore.setBaudRate(Number((e.currentTarget as HTMLSelectElement).value))}> |
| 931 | + <Select size="xs" value={String(rotatorStore.baudRate)} |
| 932 | + onchange={(e) => rotatorStore.setBaudRate(Number((e.target as HTMLSelectElement).value))}> |
931 | 933 | <option value="4800">4800</option> |
932 | 934 | <option value="9600">9600</option> |
933 | 935 | <option value="19200">19200</option> |
934 | | - </select> |
| 936 | + </Select> |
935 | 937 | </div> |
936 | 938 | {/if} |
937 | 939 |
|
938 | 940 | {#if rotatorStore.mode === 'network'} |
939 | | - <div class="rot-row"> |
| 941 | + <div class="row"> |
940 | 942 | <label>URL</label> |
941 | | - <input class="radar-input rot-url" type="text" |
| 943 | + <Input size="xs" class="rot-url" type="text" |
942 | 944 | value={rotatorStore.wsUrl} |
943 | 945 | onblur={(e) => rotatorStore.setWsUrl((e.currentTarget as HTMLInputElement).value)} |
944 | 946 | onkeydown={(e) => e.key === 'Enter' && (e.target as HTMLInputElement).blur()} /> |
|
954 | 956 | min={0} max={10} step={0.5} value={rotatorStore.tolerance} |
955 | 957 | oninput={(e) => rotatorStore.setTolerance(Number((e.target as HTMLInputElement).value))} /> |
956 | 958 |
|
957 | | - <div class="rot-row"> |
958 | | - <label>Park position</label> |
959 | | - <select class="rot-select" value={rotatorStore.parkPreset} |
960 | | - onchange={(e) => rotatorStore.setParkPreset((e.currentTarget as HTMLSelectElement).value as ParkPreset)}> |
| 959 | + <div class="row"> |
| 960 | + <label>Park Position</label> |
| 961 | + <Select size="xs" value={rotatorStore.parkPreset} |
| 962 | + onchange={(e) => rotatorStore.setParkPreset((e.target as HTMLSelectElement).value as ParkPreset)}> |
961 | 963 | {#each Object.entries(PARK_PRESETS) as [key, p]} |
962 | 964 | <option value={key}>{p.label}</option> |
963 | 965 | {/each} |
964 | 966 | <option value="custom">Custom</option> |
965 | | - </select> |
| 967 | + </Select> |
966 | 968 | </div> |
967 | 969 | {#if rotatorStore.parkPreset === 'custom'} |
968 | | - <div class="rot-row"> |
| 970 | + <div class="row"> |
969 | 971 | <label>Park Az / El</label> |
970 | 972 | <div class="park-custom"> |
971 | | - <input class="radar-input" type="number" min="0" max="360" step="0.1" |
| 973 | + <Input size="xs" type="number" min="0" max="360" step="0.1" |
972 | 974 | value={rotatorStore.parkAz} |
973 | 975 | onblur={(e) => rotatorStore.setParkPosition(Number((e.currentTarget as HTMLInputElement).value), rotatorStore.parkEl)} |
974 | 976 | onkeydown={(e) => e.key === 'Enter' && (e.target as HTMLInputElement).blur()} /> |
975 | | - <span class="radar-unit">°</span> |
976 | | - <input class="radar-input" type="number" min="0" max="90" step="0.1" |
| 977 | + <span class="unit">°</span> |
| 978 | + <Input size="xs" type="number" min="0" max="90" step="0.1" |
977 | 979 | value={rotatorStore.parkEl} |
978 | 980 | onblur={(e) => rotatorStore.setParkPosition(rotatorStore.parkAz, Number((e.currentTarget as HTMLInputElement).value))} |
979 | 981 | onkeydown={(e) => e.key === 'Enter' && (e.target as HTMLInputElement).blur()} /> |
980 | | - <span class="radar-unit">°</span> |
| 982 | + <span class="unit">°</span> |
981 | 983 | </div> |
982 | 984 | </div> |
983 | 985 | {/if} |
984 | 986 |
|
985 | | - <div class="rot-row"> |
986 | | - <label>After pass<InfoTip>Action when a tracked satellite goes below the horizon. Disables auto-slew automatically.</InfoTip></label> |
987 | | - <select class="rot-select" value={rotatorStore.passEndAction} |
988 | | - onchange={(e) => rotatorStore.setPassEndAction((e.currentTarget as HTMLSelectElement).value as PassEndAction)}> |
| 987 | + <div class="row"> |
| 988 | + <label>After Pass<InfoTip>Action when a tracked satellite goes below the horizon. Disables auto-slew automatically.</InfoTip></label> |
| 989 | + <Select size="xs" value={rotatorStore.passEndAction} |
| 990 | + onchange={(e) => rotatorStore.setPassEndAction((e.target as HTMLSelectElement).value as PassEndAction)}> |
989 | 991 | <option value="nothing">Do nothing</option> |
990 | 992 | <option value="park">Park</option> |
991 | 993 | <option value="slew-next">Slew to next AOS</option> |
992 | | - </select> |
| 994 | + </Select> |
993 | 995 | </div> |
| 996 | + {#if rotatorStore.passEndAction !== 'nothing'} |
| 997 | + {#snippet settleTip()}<InfoTip>Wait this many seconds after LOS before parking or slewing. Lets large antennas stop wobbling.</InfoTip>{/snippet} |
| 998 | + <Slider label="Settle Delay" display="{rotatorStore.settleDelaySec}s" tip={settleTip} |
| 999 | + min={0} max={30} step={1} value={rotatorStore.settleDelaySec} |
| 1000 | + oninput={(e) => rotatorStore.setSettleDelay(Number((e.target as HTMLInputElement).value))} /> |
| 1001 | + {/if} |
994 | 1002 |
|
995 | 1003 | <h4 class="section-header">Visual</h4> |
996 | | - <div class="rot-row"> |
997 | | - <label>Cone without rotator<InfoTip>Show the beam cone in the 3D view when no rotator is connected. The cone always follows the rotator when connected.</InfoTip></label> |
| 1004 | + <div class="row"> |
| 1005 | + <label>Cone Without Rotator<InfoTip>Show the beam cone in the 3D view when no rotator is connected. The cone always follows the rotator when connected.</InfoTip></label> |
998 | 1006 | <Checkbox checked={beamStore.coneVisible} onchange={() => beamStore.setConeVisible(!beamStore.coneVisible)} /> |
999 | 1007 | </div> |
1000 | | - <div class="rot-row"> |
| 1008 | + <div class="row"> |
1001 | 1009 | <label>Radar VFX<InfoTip>Sweep line and phosphor afterglow effect on satellite blips.</InfoTip></label> |
1002 | 1010 | <Checkbox checked={uiStore.radarVfx} onchange={() => uiStore.setToggle('radarVfx', !uiStore.radarVfx)} /> |
1003 | 1011 | </div> |
|
1164 | 1172 | display: flex; |
1165 | 1173 | gap: 2px; |
1166 | 1174 | } |
1167 | | - .rot-select { |
1168 | | - font-size: 10px; |
1169 | | - font-family: 'Overpass Mono', monospace; |
1170 | | - background: var(--ui-bg); |
1171 | | - border: 1px solid var(--border); |
1172 | | - color: var(--text-dim); |
1173 | | - padding: 1px 3px; |
1174 | | - border-radius: 2px; |
1175 | | - } |
1176 | | - .rot-select:hover { border-color: var(--border-hover); } |
1177 | | - .rot-select:focus { border-color: var(--border-hover); outline: none; color: var(--text); } |
1178 | | - .rot-url { width: 160px; } |
1179 | 1175 | .park-custom { |
1180 | 1176 | display: flex; |
1181 | 1177 | align-items: center; |
|
1281 | 1277 | display: flex; |
1282 | 1278 | gap: 2px; |
1283 | 1279 | } |
1284 | | - .antenna-summary { |
| 1280 | + .antenna-summary.row { |
1285 | 1281 | gap: 10px; |
1286 | 1282 | justify-content: flex-start; |
1287 | 1283 | font-size: 9px; |
|
1306 | 1302 | border-bottom: 1px solid var(--border); |
1307 | 1303 | } |
1308 | 1304 | .section-header:first-child { margin-top: 0; } |
| 1305 | + .row { |
| 1306 | + display: flex; |
| 1307 | + align-items: center; |
| 1308 | + justify-content: space-between; |
| 1309 | + margin-bottom: 6px; |
| 1310 | + } |
| 1311 | + .row:last-child { margin-bottom: 0; } |
| 1312 | + .row label { color: var(--text-dim); font-size: 12px; } |
| 1313 | + .unit { color: var(--text-ghost); font-size: 11px; } |
| 1314 | + :global(.rot-url) { width: 160px; } |
1309 | 1315 | .guide-details { |
1310 | 1316 | margin-top: 8px; |
1311 | 1317 | } |
|
0 commit comments