import { IDisposable } from '@phosphor/disposable' ; import { Menu , Widget } from '@phosphor/widgets' ; import { DataModel , DataGrid , TextRenderer , CellRenderer } from '@phosphor/datagrid' ; import { ISignal , Signal } from '@phosphor/signaling' ; import * as DataGridExtensions from '../services/dataGridExtensions' ; export namespace Table { export interface IOptions { contextMenu : Menu ; } } namespace Colors { export const unselectedBackgroundColor = 'white' ; export const selectedBackgroundColor = '#2196f3' ; export const unselectedTextColor = 'black' ; export const selectedTextColor = 'white' ; } export class Table implements IDisposable { constructor ( model , options ) { this . _grid = new DataGrid ( ) ; this . _grid . model = model ; this . _options = options ; this . _selectionManager = new DataGridExtensions . SelectionManager ( model ) ; this . _onClick = this . _onClick . bind ( this ) ; this . _onContextMenu = this . _onContextMenu . bind ( this ) ; this . _onDoubleClick = this . _onDoubleClick . bind ( this ) ; this . _selectionManager . selectionChanged . connect ( ( ) => { this . _updateRenderers ( ) ; } ) ; this . _clickEventHandler = DataGridExtensions . addMouseEventListener ( 'click' , this . _grid , this . _onClick ) ; this . _contextMenuEventHandler = DataGridExtensions . addMouseEventListener ( 'contextmenu' , this . _grid , this . _onContextMenu ) ; this . _dblclickEventHandler = DataGridExtensions . addMouseEventListener ( 'dblclick' , this . _grid , this . _onDoubleClick ) ; this . _fitColumnWidths ( ) ; } static fromKeysRows ( keys , data , options ) { const model = new TableDataModel ( keys , data ) ; return new Table ( model , options ) ; } get widget ( ) : Widget { return this . _grid ; } get selection ( ) : DataGridExtensions . BodyCellIndex | null { return this . _selectionManager . selection ; } get selectionValue ( ) : any | null { const selection = this . selection ; if ( selection !== null ) { return this . getCellValue ( selection ) ; } return null ; } get isDisposed ( ) : boolean { return this . _isDisposed ; } get dblclickSignal ( ) : ISignal < this , DataGridExtensions . BodyCellIndex > { return this . _dblclickSignal ; } getCellValue ( cellIndex ) { const { rowIndex , columnIndex } = cellIndex ; const value = this . _grid . model . data ( 'body' , rowIndex , columnIndex ) ; return value ; } dispose ( ) { this . _clickEventHandler . dispose ( ) ; this . _contextMenuEventHandler . dispose ( ) ; this . _dblclickEventHandler . dispose ( ) ; this . _grid . dispose ( ) ; this . _isDisposed = true ; } private _fitColumnWidths ( ) { DataGridExtensions . fitColumnWidths ( this . _grid , new TextRenderer ( ) ) ; } private _onClick ( event ) { const { row , column } = event ; this . _updateSelection ( row , column ) ; } private _onContextMenu ( event ) { const { row , column , rawEvent } = event ; this . _updateSelection ( row , column ) ; if ( this . _isInBody ( row , column ) ) { this . _options . contextMenu . open ( rawEvent . clientX , rawEvent . clientY ) ; rawEvent . preventDefault ( ) ; } } private _onDoubleClick ( event ) { const { row , column } = event ; if ( this . _isInBody ( row , column ) ) { const cellIndex = { rowIndex : row . index , columnIndex : column . index } ; this . _dblclickSignal . emit ( cellIndex ) ; } } private _updateSelection ( row , column ) { if ( this . _isInBody ( row , column ) ) { this . _selectionManager . selection = { rowIndex : row . index , columnIndex : column . index } ; } else { this . _selectionManager . selection = null ; } } private _isInBody ( row , column ) { return ( row . section === 'row' && column . section === 'column' && row . index !== null && column . index !== null ) ; } private _updateRenderers ( ) { const renderer = this . _textRendererForSelection ( this . _selectionManager . selection ) ; this . _grid . cellRenderers . set ( 'body' , { } , renderer ) ; } private _textRendererForSelection ( selectedCell ) { let backgroundColor ; let textColor ; if ( selectedCell === null ) { backgroundColor = Colors . unselectedBackgroundColor ; textColor = Colors . unselectedTextColor ; } else { const selectedRow = selectedCell . rowIndex ; const selectedColumn = selectedCell . columnIndex ; backgroundColor = ( { row , column } ) => { if ( row === selectedRow && column === selectedColumn ) { return Colors . selectedBackgroundColor ; } else { return Colors . unselectedBackgroundColor ; } } ; textColor = ( { row , column } ) => { if ( row === selectedRow && column === selectedColumn ) { return Colors . selectedTextColor ; } else { return Colors . unselectedTextColor ; } } ; } return new TextRenderer ( { backgroundColor , textColor } ) ; } private readonly _grid : DataGrid ; private readonly _selectionManager : DataGridExtensions . SelectionManager ; private readonly _clickEventHandler : IDisposable ; private readonly _contextMenuEventHandler : IDisposable ; private readonly _dblclickEventHandler : IDisposable ; private readonly _options : Table . IOptions ; private readonly _dblclickSignal : Signal < this , DataGridExtensions . BodyCellIndex > = new Signal ( this ) ; private _isDisposed : boolean = false ; } class TableDataModel extends DataModel { constructor ( keys , data ) { super ( ) ; this . _data = data ; this . _keys = keys ; } readonly _data : Array < Array < any > > ; readonly _keys : Array < string > ; rowCount ( region ) { return region === 'body' ? this . _data . length : 1 ; } columnCount ( region ) { return region === 'body' ? this . _keys . length : 1 ; } data ( region , row , column ) { if ( region === 'row-header' ) { return row ; } if ( region === 'column-header' ) { return this . _keys [ column ] ; } if ( region === 'corner-header' ) { return '' ; } return this . _serializeData ( this . _data [ row ] [ column ] ) ; } _serializeData ( data ) { const _type = typeof data ; if ( _type === 'object' ) { return JSON . stringify ( data ) ; } return data ; } }