diff --git a/Directory.Build.props b/Directory.Build.props index 5dfaea9e8..9cf1e0d45 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,7 +22,7 @@ changes! --> 3.0.26 - 50 + 51 $(NitrocidModAPIVersionMajor).$(NitrocidModAPIVersionChangeset) diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs b/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs index 5a3ba37c4..b5fc785ca 100644 --- a/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs +++ b/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs @@ -37,6 +37,7 @@ using Nitrocid.Extras.Calendar.Calendar; using Nitrocid.Kernel.Time.Calendars; using EventInfo = Nitrocid.Extras.Calendar.Calendar.Events.EventInfo; +using Nitrocid.Shell.Homepage; namespace Nitrocid.Extras.Calendar { @@ -247,6 +248,9 @@ void IAddon.FinalizeAddon() EventManager.LoadEvents(); ReminderManager.LoadReminders(); DebugWriter.WriteDebug(DebugLevel.I, "Loaded events & reminders."); + + // Add the calendar option to the homepage + HomepageTools.RegisterBuiltinAction(/* Localizable */ "Calendar", CalendarTui.OpenInteractive); } void IAddon.StartAddon() @@ -262,6 +266,7 @@ void IAddon.StopAddon() EventManager.CalendarEvents.Clear(); CommandManager.UnregisterAddonCommands(ShellType.Shell, [.. addonCommands.Select((ci) => ci.Command)]); ConfigTools.UnregisterBaseSetting(nameof(CalendarConfig)); + HomepageTools.UnregisterBuiltinAction("Calendar"); } } } diff --git a/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs b/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs index 0f4aee44b..7de63d69c 100644 --- a/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs +++ b/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs @@ -149,6 +149,7 @@ internal static class KernelExceptionMessages { KernelExceptionType.Bootloader, Translate.DoTranslation("There was an error when trying to process a bootloader operation.") }, { KernelExceptionType.Alarm, Translate.DoTranslation("There was an error when trying to process an alarm system operation.") }, { KernelExceptionType.Widget, Translate.DoTranslation("There was an error when trying to process a widget system operation. If you're sure that this widget is registered properly, please make sure that you've written the widget class name properly.") }, + { KernelExceptionType.Homepage, Translate.DoTranslation("The homepage tools has encountered an error when trying to process your request. Please make sure that you've entered all the necessary data correctly.") }, }; internal static string GetFinalExceptionMessage(KernelExceptionType exceptionType, string message, Exception? e, params object[] vars) diff --git a/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs b/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs index 0889e008a..4e60e7a65 100644 --- a/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs +++ b/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs @@ -496,5 +496,9 @@ public enum KernelExceptionType /// There was an error when trying to process a widget system operation. If you're sure that this widget is registered properly, please make sure that you've written the widget class name properly. /// Widget, + /// + /// The homepage tools has encountered an error when trying to process your request. Please make sure that you've entered all the necessary data correctly. + /// + Homepage, } } diff --git a/public/Nitrocid/Shell/Homepage/HomepageTools.cs b/public/Nitrocid/Shell/Homepage/HomepageTools.cs index 161128b63..81785d336 100644 --- a/public/Nitrocid/Shell/Homepage/HomepageTools.cs +++ b/public/Nitrocid/Shell/Homepage/HomepageTools.cs @@ -34,6 +34,8 @@ using Nitrocid.Users.Login.Widgets; using Nitrocid.Users.Login.Widgets.Implementations; using System; +using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading; using Terminaux.Base; @@ -61,6 +63,16 @@ public static class HomepageTools { internal static bool isHomepageEnabled = true; private static bool isOnHomepage = false; + private static Dictionary choiceActionsAddons = []; + private static Dictionary choiceActionsCustom = []; + private static Dictionary choiceActionsBuiltin = new() + { + { /* Localizable */ "File Manager", OpenFileManagerCli }, + { /* Localizable */ "Alarm Manager", OpenAlarmCli }, + { /* Localizable */ "Notifications", OpenNotificationsCli }, + { /* Localizable */ "Task Manager", OpenTaskManagerCli }, + { /* Localizable */ "About Nitrocid", OpenAboutBox }, + }; private static readonly Keybinding[] bindings = [ // Keyboard @@ -85,14 +97,7 @@ public static void OpenHomepage() var homeScreen = new Screen(); int choiceIdx = 0; bool settingsHighlighted = false; - InputChoiceInfo[] choices = - [ - new("1", Translate.DoTranslation("File Manager")), - new("2", Translate.DoTranslation("Alarm Manager")), - new("3", Translate.DoTranslation("Notifications")), - new("4", Translate.DoTranslation("Task Manager")), - new("5", Translate.DoTranslation("About Nitrocid")), - ]; + var choices = PopulateChoices(); try { @@ -194,8 +199,9 @@ public static void OpenHomepage() builder.Append(CenteredTextColor.RenderCenteredOneLine(settingsButtonPosY + 1, Translate.DoTranslation("Settings"), settingsHighlighted ? new Color(ConsoleColors.Black) : KernelColorTools.GetColor(KernelColorType.NeutralText), settingsHighlighted ? KernelColorTools.GetColor(KernelColorType.TuiPaneSelectedSeparator) : ColorTools.CurrentBackgroundColor, settingsButtonPosX + 1, settingsButtonWidth + settingsButtonPosX + 5 - ConsoleWrapper.WindowWidth % 2)); // Populate the available options + var availableChoices = choices.Select((tuple) => tuple.Item1).ToArray(); builder.Append(BorderColor.RenderBorder(settingsButtonPosX, clockTop, widgetWidth - 1 + ConsoleWrapper.WindowWidth % 2, widgetHeight + 2, KernelColorTools.GetColor(!settingsHighlighted ? KernelColorType.TuiPaneSelectedSeparator : KernelColorType.TuiPaneSeparator))); - builder.Append(SelectionInputTools.RenderSelections(choices, settingsButtonPosX + 1, clockTop + 1, choiceIdx, widgetHeight + 2, widgetWidth - 1 + ConsoleWrapper.WindowWidth % 2, foregroundColor: KernelColorTools.GetColor(KernelColorType.NeutralText), selectedForegroundColor: KernelColorTools.GetColor(KernelColorType.TuiPaneSelectedSeparator))); + builder.Append(SelectionInputTools.RenderSelections(availableChoices, settingsButtonPosX + 1, clockTop + 1, choiceIdx, widgetHeight + 2, widgetWidth - 1 + ConsoleWrapper.WindowWidth % 2, foregroundColor: KernelColorTools.GetColor(KernelColorType.NeutralText), selectedForegroundColor: KernelColorTools.GetColor(KernelColorType.TuiPaneSelectedSeparator))); // Return the resulting homepage return builder.ToString(); @@ -205,67 +211,12 @@ public static void OpenHomepage() // Helper function void DoAction(int choiceIdx) { - switch (choiceIdx) - { - case 0: - { - var tui = new FileManagerCli - { - firstPanePath = PathsManagement.HomePath, - secondPanePath = PathsManagement.HomePath - }; - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Open"), ConsoleKey.Enter, (entry1, _, entry2, _) => tui.Open(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy"), ConsoleKey.F1, (entry1, _, entry2, _) => tui.CopyFileOrDir(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move"), ConsoleKey.F2, (entry1, _, entry2, _) => tui.MoveFileOrDir(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Delete"), ConsoleKey.F3, (entry1, _, entry2, _) => tui.RemoveFileOrDir(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Up"), ConsoleKey.F4, (_, _, _, _) => tui.GoUp())); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Info"), ConsoleKey.F5, (entry1, _, entry2, _) => tui.PrintFileSystemEntry(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Go To"), ConsoleKey.F6, (_, _, _, _) => tui.GoTo())); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy To"), ConsoleKey.F1, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.CopyTo(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move to"), ConsoleKey.F2, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.MoveTo(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Rename"), ConsoleKey.F9, (entry1, _, entry2, _) => tui.Rename(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("New Folder"), ConsoleKey.F10, (_, _, _, _) => tui.MakeDir())); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Hash"), ConsoleKey.F11, (entry1, _, entry2, _) => tui.Hash(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Verify"), ConsoleKey.F12, (entry1, _, entry2, _) => tui.Verify(entry1, entry2))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Preview"), ConsoleKey.P, (entry1, _, entry2, _) => tui.Preview(entry1, entry2))); - InteractiveTuiTools.OpenInteractiveTui(tui); - } - break; - case 1: - { - var tui = new AlarmCli(); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Add"), ConsoleKey.A, (_, _, _, _) => tui.Start(), true)); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Remove"), ConsoleKey.Delete, (alarm, _, _, _) => tui.Stop(alarm))); - InteractiveTuiTools.OpenInteractiveTui(tui); - } - break; - case 2: - { - var tui = new NotificationsCli(); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss"), ConsoleKey.Delete, (notif, _, _, _) => tui.Dismiss(notif))); - tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss All"), ConsoleKey.Delete, ConsoleModifiers.Control, (_, _, _, _) => tui.DismissAll())); - InteractiveTuiTools.OpenInteractiveTui(tui); - } - break; - case 3: - { - var tui = new TaskManagerCli(); - tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Kill"), ConsoleKey.F1, (thread, _, _, _) => tui.KillThread(thread))); - tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Switch"), ConsoleKey.F2, (_, _, _, _) => tui.SwitchMode())); - InteractiveTuiTools.OpenInteractiveTui(tui); - } - break; - case 4: - InfoBoxButtonsColor.WriteInfoBoxButtons( - Translate.DoTranslation("About Nitrocid"), - [new InputChoiceInfo(Translate.DoTranslation("Close"), Translate.DoTranslation("Close"))], - Translate.DoTranslation("Nitrocid KS simulates our future kernel, the Nitrocid Kernel.") + "\n\n" + - Translate.DoTranslation("Version") + $": {KernelMain.VersionFullStr}" + "\n" + - Translate.DoTranslation("Mod API") + $": {KernelMain.ApiVersion}" + "\n\n" + - Translate.DoTranslation("Copyright (C) 2018-2024 Aptivi - All rights reserved") + " - https://aptivi.github.io" - ); - break; - } + if (choiceIdx < 0 || choiceIdx >= choices.Length) + return; + + // Now, do the action! + var action = choices[choiceIdx].Item2; + action.Invoke(); } // Render the thing and wait for a keypress @@ -418,5 +369,205 @@ [new InputChoiceInfo(Translate.DoTranslation("Close"), Translate.DoTranslation(" ColorTools.LoadBack(); } } + + private static (InputChoiceInfo, Action)[] PopulateChoices() + { + // Variables + var choices = new List<(InputChoiceInfo, Action)>(); + + // First, deal with the builtin choices that are added by the core kernel + foreach (var choiceAction in choiceActionsBuiltin) + { + // Sanity checks + string key = choiceAction.Key; + var action = choiceAction.Value; + if (action is null) + continue; + if (string.IsNullOrEmpty(key)) + continue; + + // Add this action + var inputChoiceInfo = new InputChoiceInfo($"{choices.Count + 1}", Translate.DoTranslation(key)); + choices.Add((inputChoiceInfo, action)); + } + + // Then, deal with the choices that are added by the addons + foreach (var choiceAction in choiceActionsAddons) + { + // Sanity checks + string key = choiceAction.Key; + var action = choiceAction.Value; + if (action is null) + continue; + if (string.IsNullOrEmpty(key)) + continue; + + // Add this action + var inputChoiceInfo = new InputChoiceInfo($"{choices.Count + 1}", Translate.DoTranslation(key)); + choices.Add((inputChoiceInfo, action)); + } + + // Now, deal with the custom choices that are added by the mods + foreach (var choiceAction in choiceActionsCustom) + { + // Sanity checks + string key = choiceAction.Key; + var action = choiceAction.Value; + if (action is null) + continue; + if (string.IsNullOrEmpty(key)) + continue; + + // Add this action + var inputChoiceInfo = new InputChoiceInfo($"{choices.Count + 1}", Translate.DoTranslation(key)); + choices.Add((inputChoiceInfo, action)); + } + + // Finally, return the result for the homepage to recognize them + return [.. choices]; + } + + /// + /// Checks to see if the homepage action is registered or not + /// + /// Action name to search (case sensitive) + /// True if it exists; false otherwise + public static bool IsHomepageActionRegistered(string actionName) + { + if (string.IsNullOrEmpty(actionName)) + return false; + if (IsHomepageActionBuiltin(actionName)) + return true; + var actions = choiceActionsAddons.Union(choiceActionsCustom).Select((kvp) => kvp.Key).ToArray(); + return actions.Contains(actionName); + } + + /// + /// Checks to see if the homepage action is bulitin or not + /// + /// Action name to search (case sensitive) + /// True if it exists; false otherwise + public static bool IsHomepageActionBuiltin(string actionName) + { + if (string.IsNullOrEmpty(actionName)) + return false; + var actions = choiceActionsBuiltin.Select((kvp) => kvp.Key).ToArray(); + return actions.Contains(actionName); + } + + /// + /// Registers a custom action + /// + /// Action name (case sensitive) + /// Action to delegate a specific function to + /// + public static void RegisterAction(string actionName, Action? action) + { + if (string.IsNullOrEmpty(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified.")); + if (action is null) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action is not specified.")); + if (IsHomepageActionRegistered(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action already exists.")); + choiceActionsCustom.Add(actionName, action); + } + + /// + /// Unregisters a custom action + /// + /// Action name to delete (case sensitive) + /// + public static void UnregisterAction(string actionName) + { + if (string.IsNullOrEmpty(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified.")); + if (!IsHomepageActionRegistered(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action doesn't exist.")); + if (IsHomepageActionBuiltin(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Built-in action can't be removed.")); + choiceActionsCustom.Remove(actionName); + } + + internal static void RegisterBuiltinAction(string actionName, Action? action) + { + if (string.IsNullOrEmpty(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified.")); + if (action is null) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action is not specified.")); + if (IsHomepageActionRegistered(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action already exists.")); + choiceActionsAddons.Add(actionName, action); + } + + internal static void UnregisterBuiltinAction(string actionName) + { + if (string.IsNullOrEmpty(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified.")); + if (!IsHomepageActionRegistered(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action doesn't exist.")); + if (IsHomepageActionBuiltin(actionName)) + throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Built-in action can't be removed.")); + choiceActionsAddons.Remove(actionName); + } + + private static void OpenFileManagerCli() + { + var tui = new FileManagerCli + { + firstPanePath = PathsManagement.HomePath, + secondPanePath = PathsManagement.HomePath + }; + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Open"), ConsoleKey.Enter, (entry1, _, entry2, _) => tui.Open(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy"), ConsoleKey.F1, (entry1, _, entry2, _) => tui.CopyFileOrDir(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move"), ConsoleKey.F2, (entry1, _, entry2, _) => tui.MoveFileOrDir(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Delete"), ConsoleKey.F3, (entry1, _, entry2, _) => tui.RemoveFileOrDir(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Up"), ConsoleKey.F4, (_, _, _, _) => tui.GoUp())); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Info"), ConsoleKey.F5, (entry1, _, entry2, _) => tui.PrintFileSystemEntry(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Go To"), ConsoleKey.F6, (_, _, _, _) => tui.GoTo())); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy To"), ConsoleKey.F1, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.CopyTo(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move to"), ConsoleKey.F2, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.MoveTo(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Rename"), ConsoleKey.F9, (entry1, _, entry2, _) => tui.Rename(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("New Folder"), ConsoleKey.F10, (_, _, _, _) => tui.MakeDir())); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Hash"), ConsoleKey.F11, (entry1, _, entry2, _) => tui.Hash(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Verify"), ConsoleKey.F12, (entry1, _, entry2, _) => tui.Verify(entry1, entry2))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Preview"), ConsoleKey.P, (entry1, _, entry2, _) => tui.Preview(entry1, entry2))); + InteractiveTuiTools.OpenInteractiveTui(tui); + } + + private static void OpenAlarmCli() + { + var tui = new AlarmCli(); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Add"), ConsoleKey.A, (_, _, _, _) => tui.Start(), true)); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Remove"), ConsoleKey.Delete, (alarm, _, _, _) => tui.Stop(alarm))); + InteractiveTuiTools.OpenInteractiveTui(tui); + } + + private static void OpenNotificationsCli() + { + var tui = new NotificationsCli(); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss"), ConsoleKey.Delete, (notif, _, _, _) => tui.Dismiss(notif))); + tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss All"), ConsoleKey.Delete, ConsoleModifiers.Control, (_, _, _, _) => tui.DismissAll())); + InteractiveTuiTools.OpenInteractiveTui(tui); + } + + private static void OpenTaskManagerCli() + { + var tui = new TaskManagerCli(); + tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Kill"), ConsoleKey.F1, (thread, _, _, _) => tui.KillThread(thread))); + tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Switch"), ConsoleKey.F2, (_, _, _, _) => tui.SwitchMode())); + InteractiveTuiTools.OpenInteractiveTui(tui); + } + + private static void OpenAboutBox() + { + InfoBoxButtonsColor.WriteInfoBoxButtons( + Translate.DoTranslation("About Nitrocid"), + [new InputChoiceInfo(Translate.DoTranslation("Close"), Translate.DoTranslation("Close"))], + Translate.DoTranslation("Nitrocid KS simulates our future kernel, the Nitrocid Kernel.") + "\n\n" + + Translate.DoTranslation("Version") + $": {KernelMain.VersionFullStr}" + "\n" + + Translate.DoTranslation("Mod API") + $": {KernelMain.ApiVersion}" + "\n\n" + + Translate.DoTranslation("Copyright (C) 2018-2024 Aptivi - All rights reserved") + " - https://aptivi.github.io" + ); + } } }