Skip to content

Commit ee5a4e9

Browse files
committed
bigtrace-ui: cleanup
1 parent 607e7ad commit ee5a4e9

20 files changed

+1892
-873
lines changed

python/tools/check_imports.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,20 @@
121121

122122
# Bigtrace deps.
123123
('/bigtrace/*', [
124-
'/bigtrace/*', '/base/*', '/widgets/*', '/trace_processor/*',
125-
'/components/*', '/public/*', '/frontend/theme_provider',
126-
'/core/live_reload', '/core/raf_scheduler', '/core/local_storage'
124+
'/bigtrace/*',
125+
'/base/*',
126+
'/widgets/*',
127+
'/trace_processor/*',
128+
'/components/*',
129+
'/public/*',
130+
'/frontend/theme_provider',
131+
'/core/live_reload',
132+
'/core/raf_scheduler',
133+
'/core/local_storage',
134+
'/core/omnibox_manager',
135+
'/core/command_manager',
136+
'/plugins/dev.perfetto.QueryPage/table_list',
137+
'/plugins/dev.perfetto.SqlModules/sql_modules',
127138
]),
128139

129140
# TODO(primiano): misc tech debt.

ui/src/assets/bigtrace.scss

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// BigTrace-specific styles. Classes use the .pf-bt- prefix to avoid
16+
// collisions with existing Perfetto UI classes.
17+
18+
// ---------- Settings page ----------
19+
20+
.pf-bt-settings-card-wrapper--disabled {
21+
opacity: 0.6;
22+
}
23+
24+
.pf-bt-settings-controls--disabled {
25+
pointer-events: none;
26+
}
27+
28+
.pf-bt-settings-category-header {
29+
display: flex;
30+
align-items: center;
31+
gap: 16px;
32+
}

ui/src/assets/perfetto.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
@import "widgets/ui_main";
9090
@import "widgets/virtual_overlay_canvas";
9191

92+
// BigTrace-specific styles
93+
@import "bigtrace";
94+
9295
// Auto generated imports
9396
@import "../gen/all_plugins";
9497
@import "../gen/all_core_plugins";

ui/src/bigtrace/bigtrace_app.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {CommandManagerImpl} from '../core/command_manager';
16+
import {OmniboxManagerImpl} from '../core/omnibox_manager';
17+
18+
// Lightweight singleton for the BigTrace app. Holds only the managers needed
19+
// for the omnibox and command palette — no WASM, no trace processor.
20+
export class BigTraceApp {
21+
readonly omnibox: OmniboxManagerImpl;
22+
readonly commands: CommandManagerImpl;
23+
24+
private constructor() {
25+
this.omnibox = new OmniboxManagerImpl();
26+
this.commands = new CommandManagerImpl(this.omnibox);
27+
}
28+
29+
private static _instance?: BigTraceApp;
30+
31+
static get instance(): BigTraceApp {
32+
if (!BigTraceApp._instance) {
33+
BigTraceApp._instance = new BigTraceApp();
34+
}
35+
return BigTraceApp._instance;
36+
}
37+
}

ui/src/bigtrace/index.ts

Lines changed: 117 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ import {bigTraceSettingsStorage} from './settings/bigtrace_settings_storage';
2727
import {queryState} from './query/query_state';
2828
import {SettingsPage} from './pages/settings_page';
2929
import {Topbar} from './layout/topbar';
30-
import {Sidebar, SidebarMenuItem, SIDEBAR_SECTIONS} from './layout/sidebar';
30+
import {BigTraceApp as BigTraceAppSingleton} from './bigtrace_app';
31+
import {OmniboxMode} from '../core/omnibox_manager';
32+
import {Sidebar, SidebarMenuItem} from './layout/sidebar';
33+
import {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
34+
import {initAssets} from '../base/assets';
35+
import {getCurrentRoute, initRouter} from './router';
36+
import {Routes} from './routes';
3137

3238
function getRoot() {
3339
// Works out the root directory where the content should be served from
@@ -82,6 +88,7 @@ function main() {
8288
});
8389
}
8490
setupContentSecurityPolicy();
91+
initAssets();
8592
// Settings will be lazy-loaded by the UI components that require them.
8693

8794
// Load the css. The load is asynchronous and the CSS is not ready by the time
@@ -101,7 +108,7 @@ function main() {
101108
document.head.append(css);
102109

103110
// Add Error handlers for JS error and for uncaught exceptions in promises.
104-
addErrorHandler((err: ErrorDetails) => console.log(err.message, err.stack));
111+
addErrorHandler((err: ErrorDetails) => console.error(err.message, err.stack));
105112
window.addEventListener('error', (e) => reportError(e));
106113
window.addEventListener('unhandledrejection', (e) => reportError(e));
107114

@@ -117,134 +124,158 @@ function main() {
117124
cssLoadPromise.then(() => onCssLoaded());
118125
}
119126

120-
class BigTraceApp implements m.ClassComponent {
127+
// Allows the sidebar toggle command (registered globally) to reach into the
128+
// BigTraceApp component's local state.
129+
let sidebarToggleFn: (() => void) | undefined;
130+
131+
class BigTraceLayout implements m.ClassComponent {
121132
private sidebarVisible = true;
122133

123134
oninit() {
124135
bigTraceSettingsStorage.loadSettings();
136+
sidebarToggleFn = () => {
137+
this.sidebarVisible = !this.sidebarVisible;
138+
};
125139
}
126140

127141
view(vnode: m.Vnode) {
128-
const currentRoute = m.route.get();
142+
const currentRoute = getCurrentRoute();
129143

130144
const items: SidebarMenuItem[] = [
131145
{
132146
section: 'home',
133147
text: 'Home',
134-
href: '#!/',
148+
href: `#!${Routes.HOME}`,
135149
icon: 'home',
136-
active: currentRoute === '/' || currentRoute === '',
150+
active: currentRoute === Routes.HOME || currentRoute === '',
137151
onclick: () => {},
138152
},
139153
{
140154
section: 'query',
141155
text: 'Query Editor',
142-
href: '#!/query',
156+
href: `#!${Routes.QUERY}`,
143157
icon: 'line_style',
144-
active: currentRoute === '/query',
158+
active: currentRoute === Routes.QUERY,
145159
onclick: () => {},
146160
},
147161
{
148162
section: 'settings',
149163
text: 'Settings',
150-
href: '#!/settings',
164+
href: `#!${Routes.SETTINGS}`,
151165
icon: 'settings',
152-
active: currentRoute === '/settings',
166+
active: currentRoute === Routes.SETTINGS,
153167
onclick: () => {},
154168
},
155169
];
156170

157-
const currentItem = items.find((item) => item.active);
158-
const title = currentItem
159-
? `${SIDEBAR_SECTIONS[currentItem.section].title} > ${currentItem.text}`
160-
: '';
161-
162-
return m(
163-
'.pf-ui-main',
164-
{
165-
style: {
166-
display: 'flex',
167-
height: '100vh',
168-
overflow: 'hidden',
171+
return m('main.pf-ui-main', [
172+
m(Sidebar, {
173+
items,
174+
onToggleSidebar: () => {
175+
this.sidebarVisible = !this.sidebarVisible;
169176
},
170-
},
171-
[
172-
// Left Sidebar
173-
m(Sidebar, {
174-
items,
175-
onToggleSidebar: () => {
176-
this.sidebarVisible = !this.sidebarVisible;
177-
},
178-
visible: this.sidebarVisible,
179-
}),
180-
181-
m(
182-
'.pf-main-content',
183-
{
184-
style: {
185-
display: 'flex',
186-
flexDirection: 'column',
187-
flex: 1,
188-
overflow: 'hidden',
189-
},
190-
},
191-
[
192-
m(Topbar, {
193-
sidebarVisible: this.sidebarVisible,
194-
onToggleSidebar: () => {
195-
this.sidebarVisible = !this.sidebarVisible;
196-
},
197-
title: title,
198-
}),
199-
vnode.children,
200-
],
201-
),
202-
],
203-
);
177+
visible: this.sidebarVisible,
178+
}),
179+
m(Topbar, {sidebarVisible: this.sidebarVisible}),
180+
m('.pf-ui-main__page-container', vnode.children),
181+
]);
204182
}
205183
}
206184

207-
const BigTraceLayout: m.Component = {
208-
view(vnode) {
185+
// Root component: routing, theme, hotkeys, and layout.
186+
// Uses m.mount (not m.route) so that all rendering goes through the raf
187+
// scheduler's mount system. m.route() caches the original m.mount and
188+
// bypasses the raf scheduler, which breaks cross-tree redraws for
189+
// portal-based popups (e.g. the omnibox dropdown).
190+
class BigTraceRoot implements m.ClassComponent {
191+
private prevRoute = '';
192+
private queryInitialQuery: string | undefined;
193+
194+
view(): m.Children {
195+
const route = getCurrentRoute();
196+
197+
// Capture initialQuery on first navigation to /query.
198+
if (route === Routes.QUERY && this.prevRoute !== Routes.QUERY) {
199+
this.queryInitialQuery = queryState.initialQuery;
200+
queryState.initialQuery = undefined;
201+
}
202+
this.prevRoute = route;
203+
204+
const page = this.resolvePage(route);
205+
209206
const theme = settingsStorage.get('theme');
210207
const themeValue = theme ? theme.get() : 'light';
208+
209+
const commands = BigTraceAppSingleton.instance.commands;
210+
const hotkeys: HotkeyConfig[] = [];
211+
for (const {id, defaultHotkey} of commands.commands) {
212+
if (defaultHotkey) {
213+
hotkeys.push({
214+
callback: () => commands.runCommand(id),
215+
hotkey: defaultHotkey,
216+
});
217+
}
218+
}
219+
211220
return m(ThemeProvider, {theme: themeValue as 'dark' | 'light'}, [
212-
m(OverlayContainer, {fillHeight: true}, [m(BigTraceApp, vnode.children)]),
221+
m(
222+
HotkeyContext,
223+
{hotkeys, fillHeight: true, focusable: false},
224+
m(OverlayContainer, {fillHeight: true}, [m(BigTraceLayout, page)]),
225+
),
213226
]);
214-
},
215-
};
227+
}
216228

217-
function onCssLoaded() {
218-
// Clear all the contents of the initial page
219-
document.body.innerHTML = '';
229+
private resolvePage(route: string): m.Children {
230+
switch (route) {
231+
case Routes.QUERY:
232+
return m(QueryPage, {
233+
useBrushBackend: true,
234+
initialQuery: this.queryInitialQuery,
235+
});
236+
case Routes.SETTINGS:
237+
return m(SettingsPage);
238+
default:
239+
return m(HomePage);
240+
}
241+
}
242+
}
220243

221-
m.route.prefix = '#!';
222-
m.route(document.body, '/', {
223-
'/': {
224-
render: () => m(BigTraceLayout, m(HomePage)),
225-
},
226-
'/query': {
227-
onmatch: () => {
228-
const initialQuery = queryState.initialQuery;
229-
queryState.initialQuery = undefined;
230-
return {
231-
view: () =>
232-
m(
233-
BigTraceLayout,
234-
m(QueryPage, {
235-
useBrushBackend: true,
236-
initialQuery,
237-
}),
238-
),
239-
};
240-
},
244+
function registerCommands() {
245+
const app = BigTraceAppSingleton.instance;
246+
247+
app.commands.registerCommand({
248+
id: 'bigtrace.ToggleTheme',
249+
name: 'Toggle UI Theme (Dark/Light)',
250+
callback: () => {
251+
const theme = settingsStorage.get('theme');
252+
if (theme) theme.set(theme.get() === 'light' ? 'dark' : 'light');
241253
},
242-
'/settings': {
243-
render: () => m(BigTraceLayout, m(SettingsPage)),
254+
});
255+
256+
app.commands.registerCommand({
257+
id: 'bigtrace.OpenCommandPalette',
258+
name: 'Open command palette',
259+
callback: () => app.omnibox.setMode(OmniboxMode.Command),
260+
defaultHotkey: '!Mod+Shift+P',
261+
});
262+
263+
app.commands.registerCommand({
264+
id: 'bigtrace.ToggleLeftSidebar',
265+
name: 'Toggle left sidebar',
266+
callback: () => {
267+
sidebarToggleFn?.();
244268
},
269+
defaultHotkey: '!Mod+B',
245270
});
271+
}
246272

273+
function onCssLoaded() {
274+
document.body.innerHTML = '';
275+
initRouter();
276+
m.mount(document.body, BigTraceRoot);
247277
initLiveReload();
278+
registerCommands();
248279
}
249280

250281
main();

0 commit comments

Comments
 (0)