@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.
- package/dist/mainAreaWorkerMain.js +1148 -901
- package/package.json +1 -1
|
@@ -87,7 +87,231 @@ const terminate = () => {
|
|
|
87
87
|
globalThis.close();
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
const
|
|
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$
|
|
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$
|
|
1622
|
+
} = create$3(ClipBoardWorker);
|
|
1397
1623
|
|
|
1398
1624
|
const {
|
|
1399
1625
|
set: set$3
|
|
1400
|
-
} = create$
|
|
1626
|
+
} = create$3(ExtensionHostWorker);
|
|
1401
1627
|
|
|
1402
1628
|
const {
|
|
1403
1629
|
invoke: invoke$1,
|
|
1404
1630
|
set: set$2
|
|
1405
|
-
} = create$
|
|
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$
|
|
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
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
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$
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
|
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
|
-
|
|
1702
|
-
|
|
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
|
-
|
|
1726
|
-
|
|
1727
|
-
isEmpty: newTabs.length === 0,
|
|
1728
|
-
tabs: newTabs
|
|
2005
|
+
groupId: group.id,
|
|
2006
|
+
tab
|
|
1729
2007
|
};
|
|
1730
2008
|
}
|
|
1731
|
-
|
|
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
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
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
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
|
|
1787
|
-
|
|
1788
|
-
} = focusedGroup;
|
|
1789
|
-
if (activeTabId === undefined) {
|
|
1790
|
-
return state;
|
|
2040
|
+
if (tab.loadingState === 'loading') {
|
|
2041
|
+
return false;
|
|
1791
2042
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
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
|
|
1814
|
-
|
|
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
|
-
|
|
1835
|
-
|
|
2067
|
+
|
|
2068
|
+
// Validate indexes
|
|
2069
|
+
if (groupIndex < 0 || groupIndex >= groups.length) {
|
|
1836
2070
|
return state;
|
|
1837
2071
|
}
|
|
1838
|
-
const group = groups
|
|
1839
|
-
if (
|
|
2072
|
+
const group = groups[groupIndex];
|
|
2073
|
+
if (index < 0 || index >= group.tabs.length) {
|
|
1840
2074
|
return state;
|
|
1841
2075
|
}
|
|
1842
|
-
const
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
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
|
|
2326
|
-
const
|
|
2327
|
-
if (!
|
|
2839
|
+
const retryOpen = async state => {
|
|
2840
|
+
const activeTabData = getActiveTab(state);
|
|
2841
|
+
if (!activeTabData) {
|
|
2328
2842
|
return state;
|
|
2329
2843
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
|
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
|
|
2743
|
-
|
|
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
|
-
|
|
2763
|
-
|
|
2764
|
-
if (lastSlashIndex === -1) {
|
|
2765
|
-
return uri;
|
|
3187
|
+
const parse = sashId => {
|
|
3188
|
+
if (!sashId) {
|
|
3189
|
+
return undefined;
|
|
2766
3190
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
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
|
-
|
|
2782
|
-
|
|
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
|
|
2796
|
-
|
|
2797
|
-
|
|
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
|
-
|
|
2805
|
-
|
|
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
|
|
2810
|
-
|
|
2811
|
-
|
|
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
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
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
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
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
|
-
|
|
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
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
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
|
-
|
|
2841
|
-
|
|
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
|
|
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
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
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
|
|
3560
|
-
|
|
3561
|
-
|
|
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:
|
|
4066
|
+
className: TextEditor,
|
|
3868
4067
|
type: Div
|
|
3869
4068
|
}, {
|
|
3870
4069
|
childCount: 1,
|
|
3871
|
-
className:
|
|
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:
|
|
3879
|
-
className:
|
|
4087
|
+
childCount: 2,
|
|
4088
|
+
className: TextEditorError,
|
|
3880
4089
|
type: Div
|
|
3881
4090
|
}, {
|
|
3882
|
-
childCount:
|
|
3883
|
-
className:
|
|
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:
|
|
4106
|
+
className: TextEditorLoading,
|
|
3892
4107
|
type: Div
|
|
3893
4108
|
}, {
|
|
3894
4109
|
childCount: 1,
|
|
3895
|
-
className:
|
|
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:
|
|
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:
|
|
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:
|
|
4168
|
+
className: EditorTabCloseButton,
|
|
3961
4169
|
'data-groupIndex': groupIndex,
|
|
3962
4170
|
'data-index': tabIndex,
|
|
3963
4171
|
onClick: HandleClickClose,
|
|
3964
|
-
type: Button$
|
|
4172
|
+
type: Button$2
|
|
3965
4173
|
}, {
|
|
3966
4174
|
childCount: 0,
|
|
3967
|
-
className:
|
|
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 =
|
|
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:
|
|
3995
|
-
role:
|
|
4202
|
+
className: TabIcon,
|
|
4203
|
+
role: None$1,
|
|
3996
4204
|
src: tab.icon,
|
|
3997
4205
|
type: Img
|
|
3998
4206
|
}, {
|
|
3999
4207
|
childCount: 1,
|
|
4000
|
-
className:
|
|
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:
|
|
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:
|
|
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$
|
|
4246
|
+
type: Button$2
|
|
4039
4247
|
}, {
|
|
4040
4248
|
childCount: 0,
|
|
4041
|
-
className:
|
|
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:
|
|
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$
|
|
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:
|
|
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:
|
|
4279
|
+
className: EditorGroupHeader,
|
|
4072
4280
|
onDblClick: HandleHeaderDoubleClick,
|
|
4073
|
-
role:
|
|
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:
|
|
4291
|
+
className: EditorGroup,
|
|
4292
|
+
style,
|
|
4083
4293
|
type: Div
|
|
4084
4294
|
}, ...renderEditorGroupHeader(group, groupIndex, splitButtonEnabled), {
|
|
4085
4295
|
childCount: 1,
|
|
4086
|
-
className:
|
|
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:
|
|
4327
|
+
className: Main,
|
|
4095
4328
|
type: Div
|
|
4096
4329
|
}, {
|
|
4097
|
-
childCount:
|
|
4098
|
-
className:
|
|
4099
|
-
role:
|
|
4330
|
+
childCount: children.length,
|
|
4331
|
+
className: EDITOR_GROUPS_CONTAINER,
|
|
4332
|
+
role: None$1,
|
|
4100
4333
|
type: Div
|
|
4101
|
-
}, ...
|
|
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',
|
|
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),
|