fix(tabs): pin indicator, history preload, drop Promote from temp menu
Smoke-test round 2 feedback from Jin: - Promote-to-permanent label "Dauerhaft behalten" was indistinguishable from Pin in German, leading to misclicks that dropped the tell-target. Removed the menu entry from TempTabs entirely — Promote stays as a service method for future use, but the user-facing path is gone. Anyone who wants a regular tab can still create one via the existing "neuen Tab anlegen" flow. - No visual confirmation that pin took effect. Added a FontAwesome thumbtack overlay top-left of the sidebar icon, accent-coloured, and appended a "Pinned — survives relog" line to the hover tooltip. - Pinned tabs came back empty after a full disable/enable cycle because Tab.Messages is NonSerialized. RehydratePinnedTabs now also runs the same MessageStore-backed PreloadHistory the spawn path uses, so the recent conversation window reappears alongside the rehydrated TellTarget. Diagnose-logging on TryPin/Unpin/Promote/Rehydrate stays in so the next smoke can confirm at a glance which path fired from the Dalamud console.
This commit is contained in:
@@ -64,18 +64,38 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RehydratePinnedTabs()
|
private void RehydratePinnedTabs()
|
||||||
{
|
{
|
||||||
|
var pinned = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool);
|
||||||
|
Plugin.LogProxy.Info($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found");
|
||||||
|
|
||||||
foreach (var tab in Plugin.Config.Tabs)
|
foreach (var tab in Plugin.Config.Tabs)
|
||||||
{
|
{
|
||||||
if (!TabLifecycleHelpers.IsInPinnedPool(tab))
|
if (!TabLifecycleHelpers.IsInPinnedPool(tab))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (tab.TellTarget is null || !tab.TellTarget.IsSet())
|
if (tab.TellTarget is null || !tab.TellTarget.IsSet())
|
||||||
|
{
|
||||||
|
Plugin.LogProxy.Warning(
|
||||||
|
$"[Pin] Pinned tab '{tab.Name}' has no usable TellTarget "
|
||||||
|
+ $"(Name={tab.TellTarget?.Name ?? "<null>"} World={tab.TellTarget?.World ?? 0}). "
|
||||||
|
+ "Chat input on this tab will be empty until the partner sends a tell or you /tell manually."
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tab.Channel ??= InputChannel.Tell;
|
tab.Channel ??= InputChannel.Tell;
|
||||||
tab.CurrentChannel.Channel = InputChannel.Tell;
|
tab.CurrentChannel.Channel = InputChannel.Tell;
|
||||||
tab.CurrentChannel.TellTarget = tab.TellTarget.Clone();
|
tab.CurrentChannel.TellTarget = tab.TellTarget.Clone();
|
||||||
|
|
||||||
|
// MessageList is NonSerialized so pinned tabs come back empty.
|
||||||
|
// Preload the same history window the spawn path uses so the user
|
||||||
|
// sees the recent conversation, not a blank tab.
|
||||||
|
PreloadHistory(tab, tab.TellTarget.Name, tab.TellTarget.World, Guid.Empty);
|
||||||
|
|
||||||
|
Plugin.LogProxy.Info(
|
||||||
|
$"[Pin] Rehydrated '{tab.Name}' -> Tell target {tab.TellTarget.Name}@{tab.TellTarget.World}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +456,9 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
{
|
{
|
||||||
if (!tab.IsTempTab || tab.IsPinned)
|
if (!tab.IsTempTab || tab.IsPinned)
|
||||||
{
|
{
|
||||||
|
Plugin.LogProxy.Info(
|
||||||
|
$"[Pin] TryPin skipped: IsTempTab={tab.IsTempTab} IsPinned={tab.IsPinned}"
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,6 +472,9 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
tab.IsPinned = true;
|
tab.IsPinned = true;
|
||||||
|
Plugin.LogProxy.Info(
|
||||||
|
$"[Pin] Pinned tab '{tab.Name}' target={tab.TellTarget?.Name}@{tab.TellTarget?.World}"
|
||||||
|
);
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -469,6 +495,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
tab.IsPinned = false;
|
tab.IsPinned = false;
|
||||||
|
Plugin.LogProxy.Info($"[Pin] Unpinned tab '{tab.Name}'");
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,6 +509,9 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
tab.IsTempTab = false;
|
tab.IsTempTab = false;
|
||||||
tab.IsPinned = false;
|
tab.IsPinned = false;
|
||||||
tab.TellTarget = TellTarget.Empty();
|
tab.TellTarget = TellTarget.Empty();
|
||||||
|
Plugin.LogProxy.Info(
|
||||||
|
$"[Pin] Promoted tab '{tab.Name}' to permanent (tell-binding dropped)"
|
||||||
|
);
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ internal class HellionStrings
|
|||||||
internal static string PinTab_PromoteTooltip => Get(nameof(PinTab_PromoteTooltip));
|
internal static string PinTab_PromoteTooltip => Get(nameof(PinTab_PromoteTooltip));
|
||||||
internal static string PinTab_LimitReached => Get(nameof(PinTab_LimitReached));
|
internal static string PinTab_LimitReached => Get(nameof(PinTab_LimitReached));
|
||||||
internal static string PinTab_PinnedTooltip => Get(nameof(PinTab_PinnedTooltip));
|
internal static string PinTab_PinnedTooltip => Get(nameof(PinTab_PinnedTooltip));
|
||||||
|
internal static string PinTab_PinTooltip => Get(nameof(PinTab_PinTooltip));
|
||||||
|
|
||||||
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
||||||
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
||||||
|
|||||||
@@ -390,10 +390,10 @@
|
|||||||
<value>Tab lösen</value>
|
<value>Tab lösen</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PinTab_MenuPromote" xml:space="preserve">
|
<data name="PinTab_MenuPromote" xml:space="preserve">
|
||||||
<value>Dauerhaft behalten</value>
|
<value>In Standard-Tab umwandeln</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PinTab_PromoteTooltip" xml:space="preserve">
|
<data name="PinTab_PromoteTooltip" xml:space="preserve">
|
||||||
<value>„Dauerhaft behalten" macht aus dem Tab einen regulären Tab, der zu allen channel-gefilterten Nachrichten passt — bei Bedarf danach umbenennen.</value>
|
<value>Wandelt den TempTell in einen regulären Tab um. Die Tell-Bindung an die Person geht verloren, der Tab fängt dann Nachrichten anhand der Channel-Filter ein. Für „Tab überlebt Relog" stattdessen „Tab anpinnen" wählen.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PinTab_LimitReached" xml:space="preserve">
|
<data name="PinTab_LimitReached" xml:space="preserve">
|
||||||
<value>Maximal {0} angepinnte Tell-Tabs erreicht. Erst einen lösen oder dauerhaft behalten.</value>
|
<value>Maximal {0} angepinnte Tell-Tabs erreicht. Erst einen lösen oder dauerhaft behalten.</value>
|
||||||
@@ -401,6 +401,9 @@
|
|||||||
<data name="PinTab_PinnedTooltip" xml:space="preserve">
|
<data name="PinTab_PinnedTooltip" xml:space="preserve">
|
||||||
<value>Angepinnt — überlebt Relog.</value>
|
<value>Angepinnt — überlebt Relog.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||||
|
<value>Angepinnte Tabs überleben Relog und behalten die Bindung an die Tell-Person.</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
<!-- Hellion Chat — Auto-Tell-Tabs (Chat-Einstellungstab) -->
|
<!-- Hellion Chat — Auto-Tell-Tabs (Chat-Einstellungstab) -->
|
||||||
<data name="ChatLog_AutoTellTabs_Section_Title" xml:space="preserve">
|
<data name="ChatLog_AutoTellTabs_Section_Title" xml:space="preserve">
|
||||||
|
|||||||
@@ -393,7 +393,10 @@
|
|||||||
<value>Promote to permanent</value>
|
<value>Promote to permanent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PinTab_PromoteTooltip" xml:space="preserve">
|
<data name="PinTab_PromoteTooltip" xml:space="preserve">
|
||||||
<value>Promote turns this into a regular tab matching all channel-filtered messages — rename afterwards if needed.</value>
|
<value>Turns this TempTell into a regular tab. The tell binding to the partner is dropped — the tab will catch messages by its channel filters from now on. For "tab survives relog while staying bound to this partner", use Pin Tab instead.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||||
|
<value>Pinned tabs survive relog and stay bound to this conversation partner.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PinTab_LimitReached" xml:space="preserve">
|
<data name="PinTab_LimitReached" xml:space="preserve">
|
||||||
<value>Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent.</value>
|
<value>Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent.</value>
|
||||||
|
|||||||
@@ -1871,11 +1871,34 @@ public sealed class ChatLogWindow : Window
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pin indicator: small thumbtack glyph top-left of the icon.
|
||||||
|
// Sits opposite the unread dot so they never collide.
|
||||||
|
if (tab.IsPinned)
|
||||||
|
{
|
||||||
|
var min = ImGui.GetItemRectMin();
|
||||||
|
const float pinPadding = 2f;
|
||||||
|
var pinPos = new Vector2(min.X + pinPadding, min.Y + pinPadding);
|
||||||
|
using (Plugin.FontManager.FontAwesome.Push())
|
||||||
|
{
|
||||||
|
ImGui
|
||||||
|
.GetWindowDrawList()
|
||||||
|
.AddText(
|
||||||
|
pinPos,
|
||||||
|
ColourUtil.RgbaToAbgr(theme.Colors.Accent),
|
||||||
|
FontAwesomeIcon.Thumbtack.ToIconString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Tooltip mit Tab-Name + Unread-Counter beim Hover.
|
// Tooltip mit Tab-Name + Unread-Counter beim Hover.
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
using var tt = ImRaii.Tooltip();
|
using var tt = ImRaii.Tooltip();
|
||||||
ImGui.TextUnformatted($"{tab.Name}{unread}");
|
ImGui.TextUnformatted($"{tab.Name}{unread}");
|
||||||
|
if (tab.IsPinned)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(HellionStrings.PinTab_PinnedTooltip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawTabContextMenu(tab, tabI);
|
DrawTabContextMenu(tab, tabI);
|
||||||
@@ -2182,22 +2205,18 @@ public sealed class ChatLogWindow : Window
|
|||||||
if (svc.TryPin(tab))
|
if (svc.TryPin(tab))
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
if (atCap && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
|
{
|
||||||
ImGui.SetTooltip(
|
ImGui.SetTooltip(
|
||||||
string.Format(
|
atCap
|
||||||
HellionStrings.PinTab_LimitReached,
|
? string.Format(
|
||||||
AutoTellTabsService.MaxPinnedTempTabs
|
HellionStrings.PinTab_LimitReached,
|
||||||
)
|
AutoTellTabsService.MaxPinnedTempTabs
|
||||||
|
)
|
||||||
|
: HellionStrings.PinTab_PinTooltip
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem(HellionStrings.PinTab_MenuPromote))
|
|
||||||
{
|
|
||||||
svc.PromoteToPermanent(tab);
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(HellionStrings.PinTab_PromoteTooltip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal readonly List<bool> PopOutDocked = [];
|
internal readonly List<bool> PopOutDocked = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user