From 00594663643195f22ff27ec1e1156f139fe0b177 Mon Sep 17 00:00:00 2001 From: tonypzy <2225005901@qq.com> Date: Sat, 1 Aug 2026 23:16:17 -0500 Subject: [PATCH 1/4] correct relayout ordering and state updates --- js/main.js | 129 ++++++++++++++++++++++++++++------------------------- 1 file changed, 67 insertions(+), 62 deletions(-) diff --git a/js/main.js b/js/main.js index 015de3307..2f674b72e 100644 --- a/js/main.js +++ b/js/main.js @@ -432,7 +432,7 @@ const App = () => { return { ...prev, panes: newPanes, layout: newLayout }; }); setFocusedPaneID(focusedPaneID === paneID ? null : focusedPaneID); - callbacks.current.push('relayout'); + relayout(); } }; @@ -617,19 +617,21 @@ const App = () => { updateLayout(layout); }; - const rebin = (layout) => { + const rebin = (layout, layoutID = selection.layoutID) => { layout = layout ? layout : storeData.layout; - let layoutID = selection.layoutID; if (layoutID !== DEFAULT_LAYOUT) { let envLayoutList = getCurrLayoutList(); - let layoutMap = envLayoutList.get(selection.layoutID); + let layoutMap = envLayoutList.get(layoutID); layout = layout.map((paneLayout) => { if (layoutMap.has(paneLayout.i)) { let storedVals = layoutMap.get(paneLayout.i); - paneLayout.h = storedVals[1]; - paneLayout.height = storedVals[1]; - paneLayout.w = storedVals[2]; - paneLayout.width = storedVals[2]; + return { + ...paneLayout, + h: storedVals[1], + height: storedVals[1], + w: storedVals[2], + width: storedVals[2], + }; } return paneLayout; }); @@ -653,55 +655,63 @@ const App = () => { } }; - const relayout = () => { - let layout = rebin(); - - let sorted = sortLayout(layout); - let newPanes = Object.assign({}, storeData.panes); - let filter = getValidFilter(filterString); - let old_sorted = sorted.slice(); - let layoutID = selection.layoutID; + const relayout = ({ + layoutID = selection.layoutID, + filterString: nextFilterString = filterString, + } = {}) => { let envLayoutList = getCurrLayoutList(); - let layoutMap = envLayoutList.get(selection.layoutID); - // Sort out things that were filtered away - sorted = sorted.sort(function (a, b) { - let diff = - (newPanes[a.i].title.match(filter) != null) - - (newPanes[b.i].title.match(filter) != null); - if (diff != 0) { - return -diff; - } else if (layoutID !== DEFAULT_LAYOUT) { - let aVal = layoutMap.has(a.i) ? -layoutMap.get(a.i)[0] : 1; - let bVal = layoutMap.has(b.i) ? -layoutMap.get(b.i)[0] : 1; - let diff = bVal - aVal; + let layoutMap = envLayoutList.get(layoutID); + let filter = getValidFilter(nextFilterString); + + setStoreData((prev) => { + let sorted = sortLayout(prev.layout); + let old_sorted = sorted.slice(); + let newPanes = Object.assign({}, prev.panes); + + // Sort out things that were filtered away + sorted = sorted.sort(function (a, b) { + let diff = + (newPanes[a.i].title.match(filter) != null) - + (newPanes[b.i].title.match(filter) != null); if (diff != 0) { - // At least one of the two was in the layout map. - return diff; + return -diff; + } else if (layoutID !== DEFAULT_LAYOUT) { + let aVal = layoutMap.has(a.i) ? -layoutMap.get(a.i)[0] : 1; + let bVal = layoutMap.has(b.i) ? -layoutMap.get(b.i)[0] : 1; + let diff = bVal - aVal; + if (diff != 0) { + // At least one of the two was in the layout map. + return diff; + } } - } - return old_sorted.indexOf(a) - old_sorted.indexOf(b); // stable sort - }); + return old_sorted.indexOf(a) - old_sorted.indexOf(b); // stable sort + }); - let newLayout = sorted.map((paneLayout, idx) => { - let pos = _bin.current.position(idx, windowSize.current.cols); + // The bin packer indexes its dimensions by pane order, so initialize it + // only after the final filtered/saved-view order has been determined. + sorted = rebin(sorted, layoutID); - newPanes[paneLayout.i].i = idx; + let newLayout = sorted.map((paneLayout, idx) => { + let pos = _bin.current.position(idx, windowSize.current.cols); - return Object.assign({}, paneLayout, pos); - }); + newPanes[paneLayout.i] = { + ...newPanes[paneLayout.i], + i: idx, + }; - setStoreData((prev) => ({ - ...prev, - panes: newPanes, - })); - updateLayout(newLayout); + return Object.assign({}, paneLayout, pos); + }); + + return { + ...prev, + panes: newPanes, + layout: newLayout, + }; + }); }; const updateLayout = (layout) => { setStoreData((prev) => ({ ...prev, layout: layout })); - // TODO this is very non-conventional react, someday it shall be fixed but - // for now it's important to fix relayout grossness - storeData.layout = layout; }; const resizePaneLive = (layout) => { updateLayout(layout); @@ -724,13 +734,8 @@ const App = () => { ...prev, layoutID: newLayoutID, })); - // TODO this is very non-conventional react, someday it shall be fixed but - // for now it's important to fix relayout grossness - selection.layoutID = newLayoutID; - if (selection.layoutID !== DEFAULT_LAYOUT) { - callbacks.current.push('relayout'); - callbacks.current.push('relayout'); - callbacks.current.push('relayout'); + if (newLayoutID !== DEFAULT_LAYOUT) { + relayout({ layoutID: newLayoutID }); } }; @@ -827,13 +832,13 @@ const App = () => { } }, []); - // flush pre-render callbacks + // Run callbacks after state updates have been committed. const callbacks = useRef([]); - callbacks.current.forEach((cb) => { - if (cb === 'relayout') relayout(); - else if (cb) cb(); + useEffect(() => { + let pendingCallbacks = callbacks.current; + callbacks.current = []; + pendingCallbacks.forEach((cb) => cb()); }); - callbacks.current = []; // ask server for envs after registration succeeded useEffect(() => { @@ -1054,7 +1059,6 @@ const App = () => { layoutList={getCurrLayoutList()} onRepackButton={() => { relayout(); - relayout(); }} onViewChange={updateToLayout} onViewManageButton={() => setShowViewModal(!showViewModal)} @@ -1071,12 +1075,13 @@ const App = () => { { - setFilterString(ev.target.value); - callbacks.current.push('relayout'); + let nextFilterString = ev.target.value; + setFilterString(nextFilterString); + relayout({ filterString: nextFilterString }); }} onFilterClear={() => { setFilterString(''); - callbacks.current.push('relayout'); + relayout({ filterString: '' }); }} /> ); From 2db7214293f61db2c2228ddbe123ebe4568305f0 Mon Sep 17 00:00:00 2001 From: tonypzy <2225005901@qq.com> Date: Sun, 2 Aug 2026 19:25:02 -0600 Subject: [PATCH 2/4] keep relayout state updater pure --- js/main.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/js/main.js b/js/main.js index 2f674b72e..98642067b 100644 --- a/js/main.js +++ b/js/main.js @@ -299,7 +299,7 @@ const App = () => { showSavedStateRecoveryToast(); }); if (_bin.current == null) { - rebin(); + _bin.current = createBin(newLayout, windowSize.current.cols); } let paneLayout; if (stored) { @@ -443,7 +443,6 @@ const App = () => { Object.keys(storeData.panes).map((paneID) => { closePane(paneID, false, false); }); - rebin(); setStoreData((prev) => ({ ...prev, layout: [], @@ -617,12 +616,9 @@ const App = () => { updateLayout(layout); }; - const rebin = (layout, layoutID = selection.layoutID) => { - layout = layout ? layout : storeData.layout; + const applySavedLayout = (layout, layoutID, layoutMap) => { if (layoutID !== DEFAULT_LAYOUT) { - let envLayoutList = getCurrLayoutList(); - let layoutMap = envLayoutList.get(layoutID); - layout = layout.map((paneLayout) => { + return layout.map((paneLayout) => { if (layoutMap.has(paneLayout.i)) { let storedVals = layoutMap.get(paneLayout.i); return { @@ -636,6 +632,11 @@ const App = () => { return paneLayout; }); } + + return layout; + }; + + const createBin = (layout, cols) => { let contents = layout.map((paneLayout) => { return { width: paneLayout.w, @@ -643,8 +644,7 @@ const App = () => { }; }); - _bin.current = new Bin.ShelfFirst(contents, windowSize.current.cols); - return layout; + return new Bin.ShelfFirst(contents, cols); }; const getCurrLayoutList = () => { @@ -662,6 +662,7 @@ const App = () => { let envLayoutList = getCurrLayoutList(); let layoutMap = envLayoutList.get(layoutID); let filter = getValidFilter(nextFilterString); + let cols = windowSize.current.cols; setStoreData((prev) => { let sorted = sortLayout(prev.layout); @@ -689,10 +690,11 @@ const App = () => { // The bin packer indexes its dimensions by pane order, so initialize it // only after the final filtered/saved-view order has been determined. - sorted = rebin(sorted, layoutID); + sorted = applySavedLayout(sorted, layoutID, layoutMap); + let bin = createBin(sorted, cols); let newLayout = sorted.map((paneLayout, idx) => { - let pos = _bin.current.position(idx, windowSize.current.cols); + let pos = bin.position(idx, cols); newPanes[paneLayout.i] = { ...newPanes[paneLayout.i], @@ -716,6 +718,9 @@ const App = () => { const resizePaneLive = (layout) => { updateLayout(layout); }; + React.useLayoutEffect(() => { + _bin.current = createBin(storeData.layout, windowSize.current.cols); + }, [storeData.layout]); useEffect(() => { clearTimeout(localStorageTimer.current); localStorageTimer.current = setTimeout(() => { From 0813e1890add42ed1d6b85dccc11a815d3a17cbf Mon Sep 17 00:00:00 2001 From: tonypzy <2225005901@qq.com> Date: Tue, 4 Aug 2026 00:44:49 -0700 Subject: [PATCH 3/4] move layoutMap to setStoreData --- js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/main.js b/js/main.js index 98642067b..49b9a552a 100644 --- a/js/main.js +++ b/js/main.js @@ -660,11 +660,11 @@ const App = () => { filterString: nextFilterString = filterString, } = {}) => { let envLayoutList = getCurrLayoutList(); - let layoutMap = envLayoutList.get(layoutID); let filter = getValidFilter(nextFilterString); let cols = windowSize.current.cols; setStoreData((prev) => { + let layoutMap = envLayoutList.get(layoutID); let sorted = sortLayout(prev.layout); let old_sorted = sorted.slice(); let newPanes = Object.assign({}, prev.panes); From be2118b4d1e413e10a8465e91464587b294363ea Mon Sep 17 00:00:00 2001 From: tonypzy <2225005901@qq.com> Date: Wed, 5 Aug 2026 13:28:46 -0700 Subject: [PATCH 4/4] match style --- js/main.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/main.js b/js/main.js index 49b9a552a..ecca2bd67 100644 --- a/js/main.js +++ b/js/main.js @@ -14,7 +14,13 @@ import 'fetch'; import 'rc-tree-select/assets/index.less'; -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { + useContext, + useEffect, + useLayoutEffect, + useRef, + useState, +} from 'react'; import ReactDOM from 'react-dom'; import ReactGridLayout, { getLayoutItem, @@ -718,7 +724,7 @@ const App = () => { const resizePaneLive = (layout) => { updateLayout(layout); }; - React.useLayoutEffect(() => { + useLayoutEffect(() => { _bin.current = createBin(storeData.layout, windowSize.current.cols); }, [storeData.layout]); useEffect(() => {