Initial release of Orbit 1.1.2

This commit is contained in:
Dario P. committed 2026-10-15 22:46:21 +02:00
commit 93ab510b9f
20 files changed
+1121

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ShortcutsPanel;
internal static class FeatureChecks
{
static void Check(bool condition, string name) { if (!condition) throw new Exception(name); Console.WriteLine("PASS " + name); }
[STAThread]
static void Main()
{
var directory = Path.Combine(Path.GetTempPath(), "OrbitFeatures-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var store = new SettingsStore(@"Software\OrbitTests-" + Guid.NewGuid().ToString("N"), Path.Combine(directory, "absent.json"), directory);
var app = new App(); app.InitializeComponent();
Check(MainWindow.LoadAppIcon().PixelWidth > 0, "About icon loads from embedded resource");
var window = new MainWindow(store);
object? Call(string method, params object?[] args) => typeof(MainWindow).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Instance)!.Invoke(window, args);
try
{
var original = LibraryOperations.Validate([new("A", "https://a.example", "One", "#A697FF"), new("B", "https://b.example", "Two", "#60D6B2"), new("C", "https://c.example", "One", "#60D6B2")]);
Check(original.Select(x => x.Id).Distinct().Count() == 3, "Legacy links receive unique identities");
var moved = LibraryOperations.MoveBefore(original, original[2], original[0]);
Check(moved.Select(x => x.Name).SequenceEqual(new[] { "C", "A", "B" }), "Card reorder preserves all entries");
Check(LibraryOperations.MoveAfter(original, original[0], original[2]).Select(x => x.Name).SequenceEqual(new[] { "B", "C", "A" }), "Dragging after last card reaches final position");
var order = LibraryOperations.MoveBefore(new[] { "One", "Two", "Empty" }, "Two", "One");
Check((bool)Call("Save", moved, order)!, "Save reordered library");
Check(store.LoadCollections().SequenceEqual(order), "Collection order and empty collection persist");
Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)![0].Name == "C", "Card order persists");
Check((bool)Call("DeleteShortcut", original[0])!, "Delete shortcut");
Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)!.Count == 2, "Deletion saved");
Call("UndoDelete");
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 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 encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmap)); using (var file = File.Create(pngPath)) encoder.Save(file);
var icon = CustomIcons.FromFile(pngPath); moved[0] = moved[0] with { Icon = icon };
var export = new LibraryExport(1, moved, order);
var roundTrip = LibraryOperations.ParseExport(JsonSerializer.Serialize(export));
Check(roundTrip.Links[0].Icon == icon && CustomIcons.Decode(roundTrip.Links[0].Icon!).PixelWidth > 0, "Custom icon survives export/import");
Check(roundTrip.Collections.SequenceEqual(order) && roundTrip.Links.Select(x => x.Id).SequenceEqual(moved.Select(x => x.Id)), "Export retains identities and order");
Check(LibraryOperations.Merge(moved, roundTrip.Links).Count == 3, "Merge skips duplicate shortcuts");
Check(LibraryOperations.ParseExport(JsonSerializer.Serialize(original)).Links.Count == 3, "Legacy array import supported");
foreach (var invalid in new[] { "{}", "{\"Version\":99,\"Links\":[]}", "[{\"Name\":\"Unsafe\",\"Url\":\"file:///C:/test\",\"Group\":\"One\",\"Color\":\"#FFFFFF\"}]" })
{
bool rejected = false; try { LibraryOperations.ParseExport(invalid); } catch { rejected = true; }
Check(rejected, "Invalid import rejected");
}
Check((bool)Call("Save", roundTrip.Links, roundTrip.Collections)!, "Custom-icon library saves");
Check(JsonSerializer.Deserialize<List<Shortcut>>(store.Load()!)![0].Icon == icon, "Custom icon persists in JSON");
}
finally
{
window.Close(); app.Shutdown();
foreach (var file in Directory.GetFiles(directory)) File.Delete(file);
Directory.Delete(directory);
}
}
}
+4
View File
@@ -0,0 +1,4 @@
<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>
<ItemGroup><Compile Include="FeatureChecks.cs"/><ProjectReference Include="../ShortcutsPanel.csproj"/></ItemGroup>
</Project>
+41
View File
@@ -0,0 +1,41 @@
using Microsoft.Win32;
using ShortcutsPanel;
var key = @"Software\OrbitStorageChecks\" + Guid.NewGuid().ToString("N");
var folder = Path.Combine(Path.GetTempPath(), "OrbitStorageChecks-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(folder);
var legacy = Path.Combine(folder, "shortcuts.json");
var dataFolder = Path.Combine(folder, "data");
void Check(bool condition, string label) { if (!condition) throw new Exception(label); Console.WriteLine("PASS " + label); }
SettingsStore Store() => new(key, legacy, dataFolder);
try
{
using (var old = Registry.CurrentUser.CreateSubKey(key)) { old.SetValue("Library", "[{\"Name\":\"Original\"}]"); old.SetValue("DarkTheme", 1); }
var store = Store();
Check(store.LoadDarkTheme(), "Registry theme migrated");
Check(store.Load()!.Contains("Original"), "Registry links migrated");
Check(File.Exists(store.SettingsPath), "Migration creates JSON in app data directory");
using (var old = Registry.CurrentUser.CreateSubKey(key)) old.SetValue("Library", "[]");
Check(Store().Load()!.Contains("Original"), "JSON takes precedence after migration");
store.Save("[{\"Name\":\"Edited\"}]");
Check(Store().Load()!.Contains("Edited"), "Edited links persist");
Check(Store().LoadDarkTheme(), "Link save preserves theme");
store.SaveDarkTheme(false);
Check(!Store().LoadDarkTheme() && Store().Load()!.Contains("Edited"), "Theme save preserves links");
Check(File.Exists(store.SettingsPath + ".bak"), "Previous JSON backed up");
store.Save("[]"); Check(Store().Load() == "[]", "Empty library persists");
File.WriteAllText(store.SettingsPath, "broken");
try { store.Save("[]"); throw new Exception("Corrupt settings overwritten"); } catch (System.Text.Json.JsonException) { }
Check(File.ReadAllText(store.SettingsPath) == "broken", "Corrupt settings preserved");
File.Delete(store.SettingsPath);
Registry.CurrentUser.DeleteSubKeyTree(key, false);
File.WriteAllText(legacy, "[]"); File.SetAttributes(legacy, FileAttributes.ReadOnly);
Check(Store().Load() == "[]", "Read-only sidecar migration");
Check(File.ReadAllText(legacy) == "[]", "Legacy file untouched");
}
finally
{
Registry.CurrentUser.DeleteSubKeyTree(key, false);
if (File.Exists(legacy)) { File.SetAttributes(legacy, FileAttributes.Normal); File.Delete(legacy); }
if (Directory.Exists(dataFolder)) { foreach (var file in Directory.GetFiles(dataFolder)) File.Delete(file); Directory.Delete(dataFolder); }
Directory.Delete(folder);
}
+4
View File
@@ -0,0 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0-windows</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup>
<ItemGroup><Compile Remove="ThemeChecks.cs;FeatureChecks.cs"/><Compile Include="../SettingsStore.cs" Link="SettingsStore.cs" /></ItemGroup>
</Project>
+36
View File
@@ -0,0 +1,36 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using ShortcutsPanel;
internal static class ThemeChecks
{
[STAThread]
static void Main()
{
var discovered = WebsiteIcons.Discover("<base href='/assets/'><link href='logo.png?a=1&amp;b=2' rel='apple-touch-icon'><link rel='shortcut icon' href=../favicon.ico><link rel='icon' href='icon.svg'><link rel='icon' href='javascript:alert(1)'>", new Uri("https://example.com/page")).ToArray();
if (discovered.Length != 2 || discovered[0].AbsoluteUri != "https://example.com/assets/logo.png?a=1&b=2" || discovered[1].AbsoluteUri != "https://example.com/favicon.ico") throw new Exception("Icon discovery failed");
Console.WriteLine("PASS icon discovery resolves relative URLs, entities, and filters unsupported icons");
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"));
var app = new App(); app.InitializeComponent();
var window = new MainWindow();
var content = (FrameworkElement)window.Content;
var output = Path.Combine(Path.GetTempPath(), "OrbitThemePreview"); Directory.CreateDirectory(output);
foreach (var dark in new[] { false, true, false })
{
Theme.Apply(dark);
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");
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)); }
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);
Console.WriteLine("PASS rendered " + (dark ? "dark" : "light") + " theme");
}
Console.WriteLine(output);
window.Close(); app.Shutdown();
}
}
+4
View File
@@ -0,0 +1,4 @@
<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>
<ItemGroup><Compile Include="ThemeChecks.cs"/><ProjectReference Include="../ShortcutsPanel.csproj"/></ItemGroup>
</Project>