Improve space toggles and email warmup
This commit is contained in:
@@ -456,7 +456,8 @@ def setup_email_routes():
|
||||
_IMAP_POOL = {} # account_id → (conn, last_used_at)
|
||||
_IMAP_IDLE_MAX = 60.0
|
||||
_WARMING_READS = set()
|
||||
_WARM_READ_LIMIT = 24
|
||||
_WARM_READ_LIMIT = 3
|
||||
_WARM_MAX_BYTES = 128 * 1024
|
||||
_WARM_RECENT_SECONDS = 7 * 24 * 60 * 60
|
||||
_pool_lock = _threading.Lock()
|
||||
|
||||
@@ -1322,9 +1323,16 @@ def setup_email_routes():
|
||||
epoch = 0
|
||||
if epoch and now - epoch > _WARM_RECENT_SECONDS:
|
||||
continue
|
||||
try:
|
||||
size = int((em or {}).get("size") or 0)
|
||||
except Exception:
|
||||
size = 0
|
||||
if size > _WARM_MAX_BYTES:
|
||||
continue
|
||||
ck = _read_cache_key(account_id, folder, uid, owner=owner)
|
||||
if _read_cache_get(ck) is not None or ck in _WARMING_READS:
|
||||
continue
|
||||
_WARMING_READS.add(ck)
|
||||
selected.append((uid, ck))
|
||||
if len(selected) >= _WARM_READ_LIMIT:
|
||||
break
|
||||
@@ -1334,8 +1342,8 @@ def setup_email_routes():
|
||||
async def _warm():
|
||||
for uid, ck in selected:
|
||||
if _read_cache_get(ck) is not None:
|
||||
_WARMING_READS.discard(ck)
|
||||
continue
|
||||
_WARMING_READS.add(ck)
|
||||
try:
|
||||
result = await _asyncio.to_thread(_read_email_sync, uid, folder, account_id, owner, False)
|
||||
if result and not result.get("error"):
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
import { previewZoneAt, clearPreview, snapModalToZone } from './tileManager.js';
|
||||
import { suspendDock, resumeDock, clearRightDock, applyEdgeDock } from './modalSnap.js';
|
||||
|
||||
const _state = new Map(); // id -> { restoreFn, closeFn, railBtnId, isMinimized }
|
||||
const _state = new Map(); // id -> { restoreFn, closeFn, railBtnId, isMinimized, restoreMinHeight }
|
||||
|
||||
const _rememberedDockKey = (id) => `odysseus-modal-remembered-dock-${id}`;
|
||||
function _rememberDock(id, side) {
|
||||
@@ -73,6 +73,26 @@ function _emitModalOpened(id, modal) {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function _captureRestoreHeight(modal, state) {
|
||||
if (!modal || !state) return;
|
||||
const content = modal.querySelector('.modal-content');
|
||||
if (!content) return;
|
||||
const rect = content.getBoundingClientRect();
|
||||
if (!rect || rect.height < 120) return;
|
||||
const maxHeight = Math.max(180, window.innerHeight - 24);
|
||||
state.restoreMinHeight = `${Math.round(Math.min(rect.height, maxHeight))}px`;
|
||||
}
|
||||
|
||||
function _applyRestoreHeight(modal, state) {
|
||||
if (!modal || !state?.restoreMinHeight) return;
|
||||
const content = modal.querySelector('.modal-content');
|
||||
if (!content) return;
|
||||
const maxHeight = Math.max(180, window.innerHeight - 24);
|
||||
const requested = parseInt(state.restoreMinHeight, 10);
|
||||
const height = Number.isFinite(requested) ? Math.min(requested, maxHeight) : null;
|
||||
if (height) content.style.minHeight = `${height}px`;
|
||||
}
|
||||
|
||||
function _setBadge(btnIds, on) {
|
||||
if (!btnIds) return;
|
||||
const ids = Array.isArray(btnIds) ? btnIds : [btnIds];
|
||||
@@ -1109,6 +1129,7 @@ export function register(id, { restoreFn, closeFn, railBtnId, sidebarBtnId, labe
|
||||
closeFn: closeFn || (() => {}),
|
||||
btnIds,
|
||||
isMinimized: false,
|
||||
restoreMinHeight: '',
|
||||
});
|
||||
// Auto-stack: whichever modal becomes visible last sits on top of any
|
||||
// already-open modals. The various tool open() functions (gallery,
|
||||
@@ -1188,6 +1209,7 @@ export function minimize(id) {
|
||||
// and let the chip drive restore/close via the registered functions.
|
||||
const modal = document.getElementById(id);
|
||||
if (modal) {
|
||||
_captureRestoreHeight(modal, s);
|
||||
// If this window is edge-docked (right/left), SUSPEND the dock: release
|
||||
// the body push so the chat returns to full width while the window is
|
||||
// minimized, but keep the dock so restoring the chip snaps it back in.
|
||||
@@ -1218,6 +1240,7 @@ export function restore(id) {
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden', 'modal-minimized');
|
||||
modal.style.display = '';
|
||||
_applyRestoreHeight(modal, s);
|
||||
// Surface above any already-open tool window — restoring from the dock
|
||||
// should bring this tool to the front, not leave it stuck behind one with
|
||||
// a higher static z-index.
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
*/
|
||||
|
||||
import themeModule from './theme.js';
|
||||
import * as Modals from './modalManager.js';
|
||||
|
||||
let toastEl = null;
|
||||
let autoScrollEnabled = true;
|
||||
let hoveredToggleCard = null;
|
||||
let hoveredToggleWindow = null;
|
||||
let hoveredDockChip = null;
|
||||
|
||||
// Smooth scroll state
|
||||
let _scrollRafId = null;
|
||||
@@ -19,25 +22,92 @@ function _isTextEditingTarget(target) {
|
||||
return !!(el && el.closest('input, textarea, select, [contenteditable="true"], [contenteditable=""]'));
|
||||
}
|
||||
|
||||
const SPACE_CARD_SELECTOR = [
|
||||
'#email-lib-modal .doclib-card',
|
||||
'#doclib-modal .doclib-card',
|
||||
'#memory-modal .doclib-card',
|
||||
'#tasks-modal .task-card',
|
||||
'#tasks-modal .task-log-row',
|
||||
'#research-overlay [data-job-id]',
|
||||
'#cookbook-modal .doclib-card',
|
||||
'.email-reader-tab-modal .doclib-card',
|
||||
'.email-window-modal .doclib-card',
|
||||
].join(', ');
|
||||
|
||||
const SPACE_BLOCKED_SELECTOR = [
|
||||
'button',
|
||||
'a',
|
||||
'input',
|
||||
'textarea',
|
||||
'select',
|
||||
'[contenteditable="true"]',
|
||||
'[contenteditable=""]',
|
||||
'.recipient-chip',
|
||||
'.doclib-card-dropdown',
|
||||
'.email-card-dropdown',
|
||||
'.task-log-row-actions',
|
||||
'.modal-header',
|
||||
].join(', ');
|
||||
|
||||
function _visibleModalForSpace(win) {
|
||||
const modal = win?.closest?.('.modal[id]');
|
||||
if (!modal || modal.classList.contains('hidden') || modal.classList.contains('modal-minimized')) return null;
|
||||
return modal;
|
||||
}
|
||||
|
||||
function _isSpaceVisible(el) {
|
||||
if (!el || !document.contains(el)) return false;
|
||||
if (el.closest?.('.modal.hidden, .modal.modal-minimized, [hidden]')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function _spaceWindowId(win) {
|
||||
if (!win || !document.contains(win)) return null;
|
||||
const modal = _visibleModalForSpace(win);
|
||||
if (modal && Modals.isRegistered(modal.id)) return modal.id;
|
||||
if (win.closest?.('.doc-editor-pane') && Modals.isRegistered('doc-panel') && !Modals.isMinimized('doc-panel')) return 'doc-panel';
|
||||
return null;
|
||||
}
|
||||
|
||||
function _initHoverCardSpaceToggle() {
|
||||
if (document._odysseusHoverCardSpaceToggle) return;
|
||||
document._odysseusHoverCardSpaceToggle = true;
|
||||
document.addEventListener('pointerover', (e) => {
|
||||
const card = e.target?.closest?.('#email-lib-modal .doclib-card, #doclib-modal .doclib-card, .email-reader-tab-modal .doclib-card, .email-window-modal .doclib-card');
|
||||
const chip = e.target?.closest?.('.minimized-dock-chip[data-modal-id]');
|
||||
if (chip) hoveredDockChip = chip;
|
||||
const card = e.target?.closest?.(SPACE_CARD_SELECTOR);
|
||||
if (card) hoveredToggleCard = card;
|
||||
const win = e.target?.closest?.('.modal:not(.hidden):not(.modal-minimized) .modal-content, .doc-editor-pane');
|
||||
if (win) hoveredToggleWindow = win;
|
||||
}, true);
|
||||
document.addEventListener('pointerout', (e) => {
|
||||
if (!hoveredToggleCard) return;
|
||||
const next = e.relatedTarget;
|
||||
if (!next || !hoveredToggleCard.contains(next)) hoveredToggleCard = null;
|
||||
if (hoveredDockChip && (!next || !hoveredDockChip.contains(next))) hoveredDockChip = null;
|
||||
if (hoveredToggleCard && (!next || !hoveredToggleCard.contains(next))) hoveredToggleCard = null;
|
||||
if (hoveredToggleWindow && (!next || !hoveredToggleWindow.contains(next))) hoveredToggleWindow = null;
|
||||
}, true);
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.code !== 'Space' || e.repeat || !hoveredToggleCard || !document.contains(hoveredToggleCard)) return;
|
||||
if (e.code !== 'Space' || e.repeat) return;
|
||||
if (_isTextEditingTarget(e.target)) return;
|
||||
const blocked = e.target?.closest?.('button, a, input, textarea, select, [contenteditable="true"], [contenteditable=""], .recipient-chip, .doclib-card-dropdown, .email-card-dropdown');
|
||||
const blocked = e.target?.closest?.(SPACE_BLOCKED_SELECTOR);
|
||||
if (blocked) return;
|
||||
if (hoveredToggleCard && _isSpaceVisible(hoveredToggleCard)) {
|
||||
e.preventDefault();
|
||||
hoveredToggleCard.click();
|
||||
return;
|
||||
}
|
||||
if (hoveredDockChip && document.contains(hoveredDockChip)) {
|
||||
const id = hoveredDockChip.dataset.modalId;
|
||||
if (id && Modals.isRegistered(id)) {
|
||||
e.preventDefault();
|
||||
Modals.restore(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const id = _spaceWindowId(hoveredToggleWindow);
|
||||
if (!id) return;
|
||||
e.preventDefault();
|
||||
hoveredToggleCard.click();
|
||||
Modals.minimize(id);
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user