Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions src/TextForge.Desktop/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TextForge.Core;
using TextForge.Core.Documents;
using TextForge.Core.Engine;
using TextForge.Core.Modules;
using TextForge.Core.Templates;

namespace TextForge.Desktop.ViewModels;
Expand All @@ -16,7 +21,7 @@ public partial class MainViewModel : ObservableObject

// --- NEW DRAWER STATE ---
[ObservableProperty]
public partial string ActiveTab { get; set; } = string.Empty;
private partial string ActiveTab { get; set; } = string.Empty;

// Derived flags for XAML IsVisible bindings
public bool IsDrawerOpen => !string.IsNullOrEmpty(ActiveTab);
Expand All @@ -37,14 +42,61 @@ partial void OnActiveTabChanged(string value)
}

[RelayCommand]
public void ToggleTab(string tabName)
private void ToggleTab(string tabName)
{
ActiveTab = (ActiveTab == tabName) ? string.Empty : tabName;
}

[RelayCommand]
public void CloseDrawer()
private void CloseDrawer()
{
ActiveTab = string.Empty;
}

public IReadOnlyList<ModuleDefinition> AvailableModules { get; } = ModuleRegistry.GetAvailableModules();
[RelayCommand]
private void ClearAllModules()
{
CurrentDocument.ClearModules();
}

[ObservableProperty]
public partial string ExportStatusMessage { get; set; } = "Status: Ready";

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ExportPdfCommand))]
public partial bool IsExporting { get; set; }

private bool CanExportPdf() => !IsExporting && CurrentDocument is not null;

[RelayCommand(CanExecute = nameof(CanExportPdf))]
private async Task ExportPdfAsync()
{
try
{
IsExporting = true;
ExportStatusMessage = "Status: Exporting PDF...";

await PdfService.CreatePdfAsync(CurrentDocument, Template, "output.pdf");

ExportStatusMessage = "Status: PDF Exported successfully!";
}
catch (Exception ex)
{
ExportStatusMessage = $"Status: Export failed ({ex.Message})";
}
finally
{
IsExporting = false;
}
}
[RelayCommand]
private void AddModule(ModuleDefinition definition)
{
var newModule = definition.Create();
var targetParent = CurrentDocument.IsRootSelected ? null : CurrentDocument.SelectedModule;

CurrentDocument.AddModule(newModule, parent: targetParent);
}

}
11 changes: 9 additions & 2 deletions src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mod="using:TextForge.Core.Modules"
x:Class="TextForge.Desktop.Views.Components.ModuleEditorView">
xmlns:vm="using:TextForge.Desktop.ViewModels"
x:Class="TextForge.Desktop.Views.Components.ModuleEditorView"
x:DataType="vm:MainViewModel">

<UserControl.Styles>
<Style Selector="ListBoxItem">
Expand Down Expand Up @@ -60,7 +62,9 @@
<!-- Self-Referencing Recursive Template for Modules & Submodules at any depth -->
<DataTemplate x:Key="RecursiveModuleTemplate" x:DataType="mod:Module">
<Border Classes="module-card"
Classes.selected="{Binding IsSelected}"
Tag="{Binding}"
PointerPressed="ModuleCard_PointerPressed"
Margin="0,1"
Padding="2">
<Expander IsExpanded="{Binding IsExpanded, Mode=TwoWay, FallbackValue=False}"
Expand Down Expand Up @@ -148,10 +152,13 @@
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Background="Transparent">
<StackPanel PointerPressed="DocumentRootCard_PointerPressed" Cursor="Hand" Classes="root-card selected" Name="EditorStackPanel" Spacing="6" Margin="0,0,0,12" Background="Transparent">
<StackPanel PointerPressed="DocumentRootCard_PointerPressed" Cursor="Hand" Classes="root-card"
Classes.selected="{Binding CurrentDocument.IsRootSelected}"
Name="EditorStackPanel" Spacing="6" Margin="0,0,0,12" Background="Transparent">
<!-- Modules List (Using ItemsControl to prevent VirtualizingStackPanel focus scroll jumps) -->
<ItemsControl Name="ModuleListBox"
Background="Transparent"
ItemsSource="{Binding CurrentDocument.Modules}"
ItemTemplate="{StaticResource RecursiveModuleTemplate}" />
</StackPanel>
</ScrollViewer>
Expand Down
36 changes: 23 additions & 13 deletions src/TextForge.Desktop/Views/Components/ModuleEditorView.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using TextForge.Core;
using TextForge.Core.Documents;
using TextForge.Core.Modules;
using TextForge.Desktop.ViewModels;

namespace TextForge.Desktop.Views.Components;

Expand Down Expand Up @@ -38,24 +41,31 @@ public ModuleEditorView()

private void DocumentRootCard_PointerPressed(object? sender, PointerPressedEventArgs e)
{
RootSelected?.Invoke(this, EventArgs.Empty);
}
// If the click happened on a nested module card or button, DON'T select root!
if (e.Source is Visual visual && visual.FindAncestorOfType<Border>()?.Classes.Contains("module-card") == true)
{
return; // Let the module card handle its own selection
}

public void SetRootSelectionHighlight(bool isRootSelected)
var vm = DataContext as MainViewModel ?? this.FindAncestorOfType<Window>()?.DataContext as MainViewModel;
vm?.CurrentDocument.SelectRoot();
}
private void ModuleCard_PointerPressed(object? sender, PointerPressedEventArgs e)
{
var rootCard = this.FindControl<Border>("DocumentRootCard");
if (rootCard is null) return;

if (isRootSelected)
if (sender is Border border && border.DataContext is Module clickedModule)
{
if (!rootCard.Classes.Contains("selected"))
// 1. Grab MainViewModel from ModuleEditorView (this), NOT the border's DataContext
if (DataContext is MainViewModel vm)
{
rootCard.Classes.Add("selected");
vm.CurrentDocument.SelectModule(clickedModule);
}
}
else
{
rootCard.Classes.Remove("selected");
else if (this.FindAncestorOfType<Window>()?.DataContext is MainViewModel windowVm)
{
windowVm.CurrentDocument.SelectModule(clickedModule);
}

// 2. Stop event from bubbling up to DocumentRootCard_PointerPressed
e.Handled = true;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cp="using:Avalonia.Controls"
x:Class="TextForge.Desktop.Views.Components.PropertiesToolbarView">
xmlns:vm="using:TextForge.Desktop.ViewModels"
x:Class="TextForge.Desktop.Views.Components.PropertiesToolbarView"
x:DataType="vm:MainViewModel">
<Border Background="#F9FAFB"
BorderBrush="#E5E7EB"
BorderThickness="1"
Expand Down Expand Up @@ -219,7 +221,7 @@
</Button>

</StackPanel>
<Button Content="Clear Page" Grid.Column="1"/>
<Button Content="Clear Page" Grid.Column="1" Command="{Binding ClearAllModulesCommand}"/>
</Grid>
</Border>
</UserControl>
23 changes: 17 additions & 6 deletions src/TextForge.Desktop/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8"
VerticalAlignment="Center">
<Button Name="AddModuleButton" Content="Add Module" />
<Button Name="ConvertButton" Content="Export PDF" Click="ConvertButton_Click" />
<Button Name="ConvertButton" Content="Export PDF" Command="{Binding ExportPdfCommand}" />
</StackPanel>
</Grid>
</Border>
Expand Down Expand Up @@ -114,7 +114,8 @@
FontSize="11" Foreground="{DynamicResource FaintGrayBrush}" TextWrapping="Wrap" />

<!-- Dynamically populated module definitions -->
<ItemsControl Name="AvailableModulesList">
<ItemsControl Name="AvailableModulesList"
ItemsSource="{Binding AvailableModules}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="mod:ModuleDefinition">
<Button HorizontalAlignment="Stretch"
Expand All @@ -126,7 +127,9 @@
Padding="8"
Margin="0,0,0,6"
Tag="{Binding}"
Click="PaletteItem_Click">
Command="{Binding $parent[Window].((vm:MainViewModel)DataContext).AddModuleCommand}"
CommandParameter="{Binding}"
ToolTip.Tip="{Binding Description}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock Text="{Binding Icon}" FontSize="16"
VerticalAlignment="Center" />
Expand Down Expand Up @@ -164,7 +167,13 @@
<components:PropertiesToolbarView Grid.Row="1" Name="PropertiesToolbar" />

<!-- Module Editor Component -->
<components:ModuleEditorView Grid.Row="2" Name="ModuleEditor" />
<components:ModuleEditorView Grid.Row="2"
Name="ModuleEditor"
ModuleMoveUpRequested="ModuleEditor_ModuleMoveUpRequested"
ModuleMoveDownRequested="ModuleEditor_ModuleMoveDownRequested"
ModuleDeleteRequested="ModuleEditor_ModuleDeleteRequested"
ModuleDetachRequested="ModuleEditor_ModuleDetachRequested"
RootSelected="ModuleEditor_RootSelected" />
</Grid>
</Border>

Expand All @@ -182,8 +191,10 @@
<Border Grid.Row="2" Height="24" BorderThickness="0,1,0,0" BorderBrush="#E5E7EB"
Background="#FAFAFA">
<Grid ColumnDefinitions="Auto,*,Auto" Margin="12,0">
<TextBlock Grid.Column="0" Name="StatusText" Text="Status: Synced" FontSize="11"
Foreground="{DynamicResource FaintGrayBrush}" VerticalAlignment="Center" />
<TextBlock Grid.Column="0" Name="StatusText" Text="{Binding ExportStatusMessage}" FontSize="11"
Foreground="{DynamicResource FaintGrayBrush}" VerticalAlignment="Center"

/>
<TextBlock Grid.Column="2" Text="Page 1 of 1 • 100%" FontSize="11"
Foreground="{DynamicResource FaintGrayBrush}" VerticalAlignment="Center" />
</Grid>
Expand Down
Loading
Loading