@lvce-editor/main-area-worker 8.5.0 → 8.7.0

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.
@@ -87,7 +87,231 @@ const terminate = () => {
87
87
  globalThis.close();
88
88
  };
89
89
 
90
- const Button$1 = 1;
90
+ const closeTab = (state, groupId, tabId) => {
91
+ const {
92
+ layout
93
+ } = state;
94
+ const {
95
+ activeGroupId,
96
+ groups
97
+ } = layout;
98
+
99
+ // Find the group to close the tab from
100
+ const groupIndex = groups.findIndex(g => g.id === groupId);
101
+ if (groupIndex === -1) {
102
+ return state;
103
+ }
104
+ const group = groups[groupIndex];
105
+ // Check if the tab exists in the group
106
+ const tabWasRemoved = group.tabs.some(tab => tab.id === tabId);
107
+ if (!tabWasRemoved) {
108
+ return state;
109
+ }
110
+ const newGroups = groups.map(grp => {
111
+ if (grp.id === groupId) {
112
+ const newTabs = grp.tabs.filter(tab => tab.id !== tabId);
113
+ let newActiveTabId = grp.activeTabId;
114
+ if (grp.activeTabId === tabId) {
115
+ const tabIndex = grp.tabs.findIndex(tab => tab.id === tabId);
116
+ if (newTabs.length > 0) {
117
+ newActiveTabId = newTabs[Math.min(tabIndex, newTabs.length - 1)].id;
118
+ } else {
119
+ newActiveTabId = undefined;
120
+ }
121
+ }
122
+ return {
123
+ ...grp,
124
+ activeTabId: newActiveTabId,
125
+ isEmpty: newTabs.length === 0,
126
+ tabs: newTabs
127
+ };
128
+ }
129
+ return grp;
130
+ });
131
+
132
+ // If the group has no tabs left after closing, remove the group
133
+ const groupIsNowEmpty = newGroups[groupIndex].tabs.length === 0;
134
+ if (groupIsNowEmpty) {
135
+ const remainingGroups = newGroups.filter(group => group.id !== groupId);
136
+
137
+ // If there are remaining groups, redistribute sizes
138
+ if (remainingGroups.length > 0) {
139
+ const redistributedGroups = remainingGroups.map(grp => ({
140
+ ...grp,
141
+ size: Math.round(100 / remainingGroups.length)
142
+ }));
143
+ const newActiveGroupId = activeGroupId === groupId ? remainingGroups[0]?.id : activeGroupId;
144
+ return {
145
+ ...state,
146
+ layout: {
147
+ ...layout,
148
+ activeGroupId: newActiveGroupId,
149
+ groups: redistributedGroups
150
+ }
151
+ };
152
+ }
153
+
154
+ // If no remaining groups, return empty layout
155
+ return {
156
+ ...state,
157
+ layout: {
158
+ ...layout,
159
+ activeGroupId: undefined,
160
+ groups: []
161
+ }
162
+ };
163
+ }
164
+ return {
165
+ ...state,
166
+ layout: {
167
+ ...layout,
168
+ groups: newGroups
169
+ }
170
+ };
171
+ };
172
+
173
+ const closeActiveEditor = state => {
174
+ const {
175
+ layout
176
+ } = state;
177
+ const {
178
+ groups
179
+ } = layout;
180
+ const focusedGroup = groups.find(group => group.focused);
181
+ if (!focusedGroup) {
182
+ return state;
183
+ }
184
+ const {
185
+ activeTabId
186
+ } = focusedGroup;
187
+ if (activeTabId === undefined) {
188
+ return state;
189
+ }
190
+ return closeTab(state, focusedGroup.id, activeTabId);
191
+ };
192
+
193
+ const closeAll$1 = state => {
194
+ return {
195
+ ...state,
196
+ layout: {
197
+ ...state.layout,
198
+ activeGroupId: undefined,
199
+ groups: []
200
+ }
201
+ };
202
+ };
203
+
204
+ const closeFocusedTab = state => {
205
+ const {
206
+ layout
207
+ } = state;
208
+ const {
209
+ groups
210
+ } = layout;
211
+ const focusedGroup = groups.find(group => group.focused);
212
+ if (!focusedGroup) {
213
+ return state;
214
+ }
215
+ const {
216
+ activeTabId
217
+ } = focusedGroup;
218
+ if (activeTabId === undefined) {
219
+ return state;
220
+ }
221
+ return closeTab(state, focusedGroup.id, activeTabId);
222
+ };
223
+
224
+ const closeOtherTabs = (state, groupId) => {
225
+ const {
226
+ layout
227
+ } = state;
228
+ const {
229
+ activeGroupId,
230
+ groups
231
+ } = layout;
232
+ const targetGroupId = groupId ?? activeGroupId;
233
+ if (targetGroupId === undefined) {
234
+ return state;
235
+ }
236
+ const group = groups.find(g => g.id === targetGroupId);
237
+ if (!group) {
238
+ return state;
239
+ }
240
+ const {
241
+ activeTabId
242
+ } = group;
243
+ if (activeTabId === undefined) {
244
+ return state;
245
+ }
246
+ const newGroups = groups.map(g => {
247
+ if (g.id === targetGroupId) {
248
+ const newTabs = g.tabs.filter(tab => tab.id === activeTabId);
249
+ return {
250
+ ...g,
251
+ activeTabId,
252
+ isEmpty: newTabs.length === 0,
253
+ tabs: newTabs
254
+ };
255
+ }
256
+ return g;
257
+ });
258
+ return {
259
+ ...state,
260
+ layout: {
261
+ ...layout,
262
+ groups: newGroups
263
+ }
264
+ };
265
+ };
266
+
267
+ const closeTabsRight = (state, groupId) => {
268
+ const {
269
+ layout
270
+ } = state;
271
+ const {
272
+ groups
273
+ } = layout;
274
+ const group = groups.find(g => g.id === groupId);
275
+ if (!group) {
276
+ return state;
277
+ }
278
+ const {
279
+ activeTabId,
280
+ tabs
281
+ } = group;
282
+ if (activeTabId === undefined) {
283
+ return state;
284
+ }
285
+ const activeTabIndex = tabs.findIndex(tab => tab.id === activeTabId);
286
+ if (activeTabIndex === -1) {
287
+ return state;
288
+ }
289
+ const newTabs = tabs.slice(0, activeTabIndex + 1);
290
+ if (newTabs.length === tabs.length) {
291
+ return state;
292
+ }
293
+ const newGroups = groups.map(g => {
294
+ if (g.id === groupId) {
295
+ return {
296
+ ...g,
297
+ isEmpty: newTabs.length === 0,
298
+ tabs: newTabs
299
+ };
300
+ }
301
+ return g;
302
+ });
303
+ return {
304
+ ...state,
305
+ layout: {
306
+ ...layout,
307
+ groups: newGroups
308
+ }
309
+ };
310
+ };
311
+
312
+ const None$1 = 'none';
313
+
314
+ const Button$2 = 1;
91
315
  const Div = 4;
92
316
  const Span = 8;
93
317
  const Text = 12;
@@ -95,9 +319,10 @@ const Img = 17;
95
319
  const Pre = 51;
96
320
  const Reference = 100;
97
321
 
98
- const Button = 'event.button';
322
+ const Button$1 = 'event.button';
99
323
  const ClientX = 'event.clientX';
100
324
  const ClientY = 'event.clientY';
325
+ const EventTargetClassName = 'event.target.className';
101
326
  const TargetName = 'event.target.name';
102
327
 
103
328
  const Tab = 13;
@@ -110,6 +335,7 @@ const ExtensionHostWorker = 44;
110
335
  const IconThemeWorker = 7009;
111
336
  const RendererWorker = 1;
112
337
 
338
+ const SetCss = 'Viewlet.setCss';
113
339
  const SetDom2 = 'Viewlet.setDom2';
114
340
  const SetPatches = 'Viewlet.setPatches';
115
341
 
@@ -1261,7 +1487,7 @@ const create$j = async ({
1261
1487
  send
1262
1488
  }) => {
1263
1489
  return createSharedLazyRpc(() => {
1264
- return create$3({
1490
+ return create$3$1({
1265
1491
  commandMap,
1266
1492
  isMessagePortOpen,
1267
1493
  send
@@ -1289,7 +1515,7 @@ const create$5 = async ({
1289
1515
  messagePort.start();
1290
1516
  return rpc;
1291
1517
  };
1292
- const create$3 = async ({
1518
+ const create$3$1 = async ({
1293
1519
  commandMap,
1294
1520
  isMessagePortOpen,
1295
1521
  send
@@ -1307,7 +1533,7 @@ const create$3 = async ({
1307
1533
  };
1308
1534
  const TransferMessagePortRpcParent = {
1309
1535
  __proto__: null,
1310
- create: create$3
1536
+ create: create$3$1
1311
1537
  };
1312
1538
  const create$2$1 = async ({
1313
1539
  commandMap
@@ -1355,7 +1581,7 @@ const remove = id => {
1355
1581
  };
1356
1582
 
1357
1583
  /* eslint-disable @typescript-eslint/explicit-function-return-type */
1358
- const create$2 = rpcId => {
1584
+ const create$3 = rpcId => {
1359
1585
  return {
1360
1586
  async dispose() {
1361
1587
  const rpc = get$1(rpcId);
@@ -1393,16 +1619,16 @@ const create$2 = rpcId => {
1393
1619
 
1394
1620
  const {
1395
1621
  set: set$4
1396
- } = create$2(ClipBoardWorker);
1622
+ } = create$3(ClipBoardWorker);
1397
1623
 
1398
1624
  const {
1399
1625
  set: set$3
1400
- } = create$2(ExtensionHostWorker);
1626
+ } = create$3(ExtensionHostWorker);
1401
1627
 
1402
1628
  const {
1403
1629
  invoke: invoke$1,
1404
1630
  set: set$2
1405
- } = create$2(IconThemeWorker);
1631
+ } = create$3(IconThemeWorker);
1406
1632
  const getIcons = async iconRequests => {
1407
1633
  // @ts-ignore
1408
1634
  return invoke$1('IconTheme.getIcons', iconRequests);
@@ -1412,7 +1638,7 @@ const {
1412
1638
  invoke,
1413
1639
  invokeAndTransfer,
1414
1640
  set: set$1
1415
- } = create$2(RendererWorker);
1641
+ } = create$3(RendererWorker);
1416
1642
  const showContextMenu2 = async (uid, menuId, x, y, args) => {
1417
1643
  number(uid);
1418
1644
  number(menuId);
@@ -1433,10 +1659,17 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
1433
1659
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
1434
1660
  };
1435
1661
 
1436
- const handleAttach = async command => {
1437
- // TODO find a better way to append editors
1438
- const parentNodeSelector = '.editor-groups-container';
1439
- await invoke('Layout.attachViewlet', parentNodeSelector, command.instanceId);
1662
+ const copyPath$1 = async (state, path) => {
1663
+ string(path);
1664
+ await invoke('ClipBoard.writeText', path);
1665
+ return state;
1666
+ };
1667
+
1668
+ const copyRelativePath$1 = async (state, path) => {
1669
+ string(path);
1670
+ const relativePath = await invoke('Workspace.pathBaseName', path);
1671
+ await invoke('ClipBoard.writeText', relativePath);
1672
+ return state;
1440
1673
  };
1441
1674
 
1442
1675
  const {
@@ -1448,24 +1681,93 @@ const {
1448
1681
  wrapGetter
1449
1682
  } = create$6();
1450
1683
 
1451
- const create$1 = () => {
1452
- return Math.random();
1453
- };
1454
-
1455
- const updateTab = (state, tabId, updates) => {
1456
- const {
1457
- layout
1458
- } = state;
1459
- const {
1460
- groups
1461
- } = layout;
1462
- const updatedGroups = groups.map(group => {
1463
- const tabIndex = group.tabs.findIndex(t => t.id === tabId);
1464
- if (tabIndex === -1) {
1465
- return group;
1466
- }
1467
- const updatedTabs = group.tabs.map((tab, index) => {
1468
- if (index === tabIndex) {
1684
+ const create$2 = (uid, uri, x, y, width, height, platform, assetDir, tabHeight = 35) => {
1685
+ const state = {
1686
+ assetDir,
1687
+ fileIconCache: {},
1688
+ height,
1689
+ iframes: [],
1690
+ initial: true,
1691
+ layout: {
1692
+ activeGroupId: undefined,
1693
+ direction: 'horizontal',
1694
+ groups: []
1695
+ },
1696
+ platform,
1697
+ splitButtonEnabled: false,
1698
+ tabHeight,
1699
+ uid,
1700
+ width,
1701
+ workspaceuri: uri,
1702
+ x,
1703
+ y
1704
+ };
1705
+ set(uid, state, state);
1706
+ };
1707
+
1708
+ const isEqual$1 = (oldState, newState) => {
1709
+ return oldState.width === newState.width && oldState.height === newState.height;
1710
+ };
1711
+
1712
+ const isEqual = (oldState, newState) => {
1713
+ return oldState.layout === newState.layout;
1714
+ };
1715
+
1716
+ const RenderItems = 4;
1717
+ const RenderCss = 10;
1718
+ const RenderIncremental = 11;
1719
+
1720
+ const modules = [isEqual, isEqual$1];
1721
+ const numbers = [RenderIncremental, RenderCss];
1722
+
1723
+ const diff = (oldState, newState) => {
1724
+ const diffResult = [];
1725
+ for (let i = 0; i < modules.length; i++) {
1726
+ const fn = modules[i];
1727
+ if (!fn(oldState, newState)) {
1728
+ diffResult.push(numbers[i]);
1729
+ }
1730
+ }
1731
+ return diffResult;
1732
+ };
1733
+
1734
+ const diff2 = uid => {
1735
+ const {
1736
+ newState,
1737
+ oldState
1738
+ } = get(uid);
1739
+ const result = diff(oldState, newState);
1740
+ return result;
1741
+ };
1742
+
1743
+ const createViewlet = async (viewletModuleId, editorUid, tabId, bounds, uri) => {
1744
+ await invoke('Layout.createViewlet', viewletModuleId, editorUid, tabId, bounds, uri);
1745
+ };
1746
+
1747
+ const handleAttach = async command => {
1748
+ // TODO find a better way to append editors
1749
+ const parentNodeSelector = '.editor-groups-container';
1750
+ await invoke('Layout.attachViewlet', parentNodeSelector, command.instanceId);
1751
+ };
1752
+
1753
+ const create$1 = () => {
1754
+ return Math.random();
1755
+ };
1756
+
1757
+ const updateTab = (state, tabId, updates) => {
1758
+ const {
1759
+ layout
1760
+ } = state;
1761
+ const {
1762
+ groups
1763
+ } = layout;
1764
+ const updatedGroups = groups.map(group => {
1765
+ const tabIndex = group.tabs.findIndex(t => t.id === tabId);
1766
+ if (tabIndex === -1) {
1767
+ return group;
1768
+ }
1769
+ const updatedTabs = group.tabs.map((tab, index) => {
1770
+ if (index === tabIndex) {
1469
1771
  return {
1470
1772
  ...tab,
1471
1773
  ...updates
@@ -1689,387 +1991,94 @@ const executeViewletCommands = async commands => {
1689
1991
  }
1690
1992
  };
1691
1993
 
1692
- const closeTab = (state, groupId, tabId) => {
1994
+ const findTabById = (state, tabId) => {
1693
1995
  const {
1694
1996
  layout
1695
1997
  } = state;
1696
1998
  const {
1697
- activeGroupId,
1698
1999
  groups
1699
2000
  } = layout;
1700
-
1701
- // Find the group to close the tab from
1702
- const groupIndex = groups.findIndex(g => g.id === groupId);
1703
- if (groupIndex === -1) {
1704
- return state;
1705
- }
1706
- const group = groups[groupIndex];
1707
- // Check if the tab exists in the group
1708
- const tabWasRemoved = group.tabs.some(tab => tab.id === tabId);
1709
- if (!tabWasRemoved) {
1710
- return state;
1711
- }
1712
- const newGroups = groups.map(grp => {
1713
- if (grp.id === groupId) {
1714
- const newTabs = grp.tabs.filter(tab => tab.id !== tabId);
1715
- let newActiveTabId = grp.activeTabId;
1716
- if (grp.activeTabId === tabId) {
1717
- const tabIndex = grp.tabs.findIndex(tab => tab.id === tabId);
1718
- if (newTabs.length > 0) {
1719
- newActiveTabId = newTabs[Math.min(tabIndex, newTabs.length - 1)].id;
1720
- } else {
1721
- newActiveTabId = undefined;
1722
- }
1723
- }
2001
+ for (const group of groups) {
2002
+ const tab = group.tabs.find(t => t.id === tabId);
2003
+ if (tab) {
1724
2004
  return {
1725
- ...grp,
1726
- activeTabId: newActiveTabId,
1727
- isEmpty: newTabs.length === 0,
1728
- tabs: newTabs
2005
+ groupId: group.id,
2006
+ tab
1729
2007
  };
1730
2008
  }
1731
- return grp;
1732
- });
1733
-
1734
- // If the group has no tabs left after closing, remove the group
1735
- const groupIsNowEmpty = newGroups[groupIndex].tabs.length === 0;
1736
- if (groupIsNowEmpty) {
1737
- const remainingGroups = newGroups.filter(group => group.id !== groupId);
2009
+ }
2010
+ return undefined;
2011
+ };
1738
2012
 
1739
- // If there are remaining groups, redistribute sizes
1740
- if (remainingGroups.length > 0) {
1741
- const redistributedGroups = remainingGroups.map(grp => ({
1742
- ...grp,
1743
- size: Math.round(100 / remainingGroups.length)
1744
- }));
1745
- const newActiveGroupId = activeGroupId === groupId ? remainingGroups[0]?.id : activeGroupId;
1746
- return {
1747
- ...state,
1748
- layout: {
1749
- ...layout,
1750
- activeGroupId: newActiveGroupId,
1751
- groups: redistributedGroups
1752
- }
1753
- };
1754
- }
2013
+ // Counter for request IDs to handle race conditions
2014
+ let requestIdCounter = 0;
2015
+ const getNextRequestId = () => {
2016
+ return ++requestIdCounter;
2017
+ };
1755
2018
 
1756
- // If no remaining groups, return empty layout
1757
- return {
1758
- ...state,
1759
- layout: {
1760
- ...layout,
1761
- activeGroupId: undefined,
1762
- groups: []
1763
- }
2019
+ const startContentLoading = async (oldState, state, tabId, path, requestId) => {
2020
+ try {
2021
+ const getLatestState = () => {
2022
+ return get(state.uid).newState;
1764
2023
  };
2024
+ set(state.uid, oldState, state);
2025
+ const newState = await loadTabContentAsync(tabId, path, requestId, getLatestState);
2026
+ return newState;
2027
+ } catch {
2028
+ // Silently ignore errors - the tab may have been closed or the component unmounted
1765
2029
  }
1766
- return {
1767
- ...state,
1768
- layout: {
1769
- ...layout,
1770
- groups: newGroups
1771
- }
1772
- };
2030
+ return state;
1773
2031
  };
1774
2032
 
1775
- const closeActiveEditor = state => {
1776
- const {
1777
- layout
1778
- } = state;
1779
- const {
1780
- groups
1781
- } = layout;
1782
- const focusedGroup = groups.find(group => group.focused);
1783
- if (!focusedGroup) {
1784
- return state;
2033
+ const shouldLoadContent = tab => {
2034
+ // Load if:
2035
+ // - Has a path (file-based tab)
2036
+ // - Not already loaded or currently loading
2037
+ if (!tab.uri) {
2038
+ return false;
1785
2039
  }
1786
- const {
1787
- activeTabId
1788
- } = focusedGroup;
1789
- if (activeTabId === undefined) {
1790
- return state;
2040
+ if (tab.loadingState === 'loading') {
2041
+ return false;
1791
2042
  }
1792
- return closeTab(state, focusedGroup.id, activeTabId);
1793
- };
1794
-
1795
- const closeAll$1 = state => {
1796
- return {
1797
- ...state,
1798
- layout: {
1799
- ...state.layout,
1800
- activeGroupId: undefined,
1801
- groups: []
1802
- }
1803
- };
2043
+ if (tab.loadingState === 'loaded') {
2044
+ return false;
2045
+ }
2046
+ return true;
1804
2047
  };
1805
-
1806
- const closeFocusedTab = state => {
2048
+ const getActiveTabId$1 = state => {
1807
2049
  const {
1808
2050
  layout
1809
2051
  } = state;
1810
2052
  const {
2053
+ activeGroupId,
1811
2054
  groups
1812
2055
  } = layout;
1813
- const focusedGroup = groups.find(group => group.focused);
1814
- if (!focusedGroup) {
1815
- return state;
1816
- }
1817
- const {
1818
- activeTabId
1819
- } = focusedGroup;
1820
- if (activeTabId === undefined) {
1821
- return state;
1822
- }
1823
- return closeTab(state, focusedGroup.id, activeTabId);
2056
+ const activeGroup = groups.find(g => g.id === activeGroupId);
2057
+ return activeGroup?.activeTabId;
1824
2058
  };
1825
-
1826
- const closeOtherTabs = (state, groupId) => {
2059
+ const selectTab = async (state, groupIndex, index) => {
1827
2060
  const {
1828
- layout
2061
+ layout,
2062
+ uid
1829
2063
  } = state;
1830
2064
  const {
1831
- activeGroupId,
1832
2065
  groups
1833
2066
  } = layout;
1834
- const targetGroupId = groupId ?? activeGroupId;
1835
- if (targetGroupId === undefined) {
2067
+
2068
+ // Validate indexes
2069
+ if (groupIndex < 0 || groupIndex >= groups.length) {
1836
2070
  return state;
1837
2071
  }
1838
- const group = groups.find(g => g.id === targetGroupId);
1839
- if (!group) {
2072
+ const group = groups[groupIndex];
2073
+ if (index < 0 || index >= group.tabs.length) {
1840
2074
  return state;
1841
2075
  }
1842
- const {
1843
- activeTabId
1844
- } = group;
1845
- if (activeTabId === undefined) {
1846
- return state;
1847
- }
1848
- const newGroups = groups.map(g => {
1849
- if (g.id === targetGroupId) {
1850
- const newTabs = g.tabs.filter(tab => tab.id === activeTabId);
1851
- return {
1852
- ...g,
1853
- activeTabId,
1854
- isEmpty: newTabs.length === 0,
1855
- tabs: newTabs
1856
- };
1857
- }
1858
- return g;
1859
- });
1860
- return {
1861
- ...state,
1862
- layout: {
1863
- ...layout,
1864
- groups: newGroups
1865
- }
1866
- };
1867
- };
1868
-
1869
- const closeTabsRight = (state, groupId) => {
1870
- const {
1871
- layout
1872
- } = state;
1873
- const {
1874
- groups
1875
- } = layout;
1876
- const group = groups.find(g => g.id === groupId);
1877
- if (!group) {
1878
- return state;
1879
- }
1880
- const {
1881
- activeTabId,
1882
- tabs
1883
- } = group;
1884
- if (activeTabId === undefined) {
1885
- return state;
1886
- }
1887
- const activeTabIndex = tabs.findIndex(tab => tab.id === activeTabId);
1888
- if (activeTabIndex === -1) {
1889
- return state;
1890
- }
1891
- const newTabs = tabs.slice(0, activeTabIndex + 1);
1892
- if (newTabs.length === tabs.length) {
1893
- return state;
1894
- }
1895
- const newGroups = groups.map(g => {
1896
- if (g.id === groupId) {
1897
- return {
1898
- ...g,
1899
- isEmpty: newTabs.length === 0,
1900
- tabs: newTabs
1901
- };
1902
- }
1903
- return g;
1904
- });
1905
- return {
1906
- ...state,
1907
- layout: {
1908
- ...layout,
1909
- groups: newGroups
1910
- }
1911
- };
1912
- };
1913
-
1914
- const copyPath$1 = async (state, path) => {
1915
- string(path);
1916
- await invoke('ClipBoard.writeText', path);
1917
- return state;
1918
- };
1919
-
1920
- const copyRelativePath$1 = async (state, path) => {
1921
- string(path);
1922
- const relativePath = await invoke('Workspace.pathBaseName', path);
1923
- await invoke('ClipBoard.writeText', relativePath);
1924
- return state;
1925
- };
1926
-
1927
- const create = (uid, uri, x, y, width, height, platform, assetDir, tabHeight = 35) => {
1928
- const state = {
1929
- assetDir,
1930
- fileIconCache: {},
1931
- height,
1932
- iframes: [],
1933
- initial: true,
1934
- layout: {
1935
- activeGroupId: undefined,
1936
- direction: 'horizontal',
1937
- groups: []
1938
- },
1939
- platform,
1940
- splitButtonEnabled: false,
1941
- tabHeight,
1942
- uid,
1943
- width,
1944
- workspaceuri: uri,
1945
- x,
1946
- y
1947
- };
1948
- set(uid, state, state);
1949
- };
1950
-
1951
- const isEqual = (oldState, newState) => {
1952
- return oldState.layout === newState.layout;
1953
- };
1954
-
1955
- const RenderItems = 4;
1956
- const RenderIncremental = 11;
1957
-
1958
- const modules = [isEqual];
1959
- const numbers = [RenderIncremental];
1960
-
1961
- const diff = (oldState, newState) => {
1962
- const diffResult = [];
1963
- for (let i = 0; i < modules.length; i++) {
1964
- const fn = modules[i];
1965
- if (!fn(oldState, newState)) {
1966
- diffResult.push(numbers[i]);
1967
- }
1968
- }
1969
- return diffResult;
1970
- };
1971
-
1972
- const diff2 = uid => {
1973
- const {
1974
- newState,
1975
- oldState
1976
- } = get(uid);
1977
- const result = diff(oldState, newState);
1978
- return result;
1979
- };
1980
-
1981
- const createViewlet = async (viewletModuleId, editorUid, tabId, bounds, uri) => {
1982
- await invoke('Layout.createViewlet', viewletModuleId, editorUid, tabId, bounds, uri);
1983
- };
1984
-
1985
- const findTabById = (state, tabId) => {
1986
- const {
1987
- layout
1988
- } = state;
1989
- const {
1990
- groups
1991
- } = layout;
1992
- for (const group of groups) {
1993
- const tab = group.tabs.find(t => t.id === tabId);
1994
- if (tab) {
1995
- return {
1996
- groupId: group.id,
1997
- tab
1998
- };
1999
- }
2000
- }
2001
- return undefined;
2002
- };
2003
-
2004
- // Counter for request IDs to handle race conditions
2005
- let requestIdCounter = 0;
2006
- const getNextRequestId = () => {
2007
- return ++requestIdCounter;
2008
- };
2009
-
2010
- const startContentLoading = async (oldState, state, tabId, path, requestId) => {
2011
- try {
2012
- const getLatestState = () => {
2013
- return get(state.uid).newState;
2014
- };
2015
- set(state.uid, oldState, state);
2016
- const newState = await loadTabContentAsync(tabId, path, requestId, getLatestState);
2017
- return newState;
2018
- } catch {
2019
- // Silently ignore errors - the tab may have been closed or the component unmounted
2020
- }
2021
- return state;
2022
- };
2023
-
2024
- const shouldLoadContent = tab => {
2025
- // Load if:
2026
- // - Has a path (file-based tab)
2027
- // - Not already loaded or currently loading
2028
- if (!tab.uri) {
2029
- return false;
2030
- }
2031
- if (tab.loadingState === 'loading') {
2032
- return false;
2033
- }
2034
- if (tab.loadingState === 'loaded') {
2035
- return false;
2036
- }
2037
- return true;
2038
- };
2039
- const getActiveTabId$1 = state => {
2040
- const {
2041
- layout
2042
- } = state;
2043
- const {
2044
- activeGroupId,
2045
- groups
2046
- } = layout;
2047
- const activeGroup = groups.find(g => g.id === activeGroupId);
2048
- return activeGroup?.activeTabId;
2049
- };
2050
- const selectTab = async (state, groupIndex, index) => {
2051
- const {
2052
- layout,
2053
- uid
2054
- } = state;
2055
- const {
2056
- groups
2057
- } = layout;
2058
-
2059
- // Validate indexes
2060
- if (groupIndex < 0 || groupIndex >= groups.length) {
2061
- return state;
2062
- }
2063
- const group = groups[groupIndex];
2064
- if (index < 0 || index >= group.tabs.length) {
2065
- return state;
2066
- }
2067
- const tab = group.tabs[index];
2068
- const groupId = group.id;
2069
- const tabId = tab.id;
2070
-
2071
- // Return same state if this group and tab are already active
2072
- if (layout.activeGroupId === groupId && group.activeTabId === tabId) {
2076
+ const tab = group.tabs[index];
2077
+ const groupId = group.id;
2078
+ const tabId = tab.id;
2079
+
2080
+ // Return same state if this group and tab are already active
2081
+ if (layout.activeGroupId === groupId && group.activeTabId === tabId) {
2073
2082
  return state;
2074
2083
  }
2075
2084
 
@@ -2316,19 +2325,529 @@ const getActiveTab = state => {
2316
2325
  if (!activeTab) {
2317
2326
  return undefined;
2318
2327
  }
2319
- return {
2320
- groupId: activeGroup.id,
2321
- tab: activeTab
2322
- };
2328
+ return {
2329
+ groupId: activeGroup.id,
2330
+ tab: activeTab
2331
+ };
2332
+ };
2333
+
2334
+ const handleClickTogglePreview = async state => {
2335
+ const activeTabInfo = getActiveTab(state);
2336
+ if (!activeTabInfo || !activeTabInfo.tab.uri) {
2337
+ return state;
2338
+ }
2339
+ await invoke('Layout.showPreview', activeTabInfo.tab.uri);
2340
+ return state;
2341
+ };
2342
+
2343
+ const getBasename$1 = uri => {
2344
+ const lastSlashIndex = uri.lastIndexOf('/');
2345
+ if (lastSlashIndex === -1) {
2346
+ return uri;
2347
+ }
2348
+ return uri.slice(lastSlashIndex + 1);
2349
+ };
2350
+ const getLabel = uri => {
2351
+ if (uri.startsWith('settings://')) {
2352
+ return 'Settings';
2353
+ }
2354
+ if (uri.startsWith('simple-browser://')) {
2355
+ return 'Simple Browser';
2356
+ }
2357
+ if (uri.startsWith('language-models://')) {
2358
+ return 'Language Models';
2359
+ }
2360
+ return getBasename$1(uri);
2361
+ };
2362
+
2363
+ const createEmptyGroup = (state, uri, requestId) => {
2364
+ const {
2365
+ layout
2366
+ } = state;
2367
+ const {
2368
+ groups
2369
+ } = layout;
2370
+ const groupId = create$1();
2371
+ const title = getLabel(uri);
2372
+ const tabId = create$1();
2373
+ const editorUid = create$1();
2374
+ const newTab = {
2375
+ editorType: 'text',
2376
+ editorUid,
2377
+ errorMessage: '',
2378
+ icon: '',
2379
+ id: tabId,
2380
+ isDirty: false,
2381
+ language: '',
2382
+ loadingState: 'loading',
2383
+ title,
2384
+ uri
2385
+ };
2386
+ const newGroup = {
2387
+ activeTabId: newTab.id,
2388
+ focused: true,
2389
+ id: groupId,
2390
+ isEmpty: false,
2391
+ size: 100,
2392
+ tabs: [newTab]
2393
+ };
2394
+ return {
2395
+ ...state,
2396
+ layout: {
2397
+ ...layout,
2398
+ activeGroupId: groupId,
2399
+ groups: [...groups, newGroup]
2400
+ }
2401
+ };
2402
+ };
2403
+
2404
+ const openTab = (state, groupId, tab) => {
2405
+ const newTab = 'id' in tab && tab.id !== undefined ? tab : {
2406
+ ...tab,
2407
+ id: create$1()
2408
+ };
2409
+ const {
2410
+ layout
2411
+ } = state;
2412
+ const {
2413
+ groups
2414
+ } = layout;
2415
+ const updatedGroups = groups.map(group => {
2416
+ if (group.id === groupId) {
2417
+ const newTabs = [...group.tabs, newTab];
2418
+ return {
2419
+ ...group,
2420
+ activeTabId: newTab.id,
2421
+ isEmpty: newTabs.length === 0,
2422
+ tabs: newTabs
2423
+ };
2424
+ }
2425
+ return group;
2426
+ });
2427
+ return {
2428
+ ...state,
2429
+ layout: {
2430
+ ...layout,
2431
+ groups: updatedGroups
2432
+ }
2433
+ };
2434
+ };
2435
+
2436
+ const ensureActiveGroup = (state, uri) => {
2437
+ // Find the active group (by activeGroupId or focused flag)
2438
+ const {
2439
+ layout
2440
+ } = state;
2441
+ const {
2442
+ activeGroupId,
2443
+ groups
2444
+ } = layout;
2445
+ const activeGroup = activeGroupId === undefined ? groups.find(group => group.focused) : groups.find(group => group.id === activeGroupId);
2446
+
2447
+ // Generate a request ID for content loading
2448
+ getNextRequestId();
2449
+
2450
+ // If no active group exists, create one
2451
+ let newState;
2452
+ if (activeGroup) {
2453
+ // Create a new tab with the URI in the active group
2454
+ const title = getLabel(uri);
2455
+ const tabId = create$1();
2456
+ const editorUid = create$1();
2457
+ const newTab = {
2458
+ editorType: 'text',
2459
+ editorUid,
2460
+ errorMessage: '',
2461
+ icon: '',
2462
+ id: tabId,
2463
+ isDirty: false,
2464
+ language: '',
2465
+ loadingState: 'loading',
2466
+ title,
2467
+ uri: uri
2468
+ };
2469
+ newState = openTab(state, activeGroup.id, newTab);
2470
+ } else {
2471
+ newState = createEmptyGroup(state, uri);
2472
+ }
2473
+ return newState;
2474
+ };
2475
+
2476
+ const findTabByUri = (state, uri) => {
2477
+ const {
2478
+ layout
2479
+ } = state;
2480
+ const {
2481
+ groups
2482
+ } = layout;
2483
+ for (const group of groups) {
2484
+ const tab = group.tabs.find(t => t.uri === uri);
2485
+ if (tab) {
2486
+ return {
2487
+ groupId: group.id,
2488
+ tab
2489
+ };
2490
+ }
2491
+ }
2492
+ return undefined;
2493
+ };
2494
+
2495
+ const focusEditorGroup = (state, groupId) => {
2496
+ const {
2497
+ layout
2498
+ } = state;
2499
+ const {
2500
+ groups
2501
+ } = layout;
2502
+ const updatedGroups = groups.map(group => ({
2503
+ ...group,
2504
+ focused: group.id === groupId
2505
+ }));
2506
+ return {
2507
+ ...state,
2508
+ layout: {
2509
+ ...layout,
2510
+ activeGroupId: groupId,
2511
+ groups: updatedGroups
2512
+ }
2513
+ };
2514
+ };
2515
+
2516
+ const getActiveTabId = state => {
2517
+ const {
2518
+ layout
2519
+ } = state;
2520
+ const {
2521
+ activeGroupId,
2522
+ groups
2523
+ } = layout;
2524
+ const activeGroup = groups.find(g => g.id === activeGroupId);
2525
+ return activeGroup?.activeTabId;
2526
+ };
2527
+
2528
+ const getIconsCached = (dirents, fileIconCache) => {
2529
+ return dirents.map(dirent => fileIconCache[dirent]);
2530
+ };
2531
+
2532
+ const getBasename = uri => {
2533
+ const lastSlashIndex = uri.lastIndexOf('/');
2534
+ if (lastSlashIndex === -1) {
2535
+ return uri;
2536
+ }
2537
+ return uri.slice(lastSlashIndex + 1);
2538
+ };
2539
+ const getMissingTabs = (tabs, fileIconCache) => {
2540
+ const missingTabs = [];
2541
+ for (const tab of tabs) {
2542
+ if (tab.uri && !(tab.uri in fileIconCache)) {
2543
+ missingTabs.push(tab);
2544
+ }
2545
+ }
2546
+ return missingTabs;
2547
+ };
2548
+ const tabToIconRequest = tab => {
2549
+ const uri = tab.uri || '';
2550
+ return {
2551
+ name: getBasename(uri),
2552
+ path: uri,
2553
+ type: 0 // file type
2554
+ };
2555
+ };
2556
+ const getMissingIconRequestsForTabs = (tabs, fileIconCache) => {
2557
+ const missingRequests = getMissingTabs(tabs, fileIconCache);
2558
+ const iconRequests = missingRequests.map(tabToIconRequest);
2559
+ return iconRequests;
2560
+ };
2561
+
2562
+ const Directory = 3;
2563
+ const DirectoryExpanded = 4;
2564
+
2565
+ const getSimpleIconRequestType = direntType => {
2566
+ if (direntType === Directory || direntType === DirectoryExpanded) {
2567
+ return 2;
2568
+ }
2569
+ return 1;
2570
+ };
2571
+
2572
+ const toSimpleIconRequest = request => {
2573
+ return {
2574
+ name: request.name,
2575
+ type: getSimpleIconRequestType(request.type)
2576
+ };
2577
+ };
2578
+
2579
+ const requestFileIcons = async requests => {
2580
+ if (requests.length === 0) {
2581
+ return [];
2582
+ }
2583
+ const simpleRequests = requests.map(toSimpleIconRequest);
2584
+ const icons = await getIcons(simpleRequests);
2585
+ return icons;
2586
+ };
2587
+
2588
+ const updateIconCache = (iconCache, missingRequests, newIcons) => {
2589
+ if (missingRequests.length === 0) {
2590
+ return iconCache;
2591
+ }
2592
+ const newFileIconCache = {
2593
+ ...iconCache
2594
+ };
2595
+ for (let i = 0; i < missingRequests.length; i++) {
2596
+ const request = missingRequests[i];
2597
+ const icon = newIcons[i];
2598
+ newFileIconCache[request.path] = icon;
2599
+ }
2600
+ return newFileIconCache;
2601
+ };
2602
+
2603
+ const getFileIconsForTabs = async (tabs, fileIconCache) => {
2604
+ const missingRequests = getMissingIconRequestsForTabs(tabs, fileIconCache);
2605
+ const newIcons = await requestFileIcons(missingRequests);
2606
+ const newFileIconCache = updateIconCache(fileIconCache, missingRequests, newIcons);
2607
+ const tabUris = tabs.map(tab => tab.uri || '');
2608
+ const icons = getIconsCached(tabUris, newFileIconCache);
2609
+ return {
2610
+ icons,
2611
+ newFileIconCache
2612
+ };
2613
+ };
2614
+
2615
+ const getOptionUriOptions = options => {
2616
+ let uri = '';
2617
+ if (typeof options === 'string') {
2618
+ uri = options;
2619
+ } else {
2620
+ const {
2621
+ uri: optionsUri
2622
+ } = options;
2623
+ uri = optionsUri;
2624
+ }
2625
+ return uri;
2626
+ };
2627
+
2628
+ const getViewletModuleId = async uri => {
2629
+ // Query RendererWorker for viewlet module ID (optional, may fail in tests)
2630
+ let viewletModuleId;
2631
+ try {
2632
+ viewletModuleId = await invoke('Layout.getModuleId', uri);
2633
+ } catch {
2634
+ // Viewlet creation is optional - silently ignore if RendererWorker isn't available
2635
+ }
2636
+ return viewletModuleId;
2637
+ };
2638
+
2639
+ const switchTab = (state, groupId, tabId) => {
2640
+ const {
2641
+ layout
2642
+ } = state;
2643
+ const {
2644
+ groups
2645
+ } = layout;
2646
+ const updatedGroups = groups.map(group => {
2647
+ if (group.id === groupId) {
2648
+ const tabExists = group.tabs.some(tab => tab.id === tabId);
2649
+ if (tabExists) {
2650
+ return {
2651
+ ...group,
2652
+ activeTabId: tabId
2653
+ };
2654
+ }
2655
+ }
2656
+ return group;
2657
+ });
2658
+ return {
2659
+ ...state,
2660
+ layout: {
2661
+ ...layout,
2662
+ groups: updatedGroups
2663
+ }
2664
+ };
2665
+ };
2666
+
2667
+ const openUri = async (state, options) => {
2668
+ object(state);
2669
+ const {
2670
+ uid
2671
+ } = state;
2672
+ const uri = getOptionUriOptions(options);
2673
+
2674
+ // Check if a tab with this URI already exists in the passed-in state
2675
+ const existingTab = findTabByUri(state, uri);
2676
+ const shouldRetryExistingTab = existingTab && existingTab.tab.loadingState === 'error';
2677
+ if (existingTab && !shouldRetryExistingTab) {
2678
+ // Tab exists, switch to it and focus its group
2679
+ const focusedState = focusEditorGroup(state, existingTab.groupId);
2680
+ return switchTab(focusedState, existingTab.groupId, existingTab.tab.id);
2681
+ }
2682
+
2683
+ // Get previous active tab ID for viewlet switching
2684
+ const previousTabId = getActiveTabId(state);
2685
+
2686
+ // Check if there's existing state in the global store
2687
+ const stateFromStore = get(uid);
2688
+ let currentState;
2689
+ if (stateFromStore) {
2690
+ const storedState = stateFromStore.newState;
2691
+ // Use the stored state if it has more tabs than the passed-in state
2692
+ // (indicating concurrent calls have already added tabs)
2693
+ // Otherwise use the passed-in state (test setup with initial data)
2694
+ const storedTabCount = storedState.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
2695
+ const passedTabCount = state.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
2696
+ if (storedTabCount > passedTabCount) {
2697
+ // Stored state has more tabs - concurrent calls have added tabs
2698
+ currentState = storedState;
2699
+ } else {
2700
+ // Passed-in state has same or more tabs, use it (likely fresh test setup)
2701
+ currentState = state;
2702
+ set(uid, state, state);
2703
+ }
2704
+ } else {
2705
+ // No state in store yet, register the passed-in state
2706
+ currentState = state;
2707
+ set(uid, state, state);
2708
+ }
2709
+ let tabId;
2710
+ let stateWithTab;
2711
+ if (shouldRetryExistingTab && existingTab) {
2712
+ const focusedState = focusEditorGroup(currentState, existingTab.groupId);
2713
+ stateWithTab = updateTab(focusedState, existingTab.tab.id, {
2714
+ errorMessage: '',
2715
+ loadingState: 'loading'
2716
+ });
2717
+ tabId = existingTab.tab.id;
2718
+ } else {
2719
+ // Add tab to state BEFORE any async calls to prevent race conditions
2720
+ stateWithTab = ensureActiveGroup(currentState, uri);
2721
+ tabId = getActiveTabId(stateWithTab);
2722
+ }
2723
+
2724
+ // Save state immediately after adding tab or retrying
2725
+ set(uid, state, stateWithTab);
2726
+ try {
2727
+ const viewletModuleId = await getViewletModuleId(uri);
2728
+
2729
+ // After async call, get the latest state to account for any concurrent changes
2730
+ const {
2731
+ newState: stateAfterModuleId
2732
+ } = get(uid);
2733
+ if (!viewletModuleId) {
2734
+ return updateTab(stateAfterModuleId, tabId, {
2735
+ errorMessage: 'Could not determine editor type for this URI',
2736
+ loadingState: 'error'
2737
+ });
2738
+ }
2739
+
2740
+ // Calculate bounds: use main area bounds minus tab height
2741
+ const bounds = {
2742
+ height: stateAfterModuleId.height - stateAfterModuleId.tabHeight,
2743
+ width: stateAfterModuleId.width,
2744
+ x: stateAfterModuleId.x,
2745
+ y: stateAfterModuleId.y + stateAfterModuleId.tabHeight
2746
+ };
2747
+ const stateWithViewlet = createViewletForTab(stateAfterModuleId, tabId, viewletModuleId, bounds);
2748
+ let intermediateState1 = stateWithViewlet;
2749
+
2750
+ // Switch viewlet (detach old, attach new if ready)
2751
+ const {
2752
+ newState: switchedState
2753
+ } = switchViewlet(intermediateState1, previousTabId, tabId);
2754
+ intermediateState1 = switchedState;
2755
+ set(uid, state, intermediateState1);
2756
+
2757
+ // Get the tab to extract editorUid
2758
+ const tabWithViewlet = findTabById(intermediateState1, tabId);
2759
+ if (!tabWithViewlet) {
2760
+ return intermediateState1;
2761
+ }
2762
+ const {
2763
+ editorUid
2764
+ } = tabWithViewlet.tab;
2765
+ if (editorUid === -1) {
2766
+ throw new Error(`invalid editorUid`);
2767
+ }
2768
+ await createViewlet(viewletModuleId, editorUid, tabId, bounds, uri);
2769
+
2770
+ // After viewlet is created, get the latest state and mark it as ready
2771
+ // This ensures we have any state updates that occurred during viewlet creation
2772
+ const {
2773
+ newState: latestState
2774
+ } = get(uid);
2775
+
2776
+ // Attachment is handled automatically by virtual DOM reference nodes
2777
+ const readyState = handleViewletReady(latestState, editorUid);
2778
+
2779
+ // Save state before async icon request
2780
+ set(uid, state, readyState);
2781
+
2782
+ // Request file icon for the newly opened tab
2783
+ try {
2784
+ const newTab = findTabById(readyState, tabId);
2785
+ if (newTab && newTab.tab.uri) {
2786
+ const {
2787
+ newFileIconCache
2788
+ } = await getFileIconsForTabs([newTab.tab], readyState.fileIconCache);
2789
+
2790
+ // After async call, get the latest state again
2791
+ const {
2792
+ newState: stateBeforeIconUpdate
2793
+ } = get(uid);
2794
+ const icon = newFileIconCache[newTab.tab.uri] || '';
2795
+
2796
+ // Update the tab with the icon in the latest state
2797
+ const stateWithIcon = {
2798
+ ...stateBeforeIconUpdate,
2799
+ fileIconCache: newFileIconCache,
2800
+ layout: {
2801
+ ...stateBeforeIconUpdate.layout,
2802
+ groups: stateBeforeIconUpdate.layout.groups.map(group => ({
2803
+ ...group,
2804
+ tabs: group.tabs.map(tab => tab.id === tabId ? {
2805
+ ...tab,
2806
+ icon
2807
+ } : tab)
2808
+ }))
2809
+ }
2810
+ };
2811
+
2812
+ // Save the state with icon update so concurrent calls can see it
2813
+ set(uid, state, stateWithIcon);
2814
+ return stateWithIcon;
2815
+ }
2816
+ } catch {
2817
+ // If icon request fails, continue without icon
2818
+ }
2819
+
2820
+ // Get final latest state
2821
+ const {
2822
+ newState: finalState
2823
+ } = get(uid);
2824
+ return finalState;
2825
+ } catch (error) {
2826
+ const {
2827
+ newState: latestState
2828
+ } = get(uid);
2829
+ const errorMessage = error instanceof Error ? error.message : 'Failed to open URI';
2830
+ const errorState = updateTab(latestState, tabId, {
2831
+ errorMessage,
2832
+ loadingState: 'error'
2833
+ });
2834
+ set(uid, state, errorState);
2835
+ return errorState;
2836
+ }
2323
2837
  };
2324
2838
 
2325
- const handleClickTogglePreview = async state => {
2326
- const activeTabInfo = getActiveTab(state);
2327
- if (!activeTabInfo || !activeTabInfo.tab.uri) {
2839
+ const retryOpen = async state => {
2840
+ const activeTabData = getActiveTab(state);
2841
+ if (!activeTabData) {
2328
2842
  return state;
2329
2843
  }
2330
- await invoke('Layout.showPreview', activeTabInfo.tab.uri);
2331
- return state;
2844
+ const {
2845
+ tab
2846
+ } = activeTabData;
2847
+ if (!tab.uri) {
2848
+ return state;
2849
+ }
2850
+ return openUri(state, tab.uri);
2332
2851
  };
2333
2852
 
2334
2853
  const splitEditorGroup$1 = (state, groupId, direction) => {
@@ -2400,6 +2919,8 @@ const handleClickAction = async (state, action, rawGroupId) => {
2400
2919
  }
2401
2920
  }
2402
2921
  return state;
2922
+ case 'retry-open':
2923
+ return retryOpen(state);
2403
2924
  case 'split-right':
2404
2925
  return splitEditorGroup$1(state, activeGroup.id, Right);
2405
2926
  case 'toggle-preview':
@@ -2450,112 +2971,33 @@ const handleDoubleClick = async state => {
2450
2971
  return state;
2451
2972
  };
2452
2973
 
2974
+ const EditorGroupHeader = 'EditorGroupHeader';
2975
+ const EditorGroup = 'EditorGroup';
2976
+ const EditorContainer = 'EditorContainer';
2977
+ const EditorGroupActions = 'EditorGroupActions';
2978
+ const EditorGroupActionButton = 'EditorGroupActionButton';
2979
+ const SplitEditorGroupButton = 'SplitEditorGroupButton';
2980
+ const MaskIconPreview = 'MaskIcon MaskIconPreview';
2981
+ const MaskIconCircleFilled = 'MaskIcon MaskIconCircleFilled';
2982
+ const MaskIconClose = 'MaskIcon MaskIconClose';
2983
+ const TextEditor = 'TextEditor';
2984
+ const TextEditorLoading = 'TextEditor TextEditor--loading';
2985
+ const EditorContentLoading = 'EditorContent EditorContent--loading';
2986
+ const TextEditorError = 'TextEditor TextEditor--error';
2987
+ const EditorContentError = 'EditorContent EditorContent--error';
2988
+ const Main = 'Main';
2989
+ const EDITOR_GROUPS_CONTAINER = 'editor-groups-container';
2990
+ const TabIcon = 'TabIcon';
2991
+ const TabTitle = 'TabTitle';
2992
+ const Button = 'Button';
2993
+ const ButtonSecondary = 'ButtonSecondary';
2994
+ const EditorContent = 'EditorContent';
2995
+ const EditorTabCloseButton = 'EditorTabCloseButton';
2996
+ const IconButton = 'IconButton';
2997
+ const MainTab = 'MainTab';
2453
2998
  const MainTabs = 'MainTabs';
2454
-
2455
- const getBasename$1 = uri => {
2456
- const lastSlashIndex = uri.lastIndexOf('/');
2457
- if (lastSlashIndex === -1) {
2458
- return uri;
2459
- }
2460
- return uri.slice(lastSlashIndex + 1);
2461
- };
2462
- const getLabel = uri => {
2463
- if (uri.startsWith('settings://')) {
2464
- return 'Settings';
2465
- }
2466
- if (uri.startsWith('simple-browser://')) {
2467
- return 'Simple Browser';
2468
- }
2469
- if (uri.startsWith('language-models://')) {
2470
- return 'Language Models';
2471
- }
2472
- return getBasename$1(uri);
2473
- };
2474
-
2475
- const createEmptyGroup = (state, uri, requestId) => {
2476
- const {
2477
- layout
2478
- } = state;
2479
- const {
2480
- groups
2481
- } = layout;
2482
- const groupId = create$1();
2483
- const title = getLabel(uri);
2484
- const tabId = create$1();
2485
- const editorUid = create$1();
2486
- const newTab = {
2487
- editorType: 'text',
2488
- editorUid,
2489
- errorMessage: '',
2490
- icon: '',
2491
- id: tabId,
2492
- isDirty: false,
2493
- language: '',
2494
- loadingState: 'loading',
2495
- title,
2496
- uri
2497
- };
2498
- const newGroup = {
2499
- activeTabId: newTab.id,
2500
- focused: true,
2501
- id: groupId,
2502
- isEmpty: false,
2503
- size: 100,
2504
- tabs: [newTab]
2505
- };
2506
- return {
2507
- ...state,
2508
- layout: {
2509
- ...layout,
2510
- activeGroupId: groupId,
2511
- groups: [...groups, newGroup]
2512
- }
2513
- };
2514
- };
2515
-
2516
- const getActiveTabId = state => {
2517
- const {
2518
- layout
2519
- } = state;
2520
- const {
2521
- activeGroupId,
2522
- groups
2523
- } = layout;
2524
- const activeGroup = groups.find(g => g.id === activeGroupId);
2525
- return activeGroup?.activeTabId;
2526
- };
2527
-
2528
- const openTab = (state, groupId, tab) => {
2529
- const newTab = 'id' in tab && tab.id !== undefined ? tab : {
2530
- ...tab,
2531
- id: create$1()
2532
- };
2533
- const {
2534
- layout
2535
- } = state;
2536
- const {
2537
- groups
2538
- } = layout;
2539
- const updatedGroups = groups.map(group => {
2540
- if (group.id === groupId) {
2541
- const newTabs = [...group.tabs, newTab];
2542
- return {
2543
- ...group,
2544
- activeTabId: newTab.id,
2545
- isEmpty: newTabs.length === 0,
2546
- tabs: newTabs
2547
- };
2548
- }
2549
- return group;
2550
- });
2551
- return {
2552
- ...state,
2553
- layout: {
2554
- ...layout,
2555
- groups: updatedGroups
2556
- }
2557
- };
2558
- };
2999
+ const MainTabModified = 'MainTabModified';
3000
+ const MainTabSelected = 'MainTabSelected';
2559
3001
 
2560
3002
  const newFile = async state => {
2561
3003
  object(state);
@@ -2739,109 +3181,135 @@ const handleResize = async (state, dimensions) => {
2739
3181
  return allResizeCommands;
2740
3182
  };
2741
3183
 
2742
- const show2 = async (uid, menuId, x, y, args) => {
2743
- await showContextMenu2(uid, menuId, x, y, args);
2744
- };
2745
-
2746
- const handleTabContextMenu = async (state, button, x, y) => {
2747
- number(x);
2748
- number(y);
2749
- const {
2750
- uid
2751
- } = state;
2752
- await show2(uid, Tab, x, y, {
2753
- menuId: Tab
2754
- });
2755
- return state;
2756
- };
2757
-
2758
- const getIconsCached = (dirents, fileIconCache) => {
2759
- return dirents.map(dirent => fileIconCache[dirent]);
3184
+ const create = (beforeGroupId, afterGroupId) => {
3185
+ return `${beforeGroupId}:${afterGroupId}`;
2760
3186
  };
2761
-
2762
- const getBasename = uri => {
2763
- const lastSlashIndex = uri.lastIndexOf('/');
2764
- if (lastSlashIndex === -1) {
2765
- return uri;
3187
+ const parse = sashId => {
3188
+ if (!sashId) {
3189
+ return undefined;
2766
3190
  }
2767
- return uri.slice(lastSlashIndex + 1);
2768
- };
2769
- const getMissingTabs = (tabs, fileIconCache) => {
2770
- const missingTabs = [];
2771
- for (const tab of tabs) {
2772
- if (tab.uri && !(tab.uri in fileIconCache)) {
2773
- missingTabs.push(tab);
2774
- }
3191
+ const [beforeRaw, afterRaw] = sashId.split(':');
3192
+ if (!beforeRaw || !afterRaw) {
3193
+ return undefined;
3194
+ }
3195
+ const beforeGroupId = Number.parseInt(beforeRaw, 10);
3196
+ const afterGroupId = Number.parseInt(afterRaw, 10);
3197
+ if (!Number.isFinite(beforeGroupId) || !Number.isFinite(afterGroupId)) {
3198
+ return undefined;
2775
3199
  }
2776
- return missingTabs;
2777
- };
2778
- const tabToIconRequest = tab => {
2779
- const uri = tab.uri || '';
2780
3200
  return {
2781
- name: getBasename(uri),
2782
- path: uri,
2783
- type: 0 // file type
3201
+ afterGroupId,
3202
+ beforeGroupId
2784
3203
  };
2785
3204
  };
2786
- const getMissingIconRequestsForTabs = (tabs, fileIconCache) => {
2787
- const missingRequests = getMissingTabs(tabs, fileIconCache);
2788
- const iconRequests = missingRequests.map(tabToIconRequest);
2789
- return iconRequests;
2790
- };
2791
-
2792
- const Directory = 3;
2793
- const DirectoryExpanded = 4;
2794
3205
 
2795
- const getSimpleIconRequestType = direntType => {
2796
- if (direntType === Directory || direntType === DirectoryExpanded) {
2797
- return 2;
3206
+ const handleSashPointerDown = async (state, sashId, clientXRaw, clientYRaw) => {
3207
+ const parsed = parse(sashId);
3208
+ if (!parsed) {
3209
+ return state;
3210
+ }
3211
+ const beforeGroup = state.layout.groups.find(group => group.id === parsed.beforeGroupId);
3212
+ const afterGroup = state.layout.groups.find(group => group.id === parsed.afterGroupId);
3213
+ if (!beforeGroup || !afterGroup) {
3214
+ return state;
3215
+ }
3216
+ const clientX = Number.parseFloat(clientXRaw);
3217
+ const clientY = Number.parseFloat(clientYRaw);
3218
+ if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
3219
+ return state;
2798
3220
  }
2799
- return 1;
2800
- };
2801
-
2802
- const toSimpleIconRequest = request => {
2803
3221
  return {
2804
- name: request.name,
2805
- type: getSimpleIconRequestType(request.type)
3222
+ ...state,
3223
+ sashDrag: {
3224
+ afterGroupId: parsed.afterGroupId,
3225
+ afterSize: afterGroup.size,
3226
+ beforeGroupId: parsed.beforeGroupId,
3227
+ beforeSize: beforeGroup.size,
3228
+ sashId,
3229
+ startClientX: clientX,
3230
+ startClientY: clientY
3231
+ }
2806
3232
  };
2807
3233
  };
2808
3234
 
2809
- const requestFileIcons = async requests => {
2810
- if (requests.length === 0) {
2811
- return [];
2812
- }
2813
- const simpleRequests = requests.map(toSimpleIconRequest);
2814
- const icons = await getIcons(simpleRequests);
2815
- return icons;
3235
+ const MIN_GROUP_SIZE = 10;
3236
+ const clamp = (value, min, max) => {
3237
+ return Math.min(max, Math.max(min, value));
2816
3238
  };
2817
-
2818
- const updateIconCache = (iconCache, missingRequests, newIcons) => {
2819
- if (missingRequests.length === 0) {
2820
- return iconCache;
3239
+ const round = value => {
3240
+ return Math.round(value * 100) / 100;
3241
+ };
3242
+ const handleSashPointerMove = async (state, clientXRaw, clientYRaw) => {
3243
+ const {
3244
+ sashDrag
3245
+ } = state;
3246
+ if (!sashDrag) {
3247
+ return state;
2821
3248
  }
2822
- const newFileIconCache = {
2823
- ...iconCache
2824
- };
2825
- for (let i = 0; i < missingRequests.length; i++) {
2826
- const request = missingRequests[i];
2827
- const icon = newIcons[i];
2828
- newFileIconCache[request.path] = icon;
3249
+ const clientX = Number.parseFloat(clientXRaw);
3250
+ const clientY = Number.parseFloat(clientYRaw);
3251
+ if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) {
3252
+ return state;
2829
3253
  }
2830
- return newFileIconCache;
3254
+ const axisSize = state.layout.direction === 'horizontal' ? state.width : state.height;
3255
+ if (!axisSize) {
3256
+ return state;
3257
+ }
3258
+ const deltaPx = state.layout.direction === 'horizontal' ? clientX - sashDrag.startClientX : clientY - sashDrag.startClientY;
3259
+ const deltaPercent = deltaPx / axisSize * 100;
3260
+ const totalResizableSize = sashDrag.beforeSize + sashDrag.afterSize;
3261
+ const beforeSize = clamp(sashDrag.beforeSize + deltaPercent, MIN_GROUP_SIZE, totalResizableSize - MIN_GROUP_SIZE);
3262
+ const afterSize = totalResizableSize - beforeSize;
3263
+ const groups = state.layout.groups.map(group => {
3264
+ if (group.id === sashDrag.beforeGroupId) {
3265
+ return {
3266
+ ...group,
3267
+ size: round(beforeSize)
3268
+ };
3269
+ }
3270
+ if (group.id === sashDrag.afterGroupId) {
3271
+ return {
3272
+ ...group,
3273
+ size: round(afterSize)
3274
+ };
3275
+ }
3276
+ return group;
3277
+ });
3278
+ return {
3279
+ ...state,
3280
+ layout: {
3281
+ ...state.layout,
3282
+ groups
3283
+ }
3284
+ };
2831
3285
  };
2832
3286
 
2833
- const getFileIconsForTabs = async (tabs, fileIconCache) => {
2834
- const missingRequests = getMissingIconRequestsForTabs(tabs, fileIconCache);
2835
- const newIcons = await requestFileIcons(missingRequests);
2836
- const newFileIconCache = updateIconCache(fileIconCache, missingRequests, newIcons);
2837
- const tabUris = tabs.map(tab => tab.uri || '');
2838
- const icons = getIconsCached(tabUris, newFileIconCache);
3287
+ const handleSashPointerUp = async state => {
3288
+ if (!state.sashDrag) {
3289
+ return state;
3290
+ }
2839
3291
  return {
2840
- icons,
2841
- newFileIconCache
3292
+ ...state,
3293
+ sashDrag: undefined
2842
3294
  };
2843
3295
  };
2844
3296
 
3297
+ const show2 = async (uid, menuId, x, y, args) => {
3298
+ await showContextMenu2(uid, menuId, x, y, args);
3299
+ };
3300
+
3301
+ const handleTabContextMenu = async (state, button, x, y) => {
3302
+ number(x);
3303
+ number(y);
3304
+ const {
3305
+ uid
3306
+ } = state;
3307
+ await show2(uid, Tab, x, y, {
3308
+ menuId: Tab
3309
+ });
3310
+ return state;
3311
+ };
3312
+
2845
3313
  const getAllTabs = layout => {
2846
3314
  const allTabs = [];
2847
3315
  for (const group of layout.groups) {
@@ -3025,17 +3493,6 @@ const createViewlets = async (layout, viewletModuleIds, bounds) => {
3025
3493
  return editorUids;
3026
3494
  };
3027
3495
 
3028
- const getViewletModuleId = async uri => {
3029
- // Query RendererWorker for viewlet module ID (optional, may fail in tests)
3030
- let viewletModuleId;
3031
- try {
3032
- viewletModuleId = await invoke('Layout.getModuleId', uri);
3033
- } catch {
3034
- // Viewlet creation is optional - silently ignore if RendererWorker isn't available
3035
- }
3036
- return viewletModuleId;
3037
- };
3038
-
3039
3496
  const getViewletModuleIds = async layout => {
3040
3497
  const viewletModuleIds = {};
3041
3498
  for (const group of layout.groups) {
@@ -3289,277 +3746,22 @@ const getMenuEntries = async (state, props) => {
3289
3746
  }
3290
3747
  };
3291
3748
 
3292
- const ensureActiveGroup = (state, uri) => {
3293
- // Find the active group (by activeGroupId or focused flag)
3294
- const {
3295
- layout
3296
- } = state;
3297
- const {
3298
- activeGroupId,
3299
- groups
3300
- } = layout;
3301
- const activeGroup = activeGroupId === undefined ? groups.find(group => group.focused) : groups.find(group => group.id === activeGroupId);
3302
-
3303
- // Generate a request ID for content loading
3304
- getNextRequestId();
3305
-
3306
- // If no active group exists, create one
3307
- let newState;
3308
- if (activeGroup) {
3309
- // Create a new tab with the URI in the active group
3310
- const title = getLabel(uri);
3311
- const tabId = create$1();
3312
- const editorUid = create$1();
3313
- const newTab = {
3314
- editorType: 'text',
3315
- editorUid,
3316
- errorMessage: '',
3317
- icon: '',
3318
- id: tabId,
3319
- isDirty: false,
3320
- language: '',
3321
- loadingState: 'loading',
3322
- title,
3323
- uri: uri
3324
- };
3325
- newState = openTab(state, activeGroup.id, newTab);
3326
- } else {
3327
- newState = createEmptyGroup(state, uri);
3328
- }
3329
- return newState;
3330
- };
3331
-
3332
- const findTabByUri = (state, uri) => {
3333
- const {
3334
- layout
3335
- } = state;
3336
- const {
3337
- groups
3338
- } = layout;
3339
- for (const group of groups) {
3340
- const tab = group.tabs.find(t => t.uri === uri);
3341
- if (tab) {
3342
- return {
3343
- groupId: group.id,
3344
- tab
3345
- };
3346
- }
3347
- }
3348
- return undefined;
3349
- };
3350
-
3351
- const focusEditorGroup = (state, groupId) => {
3352
- const {
3353
- layout
3354
- } = state;
3355
- const {
3356
- groups
3357
- } = layout;
3358
- const updatedGroups = groups.map(group => ({
3359
- ...group,
3360
- focused: group.id === groupId
3361
- }));
3362
- return {
3363
- ...state,
3364
- layout: {
3365
- ...layout,
3366
- activeGroupId: groupId,
3367
- groups: updatedGroups
3368
- }
3369
- };
3370
- };
3371
-
3372
- const getOptionUriOptions = options => {
3373
- let uri = '';
3374
- if (typeof options === 'string') {
3375
- uri = options;
3376
- } else {
3377
- const {
3378
- uri: optionsUri
3379
- } = options;
3380
- uri = optionsUri;
3381
- }
3382
- return uri;
3383
- };
3384
-
3385
- const switchTab = (state, groupId, tabId) => {
3386
- const {
3387
- layout
3388
- } = state;
3389
- const {
3390
- groups
3391
- } = layout;
3392
- const updatedGroups = groups.map(group => {
3393
- if (group.id === groupId) {
3394
- const tabExists = group.tabs.some(tab => tab.id === tabId);
3395
- if (tabExists) {
3396
- return {
3397
- ...group,
3398
- activeTabId: tabId
3399
- };
3400
- }
3401
- }
3402
- return group;
3403
- });
3749
+ const refresh = state => {
3404
3750
  return {
3405
- ...state,
3406
- layout: {
3407
- ...layout,
3408
- groups: updatedGroups
3409
- }
3751
+ ...state
3410
3752
  };
3411
3753
  };
3412
3754
 
3413
- const openUri = async (state, options) => {
3414
- object(state);
3415
- const {
3416
- uid
3417
- } = state;
3418
- const uri = getOptionUriOptions(options);
3419
-
3420
- // Check if a tab with this URI already exists in the passed-in state
3421
- const existingTab = findTabByUri(state, uri);
3422
- if (existingTab) {
3423
- // Tab exists, switch to it and focus its group
3424
- const focusedState = focusEditorGroup(state, existingTab.groupId);
3425
- return switchTab(focusedState, existingTab.groupId, existingTab.tab.id);
3426
- }
3427
-
3428
- // Get previous active tab ID for viewlet switching
3429
- getActiveTabId(state);
3430
-
3431
- // Check if there's existing state in the global store
3432
- const stateFromStore = get(uid);
3433
- let currentState;
3434
- if (stateFromStore) {
3435
- const storedState = stateFromStore.newState;
3436
- // Use the stored state if it has more tabs than the passed-in state
3437
- // (indicating concurrent calls have already added tabs)
3438
- // Otherwise use the passed-in state (test setup with initial data)
3439
- const storedTabCount = storedState.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
3440
- const passedTabCount = state.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
3441
- if (storedTabCount > passedTabCount) {
3442
- // Stored state has more tabs - concurrent calls have added tabs
3443
- currentState = storedState;
3444
- } else {
3445
- // Passed-in state has same or more tabs, use it (likely fresh test setup)
3446
- currentState = state;
3447
- set(uid, state, state);
3448
- }
3449
- } else {
3450
- // No state in store yet, register the passed-in state
3451
- currentState = state;
3452
- set(uid, state, state);
3453
- }
3454
-
3455
- // Add tab to state BEFORE any async calls to prevent race conditions
3456
- const newState = ensureActiveGroup(currentState, uri);
3457
- const tabId = getActiveTabId(newState);
3458
-
3459
- // Save state immediately after adding tab
3460
- set(uid, state, newState);
3461
- const viewletModuleId = await getViewletModuleId(uri);
3462
-
3463
- // After async call, get the latest state to account for any concurrent changes
3464
- const {
3465
- newState: stateAfterModuleId
3466
- } = get(uid);
3467
- if (!viewletModuleId) {
3468
- // TODO display some kind of errro that editor couldn't be opened
3469
- return stateAfterModuleId;
3470
- }
3471
-
3472
- // Calculate bounds: use main area bounds minus tab height
3473
- const bounds = {
3474
- height: stateAfterModuleId.height - stateAfterModuleId.tabHeight,
3475
- width: stateAfterModuleId.width,
3476
- x: stateAfterModuleId.x,
3477
- y: stateAfterModuleId.y + stateAfterModuleId.tabHeight
3478
- };
3479
- const stateWithViewlet = createViewletForTab(stateAfterModuleId, tabId);
3480
- let intermediateState1 = stateWithViewlet;
3481
-
3482
- // Switch viewlet (detach old, attach new if ready)
3483
- const {
3484
- newState: switchedState
3485
- } = switchViewlet(intermediateState1);
3486
- intermediateState1 = switchedState;
3487
- set(uid, state, intermediateState1);
3488
-
3489
- // Get the tab to extract editorUid
3490
- const tabWithViewlet = findTabById(intermediateState1, tabId);
3491
- if (!tabWithViewlet) {
3492
- return intermediateState1;
3493
- }
3494
- const {
3495
- editorUid
3496
- } = tabWithViewlet.tab;
3497
- if (editorUid === -1) {
3498
- throw new Error(`invalid editorUid`);
3499
- }
3500
- await createViewlet(viewletModuleId, editorUid, tabId, bounds, uri);
3501
-
3502
- // After viewlet is created, get the latest state and mark it as ready
3503
- // This ensures we have any state updates that occurred during viewlet creation
3504
- const {
3505
- newState: latestState
3506
- } = get(uid);
3507
-
3508
- // Attachment is handled automatically by virtual DOM reference nodes
3509
- const readyState = handleViewletReady(latestState, editorUid);
3510
-
3511
- // Save state before async icon request
3512
- set(uid, state, readyState);
3513
-
3514
- // Request file icon for the newly opened tab
3515
- try {
3516
- const newTab = findTabById(readyState, tabId);
3517
- if (newTab && newTab.tab.uri) {
3518
- const {
3519
- newFileIconCache
3520
- } = await getFileIconsForTabs([newTab.tab], readyState.fileIconCache);
3521
-
3522
- // After async call, get the latest state again
3523
- const {
3524
- newState: stateBeforeIconUpdate
3525
- } = get(uid);
3526
- const icon = newFileIconCache[newTab.tab.uri] || '';
3527
-
3528
- // Update the tab with the icon in the latest state
3529
- const stateWithIcon = {
3530
- ...stateBeforeIconUpdate,
3531
- fileIconCache: newFileIconCache,
3532
- layout: {
3533
- ...stateBeforeIconUpdate.layout,
3534
- groups: stateBeforeIconUpdate.layout.groups.map(group => ({
3535
- ...group,
3536
- tabs: group.tabs.map(tab => tab.id === tabId ? {
3537
- ...tab,
3538
- icon
3539
- } : tab)
3540
- }))
3541
- }
3542
- };
3543
-
3544
- // Save the state with icon update so concurrent calls can see it
3545
- set(uid, state, stateWithIcon);
3546
- return stateWithIcon;
3547
- }
3548
- } catch {
3549
- // If icon request fails, continue without icon
3550
- }
3551
-
3552
- // Get final latest state
3553
- const {
3554
- newState: finalState
3555
- } = get(uid);
3556
- return finalState;
3755
+ const getCss = () => {
3756
+ const rules = [`.MainArea {
3757
+ }`];
3758
+ const css = rules.join('\n');
3759
+ return css;
3557
3760
  };
3558
3761
 
3559
- const refresh = state => {
3560
- return {
3561
- ...state
3562
- };
3762
+ const renderCss = (oldState, newState) => {
3763
+ const css = getCss();
3764
+ return [SetCss, newState.uid, css];
3563
3765
  };
3564
3766
 
3565
3767
  const text = data => {
@@ -3858,41 +4060,54 @@ const diffTree = (oldNodes, newNodes) => {
3858
4060
  return removeTrailingNavigationPatches(patches);
3859
4061
  };
3860
4062
 
3861
- const CSS_CLASSES = {
3862
- EDITOR_GROUPS_CONTAINER: 'editor-groups-container'};
3863
-
3864
4063
  const renderContent = content => {
3865
4064
  return [{
3866
4065
  childCount: 1,
3867
- className: 'TextEditor',
4066
+ className: TextEditor,
3868
4067
  type: Div
3869
4068
  }, {
3870
4069
  childCount: 1,
3871
- className: 'EditorContent',
4070
+ className: EditorContent,
3872
4071
  type: Pre
3873
4072
  }, text(content)];
3874
4073
  };
3875
4074
 
4075
+ const HandleClickAction = 10;
4076
+ const HandleClick = 11;
4077
+ const HandleClickClose = 12;
4078
+ const HandleClickTab = 13;
4079
+ const HandleTabContextMenu = 14;
4080
+ const HandleHeaderDoubleClick = 15;
4081
+ const HandleSashPointerDown = 16;
4082
+ const HandleSashPointerMove = 17;
4083
+ const HandleSashPointerUp = 18;
4084
+
3876
4085
  const renderError = errorMessage => {
3877
4086
  return [{
3878
- childCount: 1,
3879
- className: 'TextEditor TextEditor--error',
4087
+ childCount: 2,
4088
+ className: TextEditorError,
3880
4089
  type: Div
3881
4090
  }, {
3882
- childCount: 1,
3883
- className: 'EditorContent EditorContent--error',
4091
+ childCount: 2,
4092
+ className: EditorContentError,
3884
4093
  type: Div
3885
- }, text(`Error: ${errorMessage}`)];
4094
+ }, text(`Error: ${errorMessage}`), {
4095
+ childCount: 1,
4096
+ className: `${Button} ${ButtonSecondary}`,
4097
+ 'data-action': 'retry-open',
4098
+ onClick: HandleClickAction,
4099
+ type: Button$2
4100
+ }, text('Retry')];
3886
4101
  };
3887
4102
 
3888
4103
  const renderLoading = () => {
3889
4104
  return [{
3890
4105
  childCount: 1,
3891
- className: 'TextEditor TextEditor--loading',
4106
+ className: TextEditorLoading,
3892
4107
  type: Div
3893
4108
  }, {
3894
4109
  childCount: 1,
3895
- className: 'EditorContent EditorContent--loading',
4110
+ className: EditorContentLoading,
3896
4111
  type: Div
3897
4112
  }, text('Loading...')];
3898
4113
  };
@@ -3933,50 +4148,43 @@ const renderEditor = tab => {
3933
4148
  return renderContent('');
3934
4149
  };
3935
4150
 
3936
- const HandleClickAction = 10;
3937
- const HandleClick = 11;
3938
- const HandleClickClose = 12;
3939
- const HandleClickTab = 13;
3940
- const HandleTabContextMenu = 14;
3941
- const HandleHeaderDoubleClick = 15;
3942
-
3943
4151
  const renderTabActions = (isDirty, tabIndex, groupIndex) => {
3944
4152
  if (isDirty) {
3945
4153
  return [{
3946
4154
  childCount: 1,
3947
- className: 'EditorTabCloseButton',
4155
+ className: EditorTabCloseButton,
3948
4156
  'data-groupIndex': groupIndex,
3949
4157
  'data-index': tabIndex,
3950
4158
  type: Div
3951
4159
  }, {
3952
4160
  childCount: 0,
3953
- className: 'MaskIcon MaskIconCircleFilled',
4161
+ className: MaskIconCircleFilled,
3954
4162
  type: Div
3955
4163
  }];
3956
4164
  }
3957
4165
  return [{
3958
4166
  'aria-label': 'Close',
3959
4167
  childCount: 1,
3960
- className: 'EditorTabCloseButton',
4168
+ className: EditorTabCloseButton,
3961
4169
  'data-groupIndex': groupIndex,
3962
4170
  'data-index': tabIndex,
3963
4171
  onClick: HandleClickClose,
3964
- type: Button$1
4172
+ type: Button$2
3965
4173
  }, {
3966
4174
  childCount: 0,
3967
- className: 'MaskIcon MaskIconClose',
4175
+ className: MaskIconClose,
3968
4176
  type: Div
3969
4177
  }];
3970
4178
  };
3971
4179
 
3972
4180
  const renderTab = (tab, isActive, tabIndex, groupIndex) => {
3973
4181
  const closeButtonNodes = renderTabActions(tab.isDirty, tabIndex, groupIndex);
3974
- let className = 'MainTab';
4182
+ let className = MainTab;
3975
4183
  if (isActive) {
3976
- className += ' MainTabSelected';
4184
+ className += ' ' + MainTabSelected;
3977
4185
  }
3978
4186
  if (tab.isDirty) {
3979
- className += ' MainTabModified';
4187
+ className += ' ' + MainTabModified;
3980
4188
  }
3981
4189
  return [{
3982
4190
  'aria-selected': isActive,
@@ -3991,13 +4199,13 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
3991
4199
  type: Div
3992
4200
  }, {
3993
4201
  childCount: 0,
3994
- className: 'TabIcon',
3995
- role: 'none',
4202
+ className: TabIcon,
4203
+ role: None$1,
3996
4204
  src: tab.icon,
3997
4205
  type: Img
3998
4206
  }, {
3999
4207
  childCount: 1,
4000
- className: 'TabTitle',
4208
+ className: TabTitle,
4001
4209
  type: Span
4002
4210
  }, text(tab.title), ...closeButtonNodes];
4003
4211
  };
@@ -4005,7 +4213,7 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
4005
4213
  const getTabsVirtualDom = (group, groupIndex, tabsChildCount) => {
4006
4214
  return [{
4007
4215
  childCount: tabsChildCount,
4008
- className: 'MainTabs',
4216
+ className: MainTabs,
4009
4217
  role: 'tablist',
4010
4218
  type: Div
4011
4219
  }, ...group.tabs.flatMap((tab, tabIndex) => renderTab(tab, tab.id === group.activeTabId, tabIndex, groupIndex))];
@@ -4029,34 +4237,34 @@ const renderEditorGroupActions = (group, groupIndex, splitButtonEnabled) => {
4029
4237
  buttons.push({
4030
4238
  ariaLabel: 'Preview',
4031
4239
  childCount: 1,
4032
- className: 'IconButton',
4240
+ className: IconButton,
4033
4241
  'data-action': 'toggle-preview',
4034
4242
  'data-groupId': String(group.id),
4035
4243
  name: 'toggle-preview',
4036
4244
  onClick: HandleClickAction,
4037
4245
  title: togglePreview(),
4038
- type: Button$1
4246
+ type: Button$2
4039
4247
  }, {
4040
4248
  childCount: 0,
4041
- className: 'MaskIcon MaskIconPreview',
4249
+ className: MaskIconPreview,
4042
4250
  type: Div
4043
4251
  });
4044
4252
  }
4045
4253
  if (splitButtonEnabled) {
4046
4254
  buttons.push({
4047
4255
  childCount: 1,
4048
- className: 'EditorGroupActionButton SplitEditorGroupButton',
4256
+ className: EditorGroupActionButton + ' ' + SplitEditorGroupButton,
4049
4257
  'data-action': 'split-right',
4050
4258
  'data-groupId': String(group.id),
4051
4259
  onClick: HandleClickAction,
4052
4260
  title: splitEditorGroup(),
4053
- type: Button$1
4261
+ type: Button$2
4054
4262
  }, text('split'));
4055
4263
  }
4056
4264
  return [{
4057
4265
  childCount: buttons.length / 2,
4058
4266
  // Each button has 2 nodes (button + text)
4059
- className: 'EditorGroupActions',
4267
+ className: EditorGroupActions,
4060
4268
  role: 'toolbar',
4061
4269
  type: Div
4062
4270
  }, ...buttons];
@@ -4068,37 +4276,62 @@ const renderEditorGroupHeader = (group, groupIndex, splitButtonEnabled) => {
4068
4276
  const hasActions = actions.length > 0;
4069
4277
  return [{
4070
4278
  childCount: hasActions ? 2 : 1,
4071
- className: 'EditorGroupHeader',
4279
+ className: EditorGroupHeader,
4072
4280
  onDblClick: HandleHeaderDoubleClick,
4073
- role: 'none',
4281
+ role: None$1,
4074
4282
  type: Div
4075
4283
  }, ...getTabsVirtualDom(group, groupIndex, tabsChildCount), ...actions];
4076
4284
  };
4077
4285
 
4078
- const renderEditorGroup = (group, groupIndex, splitButtonEnabled = false) => {
4286
+ const renderEditorGroup = (group, groupIndex, splitButtonEnabled = false, direction = 'horizontal') => {
4079
4287
  const activeTab = group.tabs.find(tab => tab.id === group.activeTabId);
4288
+ const style = direction === 'horizontal' ? `width:${group.size}%;` : `height:${group.size}%;`;
4080
4289
  return [{
4081
4290
  childCount: 2,
4082
- className: 'EditorGroup',
4291
+ className: EditorGroup,
4292
+ style,
4083
4293
  type: Div
4084
4294
  }, ...renderEditorGroupHeader(group, groupIndex, splitButtonEnabled), {
4085
4295
  childCount: 1,
4086
- className: 'EditorContainer',
4296
+ className: EditorContainer,
4087
4297
  type: Div
4088
4298
  }, ...renderEditor(activeTab)];
4089
4299
  };
4090
4300
 
4301
+ const renderSash = (direction, sashId) => {
4302
+ return {
4303
+ childCount: 0,
4304
+ className: direction === 'vertical' ? 'SashVertical' : 'SashHorizontal',
4305
+ 'data-sashId': sashId,
4306
+ onPointerDown: HandleSashPointerDown,
4307
+ onPointerMove: HandleSashPointerMove,
4308
+ onPointerUp: HandleSashPointerUp,
4309
+ type: Div
4310
+ };
4311
+ };
4312
+
4091
4313
  const getMainAreaVirtualDom = (layout, splitButtonEnabled = false) => {
4314
+ const children = [];
4315
+ for (let i = 0; i < layout.groups.length; i++) {
4316
+ if (i > 0) {
4317
+ // Insert sash between groups
4318
+ const beforeGroupId = layout.groups[i - 1].id;
4319
+ const afterGroupId = layout.groups[i].id;
4320
+ const sashId = create(beforeGroupId, afterGroupId);
4321
+ children.push(renderSash(layout.direction, sashId));
4322
+ }
4323
+ children.push(...renderEditorGroup(layout.groups[i], i, splitButtonEnabled, layout.direction));
4324
+ }
4092
4325
  return [{
4093
4326
  childCount: 1,
4094
- className: 'Main',
4327
+ className: Main,
4095
4328
  type: Div
4096
4329
  }, {
4097
- childCount: layout.groups.length,
4098
- className: CSS_CLASSES.EDITOR_GROUPS_CONTAINER,
4099
- role: 'none',
4330
+ childCount: children.length,
4331
+ className: EDITOR_GROUPS_CONTAINER,
4332
+ role: None$1,
4100
4333
  type: Div
4101
- }, ...layout.groups.flatMap((group, groupIndex) => renderEditorGroup(group, groupIndex, splitButtonEnabled))];
4334
+ }, ...children];
4102
4335
  };
4103
4336
 
4104
4337
  const renderItems = (oldState, newState) => {
@@ -4124,6 +4357,8 @@ const renderIncremental = (oldState, newState) => {
4124
4357
 
4125
4358
  const getRenderer = diffType => {
4126
4359
  switch (diffType) {
4360
+ case RenderCss:
4361
+ return renderCss;
4127
4362
  case RenderIncremental:
4128
4363
  return renderIncremental;
4129
4364
  case RenderItems:
@@ -4167,14 +4402,23 @@ const renderEventListeners = () => {
4167
4402
  params: ['handleClickTab', 'event.target.dataset.groupIndex', 'event.target.dataset.index']
4168
4403
  }, {
4169
4404
  name: HandleTabContextMenu,
4170
- params: ['handleTabContextMenu', Button, ClientX, ClientY],
4405
+ params: ['handleTabContextMenu', Button$1, ClientX, ClientY],
4171
4406
  preventDefault: true
4172
4407
  }, {
4173
4408
  name: HandleClickAction,
4174
4409
  params: ['handleClickAction', TargetName, 'event.target.dataset.groupId']
4175
4410
  }, {
4176
4411
  name: HandleHeaderDoubleClick,
4177
- params: ['handleHeaderDoubleClick', 'event.target.className', 'event.target.dataset.groupId']
4412
+ params: ['handleHeaderDoubleClick', EventTargetClassName, 'event.target.dataset.groupId']
4413
+ }, {
4414
+ name: HandleSashPointerDown,
4415
+ params: ['handleSashPointerDown', 'event.target.dataset.sashId', ClientX, ClientY]
4416
+ }, {
4417
+ name: HandleSashPointerMove,
4418
+ params: ['handleSashPointerMove', ClientX, ClientY]
4419
+ }, {
4420
+ name: HandleSashPointerUp,
4421
+ params: ['handleSashPointerUp', ClientX, ClientY]
4178
4422
  }];
4179
4423
  };
4180
4424
 
@@ -4308,7 +4552,7 @@ const commandMap = {
4308
4552
  'MainArea.closeTabsRight': wrapCommand(closeTabsRight),
4309
4553
  'MainArea.copyPath': wrapCommand(copyPath$1),
4310
4554
  'MainArea.copyRelativePath': wrapCommand(copyRelativePath$1),
4311
- 'MainArea.create': create,
4555
+ 'MainArea.create': create$2,
4312
4556
  'MainArea.diff2': diff2,
4313
4557
  'MainArea.focusNext': wrapCommand(focusNextTab),
4314
4558
  'MainArea.focusNextTab': wrapCommand(focusNextTab),
@@ -4327,6 +4571,9 @@ const commandMap = {
4327
4571
  'MainArea.handleModifiedStatusChange': wrapCommand(handleModifiedStatusChange),
4328
4572
  'MainArea.handleResize': wrapGetter(handleResize),
4329
4573
  // TODO would need to have a function that returns newstate as well as commands
4574
+ 'MainArea.handleSashPointerDown': wrapCommand(handleSashPointerDown),
4575
+ 'MainArea.handleSashPointerMove': wrapCommand(handleSashPointerMove),
4576
+ 'MainArea.handleSashPointerUp': wrapCommand(handleSashPointerUp),
4330
4577
  'MainArea.handleTabContextMenu': wrapCommand(handleTabContextMenu),
4331
4578
  'MainArea.handleUriChange': wrapCommand(handleUriChange),
4332
4579
  'MainArea.handleWorkspaceChange': wrapCommand(handleWorkspaceChange),