mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 15:02:20 +02:00
89 lines
3.2 KiB
JavaScript
89 lines
3.2 KiB
JavaScript
/**
|
||
* Topbar overflow handler — keeps lightweight labels updated and hides
|
||
* only low-priority AI model controls when the editor window gets narrow.
|
||
*
|
||
* Plus the small canvas-size display label updater (since it sits in
|
||
* the topbar too).
|
||
*
|
||
* Import and Canvas stay as real topbar buttons; there is intentionally
|
||
* no "More" overflow menu here.
|
||
*
|
||
* @param {{
|
||
* container: HTMLElement,
|
||
* registerDocClickAway: (handler: (e: Event) => void) => void,
|
||
* }} deps
|
||
*/
|
||
import { state } from './state.js';
|
||
|
||
let disposeTopbar;
|
||
|
||
export function wireTopbarOverflow({ container }) {
|
||
disposeTopbar?.();
|
||
const cleanups = [];
|
||
disposeTopbar = () => cleanups.forEach(cleanup => cleanup());
|
||
// Canvas-size badge updater (kept simple — it lives in the topbar).
|
||
const sizeLabel = document.getElementById('ge-canvas-size');
|
||
function updateSizeLabel() {
|
||
if (sizeLabel) sizeLabel.textContent = `${state.imgWidth}×${state.imgHeight}`;
|
||
}
|
||
updateSizeLabel();
|
||
|
||
const topbar = container.querySelector('.ge-topbar');
|
||
// The Gen control + its "Gen" label span — collapse as a group when
|
||
// narrow. The Inpaint model selector moved into the side panel.
|
||
const aiGroup = [
|
||
container.querySelector('#ge-ai-model'),
|
||
...container.querySelectorAll('.ge-topbar span[style*="font-size:9px"]'),
|
||
].filter(Boolean);
|
||
|
||
// Native popovers keep the existing DOM/event ownership while escaping
|
||
// the toolbar's horizontal scroll clip and the editor's stacking context.
|
||
topbar?.querySelectorAll('.dropdown[hidden]').forEach(menu => {
|
||
if (!menu.showPopover) return;
|
||
const anchor = menu.parentElement.querySelector('button');
|
||
if (!anchor) return;
|
||
menu.setAttribute('popover', 'manual');
|
||
const position = () => {
|
||
const rect = anchor.getBoundingClientRect();
|
||
menu.style.top = `${rect.bottom + 4}px`;
|
||
menu.style.right = `${Math.max(8, window.innerWidth - rect.right)}px`;
|
||
menu.style.left = 'auto';
|
||
};
|
||
const sync = () => {
|
||
if (menu.hidden) {
|
||
if (menu.matches(':popover-open')) menu.hidePopover();
|
||
} else if (menu.isConnected) {
|
||
position();
|
||
if (!menu.matches(':popover-open')) menu.showPopover();
|
||
}
|
||
};
|
||
const observer = new MutationObserver(sync);
|
||
observer.observe(menu, { attributes: true, attributeFilter: ['hidden'] });
|
||
topbar.addEventListener('scroll', position, { passive: true });
|
||
window.addEventListener('resize', position);
|
||
cleanups.push(() => {
|
||
observer.disconnect();
|
||
topbar.removeEventListener('scroll', position);
|
||
window.removeEventListener('resize', position);
|
||
if (menu.matches(':popover-open')) menu.hidePopover();
|
||
});
|
||
});
|
||
|
||
function syncOverflow() {
|
||
if (!topbar) return;
|
||
aiGroup.forEach(el => { el.style.display = ''; });
|
||
if (topbar.scrollWidth > topbar.clientWidth) {
|
||
// Hide AI group first — bulky and least essential at narrow widths.
|
||
aiGroup.forEach(el => { el.style.display = 'none'; });
|
||
}
|
||
}
|
||
|
||
if (topbar && window.ResizeObserver) {
|
||
const ro = new ResizeObserver(() => syncOverflow());
|
||
ro.observe(topbar);
|
||
cleanups.push(() => ro.disconnect());
|
||
}
|
||
// Initial pass after layout settles.
|
||
requestAnimationFrame(syncOverflow);
|
||
}
|