fix(entry): relocate floating trigger to top-left safe canvas area and auto-heal sidebar occlusion

This commit is contained in:
DemonGatanjieu
2026-09-20 21:01:29 +08:00
parent 6c66e67742
commit 6f8fd99e11
5 changed files with 51 additions and 19 deletions
+2 -2
View File
@@ -92,8 +92,8 @@ DOM or live LiteGraph state.
browser class and extracted-method wiring; `browser_entry.js` owns the single
browser instance plus floating/topbar/menu entry behavior; `entry_controls.js`
owns entry mode, trigger sizing/styling normalization, viewport-safe boundary
clamping (`clampFloatingTriggerPosition`), and legacy top-left auto-healing
(`isValidSavedTriggerPosition`); and `interface_settings.js` owns language and theme preferences.
clamping (`clampFloatingTriggerPosition`), and toolbar occlusion auto-healing
(`isValidSavedTriggerPosition`, `normalizeSavedTriggerPosition`); and `interface_settings.js` owns language and theme preferences.
- `ui_domain_switcher.js` owns the dual-domain mode toggle (Visual Studio 🎨 vs Audio & Voice Studio 🎙️) embedded in the brand header.
- `ui_audio_studio.js` owns the Audio & Voice Studio workspace, presenting character voice cards, emotion slice playback, prompt syntax copying, real-time search, animated equalizers, and canvas drag-and-drop.
- `ui_audio_sidebar.js` owns the Audio Sidebar navigation, rendering trees for All Voices, Character Presets (e.g. Arona) with count badges, and Output History Vault.
+5 -3
View File
@@ -23,12 +23,14 @@ the real ComfyUI runtime creates the configured entry. Floating trigger styling
the ordered stylesheet bundle rooted at `web/styles.css` uses dynamic `1em` SVG
scaling and flex centering to guarantee
consistent visual presentation across all configured trigger sizes. By default, the
floating trigger button is positioned via pure responsive CSS (`bottom: 30px; right: 30px;`)
floating trigger button is positioned via pure responsive CSS (`top: 60px; left: 60px;`),
leaving comfortable 12px margins from both the ComfyUI left sidebar (~48px) and topbar (~48px)
without inline left/top overrides. `browser_entry.js` strictly distinguishes clicks from
drags (threshold: 5px) to prevent accidental persistence, while `entry_controls.js`
provides robust boundary clamping (`clampFloatingTriggerPosition`) with safe viewport
fallbacks (1024x768) and automatic healing of legacy collapsed top-left coordinates
(`isValidSavedTriggerPosition`). Resetting trigger position cleanly restores default CSS positioning.
fallbacks (1024x768) and automatic healing of legacy or accidental toolbar/sidebar
occlusion coordinates (`isValidSavedTriggerPosition`, `normalizeSavedTriggerPosition`).
Resetting trigger position cleanly restores default CSS positioning.
Major UI panels live in `web/modules/ui_*.js`. Shared browser state remains on
the `AnomalousBrowser` instance. Pure parsing, normalization, comparison, and
+21 -10
View File
@@ -2,10 +2,12 @@ import { app } from '../../../scripts/app.js';
import { AnomalousBrowser } from './browser.js';
import {
clampFloatingTriggerPosition,
DEFAULT_TRIGGER_POSITION,
isValidSavedTriggerPosition,
normalizeEntryMode,
normalizeFloatingTriggerSize,
normalizeFloatingTriggerStyle
normalizeFloatingTriggerStyle,
normalizeSavedTriggerPosition
} from './entry_controls.js';
import {
createShortcutSettingControl,
@@ -304,8 +306,13 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) {
isDragging = false;
btn.style.transition = 'transform 0.15s, box-shadow 0.15s';
if (hasMoved) {
localStorage.setItem('anomalous_btn_x', btn.style.left);
localStorage.setItem('anomalous_btn_y', btn.style.top);
const norm = normalizeSavedTriggerPosition(btn.style.left, btn.style.top);
if (norm) {
btn.style.left = norm.x + 'px';
btn.style.top = norm.y + 'px';
localStorage.setItem('anomalous_btn_x', btn.style.left);
localStorage.setItem('anomalous_btn_y', btn.style.top);
}
} else {
open();
}
@@ -314,7 +321,8 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) {
const updateBtnBounds = () => {
const savedX = localStorage.getItem('anomalous_btn_x');
const savedY = localStorage.getItem('anomalous_btn_y');
if (!isValidSavedTriggerPosition(savedX, savedY)) {
const norm = normalizeSavedTriggerPosition(savedX, savedY);
if (!norm) {
btn.style.left = '';
btn.style.top = '';
btn.style.right = '';
@@ -322,8 +330,8 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) {
return;
}
const position = clampFloatingTriggerPosition({
x: btn.style.left || savedX,
y: btn.style.top || savedY,
x: btn.style.left || (norm.x + 'px'),
y: btn.style.top || (norm.y + 'px'),
width: btn.offsetWidth,
height: btn.offsetHeight,
viewportWidth: window.innerWidth,
@@ -338,11 +346,14 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) {
const initialSavedX = localStorage.getItem('anomalous_btn_x');
const initialSavedY = localStorage.getItem('anomalous_btn_y');
if (isValidSavedTriggerPosition(initialSavedX, initialSavedY)) {
const initialNorm = normalizeSavedTriggerPosition(initialSavedX, initialSavedY);
if (initialNorm) {
btn.style.right = 'auto';
btn.style.bottom = 'auto';
btn.style.left = initialSavedX;
btn.style.top = initialSavedY;
btn.style.left = initialNorm.x + 'px';
btn.style.top = initialNorm.y + 'px';
localStorage.setItem('anomalous_btn_x', btn.style.left);
localStorage.setItem('anomalous_btn_y', btn.style.top);
setTimeout(updateBtnBounds, 200);
} else {
localStorage.removeItem('anomalous_btn_x');
@@ -355,7 +366,7 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) {
window.addEventListener('resize', () => {
const curX = localStorage.getItem('anomalous_btn_x');
const curY = localStorage.getItem('anomalous_btn_y');
if (isValidSavedTriggerPosition(curX, curY)) {
if (normalizeSavedTriggerPosition(curX, curY)) {
updateBtnBounds();
}
syncVisibility();
+21 -2
View File
@@ -14,6 +14,8 @@ export function normalizeEntryMode(value) {
return ENTRY_MODES.has(value) ? value : 'floating';
}
export const DEFAULT_TRIGGER_POSITION = Object.freeze({ x: 60, y: 60 });
export function isValidSavedTriggerPosition(x, y) {
if (x == null || y == null) return false;
const px = Number.parseFloat(x);
@@ -24,6 +26,23 @@ export function isValidSavedTriggerPosition(x, y) {
return true;
}
export function normalizeSavedTriggerPosition(x, y) {
if (x == null || y == null) return null;
let px = Number.parseFloat(x);
let py = Number.parseFloat(y);
if (!Number.isFinite(px) || !Number.isFinite(py)) return null;
if (px < 10 && py < 10) return null;
// Auto-adjust away from left sidebar occlusion zone (sidebar width is ~48px)
if (px < 56 && py < 300) {
px = DEFAULT_TRIGGER_POSITION.x;
}
// Auto-adjust away from topbar occlusion zone (topbar height is ~48px)
if (py < 52 && px < 400) {
py = DEFAULT_TRIGGER_POSITION.y;
}
return { x: px, y: py };
}
export function clampFloatingTriggerPosition({
x,
y,
@@ -39,8 +58,8 @@ export function clampFloatingTriggerPosition({
const safeVh = Math.max(safeHeight + margin, Number(viewportHeight) || (typeof window !== 'undefined' ? window.innerHeight : 768) || 768);
const maxX = Math.max(0, safeVw - safeWidth);
const maxY = Math.max(0, safeVh - safeHeight);
const fallbackX = Math.max(0, maxX - margin);
const fallbackY = Math.max(0, maxY - margin);
const fallbackX = DEFAULT_TRIGGER_POSITION.x;
const fallbackY = DEFAULT_TRIGGER_POSITION.y;
const parsedX = Number.parseFloat(x);
const parsedY = Number.parseFloat(y);
+2 -2
View File
@@ -77,8 +77,8 @@ html.theme-abyssal-scarlet,
--anomalous-trigger-font-size: 28px;
--anomalous-trigger-pill-padding: 18px;
position: fixed;
bottom: 30px;
right: 30px;
top: 60px;
left: 60px;
width: var(--anomalous-trigger-size);
height: var(--anomalous-trigger-size);
border-radius: 50%;