Compare commits

4 Commits
19 changed files with 93 additions and 44 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
<Application x:Class="ShortcutsPanel.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Application x:Class="Orbit.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources> <Application.Resources>
+1 -1
View File
@@ -1,5 +1,5 @@
using System.Windows; using System.Windows;
namespace ShortcutsPanel; namespace Orbit;
public partial class App : Application public partial class App : Application
{ {
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
+1 -1
View File
@@ -1,7 +1,7 @@
using System.IO; using System.IO;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
namespace ShortcutsPanel; namespace Orbit;
public static class CustomIcons public static class CustomIcons
{ {
+1 -1
View File
@@ -2,7 +2,7 @@ using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace ShortcutsPanel; namespace Orbit;
public sealed record LibraryExport(int Version, List<Shortcut> Links, List<string> Collections); public sealed record LibraryExport(int Version, List<Shortcut> Links, List<string> Collections);
+11 -6
View File
@@ -1,4 +1,4 @@
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
@@ -7,7 +7,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
namespace ShortcutsPanel; namespace Orbit;
public partial class MainWindow public partial class MainWindow
{ {
@@ -72,7 +72,7 @@ public partial class MainWindow
if (choice == MessageBoxResult.Cancel) return; if (choice == MessageBoxResult.Cancel) return;
var next = choice == MessageBoxResult.Yes ? LibraryOperations.Merge(entries, incoming.Links) : incoming.Links; var next = choice == MessageBoxResult.Yes ? LibraryOperations.Merge(entries, incoming.Links) : incoming.Links;
var order = choice == MessageBoxResult.Yes ? collectionOrder.Concat(incoming.Collections).Distinct().ToList() : incoming.Collections; var order = choice == MessageBoxResult.Yes ? collectionOrder.Concat(incoming.Collections).Distinct().ToList() : incoming.Collections;
if (Save(next, order)) { deleted.Clear(); if (undoButton != null) undoButton.IsEnabled = false; dialog.Close(); } if (Save(next, order)) { deleted.Clear(); UpdateUndoNotice(); dialog.Close(); }
} }
catch (Exception ex) { MessageBox.Show(dialog, ex.Message, "Import failed — library unchanged"); } catch (Exception ex) { MessageBox.Show(dialog, ex.Message, "Import failed — library unchanged"); }
}); import.Margin = new Thickness(0,12,0,18); panel.Children.Add(import); }); import.Margin = new Thickness(0,12,0,18); panel.Children.Add(import);
@@ -83,14 +83,19 @@ public partial class MainWindow
var index = entries.FindIndex(x => x.Id == item.Id); if (index < 0) return false; var index = entries.FindIndex(x => x.Id == item.Id); if (index < 0) return false;
var next = entries.ToList(); next.RemoveAt(index); var next = entries.ToList(); next.RemoveAt(index);
if (!Save(next)) return false; if (!Save(next)) return false;
deleted.Push((item, index)); if (undoButton != null) undoButton.IsEnabled = true; deleted.Push((item, index)); UpdateUndoNotice(); return true;
status.Text = $"Deleted {item.Name} · Undo is available"; return true; }
void UpdateUndoNotice()
{
undoNotice.Visibility = deleted.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
status.Text = deleted.TryPeek(out var latest) ? $"Removed {latest.Item.Name}" : "";
if (undoButton != null) undoButton.IsEnabled = deleted.Count > 0;
} }
void UndoDelete() void UndoDelete()
{ {
if (!deleted.TryPeek(out var restore)) return; if (!deleted.TryPeek(out var restore)) return;
var next = entries.ToList(); next.Insert(Math.Min(restore.Index, next.Count), restore.Item); var next = entries.ToList(); next.Insert(Math.Min(restore.Index, next.Count), restore.Item);
if (Save(next)) { deleted.Pop(); if (undoButton != null) undoButton.IsEnabled = deleted.Count > 0; } if (Save(next)) { deleted.Pop(); UpdateUndoNotice(); }
} }
void ManageCollections() void ManageCollections()
{ {
+22 -14
View File
@@ -1,4 +1,4 @@
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Windows; using System.Windows;
@@ -7,7 +7,7 @@ using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Net.Http; using System.Net.Http;
namespace ShortcutsPanel; namespace Orbit;
public record Shortcut(string Name, string Url, string Group, string Color, string? Icon = null, string Id = ""); public record Shortcut(string Name, string Url, string Group, string Color, string? Icon = null, string Id = "");
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
@@ -17,7 +17,9 @@ public partial class MainWindow : Window
readonly TextBlock heading = new() { FontSize = 30, FontWeight = FontWeights.SemiBold }; readonly TextBlock heading = new() { FontSize = 30, FontWeight = FontWeights.SemiBold };
static readonly Dictionary<string, Task<BitmapSource?>> logos = new(); static readonly Dictionary<string, Task<BitmapSource?>> logos = new();
readonly TextBox search = new() { Width = 280, ToolTip = "Search by name, website, or collection" }; readonly TextBox search = new() { Width = 280, ToolTip = "Search by name, website, or collection" };
readonly TextBlock status = new() { Foreground = Brush("#939DB8"), FontSize = 12 }; readonly TextBlock websiteCount = new() { FontSize = 12, Foreground = Brush("#7B8377"), VerticalAlignment = VerticalAlignment.Center };
readonly TextBlock status = new() { FontSize = 12, VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis };
Border undoNotice = null!;
List<Shortcut> entries = []; List<Shortcut> entries = [];
string selectedGroup = "All"; string selectedGroup = "All";
List<string> collectionOrder = []; List<string> collectionOrder = [];
@@ -31,7 +33,7 @@ public partial class MainWindow : Window
try { Theme.Apply(settings.LoadDarkTheme()); } try { Theme.Apply(settings.LoadDarkTheme()); }
catch { Theme.Apply(false); } catch { Theme.Apply(false); }
Background = Brush("#FAF9F6"); Foreground = Brush("#252924"); FontFamily = new FontFamily("Segoe UI"); Background = Brush("#FAF9F6"); Foreground = Brush("#252924"); FontFamily = new FontFamily("Segoe UI");
Title = "Orbit • Shortcuts Panel"; Width = 1100; Height = 720; MinWidth = 800; MinHeight = 520; Title = "Orbit"; Width = 1100; Height = 720; MinWidth = 800; MinHeight = 520;
WindowStartupLocation = WindowStartupLocation.CenterScreen; WindowState = WindowState.Maximized; WindowStartupLocation = WindowStartupLocation.CenterScreen; WindowState = WindowState.Maximized;
var root = new Grid(); Content = root; var root = new Grid(); Content = root;
root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(210) }); root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(210) });
@@ -39,7 +41,7 @@ public partial class MainWindow : Window
var sidebar = new DockPanel { Margin = new Thickness(20, 32, 20, 24) }; var sidebar = new DockPanel { Margin = new Thickness(20, 32, 20, 24) };
var sideSurface = new Border { Background = Brush("#F1F2ED"), BorderBrush = Brush("#E2E5DD"), BorderThickness = new Thickness(0,0,1,0), Child = sidebar }; root.Children.Add(sideSurface); var sideSurface = new Border { Background = Brush("#F1F2ED"), BorderBrush = Brush("#E2E5DD"), BorderThickness = new Thickness(0,0,1,0), Child = sidebar }; root.Children.Add(sideSurface);
var brand = new StackPanel { Margin = new Thickness(12, 0, 0, 36) }; var brand = new StackPanel { Margin = new Thickness(12, 0, 0, 36) };
brand.Children.Add(new TextBlock { Text = "◉ orbit", FontSize = 27, FontWeight = FontWeights.SemiBold, Foreground = Brush("#294B3C") }); brand.Children.Add(new TextBlock { Text = "◉ Orbit", FontSize = 27, FontWeight = FontWeights.SemiBold, Foreground = Brush("#294B3C") });
brand.Children.Add(new TextBlock { Text = "A little more organized.", FontSize = 11, Foreground = Brush("#80877D"), Margin = new Thickness(1, 6, 0, 0) }); brand.Children.Add(new TextBlock { Text = "A little more organized.", FontSize = 11, Foreground = Brush("#80877D"), Margin = new Thickness(1, 6, 0, 0) });
DockPanel.SetDock(brand, Dock.Top); sidebar.Children.Add(brand); DockPanel.SetDock(brand, Dock.Top); sidebar.Children.Add(brand);
var bottom = new StackPanel(); var bottom = new StackPanel();
@@ -68,17 +70,23 @@ public partial class MainWindow : Window
var top = new StackPanel(); DockPanel.SetDock(top, Dock.Top); main.Children.Add(top); var top = new StackPanel(); DockPanel.SetDock(top, Dock.Top); main.Children.Add(top);
var header = new DockPanel(); top.Children.Add(header); var header = new DockPanel(); top.Children.Add(header);
var add = Button("+ Add website", () => Edit(null), "#294B3D"); add.Foreground = Brushes.White; add.VerticalAlignment = VerticalAlignment.Center; DockPanel.SetDock(add, Dock.Right); header.Children.Add(add); var add = Button("+ Add website", () => Edit(null), "#294B3D"); add.Foreground = Brushes.White; add.VerticalAlignment = VerticalAlignment.Center; DockPanel.SetDock(add, Dock.Right); header.Children.Add(add);
header.Children.Add(heading); var titleRow = new DockPanel();
var countBadge = new Border { Background = Brush("#DFE6DA"), CornerRadius = new CornerRadius(10), Padding = new Thickness(10,5,10,5), Margin = new Thickness(14,0,18,0), VerticalAlignment = VerticalAlignment.Center, Child = websiteCount };
DockPanel.SetDock(heading, Dock.Left); titleRow.Children.Add(heading); titleRow.Children.Add(countBadge);
countBadge.HorizontalAlignment = HorizontalAlignment.Left;
header.Children.Add(titleRow);
top.Children.Add(new TextBlock { Text = "Your favorite places, thoughtfully collected.", FontSize = 13, Foreground = Brush("#7B8377"), Margin = new Thickness(0, 9, 0, 26) }); top.Children.Add(new TextBlock { Text = "Your favorite places, thoughtfully collected.", FontSize = 13, Foreground = Brush("#7B8377"), Margin = new Thickness(0, 9, 0, 26) });
search.Width = double.NaN; search.HorizontalAlignment = HorizontalAlignment.Stretch; search.Width = double.NaN; search.HorizontalAlignment = HorizontalAlignment.Stretch;
search.ToolTip = "Search websites and collections"; search.ToolTip = "Search websites and collections";
System.Windows.Automation.AutomationProperties.SetName(search, "Search websites"); System.Windows.Automation.AutomationProperties.SetName(search, "Search websites");
var searchRow = new DockPanel { Margin = new Thickness(0, 0, 8, 28) }; var searchRow = new DockPanel { Margin = new Thickness(0, 0, 8, 28) };
var searchLabel = new TextBlock { Text = "Search", VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0,0,12,0), Foreground = Brush("#7B8377"), FontSize = 12 }; searchRow.Children.Add(searchLabel); searchRow.Children.Add(search); top.Children.Add(searchRow); var searchLabel = new TextBlock { Text = "Search", VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0,0,12,0), Foreground = Brush("#7B8377"), FontSize = 12 }; searchRow.Children.Add(searchLabel); searchRow.Children.Add(search); top.Children.Add(searchRow);
var footer = new DockPanel { Margin = new Thickness(0, 16, 8, 0) }; DockPanel.SetDock(footer, Dock.Bottom); main.Children.Add(footer); var noticeRow = new DockPanel();
undoButton = Button("Undo deletion", UndoDelete); undoButton.IsEnabled = false; undoButton = Button("Undo", UndoDelete); undoButton.Margin = new Thickness(16,0,0,0);
DockPanel.SetDock(undoButton, Dock.Right); footer.Children.Add(undoButton); DockPanel.SetDock(undoButton, Dock.Right); noticeRow.Children.Add(undoButton);
status.Foreground = Brush("#868D80"); footer.Children.Add(status); status.Foreground = Brush("#374632"); noticeRow.Children.Add(status);
undoNotice = new Border { Background = Brush("#DFE6DA"), BorderBrush = Brush("#E2E5DD"), BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(10), Padding = new Thickness(14,8,8,8), Margin = new Thickness(0,12,8,0), MaxWidth = 460, HorizontalAlignment = HorizontalAlignment.Left, Visibility = Visibility.Collapsed, Child = noticeRow };
DockPanel.SetDock(undoNotice, Dock.Bottom); main.Children.Add(undoNotice);
main.Children.Add(new ScrollViewer { Content = cards, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled }); main.Children.Add(new ScrollViewer { Content = cards, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled });
search.TextChanged += (_, _) => RenderCards(); search.TextChanged += (_, _) => RenderCards();
Load(); Render(); Load(); Render();
@@ -126,9 +134,9 @@ public partial class MainWindow : Window
b.HorizontalContentAlignment = HorizontalAlignment.Stretch; b.HorizontalContentAlignment = HorizontalAlignment.Stretch;
var row = new DockPanel { Width = 138 }; var row = new DockPanel { Width = 138 };
var number = new TextBlock { Text = count.ToString(), Foreground = Brush("#7B8377"), FontSize = 11, VerticalAlignment = VerticalAlignment.Center }; DockPanel.SetDock(number, Dock.Right); row.Children.Add(number); var number = new TextBlock { Text = count.ToString(), Foreground = Brush("#7B8377"), FontSize = 11, VerticalAlignment = VerticalAlignment.Center }; DockPanel.SetDock(number, Dock.Right); row.Children.Add(number);
row.Children.Add(new TextBlock { Text = name == "All" ? "▦ All websites" : "• " + name, FontSize = 12, TextTrimming = TextTrimming.CharacterEllipsis, Foreground = Brush("#374632") }); row.Children.Add(new TextBlock { Text = name == "All" ? "▦ All Websites" : "• " + name, FontSize = 12, TextTrimming = TextTrimming.CharacterEllipsis, Foreground = Brush("#374632") });
b.Content = row; b.Margin = new Thickness(0,0,0,6); groups.Children.Add(b); b.Content = row; b.Margin = new Thickness(0,0,0,6); groups.Children.Add(b);
System.Windows.Automation.AutomationProperties.SetName(b, name == "All" ? "All websites" : name); System.Windows.Automation.AutomationProperties.SetName(b, name == "All" ? "All Websites" : name);
if (name != "All") ConfigureCollection(b, name); if (name != "All") ConfigureCollection(b, name);
} }
RenderCards(); RenderCards();
@@ -160,8 +168,8 @@ public partial class MainWindow : Window
cards.Children.Add(card); ConfigureCard(card, item); cards.Children.Add(card); ConfigureCard(card, item);
} }
if (filtered.Count == 0) cards.Children.Add(new TextBlock { Text = entries.Count == 0 ? "A fresh start. Add your first website above." : "No websites match. Try another search or collection.", FontSize = 18, Margin = new Thickness(0, 35, 0, 35) }); if (filtered.Count == 0) cards.Children.Add(new TextBlock { Text = entries.Count == 0 ? "A fresh start. Add your first website above." : "No websites match. Try another search or collection.", FontSize = 18, Margin = new Thickness(0, 35, 0, 35) });
heading.Text = selectedGroup == "All" ? "All websites" : selectedGroup; heading.Text = selectedGroup == "All" ? "All Websites" : selectedGroup;
status.Text = $"{filtered.Count} websites · Opens in your default browser"; websiteCount.Text = $"{filtered.Count} {(filtered.Count == 1 ? "Website" : "Websites")}";
} }
static async Task ApplyLogo(Border badge, string url) static async Task ApplyLogo(Border badge, string url)
{ {
+5 -2
View File
@@ -6,8 +6,11 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ApplicationIcon>app.ico</ApplicationIcon> <ApplicationIcon>app.ico</ApplicationIcon>
<AssemblyName>ShortcutsPanel</AssemblyName> <AssemblyName>Orbit</AssemblyName>
<Version>1.1.2</Version> <Version>1.1.3</Version>
<Product>Orbit</Product>
<Title>Orbit</Title>
<Description>Orbit website launcher</Description>
</PropertyGroup> </PropertyGroup>
<ItemGroup><Compile Remove="tests/**/*.cs" /></ItemGroup> <ItemGroup><Compile Remove="tests/**/*.cs" /></ItemGroup>
<ItemGroup><EmbeddedResource Include="app.ico" LogicalName="OrbitIcon" /></ItemGroup> <ItemGroup><EmbeddedResource Include="app.ico" LogicalName="OrbitIcon" /></ItemGroup>
+15 -3
View File
@@ -1,6 +1,18 @@
# Orbit # Orbit
A portable C# / WPF website launcher for Windows, created by **Dario P.** A portable C# / WPF website launcher for Windows.
[**Download for Windows**](https://github.com/donishki/orbit-shortcuts/releases/latest) · [Features](#features) · [Build from source](#build)
## Screenshots
### Charcoal
![Orbit in charcoal mode, showing website cards and the collection sidebar](docs/screenshots/orbit-dark.png)
### Light
![Orbit in light mode, showing website cards and the collection sidebar](docs/screenshots/orbit-light.png)
## Features ## Features
@@ -14,7 +26,7 @@ A portable C# / WPF website launcher for Windows, created by **Dario P.**
## Run ## Run
Download the portable Windows x64 package from this repository's Releases page, Download the portable Windows x64 package from this repository's Releases page,
extract it, and run `ShortcutsPanel.exe`. No installer or separate .NET runtime extract it, and run `Orbit.exe`. No installer or separate .NET runtime
is required. Windows 10 or Windows 11, 64-bit. is required. Windows 10 or Windows 11, 64-bit.
## Settings ## Settings
@@ -32,7 +44,7 @@ the theme remains local. Undo history lasts for the current session.
Requires Windows and the .NET 8 SDK. Requires Windows and the .NET 8 SDK.
```powershell ```powershell
dotnet publish ShortcutsPanel.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o release dotnet publish Orbit.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o release
``` ```
## Checks ## Checks
+6 -4
View File
@@ -1,7 +1,7 @@
ORBIT 1.1.2 - WEBSITE SHORTCUTS ORBIT 1.1.3 - WEBSITE SHORTCUTS
Created by Dario P. Created by Dario P.
Run ShortcutsPanel.exe on 64-bit Windows 10 or Windows 11. Run Orbit.exe on 64-bit Windows 10 or Windows 11.
No installer or separate .NET installation is required. No installer or separate .NET installation is required.
NEW IN 1.1 NEW IN 1.1
@@ -20,7 +20,7 @@ NEW IN 1.1
Importing a library clears undo history. Undo history is not saved on exit. Importing a library clears undo history. Undo history is not saved on exit.
- Open collection is available in Manage collections and the right-click menu. - Open collection is available in Manage collections and the right-click menu.
Opening multiple sites asks for confirmation and opens each distinct URL once. Opening multiple sites asks for confirmation and opens each distinct URL once.
- About Orbit displays version 1.1.2 and author Dario P. - About Orbit displays version 1.1.3 and author Dario P.
Add, edit, delete, search, and group website cards. Dark mode is optional. Add, edit, delete, search, and group website cards. Dark mode is optional.
Website icons are discovered from page metadata, then /favicon.ico. Website icons are discovered from page metadata, then /favicon.ico.
@@ -44,7 +44,7 @@ The bundled .NET runtime extracts supporting files to temporary storage.
This executable is not digitally signed. This executable is not digitally signed.
Build: Build:
dotnet publish ShortcutsPanel.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o release dotnet publish Orbit.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -p:DebugSymbols=false -o release
Checks: Checks:
dotnet run --project tests/StorageChecks.csproj dotnet run --project tests/StorageChecks.csproj
@@ -54,3 +54,5 @@ dotnet run --project tests/ThemeChecks.csproj
1.1.1: aligned sidebar rows, whole-card dragging, and the Orbit icon in About. 1.1.1: aligned sidebar rows, whole-card dragging, and the Orbit icon in About.
1.1.2: starts maximized; green outer borders for drag/focus; Dark mode is at the sidebar bottom; About credits Dario P. and 2026. 1.1.2: starts maximized; green outer borders for drag/focus; Dark mode is at the sidebar bottom; About credits Dario P. and 2026.
1.1.3: Orbit naming throughout; website count beside the heading; contextual Undo notice; cleaner library layout.
+1 -1
View File
@@ -2,7 +2,7 @@ using System.IO;
using System.Text.Json; using System.Text.Json;
using Microsoft.Win32; using Microsoft.Win32;
namespace ShortcutsPanel; namespace Orbit;
public sealed class SettingsStore public sealed class SettingsStore
{ {
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@@ -8,7 +8,7 @@ using System.ComponentModel;
namespace ShortcutsPanel; namespace Orbit;
+1 -1
View File
@@ -5,7 +5,7 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
namespace ShortcutsPanel; namespace Orbit;
public static class WebsiteIcons public static class WebsiteIcons
{ {
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+5 -1
View File
@@ -3,7 +3,7 @@ using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using ShortcutsPanel; using Orbit;
internal static class FeatureChecks internal static class FeatureChecks
{ {
@@ -17,6 +17,8 @@ internal static class FeatureChecks
var app = new App(); app.InitializeComponent(); var app = new App(); app.InitializeComponent();
Check(MainWindow.LoadAppIcon().PixelWidth > 0, "About icon loads from embedded resource"); Check(MainWindow.LoadAppIcon().PixelWidth > 0, "About icon loads from embedded resource");
var window = new MainWindow(store); var window = new MainWindow(store);
var notice = (System.Windows.Controls.Border)typeof(MainWindow).GetField("undoNotice", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(window)!;
Check(window.Title == "Orbit" && notice.Visibility == System.Windows.Visibility.Collapsed, "Orbit title and hidden initial Undo notice");
object? Call(string method, params object?[] args) => typeof(MainWindow).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Instance)!.Invoke(window, args); object? Call(string method, params object?[] args) => typeof(MainWindow).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Instance)!.Invoke(window, args);
try try
{ {
@@ -31,7 +33,9 @@ internal static class FeatureChecks
Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)![0].Name == "C", "Card order persists"); Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)![0].Name == "C", "Card order persists");
Check((bool)Call("DeleteShortcut", original[0])!, "Delete shortcut"); Check((bool)Call("DeleteShortcut", original[0])!, "Delete shortcut");
Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)!.Count == 2, "Deletion saved"); Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)!.Count == 2, "Deletion saved");
Check(notice.Visibility == System.Windows.Visibility.Visible, "Undo notice appears after deletion");
Call("UndoDelete"); Call("UndoDelete");
Check(notice.Visibility == System.Windows.Visibility.Collapsed, "Undo notice hides after restoring last deletion");
Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)!.Select(x => x.Name).SequenceEqual(new[] { "C", "A", "B" }), "Undo restores original position"); Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)!.Select(x => x.Name).SequenceEqual(new[] { "C", "A", "B" }), "Undo restores original position");
var pngPath = Path.Combine(directory, "icon.png"); var pngPath = Path.Combine(directory, "icon.png");
var bitmap = BitmapSource.Create(2, 2, 96, 96, PixelFormats.Bgra32, null, new byte[] { 0,0,255,255, 0,255,0,255, 255,0,0,255, 255,255,255,255 }, 8); var bitmap = BitmapSource.Create(2, 2, 96, 96, PixelFormats.Bgra32, null, new byte[] { 0,0,255,255, 0,255,0,255, 255,0,0,255, 255,255,255,255 }, 8);
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0-windows</TargetFramework><UseWPF>true</UseWPF><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><EnableDefaultCompileItems>false</EnableDefaultCompileItems></PropertyGroup> <PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0-windows</TargetFramework><UseWPF>true</UseWPF><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><EnableDefaultCompileItems>false</EnableDefaultCompileItems></PropertyGroup>
<ItemGroup><Compile Include="FeatureChecks.cs"/><ProjectReference Include="../ShortcutsPanel.csproj"/></ItemGroup> <ItemGroup><Compile Include="FeatureChecks.cs"/><ProjectReference Include="../Orbit.csproj"/></ItemGroup>
</Project> </Project>
+1 -1
View File
@@ -1,5 +1,5 @@
using Microsoft.Win32; using Microsoft.Win32;
using ShortcutsPanel; using Orbit;
var key = @"Software\OrbitStorageChecks\" + Guid.NewGuid().ToString("N"); var key = @"Software\OrbitStorageChecks\" + Guid.NewGuid().ToString("N");
var folder = Path.Combine(Path.GetTempPath(), "OrbitStorageChecks-" + Guid.NewGuid().ToString("N")); var folder = Path.Combine(Path.GetTempPath(), "OrbitStorageChecks-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(folder); Directory.CreateDirectory(folder);
+18 -3
View File
@@ -3,7 +3,7 @@ using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.IO; using System.IO;
using ShortcutsPanel; using Orbit;
internal static class ThemeChecks internal static class ThemeChecks
{ {
@@ -16,16 +16,26 @@ internal static class ThemeChecks
foreach (var site in new[] { "https://github.com", "https://www.google.com" }) foreach (var site in new[] { "https://github.com", "https://www.google.com" })
Console.WriteLine("Live icon " + site + ": " + (WebsiteIcons.Fetch(site).GetAwaiter().GetResult() != null ? "loaded" : "fallback")); Console.WriteLine("Live icon " + site + ": " + (WebsiteIcons.Fetch(site).GetAwaiter().GetResult() != null ? "loaded" : "fallback"));
var app = new App(); app.InitializeComponent(); var app = new App(); app.InitializeComponent();
var window = new MainWindow(); SynchronizationContext.SetSynchronizationContext(new System.Windows.Threading.DispatcherSynchronizationContext());
var isolated = Path.Combine(Path.GetTempPath(), "OrbitPreview-" + Guid.NewGuid().ToString("N"));
var window = new MainWindow(new SettingsStore(@"Software\OrbitPreview-" + Guid.NewGuid().ToString("N"), Path.Combine(isolated, "absent.json"), isolated));
var content = (FrameworkElement)window.Content; var content = (FrameworkElement)window.Content;
window.WindowState = WindowState.Normal;
content.Width = 1100; content.Height = 680;
// Let asynchronous website icons finish without reading or altering the user's library.
var wait = new System.Windows.Threading.DispatcherFrame();
var timer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(8) };
timer.Tick += (_, _) => { timer.Stop(); wait.Continue = false; };
timer.Start(); System.Windows.Threading.Dispatcher.PushFrame(wait);
var output = Path.Combine(Path.GetTempPath(), "OrbitThemePreview"); Directory.CreateDirectory(output); var output = Path.Combine(Path.GetTempPath(), "OrbitThemePreview"); Directory.CreateDirectory(output);
foreach (var dark in new[] { false, true, false }) foreach (var dark in new[] { false, true, false })
{ {
Theme.Apply(dark); Theme.Apply(dark);
SetSwitch(content, dark);
content.Measure(new Size(1100, 680)); content.Arrange(new Rect(0, 0, 1100, 680)); content.UpdateLayout(); content.Measure(new Size(1100, 680)); content.Arrange(new Rect(0, 0, 1100, 680)); content.UpdateLayout();
if (((SolidColorBrush)window.Background).Color != (Color)ColorConverter.ConvertFromString(dark ? "#202020" : "#FAF9F6")) throw new Exception("Window theme did not update"); if (((SolidColorBrush)window.Background).Color != (Color)ColorConverter.ConvertFromString(dark ? "#202020" : "#FAF9F6")) throw new Exception("Window theme did not update");
var bitmap = new RenderTargetBitmap(1100, 680, 96, 96, PixelFormats.Pbgra32); var bitmap = new RenderTargetBitmap(1100, 680, 96, 96, PixelFormats.Pbgra32);
var frame = new DrawingVisual(); using (var dc = frame.RenderOpen()) { dc.DrawRectangle(window.Background, null, new Rect(0,0,1100,680)); dc.DrawRectangle(new VisualBrush(content), null, new Rect(0,0,1100,680)); } var frame = new DrawingVisual(); using (var dc = frame.RenderOpen()) { dc.DrawRectangle(window.Background, null, new Rect(0,0,1100,680)); dc.DrawRectangle(new VisualBrush(content) { ViewboxUnits = BrushMappingMode.Absolute, Viewbox = new Rect(0,0,1100,680) }, null, new Rect(0,0,1100,680)); }
bitmap.Render(frame); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmap)); bitmap.Render(frame); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmap));
using var file = File.Create(Path.Combine(output, dark ? "dark.png" : "light.png")); encoder.Save(file); using var file = File.Create(Path.Combine(output, dark ? "dark.png" : "light.png")); encoder.Save(file);
Console.WriteLine("PASS rendered " + (dark ? "dark" : "light") + " theme"); Console.WriteLine("PASS rendered " + (dark ? "dark" : "light") + " theme");
@@ -33,4 +43,9 @@ internal static class ThemeChecks
Console.WriteLine(output); Console.WriteLine(output);
window.Close(); app.Shutdown(); window.Close(); app.Shutdown();
} }
static void SetSwitch(DependencyObject parent, bool dark)
{
if (parent is CheckBox toggle && toggle.Content as string == "Dark mode") toggle.IsChecked = dark;
for (int index = 0; index < VisualTreeHelper.GetChildrenCount(parent); index++) SetSwitch(VisualTreeHelper.GetChild(parent, index), dark);
}
} }
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0-windows</TargetFramework><UseWPF>true</UseWPF><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><EnableDefaultCompileItems>false</EnableDefaultCompileItems></PropertyGroup> <PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0-windows</TargetFramework><UseWPF>true</UseWPF><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><EnableDefaultCompileItems>false</EnableDefaultCompileItems></PropertyGroup>
<ItemGroup><Compile Include="ThemeChecks.cs"/><ProjectReference Include="../ShortcutsPanel.csproj"/></ItemGroup> <ItemGroup><Compile Include="ThemeChecks.cs"/><ProjectReference Include="../Orbit.csproj"/></ItemGroup>
</Project> </Project>