feat(ui): tasks island with rows, chips, add-task, selection
TaskRowView with status chip (EqStatus converter + parameter), StrikeIfTrue, NotNullToBool converters. TasksIslandView with header, add-task TextBox (Enter=AddCommand), ItemsControl + flat Button for selection. Converters registered in App.axaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="ClaudeDo.App.App"
|
x:Class="ClaudeDo.App.App"
|
||||||
xmlns:local="using:ClaudeDo.App"
|
xmlns:local="using:ClaudeDo.App"
|
||||||
|
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
||||||
RequestedThemeVariant="Dark">
|
RequestedThemeVariant="Dark">
|
||||||
|
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
@@ -10,6 +11,11 @@
|
|||||||
<ResourceInclude Source="avares://ClaudeDo.Ui/Design/Tokens.axaml" />
|
<ResourceInclude Source="avares://ClaudeDo.Ui/Design/Tokens.axaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<!-- Converters -->
|
||||||
|
<converters:NotNullToBoolConverter x:Key="NotNullToBool"/>
|
||||||
|
<converters:StrikeIfTrueConverter x:Key="StrikeIfTrue"/>
|
||||||
|
<converters:EqStatusConverter x:Key="EqStatus"/>
|
||||||
|
|
||||||
<!-- Accent: Forest Teal -->
|
<!-- Accent: Forest Teal -->
|
||||||
<SolidColorBrush x:Key="AccentBrush" Color="#3d9474"/>
|
<SolidColorBrush x:Key="AccentBrush" Color="#3d9474"/>
|
||||||
<SolidColorBrush x:Key="AccentLightBrush" Color="#6bb89e"/>
|
<SolidColorBrush x:Key="AccentLightBrush" Color="#6bb89e"/>
|
||||||
|
|||||||
26
src/ClaudeDo.Ui/Converters/EqStatusConverter.cs
Normal file
26
src/ClaudeDo.Ui/Converters/EqStatusConverter.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Converters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true when the bound TaskStatus equals the ConverterParameter string.
|
||||||
|
/// Usage: Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
|
||||||
|
/// </summary>
|
||||||
|
public class EqStatusConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public static EqStatusConverter Instance { get; } = new();
|
||||||
|
|
||||||
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is TaskStatus status && parameter is string name &&
|
||||||
|
Enum.TryParse<TaskStatus>(name, ignoreCase: true, out var target))
|
||||||
|
return status == target;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
}
|
||||||
15
src/ClaudeDo.Ui/Converters/NotNullToBoolConverter.cs
Normal file
15
src/ClaudeDo.Ui/Converters/NotNullToBoolConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Converters;
|
||||||
|
|
||||||
|
public class NotNullToBoolConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public static NotNullToBoolConverter Instance { get; } = new();
|
||||||
|
|
||||||
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> value is not null;
|
||||||
|
|
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
}
|
||||||
16
src/ClaudeDo.Ui/Converters/StrikeIfTrueConverter.cs
Normal file
16
src/ClaudeDo.Ui/Converters/StrikeIfTrueConverter.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Converters;
|
||||||
|
|
||||||
|
public class StrikeIfTrueConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public static StrikeIfTrueConverter Instance { get; } = new();
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> value is true ? TextDecorations.Strikethrough : null;
|
||||||
|
|
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
=> throw new NotSupportedException();
|
||||||
|
}
|
||||||
57
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
Normal file
57
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||||
|
x:Class="ClaudeDo.Ui.Views.Islands.TaskRowView"
|
||||||
|
x:DataType="vm:TaskRowViewModel">
|
||||||
|
<Border Classes="task-row" Classes.selected="{Binding IsSelected}">
|
||||||
|
<Grid ColumnDefinitions="36,*,32" Margin="10,10">
|
||||||
|
<!-- Done toggle -->
|
||||||
|
<Button Grid.Column="0" Classes="check-btn" VerticalAlignment="Center"
|
||||||
|
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleDoneCommand}"
|
||||||
|
CommandParameter="{Binding}">
|
||||||
|
<Ellipse Width="18" Height="18" Classes="task-check"
|
||||||
|
Classes.done="{Binding Done}"/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Title + chip row + live tail -->
|
||||||
|
<StackPanel Grid.Column="1" Spacing="6" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding Title}" FontSize="14"
|
||||||
|
Foreground="{DynamicResource TextBrush}"
|
||||||
|
TextDecorations="{Binding Done, Converter={StaticResource StrikeIfTrue}}"/>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<Border Classes="chip"
|
||||||
|
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
|
||||||
|
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
|
||||||
|
Classes.error="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Failed}"
|
||||||
|
Classes.queued="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Queued}">
|
||||||
|
<TextBlock Text="{Binding Status}" FontSize="10"
|
||||||
|
FontFamily="{DynamicResource MonoFamily}" Margin="6,2"/>
|
||||||
|
</Border>
|
||||||
|
<Border Classes="chip">
|
||||||
|
<TextBlock Text="{Binding ListName}" FontSize="10" Margin="6,2"
|
||||||
|
Foreground="{DynamicResource TextDimBrush}"/>
|
||||||
|
</Border>
|
||||||
|
<Border Classes="chip" IsVisible="{Binding Branch, Converter={StaticResource NotNullToBool}}">
|
||||||
|
<TextBlock Text="{Binding Branch}" FontFamily="{DynamicResource MonoFamily}"
|
||||||
|
FontSize="10" Margin="6,2"/>
|
||||||
|
</Border>
|
||||||
|
<Border Classes="chip" IsVisible="{Binding DiffStat, Converter={StaticResource NotNullToBool}}">
|
||||||
|
<TextBlock Text="{Binding DiffStat}" FontFamily="{DynamicResource MonoFamily}"
|
||||||
|
FontSize="10" Margin="6,2"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="{Binding LiveTail}" FontFamily="{DynamicResource MonoFamily}"
|
||||||
|
FontSize="11" Foreground="{DynamicResource TextMuteBrush}"
|
||||||
|
TextTrimming="CharacterEllipsis" MaxLines="1"
|
||||||
|
IsVisible="{Binding LiveTail, Converter={StaticResource NotNullToBool}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Star toggle -->
|
||||||
|
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center"
|
||||||
|
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleStarCommand}"
|
||||||
|
CommandParameter="{Binding}">
|
||||||
|
<PathIcon Width="14" Height="14"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
8
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
Normal file
8
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
|
public partial class TaskRowView : UserControl
|
||||||
|
{
|
||||||
|
public TaskRowView() { InitializeComponent(); }
|
||||||
|
}
|
||||||
@@ -1,8 +1,47 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||||
|
xmlns:islands="using:ClaudeDo.Ui.Views.Islands"
|
||||||
x:Class="ClaudeDo.Ui.Views.Islands.TasksIslandView"
|
x:Class="ClaudeDo.Ui.Views.Islands.TasksIslandView"
|
||||||
x:DataType="vm:TasksIslandViewModel">
|
x:DataType="vm:TasksIslandViewModel">
|
||||||
<TextBlock Margin="14" Text="Tasks (placeholder)"
|
<DockPanel LastChildFill="True">
|
||||||
Foreground="{DynamicResource TextDimBrush}"/>
|
|
||||||
|
<!-- Header -->
|
||||||
|
<Border DockPanel.Dock="Top" Classes="island-header">
|
||||||
|
<StackPanel Margin="18,14" Spacing="4">
|
||||||
|
<TextBlock Classes="eyebrow" Text="{Binding HeaderEyebrow}"/>
|
||||||
|
<TextBlock FontFamily="{DynamicResource SansFamily}" FontSize="24"
|
||||||
|
FontWeight="SemiBold" Foreground="{DynamicResource TextBrush}"
|
||||||
|
Text="{Binding HeaderTitle}"/>
|
||||||
|
<TextBlock FontFamily="{DynamicResource MonoFamily}" FontSize="11"
|
||||||
|
Foreground="{DynamicResource TextMuteBrush}" Text="{Binding Subtitle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Add-task row -->
|
||||||
|
<Border DockPanel.Dock="Top" Margin="18,8,18,4">
|
||||||
|
<TextBox Watermark="Add a task…" Text="{Binding NewTaskTitle, Mode=TwoWay}">
|
||||||
|
<TextBox.KeyBindings>
|
||||||
|
<KeyBinding Gesture="Enter" Command="{Binding AddCommand}"/>
|
||||||
|
</TextBox.KeyBindings>
|
||||||
|
</TextBox>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Task list -->
|
||||||
|
<ScrollViewer>
|
||||||
|
<ItemsControl ItemsSource="{Binding Items}" Margin="10,4">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="vm:TaskRowViewModel">
|
||||||
|
<Button Classes="flat" HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
Command="{Binding $parent[ItemsControl].DataContext.SelectCommand}"
|
||||||
|
CommandParameter="{Binding}">
|
||||||
|
<islands:TaskRowView/>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
</DockPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user