@theia/core 1.74.0-next.9 → 1.74.1
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/README.md +10 -10
- package/i18n/nls.cs.json +143 -16
- package/i18n/nls.de.json +143 -16
- package/i18n/nls.es.json +143 -16
- package/i18n/nls.fr.json +143 -16
- package/i18n/nls.hu.json +143 -16
- package/i18n/nls.it.json +143 -16
- package/i18n/nls.ja.json +143 -16
- package/i18n/nls.json +143 -16
- package/i18n/nls.ko.json +143 -16
- package/i18n/nls.pl.json +143 -16
- package/i18n/nls.pt-br.json +143 -16
- package/i18n/nls.ru.json +143 -16
- package/i18n/nls.tr.json +143 -16
- package/i18n/nls.zh-cn.json +143 -16
- package/i18n/nls.zh-tw.json +143 -16
- package/lib/browser/browser-clipboard-service.d.ts +3 -0
- package/lib/browser/browser-clipboard-service.d.ts.map +1 -1
- package/lib/browser/browser-clipboard-service.js +8 -1
- package/lib/browser/browser-clipboard-service.js.map +1 -1
- package/lib/browser/catalog.json +505 -46
- package/lib/browser/clipboard-service.d.ts +11 -0
- package/lib/browser/clipboard-service.d.ts.map +1 -1
- package/lib/browser/clipboard-service.js.map +1 -1
- package/lib/browser/common-frontend-contribution.d.ts +3 -0
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +22 -10
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/common-frontend-contribution.spec.d.ts +2 -0
- package/lib/browser/common-frontend-contribution.spec.d.ts.map +1 -0
- package/lib/browser/common-frontend-contribution.spec.js +71 -0
- package/lib/browser/common-frontend-contribution.spec.js.map +1 -0
- package/lib/browser/frontend-application-contribution.d.ts +1 -1
- package/lib/browser/frontend-application-module.d.ts +2 -2
- package/lib/browser/frontend-application-module.d.ts.map +1 -1
- package/lib/browser/frontend-application-module.js +13 -2
- package/lib/browser/frontend-application-module.js.map +1 -1
- package/lib/browser/frontend-application.d.ts.map +1 -1
- package/lib/browser/frontend-application.js +12 -2
- package/lib/browser/frontend-application.js.map +1 -1
- package/lib/browser/index.d.ts +1 -0
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +1 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/keybinding.d.ts.map +1 -1
- package/lib/browser/keybinding.js +12 -7
- package/lib/browser/keybinding.js.map +1 -1
- package/lib/browser/perspective-service.d.ts +173 -0
- package/lib/browser/perspective-service.d.ts.map +1 -0
- package/lib/browser/perspective-service.js +484 -0
- package/lib/browser/perspective-service.js.map +1 -0
- package/lib/browser/perspective-service.spec.d.ts +2 -0
- package/lib/browser/perspective-service.spec.d.ts.map +1 -0
- package/lib/browser/perspective-service.spec.js +1806 -0
- package/lib/browser/perspective-service.spec.js.map +1 -0
- package/lib/browser/preferences/preference-contribution.d.ts.map +1 -1
- package/lib/browser/preferences/preference-contribution.js +9 -1
- package/lib/browser/preferences/preference-contribution.js.map +1 -1
- package/lib/browser/preferences/preference-service.spec.js +63 -0
- package/lib/browser/preferences/preference-service.spec.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +7 -0
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js +38 -28
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.d.ts +9 -0
- package/lib/browser/shell/shell-layout-restorer.d.ts.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.js +124 -14
- package/lib/browser/shell/shell-layout-restorer.js.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.spec.d.ts +2 -0
- package/lib/browser/shell/shell-layout-restorer.spec.d.ts.map +1 -0
- package/lib/browser/shell/shell-layout-restorer.spec.js +539 -0
- package/lib/browser/shell/shell-layout-restorer.spec.js.map +1 -0
- package/lib/browser/shell/view-contribution.d.ts +2 -0
- package/lib/browser/shell/view-contribution.d.ts.map +1 -1
- package/lib/browser/shell/view-contribution.js +5 -1
- package/lib/browser/shell/view-contribution.js.map +1 -1
- package/lib/browser/status-bar/status-bar.d.ts +2 -0
- package/lib/browser/status-bar/status-bar.d.ts.map +1 -1
- package/lib/browser/status-bar/status-bar.js +11 -3
- package/lib/browser/status-bar/status-bar.js.map +1 -1
- package/lib/browser/tree/tree-consistency.spec.js +103 -0
- package/lib/browser/tree/tree-consistency.spec.js.map +1 -1
- package/lib/browser/tree/tree-widget.d.ts +7 -33
- package/lib/browser/tree/tree-widget.d.ts.map +1 -1
- package/lib/browser/tree/tree-widget.js +7 -54
- package/lib/browser/tree/tree-widget.js.map +1 -1
- package/lib/browser/tree/tree.d.ts.map +1 -1
- package/lib/browser/tree/tree.js +6 -0
- package/lib/browser/tree/tree.js.map +1 -1
- package/lib/browser/widgets/index.d.ts +1 -0
- package/lib/browser/widgets/index.d.ts.map +1 -1
- package/lib/browser/widgets/index.js +1 -0
- package/lib/browser/widgets/index.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts +15 -2
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +86 -24
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/browser/widgets/tab-bar-tracker.d.ts +23 -0
- package/lib/browser/widgets/tab-bar-tracker.d.ts.map +1 -0
- package/lib/browser/widgets/tab-bar-tracker.js +65 -0
- package/lib/browser/widgets/tab-bar-tracker.js.map +1 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.d.ts +2 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.d.ts.map +1 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.js +109 -0
- package/lib/browser/widgets/tab-bar-tracker.spec.js.map +1 -0
- package/lib/browser/window/default-secondary-window-service.d.ts +2 -0
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +8 -2
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.d.ts +19 -0
- package/lib/browser/window/default-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-window-service.js +34 -5
- package/lib/browser/window/default-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.spec.js +22 -0
- package/lib/browser/window/default-window-service.spec.js.map +1 -1
- package/lib/browser/window/secondary-window-service.d.ts +7 -0
- package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/window-service.d.ts +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +13 -2
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +31 -3
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
- package/lib/common/preferences/index.d.ts +1 -0
- package/lib/common/preferences/index.d.ts.map +1 -1
- package/lib/common/preferences/index.js +1 -0
- package/lib/common/preferences/index.js.map +1 -1
- package/lib/common/preferences/preference-provider.d.ts +15 -0
- package/lib/common/preferences/preference-provider.d.ts.map +1 -1
- package/lib/common/preferences/preference-provider.js +21 -1
- package/lib/common/preferences/preference-provider.js.map +1 -1
- package/lib/common/preferences/preference-provider.spec.js +17 -0
- package/lib/common/preferences/preference-provider.spec.js.map +1 -1
- package/lib/common/preferences/preference-scope.d.ts +6 -1
- package/lib/common/preferences/preference-scope.d.ts.map +1 -1
- package/lib/common/preferences/preference-scope.js +5 -0
- package/lib/common/preferences/preference-scope.js.map +1 -1
- package/lib/common/preferences/preference-scope.spec.js +5 -3
- package/lib/common/preferences/preference-scope.spec.js.map +1 -1
- package/lib/common/preferences/preference-service.d.ts +19 -0
- package/lib/common/preferences/preference-service.d.ts.map +1 -1
- package/lib/common/preferences/preference-service.js +34 -5
- package/lib/common/preferences/preference-service.js.map +1 -1
- package/lib/common/preferences/session-preference-provider.d.ts +17 -0
- package/lib/common/preferences/session-preference-provider.d.ts.map +1 -0
- package/lib/common/preferences/session-preference-provider.js +77 -0
- package/lib/common/preferences/session-preference-provider.js.map +1 -0
- package/lib/common/preferences/session-preference-provider.spec.d.ts +2 -0
- package/lib/common/preferences/session-preference-provider.spec.d.ts.map +1 -0
- package/lib/common/preferences/session-preference-provider.spec.js +71 -0
- package/lib/common/preferences/session-preference-provider.spec.js.map +1 -0
- package/lib/electron-browser/electron-clipboard-service.d.ts +3 -0
- package/lib/electron-browser/electron-clipboard-service.d.ts.map +1 -1
- package/lib/electron-browser/electron-clipboard-service.js +6 -0
- package/lib/electron-browser/electron-clipboard-service.js.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
- package/lib/electron-browser/preload.d.ts.map +1 -1
- package/lib/electron-browser/preload.js +2 -1
- package/lib/electron-browser/preload.js.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
- package/lib/electron-browser/window/electron-window-service.js +3 -3
- package/lib/electron-browser/window/electron-window-service.js.map +1 -1
- package/lib/electron-common/electron-api.d.ts +2 -0
- package/lib/electron-common/electron-api.d.ts.map +1 -1
- package/lib/electron-common/electron-api.js +2 -1
- package/lib/electron-common/electron-api.js.map +1 -1
- package/lib/electron-main/electron-api-main.d.ts.map +1 -1
- package/lib/electron-main/electron-api-main.js +43 -0
- package/lib/electron-main/electron-api-main.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +3 -0
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/lib/electron-node/hosting/electron-backend-hosting-module.d.ts.map +1 -1
- package/lib/electron-node/hosting/electron-backend-hosting-module.js +7 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.js.map +1 -1
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.d.ts +2 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.d.ts.map +1 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js +39 -0
- package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js.map +1 -0
- package/lib/node/hosting/backend-hosting-module.d.ts.map +1 -1
- package/lib/node/hosting/backend-hosting-module.js +4 -1
- package/lib/node/hosting/backend-hosting-module.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.d.ts +29 -1
- package/lib/node/hosting/browser-connection-token.d.ts.map +1 -1
- package/lib/node/hosting/browser-connection-token.js +25 -1
- package/lib/node/hosting/browser-connection-token.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.spec.js +70 -0
- package/lib/node/hosting/browser-connection-token.spec.js.map +1 -1
- package/lib/node/index.d.ts +1 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +3 -1
- package/lib/node/index.js.map +1 -1
- package/package.json +11 -11
- package/src/browser/browser-clipboard-service.ts +7 -1
- package/src/browser/clipboard-service.ts +11 -0
- package/src/browser/common-frontend-contribution.spec.ts +84 -0
- package/src/browser/common-frontend-contribution.ts +22 -10
- package/src/browser/frontend-application-contribution.ts +1 -1
- package/src/browser/frontend-application-module.ts +15 -3
- package/src/browser/frontend-application.ts +12 -4
- package/src/browser/index.ts +1 -0
- package/src/browser/keybinding.ts +12 -7
- package/src/browser/perspective-service.spec.ts +2270 -0
- package/src/browser/perspective-service.ts +625 -0
- package/src/browser/preferences/preference-contribution.ts +9 -1
- package/src/browser/preferences/preference-service.spec.ts +72 -0
- package/src/browser/shell/application-shell.ts +41 -26
- package/src/browser/shell/shell-layout-restorer.spec.ts +746 -0
- package/src/browser/shell/shell-layout-restorer.ts +134 -13
- package/src/browser/shell/view-contribution.ts +6 -1
- package/src/browser/status-bar/status-bar.tsx +13 -3
- package/src/browser/style/select-component.css +16 -0
- package/src/browser/style/sidepanel.css +5 -0
- package/src/browser/style/status-bar.css +1 -1
- package/src/browser/tree/tree-consistency.spec.ts +119 -0
- package/src/browser/tree/tree-widget.tsx +12 -78
- package/src/browser/tree/tree.ts +6 -0
- package/src/browser/widgets/index.ts +1 -0
- package/src/browser/widgets/select-component.tsx +90 -24
- package/src/browser/widgets/tab-bar-tracker.spec.ts +121 -0
- package/src/browser/widgets/tab-bar-tracker.ts +68 -0
- package/src/browser/window/default-secondary-window-service.ts +8 -2
- package/src/browser/window/default-window-service.spec.ts +22 -0
- package/src/browser/window/default-window-service.ts +37 -5
- package/src/browser/window/secondary-window-service.ts +7 -0
- package/src/browser/window/window-service.ts +1 -1
- package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +32 -3
- package/src/common/i18n/nls.metadata.json +4149 -888
- package/src/common/preferences/index.ts +1 -0
- package/src/common/preferences/preference-provider.spec.ts +17 -0
- package/src/common/preferences/preference-provider.ts +22 -1
- package/src/common/preferences/preference-scope.spec.ts +5 -3
- package/src/common/preferences/preference-scope.ts +6 -1
- package/src/common/preferences/preference-service.ts +39 -5
- package/src/common/preferences/session-preference-provider.spec.ts +78 -0
- package/src/common/preferences/session-preference-provider.ts +69 -0
- package/src/electron-browser/electron-clipboard-service.ts +5 -0
- package/src/electron-browser/menu/electron-menu-contribution.ts +1 -1
- package/src/electron-browser/menu/electron-menu-style.css +3 -1
- package/src/electron-browser/preload.ts +5 -2
- package/src/electron-browser/window/electron-secondary-window-service.ts +1 -1
- package/src/electron-browser/window/electron-window-service.ts +3 -3
- package/src/electron-common/electron-api.ts +4 -0
- package/src/electron-main/electron-api-main.ts +56 -3
- package/src/electron-main/electron-main-application.ts +3 -0
- package/src/electron-node/hosting/electron-backend-hosting-module.spec.ts +42 -0
- package/src/electron-node/hosting/electron-backend-hosting-module.ts +8 -0
- package/src/node/hosting/backend-hosting-module.ts +5 -2
- package/src/node/hosting/browser-connection-token.spec.ts +92 -0
- package/src/node/hosting/browser-connection-token.ts +45 -1
- package/src/node/index.ts +1 -0
|
@@ -28,6 +28,7 @@ import { CommonCommands } from '../common-commands';
|
|
|
28
28
|
import { WindowService } from '../window/window-service';
|
|
29
29
|
import { StopReason } from '../../common/frontend-application-state';
|
|
30
30
|
import { isFunction, isObject, MaybePromise } from '../../common';
|
|
31
|
+
import { PerspectiveServiceInternal } from '../perspective-service';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* A contract for widgets that want to store and restore their inner state, between sessions.
|
|
@@ -124,6 +125,13 @@ export interface ShellLayoutTransformer {
|
|
|
124
125
|
transformLayoutOnRestore(layoutData: ApplicationShell.LayoutData): void;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
export const PERSPECTIVE_LAYOUTS_STORAGE_KEY = 'perspective-layouts';
|
|
129
|
+
|
|
130
|
+
export interface PersistedPerspectiveData {
|
|
131
|
+
activePerspectiveId: string;
|
|
132
|
+
layouts: Record<string, string>;
|
|
133
|
+
}
|
|
134
|
+
|
|
127
135
|
export const RESET_LAYOUT = Command.toLocalizedCommand({
|
|
128
136
|
id: 'reset.layout',
|
|
129
137
|
category: CommonCommands.VIEW_CATEGORY,
|
|
@@ -141,6 +149,9 @@ export class ShellLayoutRestorer implements CommandContribution {
|
|
|
141
149
|
@inject(WindowService) protected readonly windowService: WindowService;
|
|
142
150
|
@inject(ThemeService) protected readonly themeService: ThemeService;
|
|
143
151
|
|
|
152
|
+
@inject(PerspectiveServiceInternal)
|
|
153
|
+
protected readonly perspectiveService: PerspectiveServiceInternal;
|
|
154
|
+
|
|
144
155
|
constructor(
|
|
145
156
|
@inject(WidgetManager) protected widgetManager: WidgetManager,
|
|
146
157
|
@inject(ILogger) protected logger: ILogger,
|
|
@@ -157,6 +168,8 @@ export class ShellLayoutRestorer implements CommandContribution {
|
|
|
157
168
|
this.logger.info('>>> Resetting layout...');
|
|
158
169
|
this.shouldStoreLayout = false;
|
|
159
170
|
this.storageService.setData(this.storageKey, undefined);
|
|
171
|
+
this.perspectiveService.clearSavedLayouts();
|
|
172
|
+
this.storageService.setData(PERSPECTIVE_LAYOUTS_STORAGE_KEY, undefined);
|
|
160
173
|
this.themeService.reset();
|
|
161
174
|
this.logger.info('<<< The layout has been successfully reset.');
|
|
162
175
|
this.windowService.reload();
|
|
@@ -167,29 +180,137 @@ export class ShellLayoutRestorer implements CommandContribution {
|
|
|
167
180
|
if (this.shouldStoreLayout) {
|
|
168
181
|
try {
|
|
169
182
|
this.logger.info('>>> Storing the layout...');
|
|
170
|
-
|
|
171
|
-
const serializedLayoutData = this.deflate(layoutData);
|
|
172
|
-
this.storageService.setData(this.storageKey, serializedLayoutData);
|
|
183
|
+
this.storePerspectiveLayouts(app);
|
|
173
184
|
this.logger.info('<<< The layout has been successfully stored.');
|
|
174
185
|
} catch (error) {
|
|
175
|
-
this.storageService.setData(this.storageKey, undefined);
|
|
176
186
|
this.logger.error('Error during serialization of layout data', error);
|
|
177
187
|
}
|
|
178
188
|
}
|
|
179
189
|
}
|
|
180
190
|
|
|
191
|
+
protected storePerspectiveLayouts(app: FrontendApplication): void {
|
|
192
|
+
const provider = this.perspectiveService;
|
|
193
|
+
const activeId = provider.getActivePerspectiveId();
|
|
194
|
+
const layouts: Record<string, string> = {};
|
|
195
|
+
|
|
196
|
+
// Snapshot current shell as the active perspective's layout
|
|
197
|
+
try {
|
|
198
|
+
const currentLayout = app.shell.getLayoutData();
|
|
199
|
+
layouts[activeId] = this.deflate(currentLayout);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.logger.warn(`Could not deflate layout for active perspective '${activeId}'`, error);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Deflate all other saved (inactive) perspective layouts
|
|
205
|
+
for (const perspId of provider.getSavedPerspectiveIds()) {
|
|
206
|
+
if (perspId === activeId) {
|
|
207
|
+
continue; // already handled above from live shell
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const layout = provider.getSavedLayout(perspId);
|
|
211
|
+
if (layout) {
|
|
212
|
+
layouts[perspId] = this.deflate(layout);
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
this.logger.warn(`Could not deflate layout for perspective '${perspId}'`, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const perspectiveIds = Object.keys(layouts);
|
|
220
|
+
const onlyDefault = perspectiveIds.length <= 1 && (perspectiveIds.length === 0 || perspectiveIds[0] === provider.defaultPerspectiveId);
|
|
221
|
+
if (onlyDefault) {
|
|
222
|
+
// If only the default perspective has ever been used, continue writing
|
|
223
|
+
// to the legacy key for backward compatibility.
|
|
224
|
+
const defaultLayout = layouts[provider.defaultPerspectiveId];
|
|
225
|
+
if (defaultLayout) {
|
|
226
|
+
this.storageService.setData(this.storageKey, defaultLayout).catch(error => {
|
|
227
|
+
this.logger.error('Error persisting default layout', error);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
this.storageService.setData(PERSPECTIVE_LAYOUTS_STORAGE_KEY, undefined);
|
|
231
|
+
} else {
|
|
232
|
+
const data: PersistedPerspectiveData = { activePerspectiveId: activeId, layouts };
|
|
233
|
+
this.storageService.setData(PERSPECTIVE_LAYOUTS_STORAGE_KEY, data).catch(error => {
|
|
234
|
+
this.logger.error('Error persisting perspective layouts, clearing stored data', error);
|
|
235
|
+
this.storageService.setData(PERSPECTIVE_LAYOUTS_STORAGE_KEY, undefined);
|
|
236
|
+
});
|
|
237
|
+
// Clear the legacy key when perspectives are in use
|
|
238
|
+
this.storageService.setData(this.storageKey, undefined);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
181
242
|
async restoreLayout(app: FrontendApplication): Promise<boolean> {
|
|
182
243
|
this.logger.info('>>> Restoring the layout state...');
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
244
|
+
return this.restorePerspectiveLayouts(app);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
protected async restorePerspectiveLayouts(app: FrontendApplication): Promise<boolean> {
|
|
248
|
+
const persisted = await this.storageService.getData<PersistedPerspectiveData>(PERSPECTIVE_LAYOUTS_STORAGE_KEY);
|
|
249
|
+
|
|
250
|
+
let activeId: string | undefined;
|
|
251
|
+
|
|
252
|
+
if (persisted) {
|
|
253
|
+
activeId = persisted.activePerspectiveId;
|
|
254
|
+
// Inflate all perspective layouts, apply transforms, and push to provider
|
|
255
|
+
for (const [perspId, deflated] of Object.entries(persisted.layouts)) {
|
|
256
|
+
try {
|
|
257
|
+
const layout = await this.inflate(deflated);
|
|
258
|
+
this.transformations.getContributions()
|
|
259
|
+
.forEach(t => t.transformLayoutOnRestore(layout));
|
|
260
|
+
this.perspectiveService.setSavedLayout(perspId, layout);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
this.logger.warn(`Could not inflate layout for perspective '${perspId}'`, error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
// Migration: try legacy single-layout key. The key is deliberately left in place here:
|
|
267
|
+
// `storePerspectiveLayouts` rewrites it (default perspective only) or clears it (perspectives
|
|
268
|
+
// in use) on shutdown, so an unclean exit right after startup doesn't lose the layout.
|
|
269
|
+
const legacyData = await this.storageService.getData<string>(this.storageKey);
|
|
270
|
+
if (legacyData) {
|
|
271
|
+
try {
|
|
272
|
+
const layout = await this.inflate(legacyData);
|
|
273
|
+
this.transformations.getContributions()
|
|
274
|
+
.forEach(t => t.transformLayoutOnRestore(layout));
|
|
275
|
+
this.perspectiveService.setSavedLayout(this.perspectiveService.defaultPerspectiveId, layout);
|
|
276
|
+
activeId = this.perspectiveService.defaultPerspectiveId;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.logger.warn('Could not inflate legacy layout for migration', error);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (activeId) {
|
|
284
|
+
const accepted = this.perspectiveService.setActivePerspectiveId(activeId);
|
|
285
|
+
if (!accepted) {
|
|
286
|
+
activeId = this.perspectiveService.getActivePerspectiveId();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Apply the active perspective's layout to the shell
|
|
291
|
+
const effectiveId = activeId ?? this.perspectiveService.getActivePerspectiveId();
|
|
292
|
+
let activeLayout = this.perspectiveService.getSavedLayout(effectiveId);
|
|
293
|
+
|
|
294
|
+
// Fallback: if the active perspective has no saved layout, try the default
|
|
295
|
+
if (!activeLayout && effectiveId !== this.perspectiveService.defaultPerspectiveId) {
|
|
296
|
+
this.logger.warn(`No saved layout for perspective '${effectiveId}', falling back to default.`);
|
|
297
|
+
const defaultId = this.perspectiveService.defaultPerspectiveId;
|
|
298
|
+
this.perspectiveService.setActivePerspectiveId(defaultId);
|
|
299
|
+
activeLayout = this.perspectiveService.getSavedLayout(defaultId);
|
|
187
300
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
301
|
+
|
|
302
|
+
if (activeLayout) {
|
|
303
|
+
await app.shell.setLayoutData(activeLayout);
|
|
304
|
+
const restoredId = this.perspectiveService.getActivePerspectiveId();
|
|
305
|
+
this.perspectiveService.onLayoutRestored(restoredId);
|
|
306
|
+
this.logger.info('<<< The layout has been successfully restored.');
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Even with no layout to restore, ensure chrome is applied for whatever perspective is active
|
|
311
|
+
this.perspectiveService.onLayoutRestored(this.perspectiveService.getActivePerspectiveId());
|
|
312
|
+
this.logger.info('<<< Nothing to restore.');
|
|
313
|
+
return false;
|
|
193
314
|
}
|
|
194
315
|
|
|
195
316
|
protected isWidgetProperty(propertyName: string): boolean {
|
|
@@ -87,6 +87,11 @@ export abstract class AbstractViewContribution<T extends Widget> implements Comm
|
|
|
87
87
|
return this.options.defaultWidgetOptions;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** The widget ID used for shell operations — `viewContainerId` if set, otherwise `widgetId`. */
|
|
91
|
+
get effectiveWidgetId(): string {
|
|
92
|
+
return this.options.viewContainerId || this.options.widgetId;
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
get widget(): Promise<T> {
|
|
91
96
|
return this.widgetManager.getOrCreateWidget(this.viewId);
|
|
92
97
|
}
|
|
@@ -97,7 +102,7 @@ export abstract class AbstractViewContribution<T extends Widget> implements Comm
|
|
|
97
102
|
|
|
98
103
|
async openView(args: Partial<OpenViewArguments> = {}): Promise<T> {
|
|
99
104
|
const shell = this.shell;
|
|
100
|
-
const widget = await this.widgetManager.getOrCreateWidget(this.
|
|
105
|
+
const widget = await this.widgetManager.getOrCreateWidget(this.effectiveWidgetId);
|
|
101
106
|
const tabBar = shell.getTabBarFor(widget);
|
|
102
107
|
const area = shell.getAreaFor(widget);
|
|
103
108
|
if (!tabBar) {
|
|
@@ -35,6 +35,8 @@ export class StatusBarImpl extends ReactWidget implements StatusBar {
|
|
|
35
35
|
protected backgroundColor: string | undefined;
|
|
36
36
|
protected color: string | undefined;
|
|
37
37
|
|
|
38
|
+
protected preferencesReady = false;
|
|
39
|
+
|
|
38
40
|
constructor(
|
|
39
41
|
@inject(CommandService) protected readonly commands: CommandService,
|
|
40
42
|
@inject(LabelParser) protected readonly entryService: LabelParser,
|
|
@@ -50,19 +52,27 @@ export class StatusBarImpl extends ReactWidget implements StatusBar {
|
|
|
50
52
|
// Hide the status bar until the `workbench.statusBar.visible` preference returns with a `true` value.
|
|
51
53
|
this.hide();
|
|
52
54
|
this.preferences.ready.then(() => {
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
+
this.preferencesReady = true;
|
|
56
|
+
this.updateVisibility();
|
|
55
57
|
});
|
|
56
58
|
this.toDispose.push(
|
|
57
59
|
this.preferences.onPreferenceChanged(preference => {
|
|
58
60
|
if (preference.preferenceName === 'workbench.statusBar.visible') {
|
|
59
|
-
this.
|
|
61
|
+
this.updateVisibility();
|
|
60
62
|
}
|
|
61
63
|
})
|
|
62
64
|
);
|
|
63
65
|
this.toDispose.push(this.viewModel.onDidChange(() => this.debouncedUpdate()));
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
protected updateVisibility(): void {
|
|
69
|
+
if (!this.preferencesReady) {
|
|
70
|
+
return; // Don't change visibility until preferences have loaded
|
|
71
|
+
}
|
|
72
|
+
const prefHides = !this.preferences.get<boolean>('workbench.statusBar.visible', true);
|
|
73
|
+
this.setHidden(prefHides);
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
protected debouncedUpdate = debounce(() => this.update(), 50);
|
|
67
77
|
|
|
68
78
|
protected get ready(): Promise<void> {
|
|
@@ -99,6 +99,22 @@
|
|
|
99
99
|
background: var(--theia-list-activeSelectionBackground);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
.theia-select-component-dropdown .theia-select-component-option.disabled,
|
|
103
|
+
.theia-select-component-dropdown .theia-select-component-option.disabled .theia-select-component-option-detail {
|
|
104
|
+
color: var(--theia-disabledForeground);
|
|
105
|
+
cursor: default;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.theia-select-component-dropdown .theia-select-component-option.disabled .theia-select-component-option-value {
|
|
109
|
+
text-decoration: line-through;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Keep a hovered/selected disabled option readable against the active-selection background. */
|
|
113
|
+
.theia-select-component-dropdown .theia-select-component-option.disabled.selected,
|
|
114
|
+
.theia-select-component-dropdown .theia-select-component-option.disabled.selected .theia-select-component-option-detail {
|
|
115
|
+
color: var(--theia-list-activeSelectionForeground);
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
.theia-select-component-dropdown .theia-select-component-separator {
|
|
103
119
|
width: 84px;
|
|
104
120
|
height: 1px;
|
|
@@ -276,6 +276,11 @@
|
|
|
276
276
|
background: inherit;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/* Ensure the empty bottom panel keeps a top border separating it from the main panel. */
|
|
280
|
+
#theia-bottom-content-panel:not(:has(.lm-TabBar)) {
|
|
281
|
+
border-top: var(--theia-border-width) solid var(--theia-panel-border);
|
|
282
|
+
}
|
|
283
|
+
|
|
279
284
|
#theia-bottom-content-panel .lm-TabBar-tab {
|
|
280
285
|
background: inherit;
|
|
281
286
|
}
|
|
@@ -63,7 +63,7 @@ body.theia-no-open-workspace #theia-statusBar {
|
|
|
63
63
|
padding-inline: var(--theia-ui-padding);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
#theia-statusBar .area.left .element.has-background {
|
|
66
|
+
#theia-statusBar .area.left .element.has-background:not(#session-preference-status) {
|
|
67
67
|
margin-left: calc(-1 * var(--theia-ui-padding));
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -21,6 +21,10 @@ import { TreeImpl, CompositeTreeNode, TreeNode } from './tree';
|
|
|
21
21
|
import { TreeModel } from './tree-model';
|
|
22
22
|
import { ExpandableTreeNode } from './tree-expansion';
|
|
23
23
|
import { TreeLabelProvider } from './tree-label-provider';
|
|
24
|
+
import { MockTreeModel } from './test/mock-tree-model';
|
|
25
|
+
import { MockLogger } from '../../common/test/mock-logger';
|
|
26
|
+
import { ILogger } from '../../common';
|
|
27
|
+
import { Deferred, timeout } from '../../common/promise-util';
|
|
24
28
|
|
|
25
29
|
@injectable()
|
|
26
30
|
class ConsistencyTestTree extends TreeImpl {
|
|
@@ -69,6 +73,48 @@ function createConsistencyTestRoot(rootName: string): CompositeTreeNode {
|
|
|
69
73
|
return root;
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
/**
|
|
77
|
+
* A `TreeImpl` whose `resolveChildren` can be stalled or overridden per node id, to
|
|
78
|
+
* simulate slow child resolution (e.g. a file system request) in tests.
|
|
79
|
+
*/
|
|
80
|
+
@injectable()
|
|
81
|
+
class StallingTestTree extends TreeImpl {
|
|
82
|
+
|
|
83
|
+
protected readonly stalled = new Map<string, Deferred<TreeNode[]>>();
|
|
84
|
+
protected readonly overrides = new Map<string, () => TreeNode[]>();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Stall the *next* `resolveChildren` for `id` on the returned deferred. Later
|
|
88
|
+
* resolutions for the same id proceed normally, so the stall cannot leak into
|
|
89
|
+
* unrelated refreshes (e.g. the cascade of a newly set root).
|
|
90
|
+
*/
|
|
91
|
+
stallNextResolve(id: string): Deferred<TreeNode[]> {
|
|
92
|
+
const gate = new Deferred<TreeNode[]>();
|
|
93
|
+
this.stalled.set(id, gate);
|
|
94
|
+
return gate;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Make the *next* `resolveChildren` for `id` return `children()` instead of the current child nodes. */
|
|
98
|
+
overrideNextResolve(id: string, children: () => TreeNode[]): void {
|
|
99
|
+
this.overrides.set(id, children);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
protected override resolveChildren(parent: CompositeTreeNode): Promise<TreeNode[]> {
|
|
103
|
+
const gate = this.stalled.get(parent.id);
|
|
104
|
+
if (gate) {
|
|
105
|
+
this.stalled.delete(parent.id);
|
|
106
|
+
return gate.promise;
|
|
107
|
+
}
|
|
108
|
+
const override = this.overrides.get(parent.id);
|
|
109
|
+
if (override) {
|
|
110
|
+
this.overrides.delete(parent.id);
|
|
111
|
+
return Promise.resolve(override());
|
|
112
|
+
}
|
|
113
|
+
return super.resolveChildren(parent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
72
118
|
describe('Tree Consistency', () => {
|
|
73
119
|
|
|
74
120
|
it('setting different tree roots should finish', async () => {
|
|
@@ -102,4 +148,77 @@ describe('Tree Consistency', () => {
|
|
|
102
148
|
assert.ok(false, 'Resolving does not stop, attempts: ' + tree.resolveCounter);
|
|
103
149
|
});
|
|
104
150
|
|
|
151
|
+
describe('stale refreshes', () => {
|
|
152
|
+
|
|
153
|
+
let tree: StallingTestTree;
|
|
154
|
+
let model: TreeModel;
|
|
155
|
+
let loggedErrors: unknown[][];
|
|
156
|
+
|
|
157
|
+
beforeEach(async () => {
|
|
158
|
+
const container = createTreeTestContainer();
|
|
159
|
+
container.bind(StallingTestTree).toSelf();
|
|
160
|
+
container.rebind(TreeImpl).toService(StallingTestTree);
|
|
161
|
+
tree = container.get(StallingTestTree);
|
|
162
|
+
model = container.get<TreeModel>(TreeModel);
|
|
163
|
+
loggedErrors = [];
|
|
164
|
+
const logger = container.get<MockLogger>(ILogger);
|
|
165
|
+
logger.error = async (...args: unknown[]) => { loggedErrors.push(args); };
|
|
166
|
+
model.root = MockTreeModel.HIERARCHICAL_MOCK_ROOT();
|
|
167
|
+
// let the refresh cascade triggered by setting the root settle
|
|
168
|
+
await timeout(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('drops an in-flight refresh when the root is replaced meanwhile', async () => {
|
|
172
|
+
const stale = model.getNode('1.2') as CompositeTreeNode;
|
|
173
|
+
const gate = tree.stallNextResolve('1.2');
|
|
174
|
+
const pendingRefresh = tree.refresh(stale);
|
|
175
|
+
|
|
176
|
+
model.root = MockTreeModel.HIERARCHICAL_MOCK_ROOT();
|
|
177
|
+
gate.resolve(Array.from(stale.children));
|
|
178
|
+
|
|
179
|
+
assert.strictEqual(await pendingRefresh, undefined);
|
|
180
|
+
assert.deepStrictEqual(loggedErrors, []);
|
|
181
|
+
const fresh = model.getNode('1.2');
|
|
182
|
+
assert.ok(fresh);
|
|
183
|
+
assert.notStrictEqual(fresh, stale);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('does not resurrect a removed subtree when a stale refresh completes', async () => {
|
|
187
|
+
const target = model.getNode('1.2') as CompositeTreeNode;
|
|
188
|
+
const gate = tree.stallNextResolve('1.2');
|
|
189
|
+
const pendingRefresh = tree.refresh(target);
|
|
190
|
+
|
|
191
|
+
CompositeTreeNode.removeChild(target.parent as CompositeTreeNode, target, tree);
|
|
192
|
+
assert.strictEqual(model.getNode('1.2'), undefined);
|
|
193
|
+
gate.resolve(Array.from(target.children));
|
|
194
|
+
|
|
195
|
+
assert.strictEqual(await pendingRefresh, undefined);
|
|
196
|
+
assert.strictEqual(model.getNode('1.2'), undefined);
|
|
197
|
+
assert.strictEqual(model.getNode('1.2.1'), undefined);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('does not re-index a node object that a concurrent ancestor refresh replaced', async () => {
|
|
201
|
+
const stale = model.getNode('1.2') as CompositeTreeNode;
|
|
202
|
+
const gate = tree.stallNextResolve('1.2');
|
|
203
|
+
const pendingRefresh = tree.refresh(stale);
|
|
204
|
+
|
|
205
|
+
// an ancestor refresh yields a *fresh* object for the same id, as trees whose
|
|
206
|
+
// `resolveChildren` builds new nodes do
|
|
207
|
+
const parent = model.getNode('1') as CompositeTreeNode;
|
|
208
|
+
const replacement: CompositeTreeNode = { id: '1.2', name: '1.2', parent, children: [] };
|
|
209
|
+
tree.overrideNextResolve('1', () => parent.children.map(child => child.id === '1.2' ? replacement : child));
|
|
210
|
+
await tree.refresh(parent);
|
|
211
|
+
assert.strictEqual(model.getNode('1.2'), replacement);
|
|
212
|
+
|
|
213
|
+
gate.resolve(Array.from(stale.children));
|
|
214
|
+
|
|
215
|
+
assert.strictEqual(await pendingRefresh, undefined);
|
|
216
|
+
assert.deepStrictEqual(loggedErrors, []);
|
|
217
|
+
// the stale object must not take the replacement's place in the index, nor bring its children back
|
|
218
|
+
assert.strictEqual(model.getNode('1.2'), replacement);
|
|
219
|
+
assert.strictEqual(model.getNode('1.2.1'), undefined);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
});
|
|
223
|
+
|
|
105
224
|
});
|
|
@@ -64,28 +64,10 @@ export const TREE_NODE_CAPTION_CLASS = 'theia-TreeNodeCaption';
|
|
|
64
64
|
export const TREE_NODE_INDENT_GUIDE_CLASS = 'theia-tree-node-indent';
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Threshold in pixels to consider the view as being scrolled to the bottom
|
|
67
|
+
* Threshold in pixels to consider the view as being scrolled to the bottom.
|
|
68
68
|
*/
|
|
69
69
|
export const SCROLL_BOTTOM_THRESHOLD = 30;
|
|
70
70
|
|
|
71
|
-
/**
|
|
72
|
-
* Tree scroll event data.
|
|
73
|
-
*/
|
|
74
|
-
export interface TreeScrollEvent {
|
|
75
|
-
readonly scrollTop: number;
|
|
76
|
-
readonly scrollLeft: number;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Tree scroll state data.
|
|
81
|
-
*/
|
|
82
|
-
export interface TreeScrollState {
|
|
83
|
-
readonly scrollTop: number;
|
|
84
|
-
readonly isAtBottom: boolean;
|
|
85
|
-
readonly scrollHeight?: number;
|
|
86
|
-
readonly clientHeight?: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
71
|
export const TreeProps = Symbol('TreeProps');
|
|
90
72
|
|
|
91
73
|
/**
|
|
@@ -189,8 +171,8 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
189
171
|
protected searchBox: SearchBox;
|
|
190
172
|
protected searchHighlights: Map<string, TreeDecoration.CaptionHighlight>;
|
|
191
173
|
|
|
192
|
-
protected readonly
|
|
193
|
-
readonly
|
|
174
|
+
protected readonly onAtBottomStateChangeEmitter = new Emitter<boolean>();
|
|
175
|
+
readonly onAtBottomStateChange: TheiaEvent<boolean> = this.onAtBottomStateChangeEmitter.event;
|
|
194
176
|
|
|
195
177
|
@inject(TreeDecoratorService)
|
|
196
178
|
protected readonly decoratorService: TreeDecoratorService;
|
|
@@ -285,7 +267,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
285
267
|
this.node.addEventListener('mouseup', this.handleMiddleClickEvent.bind(this));
|
|
286
268
|
this.node.addEventListener('auxclick', this.handleMiddleClickEvent.bind(this));
|
|
287
269
|
this.toDispose.pushAll([
|
|
288
|
-
this.
|
|
270
|
+
this.onAtBottomStateChangeEmitter,
|
|
289
271
|
this.model,
|
|
290
272
|
this.model.onChanged(() => this.updateRows()),
|
|
291
273
|
this.model.onSelectionChanged(() => this.scheduleUpdateScrollToRow({ resize: false })),
|
|
@@ -551,7 +533,7 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
551
533
|
rows={rows}
|
|
552
534
|
renderNodeRow={this.renderNodeRow}
|
|
553
535
|
scrollToRow={this.scrollToRow}
|
|
554
|
-
|
|
536
|
+
onAtBottomStateChangeEmitter={this.onAtBottomStateChangeEmitter}
|
|
555
537
|
{...this.props.viewProps}
|
|
556
538
|
/>;
|
|
557
539
|
}
|
|
@@ -1201,39 +1183,6 @@ export class TreeWidget extends ReactWidget implements StatefulWidget {
|
|
|
1201
1183
|
return this.node;
|
|
1202
1184
|
}
|
|
1203
1185
|
|
|
1204
|
-
/**
|
|
1205
|
-
* Get the current scroll state from the virtualized view.
|
|
1206
|
-
* This should be used instead of accessing the DOM scroll properties directly
|
|
1207
|
-
* when the tree is virtualized.
|
|
1208
|
-
*/
|
|
1209
|
-
protected getVirtualizedScrollState(): TreeScrollState | undefined {
|
|
1210
|
-
return this.view?.getScrollState();
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
/**
|
|
1214
|
-
* Check if the tree is scrolled to the bottom.
|
|
1215
|
-
* Works with both virtualized and non-virtualized trees.
|
|
1216
|
-
*/
|
|
1217
|
-
isScrolledToBottom(): boolean {
|
|
1218
|
-
if (this.props.virtualized !== false && this.view) {
|
|
1219
|
-
// Use virtualized scroll state
|
|
1220
|
-
const scrollState = this.getVirtualizedScrollState();
|
|
1221
|
-
return scrollState?.isAtBottom ?? true;
|
|
1222
|
-
} else {
|
|
1223
|
-
// Fallback to DOM-based calculation for non-virtualized trees
|
|
1224
|
-
const scrollContainer = this.node;
|
|
1225
|
-
const scrollHeight = scrollContainer.scrollHeight;
|
|
1226
|
-
const scrollTop = scrollContainer.scrollTop;
|
|
1227
|
-
const clientHeight = scrollContainer.clientHeight;
|
|
1228
|
-
|
|
1229
|
-
if (scrollHeight <= clientHeight) {
|
|
1230
|
-
return true;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
return scrollHeight - scrollTop - clientHeight <= SCROLL_BOTTOM_THRESHOLD;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
1186
|
protected override onAfterAttach(msg: Message): void {
|
|
1238
1187
|
const up = [
|
|
1239
1188
|
Key.ARROW_UP,
|
|
@@ -1658,11 +1607,14 @@ export namespace TreeWidget {
|
|
|
1658
1607
|
/**
|
|
1659
1608
|
* Optional scroll event emitter.
|
|
1660
1609
|
*/
|
|
1661
|
-
|
|
1610
|
+
/**
|
|
1611
|
+
* Optional emitter fired when the at-bottom state changes.
|
|
1612
|
+
*/
|
|
1613
|
+
onAtBottomStateChangeEmitter?: Emitter<boolean>
|
|
1662
1614
|
}
|
|
1663
1615
|
export class View extends React.Component<ViewProps> {
|
|
1664
1616
|
list: VirtuosoHandle | undefined;
|
|
1665
|
-
|
|
1617
|
+
|
|
1666
1618
|
/**
|
|
1667
1619
|
* Ensure the selected row is scrolled into view when virtualization finishes updating.
|
|
1668
1620
|
*/
|
|
@@ -1687,28 +1639,14 @@ export namespace TreeWidget {
|
|
|
1687
1639
|
}
|
|
1688
1640
|
|
|
1689
1641
|
override render(): React.ReactNode {
|
|
1690
|
-
const { rows, width, height, scrollToRow, renderNodeRow,
|
|
1642
|
+
const { rows, width, height, scrollToRow, renderNodeRow, onAtBottomStateChangeEmitter, ...other } = this.props;
|
|
1691
1643
|
return <Virtuoso
|
|
1692
1644
|
ref={(list: VirtuosoHandle | null) => {
|
|
1693
1645
|
this.list = (list || undefined);
|
|
1694
1646
|
this.scrollIntoViewIfNeeded();
|
|
1695
1647
|
}}
|
|
1696
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1697
|
-
onScroll={(e: any) => {
|
|
1698
|
-
const scrollTop = e.target.scrollTop;
|
|
1699
|
-
const scrollHeight = e.target.scrollHeight;
|
|
1700
|
-
const clientHeight = e.target.clientHeight;
|
|
1701
|
-
const isAtBottom = scrollHeight - scrollTop - clientHeight <= SCROLL_BOTTOM_THRESHOLD;
|
|
1702
|
-
|
|
1703
|
-
// Store scroll state before firing the event to prevent jitter during inference and scrolling
|
|
1704
|
-
this.lastScrollState = { scrollTop, isAtBottom, scrollHeight, clientHeight };
|
|
1705
|
-
onScrollEmitter?.fire({ scrollTop, scrollLeft: e.target.scrollLeft || 0 });
|
|
1706
|
-
}}
|
|
1707
1648
|
atBottomStateChange={(atBottom: boolean) => {
|
|
1708
|
-
|
|
1709
|
-
...this.lastScrollState,
|
|
1710
|
-
isAtBottom: atBottom
|
|
1711
|
-
};
|
|
1649
|
+
onAtBottomStateChangeEmitter?.fire(atBottom);
|
|
1712
1650
|
}}
|
|
1713
1651
|
atBottomThreshold={SCROLL_BOTTOM_THRESHOLD}
|
|
1714
1652
|
totalCount={rows.length}
|
|
@@ -1721,9 +1659,5 @@ export namespace TreeWidget {
|
|
|
1721
1659
|
{...other}
|
|
1722
1660
|
/>;
|
|
1723
1661
|
}
|
|
1724
|
-
|
|
1725
|
-
getScrollState(): TreeScrollState {
|
|
1726
|
-
return { ...this.lastScrollState };
|
|
1727
|
-
}
|
|
1728
1662
|
}
|
|
1729
1663
|
}
|
package/src/browser/tree/tree.ts
CHANGED
|
@@ -354,6 +354,12 @@ export class TreeImpl implements Tree {
|
|
|
354
354
|
result = parent;
|
|
355
355
|
const children = await this.resolveChildren(parent);
|
|
356
356
|
if (cancellationToken?.isCancellationRequested) { return; }
|
|
357
|
+
if (this.validateNode(parent) !== parent) {
|
|
358
|
+
// the tree changed while children were being resolved (e.g. a new root was
|
|
359
|
+
// set or the node was removed): drop the stale refresh
|
|
360
|
+
this.logger.debug(`Refresh of node '${parent.id}' dropped, it no longer belongs to the tree.`);
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
357
363
|
result = await this.setChildren(parent, children);
|
|
358
364
|
if (cancellationToken?.isCancellationRequested) { return; }
|
|
359
365
|
} finally {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
export * from './widget';
|
|
18
|
+
export * from './tab-bar-tracker';
|
|
18
19
|
export * from './react-renderer';
|
|
19
20
|
export * from './react-widget';
|
|
20
21
|
export * from './extractable-widget';
|