182 lines
5.8 KiB
Svelte
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] -->
|