Refact: 分离viewe update reducer; 添加typescript (#24)

* 分离vieweupdate reducer; 添加typescript

* Fix: Dispatch navigator actions

* fix: dispatch change sub title
This commit is contained in:
TS 2020-05-11 22:27:07 -07:00 committed by GitHub
parent 0e8e26e4de
commit 20e3b499cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 483 additions and 395 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
dist/

View File

@ -8,6 +8,8 @@
"@material-ui/icons": "^4.5.1",
"@material-ui/lab": "^4.0.0-alpha.42",
"@svgr/webpack": "4.3.2",
"@types/invariant": "^2.2.32",
"@types/react-dom": "^16.9.7",
"@typescript-eslint/eslint-plugin": "^2.2.0",
"@typescript-eslint/parser": "^2.2.0",
"axios": "^0.19.0",
@ -37,6 +39,7 @@
"http-proxy-middleware": "^0.20.0",
"husky": "^4.2.5",
"identity-obj-proxy": "3.0.0",
"invariant": "^2.2.4",
"is-wsl": "^1.1.0",
"jest": "24.9.0",
"jest-environment-jsdom-fourteen": "0.1.0",
@ -74,6 +77,7 @@
"react-router-dom": "^5.1.2",
"recharts": "^2.0.0-beta.1",
"redux": "^4.0.4",
"redux-mock-store": "^1.5.4",
"redux-thunk": "^2.3.0",
"resolve": "1.12.0",
"resolve-url-loader": "3.1.0",
@ -83,6 +87,7 @@
"terser-webpack-plugin": "1.4.1",
"timeago-react": "^3.0.0",
"ts-pnp": "1.1.4",
"typescript": "^3.8.3",
"url-loader": "2.1.0",
"webpack": "4.41.0",
"webpack-dev-server": "3.2.1",

View File

@ -71,7 +71,7 @@ export default function App() {
},
content: {
flexGrow: 1,
padding: theme.spacing.unit * 0,
padding: theme.spacing(0),
minWidth: 0
},
toolbar: theme.mixins.toolbar

View File

@ -1,16 +1,30 @@
export * from './explorer'
export const navigateTo = path => {
return {
type: "NAVIGATOR_TO",
path: path
};
return (dispatch, getState) => {
const state = getState()
const navigatorLoading = path !== state.navigator.path
return dispatch({
type: "NAVIGATOR_TO",
path: path,
navigatorLoading,
})
}
};
export const navigateUp = () => {
return {
type: "NAVIGATOR_UP"
};
return (dispatch, getState) => {
const state = getState()
const pathSplit = state.navigator.path.split("/");
pathSplit.pop();
const newPath = pathSplit.length===1? "/":pathSplit.join("/");
const navigatorLoading = newPath !== state.navigator.path
return dispatch({
type: "NAVIGATOR_UP",
path: newPath,
navigatorLoading
})
}
};
export const drawerToggleAction = open => {
@ -35,13 +49,6 @@ export const changeViewMethod = method => {
};
};
export const changeSubTitle = title =>{
return {
type: "CHANGE_SUB_TITLE",
title: title
};
};
export const toggleDaylightMode = ()=>{
return {
type: "TOGGLE_DAYLIGHT_MODE",

View File

@ -22,7 +22,7 @@ import React, { useCallback, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useHistory, useLocation } from "react-router";
import { useRouteMatch } from "react-router-dom";
import { changeSubTitle } from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import pathHelper from "../../utils/page";
import UserAvatar from "../Navbar/UserAvatar";

View File

@ -3,7 +3,8 @@ import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import { connect } from "react-redux"
import { withRouter } from "react-router-dom"
import { changeSubTitle, closeAllModals, navigateTo, setSelectedTarget, toggleSnackbar } from "../../actions"
import { closeAllModals, navigateTo, setSelectedTarget, toggleSnackbar } from "../../actions"
import { changeSubTitle } from "../../redux/viewUpdate/action";
import pathHelper from "../../utils/page"
import DragLayer from "./DnD/DragLayer"
import Explorer from "./Explorer"

View File

@ -2,7 +2,8 @@ import React, { Suspense, useCallback, useEffect, useState } from "react";
import PageLoading from "../Placeholder/PageLoading";
import { useParams } from "react-router";
import API from "../../middleware/Api";
import { changeSubTitle, toggleSnackbar } from "../../actions";
import { toggleSnackbar } from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import { useDispatch } from "react-redux";
import Notice from "./NotFound";
import LockedFile from "./LockedFile";

View File

@ -4,7 +4,8 @@ import { makeStyles } from "@material-ui/core/styles";
import { useLocation, useParams, useRouteMatch } from "react-router";
import API from "../../middleware/Api";
import { useDispatch } from "react-redux";
import { changeSubTitle, toggleSnackbar } from "../../actions";
import { toggleSnackbar } from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import pathHelper from "../../utils/page";
import SaveButton from "../Dial/Save";
import { codePreviewSuffix } from "../../config";

View File

@ -3,7 +3,8 @@ import { makeStyles } from "@material-ui/core/styles";
import {useLocation, useParams, useRouteMatch} from "react-router";
import API from "../../middleware/Api";
import {useDispatch} from "react-redux";
import {changeSubTitle, toggleSnackbar} from "../../actions";
import {toggleSnackbar} from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import pathHelper from "../../utils/page";
const useStyles = makeStyles(() => ({

View File

@ -5,7 +5,8 @@ import { makeStyles } from "@material-ui/core/styles";
import { useLocation, useParams, useRouteMatch } from "react-router";
import { getBaseURL } from "../../middleware/Api";
import { useDispatch } from "react-redux";
import { changeSubTitle, toggleSnackbar } from "../../actions";
import { toggleSnackbar } from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import pathHelper from "../../utils/page";
import TextLoading from "../Placeholder/TextLoading";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

View File

@ -4,7 +4,8 @@ import { makeStyles } from "@material-ui/core/styles";
import {useLocation, useParams, useRouteMatch} from "react-router";
import API from "../../middleware/Api";
import { useDispatch } from "react-redux";
import { changeSubTitle, toggleSnackbar } from "../../actions";
import { toggleSnackbar } from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import Editor from 'for-editor'
import SaveButton from "../Dial/Save";
import pathHelper from "../../utils/page";

View File

@ -5,7 +5,7 @@ import { makeStyles } from "@material-ui/core/styles";
import { useLocation, useParams, useRouteMatch } from "react-router";
import { getBaseURL } from "../../middleware/Api";
import { useDispatch } from "react-redux";
import { changeSubTitle } from "../../actions";
import { changeSubTitle } from "../../redux/viewUpdate/action";
import pathHelper from "../../utils/page";
const useStyles = makeStyles(theme => ({

View File

@ -1,19 +1,16 @@
const Auth = {
isAuthenticated: false,
authenticate(cb) {
authenticate(cb: any) {
Auth.SetUser(cb);
Auth.isAuthenticated = true;
},
GetUser(){
return JSON.parse(localStorage.getItem("user"))
},
SetUser(newUser){
SetUser(newUser: any){
localStorage.setItem("user", JSON.stringify(newUser));
},
/**
* @return {boolean}
*/
Check() {
Check(): boolean {
if (Auth.isAuthenticated) {
return true;
}
@ -29,16 +26,13 @@ const Auth = {
oldUser.id = 0;
localStorage.setItem("user", JSON.stringify(oldUser));
},
SetPreference(key,value){
SetPreference(key: string,value: any){
let preference = JSON.parse(localStorage.getItem("user_preference"));
preference = (preference == null) ? {} : preference;
preference[key] = value;
localStorage.setItem("user_preference", JSON.stringify(preference));
},
/**
* @return {null}
*/
GetPreference(key){
GetPreference(key: string): any | null{
let preference = JSON.parse(localStorage.getItem("user_preference"));
if (preference && preference[key]){
return preference[key];

View File

@ -15,8 +15,6 @@ export var InitSiteConfig = (rawStore) => {
rawStore.navigator.path = c===null?"/":c;
// 初始化用户个性配置
rawStore.siteConfig = initUserConfig(rawStore.siteConfig);
// 是否登录
rawStore.viewUpdate.isLogin = Auth.Check();
// 更改站点标题
document.title = rawStore.siteConfig.title;

View File

@ -1,5 +1,7 @@
/* eslint-disable no-case-declarations */
import { InitSiteConfig } from "../middleware/Init";
import { combineReducers } from '../redux/combineReducers'
import viewUpdate from '../redux/viewUpdate/reducer'
const checkSelectedProps = (state)=>{
let isMultiple,withFolder,withFile=false;
@ -15,17 +17,12 @@ const checkSelectedProps = (state)=>{
return [isMultiple,withFolder,withFile];
}
const doNavigate = (path,state)=>{
const doNavigate = (path, state)=>{
window.currntPath = path;
return Object.assign({}, state, {
navigator:Object.assign({}, state.navigator, {
path: path
}),
viewUpdate:Object.assign({}, state.viewUpdate, {
contextOpen:false,
navigatorError:false,
navigatorLoading:path !== state.navigator.path,
}),
explorer:Object.assign({}, state.explorer, {
selected:[],
selectProps: {
@ -92,46 +89,6 @@ export const initState = {
path: "/",
refresh: true
},
viewUpdate: {
isLogin:false,
loadUploader:false,
open: false,
explorerViewMethod: "icon",
sortMethod: "timePos",
subTitle:null,
contextType: "none",
menuOpen: false,
navigatorLoading: true,
navigatorError: false,
navigatorErrorMsg: null,
modalsLoading: false,
storageRefresh: false,
userPopoverAnchorEl: null,
shareUserPopoverAnchorEl: null,
modals: {
createNewFolder: false,
createNewFile: false,
rename: false,
move: false,
remove: false,
share: false,
music: false,
remoteDownload: false,
torrentDownload: false,
getSource: false,
copy:false,
resave: false,
compress:false,
decompress:false,
},
snackbar: {
toggle: false,
vertical: "top",
horizontal: "center",
msg: "",
color: ""
}
},
explorer: {
dndSignal:false,
dndTarget:null,
@ -162,18 +119,6 @@ const defaultStatus = InitSiteConfig(initState);
// TODO: 将cloureveApp切分成小的reducer
const cloudreveApp = (state = defaultStatus, action) => {
switch (action.type) {
case 'DRAWER_TOGGLE':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
open:action.open,
}),
});
case 'CHANGE_VIEW_METHOD':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
explorerViewMethod:action.method,
}),
});
case 'CHANGE_SORT_METHOD':
let list = [...state.explorer.fileList,...state.explorer.dirList];
// eslint-disable-next-line
@ -203,24 +148,11 @@ const cloudreveApp = (state = defaultStatus, action) => {
});
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
sortMethod:action.method,
}),
explorer: Object.assign({}, state.explorer, {
fileList: fileList,
dirList: dirList,
}),
});
case 'CHANGE_CONTEXT_MENU':
if(state.viewUpdate.contextOpen && action.open){
return Object.assign({}, state);
}
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
contextOpen: action.open,
contextType:action.menuType,
}),
});
case 'DRAG_AND_DROP':
return Object.assign({}, state, {
explorer: Object.assign({}, state.explorer, {
@ -229,19 +161,6 @@ const cloudreveApp = (state = defaultStatus, action) => {
dndSource:action.source,
}),
});
case 'SET_NAVIGATOR_LOADING_STATUE':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
navigatorLoading:action.status,
}),
});
case 'SET_NAVIGATOR_ERROR':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
navigatorError: action.status,
navigatorErrorMsg: action.msg,
}),
});
case 'UPDATE_FILE_LIST':
// eslint-disable-next-line
action.list.sort((a,b)=>{
@ -379,224 +298,11 @@ const cloudreveApp = (state = defaultStatus, action) => {
}
break
case 'NAVIGATOR_UP':
var pathSplit = state.navigator.path.split("/");
pathSplit.pop();
var newPath = pathSplit.length===1?"/":pathSplit.join("/");
return doNavigate(newPath,state);
case 'OPEN_CREATE_FOLDER_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
createNewFolder:true,
}),
contextOpen:false,
}),
});
case 'OPEN_CREATE_FILE_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
createNewFile:true,
}),
contextOpen:false,
}),
});
case 'OPEN_RENAME_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
rename:true,
}),
contextOpen:false,
}),
});
case 'OPEN_REMOVE_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
remove:true,
}),
contextOpen:false,
}),
});
case 'OPEN_MOVE_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
move:true,
}),
contextOpen:false,
}),
});
case 'OPEN_RESAVE_DIALOG':
window.shareKey = action.key;
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
resave:true,
}),
contextOpen:false,
}),
});
case 'SET_USER_POPOVER':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
userPopoverAnchorEl:action.anchor,
}),
});
case 'SET_SHARE_USER_POPOVER':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
shareUserPopoverAnchorEl:action.anchor,
}),
});
case 'OPEN_SHARE_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
share:true,
}),
contextOpen:false,
}),
});
return doNavigate(action.path, state);
case 'SET_SITE_CONFIG':
return Object.assign({}, state, {
siteConfig: action.config,
});
case 'OPEN_MUSIC_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
music:true,
}),
contextOpen:false,
}),
});
case 'OPEN_REMOTE_DOWNLOAD_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
remoteDownload:true,
}),
contextOpen:false,
}),
});
case 'OPEN_TORRENT_DOWNLOAD_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
torrentDownload:true,
}),
contextOpen:false,
}),
});
case 'OPEN_DECOMPRESS_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
decompress:true,
}),
contextOpen:false,
}),
});
case 'OPEN_COMPRESS_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
compress:true,
}),
contextOpen:false,
}),
});
case 'OPEN_GET_SOURCE_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
getSource:true,
}),
contextOpen:false,
}),
});
case 'OPEN_COPY_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
copy:true,
}),
contextOpen:false,
}),
});
case 'OPEN_LOADING_DIALOG':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
loading:true,
loadingText:action.text,
}),
contextOpen:false,
}),
});
case 'CLOSE_ALL_MODALS':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modals: Object.assign({}, state.viewUpdate.modals, {
createNewFolder:false,
createNewFile:false,
rename:false,
move:false,
remove:false,
share:false,
music:false,
remoteDownload:false,
torrentDownload:false,
getSource:false,
resave:false,
copy:false,
loading:false,
compress:false,
decompress:false,
}),
}),
});
case 'CHANGE_SUB_TITLE':
document.title = (action.title === null || action.title === undefined) ? state.siteConfig.title : (action.title + " - " +state.siteConfig.title);
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
subTitle: action.title,
}),
});
case 'TOGGLE_SNACKBAR':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
snackbar:{
toggle:!state.viewUpdate.snackbar.toggle,
vertical:action.vertical,
horizontal:action.horizontal,
msg:action.msg,
color:action.color,
},
}),
});
case 'SET_MODALS_LOADING':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
modalsLoading:action.status,
}),
});
case 'SET_SESSION_STATUS':
return {
...state,
viewUpdate: {
...state.viewUpdate,
isLogin:action.status,
}
}
case 'ENABLE_LOAD_UPLOADER':
return Object.assign({}, state, {
viewUpdate: Object.assign({}, state.viewUpdate, {
loadUploader:true,
}),
});
case 'REFRESH_FILE_LIST':
return Object.assign({}, state, {
navigator: Object.assign({}, state.navigator, {
@ -617,11 +323,6 @@ const cloudreveApp = (state = defaultStatus, action) => {
path: "/搜索结果",
refresh:state.explorer.keywords === null? state.navigator.refresh:!state.navigator.refresh,
}),
viewUpdate:Object.assign({}, state.viewUpdate, {
contextOpen:false,
navigatorError:false,
navigatorLoading:true,
}),
explorer:Object.assign({}, state.explorer, {
selected:[],
selectProps: {
@ -641,12 +342,6 @@ const cloudreveApp = (state = defaultStatus, action) => {
},
}),
});
case 'REFRESH_STORAGE':
return Object.assign({}, state, {
viewUpdate:Object.assign({}, state.viewUpdate, {
storageRefresh:!state.viewUpdate.storageRefresh,
}),
});
case 'SAVE_FILE':
return Object.assign({}, state, {
explorer:Object.assign({}, state.explorer, {
@ -679,4 +374,12 @@ const cloudreveApp = (state = defaultStatus, action) => {
}
}
export default cloudreveApp
export default (state, action) => {
const { viewUpdate: viewUpdateState } = state || {}
const appState = cloudreveApp(state, action)
const combinedState = combineReducers({ viewUpdate })({ viewUpdate: viewUpdateState }, action)
return {
...appState,
...combinedState,
}
}

View File

@ -1,4 +1,8 @@
import cloudreveApp, { initState } from './index'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import cloudreveApp, { initState as cloudreveState } from './index'
import { initState as viewUpdateState } from '../redux/viewUpdate/reducer'
import { setModalsLoading, openLoadingDialog, openGetSourceDialog,
openShareDialog, openMoveDialog, navigateUp, navigateTo,
drawerToggleAction, changeViewMethod, changeSortMethod,
@ -10,13 +14,23 @@ import { setModalsLoading, openLoadingDialog, openGetSourceDialog,
setShareUserPopover, setSiteConfig, openMusicDialog,
openRemoteDownloadDialog, openTorrentDownloadDialog,
openDecompressDialog, openCompressDialog, openCopyDialog,
closeAllModals, changeSubTitle, toggleSnackbar, setSessionStatus,
enableLoadUploader, refreshFileList, searchMyFile, showImgPreivew, refreshStorage, saveFile, setLastSelect, setShiftSelectedIds
closeAllModals, toggleSnackbar, setSessionStatus,
enableLoadUploader, refreshFileList, searchMyFile, showImgPreivew,
refreshStorage, saveFile, setLastSelect, setShiftSelectedIds
} from '../actions/index'
import { changeSubTitle } from "../redux/viewUpdate/action"
const initState = {
...cloudreveState,
viewUpdate: viewUpdateState,
}
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('index reducer', () => {
it('should return the initial state', () => {
expect(cloudreveApp(undefined, {})).toEqual(initState)
expect(cloudreveApp(undefined, { type: '@@INIT'})).toEqual(initState)
})
it('should handle DRAWER_TOGGLE', () => {
@ -593,9 +607,11 @@ describe('index reducer', () => {
})
})
it('should handle NAVIGATOR_TO', () => {
it('should handle NAVIGATOR_TO', async () => {
const store = mockStore(initState)
const action = navigateTo('/somewhere')
expect(cloudreveApp(initState, action)).toEqual({
const navAction = await store.dispatch(action)
expect(cloudreveApp(initState, navAction)).toEqual({
...initState,
navigator: {
...initState.navigator,
@ -621,8 +637,7 @@ describe('index reducer', () => {
expect(window.currntPath).toEqual('/somewhere')
})
it('should handle NAVIGATOR_UP', () => {
const action = navigateUp('somewhere')
it('should handle NAVIGATOR_UP', async () => {
const navState = {
...initState,
navigator: {
@ -630,7 +645,10 @@ describe('index reducer', () => {
path: '/to/somewhere'
}
}
expect(cloudreveApp(navState, action)).toEqual({
const store = mockStore(navState)
const action = navigateUp('somewhere')
const navAction = await store.dispatch(action)
expect(cloudreveApp(navState, navAction)).toEqual({
...initState,
navigator: {
...initState.navigator,
@ -993,15 +1011,18 @@ describe('index reducer', () => {
})
})
it('should handle CHANGE_SUB_TITLE', () => {
it('should handle CHANGE_SUB_TITLE', async () => {
const store = mockStore(initState)
const action = changeSubTitle('test sub title')
expect(cloudreveApp(initState, action)).toEqual({
const changeSubtitleAction = await store.dispatch(action)
expect(cloudreveApp(initState, changeSubtitleAction)).toEqual({
...initState,
viewUpdate: {
...initState.viewUpdate,
subTitle: 'test sub title',
}
})
expect(document.title).toEqual('test sub title - Cloudreve')
})
it('should handle TOGGLE_SNACKBAR', () => {

View File

@ -1,38 +0,0 @@
const viewUpdate = (state = [], action) => {
switch (action.type) {
case 'DRAWER_TOGGLE':
return Object.assign({}, state, {
open: action.open
});
case 'CHANGE_VIEW_METHOD':
return Object.assign({}, state, {
explorerViewMethod: action.method
});
case 'CHANGE_SORT_METHOD':
return Object.assign({}, state, {
sortMethod: action.method
});
case 'CHANGE_CONTEXT_MENU':
if(state.contextOpen && action.open){
return Object.assign({}, state);
}
return Object.assign({}, state, {
contextType: action.menuType,
contextOpen: action.open,
});
case 'SET_NAVIGATOR_LOADING_STATUE':
return Object.assign({}, state, {
navigatorLoading: action.status
});
case 'SET_NAVIGATOR_ERROR':
return Object.assign({}, state, {
navigatorError: action.status,
navigatorErrorMsg: action.msg,
});
default:
return state
}
}
export default viewUpdate

View File

@ -0,0 +1,16 @@
import { combineReducers as combine, ReducersMapObject, AnyAction } from 'redux'
import invariant from 'invariant'
export const combineReducers = (reducers: ReducersMapObject) => {
const combinedReducer = combine(reducers)
// TODO: define state type
return (state: any, action: AnyAction) => {
if (action.type.split('/').length > 1) {
const namespace = action.type.split('/')[0]
const reducer = reducers[namespace]
invariant(!!reducer, `reducer ${namespace} doesn't exist`)
return reducer && reducer(state, action)
}
return combinedReducer(state, action)
}
}

View File

@ -0,0 +1,18 @@
import { ThunkAction } from 'redux-thunk'
export interface ACTION_CHANGE_SUBTITLE {
type: "CHANGE_SUB_TITLE",
title: string,
}
export const changeSubTitle = (title: string):
ThunkAction<ACTION_CHANGE_SUBTITLE, any, any, any> => {
return (dispatch, getState) => {
const state = getState()
document.title = (title === null || title === undefined) ? state.siteConfig.title : (title + " - " +state.siteConfig.title);
return dispatch({
type: "CHANGE_SUB_TITLE",
title: title
})
}
}

View File

@ -0,0 +1,7 @@
import * as actions from './action'
import * as reducers from './reducer'
export default {
actions,
reducers,
}

View File

@ -0,0 +1,309 @@
import { AnyAction } from "redux"
import Auth from "../../middleware/Auth"
export interface ViewUpdateState {
isLogin: boolean,
loadUploader:boolean,
open: boolean,
explorerViewMethod: string,
sortMethod: string,
subTitle: string | null,
contextType: string,
contextOpen: boolean,
menuOpen: boolean,
navigatorLoading: boolean,
navigatorError: boolean,
navigatorErrorMsg: string | null,
modalsLoading: boolean,
storageRefresh: boolean,
userPopoverAnchorEl: any,
shareUserPopoverAnchorEl: any,
modals: {
createNewFolder: boolean,
createNewFile: boolean,
rename: boolean,
move: boolean,
remove: boolean,
share: boolean,
music: boolean,
remoteDownload: boolean,
torrentDownload: boolean,
getSource: boolean,
copy:boolean,
resave: boolean,
compress:boolean,
decompress:boolean,
},
snackbar: {
toggle: boolean,
vertical: string,
horizontal: string,
msg: string,
color: string
}
}
export const initState: ViewUpdateState = {
// 是否登录
isLogin: Auth.Check(),
loadUploader:false,
open: false,
explorerViewMethod: "icon",
sortMethod: "timePos",
subTitle: null,
contextType: "none",
contextOpen: false,
menuOpen: false,
navigatorLoading: true,
navigatorError: false,
navigatorErrorMsg: null,
modalsLoading: false,
storageRefresh: false,
userPopoverAnchorEl: null,
shareUserPopoverAnchorEl: null,
modals: {
createNewFolder: false,
createNewFile: false,
rename: false,
move: false,
remove: false,
share: false,
music: false,
remoteDownload: false,
torrentDownload: false,
getSource: false,
copy:false,
resave: false,
compress:false,
decompress:false,
},
snackbar: {
toggle: false,
vertical: "top",
horizontal: "center",
msg: "",
color: ""
}
}
const viewUpdate = (state: ViewUpdateState = initState, action: AnyAction) => {
switch (action.type) {
case 'DRAWER_TOGGLE':
return Object.assign({}, state, {
open: action.open,
});
case 'CHANGE_VIEW_METHOD':
return Object.assign({}, state, {
explorerViewMethod: action.method,
});
case 'SET_NAVIGATOR_LOADING_STATUE':
return Object.assign({}, state, {
navigatorLoading:action.status,
});
case 'SET_NAVIGATOR_ERROR':
return Object.assign({}, state, {
navigatorError: action.status,
navigatorErrorMsg: action.msg,
})
case 'OPEN_CREATE_FOLDER_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
createNewFolder:true,
}),
contextOpen:false,
});
case 'OPEN_CREATE_FILE_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
createNewFile:true,
}),
contextOpen:false,
});
case 'OPEN_RENAME_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
rename:true,
}),
contextOpen:false,
});
case 'OPEN_REMOVE_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
remove:true,
}),
contextOpen:false,
});
case 'OPEN_MOVE_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
move:true,
}),
contextOpen:false,
});
case 'OPEN_RESAVE_DIALOG':
// window.shareKey = action.key;
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
resave:true,
}),
contextOpen:false,
});
case 'SET_USER_POPOVER':
return Object.assign({}, state, {
userPopoverAnchorEl:action.anchor,
});
case 'SET_SHARE_USER_POPOVER':
return Object.assign({}, state, {
shareUserPopoverAnchorEl:action.anchor,
});
case 'OPEN_SHARE_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
share:true,
}),
contextOpen:false,
});
case 'OPEN_MUSIC_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
music:true,
}),
contextOpen:false,
});
case 'OPEN_REMOTE_DOWNLOAD_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
remoteDownload:true,
}),
contextOpen:false,
});
case 'OPEN_TORRENT_DOWNLOAD_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
torrentDownload:true,
}),
contextOpen:false,
});
case 'OPEN_DECOMPRESS_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
decompress:true,
}),
contextOpen:false,
});
case 'OPEN_COMPRESS_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
compress:true,
}),
contextOpen:false,
});
case 'OPEN_GET_SOURCE_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
getSource:true,
}),
contextOpen:false,
});
case 'OPEN_COPY_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
copy:true,
}),
contextOpen:false,
});
case 'OPEN_LOADING_DIALOG':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
loading:true,
loadingText:action.text,
}),
contextOpen:false,
});
case 'CLOSE_ALL_MODALS':
return Object.assign({}, state, {
modals: Object.assign({}, state.modals, {
createNewFolder:false,
createNewFile:false,
rename:false,
move:false,
remove:false,
share:false,
music:false,
remoteDownload:false,
torrentDownload:false,
getSource:false,
resave:false,
copy:false,
loading:false,
compress:false,
decompress:false,
}),
});
case 'TOGGLE_SNACKBAR':
return Object.assign({}, state, {
snackbar:{
toggle:!state.snackbar.toggle,
vertical:action.vertical,
horizontal:action.horizontal,
msg:action.msg,
color:action.color,
},
});
case 'SET_MODALS_LOADING':
return Object.assign({}, state, {
modalsLoading:action.status,
});
case 'SET_SESSION_STATUS':
return {
...state,
isLogin: action.status,
}
case 'ENABLE_LOAD_UPLOADER':
return Object.assign({}, state, {
loadUploader:true,
});
case 'REFRESH_STORAGE':
return Object.assign({}, state, {
storageRefresh:!state.storageRefresh,
});
case 'SEARCH_MY_FILE':
return Object.assign({}, state, {
contextOpen:false,
navigatorError:false,
navigatorLoading:true,
})
case 'CHANGE_CONTEXT_MENU':
if(state.contextOpen && action.open){
return Object.assign({}, state);
}
return Object.assign({}, state, {
contextOpen: action.open,
contextType: action.menuType,
});
case 'CHANGE_SUB_TITLE':
return Object.assign({}, state, {
subTitle: action.title,
});
case 'CHANGE_SORT_METHOD':
return {
...state,
sortMethod: action.method
}
case 'NAVIGATOR_TO':
return {
...state,
contextOpen:false,
navigatorError:false,
navigatorLoading: action.navigatorLoading
}
// case 'NAVIGATOR_TO':
// return Object.assign({}, state, {
// contextOpen:false,
// navigatorError:false,
// navigatorLoading:true,
// })
default:
return state
}
}
export default viewUpdate

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"esModuleInterop": true,
"module": "commonjs",
"target": "es6",
"jsx": "react"
}
}

View File

@ -1381,6 +1381,11 @@
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.30.tgz#20efa342807606ada5483731a8137cb1561e5fe9"
integrity sha512-98fB+yo7imSD2F7PF7GIpELNgtLNgo5wjivu0W5V4jx+KVVJxo6p/qN4zdzSTBWy4/sN3pPyXwnhRSD28QX+ag==
"@types/invariant@^2.2.32":
version "2.2.32"
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.32.tgz#cf523a609062564e36e7a7dadb5089ed87da6382"
integrity sha512-WjY4WVFaehHv+TOgm+dS3UI559NvsPGFz/C0nIo7KOOdC+HeC7Y3/yLzdJYQ3+oFQaTXrOVm7cNtIgMataIDVg==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -1421,6 +1426,13 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
"@types/react-dom@^16.9.7":
version "16.9.7"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.7.tgz#60844d48ce252d7b2dccf0c7bb937130e27c0cd2"
integrity sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.2.3.tgz#4924133f7268694058e415bf7aea2d4c21131470"
@ -6764,6 +6776,11 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@ -9465,6 +9482,13 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^1.0.0"
redux-mock-store@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.4.tgz#90d02495fd918ddbaa96b83aef626287c9ab5872"
integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==
dependencies:
lodash.isplainobject "^4.0.6"
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
@ -10946,6 +10970,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
ua-parser-js@^0.7.18:
version "0.7.20"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098"