import { MarkdownRenderChild , MetadataCache , TFile , Vault } from 'obsidian' ; import clamp from 'lodash/clamp' ; import { html } from 'common-tags' ; import BannersPlugin , { MPPCPlus } from './main' ; import MetaManager , { BannerMetadata } from './MetaManager' ; interface XY { x : number ; y : number ; } export default class Banner extends MarkdownRenderChild { wrapper : HTMLElement ; plugin : BannersPlugin ; metadataCache : MetadataCache ; vault : Vault ; metaManager : MetaManager ; ctx : MPPCPlus ; bannerData : BannerMetadata ; isEmbed : boolean ; isDragging : boolean ; prevPos : XY ; constructor ( plugin , el , wrapper , ctx , bannerData , isEmbed ) { super ( el ) ; this . wrapper = wrapper ; this . plugin = plugin ; this . metadataCache = plugin . app . metadataCache ; this . vault = plugin . vault ; this . metaManager = plugin . metaManager ; this . ctx = ctx ; this . bannerData = bannerData ; this . isEmbed = isEmbed ; this . isDragging = false ; this . prevPos = null ; } onload ( ) { const { allowMobileDrag , style } = this . plugin . settings ; const { containerEl : contentEl } = this . ctx ; const { banner : src , banner_x = 0.5 , banner_y = 0.5 } = this . bannerData ; this . wrapper . addClass ( 'obsidian-banner-wrapper' ) ; this . containerEl . addClasses ( [ 'obsidian-banner' , style ] ) ; const messageBox = document . createElement ( 'div' ) ; messageBox . className = 'banner-message' ; messageBox . innerHTML = html `
      <div class="spinner">
        <div class="bounce1"></div>
        <div class="bounce2"></div>
        <div class="bounce3"></div>
      </div>
    ` ; const img = document . createElement ( 'img' ) ; img . className = 'banner-image full-width' ; img . style . objectPosition = `${ banner_x * 100 }% ${ banner_y * 100 }%` ; img . draggable = false ; img . onload = ( ) => { this . wrapper . addClass ( 'loaded' ) ; } ; img . onerror = ( ) => { messageBox . innerHTML = `<p>Error loading banner image! Is the <code>${ this . plugin . getSettingValue ( 'frontmatterField' ) }</code> field valid?</p>` ; this . wrapper . addClass ( 'error' ) ; } ; if ( ! this . isEmbed ) { img . onmousedown = ( e ) => this . handleDragStart ( e ) ; img . onmousemove = ( e ) => this . handleDragMove ( e ) ; contentEl . parentElement . onmouseup = ( ) => this . handleDragEnd ( img ) ; if ( allowMobileDrag ) { img . ontouchstart = ( e ) => this . handleDragStart ( e ) ; img . ontouchmove = ( e ) => this . handleDragMove ( e ) ; contentEl . parentElement . ontouchend = ( ) => this . handleDragEnd ( img ) ; } } img . src = this . parseSource ( src ) ; this . containerEl . append ( messageBox , img ) ; this . wrapper . prepend ( this . containerEl ) ; } handleDragStart ( e ) { this . prevPos = this . getMousePos ( e , this . containerEl ) ; this . isDragging = true ; } handleDragMove ( e ) { const img = e . target as HTMLImageElement ; const wrapper = img . parentElement ; if ( ! this . isDragging ) { return ; } const currentPos = this . getMousePos ( e , this . containerEl ) ; const delta = { x : ( currentPos . x - this . prevPos . x ) / wrapper . clientWidth * 100 , y : ( currentPos . y - this . prevPos . y ) / wrapper . clientHeight * 100 } ; this . prevPos = currentPos ; const [ x , y ] = img . style . objectPosition . split ( ' ' ) . map ( n => parseFloat ( n ) ) ; if ( ( img . naturalHeight / img . naturalWidth ) >= ( wrapper . clientHeight / wrapper . clientWidth ) ) { const newY = clamp ( y - delta . y , 0 , 100 ) ; img . style . objectPosition = `${ x }% ${ newY }%` ; } else { const newX = clamp ( x - delta . x , 0 , 100 ) ; img . style . objectPosition = `${ newX }% ${ y }%` ; } } async handleDragEnd ( img ) { const { sourcePath } = this . ctx ; if ( ! this . isDragging ) { return ; } this . isDragging = false ; const [ x , y ] = img . style . objectPosition . split ( ' ' ) . map ( n => Math . round ( parseFloat ( n ) * 1000 ) / 100000 ) ; await this . metaManager . upsertBannerData ( sourcePath , { banner_x : x , banner_y : y } ) ; } parseSource ( src ) { if ( /^\!\[\[.+\]\]$/ . test ( src ) ) { const link = src . slice ( 3 , - 2 ) ; const file = this . metadataCache . getFirstLinkpathDest ( link , this . ctx . sourcePath ) ; return file ? this . vault . getResourcePath ( file ) : link ; } const path = src . startsWith ( '/' ) ? src . slice ( 1 ) : src ; const file = this . vault . getAbstractFileByPath ( path ) ; return ( file instanceof TFile ) ? this . vault . getResourcePath ( file ) : src ; } getMousePos ( e , div ) { const { pageX , pageY } = ( e instanceof MouseEvent ) ? e : e . targetTouches [ 0 ] ; return { x : pageX - div . offsetTop , y : pageY - div . offsetLeft } ; } }