Files
ss-tools/frontend/src/components/git/BranchSelector.svelte
2026-02-23 20:20:25 +03:00

182 lines
5.8 KiB
Svelte

<!-- [DEF:BranchSelector:Component] -->
<!--
@SEMANTICS: git, branch, selection, checkout
@PURPOSE: UI для выбора и создания веток Git.
@LAYER: Component
@RELATION: CALLS -> gitService.getBranches
@RELATION: CALLS -> gitService.checkoutBranch
@RELATION: CALLS -> gitService.createBranch
@RELATION: DISPATCHES -> change
-->
<script>
// [SECTION: IMPORTS]
import { onMount, createEventDispatcher } 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,
currentBranch = 'main',
} = $props();
// [/SECTION]
// [SECTION: STATE]
let branches = $state([]);
let loading = $state(false);
let showCreate = $state(false);
let newBranchName = $state('');
// [/SECTION]
const dispatch = createEventDispatcher();
// [DEF:onMount:Function]
/**
* @purpose Load branches when component is mounted.
* @pre Component is initialized.
* @post loadBranches is called.
*/
onMount(async () => {
await loadBranches();
});
// [/DEF:onMount:Function]
// [DEF:loadBranches: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);
console.log(`[BranchSelector][Coherence:OK] Loaded ${branches.length} branches`);
} catch (e) {
console.error(`[BranchSelector][Coherence:Failed] ${e.message}`);
toast('Failed to load branches', 'error');
} finally {
loading = false;
}
}
// [/DEF:loadBranches:Function]
// [DEF:handleSelect: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);
}
// [/DEF:handleSelect:Function]
// [DEF:handleCheckout:Function]
/**
* @purpose Переключает текущую ветку.
* @param {string} branchName - Имя ветки.
* @post currentBranch обновлен, событие отправлено.
*/
async function handleCheckout(branchName) {
console.log(`[BranchSelector][Action] Checking out branch ${branchName}`);
try {
await gitService.checkoutBranch(dashboardId, branchName);
currentBranch = branchName;
dispatch('change', { branch: branchName });
toast(`Switched to ${branchName}`, 'success');
console.log(`[BranchSelector][Coherence:OK] Checked out ${branchName}`);
} catch (e) {
console.error(`[BranchSelector][Coherence:Failed] ${e.message}`);
toast(e.message, 'error');
}
}
// [/DEF:handleCheckout:Function]
// [DEF:handleCreate: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);
toast(`Created 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');
}
}
// [/DEF: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-blue-600"
>
+ {$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="branch-name"
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] -->
<!-- [/DEF:BranchSelector:Component] -->