import _ from 'lodash'; import { createAction } from 'redux-actions'; import { createThunk, handleThunks } from 'Store/thunks'; import createAjaxRequest from 'Utilities/createAjaxRequest'; import getSectionState from 'Utilities/State/getSectionState'; import updateSectionState from 'Utilities/State/updateSectionState'; import { fetchTranslations as fetchAppTranslations } from 'Utilities/String/translate'; import createHandleActions from './Creators/createHandleActions'; function getDimensions(width, height) { const dimensions = { width, height, isExtraSmallScreen: width <= 480, isSmallScreen: width <= 768, isMediumScreen: width <= 992, isLargeScreen: width <= 1200 }; return dimensions; } // // Variables export const section = 'app'; const messagesSection = 'app.messages'; let abortPingServer = null; let pingTimeout = null; // // State export const defaultState = { dimensions: getDimensions(window.innerWidth, window.innerHeight), messages: { items: [] }, version: window.Lidarr.version, isUpdated: false, isConnected: true, isReconnecting: false, isDisconnected: false, isRestarting: false, isSidebarVisible: !getDimensions(window.innerWidth, window.innerHeight).isSmallScreen, translations: { isFetching: true, isPopulated: false, error: null } }; // // Action Types export const SHOW_MESSAGE = 'app/showMessage'; export const HIDE_MESSAGE = 'app/hideMessage'; export const CLEAR_MESSAGES = 'app/clearMessages'; export const SAVE_DIMENSIONS = 'app/saveDimensions'; export const SET_VERSION = 'app/setVersion'; export const SET_APP_VALUE = 'app/setAppValue'; export const SET_IS_SIDEBAR_VISIBLE = 'app/setIsSidebarVisible'; export const FETCH_TRANSLATIONS = 'app/fetchTranslations'; export const PING_SERVER = 'app/pingServer'; // // Action Creators export const saveDimensions = createAction(SAVE_DIMENSIONS); export const setVersion = createAction(SET_VERSION); export const setIsSidebarVisible = createAction(SET_IS_SIDEBAR_VISIBLE); export const setAppValue = createAction(SET_APP_VALUE); export const showMessage = createAction(SHOW_MESSAGE); export const hideMessage = createAction(HIDE_MESSAGE); export const clearMessages = createAction(CLEAR_MESSAGES); export const pingServer = createThunk(PING_SERVER); export const fetchTranslations = createThunk(FETCH_TRANSLATIONS); // // Helpers function pingServerAfterTimeout(getState, dispatch) { if (abortPingServer) { abortPingServer(); abortPingServer = null; } if (pingTimeout) { clearTimeout(pingTimeout); pingTimeout = null; } pingTimeout = setTimeout(() => { if (!getState().isRestarting && getState().isConnected) { return; } const ajaxOptions = { url: '/system/status', method: 'GET', contentType: 'application/json' }; const { request, abortRequest } = createAjaxRequest(ajaxOptions); abortPingServer = abortRequest; request.done(() => { abortPingServer = null; pingTimeout = null; dispatch(setAppValue({ isRestarting: false })); }); request.fail((xhr) => { abortPingServer = null; pingTimeout = null; // Unauthorized, but back online if (xhr.status === 401) { dispatch(setAppValue({ isRestarting: false })); } else { pingServerAfterTimeout(getState, dispatch); } }); }, 5000); } // // Action Handlers export const actionHandlers = handleThunks({ [PING_SERVER]: function(getState, payload, dispatch) { pingServerAfterTimeout(getState, dispatch); }, [FETCH_TRANSLATIONS]: async function(getState, payload, dispatch) { const isFetchingComplete = await fetchAppTranslations(); dispatch(setAppValue({ translations: { isFetching: false, isPopulated: isFetchingComplete, error: isFetchingComplete ? null : 'Failed to load translations from API' } })); } }); // // Reducers export const reducers = createHandleActions({ [SAVE_DIMENSIONS]: function(state, { payload }) { const { width, height } = payload; const dimensions = getDimensions(width, height); return Object.assign({}, state, { dimensions }); }, [SHOW_MESSAGE]: function(state, { payload }) { const newState = getSectionState(state, messagesSection); const items = newState.items; const index = _.findIndex(items, { id: payload.id }); newState.items = [...items]; if (index >= 0) { const item = items[index]; newState.items.splice(index, 1, { ...item, ...payload }); } else { newState.items.push({ ...payload }); } return updateSectionState(state, messagesSection, newState); }, [HIDE_MESSAGE]: function(state, { payload }) { const newState = getSectionState(state, messagesSection); newState.items = [...newState.items]; _.remove(newState.items, { id: payload.id }); return updateSectionState(state, messagesSection, newState); }, [CLEAR_MESSAGES]: function(state) { const newState = getSectionState(state, messagesSection); newState.items = []; return updateSectionState(state, messagesSection, newState); }, [SET_APP_VALUE]: function(state, { payload }) { const newState = Object.assign(getSectionState(state, section), payload); return updateSectionState(state, section, newState); }, [SET_VERSION]: function(state, { payload }) { const version = payload.version; const newState = { version }; if (state.version !== version) { if (!state.prevVersion) { newState.prevVersion = state.version; } newState.isUpdated = true; } return Object.assign({}, state, newState); }, [SET_IS_SIDEBAR_VISIBLE]: function(state, { payload }) { const newState = { isSidebarVisible: payload.isSidebarVisible }; return Object.assign({}, state, newState); } }, defaultState, section);