tabminal 3.0.10 → 3.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +268 -83
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -97,6 +97,7 @@ const editorPane = document.getElementById('editor-pane');
|
|
|
97
97
|
// #region Configuration
|
|
98
98
|
const HEARTBEAT_INTERVAL_MS = 1000;
|
|
99
99
|
const RECONNECT_RETRY_MS = 5000;
|
|
100
|
+
const FILE_TREE_REFRESH_INTERVAL_MS = 3000;
|
|
100
101
|
const MAIN_SERVER_ID = 'main';
|
|
101
102
|
const RUNTIME_BOOT_ID_STORAGE_KEY = 'tabminal_runtime_boot_id';
|
|
102
103
|
const WORKSPACE_DEVICE_ID_STORAGE_KEY = 'tabminal_workspace_device_id';
|
|
@@ -668,6 +669,7 @@ class EditorManager {
|
|
|
668
669
|
this.currentSession = null;
|
|
669
670
|
this.iconMap = null;
|
|
670
671
|
this.agentTimestampTimer = null;
|
|
672
|
+
this.treeRefreshTimer = null;
|
|
671
673
|
|
|
672
674
|
// DOM Elements
|
|
673
675
|
this.pane = document.getElementById('editor-pane');
|
|
@@ -1523,8 +1525,234 @@ class EditorManager {
|
|
|
1523
1525
|
|
|
1524
1526
|
refreshSessionTree(session) {
|
|
1525
1527
|
if (!session || !session.fileTreeElement) return;
|
|
1526
|
-
session.
|
|
1527
|
-
|
|
1528
|
+
session.fileTreeRenderToken = (session.fileTreeRenderToken || 0) + 1;
|
|
1529
|
+
const renderToken = session.fileTreeRenderToken;
|
|
1530
|
+
const scrollTop = session.fileTreeElement.scrollTop;
|
|
1531
|
+
void this.renderTree(
|
|
1532
|
+
session.cwd,
|
|
1533
|
+
session.fileTreeElement,
|
|
1534
|
+
session,
|
|
1535
|
+
renderToken
|
|
1536
|
+
).finally(() => {
|
|
1537
|
+
if (
|
|
1538
|
+
session.fileTreeElement
|
|
1539
|
+
&& session.fileTreeRenderToken === renderToken
|
|
1540
|
+
) {
|
|
1541
|
+
session.fileTreeElement.scrollTop = scrollTop;
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
this.updateTreeAutoRefresh();
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
isSessionTreeVisible(session) {
|
|
1548
|
+
return !!session?.fileTreeElement && !!session?.editorState?.isVisible;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
refreshVisibleSessionTrees() {
|
|
1552
|
+
for (const session of state.sessions.values()) {
|
|
1553
|
+
if (this.isSessionTreeVisible(session)) {
|
|
1554
|
+
this.requestSessionTreeRefresh(session);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
requestSessionTreeRefresh(session) {
|
|
1560
|
+
if (!this.isSessionTreeVisible(session)) {
|
|
1561
|
+
this.updateTreeAutoRefresh();
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
if (session.fileTreeRefreshQueued) return;
|
|
1565
|
+
session.fileTreeRefreshQueued = true;
|
|
1566
|
+
requestAnimationFrame(() => {
|
|
1567
|
+
session.fileTreeRefreshQueued = false;
|
|
1568
|
+
if (this.isSessionTreeVisible(session)) {
|
|
1569
|
+
this.refreshSessionTree(session);
|
|
1570
|
+
} else {
|
|
1571
|
+
this.updateTreeAutoRefresh();
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
updateTreeAutoRefresh() {
|
|
1577
|
+
const shouldRun = (
|
|
1578
|
+
document.visibilityState === 'visible'
|
|
1579
|
+
&& Array.from(state.sessions.values()).some(
|
|
1580
|
+
(session) => this.isSessionTreeVisible(session)
|
|
1581
|
+
)
|
|
1582
|
+
);
|
|
1583
|
+
if (shouldRun && !this.treeRefreshTimer) {
|
|
1584
|
+
this.treeRefreshTimer = window.setInterval(() => {
|
|
1585
|
+
if (document.visibilityState !== 'visible') {
|
|
1586
|
+
this.updateTreeAutoRefresh();
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
const hasVisibleTrees = Array.from(state.sessions.values()).some(
|
|
1590
|
+
(session) => this.isSessionTreeVisible(session)
|
|
1591
|
+
);
|
|
1592
|
+
if (!hasVisibleTrees) {
|
|
1593
|
+
this.updateTreeAutoRefresh();
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
this.refreshVisibleSessionTrees();
|
|
1597
|
+
}, FILE_TREE_REFRESH_INTERVAL_MS);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (!shouldRun && this.treeRefreshTimer) {
|
|
1601
|
+
window.clearInterval(this.treeRefreshTimer);
|
|
1602
|
+
this.treeRefreshTimer = null;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
ensureTreeList(container) {
|
|
1607
|
+
const existing = Array.from(container.children).find(
|
|
1608
|
+
(child) => child.tagName === 'UL'
|
|
1609
|
+
);
|
|
1610
|
+
if (existing) return existing;
|
|
1611
|
+
const list = document.createElement('ul');
|
|
1612
|
+
container.appendChild(list);
|
|
1613
|
+
return list;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
getTreeChildList(item) {
|
|
1617
|
+
return Array.from(item.children).find((child) => child.tagName === 'UL')
|
|
1618
|
+
|| null;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
getTreeItemExpanded(filePath, session) {
|
|
1622
|
+
return session.sharedWorkspaceState.expandedPaths.includes(filePath);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
updateTreeItem(li, file, session, renderToken) {
|
|
1626
|
+
li.dataset.path = file.path;
|
|
1627
|
+
li.dataset.isDirectory = file.isDirectory ? '1' : '0';
|
|
1628
|
+
|
|
1629
|
+
let row = Array.from(li.children).find(
|
|
1630
|
+
(child) => child.classList?.contains('file-tree-item')
|
|
1631
|
+
);
|
|
1632
|
+
if (!row) {
|
|
1633
|
+
row = document.createElement('div');
|
|
1634
|
+
row.className = 'file-tree-item';
|
|
1635
|
+
li.prepend(row);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
let icon = row.querySelector('.icon');
|
|
1639
|
+
if (!icon) {
|
|
1640
|
+
icon = document.createElement('span');
|
|
1641
|
+
icon.className = 'icon';
|
|
1642
|
+
row.appendChild(icon);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
let name = Array.from(row.children).find(
|
|
1646
|
+
(child) => child !== icon
|
|
1647
|
+
);
|
|
1648
|
+
if (!name) {
|
|
1649
|
+
name = document.createElement('span');
|
|
1650
|
+
row.appendChild(name);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
row.className = 'file-tree-item';
|
|
1654
|
+
if (file.isDirectory) {
|
|
1655
|
+
row.classList.add('is-dir');
|
|
1656
|
+
}
|
|
1657
|
+
row.classList.toggle(
|
|
1658
|
+
'active',
|
|
1659
|
+
!file.isDirectory
|
|
1660
|
+
&& session.editorState.activeFilePath === file.path
|
|
1661
|
+
);
|
|
1662
|
+
|
|
1663
|
+
const isExpanded = file.isDirectory
|
|
1664
|
+
&& this.getTreeItemExpanded(file.path, session);
|
|
1665
|
+
li.classList.toggle('expanded', isExpanded);
|
|
1666
|
+
icon.innerHTML = this.getIcon(file.name, file.isDirectory, isExpanded);
|
|
1667
|
+
name.textContent = file.name;
|
|
1668
|
+
|
|
1669
|
+
row.onclick = async (e) => {
|
|
1670
|
+
e.stopPropagation();
|
|
1671
|
+
if (file.isDirectory) {
|
|
1672
|
+
if (li.classList.contains('expanded')) {
|
|
1673
|
+
li.classList.remove('expanded');
|
|
1674
|
+
session.sharedWorkspaceState.expandedPaths =
|
|
1675
|
+
session.sharedWorkspaceState.expandedPaths
|
|
1676
|
+
.filter((path) => path !== file.path);
|
|
1677
|
+
session.saveState({ touchWorkspace: true });
|
|
1678
|
+
void session.server.fetch('/api/memory/expand', {
|
|
1679
|
+
method: 'POST',
|
|
1680
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1681
|
+
body: JSON.stringify({
|
|
1682
|
+
path: file.path,
|
|
1683
|
+
expanded: false
|
|
1684
|
+
})
|
|
1685
|
+
});
|
|
1686
|
+
icon.innerHTML = this.getIcon(file.name, true, false);
|
|
1687
|
+
const childUl = this.getTreeChildList(li);
|
|
1688
|
+
if (childUl) {
|
|
1689
|
+
childUl.remove();
|
|
1690
|
+
}
|
|
1691
|
+
this.updateTreeAutoRefresh();
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
li.classList.add('expanded');
|
|
1696
|
+
session.sharedWorkspaceState.expandedPaths =
|
|
1697
|
+
uniqueStringList([
|
|
1698
|
+
...session.sharedWorkspaceState.expandedPaths,
|
|
1699
|
+
file.path
|
|
1700
|
+
]);
|
|
1701
|
+
session.saveState({ touchWorkspace: true });
|
|
1702
|
+
void session.server.fetch('/api/memory/expand', {
|
|
1703
|
+
method: 'POST',
|
|
1704
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1705
|
+
body: JSON.stringify({
|
|
1706
|
+
path: file.path,
|
|
1707
|
+
expanded: true
|
|
1708
|
+
})
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
icon.innerHTML = this.getIcon(file.name, true, true);
|
|
1712
|
+
await this.renderTree(file.path, li, session, renderToken);
|
|
1713
|
+
this.updateTreeAutoRefresh();
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
await this.openFile(file.path, session);
|
|
1718
|
+
this.requestSessionTreeRefresh(session);
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
if (!isExpanded) {
|
|
1722
|
+
const childUl = this.getTreeChildList(li);
|
|
1723
|
+
if (childUl) {
|
|
1724
|
+
childUl.remove();
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
reconcileTreeList(list, files, session, renderToken) {
|
|
1730
|
+
const existingItems = new Map();
|
|
1731
|
+
Array.from(list.children).forEach((child) => {
|
|
1732
|
+
if (child.tagName === 'LI' && child.dataset.path) {
|
|
1733
|
+
existingItems.set(child.dataset.path, child);
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
const orderedItems = [];
|
|
1738
|
+
for (const file of files) {
|
|
1739
|
+
let li = existingItems.get(file.path) || null;
|
|
1740
|
+
if (!li) {
|
|
1741
|
+
li = document.createElement('li');
|
|
1742
|
+
} else {
|
|
1743
|
+
existingItems.delete(file.path);
|
|
1744
|
+
}
|
|
1745
|
+
this.updateTreeItem(li, file, session, renderToken);
|
|
1746
|
+
orderedItems.push(li);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
for (const li of existingItems.values()) {
|
|
1750
|
+
li.remove();
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
for (const li of orderedItems) {
|
|
1754
|
+
list.appendChild(li);
|
|
1755
|
+
}
|
|
1528
1756
|
}
|
|
1529
1757
|
|
|
1530
1758
|
initMonaco() {
|
|
@@ -1656,6 +1884,7 @@ class EditorManager {
|
|
|
1656
1884
|
this.updateEditorPaneVisibility();
|
|
1657
1885
|
}
|
|
1658
1886
|
|
|
1887
|
+
this.updateTreeAutoRefresh();
|
|
1659
1888
|
session.updateTabUI();
|
|
1660
1889
|
session.saveState({ touchWorkspace: true });
|
|
1661
1890
|
}
|
|
@@ -1694,6 +1923,7 @@ class EditorManager {
|
|
|
1694
1923
|
|
|
1695
1924
|
this.updateEditorPaneVisibility();
|
|
1696
1925
|
this.updateTerminalLayoutButton();
|
|
1926
|
+
this.updateTreeAutoRefresh();
|
|
1697
1927
|
|
|
1698
1928
|
// Restore layout
|
|
1699
1929
|
if (session.layoutState) {
|
|
@@ -1719,96 +1949,37 @@ class EditorManager {
|
|
|
1719
1949
|
}
|
|
1720
1950
|
}
|
|
1721
1951
|
|
|
1722
|
-
async renderTree(
|
|
1952
|
+
async renderTree(
|
|
1953
|
+
dirPath,
|
|
1954
|
+
container,
|
|
1955
|
+
session,
|
|
1956
|
+
renderToken = session?.fileTreeRenderToken || 0
|
|
1957
|
+
) {
|
|
1723
1958
|
try {
|
|
1724
|
-
const res = await session.server.fetch(
|
|
1959
|
+
const res = await session.server.fetch(
|
|
1960
|
+
`/api/fs/list?path=${encodeURIComponent(dirPath)}`
|
|
1961
|
+
);
|
|
1725
1962
|
if (!res.ok) return;
|
|
1726
1963
|
const files = await res.json();
|
|
1964
|
+
if ((session.fileTreeRenderToken || 0) !== renderToken) return;
|
|
1965
|
+
|
|
1966
|
+
const list = this.ensureTreeList(container);
|
|
1967
|
+
this.reconcileTreeList(list, files, session, renderToken);
|
|
1968
|
+
if ((session.fileTreeRenderToken || 0) !== renderToken) return;
|
|
1727
1969
|
|
|
1728
|
-
const ul = document.createElement('ul');
|
|
1729
|
-
|
|
1730
1970
|
for (const file of files) {
|
|
1731
|
-
const li = document.createElement('li');
|
|
1732
|
-
const div = document.createElement('div');
|
|
1733
|
-
div.className = 'file-tree-item';
|
|
1734
|
-
if (file.isDirectory) div.classList.add('is-dir');
|
|
1735
|
-
|
|
1736
|
-
let isExpanded = false;
|
|
1737
1971
|
if (
|
|
1738
1972
|
file.isDirectory
|
|
1739
|
-
&&
|
|
1740
|
-
file.path
|
|
1741
|
-
)
|
|
1973
|
+
&& this.getTreeItemExpanded(file.path, session)
|
|
1742
1974
|
) {
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
icon.className = 'icon';
|
|
1749
|
-
icon.innerHTML = this.getIcon(file.name, file.isDirectory, isExpanded);
|
|
1750
|
-
|
|
1751
|
-
const name = document.createElement('span');
|
|
1752
|
-
name.textContent = file.name;
|
|
1753
|
-
|
|
1754
|
-
div.appendChild(icon);
|
|
1755
|
-
div.appendChild(name);
|
|
1756
|
-
|
|
1757
|
-
div.addEventListener('click', async (e) => {
|
|
1758
|
-
e.stopPropagation();
|
|
1759
|
-
if (file.isDirectory) {
|
|
1760
|
-
if (li.classList.contains('expanded')) {
|
|
1761
|
-
li.classList.remove('expanded');
|
|
1762
|
-
session.sharedWorkspaceState.expandedPaths =
|
|
1763
|
-
session.sharedWorkspaceState.expandedPaths
|
|
1764
|
-
.filter((path) => path !== file.path);
|
|
1765
|
-
session.saveState({ touchWorkspace: true });
|
|
1766
|
-
void session.server.fetch('/api/memory/expand', {
|
|
1767
|
-
method: 'POST',
|
|
1768
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1769
|
-
body: JSON.stringify({
|
|
1770
|
-
path: file.path,
|
|
1771
|
-
expanded: false
|
|
1772
|
-
})
|
|
1773
|
-
});
|
|
1774
|
-
|
|
1775
|
-
icon.innerHTML = this.getIcon(file.name, true, false);
|
|
1776
|
-
const childUl = li.querySelector('ul');
|
|
1777
|
-
if (childUl) childUl.remove();
|
|
1778
|
-
} else {
|
|
1779
|
-
li.classList.add('expanded');
|
|
1780
|
-
session.sharedWorkspaceState.expandedPaths =
|
|
1781
|
-
uniqueStringList([
|
|
1782
|
-
...session.sharedWorkspaceState.expandedPaths,
|
|
1783
|
-
file.path
|
|
1784
|
-
]);
|
|
1785
|
-
session.saveState({ touchWorkspace: true });
|
|
1786
|
-
void session.server.fetch('/api/memory/expand', {
|
|
1787
|
-
method: 'POST',
|
|
1788
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1789
|
-
body: JSON.stringify({
|
|
1790
|
-
path: file.path,
|
|
1791
|
-
expanded: true
|
|
1792
|
-
})
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
icon.innerHTML = this.getIcon(file.name, true, true);
|
|
1796
|
-
await this.renderTree(file.path, li, session);
|
|
1797
|
-
}
|
|
1798
|
-
} else {
|
|
1799
|
-
await this.openFile(file.path, session);
|
|
1975
|
+
const item = Array.from(list.children).find(
|
|
1976
|
+
(child) => child.dataset.path === file.path
|
|
1977
|
+
);
|
|
1978
|
+
if (item) {
|
|
1979
|
+
void this.renderTree(file.path, item, session, renderToken);
|
|
1800
1980
|
}
|
|
1801
|
-
});
|
|
1802
|
-
|
|
1803
|
-
li.appendChild(div);
|
|
1804
|
-
|
|
1805
|
-
if (isExpanded) {
|
|
1806
|
-
this.renderTree(file.path, li, session);
|
|
1807
1981
|
}
|
|
1808
|
-
|
|
1809
|
-
ul.appendChild(li);
|
|
1810
1982
|
}
|
|
1811
|
-
container.appendChild(ul);
|
|
1812
1983
|
} catch (err) {
|
|
1813
1984
|
console.error('Failed to render tree:', err);
|
|
1814
1985
|
}
|
|
@@ -4884,11 +5055,12 @@ class Session {
|
|
|
4884
5055
|
if (workspaceChanged) {
|
|
4885
5056
|
if (this.fileTreeElement) {
|
|
4886
5057
|
if (this.editorState.isVisible) {
|
|
4887
|
-
editorManager.
|
|
5058
|
+
editorManager.requestSessionTreeRefresh(this);
|
|
4888
5059
|
} else {
|
|
4889
5060
|
this.fileTreeElement.innerHTML = '';
|
|
4890
5061
|
}
|
|
4891
5062
|
}
|
|
5063
|
+
editorManager.updateTreeAutoRefresh();
|
|
4892
5064
|
}
|
|
4893
5065
|
if (workspaceChanged && state.activeSessionKey === this.key) {
|
|
4894
5066
|
refreshWorkspaceIfSessionActive(this);
|
|
@@ -5146,6 +5318,9 @@ class Session {
|
|
|
5146
5318
|
this.runningCommand = '';
|
|
5147
5319
|
this.needsAttention = false;
|
|
5148
5320
|
this.updateTabUI();
|
|
5321
|
+
if (this.editorState.isVisible) {
|
|
5322
|
+
editorManager.requestSessionTreeRefresh(this);
|
|
5323
|
+
}
|
|
5149
5324
|
if (state.activeSessionKey === this.key) {
|
|
5150
5325
|
editorManager.renderEditorTabs();
|
|
5151
5326
|
}
|
|
@@ -5205,6 +5380,10 @@ class Session {
|
|
|
5205
5380
|
this.needsAttention = false;
|
|
5206
5381
|
}
|
|
5207
5382
|
|
|
5383
|
+
if (this.editorState.isVisible) {
|
|
5384
|
+
editorManager.requestSessionTreeRefresh(this);
|
|
5385
|
+
}
|
|
5386
|
+
|
|
5208
5387
|
this.updateTabUI();
|
|
5209
5388
|
if (state.activeSessionKey === this.key) {
|
|
5210
5389
|
editorManager.renderEditorTabs();
|
|
@@ -10876,7 +11055,7 @@ function createTabElement(session) {
|
|
|
10876
11055
|
session.fileTreeElement = fileTree;
|
|
10877
11056
|
|
|
10878
11057
|
if (session.editorState && session.editorState.isVisible) {
|
|
10879
|
-
editorManager.
|
|
11058
|
+
editorManager.refreshSessionTree(session);
|
|
10880
11059
|
}
|
|
10881
11060
|
tab.appendChild(fileTree);
|
|
10882
11061
|
|
|
@@ -11244,10 +11423,14 @@ document.addEventListener('keydown', noteAppInteraction, {
|
|
|
11244
11423
|
window.addEventListener('focus', () => {
|
|
11245
11424
|
noteAppInteraction();
|
|
11246
11425
|
enterAppNotificationQuietPeriod();
|
|
11426
|
+
editorManager.refreshVisibleSessionTrees();
|
|
11427
|
+
editorManager.updateTreeAutoRefresh();
|
|
11247
11428
|
});
|
|
11248
11429
|
window.addEventListener('pageshow', () => {
|
|
11249
11430
|
noteAppInteraction();
|
|
11250
11431
|
enterAppNotificationQuietPeriod();
|
|
11432
|
+
editorManager.refreshVisibleSessionTrees();
|
|
11433
|
+
editorManager.updateTreeAutoRefresh();
|
|
11251
11434
|
});
|
|
11252
11435
|
|
|
11253
11436
|
document.addEventListener('click', () => {
|
|
@@ -11258,7 +11441,9 @@ document.addEventListener('visibilitychange', () => {
|
|
|
11258
11441
|
noteAppInteraction();
|
|
11259
11442
|
enterAppNotificationQuietPeriod();
|
|
11260
11443
|
clearVisibleAttentionState();
|
|
11444
|
+
editorManager.refreshVisibleSessionTrees();
|
|
11261
11445
|
}
|
|
11446
|
+
editorManager.updateTreeAutoRefresh();
|
|
11262
11447
|
});
|
|
11263
11448
|
// #endregion
|
|
11264
11449
|
|