import { call , put , select , take , takeLatest } from 'redux-saga/effects' ; import { stringifySync } from 'subtitle' ; import FileSaver from 'file-saver' ; import { getSubtitles , updateCurrentItem } from './utils' ; import { ActionTypes } from "./actionTypes" ; import { ActionTypes as EditorActionTypes } from "../editor/actionTypes" ; import { ActionTypes as CommonActionTypes } from "../common/actionTypes" ; import { AppStore } from "../store" ; import { SubtitlesOptions , SubtitlesState } from "./index" ; import { Bundle } from "../linker" ; import { App } from "../index" ; import { Screen } from "../common/screens" ; export default function ( bundle ) { bundle . defineAction ( ActionTypes . SubtitlesSelected ) ; bundle . addReducer ( ActionTypes . SubtitlesSelected , subtitlesSelectedReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesOptionAdd ) ; bundle . addReducer ( ActionTypes . SubtitlesOptionAdd , subtitlesAddOptionReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesOptionRemove ) ; bundle . addReducer ( ActionTypes . SubtitlesOptionRemove , subtitlesRemoveOptionReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesOptionSave ) ; bundle . defineAction ( ActionTypes . SubtitlesTextReverted ) ; bundle . defineAction ( ActionTypes . SubtitlesTextLoaded ) ; bundle . defineAction ( ActionTypes . SubtitlesTextChanged ) ; bundle . addReducer ( ActionTypes . SubtitlesTextChanged , subtitlesTextChangedReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesEditorEnter ) ; bundle . defineAction ( ActionTypes . SubtitlesEditorReturn ) ; bundle . defineAction ( ActionTypes . SubtitlesItemChanged ) ; bundle . addReducer ( ActionTypes . SubtitlesItemChanged , subtitlesItemChangedReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesItemInserted ) ; bundle . addReducer ( ActionTypes . SubtitlesItemInserted , subtitlesItemInsertedReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesItemRemoved ) ; bundle . addReducer ( ActionTypes . SubtitlesItemRemoved , subtitlesItemRemovedReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesItemShifted ) ; bundle . addReducer ( ActionTypes . SubtitlesItemShifted , subtitlesItemShiftedReducer ) ; bundle . defineAction ( ActionTypes . SubtitlesSave ) ; bundle . addReducer ( ActionTypes . SubtitlesSave , subtitlesSaveReducer ) ; bundle . addSaga ( subtitlesEditorSaga ) ; } function setUnsaved ( subtitles ) { subtitles . unsaved = true ; } function subtitlesSelectedReducer ( state , { payload : { option } } ) { state . subtitles . selectedKey = option . key ; } function subtitlesAddOptionReducer ( state , { payload : { key , select } } ) { const option = state . subtitles . availableOptions [ key ] ; if ( ! option ) { const base = state . subtitles . langOptions . find ( option => option . value === key ) ; state . subtitles . availableOptions [ key ] = { key , text : '' , unsaved : true , removed : false , url : '' , ... base } ; } else if ( option . removed ) { state . subtitles . availableOptions [ key ] . removed = false ; } if ( select && state . subtitles . availableOptions [ key ] ) { state . subtitles . selectedKey = key ; } setUnsaved ( state . subtitles ) ; } function subtitlesRemoveOptionReducer ( state , { payload : { key } } ) { state . subtitles . availableOptions [ key ] . removed = true ; if ( state . subtitles . selectedKey === key ) { state . subtitles . selectedKey = null ; } } function subtitlesTextChangedReducer ( state , { payload : { text , unsaved } } ) { const { selectedKey : key } = state . subtitles ; state . subtitles . availableOptions [ key ] . text = text ; if ( typeof unsaved === 'boolean' ) { state . subtitles . availableOptions [ key ] . unsaved = unsaved ; } setUnsaved ( state . subtitles ) ; } function subtitlesItemChangedReducer ( state , { payload : { index , text } } ) { state . subtitles . items [ index ] . data . text = text ; subtitlesSaveReducer ( state ) ; } function subtitlesItemInsertedReducer ( state , { payload : { index , offset , where } } ) { const { data : { start , end , text } } = state . subtitles . items [ index ] ; const split = start + offset ; if ( start > split && split > end ) { return ; } let jumpTo = start ; if ( where === 'below' ) { state . subtitles . items . splice ( index , 1 , { data : { start , end : split - 1 , text } , type : 'cue' } , { data : { start : split , end , text : '' } , type : 'cue' } ) ; jumpTo = split ; } if ( where === 'above' ) { state . subtitles . items . splice ( index , 1 , { data : { start , end : split - 1 , text : '' } , type : 'cue' } , { data : { start : split , end , text } , type : 'cue' } ) ; jumpTo = start ; } subtitlesSaveReducer ( state ) ; return updateCurrentItem ( state . subtitles , jumpTo ) ; } function subtitlesItemRemovedReducer ( state , { payload : { index , merge } } ) { if ( index === 0 && merge === 'up' ) { return ; } if ( index === state . subtitles . items . length - 1 && merge === 'down' ) { return ; } const otherIndex = merge === 'up' ? index - 1 : index + 1 ; const firstIndex = Math . min ( index , otherIndex ) ; const { start } = state . subtitles . items [ firstIndex ] . data ; const { end } = state . subtitles . items [ firstIndex + 1 ] . data ; const { text } = state . subtitles . items [ otherIndex ] . data ; state . subtitles . items . splice ( firstIndex , 2 , { data : { start , end , text } , type : 'cue' } ) ; subtitlesSaveReducer ( state ) ; const audioTime = state . player . audioTime ; updateCurrentItem ( state . subtitles , audioTime ) ; } function subtitlesItemShiftedReducer ( state , { payload : { index , amount } } ) { if ( index === 0 ) { return ; } function shift ( ms ) { return ms + amount ; } state . subtitles . items [ index - 1 ] . data . end = shift ( state . subtitles . items [ index - 1 ] . data . end ) ; state . subtitles . items [ index ] . data . start = shift ( state . subtitles . items [ index ] . data . start ) ; subtitlesSaveReducer ( state ) ; } function subtitlesSaveReducer ( state ) { const { selectedKey : key , items } = state . subtitles ; const text = stringifySync ( items , { format : 'SRT' } ) ; state . subtitles . availableOptions [ key ] . text = text ; state . subtitles . availableOptions [ key ] . unsaved = true ; setUnsaved ( state . subtitles ) ; } export function clearAllUnsaved ( options ) { for ( let key of Object . keys ( options ) ) { options [ key ] . unsaved = false ; } } function * subtitlesEditorSaga ( state ) { yield takeLatest ( ActionTypes . SubtitlesSelected , subtitlesSelectedSaga , state ) ; yield takeLatest ( ActionTypes . SubtitlesEditorEnter , subtitlesEditorEnterSaga , state ) ; yield takeLatest ( ActionTypes . SubtitlesEditorReturn , subtitlesEditorReturnSaga , state ) ; yield takeLatest ( ActionTypes . SubtitlesTextReverted , subtitlesTextRevertedSaga , state ) ; yield takeLatest ( ActionTypes . SubtitlesTextLoaded , subtitlesTextLoadedSaga , state ) ; yield takeLatest ( ActionTypes . SubtitlesOptionSave , subtitlesSaveOptionSaga , state ) ; yield takeLatest ( ActionTypes . SubtitlesOptionAdd , function * ( ) { yield put ( { type : ActionTypes . SubtitlesReload } ) ; } ) ; yield takeLatest ( ActionTypes . SubtitlesOptionRemove , function * ( ) { yield put ( { type : ActionTypes . SubtitlesReload } ) ; } ) ; } function * subtitlesSelectedSaga ( state , action ) { const { key , url , text } = action . payload . option ; if ( url && ! text ) { yield put ( { type : ActionTypes . SubtitlesTextReverted , payload : { key , url } } ) ; } else { yield put ( { type : ActionTypes . SubtitlesReload } ) ; } } function * subtitlesEditorEnterSaga ( state , _action ) { yield put ( { type : ActionTypes . SubtitlesEditingChanged , payload : { editing : true } } ) ; yield put ( { type : EditorActionTypes . EditorControlsChanged , payload : { controls : 'subtitles' } } ) ; yield put ( { type : ActionTypes . SubtitlesReload } ) ; yield put ( { type : CommonActionTypes . AppSwitchToScreen , payload : { screen : Screen . Edit } } ) ; } function * subtitlesEditorReturnSaga ( state , _action ) { yield put ( { type : ActionTypes . SubtitlesSave } ) ; yield put ( { type : ActionTypes . SubtitlesEditingChanged , payload : { editing : false } } ) ; yield put ( { type : EditorActionTypes . EditorControlsChanged , payload : { controls : 'none' } } ) ; yield put ( { type : CommonActionTypes . AppSwitchToScreen , payload : { screen : Screen . Setup } } ) ; } function * subtitlesTextRevertedSaga ( state , action ) { const text = yield call ( getSubtitles , action . payload . url ) ; yield put ( { type : ActionTypes . SubtitlesTextChanged , payload : { text , unsaved : false } } ) ; yield put ( { type : ActionTypes . SubtitlesReload } ) ; } function * subtitlesTextLoadedSaga ( state , action ) { yield put ( { type : ActionTypes . SubtitlesLoadFromFile , payload : { key : action . payload . key , file : action . payload . file } } ) ; while ( true ) { const loadAction = yield take ( [ ActionTypes . SubtitlesLoadSucceeded , ActionTypes . SubtitlesLoadFailed ] ) ; if ( loadAction . payload . key !== action . payload . key ) { continue ; } if ( loadAction . type === ActionTypes . SubtitlesLoadSucceeded ) { const { text } = loadAction . payload ; yield put ( { type : ActionTypes . SubtitlesTextChanged , payload : { text , unsaved : true } } ) ; } break ; } } function * subtitlesSaveOptionSaga ( app , action ) { const state = yield select ( ) ; let blobContent ; if ( state . options . data && state . options . data . subtitlesData && action . payload . key in state . options . data . subtitlesData ) { blobContent = stringifySync ( state . options . data . subtitlesData [ action . payload . key ] , { format : 'SRT' } ) ; } else { const { text } = state . subtitles . availableOptions [ action . payload . key ] ; blobContent = text ; } const blob = new Blob ( [ blobContent ] , { type : "text/plain;charset=utf-8" } ) ; yield call ( FileSaver . saveAs , blob , `${ action . payload . key }.srt` ) ; }