Deploy auto de l'API PHP de stats (parallèle Report/Doc) : - ApiToolDeployer : copy _api/ → C:\xampp\htdocs\proserve\ avec atomic rename + backups horodatés + revert + pruning. Strict clone du pattern DocToolDeployer. - ApiToolConfig dans LocalConfig (default folder "proserve" en minuscule pour matcher l'install standard PROSERVE côté client). - Settings → Avancés : section dédiée (HtdocsRoot, FolderName, AutoDeploy, MaxBackups, Re-déployer, liste backups + revert) + ApiBackupViewModel. - DI registration dans App.xaml.cs, injection MainViewModel, appel post-install après le bloc Doc. - 8 nouvelles strings i18n (5 langues). Fix WebView2 vide en Program Files : - Set WEBVIEW2_USER_DATA_FOLDER vers %LocalAppData%\PSLauncher\WebView2\ dans App.OnStartup. Sans ça, WebView2 essaie d'écrire son cache à côté du .exe (read-only en Program Files) → init silencieusement KO → onglets Report/Doc vides. Refresh auto WebView2 sur navigation d'onglet : - Hook IsVisibleChanged sur ReportWebView et DocsWebView. À chaque ré-affichage, CoreWebView2.Reload() pour voir les données serveur à jour. Le binding Source garde la nav initiale au cold start. Fix UI : Cancel décalant le footer pendant Check updates : - Nouveau flag IsDownloadActive distinct de IsBusy. Visibility du bouton Cancel passe de IsBusy → IsDownloadActive : couvre uniquement les DL, plus aucune apparition pendant Check updates / Install / Uninstall / Self-update qui décalait tout le layout 1 s. DL robustness (multi-PC) : - SafeInstallAsync : error boundary sur l'install handler. Plus aucune exception silencieuse — log + popup d'erreur visible (résout les cas "le 2e PC ne démarre pas, sans message"). - DownloadManager : vérif explicite que CHAQUE segment a Completed=true && DownloadedBytes >= Length AVANT le SHA-256. Le check actualSize était inutile (fichier sparse-préalloué). - MaxRetryAttempts Polly 6 → 10 pour absorber les outages PHP-FPM request_terminate_timeout sous charge multi-PC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
775 lines
50 KiB
XML
775 lines
50 KiB
XML
<Window x:Class="PSLauncher.App.Views.MainWindow"
|
||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:vm="clr-namespace:PSLauncher.App.ViewModels"
|
||
xmlns:loc="clr-namespace:PSLauncher.Core.Localization;assembly=PSLauncher.Core"
|
||
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
|
||
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||
Title="PROSERVE Launcher"
|
||
Icon="pack://application:,,,/Resources/favicon.ico"
|
||
Background="Black"
|
||
Width="1280" Height="720"
|
||
MinWidth="980" MinHeight="600"
|
||
WindowStartupLocation="CenterScreen"
|
||
WindowState="Maximized"
|
||
WindowStyle="None"
|
||
ResizeMode="CanResize"
|
||
AllowsTransparency="False"
|
||
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
|
||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||
mc:Ignorable="d"
|
||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
|
||
|
||
<shell:WindowChrome.WindowChrome>
|
||
<shell:WindowChrome CaptionHeight="56"
|
||
GlassFrameThickness="0"
|
||
CornerRadius="0"
|
||
ResizeBorderThickness="6"
|
||
UseAeroCaptionButtons="False" />
|
||
</shell:WindowChrome.WindowChrome>
|
||
|
||
<Window.Resources>
|
||
|
||
<!-- =================== TEMPLATE : LIGNE COMPACTE (autres versions) =================== -->
|
||
<DataTemplate x:Key="CompactRowTemplate" DataType="{x:Type vm:VersionRowViewModel}">
|
||
<Border Margin="0,0,0,8" CornerRadius="6" BorderThickness="1">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Bg.Card}" />
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Border}" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsRemoteOnly}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.AvailableBg}" />
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Available}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding IsBusy}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.BusyBg}" />
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="4" /> <!-- accent strip -->
|
||
<ColumnDefinition Width="*" />
|
||
<ColumnDefinition Width="Auto" />
|
||
<ColumnDefinition Width="Auto" />
|
||
</Grid.ColumnDefinitions>
|
||
|
||
<!-- Strip latérale colorée : Border avec coins arrondis à gauche
|
||
pour suivre la rondeur (CornerRadius=6) de la card parent. -->
|
||
<Border Grid.Column="0" CornerRadius="5,0,0,5">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Installed}" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsRemoteOnly}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Available}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding IsBusy}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
</Border>
|
||
|
||
<Grid Grid.Column="1" Margin="16,12">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto" />
|
||
<RowDefinition Height="Auto" />
|
||
</Grid.RowDefinitions>
|
||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||
<TextBlock Text="{Binding Version, StringFormat='v{0}'}"
|
||
FontSize="15" FontWeight="SemiBold"
|
||
VerticalAlignment="Center"
|
||
Foreground="{StaticResource Brush.Text.Primary}" />
|
||
<Border CornerRadius="10" Padding="8,3" Margin="12,0,0,0"
|
||
VerticalAlignment="Center">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Installed}" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsRemoteOnly}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Available}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding IsBusy}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<TextBlock Text="{Binding StateLabel}" FontSize="11" FontWeight="SemiBold"
|
||
Foreground="White" />
|
||
</Border>
|
||
</StackPanel>
|
||
<TextBlock Grid.Row="1" FontSize="11"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
Margin="0,4,0,0">
|
||
<Run Text="{Binding SizeDisplay, Mode=OneWay}" />
|
||
<Run Text=" • " />
|
||
<Run Text="{Binding PrimaryDate, Mode=OneWay}" />
|
||
</TextBlock>
|
||
</Grid>
|
||
|
||
<!-- Action button -->
|
||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center" Margin="12,0,0,0">
|
||
<Button Style="{StaticResource PrimaryButton}"
|
||
Content="{x:Static loc:Strings.ActionLaunch}" Padding="22,8" FontSize="13"
|
||
Command="{Binding LaunchCommand}"
|
||
Visibility="{Binding IsInstalled, Converter={StaticResource BoolToVisibility}}" />
|
||
<Button Style="{StaticResource AccentButton}"
|
||
Content="{Binding InstallButtonLabel}" Padding="22,8" FontSize="13"
|
||
Command="{Binding InstallCommand}"
|
||
Visibility="{Binding IsRemoteOnly, Converter={StaticResource BoolToVisibility}}" />
|
||
<!-- Bouton rouge « Annuler » : visible uniquement quand on a un partial.
|
||
Permet d'abandonner le partial et repartir de zéro (avec confirmation). -->
|
||
<Button Style="{StaticResource DangerButton}"
|
||
Content="{x:Static loc:Strings.ActionCancel}"
|
||
Padding="14,8" FontSize="13" Margin="6,0,0,0"
|
||
Command="{Binding RestartFromZeroCommand}"
|
||
ToolTip="{x:Static loc:Strings.MenuRestartFromZero}"
|
||
Visibility="{Binding ShowRestartFromZero, Converter={StaticResource BoolToVisibility}}" />
|
||
<StackPanel Orientation="Horizontal"
|
||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibility}}">
|
||
<ProgressBar Width="140" Height="6"
|
||
Value="{Binding ProgressPercent}" Maximum="100"
|
||
Foreground="{StaticResource Brush.Status.Busy}"
|
||
Background="#2A2A30" BorderThickness="0"
|
||
VerticalAlignment="Center" />
|
||
<TextBlock Text="{Binding ProgressPercent, StringFormat={}{0:0}%}"
|
||
FontSize="11" Margin="8,0,0,0" VerticalAlignment="Center"
|
||
Foreground="{StaticResource Brush.Text.Secondary}" />
|
||
</StackPanel>
|
||
</StackPanel>
|
||
|
||
<!-- ... menu -->
|
||
<Button Grid.Column="3"
|
||
Style="{StaticResource IconButton}"
|
||
Content="⋯" FontSize="18" FontWeight="Bold"
|
||
Margin="8,0,8,0" VerticalAlignment="Center"
|
||
ToolTip="Plus d'actions"
|
||
Tag="{Binding}"
|
||
Click="OnMoreMenuClick">
|
||
<Button.ContextMenu>
|
||
<ContextMenu>
|
||
<MenuItem Header="{x:Static loc:Strings.MenuReleaseNotes}"
|
||
Command="{Binding PlacementTarget.Tag.ShowReleaseNotesCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||
<MenuItem Header="{x:Static loc:Strings.MenuOpenFolder}"
|
||
Command="{Binding PlacementTarget.Tag.OpenFolderCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||
Visibility="{Binding PlacementTarget.Tag.IsInstalled, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
<MenuItem Header="{x:Static loc:Strings.MenuRestartFromZero}"
|
||
Command="{Binding PlacementTarget.Tag.RestartFromZeroCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||
Visibility="{Binding PlacementTarget.Tag.HasResumableDownload, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
<Separator Visibility="{Binding PlacementTarget.Tag.IsInstalled, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
<MenuItem Header="{x:Static loc:Strings.MenuUninstall}"
|
||
Command="{Binding PlacementTarget.Tag.UninstallCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||
Visibility="{Binding PlacementTarget.Tag.IsInstalled, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
</ContextMenu>
|
||
</Button.ContextMenu>
|
||
</Button>
|
||
</Grid>
|
||
</Border>
|
||
</DataTemplate>
|
||
</Window.Resources>
|
||
|
||
<!--
|
||
Layout principal — grille 2 colonnes × 3 rangées :
|
||
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Top bar (ColSpan 2) │ Row 0
|
||
├──────────┬──────────────────────────────────────────────────┤
|
||
│ │ │
|
||
│ Sidebar │ Content area │ Row 1
|
||
│ (Library │ (Library / Report / Doc) │
|
||
│ Report │ │
|
||
│ Doc) ├──────────────────────────────────────────────────┤
|
||
│ RowSpan2 │ Footer (DL/install) │ Row 2
|
||
│ │ visible only on Library │
|
||
└──────────┴──────────────────────────────────────────────────┘
|
||
200px *
|
||
|
||
La sidebar a RowSpan=2 → s'étend du sous-topbar jusqu'au bas
|
||
de fenêtre. Le footer est confiné à la colonne content.
|
||
-->
|
||
<Grid Background="{StaticResource Brush.Bg.Window}">
|
||
<!-- La fenêtre est correctement clampée à la work area du moniteur en mode
|
||
Maximized via WM_GETMINMAXINFO (cf. MainWindow.xaml.cs). Plus besoin
|
||
de hack via Margin sur trigger WindowState. -->
|
||
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="200" />
|
||
<ColumnDefinition Width="*" />
|
||
</Grid.ColumnDefinitions>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto" /> <!-- Top bar -->
|
||
<RowDefinition Height="Auto" /> <!-- Health banner -->
|
||
<RowDefinition Height="*" /> <!-- Body (sidebar + content) -->
|
||
<RowDefinition Height="Auto" /> <!-- Footer -->
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- Background image : limité à la zone content (Row 2, Col 1) pour ne pas
|
||
passer derrière la sidebar, le banner ou le footer. -->
|
||
<Rectangle Grid.Row="2" Grid.Column="1" IsHitTestVisible="False">
|
||
<Rectangle.Fill>
|
||
<ImageBrush ImageSource="pack://application:,,,/Resources/Background.png"
|
||
Stretch="UniformToFill"
|
||
AlignmentX="Right"
|
||
AlignmentY="Bottom"
|
||
Opacity="0.25" />
|
||
</Rectangle.Fill>
|
||
</Rectangle>
|
||
<Rectangle Grid.Row="2" Grid.Column="1"
|
||
Fill="{StaticResource Brush.Bg.BlueTint}"
|
||
IsHitTestVisible="False" />
|
||
|
||
<!-- Top bar : 3 colonnes (brand / license center / chrome).
|
||
Grid.ColumnSpan=2 → s'étend sur sidebar + content. -->
|
||
<Border Grid.Row="0" Grid.ColumnSpan="2" Background="{StaticResource Brush.Bg.Sidebar}"
|
||
BorderBrush="{StaticResource Brush.Border}" BorderThickness="0,0,0,1"
|
||
Padding="20,12">
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*" /> <!-- brand -->
|
||
<ColumnDefinition Width="*" /> <!-- license (center) -->
|
||
<ColumnDefinition Width="*" /> <!-- chrome (right) -->
|
||
</Grid.ColumnDefinitions>
|
||
<!-- Col 1 : brand -->
|
||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||
<Image Source="pack://application:,,,/Resources/logo-asterion-white.png"
|
||
Height="36"
|
||
VerticalAlignment="Center"
|
||
Margin="0,0,14,0"
|
||
SnapsToDevicePixels="True"
|
||
RenderOptions.BitmapScalingMode="HighQuality" />
|
||
<TextBlock Text="PROSERVE"
|
||
FontFamily="{StaticResource Font.Brand}"
|
||
FontSize="22"
|
||
Foreground="{StaticResource Brush.Text.Primary}"
|
||
VerticalAlignment="Center" />
|
||
<TextBlock Text="Launcher"
|
||
FontSize="20"
|
||
FontWeight="Bold"
|
||
Foreground="{StaticResource Brush.Text.Primary}"
|
||
VerticalAlignment="Center"
|
||
Margin="14,0,0,0" />
|
||
</StackPanel>
|
||
|
||
<!-- Col 2 : badge license cliquable, centré -->
|
||
<Border Grid.Column="1"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||
Cursor="Hand"
|
||
CornerRadius="16" Padding="14,6"
|
||
shell:WindowChrome.IsHitTestVisibleInChrome="True"
|
||
ToolTip="{x:Static loc:Strings.LicenseTooltip}">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="#2A1414" />
|
||
<Setter Property="BorderBrush" Value="#EF4444" />
|
||
<Setter Property="BorderThickness" Value="1" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Ok">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.InstalledBg}" />
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Installed}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Warning">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.BusyBg}" />
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<Border.InputBindings>
|
||
<MouseBinding Gesture="LeftClick" Command="{Binding OpenLicenseCommand}" />
|
||
</Border.InputBindings>
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="{Binding LicenseIcon}"
|
||
FontSize="15" FontWeight="Bold"
|
||
VerticalAlignment="Center"
|
||
Margin="0,0,8,0">
|
||
<TextBlock.Style>
|
||
<Style TargetType="TextBlock">
|
||
<Setter Property="Foreground" Value="#EF4444" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Ok">
|
||
<Setter Property="Foreground" Value="{StaticResource Brush.Status.Installed}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Warning">
|
||
<Setter Property="Foreground" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</TextBlock.Style>
|
||
</TextBlock>
|
||
<TextBlock Text="{Binding LicenseSummary}"
|
||
FontSize="13" FontWeight="SemiBold"
|
||
VerticalAlignment="Center">
|
||
<TextBlock.Style>
|
||
<Style TargetType="TextBlock">
|
||
<Setter Property="Foreground" Value="#FCA5A5" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Ok">
|
||
<Setter Property="Foreground" Value="#86EFAC" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Warning">
|
||
<Setter Property="Foreground" Value="#FCD34D" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</TextBlock.Style>
|
||
</TextBlock>
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<!-- Col 3 : Paramètres + window chrome -->
|
||
<StackPanel Grid.Column="2" Orientation="Horizontal"
|
||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||
<Button Style="{StaticResource SecondaryButton}"
|
||
Content="{x:Static loc:Strings.TopBarSettings}"
|
||
shell:WindowChrome.IsHitTestVisibleInChrome="True"
|
||
Command="{Binding OpenSettingsCommand}"
|
||
Margin="0,0,16,0" />
|
||
<Button Style="{StaticResource WindowControlButton}"
|
||
Content="—" ToolTip="{x:Static loc:Strings.Minimize}"
|
||
shell:WindowChrome.IsHitTestVisibleInChrome="True"
|
||
Click="OnMinimizeClick" />
|
||
<Button Style="{StaticResource WindowControlButton}"
|
||
Content="☐" ToolTip="{x:Static loc:Strings.Maximize}"
|
||
shell:WindowChrome.IsHitTestVisibleInChrome="True"
|
||
Click="OnMaxRestoreClick" />
|
||
<Button Style="{StaticResource WindowCloseButton}"
|
||
Content="✕" ToolTip="{x:Static loc:Strings.CloseTooltip}"
|
||
shell:WindowChrome.IsHitTestVisibleInChrome="True"
|
||
Click="OnCloseClick" />
|
||
</StackPanel>
|
||
</Grid>
|
||
</Border>
|
||
|
||
<!-- ============== Health banner (Row 1, Col 1 uniquement) ==============
|
||
Pills colorées indiquant l'état des dépendances système (SteamVR,
|
||
Vive Streaming, ping casque…). Le banner ne couvre QUE la colonne
|
||
content — la sidebar à gauche s'étend du sous-topbar jusqu'en bas
|
||
en RowSpan=3 (banner + body + footer) pour rester visuellement le
|
||
chapeau de tout. Visible uniquement sur la page Library. -->
|
||
<Border Grid.Row="1" Grid.Column="1"
|
||
Background="{StaticResource Brush.Bg.Sidebar}"
|
||
BorderBrush="{StaticResource Brush.Border}"
|
||
BorderThickness="0,0,0,1"
|
||
Padding="16,8"
|
||
Visibility="{Binding ShowHealthBanner, Converter={StaticResource BoolToVisibility}}">
|
||
<ItemsControl ItemsSource="{Binding HealthIndicators}">
|
||
<ItemsControl.ItemsPanel>
|
||
<ItemsPanelTemplate>
|
||
<StackPanel Orientation="Horizontal" />
|
||
</ItemsPanelTemplate>
|
||
</ItemsControl.ItemsPanel>
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate>
|
||
<Border Background="{Binding StatusBrush}"
|
||
BorderBrush="{Binding BorderBrush}"
|
||
BorderThickness="1"
|
||
CornerRadius="12"
|
||
Padding="10,4"
|
||
Margin="0,0,8,0"
|
||
ToolTip="{Binding Tooltip}"
|
||
ToolTipService.InitialShowDelay="200"
|
||
ToolTipService.ShowDuration="20000">
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="{Binding Icon}"
|
||
FontSize="14"
|
||
Foreground="{Binding IconForeground}"
|
||
VerticalAlignment="Center" />
|
||
<TextBlock Text="{Binding Name}"
|
||
Margin="6,0,0,0"
|
||
FontSize="12"
|
||
Foreground="{StaticResource Brush.Text.Primary}"
|
||
VerticalAlignment="Center" />
|
||
</StackPanel>
|
||
</Border>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
</Border>
|
||
|
||
<!-- ============== Content area (Row 2, Col 1, swap par CurrentPage) ==============
|
||
Superpose plusieurs blocs (Library / Report / Documentation) gérés par
|
||
leur Visibility liée à CurrentPage. -->
|
||
<Grid Grid.Row="2" Grid.Column="1">
|
||
|
||
<!-- ====================== LIBRARY PAGE ====================== -->
|
||
<Grid Visibility="{Binding IsLibrary, Converter={StaticResource BoolToVisibility}}">
|
||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||
<StackPanel Margin="32,28">
|
||
|
||
<!-- ========= FEATURED (version courante) ========= -->
|
||
<Border CornerRadius="10" BorderThickness="1"
|
||
Padding="0"
|
||
Margin="0,0,0,32"
|
||
Visibility="{Binding HasFeaturedVersion, Converter={StaticResource BoolToVisibility}}">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Bg.Card}" />
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Installed}" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding FeaturedVersion.IsRemoteOnly}" Value="True">
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Available}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding FeaturedVersion.IsBusy}" Value="True">
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="6" />
|
||
<ColumnDefinition Width="*" />
|
||
</Grid.ColumnDefinitions>
|
||
|
||
<!-- Bande latérale large : Border avec coins arrondis seulement à
|
||
gauche pour suivre la rondeur de la card parent (CornerRadius=10).
|
||
Rectangle ne se clippe pas sur le parent ⇒ on utilisait un visuel
|
||
qui dépassait les coins. -->
|
||
<Border Grid.Column="0" CornerRadius="9,0,0,9">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Installed}" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding FeaturedVersion.IsRemoteOnly}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Available}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding FeaturedVersion.IsBusy}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
</Border>
|
||
|
||
<Grid Grid.Column="1" Margin="32,28">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*" />
|
||
<ColumnDefinition Width="Auto" />
|
||
<ColumnDefinition Width="Auto" />
|
||
</Grid.ColumnDefinitions>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto" />
|
||
<RowDefinition Height="Auto" />
|
||
<RowDefinition Height="Auto" />
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- Label "Version courante" -->
|
||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||
Text="{x:Static loc:Strings.FeaturedCurrent}"
|
||
FontSize="11" FontWeight="Bold"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
Margin="0,0,0,4" />
|
||
|
||
<!-- Numéro version + badge -->
|
||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
|
||
<TextBlock Text="{Binding FeaturedVersion.Version, StringFormat='PROSERVE v{0}'}"
|
||
FontSize="32" FontWeight="Bold"
|
||
Foreground="{StaticResource Brush.Text.Primary}" />
|
||
<Border CornerRadius="12" Padding="10,4" Margin="16,8,0,0"
|
||
VerticalAlignment="Center">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Installed}" />
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding FeaturedVersion.IsRemoteOnly}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Available}" />
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding FeaturedVersion.IsBusy}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.Status.Busy}" />
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<TextBlock Text="{Binding FeaturedVersion.StateLabel}" FontSize="12" FontWeight="SemiBold"
|
||
Foreground="White" />
|
||
</Border>
|
||
</StackPanel>
|
||
|
||
<!-- Meta -->
|
||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||
FontSize="13"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
Margin="0,8,0,0">
|
||
<Run Text="{x:Static loc:Strings.ReleasedOn}" />
|
||
<Run Text="{Binding FeaturedVersion.PrimaryDate, Mode=OneWay}" />
|
||
<Run Text=" • " />
|
||
<Run Text="{Binding FeaturedVersion.SizeDisplay, Mode=OneWay}" />
|
||
</TextBlock>
|
||
|
||
<!-- Action principale (gros bouton) -->
|
||
<Button Grid.Row="0" Grid.RowSpan="3" Grid.Column="1"
|
||
Style="{StaticResource PrimaryButton}"
|
||
Content="{x:Static loc:Strings.ActionLaunchBig}"
|
||
FontSize="20" Padding="56,16"
|
||
VerticalAlignment="Center"
|
||
Command="{Binding FeaturedVersion.LaunchCommand}"
|
||
Visibility="{Binding FeaturedVersion.IsInstalled, Converter={StaticResource BoolToVisibility}}" />
|
||
|
||
<StackPanel Grid.Row="0" Grid.RowSpan="3" Grid.Column="1"
|
||
Orientation="Vertical" VerticalAlignment="Center"
|
||
Visibility="{Binding FeaturedVersion.IsRemoteOnly, Converter={StaticResource BoolToVisibility}}">
|
||
<Button Style="{StaticResource AccentButton}"
|
||
Content="{Binding FeaturedVersion.InstallButtonLabel}"
|
||
FontSize="20" Padding="48,16"
|
||
Command="{Binding FeaturedVersion.InstallCommand}" />
|
||
<!-- Bouton rouge « Annuler » : visible uniquement quand un partial existe.
|
||
Permet d'abandonner et repartir de zéro (confirmation requise). -->
|
||
<Button Style="{StaticResource DangerButton}"
|
||
Content="{x:Static loc:Strings.ActionCancel}"
|
||
FontSize="13" Padding="20,8" Margin="0,8,0,0"
|
||
HorizontalAlignment="Center"
|
||
Command="{Binding FeaturedVersion.RestartFromZeroCommand}"
|
||
ToolTip="{x:Static loc:Strings.MenuRestartFromZero}"
|
||
Visibility="{Binding FeaturedVersion.ShowRestartFromZero, Converter={StaticResource BoolToVisibility}}" />
|
||
</StackPanel>
|
||
|
||
<StackPanel Grid.Row="0" Grid.RowSpan="3" Grid.Column="1"
|
||
Orientation="Vertical" VerticalAlignment="Center" Width="220"
|
||
Visibility="{Binding FeaturedVersion.IsBusy, Converter={StaticResource BoolToVisibility}}">
|
||
<TextBlock Text="{Binding FeaturedVersion.StateLabel}"
|
||
HorizontalAlignment="Center"
|
||
Foreground="{StaticResource Brush.Status.Busy}"
|
||
FontWeight="SemiBold" Margin="0,0,0,8" />
|
||
<ProgressBar Height="8"
|
||
Value="{Binding FeaturedVersion.ProgressPercent}" Maximum="100"
|
||
Foreground="{StaticResource Brush.Status.Busy}"
|
||
Background="#2A2A30" BorderThickness="0" />
|
||
<TextBlock Text="{Binding FeaturedVersion.ProgressPercent, StringFormat={}{0:0}%}"
|
||
HorizontalAlignment="Center"
|
||
FontSize="12" Margin="0,4,0,0"
|
||
Foreground="{StaticResource Brush.Text.Secondary}" />
|
||
</StackPanel>
|
||
|
||
<!-- ... menu -->
|
||
<Button Grid.Row="0" Grid.RowSpan="3" Grid.Column="2"
|
||
Style="{StaticResource IconButton}"
|
||
Content="⋯" FontSize="22" FontWeight="Bold"
|
||
Margin="12,0,0,0"
|
||
VerticalAlignment="Center"
|
||
ToolTip="Plus d'actions"
|
||
Tag="{Binding FeaturedVersion}"
|
||
Click="OnMoreMenuClick">
|
||
<Button.ContextMenu>
|
||
<ContextMenu>
|
||
<MenuItem Header="{x:Static loc:Strings.MenuReleaseNotes}"
|
||
Command="{Binding PlacementTarget.Tag.ShowReleaseNotesCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||
<MenuItem Header="{x:Static loc:Strings.MenuOpenFolder}"
|
||
Command="{Binding PlacementTarget.Tag.OpenFolderCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||
Visibility="{Binding PlacementTarget.Tag.IsInstalled, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
<MenuItem Header="{x:Static loc:Strings.MenuRestartFromZero}"
|
||
Command="{Binding PlacementTarget.Tag.RestartFromZeroCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||
Visibility="{Binding PlacementTarget.Tag.HasResumableDownload, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
<Separator Visibility="{Binding PlacementTarget.Tag.IsInstalled, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
<MenuItem Header="{x:Static loc:Strings.MenuUninstall}"
|
||
Command="{Binding PlacementTarget.Tag.UninstallCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||
Visibility="{Binding PlacementTarget.Tag.IsInstalled, RelativeSource={RelativeSource AncestorType=ContextMenu}, Converter={StaticResource BoolToVisibility}}" />
|
||
</ContextMenu>
|
||
</Button.ContextMenu>
|
||
</Button>
|
||
</Grid>
|
||
</Grid>
|
||
</Border>
|
||
|
||
<!-- ========= AUTRES VERSIONS ========= -->
|
||
<StackPanel Visibility="{Binding HasOtherVersions, Converter={StaticResource BoolToVisibility}}">
|
||
<Grid Margin="0,0,0,12">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="Auto" />
|
||
<ColumnDefinition Width="*" />
|
||
</Grid.ColumnDefinitions>
|
||
<TextBlock Grid.Column="0" Text="{x:Static loc:Strings.SectionOther}"
|
||
FontSize="12" FontWeight="Bold"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
VerticalAlignment="Center" />
|
||
<Border Grid.Column="1" Background="{StaticResource Brush.Border}"
|
||
Height="1" Margin="12,0,0,0" VerticalAlignment="Center" />
|
||
</Grid>
|
||
|
||
<ItemsControl ItemsSource="{Binding OtherVersions}"
|
||
ItemTemplate="{StaticResource CompactRowTemplate}" />
|
||
</StackPanel>
|
||
|
||
<!-- Empty state -->
|
||
<Border Background="{StaticResource Brush.Bg.Card}"
|
||
BorderBrush="{StaticResource Brush.Border}" BorderThickness="1"
|
||
CornerRadius="6"
|
||
Padding="32"
|
||
Visibility="{Binding EmptyHintVisibility}">
|
||
<TextBlock Text="{Binding EmptyHint}"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
TextWrapping="Wrap" />
|
||
</Border>
|
||
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
|
||
<!-- Bouton flottant "Vérifier les MAJ" : ancré bas-gauche du body, au-dessus
|
||
du background bitmap, à l'extérieur du footer. -->
|
||
<Button HorizontalAlignment="Left" VerticalAlignment="Bottom"
|
||
Margin="32,0,0,24"
|
||
Style="{StaticResource SecondaryButton}"
|
||
Content="{x:Static loc:Strings.TopBarCheckUpdates}"
|
||
Command="{Binding CheckForUpdatesCommand}" />
|
||
|
||
<!-- Copyright flottant en bas centré, sur une pill sombre pour rester
|
||
lisible quel que soit l'arrière-plan (image, voile noir, etc.).
|
||
Bottom margin volontairement faible pour rester collé au footer
|
||
(barre de DL) quand il s'affiche. -->
|
||
<Border HorizontalAlignment="Center" VerticalAlignment="Bottom"
|
||
Margin="0,0,0,8"
|
||
Background="#A0000000"
|
||
CornerRadius="10"
|
||
Padding="10,4">
|
||
<TextBlock Text="{x:Static loc:Strings.Copyright}"
|
||
FontSize="11"
|
||
Foreground="White" />
|
||
</Border>
|
||
</Grid>
|
||
<!-- ====================== END LIBRARY PAGE ====================== -->
|
||
|
||
<!-- ====================== REPORT PAGE (WebView2) ====================== -->
|
||
<Grid Background="{StaticResource Brush.Bg.Window}"
|
||
Visibility="{Binding IsReport, Converter={StaticResource BoolToVisibility}}">
|
||
<wv2:WebView2 x:Name="ReportWebView"
|
||
Source="{Binding ReportUri, Mode=OneWay}" />
|
||
</Grid>
|
||
|
||
<!-- ====================== DOCUMENTATION PAGE ====================== -->
|
||
<Grid Background="{StaticResource Brush.Bg.Window}"
|
||
Visibility="{Binding IsDocumentation, Converter={StaticResource BoolToVisibility}}">
|
||
<!-- WebView2 si une URL est configurée, sinon un placeholder explicatif -->
|
||
<wv2:WebView2 x:Name="DocsWebView"
|
||
Source="{Binding DocumentationUri, Mode=OneWay}"
|
||
Visibility="{Binding HasDocumentationUrl, Converter={StaticResource BoolToVisibility}}" />
|
||
<TextBlock Text="{x:Static loc:Strings.DocsPlaceholder}"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
FontSize="14" TextAlignment="Center" TextWrapping="Wrap"
|
||
MaxWidth="500"
|
||
Visibility="{Binding HasDocumentationUrl, Converter={StaticResource InverseBoolToVisibility}}" />
|
||
</Grid>
|
||
|
||
</Grid>
|
||
<!-- ============== END Content area ============== -->
|
||
|
||
<!-- ============== Left sidebar : nav buttons ==============
|
||
Row 1 + RowSpan=3 → la sidebar s'étend du bas du top bar jusqu'au
|
||
bas de la fenêtre, couvrant les 3 rows (health banner, content,
|
||
footer). Visuellement la sidebar reste le « chapeau de tout »
|
||
côté gauche, peu importe ce qui se passe à droite. -->
|
||
<Border Grid.Row="1" Grid.RowSpan="3" Grid.Column="0"
|
||
Background="#0A0A0E"
|
||
BorderBrush="{StaticResource Brush.Border}"
|
||
BorderThickness="0,0,1,0">
|
||
<DockPanel LastChildFill="True">
|
||
<!-- Bloc d'info en bas de sidebar : version du launcher + IP locale.
|
||
Utilité diagnostic terrain : le support peut lire les deux infos
|
||
sans avoir à naviguer dans Windows ou About. DockPanel.Dock=Bottom
|
||
pour rester collé au bas peu importe la hauteur de la fenêtre. -->
|
||
<Border DockPanel.Dock="Bottom"
|
||
BorderBrush="{StaticResource Brush.Border}"
|
||
BorderThickness="0,1,0,0"
|
||
Padding="16,10">
|
||
<!-- Grid 2 colonnes : la colonne label (Auto) prend la largeur du
|
||
plus long ("PS_Launcher:"), la colonne valeur démarre au même X
|
||
sur les deux lignes → version et IP visuellement alignées. -->
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="Auto" />
|
||
<ColumnDefinition Width="*" />
|
||
</Grid.ColumnDefinitions>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto" />
|
||
<RowDefinition Height="Auto" />
|
||
</Grid.RowDefinitions>
|
||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||
Text="PS_Launcher"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
FontSize="11" Margin="0,0,8,0"
|
||
ToolTip="Version du launcher" />
|
||
<TextBlock Grid.Row="0" Grid.Column="1"
|
||
Text="{Binding AppVersion}"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
FontSize="11" />
|
||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||
Text="IP"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
FontSize="11" Margin="0,2,8,0"
|
||
ToolTip="IPv4 locale du PC (interface avec gateway par défaut)" />
|
||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||
Text="{Binding LocalIpAddress}"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
FontSize="11" Margin="0,2,0,0" />
|
||
</Grid>
|
||
</Border>
|
||
|
||
<StackPanel Orientation="Vertical" Margin="0,16,0,0">
|
||
<!-- Style des boutons définis dans Theme.xaml (NavButton) -->
|
||
<Button Style="{StaticResource NavButton}"
|
||
Command="{Binding NavigateLibraryCommand}"
|
||
Tag="{Binding IsLibrary}">
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="📚" FontSize="16" Margin="0,0,12,0" VerticalAlignment="Center" />
|
||
<TextBlock Text="{x:Static loc:Strings.NavLibrary}" VerticalAlignment="Center" />
|
||
</StackPanel>
|
||
</Button>
|
||
<Button Style="{StaticResource NavButton}"
|
||
Command="{Binding NavigateReportCommand}"
|
||
Tag="{Binding IsReport}">
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="📊" FontSize="16" Margin="0,0,12,0" VerticalAlignment="Center" />
|
||
<TextBlock Text="{x:Static loc:Strings.NavReport}" VerticalAlignment="Center" />
|
||
</StackPanel>
|
||
</Button>
|
||
<Button Style="{StaticResource NavButton}"
|
||
Command="{Binding NavigateDocumentationCommand}"
|
||
Tag="{Binding IsDocumentation}">
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="📖" FontSize="16" Margin="0,0,12,0" VerticalAlignment="Center" />
|
||
<TextBlock Text="{x:Static loc:Strings.NavDocumentation}" VerticalAlignment="Center" />
|
||
</StackPanel>
|
||
</Button>
|
||
</StackPanel>
|
||
</DockPanel>
|
||
</Border>
|
||
|
||
<!-- Footer : Row 3, Col 1 → cantonné à la colonne content, sous la zone
|
||
Library/Report/Doc. La sidebar (RowSpan=2 sur rows 2-3) le contourne
|
||
par sa colonne. -->
|
||
<Border Grid.Row="3" Grid.Column="1"
|
||
Background="{StaticResource Brush.Bg.Footer}"
|
||
BorderBrush="{StaticResource Brush.Border}" BorderThickness="0,1,0,0"
|
||
Padding="20,10"
|
||
Visibility="{Binding FooterVisibility}">
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*" />
|
||
<ColumnDefinition Width="Auto" />
|
||
</Grid.ColumnDefinitions>
|
||
<StackPanel Grid.Column="0">
|
||
<TextBlock Text="{Binding FooterText}"
|
||
Foreground="{StaticResource Brush.Text.Secondary}"
|
||
FontSize="12" />
|
||
<ProgressBar Height="4" Margin="0,4,0,0"
|
||
Value="{Binding ProgressPercent}" Maximum="100"
|
||
Foreground="{StaticResource Brush.Accent}"
|
||
Background="#2A2A30" BorderThickness="0" />
|
||
</StackPanel>
|
||
<Button Grid.Column="1"
|
||
Style="{StaticResource SecondaryButton}"
|
||
Content="{x:Static loc:Strings.ActionCancel}"
|
||
VerticalAlignment="Center" Margin="12,0,0,0"
|
||
Command="{Binding CancelDownloadCommand}"
|
||
Visibility="{Binding IsDownloadActive, Converter={StaticResource BoolToVisibility}}" />
|
||
</Grid>
|
||
</Border>
|
||
</Grid>
|
||
</Window>
|