import assign from 'object-assign' ; import CabinetBase , { emitter } from './base' ; import './sheet.css' ; import ToolbarEvents from '../../typings/sheet/plugins/toolbar/events' ; import { sheetPluginInitOrders , events , ReadyState } from './constants' ; const STATUS = { OFFLINE : 'offline' , OFFLINE_SAVING : 'offlineSaving' , OFFLINE_SAVED : 'offlineSaved' , OFFLINE_SAVE_FAILED : 'offlineSaveFailed' , ONLINE : 'online' , ONLINE_SAVING : 'onlineSaving' , ONLINE_SAVED : 'onlineSaved' , ONLINE_SAVE_FAILED : 'onlineSaveFailed' , SERVER_CHANGE_APPLIED : 'serverChangeApplied' } ; class ShimoSheetCabinet extends CabinetBase { public editor : ShimoSDK . Sheet . Editor ; public plugins : { collaboration ? : ShimoSDK . Common . Collaboration ; collaborators ? : ShimoSDK . Sheet . Collaborators ; comment ? : ShimoSDK . Sheet . Comment ; conditionalFormat ? : ShimoSDK . Sheet . ConditionalFormat ; formulaSidebar ? : ShimoSDK . Sheet . FormulaSidebar ; historySidebarSkeleton ? : ShimoSDK . Sheet . HistorySidebarSkeleton ; pivotTable ? : ShimoSDK . Sheet . PivotTable ; print ? : ShimoSDK . Sheet . Print ; toolbar ? : ShimoSDK . Sheet . Toolbar ; contextMenu ? : ShimoSDK . Sheet . SheetContextmenu ; shortcut ? : ShimoSDK . Sheet . Shortcut ; chart ? : ShimoSDK . Sheet . Chart ; fill ? : ShimoSDK . Sheet . Fill ; lock ? : ShimoSDK . Sheet . Lock ; filterViewport ? : ShimoSDK . Sheet . FilterViewport ; sheetTab ? : ShimoSDK . Sheet . SheetTab ; basic ? : ShimoSDK . Sheet . BasicPlugins ; dataValidation ? : ShimoSDK . Sheet . DataValidation ; } ; public getPlugin : ( name : string ) => Promise < any > ; private sdkSheet : any ; private sdkCommon : any ; private user : ShimoSDK . User ; private token : string ; private file : ShimoSDK . File ; private editorOptions : ShimoSDK . Sheet . EditorOptions ; private collaboration : ShimoSDK . Common . Collaboration ; private afterPluginReady : ( ( ) => void ) [ ] ; private onError : ( error : any ) => void ; private async ? : boolean ; protected pluginOptions : ShimoSDK . Sheet . Plugins ; protected emitter : emitter ; constructor ( options ) { super ( options . element ) ; this . sdkSheet = options . sdkSheet ; this . sdkCommon = options . sdkCommon ; this . user = options . user ; this . entrypoint = options . entrypoint ; this . token = options . token ; this . plugins = { } ; this . getPlugin = options . getPlugin ; this . async = options . async ; if ( typeof options . onError === 'function' ) { this . onError = options . onError ; } else { this . onError = err => { throw err ; } ; } const file = this . file = options . file ; this . editorOptions = assign ( { uploadConfig : { origin : file . config . uploadOrigin , server : file . config . uploadServer , token : file . config . uploadToken , maxFileSize : file . config . uploadMaxFileSize } , downloadConfig : { entryPoint : this . entrypoint } } , options . editorOptions , { editable : file . permissions . editable , commentable : file . permissions . commentable , user : file . config . user } ) ; this . availablePlugins = options . availablePlugins ; this . pluginOptions = this . preparePlugins ( options . editorOptions . plugins , { Lock : false } , this . editorOptions . isMobile ? plugin => [ 'ContextMenu' , 'Toolbar' , 'HistorySidebarSkeleton' , 'Print' , 'File' , 'Basic' , 'Shortcut' , 'FormulaSidebar' ] . indexOf ( plugin ) === - 1 : undefined ) ; this . afterPluginReady = [ ] ; if ( typeof options . emitter === 'function' ) { this . emitter = options . emitter ; } } public async render ( options ? ) { const editorElm = this . getElement ( undefined , 'div' , { id : 'sm-editor' , classList : [ 'sm-editor' ] } ) ; this . element . appendChild ( editorElm ) ; const editor = this . editor = this . initEditor ( this . editorOptions ) ; editor . render ( assign ( { spreadOptions : { allowScrollByPixel : true } } , options , { content : this . file . content , container : editorElm } ) ) ; this . emitter ( events . readyState , { [ events . readyState ] : ReadyState . editorReady } ) ; await Promise . all ( sheetPluginInitOrders . highest . map ( p => this . initPlugin ( editor , p ) ) ) ; editor . spread . gcSpread . _doResize ( ) ; const p = Promise . resolve ( ) . then ( ( ) => { for ( const cb of this . afterPluginReady ) { cb . call ( this ) ; } this . afterPluginReady = [ ] ; const referenceNode = document . getElementById ( 'toolbar' ) && document . getElementById ( 'contextmenu' ) ; if ( referenceNode ) { this . insertAfter ( referenceNode , editorElm ) ; } } ) . then ( ( ) => Promise . all ( sheetPluginInitOrders . normal . map ( p => this . initPlugin ( editor , p ) ) ) ) . catch ( err => this . onError ( err ) ) . then ( ( ) => { for ( const cb of this . afterPluginReady ) { cb . call ( this ) ; } this . afterPluginReady = [ ] ; this . emitter ( events . readyState , { [ events . readyState ] : ReadyState . pluginReady } ) ; this . emitter ( events . readyState , { [ events . readyState ] : ReadyState . allReady } ) ; } ) . catch ( err => this . onError ( err ) ) ; if ( ! this . async ) { await p ; } editor . on ( this . sdkSheet . sheets . Events , ( msg ) => { this . sdkSheet . sheets . utils . confirm ( { title : '表格异常' , description : '表格出现异常，无法编辑，请刷新后继续使用。<br /><i style="font-size:12px;color:#ccc;">' + msg + '</i>' , buttons : [ { type : 'button' , buttonLabel : '确定' , customClass : 'btn-ok' , closeAfterClick : true } ] } ) ; } ) ; return editor ; } public destroy ( ) { this . editor . destroy ( ) ; for ( const k in this . plugins ) { const plugin = this . plugins [ k ] ; if ( plugin ) { if ( typeof plugin . destroy === 'function' ) { plugin . destroy ( ) ; } } } } public initEditor ( options ) { return new this . sdkSheet . Editor ( options ) ; } private async initPlugin ( editor , plugin ) { if ( plugin in this . pluginOptions === false ) { return ; } if ( plugin !== 'Collaboration' && plugin !== 'Collaborators' ) { await this . getPlugin ( plugin ) ; } else { await this . getPlugin ( 'Collaborators' ) ; } const method = `init${ plugin }` ; if ( typeof this [ method ] === 'function' ) { this [ method ] ( editor ) ; } } public initBasicPlugins ( editor ) { const options = assign ( { editor , disableHighlightPosition : false , disableSplitColumns : this . editorOptions . isMobile , disableNumberText : this . editorOptions . isMobile , disableZoomScale : this . editorOptions . isMobile , disableFullscreen : this . editorOptions . isMobile , disableStatusBar : this . editorOptions . isMobile } , this . pluginOptions . Toolbar , { editor } ) as ShimoSDK . Sheet . BasePluginOptions ; this . plugins . basic = new this . sdkSheet . plugins . BasicPlugins ( options ) ; } public initToolbar ( editor ) { if ( this . pluginOptions . Toolbar === false || this . editorOptions . isMobile ) { return ; } const options = assign ( { } , this . pluginOptions . Toolbar , { editor } ) ; let container = this . getElement ( options . container ) ; if ( container === null ) { container = this . getElement ( undefined , 'div' , { id : 'sm-toolbar' } ) ; this . element . insertBefore ( container , this . element . firstChild ) ; } container . classList . add ( 'sm-toolbar' ) ; const toolbar = new this . sdkSheet . plugins . Toolbar ( options ) ; toolbar . render ( { container } ) ; this . plugins . toolbar = toolbar ; } public initMobileToolbar ( editor ) { if ( this . editorOptions . isMobile ) { const options = assign ( { } , this . pluginOptions . MobileToolbar , { editor } ) as ShimoSDK . Sheet . BasePluginOptions ; let container = this . getElement ( options . container ) ; if ( ! container ) { container = this . getElement ( undefined , 'div' , { id : 'sm-toolbar' } ) ; this . element . insertBefore ( container , this . element . firstChild ) ; } container . classList . add ( 'sm-toolbar' ) ; this . plugins . toolbar = new window . shimo . sdk . sheet . plugins . MobileToolbar ( { editor , container } ) ; } } public initMobileContextmenu ( editor ) { if ( this . editorOptions . isMobile ) { this . plugins . contextMenu = new this . sdkSheet . plugins . MobileContextmenu ( { editor } ) ; return ; } } public initContextMenu ( editor ) { if ( this . pluginOptions . ContextMenu === false || this . editorOptions . isMobile ) { return ; } const options = assign ( { } , this . pluginOptions . ContextMenu , { editor } ) ; const contextMenu = new this . sdkSheet . plugins . ContextMenu ( options ) ; const container = this . getElement ( options . container , 'div' , { id : 'sm-contextmenu' , classList : [ 'sm-contextmenu' ] } , this . element ) ; contextMenu . render ( { container } ) ; this . plugins . contextMenu = contextMenu ; } public initComment ( editor ) { if ( this . pluginOptions . Comment === false ) { return ; } const container = this . getElement ( undefined , 'div' , { id : 'sm-comment' , classList : [ 'sm-comment' ] } , this . element ) ; const options = assign ( { editor , container , currentUser : this . user , guid : this . file . guid , usePollingInsteadOfSocket : { interval : 10000 } , queryCommentOptions : { url : `${ this . entrypoint }/files/${ this . file . guid }/comments?accessToken=${ this . token }&_legacy=1` } , deleteCommentOptions : { url : `${ this . entrypoint }/files/${ this . file . guid }` + `/comments/{comment-id}?accessToken=${ this . token }&_legacy=1` } , closeCommentOptions : { url : `${ this . entrypoint }/comments/closeComments?accessToken=${ this . token }&_legacy=1` } , createCommentOptions : { url : `${ this . entrypoint }/files/${ this . file . guid }/comments?accessToken=${ this . token }&_legacy=1` } , fetchLocaleSync : ( locale ) => { return this . sdkSheet . plugins . CommentLocaleResources [ locale ] ; } } , this . pluginOptions . Comment ) ; this . plugins . comment = new this . sdkSheet . plugins . Comment ( options ) ; } public initHistorySidebarSkeleton ( editor ) { if ( this . pluginOptions . HistorySidebarSkeleton === false || this . editorOptions . isMobile ) { return ; } const sidebarOptions = assign ( { } , this . pluginOptions . HistorySidebarSkeleton ) ; const container = this . getElement ( sidebarOptions . container , 'div' , { classList : [ 'sm-history-sidebar-container' ] } ) ; this . element . appendChild ( container ) ; const options = { editor , container , guid : this . file . guid , currentUserId : `${ this . user . id }` , history : { loadHistoryUrl : `${ this . entrypoint }/files/${ this . file . guid }/` + `histories?accessToken=${ this . token }&_legacy=1` , revertUrl : `${ this . entrypoint }/files/${ this . file . guid }/revert?accessToken=${ this . token }&_legacy=1` , snapshotUrl : `${ this . entrypoint }/files/${ this . file . guid }/snapshot?accessToken=${ this . token }&_legacy=1` , loadStepsUrl : `${ this . entrypoint }/files/${ this . file . guid }/` + `changes?from={from}&to={to}&accessToken=${ this . token }&_legacy=1` , contactUrl : `${ this . entrypoint }/users?accessToken=${ this . token }&_legacy=1` } } ; const historySidebarSkeleton = new this . sdkSheet . plugins . HistorySidebarSkeleton ( options ) ; this . plugins . historySidebarSkeleton = historySidebarSkeleton ; const externalActions = this . getElement ( '#sm-external-actions' , 'span' , { id : 'sm-external-actions' , classList : [ 'sm-external-actions' , 'toolBar--tool' ] } ) ; const historyBtn = this . getElement ( undefined , 'span' , { classList : [ 'sm-external-actions-history' , 'toolBar-iconBtn' , 'indicator' , 'tooltip--docker' ] } ) ; if ( ! historyBtn . innerText ) { historyBtn . innerText = '历史' ; } historyBtn . setAttribute ( 'data-tooltip' , '历史' ) ; historyBtn . addEventListener ( 'click' , ( ) => historySidebarSkeleton . show ( ) ) ; externalActions . appendChild ( historyBtn ) ; this . afterPluginReady . push ( ( ) => { if ( this . plugins . toolbar ) { this . plugins . toolbar . on ( ToolbarEvents . MenuLayoutChanged , ( ) => { const toolbar = document . querySelector ( '.sm-toolbar' ) ; const groups = toolbar ! . querySelectorAll ( '.toolBar--content .toolBar--group' ) ; const elm = groups [ groups . length - 2 ] ; elm . insertBefore ( externalActions , elm . firstChild ) ; } ) ; } } ) ; } public initFormulaSidebar ( editor ) { if ( this . pluginOptions . FormulaSidebar === false ) { return ; } const container = this . getElement ( undefined , 'div' , { id : 'sm-formula-sidebar' } , this . element ) ; const formulaSidebar = new this . sdkSheet . plugins . FormulaSidebar ( assign ( { container } , this . pluginOptions . FormulaSidebar , { editor } ) ) ; this . plugins . formulaSidebar = formulaSidebar ; const btn = this . getElement ( undefined , 'span' , { id : 'sm-formula-btn' , classList : [ 'sm-formula-btn' ] } ) ; btn . addEventListener ( 'click' , ( ) => formulaSidebar . show ( ) ) ; } public initShortcut ( editor ) { if ( this . pluginOptions . Shortcut === false ) { return ; } const options = assign ( { } , this . pluginOptions . Shortcut , { editor } ) ; this . plugins . shortcut = new this . sdkSheet . plugins . Shortcut ( options ) ; } public initChart ( editor ) { if ( this . pluginOptions . Chart === false ) { return ; } const options = assign ( { } , this . pluginOptions . Shortcut , { editor } ) ; this . plugins . chart = new this . sdkSheet . plugins . Chart ( options ) ; } public initFill ( editor ) { if ( this . pluginOptions . Fill === false ) { return ; } const options = assign ( { } , this . pluginOptions . Fill , { editor } ) ; this . plugins . fill = new this . sdkSheet . plugins . Fill ( options ) ; } public initFilterViewport ( editor ) { if ( this . pluginOptions . FilterViewport === false ) { return ; } const options = assign ( { } , this . pluginOptions . FilterViewport , { editor } ) ; this . plugins . filterViewport = new this . sdkSheet . plugins . FilterViewport ( options ) ; } public initCollaboration ( editor ) { if ( this . pluginOptions . Collaboration === false ) { return ; } this . plugins . collaborators = editor . collaborators = this . plugins . collaborators = new this . sdkSheet . plugins . Collaborators ( assign ( { } , this . pluginOptions . Collaborators , { editor } ) ) ; const collaborationOptions = assign ( { pullUrl : `${ this . entrypoint }/files/${ this . file . guid }/pull?accessToken=${ this . token }` , composeUrl : `${ this . entrypoint }/files/${ this . file . guid }/compose?accessToken=${ this . token }` , selectUrl : `${ this . entrypoint }/files/${ this . file . guid }/select?accessToken=${ this . token }` , collaborators : this . plugins . collaborators , offlineEditable : false } , this . pluginOptions . Collaboration , { editor , rev : this . file . head , guid : this . file . guid } ) ; const collaboration = new this . sdkCommon . Collaboration ( collaborationOptions ) ; collaboration . start ( ) ; this . promptIfHasUnsavedChanges ( collaboration ) ; this . plugins . collaboration = this . collaboration = collaboration ; this . afterPluginReady . push ( ( ) => { if ( ! this . collaboration ) { return ; } let statusChangeHandler = collaborationOptions . onSaveStatusChange ; if ( typeof statusChangeHandler !== 'function' ) { statusChangeHandler = getStatusChangeHandler . call ( this ) ; } this . collaboration . on ( 'saveStatusChange' as ShimoSDK . Common . CollaborationEvents , statusChangeHandler ! ) ; function getStatusChangeHandler ( ) { const toolbarElm = document . querySelector ( '.sm-toolbar .tabStrip' ) ; let changeText = ( text ) => { } ; if ( toolbarElm ) { const statusElm = this . getElement ( undefined , 'div' , { classList : [ 'menu' , 'tabMenu' , 'sm-save-status' ] } ) ; statusElm . textContent = getText ( STATUS . ONLINE as ShimoSDK . Common . CollaborationStatus ) ; toolbarElm . appendChild ( statusElm ) ; changeText = ( text ) => { statusElm . textContent = text ; } ; } const showAlert = this . sdkSheet . sheets . utils . showAlert ; const hideAlert = this . sdkSheet . sheets . utils . hideAlert ; return ( status , data ) => { const text = getText ( status ) ; switch ( status ) { case STATUS . ONLINE_SAVING : changeText ( text ) ; break ; case STATUS . ONLINE_SAVED : this . updateEditorOptions ( ) ; hideAlert ( ) ; changeText ( text ) ; break ; case STATUS . OFFLINE : changeText ( text ) ; if ( ! ( data && data . forceOffline ) ) { return ; } this . updateEditorOptions ( { commentable : false , editable : false } ) ; showAlert ( { title : '网络已断开，无法继续编写，请等待网络恢复' , type : 'error' , autoHide : false } ) ; this . log ( 'warn' , { event : 'offline' , fileType : 'sheet' , fileGuid : this . file . guid , haveUnsavedChange : this . collaboration . haveUnsavedChange ( ) , isCsQueueEmpty : this . editor . isCsQueueEmpty ( ) } ) ; break ; case STATUS . ONLINE : changeText ( text ) ; this . updateEditorOptions ( ) ; break ; case STATUS . ONLINE_SAVE_FAILED : this . updateEditorOptions ( { commentable : false , editable : false } ) ; changeText ( text ) ; showAlert ( { title : '保存失败，请刷新页面重试' , type : 'error' , autoHide : false } ) ; break ; case STATUS . OFFLINE_SAVE_FAILED : changeText ( text ) ; showAlert ( { title : '保存失败，请刷新页面重试' , type : 'error' , autoHide : false } ) ; this . updateEditorOptions ( { commentable : false , editable : false } ) ; break ; } } ; function getText ( status ) { switch ( status ) { case STATUS . ONLINE_SAVING : return '保存中...' ; case STATUS . ONLINE_SAVED : return '保存成功' ; case STATUS . OFFLINE : return '已离线' ; case STATUS . ONLINE : return '自动保存已启用' ; case STATUS . ONLINE_SAVE_FAILED : return '保存失败' ; case STATUS . OFFLINE_SAVE_FAILED : return '离线保存失败' ; } return '' ; } } } ) ; } public initLock ( editor ) { if ( this . pluginOptions . Lock === false ) { return ; } const options = assign ( { } , this . pluginOptions . Lock , { editor , currentUser : { id : this . user . id } } ) as ShimoSDK . Sheet . LockOptions ; const plugins = this . pluginOptions ; const lockOptions = plugins . Lock ! as ShimoSDK . Sheet . LockOptions ; if ( typeof lockOptions . fetchCollaborators === 'function' ) { options . fetchCollaborators = lockOptions . fetchCollaborators ; } this . plugins . lock = new this . sdkSheet . plugins . Lock ( options ) ; } public initPrint ( editor ) { if ( this . pluginOptions . Print === false ) { return ; } this . plugins . print = new this . sdkSheet . plugins . Print ( { editor } ) ; } public initConditionalFormat ( editor ) { if ( this . pluginOptions . ConditionalFormat === false ) { return ; } this . plugins . conditionalFormat = new this . sdkSheet . plugins . ConditionalFormat ( { editor } ) ; } public initPivotTable ( editor ) { if ( this . pluginOptions . PivotTable === false ) { return ; } this . plugins . pivotTable = new this . sdkSheet . plugins . PivotTable ( { editor } ) ; } public initMobileSheetTab ( editor ) { if ( ! this . editorOptions . isMobile || this . pluginOptions . MobileSheetTab === false ) { return ; } const options = assign ( { } , this . pluginOptions . MobileSheetTab ) as ShimoSDK . Sheet . BasePluginOptions ; let container = this . getElement ( options . container ) ; if ( ! container ) { container = this . getElement ( undefined , 'div' , { id : 'sm-mobile-sheet-tab' } ) ; this . element . append ( container ) ; } container . classList . add ( 'sm-mobile-sheet-tab' ) ; this . plugins . sheetTab = new this . sdkSheet . plugins . MobileSheetTab ( { editor , container : container } ) ; } public initDataValidation ( editor ) { if ( this . pluginOptions . DataValidation === false ) { return ; } this . plugins . dataValidation = new this . sdkSheet . plugins . DataValidation ( { editor } ) ; } private updateEditorOptions ( options ? ) { if ( this . editor ) { this . editor . updateOptions ( assign ( { commentable : this . editorOptions . commentable , editable : this . editorOptions . editable } , options ) ) ; } } } export default ShimoSheetCabinet ;