/********************************************************************************
 * Copyright (C) 2017 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { injectable , inject , postConstruct } from 'inversify' ; import { Emitter , Event , Disposable } from '../../common' ; import { CompositeTreeNode , TreeNode , Tree } from './tree' ; export const TreeExpansionService = Symbol ( 'TreeExpansionService' ) ; /**
 * The tree expandable service.
 */ export interface TreeExpansionService extends Disposable { /**
     * Emit when the node is expanded or collapsed.
     */ readonly onExpansionChanged : Event < Readonly < ExpandableTreeNode > > ; /**
     * Expand a node for the given node id if it is valid and collapsed.
     * Expanding a node refreshes all its children.
     *
     * Return a valid expanded refreshed node or `undefined` if such does not exist.
     */ expandNode ( node ) : Promise < Readonly < ExpandableTreeNode > | undefined > ; /**
     * If the given node is valid and expanded then collapse it.
     *
     * Return true if a node has been collapsed; otherwise false.
     */ collapseNode ( node ) : Promise < boolean > ; /**
     * If the given node is valid then collapse it recursively.
     *
     * Return true if a node has been collapsed; otherwise false.
     */ collapseAll ( node ) : Promise < boolean > ; /**
     * If the given node is invalid then does nothing.
     * If the given node is collapsed then expand it; otherwise collapse it.
     */ toggleNodeExpansion ( node ) : Promise < void > ; } /**
 * The expandable tree node.
 */ export interface ExpandableTreeNode extends CompositeTreeNode { /**
     * Test whether this tree node is expanded.
     */ expanded : boolean ; } export namespace ExpandableTreeNode { export function is ( node ) { return ! ! node && CompositeTreeNode . is ( node ) && 'expanded' in node ; } export function isExpanded ( node ) { return ExpandableTreeNode . is ( node ) && node . expanded ; } export function isCollapsed ( node ) { return ExpandableTreeNode . is ( node ) && ! node . expanded ; } } @ injectable ( ) export class TreeExpansionServiceImpl implements TreeExpansionService { @ inject ( Tree ) protected readonly tree : Tree ; protected readonly onExpansionChangedEmitter = new Emitter < ExpandableTreeNode > ( ) ; @ postConstruct ( ) protected init ( ) { this . tree . onNodeRefreshed ( node => { for ( const child of node . children ) { if ( ExpandableTreeNode . isExpanded ( child ) ) { node . waitUntil ( this . tree . refresh ( child ) ) ; } } } ) ; } dispose ( ) { this . onExpansionChangedEmitter . dispose ( ) ; } get onExpansionChanged ( ) : Event < ExpandableTreeNode > { return this . onExpansionChangedEmitter . event ; } protected fireExpansionChanged ( node ) { this . onExpansionChangedEmitter . fire ( node ) ; } async expandNode ( raw ) { const node = this . tree . validateNode ( raw ) ; if ( ExpandableTreeNode . isCollapsed ( node ) ) { return this . doExpandNode ( node ) ; } return undefined ; } protected async doExpandNode ( node ) { const refreshed = await this . tree . refresh ( node ) ; if ( ExpandableTreeNode . is ( refreshed ) ) { refreshed . expanded = true ; this . fireExpansionChanged ( refreshed ) ; return refreshed ; } return undefined ; } async collapseNode ( raw ) { const node = this . tree . validateNode ( raw ) ; return this . doCollapseNode ( node ) ; } async collapseAll ( raw ) { const node = this . tree . validateNode ( raw ) ; return this . doCollapseAll ( node ) ; } protected doCollapseAll ( node ) { let result = false ; if ( CompositeTreeNode . is ( node ) ) { for ( const child of node . children ) { result = this . doCollapseAll ( child ) || result ; } } return this . doCollapseNode ( node ) || result ; } protected doCollapseNode ( node ) { if ( ! ExpandableTreeNode . isExpanded ( node ) ) { return false ; } node . expanded = false ; this . fireExpansionChanged ( node ) ; return true ; } async toggleNodeExpansion ( node ) { if ( node . expanded ) { await this . collapseNode ( node ) ; } else { await this . expandNode ( node ) ; } } }