From 6a4a99c3a7a42d98ea93990af32e4a69cfb39552 Mon Sep 17 00:00:00 2001 From: Primiano Tucci Date: Sat, 7 Sep 2024 12:01:19 +0100 Subject: [PATCH] ui: Add WorkspaceManager Encapsulate the workspace-management logic into a class rather than being baked into Globals. This makes it easier to move it into AppImpl into next CLs. Change-Id: I2bb562c9a5ab176615a7e59ab81d610acb68caa9 --- ui/src/common/plugins.ts | 6 +++- ui/src/core/workspace_manager.ts | 49 +++++++++++++++++++++++++++ ui/src/core_plugins/commands/index.ts | 14 ++++---- ui/src/frontend/globals.ts | 24 +++++-------- ui/src/public/trace.ts | 3 +- ui/src/public/workspace.ts | 8 +++++ 6 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 ui/src/core/workspace_manager.ts diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts index a2251eb734..4548ab5f22 100644 --- a/ui/src/common/plugins.ts +++ b/ui/src/common/plugins.ts @@ -32,7 +32,7 @@ import {defaultPlugins} from '../core/default_plugins'; import {PromptOption} from '../public/omnibox'; import {DisposableStack} from '../base/disposable_stack'; import {TraceInfo} from '../public/trace_info'; -import {Workspace} from '../public/workspace'; +import {Workspace, WorkspaceManager} from '../public/workspace'; import {Migrate, Store} from '../base/store'; import {LegacyDetailsPanel} from '../public/details_panel'; @@ -236,6 +236,10 @@ class PluginContextTraceImpl implements Trace, Disposable { return globals.store.createSubStore(['plugins', this.pluginId], migrate); } + get workspaces(): WorkspaceManager { + return globals.workspaceManager; + } + get traceInfo(): TraceInfo { return globals.traceContext; } diff --git a/ui/src/core/workspace_manager.ts b/ui/src/core/workspace_manager.ts new file mode 100644 index 0000000000..bcf26d9496 --- /dev/null +++ b/ui/src/core/workspace_manager.ts @@ -0,0 +1,49 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {assertTrue} from '../base/logging'; +import {Workspace, WorkspaceManager} from '../public/workspace'; + +const DEFAULT_WORKSPACE_NAME = 'Default Workspace'; + +export class WorkspaceManagerImpl implements WorkspaceManager { + private _workspaces: Workspace[] = []; + private _currentWorkspace: Workspace; + + constructor() { + // TS compiler cannot see that we are indirectly initializing + // _currentWorkspace via resetWorkspaces(), hence the re-assignment. + this._currentWorkspace = this.createEmptyWorkspace(DEFAULT_WORKSPACE_NAME); + } + + createEmptyWorkspace(displayName: string): Workspace { + const workspace = new Workspace(displayName); + this._workspaces.push(workspace); + return workspace; + } + + switchWorkspace(workspace: Workspace): void { + // If this fails the workspace doesn't come from createEmptyWorkspace(). + assertTrue(this._workspaces.includes(workspace)); + this._currentWorkspace = workspace; + } + + get all(): ReadonlyArray { + return this._workspaces; + } + + get currentWorkspace() { + return this._currentWorkspace; + } +} diff --git a/ui/src/core_plugins/commands/index.ts b/ui/src/core_plugins/commands/index.ts index 0221484e6a..85d62544ca 100644 --- a/ui/src/core_plugins/commands/index.ts +++ b/ui/src/core_plugins/commands/index.ts @@ -30,7 +30,6 @@ import { addSqlTableTabImpl, SqlTableTabConfig, } from '../../frontend/sql_table_tab'; -import {Workspace} from '../../public/workspace'; const SQL_STATS = ` with first as (select started as ts from sqlstats limit 1) @@ -298,9 +297,9 @@ class CoreCommandsPlugin implements PerfettoPlugin { callback: async () => { const name = await ctx.omnibox.prompt('Give it a name...'); if (name === undefined || name === '') return; - const newWorkspace = new Workspace(name); - globals.workspaces.push(newWorkspace); - globals.switchWorkspace(newWorkspace); + globals.workspaceManager.switchWorkspace( + globals.workspaceManager.createEmptyWorkspace(name), + ); }, }); @@ -308,7 +307,8 @@ class CoreCommandsPlugin implements PerfettoPlugin { id: 'switchWorkspace', name: 'Switch workspace', callback: async () => { - const options = globals.workspaces.map((ws) => { + const workspaceManager = globals.workspaceManager; + const options = workspaceManager.all.map((ws) => { return {key: ws.uuid, displayName: ws.displayName}; }); const workspaceUuid = await ctx.omnibox.prompt( @@ -316,11 +316,11 @@ class CoreCommandsPlugin implements PerfettoPlugin { options, ); if (workspaceUuid === undefined) return; - const workspace = globals.workspaces.find( + const workspace = workspaceManager.all.find( (ws) => ws.uuid === workspaceUuid, ); if (workspace) { - globals.switchWorkspace(workspace); + workspaceManager.switchWorkspace(workspace); } }, }); diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts index 7553149be5..cd6af73633 100644 --- a/ui/src/frontend/globals.ts +++ b/ui/src/frontend/globals.ts @@ -49,12 +49,13 @@ import { import {TraceInfo} from '../public/trace_info'; import {Registry} from '../base/registry'; import {SidebarMenuItem} from '../public/sidebar'; -import {Workspace} from '../public/workspace'; +import {Workspace, WorkspaceManager} from '../public/workspace'; import {ratelimit} from './rate_limiters'; import {NoteManagerImpl} from '../core/note_manager'; import {SearchManagerImpl} from '../core/search_manager'; import {SearchResult} from '../public/search'; import {selectCurrentSearchResult} from './search_handler'; +import {WorkspaceManagerImpl} from '../core/workspace_manager'; const INSTANT_FOCUS_DURATION = 1n; const INCOMPLETE_SLICE_DURATION = 30_000n; @@ -174,8 +175,6 @@ interface SqlPackage { readonly modules: SqlModule[]; } -const DEFAULT_WORKSPACE_NAME = 'Default Workspace'; - /** * Global accessors for state/dispatch in the frontend. */ @@ -216,8 +215,7 @@ class Globals { private _noteManager = new NoteManagerImpl(this._selectionManager); private _hasFtrace: boolean = false; private _searchOverviewTrack?: SearchOverviewTrack; - readonly workspaces: Workspace[] = []; - private _currentWorkspace: Workspace; + private _workspaceManager = new WorkspaceManagerImpl(); readonly omnibox = new OmniboxManagerImpl(); // TODO(primiano): this is a hack to work around circular deps in globals. @@ -237,11 +235,11 @@ class Globals { readonly sidebarMenuItems = new Registry((m) => m.commandId); get workspace(): Workspace { - return this._currentWorkspace; + return this._workspaceManager.currentWorkspace; } - switchWorkspace(workspace: Workspace): void { - this._currentWorkspace = workspace; + get workspaceManager(): WorkspaceManager { + return this._workspaceManager; } // This is the app's equivalent of a plugin's onTraceLoad() function. @@ -252,10 +250,7 @@ class Globals { this.traceContext = traceCtx; // Reset workspaces - this.workspaces.length = 0; - const defaultWorkspace = new Workspace(DEFAULT_WORKSPACE_NAME); - this.workspaces.push(defaultWorkspace); - this._currentWorkspace = defaultWorkspace; + this._workspaceManager = new WorkspaceManagerImpl(); const {start, end} = traceCtx; this._timeline = new TimelineImpl(new TimeSpan(start, end)); @@ -269,7 +264,7 @@ class Globals { this._searchManager = new SearchManagerImpl({ timeline: this._timeline, trackManager: this._trackManager, - workspace: this._currentWorkspace, + workspace: this._workspaceManager.currentWorkspace, engine, onResultStep: (step: SearchResult) => { selectCurrentSearchResult( @@ -316,9 +311,6 @@ class Globals { constructor() { const {start, end} = defaultTraceContext; this._timeline = new TimelineImpl(new TimeSpan(start, end)); - const defaultWorkspace = new Workspace(DEFAULT_WORKSPACE_NAME); - this.workspaces.push(defaultWorkspace); - this._currentWorkspace = defaultWorkspace; this._selectionManager.onSelectionChange = ( _s: Selection, diff --git a/ui/src/public/trace.ts b/ui/src/public/trace.ts index 265fa15f50..a3e379c6a4 100644 --- a/ui/src/public/trace.ts +++ b/ui/src/public/trace.ts @@ -19,8 +19,8 @@ import {App} from './app'; import {TabManager} from './tab'; import {TrackManager} from './track'; import {Timeline} from './timeline'; +import {Workspace, WorkspaceManager} from './workspace'; import {LegacyDetailsPanel} from './details_panel'; -import {Workspace} from './workspace'; import {SelectionManager} from './selection'; /** @@ -38,6 +38,7 @@ export interface Trace extends App { readonly tracks: TrackManager; readonly selection: SelectionManager; readonly workspace: Workspace; + readonly workspaces: WorkspaceManager; readonly traceInfo: TraceInfo; // TODO(primiano): remove this once the Legacy vs non-Legacy details panel is diff --git a/ui/src/public/workspace.ts b/ui/src/public/workspace.ts index 6ba83b2c6e..d8ff9fc48e 100644 --- a/ui/src/public/workspace.ts +++ b/ui/src/public/workspace.ts @@ -16,6 +16,14 @@ import {Optional} from '../base/utils'; import {uuidv4} from '../base/uuid'; import {raf} from '../core/raf_scheduler'; +export interface WorkspaceManager { + // This is the same of ctx.workspace, exposed for consistency also here. + readonly currentWorkspace: Workspace; + readonly all: ReadonlyArray; + createEmptyWorkspace(displayName: string): Workspace; + switchWorkspace(workspace: Workspace): void; +} + export class TrackNode { // This is the URI of the track this node references. public readonly uri: string;