feat: refactor queue panel and split layout with deferred reveal and pointer events
- Add PointerEvent support for queue toggle, edge drag, and splitter interactions - Implement deferred queue panel reveal animation (mr-queue-opening class) - Make queue panel updates demand-driven; skip refresh when panel is hidden - Replace per-item remove listeners with event delegation on queue list - Track queue split container width via ResizeObserver with safe fallbacks - Centralize split flex application and persist queue width/collapsed state with debounce - Fix missing browser split pane styles to use helper-track CSS variables - Add deferred UI cleanup for missing browser split drag end - Add prewarm frame fallback when ResizeObserver is unavailable
This commit is contained in:
@@ -502,10 +502,13 @@
|
||||
--mr-missing-line: rgba(255, 255, 255, 0.075);
|
||||
--mr-missing-line-strong: rgba(255, 255, 255, 0.13);
|
||||
--mr-missing-radius: 6px;
|
||||
--mr-missing-splitter-track: 10px;
|
||||
--mr-missing-list-track: calc(100% - var(--mr-missing-splitter-track) - var(--mr-missing-detail-track, 42%));
|
||||
display: flex;
|
||||
gap: 0;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--mr-missing-line);
|
||||
background: var(--mr-missing-bg);
|
||||
@@ -517,12 +520,12 @@
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
contain: layout paint style;
|
||||
contain: size layout paint style;
|
||||
background: var(--mr-missing-bg);
|
||||
}
|
||||
|
||||
.mr-missing-list-pane {
|
||||
flex: 1 1 0;
|
||||
flex: 0 0 var(--mr-missing-list-track);
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
border-right: 0;
|
||||
@@ -531,9 +534,9 @@
|
||||
|
||||
.mr-missing-browser-splitter {
|
||||
position: relative;
|
||||
flex: 0 0 10px;
|
||||
min-width: 10px;
|
||||
width: 10px;
|
||||
flex: 0 0 var(--mr-missing-splitter-track);
|
||||
min-width: var(--mr-missing-splitter-track);
|
||||
width: var(--mr-missing-splitter-track);
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
|
||||
@@ -179,16 +179,19 @@
|
||||
padding: 8px 6px 6px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--mr-bg, #121212);
|
||||
}
|
||||
|
||||
#model-resolver-content {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
contain: layout paint style;
|
||||
min-width: 0;
|
||||
contain: size layout paint style;
|
||||
background: var(--mr-bg, #222);
|
||||
}
|
||||
|
||||
@@ -196,14 +199,34 @@
|
||||
width: 320px;
|
||||
min-width: 240px;
|
||||
max-width: 70%;
|
||||
flex: 0 0 320px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
contain: layout paint style;
|
||||
contain: size layout paint style;
|
||||
background: var(--mr-bg, #121212);
|
||||
}
|
||||
|
||||
#model-resolver-queue.mr-queue-opening .mr-queue-stack {
|
||||
content-visibility: hidden;
|
||||
contain-intrinsic-size: 320px 420px;
|
||||
}
|
||||
|
||||
#model-resolver-body.mr-queue-opening-layout #model-resolver-queue {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 6px;
|
||||
bottom: 6px;
|
||||
z-index: 998;
|
||||
box-shadow: -12px 0 24px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
#model-resolver-body.mr-queue-opening-layout #model-resolver-splitter {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#model-resolver-body.mr-is-resizing-queue #model-resolver-content,
|
||||
#model-resolver-body.mr-is-resizing-queue #model-resolver-queue {
|
||||
pointer-events: none;
|
||||
@@ -292,6 +315,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export const queueMethods = {
|
||||
body.appendChild(this.contentElement);
|
||||
body.appendChild(this.splitterElement);
|
||||
body.appendChild(this.queueElement);
|
||||
this.observeQueueSplitContainer(body);
|
||||
|
||||
// Restore saved queue width and wire splitter
|
||||
try {
|
||||
@@ -36,13 +37,16 @@ export const queueMethods = {
|
||||
const w = parseInt(savedSplit, 10);
|
||||
if (!isNaN(w) && w > 0) {
|
||||
this.queueElement.style.width = `${w}px`;
|
||||
this.queueElement.style.flexBasis = `${w}px`;
|
||||
this.queueElement.style.flexGrow = '0';
|
||||
this.queueElement.style.flexShrink = '0';
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
try {
|
||||
const onSplitMouseDown = (e) => this.startSplitDrag(e);
|
||||
this.splitterElement.addEventListener('mousedown', onSplitMouseDown);
|
||||
this.splitterElement.addEventListener(typeof PointerEvent === 'function' ? 'pointerdown' : 'mousedown', onSplitMouseDown);
|
||||
this._splitterMouseDown = onSplitMouseDown;
|
||||
} catch (e) { }
|
||||
|
||||
@@ -52,9 +56,12 @@ export const queueMethods = {
|
||||
id: "queue-toggle-icon",
|
||||
type: "button",
|
||||
"aria-label": "Toggle queued selections",
|
||||
onclick: (event) => this.onQueueEdgeClick(event),
|
||||
onmousedown: (event) => this.startQueueEdgeDrag(event)
|
||||
onclick: (event) => this.onQueueEdgeClick(event)
|
||||
});
|
||||
this.queueToggleIcon.addEventListener(
|
||||
typeof PointerEvent === 'function' ? 'pointerdown' : 'mousedown',
|
||||
(event) => this.startQueueEdgeDrag(event)
|
||||
);
|
||||
body.appendChild(this.queueToggleIcon);
|
||||
this.updateQueueToggleIcon();
|
||||
} catch (e) { }
|
||||
@@ -62,7 +69,7 @@ export const queueMethods = {
|
||||
// Restore queue collapsed state
|
||||
try {
|
||||
const col = localStorage.getItem('model_resolver_queue_collapsed');
|
||||
if (col === '1') this.setQueueCollapsed(true);
|
||||
if (col === '1') this.setQueueCollapsed(true, { persist: false, updatePanel: false });
|
||||
} catch (e) { }
|
||||
|
||||
return body;
|
||||
@@ -105,6 +112,17 @@ export const queueMethods = {
|
||||
|
||||
// Scrollable list
|
||||
this.queueList = $el("div#queue-list.mr-queue-list");
|
||||
this.queueList.addEventListener('click', (event) => {
|
||||
const button = event.target instanceof Element
|
||||
? event.target.closest('.mr-queue-remove')
|
||||
: null;
|
||||
if (!(button instanceof HTMLElement)) return;
|
||||
const index = Number.parseInt(button.dataset.queueIndex || '-1', 10);
|
||||
if (!Number.isInteger(index) || index < 0) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.removeQueuedByIndex(index);
|
||||
});
|
||||
|
||||
const panel = $el("div.mr-queue-panel", {}, [
|
||||
$el("div.mr-queue-stack", {}, [this.queueTabs, this.queueHeader, this.queueList])
|
||||
@@ -126,16 +144,21 @@ export const queueMethods = {
|
||||
this.updateQueuePanel();
|
||||
},
|
||||
|
||||
updateQueuePanel() {
|
||||
updateQueuePanel({ force = false } = {}) {
|
||||
if (!this.queueList || !this.queueHeader) return;
|
||||
if (this._queuePanelUpdateTimer) {
|
||||
clearTimeout(this._queuePanelUpdateTimer);
|
||||
this._queuePanelUpdateTimer = null;
|
||||
}
|
||||
if (!force && !this.isQueuePanelVisible()) {
|
||||
this._queuePanelDirty = true;
|
||||
return;
|
||||
}
|
||||
this._queuePanelDirty = false;
|
||||
const list = Array.isArray(this.pendingResolutions) ? this.pendingResolutions : [];
|
||||
const downloads = this.getActiveQueuePanelDownloads();
|
||||
const history = this.getDownloadHistory();
|
||||
const activeTab = this.queuePanelActiveTab === 'downloads' ? 'downloads' : 'queued';
|
||||
const history = activeTab === 'downloads' ? this.getDownloadHistory() : [];
|
||||
const clearBtn = this.queueClearButton || this.queueHeader.querySelector('#queue-clear');
|
||||
const showActions = activeTab === 'queued';
|
||||
this.queueHeader.style.display = showActions ? '' : 'none';
|
||||
@@ -150,15 +173,140 @@ export const queueMethods = {
|
||||
this.renderQueuedSelections(list);
|
||||
},
|
||||
|
||||
requestQueuePanelUpdate(delayMs = 180) {
|
||||
requestQueuePanelUpdate(delayMs = 180, { force = false } = {}) {
|
||||
if (!this.queueList || !this.queueHeader) return;
|
||||
if (!force && !this.isQueuePanelVisible()) {
|
||||
this._queuePanelDirty = true;
|
||||
return;
|
||||
}
|
||||
if (this._queuePanelUpdateTimer) return;
|
||||
this._queuePanelUpdateTimer = setTimeout(() => {
|
||||
this._queuePanelUpdateTimer = null;
|
||||
this.updateQueuePanel();
|
||||
this.updateQueuePanel({ force });
|
||||
}, delayMs);
|
||||
},
|
||||
|
||||
isQueuePanelVisible() {
|
||||
return Boolean(
|
||||
this.queueElement
|
||||
&& this.activeTab === 'missing'
|
||||
&& !this.queueCollapsed
|
||||
&& this.queueElement.style.display !== 'none'
|
||||
);
|
||||
},
|
||||
|
||||
scheduleQueuePanelOpenUpdate(delayMs = 80) {
|
||||
if (!this.queueElement || !this.queueList || !this.queueHeader) return;
|
||||
if (this._queuePanelOpenFrame) {
|
||||
cancelAnimationFrame(this._queuePanelOpenFrame);
|
||||
this._queuePanelOpenFrame = null;
|
||||
}
|
||||
if (this._queuePanelOpenTimer) {
|
||||
clearTimeout(this._queuePanelOpenTimer);
|
||||
this._queuePanelOpenTimer = null;
|
||||
}
|
||||
|
||||
const runUpdate = () => {
|
||||
this._queuePanelOpenTimer = null;
|
||||
document.getElementById('model-resolver-body')?.classList.remove('mr-queue-opening-layout');
|
||||
this.updateQueuePanel({ force: true });
|
||||
this.queueElement?.classList?.remove('mr-queue-opening');
|
||||
this.stabilizeQueueSplitLayout();
|
||||
};
|
||||
const scheduleTimer = () => {
|
||||
this._queuePanelOpenFrame = null;
|
||||
this._queuePanelOpenTimer = setTimeout(runUpdate, delayMs);
|
||||
};
|
||||
|
||||
this._queuePanelOpenFrame = requestAnimationFrame(scheduleTimer);
|
||||
},
|
||||
|
||||
scheduleQueuePanelReveal() {
|
||||
if (!this.queueElement || !this.queueList || !this.queueHeader) return;
|
||||
this.cancelQueuePanelReveal();
|
||||
|
||||
const reveal = () => {
|
||||
this._queuePanelRevealFrame = null;
|
||||
if (this.queueCollapsed || this.activeTab !== 'missing') return;
|
||||
this.queueElement?.classList?.add('mr-queue-opening');
|
||||
document.getElementById('model-resolver-body')?.classList.add('mr-queue-opening-layout');
|
||||
this.updateQueueVisibility();
|
||||
this.scheduleQueuePanelOpenUpdate();
|
||||
};
|
||||
|
||||
this._queuePanelRevealFrame = requestAnimationFrame(() => {
|
||||
this._queuePanelRevealFrame = requestAnimationFrame(reveal);
|
||||
});
|
||||
},
|
||||
|
||||
cancelQueuePanelReveal() {
|
||||
if (this._queuePanelRevealFrame) {
|
||||
cancelAnimationFrame(this._queuePanelRevealFrame);
|
||||
this._queuePanelRevealFrame = null;
|
||||
}
|
||||
},
|
||||
|
||||
cancelQueuePanelOpenUpdate() {
|
||||
this.cancelQueuePanelReveal();
|
||||
if (this._queuePanelOpenFrame) {
|
||||
cancelAnimationFrame(this._queuePanelOpenFrame);
|
||||
this._queuePanelOpenFrame = null;
|
||||
}
|
||||
if (this._queuePanelOpenTimer) {
|
||||
clearTimeout(this._queuePanelOpenTimer);
|
||||
this._queuePanelOpenTimer = null;
|
||||
}
|
||||
this.queueElement?.classList?.remove('mr-queue-opening');
|
||||
document.getElementById('model-resolver-body')?.classList.remove('mr-queue-opening-layout');
|
||||
},
|
||||
|
||||
clearQueueContentSplitFlex() {
|
||||
if (!(this.contentElement instanceof HTMLElement)) return;
|
||||
this.contentElement.style.removeProperty('flex-basis');
|
||||
this.contentElement.style.removeProperty('flex-grow');
|
||||
this.contentElement.style.removeProperty('flex-shrink');
|
||||
},
|
||||
|
||||
persistQueueCollapsedState(collapsed, delayMs = 250) {
|
||||
if (this._queueCollapsedPersistTimer) {
|
||||
clearTimeout(this._queueCollapsedPersistTimer);
|
||||
this._queueCollapsedPersistTimer = null;
|
||||
}
|
||||
const writeState = () => {
|
||||
this._queueCollapsedPersistTimer = null;
|
||||
try {
|
||||
localStorage.setItem('model_resolver_queue_collapsed', collapsed ? '1' : '0');
|
||||
} catch (e) { }
|
||||
};
|
||||
const delay = Math.max(0, Number(delayMs) || 0);
|
||||
if (delay > 0) {
|
||||
this._queueCollapsedPersistTimer = setTimeout(writeState, delay);
|
||||
} else {
|
||||
writeState();
|
||||
}
|
||||
},
|
||||
|
||||
persistQueueSplitWidth(width, delayMs = 250) {
|
||||
const nextWidth = Math.round(Number(width));
|
||||
if (!Number.isFinite(nextWidth) || nextWidth <= 0) return;
|
||||
if (this._queueSplitPersistTimer) {
|
||||
clearTimeout(this._queueSplitPersistTimer);
|
||||
this._queueSplitPersistTimer = null;
|
||||
}
|
||||
const writeWidth = () => {
|
||||
this._queueSplitPersistTimer = null;
|
||||
try {
|
||||
localStorage.setItem('model_resolver_split_w', String(nextWidth));
|
||||
} catch (e) { }
|
||||
};
|
||||
const delay = Math.max(0, Number(delayMs) || 0);
|
||||
if (delay > 0) {
|
||||
this._queueSplitPersistTimer = setTimeout(writeWidth, delay);
|
||||
} else {
|
||||
writeWidth();
|
||||
}
|
||||
},
|
||||
|
||||
updateQueuePanelTabs(queueCount, downloadCount, activeTab) {
|
||||
const queuedTab = this.queueQueuedTabButton || this.queueTabs?.querySelector?.('.mr-queue-tab[data-tab="queued"]');
|
||||
const downloadsTab = this.queueDownloadsTabButton || this.queueTabs?.querySelector?.('.mr-queue-tab[data-tab="downloads"]');
|
||||
@@ -194,25 +342,15 @@ export const queueMethods = {
|
||||
const label = (r.resolved_model?.relative_path || r.resolved_model?.filename || r.resolved_path || '').toString();
|
||||
const nodeLabel = r.node_label || r.node_type || (r.subgraph_id ? 'Subgraph' : 'Node');
|
||||
const orig = (r.original_path || '').toString();
|
||||
const rmId = `queue-remove-${i}`;
|
||||
html += `<div class="mr-queue-item">`;
|
||||
html += `<div class="mr-queue-item-title">${this.escapeHtml(nodeLabel)} #${this.escapeHtml(String(r.node_id))}</div>`;
|
||||
html += `<div class="mr-queue-item-meta"><span>Original</span><code>${this.escapeHtml(orig)}</code></div>`;
|
||||
html += `<div class="mr-queue-item-selection"><span>Selected</span><code>${this.escapeHtml(label)}</code></div>`;
|
||||
html += `<div class="mr-queue-item-actions"><button id="${rmId}" class="mr-btn mr-btn-danger mr-btn-sm">Remove</button></div>`;
|
||||
html += `<div class="mr-queue-item-actions"><button type="button" class="mr-btn mr-btn-danger mr-btn-sm mr-queue-remove" data-queue-index="${this.escapeHtml(String(i))}">Remove</button></div>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
this.queueList.innerHTML = html;
|
||||
|
||||
// Wire remove buttons
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const rmId = `queue-remove-${i}`;
|
||||
const btn = this.queueList.querySelector(`#${rmId}`);
|
||||
if (btn) {
|
||||
btn.addEventListener('click', () => this.removeQueuedByIndex(i));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getActiveQueuePanelDownloads() {
|
||||
@@ -1470,15 +1608,34 @@ export const queueMethods = {
|
||||
this.setQueueCollapsed(!this.queueCollapsed);
|
||||
},
|
||||
|
||||
setQueueCollapsed(collapsed) {
|
||||
this.queueCollapsed = !!collapsed;
|
||||
if (this.queueCollapsed) {
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '1'); } catch (e) { }
|
||||
setQueueCollapsed(collapsed, { persist = true, updatePanel = true, deferOpen = false } = {}) {
|
||||
const nextCollapsed = !!collapsed;
|
||||
const wasCollapsed = !!this.queueCollapsed;
|
||||
const opening = wasCollapsed && !nextCollapsed;
|
||||
this.queueCollapsed = nextCollapsed;
|
||||
if (persist) {
|
||||
this.persistQueueCollapsedState(this.queueCollapsed, opening ? 500 : 0);
|
||||
}
|
||||
if (opening && deferOpen) {
|
||||
this.updateQueueToggleIcon();
|
||||
this.scheduleQueuePanelReveal();
|
||||
return;
|
||||
}
|
||||
if (opening) {
|
||||
this.queueElement?.classList?.add('mr-queue-opening');
|
||||
document.getElementById('model-resolver-body')?.classList.add('mr-queue-opening-layout');
|
||||
} else {
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '0'); } catch (e) { }
|
||||
this.cancelQueuePanelOpenUpdate();
|
||||
}
|
||||
this.updateQueueVisibility();
|
||||
this.updateQueuePanel();
|
||||
if (!updatePanel) return;
|
||||
if (opening) {
|
||||
this.scheduleQueuePanelOpenUpdate();
|
||||
} else if (this.isQueuePanelVisible()) {
|
||||
this.updateQueuePanel();
|
||||
} else {
|
||||
this._queuePanelDirty = true;
|
||||
}
|
||||
},
|
||||
|
||||
getResolutionQueueKey(resolution = {}) {
|
||||
@@ -1555,6 +1712,11 @@ export const queueMethods = {
|
||||
if (this.queueElement) {
|
||||
this.queueElement.style.display = showQueuePanel ? '' : 'none';
|
||||
}
|
||||
if (!showQueuePanel) {
|
||||
this.clearQueueContentSplitFlex();
|
||||
} else if (!this._splitDragging && !this._queuePanelDirty) {
|
||||
this.stabilizeQueueSplitLayout();
|
||||
}
|
||||
if (this.splitterElement) {
|
||||
this.splitterElement.style.display = showQueuePanel ? '' : 'none';
|
||||
}
|
||||
@@ -1563,6 +1725,11 @@ export const queueMethods = {
|
||||
}
|
||||
|
||||
this.updateQueueToggleIcon();
|
||||
if (showQueuePanel && this._queuePanelDirty) {
|
||||
this.queueElement?.classList?.add('mr-queue-opening');
|
||||
document.getElementById('model-resolver-body')?.classList.add('mr-queue-opening-layout');
|
||||
this.scheduleQueuePanelOpenUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
updateQueueToggleIcon() {
|
||||
@@ -1587,15 +1754,83 @@ export const queueMethods = {
|
||||
this._suppressQueueEdgeClick = false;
|
||||
return;
|
||||
}
|
||||
this.toggleQueueCollapsed();
|
||||
event?.preventDefault?.();
|
||||
event?.stopPropagation?.();
|
||||
this.setQueueCollapsed(!this.queueCollapsed, { deferOpen: true });
|
||||
},
|
||||
|
||||
startQueueEdgeDrag(event) {
|
||||
if (event.button !== 0 || !this.queueCollapsed || !this.queueElement) return;
|
||||
|
||||
const startX = event.clientX;
|
||||
const previousCursor = document.body.style.cursor;
|
||||
event.preventDefault?.();
|
||||
event.stopPropagation?.();
|
||||
this._suppressQueueEdgeClick = true;
|
||||
this.setQueueCollapsed(false, { deferOpen: true });
|
||||
|
||||
this.clearQueueEdgeDragCandidate();
|
||||
const usingPointerEvents = event.type === 'pointerdown';
|
||||
this._queueEdgeDragCandidate = {
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
previousCursor: document.body.style.cursor,
|
||||
openedOnPointerDown: true
|
||||
};
|
||||
this._onQueueEdgeCandidateMove = (moveEvent) => this.onQueueEdgeDragCandidateMove(moveEvent);
|
||||
this._onQueueEdgeCandidateUp = () => {
|
||||
const shouldClearSuppress = !!this._queueEdgeDragCandidate?.openedOnPointerDown;
|
||||
this.clearQueueEdgeDragCandidate();
|
||||
if (shouldClearSuppress) {
|
||||
setTimeout(() => { this._suppressQueueEdgeClick = false; }, 0);
|
||||
}
|
||||
};
|
||||
this._queueEdgeCandidateMoveEvent = usingPointerEvents ? 'pointermove' : 'mousemove';
|
||||
this._queueEdgeCandidateUpEvent = usingPointerEvents ? 'pointerup' : 'mouseup';
|
||||
this._queueEdgeCandidateCancelEvent = usingPointerEvents ? 'pointercancel' : null;
|
||||
document.addEventListener(this._queueEdgeCandidateMoveEvent, this._onQueueEdgeCandidateMove, true);
|
||||
document.addEventListener(this._queueEdgeCandidateUpEvent, this._onQueueEdgeCandidateUp, { once: true, capture: true });
|
||||
if (this._queueEdgeCandidateCancelEvent) {
|
||||
document.addEventListener(this._queueEdgeCandidateCancelEvent, this._onQueueEdgeCandidateUp, { once: true, capture: true });
|
||||
}
|
||||
},
|
||||
|
||||
onQueueEdgeDragCandidateMove(event) {
|
||||
const candidate = this._queueEdgeDragCandidate;
|
||||
if (!candidate) return;
|
||||
|
||||
const dx = event.clientX - candidate.startX;
|
||||
const dy = event.clientY - candidate.startY;
|
||||
if ((dx * dx) + (dy * dy) < 16) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.clearQueueEdgeDragCandidate();
|
||||
this._suppressQueueEdgeClick = true;
|
||||
this.beginQueueEdgeDrag(candidate.startX, candidate.previousCursor, event);
|
||||
this.onSplitDrag(event);
|
||||
},
|
||||
|
||||
clearQueueEdgeDragCandidate() {
|
||||
if (this._onQueueEdgeCandidateMove && this._queueEdgeCandidateMoveEvent) {
|
||||
document.removeEventListener(this._queueEdgeCandidateMoveEvent, this._onQueueEdgeCandidateMove, true);
|
||||
}
|
||||
if (this._onQueueEdgeCandidateUp && this._queueEdgeCandidateUpEvent) {
|
||||
document.removeEventListener(this._queueEdgeCandidateUpEvent, this._onQueueEdgeCandidateUp, true);
|
||||
}
|
||||
if (this._onQueueEdgeCandidateUp && this._queueEdgeCandidateCancelEvent) {
|
||||
document.removeEventListener(this._queueEdgeCandidateCancelEvent, this._onQueueEdgeCandidateUp, true);
|
||||
}
|
||||
this._queueEdgeDragCandidate = null;
|
||||
this._onQueueEdgeCandidateMove = null;
|
||||
this._onQueueEdgeCandidateUp = null;
|
||||
this._queueEdgeCandidateMoveEvent = null;
|
||||
this._queueEdgeCandidateUpEvent = null;
|
||||
this._queueEdgeCandidateCancelEvent = null;
|
||||
},
|
||||
|
||||
beginQueueEdgeDrag(startX, previousCursor = document.body.style.cursor, sourceEvent = null) {
|
||||
const dragStartX = Number(startX);
|
||||
if (!Number.isFinite(dragStartX)) return;
|
||||
const usePointerEvents = String(sourceEvent?.type || '').startsWith('pointer');
|
||||
document.body.style.cursor = 'col-resize';
|
||||
this._suppressQueueEdgeClick = true;
|
||||
this.splitterElement.style.display = '';
|
||||
@@ -1603,56 +1838,258 @@ export const queueMethods = {
|
||||
this.queueElement.style.display = 'none';
|
||||
this.queueElement.style.width = '0px';
|
||||
this.startSplitDrag(
|
||||
{ clientX: startX, preventDefault: () => {} },
|
||||
{
|
||||
clientX: dragStartX,
|
||||
type: usePointerEvents ? 'pointerdown' : 'mousedown',
|
||||
pointerId: sourceEvent?.pointerId,
|
||||
preventDefault: () => {},
|
||||
stopPropagation: () => {}
|
||||
},
|
||||
{ startWidth: 0, edgeOpen: true, previousCursor, startCollapsedPreview: true }
|
||||
);
|
||||
},
|
||||
|
||||
getQueueSplitBody() {
|
||||
return document.getElementById('model-resolver-body');
|
||||
},
|
||||
|
||||
observeQueueSplitContainer(body = this.getQueueSplitBody()) {
|
||||
if (!(body instanceof HTMLElement)) return;
|
||||
|
||||
const rememberWidth = (width) => {
|
||||
const nextWidth = Number(width);
|
||||
if (!Number.isFinite(nextWidth) || nextWidth <= 0) return;
|
||||
this._queueSplitLastContainerWidth = nextWidth;
|
||||
if (!this._splitDragging && this.isQueuePanelVisible?.() && !this._queuePanelDirty) {
|
||||
this.stabilizeQueueSplitLayout({ containerWidth: nextWidth });
|
||||
}
|
||||
};
|
||||
|
||||
this._queueSplitFallbackContainerWidth = Number(window.innerWidth || 720);
|
||||
if (this._queueSplitResizeObserver) {
|
||||
try { this._queueSplitResizeObserver.disconnect(); } catch (e) { }
|
||||
this._queueSplitResizeObserver = null;
|
||||
}
|
||||
|
||||
if (typeof ResizeObserver === 'function') {
|
||||
this._queueSplitResizeObserver = new ResizeObserver((entries) => {
|
||||
rememberWidth(entries?.[0]?.contentRect?.width);
|
||||
});
|
||||
this._queueSplitResizeObserver.observe(body);
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
rememberWidth(body.clientWidth || body.getBoundingClientRect().width);
|
||||
});
|
||||
},
|
||||
|
||||
getQueueSplitContainerWidth(body = null, { allowMeasure = false } = {}) {
|
||||
const cached = Number(this._queueSplitLastContainerWidth);
|
||||
if (Number.isFinite(cached) && cached > 0) return cached;
|
||||
|
||||
if (allowMeasure && body instanceof HTMLElement) {
|
||||
const measured = Number(body.clientWidth || body.getBoundingClientRect().width);
|
||||
if (Number.isFinite(measured) && measured > 0) {
|
||||
this._queueSplitLastContainerWidth = measured;
|
||||
return measured;
|
||||
}
|
||||
}
|
||||
|
||||
const width = Number(this._queueSplitFallbackContainerWidth || window.innerWidth || 0);
|
||||
const safeWidth = Number.isFinite(width) && width > 0 ? width : 720;
|
||||
this._queueSplitLastContainerWidth = safeWidth;
|
||||
return safeWidth;
|
||||
},
|
||||
|
||||
getQueueSplitterWidth() {
|
||||
const cached = Number(this._queueSplitterWidth);
|
||||
if (Number.isFinite(cached) && cached > 0) return cached;
|
||||
|
||||
this._queueSplitterWidth = 10;
|
||||
return 10;
|
||||
},
|
||||
|
||||
getQueueSplitBounds(containerWidth, { edgeOpen = false } = {}) {
|
||||
const safeWidth = Math.max(1, Number(containerWidth) || 1);
|
||||
const splitterWidth = this.getQueueSplitterWidth();
|
||||
const available = Math.max(1, safeWidth - splitterWidth);
|
||||
const min = edgeOpen ? 0 : 56;
|
||||
const max = Math.max(min, Math.floor(available - 360));
|
||||
return { min, max, available };
|
||||
},
|
||||
|
||||
getQueuePaneWidth(element, { allowMeasure = false } = {}) {
|
||||
if (!(element instanceof HTMLElement)) return null;
|
||||
|
||||
const flexBasis = String(element.style.flexBasis || '').trim();
|
||||
if (flexBasis.endsWith('px')) {
|
||||
const width = parseFloat(flexBasis);
|
||||
if (Number.isFinite(width) && width >= 0) return width;
|
||||
}
|
||||
|
||||
const inlineWidth = String(element.style.width || '').trim();
|
||||
if (inlineWidth.endsWith('px')) {
|
||||
const width = parseFloat(inlineWidth);
|
||||
if (Number.isFinite(width) && width >= 0) return width;
|
||||
}
|
||||
|
||||
if (!allowMeasure) return null;
|
||||
|
||||
const measured = Number(element.offsetWidth || element.getBoundingClientRect().width || 0);
|
||||
return Number.isFinite(measured) && measured >= 0 ? measured : null;
|
||||
},
|
||||
|
||||
applyQueueSplitWidth(width, { bounds = null, containerWidth = null, skipIfUnchanged = false } = {}) {
|
||||
if (!(this.queueElement instanceof HTMLElement) || !(this.contentElement instanceof HTMLElement)) return false;
|
||||
const resolvedBounds = bounds || this.getQueueSplitBounds(containerWidth || this.getQueueSplitContainerWidth(), {
|
||||
edgeOpen: this._splitEdgeOpen
|
||||
});
|
||||
const nextWidth = Math.round(Math.max(resolvedBounds.min, Math.min(resolvedBounds.max, Number(width) || 0)));
|
||||
const contentWidth = Math.max(1, Math.round(resolvedBounds.available - nextWidth));
|
||||
const queueValue = `${nextWidth}px`;
|
||||
const contentValue = `${contentWidth}px`;
|
||||
|
||||
if (
|
||||
skipIfUnchanged
|
||||
&& this.queueElement.style.flexBasis === queueValue
|
||||
&& this.contentElement.style.flexBasis === contentValue
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.contentElement.style.flexBasis = contentValue;
|
||||
this.contentElement.style.flexGrow = '0';
|
||||
this.contentElement.style.flexShrink = '0';
|
||||
this.queueElement.style.width = queueValue;
|
||||
this.queueElement.style.flexBasis = queueValue;
|
||||
this.queueElement.style.flexGrow = '0';
|
||||
this.queueElement.style.flexShrink = '0';
|
||||
this._appliedSplitWidth = nextWidth;
|
||||
return true;
|
||||
},
|
||||
|
||||
stabilizeQueueSplitLayout({ containerWidth = null } = {}) {
|
||||
if (!this.isQueuePanelVisible() || this._splitDragging) return false;
|
||||
const measuredContainerWidth = Number(containerWidth);
|
||||
this._queueSplitLastContainerWidth = Number.isFinite(measuredContainerWidth) && measuredContainerWidth > 0
|
||||
? measuredContainerWidth
|
||||
: this.getQueueSplitContainerWidth();
|
||||
const bounds = this.getQueueSplitBounds(this._queueSplitLastContainerWidth);
|
||||
const queueWidth = this.getQueuePaneWidth(this.queueElement) || 320;
|
||||
return this.applyQueueSplitWidth(queueWidth, {
|
||||
bounds,
|
||||
containerWidth: this._queueSplitLastContainerWidth,
|
||||
skipIfUnchanged: true
|
||||
});
|
||||
},
|
||||
|
||||
deferQueueSplitReleaseUi(body, pointerId, previousUserSelect, previousCursor, fallbackCursor) {
|
||||
if (this._queueSplitReleaseCleanupFrame) {
|
||||
cancelAnimationFrame(this._queueSplitReleaseCleanupFrame);
|
||||
this._queueSplitReleaseCleanupFrame = null;
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
this._queueSplitReleaseCleanupFrame = null;
|
||||
body?.classList?.remove('mr-is-resizing-queue');
|
||||
body?.classList?.remove('mr-queue-preview-collapsed');
|
||||
if (pointerId !== undefined && this.splitterElement?.releasePointerCapture) {
|
||||
try {
|
||||
this.splitterElement.releasePointerCapture(pointerId);
|
||||
} catch (error) { }
|
||||
}
|
||||
const cursor = previousCursor !== null && previousCursor !== undefined
|
||||
? previousCursor
|
||||
: (fallbackCursor || '');
|
||||
try {
|
||||
document.body.style.userSelect = previousUserSelect || '';
|
||||
document.body.style.cursor = cursor;
|
||||
} catch (e) { }
|
||||
};
|
||||
|
||||
this._queueSplitReleaseCleanupFrame = requestAnimationFrame(() => {
|
||||
this._queueSplitReleaseCleanupFrame = requestAnimationFrame(restore);
|
||||
});
|
||||
},
|
||||
|
||||
// Begin split drag for resizable panels
|
||||
startSplitDrag(e, { startWidth = null, edgeOpen = false, previousCursor = null, startCollapsedPreview = false } = {}) {
|
||||
try {
|
||||
if (e?.button !== undefined && e.button !== 0) return;
|
||||
e?.preventDefault?.();
|
||||
if (!this.queueElement) return;
|
||||
const rect = this.queueElement.getBoundingClientRect();
|
||||
const body = document.getElementById('model-resolver-body');
|
||||
const bodyRect = body ? body.getBoundingClientRect() : { width: window.innerWidth };
|
||||
e?.stopPropagation?.();
|
||||
if (!this.queueElement || !this.contentElement) return;
|
||||
|
||||
const body = this.getQueueSplitBody();
|
||||
this.cancelQueuePanelOpenUpdate();
|
||||
this._queueSplitLastContainerWidth = this.getQueueSplitContainerWidth();
|
||||
const bounds = this.getQueueSplitBounds(this._queueSplitLastContainerWidth, { edgeOpen });
|
||||
const currentWidth = this.getQueuePaneWidth(this.queueElement) || 320;
|
||||
const initialWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, Number.isFinite(startWidth) ? startWidth : currentWidth)));
|
||||
|
||||
this._splitDragging = true;
|
||||
body?.classList.add('mr-is-resizing-queue');
|
||||
if (!startCollapsedPreview) {
|
||||
this.applyQueueSplitWidth(initialWidth, {
|
||||
bounds,
|
||||
containerWidth: this._queueSplitLastContainerWidth,
|
||||
skipIfUnchanged: true
|
||||
});
|
||||
}
|
||||
this._splitStart = {
|
||||
x: e.clientX,
|
||||
startWidth: Number.isFinite(startWidth) ? startWidth : rect.width,
|
||||
containerWidth: bodyRect.width
|
||||
startWidth: initialWidth,
|
||||
containerWidth: this._queueSplitLastContainerWidth,
|
||||
bounds
|
||||
};
|
||||
this._lastSplitDragApply = 0;
|
||||
this._pendingSplitWidth = initialWidth;
|
||||
this._appliedSplitWidth = initialWidth;
|
||||
this._splitPreviewCollapsed = !!startCollapsedPreview;
|
||||
this._splitEdgeOpen = !!edgeOpen;
|
||||
this._splitEdgeOpenedPastThreshold = false;
|
||||
this._splitPreviousCursor = previousCursor;
|
||||
this._prevUserSelect = document.body.style.userSelect;
|
||||
this._prevQueueSplitCursor = document.body.style.cursor;
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = 'col-resize';
|
||||
if (e?.pointerId !== undefined && this.splitterElement?.setPointerCapture) {
|
||||
try {
|
||||
this.splitterElement.setPointerCapture(e.pointerId);
|
||||
} catch (error) { }
|
||||
}
|
||||
this._onSplitMove = (ev) => this.onSplitDrag(ev);
|
||||
this._onSplitUp = () => this.endSplitDrag();
|
||||
document.addEventListener('mousemove', this._onSplitMove);
|
||||
document.addEventListener('mouseup', this._onSplitUp, { once: true });
|
||||
this._onSplitUp = (ev) => this.endSplitDrag(ev);
|
||||
this._splitMoveEvent = e?.type === 'pointerdown' ? 'pointermove' : 'mousemove';
|
||||
this._splitUpEvent = e?.type === 'pointerdown' ? 'pointerup' : 'mouseup';
|
||||
this._splitCancelEvent = e?.type === 'pointerdown' ? 'pointercancel' : null;
|
||||
document.addEventListener(this._splitMoveEvent, this._onSplitMove, true);
|
||||
document.addEventListener(this._splitUpEvent, this._onSplitUp, { once: true, capture: true });
|
||||
if (this._splitCancelEvent) {
|
||||
document.addEventListener(this._splitCancelEvent, this._onSplitUp, { once: true, capture: true });
|
||||
}
|
||||
} catch (err) { /* ignore */ }
|
||||
},
|
||||
|
||||
onSplitDrag(e) {
|
||||
if (!this._splitDragging || !this._splitStart || !this.queueElement) return;
|
||||
e?.preventDefault?.();
|
||||
e?.stopPropagation?.();
|
||||
const dx = e.clientX - this._splitStart.x;
|
||||
let newW = this._splitStart.startWidth - dx;
|
||||
const minW = this._splitEdgeOpen ? 0 : 56;
|
||||
const maxW = Math.max(minW, Math.floor(this._splitStart.containerWidth - 360));
|
||||
if (newW < minW) newW = minW;
|
||||
if (newW > maxW) newW = maxW;
|
||||
const bounds = this._splitStart.bounds || this.getQueueSplitBounds(this._splitStart.containerWidth, { edgeOpen: this._splitEdgeOpen });
|
||||
if (newW < bounds.min) newW = bounds.min;
|
||||
if (newW > bounds.max) newW = bounds.max;
|
||||
this._pendingSplitWidth = Math.round(newW);
|
||||
if (this._splitEdgeOpen && this._pendingSplitWidth > 140) {
|
||||
this._splitEdgeOpenedPastThreshold = true;
|
||||
if (this.queueCollapsed) {
|
||||
this.queueCollapsed = false;
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '0'); } catch (error) { }
|
||||
this.persistQueueCollapsedState(false, 500);
|
||||
this.updateQueueToggleIcon();
|
||||
this.updateQueuePanel();
|
||||
}
|
||||
if (this._queuePanelDirty) {
|
||||
this.queueElement?.classList?.add('mr-queue-opening');
|
||||
this.scheduleQueuePanelOpenUpdate();
|
||||
}
|
||||
}
|
||||
const previewCollapsed = this._splitEdgeOpen
|
||||
@@ -1664,50 +2101,69 @@ export const queueMethods = {
|
||||
body?.classList.toggle('mr-queue-preview-collapsed', previewCollapsed);
|
||||
this.queueElement.style.display = previewCollapsed ? 'none' : '';
|
||||
}
|
||||
const now = performance.now();
|
||||
if (this._lastSplitDragApply && now - this._lastSplitDragApply < 33) return;
|
||||
this._lastSplitDragApply = now;
|
||||
if (this._splitDragFrame) return;
|
||||
this._splitDragFrame = requestAnimationFrame(() => {
|
||||
this._splitDragFrame = null;
|
||||
if (!this._splitDragging || !this.queueElement || !this._pendingSplitWidth || this._splitPreviewCollapsed) return;
|
||||
this.queueElement.style.width = `${this._pendingSplitWidth}px`;
|
||||
if (this._splitPreviewCollapsed) return;
|
||||
this.applyQueueSplitWidth(this._pendingSplitWidth, {
|
||||
bounds,
|
||||
containerWidth: this._splitStart.containerWidth,
|
||||
skipIfUnchanged: true
|
||||
});
|
||||
},
|
||||
|
||||
endSplitDrag() {
|
||||
endSplitDrag(e = null) {
|
||||
if (!this._splitDragging) return;
|
||||
e?.preventDefault?.();
|
||||
e?.stopPropagation?.();
|
||||
const releasePointerId = e?.pointerId;
|
||||
const previousUserSelect = this._prevUserSelect;
|
||||
const previousCursor = this._splitPreviousCursor;
|
||||
const fallbackCursor = this._prevQueueSplitCursor;
|
||||
this._splitDragging = false;
|
||||
if (this._splitDragFrame) {
|
||||
cancelAnimationFrame(this._splitDragFrame);
|
||||
this._splitDragFrame = null;
|
||||
}
|
||||
if (this.queueElement && this._pendingSplitWidth && !this._splitPreviewCollapsed) {
|
||||
this.queueElement.style.width = `${this._pendingSplitWidth}px`;
|
||||
this.applyQueueSplitWidth(this._pendingSplitWidth, {
|
||||
bounds: this._splitStart?.bounds,
|
||||
containerWidth: this._splitStart?.containerWidth,
|
||||
skipIfUnchanged: true
|
||||
});
|
||||
}
|
||||
const body = this.getQueueSplitBody();
|
||||
if (this._splitMoveEvent) {
|
||||
document.removeEventListener(this._splitMoveEvent, this._onSplitMove, true);
|
||||
}
|
||||
if (this._splitUpEvent) {
|
||||
document.removeEventListener(this._splitUpEvent, this._onSplitUp, true);
|
||||
}
|
||||
if (this._splitCancelEvent) {
|
||||
document.removeEventListener(this._splitCancelEvent, this._onSplitUp, true);
|
||||
}
|
||||
const body = document.getElementById('model-resolver-body');
|
||||
body?.classList.remove('mr-is-resizing-queue');
|
||||
body?.classList.remove('mr-queue-preview-collapsed');
|
||||
document.removeEventListener('mousemove', this._onSplitMove);
|
||||
if (this._splitEdgeOpen && !this._pendingSplitWidth) {
|
||||
try {
|
||||
const restoreWidth = Math.max(240, Math.round(this._splitStart?.startWidth || 320));
|
||||
this.queueElement.style.width = `${restoreWidth}px`;
|
||||
this.applyQueueSplitWidth(restoreWidth, {
|
||||
bounds: this._splitStart?.bounds,
|
||||
containerWidth: this._splitStart?.containerWidth
|
||||
});
|
||||
this.queueElement.style.display = '';
|
||||
if (this.splitterElement) this.splitterElement.style.display = '';
|
||||
localStorage.setItem('model_resolver_split_w', String(restoreWidth));
|
||||
this.persistQueueSplitWidth(restoreWidth, 500);
|
||||
} catch (e) { }
|
||||
this.queueCollapsed = false;
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '0'); } catch (e) { }
|
||||
this.persistQueueCollapsedState(false, 500);
|
||||
this.queueElement?.classList?.add('mr-queue-opening');
|
||||
document.getElementById('model-resolver-body')?.classList.add('mr-queue-opening-layout');
|
||||
this.updateQueueVisibility();
|
||||
this.updateQueuePanel();
|
||||
this.scheduleQueuePanelOpenUpdate();
|
||||
this._splitEdgeOpen = false;
|
||||
this._pendingSplitWidth = null;
|
||||
this._lastSplitDragApply = 0;
|
||||
this._appliedSplitWidth = null;
|
||||
this._splitPreviewCollapsed = false;
|
||||
try { document.body.style.userSelect = this._prevUserSelect || ''; } catch (e) { }
|
||||
try { document.body.style.cursor = this._splitPreviousCursor || ''; } catch (e) { }
|
||||
this.deferQueueSplitReleaseUi(body, releasePointerId, previousUserSelect, previousCursor, fallbackCursor);
|
||||
this._splitPreviousCursor = null;
|
||||
this._prevQueueSplitCursor = null;
|
||||
this._prevUserSelect = null;
|
||||
this._splitEdgeOpenedPastThreshold = false;
|
||||
setTimeout(() => { this._suppressQueueEdgeClick = false; }, 0);
|
||||
return;
|
||||
@@ -1716,37 +2172,46 @@ export const queueMethods = {
|
||||
if (shouldCollapse) {
|
||||
try {
|
||||
const restoreWidth = Math.max(240, Math.round(this._splitStart?.startWidth || 320));
|
||||
this.queueElement.style.width = `${restoreWidth}px`;
|
||||
this.applyQueueSplitWidth(restoreWidth, {
|
||||
bounds: this._splitStart?.bounds,
|
||||
containerWidth: this._splitStart?.containerWidth
|
||||
});
|
||||
this.queueElement.style.display = '';
|
||||
localStorage.setItem('model_resolver_split_w', String(restoreWidth));
|
||||
this.persistQueueSplitWidth(restoreWidth);
|
||||
} catch (e) { }
|
||||
this._pendingSplitWidth = null;
|
||||
this._lastSplitDragApply = 0;
|
||||
this._appliedSplitWidth = null;
|
||||
this._splitPreviewCollapsed = false;
|
||||
this._splitEdgeOpen = false;
|
||||
this._splitEdgeOpenedPastThreshold = false;
|
||||
try { document.body.style.userSelect = this._prevUserSelect || ''; } catch (e) { }
|
||||
try { document.body.style.cursor = this._splitPreviousCursor || ''; } catch (e) { }
|
||||
this.deferQueueSplitReleaseUi(body, releasePointerId, previousUserSelect, previousCursor, fallbackCursor);
|
||||
this._splitPreviousCursor = null;
|
||||
this._prevQueueSplitCursor = null;
|
||||
this._prevUserSelect = null;
|
||||
this.setQueueCollapsed(true);
|
||||
setTimeout(() => { this._suppressQueueEdgeClick = false; }, 0);
|
||||
return;
|
||||
}
|
||||
if (this.queueElement) this.queueElement.style.display = '';
|
||||
try {
|
||||
const rect = this.queueElement.getBoundingClientRect();
|
||||
localStorage.setItem('model_resolver_split_w', String(Math.round(rect.width)));
|
||||
const width = this._pendingSplitWidth
|
||||
|| this._appliedSplitWidth
|
||||
|| Number.parseInt(String(this.queueElement.style.width || ''), 10)
|
||||
|| this.getQueuePaneWidth(this.queueElement);
|
||||
this.persistQueueSplitWidth(width);
|
||||
} catch (e) { }
|
||||
this._pendingSplitWidth = null;
|
||||
this._lastSplitDragApply = 0;
|
||||
this._appliedSplitWidth = null;
|
||||
this._splitPreviewCollapsed = false;
|
||||
this._splitEdgeOpen = false;
|
||||
this._splitEdgeOpenedPastThreshold = false;
|
||||
try { document.body.style.userSelect = this._prevUserSelect || ''; } catch (e) { }
|
||||
if (this._splitPreviousCursor !== null) {
|
||||
try { document.body.style.cursor = this._splitPreviousCursor || ''; } catch (e) { }
|
||||
this._splitPreviousCursor = null;
|
||||
}
|
||||
this.deferQueueSplitReleaseUi(body, releasePointerId, previousUserSelect, previousCursor, fallbackCursor);
|
||||
this._splitPreviousCursor = null;
|
||||
this._prevQueueSplitCursor = null;
|
||||
this._prevUserSelect = null;
|
||||
this._splitMoveEvent = null;
|
||||
this._splitUpEvent = null;
|
||||
this._splitCancelEvent = null;
|
||||
setTimeout(() => { this._suppressQueueEdgeClick = false; }, 0);
|
||||
},
|
||||
|
||||
|
||||
@@ -531,12 +531,13 @@ export const missingBrowserMethods = {
|
||||
? `--mr-missing-detail-track:${savedDetailWidth}px;`
|
||||
: '';
|
||||
const listStyle = `--mr-missing-model-col:${listLayout.modelPx}px;--mr-missing-type-col:${listLayout.typePx}px;`;
|
||||
const browserStyle = `${listStyle}${splitStyle}`;
|
||||
const titleText = this.showResolvedModels && resolvedCount > 0
|
||||
? `${totalMissing} shown (${missingCount} missing / ${resolvedCount} resolved)`
|
||||
: `${totalMissing} missing model${totalMissing === 1 ? '' : 's'}`;
|
||||
|
||||
let html = `
|
||||
<div class="mr-missing-browser" style="${listStyle}">
|
||||
<div class="mr-missing-browser" style="${browserStyle}">
|
||||
<section class="mr-missing-list-pane" aria-label="Missing model list">
|
||||
<div class="mr-missing-list-toolbar">
|
||||
<div>
|
||||
@@ -628,7 +629,7 @@ export const missingBrowserMethods = {
|
||||
</div>
|
||||
</section>
|
||||
<div class="mr-missing-browser-splitter" role="separator" aria-orientation="vertical" aria-label="Resize missing model panes" tabindex="0"></div>
|
||||
<section class="mr-missing-detail-pane" aria-label="Missing model details" style="${splitStyle}">
|
||||
<section class="mr-missing-detail-pane" aria-label="Missing model details">
|
||||
${detailMissing ? this.renderMissingModel(detailMissing, detailIndex) : this.renderStatusMessage('Select a missing model to inspect details.', 'info')}
|
||||
</section>
|
||||
</div>
|
||||
@@ -760,12 +761,13 @@ export const missingBrowserMethods = {
|
||||
const browser = container.querySelector('.mr-missing-browser');
|
||||
const splitter = browser?.querySelector('.mr-missing-browser-splitter');
|
||||
if (!(browser instanceof HTMLElement) || !(splitter instanceof HTMLElement)) return;
|
||||
|
||||
this.restoreMissingBrowserSplitWidth(browser);
|
||||
const listPane = browser.querySelector('.mr-missing-list-pane');
|
||||
const detailPane = browser.querySelector('.mr-missing-detail-pane');
|
||||
if (!(listPane instanceof HTMLElement) || !(detailPane instanceof HTMLElement)) return;
|
||||
|
||||
const startSplitDrag = (event) => {
|
||||
if (event.button !== undefined && event.button !== 0) return;
|
||||
this.startMissingBrowserSplitDrag(event, browser, splitter);
|
||||
this.startMissingBrowserSplitDrag(event, browser, splitter, { listPane, detailPane });
|
||||
};
|
||||
if (typeof PointerEvent === 'function') {
|
||||
splitter.addEventListener('pointerdown', startSplitDrag);
|
||||
@@ -797,25 +799,37 @@ export const missingBrowserMethods = {
|
||||
if (typeof ResizeObserver === 'function') {
|
||||
this._missingBrowserResizeObserver = new ResizeObserver((entries) => {
|
||||
const observedWidth = Number(entries?.[0]?.contentRect?.width);
|
||||
if (Number.isFinite(observedWidth) && observedWidth > 0) {
|
||||
this._missingBrowserObservedWidth = observedWidth;
|
||||
this._missingBrowserLastBrowserWidth = observedWidth;
|
||||
if (!this._missingBrowserLastDetailWidth) {
|
||||
this._missingBrowserLastDetailWidth = this.getDefaultMissingBrowserDetailWidth(observedWidth);
|
||||
}
|
||||
}
|
||||
this.rememberMissingBrowserWidth(observedWidth);
|
||||
const settlingUntil = Number(this._missingBrowserSplitSettlingUntil || 0);
|
||||
const isSettling = typeof performance === 'object'
|
||||
&& typeof performance.now === 'function'
|
||||
&& performance.now() < settlingUntil;
|
||||
if (!this._missingBrowserSplitDragging && !isSettling) {
|
||||
this.scheduleMissingBrowserSplitRestore(browser, observedWidth, { useDefault: true });
|
||||
this.scheduleMissingBrowserSplitRestore(browser, observedWidth, { useDefault: false });
|
||||
}
|
||||
});
|
||||
this._missingBrowserResizeObserver.observe(browser);
|
||||
} else {
|
||||
this.cancelMissingBrowserPrewarmFrame();
|
||||
this._missingBrowserPrewarmFrame = requestAnimationFrame(() => {
|
||||
this._missingBrowserPrewarmFrame = null;
|
||||
if (!browser.isConnected) return;
|
||||
if (this._missingBrowserSplitDragging) return;
|
||||
const measuredWidth = Number(browser.clientWidth || browser.getBoundingClientRect().width);
|
||||
this.rememberMissingBrowserWidth(measuredWidth);
|
||||
if (!this._missingBrowserSplitDragging) {
|
||||
this.scheduleMissingBrowserSplitRestore(browser, measuredWidth, { useDefault: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
cancelMissingBrowserPrewarmFrame() {
|
||||
if (!this._missingBrowserPrewarmFrame) return;
|
||||
cancelAnimationFrame(this._missingBrowserPrewarmFrame);
|
||||
this._missingBrowserPrewarmFrame = null;
|
||||
},
|
||||
|
||||
getStoredMissingBrowserSplitWidth() {
|
||||
if (this._missingBrowserStoredSplitWidthLoaded) {
|
||||
return this._missingBrowserStoredSplitWidth || null;
|
||||
@@ -832,23 +846,34 @@ export const missingBrowserMethods = {
|
||||
return savedWidth;
|
||||
},
|
||||
|
||||
getFastMissingBrowserWidthEstimate(browser = null) {
|
||||
rememberMissingBrowserWidth(width) {
|
||||
const observedWidth = Number(width);
|
||||
if (!Number.isFinite(observedWidth) || observedWidth <= 0) return null;
|
||||
this._missingBrowserObservedWidth = observedWidth;
|
||||
this._missingBrowserLastBrowserWidth = observedWidth;
|
||||
if (!this._missingBrowserLastDetailWidth) {
|
||||
this._missingBrowserLastDetailWidth = this.getDefaultMissingBrowserDetailWidth(observedWidth);
|
||||
}
|
||||
return observedWidth;
|
||||
},
|
||||
|
||||
getFastMissingBrowserWidthEstimate(browser = null, { allowMeasure = false } = {}) {
|
||||
const observedWidth = Number(this._missingBrowserObservedWidth);
|
||||
if (Number.isFinite(observedWidth) && observedWidth > 0) return observedWidth;
|
||||
|
||||
const lastWidth = Number(this._missingBrowserLastBrowserWidth);
|
||||
if (Number.isFinite(lastWidth) && lastWidth > 0) return lastWidth;
|
||||
|
||||
if (browser instanceof HTMLElement) {
|
||||
if (allowMeasure && browser instanceof HTMLElement) {
|
||||
const clientWidth = Number(browser.clientWidth || browser.offsetWidth);
|
||||
if (Number.isFinite(clientWidth) && clientWidth > 0) {
|
||||
this._missingBrowserObservedWidth = clientWidth;
|
||||
this._missingBrowserLastBrowserWidth = clientWidth;
|
||||
this.rememberMissingBrowserWidth(clientWidth);
|
||||
return clientWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return 720;
|
||||
const fallbackWidth = Number(this._queueSplitLastContainerWidth || window.innerWidth || 720);
|
||||
return Number.isFinite(fallbackWidth) && fallbackWidth > 0 ? fallbackWidth : 720;
|
||||
},
|
||||
|
||||
getMissingBrowserSplitBoundsForWidth(width) {
|
||||
@@ -910,23 +935,26 @@ export const missingBrowserMethods = {
|
||||
});
|
||||
},
|
||||
|
||||
startMissingBrowserSplitDrag(event, browser, splitter = null) {
|
||||
startMissingBrowserSplitDrag(event, browser, splitter = null, panes = {}) {
|
||||
if (!(browser instanceof HTMLElement)) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation?.();
|
||||
if (this._missingBrowserReleaseCleanupFrame) {
|
||||
cancelAnimationFrame(this._missingBrowserReleaseCleanupFrame);
|
||||
this._missingBrowserReleaseCleanupFrame = null;
|
||||
}
|
||||
this.cancelMissingBrowserPrewarmFrame();
|
||||
this.flushMissingBrowserSplitReleaseUi();
|
||||
|
||||
const detailPane = browser.querySelector('.mr-missing-detail-pane');
|
||||
const listPane = browser.querySelector('.mr-missing-list-pane');
|
||||
const detailPane = panes.detailPane instanceof HTMLElement
|
||||
? panes.detailPane
|
||||
: browser.querySelector('.mr-missing-detail-pane');
|
||||
const listPane = panes.listPane instanceof HTMLElement
|
||||
? panes.listPane
|
||||
: browser.querySelector('.mr-missing-list-pane');
|
||||
if (!(detailPane instanceof HTMLElement) || !(listPane instanceof HTMLElement)) return;
|
||||
const browserWidth = this.getFastMissingBrowserWidthEstimate(browser);
|
||||
const bounds = this.getMissingBrowserSplitBoundsForWidth(browserWidth);
|
||||
const savedWidth = this.getStoredMissingBrowserSplitWidth();
|
||||
const detailWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, this.getMissingBrowserDetailTrackWidth(detailPane)
|
||||
|| this._missingBrowserLastDetailWidth
|
||||
|| savedWidth
|
||||
|| this.getDefaultMissingBrowserDetailWidth(browserWidth))));
|
||||
|
||||
@@ -938,12 +966,12 @@ export const missingBrowserMethods = {
|
||||
this._pendingMissingBrowserRestoreWidth = null;
|
||||
this._pendingMissingBrowserRestoreUseDefault = false;
|
||||
this.cancelMissingBrowserSplitWidthPersist();
|
||||
this.applyMissingBrowserDetailWidth(detailPane, detailWidth, {
|
||||
browserWidth,
|
||||
availableWidth: bounds.available,
|
||||
splitBounds: bounds,
|
||||
skipIfUnchanged: true
|
||||
});
|
||||
this._missingBrowserSplitBrowser = browser;
|
||||
this._missingBrowserSplitListPane = listPane;
|
||||
this._missingBrowserSplitDetailPane = detailPane;
|
||||
this._missingBrowserSplitSplitter = splitter instanceof HTMLElement
|
||||
? splitter
|
||||
: browser.querySelector('.mr-missing-browser-splitter');
|
||||
this._missingBrowserSplitDragging = true;
|
||||
this._missingBrowserSplitStart = {
|
||||
x: event.clientX,
|
||||
@@ -953,22 +981,7 @@ export const missingBrowserMethods = {
|
||||
availableWidth: bounds.available
|
||||
};
|
||||
this._appliedMissingBrowserSplitWidth = Math.round(this._missingBrowserSplitStart.width);
|
||||
this._missingBrowserSplitBrowser = browser;
|
||||
this._missingBrowserSplitListPane = listPane;
|
||||
this._missingBrowserSplitDetailPane = detailPane;
|
||||
this._missingBrowserSplitSplitter = splitter instanceof HTMLElement
|
||||
? splitter
|
||||
: browser.querySelector('.mr-missing-browser-splitter');
|
||||
this._missingBrowserPrevUserSelect = document.body.style.userSelect;
|
||||
this._missingBrowserPrevCursor = document.body.style.cursor;
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = 'col-resize';
|
||||
this._missingBrowserSplitSplitter?.classList?.add('is-resizing');
|
||||
if (event.pointerId !== undefined && this._missingBrowserSplitSplitter?.setPointerCapture) {
|
||||
try {
|
||||
this._missingBrowserSplitSplitter.setPointerCapture(event.pointerId);
|
||||
} catch (e) {}
|
||||
}
|
||||
this._missingBrowserSplitUiActive = false;
|
||||
|
||||
this._onMissingBrowserSplitMove = (moveEvent) => this.onMissingBrowserSplitDrag(moveEvent);
|
||||
this._onMissingBrowserSplitUp = (upEvent) => this.endMissingBrowserSplitDrag(upEvent);
|
||||
@@ -982,6 +995,23 @@ export const missingBrowserMethods = {
|
||||
}
|
||||
},
|
||||
|
||||
activateMissingBrowserSplitUi() {
|
||||
if (this._missingBrowserSplitUiActive) return;
|
||||
this._missingBrowserSplitUiActive = true;
|
||||
if (this._missingBrowserResizeClassFrame) {
|
||||
cancelAnimationFrame(this._missingBrowserResizeClassFrame);
|
||||
this._missingBrowserResizeClassFrame = null;
|
||||
}
|
||||
this._missingBrowserResizeClassFrame = requestAnimationFrame(() => {
|
||||
this._missingBrowserResizeClassFrame = requestAnimationFrame(() => {
|
||||
this._missingBrowserResizeClassFrame = null;
|
||||
if (this._missingBrowserSplitDragging) {
|
||||
this._missingBrowserSplitSplitter?.classList?.add('is-resizing');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onMissingBrowserSplitDrag(event) {
|
||||
if (!this._missingBrowserSplitDragging || !this._missingBrowserSplitStart || !this._missingBrowserSplitBrowser) return;
|
||||
|
||||
@@ -992,6 +1022,7 @@ export const missingBrowserMethods = {
|
||||
this._pendingMissingBrowserSplitWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, nextWidth)));
|
||||
if (this._pendingMissingBrowserSplitWidth === this._appliedMissingBrowserSplitWidth) return;
|
||||
if (!this._missingBrowserSplitDetailPane) return;
|
||||
this.activateMissingBrowserSplitUi();
|
||||
this.applyMissingBrowserDetailWidth(this._missingBrowserSplitDetailPane, this._pendingMissingBrowserSplitWidth, {
|
||||
browserWidth: this._missingBrowserSplitStart.browserWidth,
|
||||
availableWidth: this._missingBrowserSplitStart.availableWidth,
|
||||
@@ -1011,9 +1042,7 @@ export const missingBrowserMethods = {
|
||||
: Date.now();
|
||||
this._missingBrowserSplitSettlingUntil = settlingNow + 350;
|
||||
const releaseSplitter = this._missingBrowserSplitSplitter;
|
||||
const releasePointerId = event?.pointerId;
|
||||
const previousUserSelect = this._missingBrowserPrevUserSelect;
|
||||
const previousCursor = this._missingBrowserPrevCursor;
|
||||
const wasUiActive = !!this._missingBrowserSplitUiActive;
|
||||
const finalWidth = this._pendingMissingBrowserSplitWidth || this._appliedMissingBrowserSplitWidth;
|
||||
this._missingBrowserSplitDragging = false;
|
||||
if (this._missingBrowserSplitMoveEvent) {
|
||||
@@ -1042,12 +1071,13 @@ export const missingBrowserMethods = {
|
||||
if (finalWidth) {
|
||||
this.persistMissingBrowserSplitWidth(finalWidth, { delay: 600 });
|
||||
}
|
||||
this.deferMissingBrowserSplitReleaseUi(releaseSplitter, releasePointerId, previousUserSelect, previousCursor);
|
||||
this.deferMissingBrowserSplitReleaseUi(releaseSplitter, wasUiActive);
|
||||
this._missingBrowserSplitBrowser = null;
|
||||
this._missingBrowserSplitListPane = null;
|
||||
this._missingBrowserSplitDetailPane = null;
|
||||
this._missingBrowserSplitSplitter = null;
|
||||
this._missingBrowserSplitStart = null;
|
||||
this._missingBrowserSplitUiActive = false;
|
||||
this._pendingMissingBrowserSplitWidth = null;
|
||||
this._appliedMissingBrowserSplitWidth = null;
|
||||
this._missingBrowserSplitMoveEvent = null;
|
||||
@@ -1055,22 +1085,40 @@ export const missingBrowserMethods = {
|
||||
this._missingBrowserSplitCancelEvent = null;
|
||||
},
|
||||
|
||||
deferMissingBrowserSplitReleaseUi(splitter, pointerId, previousUserSelect, previousCursor) {
|
||||
applyMissingBrowserSplitReleaseCleanup(cleanup) {
|
||||
if (!cleanup) return;
|
||||
cleanup.splitter?.classList?.remove('is-resizing');
|
||||
},
|
||||
|
||||
flushMissingBrowserSplitReleaseUi() {
|
||||
if (this._missingBrowserReleaseCleanupFrame) {
|
||||
cancelAnimationFrame(this._missingBrowserReleaseCleanupFrame);
|
||||
this._missingBrowserReleaseCleanupFrame = null;
|
||||
}
|
||||
const cleanup = this._missingBrowserPendingReleaseCleanup;
|
||||
this._missingBrowserPendingReleaseCleanup = null;
|
||||
this.applyMissingBrowserSplitReleaseCleanup(cleanup);
|
||||
},
|
||||
|
||||
deferMissingBrowserSplitReleaseUi(splitter, shouldCleanup = true) {
|
||||
if (this._missingBrowserResizeClassFrame) {
|
||||
cancelAnimationFrame(this._missingBrowserResizeClassFrame);
|
||||
this._missingBrowserResizeClassFrame = null;
|
||||
}
|
||||
this._missingBrowserPendingReleaseCleanup = {
|
||||
splitter,
|
||||
shouldCleanup
|
||||
};
|
||||
const release = () => {
|
||||
this._missingBrowserReleaseCleanupFrame = null;
|
||||
if (pointerId !== undefined && splitter?.releasePointerCapture) {
|
||||
try {
|
||||
splitter.releasePointerCapture(pointerId);
|
||||
} catch (e) {}
|
||||
}
|
||||
splitter?.classList?.remove('is-resizing');
|
||||
try {
|
||||
document.body.style.userSelect = previousUserSelect || '';
|
||||
document.body.style.cursor = previousCursor || '';
|
||||
} catch (e) {}
|
||||
const cleanup = this._missingBrowserPendingReleaseCleanup;
|
||||
this._missingBrowserPendingReleaseCleanup = null;
|
||||
this.applyMissingBrowserSplitReleaseCleanup(cleanup);
|
||||
};
|
||||
if (typeof requestAnimationFrame === 'function') {
|
||||
this._missingBrowserReleaseCleanupFrame = requestAnimationFrame(release);
|
||||
this._missingBrowserReleaseCleanupFrame = requestAnimationFrame(() => {
|
||||
this._missingBrowserReleaseCleanupFrame = requestAnimationFrame(release);
|
||||
});
|
||||
} else {
|
||||
release();
|
||||
}
|
||||
@@ -1120,14 +1168,13 @@ export const missingBrowserMethods = {
|
||||
}
|
||||
|
||||
if (listPane instanceof HTMLElement) {
|
||||
listPane.style.flexBasis = listValue;
|
||||
listPane.style.flexGrow = '0';
|
||||
listPane.style.flexShrink = '0';
|
||||
if (listPane.style.flexBasis !== listValue) listPane.style.flexBasis = listValue;
|
||||
if (listPane.style.flexGrow !== '0') listPane.style.flexGrow = '0';
|
||||
if (listPane.style.flexShrink !== '0') listPane.style.flexShrink = '0';
|
||||
}
|
||||
detailPane.style.setProperty('--mr-missing-detail-track', detailValue);
|
||||
detailPane.style.flexBasis = detailValue;
|
||||
detailPane.style.flexGrow = '0';
|
||||
detailPane.style.flexShrink = '0';
|
||||
if (detailPane.style.flexBasis !== detailValue) detailPane.style.flexBasis = detailValue;
|
||||
if (detailPane.style.flexGrow !== '0') detailPane.style.flexGrow = '0';
|
||||
if (detailPane.style.flexShrink !== '0') detailPane.style.flexShrink = '0';
|
||||
this._missingBrowserLastDetailWidth = nextWidth;
|
||||
return true;
|
||||
},
|
||||
@@ -1215,14 +1262,13 @@ export const missingBrowserMethods = {
|
||||
}
|
||||
},
|
||||
|
||||
getMissingBrowserSplitBounds(browser, browserWidth = null) {
|
||||
getMissingBrowserSplitBounds(browser, browserWidth = null, { allowMeasure = false } = {}) {
|
||||
const measuredWidth = Number(browserWidth);
|
||||
const width = Number.isFinite(measuredWidth) && measuredWidth > 0
|
||||
? measuredWidth
|
||||
: browser.getBoundingClientRect().width;
|
||||
: this.getFastMissingBrowserWidthEstimate(browser, { allowMeasure });
|
||||
if (Number.isFinite(width) && width > 0) {
|
||||
this._missingBrowserObservedWidth = width;
|
||||
this._missingBrowserLastBrowserWidth = width;
|
||||
this.rememberMissingBrowserWidth(width);
|
||||
}
|
||||
return this.getMissingBrowserSplitBoundsForWidth(width);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user