@lvce-editor/main-area-worker 8.4.0 → 8.6.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 +1027 -937
- 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;
|
|
@@ -1433,10 +1658,17 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
|
|
|
1433
1658
|
await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
|
|
1434
1659
|
};
|
|
1435
1660
|
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1661
|
+
const copyPath$1 = async (state, path) => {
|
|
1662
|
+
string(path);
|
|
1663
|
+
await invoke('ClipBoard.writeText', path);
|
|
1664
|
+
return state;
|
|
1665
|
+
};
|
|
1666
|
+
|
|
1667
|
+
const copyRelativePath$1 = async (state, path) => {
|
|
1668
|
+
string(path);
|
|
1669
|
+
const relativePath = await invoke('Workspace.pathBaseName', path);
|
|
1670
|
+
await invoke('ClipBoard.writeText', relativePath);
|
|
1671
|
+
return state;
|
|
1440
1672
|
};
|
|
1441
1673
|
|
|
1442
1674
|
const {
|
|
@@ -1448,25 +1680,89 @@ const {
|
|
|
1448
1680
|
wrapGetter
|
|
1449
1681
|
} = create$6();
|
|
1450
1682
|
|
|
1451
|
-
const create$1 = () => {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1683
|
+
const create$1 = (uid, uri, x, y, width, height, platform, assetDir, tabHeight = 35) => {
|
|
1684
|
+
const state = {
|
|
1685
|
+
assetDir,
|
|
1686
|
+
fileIconCache: {},
|
|
1687
|
+
height,
|
|
1688
|
+
iframes: [],
|
|
1689
|
+
initial: true,
|
|
1690
|
+
layout: {
|
|
1691
|
+
activeGroupId: undefined,
|
|
1692
|
+
direction: 'horizontal',
|
|
1693
|
+
groups: []
|
|
1694
|
+
},
|
|
1695
|
+
platform,
|
|
1696
|
+
splitButtonEnabled: false,
|
|
1697
|
+
tabHeight,
|
|
1698
|
+
uid,
|
|
1699
|
+
width,
|
|
1700
|
+
workspaceuri: uri,
|
|
1701
|
+
x,
|
|
1702
|
+
y
|
|
1703
|
+
};
|
|
1704
|
+
set(uid, state, state);
|
|
1705
|
+
};
|
|
1706
|
+
|
|
1707
|
+
const isEqual = (oldState, newState) => {
|
|
1708
|
+
return oldState.layout === newState.layout;
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
const RenderItems = 4;
|
|
1712
|
+
const RenderIncremental = 11;
|
|
1713
|
+
|
|
1714
|
+
const modules = [isEqual];
|
|
1715
|
+
const numbers = [RenderIncremental];
|
|
1716
|
+
|
|
1717
|
+
const diff = (oldState, newState) => {
|
|
1718
|
+
const diffResult = [];
|
|
1719
|
+
for (let i = 0; i < modules.length; i++) {
|
|
1720
|
+
const fn = modules[i];
|
|
1721
|
+
if (!fn(oldState, newState)) {
|
|
1722
|
+
diffResult.push(numbers[i]);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return diffResult;
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
const diff2 = uid => {
|
|
1729
|
+
const {
|
|
1730
|
+
newState,
|
|
1731
|
+
oldState
|
|
1732
|
+
} = get(uid);
|
|
1733
|
+
const result = diff(oldState, newState);
|
|
1734
|
+
return result;
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1737
|
+
const createViewlet = async (viewletModuleId, editorUid, tabId, bounds, uri) => {
|
|
1738
|
+
await invoke('Layout.createViewlet', viewletModuleId, editorUid, tabId, bounds, uri);
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
const handleAttach = async command => {
|
|
1742
|
+
// TODO find a better way to append editors
|
|
1743
|
+
const parentNodeSelector = '.editor-groups-container';
|
|
1744
|
+
await invoke('Layout.attachViewlet', parentNodeSelector, command.instanceId);
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
const create = () => {
|
|
1748
|
+
return Math.random();
|
|
1749
|
+
};
|
|
1750
|
+
|
|
1751
|
+
const updateTab = (state, tabId, updates) => {
|
|
1752
|
+
const {
|
|
1753
|
+
layout
|
|
1754
|
+
} = state;
|
|
1755
|
+
const {
|
|
1756
|
+
groups
|
|
1757
|
+
} = layout;
|
|
1758
|
+
const updatedGroups = groups.map(group => {
|
|
1759
|
+
const tabIndex = group.tabs.findIndex(t => t.id === tabId);
|
|
1760
|
+
if (tabIndex === -1) {
|
|
1761
|
+
return group;
|
|
1762
|
+
}
|
|
1763
|
+
const updatedTabs = group.tabs.map((tab, index) => {
|
|
1764
|
+
if (index === tabIndex) {
|
|
1765
|
+
return {
|
|
1470
1766
|
...tab,
|
|
1471
1767
|
...updates
|
|
1472
1768
|
};
|
|
@@ -1525,7 +1821,7 @@ const loadTabContentAsync = async (tabId, path, requestId, getLatestState) => {
|
|
|
1525
1821
|
}
|
|
1526
1822
|
|
|
1527
1823
|
// Assign editorUid if tab doesn't have one yet
|
|
1528
|
-
const editorUid = latestTab.editorUid === -1 ? create
|
|
1824
|
+
const editorUid = latestTab.editorUid === -1 ? create() : latestTab.editorUid;
|
|
1529
1825
|
return updateTab(latestState, tabId, {
|
|
1530
1826
|
editorUid,
|
|
1531
1827
|
errorMessage: undefined,
|
|
@@ -1565,7 +1861,7 @@ const createViewletForTab = (state, tabId, viewletModuleId, bounds) => {
|
|
|
1565
1861
|
if (tab.editorUid !== -1 || tab.loadingState === 'loading' || tab.loadingState === 'loaded') {
|
|
1566
1862
|
return state;
|
|
1567
1863
|
}
|
|
1568
|
-
const editorUid = create
|
|
1864
|
+
const editorUid = create();
|
|
1569
1865
|
const newState = updateTab(state, tabId, {
|
|
1570
1866
|
editorUid
|
|
1571
1867
|
});
|
|
@@ -1689,387 +1985,94 @@ const executeViewletCommands = async commands => {
|
|
|
1689
1985
|
}
|
|
1690
1986
|
};
|
|
1691
1987
|
|
|
1692
|
-
const
|
|
1988
|
+
const findTabById = (state, tabId) => {
|
|
1693
1989
|
const {
|
|
1694
1990
|
layout
|
|
1695
1991
|
} = state;
|
|
1696
1992
|
const {
|
|
1697
|
-
activeGroupId,
|
|
1698
1993
|
groups
|
|
1699
1994
|
} = 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
|
-
}
|
|
1995
|
+
for (const group of groups) {
|
|
1996
|
+
const tab = group.tabs.find(t => t.id === tabId);
|
|
1997
|
+
if (tab) {
|
|
1724
1998
|
return {
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
isEmpty: newTabs.length === 0,
|
|
1728
|
-
tabs: newTabs
|
|
1999
|
+
groupId: group.id,
|
|
2000
|
+
tab
|
|
1729
2001
|
};
|
|
1730
2002
|
}
|
|
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);
|
|
2003
|
+
}
|
|
2004
|
+
return undefined;
|
|
2005
|
+
};
|
|
1738
2006
|
|
|
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
|
-
}
|
|
2007
|
+
// Counter for request IDs to handle race conditions
|
|
2008
|
+
let requestIdCounter = 0;
|
|
2009
|
+
const getNextRequestId = () => {
|
|
2010
|
+
return ++requestIdCounter;
|
|
2011
|
+
};
|
|
1755
2012
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
...layout,
|
|
1761
|
-
activeGroupId: undefined,
|
|
1762
|
-
groups: []
|
|
1763
|
-
}
|
|
2013
|
+
const startContentLoading = async (oldState, state, tabId, path, requestId) => {
|
|
2014
|
+
try {
|
|
2015
|
+
const getLatestState = () => {
|
|
2016
|
+
return get(state.uid).newState;
|
|
1764
2017
|
};
|
|
2018
|
+
set(state.uid, oldState, state);
|
|
2019
|
+
const newState = await loadTabContentAsync(tabId, path, requestId, getLatestState);
|
|
2020
|
+
return newState;
|
|
2021
|
+
} catch {
|
|
2022
|
+
// Silently ignore errors - the tab may have been closed or the component unmounted
|
|
1765
2023
|
}
|
|
1766
|
-
return
|
|
1767
|
-
...state,
|
|
1768
|
-
layout: {
|
|
1769
|
-
...layout,
|
|
1770
|
-
groups: newGroups
|
|
1771
|
-
}
|
|
1772
|
-
};
|
|
2024
|
+
return state;
|
|
1773
2025
|
};
|
|
1774
2026
|
|
|
1775
|
-
const
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
} = layout;
|
|
1782
|
-
const focusedGroup = groups.find(group => group.focused);
|
|
1783
|
-
if (!focusedGroup) {
|
|
1784
|
-
return state;
|
|
2027
|
+
const shouldLoadContent = tab => {
|
|
2028
|
+
// Load if:
|
|
2029
|
+
// - Has a path (file-based tab)
|
|
2030
|
+
// - Not already loaded or currently loading
|
|
2031
|
+
if (!tab.uri) {
|
|
2032
|
+
return false;
|
|
1785
2033
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
} = focusedGroup;
|
|
1789
|
-
if (activeTabId === undefined) {
|
|
1790
|
-
return state;
|
|
2034
|
+
if (tab.loadingState === 'loading') {
|
|
2035
|
+
return false;
|
|
1791
2036
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
return {
|
|
1797
|
-
...state,
|
|
1798
|
-
layout: {
|
|
1799
|
-
...state.layout,
|
|
1800
|
-
activeGroupId: undefined,
|
|
1801
|
-
groups: []
|
|
1802
|
-
}
|
|
1803
|
-
};
|
|
2037
|
+
if (tab.loadingState === 'loaded') {
|
|
2038
|
+
return false;
|
|
2039
|
+
}
|
|
2040
|
+
return true;
|
|
1804
2041
|
};
|
|
1805
|
-
|
|
1806
|
-
const closeFocusedTab = state => {
|
|
2042
|
+
const getActiveTabId$1 = state => {
|
|
1807
2043
|
const {
|
|
1808
2044
|
layout
|
|
1809
2045
|
} = state;
|
|
1810
2046
|
const {
|
|
2047
|
+
activeGroupId,
|
|
1811
2048
|
groups
|
|
1812
2049
|
} = 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);
|
|
2050
|
+
const activeGroup = groups.find(g => g.id === activeGroupId);
|
|
2051
|
+
return activeGroup?.activeTabId;
|
|
1824
2052
|
};
|
|
1825
|
-
|
|
1826
|
-
const closeOtherTabs = (state, groupId) => {
|
|
2053
|
+
const selectTab = async (state, groupIndex, index) => {
|
|
1827
2054
|
const {
|
|
1828
|
-
layout
|
|
2055
|
+
layout,
|
|
2056
|
+
uid
|
|
1829
2057
|
} = state;
|
|
1830
2058
|
const {
|
|
1831
|
-
activeGroupId,
|
|
1832
2059
|
groups
|
|
1833
2060
|
} = layout;
|
|
1834
|
-
|
|
1835
|
-
|
|
2061
|
+
|
|
2062
|
+
// Validate indexes
|
|
2063
|
+
if (groupIndex < 0 || groupIndex >= groups.length) {
|
|
1836
2064
|
return state;
|
|
1837
2065
|
}
|
|
1838
|
-
const group = groups
|
|
1839
|
-
if (
|
|
2066
|
+
const group = groups[groupIndex];
|
|
2067
|
+
if (index < 0 || index >= group.tabs.length) {
|
|
1840
2068
|
return state;
|
|
1841
2069
|
}
|
|
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) {
|
|
2070
|
+
const tab = group.tabs[index];
|
|
2071
|
+
const groupId = group.id;
|
|
2072
|
+
const tabId = tab.id;
|
|
2073
|
+
|
|
2074
|
+
// Return same state if this group and tab are already active
|
|
2075
|
+
if (layout.activeGroupId === groupId && group.activeTabId === tabId) {
|
|
2073
2076
|
return state;
|
|
2074
2077
|
}
|
|
2075
2078
|
|
|
@@ -2291,44 +2294,554 @@ const closeEditorGroup = (state, groupId) => {
|
|
|
2291
2294
|
activeGroupId: newActiveGroupId,
|
|
2292
2295
|
groups: redistributedGroups
|
|
2293
2296
|
}
|
|
2294
|
-
};
|
|
2295
|
-
};
|
|
2297
|
+
};
|
|
2298
|
+
};
|
|
2299
|
+
|
|
2300
|
+
const getActiveGroup = (groups, activeGroupId) => {
|
|
2301
|
+
return groups.find(g => g.id === activeGroupId);
|
|
2302
|
+
};
|
|
2303
|
+
|
|
2304
|
+
const Left = 'left';
|
|
2305
|
+
const Right = 'right';
|
|
2306
|
+
|
|
2307
|
+
const getActiveTab = state => {
|
|
2308
|
+
const {
|
|
2309
|
+
layout
|
|
2310
|
+
} = state;
|
|
2311
|
+
const {
|
|
2312
|
+
groups
|
|
2313
|
+
} = layout;
|
|
2314
|
+
const activeGroup = groups.find(group => group.focused);
|
|
2315
|
+
if (!activeGroup || !activeGroup.activeTabId) {
|
|
2316
|
+
return undefined;
|
|
2317
|
+
}
|
|
2318
|
+
const activeTab = activeGroup.tabs.find(tab => tab.id === activeGroup.activeTabId);
|
|
2319
|
+
if (!activeTab) {
|
|
2320
|
+
return undefined;
|
|
2321
|
+
}
|
|
2322
|
+
return {
|
|
2323
|
+
groupId: activeGroup.id,
|
|
2324
|
+
tab: activeTab
|
|
2325
|
+
};
|
|
2326
|
+
};
|
|
2327
|
+
|
|
2328
|
+
const handleClickTogglePreview = async state => {
|
|
2329
|
+
const activeTabInfo = getActiveTab(state);
|
|
2330
|
+
if (!activeTabInfo || !activeTabInfo.tab.uri) {
|
|
2331
|
+
return state;
|
|
2332
|
+
}
|
|
2333
|
+
await invoke('Layout.showPreview', activeTabInfo.tab.uri);
|
|
2334
|
+
return state;
|
|
2335
|
+
};
|
|
2336
|
+
|
|
2337
|
+
const getBasename$1 = uri => {
|
|
2338
|
+
const lastSlashIndex = uri.lastIndexOf('/');
|
|
2339
|
+
if (lastSlashIndex === -1) {
|
|
2340
|
+
return uri;
|
|
2341
|
+
}
|
|
2342
|
+
return uri.slice(lastSlashIndex + 1);
|
|
2343
|
+
};
|
|
2344
|
+
const getLabel = uri => {
|
|
2345
|
+
if (uri.startsWith('settings://')) {
|
|
2346
|
+
return 'Settings';
|
|
2347
|
+
}
|
|
2348
|
+
if (uri.startsWith('simple-browser://')) {
|
|
2349
|
+
return 'Simple Browser';
|
|
2350
|
+
}
|
|
2351
|
+
if (uri.startsWith('language-models://')) {
|
|
2352
|
+
return 'Language Models';
|
|
2353
|
+
}
|
|
2354
|
+
return getBasename$1(uri);
|
|
2355
|
+
};
|
|
2356
|
+
|
|
2357
|
+
const createEmptyGroup = (state, uri, requestId) => {
|
|
2358
|
+
const {
|
|
2359
|
+
layout
|
|
2360
|
+
} = state;
|
|
2361
|
+
const {
|
|
2362
|
+
groups
|
|
2363
|
+
} = layout;
|
|
2364
|
+
const groupId = create();
|
|
2365
|
+
const title = getLabel(uri);
|
|
2366
|
+
const tabId = create();
|
|
2367
|
+
const editorUid = create();
|
|
2368
|
+
const newTab = {
|
|
2369
|
+
editorType: 'text',
|
|
2370
|
+
editorUid,
|
|
2371
|
+
errorMessage: '',
|
|
2372
|
+
icon: '',
|
|
2373
|
+
id: tabId,
|
|
2374
|
+
isDirty: false,
|
|
2375
|
+
language: '',
|
|
2376
|
+
loadingState: 'loading',
|
|
2377
|
+
title,
|
|
2378
|
+
uri
|
|
2379
|
+
};
|
|
2380
|
+
const newGroup = {
|
|
2381
|
+
activeTabId: newTab.id,
|
|
2382
|
+
focused: true,
|
|
2383
|
+
id: groupId,
|
|
2384
|
+
isEmpty: false,
|
|
2385
|
+
size: 100,
|
|
2386
|
+
tabs: [newTab]
|
|
2387
|
+
};
|
|
2388
|
+
return {
|
|
2389
|
+
...state,
|
|
2390
|
+
layout: {
|
|
2391
|
+
...layout,
|
|
2392
|
+
activeGroupId: groupId,
|
|
2393
|
+
groups: [...groups, newGroup]
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
};
|
|
2397
|
+
|
|
2398
|
+
const openTab = (state, groupId, tab) => {
|
|
2399
|
+
const newTab = 'id' in tab && tab.id !== undefined ? tab : {
|
|
2400
|
+
...tab,
|
|
2401
|
+
id: create()
|
|
2402
|
+
};
|
|
2403
|
+
const {
|
|
2404
|
+
layout
|
|
2405
|
+
} = state;
|
|
2406
|
+
const {
|
|
2407
|
+
groups
|
|
2408
|
+
} = layout;
|
|
2409
|
+
const updatedGroups = groups.map(group => {
|
|
2410
|
+
if (group.id === groupId) {
|
|
2411
|
+
const newTabs = [...group.tabs, newTab];
|
|
2412
|
+
return {
|
|
2413
|
+
...group,
|
|
2414
|
+
activeTabId: newTab.id,
|
|
2415
|
+
isEmpty: newTabs.length === 0,
|
|
2416
|
+
tabs: newTabs
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
return group;
|
|
2420
|
+
});
|
|
2421
|
+
return {
|
|
2422
|
+
...state,
|
|
2423
|
+
layout: {
|
|
2424
|
+
...layout,
|
|
2425
|
+
groups: updatedGroups
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
};
|
|
2429
|
+
|
|
2430
|
+
const ensureActiveGroup = (state, uri) => {
|
|
2431
|
+
// Find the active group (by activeGroupId or focused flag)
|
|
2432
|
+
const {
|
|
2433
|
+
layout
|
|
2434
|
+
} = state;
|
|
2435
|
+
const {
|
|
2436
|
+
activeGroupId,
|
|
2437
|
+
groups
|
|
2438
|
+
} = layout;
|
|
2439
|
+
const activeGroup = activeGroupId === undefined ? groups.find(group => group.focused) : groups.find(group => group.id === activeGroupId);
|
|
2440
|
+
|
|
2441
|
+
// Generate a request ID for content loading
|
|
2442
|
+
getNextRequestId();
|
|
2443
|
+
|
|
2444
|
+
// If no active group exists, create one
|
|
2445
|
+
let newState;
|
|
2446
|
+
if (activeGroup) {
|
|
2447
|
+
// Create a new tab with the URI in the active group
|
|
2448
|
+
const title = getLabel(uri);
|
|
2449
|
+
const tabId = create();
|
|
2450
|
+
const editorUid = create();
|
|
2451
|
+
const newTab = {
|
|
2452
|
+
editorType: 'text',
|
|
2453
|
+
editorUid,
|
|
2454
|
+
errorMessage: '',
|
|
2455
|
+
icon: '',
|
|
2456
|
+
id: tabId,
|
|
2457
|
+
isDirty: false,
|
|
2458
|
+
language: '',
|
|
2459
|
+
loadingState: 'loading',
|
|
2460
|
+
title,
|
|
2461
|
+
uri: uri
|
|
2462
|
+
};
|
|
2463
|
+
newState = openTab(state, activeGroup.id, newTab);
|
|
2464
|
+
} else {
|
|
2465
|
+
newState = createEmptyGroup(state, uri);
|
|
2466
|
+
}
|
|
2467
|
+
return newState;
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
const findTabByUri = (state, uri) => {
|
|
2471
|
+
const {
|
|
2472
|
+
layout
|
|
2473
|
+
} = state;
|
|
2474
|
+
const {
|
|
2475
|
+
groups
|
|
2476
|
+
} = layout;
|
|
2477
|
+
for (const group of groups) {
|
|
2478
|
+
const tab = group.tabs.find(t => t.uri === uri);
|
|
2479
|
+
if (tab) {
|
|
2480
|
+
return {
|
|
2481
|
+
groupId: group.id,
|
|
2482
|
+
tab
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
return undefined;
|
|
2487
|
+
};
|
|
2488
|
+
|
|
2489
|
+
const focusEditorGroup = (state, groupId) => {
|
|
2490
|
+
const {
|
|
2491
|
+
layout
|
|
2492
|
+
} = state;
|
|
2493
|
+
const {
|
|
2494
|
+
groups
|
|
2495
|
+
} = layout;
|
|
2496
|
+
const updatedGroups = groups.map(group => ({
|
|
2497
|
+
...group,
|
|
2498
|
+
focused: group.id === groupId
|
|
2499
|
+
}));
|
|
2500
|
+
return {
|
|
2501
|
+
...state,
|
|
2502
|
+
layout: {
|
|
2503
|
+
...layout,
|
|
2504
|
+
activeGroupId: groupId,
|
|
2505
|
+
groups: updatedGroups
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
const getActiveTabId = state => {
|
|
2511
|
+
const {
|
|
2512
|
+
layout
|
|
2513
|
+
} = state;
|
|
2514
|
+
const {
|
|
2515
|
+
activeGroupId,
|
|
2516
|
+
groups
|
|
2517
|
+
} = layout;
|
|
2518
|
+
const activeGroup = groups.find(g => g.id === activeGroupId);
|
|
2519
|
+
return activeGroup?.activeTabId;
|
|
2520
|
+
};
|
|
2521
|
+
|
|
2522
|
+
const getIconsCached = (dirents, fileIconCache) => {
|
|
2523
|
+
return dirents.map(dirent => fileIconCache[dirent]);
|
|
2524
|
+
};
|
|
2525
|
+
|
|
2526
|
+
const getBasename = uri => {
|
|
2527
|
+
const lastSlashIndex = uri.lastIndexOf('/');
|
|
2528
|
+
if (lastSlashIndex === -1) {
|
|
2529
|
+
return uri;
|
|
2530
|
+
}
|
|
2531
|
+
return uri.slice(lastSlashIndex + 1);
|
|
2532
|
+
};
|
|
2533
|
+
const getMissingTabs = (tabs, fileIconCache) => {
|
|
2534
|
+
const missingTabs = [];
|
|
2535
|
+
for (const tab of tabs) {
|
|
2536
|
+
if (tab.uri && !(tab.uri in fileIconCache)) {
|
|
2537
|
+
missingTabs.push(tab);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
return missingTabs;
|
|
2541
|
+
};
|
|
2542
|
+
const tabToIconRequest = tab => {
|
|
2543
|
+
const uri = tab.uri || '';
|
|
2544
|
+
return {
|
|
2545
|
+
name: getBasename(uri),
|
|
2546
|
+
path: uri,
|
|
2547
|
+
type: 0 // file type
|
|
2548
|
+
};
|
|
2549
|
+
};
|
|
2550
|
+
const getMissingIconRequestsForTabs = (tabs, fileIconCache) => {
|
|
2551
|
+
const missingRequests = getMissingTabs(tabs, fileIconCache);
|
|
2552
|
+
const iconRequests = missingRequests.map(tabToIconRequest);
|
|
2553
|
+
return iconRequests;
|
|
2554
|
+
};
|
|
2555
|
+
|
|
2556
|
+
const Directory = 3;
|
|
2557
|
+
const DirectoryExpanded = 4;
|
|
2558
|
+
|
|
2559
|
+
const getSimpleIconRequestType = direntType => {
|
|
2560
|
+
if (direntType === Directory || direntType === DirectoryExpanded) {
|
|
2561
|
+
return 2;
|
|
2562
|
+
}
|
|
2563
|
+
return 1;
|
|
2564
|
+
};
|
|
2565
|
+
|
|
2566
|
+
const toSimpleIconRequest = request => {
|
|
2567
|
+
return {
|
|
2568
|
+
name: request.name,
|
|
2569
|
+
type: getSimpleIconRequestType(request.type)
|
|
2570
|
+
};
|
|
2571
|
+
};
|
|
2572
|
+
|
|
2573
|
+
const requestFileIcons = async requests => {
|
|
2574
|
+
if (requests.length === 0) {
|
|
2575
|
+
return [];
|
|
2576
|
+
}
|
|
2577
|
+
const simpleRequests = requests.map(toSimpleIconRequest);
|
|
2578
|
+
const icons = await getIcons(simpleRequests);
|
|
2579
|
+
return icons;
|
|
2580
|
+
};
|
|
2581
|
+
|
|
2582
|
+
const updateIconCache = (iconCache, missingRequests, newIcons) => {
|
|
2583
|
+
if (missingRequests.length === 0) {
|
|
2584
|
+
return iconCache;
|
|
2585
|
+
}
|
|
2586
|
+
const newFileIconCache = {
|
|
2587
|
+
...iconCache
|
|
2588
|
+
};
|
|
2589
|
+
for (let i = 0; i < missingRequests.length; i++) {
|
|
2590
|
+
const request = missingRequests[i];
|
|
2591
|
+
const icon = newIcons[i];
|
|
2592
|
+
newFileIconCache[request.path] = icon;
|
|
2593
|
+
}
|
|
2594
|
+
return newFileIconCache;
|
|
2595
|
+
};
|
|
2596
|
+
|
|
2597
|
+
const getFileIconsForTabs = async (tabs, fileIconCache) => {
|
|
2598
|
+
const missingRequests = getMissingIconRequestsForTabs(tabs, fileIconCache);
|
|
2599
|
+
const newIcons = await requestFileIcons(missingRequests);
|
|
2600
|
+
const newFileIconCache = updateIconCache(fileIconCache, missingRequests, newIcons);
|
|
2601
|
+
const tabUris = tabs.map(tab => tab.uri || '');
|
|
2602
|
+
const icons = getIconsCached(tabUris, newFileIconCache);
|
|
2603
|
+
return {
|
|
2604
|
+
icons,
|
|
2605
|
+
newFileIconCache
|
|
2606
|
+
};
|
|
2607
|
+
};
|
|
2608
|
+
|
|
2609
|
+
const getOptionUriOptions = options => {
|
|
2610
|
+
let uri = '';
|
|
2611
|
+
if (typeof options === 'string') {
|
|
2612
|
+
uri = options;
|
|
2613
|
+
} else {
|
|
2614
|
+
const {
|
|
2615
|
+
uri: optionsUri
|
|
2616
|
+
} = options;
|
|
2617
|
+
uri = optionsUri;
|
|
2618
|
+
}
|
|
2619
|
+
return uri;
|
|
2620
|
+
};
|
|
2621
|
+
|
|
2622
|
+
const getViewletModuleId = async uri => {
|
|
2623
|
+
// Query RendererWorker for viewlet module ID (optional, may fail in tests)
|
|
2624
|
+
let viewletModuleId;
|
|
2625
|
+
try {
|
|
2626
|
+
viewletModuleId = await invoke('Layout.getModuleId', uri);
|
|
2627
|
+
} catch {
|
|
2628
|
+
// Viewlet creation is optional - silently ignore if RendererWorker isn't available
|
|
2629
|
+
}
|
|
2630
|
+
return viewletModuleId;
|
|
2631
|
+
};
|
|
2632
|
+
|
|
2633
|
+
const switchTab = (state, groupId, tabId) => {
|
|
2634
|
+
const {
|
|
2635
|
+
layout
|
|
2636
|
+
} = state;
|
|
2637
|
+
const {
|
|
2638
|
+
groups
|
|
2639
|
+
} = layout;
|
|
2640
|
+
const updatedGroups = groups.map(group => {
|
|
2641
|
+
if (group.id === groupId) {
|
|
2642
|
+
const tabExists = group.tabs.some(tab => tab.id === tabId);
|
|
2643
|
+
if (tabExists) {
|
|
2644
|
+
return {
|
|
2645
|
+
...group,
|
|
2646
|
+
activeTabId: tabId
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
return group;
|
|
2651
|
+
});
|
|
2652
|
+
return {
|
|
2653
|
+
...state,
|
|
2654
|
+
layout: {
|
|
2655
|
+
...layout,
|
|
2656
|
+
groups: updatedGroups
|
|
2657
|
+
}
|
|
2658
|
+
};
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
const openUri = async (state, options) => {
|
|
2662
|
+
object(state);
|
|
2663
|
+
const {
|
|
2664
|
+
uid
|
|
2665
|
+
} = state;
|
|
2666
|
+
const uri = getOptionUriOptions(options);
|
|
2667
|
+
|
|
2668
|
+
// Check if a tab with this URI already exists in the passed-in state
|
|
2669
|
+
const existingTab = findTabByUri(state, uri);
|
|
2670
|
+
const shouldRetryExistingTab = existingTab && existingTab.tab.loadingState === 'error';
|
|
2671
|
+
if (existingTab && !shouldRetryExistingTab) {
|
|
2672
|
+
// Tab exists, switch to it and focus its group
|
|
2673
|
+
const focusedState = focusEditorGroup(state, existingTab.groupId);
|
|
2674
|
+
return switchTab(focusedState, existingTab.groupId, existingTab.tab.id);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
// Get previous active tab ID for viewlet switching
|
|
2678
|
+
const previousTabId = getActiveTabId(state);
|
|
2679
|
+
|
|
2680
|
+
// Check if there's existing state in the global store
|
|
2681
|
+
const stateFromStore = get(uid);
|
|
2682
|
+
let currentState;
|
|
2683
|
+
if (stateFromStore) {
|
|
2684
|
+
const storedState = stateFromStore.newState;
|
|
2685
|
+
// Use the stored state if it has more tabs than the passed-in state
|
|
2686
|
+
// (indicating concurrent calls have already added tabs)
|
|
2687
|
+
// Otherwise use the passed-in state (test setup with initial data)
|
|
2688
|
+
const storedTabCount = storedState.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
|
|
2689
|
+
const passedTabCount = state.layout.groups.reduce((sum, g) => sum + g.tabs.length, 0);
|
|
2690
|
+
if (storedTabCount > passedTabCount) {
|
|
2691
|
+
// Stored state has more tabs - concurrent calls have added tabs
|
|
2692
|
+
currentState = storedState;
|
|
2693
|
+
} else {
|
|
2694
|
+
// Passed-in state has same or more tabs, use it (likely fresh test setup)
|
|
2695
|
+
currentState = state;
|
|
2696
|
+
set(uid, state, state);
|
|
2697
|
+
}
|
|
2698
|
+
} else {
|
|
2699
|
+
// No state in store yet, register the passed-in state
|
|
2700
|
+
currentState = state;
|
|
2701
|
+
set(uid, state, state);
|
|
2702
|
+
}
|
|
2703
|
+
let tabId;
|
|
2704
|
+
let stateWithTab;
|
|
2705
|
+
if (shouldRetryExistingTab && existingTab) {
|
|
2706
|
+
const focusedState = focusEditorGroup(currentState, existingTab.groupId);
|
|
2707
|
+
stateWithTab = updateTab(focusedState, existingTab.tab.id, {
|
|
2708
|
+
errorMessage: '',
|
|
2709
|
+
loadingState: 'loading'
|
|
2710
|
+
});
|
|
2711
|
+
tabId = existingTab.tab.id;
|
|
2712
|
+
} else {
|
|
2713
|
+
// Add tab to state BEFORE any async calls to prevent race conditions
|
|
2714
|
+
stateWithTab = ensureActiveGroup(currentState, uri);
|
|
2715
|
+
tabId = getActiveTabId(stateWithTab);
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// Save state immediately after adding tab or retrying
|
|
2719
|
+
set(uid, state, stateWithTab);
|
|
2720
|
+
try {
|
|
2721
|
+
const viewletModuleId = await getViewletModuleId(uri);
|
|
2722
|
+
|
|
2723
|
+
// After async call, get the latest state to account for any concurrent changes
|
|
2724
|
+
const {
|
|
2725
|
+
newState: stateAfterModuleId
|
|
2726
|
+
} = get(uid);
|
|
2727
|
+
if (!viewletModuleId) {
|
|
2728
|
+
return updateTab(stateAfterModuleId, tabId, {
|
|
2729
|
+
errorMessage: 'Could not determine editor type for this URI',
|
|
2730
|
+
loadingState: 'error'
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
// Calculate bounds: use main area bounds minus tab height
|
|
2735
|
+
const bounds = {
|
|
2736
|
+
height: stateAfterModuleId.height - stateAfterModuleId.tabHeight,
|
|
2737
|
+
width: stateAfterModuleId.width,
|
|
2738
|
+
x: stateAfterModuleId.x,
|
|
2739
|
+
y: stateAfterModuleId.y + stateAfterModuleId.tabHeight
|
|
2740
|
+
};
|
|
2741
|
+
const stateWithViewlet = createViewletForTab(stateAfterModuleId, tabId, viewletModuleId, bounds);
|
|
2742
|
+
let intermediateState1 = stateWithViewlet;
|
|
2743
|
+
|
|
2744
|
+
// Switch viewlet (detach old, attach new if ready)
|
|
2745
|
+
const {
|
|
2746
|
+
newState: switchedState
|
|
2747
|
+
} = switchViewlet(intermediateState1, previousTabId, tabId);
|
|
2748
|
+
intermediateState1 = switchedState;
|
|
2749
|
+
set(uid, state, intermediateState1);
|
|
2750
|
+
|
|
2751
|
+
// Get the tab to extract editorUid
|
|
2752
|
+
const tabWithViewlet = findTabById(intermediateState1, tabId);
|
|
2753
|
+
if (!tabWithViewlet) {
|
|
2754
|
+
return intermediateState1;
|
|
2755
|
+
}
|
|
2756
|
+
const {
|
|
2757
|
+
editorUid
|
|
2758
|
+
} = tabWithViewlet.tab;
|
|
2759
|
+
if (editorUid === -1) {
|
|
2760
|
+
throw new Error(`invalid editorUid`);
|
|
2761
|
+
}
|
|
2762
|
+
await createViewlet(viewletModuleId, editorUid, tabId, bounds, uri);
|
|
2296
2763
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2764
|
+
// After viewlet is created, get the latest state and mark it as ready
|
|
2765
|
+
// This ensures we have any state updates that occurred during viewlet creation
|
|
2766
|
+
const {
|
|
2767
|
+
newState: latestState
|
|
2768
|
+
} = get(uid);
|
|
2300
2769
|
|
|
2301
|
-
|
|
2302
|
-
const
|
|
2770
|
+
// Attachment is handled automatically by virtual DOM reference nodes
|
|
2771
|
+
const readyState = handleViewletReady(latestState, editorUid);
|
|
2303
2772
|
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2773
|
+
// Save state before async icon request
|
|
2774
|
+
set(uid, state, readyState);
|
|
2775
|
+
|
|
2776
|
+
// Request file icon for the newly opened tab
|
|
2777
|
+
try {
|
|
2778
|
+
const newTab = findTabById(readyState, tabId);
|
|
2779
|
+
if (newTab && newTab.tab.uri) {
|
|
2780
|
+
const {
|
|
2781
|
+
newFileIconCache
|
|
2782
|
+
} = await getFileIconsForTabs([newTab.tab], readyState.fileIconCache);
|
|
2783
|
+
|
|
2784
|
+
// After async call, get the latest state again
|
|
2785
|
+
const {
|
|
2786
|
+
newState: stateBeforeIconUpdate
|
|
2787
|
+
} = get(uid);
|
|
2788
|
+
const icon = newFileIconCache[newTab.tab.uri] || '';
|
|
2789
|
+
|
|
2790
|
+
// Update the tab with the icon in the latest state
|
|
2791
|
+
const stateWithIcon = {
|
|
2792
|
+
...stateBeforeIconUpdate,
|
|
2793
|
+
fileIconCache: newFileIconCache,
|
|
2794
|
+
layout: {
|
|
2795
|
+
...stateBeforeIconUpdate.layout,
|
|
2796
|
+
groups: stateBeforeIconUpdate.layout.groups.map(group => ({
|
|
2797
|
+
...group,
|
|
2798
|
+
tabs: group.tabs.map(tab => tab.id === tabId ? {
|
|
2799
|
+
...tab,
|
|
2800
|
+
icon
|
|
2801
|
+
} : tab)
|
|
2802
|
+
}))
|
|
2803
|
+
}
|
|
2804
|
+
};
|
|
2805
|
+
|
|
2806
|
+
// Save the state with icon update so concurrent calls can see it
|
|
2807
|
+
set(uid, state, stateWithIcon);
|
|
2808
|
+
return stateWithIcon;
|
|
2809
|
+
}
|
|
2810
|
+
} catch {
|
|
2811
|
+
// If icon request fails, continue without icon
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
// Get final latest state
|
|
2815
|
+
const {
|
|
2816
|
+
newState: finalState
|
|
2817
|
+
} = get(uid);
|
|
2818
|
+
return finalState;
|
|
2819
|
+
} catch (error) {
|
|
2820
|
+
const {
|
|
2821
|
+
newState: latestState
|
|
2822
|
+
} = get(uid);
|
|
2823
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to open URI';
|
|
2824
|
+
const errorState = updateTab(latestState, tabId, {
|
|
2825
|
+
errorMessage,
|
|
2826
|
+
loadingState: 'error'
|
|
2827
|
+
});
|
|
2828
|
+
set(uid, state, errorState);
|
|
2829
|
+
return errorState;
|
|
2318
2830
|
}
|
|
2319
|
-
return {
|
|
2320
|
-
groupId: activeGroup.id,
|
|
2321
|
-
tab: activeTab
|
|
2322
|
-
};
|
|
2323
2831
|
};
|
|
2324
2832
|
|
|
2325
|
-
const
|
|
2326
|
-
const
|
|
2327
|
-
if (!
|
|
2833
|
+
const retryOpen = async state => {
|
|
2834
|
+
const activeTabData = getActiveTab(state);
|
|
2835
|
+
if (!activeTabData) {
|
|
2328
2836
|
return state;
|
|
2329
2837
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2838
|
+
const {
|
|
2839
|
+
tab
|
|
2840
|
+
} = activeTabData;
|
|
2841
|
+
if (!tab.uri) {
|
|
2842
|
+
return state;
|
|
2843
|
+
}
|
|
2844
|
+
return openUri(state, tab.uri);
|
|
2332
2845
|
};
|
|
2333
2846
|
|
|
2334
2847
|
const splitEditorGroup$1 = (state, groupId, direction) => {
|
|
@@ -2342,7 +2855,7 @@ const splitEditorGroup$1 = (state, groupId, direction) => {
|
|
|
2342
2855
|
if (!sourceGroup) {
|
|
2343
2856
|
return state;
|
|
2344
2857
|
}
|
|
2345
|
-
const newGroupId = create
|
|
2858
|
+
const newGroupId = create();
|
|
2346
2859
|
const isHorizontalSplit = direction === Left || direction === Right;
|
|
2347
2860
|
const newLayoutDirection = isHorizontalSplit ? 'horizontal' : 'vertical';
|
|
2348
2861
|
const updatedGroups = groups.map(group => {
|
|
@@ -2400,6 +2913,8 @@ const handleClickAction = async (state, action, rawGroupId) => {
|
|
|
2400
2913
|
}
|
|
2401
2914
|
}
|
|
2402
2915
|
return state;
|
|
2916
|
+
case 'retry-open':
|
|
2917
|
+
return retryOpen(state);
|
|
2403
2918
|
case 'split-right':
|
|
2404
2919
|
return splitEditorGroup$1(state, activeGroup.id, Right);
|
|
2405
2920
|
case 'toggle-preview':
|
|
@@ -2450,112 +2965,33 @@ const handleDoubleClick = async state => {
|
|
|
2450
2965
|
return state;
|
|
2451
2966
|
};
|
|
2452
2967
|
|
|
2968
|
+
const EditorGroupHeader = 'EditorGroupHeader';
|
|
2969
|
+
const EditorGroup = 'EditorGroup';
|
|
2970
|
+
const EditorContainer = 'EditorContainer';
|
|
2971
|
+
const EditorGroupActions = 'EditorGroupActions';
|
|
2972
|
+
const EditorGroupActionButton = 'EditorGroupActionButton';
|
|
2973
|
+
const SplitEditorGroupButton = 'SplitEditorGroupButton';
|
|
2974
|
+
const MaskIconPreview = 'MaskIcon MaskIconPreview';
|
|
2975
|
+
const MaskIconCircleFilled = 'MaskIcon MaskIconCircleFilled';
|
|
2976
|
+
const MaskIconClose = 'MaskIcon MaskIconClose';
|
|
2977
|
+
const TextEditor = 'TextEditor';
|
|
2978
|
+
const TextEditorLoading = 'TextEditor TextEditor--loading';
|
|
2979
|
+
const EditorContentLoading = 'EditorContent EditorContent--loading';
|
|
2980
|
+
const TextEditorError = 'TextEditor TextEditor--error';
|
|
2981
|
+
const EditorContentError = 'EditorContent EditorContent--error';
|
|
2982
|
+
const Main = 'Main';
|
|
2983
|
+
const EDITOR_GROUPS_CONTAINER = 'editor-groups-container';
|
|
2984
|
+
const TabIcon = 'TabIcon';
|
|
2985
|
+
const TabTitle = 'TabTitle';
|
|
2986
|
+
const Button = 'Button';
|
|
2987
|
+
const ButtonSecondary = 'ButtonSecondary';
|
|
2988
|
+
const EditorContent = 'EditorContent';
|
|
2989
|
+
const EditorTabCloseButton = 'EditorTabCloseButton';
|
|
2990
|
+
const IconButton = 'IconButton';
|
|
2991
|
+
const MainTab = 'MainTab';
|
|
2453
2992
|
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
|
-
};
|
|
2993
|
+
const MainTabModified = 'MainTabModified';
|
|
2994
|
+
const MainTabSelected = 'MainTabSelected';
|
|
2559
2995
|
|
|
2560
2996
|
const newFile = async state => {
|
|
2561
2997
|
object(state);
|
|
@@ -2608,8 +3044,8 @@ const newFile = async state => {
|
|
|
2608
3044
|
getActiveTabId(newState);
|
|
2609
3045
|
|
|
2610
3046
|
// Create a new empty tab
|
|
2611
|
-
const tabId = create
|
|
2612
|
-
const editorUid = create
|
|
3047
|
+
const tabId = create();
|
|
3048
|
+
const editorUid = create();
|
|
2613
3049
|
const newTab = {
|
|
2614
3050
|
editorType: 'text',
|
|
2615
3051
|
editorUid,
|
|
@@ -2745,101 +3181,14 @@ const show2 = async (uid, menuId, x, y, args) => {
|
|
|
2745
3181
|
|
|
2746
3182
|
const handleTabContextMenu = async (state, button, x, y) => {
|
|
2747
3183
|
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]);
|
|
2760
|
-
};
|
|
2761
|
-
|
|
2762
|
-
const getBasename = uri => {
|
|
2763
|
-
const lastSlashIndex = uri.lastIndexOf('/');
|
|
2764
|
-
if (lastSlashIndex === -1) {
|
|
2765
|
-
return uri;
|
|
2766
|
-
}
|
|
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
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
return missingTabs;
|
|
2777
|
-
};
|
|
2778
|
-
const tabToIconRequest = tab => {
|
|
2779
|
-
const uri = tab.uri || '';
|
|
2780
|
-
return {
|
|
2781
|
-
name: getBasename(uri),
|
|
2782
|
-
path: uri,
|
|
2783
|
-
type: 0 // file type
|
|
2784
|
-
};
|
|
2785
|
-
};
|
|
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
|
-
|
|
2795
|
-
const getSimpleIconRequestType = direntType => {
|
|
2796
|
-
if (direntType === Directory || direntType === DirectoryExpanded) {
|
|
2797
|
-
return 2;
|
|
2798
|
-
}
|
|
2799
|
-
return 1;
|
|
2800
|
-
};
|
|
2801
|
-
|
|
2802
|
-
const toSimpleIconRequest = request => {
|
|
2803
|
-
return {
|
|
2804
|
-
name: request.name,
|
|
2805
|
-
type: getSimpleIconRequestType(request.type)
|
|
2806
|
-
};
|
|
2807
|
-
};
|
|
2808
|
-
|
|
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;
|
|
2816
|
-
};
|
|
2817
|
-
|
|
2818
|
-
const updateIconCache = (iconCache, missingRequests, newIcons) => {
|
|
2819
|
-
if (missingRequests.length === 0) {
|
|
2820
|
-
return iconCache;
|
|
2821
|
-
}
|
|
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;
|
|
2829
|
-
}
|
|
2830
|
-
return newFileIconCache;
|
|
2831
|
-
};
|
|
2832
|
-
|
|
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);
|
|
2839
|
-
return {
|
|
2840
|
-
icons,
|
|
2841
|
-
newFileIconCache
|
|
2842
|
-
};
|
|
3184
|
+
number(y);
|
|
3185
|
+
const {
|
|
3186
|
+
uid
|
|
3187
|
+
} = state;
|
|
3188
|
+
await show2(uid, Tab, x, y, {
|
|
3189
|
+
menuId: Tab
|
|
3190
|
+
});
|
|
3191
|
+
return state;
|
|
2843
3192
|
};
|
|
2844
3193
|
|
|
2845
3194
|
const getAllTabs = layout => {
|
|
@@ -3017,7 +3366,7 @@ const createViewlets = async (layout, viewletModuleIds, bounds) => {
|
|
|
3017
3366
|
for (const group of layout.groups) {
|
|
3018
3367
|
const activeTab = group.tabs.find(tab => tab.id === group.activeTabId);
|
|
3019
3368
|
if (activeTab && viewletModuleIds[activeTab.id]) {
|
|
3020
|
-
const editorUid = activeTab.editorUid === -1 ? create
|
|
3369
|
+
const editorUid = activeTab.editorUid === -1 ? create() : activeTab.editorUid;
|
|
3021
3370
|
editorUids[activeTab.id] = editorUid;
|
|
3022
3371
|
await createViewlet(viewletModuleIds[activeTab.id], editorUid, activeTab.id, bounds, activeTab.uri);
|
|
3023
3372
|
}
|
|
@@ -3025,17 +3374,6 @@ const createViewlets = async (layout, viewletModuleIds, bounds) => {
|
|
|
3025
3374
|
return editorUids;
|
|
3026
3375
|
};
|
|
3027
3376
|
|
|
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
3377
|
const getViewletModuleIds = async layout => {
|
|
3040
3378
|
const viewletModuleIds = {};
|
|
3041
3379
|
for (const group of layout.groups) {
|
|
@@ -3289,273 +3627,6 @@ const getMenuEntries = async (state, props) => {
|
|
|
3289
3627
|
}
|
|
3290
3628
|
};
|
|
3291
3629
|
|
|
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
|
-
});
|
|
3404
|
-
return {
|
|
3405
|
-
...state,
|
|
3406
|
-
layout: {
|
|
3407
|
-
...layout,
|
|
3408
|
-
groups: updatedGroups
|
|
3409
|
-
}
|
|
3410
|
-
};
|
|
3411
|
-
};
|
|
3412
|
-
|
|
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;
|
|
3557
|
-
};
|
|
3558
|
-
|
|
3559
3630
|
const refresh = state => {
|
|
3560
3631
|
return {
|
|
3561
3632
|
...state
|
|
@@ -3858,41 +3929,51 @@ const diffTree = (oldNodes, newNodes) => {
|
|
|
3858
3929
|
return removeTrailingNavigationPatches(patches);
|
|
3859
3930
|
};
|
|
3860
3931
|
|
|
3861
|
-
const CSS_CLASSES = {
|
|
3862
|
-
EDITOR_GROUPS_CONTAINER: 'editor-groups-container'};
|
|
3863
|
-
|
|
3864
3932
|
const renderContent = content => {
|
|
3865
3933
|
return [{
|
|
3866
3934
|
childCount: 1,
|
|
3867
|
-
className:
|
|
3935
|
+
className: TextEditor,
|
|
3868
3936
|
type: Div
|
|
3869
3937
|
}, {
|
|
3870
3938
|
childCount: 1,
|
|
3871
|
-
className:
|
|
3939
|
+
className: EditorContent,
|
|
3872
3940
|
type: Pre
|
|
3873
3941
|
}, text(content)];
|
|
3874
3942
|
};
|
|
3875
3943
|
|
|
3944
|
+
const HandleClickAction = 10;
|
|
3945
|
+
const HandleClick = 11;
|
|
3946
|
+
const HandleClickClose = 12;
|
|
3947
|
+
const HandleClickTab = 13;
|
|
3948
|
+
const HandleTabContextMenu = 14;
|
|
3949
|
+
const HandleHeaderDoubleClick = 15;
|
|
3950
|
+
|
|
3876
3951
|
const renderError = errorMessage => {
|
|
3877
3952
|
return [{
|
|
3878
|
-
childCount:
|
|
3879
|
-
className:
|
|
3953
|
+
childCount: 2,
|
|
3954
|
+
className: TextEditorError,
|
|
3880
3955
|
type: Div
|
|
3881
3956
|
}, {
|
|
3882
|
-
childCount:
|
|
3883
|
-
className:
|
|
3957
|
+
childCount: 2,
|
|
3958
|
+
className: EditorContentError,
|
|
3884
3959
|
type: Div
|
|
3885
|
-
}, text(`Error: ${errorMessage}`)
|
|
3960
|
+
}, text(`Error: ${errorMessage}`), {
|
|
3961
|
+
childCount: 1,
|
|
3962
|
+
className: `${Button} ${ButtonSecondary}`,
|
|
3963
|
+
'data-action': 'retry-open',
|
|
3964
|
+
onClick: HandleClickAction,
|
|
3965
|
+
type: Button$2
|
|
3966
|
+
}, text('Retry')];
|
|
3886
3967
|
};
|
|
3887
3968
|
|
|
3888
3969
|
const renderLoading = () => {
|
|
3889
3970
|
return [{
|
|
3890
3971
|
childCount: 1,
|
|
3891
|
-
className:
|
|
3972
|
+
className: TextEditorLoading,
|
|
3892
3973
|
type: Div
|
|
3893
3974
|
}, {
|
|
3894
3975
|
childCount: 1,
|
|
3895
|
-
className:
|
|
3976
|
+
className: EditorContentLoading,
|
|
3896
3977
|
type: Div
|
|
3897
3978
|
}, text('Loading...')];
|
|
3898
3979
|
};
|
|
@@ -3933,50 +4014,43 @@ const renderEditor = tab => {
|
|
|
3933
4014
|
return renderContent('');
|
|
3934
4015
|
};
|
|
3935
4016
|
|
|
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
4017
|
const renderTabActions = (isDirty, tabIndex, groupIndex) => {
|
|
3944
4018
|
if (isDirty) {
|
|
3945
4019
|
return [{
|
|
3946
4020
|
childCount: 1,
|
|
3947
|
-
className:
|
|
4021
|
+
className: EditorTabCloseButton,
|
|
3948
4022
|
'data-groupIndex': groupIndex,
|
|
3949
4023
|
'data-index': tabIndex,
|
|
3950
4024
|
type: Div
|
|
3951
4025
|
}, {
|
|
3952
4026
|
childCount: 0,
|
|
3953
|
-
className:
|
|
4027
|
+
className: MaskIconCircleFilled,
|
|
3954
4028
|
type: Div
|
|
3955
4029
|
}];
|
|
3956
4030
|
}
|
|
3957
4031
|
return [{
|
|
3958
4032
|
'aria-label': 'Close',
|
|
3959
4033
|
childCount: 1,
|
|
3960
|
-
className:
|
|
4034
|
+
className: EditorTabCloseButton,
|
|
3961
4035
|
'data-groupIndex': groupIndex,
|
|
3962
4036
|
'data-index': tabIndex,
|
|
3963
4037
|
onClick: HandleClickClose,
|
|
3964
|
-
type: Button$
|
|
4038
|
+
type: Button$2
|
|
3965
4039
|
}, {
|
|
3966
4040
|
childCount: 0,
|
|
3967
|
-
className:
|
|
4041
|
+
className: MaskIconClose,
|
|
3968
4042
|
type: Div
|
|
3969
4043
|
}];
|
|
3970
4044
|
};
|
|
3971
4045
|
|
|
3972
4046
|
const renderTab = (tab, isActive, tabIndex, groupIndex) => {
|
|
3973
4047
|
const closeButtonNodes = renderTabActions(tab.isDirty, tabIndex, groupIndex);
|
|
3974
|
-
let className =
|
|
4048
|
+
let className = MainTab;
|
|
3975
4049
|
if (isActive) {
|
|
3976
|
-
className += ' MainTabSelected
|
|
4050
|
+
className += ' ' + MainTabSelected;
|
|
3977
4051
|
}
|
|
3978
4052
|
if (tab.isDirty) {
|
|
3979
|
-
className += ' MainTabModified
|
|
4053
|
+
className += ' ' + MainTabModified;
|
|
3980
4054
|
}
|
|
3981
4055
|
return [{
|
|
3982
4056
|
'aria-selected': isActive,
|
|
@@ -3991,13 +4065,13 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
|
|
|
3991
4065
|
type: Div
|
|
3992
4066
|
}, {
|
|
3993
4067
|
childCount: 0,
|
|
3994
|
-
className:
|
|
3995
|
-
role:
|
|
4068
|
+
className: TabIcon,
|
|
4069
|
+
role: None$1,
|
|
3996
4070
|
src: tab.icon,
|
|
3997
4071
|
type: Img
|
|
3998
4072
|
}, {
|
|
3999
4073
|
childCount: 1,
|
|
4000
|
-
className:
|
|
4074
|
+
className: TabTitle,
|
|
4001
4075
|
type: Span
|
|
4002
4076
|
}, text(tab.title), ...closeButtonNodes];
|
|
4003
4077
|
};
|
|
@@ -4005,7 +4079,7 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
|
|
|
4005
4079
|
const getTabsVirtualDom = (group, groupIndex, tabsChildCount) => {
|
|
4006
4080
|
return [{
|
|
4007
4081
|
childCount: tabsChildCount,
|
|
4008
|
-
className:
|
|
4082
|
+
className: MainTabs,
|
|
4009
4083
|
role: 'tablist',
|
|
4010
4084
|
type: Div
|
|
4011
4085
|
}, ...group.tabs.flatMap((tab, tabIndex) => renderTab(tab, tab.id === group.activeTabId, tabIndex, groupIndex))];
|
|
@@ -4029,34 +4103,34 @@ const renderEditorGroupActions = (group, groupIndex, splitButtonEnabled) => {
|
|
|
4029
4103
|
buttons.push({
|
|
4030
4104
|
ariaLabel: 'Preview',
|
|
4031
4105
|
childCount: 1,
|
|
4032
|
-
className:
|
|
4106
|
+
className: IconButton,
|
|
4033
4107
|
'data-action': 'toggle-preview',
|
|
4034
4108
|
'data-groupId': String(group.id),
|
|
4035
4109
|
name: 'toggle-preview',
|
|
4036
4110
|
onClick: HandleClickAction,
|
|
4037
4111
|
title: togglePreview(),
|
|
4038
|
-
type: Button$
|
|
4112
|
+
type: Button$2
|
|
4039
4113
|
}, {
|
|
4040
4114
|
childCount: 0,
|
|
4041
|
-
className:
|
|
4115
|
+
className: MaskIconPreview,
|
|
4042
4116
|
type: Div
|
|
4043
4117
|
});
|
|
4044
4118
|
}
|
|
4045
4119
|
if (splitButtonEnabled) {
|
|
4046
4120
|
buttons.push({
|
|
4047
4121
|
childCount: 1,
|
|
4048
|
-
className:
|
|
4122
|
+
className: EditorGroupActionButton + ' ' + SplitEditorGroupButton,
|
|
4049
4123
|
'data-action': 'split-right',
|
|
4050
4124
|
'data-groupId': String(group.id),
|
|
4051
4125
|
onClick: HandleClickAction,
|
|
4052
4126
|
title: splitEditorGroup(),
|
|
4053
|
-
type: Button$
|
|
4127
|
+
type: Button$2
|
|
4054
4128
|
}, text('split'));
|
|
4055
4129
|
}
|
|
4056
4130
|
return [{
|
|
4057
4131
|
childCount: buttons.length / 2,
|
|
4058
4132
|
// Each button has 2 nodes (button + text)
|
|
4059
|
-
className:
|
|
4133
|
+
className: EditorGroupActions,
|
|
4060
4134
|
role: 'toolbar',
|
|
4061
4135
|
type: Div
|
|
4062
4136
|
}, ...buttons];
|
|
@@ -4068,9 +4142,9 @@ const renderEditorGroupHeader = (group, groupIndex, splitButtonEnabled) => {
|
|
|
4068
4142
|
const hasActions = actions.length > 0;
|
|
4069
4143
|
return [{
|
|
4070
4144
|
childCount: hasActions ? 2 : 1,
|
|
4071
|
-
className:
|
|
4145
|
+
className: EditorGroupHeader,
|
|
4072
4146
|
onDblClick: HandleHeaderDoubleClick,
|
|
4073
|
-
role:
|
|
4147
|
+
role: None$1,
|
|
4074
4148
|
type: Div
|
|
4075
4149
|
}, ...getTabsVirtualDom(group, groupIndex, tabsChildCount), ...actions];
|
|
4076
4150
|
};
|
|
@@ -4079,26 +4153,42 @@ const renderEditorGroup = (group, groupIndex, splitButtonEnabled = false) => {
|
|
|
4079
4153
|
const activeTab = group.tabs.find(tab => tab.id === group.activeTabId);
|
|
4080
4154
|
return [{
|
|
4081
4155
|
childCount: 2,
|
|
4082
|
-
className:
|
|
4156
|
+
className: EditorGroup,
|
|
4083
4157
|
type: Div
|
|
4084
4158
|
}, ...renderEditorGroupHeader(group, groupIndex, splitButtonEnabled), {
|
|
4085
4159
|
childCount: 1,
|
|
4086
|
-
className:
|
|
4160
|
+
className: EditorContainer,
|
|
4087
4161
|
type: Div
|
|
4088
4162
|
}, ...renderEditor(activeTab)];
|
|
4089
4163
|
};
|
|
4090
4164
|
|
|
4165
|
+
const renderSash = direction => {
|
|
4166
|
+
return {
|
|
4167
|
+
childCount: 0,
|
|
4168
|
+
className: direction === 'vertical' ? 'SashVertical' : 'SashHorizontal',
|
|
4169
|
+
type: Div
|
|
4170
|
+
};
|
|
4171
|
+
};
|
|
4172
|
+
|
|
4091
4173
|
const getMainAreaVirtualDom = (layout, splitButtonEnabled = false) => {
|
|
4174
|
+
const children = [];
|
|
4175
|
+
for (let i = 0; i < layout.groups.length; i++) {
|
|
4176
|
+
if (i > 0) {
|
|
4177
|
+
// Insert sash between groups
|
|
4178
|
+
children.push(renderSash(layout.direction));
|
|
4179
|
+
}
|
|
4180
|
+
children.push(...renderEditorGroup(layout.groups[i], i, splitButtonEnabled));
|
|
4181
|
+
}
|
|
4092
4182
|
return [{
|
|
4093
4183
|
childCount: 1,
|
|
4094
|
-
className:
|
|
4184
|
+
className: Main,
|
|
4095
4185
|
type: Div
|
|
4096
4186
|
}, {
|
|
4097
|
-
childCount:
|
|
4098
|
-
className:
|
|
4099
|
-
role:
|
|
4187
|
+
childCount: children.length,
|
|
4188
|
+
className: EDITOR_GROUPS_CONTAINER,
|
|
4189
|
+
role: None$1,
|
|
4100
4190
|
type: Div
|
|
4101
|
-
}, ...
|
|
4191
|
+
}, ...children];
|
|
4102
4192
|
};
|
|
4103
4193
|
|
|
4104
4194
|
const renderItems = (oldState, newState) => {
|
|
@@ -4167,14 +4257,14 @@ const renderEventListeners = () => {
|
|
|
4167
4257
|
params: ['handleClickTab', 'event.target.dataset.groupIndex', 'event.target.dataset.index']
|
|
4168
4258
|
}, {
|
|
4169
4259
|
name: HandleTabContextMenu,
|
|
4170
|
-
params: ['handleTabContextMenu', Button, ClientX, ClientY],
|
|
4260
|
+
params: ['handleTabContextMenu', Button$1, ClientX, ClientY],
|
|
4171
4261
|
preventDefault: true
|
|
4172
4262
|
}, {
|
|
4173
4263
|
name: HandleClickAction,
|
|
4174
4264
|
params: ['handleClickAction', TargetName, 'event.target.dataset.groupId']
|
|
4175
4265
|
}, {
|
|
4176
4266
|
name: HandleHeaderDoubleClick,
|
|
4177
|
-
params: ['handleHeaderDoubleClick',
|
|
4267
|
+
params: ['handleHeaderDoubleClick', EventTargetClassName, 'event.target.dataset.groupId']
|
|
4178
4268
|
}];
|
|
4179
4269
|
};
|
|
4180
4270
|
|
|
@@ -4239,7 +4329,7 @@ const splitDown = (state, groupId) => {
|
|
|
4239
4329
|
|
|
4240
4330
|
// If there are no groups, create an initial empty group first
|
|
4241
4331
|
if (groups.length === 0) {
|
|
4242
|
-
const initialGroupId = create
|
|
4332
|
+
const initialGroupId = create();
|
|
4243
4333
|
const initialGroup = {
|
|
4244
4334
|
activeTabId: undefined,
|
|
4245
4335
|
focused: true,
|
|
@@ -4276,7 +4366,7 @@ const splitRight = (state, groupId) => {
|
|
|
4276
4366
|
|
|
4277
4367
|
// If there are no groups, create an initial empty group first
|
|
4278
4368
|
if (groups.length === 0) {
|
|
4279
|
-
const initialGroupId = create
|
|
4369
|
+
const initialGroupId = create();
|
|
4280
4370
|
const initialGroup = {
|
|
4281
4371
|
activeTabId: undefined,
|
|
4282
4372
|
focused: true,
|
|
@@ -4308,7 +4398,7 @@ const commandMap = {
|
|
|
4308
4398
|
'MainArea.closeTabsRight': wrapCommand(closeTabsRight),
|
|
4309
4399
|
'MainArea.copyPath': wrapCommand(copyPath$1),
|
|
4310
4400
|
'MainArea.copyRelativePath': wrapCommand(copyRelativePath$1),
|
|
4311
|
-
'MainArea.create': create,
|
|
4401
|
+
'MainArea.create': create$1,
|
|
4312
4402
|
'MainArea.diff2': diff2,
|
|
4313
4403
|
'MainArea.focusNext': wrapCommand(focusNextTab),
|
|
4314
4404
|
'MainArea.focusNextTab': wrapCommand(focusNextTab),
|