aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src')
-rw-r--r--ui/src/app.css97
-rw-r--r--ui/src/components/BottomBar.svelte97
-rw-r--r--ui/src/components/CustomSelect.svelte136
-rw-r--r--ui/src/components/ModLoaderSelector.svelte150
-rw-r--r--ui/src/components/SettingsView.svelte33
-rw-r--r--ui/src/lib/GameConsole.svelte19
6 files changed, 443 insertions, 89 deletions
diff --git a/ui/src/app.css b/ui/src/app.css
index 5d0404b..82aa72f 100644
--- a/ui/src/app.css
+++ b/ui/src/app.css
@@ -14,43 +14,48 @@ select {
padding-right: 2rem;
}
-/* Custom scrollbar for dropdowns and lists */
-select,
-.custom-select {
- scrollbar-width: thin;
- scrollbar-color: #3f3f46 #18181b;
-}
-
-/* Webkit scrollbar for select (when expanded, browser-dependent) */
-select::-webkit-scrollbar {
- width: 8px;
+/* Option styling - works in WebView/Chromium */
+select option {
+ background-color: #18181b;
+ color: #e4e4e7;
+ padding: 12px 16px;
+ font-size: 13px;
+ border: none;
}
-select::-webkit-scrollbar-track {
- background: #18181b;
+select option:hover,
+select option:focus {
+ background-color: #3730a3 !important;
+ color: white !important;
}
-select::-webkit-scrollbar-thumb {
- background-color: #3f3f46;
- border-radius: 4px;
+select option:checked {
+ background: linear-gradient(0deg, #4f46e5 0%, #4f46e5 100%);
+ color: white;
+ font-weight: 500;
}
-select::-webkit-scrollbar-thumb:hover {
- background-color: #52525b;
+select option:disabled {
+ color: #52525b;
+ background-color: #18181b;
}
-/* Option styling (limited browser support but good for Tauri/WebView) */
-select option {
+/* Optgroup styling */
+select optgroup {
background-color: #18181b;
- color: #e4e4e7;
- padding: 8px 12px;
+ color: #a1a1aa;
+ font-weight: 600;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ padding: 8px 12px 4px;
}
-select option:hover,
-select option:focus,
-select option:checked {
- background: linear-gradient(#3730a3, #3730a3);
- color: white;
+/* Select focus state */
+select:focus {
+ outline: none;
+ border-color: #6366f1;
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}
/* ==================== Custom Scrollbar (Global) ==================== */
@@ -118,3 +123,43 @@ input[type="number"]::-webkit-inner-spin-button {
input[type="number"] {
-moz-appearance: textfield;
}
+
+/* ==================== Checkbox Styling ==================== */
+
+input[type="checkbox"] {
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ border: 1px solid #3f3f46;
+ border-radius: 4px;
+ background-color: #18181b;
+ cursor: pointer;
+ position: relative;
+ transition: all 0.15s ease;
+}
+
+input[type="checkbox"]:hover {
+ border-color: #52525b;
+}
+
+input[type="checkbox"]:checked {
+ background-color: #4f46e5;
+ border-color: #4f46e5;
+}
+
+input[type="checkbox"]:checked::after {
+ content: '';
+ position: absolute;
+ left: 5px;
+ top: 2px;
+ width: 4px;
+ height: 8px;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+}
+
+input[type="checkbox"]:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
+}
diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte
index 198d4e6..abb0b23 100644
--- a/ui/src/components/BottomBar.svelte
+++ b/ui/src/components/BottomBar.svelte
@@ -2,7 +2,39 @@
import { authState } from "../stores/auth.svelte";
import { gameState } from "../stores/game.svelte";
import { uiState } from "../stores/ui.svelte";
- import { Terminal, ChevronDown, Play, User } from 'lucide-svelte';
+ import { Terminal, ChevronDown, Play, User, Check } from 'lucide-svelte';
+
+ let isVersionDropdownOpen = $state(false);
+ let dropdownRef: HTMLDivElement;
+
+ let versionOptions = $derived(
+ gameState.versions.length === 0
+ ? [{ id: "loading", type: "loading", label: "Loading..." }]
+ : gameState.versions.map(v => ({
+ ...v,
+ label: `${v.id}${v.type !== 'release' ? ` (${v.type})` : ''}`
+ }))
+ );
+
+ function selectVersion(id: string) {
+ if (id !== "loading") {
+ gameState.selectedVersion = id;
+ isVersionDropdownOpen = false;
+ }
+ }
+
+ function handleClickOutside(e: MouseEvent) {
+ if (dropdownRef && !dropdownRef.contains(e.target as Node)) {
+ isVersionDropdownOpen = false;
+ }
+ }
+
+ $effect(() => {
+ if (isVersionDropdownOpen) {
+ document.addEventListener('click', handleClickOutside);
+ return () => document.removeEventListener('click', handleClickOutside);
+ }
+ });
</script>
<div
@@ -60,23 +92,56 @@
<!-- Action Area -->
<div class="flex items-center gap-4">
<div class="flex flex-col items-end mr-2">
- <div class="relative group">
- <select
- id="version-select"
- bind:value={gameState.selectedVersion}
- class="appearance-none dark:bg-zinc-900 bg-zinc-50 dark:text-white text-gray-900 border dark:border-white/10 border-black/10 rounded-sm pl-4 pr-10 py-2 dark:hover:border-white/30 hover:border-black/30 transition-all cursor-pointer outline-none focus:ring-1 focus:ring-zinc-500 w-56 text-sm font-mono"
+ <!-- Custom Version Dropdown -->
+ <div class="relative" bind:this={dropdownRef}>
+ <button
+ type="button"
+ onclick={() => isVersionDropdownOpen = !isVersionDropdownOpen}
+ class="flex items-center justify-between gap-2 w-56 px-4 py-2.5 text-left
+ dark:bg-zinc-900 bg-zinc-50 border dark:border-zinc-700 border-zinc-300 rounded-md
+ text-sm font-mono dark:text-white text-gray-900
+ dark:hover:border-zinc-600 hover:border-zinc-400
+ focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30
+ transition-colors cursor-pointer outline-none"
>
- {#if gameState.versions.length === 0}
- <option>Loading...</option>
- {:else}
- {#each gameState.versions as version}
- <option value={version.id}>{version.id} {version.type !== 'release' ? `(${version.type})` : ''}</option>
+ <span class="truncate">
+ {#if gameState.versions.length === 0}
+ Loading...
+ {:else}
+ {gameState.selectedVersion || "Select version"}
+ {/if}
+ </span>
+ <ChevronDown
+ size={14}
+ class="shrink-0 dark:text-zinc-500 text-zinc-400 transition-transform duration-200 {isVersionDropdownOpen ? 'rotate-180' : ''}"
+ />
+ </button>
+
+ {#if isVersionDropdownOpen}
+ <div
+ class="absolute z-50 w-full mt-1 py-1 dark:bg-zinc-900 bg-white border dark:border-zinc-700 border-zinc-300 rounded-md shadow-xl
+ max-h-72 overflow-y-auto animate-in fade-in slide-in-from-top-1 duration-150 bottom-full mb-1"
+ >
+ {#each versionOptions as version}
+ <button
+ type="button"
+ onclick={() => selectVersion(version.id)}
+ disabled={version.id === "loading"}
+ class="w-full flex items-center justify-between px-3 py-2 text-sm font-mono text-left
+ transition-colors outline-none
+ {version.id === gameState.selectedVersion
+ ? 'bg-indigo-600 text-white'
+ : 'dark:text-zinc-300 text-gray-700 dark:hover:bg-zinc-800 hover:bg-zinc-100'}
+ {version.id === 'loading' ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
+ >
+ <span class="truncate">{version.label}</span>
+ {#if version.id === gameState.selectedVersion}
+ <Check size={14} class="shrink-0 ml-2" />
+ {/if}
+ </button>
{/each}
- {/if}
- </select>
- <div class="absolute right-3 top-1/2 -translate-y-1/2 dark:text-white/20 text-black/20 pointer-events-none">
- <ChevronDown size={14} />
- </div>
+ </div>
+ {/if}
</div>
</div>
diff --git a/ui/src/components/CustomSelect.svelte b/ui/src/components/CustomSelect.svelte
new file mode 100644
index 0000000..2e89c75
--- /dev/null
+++ b/ui/src/components/CustomSelect.svelte
@@ -0,0 +1,136 @@
+<script lang="ts">
+ import { ChevronDown, Check } from 'lucide-svelte';
+
+ interface Option {
+ value: string;
+ label: string;
+ disabled?: boolean;
+ }
+
+ interface Props {
+ options: Option[];
+ value: string;
+ placeholder?: string;
+ disabled?: boolean;
+ class?: string;
+ onchange?: (value: string) => void;
+ }
+
+ let {
+ options,
+ value = $bindable(),
+ placeholder = "Select...",
+ disabled = false,
+ class: className = "",
+ onchange
+ }: Props = $props();
+
+ let isOpen = $state(false);
+ let containerRef: HTMLDivElement;
+
+ let selectedOption = $derived(options.find(o => o.value === value));
+
+ function toggle() {
+ if (!disabled) {
+ isOpen = !isOpen;
+ }
+ }
+
+ function select(option: Option) {
+ if (option.disabled) return;
+ value = option.value;
+ isOpen = false;
+ onchange?.(option.value);
+ }
+
+ function handleKeydown(e: KeyboardEvent) {
+ if (disabled) return;
+
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ toggle();
+ } else if (e.key === 'Escape') {
+ isOpen = false;
+ } else if (e.key === 'ArrowDown' && isOpen) {
+ e.preventDefault();
+ const currentIndex = options.findIndex(o => o.value === value);
+ const nextIndex = Math.min(currentIndex + 1, options.length - 1);
+ if (!options[nextIndex].disabled) {
+ value = options[nextIndex].value;
+ }
+ } else if (e.key === 'ArrowUp' && isOpen) {
+ e.preventDefault();
+ const currentIndex = options.findIndex(o => o.value === value);
+ const prevIndex = Math.max(currentIndex - 1, 0);
+ if (!options[prevIndex].disabled) {
+ value = options[prevIndex].value;
+ }
+ }
+ }
+
+ function handleClickOutside(e: MouseEvent) {
+ if (containerRef && !containerRef.contains(e.target as Node)) {
+ isOpen = false;
+ }
+ }
+
+ $effect(() => {
+ if (isOpen) {
+ document.addEventListener('click', handleClickOutside);
+ return () => document.removeEventListener('click', handleClickOutside);
+ }
+ });
+</script>
+
+<div
+ bind:this={containerRef}
+ class="relative {className}"
+>
+ <!-- Trigger Button -->
+ <button
+ type="button"
+ onclick={toggle}
+ onkeydown={handleKeydown}
+ {disabled}
+ class="w-full flex items-center justify-between gap-2 px-3 py-2 pr-8 text-left
+ bg-zinc-900 border border-zinc-700 rounded-md text-sm text-zinc-200
+ hover:border-zinc-600 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30
+ transition-colors cursor-pointer outline-none
+ disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:border-zinc-700"
+ >
+ <span class="truncate {!selectedOption ? 'text-zinc-500' : ''}">
+ {selectedOption?.label || placeholder}
+ </span>
+ <ChevronDown
+ size={14}
+ class="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}"
+ />
+ </button>
+
+ <!-- Dropdown Menu -->
+ {#if isOpen}
+ <div
+ class="absolute z-50 w-full mt-1 py-1 bg-zinc-900 border border-zinc-700 rounded-md shadow-xl
+ max-h-60 overflow-y-auto animate-in fade-in slide-in-from-top-1 duration-150"
+ >
+ {#each options as option}
+ <button
+ type="button"
+ onclick={() => select(option)}
+ disabled={option.disabled}
+ class="w-full flex items-center justify-between px-3 py-2 text-sm text-left
+ transition-colors outline-none
+ {option.value === value
+ ? 'bg-indigo-600 text-white'
+ : 'text-zinc-300 hover:bg-zinc-800'}
+ {option.disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}"
+ >
+ <span class="truncate">{option.label}</span>
+ {#if option.value === value}
+ <Check size={14} class="shrink-0 ml-2" />
+ {/if}
+ </button>
+ {/each}
+ </div>
+ {/if}
+</div>
diff --git a/ui/src/components/ModLoaderSelector.svelte b/ui/src/components/ModLoaderSelector.svelte
index d0c1b59..cb949c5 100644
--- a/ui/src/components/ModLoaderSelector.svelte
+++ b/ui/src/components/ModLoaderSelector.svelte
@@ -6,7 +6,7 @@
ForgeVersion,
ModLoaderType,
} from "../types";
- import { Loader2, Download, AlertCircle, Check } from 'lucide-svelte';
+ import { Loader2, Download, AlertCircle, Check, ChevronDown } from 'lucide-svelte';
interface Props {
selectedGameVersion: string;
@@ -23,10 +23,15 @@
// Fabric state
let fabricLoaders = $state<FabricLoaderVersion[]>([]);
let selectedFabricLoader = $state("");
+ let isFabricDropdownOpen = $state(false);
// Forge state
let forgeVersions = $state<ForgeVersion[]>([]);
let selectedForgeVersion = $state("");
+ let isForgeDropdownOpen = $state(false);
+
+ let fabricDropdownRef = $state<HTMLDivElement | null>(null);
+ let forgeDropdownRef = $state<HTMLDivElement | null>(null);
// Load mod loader versions when game version changes
$effect(() => {
@@ -111,6 +116,44 @@
loadModLoaderVersions();
}
}
+
+ function handleFabricClickOutside(e: MouseEvent) {
+ if (fabricDropdownRef && !fabricDropdownRef.contains(e.target as Node)) {
+ isFabricDropdownOpen = false;
+ }
+ }
+
+ function handleForgeClickOutside(e: MouseEvent) {
+ if (forgeDropdownRef && !forgeDropdownRef.contains(e.target as Node)) {
+ isForgeDropdownOpen = false;
+ }
+ }
+
+ $effect(() => {
+ if (isFabricDropdownOpen) {
+ document.addEventListener('click', handleFabricClickOutside);
+ return () => document.removeEventListener('click', handleFabricClickOutside);
+ }
+ });
+
+ $effect(() => {
+ if (isForgeDropdownOpen) {
+ document.addEventListener('click', handleForgeClickOutside);
+ return () => document.removeEventListener('click', handleForgeClickOutside);
+ }
+ });
+
+ let selectedFabricLabel = $derived(
+ fabricLoaders.find(l => l.version === selectedFabricLoader)
+ ? `${selectedFabricLoader}${fabricLoaders.find(l => l.version === selectedFabricLoader)?.stable ? ' (stable)' : ''}`
+ : selectedFabricLoader || 'Select version'
+ );
+
+ let selectedForgeLabel = $derived(
+ forgeVersions.find(v => v.version === selectedForgeVersion)
+ ? `${selectedForgeVersion}${forgeVersions.find(v => v.version === selectedForgeVersion)?.recommended ? ' (Recommended)' : ''}`
+ : selectedForgeVersion || 'Select version'
+ );
</script>
<div class="space-y-4">
@@ -163,18 +206,48 @@
<label for="fabric-loader-select" class="block text-[10px] uppercase font-bold text-zinc-500 mb-2"
>Loader Version</label
>
- <div class="relative">
- <select
- id="fabric-loader-select"
- class="w-full bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md px-4 py-2.5 pr-10 text-sm focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 text-gray-900 dark:text-white transition-colors cursor-pointer hover:border-zinc-400 dark:hover:border-zinc-600"
- bind:value={selectedFabricLoader}
+ <!-- Custom Fabric Dropdown -->
+ <div class="relative" bind:this={fabricDropdownRef}>
+ <button
+ type="button"
+ onclick={() => isFabricDropdownOpen = !isFabricDropdownOpen}
+ class="w-full flex items-center justify-between gap-2 px-4 py-2.5 text-left
+ bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md
+ text-sm text-gray-900 dark:text-white
+ hover:border-zinc-400 dark:hover:border-zinc-600
+ focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30
+ transition-colors cursor-pointer outline-none"
>
- {#each fabricLoaders as loader}
- <option value={loader.version}>
- {loader.version} {loader.stable ? "(stable)" : ""}
- </option>
- {/each}
- </select>
+ <span class="truncate">{selectedFabricLabel}</span>
+ <ChevronDown
+ size={14}
+ class="shrink-0 text-zinc-400 dark:text-zinc-500 transition-transform duration-200 {isFabricDropdownOpen ? 'rotate-180' : ''}"
+ />
+ </button>
+
+ {#if isFabricDropdownOpen}
+ <div
+ class="absolute z-50 w-full mt-1 py-1 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md shadow-xl
+ max-h-48 overflow-y-auto animate-in fade-in slide-in-from-top-1 duration-150"
+ >
+ {#each fabricLoaders as loader}
+ <button
+ type="button"
+ onclick={() => { selectedFabricLoader = loader.version; isFabricDropdownOpen = false; }}
+ class="w-full flex items-center justify-between px-3 py-2 text-sm text-left
+ transition-colors outline-none cursor-pointer
+ {loader.version === selectedFabricLoader
+ ? 'bg-indigo-600 text-white'
+ : 'text-gray-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800'}"
+ >
+ <span class="truncate">{loader.version} {loader.stable ? "(stable)" : ""}</span>
+ {#if loader.version === selectedFabricLoader}
+ <Check size={14} class="shrink-0 ml-2" />
+ {/if}
+ </button>
+ {/each}
+ </div>
+ {/if}
</div>
</div>
@@ -199,19 +272,48 @@
<label for="forge-version-select" class="block text-[10px] uppercase font-bold text-zinc-500 mb-2"
>Forge Version</label
>
- <div class="relative">
- <select
- id="forge-version-select"
- class="w-full bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md px-4 py-2.5 pr-10 text-sm focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 text-gray-900 dark:text-white transition-colors cursor-pointer hover:border-zinc-400 dark:hover:border-zinc-600"
- bind:value={selectedForgeVersion}
+ <!-- Custom Forge Dropdown -->
+ <div class="relative" bind:this={forgeDropdownRef}>
+ <button
+ type="button"
+ onclick={() => isForgeDropdownOpen = !isForgeDropdownOpen}
+ class="w-full flex items-center justify-between gap-2 px-4 py-2.5 text-left
+ bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md
+ text-sm text-gray-900 dark:text-white
+ hover:border-zinc-400 dark:hover:border-zinc-600
+ focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30
+ transition-colors cursor-pointer outline-none"
>
- {#each forgeVersions as version}
- <option value={version.version}>
- {version.version}
- {version.recommended ? " (Recommended)" : ""}
- </option>
- {/each}
- </select>
+ <span class="truncate">{selectedForgeLabel}</span>
+ <ChevronDown
+ size={14}
+ class="shrink-0 text-zinc-400 dark:text-zinc-500 transition-transform duration-200 {isForgeDropdownOpen ? 'rotate-180' : ''}"
+ />
+ </button>
+
+ {#if isForgeDropdownOpen}
+ <div
+ class="absolute z-50 w-full mt-1 py-1 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md shadow-xl
+ max-h-48 overflow-y-auto animate-in fade-in slide-in-from-top-1 duration-150"
+ >
+ {#each forgeVersions as version}
+ <button
+ type="button"
+ onclick={() => { selectedForgeVersion = version.version; isForgeDropdownOpen = false; }}
+ class="w-full flex items-center justify-between px-3 py-2 text-sm text-left
+ transition-colors outline-none cursor-pointer
+ {version.version === selectedForgeVersion
+ ? 'bg-indigo-600 text-white'
+ : 'text-gray-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800'}"
+ >
+ <span class="truncate">{version.version} {version.recommended ? "(Recommended)" : ""}</span>
+ {#if version.version === selectedForgeVersion}
+ <Check size={14} class="shrink-0 ml-2" />
+ {/if}
+ </button>
+ {/each}
+ </div>
+ {/if}
</div>
</div>
diff --git a/ui/src/components/SettingsView.svelte b/ui/src/components/SettingsView.svelte
index 732f857..76d441b 100644
--- a/ui/src/components/SettingsView.svelte
+++ b/ui/src/components/SettingsView.svelte
@@ -1,11 +1,22 @@
<script lang="ts">
import { open } from "@tauri-apps/plugin-dialog";
import { settingsState } from "../stores/settings.svelte";
+ import CustomSelect from "./CustomSelect.svelte";
// Use convertFileSrc directly from settingsState.backgroundUrl for cleaner approach
// or use the imported one if passing raw path.
import { convertFileSrc } from "@tauri-apps/api/core";
+ const effectOptions = [
+ { value: "saturn", label: "Saturn" },
+ { value: "constellation", label: "Network (Constellation)" }
+ ];
+
+ const logServiceOptions = [
+ { value: "paste.rs", label: "paste.rs (Free, No Account)" },
+ { value: "pastebin.com", label: "pastebin.com (Requires API Key)" }
+ ];
+
async function selectBackground() {
try {
const selected = await open({
@@ -116,15 +127,12 @@
<h4 class="text-sm font-medium dark:text-white/90 text-black/80" id="theme-effect-label">Theme Effect</h4>
<p class="text-xs dark:text-white/40 text-black/50 mt-1">Select the active visual theme.</p>
</div>
- <select
- aria-labelledby="theme-effect-label"
+ <CustomSelect
+ options={effectOptions}
bind:value={settingsState.settings.active_effect}
onchange={() => settingsState.saveSettings()}
- class="dark:bg-zinc-900 bg-white dark:text-white text-black text-xs px-3 py-2 pr-8 rounded-lg border dark:border-zinc-700 border-gray-300 outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 cursor-pointer hover:border-zinc-600 transition-colors"
- >
- <option value="saturn">Saturn (Saturn)</option>
- <option value="constellation">Network (Constellation)</option>
- </select>
+ class="w-52"
+ />
</div>
{/if}
@@ -308,14 +316,11 @@
<div class="space-y-4">
<div>
<label for="log-service" class="block text-sm font-medium text-white/70 mb-2">Log Upload Service</label>
- <select
- id="log-service"
+ <CustomSelect
+ options={logServiceOptions}
bind:value={settingsState.settings.log_upload_service}
- class="dark:bg-zinc-900 bg-white dark:text-white text-black w-full px-4 py-3 pr-10 rounded-xl border dark:border-zinc-700 border-gray-300 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 outline-none cursor-pointer hover:border-zinc-600 transition-colors"
- >
- <option value="paste.rs">paste.rs (Free, No Account)</option>
- <option value="pastebin.com">pastebin.com (Requires API Key)</option>
- </select>
+ class="w-full"
+ />
</div>
{#if settingsState.settings.log_upload_service === 'pastebin.com'}
diff --git a/ui/src/lib/GameConsole.svelte b/ui/src/lib/GameConsole.svelte
index 1b1ab53..bc5edbc 100644
--- a/ui/src/lib/GameConsole.svelte
+++ b/ui/src/lib/GameConsole.svelte
@@ -6,6 +6,8 @@
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-shell";
import { onMount, tick } from "svelte";
+ import CustomSelect from "../components/CustomSelect.svelte";
+ import { ChevronDown, Check } from 'lucide-svelte';
let consoleElement: HTMLDivElement;
let autoScroll = $state(true);
@@ -21,7 +23,10 @@
let selectedSource = $state("all");
// Get sorted sources for dropdown
- let sortedSources = $derived([...logsState.sources].sort());
+ let sourceOptions = $derived([
+ { value: "all", label: "All Sources" },
+ ...[...logsState.sources].sort().map(s => ({ value: s, label: s }))
+ ]);
// Derived filtered logs
let filteredLogs = $derived(logsState.logs.filter((log) => {
@@ -151,15 +156,11 @@
<h3 class="font-bold text-zinc-100 uppercase tracking-wider px-2">Console</h3>
<!-- Source Dropdown -->
- <select
+ <CustomSelect
+ options={sourceOptions}
bind:value={selectedSource}
- class="bg-zinc-900 border border-zinc-700 rounded-md px-3 py-1.5 pr-8 text-zinc-300 text-xs focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500/30 cursor-pointer hover:border-zinc-600 transition-colors"
- >
- <option value="all">All Sources</option>
- {#each sortedSources as source}
- <option value={source}>{source}</option>
- {/each}
- </select>
+ class="w-36"
+ />
<!-- Level Filters -->
<div class="flex items-center bg-[#1e1e1e] rounded border border-[#3e3e42] overflow-hidden">