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 {assertTrue} from '../base/logging'; 16*6dbdd20aSAndroid Build Coastguard Worker 17*6dbdd20aSAndroid Build Coastguard Workerexport interface WorkspaceManager { 18*6dbdd20aSAndroid Build Coastguard Worker // This is the same of ctx.workspace, exposed for consistency also here. 19*6dbdd20aSAndroid Build Coastguard Worker readonly currentWorkspace: Workspace; 20*6dbdd20aSAndroid Build Coastguard Worker readonly all: ReadonlyArray<Workspace>; 21*6dbdd20aSAndroid Build Coastguard Worker createEmptyWorkspace(displayName: string): Workspace; 22*6dbdd20aSAndroid Build Coastguard Worker switchWorkspace(workspace: Workspace): void; 23*6dbdd20aSAndroid Build Coastguard Worker} 24*6dbdd20aSAndroid Build Coastguard Worker 25*6dbdd20aSAndroid Build Coastguard Workerlet sessionUniqueIdCounter = 0; 26*6dbdd20aSAndroid Build Coastguard Worker 27*6dbdd20aSAndroid Build Coastguard Worker/** 28*6dbdd20aSAndroid Build Coastguard Worker * Creates a short ID which is unique to this instance of the UI. 29*6dbdd20aSAndroid Build Coastguard Worker * 30*6dbdd20aSAndroid Build Coastguard Worker * The advantage of using this over uuidv4() is that the ids produced are 31*6dbdd20aSAndroid Build Coastguard Worker * significantly shorter, saving memory and making them more human 32*6dbdd20aSAndroid Build Coastguard Worker * read/write-able which helps when debugging. 33*6dbdd20aSAndroid Build Coastguard Worker * 34*6dbdd20aSAndroid Build Coastguard Worker * Note: The ID range will reset every time the UI is restarted, so be careful 35*6dbdd20aSAndroid Build Coastguard Worker * not rely on these IDs in any medium that can survive between UI instances. 36*6dbdd20aSAndroid Build Coastguard Worker * 37*6dbdd20aSAndroid Build Coastguard Worker * TODO(stevegolton): We could possibly move this into its own module and use it 38*6dbdd20aSAndroid Build Coastguard Worker * everywhere where session-unique ids are required. 39*6dbdd20aSAndroid Build Coastguard Worker */ 40*6dbdd20aSAndroid Build Coastguard Workerfunction createSessionUniqueId(): string { 41*6dbdd20aSAndroid Build Coastguard Worker // Return the counter in base36 (0-z) to keep the string as short as possible 42*6dbdd20aSAndroid Build Coastguard Worker // but still human readable. 43*6dbdd20aSAndroid Build Coastguard Worker return (sessionUniqueIdCounter++).toString(36); 44*6dbdd20aSAndroid Build Coastguard Worker} 45*6dbdd20aSAndroid Build Coastguard Worker 46*6dbdd20aSAndroid Build Coastguard Worker/** 47*6dbdd20aSAndroid Build Coastguard Worker * Describes generic parent track node functionality - i.e. any entity that can 48*6dbdd20aSAndroid Build Coastguard Worker * contain child TrackNodes, providing methods to add, remove, and access child 49*6dbdd20aSAndroid Build Coastguard Worker * nodes. 50*6dbdd20aSAndroid Build Coastguard Worker * 51*6dbdd20aSAndroid Build Coastguard Worker * This class is abstract because, while it can technically be instantiated on 52*6dbdd20aSAndroid Build Coastguard Worker * its own (no abstract methods/properties), it can't and shouldn't be 53*6dbdd20aSAndroid Build Coastguard Worker * instantiated anywhere in practice - all APIs require either a TrackNode or a 54*6dbdd20aSAndroid Build Coastguard Worker * Workspace. 55*6dbdd20aSAndroid Build Coastguard Worker * 56*6dbdd20aSAndroid Build Coastguard Worker * Thus, it serves two purposes: 57*6dbdd20aSAndroid Build Coastguard Worker * 1. Avoiding duplication between Workspace and TrackNode, which is an internal 58*6dbdd20aSAndroid Build Coastguard Worker * implementation detail of this module. 59*6dbdd20aSAndroid Build Coastguard Worker * 2. Providing a typescript interface for a generic TrackNode container class, 60*6dbdd20aSAndroid Build Coastguard Worker * which otherwise you might have to achieve using `Workspace | TrackNode` 61*6dbdd20aSAndroid Build Coastguard Worker * which is uglier. 62*6dbdd20aSAndroid Build Coastguard Worker * 63*6dbdd20aSAndroid Build Coastguard Worker * If you find yourself using this as a Javascript class in external code, e.g. 64*6dbdd20aSAndroid Build Coastguard Worker * `instance of TrackNodeContainer`, you're probably doing something wrong. 65*6dbdd20aSAndroid Build Coastguard Worker */ 66*6dbdd20aSAndroid Build Coastguard Worker 67*6dbdd20aSAndroid Build Coastguard Workerexport interface TrackNodeArgs { 68*6dbdd20aSAndroid Build Coastguard Worker title: string; 69*6dbdd20aSAndroid Build Coastguard Worker id: string; 70*6dbdd20aSAndroid Build Coastguard Worker uri: string; 71*6dbdd20aSAndroid Build Coastguard Worker headless: boolean; 72*6dbdd20aSAndroid Build Coastguard Worker sortOrder: number; 73*6dbdd20aSAndroid Build Coastguard Worker collapsed: boolean; 74*6dbdd20aSAndroid Build Coastguard Worker isSummary: boolean; 75*6dbdd20aSAndroid Build Coastguard Worker removable: boolean; 76*6dbdd20aSAndroid Build Coastguard Worker} 77*6dbdd20aSAndroid Build Coastguard Worker 78*6dbdd20aSAndroid Build Coastguard Worker/** 79*6dbdd20aSAndroid Build Coastguard Worker * A base class for any node with children (i.e. a group or a workspace). 80*6dbdd20aSAndroid Build Coastguard Worker */ 81*6dbdd20aSAndroid Build Coastguard Workerexport class TrackNode { 82*6dbdd20aSAndroid Build Coastguard Worker // Immutable unique (within the workspace) ID of this track node. Used for 83*6dbdd20aSAndroid Build Coastguard Worker // efficiently retrieving this node object from a workspace. Note: This is 84*6dbdd20aSAndroid Build Coastguard Worker // different to |uri| which is used to reference a track to render on the 85*6dbdd20aSAndroid Build Coastguard Worker // track. If this means nothing to you, don't bother using it. 86*6dbdd20aSAndroid Build Coastguard Worker public readonly id: string; 87*6dbdd20aSAndroid Build Coastguard Worker 88*6dbdd20aSAndroid Build Coastguard Worker // A human readable string for this track - displayed in the track shell. 89*6dbdd20aSAndroid Build Coastguard Worker // TODO(stevegolton): Make this optional, so that if we implement a string for 90*6dbdd20aSAndroid Build Coastguard Worker // this track then we can implement it here as well. 91*6dbdd20aSAndroid Build Coastguard Worker public title: string; 92*6dbdd20aSAndroid Build Coastguard Worker 93*6dbdd20aSAndroid Build Coastguard Worker // The URI of the track content to display here. 94*6dbdd20aSAndroid Build Coastguard Worker public uri?: string; 95*6dbdd20aSAndroid Build Coastguard Worker 96*6dbdd20aSAndroid Build Coastguard Worker // Optional sort order, which workspaces may or may not take advantage of for 97*6dbdd20aSAndroid Build Coastguard Worker // sorting when displaying the workspace. 98*6dbdd20aSAndroid Build Coastguard Worker public sortOrder?: number; 99*6dbdd20aSAndroid Build Coastguard Worker 100*6dbdd20aSAndroid Build Coastguard Worker // Don't show the header at all for this track, just show its un-nested 101*6dbdd20aSAndroid Build Coastguard Worker // children. This is helpful to group together tracks that logically belong to 102*6dbdd20aSAndroid Build Coastguard Worker // the same group (e.g. all ftrace cpu tracks) and ease the job of 103*6dbdd20aSAndroid Build Coastguard Worker // sorting/grouping plugins. 104*6dbdd20aSAndroid Build Coastguard Worker public headless: boolean; 105*6dbdd20aSAndroid Build Coastguard Worker 106*6dbdd20aSAndroid Build Coastguard Worker // If true, this track is to be used as a summary for its children. When the 107*6dbdd20aSAndroid Build Coastguard Worker // group is expanded the track will become sticky to the top of the viewport 108*6dbdd20aSAndroid Build Coastguard Worker // to provide context for the tracks within, and the content of this track 109*6dbdd20aSAndroid Build Coastguard Worker // shall be omitted. It will also be squashed down to a smaller height to save 110*6dbdd20aSAndroid Build Coastguard Worker // vertical space. 111*6dbdd20aSAndroid Build Coastguard Worker public isSummary: boolean; 112*6dbdd20aSAndroid Build Coastguard Worker 113*6dbdd20aSAndroid Build Coastguard Worker // If true, this node will be removable by the user. It will show a little 114*6dbdd20aSAndroid Build Coastguard Worker // close button in the track shell which the user can press to remove the 115*6dbdd20aSAndroid Build Coastguard Worker // track from the workspace. 116*6dbdd20aSAndroid Build Coastguard Worker public removable: boolean; 117*6dbdd20aSAndroid Build Coastguard Worker 118*6dbdd20aSAndroid Build Coastguard Worker protected _collapsed = true; 119*6dbdd20aSAndroid Build Coastguard Worker protected _children: Array<TrackNode> = []; 120*6dbdd20aSAndroid Build Coastguard Worker protected readonly tracksById = new Map<string, TrackNode>(); 121*6dbdd20aSAndroid Build Coastguard Worker protected readonly tracksByUri = new Map<string, TrackNode>(); 122*6dbdd20aSAndroid Build Coastguard Worker private _parent?: TrackNode; 123*6dbdd20aSAndroid Build Coastguard Worker public _workspace?: Workspace; 124*6dbdd20aSAndroid Build Coastguard Worker 125*6dbdd20aSAndroid Build Coastguard Worker get parent(): TrackNode | undefined { 126*6dbdd20aSAndroid Build Coastguard Worker return this._parent; 127*6dbdd20aSAndroid Build Coastguard Worker } 128*6dbdd20aSAndroid Build Coastguard Worker 129*6dbdd20aSAndroid Build Coastguard Worker constructor(args?: Partial<TrackNodeArgs>) { 130*6dbdd20aSAndroid Build Coastguard Worker const { 131*6dbdd20aSAndroid Build Coastguard Worker title = '', 132*6dbdd20aSAndroid Build Coastguard Worker id = createSessionUniqueId(), 133*6dbdd20aSAndroid Build Coastguard Worker uri, 134*6dbdd20aSAndroid Build Coastguard Worker headless = false, 135*6dbdd20aSAndroid Build Coastguard Worker sortOrder, 136*6dbdd20aSAndroid Build Coastguard Worker collapsed = true, 137*6dbdd20aSAndroid Build Coastguard Worker isSummary = false, 138*6dbdd20aSAndroid Build Coastguard Worker removable = false, 139*6dbdd20aSAndroid Build Coastguard Worker } = args ?? {}; 140*6dbdd20aSAndroid Build Coastguard Worker 141*6dbdd20aSAndroid Build Coastguard Worker this.id = id; 142*6dbdd20aSAndroid Build Coastguard Worker this.uri = uri; 143*6dbdd20aSAndroid Build Coastguard Worker this.headless = headless; 144*6dbdd20aSAndroid Build Coastguard Worker this.title = title; 145*6dbdd20aSAndroid Build Coastguard Worker this.sortOrder = sortOrder; 146*6dbdd20aSAndroid Build Coastguard Worker this.isSummary = isSummary; 147*6dbdd20aSAndroid Build Coastguard Worker this._collapsed = collapsed; 148*6dbdd20aSAndroid Build Coastguard Worker this.removable = removable; 149*6dbdd20aSAndroid Build Coastguard Worker } 150*6dbdd20aSAndroid Build Coastguard Worker 151*6dbdd20aSAndroid Build Coastguard Worker /** 152*6dbdd20aSAndroid Build Coastguard Worker * Remove this track from it's parent & unpin from the workspace if pinned. 153*6dbdd20aSAndroid Build Coastguard Worker */ 154*6dbdd20aSAndroid Build Coastguard Worker remove(): void { 155*6dbdd20aSAndroid Build Coastguard Worker this.workspace?.unpinTrack(this); 156*6dbdd20aSAndroid Build Coastguard Worker this.parent?.removeChild(this); 157*6dbdd20aSAndroid Build Coastguard Worker } 158*6dbdd20aSAndroid Build Coastguard Worker 159*6dbdd20aSAndroid Build Coastguard Worker /** 160*6dbdd20aSAndroid Build Coastguard Worker * Add this track to the list of pinned tracks in its parent workspace. 161*6dbdd20aSAndroid Build Coastguard Worker * 162*6dbdd20aSAndroid Build Coastguard Worker * Has no effect if this track is not added to a workspace. 163*6dbdd20aSAndroid Build Coastguard Worker */ 164*6dbdd20aSAndroid Build Coastguard Worker pin(): void { 165*6dbdd20aSAndroid Build Coastguard Worker this.workspace?.pinTrack(this); 166*6dbdd20aSAndroid Build Coastguard Worker } 167*6dbdd20aSAndroid Build Coastguard Worker 168*6dbdd20aSAndroid Build Coastguard Worker /** 169*6dbdd20aSAndroid Build Coastguard Worker * Remove this track from the list of pinned tracks in its parent workspace. 170*6dbdd20aSAndroid Build Coastguard Worker * 171*6dbdd20aSAndroid Build Coastguard Worker * Has no effect if this track is not added to a workspace. 172*6dbdd20aSAndroid Build Coastguard Worker */ 173*6dbdd20aSAndroid Build Coastguard Worker unpin(): void { 174*6dbdd20aSAndroid Build Coastguard Worker this.workspace?.unpinTrack(this); 175*6dbdd20aSAndroid Build Coastguard Worker } 176*6dbdd20aSAndroid Build Coastguard Worker 177*6dbdd20aSAndroid Build Coastguard Worker /** 178*6dbdd20aSAndroid Build Coastguard Worker * Returns true if this node is added to a workspace as is in the pinned track 179*6dbdd20aSAndroid Build Coastguard Worker * list of that workspace. 180*6dbdd20aSAndroid Build Coastguard Worker */ 181*6dbdd20aSAndroid Build Coastguard Worker get isPinned(): boolean { 182*6dbdd20aSAndroid Build Coastguard Worker return Boolean(this.workspace?.hasPinnedTrack(this)); 183*6dbdd20aSAndroid Build Coastguard Worker } 184*6dbdd20aSAndroid Build Coastguard Worker 185*6dbdd20aSAndroid Build Coastguard Worker /** 186*6dbdd20aSAndroid Build Coastguard Worker * Find the closest visible ancestor TrackNode. 187*6dbdd20aSAndroid Build Coastguard Worker * 188*6dbdd20aSAndroid Build Coastguard Worker * Given the path from the root workspace to this node, find the fist one, 189*6dbdd20aSAndroid Build Coastguard Worker * starting from the root, which is collapsed. This will be, from the user's 190*6dbdd20aSAndroid Build Coastguard Worker * point of view, the closest ancestor of this node. 191*6dbdd20aSAndroid Build Coastguard Worker * 192*6dbdd20aSAndroid Build Coastguard Worker * Returns undefined if this node is actually visible. 193*6dbdd20aSAndroid Build Coastguard Worker * 194*6dbdd20aSAndroid Build Coastguard Worker * TODO(stevegolton): Should it return itself in this case? 195*6dbdd20aSAndroid Build Coastguard Worker */ 196*6dbdd20aSAndroid Build Coastguard Worker findClosestVisibleAncestor(): TrackNode { 197*6dbdd20aSAndroid Build Coastguard Worker // Build a path from the root workspace to this node 198*6dbdd20aSAndroid Build Coastguard Worker const path: TrackNode[] = []; 199*6dbdd20aSAndroid Build Coastguard Worker let node = this.parent; 200*6dbdd20aSAndroid Build Coastguard Worker while (node) { 201*6dbdd20aSAndroid Build Coastguard Worker path.unshift(node); 202*6dbdd20aSAndroid Build Coastguard Worker node = node.parent; 203*6dbdd20aSAndroid Build Coastguard Worker } 204*6dbdd20aSAndroid Build Coastguard Worker 205*6dbdd20aSAndroid Build Coastguard Worker // Find the first collapsed track in the path starting from the root. This 206*6dbdd20aSAndroid Build Coastguard Worker // is effectively the closest we can get to this node without expanding any 207*6dbdd20aSAndroid Build Coastguard Worker // groups. 208*6dbdd20aSAndroid Build Coastguard Worker return path.find((node) => node.collapsed) ?? this; 209*6dbdd20aSAndroid Build Coastguard Worker } 210*6dbdd20aSAndroid Build Coastguard Worker 211*6dbdd20aSAndroid Build Coastguard Worker /** 212*6dbdd20aSAndroid Build Coastguard Worker * Expand all ancestor nodes. 213*6dbdd20aSAndroid Build Coastguard Worker */ 214*6dbdd20aSAndroid Build Coastguard Worker reveal(): void { 215*6dbdd20aSAndroid Build Coastguard Worker let parent = this.parent; 216*6dbdd20aSAndroid Build Coastguard Worker while (parent) { 217*6dbdd20aSAndroid Build Coastguard Worker parent.expand(); 218*6dbdd20aSAndroid Build Coastguard Worker parent = parent.parent; 219*6dbdd20aSAndroid Build Coastguard Worker } 220*6dbdd20aSAndroid Build Coastguard Worker } 221*6dbdd20aSAndroid Build Coastguard Worker 222*6dbdd20aSAndroid Build Coastguard Worker /** 223*6dbdd20aSAndroid Build Coastguard Worker * Find this node's root node - this may be a workspace or another node. 224*6dbdd20aSAndroid Build Coastguard Worker */ 225*6dbdd20aSAndroid Build Coastguard Worker get rootNode(): TrackNode { 226*6dbdd20aSAndroid Build Coastguard Worker let node: TrackNode = this; 227*6dbdd20aSAndroid Build Coastguard Worker while (node.parent) { 228*6dbdd20aSAndroid Build Coastguard Worker node = node.parent; 229*6dbdd20aSAndroid Build Coastguard Worker } 230*6dbdd20aSAndroid Build Coastguard Worker return node; 231*6dbdd20aSAndroid Build Coastguard Worker } 232*6dbdd20aSAndroid Build Coastguard Worker 233*6dbdd20aSAndroid Build Coastguard Worker /** 234*6dbdd20aSAndroid Build Coastguard Worker * Find this node's workspace if it is attached to one. 235*6dbdd20aSAndroid Build Coastguard Worker */ 236*6dbdd20aSAndroid Build Coastguard Worker get workspace(): Workspace | undefined { 237*6dbdd20aSAndroid Build Coastguard Worker return this.rootNode._workspace; 238*6dbdd20aSAndroid Build Coastguard Worker } 239*6dbdd20aSAndroid Build Coastguard Worker 240*6dbdd20aSAndroid Build Coastguard Worker /** 241*6dbdd20aSAndroid Build Coastguard Worker * Mark this node as un-collapsed, indicating its children should be rendered. 242*6dbdd20aSAndroid Build Coastguard Worker */ 243*6dbdd20aSAndroid Build Coastguard Worker expand(): void { 244*6dbdd20aSAndroid Build Coastguard Worker this._collapsed = false; 245*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 246*6dbdd20aSAndroid Build Coastguard Worker } 247*6dbdd20aSAndroid Build Coastguard Worker 248*6dbdd20aSAndroid Build Coastguard Worker /** 249*6dbdd20aSAndroid Build Coastguard Worker * Mark this node as collapsed, indicating its children should not be 250*6dbdd20aSAndroid Build Coastguard Worker * rendered. 251*6dbdd20aSAndroid Build Coastguard Worker */ 252*6dbdd20aSAndroid Build Coastguard Worker collapse(): void { 253*6dbdd20aSAndroid Build Coastguard Worker this._collapsed = true; 254*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 255*6dbdd20aSAndroid Build Coastguard Worker } 256*6dbdd20aSAndroid Build Coastguard Worker 257*6dbdd20aSAndroid Build Coastguard Worker /** 258*6dbdd20aSAndroid Build Coastguard Worker * Toggle the collapsed state. 259*6dbdd20aSAndroid Build Coastguard Worker */ 260*6dbdd20aSAndroid Build Coastguard Worker toggleCollapsed(): void { 261*6dbdd20aSAndroid Build Coastguard Worker this._collapsed = !this._collapsed; 262*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 263*6dbdd20aSAndroid Build Coastguard Worker } 264*6dbdd20aSAndroid Build Coastguard Worker 265*6dbdd20aSAndroid Build Coastguard Worker /** 266*6dbdd20aSAndroid Build Coastguard Worker * Whether this node is collapsed, indicating its children should be rendered. 267*6dbdd20aSAndroid Build Coastguard Worker */ 268*6dbdd20aSAndroid Build Coastguard Worker get collapsed(): boolean { 269*6dbdd20aSAndroid Build Coastguard Worker return this._collapsed; 270*6dbdd20aSAndroid Build Coastguard Worker } 271*6dbdd20aSAndroid Build Coastguard Worker 272*6dbdd20aSAndroid Build Coastguard Worker /** 273*6dbdd20aSAndroid Build Coastguard Worker * Whether this node is expanded - i.e. not collapsed, indicating its children 274*6dbdd20aSAndroid Build Coastguard Worker * should be rendered. 275*6dbdd20aSAndroid Build Coastguard Worker */ 276*6dbdd20aSAndroid Build Coastguard Worker get expanded(): boolean { 277*6dbdd20aSAndroid Build Coastguard Worker return !this._collapsed; 278*6dbdd20aSAndroid Build Coastguard Worker } 279*6dbdd20aSAndroid Build Coastguard Worker 280*6dbdd20aSAndroid Build Coastguard Worker /** 281*6dbdd20aSAndroid Build Coastguard Worker * Returns the list of titles representing the full path from the root node to 282*6dbdd20aSAndroid Build Coastguard Worker * the current node. This path consists only of node titles, workspaces are 283*6dbdd20aSAndroid Build Coastguard Worker * omitted. 284*6dbdd20aSAndroid Build Coastguard Worker */ 285*6dbdd20aSAndroid Build Coastguard Worker get fullPath(): ReadonlyArray<string> { 286*6dbdd20aSAndroid Build Coastguard Worker let fullPath = [this.title]; 287*6dbdd20aSAndroid Build Coastguard Worker let parent = this.parent; 288*6dbdd20aSAndroid Build Coastguard Worker while (parent) { 289*6dbdd20aSAndroid Build Coastguard Worker // Ignore headless containers as they don't appear in the tree... 290*6dbdd20aSAndroid Build Coastguard Worker if (!parent.headless && parent.title !== '') { 291*6dbdd20aSAndroid Build Coastguard Worker fullPath = [parent.title, ...fullPath]; 292*6dbdd20aSAndroid Build Coastguard Worker } 293*6dbdd20aSAndroid Build Coastguard Worker parent = parent.parent; 294*6dbdd20aSAndroid Build Coastguard Worker } 295*6dbdd20aSAndroid Build Coastguard Worker return fullPath; 296*6dbdd20aSAndroid Build Coastguard Worker } 297*6dbdd20aSAndroid Build Coastguard Worker 298*6dbdd20aSAndroid Build Coastguard Worker protected fireOnChangeListener(): void { 299*6dbdd20aSAndroid Build Coastguard Worker this.workspace?.onchange(this.workspace); 300*6dbdd20aSAndroid Build Coastguard Worker } 301*6dbdd20aSAndroid Build Coastguard Worker 302*6dbdd20aSAndroid Build Coastguard Worker /** 303*6dbdd20aSAndroid Build Coastguard Worker * True if this node has children, false otherwise. 304*6dbdd20aSAndroid Build Coastguard Worker */ 305*6dbdd20aSAndroid Build Coastguard Worker get hasChildren(): boolean { 306*6dbdd20aSAndroid Build Coastguard Worker return this._children.length > 0; 307*6dbdd20aSAndroid Build Coastguard Worker } 308*6dbdd20aSAndroid Build Coastguard Worker 309*6dbdd20aSAndroid Build Coastguard Worker /** 310*6dbdd20aSAndroid Build Coastguard Worker * The ordered list of children belonging to this node. 311*6dbdd20aSAndroid Build Coastguard Worker */ 312*6dbdd20aSAndroid Build Coastguard Worker get children(): ReadonlyArray<TrackNode> { 313*6dbdd20aSAndroid Build Coastguard Worker return this._children; 314*6dbdd20aSAndroid Build Coastguard Worker } 315*6dbdd20aSAndroid Build Coastguard Worker 316*6dbdd20aSAndroid Build Coastguard Worker /** 317*6dbdd20aSAndroid Build Coastguard Worker * Inserts a new child node considering it's sortOrder. 318*6dbdd20aSAndroid Build Coastguard Worker * 319*6dbdd20aSAndroid Build Coastguard Worker * The child will be added before the first child whose |sortOrder| is greater 320*6dbdd20aSAndroid Build Coastguard Worker * than the child node's sort order, or at the end if one does not exist. If 321*6dbdd20aSAndroid Build Coastguard Worker * |sortOrder| is omitted on either node in the comparison it is assumed to be 322*6dbdd20aSAndroid Build Coastguard Worker * 0. 323*6dbdd20aSAndroid Build Coastguard Worker * 324*6dbdd20aSAndroid Build Coastguard Worker * @param child - The child node to add. 325*6dbdd20aSAndroid Build Coastguard Worker */ 326*6dbdd20aSAndroid Build Coastguard Worker addChildInOrder(child: TrackNode): void { 327*6dbdd20aSAndroid Build Coastguard Worker const insertPoint = this._children.find( 328*6dbdd20aSAndroid Build Coastguard Worker (n) => (n.sortOrder ?? 0) > (child.sortOrder ?? 0), 329*6dbdd20aSAndroid Build Coastguard Worker ); 330*6dbdd20aSAndroid Build Coastguard Worker if (insertPoint) { 331*6dbdd20aSAndroid Build Coastguard Worker this.addChildBefore(child, insertPoint); 332*6dbdd20aSAndroid Build Coastguard Worker } else { 333*6dbdd20aSAndroid Build Coastguard Worker this.addChildLast(child); 334*6dbdd20aSAndroid Build Coastguard Worker } 335*6dbdd20aSAndroid Build Coastguard Worker } 336*6dbdd20aSAndroid Build Coastguard Worker 337*6dbdd20aSAndroid Build Coastguard Worker /** 338*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node at the start of the list of children. 339*6dbdd20aSAndroid Build Coastguard Worker * 340*6dbdd20aSAndroid Build Coastguard Worker * @param child The new child node to add. 341*6dbdd20aSAndroid Build Coastguard Worker */ 342*6dbdd20aSAndroid Build Coastguard Worker addChildLast(child: TrackNode): void { 343*6dbdd20aSAndroid Build Coastguard Worker this.adopt(child); 344*6dbdd20aSAndroid Build Coastguard Worker this._children.push(child); 345*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 346*6dbdd20aSAndroid Build Coastguard Worker } 347*6dbdd20aSAndroid Build Coastguard Worker 348*6dbdd20aSAndroid Build Coastguard Worker /** 349*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node at the end of the list of children. 350*6dbdd20aSAndroid Build Coastguard Worker * 351*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to add. 352*6dbdd20aSAndroid Build Coastguard Worker */ 353*6dbdd20aSAndroid Build Coastguard Worker addChildFirst(child: TrackNode): void { 354*6dbdd20aSAndroid Build Coastguard Worker this.adopt(child); 355*6dbdd20aSAndroid Build Coastguard Worker this._children.unshift(child); 356*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 357*6dbdd20aSAndroid Build Coastguard Worker } 358*6dbdd20aSAndroid Build Coastguard Worker 359*6dbdd20aSAndroid Build Coastguard Worker /** 360*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node before an existing child node. 361*6dbdd20aSAndroid Build Coastguard Worker * 362*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to add. 363*6dbdd20aSAndroid Build Coastguard Worker * @param referenceNode An existing child node. The new node will be added 364*6dbdd20aSAndroid Build Coastguard Worker * before this node. 365*6dbdd20aSAndroid Build Coastguard Worker */ 366*6dbdd20aSAndroid Build Coastguard Worker addChildBefore(child: TrackNode, referenceNode: TrackNode): void { 367*6dbdd20aSAndroid Build Coastguard Worker if (child === referenceNode) return; 368*6dbdd20aSAndroid Build Coastguard Worker 369*6dbdd20aSAndroid Build Coastguard Worker assertTrue(this.children.includes(referenceNode)); 370*6dbdd20aSAndroid Build Coastguard Worker 371*6dbdd20aSAndroid Build Coastguard Worker this.adopt(child); 372*6dbdd20aSAndroid Build Coastguard Worker 373*6dbdd20aSAndroid Build Coastguard Worker const indexOfReference = this.children.indexOf(referenceNode); 374*6dbdd20aSAndroid Build Coastguard Worker this._children.splice(indexOfReference, 0, child); 375*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 376*6dbdd20aSAndroid Build Coastguard Worker } 377*6dbdd20aSAndroid Build Coastguard Worker 378*6dbdd20aSAndroid Build Coastguard Worker /** 379*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node after an existing child node. 380*6dbdd20aSAndroid Build Coastguard Worker * 381*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to add. 382*6dbdd20aSAndroid Build Coastguard Worker * @param referenceNode An existing child node. The new node will be added 383*6dbdd20aSAndroid Build Coastguard Worker * after this node. 384*6dbdd20aSAndroid Build Coastguard Worker */ 385*6dbdd20aSAndroid Build Coastguard Worker addChildAfter(child: TrackNode, referenceNode: TrackNode): void { 386*6dbdd20aSAndroid Build Coastguard Worker if (child === referenceNode) return; 387*6dbdd20aSAndroid Build Coastguard Worker 388*6dbdd20aSAndroid Build Coastguard Worker assertTrue(this.children.includes(referenceNode)); 389*6dbdd20aSAndroid Build Coastguard Worker 390*6dbdd20aSAndroid Build Coastguard Worker this.adopt(child); 391*6dbdd20aSAndroid Build Coastguard Worker 392*6dbdd20aSAndroid Build Coastguard Worker const indexOfReference = this.children.indexOf(referenceNode); 393*6dbdd20aSAndroid Build Coastguard Worker this._children.splice(indexOfReference + 1, 0, child); 394*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 395*6dbdd20aSAndroid Build Coastguard Worker } 396*6dbdd20aSAndroid Build Coastguard Worker 397*6dbdd20aSAndroid Build Coastguard Worker /** 398*6dbdd20aSAndroid Build Coastguard Worker * Remove a child node from this node. 399*6dbdd20aSAndroid Build Coastguard Worker * 400*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to remove. 401*6dbdd20aSAndroid Build Coastguard Worker */ 402*6dbdd20aSAndroid Build Coastguard Worker removeChild(child: TrackNode): void { 403*6dbdd20aSAndroid Build Coastguard Worker this._children = this.children.filter((x) => child !== x); 404*6dbdd20aSAndroid Build Coastguard Worker child._parent = undefined; 405*6dbdd20aSAndroid Build Coastguard Worker this.removeFromIndex(child); 406*6dbdd20aSAndroid Build Coastguard Worker this.propagateRemoval(child); 407*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 408*6dbdd20aSAndroid Build Coastguard Worker } 409*6dbdd20aSAndroid Build Coastguard Worker 410*6dbdd20aSAndroid Build Coastguard Worker /** 411*6dbdd20aSAndroid Build Coastguard Worker * The flattened list of all descendent nodes in depth first order. 412*6dbdd20aSAndroid Build Coastguard Worker * 413*6dbdd20aSAndroid Build Coastguard Worker * Use flatTracksUnordered if you don't care about track order, as it's more 414*6dbdd20aSAndroid Build Coastguard Worker * efficient. 415*6dbdd20aSAndroid Build Coastguard Worker */ 416*6dbdd20aSAndroid Build Coastguard Worker get flatTracksOrdered(): ReadonlyArray<TrackNode> { 417*6dbdd20aSAndroid Build Coastguard Worker const tracks: TrackNode[] = []; 418*6dbdd20aSAndroid Build Coastguard Worker this.collectFlatTracks(tracks); 419*6dbdd20aSAndroid Build Coastguard Worker return tracks; 420*6dbdd20aSAndroid Build Coastguard Worker } 421*6dbdd20aSAndroid Build Coastguard Worker 422*6dbdd20aSAndroid Build Coastguard Worker private collectFlatTracks(tracks: TrackNode[]): void { 423*6dbdd20aSAndroid Build Coastguard Worker for (let i = 0; i < this.children.length; ++i) { 424*6dbdd20aSAndroid Build Coastguard Worker tracks.push(this.children[i]); // Push the current node before its children 425*6dbdd20aSAndroid Build Coastguard Worker this.children[i].collectFlatTracks(tracks); // Recurse to collect child tracks 426*6dbdd20aSAndroid Build Coastguard Worker } 427*6dbdd20aSAndroid Build Coastguard Worker } 428*6dbdd20aSAndroid Build Coastguard Worker 429*6dbdd20aSAndroid Build Coastguard Worker /** 430*6dbdd20aSAndroid Build Coastguard Worker * The flattened list of all descendent nodes in no particular order. 431*6dbdd20aSAndroid Build Coastguard Worker */ 432*6dbdd20aSAndroid Build Coastguard Worker get flatTracks(): ReadonlyArray<TrackNode> { 433*6dbdd20aSAndroid Build Coastguard Worker return Array.from(this.tracksById.values()); 434*6dbdd20aSAndroid Build Coastguard Worker } 435*6dbdd20aSAndroid Build Coastguard Worker 436*6dbdd20aSAndroid Build Coastguard Worker /** 437*6dbdd20aSAndroid Build Coastguard Worker * Remove all children from this node. 438*6dbdd20aSAndroid Build Coastguard Worker */ 439*6dbdd20aSAndroid Build Coastguard Worker clear(): void { 440*6dbdd20aSAndroid Build Coastguard Worker this._children = []; 441*6dbdd20aSAndroid Build Coastguard Worker this.tracksById.clear(); 442*6dbdd20aSAndroid Build Coastguard Worker this.fireOnChangeListener(); 443*6dbdd20aSAndroid Build Coastguard Worker } 444*6dbdd20aSAndroid Build Coastguard Worker 445*6dbdd20aSAndroid Build Coastguard Worker /** 446*6dbdd20aSAndroid Build Coastguard Worker * Find a track node by its id. 447*6dbdd20aSAndroid Build Coastguard Worker * 448*6dbdd20aSAndroid Build Coastguard Worker * Node: This is an O(1) operation. 449*6dbdd20aSAndroid Build Coastguard Worker * 450*6dbdd20aSAndroid Build Coastguard Worker * @param id The id of the node we want to find. 451*6dbdd20aSAndroid Build Coastguard Worker * @returns The node or undefined if no such node exists. 452*6dbdd20aSAndroid Build Coastguard Worker */ 453*6dbdd20aSAndroid Build Coastguard Worker getTrackById(id: string): TrackNode | undefined { 454*6dbdd20aSAndroid Build Coastguard Worker return this.tracksById.get(id); 455*6dbdd20aSAndroid Build Coastguard Worker } 456*6dbdd20aSAndroid Build Coastguard Worker 457*6dbdd20aSAndroid Build Coastguard Worker /** 458*6dbdd20aSAndroid Build Coastguard Worker * Find a track node via its URI. 459*6dbdd20aSAndroid Build Coastguard Worker * 460*6dbdd20aSAndroid Build Coastguard Worker * Node: This is an O(1) operation. 461*6dbdd20aSAndroid Build Coastguard Worker * 462*6dbdd20aSAndroid Build Coastguard Worker * @param uri The uri of the track to find. 463*6dbdd20aSAndroid Build Coastguard Worker * @returns The node or undefined if no such node exists with this URI. 464*6dbdd20aSAndroid Build Coastguard Worker */ 465*6dbdd20aSAndroid Build Coastguard Worker findTrackByUri(uri: string): TrackNode | undefined { 466*6dbdd20aSAndroid Build Coastguard Worker return this.tracksByUri.get(uri); 467*6dbdd20aSAndroid Build Coastguard Worker } 468*6dbdd20aSAndroid Build Coastguard Worker 469*6dbdd20aSAndroid Build Coastguard Worker private adopt(child: TrackNode): void { 470*6dbdd20aSAndroid Build Coastguard Worker if (child.parent) { 471*6dbdd20aSAndroid Build Coastguard Worker child.parent.removeChild(child); 472*6dbdd20aSAndroid Build Coastguard Worker } 473*6dbdd20aSAndroid Build Coastguard Worker child._parent = this; 474*6dbdd20aSAndroid Build Coastguard Worker this.addToIndex(child); 475*6dbdd20aSAndroid Build Coastguard Worker this.propagateAddition(child); 476*6dbdd20aSAndroid Build Coastguard Worker } 477*6dbdd20aSAndroid Build Coastguard Worker 478*6dbdd20aSAndroid Build Coastguard Worker private addToIndex(child: TrackNode) { 479*6dbdd20aSAndroid Build Coastguard Worker this.tracksById.set(child.id, child); 480*6dbdd20aSAndroid Build Coastguard Worker for (const [id, node] of child.tracksById) { 481*6dbdd20aSAndroid Build Coastguard Worker this.tracksById.set(id, node); 482*6dbdd20aSAndroid Build Coastguard Worker } 483*6dbdd20aSAndroid Build Coastguard Worker 484*6dbdd20aSAndroid Build Coastguard Worker child.uri && this.tracksByUri.set(child.uri, child); 485*6dbdd20aSAndroid Build Coastguard Worker for (const [uri, node] of child.tracksByUri) { 486*6dbdd20aSAndroid Build Coastguard Worker this.tracksByUri.set(uri, node); 487*6dbdd20aSAndroid Build Coastguard Worker } 488*6dbdd20aSAndroid Build Coastguard Worker } 489*6dbdd20aSAndroid Build Coastguard Worker 490*6dbdd20aSAndroid Build Coastguard Worker private removeFromIndex(child: TrackNode) { 491*6dbdd20aSAndroid Build Coastguard Worker this.tracksById.delete(child.id); 492*6dbdd20aSAndroid Build Coastguard Worker for (const [id] of child.tracksById) { 493*6dbdd20aSAndroid Build Coastguard Worker this.tracksById.delete(id); 494*6dbdd20aSAndroid Build Coastguard Worker } 495*6dbdd20aSAndroid Build Coastguard Worker 496*6dbdd20aSAndroid Build Coastguard Worker child.uri && this.tracksByUri.delete(child.uri); 497*6dbdd20aSAndroid Build Coastguard Worker for (const [uri] of child.tracksByUri) { 498*6dbdd20aSAndroid Build Coastguard Worker this.tracksByUri.delete(uri); 499*6dbdd20aSAndroid Build Coastguard Worker } 500*6dbdd20aSAndroid Build Coastguard Worker } 501*6dbdd20aSAndroid Build Coastguard Worker 502*6dbdd20aSAndroid Build Coastguard Worker private propagateAddition(node: TrackNode): void { 503*6dbdd20aSAndroid Build Coastguard Worker if (this.parent) { 504*6dbdd20aSAndroid Build Coastguard Worker this.parent.addToIndex(node); 505*6dbdd20aSAndroid Build Coastguard Worker this.parent.propagateAddition(node); 506*6dbdd20aSAndroid Build Coastguard Worker } 507*6dbdd20aSAndroid Build Coastguard Worker } 508*6dbdd20aSAndroid Build Coastguard Worker 509*6dbdd20aSAndroid Build Coastguard Worker private propagateRemoval(node: TrackNode): void { 510*6dbdd20aSAndroid Build Coastguard Worker if (this.parent) { 511*6dbdd20aSAndroid Build Coastguard Worker this.parent.removeFromIndex(node); 512*6dbdd20aSAndroid Build Coastguard Worker this.parent.propagateRemoval(node); 513*6dbdd20aSAndroid Build Coastguard Worker } 514*6dbdd20aSAndroid Build Coastguard Worker } 515*6dbdd20aSAndroid Build Coastguard Worker} 516*6dbdd20aSAndroid Build Coastguard Worker 517*6dbdd20aSAndroid Build Coastguard Worker/** 518*6dbdd20aSAndroid Build Coastguard Worker * Defines a workspace containing a track tree and a pinned area. 519*6dbdd20aSAndroid Build Coastguard Worker */ 520*6dbdd20aSAndroid Build Coastguard Workerexport class Workspace { 521*6dbdd20aSAndroid Build Coastguard Worker public title = '<untitled-workspace>'; 522*6dbdd20aSAndroid Build Coastguard Worker public readonly id: string; 523*6dbdd20aSAndroid Build Coastguard Worker onchange: (w: Workspace) => void = () => {}; 524*6dbdd20aSAndroid Build Coastguard Worker 525*6dbdd20aSAndroid Build Coastguard Worker // Dummy node to contain the pinned tracks 526*6dbdd20aSAndroid Build Coastguard Worker public readonly pinnedTracksNode = new TrackNode(); 527*6dbdd20aSAndroid Build Coastguard Worker public readonly tracks = new TrackNode(); 528*6dbdd20aSAndroid Build Coastguard Worker 529*6dbdd20aSAndroid Build Coastguard Worker get pinnedTracks(): ReadonlyArray<TrackNode> { 530*6dbdd20aSAndroid Build Coastguard Worker return this.pinnedTracksNode.children; 531*6dbdd20aSAndroid Build Coastguard Worker } 532*6dbdd20aSAndroid Build Coastguard Worker 533*6dbdd20aSAndroid Build Coastguard Worker constructor() { 534*6dbdd20aSAndroid Build Coastguard Worker this.id = createSessionUniqueId(); 535*6dbdd20aSAndroid Build Coastguard Worker this.pinnedTracksNode._workspace = this; 536*6dbdd20aSAndroid Build Coastguard Worker this.tracks._workspace = this; 537*6dbdd20aSAndroid Build Coastguard Worker 538*6dbdd20aSAndroid Build Coastguard Worker // Expanding these nodes makes the logic work 539*6dbdd20aSAndroid Build Coastguard Worker this.pinnedTracksNode.expand(); 540*6dbdd20aSAndroid Build Coastguard Worker this.tracks.expand(); 541*6dbdd20aSAndroid Build Coastguard Worker } 542*6dbdd20aSAndroid Build Coastguard Worker 543*6dbdd20aSAndroid Build Coastguard Worker /** 544*6dbdd20aSAndroid Build Coastguard Worker * Reset the entire workspace including the pinned tracks. 545*6dbdd20aSAndroid Build Coastguard Worker */ 546*6dbdd20aSAndroid Build Coastguard Worker clear(): void { 547*6dbdd20aSAndroid Build Coastguard Worker this.pinnedTracksNode.clear(); 548*6dbdd20aSAndroid Build Coastguard Worker this.tracks.clear(); 549*6dbdd20aSAndroid Build Coastguard Worker } 550*6dbdd20aSAndroid Build Coastguard Worker 551*6dbdd20aSAndroid Build Coastguard Worker /** 552*6dbdd20aSAndroid Build Coastguard Worker * Adds a track node to this workspace's pinned area. 553*6dbdd20aSAndroid Build Coastguard Worker */ 554*6dbdd20aSAndroid Build Coastguard Worker pinTrack(track: TrackNode): void { 555*6dbdd20aSAndroid Build Coastguard Worker // Make a lightweight clone of this track - just the uri and the title. 556*6dbdd20aSAndroid Build Coastguard Worker const cloned = new TrackNode({ 557*6dbdd20aSAndroid Build Coastguard Worker uri: track.uri, 558*6dbdd20aSAndroid Build Coastguard Worker title: track.title, 559*6dbdd20aSAndroid Build Coastguard Worker removable: track.removable, 560*6dbdd20aSAndroid Build Coastguard Worker }); 561*6dbdd20aSAndroid Build Coastguard Worker this.pinnedTracksNode.addChildLast(cloned); 562*6dbdd20aSAndroid Build Coastguard Worker } 563*6dbdd20aSAndroid Build Coastguard Worker 564*6dbdd20aSAndroid Build Coastguard Worker /** 565*6dbdd20aSAndroid Build Coastguard Worker * Removes a track node from this workspace's pinned area. 566*6dbdd20aSAndroid Build Coastguard Worker */ 567*6dbdd20aSAndroid Build Coastguard Worker unpinTrack(track: TrackNode): void { 568*6dbdd20aSAndroid Build Coastguard Worker const foundNode = this.pinnedTracksNode.children.find( 569*6dbdd20aSAndroid Build Coastguard Worker (t) => t.uri === track.uri, 570*6dbdd20aSAndroid Build Coastguard Worker ); 571*6dbdd20aSAndroid Build Coastguard Worker if (foundNode) { 572*6dbdd20aSAndroid Build Coastguard Worker this.pinnedTracksNode.removeChild(foundNode); 573*6dbdd20aSAndroid Build Coastguard Worker } 574*6dbdd20aSAndroid Build Coastguard Worker } 575*6dbdd20aSAndroid Build Coastguard Worker 576*6dbdd20aSAndroid Build Coastguard Worker /** 577*6dbdd20aSAndroid Build Coastguard Worker * Check if this workspace has a pinned track with the same URI as |track|. 578*6dbdd20aSAndroid Build Coastguard Worker */ 579*6dbdd20aSAndroid Build Coastguard Worker hasPinnedTrack(track: TrackNode): boolean { 580*6dbdd20aSAndroid Build Coastguard Worker return this.pinnedTracksNode.flatTracks.some((p) => p.uri === track.uri); 581*6dbdd20aSAndroid Build Coastguard Worker } 582*6dbdd20aSAndroid Build Coastguard Worker 583*6dbdd20aSAndroid Build Coastguard Worker /** 584*6dbdd20aSAndroid Build Coastguard Worker * Find a track node via its URI. 585*6dbdd20aSAndroid Build Coastguard Worker * 586*6dbdd20aSAndroid Build Coastguard Worker * Note: This in an O(N) operation where N is the number of nodes in the 587*6dbdd20aSAndroid Build Coastguard Worker * workspace. 588*6dbdd20aSAndroid Build Coastguard Worker * 589*6dbdd20aSAndroid Build Coastguard Worker * @param uri The uri of the track to find. 590*6dbdd20aSAndroid Build Coastguard Worker * @returns A reference to the track node if it exists in this workspace, 591*6dbdd20aSAndroid Build Coastguard Worker * otherwise undefined. 592*6dbdd20aSAndroid Build Coastguard Worker */ 593*6dbdd20aSAndroid Build Coastguard Worker findTrackByUri(uri: string): TrackNode | undefined { 594*6dbdd20aSAndroid Build Coastguard Worker return this.tracks.flatTracks.find((t) => t.uri === uri); 595*6dbdd20aSAndroid Build Coastguard Worker } 596*6dbdd20aSAndroid Build Coastguard Worker 597*6dbdd20aSAndroid Build Coastguard Worker /** 598*6dbdd20aSAndroid Build Coastguard Worker * Find a track by ID, also searching pinned tracks. 599*6dbdd20aSAndroid Build Coastguard Worker */ 600*6dbdd20aSAndroid Build Coastguard Worker getTrackById(id: string): TrackNode | undefined { 601*6dbdd20aSAndroid Build Coastguard Worker return ( 602*6dbdd20aSAndroid Build Coastguard Worker this.tracks.getTrackById(id) || this.pinnedTracksNode.getTrackById(id) 603*6dbdd20aSAndroid Build Coastguard Worker ); 604*6dbdd20aSAndroid Build Coastguard Worker } 605*6dbdd20aSAndroid Build Coastguard Worker 606*6dbdd20aSAndroid Build Coastguard Worker /** 607*6dbdd20aSAndroid Build Coastguard Worker * The ordered list of children belonging to this node. 608*6dbdd20aSAndroid Build Coastguard Worker */ 609*6dbdd20aSAndroid Build Coastguard Worker get children(): ReadonlyArray<TrackNode> { 610*6dbdd20aSAndroid Build Coastguard Worker return this.tracks.children; 611*6dbdd20aSAndroid Build Coastguard Worker } 612*6dbdd20aSAndroid Build Coastguard Worker 613*6dbdd20aSAndroid Build Coastguard Worker /** 614*6dbdd20aSAndroid Build Coastguard Worker * Inserts a new child node considering it's sortOrder. 615*6dbdd20aSAndroid Build Coastguard Worker * 616*6dbdd20aSAndroid Build Coastguard Worker * The child will be added before the first child whose |sortOrder| is greater 617*6dbdd20aSAndroid Build Coastguard Worker * than the child node's sort order, or at the end if one does not exist. If 618*6dbdd20aSAndroid Build Coastguard Worker * |sortOrder| is omitted on either node in the comparison it is assumed to be 619*6dbdd20aSAndroid Build Coastguard Worker * 0. 620*6dbdd20aSAndroid Build Coastguard Worker * 621*6dbdd20aSAndroid Build Coastguard Worker * @param child - The child node to add. 622*6dbdd20aSAndroid Build Coastguard Worker */ 623*6dbdd20aSAndroid Build Coastguard Worker addChildInOrder(child: TrackNode): void { 624*6dbdd20aSAndroid Build Coastguard Worker this.tracks.addChildInOrder(child); 625*6dbdd20aSAndroid Build Coastguard Worker } 626*6dbdd20aSAndroid Build Coastguard Worker 627*6dbdd20aSAndroid Build Coastguard Worker /** 628*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node at the start of the list of children. 629*6dbdd20aSAndroid Build Coastguard Worker * 630*6dbdd20aSAndroid Build Coastguard Worker * @param child The new child node to add. 631*6dbdd20aSAndroid Build Coastguard Worker */ 632*6dbdd20aSAndroid Build Coastguard Worker addChildLast(child: TrackNode): void { 633*6dbdd20aSAndroid Build Coastguard Worker this.tracks.addChildLast(child); 634*6dbdd20aSAndroid Build Coastguard Worker } 635*6dbdd20aSAndroid Build Coastguard Worker 636*6dbdd20aSAndroid Build Coastguard Worker /** 637*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node at the end of the list of children. 638*6dbdd20aSAndroid Build Coastguard Worker * 639*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to add. 640*6dbdd20aSAndroid Build Coastguard Worker */ 641*6dbdd20aSAndroid Build Coastguard Worker addChildFirst(child: TrackNode): void { 642*6dbdd20aSAndroid Build Coastguard Worker this.tracks.addChildFirst(child); 643*6dbdd20aSAndroid Build Coastguard Worker } 644*6dbdd20aSAndroid Build Coastguard Worker 645*6dbdd20aSAndroid Build Coastguard Worker /** 646*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node before an existing child node. 647*6dbdd20aSAndroid Build Coastguard Worker * 648*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to add. 649*6dbdd20aSAndroid Build Coastguard Worker * @param referenceNode An existing child node. The new node will be added 650*6dbdd20aSAndroid Build Coastguard Worker * before this node. 651*6dbdd20aSAndroid Build Coastguard Worker */ 652*6dbdd20aSAndroid Build Coastguard Worker addChildBefore(child: TrackNode, referenceNode: TrackNode): void { 653*6dbdd20aSAndroid Build Coastguard Worker this.tracks.addChildBefore(child, referenceNode); 654*6dbdd20aSAndroid Build Coastguard Worker } 655*6dbdd20aSAndroid Build Coastguard Worker 656*6dbdd20aSAndroid Build Coastguard Worker /** 657*6dbdd20aSAndroid Build Coastguard Worker * Add a new child node after an existing child node. 658*6dbdd20aSAndroid Build Coastguard Worker * 659*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to add. 660*6dbdd20aSAndroid Build Coastguard Worker * @param referenceNode An existing child node. The new node will be added 661*6dbdd20aSAndroid Build Coastguard Worker * after this node. 662*6dbdd20aSAndroid Build Coastguard Worker */ 663*6dbdd20aSAndroid Build Coastguard Worker addChildAfter(child: TrackNode, referenceNode: TrackNode): void { 664*6dbdd20aSAndroid Build Coastguard Worker this.tracks.addChildAfter(child, referenceNode); 665*6dbdd20aSAndroid Build Coastguard Worker } 666*6dbdd20aSAndroid Build Coastguard Worker 667*6dbdd20aSAndroid Build Coastguard Worker /** 668*6dbdd20aSAndroid Build Coastguard Worker * Remove a child node from this node. 669*6dbdd20aSAndroid Build Coastguard Worker * 670*6dbdd20aSAndroid Build Coastguard Worker * @param child The child node to remove. 671*6dbdd20aSAndroid Build Coastguard Worker */ 672*6dbdd20aSAndroid Build Coastguard Worker removeChild(child: TrackNode): void { 673*6dbdd20aSAndroid Build Coastguard Worker this.tracks.removeChild(child); 674*6dbdd20aSAndroid Build Coastguard Worker } 675*6dbdd20aSAndroid Build Coastguard Worker 676*6dbdd20aSAndroid Build Coastguard Worker /** 677*6dbdd20aSAndroid Build Coastguard Worker * The flattened list of all descendent nodes in depth first order. 678*6dbdd20aSAndroid Build Coastguard Worker * 679*6dbdd20aSAndroid Build Coastguard Worker * Use flatTracksUnordered if you don't care about track order, as it's more 680*6dbdd20aSAndroid Build Coastguard Worker * efficient. 681*6dbdd20aSAndroid Build Coastguard Worker */ 682*6dbdd20aSAndroid Build Coastguard Worker get flatTracksOrdered() { 683*6dbdd20aSAndroid Build Coastguard Worker return this.tracks.flatTracksOrdered; 684*6dbdd20aSAndroid Build Coastguard Worker } 685*6dbdd20aSAndroid Build Coastguard Worker 686*6dbdd20aSAndroid Build Coastguard Worker /** 687*6dbdd20aSAndroid Build Coastguard Worker * The flattened list of all descendent nodes in no particular order. 688*6dbdd20aSAndroid Build Coastguard Worker */ 689*6dbdd20aSAndroid Build Coastguard Worker get flatTracks() { 690*6dbdd20aSAndroid Build Coastguard Worker return this.tracks.flatTracks; 691*6dbdd20aSAndroid Build Coastguard Worker } 692*6dbdd20aSAndroid Build Coastguard Worker} 693