/********************************************************************************
 * 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 { inject , named , injectable } from 'inversify' ; import { Widget } from '@phosphor/widgets' ; import { ILogger , Emitter , Event , ContributionProvider , MaybePromise , WaitUntilEvent } from '../common' ; export const WidgetFactory = Symbol ( 'WidgetFactory' ) ; /**
 * A  WidgetFactory is used to create new widgets. Factory-specific information (options) can be passed as serializable JSON data.
 * The common  WidgetManager collects  `WidgetFactory` contributions and delegates to the corresponding factory when
 * a widget should be created or restored. To identify widgets the `WidgetManager` uses a description composed of the factory id and the options.
 * The `WidgetFactory` does support both, synchronous and asynchronous widget creation.
 *
 * ### Example usage
 *
 * ```typescript
 * export class MyWidget extends BaseWidget {
 * }
 *
 *  injectable export interface WidgetFactory { /**
     * The factory id.
     */ readonly id : string ; param options returns createWidget ( options ? ) : MaybePromise < Widget > ; } /**
 * Representation of the `WidgetConstructionOptions`.
 * Defines a serializable description to create widgets.
 */ export interface WidgetConstructionOptions { /**
     * The id of the widget factory to use.
     */ factoryId : string ; /**
     * The widget factory specific information.
     */ options ? : any ; } /**
 * Representation of a `willCreateWidgetEvent`.
 */ export interface WillCreateWidgetEvent extends WaitUntilEvent { /**
     * The widget which will be created.
     */ readonly widget : Widget ; /**
     * The widget factory id.
     */ readonly factoryId : string ; } /**
 * Representation of a `didCreateWidgetEvent`.
 */ export interface DidCreateWidgetEvent { /**
     * The widget which was created.
     */ readonly widget : Widget ; /**
     * The widget factory id.
     */ readonly factoryId : string ; } /**
 * The  WidgetManager is the common component responsible for creating and managing widgets. Additional widget factories
 * can be registered by using the  WidgetFactory contribution point. To identify a widget, created by a factory, the factory id and
 * the creation options are used. This key is commonly referred to as `description` of the widget.
  @ injectable ( ) export class WidgetManager { protected _cachedFactories : Map < string , WidgetFactory > ; protected readonly widgets = new Map < string , Widget > ( ) ; protected readonly widgetPromises = new Map < string , MaybePromise < Widget > > ( ) ; protected readonly pendingWidgetPromises = new Map < string , MaybePromise < Widget > > ( ) ; @ inject ( ContributionProvider ) @ named ( WidgetFactory ) protected readonly factoryProvider : ContributionProvider < WidgetFactory > ; @ inject ( ILogger ) protected readonly logger : ILogger ; protected readonly onWillCreateWidgetEmitter = new Emitter < WillCreateWidgetEvent > ( ) ; /**
     * An event can be used to participate in the widget creation.
     * Listeners may not dispose the given widget.
     */ readonly onWillCreateWidget : Event < WillCreateWidgetEvent > = this . onWillCreateWidgetEmitter . event ; protected readonly onDidCreateWidgetEmitter = new Emitter < DidCreateWidgetEvent > ( ) ; readonly onDidCreateWidget : Event < DidCreateWidgetEvent > = this . onDidCreateWidgetEmitter . event ; param factoryId returns getWidgets ( factoryId ) { const result = [ ] ; for ( const [ key , widget ] of this . widgets . entries ( ) ) { if ( this . fromKey ( key ) . factoryId === factoryId ) { result . push ( widget ) ; } } return result ; } param factoryId param options returns tryGetWidget < T extends Widget > ( factoryId , options ? ) { const key = this . toKey ( { factoryId , options } ) ; const existing = this . widgetPromises . get ( key ) ; if ( existing instanceof Widget ) { return existing as T ; } return undefined ; } param factoryId param options returns tryGetPendingWidget < T extends Widget > ( factoryId , options ? ) { const key = this . toKey ( { factoryId , options } ) ; return this . doGetWidget ( key ) ; } param factoryId param options returns async getWidget < T extends Widget > ( factoryId , options ? ) { const key = this . toKey ( { factoryId , options } ) ; const pendingWidget = this . doGetWidget < T > ( key ) ; const widget = pendingWidget && await pendingWidget ; return widget ; } protected doGetWidget < T extends Widget > ( key ) { const pendingWidget = this . widgetPromises . get ( key ) ?? this . pendingWidgetPromises . get ( key ) ; if ( pendingWidget ) { return pendingWidget as MaybePromise < T > ; } return undefined ; } param factoryId param options returns async getOrCreateWidget < T extends Widget > ( factoryId , options ? ) { const key = this . toKey ( { factoryId , options } ) ; const existingWidget = this . doGetWidget < T > ( key ) ; if ( existingWidget ) { return existingWidget ; } const factory = this . factories . get ( factoryId ) ; if ( ! factory ) { throw Error ( "No widget factory '" + factoryId + "' has been registered." ) ; } try { const widgetPromise = factory . createWidget ( options ) ; this . pendingWidgetPromises . set ( key , widgetPromise ) ; const widget = await widgetPromise ; await WaitUntilEvent . fire ( this . onWillCreateWidgetEmitter , { factoryId , widget } ) ; this . widgetPromises . set ( key , widgetPromise ) ; this . widgets . set ( key , widget ) ; widget . disposed . connect ( ( ) => { this . widgets . delete ( key ) ; this . widgetPromises . delete ( key ) ; } ) ; this . onDidCreateWidgetEmitter . fire ( { factoryId , widget } ) ; return widget as T ; } finally { this . pendingWidgetPromises . delete ( key ) ; } } param widget returns getDescription ( widget ) { for ( const [ key , aWidget ] of this . widgets . entries ( ) ) { if ( aWidget === widget ) { return this . fromKey ( key ) ; } } return undefined ; } param options returns protected toKey ( options ) { return JSON . stringify ( options ) ; } param key returns protected fromKey ( key ) { return JSON . parse ( key ) ; } protected get factories ( ) : Map < string , WidgetFactory > { if ( ! this . _cachedFactories ) { this . _cachedFactories = new Map ( ) ; for ( const factory of this . factoryProvider . getContributions ( ) ) { if ( factory . id ) { this . _cachedFactories . set ( factory . id , factory ) ; } else { this . logger . error ( 'Invalid ID for factory: ' + factory + ". ID was: '" + factory . id + "'." ) ; } } } return this . _cachedFactories ; } }