@lvce-editor/main-area-worker 1.13.0 → 4.0.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 +744 -349
- package/package.json +2 -2
|
@@ -6,42 +6,68 @@ const create$7 = () => {
|
|
|
6
6
|
const states = Object.create(null);
|
|
7
7
|
const commandMapRef = {};
|
|
8
8
|
return {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
clear() {
|
|
10
|
+
for (const key of Object.keys(states)) {
|
|
11
|
+
delete states[key];
|
|
12
|
+
}
|
|
11
13
|
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
14
|
+
diff(uid, modules, numbers) {
|
|
15
|
+
const {
|
|
16
|
+
newState,
|
|
17
|
+
oldState
|
|
18
|
+
} = states[uid];
|
|
19
|
+
const diffResult = [];
|
|
20
|
+
for (let i = 0; i < modules.length; i++) {
|
|
21
|
+
const fn = modules[i];
|
|
22
|
+
if (!fn(oldState, newState)) {
|
|
23
|
+
diffResult.push(numbers[i]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return diffResult;
|
|
17
27
|
},
|
|
18
28
|
dispose(uid) {
|
|
19
29
|
delete states[uid];
|
|
20
30
|
},
|
|
31
|
+
get(uid) {
|
|
32
|
+
return states[uid];
|
|
33
|
+
},
|
|
34
|
+
getCommandIds() {
|
|
35
|
+
const keys = Object.keys(commandMapRef);
|
|
36
|
+
const ids = keys.map(toCommandId);
|
|
37
|
+
return ids;
|
|
38
|
+
},
|
|
21
39
|
getKeys() {
|
|
22
40
|
return Object.keys(states).map(key => {
|
|
23
41
|
return Number.parseInt(key);
|
|
24
42
|
});
|
|
25
43
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
registerCommands(commandMap) {
|
|
45
|
+
Object.assign(commandMapRef, commandMap);
|
|
46
|
+
},
|
|
47
|
+
set(uid, oldState, newState) {
|
|
48
|
+
states[uid] = {
|
|
49
|
+
newState,
|
|
50
|
+
oldState
|
|
51
|
+
};
|
|
30
52
|
},
|
|
31
53
|
wrapCommand(fn) {
|
|
32
54
|
const wrapped = async (uid, ...args) => {
|
|
33
55
|
const {
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
newState,
|
|
57
|
+
oldState
|
|
36
58
|
} = states[uid];
|
|
37
59
|
const newerState = await fn(newState, ...args);
|
|
38
60
|
if (oldState === newerState || newState === newerState) {
|
|
39
61
|
return;
|
|
40
62
|
}
|
|
41
|
-
const
|
|
63
|
+
const latestOld = states[uid];
|
|
64
|
+
const latestNew = {
|
|
65
|
+
...latestOld.newState,
|
|
66
|
+
...newerState
|
|
67
|
+
};
|
|
42
68
|
states[uid] = {
|
|
43
|
-
|
|
44
|
-
|
|
69
|
+
newState: latestNew,
|
|
70
|
+
oldState: latestOld.oldState
|
|
45
71
|
};
|
|
46
72
|
};
|
|
47
73
|
return wrapped;
|
|
@@ -54,28 +80,6 @@ const create$7 = () => {
|
|
|
54
80
|
return fn(newState, ...args);
|
|
55
81
|
};
|
|
56
82
|
return wrapped;
|
|
57
|
-
},
|
|
58
|
-
diff(uid, modules, numbers) {
|
|
59
|
-
const {
|
|
60
|
-
oldState,
|
|
61
|
-
newState
|
|
62
|
-
} = states[uid];
|
|
63
|
-
const diffResult = [];
|
|
64
|
-
for (let i = 0; i < modules.length; i++) {
|
|
65
|
-
const fn = modules[i];
|
|
66
|
-
if (!fn(oldState, newState)) {
|
|
67
|
-
diffResult.push(numbers[i]);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return diffResult;
|
|
71
|
-
},
|
|
72
|
-
getCommandIds() {
|
|
73
|
-
const keys = Object.keys(commandMapRef);
|
|
74
|
-
const ids = keys.map(toCommandId);
|
|
75
|
-
return ids;
|
|
76
|
-
},
|
|
77
|
-
registerCommands(commandMap) {
|
|
78
|
-
Object.assign(commandMapRef, commandMap);
|
|
79
83
|
}
|
|
80
84
|
};
|
|
81
85
|
};
|
|
@@ -106,13 +110,17 @@ const {
|
|
|
106
110
|
const create$6 = (uid, uri, x, y, width, height, platform, assetDir) => {
|
|
107
111
|
const state = {
|
|
108
112
|
assetDir,
|
|
113
|
+
height,
|
|
109
114
|
layout: {
|
|
110
115
|
activeGroupId: undefined,
|
|
111
116
|
direction: 'horizontal',
|
|
112
117
|
groups: []
|
|
113
118
|
},
|
|
114
119
|
platform,
|
|
115
|
-
uid
|
|
120
|
+
uid,
|
|
121
|
+
width,
|
|
122
|
+
x,
|
|
123
|
+
y
|
|
116
124
|
};
|
|
117
125
|
set$3(uid, state, state);
|
|
118
126
|
};
|
|
@@ -179,95 +187,6 @@ const handleClick = async (state, name) => {
|
|
|
179
187
|
return state;
|
|
180
188
|
};
|
|
181
189
|
|
|
182
|
-
const closeTab = (state, groupId, tabId) => {
|
|
183
|
-
const {
|
|
184
|
-
layout
|
|
185
|
-
} = state;
|
|
186
|
-
const {
|
|
187
|
-
activeGroupId,
|
|
188
|
-
groups
|
|
189
|
-
} = layout;
|
|
190
|
-
const newGroups = groups.map(group => {
|
|
191
|
-
if (group.id === groupId) {
|
|
192
|
-
const newTabs = group.tabs.filter(tab => tab.id !== tabId);
|
|
193
|
-
let newActiveTabId = group.activeTabId;
|
|
194
|
-
if (group.activeTabId === tabId) {
|
|
195
|
-
const tabIndex = group.tabs.findIndex(tab => tab.id === tabId);
|
|
196
|
-
if (newTabs.length > 0) {
|
|
197
|
-
newActiveTabId = newTabs[Math.min(tabIndex, newTabs.length - 1)].id;
|
|
198
|
-
} else {
|
|
199
|
-
newActiveTabId = undefined;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
...group,
|
|
204
|
-
activeTabId: newActiveTabId,
|
|
205
|
-
tabs: newTabs
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
return group;
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// If the group has no tabs left and there are other groups, remove the group
|
|
212
|
-
const groupWithNoTabs = newGroups.find(group => group.id === groupId && group.tabs.length === 0);
|
|
213
|
-
if (groupWithNoTabs && newGroups.length > 1) {
|
|
214
|
-
const remainingGroups = newGroups.filter(group => group.id !== groupId);
|
|
215
|
-
const redistributedGroups = remainingGroups.map(group => ({
|
|
216
|
-
...group,
|
|
217
|
-
size: Math.round(100 / remainingGroups.length)
|
|
218
|
-
}));
|
|
219
|
-
const newActiveGroupId = activeGroupId === groupId ? remainingGroups[0]?.id : activeGroupId;
|
|
220
|
-
return {
|
|
221
|
-
...state,
|
|
222
|
-
layout: {
|
|
223
|
-
...layout,
|
|
224
|
-
activeGroupId: newActiveGroupId,
|
|
225
|
-
groups: redistributedGroups
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
...state,
|
|
231
|
-
layout: {
|
|
232
|
-
...layout,
|
|
233
|
-
groups: newGroups
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
const handleClickCloseTab = (state, rawGroupIndex, rawIndex) => {
|
|
239
|
-
if (!rawGroupIndex || !rawIndex) {
|
|
240
|
-
return state;
|
|
241
|
-
}
|
|
242
|
-
const groupIndex = Number.parseInt(rawGroupIndex);
|
|
243
|
-
const index = Number.parseInt(rawIndex);
|
|
244
|
-
const {
|
|
245
|
-
layout
|
|
246
|
-
} = state;
|
|
247
|
-
const {
|
|
248
|
-
groups
|
|
249
|
-
} = layout;
|
|
250
|
-
|
|
251
|
-
// Validate indexes
|
|
252
|
-
if (groupIndex < 0 || groupIndex >= groups.length) {
|
|
253
|
-
return state;
|
|
254
|
-
}
|
|
255
|
-
const group = groups[groupIndex];
|
|
256
|
-
if (index < 0 || index >= group.tabs.length) {
|
|
257
|
-
return state;
|
|
258
|
-
}
|
|
259
|
-
const tab = group.tabs[index];
|
|
260
|
-
const groupId = group.id;
|
|
261
|
-
const tabId = tab.id;
|
|
262
|
-
return closeTab(state, groupId, tabId);
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// Counter for request IDs to handle race conditions
|
|
266
|
-
let requestIdCounter = 0;
|
|
267
|
-
const getNextRequestId = () => {
|
|
268
|
-
return ++requestIdCounter;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
190
|
const normalizeLine = line => {
|
|
272
191
|
if (line.startsWith('Error: ')) {
|
|
273
192
|
return line.slice('Error: '.length);
|
|
@@ -726,7 +645,7 @@ const getFirstEvent = (eventEmitter, eventMap) => {
|
|
|
726
645
|
return promise;
|
|
727
646
|
};
|
|
728
647
|
const Message$1 = 3;
|
|
729
|
-
const create$5 = async ({
|
|
648
|
+
const create$5$1 = async ({
|
|
730
649
|
isMessagePortOpen,
|
|
731
650
|
messagePort
|
|
732
651
|
}) => {
|
|
@@ -777,7 +696,7 @@ const wrap$5 = messagePort => {
|
|
|
777
696
|
};
|
|
778
697
|
const IpcParentWithMessagePort$1 = {
|
|
779
698
|
__proto__: null,
|
|
780
|
-
create: create$5,
|
|
699
|
+
create: create$5$1,
|
|
781
700
|
signal: signal$1,
|
|
782
701
|
wrap: wrap$5
|
|
783
702
|
};
|
|
@@ -983,7 +902,7 @@ const getErrorProperty = (error, prettyError) => {
|
|
|
983
902
|
}
|
|
984
903
|
};
|
|
985
904
|
};
|
|
986
|
-
const create$1$
|
|
905
|
+
const create$1$1 = (id, error) => {
|
|
987
906
|
return {
|
|
988
907
|
jsonrpc: Two$1,
|
|
989
908
|
id,
|
|
@@ -994,9 +913,9 @@ const getErrorResponse = (id, error, preparePrettyError, logError) => {
|
|
|
994
913
|
const prettyError = preparePrettyError(error);
|
|
995
914
|
logError(error, prettyError);
|
|
996
915
|
const errorProperty = getErrorProperty(error, prettyError);
|
|
997
|
-
return create$1$
|
|
916
|
+
return create$1$1(id, errorProperty);
|
|
998
917
|
};
|
|
999
|
-
const create$
|
|
918
|
+
const create$4 = (message, result) => {
|
|
1000
919
|
return {
|
|
1001
920
|
jsonrpc: Two$1,
|
|
1002
921
|
id: message.id,
|
|
@@ -1005,7 +924,7 @@ const create$3 = (message, result) => {
|
|
|
1005
924
|
};
|
|
1006
925
|
const getSuccessResponse = (message, result) => {
|
|
1007
926
|
const resultProperty = result ?? null;
|
|
1008
|
-
return create$
|
|
927
|
+
return create$4(message, resultProperty);
|
|
1009
928
|
};
|
|
1010
929
|
const getErrorResponseSimple = (id, error) => {
|
|
1011
930
|
return {
|
|
@@ -1119,14 +1038,14 @@ const execute = (command, ...args) => {
|
|
|
1119
1038
|
};
|
|
1120
1039
|
|
|
1121
1040
|
const Two = '2.0';
|
|
1122
|
-
const create$
|
|
1041
|
+
const create$t = (method, params) => {
|
|
1123
1042
|
return {
|
|
1124
1043
|
jsonrpc: Two,
|
|
1125
1044
|
method,
|
|
1126
1045
|
params
|
|
1127
1046
|
};
|
|
1128
1047
|
};
|
|
1129
|
-
const create$
|
|
1048
|
+
const create$s = (id, method, params) => {
|
|
1130
1049
|
const message = {
|
|
1131
1050
|
id,
|
|
1132
1051
|
jsonrpc: Two,
|
|
@@ -1136,14 +1055,14 @@ const create$r = (id, method, params) => {
|
|
|
1136
1055
|
return message;
|
|
1137
1056
|
};
|
|
1138
1057
|
let id$1 = 0;
|
|
1139
|
-
const create$
|
|
1058
|
+
const create$r = () => {
|
|
1140
1059
|
return ++id$1;
|
|
1141
1060
|
};
|
|
1142
1061
|
|
|
1143
1062
|
/* eslint-disable n/no-unsupported-features/es-syntax */
|
|
1144
1063
|
|
|
1145
1064
|
const registerPromise = map => {
|
|
1146
|
-
const id = create$
|
|
1065
|
+
const id = create$r();
|
|
1147
1066
|
const {
|
|
1148
1067
|
promise,
|
|
1149
1068
|
resolve
|
|
@@ -1161,7 +1080,7 @@ const invokeHelper = async (callbacks, ipc, method, params, useSendAndTransfer)
|
|
|
1161
1080
|
id,
|
|
1162
1081
|
promise
|
|
1163
1082
|
} = registerPromise(callbacks);
|
|
1164
|
-
const message = create$
|
|
1083
|
+
const message = create$s(id, method, params);
|
|
1165
1084
|
if (useSendAndTransfer && ipc.sendAndTransfer) {
|
|
1166
1085
|
ipc.sendAndTransfer(message);
|
|
1167
1086
|
} else {
|
|
@@ -1197,7 +1116,7 @@ const createRpc = ipc => {
|
|
|
1197
1116
|
* @deprecated
|
|
1198
1117
|
*/
|
|
1199
1118
|
send(method, ...params) {
|
|
1200
|
-
const message = create$
|
|
1119
|
+
const message = create$t(method, params);
|
|
1201
1120
|
ipc.send(message);
|
|
1202
1121
|
}
|
|
1203
1122
|
};
|
|
@@ -1233,7 +1152,7 @@ const listen$1 = async (module, options) => {
|
|
|
1233
1152
|
const ipc = module.wrap(rawIpc);
|
|
1234
1153
|
return ipc;
|
|
1235
1154
|
};
|
|
1236
|
-
const create$
|
|
1155
|
+
const create$5 = async ({
|
|
1237
1156
|
commandMap,
|
|
1238
1157
|
isMessagePortOpen = true,
|
|
1239
1158
|
messagePort
|
|
@@ -1250,7 +1169,7 @@ const create$4 = async ({
|
|
|
1250
1169
|
messagePort.start();
|
|
1251
1170
|
return rpc;
|
|
1252
1171
|
};
|
|
1253
|
-
const create$
|
|
1172
|
+
const create$3 = async ({
|
|
1254
1173
|
commandMap,
|
|
1255
1174
|
isMessagePortOpen,
|
|
1256
1175
|
send
|
|
@@ -1260,7 +1179,7 @@ const create$2 = async ({
|
|
|
1260
1179
|
port2
|
|
1261
1180
|
} = new MessageChannel();
|
|
1262
1181
|
await send(port1);
|
|
1263
|
-
return create$
|
|
1182
|
+
return create$5({
|
|
1264
1183
|
commandMap,
|
|
1265
1184
|
isMessagePortOpen,
|
|
1266
1185
|
messagePort: port2
|
|
@@ -1268,9 +1187,9 @@ const create$2 = async ({
|
|
|
1268
1187
|
};
|
|
1269
1188
|
const TransferMessagePortRpcParent = {
|
|
1270
1189
|
__proto__: null,
|
|
1271
|
-
create: create$
|
|
1190
|
+
create: create$3
|
|
1272
1191
|
};
|
|
1273
|
-
const create$
|
|
1192
|
+
const create$2 = async ({
|
|
1274
1193
|
commandMap
|
|
1275
1194
|
}) => {
|
|
1276
1195
|
// TODO create a commandMap per rpc instance
|
|
@@ -1282,7 +1201,7 @@ const create$1$1 = async ({
|
|
|
1282
1201
|
};
|
|
1283
1202
|
const WebWorkerRpcClient = {
|
|
1284
1203
|
__proto__: null,
|
|
1285
|
-
create: create$
|
|
1204
|
+
create: create$2
|
|
1286
1205
|
};
|
|
1287
1206
|
const createMockRpc = ({
|
|
1288
1207
|
commandMap
|
|
@@ -1374,6 +1293,23 @@ const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
|
|
|
1374
1293
|
await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
|
|
1375
1294
|
};
|
|
1376
1295
|
|
|
1296
|
+
const handleAttach = async command => {
|
|
1297
|
+
// TODO find a better way to append editors
|
|
1298
|
+
const parentNodeSelector = '.editor-groups-container';
|
|
1299
|
+
// @ts-ignore
|
|
1300
|
+
await invoke('Layout.attachViewlet', parentNodeSelector, command.instanceId);
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
let counter = 0;
|
|
1304
|
+
const create = () => {
|
|
1305
|
+
return ++counter;
|
|
1306
|
+
};
|
|
1307
|
+
const setMinId = minId => {
|
|
1308
|
+
if (minId > counter) {
|
|
1309
|
+
counter = minId;
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1377
1313
|
const updateTab = (state, tabId, updates) => {
|
|
1378
1314
|
const {
|
|
1379
1315
|
layout
|
|
@@ -1408,6 +1344,7 @@ const updateTab = (state, tabId, updates) => {
|
|
|
1408
1344
|
}
|
|
1409
1345
|
};
|
|
1410
1346
|
};
|
|
1347
|
+
|
|
1411
1348
|
const findTab = (state, tabId) => {
|
|
1412
1349
|
const {
|
|
1413
1350
|
layout
|
|
@@ -1440,8 +1377,12 @@ const loadTabContentAsync = async (tabId, path, requestId, getLatestState) => {
|
|
|
1440
1377
|
if (!latestTab || latestTab.loadRequestId !== requestId) {
|
|
1441
1378
|
return latestState;
|
|
1442
1379
|
}
|
|
1380
|
+
|
|
1381
|
+
// Assign editorUid if tab doesn't have one yet
|
|
1382
|
+
const editorUid = latestTab.editorUid === -1 ? create() : latestTab.editorUid;
|
|
1443
1383
|
return updateTab(latestState, tabId, {
|
|
1444
1384
|
content,
|
|
1385
|
+
editorUid,
|
|
1445
1386
|
errorMessage: undefined,
|
|
1446
1387
|
loadingState: 'loaded'
|
|
1447
1388
|
});
|
|
@@ -1461,86 +1402,327 @@ const loadTabContentAsync = async (tabId, path, requestId, getLatestState) => {
|
|
|
1461
1402
|
}
|
|
1462
1403
|
};
|
|
1463
1404
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
return
|
|
1472
|
-
} catch {
|
|
1473
|
-
// Silently ignore errors - the tab may have been closed or the component unmounted
|
|
1405
|
+
/**
|
|
1406
|
+
* SAFE: Create viewlet for a tab (no visible side effects)
|
|
1407
|
+
* Can be called eagerly when tab is opened - the viewlet loads in the background.
|
|
1408
|
+
*/
|
|
1409
|
+
const createViewletForTab = (state, tabId, viewletModuleId, bounds) => {
|
|
1410
|
+
const tab = findTab(state, tabId);
|
|
1411
|
+
if (!tab) {
|
|
1412
|
+
return state;
|
|
1474
1413
|
}
|
|
1475
|
-
return state;
|
|
1476
|
-
};
|
|
1477
1414
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
// - Not already loaded or currently loading
|
|
1482
|
-
if (!tab.path) {
|
|
1483
|
-
return false;
|
|
1484
|
-
}
|
|
1485
|
-
if (tab.loadingState === 'loading') {
|
|
1486
|
-
return false;
|
|
1487
|
-
}
|
|
1488
|
-
if (tab.loadingState === 'loaded' && tab.content) {
|
|
1489
|
-
return false;
|
|
1415
|
+
// If tab already has an editorUid or is loading/loaded, don't recreate
|
|
1416
|
+
if (tab.editorUid !== -1 || tab.loadingState === 'loading' || tab.loadingState === 'loaded') {
|
|
1417
|
+
return state;
|
|
1490
1418
|
}
|
|
1491
|
-
|
|
1419
|
+
const editorUid = create();
|
|
1420
|
+
const newState = updateTab(state, tabId, {
|
|
1421
|
+
editorUid
|
|
1422
|
+
});
|
|
1423
|
+
return newState;
|
|
1492
1424
|
};
|
|
1493
|
-
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* Called when switching tabs.
|
|
1428
|
+
* With reference nodes, attachment/detachment is handled automatically by virtual DOM rendering.
|
|
1429
|
+
* No attach/detach commands needed - virtual DOM will render the correct reference at each position.
|
|
1430
|
+
*/
|
|
1431
|
+
const switchViewlet = (state, fromTabId, toTabId) => {
|
|
1432
|
+
// No commands needed - virtual DOM reference nodes handle attachment automatically
|
|
1433
|
+
// Virtual DOM will:
|
|
1434
|
+
// 1. Remove the old reference node from the previous active tab
|
|
1435
|
+
// 2. Add the new reference node for the now-active tab
|
|
1436
|
+
// This achieves the same effect as detach/attach without explicit commands
|
|
1437
|
+
|
|
1438
|
+
return {
|
|
1439
|
+
commands: [],
|
|
1440
|
+
newState: state
|
|
1441
|
+
};
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
const findTabByEditorUid = (state, editorUid) => {
|
|
1494
1445
|
const {
|
|
1495
|
-
layout
|
|
1496
|
-
uid
|
|
1446
|
+
layout
|
|
1497
1447
|
} = state;
|
|
1498
1448
|
const {
|
|
1499
1449
|
groups
|
|
1500
1450
|
} = layout;
|
|
1451
|
+
for (const group of groups) {
|
|
1452
|
+
const tab = group.tabs.find(t => t.editorUid === editorUid);
|
|
1453
|
+
if (tab) {
|
|
1454
|
+
return tab;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return undefined;
|
|
1458
|
+
};
|
|
1501
1459
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1460
|
+
/**
|
|
1461
|
+
* Called when renderer reports viewlet finished creating.
|
|
1462
|
+
* With reference nodes, attachment is handled automatically by virtual DOM rendering.
|
|
1463
|
+
* No commands needed - state update is sufficient.
|
|
1464
|
+
*/
|
|
1465
|
+
const handleViewletReady = (state, editorUid) => {
|
|
1466
|
+
if (editorUid === -1) {
|
|
1467
|
+
throw new Error('Invalid editorUid -1');
|
|
1505
1468
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1469
|
+
// Find the tab by viewletRequestId
|
|
1470
|
+
const tab = findTabByEditorUid(state, editorUid);
|
|
1471
|
+
if (!tab) {
|
|
1472
|
+
// Tab was closed, dispose viewlet
|
|
1508
1473
|
return state;
|
|
1509
1474
|
}
|
|
1510
|
-
const tab = group.tabs[index];
|
|
1511
|
-
const groupId = group.id;
|
|
1512
|
-
const tabId = tab.id;
|
|
1513
1475
|
|
|
1514
|
-
//
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1476
|
+
// Mark viewlet as ready and set instance id
|
|
1477
|
+
// Reference nodes will handle rendering at the correct position automatically
|
|
1478
|
+
const newState = updateTab(state, tab.id, {
|
|
1479
|
+
editorUid,
|
|
1480
|
+
loadingState: 'loaded'
|
|
1481
|
+
});
|
|
1518
1482
|
|
|
1519
|
-
//
|
|
1520
|
-
|
|
1521
|
-
|
|
1483
|
+
// No attach commands needed - virtual DOM reference nodes handle positioning
|
|
1484
|
+
return newState;
|
|
1485
|
+
};
|
|
1522
1486
|
|
|
1523
|
-
|
|
1524
|
-
//
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
return {
|
|
1528
|
-
...g,
|
|
1529
|
-
focused: false
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1487
|
+
const handleCreate = async command => {
|
|
1488
|
+
// Safe to call - no visible side effects
|
|
1489
|
+
// @ts-ignore
|
|
1490
|
+
const instanceId = Math.random(); // TODO try to find a better way to get consistent integer ids (thread safe)
|
|
1532
1491
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1492
|
+
await invoke('Layout.createViewlet', command.viewletModuleId, command.tabId, command.bounds, command.uri, instanceId);
|
|
1493
|
+
|
|
1494
|
+
// After viewlet is created, mark it as ready
|
|
1495
|
+
// Attachment is handled automatically by virtual DOM reference nodes
|
|
1496
|
+
const {
|
|
1497
|
+
newState: state,
|
|
1498
|
+
oldState
|
|
1499
|
+
} = get$2(command.uid);
|
|
1500
|
+
const readyState = handleViewletReady(state, command.editorUid);
|
|
1501
|
+
set$3(command.uid, oldState, readyState);
|
|
1502
|
+
return readyState;
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
const handleDetach = async command => {
|
|
1506
|
+
// Hides viewlet but keeps it alive
|
|
1507
|
+
// @ts-ignore
|
|
1508
|
+
await invoke('Viewlet.detach', command.instanceId);
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
const handleDispose = async command => {
|
|
1512
|
+
// Fully destroys viewlet
|
|
1513
|
+
// @ts-ignore
|
|
1514
|
+
await invoke('Viewlet.dispose', command.instanceId);
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
const handleSetBounds = async command => {
|
|
1518
|
+
// @ts-ignore
|
|
1519
|
+
await invoke('Viewlet.setBounds', command.instanceId, command.bounds);
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
const executeViewletCommands = async commands => {
|
|
1523
|
+
for (const command of commands) {
|
|
1524
|
+
switch (command.type) {
|
|
1525
|
+
case 'attach':
|
|
1526
|
+
await handleAttach(command);
|
|
1527
|
+
break;
|
|
1528
|
+
case 'create':
|
|
1529
|
+
await handleCreate(command);
|
|
1530
|
+
break;
|
|
1531
|
+
case 'detach':
|
|
1532
|
+
await handleDetach(command);
|
|
1533
|
+
break;
|
|
1534
|
+
case 'dispose':
|
|
1535
|
+
await handleDispose(command);
|
|
1536
|
+
break;
|
|
1537
|
+
case 'setBounds':
|
|
1538
|
+
await handleSetBounds(command);
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
const closeTab = (state, groupId, tabId) => {
|
|
1545
|
+
const {
|
|
1546
|
+
layout
|
|
1547
|
+
} = state;
|
|
1548
|
+
const {
|
|
1549
|
+
activeGroupId,
|
|
1550
|
+
groups
|
|
1551
|
+
} = layout;
|
|
1552
|
+
const newGroups = groups.map(group => {
|
|
1553
|
+
if (group.id === groupId) {
|
|
1554
|
+
const newTabs = group.tabs.filter(tab => tab.id !== tabId);
|
|
1555
|
+
let newActiveTabId = group.activeTabId;
|
|
1556
|
+
if (group.activeTabId === tabId) {
|
|
1557
|
+
const tabIndex = group.tabs.findIndex(tab => tab.id === tabId);
|
|
1558
|
+
if (newTabs.length > 0) {
|
|
1559
|
+
newActiveTabId = newTabs[Math.min(tabIndex, newTabs.length - 1)].id;
|
|
1560
|
+
} else {
|
|
1561
|
+
newActiveTabId = undefined;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return {
|
|
1565
|
+
...group,
|
|
1566
|
+
activeTabId: newActiveTabId,
|
|
1567
|
+
tabs: newTabs
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
return group;
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
// If the group has no tabs left and there are other groups, remove the group
|
|
1574
|
+
const groupWithNoTabs = newGroups.find(group => group.id === groupId && group.tabs.length === 0);
|
|
1575
|
+
if (groupWithNoTabs && newGroups.length > 1) {
|
|
1576
|
+
const remainingGroups = newGroups.filter(group => group.id !== groupId);
|
|
1577
|
+
const redistributedGroups = remainingGroups.map(group => ({
|
|
1578
|
+
...group,
|
|
1579
|
+
size: Math.round(100 / remainingGroups.length)
|
|
1580
|
+
}));
|
|
1581
|
+
const newActiveGroupId = activeGroupId === groupId ? remainingGroups[0]?.id : activeGroupId;
|
|
1582
|
+
return {
|
|
1583
|
+
...state,
|
|
1584
|
+
layout: {
|
|
1585
|
+
...layout,
|
|
1586
|
+
activeGroupId: newActiveGroupId,
|
|
1587
|
+
groups: redistributedGroups
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
return {
|
|
1592
|
+
...state,
|
|
1593
|
+
layout: {
|
|
1594
|
+
...layout,
|
|
1595
|
+
groups: newGroups
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
const handleClickCloseTab = (state, rawGroupIndex, rawIndex) => {
|
|
1601
|
+
if (!rawGroupIndex || !rawIndex) {
|
|
1602
|
+
return state;
|
|
1603
|
+
}
|
|
1604
|
+
const groupIndex = Number.parseInt(rawGroupIndex);
|
|
1605
|
+
const index = Number.parseInt(rawIndex);
|
|
1606
|
+
const {
|
|
1607
|
+
layout
|
|
1608
|
+
} = state;
|
|
1609
|
+
const {
|
|
1610
|
+
groups
|
|
1611
|
+
} = layout;
|
|
1612
|
+
|
|
1613
|
+
// Validate indexes
|
|
1614
|
+
if (groupIndex < 0 || groupIndex >= groups.length) {
|
|
1615
|
+
return state;
|
|
1616
|
+
}
|
|
1617
|
+
const group = groups[groupIndex];
|
|
1618
|
+
if (index < 0 || index >= group.tabs.length) {
|
|
1619
|
+
return state;
|
|
1620
|
+
}
|
|
1621
|
+
const tab = group.tabs[index];
|
|
1622
|
+
const groupId = group.id;
|
|
1623
|
+
const tabId = tab.id;
|
|
1624
|
+
return closeTab(state, groupId, tabId);
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
// Counter for request IDs to handle race conditions
|
|
1628
|
+
let requestIdCounter = 0;
|
|
1629
|
+
const getNextRequestId = () => {
|
|
1630
|
+
return ++requestIdCounter;
|
|
1631
|
+
};
|
|
1632
|
+
|
|
1633
|
+
const startContentLoading = async (oldState, state, tabId, path, requestId) => {
|
|
1634
|
+
try {
|
|
1635
|
+
const getLatestState = () => {
|
|
1636
|
+
return get$2(state.uid).newState;
|
|
1637
|
+
};
|
|
1638
|
+
set$3(state.uid, oldState, state);
|
|
1639
|
+
const newState = await loadTabContentAsync(tabId, path, requestId, getLatestState);
|
|
1640
|
+
return newState;
|
|
1641
|
+
} catch {
|
|
1642
|
+
// Silently ignore errors - the tab may have been closed or the component unmounted
|
|
1643
|
+
}
|
|
1644
|
+
return state;
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
const shouldLoadContent = tab => {
|
|
1648
|
+
// Load if:
|
|
1649
|
+
// - Has a path (file-based tab)
|
|
1650
|
+
// - Not already loaded or currently loading
|
|
1651
|
+
if (!tab.uri) {
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
if (tab.loadingState === 'loading') {
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
if (tab.loadingState === 'loaded' && tab.content) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
return true;
|
|
1661
|
+
};
|
|
1662
|
+
const getActiveTabId$1 = state => {
|
|
1663
|
+
const {
|
|
1664
|
+
layout
|
|
1665
|
+
} = state;
|
|
1666
|
+
const {
|
|
1667
|
+
activeGroupId,
|
|
1668
|
+
groups
|
|
1669
|
+
} = layout;
|
|
1670
|
+
const activeGroup = groups.find(g => g.id === activeGroupId);
|
|
1671
|
+
return activeGroup?.activeTabId;
|
|
1672
|
+
};
|
|
1673
|
+
const selectTab = async (state, groupIndex, index) => {
|
|
1674
|
+
const {
|
|
1675
|
+
layout,
|
|
1676
|
+
uid
|
|
1677
|
+
} = state;
|
|
1678
|
+
const {
|
|
1679
|
+
groups
|
|
1680
|
+
} = layout;
|
|
1681
|
+
|
|
1682
|
+
// Validate indexes
|
|
1683
|
+
if (groupIndex < 0 || groupIndex >= groups.length) {
|
|
1684
|
+
return state;
|
|
1685
|
+
}
|
|
1686
|
+
const group = groups[groupIndex];
|
|
1687
|
+
if (index < 0 || index >= group.tabs.length) {
|
|
1688
|
+
return state;
|
|
1689
|
+
}
|
|
1690
|
+
const tab = group.tabs[index];
|
|
1691
|
+
const groupId = group.id;
|
|
1692
|
+
const tabId = tab.id;
|
|
1693
|
+
|
|
1694
|
+
// Return same state if this group and tab are already active
|
|
1695
|
+
if (layout.activeGroupId === groupId && group.activeTabId === tabId) {
|
|
1696
|
+
return state;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// Get previous active tab ID for viewlet switching
|
|
1700
|
+
getActiveTabId$1(state);
|
|
1701
|
+
|
|
1702
|
+
// Check if we need to load content for the newly selected tab
|
|
1703
|
+
const needsLoading = shouldLoadContent(tab);
|
|
1704
|
+
const requestId = needsLoading ? getNextRequestId() : 0;
|
|
1705
|
+
|
|
1706
|
+
// Update the groups array with the new active tab and active group
|
|
1707
|
+
// Also set loading state if needed
|
|
1708
|
+
const updatedGroups = groups.map((g, i) => {
|
|
1709
|
+
if (i !== groupIndex) {
|
|
1710
|
+
return {
|
|
1711
|
+
...g,
|
|
1712
|
+
focused: false
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// This is the group being selected
|
|
1717
|
+
const updatedTabs = needsLoading ? g.tabs.map(t => {
|
|
1718
|
+
if (t.id === tabId) {
|
|
1719
|
+
return {
|
|
1720
|
+
...t,
|
|
1721
|
+
errorMessage: '',
|
|
1722
|
+
loadingState: 'loading'
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
return t;
|
|
1544
1726
|
}) : g.tabs;
|
|
1545
1727
|
return {
|
|
1546
1728
|
...g,
|
|
@@ -1549,7 +1731,7 @@ const selectTab = async (state, groupIndex, index) => {
|
|
|
1549
1731
|
tabs: updatedTabs
|
|
1550
1732
|
};
|
|
1551
1733
|
});
|
|
1552
|
-
|
|
1734
|
+
let newState = {
|
|
1553
1735
|
...state,
|
|
1554
1736
|
layout: {
|
|
1555
1737
|
...layout,
|
|
@@ -1557,11 +1739,47 @@ const selectTab = async (state, groupIndex, index) => {
|
|
|
1557
1739
|
groups: updatedGroups
|
|
1558
1740
|
}
|
|
1559
1741
|
};
|
|
1742
|
+
|
|
1743
|
+
// Switch viewlet: detach old, attach new (if ready)
|
|
1744
|
+
const {
|
|
1745
|
+
commands: switchCommands,
|
|
1746
|
+
newState: stateWithViewlet
|
|
1747
|
+
} = switchViewlet(newState);
|
|
1748
|
+
newState = stateWithViewlet;
|
|
1749
|
+
|
|
1750
|
+
// If new tab's viewlet isn't ready yet, trigger creation (idempotent)
|
|
1751
|
+
const newTab = newState.layout.groups[groupIndex].tabs[index];
|
|
1752
|
+
if (!newTab.loadingState || newTab.loadingState === 'loading') {
|
|
1753
|
+
try {
|
|
1754
|
+
// Query RendererWorker for viewlet module ID
|
|
1755
|
+
// @ts-ignore
|
|
1756
|
+
const viewletModuleId = await invoke('Layout.getModuleId', newTab.uri);
|
|
1757
|
+
if (viewletModuleId) {
|
|
1758
|
+
// Calculate bounds: use main area bounds minus 35px for tab height
|
|
1759
|
+
const TAB_HEIGHT = 35;
|
|
1760
|
+
const bounds = {
|
|
1761
|
+
height: newState.height - TAB_HEIGHT,
|
|
1762
|
+
width: newState.width,
|
|
1763
|
+
x: newState.x,
|
|
1764
|
+
y: newState.y + TAB_HEIGHT
|
|
1765
|
+
};
|
|
1766
|
+
const createdState = createViewletForTab(newState, tabId, viewletModuleId, bounds);
|
|
1767
|
+
newState = createdState;
|
|
1768
|
+
}
|
|
1769
|
+
} catch {
|
|
1770
|
+
// Viewlet creation is optional - silently ignore if RendererWorker isn't available
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1560
1773
|
set$3(uid, state, newState);
|
|
1561
1774
|
|
|
1775
|
+
// Execute viewlet commands if any
|
|
1776
|
+
if (switchCommands.length > 0) {
|
|
1777
|
+
await executeViewletCommands(switchCommands);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1562
1780
|
// Start loading content in the background if needed
|
|
1563
|
-
if (needsLoading && tab.
|
|
1564
|
-
const latestState = await startContentLoading(state, newState, tabId, tab.
|
|
1781
|
+
if (needsLoading && tab.uri) {
|
|
1782
|
+
const latestState = await startContentLoading(state, newState, tabId, tab.uri, requestId);
|
|
1565
1783
|
return latestState;
|
|
1566
1784
|
}
|
|
1567
1785
|
return newState;
|
|
@@ -1614,6 +1832,11 @@ const initialize = async () => {
|
|
|
1614
1832
|
set$1(rpc);
|
|
1615
1833
|
};
|
|
1616
1834
|
|
|
1835
|
+
const createViewlet = async (viewletModuleId, editorUid, tabId, bounds, uri) => {
|
|
1836
|
+
// @ts-ignore
|
|
1837
|
+
await invoke('Layout.createViewlet', viewletModuleId, editorUid, tabId, bounds, uri);
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1617
1840
|
const getMaxIdFromLayout = layout => {
|
|
1618
1841
|
let maxId = 0;
|
|
1619
1842
|
for (const group of layout.groups) {
|
|
@@ -1629,15 +1852,16 @@ const getMaxIdFromLayout = layout => {
|
|
|
1629
1852
|
return maxId;
|
|
1630
1853
|
};
|
|
1631
1854
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1855
|
+
const getViewletModuleId = async uri => {
|
|
1856
|
+
// Query RendererWorker for viewlet module ID (optional, may fail in tests)
|
|
1857
|
+
let viewletModuleId;
|
|
1858
|
+
try {
|
|
1859
|
+
// @ts-ignore
|
|
1860
|
+
viewletModuleId = await invoke('Layout.getModuleId', uri);
|
|
1861
|
+
} catch {
|
|
1862
|
+
// Viewlet creation is optional - silently ignore if RendererWorker isn't available
|
|
1640
1863
|
}
|
|
1864
|
+
return viewletModuleId;
|
|
1641
1865
|
};
|
|
1642
1866
|
|
|
1643
1867
|
const isValidTab = tab => {
|
|
@@ -1689,10 +1913,66 @@ const loadContent = async (state, savedState) => {
|
|
|
1689
1913
|
if (restoredLayout) {
|
|
1690
1914
|
const maxId = getMaxIdFromLayout(restoredLayout);
|
|
1691
1915
|
setMinId(maxId);
|
|
1692
|
-
|
|
1916
|
+
let newState = {
|
|
1693
1917
|
...state,
|
|
1694
1918
|
layout: restoredLayout
|
|
1695
1919
|
};
|
|
1920
|
+
|
|
1921
|
+
// Create viewlets only for active tabs in each group
|
|
1922
|
+
const TAB_HEIGHT = 35;
|
|
1923
|
+
const bounds = {
|
|
1924
|
+
height: newState.height - TAB_HEIGHT,
|
|
1925
|
+
width: newState.width,
|
|
1926
|
+
x: newState.x,
|
|
1927
|
+
y: newState.y + TAB_HEIGHT
|
|
1928
|
+
};
|
|
1929
|
+
for (const group of restoredLayout.groups) {
|
|
1930
|
+
// Find the active tab in this group
|
|
1931
|
+
const activeTab = group.tabs.find(tab => tab.id === group.activeTabId);
|
|
1932
|
+
if (activeTab && activeTab.uri) {
|
|
1933
|
+
const viewletModuleId = await getViewletModuleId(activeTab.uri);
|
|
1934
|
+
if (viewletModuleId) {
|
|
1935
|
+
// Ensure the tab has an editorUid
|
|
1936
|
+
const editorUid = activeTab.editorUid === -1 ? create() : activeTab.editorUid;
|
|
1937
|
+
|
|
1938
|
+
// Create viewlet for the tab
|
|
1939
|
+
newState = createViewletForTab(newState, activeTab.id);
|
|
1940
|
+
|
|
1941
|
+
// Update the tab with the editorUid in the state
|
|
1942
|
+
const updatedGroups = newState.layout.groups.map(g => {
|
|
1943
|
+
if (g.id !== group.id) {
|
|
1944
|
+
return g;
|
|
1945
|
+
}
|
|
1946
|
+
return {
|
|
1947
|
+
...g,
|
|
1948
|
+
tabs: g.tabs.map(t => {
|
|
1949
|
+
if (t.id !== activeTab.id) {
|
|
1950
|
+
return t;
|
|
1951
|
+
}
|
|
1952
|
+
return {
|
|
1953
|
+
...t,
|
|
1954
|
+
editorUid
|
|
1955
|
+
};
|
|
1956
|
+
})
|
|
1957
|
+
};
|
|
1958
|
+
});
|
|
1959
|
+
newState = {
|
|
1960
|
+
...newState,
|
|
1961
|
+
layout: {
|
|
1962
|
+
...newState.layout,
|
|
1963
|
+
groups: updatedGroups
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
// Create the actual viewlet instance
|
|
1968
|
+
await createViewlet(viewletModuleId, editorUid, activeTab.id, bounds, activeTab.uri);
|
|
1969
|
+
|
|
1970
|
+
// Mark the viewlet as ready
|
|
1971
|
+
newState = handleViewletReady(newState, editorUid);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
return newState;
|
|
1696
1976
|
}
|
|
1697
1977
|
return {
|
|
1698
1978
|
...state,
|
|
@@ -1716,33 +1996,30 @@ const i18nString = (key, placeholders = emptyObject) => {
|
|
|
1716
1996
|
return key.replaceAll(RE_PLACEHOLDER, replacer);
|
|
1717
1997
|
};
|
|
1718
1998
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
CloseToTheRight: 'Close To The Right',
|
|
1727
|
-
FindFileReferences: 'Find File References',
|
|
1728
|
-
RevealInExplorer: 'Reveal in Explorer'};
|
|
1999
|
+
const Close = 'Close';
|
|
2000
|
+
const CloseAll = 'Close All';
|
|
2001
|
+
const CloseOthers = 'Close Others';
|
|
2002
|
+
const CloseToTheRight = 'Close To The Right';
|
|
2003
|
+
const FindFileReferences = 'Find File References';
|
|
2004
|
+
const RevealInExplorer = 'Reveal in Explorer';
|
|
2005
|
+
|
|
1729
2006
|
const close = () => {
|
|
1730
|
-
return i18nString(
|
|
2007
|
+
return i18nString(Close);
|
|
1731
2008
|
};
|
|
1732
2009
|
const closeOthers = () => {
|
|
1733
|
-
return i18nString(
|
|
2010
|
+
return i18nString(CloseOthers);
|
|
1734
2011
|
};
|
|
1735
2012
|
const closeAll = () => {
|
|
1736
|
-
return i18nString(
|
|
2013
|
+
return i18nString(CloseAll);
|
|
1737
2014
|
};
|
|
1738
2015
|
const revealInExplorer = () => {
|
|
1739
|
-
return i18nString(
|
|
2016
|
+
return i18nString(RevealInExplorer);
|
|
1740
2017
|
};
|
|
1741
2018
|
const closeToTheRight = () => {
|
|
1742
|
-
return i18nString(
|
|
2019
|
+
return i18nString(CloseToTheRight);
|
|
1743
2020
|
};
|
|
1744
2021
|
const findFileReferences = () => {
|
|
1745
|
-
return i18nString(
|
|
2022
|
+
return i18nString(FindFileReferences);
|
|
1746
2023
|
};
|
|
1747
2024
|
|
|
1748
2025
|
const menuEntrySeparator = {
|
|
@@ -1769,7 +2046,7 @@ const getMenuEntries$1 = state => {
|
|
|
1769
2046
|
const tab = tabs[activeTabId || 0];
|
|
1770
2047
|
object(tab);
|
|
1771
2048
|
const {
|
|
1772
|
-
path
|
|
2049
|
+
uri: path
|
|
1773
2050
|
} = tab;
|
|
1774
2051
|
return [{
|
|
1775
2052
|
command: 'Main.closeFocusedTab',
|
|
@@ -1815,42 +2092,59 @@ const getMenuEntries = async (state, props) => {
|
|
|
1815
2092
|
}
|
|
1816
2093
|
};
|
|
1817
2094
|
|
|
1818
|
-
const
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
const {
|
|
1823
|
-
groups
|
|
1824
|
-
} = layout;
|
|
1825
|
-
for (const group of groups) {
|
|
1826
|
-
const tab = group.tabs.find(t => t.path === uri);
|
|
1827
|
-
if (tab) {
|
|
1828
|
-
return {
|
|
1829
|
-
groupId: group.id,
|
|
1830
|
-
tab
|
|
1831
|
-
};
|
|
1832
|
-
}
|
|
2095
|
+
const getBasename = uri => {
|
|
2096
|
+
const lastSlashIndex = uri.lastIndexOf('/');
|
|
2097
|
+
if (lastSlashIndex === -1) {
|
|
2098
|
+
return uri;
|
|
1833
2099
|
}
|
|
1834
|
-
return
|
|
2100
|
+
return uri.slice(lastSlashIndex + 1);
|
|
2101
|
+
};
|
|
2102
|
+
const getLabel = uri => {
|
|
2103
|
+
if (uri.startsWith('settings://')) {
|
|
2104
|
+
return 'Settings';
|
|
2105
|
+
}
|
|
2106
|
+
if (uri.startsWith('simple-browser://')) {
|
|
2107
|
+
return 'Simple Browser';
|
|
2108
|
+
}
|
|
2109
|
+
return getBasename(uri);
|
|
1835
2110
|
};
|
|
1836
2111
|
|
|
1837
|
-
const
|
|
2112
|
+
const createEmptyGroup = (state, uri, requestId) => {
|
|
1838
2113
|
const {
|
|
1839
2114
|
layout
|
|
1840
2115
|
} = state;
|
|
1841
2116
|
const {
|
|
1842
2117
|
groups
|
|
1843
2118
|
} = layout;
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2119
|
+
const groupId = create();
|
|
2120
|
+
const title = getLabel(uri);
|
|
2121
|
+
const tabId = create();
|
|
2122
|
+
const editorUid = create();
|
|
2123
|
+
const newTab = {
|
|
2124
|
+
content: '',
|
|
2125
|
+
editorType: 'text',
|
|
2126
|
+
editorUid,
|
|
2127
|
+
errorMessage: '',
|
|
2128
|
+
id: tabId,
|
|
2129
|
+
isDirty: false,
|
|
2130
|
+
language: '',
|
|
2131
|
+
loadingState: 'loading',
|
|
2132
|
+
title,
|
|
2133
|
+
uri
|
|
2134
|
+
};
|
|
2135
|
+
const newGroup = {
|
|
2136
|
+
activeTabId: newTab.id,
|
|
2137
|
+
focused: true,
|
|
2138
|
+
id: groupId,
|
|
2139
|
+
size: 100,
|
|
2140
|
+
tabs: [newTab]
|
|
2141
|
+
};
|
|
1848
2142
|
return {
|
|
1849
2143
|
...state,
|
|
1850
2144
|
layout: {
|
|
1851
2145
|
...layout,
|
|
1852
2146
|
activeGroupId: groupId,
|
|
1853
|
-
groups:
|
|
2147
|
+
groups: [...groups, newGroup]
|
|
1854
2148
|
}
|
|
1855
2149
|
};
|
|
1856
2150
|
};
|
|
@@ -1885,21 +2179,128 @@ const openTab = (state, groupId, tab) => {
|
|
|
1885
2179
|
};
|
|
1886
2180
|
};
|
|
1887
2181
|
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2182
|
+
const ensureActiveGroup = (state, uri) => {
|
|
2183
|
+
// Find the active group (by activeGroupId or focused flag)
|
|
2184
|
+
const {
|
|
2185
|
+
layout
|
|
2186
|
+
} = state;
|
|
2187
|
+
const {
|
|
2188
|
+
activeGroupId,
|
|
2189
|
+
groups
|
|
2190
|
+
} = layout;
|
|
2191
|
+
const activeGroup = activeGroupId === undefined ? groups.find(group => group.focused) : groups.find(group => group.id === activeGroupId);
|
|
2192
|
+
|
|
2193
|
+
// Generate a request ID for content loading
|
|
2194
|
+
getNextRequestId();
|
|
2195
|
+
|
|
2196
|
+
// If no active group exists, create one
|
|
2197
|
+
let newState;
|
|
2198
|
+
if (activeGroup) {
|
|
2199
|
+
// Create a new tab with the URI in the active group
|
|
2200
|
+
const title = getLabel(uri);
|
|
2201
|
+
const tabId = create();
|
|
2202
|
+
const editorUid = create();
|
|
2203
|
+
const newTab = {
|
|
2204
|
+
content: '',
|
|
2205
|
+
editorType: 'text',
|
|
2206
|
+
editorUid,
|
|
2207
|
+
errorMessage: '',
|
|
2208
|
+
id: tabId,
|
|
2209
|
+
isDirty: false,
|
|
2210
|
+
language: '',
|
|
2211
|
+
loadingState: 'loading',
|
|
2212
|
+
title,
|
|
2213
|
+
uri: uri
|
|
2214
|
+
};
|
|
2215
|
+
newState = openTab(state, activeGroup.id, newTab);
|
|
2216
|
+
} else {
|
|
2217
|
+
newState = createEmptyGroup(state, uri);
|
|
1892
2218
|
}
|
|
1893
|
-
return
|
|
2219
|
+
return newState;
|
|
1894
2220
|
};
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2221
|
+
|
|
2222
|
+
const findTabById = (state, tabId) => {
|
|
2223
|
+
const {
|
|
2224
|
+
layout
|
|
2225
|
+
} = state;
|
|
2226
|
+
const {
|
|
2227
|
+
groups
|
|
2228
|
+
} = layout;
|
|
2229
|
+
for (const group of groups) {
|
|
2230
|
+
const tab = group.tabs.find(t => t.id === tabId);
|
|
2231
|
+
if (tab) {
|
|
2232
|
+
return {
|
|
2233
|
+
groupId: group.id,
|
|
2234
|
+
tab
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
1898
2237
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
2238
|
+
return undefined;
|
|
2239
|
+
};
|
|
2240
|
+
|
|
2241
|
+
const findTabByUri = (state, uri) => {
|
|
2242
|
+
const {
|
|
2243
|
+
layout
|
|
2244
|
+
} = state;
|
|
2245
|
+
const {
|
|
2246
|
+
groups
|
|
2247
|
+
} = layout;
|
|
2248
|
+
for (const group of groups) {
|
|
2249
|
+
const tab = group.tabs.find(t => t.uri === uri);
|
|
2250
|
+
if (tab) {
|
|
2251
|
+
return {
|
|
2252
|
+
groupId: group.id,
|
|
2253
|
+
tab
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
1901
2256
|
}
|
|
1902
|
-
return
|
|
2257
|
+
return undefined;
|
|
2258
|
+
};
|
|
2259
|
+
|
|
2260
|
+
const focusEditorGroup = (state, groupId) => {
|
|
2261
|
+
const {
|
|
2262
|
+
layout
|
|
2263
|
+
} = state;
|
|
2264
|
+
const {
|
|
2265
|
+
groups
|
|
2266
|
+
} = layout;
|
|
2267
|
+
const updatedGroups = groups.map(group => ({
|
|
2268
|
+
...group,
|
|
2269
|
+
focused: group.id === groupId
|
|
2270
|
+
}));
|
|
2271
|
+
return {
|
|
2272
|
+
...state,
|
|
2273
|
+
layout: {
|
|
2274
|
+
...layout,
|
|
2275
|
+
activeGroupId: groupId,
|
|
2276
|
+
groups: updatedGroups
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
};
|
|
2280
|
+
|
|
2281
|
+
const getActiveTabId = state => {
|
|
2282
|
+
const {
|
|
2283
|
+
layout
|
|
2284
|
+
} = state;
|
|
2285
|
+
const {
|
|
2286
|
+
activeGroupId,
|
|
2287
|
+
groups
|
|
2288
|
+
} = layout;
|
|
2289
|
+
const activeGroup = groups.find(g => g.id === activeGroupId);
|
|
2290
|
+
return activeGroup?.activeTabId;
|
|
2291
|
+
};
|
|
2292
|
+
|
|
2293
|
+
const getOptionUriOptions = options => {
|
|
2294
|
+
let uri = '';
|
|
2295
|
+
if (typeof options === 'string') {
|
|
2296
|
+
uri = options;
|
|
2297
|
+
} else {
|
|
2298
|
+
const {
|
|
2299
|
+
uri: optionsUri
|
|
2300
|
+
} = options;
|
|
2301
|
+
uri = optionsUri;
|
|
2302
|
+
}
|
|
2303
|
+
return uri;
|
|
1903
2304
|
};
|
|
1904
2305
|
|
|
1905
2306
|
const switchTab = (state, groupId, tabId) => {
|
|
@@ -1930,16 +2331,12 @@ const switchTab = (state, groupId, tabId) => {
|
|
|
1930
2331
|
};
|
|
1931
2332
|
};
|
|
1932
2333
|
|
|
1933
|
-
/* eslint-disable prefer-destructuring */
|
|
1934
|
-
|
|
1935
2334
|
const openUri = async (state, options) => {
|
|
1936
2335
|
object(state);
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
uri = options.uri;
|
|
1942
|
-
}
|
|
2336
|
+
const {
|
|
2337
|
+
uid
|
|
2338
|
+
} = state;
|
|
2339
|
+
const uri = getOptionUriOptions(options);
|
|
1943
2340
|
|
|
1944
2341
|
// Check if a tab with this URI already exists
|
|
1945
2342
|
const existingTab = findTabByUri(state, uri);
|
|
@@ -1949,69 +2346,58 @@ const openUri = async (state, options) => {
|
|
|
1949
2346
|
return switchTab(focusedState, existingTab.groupId, existingTab.tab.id);
|
|
1950
2347
|
}
|
|
1951
2348
|
|
|
1952
|
-
//
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
const
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2349
|
+
// Get previous active tab ID for viewlet switching
|
|
2350
|
+
getActiveTabId(state);
|
|
2351
|
+
const newState = ensureActiveGroup(state, uri);
|
|
2352
|
+
const tabId = getActiveTabId(newState);
|
|
2353
|
+
const viewletModuleId = await getViewletModuleId(uri);
|
|
2354
|
+
if (!viewletModuleId) {
|
|
2355
|
+
// TODO display some kind of errro that editor couldn't be opened
|
|
2356
|
+
return newState;
|
|
2357
|
+
}
|
|
1961
2358
|
|
|
1962
|
-
//
|
|
1963
|
-
const
|
|
2359
|
+
// Calculate bounds: use main area bounds minus 35px for tab height
|
|
2360
|
+
const TAB_HEIGHT = 35;
|
|
2361
|
+
const bounds = {
|
|
2362
|
+
height: newState.height - TAB_HEIGHT,
|
|
2363
|
+
width: newState.width,
|
|
2364
|
+
x: newState.x,
|
|
2365
|
+
y: newState.y + TAB_HEIGHT
|
|
2366
|
+
};
|
|
2367
|
+
const stateWithViewlet = createViewletForTab(newState, tabId);
|
|
2368
|
+
let intermediateState1 = stateWithViewlet;
|
|
1964
2369
|
|
|
1965
|
-
//
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
content: '',
|
|
1972
|
-
editorType: 'text',
|
|
1973
|
-
id: tabId,
|
|
1974
|
-
isDirty: false,
|
|
1975
|
-
loadingState: 'loading',
|
|
1976
|
-
loadRequestId: requestId,
|
|
1977
|
-
path: uri,
|
|
1978
|
-
title
|
|
1979
|
-
};
|
|
1980
|
-
const newGroup = {
|
|
1981
|
-
activeTabId: newTab.id,
|
|
1982
|
-
focused: true,
|
|
1983
|
-
id: groupId,
|
|
1984
|
-
size: 100,
|
|
1985
|
-
tabs: [newTab]
|
|
1986
|
-
};
|
|
1987
|
-
const newState = {
|
|
1988
|
-
...state,
|
|
1989
|
-
layout: {
|
|
1990
|
-
...layout,
|
|
1991
|
-
activeGroupId: groupId,
|
|
1992
|
-
groups: [...groups, newGroup]
|
|
1993
|
-
}
|
|
1994
|
-
};
|
|
2370
|
+
// Switch viewlet (detach old, attach new if ready)
|
|
2371
|
+
const {
|
|
2372
|
+
newState: switchedState
|
|
2373
|
+
} = switchViewlet(intermediateState1);
|
|
2374
|
+
intermediateState1 = switchedState;
|
|
2375
|
+
set$3(uid, state, intermediateState1);
|
|
1995
2376
|
|
|
1996
|
-
|
|
1997
|
-
|
|
2377
|
+
// @ts-ignore
|
|
2378
|
+
|
|
2379
|
+
// Get the tab to extract editorUid
|
|
2380
|
+
const tabWithViewlet = findTabById(intermediateState1, tabId);
|
|
2381
|
+
if (!tabWithViewlet) {
|
|
2382
|
+
return intermediateState1;
|
|
2383
|
+
}
|
|
2384
|
+
const {
|
|
2385
|
+
editorUid
|
|
2386
|
+
} = tabWithViewlet.tab;
|
|
2387
|
+
if (editorUid === -1) {
|
|
2388
|
+
throw new Error(`invalid editorUid`);
|
|
1998
2389
|
}
|
|
2390
|
+
await createViewlet(viewletModuleId, editorUid, tabId, bounds, uri);
|
|
1999
2391
|
|
|
2000
|
-
//
|
|
2001
|
-
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
loadRequestId: requestId,
|
|
2010
|
-
path: uri,
|
|
2011
|
-
title
|
|
2012
|
-
};
|
|
2013
|
-
const newState = openTab(state, activeGroup.id, newTab);
|
|
2014
|
-
return startContentLoading(state, newState, tabId, uri, requestId);
|
|
2392
|
+
// After viewlet is created, get the latest state and mark it as ready
|
|
2393
|
+
// This ensures we have any state updates that occurred during viewlet creation
|
|
2394
|
+
const {
|
|
2395
|
+
newState: latestState
|
|
2396
|
+
} = get$2(uid);
|
|
2397
|
+
|
|
2398
|
+
// Attachment is handled automatically by virtual DOM reference nodes
|
|
2399
|
+
const readyState = handleViewletReady(latestState, editorUid);
|
|
2400
|
+
return readyState;
|
|
2015
2401
|
};
|
|
2016
2402
|
|
|
2017
2403
|
const refresh = state => {
|
|
@@ -2064,29 +2450,38 @@ const renderContent = content => {
|
|
|
2064
2450
|
type: Pre
|
|
2065
2451
|
}, text(content)];
|
|
2066
2452
|
};
|
|
2453
|
+
const renderViewletReference = tab => {
|
|
2454
|
+
return [{
|
|
2455
|
+
childCount: 0,
|
|
2456
|
+
type: 100,
|
|
2457
|
+
// Reference node type - frontend will append the component at this position
|
|
2458
|
+
uid: tab.editorUid
|
|
2459
|
+
}];
|
|
2460
|
+
};
|
|
2067
2461
|
const renderEditor = tab => {
|
|
2068
2462
|
if (!tab) {
|
|
2463
|
+
// Keep backward compatible behavior: render empty content
|
|
2069
2464
|
return renderContent('');
|
|
2070
2465
|
}
|
|
2071
|
-
if (tab.editorType === 'custom') {
|
|
2072
|
-
return [{
|
|
2073
|
-
childCount: 1,
|
|
2074
|
-
className: 'CustomEditor',
|
|
2075
|
-
type: Div
|
|
2076
|
-
}, text(`Custom Editor: ${tab.customEditorId}`)];
|
|
2077
|
-
}
|
|
2078
2466
|
|
|
2079
|
-
//
|
|
2467
|
+
// Viewlet is being created in background - show loading
|
|
2080
2468
|
if (tab.loadingState === 'loading') {
|
|
2081
2469
|
return renderLoading();
|
|
2082
2470
|
}
|
|
2083
2471
|
|
|
2084
|
-
//
|
|
2472
|
+
// Viewlet is ready - render a reference node
|
|
2473
|
+
// Frontend will append the pre-created component at this position using the uid
|
|
2474
|
+
// Check for viewletInstanceId to distinguish between viewlet and plain text tabs
|
|
2475
|
+
if (tab.loadingState === 'loaded' && tab.editorUid !== -1) {
|
|
2476
|
+
return renderViewletReference(tab);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// Viewlet error state
|
|
2085
2480
|
if (tab.loadingState === 'error' && tab.errorMessage) {
|
|
2086
2481
|
return renderError(tab.errorMessage);
|
|
2087
2482
|
}
|
|
2088
2483
|
|
|
2089
|
-
// Default: render content
|
|
2484
|
+
// Default: render content (fallback for simple text without viewlet)
|
|
2090
2485
|
return renderContent(tab.content || '');
|
|
2091
2486
|
};
|
|
2092
2487
|
|
|
@@ -2104,7 +2499,7 @@ const renderTab = (tab, isActive, tabIndex, groupIndex) => {
|
|
|
2104
2499
|
onClick: HandleClickTab,
|
|
2105
2500
|
onContextMenu: HandleTabContextMenu,
|
|
2106
2501
|
role: 'tab',
|
|
2107
|
-
title: tab.
|
|
2502
|
+
title: tab.uri || tab.title,
|
|
2108
2503
|
type: Div
|
|
2109
2504
|
}, {
|
|
2110
2505
|
childCount: 0,
|
|
@@ -2249,7 +2644,7 @@ const commandMap = {
|
|
|
2249
2644
|
'MainArea.render2': render2,
|
|
2250
2645
|
'MainArea.renderEventListeners': renderEventListeners,
|
|
2251
2646
|
'MainArea.resize': wrapCommand(resize),
|
|
2252
|
-
'MainArea.saveState': saveState,
|
|
2647
|
+
'MainArea.saveState': wrapGetter(saveState),
|
|
2253
2648
|
'MainArea.selectTab': wrapCommand(selectTab),
|
|
2254
2649
|
'MainArea.terminate': terminate
|
|
2255
2650
|
};
|