diff --git a/App.xaml b/App.xaml index bb43a07..26f74ea 100644 --- a/App.xaml +++ b/App.xaml @@ -1,4 +1,4 @@ - + diff --git a/App.xaml.cs b/App.xaml.cs index e3f4ffc..c154d1f 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,5 +1,5 @@ using System.Windows; -namespace ShortcutsPanel; +namespace Orbit; public partial class App : Application { protected override void OnStartup(StartupEventArgs e) diff --git a/CustomIcons.cs b/CustomIcons.cs index dc075bf..0a1a2a6 100644 --- a/CustomIcons.cs +++ b/CustomIcons.cs @@ -1,7 +1,7 @@ using System.IO; using System.Windows.Media.Imaging; -namespace ShortcutsPanel; +namespace Orbit; public static class CustomIcons { diff --git a/LibraryOperations.cs b/LibraryOperations.cs index e0e3b90..7e7d8e0 100644 --- a/LibraryOperations.cs +++ b/LibraryOperations.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text.Json; using System.Text.RegularExpressions; -namespace ShortcutsPanel; +namespace Orbit; public sealed record LibraryExport(int Version, List Links, List Collections); diff --git a/MainWindow.Features.cs b/MainWindow.Features.cs index 6a7114f..1c3b4b2 100644 --- a/MainWindow.Features.cs +++ b/MainWindow.Features.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Reflection; using System.Text.Json; @@ -7,7 +7,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -namespace ShortcutsPanel; +namespace Orbit; public partial class MainWindow { @@ -72,7 +72,7 @@ public partial class MainWindow if (choice == MessageBoxResult.Cancel) return; 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; - 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"); } }); 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 next = entries.ToList(); next.RemoveAt(index); if (!Save(next)) return false; - deleted.Push((item, index)); if (undoButton != null) undoButton.IsEnabled = true; - status.Text = $"Deleted {item.Name} · Undo is available"; return true; + deleted.Push((item, index)); UpdateUndoNotice(); 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() { if (!deleted.TryPeek(out var restore)) return; 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() { diff --git a/MainWindow.cs b/MainWindow.cs index fa0282e..f38688d 100644 --- a/MainWindow.cs +++ b/MainWindow.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Text.Json; using System.Windows; @@ -7,7 +7,7 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; 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 partial class MainWindow : Window { @@ -17,7 +17,9 @@ public partial class MainWindow : Window readonly TextBlock heading = new() { FontSize = 30, FontWeight = FontWeights.SemiBold }; static readonly Dictionary> logos = new(); 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 entries = []; string selectedGroup = "All"; List collectionOrder = []; @@ -31,7 +33,7 @@ public partial class MainWindow : Window try { Theme.Apply(settings.LoadDarkTheme()); } catch { Theme.Apply(false); } 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; var root = new Grid(); Content = root; 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 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) }; - 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) }); DockPanel.SetDock(brand, Dock.Top); sidebar.Children.Add(brand); 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 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); - 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) }); search.Width = double.NaN; search.HorizontalAlignment = HorizontalAlignment.Stretch; search.ToolTip = "Search websites and collections"; System.Windows.Automation.AutomationProperties.SetName(search, "Search websites"); 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 footer = new DockPanel { Margin = new Thickness(0, 16, 8, 0) }; DockPanel.SetDock(footer, Dock.Bottom); main.Children.Add(footer); - undoButton = Button("Undo deletion", UndoDelete); undoButton.IsEnabled = false; - DockPanel.SetDock(undoButton, Dock.Right); footer.Children.Add(undoButton); - status.Foreground = Brush("#868D80"); footer.Children.Add(status); + var noticeRow = new DockPanel(); + undoButton = Button("Undo", UndoDelete); undoButton.Margin = new Thickness(16,0,0,0); + DockPanel.SetDock(undoButton, Dock.Right); noticeRow.Children.Add(undoButton); + 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 }); search.TextChanged += (_, _) => RenderCards(); Load(); Render(); @@ -126,9 +134,9 @@ public partial class MainWindow : Window b.HorizontalContentAlignment = HorizontalAlignment.Stretch; 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); - 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); - 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); } RenderCards(); @@ -160,8 +168,8 @@ public partial class MainWindow : Window 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) }); - heading.Text = selectedGroup == "All" ? "All websites" : selectedGroup; - status.Text = $"{filtered.Count} websites · Opens in your default browser"; + heading.Text = selectedGroup == "All" ? "All Websites" : selectedGroup; + websiteCount.Text = $"{filtered.Count} {(filtered.Count == 1 ? "Website" : "Websites")}"; } static async Task ApplyLogo(Border badge, string url) { diff --git a/ShortcutsPanel.csproj b/Orbit.csproj similarity index 72% rename from ShortcutsPanel.csproj rename to Orbit.csproj index 6e6915d..ec7e14f 100644 --- a/ShortcutsPanel.csproj +++ b/Orbit.csproj @@ -6,8 +6,11 @@ enable enable app.ico - ShortcutsPanel - 1.1.2 + Orbit + 1.1.3 + Orbit + Orbit + Orbit website launcher diff --git a/README.md b/README.md index 9995c8c..10d7a0f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ A portable C# / WPF website launcher for Windows. ## Run 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. ## Settings @@ -44,7 +44,7 @@ the theme remains local. Undo history lasts for the current session. Requires Windows and the .NET 8 SDK. ```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 diff --git a/README.txt b/README.txt index 964d286..79cae90 100644 --- a/README.txt +++ b/README.txt @@ -1,7 +1,7 @@ -ORBIT 1.1.2 - WEBSITE SHORTCUTS +ORBIT 1.1.3 - WEBSITE SHORTCUTS 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. 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. - Open collection is available in Manage collections and the right-click menu. 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. 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. 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: 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.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. diff --git a/SettingsStore.cs b/SettingsStore.cs index 5fbe571..195bd10 100644 --- a/SettingsStore.cs +++ b/SettingsStore.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text.Json; using Microsoft.Win32; -namespace ShortcutsPanel; +namespace Orbit; public sealed class SettingsStore { diff --git a/Theme.cs b/Theme.cs index 92c3bee..a1d1f48 100644 --- a/Theme.cs +++ b/Theme.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; using System.Windows.Media; @@ -8,7 +8,7 @@ using System.ComponentModel; -namespace ShortcutsPanel; +namespace Orbit; diff --git a/WebsiteIcons.cs b/WebsiteIcons.cs index 5922df1..543f0f3 100644 --- a/WebsiteIcons.cs +++ b/WebsiteIcons.cs @@ -5,7 +5,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Windows.Media.Imaging; -namespace ShortcutsPanel; +namespace Orbit; public static class WebsiteIcons { diff --git a/docs/screenshots/orbit-dark.png b/docs/screenshots/orbit-dark.png index afbc0f1..0152308 100644 Binary files a/docs/screenshots/orbit-dark.png and b/docs/screenshots/orbit-dark.png differ diff --git a/docs/screenshots/orbit-light.png b/docs/screenshots/orbit-light.png index 44599c7..60356d8 100644 Binary files a/docs/screenshots/orbit-light.png and b/docs/screenshots/orbit-light.png differ diff --git a/tests/FeatureChecks.cs b/tests/FeatureChecks.cs index e550c96..b2c10f3 100644 --- a/tests/FeatureChecks.cs +++ b/tests/FeatureChecks.cs @@ -3,7 +3,7 @@ using System.Reflection; using System.Text.Json; using System.Windows.Media; using System.Windows.Media.Imaging; -using ShortcutsPanel; +using Orbit; internal static class FeatureChecks { @@ -17,6 +17,8 @@ internal static class FeatureChecks var app = new App(); app.InitializeComponent(); Check(MainWindow.LoadAppIcon().PixelWidth > 0, "About icon loads from embedded resource"); 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); try { @@ -31,7 +33,9 @@ internal static class FeatureChecks Check(JsonSerializer.Deserialize>(store.Load()!)![0].Name == "C", "Card order persists"); Check((bool)Call("DeleteShortcut", original[0])!, "Delete shortcut"); Check(JsonSerializer.Deserialize>(store.Load()!)!.Count == 2, "Deletion saved"); + Check(notice.Visibility == System.Windows.Visibility.Visible, "Undo notice appears after deletion"); Call("UndoDelete"); + Check(notice.Visibility == System.Windows.Visibility.Collapsed, "Undo notice hides after restoring last deletion"); Check(JsonSerializer.Deserialize>(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); diff --git a/tests/FeatureChecks.csproj b/tests/FeatureChecks.csproj index a650ee4..3bd3795 100644 --- a/tests/FeatureChecks.csproj +++ b/tests/FeatureChecks.csproj @@ -1,4 +1,4 @@ Exenet8.0-windowstrueenableenablefalse - + diff --git a/tests/Program.cs b/tests/Program.cs index 1469d65..b529d8f 100644 --- a/tests/Program.cs +++ b/tests/Program.cs @@ -1,5 +1,5 @@ using Microsoft.Win32; -using ShortcutsPanel; +using Orbit; var key = @"Software\OrbitStorageChecks\" + Guid.NewGuid().ToString("N"); var folder = Path.Combine(Path.GetTempPath(), "OrbitStorageChecks-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(folder); diff --git a/tests/ThemeChecks.cs b/tests/ThemeChecks.cs index bba2007..e904cf6 100644 --- a/tests/ThemeChecks.cs +++ b/tests/ThemeChecks.cs @@ -3,7 +3,7 @@ using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.IO; -using ShortcutsPanel; +using Orbit; internal static class ThemeChecks { diff --git a/tests/ThemeChecks.csproj b/tests/ThemeChecks.csproj index d039ad5..aec9ba4 100644 --- a/tests/ThemeChecks.csproj +++ b/tests/ThemeChecks.csproj @@ -1,4 +1,4 @@ Exenet8.0-windowstrueenableenablefalse - +