- 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.
183 lines
6.1 KiB
Svelte
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 -->
|