import * as Siema from 'siema' ; import { EventHub } from '../../event/EventHub' ; import { SyncEvent } from '../../event/SyncEvent' ; import { addClassName } from '../../utils/dom' ; import { uuid } from '../../utils/uuid' ; import { WhiteboardSnap } from '../AbstractWhiteboard/snap' ; import { WhitePage } from '../WhitePage/index' ; import { Mode } from './../../utils/types' ; import './index.css' ; const prefix = 'fcw-board' ; export abstract class AbstractWhiteboard { id : string = uuid ( ) ; sources : string [ ] = [ ] ; /** 元素 */ target : HTMLDivElement ; imgEles : HTMLDivElement [ ] = [ ] ; imgsContainer : HTMLDivElement ; pagesContainer : HTMLDivElement ; /** Options */ allowRollback = false ; autoFit = false ; onlyEmitSnap = false ; drawboardMountPoint : 'body' | 'parent' = 'body' ; snapInterval = 15 * 1000 ; /** UI Options */ eventHub ? : EventHub ; mode : Mode = 'master' ; isFullscreen = false ; /** 句柄 */ pages : WhitePage [ ] = [ ] ; get activePage ( ) { return this . pages [ this . visiblePageIndex ] ; } get pageMap ( ) : Record < string , WhitePage > { const map = { } ; this . pages . forEach ( p => ( map [ p . id ] = p ) ) ; return map ; } siema : any ; snapHistory : WhiteboardSnap [ ] = [ ] ; /** State | 内部状态 */ isInitialized = false ; isSyncing = false ; visiblePageIndex = 0 ; emitInterval : any ; constructor ( target , { sources , eventHub , visiblePageIndex , allowRollback , onlyEmitSnap , drawboardMountPoint , } = { } ) { if ( target ) { this . target = target ; } else { this . target = document . createElement ( 'div' ) ; document . body . appendChild ( this . target ) ; } if ( ! this . target . id ) { this . target . id = this . id ; } addClassName ( this . target , prefix ) ; if ( sources ) { this . sources = sources ; } this . eventHub = eventHub ; if ( typeof visiblePageIndex !== 'undefined' ) { this . visiblePageIndex = visiblePageIndex ; } this . onlyEmitSnap = ! ! onlyEmitSnap ; if ( typeof allowRollback !== 'undefined' ) { this . allowRollback = ! ! allowRollback ; } if ( drawboardMountPoint ) { this . drawboardMountPoint = drawboardMountPoint ; } this . init ( ) ; } /** LifeCycle */ public open ( ) { this . pages . forEach ( ( page , i ) => { page . open ( ) ; if ( i !== this . visiblePageIndex ) { page . hide ( ) ; } } ) ; } /** 关闭当前的 Whiteboard */ public close ( ) { if ( this . emitInterval ) { clearInterval ( this . emitInterval ) ; } } /** 展示当前的 WhitePage */ public show ( ) { if ( this . activePage ) { this . activePage . show ( ) ; } } public hide ( ) { if ( this . activePage ) { this . activePage . hide ( ) ; } } /** 触发事件 */ public emit ( borderEvent ) { if ( this . mode !== 'master' || ! this . eventHub ) { return ; } if ( this . onlyEmitSnap ) { if ( borderEvent . event !== 'borderSnap' ) { return ; } } if ( this . allowRollback && ( borderEvent . event === 'addMarker' || borderEvent . event === 'removeMarker' ) ) { if ( this . snapHistory . length > 20 ) { this . snapHistory . shift ( ) ; } this . snapHistory . push ( this . captureSnap ( false ) ) ; } borderEvent . timestamp = Math . floor ( Date . now ( ) / 1000 ) ; this . eventHub . emit ( 'sync' , borderEvent ) ; } /** 获取当前快照 */ public captureSnap ( shadow = true ) { if ( shadow ) { return { id : this . id , sources : this . sources , pageIds : this . pages . map ( page => page . id ) , visiblePageIndex : this . visiblePageIndex , } ; } return { id : this . id , sources : this . sources , pageIds : this . pages . map ( page => page . id ) , visiblePageIndex : this . visiblePageIndex , pages : this . pages . map ( p => p . captureSnap ( ) ) , } ; } /** 回退到上一个 Snap */ public rollbackSnap ( ) { if ( this . snapHistory . length === 0 || this . snapHistory . length === 1 ) { return ; } this . snapHistory . pop ( ) ; const snap = this . snapHistory [ this . snapHistory . length - 1 ] ; if ( snap ) { this . applySnap ( snap ) ; } } /** 销毁操作操作 */ public destroy ( ) { if ( this . emitInterval ) { clearInterval ( this . emitInterval ) ; } if ( this . eventHub ) { this . eventHub . removeAllListeners ( ) ; } if ( this . siema ) { this . siema . destroy ( ) ; } this . imgsContainer . remove ( ) ; this . pagesContainer . remove ( ) ; this . pages . forEach ( page => { page . destroy ( ) ; } ) ; } /** 初始化操作 */ protected abstract init ( ) ; /** 初始化 Siema */ protected initSiema ( ) { this . sources . forEach ( source => { const imgEle = document . createElement ( 'div' ) ; addClassName ( imgEle , `${ prefix }-img-wrapper` ) ; imgEle . style . backgroundImage = `url(${ source })` ; this . imgEles . push ( imgEle ) ; this . imgsContainer . appendChild ( imgEle ) ; } ) ; this . siema = new Siema ( { selector : this . imgsContainer , duration : 200 , easing : 'ease-out' , perPage : 1 , startIndex : 0 , draggable : false , multipleDrag : true , threshold : 20 , loop : false , rtl : false , } ) ; } protected abstract onPageChange ( nextPageIndex ) ; /** 响应获取到的快照事件 */ protected applySnap ( snap ) { const { id , sources , pageIds } = snap ; if ( ! this . isInitialized && ! this . isSyncing ) { this . id = id ; this . sources = sources ; this . isSyncing = true ; this . sources . forEach ( ( source , i ) => { const page = new WhitePage ( { imgSrc : source } , { mode : this . mode , whiteboard : this , parentContainer : this . pagesContainer , } ) ; page . id = pageIds [ i ] ; page . container . style . visibility = 'hidden' ; this . pages . push ( page ) ; page . open ( ) ; } ) ; this . initSiema ( ) ; this . isInitialized = true ; this . isSyncing = false ; } this . onPageChange ( snap . visiblePageIndex ) ; ( snap . pages || [ ] ) . forEach ( pageSnap => { const page = this . pageMap [ pageSnap . id ] ; if ( page ) { page . applySnap ( pageSnap ) ; } } ) ; } }