From efa8f06dc747845d6378ba592237a318ba29fc63 Mon Sep 17 00:00:00 2001 From: Eauldane Date: Sun, 30 Nov 2025 18:19:37 +0000 Subject: [PATCH 1/5] Allow saving third party repos without unnecesary "plus" press --- .../Widgets/ThirdRepoSettingsEntry.cs | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs index 5737b44db..b0cc61eb0 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs @@ -46,6 +46,13 @@ internal class ThirdRepoSettingsEntry : SettingsEntry public override void Save() { + var hasPendingRepo = !string.IsNullOrWhiteSpace(this.thirdRepoTempUrl); + var addedPendingRepo = this.TryAddTempRepo(); + if (!addedPendingRepo && hasPendingRepo) + { + return; + } + Service.Get().ThirdRepoList = [.. this.thirdRepoList.Select(x => x.Clone())]; @@ -235,27 +242,7 @@ internal class ThirdRepoSettingsEntry : SettingsEntry ImGui.NextColumn(); if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd(); - if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) - { - this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); - } - else if (!ValidThirdPartyRepoUrl(this.thirdRepoTempUrl)) - { - this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoNotUrl", "The entered address is not a valid URL.\nDid you mean to enter it as a DevPlugin in the fields above instead?"); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); - } - else - { - this.thirdRepoList.Add(new ThirdPartyRepoSettings - { - Url = this.thirdRepoTempUrl, - IsEnabled = true, - }); - this.thirdRepoListChanged = true; - this.thirdRepoTempUrl = string.Empty; - } + this.TryAddTempRepo(); } ImGui.Columns(1); @@ -269,4 +256,36 @@ internal class ThirdRepoSettingsEntry : SettingsEntry private static bool ValidThirdPartyRepoUrl(string url) => Uri.TryCreate(url, UriKind.Absolute, out var uriResult) && (uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp); + + private bool TryAddTempRepo() + { + if (string.IsNullOrWhiteSpace(this.thirdRepoTempUrl)) + return false; + + this.thirdRepoTempUrl = this.thirdRepoTempUrl.Trim(); + if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) + { + this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); + Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + return false; + } + + if (!ValidThirdPartyRepoUrl(this.thirdRepoTempUrl)) + { + this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoNotUrl", "The entered address is not a valid URL.\nDid you mean to enter it as a DevPlugin in the fields above instead?"); + Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + return false; + } + + this.thirdRepoList.Add(new ThirdPartyRepoSettings + { + Url = this.thirdRepoTempUrl, + IsEnabled = true, + }); + this.thirdRepoListChanged = true; + this.thirdRepoTempUrl = string.Empty; + + return true; + } + } From b461ad845d97fe546370ea90bdafc3e07eb6b692 Mon Sep 17 00:00:00 2001 From: Eauldane Date: Sun, 30 Nov 2025 20:11:39 +0000 Subject: [PATCH 2/5] Fix window closing if there's an invalid repo URL added. --- .../Internal/Windows/Settings/SettingsWindow.cs | 10 +++++++--- .../Settings/Widgets/ThirdRepoSettingsEntry.cs | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs index 581ef3746..cf9b520ef 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs @@ -175,17 +175,21 @@ internal sealed class SettingsWindow : Window { if (buttonChild) { - using var disabled = ImRaii.Disabled(this.tabs.Any(x => x.Entries.Any(y => !y.IsValid))); - using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 100f)) { using var font = ImRaii.PushFont(InterfaceManager.IconFont); + var hasInvalidEntries = this.tabs.Any(x => x.Entries.Any(y => !y.IsValid)); + + using var disabled = ImRaii.Disabled(hasInvalidEntries); + if (ImGui.Button(FontAwesomeIcon.Save.ToIconString(), new Vector2(40))) { this.Save(); - if (!ImGui.IsKeyDown(ImGuiKey.ModShift)) + hasInvalidEntries = this.tabs.Any(x => x.Entries.Any(y => !y.IsValid)); + + if (!hasInvalidEntries && !ImGui.IsKeyDown(ImGuiKey.ModShift)) this.IsOpen = false; } } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs index b0cc61eb0..d0a750c31 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs @@ -42,6 +42,7 @@ internal class ThirdRepoSettingsEntry : SettingsEntry this.thirdRepoList = [.. Service.Get().ThirdRepoList.Select(x => x.Clone())]; this.thirdRepoListChanged = false; + this.IsValid = true; } public override void Save() @@ -50,9 +51,12 @@ internal class ThirdRepoSettingsEntry : SettingsEntry var addedPendingRepo = this.TryAddTempRepo(); if (!addedPendingRepo && hasPendingRepo) { + this.IsValid = false; return; } + this.IsValid = true; + Service.Get().ThirdRepoList = [.. this.thirdRepoList.Select(x => x.Clone())]; @@ -179,6 +183,8 @@ internal class ThirdRepoSettingsEntry : SettingsEntry var url = thirdRepoSetting.Url; if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) { + this.IsValid = true; + var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url); if (thirdRepoSetting.Url == url) { @@ -196,8 +202,8 @@ internal class ThirdRepoSettingsEntry : SettingsEntry } else { + this.thirdRepoListChanged = thirdRepoSetting.Url != url; thirdRepoSetting.Url = url; - this.thirdRepoListChanged = url != thirdRepoSetting.Url; } } @@ -236,8 +242,10 @@ internal class ThirdRepoSettingsEntry : SettingsEntry ImGui.Text(repoNumber.ToString()); ImGui.NextColumn(); ImGui.SetNextItemWidth(-1); - ImGui.InputText("##thirdRepoUrlInput"u8, ref this.thirdRepoTempUrl, 300); - ImGui.NextColumn(); + if (ImGui.InputText("##thirdRepoUrlInput"u8, ref this.thirdRepoTempUrl, 300)) + { + this.IsValid = true; + } ImGui.NextColumn(); // Enabled button ImGui.NextColumn(); if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) @@ -267,6 +275,7 @@ internal class ThirdRepoSettingsEntry : SettingsEntry { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + this.IsValid = false; return false; } @@ -274,6 +283,7 @@ internal class ThirdRepoSettingsEntry : SettingsEntry { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoNotUrl", "The entered address is not a valid URL.\nDid you mean to enter it as a DevPlugin in the fields above instead?"); Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + this.IsValid = false; return false; } @@ -284,6 +294,7 @@ internal class ThirdRepoSettingsEntry : SettingsEntry }); this.thirdRepoListChanged = true; this.thirdRepoTempUrl = string.Empty; + this.IsValid = true; return true; } From bfe338e991d66172bd0604c4d3505b10e5105fb6 Mon Sep 17 00:00:00 2001 From: Eauldane Date: Sun, 30 Nov 2025 20:26:19 +0000 Subject: [PATCH 3/5] Formatting fix because of accidental backspace --- .../Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs index d0a750c31..003b8e088 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs @@ -245,7 +245,9 @@ internal class ThirdRepoSettingsEntry : SettingsEntry if (ImGui.InputText("##thirdRepoUrlInput"u8, ref this.thirdRepoTempUrl, 300)) { this.IsValid = true; - } ImGui.NextColumn(); + } + + ImGui.NextColumn(); // Enabled button ImGui.NextColumn(); if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) From e518cf6e32e54fcfc84bb1aea1e5dc6051ecf682 Mon Sep 17 00:00:00 2001 From: Eauldane Date: Wed, 3 Dec 2025 22:03:34 +0000 Subject: [PATCH 4/5] Remove error timeouts, have input validated on each change --- .../Windows/Settings/SettingsWindow.cs | 2 - .../Widgets/ThirdRepoSettingsEntry.cs | 54 ++++++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs index cf9b520ef..42edbe16f 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs @@ -187,8 +187,6 @@ internal sealed class SettingsWindow : Window { this.Save(); - hasInvalidEntries = this.tabs.Any(x => x.Entries.Any(y => !y.IsValid)); - if (!hasInvalidEntries && !ImGui.IsKeyDown(ImGuiKey.ModShift)) this.IsOpen = false; } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs index 003b8e088..1086a6a12 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs @@ -181,29 +181,34 @@ internal class ThirdRepoSettingsEntry : SettingsEntry ImGui.SetNextItemWidth(-1); var url = thirdRepoSetting.Url; - if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.InputText($"##thirdRepoInput", ref url, 65535)) { - this.IsValid = true; + var trimmedUrl = url.Trim(); - var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url); - if (thirdRepoSetting.Url == url) + if (string.Equals(thirdRepoSetting.Url, trimmedUrl, StringComparison.InvariantCultureIgnoreCase)) { - // no change. + this.thirdRepoAddError = string.Empty; + this.IsValid = true; } - else if (contains && thirdRepoSetting.Url != url) + else if (this.thirdRepoList + .Where(repo => !string.Equals(repo.Url, thirdRepoSetting.Url, StringComparison.InvariantCultureIgnoreCase)) + .Select(repo => repo.Url) + .Contains(trimmedUrl)) { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + this.IsValid = false; } - else if (!ValidThirdPartyRepoUrl(url)) + else if (!ValidThirdPartyRepoUrl(trimmedUrl)) { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoNotUrl", "The entered address is not a valid URL.\nDid you mean to enter it as a DevPlugin in the fields above instead?"); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + this.IsValid = false; } else { - this.thirdRepoListChanged = thirdRepoSetting.Url != url; - thirdRepoSetting.Url = url; + this.thirdRepoAddError = string.Empty; + this.thirdRepoListChanged = !string.Equals(thirdRepoSetting.Url, trimmedUrl, StringComparison.InvariantCultureIgnoreCase); + thirdRepoSetting.Url = trimmedUrl; + this.IsValid = true; } } @@ -244,7 +249,7 @@ internal class ThirdRepoSettingsEntry : SettingsEntry ImGui.SetNextItemWidth(-1); if (ImGui.InputText("##thirdRepoUrlInput"u8, ref this.thirdRepoTempUrl, 300)) { - this.IsValid = true; + this.ValidateTempRepoInput(); } ImGui.NextColumn(); @@ -267,16 +272,20 @@ internal class ThirdRepoSettingsEntry : SettingsEntry => Uri.TryCreate(url, UriKind.Absolute, out var uriResult) && (uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp); - private bool TryAddTempRepo() + private bool ValidateTempRepoInput() { - if (string.IsNullOrWhiteSpace(this.thirdRepoTempUrl)) - return false; - this.thirdRepoTempUrl = this.thirdRepoTempUrl.Trim(); + + if (string.IsNullOrEmpty(this.thirdRepoTempUrl)) + { + this.thirdRepoAddError = string.Empty; + this.IsValid = true; + return false; + } + if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); this.IsValid = false; return false; } @@ -284,11 +293,20 @@ internal class ThirdRepoSettingsEntry : SettingsEntry if (!ValidThirdPartyRepoUrl(this.thirdRepoTempUrl)) { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoNotUrl", "The entered address is not a valid URL.\nDid you mean to enter it as a DevPlugin in the fields above instead?"); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); this.IsValid = false; return false; } + this.thirdRepoAddError = string.Empty; + this.IsValid = true; + return true; + } + + private bool TryAddTempRepo() + { + if (!this.ValidateTempRepoInput()) + return false; + this.thirdRepoList.Add(new ThirdPartyRepoSettings { Url = this.thirdRepoTempUrl, From 6cee8ebe1aa48d9efa08188c4aea32ed7ae1dcf8 Mon Sep 17 00:00:00 2001 From: Eauldane Date: Wed, 3 Dec 2025 22:09:18 +0000 Subject: [PATCH 5/5] Improve URI validation beyond just scheme validation --- .../Settings/Widgets/ThirdRepoSettingsEntry.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs index 1086a6a12..7a64f483e 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/ThirdRepoSettingsEntry.cs @@ -269,8 +269,20 @@ internal class ThirdRepoSettingsEntry : SettingsEntry } private static bool ValidThirdPartyRepoUrl(string url) - => Uri.TryCreate(url, UriKind.Absolute, out var uriResult) - && (uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp); + { + if (!Uri.TryCreate(url, UriKind.Absolute, out var uriResult)) + return false; + + if (uriResult.Scheme != Uri.UriSchemeHttps && uriResult.Scheme != Uri.UriSchemeHttp) + return false; + + if (string.IsNullOrWhiteSpace(uriResult.Host)) + return false; + + var hostNameType = Uri.CheckHostName(uriResult.Host); + return hostNameType != UriHostNameType.Unknown + && Uri.IsWellFormedUriString(url, UriKind.Absolute); + } private bool ValidateTempRepoInput() {