1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2024 The Android Open Source Project 2*6dbdd20aSAndroid Build Coastguard Worker// 3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*6dbdd20aSAndroid Build Coastguard Worker// 7*6dbdd20aSAndroid Build Coastguard Worker// http://www.apache.org/licenses/LICENSE-2.0 8*6dbdd20aSAndroid Build Coastguard Worker// 9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License. 14*6dbdd20aSAndroid Build Coastguard Worker 15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril'; 16*6dbdd20aSAndroid Build Coastguard Workerimport {Gate} from '../base/mithril_utils'; 17*6dbdd20aSAndroid Build Coastguard Workerimport {EmptyState} from '../widgets/empty_state'; 18*6dbdd20aSAndroid Build Coastguard Workerimport {raf} from '../core/raf_scheduler'; 19*6dbdd20aSAndroid Build Coastguard Workerimport {DetailsShell} from '../widgets/details_shell'; 20*6dbdd20aSAndroid Build Coastguard Workerimport {GridLayout, GridLayoutColumn} from '../widgets/grid_layout'; 21*6dbdd20aSAndroid Build Coastguard Workerimport {Section} from '../widgets/section'; 22*6dbdd20aSAndroid Build Coastguard Workerimport {Tree, TreeNode} from '../widgets/tree'; 23*6dbdd20aSAndroid Build Coastguard Workerimport {TraceImpl, TraceImplAttrs} from '../core/trace_impl'; 24*6dbdd20aSAndroid Build Coastguard Workerimport {MenuItem, PopupMenu2} from '../widgets/menu'; 25*6dbdd20aSAndroid Build Coastguard Workerimport {Button} from '../widgets/button'; 26*6dbdd20aSAndroid Build Coastguard Workerimport {CollapsiblePanel} from '../components/widgets/collapsible_panel'; 27*6dbdd20aSAndroid Build Coastguard Worker 28*6dbdd20aSAndroid Build Coastguard Workerexport type TabPanelAttrs = TraceImplAttrs; 29*6dbdd20aSAndroid Build Coastguard Worker 30*6dbdd20aSAndroid Build Coastguard Workerexport interface Tab { 31*6dbdd20aSAndroid Build Coastguard Worker // Unique key for this tab, passed to callbacks. 32*6dbdd20aSAndroid Build Coastguard Worker key: string; 33*6dbdd20aSAndroid Build Coastguard Worker 34*6dbdd20aSAndroid Build Coastguard Worker // Tab title to show on the tab handle. 35*6dbdd20aSAndroid Build Coastguard Worker title: m.Children; 36*6dbdd20aSAndroid Build Coastguard Worker 37*6dbdd20aSAndroid Build Coastguard Worker // Whether to show a close button on the tab handle or not. 38*6dbdd20aSAndroid Build Coastguard Worker // Default = false. 39*6dbdd20aSAndroid Build Coastguard Worker hasCloseButton?: boolean; 40*6dbdd20aSAndroid Build Coastguard Worker} 41*6dbdd20aSAndroid Build Coastguard Worker 42*6dbdd20aSAndroid Build Coastguard Workerinterface TabWithContent extends Tab { 43*6dbdd20aSAndroid Build Coastguard Worker content: m.Children; 44*6dbdd20aSAndroid Build Coastguard Worker} 45*6dbdd20aSAndroid Build Coastguard Worker 46*6dbdd20aSAndroid Build Coastguard Workerexport interface TabDropdownEntry { 47*6dbdd20aSAndroid Build Coastguard Worker // Unique key for this tab dropdown entry. 48*6dbdd20aSAndroid Build Coastguard Worker key: string; 49*6dbdd20aSAndroid Build Coastguard Worker 50*6dbdd20aSAndroid Build Coastguard Worker // Title to show on this entry. 51*6dbdd20aSAndroid Build Coastguard Worker title: string; 52*6dbdd20aSAndroid Build Coastguard Worker 53*6dbdd20aSAndroid Build Coastguard Worker // Called when tab dropdown entry is clicked. 54*6dbdd20aSAndroid Build Coastguard Worker onClick: () => void; 55*6dbdd20aSAndroid Build Coastguard Worker 56*6dbdd20aSAndroid Build Coastguard Worker // Whether this tab is checked or not 57*6dbdd20aSAndroid Build Coastguard Worker checked: boolean; 58*6dbdd20aSAndroid Build Coastguard Worker} 59*6dbdd20aSAndroid Build Coastguard Worker 60*6dbdd20aSAndroid Build Coastguard Workerexport class TabPanel implements m.ClassComponent<TabPanelAttrs> { 61*6dbdd20aSAndroid Build Coastguard Worker private readonly trace: TraceImpl; 62*6dbdd20aSAndroid Build Coastguard Worker // Tabs panel starts collapsed. 63*6dbdd20aSAndroid Build Coastguard Worker 64*6dbdd20aSAndroid Build Coastguard Worker private fadeContext = new FadeContext(); 65*6dbdd20aSAndroid Build Coastguard Worker 66*6dbdd20aSAndroid Build Coastguard Worker constructor({attrs}: m.CVnode<TabPanelAttrs>) { 67*6dbdd20aSAndroid Build Coastguard Worker this.trace = attrs.trace; 68*6dbdd20aSAndroid Build Coastguard Worker } 69*6dbdd20aSAndroid Build Coastguard Worker 70*6dbdd20aSAndroid Build Coastguard Worker view() { 71*6dbdd20aSAndroid Build Coastguard Worker const tabMan = this.trace.tabs; 72*6dbdd20aSAndroid Build Coastguard Worker const tabList = this.trace.tabs.openTabsUri; 73*6dbdd20aSAndroid Build Coastguard Worker const resolvedTabs = tabMan.resolveTabs(tabList); 74*6dbdd20aSAndroid Build Coastguard Worker 75*6dbdd20aSAndroid Build Coastguard Worker const tabs = resolvedTabs.map(({uri, tab: tabDesc}): TabWithContent => { 76*6dbdd20aSAndroid Build Coastguard Worker if (tabDesc) { 77*6dbdd20aSAndroid Build Coastguard Worker return { 78*6dbdd20aSAndroid Build Coastguard Worker key: uri, 79*6dbdd20aSAndroid Build Coastguard Worker hasCloseButton: true, 80*6dbdd20aSAndroid Build Coastguard Worker title: tabDesc.content.getTitle(), 81*6dbdd20aSAndroid Build Coastguard Worker content: tabDesc.content.render(), 82*6dbdd20aSAndroid Build Coastguard Worker }; 83*6dbdd20aSAndroid Build Coastguard Worker } else { 84*6dbdd20aSAndroid Build Coastguard Worker return { 85*6dbdd20aSAndroid Build Coastguard Worker key: uri, 86*6dbdd20aSAndroid Build Coastguard Worker hasCloseButton: true, 87*6dbdd20aSAndroid Build Coastguard Worker title: 'Tab does not exist', 88*6dbdd20aSAndroid Build Coastguard Worker content: undefined, 89*6dbdd20aSAndroid Build Coastguard Worker }; 90*6dbdd20aSAndroid Build Coastguard Worker } 91*6dbdd20aSAndroid Build Coastguard Worker }); 92*6dbdd20aSAndroid Build Coastguard Worker 93*6dbdd20aSAndroid Build Coastguard Worker // Add the permanent current selection tab to the front of the list of tabs 94*6dbdd20aSAndroid Build Coastguard Worker tabs.unshift({ 95*6dbdd20aSAndroid Build Coastguard Worker key: 'current_selection', 96*6dbdd20aSAndroid Build Coastguard Worker title: 'Current Selection', 97*6dbdd20aSAndroid Build Coastguard Worker content: this.renderCSTabContentWithFading(), 98*6dbdd20aSAndroid Build Coastguard Worker }); 99*6dbdd20aSAndroid Build Coastguard Worker 100*6dbdd20aSAndroid Build Coastguard Worker return m(CollapsiblePanel, { 101*6dbdd20aSAndroid Build Coastguard Worker visibility: this.trace.tabs.tabPanelVisibility, 102*6dbdd20aSAndroid Build Coastguard Worker setVisibility: (visibility) => 103*6dbdd20aSAndroid Build Coastguard Worker this.trace.tabs.setTabPanelVisibility(visibility), 104*6dbdd20aSAndroid Build Coastguard Worker headerActions: [ 105*6dbdd20aSAndroid Build Coastguard Worker this.renderTripleDotDropdownMenu(), 106*6dbdd20aSAndroid Build Coastguard Worker this.renderTabStrip(tabs), 107*6dbdd20aSAndroid Build Coastguard Worker ], 108*6dbdd20aSAndroid Build Coastguard Worker tabs: tabs.map(({key, content}) => { 109*6dbdd20aSAndroid Build Coastguard Worker const active = key === this.trace.tabs.currentTabUri; 110*6dbdd20aSAndroid Build Coastguard Worker return m(Gate, {open: active}, content); 111*6dbdd20aSAndroid Build Coastguard Worker }), 112*6dbdd20aSAndroid Build Coastguard Worker }); 113*6dbdd20aSAndroid Build Coastguard Worker } 114*6dbdd20aSAndroid Build Coastguard Worker 115*6dbdd20aSAndroid Build Coastguard Worker private renderTripleDotDropdownMenu(): m.Child { 116*6dbdd20aSAndroid Build Coastguard Worker const entries = this.trace.tabs.tabs 117*6dbdd20aSAndroid Build Coastguard Worker .filter((tab) => tab.isEphemeral === false) 118*6dbdd20aSAndroid Build Coastguard Worker .map(({content, uri}): TabDropdownEntry => { 119*6dbdd20aSAndroid Build Coastguard Worker return { 120*6dbdd20aSAndroid Build Coastguard Worker key: uri, 121*6dbdd20aSAndroid Build Coastguard Worker title: content.getTitle(), 122*6dbdd20aSAndroid Build Coastguard Worker onClick: () => this.trace.tabs.toggleTab(uri), 123*6dbdd20aSAndroid Build Coastguard Worker checked: this.trace.tabs.isOpen(uri), 124*6dbdd20aSAndroid Build Coastguard Worker }; 125*6dbdd20aSAndroid Build Coastguard Worker }); 126*6dbdd20aSAndroid Build Coastguard Worker 127*6dbdd20aSAndroid Build Coastguard Worker return m( 128*6dbdd20aSAndroid Build Coastguard Worker '.buttons', 129*6dbdd20aSAndroid Build Coastguard Worker m( 130*6dbdd20aSAndroid Build Coastguard Worker PopupMenu2, 131*6dbdd20aSAndroid Build Coastguard Worker { 132*6dbdd20aSAndroid Build Coastguard Worker trigger: m(Button, { 133*6dbdd20aSAndroid Build Coastguard Worker compact: true, 134*6dbdd20aSAndroid Build Coastguard Worker icon: 'more_vert', 135*6dbdd20aSAndroid Build Coastguard Worker disabled: entries.length === 0, 136*6dbdd20aSAndroid Build Coastguard Worker title: 'More Tabs', 137*6dbdd20aSAndroid Build Coastguard Worker }), 138*6dbdd20aSAndroid Build Coastguard Worker }, 139*6dbdd20aSAndroid Build Coastguard Worker entries.map((entry) => { 140*6dbdd20aSAndroid Build Coastguard Worker return m(MenuItem, { 141*6dbdd20aSAndroid Build Coastguard Worker key: entry.key, 142*6dbdd20aSAndroid Build Coastguard Worker label: entry.title, 143*6dbdd20aSAndroid Build Coastguard Worker onclick: () => entry.onClick(), 144*6dbdd20aSAndroid Build Coastguard Worker icon: entry.checked ? 'check_box' : 'check_box_outline_blank', 145*6dbdd20aSAndroid Build Coastguard Worker }); 146*6dbdd20aSAndroid Build Coastguard Worker }), 147*6dbdd20aSAndroid Build Coastguard Worker ), 148*6dbdd20aSAndroid Build Coastguard Worker ); 149*6dbdd20aSAndroid Build Coastguard Worker } 150*6dbdd20aSAndroid Build Coastguard Worker 151*6dbdd20aSAndroid Build Coastguard Worker private renderTabStrip(tabs: Tab[]): m.Child { 152*6dbdd20aSAndroid Build Coastguard Worker const currentTabKey = this.trace.tabs.currentTabUri; 153*6dbdd20aSAndroid Build Coastguard Worker return m( 154*6dbdd20aSAndroid Build Coastguard Worker '.tabs', 155*6dbdd20aSAndroid Build Coastguard Worker tabs.map((tab) => { 156*6dbdd20aSAndroid Build Coastguard Worker const {key, hasCloseButton = false} = tab; 157*6dbdd20aSAndroid Build Coastguard Worker const tag = currentTabKey === key ? '.tab[active]' : '.tab'; 158*6dbdd20aSAndroid Build Coastguard Worker return m( 159*6dbdd20aSAndroid Build Coastguard Worker tag, 160*6dbdd20aSAndroid Build Coastguard Worker { 161*6dbdd20aSAndroid Build Coastguard Worker key, 162*6dbdd20aSAndroid Build Coastguard Worker onclick: (event: Event) => { 163*6dbdd20aSAndroid Build Coastguard Worker if (!event.defaultPrevented) { 164*6dbdd20aSAndroid Build Coastguard Worker this.trace.tabs.showTab(key); 165*6dbdd20aSAndroid Build Coastguard Worker } 166*6dbdd20aSAndroid Build Coastguard Worker }, 167*6dbdd20aSAndroid Build Coastguard Worker // Middle click to close 168*6dbdd20aSAndroid Build Coastguard Worker onauxclick: (event: MouseEvent) => { 169*6dbdd20aSAndroid Build Coastguard Worker if (!event.defaultPrevented) { 170*6dbdd20aSAndroid Build Coastguard Worker this.trace.tabs.hideTab(key); 171*6dbdd20aSAndroid Build Coastguard Worker } 172*6dbdd20aSAndroid Build Coastguard Worker }, 173*6dbdd20aSAndroid Build Coastguard Worker }, 174*6dbdd20aSAndroid Build Coastguard Worker m('span.pf-tab-title', tab.title), 175*6dbdd20aSAndroid Build Coastguard Worker hasCloseButton && 176*6dbdd20aSAndroid Build Coastguard Worker m(Button, { 177*6dbdd20aSAndroid Build Coastguard Worker onclick: (event: Event) => { 178*6dbdd20aSAndroid Build Coastguard Worker this.trace.tabs.hideTab(key); 179*6dbdd20aSAndroid Build Coastguard Worker event.preventDefault(); 180*6dbdd20aSAndroid Build Coastguard Worker }, 181*6dbdd20aSAndroid Build Coastguard Worker compact: true, 182*6dbdd20aSAndroid Build Coastguard Worker icon: 'close', 183*6dbdd20aSAndroid Build Coastguard Worker }), 184*6dbdd20aSAndroid Build Coastguard Worker ); 185*6dbdd20aSAndroid Build Coastguard Worker }), 186*6dbdd20aSAndroid Build Coastguard Worker ); 187*6dbdd20aSAndroid Build Coastguard Worker } 188*6dbdd20aSAndroid Build Coastguard Worker 189*6dbdd20aSAndroid Build Coastguard Worker private renderCSTabContentWithFading(): m.Children { 190*6dbdd20aSAndroid Build Coastguard Worker const section = this.renderCSTabContent(); 191*6dbdd20aSAndroid Build Coastguard Worker if (section.isLoading) { 192*6dbdd20aSAndroid Build Coastguard Worker return m(FadeIn, section.content); 193*6dbdd20aSAndroid Build Coastguard Worker } else { 194*6dbdd20aSAndroid Build Coastguard Worker return m(FadeOut, {context: this.fadeContext}, section.content); 195*6dbdd20aSAndroid Build Coastguard Worker } 196*6dbdd20aSAndroid Build Coastguard Worker } 197*6dbdd20aSAndroid Build Coastguard Worker 198*6dbdd20aSAndroid Build Coastguard Worker private renderCSTabContent(): {isLoading: boolean; content: m.Children} { 199*6dbdd20aSAndroid Build Coastguard Worker const currentSelection = this.trace.selection.selection; 200*6dbdd20aSAndroid Build Coastguard Worker if (currentSelection.kind === 'empty') { 201*6dbdd20aSAndroid Build Coastguard Worker return { 202*6dbdd20aSAndroid Build Coastguard Worker isLoading: false, 203*6dbdd20aSAndroid Build Coastguard Worker content: m( 204*6dbdd20aSAndroid Build Coastguard Worker EmptyState, 205*6dbdd20aSAndroid Build Coastguard Worker { 206*6dbdd20aSAndroid Build Coastguard Worker className: 'pf-noselection', 207*6dbdd20aSAndroid Build Coastguard Worker title: 'Nothing selected', 208*6dbdd20aSAndroid Build Coastguard Worker }, 209*6dbdd20aSAndroid Build Coastguard Worker 'Selection details will appear here', 210*6dbdd20aSAndroid Build Coastguard Worker ), 211*6dbdd20aSAndroid Build Coastguard Worker }; 212*6dbdd20aSAndroid Build Coastguard Worker } 213*6dbdd20aSAndroid Build Coastguard Worker 214*6dbdd20aSAndroid Build Coastguard Worker if (currentSelection.kind === 'track') { 215*6dbdd20aSAndroid Build Coastguard Worker return { 216*6dbdd20aSAndroid Build Coastguard Worker isLoading: false, 217*6dbdd20aSAndroid Build Coastguard Worker content: this.renderTrackDetailsPanel(currentSelection.trackUri), 218*6dbdd20aSAndroid Build Coastguard Worker }; 219*6dbdd20aSAndroid Build Coastguard Worker } 220*6dbdd20aSAndroid Build Coastguard Worker 221*6dbdd20aSAndroid Build Coastguard Worker const detailsPanel = this.trace.selection.getDetailsPanelForSelection(); 222*6dbdd20aSAndroid Build Coastguard Worker if (currentSelection.kind === 'track_event' && detailsPanel !== undefined) { 223*6dbdd20aSAndroid Build Coastguard Worker return { 224*6dbdd20aSAndroid Build Coastguard Worker isLoading: detailsPanel.isLoading, 225*6dbdd20aSAndroid Build Coastguard Worker content: detailsPanel.render(), 226*6dbdd20aSAndroid Build Coastguard Worker }; 227*6dbdd20aSAndroid Build Coastguard Worker } 228*6dbdd20aSAndroid Build Coastguard Worker 229*6dbdd20aSAndroid Build Coastguard Worker // Get the first "truthy" details panel 230*6dbdd20aSAndroid Build Coastguard Worker const detailsPanels = this.trace.tabs.detailsPanels.map((dp) => { 231*6dbdd20aSAndroid Build Coastguard Worker return { 232*6dbdd20aSAndroid Build Coastguard Worker content: dp.render(currentSelection), 233*6dbdd20aSAndroid Build Coastguard Worker isLoading: dp.isLoading?.() ?? false, 234*6dbdd20aSAndroid Build Coastguard Worker }; 235*6dbdd20aSAndroid Build Coastguard Worker }); 236*6dbdd20aSAndroid Build Coastguard Worker 237*6dbdd20aSAndroid Build Coastguard Worker const panel = detailsPanels.find(({content}) => content); 238*6dbdd20aSAndroid Build Coastguard Worker 239*6dbdd20aSAndroid Build Coastguard Worker if (panel) { 240*6dbdd20aSAndroid Build Coastguard Worker return panel; 241*6dbdd20aSAndroid Build Coastguard Worker } else { 242*6dbdd20aSAndroid Build Coastguard Worker return { 243*6dbdd20aSAndroid Build Coastguard Worker isLoading: false, 244*6dbdd20aSAndroid Build Coastguard Worker content: m( 245*6dbdd20aSAndroid Build Coastguard Worker EmptyState, 246*6dbdd20aSAndroid Build Coastguard Worker { 247*6dbdd20aSAndroid Build Coastguard Worker className: 'pf-noselection', 248*6dbdd20aSAndroid Build Coastguard Worker title: 'No details available', 249*6dbdd20aSAndroid Build Coastguard Worker icon: 'warning', 250*6dbdd20aSAndroid Build Coastguard Worker }, 251*6dbdd20aSAndroid Build Coastguard Worker `Selection kind: '${currentSelection.kind}'`, 252*6dbdd20aSAndroid Build Coastguard Worker ), 253*6dbdd20aSAndroid Build Coastguard Worker }; 254*6dbdd20aSAndroid Build Coastguard Worker } 255*6dbdd20aSAndroid Build Coastguard Worker } 256*6dbdd20aSAndroid Build Coastguard Worker 257*6dbdd20aSAndroid Build Coastguard Worker private renderTrackDetailsPanel(trackUri: string) { 258*6dbdd20aSAndroid Build Coastguard Worker const track = this.trace.tracks.getTrack(trackUri); 259*6dbdd20aSAndroid Build Coastguard Worker if (track) { 260*6dbdd20aSAndroid Build Coastguard Worker return m( 261*6dbdd20aSAndroid Build Coastguard Worker DetailsShell, 262*6dbdd20aSAndroid Build Coastguard Worker {title: 'Track', description: track.title}, 263*6dbdd20aSAndroid Build Coastguard Worker m( 264*6dbdd20aSAndroid Build Coastguard Worker GridLayout, 265*6dbdd20aSAndroid Build Coastguard Worker m( 266*6dbdd20aSAndroid Build Coastguard Worker GridLayoutColumn, 267*6dbdd20aSAndroid Build Coastguard Worker m( 268*6dbdd20aSAndroid Build Coastguard Worker Section, 269*6dbdd20aSAndroid Build Coastguard Worker {title: 'Details'}, 270*6dbdd20aSAndroid Build Coastguard Worker m( 271*6dbdd20aSAndroid Build Coastguard Worker Tree, 272*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Name', right: track.title}), 273*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'URI', right: track.uri}), 274*6dbdd20aSAndroid Build Coastguard Worker m(TreeNode, {left: 'Plugin ID', right: track.pluginId}), 275*6dbdd20aSAndroid Build Coastguard Worker m( 276*6dbdd20aSAndroid Build Coastguard Worker TreeNode, 277*6dbdd20aSAndroid Build Coastguard Worker {left: 'Tags'}, 278*6dbdd20aSAndroid Build Coastguard Worker track.tags && 279*6dbdd20aSAndroid Build Coastguard Worker Object.entries(track.tags).map(([key, value]) => { 280*6dbdd20aSAndroid Build Coastguard Worker return m(TreeNode, {left: key, right: value?.toString()}); 281*6dbdd20aSAndroid Build Coastguard Worker }), 282*6dbdd20aSAndroid Build Coastguard Worker ), 283*6dbdd20aSAndroid Build Coastguard Worker ), 284*6dbdd20aSAndroid Build Coastguard Worker ), 285*6dbdd20aSAndroid Build Coastguard Worker ), 286*6dbdd20aSAndroid Build Coastguard Worker ), 287*6dbdd20aSAndroid Build Coastguard Worker ); 288*6dbdd20aSAndroid Build Coastguard Worker } else { 289*6dbdd20aSAndroid Build Coastguard Worker return undefined; // TODO show something sensible here 290*6dbdd20aSAndroid Build Coastguard Worker } 291*6dbdd20aSAndroid Build Coastguard Worker } 292*6dbdd20aSAndroid Build Coastguard Worker} 293*6dbdd20aSAndroid Build Coastguard Worker 294*6dbdd20aSAndroid Build Coastguard Workerconst FADE_TIME_MS = 50; 295*6dbdd20aSAndroid Build Coastguard Worker 296*6dbdd20aSAndroid Build Coastguard Workerclass FadeContext { 297*6dbdd20aSAndroid Build Coastguard Worker private resolver = () => {}; 298*6dbdd20aSAndroid Build Coastguard Worker 299*6dbdd20aSAndroid Build Coastguard Worker putResolver(res: () => void) { 300*6dbdd20aSAndroid Build Coastguard Worker this.resolver = res; 301*6dbdd20aSAndroid Build Coastguard Worker } 302*6dbdd20aSAndroid Build Coastguard Worker 303*6dbdd20aSAndroid Build Coastguard Worker resolve() { 304*6dbdd20aSAndroid Build Coastguard Worker this.resolver(); 305*6dbdd20aSAndroid Build Coastguard Worker this.resolver = () => {}; 306*6dbdd20aSAndroid Build Coastguard Worker } 307*6dbdd20aSAndroid Build Coastguard Worker} 308*6dbdd20aSAndroid Build Coastguard Worker 309*6dbdd20aSAndroid Build Coastguard Workerinterface FadeOutAttrs { 310*6dbdd20aSAndroid Build Coastguard Worker context: FadeContext; 311*6dbdd20aSAndroid Build Coastguard Worker} 312*6dbdd20aSAndroid Build Coastguard Worker 313*6dbdd20aSAndroid Build Coastguard Workerclass FadeOut implements m.ClassComponent<FadeOutAttrs> { 314*6dbdd20aSAndroid Build Coastguard Worker onbeforeremove({attrs}: m.VnodeDOM<FadeOutAttrs>): Promise<void> { 315*6dbdd20aSAndroid Build Coastguard Worker return new Promise((res) => { 316*6dbdd20aSAndroid Build Coastguard Worker attrs.context.putResolver(res); 317*6dbdd20aSAndroid Build Coastguard Worker setTimeout(res, FADE_TIME_MS); 318*6dbdd20aSAndroid Build Coastguard Worker }); 319*6dbdd20aSAndroid Build Coastguard Worker } 320*6dbdd20aSAndroid Build Coastguard Worker 321*6dbdd20aSAndroid Build Coastguard Worker oncreate({attrs}: m.VnodeDOM<FadeOutAttrs>) { 322*6dbdd20aSAndroid Build Coastguard Worker attrs.context.resolve(); 323*6dbdd20aSAndroid Build Coastguard Worker } 324*6dbdd20aSAndroid Build Coastguard Worker 325*6dbdd20aSAndroid Build Coastguard Worker view(vnode: m.Vnode<FadeOutAttrs>): void | m.Children { 326*6dbdd20aSAndroid Build Coastguard Worker return vnode.children; 327*6dbdd20aSAndroid Build Coastguard Worker } 328*6dbdd20aSAndroid Build Coastguard Worker} 329*6dbdd20aSAndroid Build Coastguard Worker 330*6dbdd20aSAndroid Build Coastguard Workerclass FadeIn implements m.ClassComponent { 331*6dbdd20aSAndroid Build Coastguard Worker private show = false; 332*6dbdd20aSAndroid Build Coastguard Worker 333*6dbdd20aSAndroid Build Coastguard Worker oncreate(_: m.VnodeDOM) { 334*6dbdd20aSAndroid Build Coastguard Worker setTimeout(() => { 335*6dbdd20aSAndroid Build Coastguard Worker this.show = true; 336*6dbdd20aSAndroid Build Coastguard Worker raf.scheduleFullRedraw(); 337*6dbdd20aSAndroid Build Coastguard Worker }, FADE_TIME_MS); 338*6dbdd20aSAndroid Build Coastguard Worker } 339*6dbdd20aSAndroid Build Coastguard Worker 340*6dbdd20aSAndroid Build Coastguard Worker view(vnode: m.Vnode): m.Children { 341*6dbdd20aSAndroid Build Coastguard Worker return this.show ? vnode.children : undefined; 342*6dbdd20aSAndroid Build Coastguard Worker } 343*6dbdd20aSAndroid Build Coastguard Worker} 344