import { AmbientLightData } from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/module.mjs" ; import SETTINGS from "../../common/Settings" ; import { AmbientLightExt , AnimatorData , KeyFrame , LightAnimator , PropertyDelta } from "./LightAnimator" ; declare class LightConfigExt extends LightConfig { anims : ActiveLightConfig ; } export default class ActiveLightConfig extends Application { static ready ( ) { if ( game . user . isGM ) Hooks . on ( 'renderAmbientLightConfig' , this . _renderAmbientLightConfig ) ; } private static _renderAmbientLightConfig ( app , html ) { if ( ! ( app . object instanceof AmbientLightDocument ) ) return ; const config = app as LightConfigExt ; if ( ! config . anims ) { config . anims = new ActiveLightConfig ( app ) ; } config . anims . _inject ( html ) ; app . element [ 0 ] . style . height = '' ; app . setPosition ( { } ) ; } static get defaultOptions ( ) : Application . Options { return < any > mergeObject ( Application . defaultOptions as Partial < Application . Options > , { template : `modules/${ SETTINGS . MOD_NAME }/templates/active-light-config.hbs` , width : 600 , resizable : false , height : 465 , } ) ; } private _object : AmbientLightDocument ; private _data : AnimatorData ; constructor ( app ) { const obj = app . object ; super ( { id : obj . id + '-anims' , title : game . i18n . localize ( 'DF_ACTIVE_LIGHTS.Config.Title' ) + obj . id , } ) ; this . _object = obj ; this . _data = < AnimatorData > obj . getFlag ( SETTINGS . MOD_NAME , LightAnimator . FLAG_ANIMS ) ; if ( ! this . _data ) { this . _data = { bounce : false , offset : 0 , keys : [ ActiveLightConfig . _createKeyFrame ( 0 , obj . data ) ] } ; } else this . _data = < any > duplicate ( this . _data ) ; } getData ( _options ? ) { return { bounce : this . _data . bounce , offset : this . _data . offset === 0 ? '' : this . _data . offset } ; } private _inject ( html ) { const animConfigButton = $ ( `<button type="button" style="margin:0.25em 0" name="anim-config"><i class="fas fa-cogs"></i> ${ game . i18n . localize ( 'DF_ACTIVE_LIGHTS.Config.OpenButton' ) }</button>` ) ; animConfigButton . on ( 'click' , ( e ) => { e . preventDefault ( ) ; if ( this . _state === Application . RENDER_STATES . RENDERED ) this . bringToTop ( ) ; else this . render ( true ) ; } ) ; html . find ( 'div[data-tab="animation"]' ) . append ( animConfigButton ) ; } private static _createKeyFrame ( time , data ? ) { return { time , angle : { enabled : false , value : data ?. config ?. angle ?? 0 } , bright : { enabled : false , value : data ?. config ?. bright ?? 0 } , dim : { enabled : false , value : data ?. config ?. dim ?? 0 } , rotation : { enabled : false , value : data ?. rotation ?? 0 } , tintAlpha : { enabled : false , value : data ?. config ?. alpha ?? 0 } , tintColor : { enabled : false , value : data ?. config ?. color ?? '#000000' , isColor : true } } ; } async activateListeners ( html ) { const propContainer = < JQuery < HTMLElement > > html . find ( 'main>section' ) ; const keysContainer = < JQuery < HTMLOListElement > > html . find ( 'aside>ol' ) ; html . find ( 'input[name="bounce"]' ) . on ( 'change' , ( e ) => { this . _data . bounce = ( e . currentTarget as HTMLInputElement ) . checked ; this . _save ( ) ; } ) ; html . find ( 'input[name="offset"]' ) . on ( 'change' , ( e ) => { this . _data . offset = ( e . currentTarget as HTMLInputElement ) . valueAsNumber ; this . _save ( ) ; } ) ; html . find ( 'button[name="add-keyframe"]' ) . on ( 'click' , async ( ) => { this . _data . keys [ this . _data . keys . length - 1 ] . time ; const keyFrame = ActiveLightConfig . _createKeyFrame ( this . _data . keys [ this . _data . keys . length - 1 ] . time + 1000 ) ; this . _data . keys . push ( keyFrame ) ; await this . _appendKeyFrame ( propContainer , keysContainer , keyFrame ) ; await this . _save ( ) ; } ) ; for ( const key of this . _data . keys . slice ( 1 ) ) { await this . _appendKeyFrame ( propContainer , keysContainer , key ) ; } $ ( html . find ( 'aside>ol>li' ) [ 0 ] ) . on ( 'click' , async ( e ) => { if ( e . currentTarget . classList . contains ( 'active' ) ) return ; keysContainer . find ( 'li.active' ) . removeClass ( 'active' ) ; e . currentTarget . classList . add ( 'active' ) ; await this . _loadDataForKeyFrame ( propContainer , keysContainer , this . _data . keys [ $ ( e . currentTarget ) . index ( ) ] , $ ( e . currentTarget ) . index ( ) === 0 ) ; } ) ; this . _loadDataForKeyFrame ( propContainer , keysContainer , this . _data . keys [ 0 ] , true ) ; } private async _loadDataForKeyFrame ( propContainer , keysContainer , keyFrame , first ) { const propsElement = $ ( await renderTemplate ( `modules/${ SETTINGS . MOD_NAME }/templates/active-light-props.hbs` , mergeObject ( { first : first } , < any > keyFrame ) ) ) ; propContainer . empty ( ) ; propContainer . append ( propsElement ) ; propsElement . find ( 'input[name="time"]' ) . on ( 'change' , async ( e ) => { const newValue = ( e . currentTarget as HTMLInputElement ) . valueAsNumber ; if ( newValue === 0 ) { e . currentTarget . setAttribute ( 'value' , keyFrame . time . toString ( ) ) ; ui . notifications . warn ( game . i18n . localize ( 'DF_ACTIVE_LIGHTS.Warnings.KeyFrame_Zero' ) ) ; return ; } const conflict = this . _data . keys . find ( x => x . time === newValue ) ; if ( conflict ) { e . currentTarget . setAttribute ( 'value' , keyFrame . time . toString ( ) ) ; ui . notifications . warn ( game . i18n . localize ( 'DF_ACTIVE_LIGHTS.Warnings.KeyFrame_Exists' ) . replace ( '{0}' , newValue . toString ( ) ) ) ; return ; } e . currentTarget . setAttribute ( 'data-time' , newValue . toString ( ) ) ; const keys = < HTMLInputElement [ ] > Array . from ( keysContainer [ 0 ] . children ) ; const keyElement = keys . find ( x => parseInt ( x . getAttribute ( 'data-time' ) ) === keyFrame . time ) ; keyElement . setAttribute ( 'data-time' , newValue . toString ( ) ) ; keyElement . querySelector ( 'span' ) . innerHTML = ( newValue / 1000 ) . toNearest ( 0.001 ) . toString ( ) + ' ' + game . i18n . localize ( 'DF_ACTIVE_LIGHTS.Seconds' ) ; keys . sort ( ( a , b ) => parseInt ( a . getAttribute ( 'data-time' ) ) < parseInt ( b . getAttribute ( 'data-time' ) ) ? - 1 : 1 ) ; keysContainer . append ( keys ) ; keyFrame . time = newValue ; this . _data . keys . sort ( ( a , b ) => a . time < b . time ? - 1 : 1 ) ; await this . _save ( ) ; } ) ; const getPropDelta = ( propName ) => < PropertyDelta > this . _data . keys [ keysContainer . find ( '.active' ) . index ( ) ] [ propName ] ; propsElement . find ( 'input[data-type="prop"]' ) . on ( 'change' , async ( e ) => { const propName = e . currentTarget . parentElement . id ; const input = $ ( e . currentTarget . parentElement ) . find ( `input[name="${ propName }"]` ) [ 0 ] as HTMLInputElement ; const value = propName !== 'tintColor' ? input . valueAsNumber : input . value ; getPropDelta ( propName ) . value = value ; await this . _save ( ) ; } ) ; propsElement . find ( 'select' ) . on ( 'change' , async ( e ) => { const propName = e . currentTarget . parentElement . id ; const value = e . currentTarget . value ; getPropDelta ( propName ) . func = value ; await this . _save ( ) ; } ) ; propsElement . find ( 'input[type="checkbox"]' ) . on ( 'click' , async ( e ) => { const propName = e . currentTarget . parentElement . id ; const input = $ ( e . currentTarget . parentElement ) . find ( `input[name="${ propName }"]` ) ; const select = $ ( e . currentTarget . parentElement ) . find ( `select` ) ; if ( ( e . currentTarget as HTMLInputElement ) . checked ) { input . removeAttr ( 'disabled' ) ; select . removeAttr ( 'disabled' ) ; } else { input . attr ( 'disabled' , '' ) ; select . attr ( 'disabled' , '' ) ; } getPropDelta ( propName ) . enabled = ( e . currentTarget as HTMLInputElement ) . checked ; await this . _save ( ) ; } ) ; } private async _appendKeyFrame ( propContainer , keysContainer , keyFrame ) { const keyElement = $ ( await renderTemplate ( `modules/${ SETTINGS . MOD_NAME }/templates/active-light-key.hbs` , { time : keyFrame . time , label : ( keyFrame . time / 1000 ) . toNearest ( 0.001 ) } ) ) ; keyElement . on ( 'click' , async ( e ) => { if ( e . currentTarget . classList . contains ( 'active' ) ) return ; keysContainer . find ( 'li.active' ) . removeClass ( 'active' ) ; e . currentTarget . classList . add ( 'active' ) ; await this . _loadDataForKeyFrame ( propContainer , keysContainer , this . _data . keys [ $ ( e . currentTarget ) . index ( ) ] , $ ( e . currentTarget ) . index ( ) === 0 ) ; } ) ; keyElement . find ( 'a' ) . on ( 'click' , async ( e ) => { e . stopPropagation ( ) ; await this . _deleteKeyframe ( propContainer , keysContainer , e . currentTarget . parentElement ) ; } ) ; keysContainer . append ( keyElement ) ; } private async _deleteKeyframe ( propContainer , keysContainer , element ) { const index = $ ( element ) . index ( ) ; if ( element . classList . contains ( 'active' ) ) { keysContainer . find ( 'li' ) [ index - 1 ] . classList . add ( 'active' ) ; this . _loadDataForKeyFrame ( propContainer , keysContainer , this . _data . keys [ index - 1 ] , ( index - 1 ) === 0 ) ; } $ ( element ) . remove ( ) ; this . _data . keys . splice ( index , 1 ) ; await this . _save ( ) ; } private async _save ( ) { if ( this . _data . keys . length <= 1 ) await this . _object . setFlag ( SETTINGS . MOD_NAME , LightAnimator . FLAG_ANIMS , null ) ; else await this . _object . setFlag ( SETTINGS . MOD_NAME , LightAnimator . FLAG_ANIMS , duplicate ( this . _data ) ) ; const pointSource = canvas . lighting . sources . find ( x => x . object . id === this . _object . id ) ; delete ( pointSource . object as AmbientLightExt ) . animator ; } async close ( options ? ) { super . close ( options ) ; this . _object . update ( < any > { id : this . _object . id } , { diff : false } ) ; } }