fix(missing-browser): keep detail pane available and table-preserving across widths
Remove the brittle is-detail-collapsed responsive behavior from the missing browser. Replace the timeout-based width handler with requestAnimationFrame split restoration, clamp the list min width to 340px, and drop the collapsed-state guards so the detail pane, compositor preview, and Type column remain usable regardless of container width.
This commit is contained in:
@@ -329,16 +329,12 @@ test('missing browser splitter keeps a compositor preview between live layout fr
|
||||
/\.mr-missing-browser\.is-resizing \.mr-missing-(?:list|detail)-pane/
|
||||
);
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /deferMissingBrowserSplitReleaseUi/);
|
||||
assert.match(missingBrowserMethodsSource, /observedWidth\s*<\s*760/);
|
||||
assert.match(missingBrowserMethodsSource, /observedWidth\s*<\s*700/);
|
||||
assert.match(
|
||||
missingBrowserMethodsSource,
|
||||
/scheduleMissingBrowserExternalResizeRestore\(browser,\s*observedWidth\)/
|
||||
);
|
||||
assert.match(
|
||||
resolverMainCssSource,
|
||||
/\.mr-missing-browser\.is-detail-collapsed \.mr-missing-detail-pane\s*\{[^}]*display:\s*none/s
|
||||
);
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /is-detail-collapsed/);
|
||||
assert.doesNotMatch(resolverMainCssSource, /is-detail-collapsed/);
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /startMissingBrowserExternalResizeLock/);
|
||||
assert.doesNotMatch(resolverMainCssSource, /mr-resolver-external-resizing/);
|
||||
assert.doesNotMatch(
|
||||
@@ -423,43 +419,92 @@ test('docked ComfyUI splitter coalesces live layout while its gutter stays respo
|
||||
assert.doesNotMatch(dialogShellMethodsSource, /mr-resolver-external-resizing/);
|
||||
});
|
||||
|
||||
test('missing browser detail collapse follows container width with hysteresis', () => {
|
||||
test('missing browser detail remains available at narrow widths', () => {
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /updateMissingBrowserDetailCollapse/);
|
||||
assert.doesNotMatch(missingBrowserMethodsSource, /is-detail-collapsed/);
|
||||
assert.doesNotMatch(resolverMainCssSource, /is-detail-collapsed/);
|
||||
});
|
||||
|
||||
test('missing browser detail follows external splitter resizing every animation frame', () => {
|
||||
const scheduleRestore = eval(
|
||||
`(${extractMethod(missingBrowserMethodsSource, 'scheduleMissingBrowserExternalResizeRestore')})`
|
||||
);
|
||||
const previousHTMLElement = globalThis.HTMLElement;
|
||||
const previousRequestAnimationFrame = globalThis.requestAnimationFrame;
|
||||
const frames = [];
|
||||
const restores = [];
|
||||
|
||||
class FakeHTMLElement {}
|
||||
globalThis.HTMLElement = FakeHTMLElement;
|
||||
globalThis.requestAnimationFrame = callback => {
|
||||
frames.push(callback);
|
||||
return frames.length;
|
||||
};
|
||||
|
||||
try {
|
||||
const classes = new Set();
|
||||
const browser = new FakeHTMLElement();
|
||||
browser.classList = {
|
||||
contains(name) {
|
||||
return classes.has(name);
|
||||
},
|
||||
toggle(name, enabled) {
|
||||
if (enabled) classes.add(name);
|
||||
else classes.delete(name);
|
||||
},
|
||||
};
|
||||
|
||||
browser.isConnected = true;
|
||||
const context = {
|
||||
_missingBrowserRestoreFrame: null,
|
||||
cancelMissingBrowserExternalResizeRestore() {},
|
||||
_missingBrowserExternalResizeFrame: null,
|
||||
_pendingMissingBrowserExternalResizeBrowser: null,
|
||||
_pendingMissingBrowserExternalResizeWidth: null,
|
||||
_missingBrowserSplitDragging: false,
|
||||
restoreMissingBrowserSplitWidth(target, options) {
|
||||
restores.push([target, options]);
|
||||
},
|
||||
};
|
||||
const updateDetailCollapse = eval(
|
||||
`(${extractMethod(missingBrowserMethodsSource, 'updateMissingBrowserDetailCollapse')})`
|
||||
);
|
||||
|
||||
assert.equal(updateDetailCollapse.call(context, browser, 690), true);
|
||||
assert.equal(classes.has('is-detail-collapsed'), true);
|
||||
scheduleRestore.call(context, browser, 620);
|
||||
scheduleRestore.call(context, browser, 540);
|
||||
|
||||
assert.equal(updateDetailCollapse.call(context, browser, 730), false);
|
||||
assert.equal(classes.has('is-detail-collapsed'), true);
|
||||
assert.equal(frames.length, 1);
|
||||
assert.equal(restores.length, 0);
|
||||
|
||||
assert.equal(updateDetailCollapse.call(context, browser, 770), true);
|
||||
assert.equal(classes.has('is-detail-collapsed'), false);
|
||||
frames.shift()();
|
||||
|
||||
assert.equal(restores.length, 1);
|
||||
assert.equal(restores[0][0], browser);
|
||||
assert.deepEqual(restores[0][1], {
|
||||
browserWidth: 540,
|
||||
useDefault: false,
|
||||
});
|
||||
} finally {
|
||||
globalThis.HTMLElement = previousHTMLElement;
|
||||
globalThis.requestAnimationFrame = previousRequestAnimationFrame;
|
||||
}
|
||||
|
||||
assert.doesNotMatch(
|
||||
extractMethod(missingBrowserMethodsSource, 'scheduleMissingBrowserExternalResizeRestore'),
|
||||
/setTimeout|120/
|
||||
);
|
||||
});
|
||||
|
||||
test('missing browser splitter preserves the table through the Type column', () => {
|
||||
const getSplitBounds = eval(
|
||||
`(${extractMethod(missingBrowserMethodsSource, 'getMissingBrowserSplitBoundsForWidth')})`
|
||||
);
|
||||
const regularBounds = getSplitBounds(1000);
|
||||
const narrowBounds = getSplitBounds(620);
|
||||
|
||||
assert.equal(regularBounds.available - regularBounds.max, 340);
|
||||
assert.equal(narrowBounds.available - narrowBounds.max, 340);
|
||||
assert.equal(narrowBounds.min, narrowBounds.max);
|
||||
assert.match(
|
||||
extractMethod(missingBrowserMethodsSource, 'getMissingBrowserSplitBoundsForWidth'),
|
||||
/Math\.min\(340,\s*available\)/
|
||||
);
|
||||
assert.match(
|
||||
resolverMainCssSource,
|
||||
/\.mr-missing-browser\s*\{[^}]*--mr-missing-list-min-width:\s*340px;/s
|
||||
);
|
||||
assert.match(
|
||||
resolverMainCssSource,
|
||||
/\.mr-missing-list-pane\s*\{[^}]*min-width:\s*var\(--mr-missing-list-min-width\);/s
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
resolverMainCssSource,
|
||||
/\.mr-missing-row-best,[\s\S]*?\.mr-missing-row-sources\s*\{[^}]*display:\s*none;/
|
||||
);
|
||||
});
|
||||
|
||||
test('local and loaded model tooltips include preview image routes above full names', () => {
|
||||
|
||||
@@ -503,6 +503,7 @@
|
||||
--mr-missing-line-strong: rgba(255, 255, 255, 0.13);
|
||||
--mr-missing-radius: 6px;
|
||||
--mr-missing-splitter-track: 10px;
|
||||
--mr-missing-list-min-width: 340px;
|
||||
--mr-missing-list-track: calc(100% - var(--mr-missing-splitter-track) - var(--mr-missing-detail-track, 42%));
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -527,6 +528,7 @@
|
||||
|
||||
.mr-missing-list-pane {
|
||||
flex: 1 1 0;
|
||||
min-width: var(--mr-missing-list-min-width);
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
border-right: 0;
|
||||
@@ -1156,54 +1158,6 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mr-missing-browser.is-detail-collapsed .mr-missing-browser-splitter,
|
||||
.mr-missing-browser.is-detail-collapsed .mr-missing-detail-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mr-missing-browser.is-detail-collapsed .mr-missing-list-pane {
|
||||
flex: 1 1 100% !important;
|
||||
width: 100%;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.mr-missing-list-head {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#model-resolver-modal .mr-missing-list-row {
|
||||
grid-template-columns: 24px 18px minmax(0, 1fr);
|
||||
content-visibility: visible;
|
||||
contain-intrinsic-size: none;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
overflow: visible;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.mr-missing-row-select {
|
||||
grid-row: 1 / span 4;
|
||||
align-self: start;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.mr-missing-row-index {
|
||||
grid-row: 1 / span 4;
|
||||
align-self: start;
|
||||
margin-top: 1px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mr-missing-row-model,
|
||||
.mr-missing-row-type,
|
||||
.mr-missing-row-best,
|
||||
.mr-missing-row-match,
|
||||
.mr-missing-row-sources {
|
||||
grid-column: 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Card Styles */
|
||||
.mr-card {
|
||||
background: var(--mr-card-bg);
|
||||
|
||||
@@ -73,7 +73,7 @@ export class ResolverManagerDialog extends ComfyDialog {
|
||||
this.sidebarTabId = "comfyui-model-resolver";
|
||||
this.sidebarOpenModeStorageKey = "model_resolver_sidebar_open_mode";
|
||||
this.missingBrowserSplitStorageKey = "model_resolver_missing_browser_detail_w";
|
||||
this._missingBrowserExternalResizeTimer = null;
|
||||
this._missingBrowserExternalResizeFrame = null;
|
||||
this._pendingMissingBrowserExternalResizeBrowser = null;
|
||||
this._pendingMissingBrowserExternalResizeWidth = null;
|
||||
this._missingBrowserObservedHostWidth = null;
|
||||
|
||||
@@ -975,7 +975,7 @@ export const missingBrowserMethods = {
|
||||
this.rememberMissingBrowserWidth(observedWidth);
|
||||
|
||||
if (!Number.isFinite(previousHostWidth) || previousHostWidth <= 0) {
|
||||
this.updateMissingBrowserDetailCollapse(browser, observedWidth);
|
||||
this.scheduleMissingBrowserSplitRestore(browser, observedWidth, { useDefault: false });
|
||||
return;
|
||||
}
|
||||
if (Math.abs(previousHostWidth - observedWidth) < 0.5) return;
|
||||
@@ -1000,10 +1000,7 @@ export const missingBrowserMethods = {
|
||||
if (this._missingBrowserSplitDragging) return;
|
||||
const measuredWidth = Number(browser.clientWidth || browser.getBoundingClientRect().width);
|
||||
this.rememberMissingBrowserWidth(measuredWidth);
|
||||
this.updateMissingBrowserDetailCollapse(browser, measuredWidth);
|
||||
if (!this._missingBrowserSplitDragging && !browser.classList.contains('is-detail-collapsed')) {
|
||||
this.scheduleMissingBrowserSplitRestore(browser, measuredWidth, { useDefault: false });
|
||||
}
|
||||
this.scheduleMissingBrowserSplitRestore(browser, measuredWidth, { useDefault: false });
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -1014,51 +1011,24 @@ export const missingBrowserMethods = {
|
||||
this._missingBrowserPrewarmFrame = null;
|
||||
},
|
||||
|
||||
updateMissingBrowserDetailCollapse(browser, width) {
|
||||
if (!(browser instanceof HTMLElement)) return false;
|
||||
const observedWidth = Number(width);
|
||||
if (!Number.isFinite(observedWidth) || observedWidth <= 0) return false;
|
||||
|
||||
const wasCollapsed = browser.classList.contains('is-detail-collapsed');
|
||||
const shouldCollapse = wasCollapsed
|
||||
? observedWidth < 760
|
||||
: observedWidth < 700;
|
||||
if (shouldCollapse === wasCollapsed) return false;
|
||||
|
||||
browser.classList.toggle('is-detail-collapsed', shouldCollapse);
|
||||
if (shouldCollapse) {
|
||||
this.cancelMissingBrowserExternalResizeRestore();
|
||||
if (this._missingBrowserRestoreFrame) {
|
||||
cancelAnimationFrame(this._missingBrowserRestoreFrame);
|
||||
this._missingBrowserRestoreFrame = null;
|
||||
}
|
||||
this._pendingMissingBrowserRestoreBrowser = null;
|
||||
this._pendingMissingBrowserRestoreWidth = null;
|
||||
this._pendingMissingBrowserRestoreUseDefault = false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
cancelMissingBrowserExternalResizeRestore() {
|
||||
if (this._missingBrowserExternalResizeTimer) {
|
||||
clearTimeout(this._missingBrowserExternalResizeTimer);
|
||||
if (this._missingBrowserExternalResizeFrame) {
|
||||
cancelAnimationFrame(this._missingBrowserExternalResizeFrame);
|
||||
}
|
||||
this._missingBrowserExternalResizeTimer = null;
|
||||
this._missingBrowserExternalResizeFrame = null;
|
||||
this._pendingMissingBrowserExternalResizeBrowser = null;
|
||||
this._pendingMissingBrowserExternalResizeWidth = null;
|
||||
},
|
||||
|
||||
scheduleMissingBrowserExternalResizeRestore(browser, browserWidth, delay = 120) {
|
||||
scheduleMissingBrowserExternalResizeRestore(browser, browserWidth) {
|
||||
if (!(browser instanceof HTMLElement)) return;
|
||||
|
||||
this._pendingMissingBrowserExternalResizeBrowser = browser;
|
||||
this._pendingMissingBrowserExternalResizeWidth = Number(browserWidth) || null;
|
||||
if (this._missingBrowserExternalResizeTimer) {
|
||||
clearTimeout(this._missingBrowserExternalResizeTimer);
|
||||
}
|
||||
if (this._missingBrowserExternalResizeFrame) return;
|
||||
|
||||
this._missingBrowserExternalResizeTimer = setTimeout(() => {
|
||||
this._missingBrowserExternalResizeTimer = null;
|
||||
this._missingBrowserExternalResizeFrame = requestAnimationFrame(() => {
|
||||
this._missingBrowserExternalResizeFrame = null;
|
||||
const targetBrowser = this._pendingMissingBrowserExternalResizeBrowser;
|
||||
const targetWidth = this._pendingMissingBrowserExternalResizeWidth;
|
||||
this._pendingMissingBrowserExternalResizeBrowser = null;
|
||||
@@ -1070,10 +1040,11 @@ export const missingBrowserMethods = {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.updateMissingBrowserDetailCollapse(targetBrowser, targetWidth);
|
||||
if (targetBrowser.classList.contains('is-detail-collapsed')) return;
|
||||
this.scheduleMissingBrowserSplitRestore(targetBrowser, targetWidth, { useDefault: false });
|
||||
}, Math.max(0, Number(delay) || 0));
|
||||
this.restoreMissingBrowserSplitWidth(targetBrowser, {
|
||||
browserWidth: targetWidth,
|
||||
useDefault: false
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getStoredMissingBrowserSplitWidth() {
|
||||
@@ -1125,7 +1096,7 @@ export const missingBrowserMethods = {
|
||||
getMissingBrowserSplitBoundsForWidth(width) {
|
||||
const safeWidth = Math.max(1, Number(width) || 1);
|
||||
const available = Math.max(1, safeWidth - 10);
|
||||
const minListWidth = Math.min(360, Math.max(240, Math.floor(available * 0.28)));
|
||||
const minListWidth = Math.min(340, available);
|
||||
const max = Math.max(1, available - minListWidth);
|
||||
const min = Math.min(max, Math.min(420, Math.max(300, Math.floor(available * 0.3))));
|
||||
|
||||
@@ -1142,7 +1113,7 @@ export const missingBrowserMethods = {
|
||||
},
|
||||
|
||||
scheduleMissingBrowserSplitRestore(browser, browserWidth = null, { useDefault = false } = {}) {
|
||||
if (!(browser instanceof HTMLElement) || browser.classList.contains('is-detail-collapsed')) return;
|
||||
if (!(browser instanceof HTMLElement)) return;
|
||||
|
||||
this._pendingMissingBrowserRestoreBrowser = browser;
|
||||
this._pendingMissingBrowserRestoreWidth = Number.isFinite(Number(browserWidth)) && Number(browserWidth) > 0
|
||||
@@ -1182,7 +1153,7 @@ export const missingBrowserMethods = {
|
||||
},
|
||||
|
||||
startMissingBrowserSplitDrag(event, browser, splitter = null, panes = {}) {
|
||||
if (!(browser instanceof HTMLElement) || browser.classList.contains('is-detail-collapsed')) return;
|
||||
if (!(browser instanceof HTMLElement)) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation?.();
|
||||
|
||||
Reference in New Issue
Block a user