2026-05-14 12:31:35 +02:00
|
|
|
@inherits LayoutComponentBase
|
2026-05-20 06:12:54 +02:00
|
|
|
@implements IDisposable
|
2026-05-14 12:31:35 +02:00
|
|
|
@inject IProjectService ProjectService
|
2026-05-16 16:13:35 +02:00
|
|
|
@inject ISettingsService SettingsService
|
|
|
|
|
@inject IThemeService ThemeService
|
|
|
|
|
@inject OllamaSettings OllamaSettings
|
2026-05-21 11:04:42 +02:00
|
|
|
@inject IAuthService AuthService
|
|
|
|
|
@inject CustomAuthStateProvider AuthStateProvider
|
2026-05-14 12:31:35 +02:00
|
|
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
<div class="main-content">
|
2026-05-21 11:04:42 +02:00
|
|
|
@if (_isAuthenticated)
|
|
|
|
|
{
|
2026-05-14 12:31:35 +02:00
|
|
|
<aside class="sidebar">
|
|
|
|
|
<div class="nav-section">
|
|
|
|
|
<button class="nav-section-header" @onclick='() => ToggleSection("file")'>
|
|
|
|
|
<span class="nav-icon">@(activeSection == "file" ? "▼" : "▶")</span>
|
2026-05-20 07:27:41 +02:00
|
|
|
@T("Datei", "File")
|
2026-05-14 12:31:35 +02:00
|
|
|
</button>
|
|
|
|
|
@if (activeSection == "file")
|
|
|
|
|
{
|
|
|
|
|
<div class="nav-section-body">
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="nav-item" @onclick="CreateNewProject">@T("Neues Projekt", "New project")</button>
|
|
|
|
|
<button class="nav-item" @onclick="OpenEditor">@T("Editor \u00f6ffnen", "Open editor")</button>
|
2026-05-14 12:31:35 +02:00
|
|
|
<div class="nav-separator"></div>
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="nav-item" @onclick="ExitApp">@T("Beenden", "Exit")</button>
|
2026-05-14 12:31:35 +02:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="nav-section">
|
|
|
|
|
<button class="nav-section-header" @onclick='() => ToggleSection("extras")'>
|
|
|
|
|
<span class="nav-icon">@(activeSection == "extras" ? "▼" : "▶")</span>
|
2026-05-20 07:27:41 +02:00
|
|
|
@T("Extras", "Extras")
|
2026-05-14 12:31:35 +02:00
|
|
|
</button>
|
|
|
|
|
@if (activeSection == "extras")
|
|
|
|
|
{
|
|
|
|
|
<div class="nav-section-body">
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="nav-item" @onclick='() => NavigateTo("/settings")'>@T("Einstellungen", "Settings")</button>
|
|
|
|
|
<button class="nav-item" @onclick='() => NavigateTo("/models")'>@T("Modelle verwalten", "Manage models")</button>
|
|
|
|
|
<button class="nav-item" @onclick='() => NavigateTo("/assistant")'>@T("Assistent", "Assistant")</button>
|
2026-05-14 12:31:35 +02:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="nav-section">
|
|
|
|
|
<button class="nav-section-header" @onclick='() => ToggleSection("projects")'>
|
|
|
|
|
<span class="nav-icon">@(activeSection == "projects" ? "▼" : "▶")</span>
|
2026-05-20 07:27:41 +02:00
|
|
|
@T("Projekte", "Projects")
|
2026-05-14 12:31:35 +02:00
|
|
|
</button>
|
|
|
|
|
@if (activeSection == "projects")
|
|
|
|
|
{
|
|
|
|
|
<div class="nav-section-body">
|
2026-05-14 12:42:37 +02:00
|
|
|
<button class="nav-item nav-item-toggle" @onclick='() => showProjectList = !showProjectList'>
|
|
|
|
|
<span class="nav-icon">@(showProjectList ? "▼" : "▶")</span>
|
2026-05-20 07:27:41 +02:00
|
|
|
@T("Liste vorhandener Projekte", "List existing projects")
|
2026-05-14 12:42:37 +02:00
|
|
|
</button>
|
|
|
|
|
@if (showProjectList)
|
2026-05-14 12:31:35 +02:00
|
|
|
{
|
2026-05-14 12:42:37 +02:00
|
|
|
<div class="nav-sublist">
|
|
|
|
|
@if (projects == null)
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
<span class="nav-hint">@T("Lade Projekte...", "Loading projects...")</span>
|
2026-05-14 12:42:37 +02:00
|
|
|
}
|
|
|
|
|
else if (projects.Count == 0)
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
<span class="nav-hint">@T("Keine Projekte vorhanden.", "No projects available.")</span>
|
2026-05-14 12:42:37 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
@foreach (var p in projects)
|
|
|
|
|
{
|
2026-05-20 06:12:54 +02:00
|
|
|
<button class="nav-item nav-item-sub @(p.Id == currentProjectId ? "active" : "")" @onclick='() => SelectProject(p)'>@p.Name</button>
|
2026-05-14 12:42:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</div>
|
2026-05-14 12:31:35 +02:00
|
|
|
}
|
2026-05-14 12:42:37 +02:00
|
|
|
<div class="nav-separator"></div>
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="nav-item" @onclick="CreateNewProject">@T("Projekt erstellen", "Create project")</button>
|
2026-05-14 12:31:35 +02:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
2026-05-20 06:12:54 +02:00
|
|
|
@if (currentProjectId > 0 && fileTree != null)
|
|
|
|
|
{
|
|
|
|
|
<div class="nav-section nav-section-open">
|
|
|
|
|
<div class="nav-section-header">
|
2026-05-20 07:27:41 +02:00
|
|
|
<span>@T("Projektdateien", "Project files")</span>
|
2026-05-20 06:12:54 +02:00
|
|
|
<span class="nav-header-actions">
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="nav-item-icon" @onclick="RefreshFileTree" title="@T("Dateibaum aktualisieren", "Refresh file tree")">↻</button>
|
2026-05-20 06:12:54 +02:00
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="nav-section-body">
|
|
|
|
|
@foreach (var item in fileTree)
|
|
|
|
|
{
|
|
|
|
|
<div class="tree-item @(item.IsDirectory ? "dir" : "file") @(item.FullPath == currentFilePath ? "active" : "")"
|
|
|
|
|
@onclick="() => OpenFile(item)">
|
|
|
|
|
<span class="tree-icon">@(item.IsDirectory ? "📁" : "📄")</span>
|
|
|
|
|
<span class="tree-name">@item.Name</span>
|
|
|
|
|
@if (item.IsDirectory)
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="tree-rename" @onclick="() => BeginNewFile(item)" @onclick:stopPropagation title="@T("Neue Datei", "New file")">+</button>
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="tree-rename" @onclick="() => BeginRename(item)" @onclick:stopPropagation title="@T("Umbenennen", "Rename")">✎</button>
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
@foreach (var child in item.Children)
|
|
|
|
|
{
|
|
|
|
|
<div class="tree-item file tree-child @(child.FullPath == currentFilePath ? "active" : "")"
|
|
|
|
|
@onclick="() => OpenFile(child)">
|
|
|
|
|
<span class="tree-icon">📄</span>
|
|
|
|
|
<span class="tree-name">@child.Name</span>
|
2026-05-20 07:27:41 +02:00
|
|
|
<button class="tree-rename" @onclick="() => BeginRename(child)" @onclick:stopPropagation title="@T("Umbenennen", "Rename")">✎</button>
|
2026-05-20 06:12:54 +02:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (showRenameDialog)
|
|
|
|
|
{
|
|
|
|
|
<div class="modal-overlay">
|
|
|
|
|
<div class="modal-dialog">
|
2026-05-20 07:27:41 +02:00
|
|
|
<div class="modal-title">@T("Datei umbenennen", "Rename file")</div>
|
2026-05-20 06:12:54 +02:00
|
|
|
<div class="modal-body">
|
|
|
|
|
<div class="form-group">
|
2026-05-20 07:27:41 +02:00
|
|
|
<label>@T("Neuer Name", "New name")</label>
|
|
|
|
|
<input @bind="renameNewName" placeholder="@T("neuer_name.md", "new_name.md")" />
|
2026-05-20 06:12:54 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-actions">
|
2026-05-20 07:27:41 +02:00
|
|
|
<button @onclick="CancelRename">@T("Abbrechen", "Cancel")</button>
|
|
|
|
|
<button @onclick="ConfirmRename" disabled="@(string.IsNullOrWhiteSpace(renameNewName))" class="primary">@T("Umbenennen", "Rename")</button>
|
2026-05-20 06:12:54 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (showNewFileDialog)
|
|
|
|
|
{
|
|
|
|
|
<div class="modal-overlay">
|
|
|
|
|
<div class="modal-dialog">
|
2026-05-20 07:27:41 +02:00
|
|
|
<div class="modal-title">@T("Neue Datei", "New file")</div>
|
2026-05-20 06:12:54 +02:00
|
|
|
<div class="modal-body">
|
|
|
|
|
<div class="form-group">
|
2026-05-20 07:27:41 +02:00
|
|
|
<label>@T("Dateiname", "Filename")</label>
|
|
|
|
|
<input @bind="newFileName" placeholder="@T("meine_datei.md", "my_file.md")" />
|
2026-05-20 06:12:54 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-actions">
|
2026-05-20 07:27:41 +02:00
|
|
|
<button @onclick="CancelNewFile">@T("Abbrechen", "Cancel")</button>
|
|
|
|
|
<button @onclick="ConfirmNewFile" disabled="@(string.IsNullOrWhiteSpace(newFileName))" class="primary">@T("Erstellen", "Create")</button>
|
2026-05-20 06:12:54 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2026-05-21 11:04:42 +02:00
|
|
|
|
|
|
|
|
@if (!string.IsNullOrEmpty(_username))
|
|
|
|
|
{
|
|
|
|
|
<div class="nav-section" style="border-top:1px solid var(--border-color);margin-top:8px;padding-top:4px;">
|
|
|
|
|
<div class="nav-section-header" style="cursor:default;">
|
|
|
|
|
<span style="font-size:11px;font-weight:normal;">@T("Angemeldet als:", "Logged in as:") @_username</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="nav-section-body">
|
|
|
|
|
<button class="nav-item" @onclick="NavigateToSetup2FA">@T("2FA verwalten", "Manage 2FA")</button>
|
|
|
|
|
<button class="nav-item" @onclick="Logout">@T("Abmelden", "Logout")</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 12:31:35 +02:00
|
|
|
</aside>
|
2026-05-21 11:04:42 +02:00
|
|
|
}
|
2026-05-14 12:31:35 +02:00
|
|
|
|
|
|
|
|
<main class="detail-panel">
|
|
|
|
|
@Body
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<footer class="status-bar">
|
|
|
|
|
<span class="status-dot connected"></span>
|
|
|
|
|
<span class="status-text">@statusText</span>
|
|
|
|
|
@if (isBusy)
|
|
|
|
|
{
|
|
|
|
|
<progress value="@operationProgress" max="100"></progress>
|
|
|
|
|
}
|
|
|
|
|
</footer>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@code {
|
2026-05-20 07:27:41 +02:00
|
|
|
private string _language = "de";
|
2026-05-21 11:04:42 +02:00
|
|
|
private string _username = string.Empty;
|
|
|
|
|
private bool _isAuthenticated;
|
2026-05-14 12:31:35 +02:00
|
|
|
private string? activeSection;
|
2026-05-14 12:42:37 +02:00
|
|
|
private bool showProjectList;
|
2026-05-14 12:31:35 +02:00
|
|
|
private List<Project>? projects;
|
2026-05-20 06:12:54 +02:00
|
|
|
private List<FileTreeItem>? fileTree;
|
|
|
|
|
private int currentProjectId;
|
|
|
|
|
private string? currentFilePath;
|
2026-05-14 12:31:35 +02:00
|
|
|
private string statusText = "Bereit";
|
|
|
|
|
private bool isBusy;
|
|
|
|
|
private double operationProgress;
|
2026-05-20 06:12:54 +02:00
|
|
|
private bool showRenameDialog;
|
|
|
|
|
private string renameNewName = string.Empty;
|
|
|
|
|
private FileTreeItem? renameTarget;
|
|
|
|
|
private bool showNewFileDialog;
|
|
|
|
|
private string newFileName = string.Empty;
|
|
|
|
|
private FileTreeItem? newFileParentDir;
|
2026-05-14 12:31:35 +02:00
|
|
|
|
2026-05-20 07:27:41 +02:00
|
|
|
private string T(string de, string en) => _language == "en" ? en : de;
|
|
|
|
|
|
2026-05-14 12:31:35 +02:00
|
|
|
[Inject]
|
|
|
|
|
private NavigationManager Navigation { get; set; } = default!;
|
|
|
|
|
|
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
|
|
|
{
|
2026-05-20 06:12:54 +02:00
|
|
|
await SettingsService.InitializeAsync();
|
2026-05-20 07:27:41 +02:00
|
|
|
_language = await SettingsService.GetLanguageAsync() ?? "de";
|
|
|
|
|
statusText = T("Bereit", "Ready");
|
2026-05-20 06:12:54 +02:00
|
|
|
Navigation.LocationChanged += OnLocationChanged;
|
2026-05-21 11:04:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task LoadCurrentUser()
|
|
|
|
|
{
|
|
|
|
|
var user = await AuthService.GetCurrentUserAsync();
|
|
|
|
|
_username = user?.Name ?? string.Empty;
|
|
|
|
|
_isAuthenticated = user != null;
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task AutoLoadLastProject()
|
|
|
|
|
{
|
|
|
|
|
if (currentProjectId > 0) return;
|
|
|
|
|
var lastId = await SettingsService.GetLastProjectIdAsync();
|
|
|
|
|
if (lastId == null) return;
|
|
|
|
|
var project = projects?.FirstOrDefault(p => p.Id == lastId.Value);
|
|
|
|
|
if (project != null)
|
|
|
|
|
{
|
|
|
|
|
await SettingsService.InitializeAsync(lastId.Value);
|
|
|
|
|
await SelectProject(project);
|
|
|
|
|
}
|
2026-05-14 12:31:35 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-16 16:13:35 +02:00
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
|
|
|
{
|
|
|
|
|
if (firstRender)
|
|
|
|
|
{
|
|
|
|
|
var theme = OllamaSettings.Theme;
|
|
|
|
|
if (!string.IsNullOrEmpty(theme))
|
|
|
|
|
await ThemeService.ApplyTheme(theme);
|
2026-05-21 11:04:42 +02:00
|
|
|
|
|
|
|
|
// Auto-login runs AFTER circuit establishment (correct scoped services)
|
|
|
|
|
await LoadCurrentUser();
|
|
|
|
|
#if DEBUG
|
|
|
|
|
if (!_isAuthenticated && Program.DebugAutoLogin)
|
|
|
|
|
{
|
|
|
|
|
var users = await SettingsService.GetUsersAsync();
|
|
|
|
|
var first = users.FirstOrDefault();
|
|
|
|
|
if (first != null)
|
|
|
|
|
{
|
|
|
|
|
await AuthService.AdminBypassLoginAsync(first.Id);
|
|
|
|
|
AuthStateProvider.NotifyStateChanged();
|
|
|
|
|
await LoadCurrentUser();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
if (_isAuthenticated)
|
|
|
|
|
{
|
|
|
|
|
await LoadProjects();
|
|
|
|
|
await LoadProjectContext();
|
|
|
|
|
await AutoLoadLastProject();
|
|
|
|
|
}
|
|
|
|
|
StateHasChanged();
|
2026-05-16 16:13:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 06:12:54 +02:00
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Navigation.LocationChanged -= OnLocationChanged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
|
|
|
|
|
{
|
2026-05-21 11:04:42 +02:00
|
|
|
await LoadCurrentUser();
|
|
|
|
|
if (_isAuthenticated)
|
|
|
|
|
{
|
|
|
|
|
await LoadProjects();
|
|
|
|
|
await LoadProjectContext();
|
|
|
|
|
}
|
2026-05-20 06:12:54 +02:00
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task LoadProjectContext()
|
|
|
|
|
{
|
|
|
|
|
currentProjectId = SettingsService.CurrentProjectId ?? 0;
|
|
|
|
|
currentFilePath = SettingsService.CurrentFilePath;
|
|
|
|
|
|
|
|
|
|
if (currentProjectId > 0)
|
|
|
|
|
{
|
|
|
|
|
await LoadFileTree();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fileTree = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task LoadFileTree()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
fileTree = await ProjectService.GetProjectFileTreeAsync(currentProjectId);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
fileTree = new();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 12:31:35 +02:00
|
|
|
private async Task LoadProjects()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
projects = await ProjectService.GetAllProjectsAsync();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
projects = new();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ToggleSection(string section)
|
|
|
|
|
{
|
|
|
|
|
activeSection = activeSection == section ? null : section;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ExitApp()
|
|
|
|
|
{
|
|
|
|
|
activeSection = null;
|
|
|
|
|
Navigation.NavigateTo("/");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CreateNewProject()
|
|
|
|
|
{
|
|
|
|
|
activeSection = null;
|
2026-05-20 06:12:54 +02:00
|
|
|
Navigation.NavigateTo("/editor/newproject");
|
2026-05-14 12:31:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OpenEditor()
|
|
|
|
|
{
|
|
|
|
|
activeSection = null;
|
|
|
|
|
Navigation.NavigateTo("/editor");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void NavigateTo(string path)
|
|
|
|
|
{
|
|
|
|
|
activeSection = null;
|
|
|
|
|
Navigation.NavigateTo(path);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 11:04:42 +02:00
|
|
|
private void NavigateToSetup2FA()
|
|
|
|
|
{
|
|
|
|
|
activeSection = null;
|
|
|
|
|
Navigation.NavigateTo("/setup-2fa");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task Logout()
|
|
|
|
|
{
|
|
|
|
|
activeSection = null;
|
|
|
|
|
await AuthService.LogoutAsync();
|
|
|
|
|
_username = string.Empty;
|
|
|
|
|
AuthStateProvider.NotifyStateChanged();
|
|
|
|
|
Navigation.NavigateTo("/login", true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 06:12:54 +02:00
|
|
|
private async Task SelectProject(Project project)
|
|
|
|
|
{
|
|
|
|
|
activeSection = "files";
|
|
|
|
|
SettingsService.CurrentProjectId = project.Id;
|
|
|
|
|
currentProjectId = project.Id;
|
|
|
|
|
SettingsService.CurrentFilePath = null;
|
|
|
|
|
await SettingsService.SaveLastProjectIdAsync(project.Id);
|
|
|
|
|
await SettingsService.LoadProjectSettingsAsync();
|
|
|
|
|
await LoadFileTree();
|
|
|
|
|
Navigation.NavigateTo($"/editor/index/{project.Id}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RefreshFileTree()
|
|
|
|
|
{
|
|
|
|
|
await LoadFileTree();
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void BeginRename(FileTreeItem item)
|
|
|
|
|
{
|
|
|
|
|
renameTarget = item;
|
|
|
|
|
renameNewName = item.Name;
|
|
|
|
|
showRenameDialog = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CancelRename()
|
|
|
|
|
{
|
|
|
|
|
showRenameDialog = false;
|
|
|
|
|
renameTarget = null;
|
|
|
|
|
renameNewName = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ConfirmRename()
|
|
|
|
|
{
|
|
|
|
|
if (renameTarget == null || string.IsNullOrWhiteSpace(renameNewName)) return;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var dir = Path.GetDirectoryName(renameTarget.FullPath)!;
|
|
|
|
|
var newPath = Path.Combine(dir, renameNewName);
|
|
|
|
|
if (File.Exists(newPath))
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
statusText = T("Datei existiert bereits.", "File already exists.");
|
2026-05-20 06:12:54 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
File.Move(renameTarget.FullPath, newPath);
|
|
|
|
|
|
|
|
|
|
if (currentFilePath == renameTarget.FullPath)
|
|
|
|
|
SettingsService.CurrentFilePath = newPath;
|
|
|
|
|
|
2026-05-20 07:27:41 +02:00
|
|
|
statusText = $"{T("Umbenannt zu:", "Renamed to:")} {renameNewName}";
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
statusText = $"{T("Fehler:", "Error:")} {ex.Message}";
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
showRenameDialog = false;
|
|
|
|
|
renameTarget = null;
|
|
|
|
|
renameNewName = string.Empty;
|
|
|
|
|
await LoadFileTree();
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void BeginNewFile(FileTreeItem dir)
|
|
|
|
|
{
|
|
|
|
|
newFileParentDir = dir;
|
|
|
|
|
newFileName = string.Empty;
|
|
|
|
|
showNewFileDialog = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CancelNewFile()
|
|
|
|
|
{
|
|
|
|
|
showNewFileDialog = false;
|
|
|
|
|
newFileParentDir = null;
|
|
|
|
|
newFileName = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task ConfirmNewFile()
|
|
|
|
|
{
|
|
|
|
|
if (newFileParentDir == null || string.IsNullOrWhiteSpace(newFileName)) return;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var path = Path.Combine(newFileParentDir.FullPath, newFileName);
|
|
|
|
|
if (!path.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
path += ".md";
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
statusText = T("Datei existiert bereits.", "File already exists.");
|
2026-05-20 06:12:54 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
File.WriteAllText(path, $"# {Path.GetFileNameWithoutExtension(path)}\n\n");
|
2026-05-20 07:27:41 +02:00
|
|
|
statusText = $"{T("Erstellt:", "Created:")} {Path.GetFileName(path)}";
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-05-20 07:27:41 +02:00
|
|
|
statusText = $"{T("Fehler:", "Error:")} {ex.Message}";
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
showNewFileDialog = false;
|
|
|
|
|
newFileParentDir = null;
|
|
|
|
|
newFileName = string.Empty;
|
|
|
|
|
await LoadFileTree();
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OpenFile(FileTreeItem item)
|
2026-05-14 12:31:35 +02:00
|
|
|
{
|
2026-05-20 06:12:54 +02:00
|
|
|
if (item.IsDirectory) return;
|
2026-05-14 12:31:35 +02:00
|
|
|
activeSection = null;
|
2026-05-20 06:12:54 +02:00
|
|
|
SettingsService.CurrentFilePath = item.FullPath;
|
|
|
|
|
Navigation.NavigateTo($"/editor/file/{currentProjectId}?t={DateTime.Now.Ticks}");
|
2026-05-14 12:31:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetStatus(string text, bool busy = false, double progress = 0)
|
|
|
|
|
{
|
|
|
|
|
statusText = text;
|
|
|
|
|
isBusy = busy;
|
|
|
|
|
operationProgress = progress;
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|