Files
ss-tools/frontend/src/components/git/BranchSelector.svelte
busya 8039d09505 refactor(frontend): unify Tailwind classes — replace hardcoded colors with semantic design tokens
- Replace all bg-blue-600/hover:bg-blue-700 → bg-primary/hover:bg-primary-hover
- Replace all bg-red-600/hover:bg-red-700 → bg-destructive/hover:bg-destructive-hover
- Replace all focus:ring-blue-500 → focus-visible:ring-primary-ring
- Replace all text-blue-600 → text-primary on interactive elements
- Replace all bg-blue-50 → bg-primary-light on UI surfaces
- Replace all bg-red-50 → bg-destructive-light on error surfaces
- Replace all border-blue-200/300 → border-primary-ring
- Replace all border-red-200 → border-destructive-light
- Replace peer-checked:bg-blue-600 → peer-checked:bg-primary (toggles)
- Replace focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 patterns
- Keep status badge patterns (bg-*-100 text-*-700) and hover:bg-blue-50 intact

82 files changed, ~400 changes. Build passes.
2026-05-19 10:46:00 +03:00

183 lines
6.1 KiB
Svelte

<!-- #region BranchSelector [C:3] [TYPE Component] [SEMANTICS git, branch, checkout, create, selector] -->
<!-- @BRIEF Component component: components/git/BranchSelector.svelte -->
<!-- @LAYER UI -->
<!--
@SEMANTICS: git, branch, selection, checkout
@PURPOSE: UI для выбора и создания веток Git.
@LAYER: Component
@RELATION: CALLS -> gitService.getBranches
@RELATION: CALLS -> GitServiceBranch
@RELATION: CALLS -> gitService.createBranch
@RELATION: BINDS_TO -> onchange
-->
<script>
// [SECTION: IMPORTS]
import { onMount } from 'svelte';
import { gitService } from '../../services/gitService';
import { addToast as toast } from '../../lib/toasts.js';
import { t } from '../../lib/i18n';
import { Button, Select, Input } from '../../lib/ui';
// [/SECTION]
// [SECTION: PROPS]
let {
dashboardId,
envId = null,
currentBranch = $bindable('main'),
onchange = () => {},
} = $props();
// [/SECTION]
// [SECTION: STATE]
let branches = $state([]);
let loading = $state(false);
let showCreate = $state(false);
let newBranchName = $state('');
// [/SECTION]
// #region onMount:Function [TYPE Function]
/**
* @purpose Load branches when component is mounted.
* @pre Component is initialized.
* @post loadBranches is called.
*/
onMount(async () => {
await loadBranches();
});
// #endregion onMount:Function
// #region loadBranches:Function [TYPE Function]
/**
* @purpose Загружает список веток для дашборда.
* @pre dashboardId is provided.
* @post branches обновлен.
*/
async function loadBranches() {
console.log(`[BranchSelector][Action] Loading branches for dashboard ${dashboardId}`);
loading = true;
try {
branches = await gitService.getBranches(dashboardId, envId);
console.log(`[BranchSelector][Coherence:OK] Loaded ${branches.length} branches`);
} catch (e) {
console.error(`[BranchSelector][Coherence:Failed] ${e.message}`);
toast($t.git?.load_branches_failed, 'error');
} finally {
loading = false;
}
}
// #endregion loadBranches:Function
// #region handleSelect:Function [TYPE Function]
/**
* @purpose Handles branch selection from dropdown.
* @pre event contains branch name.
* @post handleCheckout is called with selected branch.
*/
function handleSelect(event) {
handleCheckout(event.target.value);
}
// #endregion handleSelect:Function
// #region handleCheckout:Function [TYPE Function]
/**
* @purpose Переключает текущую ветку.
* @param {string} branchName - Имя ветки.
* @post currentBranch обновлен, родительский callback вызван.
*/
async function handleCheckout(branchName) {
console.log(`[BranchSelector][Action] Checking out branch ${branchName}`);
try {
await gitService.checkoutBranch(dashboardId, branchName, envId);
currentBranch = branchName;
onchange({ branch: branchName });
toast($t.git?.switched_to?.replace('{branch}', branchName), 'success');
console.log(`[BranchSelector][Coherence:OK] Checked out ${branchName}`);
} catch (e) {
console.error(`[BranchSelector][Coherence:Failed] ${e.message}`);
toast(e.message, 'error');
}
}
// #endregion handleCheckout:Function
// #region handleCreate:Function [TYPE Function]
/**
* @purpose Создает новую ветку.
* @pre newBranchName is not empty.
* @post Новая ветка создана и загружена; showCreate reset.
*/
async function handleCreate() {
if (!newBranchName) return;
console.log(`[BranchSelector][Action] Creating branch ${newBranchName} from ${currentBranch}`);
try {
await gitService.createBranch(dashboardId, newBranchName, currentBranch, envId);
toast($t.git?.created_branch?.replace('{branch}', newBranchName), 'success');
showCreate = false;
newBranchName = '';
await loadBranches();
console.log(`[BranchSelector][Coherence:OK] Branch created`);
} catch (e) {
console.error(`[BranchSelector][Coherence:Failed] ${e.message}`);
toast(e.message, 'error');
}
}
// #endregion handleCreate:Function
</script>
<!-- [SECTION: TEMPLATE] -->
<div class="space-y-3">
<div class="flex items-center gap-3">
<div class="flex-grow">
<Select
bind:value={currentBranch}
onchange={handleSelect}
disabled={loading}
options={branches.map(b => ({ value: b.name, label: b.name }))}
/>
</div>
<Button
variant="ghost"
size="sm"
onclick={() => showCreate = !showCreate}
disabled={loading}
class="text-primary"
>
+ {$t.git.new_branch}
</Button>
</div>
{#if showCreate}
<div class="flex items-end gap-2 bg-gray-50 p-3 rounded-lg border border-dashed border-gray-200">
<div class="flex-grow">
<Input
bind:value={newBranchName}
placeholder={$t.git?.branch_name_placeholder}
disabled={loading}
/>
</div>
<Button
variant="primary"
size="sm"
onclick={handleCreate}
disabled={loading || !newBranchName}
isLoading={loading}
class="bg-green-600 hover:bg-green-700"
>
{$t.git.create}
</Button>
<Button
variant="ghost"
size="sm"
onclick={() => showCreate = false}
disabled={loading}
>
{$t.common.cancel}
</Button>
</div>
{/if}
</div>
<!-- [/SECTION] -->
<!-- #endregion BranchSelector -->