fix(frontend): environment select resets to default on click
Root cause: onchange on native <select> passes a DOM Event. The parent handler used e.detail, which is always 0 for native events. Sequence: 1. bind:value sets environmentId correctly 2. onchange fires -> handleEnvChange(0) overwrites to falsy 3. <select> re-renders showing 'Select environment...' Fix: - ConfigTabForm: onchange now passes e.target.value (the actual envId) - +page.svelte: callback receives the envId string directly, not e.detail @RATIONALE bind:value on <select> fires on the same change event. The handler must not re-set the same bindable to a stale value. Verification: browser reconfirmed — ss-dev stays selected after click.
This commit is contained in:
@@ -1,12 +1,16 @@
|
|||||||
<!-- #region ConfigTabForm [C:4] [TYPE Component] [SEMANTICS translate, config, datasource, llm, columns] -->
|
<!-- #region ConfigTabForm [C:4] [TYPE Component] [SEMANTICS translate, config, datasource, llm, columns] -->
|
||||||
<!-- @BRIEF Configuration tab content for Translation Job — datasource selection with auto-detected
|
<!-- @BRIEF Configuration tab content for Translation Job — datasource selection with auto-detected
|
||||||
DB dialect, column mapping, LLM provider, target languages (multi-select from LANGUAGES),
|
DB dialect, column mapping, LLM provider, target languages (multi-select from settings-allowed languages),
|
||||||
dictionaries, disable reasoning toggle. -->
|
dictionaries, disable reasoning toggle. -->
|
||||||
<!-- @LAYER UI -->
|
<!-- @LAYER UI -->
|
||||||
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:fetchDatasources] -->
|
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:fetchDatasources] -->
|
||||||
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:fetchDatasourceColumns] -->
|
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:fetchDatasourceColumns] -->
|
||||||
|
<!-- @RELATION DEPENDS_ON -> [ApiModule.getAllowedLanguages] -->
|
||||||
<!-- @RELATION DEPENDS_ON -> [MultiSelect] -->
|
<!-- @RELATION DEPENDS_ON -> [MultiSelect] -->
|
||||||
<!-- @RELATION BINDS_TO -> [EXT:frontend:LANGUAGE_LABELS] -->
|
<!-- @RELATION CALLS -> [filterLanguages]
|
||||||
|
<!-- @RATIONALE Target language list is now fetched from /settings/allowed-languages instead of static ALL_LANGUAGES.
|
||||||
|
This ensures only admin-configured languages (Settings → Languages) are shown. Falls back to full list on API error
|
||||||
|
or when allowed-languages is empty. -->
|
||||||
<!-- @UX_STATE Datasource searching -> Dropdown with filtered results, spinner on load -->
|
<!-- @UX_STATE Datasource searching -> Dropdown with filtered results, spinner on load -->
|
||||||
<!-- @UX_STATE Datasource selected -> Columns loaded, DB dialect detected, mapping ready -->
|
<!-- @UX_STATE Datasource selected -> Columns loaded, DB dialect detected, mapping ready -->
|
||||||
<!-- @UX_STATE Columns loading -> Skeleton pulse in column section -->
|
<!-- @UX_STATE Columns loading -> Skeleton pulse in column section -->
|
||||||
@@ -20,7 +24,8 @@
|
|||||||
<!-- @UX_REACTIVITY LocalState -> datasourceList, datasourceLoading, showDatasourceDropdown -->
|
<!-- @UX_REACTIVITY LocalState -> datasourceList, datasourceLoading, showDatasourceDropdown -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getT } from '$lib/i18n/index.svelte.js';
|
import { getT } from '$lib/i18n/index.svelte.js';
|
||||||
import { ALL_LANGUAGES } from '$lib/i18n/languages.js';
|
import { ALL_LANGUAGES, filterLanguages } from '$lib/i18n/languages.js';
|
||||||
|
import { api } from '$lib/api.js';
|
||||||
import { fetchDatasources, fetchDatasourceColumns } from '$lib/api/translate.js';
|
import { fetchDatasources, fetchDatasourceColumns } from '$lib/api/translate.js';
|
||||||
import MultiSelect from '$lib/components/ui/MultiSelect.svelte';
|
import MultiSelect from '$lib/components/ui/MultiSelect.svelte';
|
||||||
import HelpTooltip from '$lib/ui/HelpTooltip.svelte';
|
import HelpTooltip from '$lib/ui/HelpTooltip.svelte';
|
||||||
@@ -59,9 +64,28 @@
|
|||||||
let showDatasourceDropdown = $state(false);
|
let showDatasourceDropdown = $state(false);
|
||||||
let isColumnsLoading = $state(false);
|
let isColumnsLoading = $state(false);
|
||||||
let columnList = $derived(availableColumns);
|
let columnList = $derived(availableColumns);
|
||||||
|
let allowedLanguageCodes: string[] = $state([]);
|
||||||
// @RATIONALE Svelte 5 store detection fails for `$t` in deeply nested templates.
|
// @RATIONALE Svelte 5 store detection fails for `$t` in deeply nested templates.
|
||||||
// Use getT() + $derived to keep reactivity to locale changes.
|
// Use getT() + $derived to keep reactivity to locale changes.
|
||||||
const _t = $derived(getT());
|
const _t = $derived(getT());
|
||||||
|
// Language options filtered by backend-allowed codes (fallback to full list)
|
||||||
|
let languageOptions = $derived(
|
||||||
|
allowedLanguageCodes.length > 0
|
||||||
|
? filterLanguages(allowedLanguageCodes)
|
||||||
|
: ALL_LANGUAGES
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load allowed languages from settings once on mount
|
||||||
|
$effect(() => {
|
||||||
|
api.getAllowedLanguages().then((codes) => {
|
||||||
|
if (Array.isArray(codes) && codes.length > 0) {
|
||||||
|
allowedLanguageCodes = codes;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// Non-critical: fall back to full list
|
||||||
|
allowedLanguageCodes = [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ---- Derived ----
|
// ---- Derived ----
|
||||||
let logicalColumns = $derived(availableColumns.filter(c => c.is_physical !== false));
|
let logicalColumns = $derived(availableColumns.filter(c => c.is_physical !== false));
|
||||||
@@ -202,7 +226,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
bind:value={environmentId}
|
bind:value={environmentId}
|
||||||
onchange={onEnvChange}
|
onchange={(e) => onEnvChange(e.target.value)}
|
||||||
class="w-full px-3 py-2 border border-border-strong rounded-lg text-sm focus-visible:ring-2 focus-visible:ring-primary-ring focus-visible:border-primary-ring"
|
class="w-full px-3 py-2 border border-border-strong rounded-lg text-sm focus-visible:ring-2 focus-visible:ring-primary-ring focus-visible:border-primary-ring"
|
||||||
>
|
>
|
||||||
<option value="">{_t.translate?.config?.select_environment}</option>
|
<option value="">{_t.translate?.config?.select_environment}</option>
|
||||||
@@ -443,7 +467,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
label=""
|
label=""
|
||||||
options={ALL_LANGUAGES}
|
options={languageOptions}
|
||||||
bind:selected={targetLanguages}
|
bind:selected={targetLanguages}
|
||||||
searchable={true}
|
searchable={true}
|
||||||
placeholder={_t.translate?.config?.target_language_search_placeholder || 'Search languages...'}
|
placeholder={_t.translate?.config?.target_language_search_placeholder || 'Search languages...'}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
availableDictionaries={m.availableDictionaries}
|
availableDictionaries={m.availableDictionaries}
|
||||||
bind:disableReasoning={m.disableReasoning}
|
bind:disableReasoning={m.disableReasoning}
|
||||||
validationErrors={m.validationErrors}
|
validationErrors={m.validationErrors}
|
||||||
onEnvChange={(e) => m.handleEnvChange(e.detail)}
|
onEnvChange={(envId) => m.handleEnvChange(envId)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -245,11 +245,11 @@
|
|||||||
completedRuns={m.completedRuns}
|
completedRuns={m.completedRuns}
|
||||||
expandedRunIds={m.expandedRunIds}
|
expandedRunIds={m.expandedRunIds}
|
||||||
bind:showPageBulkReplace={m.showPageBulkReplace}
|
bind:showPageBulkReplace={m.showPageBulkReplace}
|
||||||
onTriggerRun={m.handleTriggerRun}
|
onTriggerRun={(full) => m.handleTriggerRun(full)}
|
||||||
onRetryRun={m.handleRetryRun}
|
onRetryRun={() => m.handleRetryRun()}
|
||||||
onRetryInsert={m.handleRetryInsert}
|
onRetryInsert={() => m.handleRetryInsert()}
|
||||||
onLoadRunHistory={m.loadRunHistory}
|
onLoadRunHistory={() => m.loadRunHistory()}
|
||||||
onToggleRunDetails={m.toggleRunDetails}
|
onToggleRunDetails={(id) => m.toggleRunDetails(id)}
|
||||||
onBulkReplaceApplied={() => { m.showPageBulkReplace = false; m.loadRunHistory(); }}
|
onBulkReplaceApplied={() => { m.showPageBulkReplace = false; m.loadRunHistory(); }}
|
||||||
onBulkReplaceClose={() => m.showPageBulkReplace = false}
|
onBulkReplaceClose={() => m.showPageBulkReplace = false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user