From 86f667f595ebe7a429652b33d2922d2852e6a620 Mon Sep 17 00:00:00 2001 From: Ray Berezowski <6616212+rberezowski@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:38:57 -0400 Subject: [PATCH] Add selected item import export --- MainWindow.xaml | 2 + MainWindow.xaml.cs | 108 ++++++++++++++++++++++++++++++++++++++ README.md | 1 + Services/ConfigService.cs | 43 +++++++++++++++ TaskbarLauncher.csproj | 4 +- docs/INSTALL.md | 1 + 6 files changed, 157 insertions(+), 2 deletions(-) diff --git a/MainWindow.xaml b/MainWindow.xaml index a8c69e7..ee4d78a 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -67,6 +67,8 @@ + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index b810677..282263e 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -1260,6 +1260,114 @@ public partial class MainWindow : Window } } + private void ExportSelectedItem_Click(object sender, RoutedEventArgs e) + { + if (selectedItem is null) + { + MessageBox.Show("Select an item or menu to export first.", "Taskbar Launcher"); + return; + } + + ApplyCurrentItem(); + string safeTitle = MakeSafeFileName(string.IsNullOrWhiteSpace(selectedItem.Title) ? "launcher-item" : selectedItem.Title); + using SaveFileDialog dialog = new() + { + Title = "Export selected launcher item", + Filter = "JSON item files|*.json|All files|*.*", + FileName = $"{safeTitle}-{DateTime.Now:yyyyMMdd-HHmmss}.json", + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + }; + + if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) + { + return; + } + + try + { + string exportPath = ConfigService.ExportItemTo(dialog.FileName, selectedItem); + StatusText.Text = $"Exported {selectedItem.Title} to {exportPath}"; + } + catch (Exception ex) + { + MessageBox.Show($"Could not export selected item.\n\n{ex.Message}", "Taskbar Launcher"); + } + } + + private void ImportItem_Click(object sender, RoutedEventArgs e) + { + if (controller is null) + { + return; + } + + using OpenFileDialog dialog = new() + { + Title = "Import launcher item", + Filter = "JSON item files|*.json|All files|*.*", + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + }; + + if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) + { + return; + } + + try + { + ApplyCurrentItem(); + LauncherItem importedItem = ConfigService.ImportItemFrom(dialog.FileName); + AddImportedItem(importedItem); + selectedPath = FindItemPath(importedItem); + RefreshView(importedItem); + StatusText.Text = $"Imported {importedItem.Title}. Click Save to keep it."; + } + catch (Exception ex) + { + MessageBox.Show($"Could not import item.\n\n{ex.Message}", "Taskbar Launcher"); + } + } + + private void AddImportedItem(LauncherItem importedItem) + { + if (controller is null) + { + return; + } + + if (selectedItem is null) + { + controller.Config.Items.Add(importedItem); + return; + } + + if (selectedItem.Type == LauncherItemType.Menu) + { + selectedItem.Children.Add(importedItem); + return; + } + + List? siblings = FindSiblings(controller.Config.Items, selectedItem); + if (siblings is null) + { + controller.Config.Items.Add(importedItem); + return; + } + + int selectedIndex = siblings.IndexOf(selectedItem); + siblings.Insert(selectedIndex + 1, importedItem); + } + + private static string MakeSafeFileName(string fileName) + { + foreach (char invalidChar in Path.GetInvalidFileNameChars()) + { + fileName = fileName.Replace(invalidChar, '-'); + } + + return fileName.Trim(); + } + private void ResetConfig_Click(object sender, RoutedEventArgs e) { MessageBoxResult result = MessageBox.Show( diff --git a/README.md b/README.md index ee1e764..567f07e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Download the compiled Windows release from: - Right-click launcher entries or Settings items to temporarily disable them - Right-click app/file entries to open their containing folder - Validate missing app, file, and folder targets from Settings +- Export and import selected items or menu sections - Right-click item duplication in Settings - Right-click items in Settings to pin them to the top of their menu - Per-item icon and display mode diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs index af23666..4ede82b 100644 --- a/Services/ConfigService.cs +++ b/Services/ConfigService.cs @@ -80,6 +80,49 @@ public static class ConfigService return destinationPath; } + public static string ExportItemTo(string destinationPath, LauncherItem item) + { + if (string.IsNullOrWhiteSpace(destinationPath)) + { + throw new ArgumentException("Choose a destination path for the exported item.", nameof(destinationPath)); + } + + string? destinationFolder = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrWhiteSpace(destinationFolder)) + { + Directory.CreateDirectory(destinationFolder); + } + + NormalizeDisplayModes(item); + string json = JsonSerializer.Serialize(item, JsonOptions); + File.WriteAllText(destinationPath, json); + return destinationPath; + } + + public static LauncherItem ImportItemFrom(string sourcePath) + { + if (!File.Exists(sourcePath)) + { + throw new FileNotFoundException("The selected item file was not found.", sourcePath); + } + + string json = File.ReadAllText(sourcePath); + LauncherItem item; + try + { + item = JsonSerializer.Deserialize(json, JsonOptions) + ?? throw new InvalidDataException("The selected item file could not be read."); + } + catch (JsonException ex) + { + throw new InvalidDataException("The selected file is not a valid Taskbar Launcher item export.", ex); + } + + ValidateItem(item); + NormalizeDisplayModes(item); + return item; + } + public static LauncherConfig ImportFrom(string sourcePath) { LauncherConfig config = ReadConfigFile(sourcePath); diff --git a/TaskbarLauncher.csproj b/TaskbarLauncher.csproj index 1597372..ec5a926 100644 --- a/TaskbarLauncher.csproj +++ b/TaskbarLauncher.csproj @@ -12,8 +12,8 @@ Ray Berezowski Assets\AppIcon.ico 1.0.1 - 1.0.1.11 - 1.0.1.11 + 1.0.1.12 + 1.0.1.12 1.0.1 diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 4998bae..a522c7e 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -42,6 +42,7 @@ From Settings you can: - Right-click an item and choose **Disable Item** or **Enable Item** to hide or restore it - Right-click an app or file item and choose **Open Containing Folder** - Use **Config... > Validate Targets** to check for missing app, file, and folder targets +- Use **Config... > Export Selected Item** or **Import Item** to share one item or menu section - Set per-item icon sizes - Back up and restore `config.json` - Enable Start with Windows