import { IGuideGenreSettingStorageModel , IGuideGenreSettingValue } from '@/model/storage/guide/IGuideGenreSettingStorageModel' ; import { inject , injectable } from 'inversify' ; import * as apid from '../../../../../api' ; import DateUtil from '../../../util/DateUtil' ; import IScheduleApiModel from '../../api/schedule/IScheduleApiModel' ; import { ISettingStorageModel } from '../../storage/setting/ISettingStorageModel' ; import IGuideProgramDialogState , { ProgramDialogOpenOption } from './IGuideProgramDialogState' ; import IGuideReserveUtil , { ReserveStateItemIndex } from './IGuideReserveUtil' ; import IGuideState , { DisplayRange , FetchGuideOption , ProgramDomItem } from './IGuideState' ; interface CreateProgramDomOption { top : number ; left : number ; height : number ; channel : apid . ScheduleChannleItem ; program : apid . ScheduleProgramItem ; isHidden : boolean ; } @ injectable ( ) class GuideState implements IGuideState { private scheduleApiModel : IScheduleApiModel ; private settingModel : ISettingStorageModel ; private programDialogState : IGuideProgramDialogState ; private reserveUtil : IGuideReserveUtil ; private genreSetting : IGuideGenreSettingStorageModel ; private displayRange : DisplayRange | null = null ; private startAt : apid . UnixtimeMS = 0 ; private endAt : apid . UnixtimeMS = 0 ; private programDoms : ProgramDomItem [ ] = [ ] ; private programDomIndex : { [ programId ] : HTMLElement [ ] ; } = { } ; private startTime : string | null = null ; private timeLength : number = 0 ; private schedules : apid . Schedule [ ] = [ ] ; private reserveIndex : ReserveStateItemIndex = { } ; constructor ( @ inject ( 'IScheduleApiModel' ) scheduleApiModel , @ inject ( 'ISettingStorageModel' ) settingModel , @ inject ( 'IGuideProgramDialogState' ) programDialogState , @ inject ( 'IGuideReserveUtil' ) reserveUtil , @ inject ( 'IGuideGenreSettingStorageModel' ) genreSetting ) { this . scheduleApiModel = scheduleApiModel ; this . settingModel = settingModel ; this . programDialogState = programDialogState ; this . reserveUtil = reserveUtil ; this . genreSetting = genreSetting ; } /**
     * データクリア
     */ public clearDate ( ) { this . displayRange = null ; this . startAt = 0 ; this . endAt = 0 ; this . programDoms = [ ] ; this . programDomIndex = { } ; this . startTime = null ; this . timeLength = 0 ; this . schedules = [ ] ; this . reserveIndex = { } ; } param baseSize public setDisplayRange ( baseSize ) { this . displayRange = baseSize ; } param option public async fetchGuide ( option ) { this . startTime = typeof option . time !== 'undefined' ? option . time : DateUtil . format ( DateUtil . getJaDate ( new Date ( ) ) , 'YYMMddhh' ) ; const startAt = this . getStartTime ( this . startTime ) ; let endAt ; if ( typeof option . channelId === 'undefined' ) { endAt = startAt + option . length * 60 * 60 * 1000 ; this . timeLength = option . length ; const scheduleOption = { startAt , endAt , isHalfWidth : option . isHalfWidth , GR : false , BS : false , CS : false , SKY : false , } ; if ( typeof option . type === 'undefined' ) { scheduleOption . GR = true ; scheduleOption . BS = true ; scheduleOption . CS = true ; scheduleOption . SKY = true ; } else { scheduleOption [ option . type ] = true ; } if ( this . settingModel . getSavedValue ( ) . isShowOnlyFreePrograms === true ) { scheduleOption . isFree = true ; } this . schedules = await this . scheduleApiModel . getSchedules ( scheduleOption ) ; } else { this . timeLength = GuideState . SINGLE_STATION_LENGTH ; endAt = startAt + 60 * 60 * GuideState . SINGLE_STATION_LENGTH * 1000 * GuideState . SINGLE_STATION_GET_DAYS ; const scheduleOption = { startAt : startAt , days : GuideState . SINGLE_STATION_GET_DAYS , isHalfWidth : option . isHalfWidth , channelId : option . channelId , } ; if ( this . settingModel . getSavedValue ( ) . isShowOnlyFreePrograms === true ) { scheduleOption . isFree = true ; } this . schedules = await this . scheduleApiModel . getChannelSchedule ( scheduleOption ) ; } this . startAt = startAt ; this . endAt = endAt ; this . reserveIndex = await this . reserveUtil . getReserveIndex ( { startAt , endAt , } ) ; } param isSingleStation public createProgramDoms ( isSingleStation ) { if ( this . displayRange === null ) { throw new Error ( 'CreateProgramDomsError' ) ; } const isHidden = this . settingModel . getSavedValue ( ) . guideMode !== 'all' ; const genreSettings = this . genreSetting . getSavedValue ( ) ; this . programDoms = [ ] ; this . programDomIndex = { } ; let baseStartAt = this . startAt ; let baseEndAt = isSingleStation === true ? baseStartAt + 60 * 60 * GuideState . SINGLE_STATION_LENGTH * 1000 : this . endAt ; for ( let i = 0 ; i < this . schedules . length ; i ++ ) { for ( const program of this . schedules [ i ] . programs ) { const programStartAt = baseStartAt > program . startAt ? baseStartAt : program . startAt ; const top = this . getTop ( baseStartAt , programStartAt ) ; const height = this . getDiffMin ( programStartAt , baseEndAt < program . endAt ? baseEndAt : program . endAt ) ; if ( height <= 0 ) { continue ; } const element = this . createProgramDom ( { top , left : i , height : height , channel : this . schedules [ i ] . channel , program : program , isHidden : isHidden , } , genreSettings ) ; this . programDoms . push ( { element , top , left : i , height , isVisible : false , genreLv1 : typeof program . genre1 !== 'undefined' ? program . genre1 : typeof program . genre2 !== 'undefined' ? program . genre2 : program . genre3 , } ) ; if ( typeof this . programDomIndex [ program . id ] === 'undefined' ) { this . programDomIndex [ program . id ] = [ ] ; } this . programDomIndex [ program . id ] . push ( element ) ; } if ( isSingleStation === true ) { baseStartAt += 60 * 60 * GuideState . SINGLE_STATION_LENGTH * 1000 ; baseEndAt = baseStartAt + 60 * 60 * GuideState . SINGLE_STATION_LENGTH * 1000 ; } } } param option param isHidden return private createProgramDom ( option , genreSettings ) { const child = [ ] ; child . push ( this . createTextElement ( 'div' , { class : 'name' } , option . program . name ) ) ; child . push ( this . createTextElement ( 'div' , { class : 'time' } , DateUtil . format ( DateUtil . getJaDate ( new Date ( option . program . startAt ) ) , 'hh:mm' ) ) ) ; if ( typeof option . program . description !== 'undefined' ) { child . push ( this . createTextElement ( 'div' , { class : 'description' } , option . program . description ) ) ; } let genreLv1 = null ; let classStr = 'item' ; if ( typeof option . program . genre1 !== 'undefined' ) { genreLv1 = option . program . genre1 ; classStr += ` ctg-${ option . program . genre1 . toString ( 10 ) }` ; } else if ( typeof option . program . genre2 !== 'undefined' ) { genreLv1 = option . program . genre2 ; classStr += ` ctg-${ option . program . genre2 . toString ( 10 ) }` ; } else if ( typeof option . program . genre3 !== 'undefined' ) { genreLv1 = option . program . genre3 ; classStr += ` ctg-${ option . program . genre3 . toString ( 10 ) }` ; } else { classStr += ' ctg-empty' ; } if ( genreLv1 !== null && typeof ( genreSettings as any ) [ genreLv1 ] !== 'undefined' && ( genreSettings as any ) [ genreLv1 ] === false ) { classStr += ' hide' ; } if ( typeof this . reserveIndex [ option . program . id ] !== 'undefined' ) { const reserve = this . reserveIndex [ option . program . id ] ; classStr += ` ${ reserve . type }` ; } if ( option . isHidden === true ) { classStr += ' hidden' ; } const element = this . createParentElement ( 'div' , { class : classStr , style : `height: calc(${ option . height } * (var(--timescale-height) / 60));` + `top: calc(${ option . top } * (var(--timescale-height) / 60)); ` + `left: calc(${ option . left } * (var(--channel-width)));` , onclick : ( e ) => { const dialogOption = { channel : option . channel , program : option . program , } ; if ( typeof this . reserveIndex [ option . program . id ] !== 'undefined' ) { dialogOption . reserve = { type : this . reserveIndex [ option . program . id ] . type , reserveId : this . reserveIndex [ option . program . id ] . item . reserveId , ruleId : this . reserveIndex [ option . program . id ] . item . ruleId , } ; } this . programDialogState . open ( dialogOption ) ; } , } , child ) ; return element ; } param tag param attrs param childs return private createParentElement ( tag , attrs , childs ) { const element = document . createElement ( tag ) ; for ( const key in attrs ) { if ( key === 'onclick' ) { element . onclick = attrs [ key ] ; } else { element . setAttribute ( key , attrs [ key ] ) ; } } childs . map ( ( child ) => { element . appendChild ( child ) ; } ) ; return element ; } param tag param attrs param text return private createTextElement ( tag , attrs , text ) { const element = document . createElement ( tag ) ; for ( const key in attrs ) { element . setAttribute ( key , attrs [ key ] ) ; } element . innerText = text ; return element ; } /**
     * 番組要素の表示状態を更新する
     */ public updateVisible ( ) { const guideMode = this . settingModel . getSavedValue ( ) . guideMode ; if ( this . displayRange === null || guideMode === 'all' ) { return ; } const baseHeight = this . displayRange . baseHeight / 60 ; const topStart = this . displayRange . offsetHeight ; const topEnd = this . displayRange . offsetHeight + this . displayRange . maxHeight ; for ( const dom of this . programDoms ) { if ( guideMode === 'sequential' && dom . isVisible === true ) { continue ; } let isVisible = true ; if ( ( dom . left + 1 ) * this . displayRange . baseWidth <= this . displayRange . offsetWidth - this . displayRange . maxWidth || dom . left * this . displayRange . baseWidth >= this . displayRange . maxWidth + this . displayRange . offsetWidth ) { isVisible = false ; } if ( dom . top * baseHeight >= topEnd || ( dom . top + dom . height ) * baseHeight <= topStart ) { isVisible = false ; } if ( dom . isVisible !== isVisible ) { dom . isVisible = isVisible ; if ( isVisible ) { dom . element . classList . remove ( 'hidden' ) ; } else { dom . element . classList . add ( 'hidden' ) ; } } } } /**
     * ジャンル情報を更新する
     */ public updateGenre ( ) { const genreSettings = this . genreSetting . getSavedValue ( ) ; for ( const dom of this . programDoms ) { if ( typeof dom . genreLv1 === 'undefined' ) { continue ; } dom . element . classList . remove ( 'hide' ) ; if ( typeof ( genreSettings as any ) [ dom . genreLv1 ] !== 'undefined' && ( genreSettings as any ) [ dom . genreLv1 ] === false ) { dom . element . classList . add ( 'hide' ) ; } } } reutn public async updateReserves ( ) { const newReserveIndex = await this . reserveUtil . getReserveIndex ( { startAt : this . startAt , endAt : this . endAt , } ) ; for ( const programId in this . reserveIndex ) { if ( typeof this . programDomIndex [ programId ] === 'undefined' ) { continue ; } for ( const element of this . programDomIndex [ programId ] ) { element . classList . remove ( this . reserveIndex [ programId ] . type ) ; } } for ( const programId in newReserveIndex ) { if ( typeof this . programDomIndex [ programId ] === 'undefined' ) { continue ; } for ( const element of this . programDomIndex [ programId ] ) { element . classList . add ( newReserveIndex [ programId ] . type ) ; } } this . reserveIndex = newReserveIndex ; if ( this . programDialogState . isOpen === true ) { const programId = this . programDialogState . getProgramId ( ) ; if ( programId !== null ) { this . programDialogState . updateReserve ( typeof this . reserveIndex [ programId ] === 'undefined' ? null : { type : this . reserveIndex [ programId ] . type , reserveId : this . reserveIndex [ programId ] . item . reserveId , ruleId : this . reserveIndex [ programId ] . item . ruleId , } ) ; } } } param startAt param programStartAt return private getTop ( startAt , programStartAt ) { return startAt === programStartAt ? 0 : Math . ceil ( Math . floor ( ( programStartAt - startAt ) / 1000 ) / 60 ) ; } param startAt param endAt return private getDiffMin ( startAt , endAt ) { return Math . ceil ( ( endAt - startAt ) / 60 / 1000 ) ; } param timeStr return private getStartTime ( timeStr ) { if ( timeStr === null ) { throw new Error ( 'StartTimeOptionIsNull' ) ; } return new Date ( `20${ timeStr . substr ( 0 , 2 ) }/${ timeStr . substr ( 2 , 2 ) }/` + `${ timeStr . substr ( 4 , 2 ) } ${ timeStr . substr ( 6 , 2 ) }:00:00 +0900` ) . getTime ( ) ; } return public getChannels ( ) { return this . schedules . map ( schedule => { return schedule . channel ; } ) ; } return public getChannelsLength ( ) { return this . schedules . length ; } /**
     * 番組取得の開始時刻を返す
     */ public getStartAt ( ) { return this . startAt ; } return public getTimes ( ) { if ( this . startTime === null || this . startTime . length === 0 ) { return [ ] ; } const start = parseInt ( this . startTime . substr ( 6 , 2 ) , 10 ) ; if ( isNaN ( start ) === true ) { return [ ] ; } const result = [ ] ; for ( let i = start ; i < start + this . timeLength ; i ++ ) { result . push ( i % 24 ) ; } return result ; } return public getTimesLength ( ) { return this . timeLength ; } return public getProgramDoms ( ) { return this . programDoms ; } param type return public getTitle ( type ? ) { let title = '番組表' ; if ( typeof type !== 'undefined' ) { title += type ; } if ( this . startAt > 0 ) { title += DateUtil . format ( DateUtil . getJaDate ( new Date ( this . startAt ) ) , ' MM/dd(w)' ) ; } return title ; } return public getSingleStationTitle ( ) { return this . schedules . length === 0 ? '番組表' : this . schedules [ 0 ] . channel . name ; } } namespace GuideState { export const SINGLE_STATION_GET_DAYS = 8 ; export const SINGLE_STATION_LENGTH = 24 ; } export default GuideState ;