feat: Add info panel persistence setting and magnet icon to allow displaying last hovered node info.

This commit is contained in:
AEmotionStudio
2026-01-02 14:50:20 -08:00
parent ea6b800b32
commit ac2eba8b73
19 changed files with 178 additions and 17 deletions
+27 -5
View File
@@ -131,6 +131,9 @@ export class InfoPanel {
});
}
// State for persistence
private lastValidNodeInfo: any = null;
updateInfo(): void {
const settings = this.stateManager.state.settings;
const isActive = this.magnifyGlass.state.active;
@@ -143,7 +146,23 @@ export class InfoPanel {
return;
}
const info = this.informationGatherer.gatherInformation();
let info = this.informationGatherer.gatherInformation();
// PERSISTENCE LOGIC START
if (info.hoveredNode) {
// We have a valid node, update our cache
this.lastValidNodeInfo = info;
} else if (settings["🔍MagnifyGlass.InfoPanelPersist"] && this.lastValidNodeInfo) {
// No node hovered, but persistence is ON and we have a cache.
// Merge the CACHED node info into the CURRENT info (so we keep current cursor pos/zoom but show old node)
info = {
...info, // Current timestamp, cursor, zoom
hoveredNode: this.lastValidNodeInfo.hoveredNode,
hoveredWidget: this.lastValidNodeInfo.hoveredWidget
};
}
// PERSISTENCE LOGIC END
this.stateManager.setCurrentInfo(info);
// Update the UI
@@ -161,10 +180,13 @@ export class InfoPanel {
}
} else {
this.stateManager.state.isHoveringNode = false;
if (this.stateManager.state.lastNodeId !== null) {
this.stateManager.state.lastNodeId = null;
if (!this.stateManager.state.isPanelHovered) {
this.stateManager.scheduleAutoCollapse();
// Only collapse if we are NOT persisting
if (!settings["🔍MagnifyGlass.InfoPanelPersist"]) {
if (this.stateManager.state.lastNodeId !== null) {
this.stateManager.state.lastNodeId = null;
if (!this.stateManager.state.isPanelHovered) {
this.stateManager.scheduleAutoCollapse();
}
}
}
}
+14
View File
@@ -207,6 +207,7 @@ export class UIManager {
this.elements.controls.innerHTML = `
<button class="control-btn unlock-btn" title="Unlock/Lock Panel from Glass" data-action="pin">${Icons.unlock}</button>
<button class="control-btn pin-btn" title="Pin/Unpin Panel Position (Prevent Drag)" data-action="lock">${Icons.pin}</button>
<button class="control-btn persist-btn" title="Toggle Persist Mode (Sticky Info)" data-action="persist">${Icons.magnet}</button>
<button class="control-btn visibility-btn" title="Toggle Panel Visibility (I)" data-action="toggle-panel">${Icons.eye}</button>
<button class="control-btn glass-btn" title="Toggle Glass Preview (G)" data-action="toggle-glass">${Icons.magnifyGlass}</button>
`;
@@ -242,6 +243,11 @@ export class UIManager {
this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);
}
break;
case 'persist':
this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelPersist"] = !this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelPersist"];
this.updateControlStates();
// Sync sidebar setting if needed (optional, sidebar updates on reopen usually)
break;
case 'toggle-panel':
if (this.stateManager.state.isPanelVisible) {
this.hide();
@@ -342,6 +348,14 @@ export class UIManager {
lockBtn.disabled = !this.stateManager.state.isPanelPinned;
}
const persistBtn = this.elements.controls.querySelector('[data-action="persist"]') as HTMLButtonElement;
if (persistBtn) {
const isPersistConfigured = !!this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelPersist"];
persistBtn.classList.toggle('active', isPersistConfigured);
persistBtn.title = isPersistConfigured ? "Disable Sticky Info" : "Enable Sticky Info (Persist)";
persistBtn.style.display = isPanelVisible ? 'flex' : 'none';
}
if (visibilityBtn) {
// Active means "Panel is Visible"
visibilityBtn.classList.toggle('active', isPanelVisible);
+5
View File
@@ -77,6 +77,11 @@ export const Icons = {
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>`,
// Magnet icon - persist/sticky mode
magnet: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 15-4-4 6.75-6.77a7.79 7.79 0 0 1 11 11L13 22l-4-4 6.39-6.36a2.2 2.2 0 0 0-3.1-3.13l-6.29 6.49Z"/>
</svg>`
};
+1
View File
@@ -57,4 +57,5 @@ export const DEFAULT_PANEL_SETTINGS: PanelSettings = {
"🔍MagnifyGlass.InfoPanelTextColor": "#6b7280",
"🔍MagnifyGlass.InfoPanelAccentColor": "#3b82f6",
"🔍MagnifyGlass.InfoPanelFontSize": 14,
"🔍MagnifyGlass.InfoPanelPersist": false,
};
+16
View File
@@ -128,6 +128,22 @@ export function registerPanelSettings(stateManager: any, uiManager: any, positio
}
});
app.ui.settings.addSetting({
id: "🔍MagnifyGlass.InfoPanelPersist",
name: "📊 Info Panel: Persist Node Info",
type: "boolean",
defaultValue: settings["🔍MagnifyGlass.InfoPanelPersist"],
tooltip: "Keep displaying the last node's info until you hover over another node.",
onChange: (value: any) => {
if (stateManager) {
stateManager.state.settings["🔍MagnifyGlass.InfoPanelPersist"] = !!value;
if (uiManager) {
uiManager.updateControlStates();
}
}
}
});
app.ui.settings.addSetting({
id: "🔍MagnifyGlass.InfoPanelAnimations",
name: "📊 Info Panel: Animations",
+1
View File
@@ -45,6 +45,7 @@ export interface PanelSettings {
"🔍MagnifyGlass.InfoPanelTextColor": string;
"🔍MagnifyGlass.InfoPanelAccentColor": string;
"🔍MagnifyGlass.InfoPanelFontSize": number;
"🔍MagnifyGlass.InfoPanelPersist": boolean;
[key: string]: string | number | boolean; // Allow indexing
}
+31
View File
@@ -396,6 +396,23 @@ export function renderSettingsPanel(container: HTMLElement): void {
`When enabled, glass stays visible. Toggle with ${activationKey}`
));
// The instruction implies this should be in glassSection, but the content suggests info panel.
// Placing it in glassSection as per instruction, assuming createToggle is intended.
// The condition `if (getSettingValue("🔍MagnifyGlass.InfoPanelEnabled", true))` is removed
// as it doesn't make sense for a glass setting to depend on info panel being enabled.
// The tooltip and callback logic are kept as provided.
glassSection.body.appendChild(createToggle('Persist Info',
getSettingValue('🔍MagnifyGlass.InfoPanelPersist', false),
(checked) => {
setSettingValue('🔍MagnifyGlass.InfoPanelPersist', checked);
// Sync with UI controls if needed
const infoPanel = (window as any).infoPanelManager;
if (infoPanel?.uiManager) {
infoPanel.uiManager.updateControlStates();
}
}, 'Keep displaying the last node info when hovering empty space'
));
container.appendChild(glassSection.section);
// ===== Hotkeys Section =====
@@ -446,6 +463,19 @@ export function renderSettingsPanel(container: HTMLElement): void {
`Show info panel with node details. Toggle: ${toggleHotkey}`
));
panelSection.body.appendChild(createToggle('Persist Info',
getSettingValue('🔍MagnifyGlass.InfoPanelPersist', false),
(checked) => {
setSettingValue('🔍MagnifyGlass.InfoPanelPersist', checked);
// Sync with UI controls if needed
const infoPanel = (window as any).infoPanelManager;
if (infoPanel?.uiManager) {
infoPanel.uiManager.updateControlStates();
}
},
'Keep displaying the last node info when hovering empty space'
));
panelSection.body.appendChild(createSelect('Position',
getSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom'), PANEL_POSITIONS,
(value) => setSettingValue('🔍MagnifyGlass.InfoPanelPosition', value),
@@ -690,6 +720,7 @@ export function renderSettingsPanel(container: HTMLElement): void {
setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 300);
setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 100);
setSettingValue('🔍MagnifyGlass.InfoPanelFontSize', 14);
setSettingValue('🔍MagnifyGlass.InfoPanelPersist', false);
setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280');
setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6');
setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false);