mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
0.7.7.1
This commit is contained in:
@@ -58,14 +58,16 @@
|
||||
BorderThickness="1"
|
||||
Foreground="#bb5649"
|
||||
Focusable="False"
|
||||
ToolTip.Tip="刷新新闻"
|
||||
ToolTip.Tip="刷新今日新闻"
|
||||
Click="OnRefreshButtonClick">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<fi:SymbolIcon Symbol="ArrowSync"
|
||||
<fi:SymbolIcon x:Name="RefreshIcon"
|
||||
Symbol="ArrowSync"
|
||||
IconVariant="Regular"
|
||||
FontSize="14"
|
||||
Foreground="#bb5649" />
|
||||
<TextBlock Text="刷新"
|
||||
<TextBlock x:Name="RefreshButtonText"
|
||||
Text="刷新"
|
||||
FontSize="13"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -625,13 +625,84 @@ public partial class JuyaNewsWidget : UserControl, IDesktopComponentWidget
|
||||
return;
|
||||
}
|
||||
|
||||
_cachedNews.Clear();
|
||||
_loadedDates.Clear();
|
||||
_dailyViews.Clear();
|
||||
NewsStackPanel.Children.Clear();
|
||||
_earliestLoadedDate = DateTime.Today;
|
||||
_isLoading = true;
|
||||
RefreshButtonText.Text = "刷新中...";
|
||||
RefreshIcon.IsEnabled = false;
|
||||
|
||||
await LoadInitialNewsAsync();
|
||||
try
|
||||
{
|
||||
var allNews = await FetchJuyaNewsAsync();
|
||||
|
||||
if (!_isAttached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var today = DateTime.Today;
|
||||
var todayNews = allNews.FirstOrDefault(n => n.Date.Date == today);
|
||||
|
||||
if (todayNews != null)
|
||||
{
|
||||
_cachedNews[today] = todayNews;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
if (!_isAttached) return;
|
||||
|
||||
var existingIndex = _loadedDates.IndexOf(today);
|
||||
if (existingIndex >= 0 && _dailyViews.Count > existingIndex)
|
||||
{
|
||||
var oldView = _dailyViews[existingIndex];
|
||||
var insertIndex = NewsStackPanel.Children.IndexOf(oldView);
|
||||
|
||||
if (insertIndex >= 0)
|
||||
{
|
||||
NewsStackPanel.Children.RemoveAt(insertIndex);
|
||||
_dailyViews.RemoveAt(existingIndex);
|
||||
|
||||
var newView = new DailyNewsView(todayNews, _isNightVisual);
|
||||
newView.CoverImageClicked += (s, e) => TryOpenUrl(todayNews.IssueUrl);
|
||||
|
||||
NewsStackPanel.Children.Insert(insertIndex, newView);
|
||||
_dailyViews.Insert(existingIndex, newView);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newView = new DailyNewsView(todayNews, _isNightVisual);
|
||||
newView.CoverImageClicked += (s, e) => TryOpenUrl(todayNews.IssueUrl);
|
||||
|
||||
NewsStackPanel.Children.Insert(0, newView);
|
||||
_dailyViews.Insert(0, newView);
|
||||
_loadedDates.Insert(0, today);
|
||||
}
|
||||
|
||||
RefreshButtonText.Text = "刷新";
|
||||
RefreshIcon.IsEnabled = true;
|
||||
UpdateAdaptiveLayout();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
RefreshButtonText.Text = "刷新";
|
||||
RefreshIcon.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
RefreshButtonText.Text = "刷新";
|
||||
RefreshIcon.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryOpenUrl(string? url)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -9,86 +9,107 @@
|
||||
d:DesignHeight="480"
|
||||
x:Class="LanMountainDesktop.Views.Components.WhiteboardWidget">
|
||||
|
||||
<Border x:Name="RootBorder"
|
||||
Background="#F1F4F9"
|
||||
CornerRadius="20"
|
||||
ClipToBounds="True"
|
||||
Padding="8">
|
||||
<Grid RowDefinitions="*,Auto"
|
||||
RowSpacing="8">
|
||||
<Border x:Name="CanvasBorder"
|
||||
Grid.Row="0"
|
||||
Background="#FFFFFF"
|
||||
BorderBrush="#24000000"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
ClipToBounds="True">
|
||||
<inking:InkCanvas x:Name="InkCanvas" />
|
||||
</Border>
|
||||
<Grid>
|
||||
<Border x:Name="RootBorder"
|
||||
Background="#F1F4F9"
|
||||
CornerRadius="20"
|
||||
ClipToBounds="True"
|
||||
Padding="8">
|
||||
<Grid RowDefinitions="*,Auto"
|
||||
RowSpacing="8">
|
||||
<Border x:Name="CanvasBorder"
|
||||
Grid.Row="0"
|
||||
Background="#FFFFFF"
|
||||
BorderBrush="#24000000"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
ClipToBounds="True">
|
||||
<inking:InkCanvas x:Name="InkCanvas" />
|
||||
</Border>
|
||||
|
||||
<Border x:Name="ToolbarBorder"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Background="#E6FFFFFF"
|
||||
BorderBrush="#16000000"
|
||||
<Border x:Name="ToolbarBorder"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Background="#E6FFFFFF"
|
||||
BorderBrush="#16000000"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="8,6">
|
||||
<StackPanel x:Name="ToolbarButtonsPanel"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="8">
|
||||
<Button x:Name="PenButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Pen"
|
||||
Click="OnPenButtonClick">
|
||||
<fi:SymbolIcon x:Name="PenIcon"
|
||||
Symbol="Pen"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
<Button x:Name="EraserButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Eraser"
|
||||
Click="OnEraserButtonClick">
|
||||
<fi:SymbolIcon x:Name="EraserIcon"
|
||||
Symbol="EraserTool"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
<Button x:Name="ClearButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Clear"
|
||||
Click="OnClearButtonClick">
|
||||
<fi:SymbolIcon x:Name="ClearIcon"
|
||||
Symbol="Delete"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
<Button x:Name="ExportButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Export SVG"
|
||||
Click="OnExportButtonClick">
|
||||
<fi:SymbolIcon x:Name="ExportIcon"
|
||||
Symbol="ArrowExport"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Popup x:Name="ColorPickerPopup"
|
||||
Placement="Top"
|
||||
PlacementTarget="{Binding #PenButton}"
|
||||
IsLightDismissEnabled="True"
|
||||
WindowManagerAddShadowHint="False">
|
||||
<Border Background="{DynamicResource SystemControlBackgroundChromeMediumBrush}"
|
||||
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="14"
|
||||
Padding="8,6">
|
||||
<StackPanel x:Name="ToolbarButtonsPanel"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="8">
|
||||
<Button x:Name="PenButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Pen"
|
||||
Click="OnPenButtonClick">
|
||||
<fi:SymbolIcon x:Name="PenIcon"
|
||||
Symbol="Pen"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
<Button x:Name="EraserButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Eraser"
|
||||
Click="OnEraserButtonClick">
|
||||
<fi:SymbolIcon x:Name="EraserIcon"
|
||||
Symbol="EraserTool"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
<Button x:Name="ClearButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Clear"
|
||||
Click="OnClearButtonClick">
|
||||
<fi:SymbolIcon x:Name="ClearIcon"
|
||||
Symbol="Delete"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
<Button x:Name="ExportButton"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Padding="0"
|
||||
CornerRadius="15"
|
||||
ToolTip.Tip="Export SVG"
|
||||
Click="OnExportButtonClick">
|
||||
<fi:SymbolIcon x:Name="ExportIcon"
|
||||
Symbol="ArrowExport"
|
||||
IconVariant="Regular"
|
||||
FontSize="14" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
CornerRadius="8"
|
||||
Padding="12">
|
||||
<ColorView x:Name="InkColorPicker"
|
||||
IsAlphaEnabled="False"
|
||||
IsColorSpectrumVisible="True"
|
||||
IsColorPaletteVisible="True"
|
||||
IsHexInputVisible="True"
|
||||
ColorChanged="OnColorPickerColorChanged" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
@@ -38,7 +39,7 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
private double _currentCellSize = 48;
|
||||
private WhiteboardToolMode _toolMode = WhiteboardToolMode.Pen;
|
||||
private bool? _isNightModeApplied;
|
||||
private SKColor _currentInkColor = SKColors.Black;
|
||||
private SKColor _selectedInkColor = SKColors.Black;
|
||||
private string _componentId = BuiltInComponentIds.DesktopWhiteboard;
|
||||
private string _placementId = string.Empty;
|
||||
private int _noteRetentionDays = WhiteboardNoteRetentionPolicy.DefaultDays;
|
||||
@@ -66,9 +67,22 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
ApplyCellSize(_currentCellSize);
|
||||
RefreshFromSettings();
|
||||
ApplyThemeVisual(force: true);
|
||||
InitializeColorPicker();
|
||||
SetToolMode(WhiteboardToolMode.Pen);
|
||||
}
|
||||
|
||||
private void InitializeColorPicker()
|
||||
{
|
||||
if (InkColorPicker is not null)
|
||||
{
|
||||
InkColorPicker.Color = new Color(
|
||||
_selectedInkColor.Alpha,
|
||||
_selectedInkColor.Red,
|
||||
_selectedInkColor.Green,
|
||||
_selectedInkColor.Blue);
|
||||
}
|
||||
}
|
||||
|
||||
public int NoteRetentionDays => _noteRetentionDays;
|
||||
|
||||
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
||||
@@ -149,7 +163,6 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
}
|
||||
|
||||
_isNightModeApplied = isNightMode;
|
||||
_currentInkColor = isNightMode ? SKColors.White : SKColors.Black;
|
||||
|
||||
RootBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#FF181B22") : Color.Parse("#FFF1F4F9"));
|
||||
CanvasBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#FF000000") : Color.Parse("#FFFFFFFF"));
|
||||
@@ -157,8 +170,6 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
ToolbarBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#1AFFFFFF") : Color.Parse("#E6FFFFFF"));
|
||||
ToolbarBorder.BorderBrush = new SolidColorBrush(isNightMode ? Color.Parse("#26FFFFFF") : Color.Parse("#16000000"));
|
||||
|
||||
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _currentInkColor;
|
||||
RecolorAllStrokes(_currentInkColor);
|
||||
RefreshToolButtonVisuals();
|
||||
}
|
||||
|
||||
@@ -204,6 +215,30 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceSaveNote()
|
||||
{
|
||||
if (_disposed || !HasValidPersistenceContext())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_noteDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_noteDirty = false;
|
||||
_noteSaveTimer.Stop();
|
||||
var noteSnapshot = BuildNoteSnapshot();
|
||||
try
|
||||
{
|
||||
_notePersistenceService.SaveNote(_componentId, _placementId, noteSnapshot, _noteRetentionDays);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -300,12 +335,22 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
|
||||
if (mode == WhiteboardToolMode.Pen)
|
||||
{
|
||||
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _currentInkColor;
|
||||
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _selectedInkColor;
|
||||
}
|
||||
|
||||
RefreshToolButtonVisuals();
|
||||
}
|
||||
|
||||
private void SetInkColor(SKColor color)
|
||||
{
|
||||
_selectedInkColor = color;
|
||||
if (_toolMode == WhiteboardToolMode.Pen)
|
||||
{
|
||||
InkCanvas.AvaloniaSkiaInkCanvas.Settings.InkColor = _selectedInkColor;
|
||||
}
|
||||
RefreshToolButtonVisuals();
|
||||
}
|
||||
|
||||
private void RefreshToolButtonVisuals()
|
||||
{
|
||||
var isNightMode = _isNightModeApplied ?? ResolveIsNightMode();
|
||||
@@ -350,7 +395,27 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
|
||||
private void OnPenButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
SetToolMode(WhiteboardToolMode.Pen);
|
||||
if (_toolMode == WhiteboardToolMode.Pen && ColorPickerPopup is not null)
|
||||
{
|
||||
if (ColorPickerPopup.IsOpen)
|
||||
{
|
||||
ColorPickerPopup.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorPickerPopup.Open();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetToolMode(WhiteboardToolMode.Pen);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnColorPickerColorChanged(object? sender, ColorChangedEventArgs e)
|
||||
{
|
||||
var color = e.NewColor;
|
||||
SetInkColor(new SKColor(color.R, color.G, color.B, color.A));
|
||||
}
|
||||
|
||||
private void OnEraserButtonClick(object? sender, RoutedEventArgs e)
|
||||
@@ -509,14 +574,13 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
_noteDirty = false;
|
||||
_noteSaveTimer.Stop();
|
||||
var noteSnapshot = BuildNoteSnapshot();
|
||||
var componentId = _componentId;
|
||||
var placementId = _placementId;
|
||||
var retentionDays = _noteRetentionDays;
|
||||
_ = Task.Run(() => _notePersistenceService.SaveNote(
|
||||
componentId,
|
||||
placementId,
|
||||
noteSnapshot,
|
||||
retentionDays));
|
||||
try
|
||||
{
|
||||
_notePersistenceService.SaveNote(_componentId, _placementId, noteSnapshot, _noteRetentionDays);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private async void SchedulePersistedNoteLoad()
|
||||
@@ -553,7 +617,6 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
|
||||
{
|
||||
ClearAllStrokes();
|
||||
ApplyNoteSnapshot(noteSnapshot);
|
||||
RecolorAllStrokes(_currentInkColor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -3276,4 +3276,19 @@ public partial class MainWindow
|
||||
_isComponentLibraryComponentGestureActive = false;
|
||||
ApplyComponentLibraryComponentOffset();
|
||||
}
|
||||
|
||||
internal void SaveAllWhiteboardNotes()
|
||||
{
|
||||
foreach (var pageGrid in _desktopPageComponentGrids.Values)
|
||||
{
|
||||
foreach (var host in pageGrid.Children.OfType<Border>())
|
||||
{
|
||||
var contentHost = TryGetContentHost(host);
|
||||
if (contentHost?.Child is WhiteboardWidget whiteboard)
|
||||
{
|
||||
whiteboard.ForceSaveNote();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +500,7 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
var wasVisible = IsVisible;
|
||||
var windowState = WindowState.ToString();
|
||||
|
||||
SaveAllWhiteboardNotes();
|
||||
PersistSettings();
|
||||
_componentEditorWindowService.Close();
|
||||
if (_detachedComponentLibraryWindow is not null)
|
||||
|
||||
Reference in New Issue
Block a user