diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 03d9549ea..89e07a45e 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,17 @@ \ No newline at end of file diff --git a/src/actions/explorerActions.js b/src/actions/explorerActions.js index c925089c1..3d779105d 100644 --- a/src/actions/explorerActions.js +++ b/src/actions/explorerActions.js @@ -1,15 +1,18 @@ import axiosInstance from "../utils/API/API"; import { - CHANGE_DISTRACTION_FREE_MODE, - CHANGE_LAYOUT_COLS, - GET_CONFIG_FOR_REMOTE, - GET_FILES_LIST, - GET_REMOTE_LIST, - REQUEST_ERROR, - REQUEST_SUCCESS + ADD_LAYOUT_CONTAINER, + CHANGE_ACTIVE_REMOTE_CONTAINER, + CHANGE_DISTRACTION_FREE_MODE, + CHANGE_LAYOUT_COLS, + GET_CONFIG_FOR_REMOTE, + GET_FILES_LIST, + GET_REMOTE_LIST, + REMOVE_LAYOUT_CONTAINER, + REQUEST_ERROR, + REQUEST_SUCCESS } from "./types"; -import {addColonAtLast, isLocalRemoteName} from "../utils/Tools"; -import {createPath} from "./explorerStateActions"; +import {addColonAtLast, isLocalRemoteName, makeUniqueID} from "../utils/Tools"; +import {createPath, removePath} from "./explorerStateActions"; import urls from "../utils/API/endpoint"; /** @@ -110,21 +113,74 @@ export const getFiles = (remoteName, remotePath) => dispatch => { /** * Changes the number of columns in current layout view. + * @param mode {string} Either "vertical or horizontal, defines the split type" * @param numCols {number} Number of columns to create * @returns {Function} */ -export const changeNumCols = (numCols) => (dispatch) => { +export const changeNumCols = (numCols, mode) => (dispatch) => { if (!numCols || numCols < 0) throw new Error(`Invalid number of cols:${numCols}`); - for (let i = 0; i < numCols; i++) { - dispatch(createPath(i.toString())) - } + // for (let i = 0; i < numCols; i++) { + // dispatch(createPath(i.toString())) + // } dispatch({ type: CHANGE_LAYOUT_COLS, payload: { - numCols + numCols, mode + } + }) +}; + +/** + * Adds a new remote container. + * @param paneID {int} pane ID + * @returns {Function} + */ +export const addRemoteContainer = (paneID) => (dispatch) => { + const uniqueID = makeUniqueID(3); + dispatch(createPath(uniqueID)); + dispatch(changeActiveRemoteContainer(uniqueID, paneID)); + dispatch({ + type: ADD_LAYOUT_CONTAINER, + payload: { + containerID: uniqueID, + paneID + } + }) +}; + + +/** + * Remove a new remote container. + * @param containerID {string} Container ID to remove + * @param paneID {int} pane ID + * @returns {Function} + */ +export const removeRemoteContainer = (containerID, paneID) => (dispatch) => { + dispatch(removePath(containerID)); + // console.log("Removing : " + containerID); + dispatch({ + type: REMOVE_LAYOUT_CONTAINER, + payload: { + containerID, paneID + } + }) +}; + +/** + * Change active remote container. + * @param containerID {string} Container ID to remove + * @param paneID {int} pane ID + * @returns {Function} + */ +export const changeActiveRemoteContainer = (containerID, paneID) => (dispatch) => { + dispatch({ + type: CHANGE_ACTIVE_REMOTE_CONTAINER, + payload: { + containerID, + paneID } }) }; diff --git a/src/actions/explorerStateActions.js b/src/actions/explorerStateActions.js index 7ca8c0389..e4dad69ac 100644 --- a/src/actions/explorerStateActions.js +++ b/src/actions/explorerStateActions.js @@ -5,11 +5,13 @@ import { CHANGE_REMOTE_NAME, CHANGE_REMOTE_PATH, CHANGE_SEARCH_QUERY, + CHANGE_SORT_FILTER, CHANGE_VISIBILITY_FILTER, CREATE_PATH, NAVIGATE_BACK, NAVIGATE_FWD, - NAVIGATE_UP + NAVIGATE_UP, + REMOVE_PATH } from "./types"; import {getFiles} from "./explorerActions"; @@ -98,6 +100,20 @@ export const createPath = (containerID) => dispatch => { }; +/** + * Creates an empty path for initialization of a container. + * @param containerID {string} + * @returns {Function} + */ +export const removePath = (containerID) => dispatch => { + + dispatch({ + type: REMOVE_PATH, + id: containerID + }) +}; + + /** * Computes and requests the path for going one level up in the working directory. * Eg: /tmp/abc -> navigateUp -> /tmp @@ -197,3 +213,14 @@ export const setLoadImages = (containerID, shouldLoad) => dispatch => { }) }; +export const changeSortFilter = (containerID, sortFilter, sortFilterAscending) => dispatch => { + dispatch({ + type: CHANGE_SORT_FILTER, + id: containerID, + payload: { + sortFilter, + sortFilterAscending + } + }) +} + diff --git a/src/actions/types.js b/src/actions/types.js index 2b3dcf3a5..080cf7809 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -7,6 +7,7 @@ export const GET_CONFIG_DUMP = 'GET_CONFIG_DUMP'; export const GET_FILES_LIST = 'GET_FILES_LIST'; export const CHANGE_PATH = 'CHANGE_PATH'; export const CREATE_PATH = 'CREATE_PATH'; +export const REMOVE_PATH = 'REMOVE_PATH'; export const CHANGE_REMOTE_NAME = 'CHANGE_REMOTE_NAME'; export const CHANGE_REMOTE_PATH = 'CHANGE_REMOTE_PATH'; export const NAVIGATE_UP = 'NAVIGATE_UP'; @@ -30,7 +31,11 @@ export const CHANGE_AXIOS_INTERCEPTOR = 'CHANGE_AXIOS_INTERCEPTOR'; export const CHANGE_LOAD_IMAGES = 'CHANGE_LOAD_IMAGES'; export const LOAD_IMAGE = 'LOAD_IMAGE'; export const CHANGE_LAYOUT_COLS = 'CHANGE_LAYOUT_COLS'; +export const ADD_LAYOUT_CONTAINER = 'ADD_LAYOUT_CONTAINER'; +export const REMOVE_LAYOUT_CONTAINER = 'REMOVE_LAYOUT_CONTAINER'; +export const CHANGE_ACTIVE_REMOTE_CONTAINER = 'CHANGE_ACTIVE_REMOTE_CONTAINER'; export const CHANGE_DISTRACTION_FREE_MODE = "CHANGE_DISTRACTION_FREE_MODE"; +export const CHANGE_SORT_FILTER = "CHANGE_SORT_FILTER"; export const REQUEST_ERROR = 'ERROR'; diff --git a/src/assets/img/brand/animatedlogo.gif b/src/assets/img/brand/animatedlogo.gif new file mode 100644 index 000000000..166d7999d Binary files /dev/null and b/src/assets/img/brand/animatedlogo.gif differ diff --git a/src/assets/img/brand/logo_symbol.png b/src/assets/img/brand/logo_symbol.png new file mode 100644 index 000000000..45b43d1a3 Binary files /dev/null and b/src/assets/img/brand/logo_symbol.png differ diff --git a/src/assets/img/double-pane1.png b/src/assets/img/double-pane1.png new file mode 100755 index 000000000..f45b38259 Binary files /dev/null and b/src/assets/img/double-pane1.png differ diff --git a/src/assets/img/new-folder.png b/src/assets/img/new-folder.png new file mode 100755 index 000000000..24c0da806 Binary files /dev/null and b/src/assets/img/new-folder.png differ diff --git a/src/assets/img/single-pane.png b/src/assets/img/single-pane.png new file mode 100755 index 000000000..849d0afca Binary files /dev/null and b/src/assets/img/single-pane.png differ diff --git a/src/assets/img/triple-pane.png b/src/assets/img/triple-pane.png new file mode 100755 index 000000000..7cd7f6ffe Binary files /dev/null and b/src/assets/img/triple-pane.png differ diff --git a/src/containers/DefaultLayout/DefaultHeader.js b/src/containers/DefaultLayout/DefaultHeader.js index c87de13bb..abd847816 100644 --- a/src/containers/DefaultLayout/DefaultHeader.js +++ b/src/containers/DefaultLayout/DefaultHeader.js @@ -3,7 +3,7 @@ import {NavLink} from 'react-router-dom'; import {Nav, NavItem} from 'reactstrap'; import PropTypes from 'prop-types'; -import {AppAsideToggler, AppNavbarBrand, AppSidebarToggler} from '@coreui/react'; +import {AppNavbarBrand, AppSidebarToggler} from '@coreui/react'; import logo from '../../assets/img/brand/logo.png' import favicon from '../../assets/img/brand/favicon.png' import BackendStatusCard from "../../views/Base/BackendStatusCard/BackendStatusCard"; @@ -24,8 +24,8 @@ class DefaultHeader extends Component { @@ -37,10 +37,8 @@ class DefaultHeader extends Component { - + ); } diff --git a/src/containers/DefaultLayout/DefaultLayout.js b/src/containers/DefaultLayout/DefaultLayout.js index eb1915186..93ef26057 100644 --- a/src/containers/DefaultLayout/DefaultLayout.js +++ b/src/containers/DefaultLayout/DefaultLayout.js @@ -4,7 +4,6 @@ import {Container} from 'reactstrap'; import {getVersion} from "../../actions/versionActions"; import { - AppAside, AppBreadcrumb, AppFooter, AppHeader, @@ -23,7 +22,7 @@ import {connect} from "react-redux"; import {AUTH_KEY} from "../../utils/Constants"; import ErrorBoundary from "../../ErrorHandling/ErrorBoundary"; -const DefaultAside = React.lazy(() => import('./DefaultAside')); +// const DefaultAside = React.lazy(() => import('./DefaultAside')); const DefaultFooter = React.lazy(() => import('./DefaultFooter')); const DefaultHeader = React.lazy(() => import('./DefaultHeader')); @@ -104,11 +103,6 @@ class DefaultLayout extends Component { - - - - - diff --git a/src/containers/DefaultLayout/__snapshots__/DefaultLayout.test.js.snap b/src/containers/DefaultLayout/__snapshots__/DefaultLayout.test.js.snap index c47846cee..b32707a67 100644 --- a/src/containers/DefaultLayout/__snapshots__/DefaultLayout.test.js.snap +++ b/src/containers/DefaultLayout/__snapshots__/DefaultLayout.test.js.snap @@ -123,7 +123,7 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` "_result": null, "_status": -1, }, - "name": "New Drive", + "name": "Edit Remote", "path": "/newdrive/edit/:drivePrefix", }, Object { @@ -134,7 +134,7 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` "_status": -1, }, "exact": true, - "name": "New Drive", + "name": "New Remote", "path": "/newdrive", }, Object { @@ -145,7 +145,7 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` "_status": -1, }, "exact": true, - "name": "New Drive", + "name": "Login Page", "path": "/login", }, Object { @@ -220,21 +220,21 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` @@ -277,25 +277,6 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` - - - Loading... - - } - > - - - item.ID !== action.payload.containerID); + state.numContainers = state.containers.length; + const lastItem = state.containers.filter(item => item.paneID === action.payload.paneID).slice(-1).pop(); + state.activeRemoteContainerID = { + ...state.activeRemoteContainerID, + [action.payload.paneID]: (lastItem ? lastItem.ID : undefined) + }; + + // state.activeRemoteContainerID = lastItem ? lastItem : ""; + return {...state}; + case CHANGE_ACTIVE_REMOTE_CONTAINER: + state.activeRemoteContainerID = { + ...state.activeRemoteContainerID, + [action.payload.paneID]: action.payload.containerID + }; + return {...state}; + case CHANGE_LAYOUT_COLS: + return { + ...state, + numCols: action.payload.numCols + }; + case CHANGE_DISTRACTION_FREE_MODE: + return { + ...state, + distractionFreeMode: action.payload + }; + default: + return state; + } -} \ No newline at end of file +} diff --git a/src/reducers/explorerReducer.test.js b/src/reducers/explorerReducer.test.js index 6350f5f01..d6af5d905 100644 --- a/src/reducers/explorerReducer.test.js +++ b/src/reducers/explorerReducer.test.js @@ -5,13 +5,16 @@ describe('Explorer Reducer', function () { it('should return default state', function () { const newState = explorerReducer(undefined, {}); expect(newState).toEqual({ - configs: {}, - remotes: [], - files: {}, - hasError: false, - numCols: 0, - distractionFreeMode: false, - }); + configs: {}, + remotes: [], + files: {}, + hasError: false, + numContainers: 0, + containers: [], + activeRemoteContainerID: {}, + distractionFreeMode: false, + numCols: 1 + }); }); it('should change state for GET_CONFIG_FOR_REMOTE SUCCESS', function () { @@ -86,4 +89,4 @@ describe('Explorer Reducer', function () { expect(newState.hasError).toBe(true); expect(newState.error).toEqual(error); }); -}); \ No newline at end of file +}); diff --git a/src/reducers/explorerStateReducer.js b/src/reducers/explorerStateReducer.js index 19a5de133..199ea7c48 100644 --- a/src/reducers/explorerStateReducer.js +++ b/src/reducers/explorerStateReducer.js @@ -1,15 +1,17 @@ import { - CHANGE_GRID_MODE, - CHANGE_LOAD_IMAGES, - CHANGE_PATH, - CHANGE_REMOTE_NAME, - CHANGE_REMOTE_PATH, - CHANGE_SEARCH_QUERY, - CHANGE_VISIBILITY_FILTER, - CREATE_PATH, - NAVIGATE_BACK, - NAVIGATE_FWD, - NAVIGATE_UP + CHANGE_GRID_MODE, + CHANGE_LOAD_IMAGES, + CHANGE_PATH, + CHANGE_REMOTE_NAME, + CHANGE_REMOTE_PATH, + CHANGE_SEARCH_QUERY, + CHANGE_SORT_FILTER, + CHANGE_VISIBILITY_FILTER, + CREATE_PATH, + NAVIGATE_BACK, + NAVIGATE_FWD, + NAVIGATE_UP, + REMOVE_PATH } from "../actions/types"; import BackStack from "../utils/classes/BackStack"; @@ -19,7 +21,9 @@ const initialState = { visibilityFilters: {}, gridMode: {}, searchQueries: {}, - loadImages: {} + loadImages: {}, + sortFilters: {}, + sortFiltersAscending: {} }; /** @@ -47,9 +51,6 @@ export default function (state = initialState, action) { } } - - // let arrayLen = array.length - 1 ; - let remoteName = action.remoteName; let remotePath = action.remotePath; @@ -67,6 +68,10 @@ export default function (state = initialState, action) { let loadImages = state.loadImages[id]; if (!loadImages) loadImages = false; + let sortFilterAscending = state.sortFiltersAscending[id]; + if(!sortFilterAscending) sortFilterAscending = true; + let sortFilter = state.sortFilters[id]; + if(!sortFilter) sortFilter = "name"; switch (action.type) { case CHANGE_PATH: @@ -88,26 +93,38 @@ export default function (state = initialState, action) { break; case CHANGE_REMOTE_PATH: - backStack.push({remoteName: backStack.peek().remoteName, remotePath: remotePath}); - // ptr++; - - break; - - case CREATE_PATH: - if (!backStack || !(backStack instanceof BackStack)) - backStack = new BackStack(); - break; - - case NAVIGATE_UP: - // TODO: Write logic for up, which will navigate one directory up - let current = backStack.peek(); - - if (current.remotePath && current.remotePath !== "") { - const splitPath = current.remotePath.split('/'); - current.remotePath = ""; - if (splitPath.length > 1) - for (let i = 0; i < splitPath.length - 1; i++) { - current.remotePath = current.remotePath + ((i !== 0) ? '/' : '') + splitPath[i]; + backStack.push({remoteName: backStack.peek().remoteName, remotePath: remotePath}); + // ptr++; + + break; + + case CREATE_PATH: + if (!backStack || !(backStack instanceof BackStack)) + backStack = new BackStack(); + break; + case REMOVE_PATH: + return { + ...state, + backStacks: {...state.backStacks, [id]: undefined}, + currentPaths: {...state.currentPaths, [id]: undefined}, + visibilityFilters: {...state.visibilityFilters, [id]: undefined}, + gridMode: {...state.gridMode, [id]: undefined}, + searchQueries: {...state.searchQueries, [id]: undefined}, + loadImages: {...state.loadImages, [id]: undefined}, + sortFilters: {...state.sortFilters, [id]: undefined}, + sortFiltersAscending: {...state.sortFiltersAscending, [id]: undefined}, + }; + // break; + case NAVIGATE_UP: + // TODO: Write logic for up, which will navigate one directory up + let current = backStack.peek(); + + if (current.remotePath && current.remotePath !== "") { + const splitPath = current.remotePath.split('/'); + current.remotePath = ""; + if (splitPath.length > 1) + for (let i = 0; i < splitPath.length - 1; i++) { + current.remotePath = current.remotePath + ((i !== 0) ? '/' : '') + splitPath[i]; } } backStack.push(current); @@ -121,8 +138,10 @@ export default function (state = initialState, action) { backStack.moveBack(); break; case CHANGE_VISIBILITY_FILTER: - if (action.filter) - visibilityFilter = action.filter; + if (action.filter) + visibilityFilter = action.filter; + else + visibilityFilter = ""; break; case CHANGE_GRID_MODE: if (action.mode) { @@ -137,6 +156,10 @@ export default function (state = initialState, action) { case CHANGE_LOAD_IMAGES: loadImages = action.payload; break; + case CHANGE_SORT_FILTER: + sortFilter = action.payload.sortFilter; + sortFilterAscending = action.payload.sortFilterAscending; + break; default: break; } @@ -147,7 +170,9 @@ export default function (state = initialState, action) { visibilityFilters: {...state.visibilityFilters, [id]: visibilityFilter}, gridMode: {...state.gridMode, [id]: gridMode}, searchQueries: {...state.searchQueries, [id]: searchQuery}, - loadImages: {...state.loadImages, [id]: loadImages} + loadImages: {...state.loadImages, [id]: loadImages}, + sortFilters: {...state.sortFilters, [id]: sortFilter}, + sortFiltersAscending: {...state.sortFiltersAscending, [id]: sortFilterAscending}, }; } else { // console.error("ID is unexpectedly null"); diff --git a/src/reducers/explorerStateReducer.test.js b/src/reducers/explorerStateReducer.test.js index 729164289..b6682f4b2 100644 --- a/src/reducers/explorerStateReducer.test.js +++ b/src/reducers/explorerStateReducer.test.js @@ -9,7 +9,9 @@ describe('Explorer Reducer', function () { visibilityFilters: {}, gridMode: {}, searchQueries: {}, - loadImages: {} + loadImages: {}, + sortFilters: {}, + sortFiltersAscending: {} }); }); diff --git a/src/routes.js b/src/routes.js index 304010961..20fcc6ef3 100644 --- a/src/routes.js +++ b/src/routes.js @@ -11,9 +11,9 @@ const RCloneDashboard = React.lazy(() => import("./views/RCloneDashboard")); // Define the routes as required const routes = [ {path: '/', exact: true, name: 'Home'}, - {path: '/newdrive/edit/:drivePrefix', name: 'New Drive', component: MyDashboard}, - {path: '/newdrive', exact: true, name: 'New Drive', component: MyDashboard}, - {path: '/login', exact: true, name: 'New Drive', component: Login}, + {path: '/newdrive/edit/:drivePrefix', name: 'Edit Remote', component: MyDashboard}, + {path: '/newdrive', exact: true, name: 'New Remote', component: MyDashboard}, + {path: '/login', exact: true, name: 'Login Page', component: Login}, {path: '/dashboard', name: 'Dashboard', component: Home}, {path: '/showconfig', name: 'Configs', component: ShowConfig}, {path: '/remoteExplorer/:remoteName/:remotePath', exact: true, name: 'Explorer', component: RemoteExplorerLayout}, diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss index ff21ca679..50b3cd1b6 100644 --- a/src/scss/_custom.scss +++ b/src/scss/_custom.scss @@ -1,4 +1,221 @@ // Here you can add other styles +body{ + font-family: Noto Sans, sans-serif; +} + +.card, .card-header, .card-body { + border-radius: 0 !important; +} + +.btn-white { + font-style: normal; + font-weight: normal; + color: rgba(44,44,44,1); + background-color: rgba(232,239,247,1); +} + +.btn-primary{ + background-color: rgba(112,202,242,1); + border:none ; +} + +.card .card-header{ + text-align: left; + background-color: rgba(188,201,214,1); + font-style: normal; + font-weight: bold; + color: rgba(44,44,44,1); +} + +.card .card-subtitle{ + font-style: normal; + font-weight: bold; + color: rgba(63,121,173,1); +} +.card .card-text { + font-style: normal; + font-weight: normal; + color: rgba(114,114,114,1); +} + +.card .card-body{ + text-align: left; +} + +// START Explorer + +.text-choose-layout { + font-style: normal; + font-weight: bold; + color: rgba(114,114,114,1); +} +.layout-change-button{ + background: none; + padding: 0; + +} + +.btn-explorer-action { + background: none; + border: none; +} + +.btn-explorer-action :hover { + border: 1px; + background: none; +} + +.table-fix-head { overflow-y: auto; height: 100px; } +.table-fix-head thead th { position: sticky; top: 0; z-index: 2; border-top: none; border-bottom: 1px solid #c8ced3;} +.table-fix-head th { background: #ffffff; } + +.animate-fade-in { + -webkit-animation: fadein 0.5s; /* Safari, Chrome and Opera > 12.1 */ + -moz-animation: fadein 0.5s; /* Firefox < 16 */ + -ms-animation: fadein 0.5s; /* Internet Explorer */ + -o-animation: fadein 0.5s; /* Opera < 12.1 */ + animation: fadein 0.5s; +} + +@keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Firefox < 16 */ +@-moz-keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Safari, Chrome and Opera > 12.1 */ +@-webkit-keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Internet Explorer */ +@-ms-keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } +} + +// END Explorer + +// START REMOTE CONFIG + +.btn-blue { + background-color: rgba(63,121,173,1); + color: #ffffff; +} + +.btn-blue :hover{ + background-color: rgba(63,121,173,1); + color: #ffffff; + +} + +.btn-no-background { + background: none; + border: none; + font-weight: bold; + color: rgba(44,44,44,1); +} +.timeline { + + padding-top: 20px; + padding-bottom: 20px; + + h4 { + padding-top: 10px; + font-weight: bold; + color: rgba(140,140,140,1); + + } + + .btn-step-count { + border-radius: 100px; + background: none; + border: 5px solid rgba(140, 140, 140, 1); + color: rgba(66, 66, 66, 1); + font-weight: bold; + + } + + .timeline-divider { + border: 2px solid rgba(66,66,66,1); + margin-top: 24px; + + } + + .step-active{ + .btn { + border: 5px solid rgba(63,121,173,1); + } + h4 { + color: rgba(66,66,66,1); + } + } + .btn-step-active { + border: 5px solid rgba(63,121,173,1); + } + +} + + +// Timeline code + +// END Remote Config + +// START Sidebar + +.sidebar{ + ul.nav { + background-color: #ffffff; + font-family: Nunito, sans-serif; + font-weight: bold; + } + .nav-link.active { + background-color: #ffffff; + color: rgb(63,121,173); + } + .nav-link.active > .nav-icon :hover{ + color: rgb(63,121,173); + } + .nav-link { + color: rgb(114,114,114); + padding-top: 15px; + padding-bottom: 15px; + border-top: 1px solid rgba(232, 239, 247, 1); + } + + .nav-link-success:hover { + color: #fff; + } +} + +// END Sidebar + +.custom-tab { + border-right: 1px solid #c8ced3; + background-color: #ffffff; + color: #000000; + overflow-x: hidden; + + i { + color: #000; + } +} + +.tab-active { + background: #1b8eb7; + color: #ffffff; + + i { + color: #fff; + } +} + .text-red { color: #FF0000; } @@ -51,7 +268,7 @@ font-size: 16px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; - z-index: 2; + z-index: 3; height: 300px; overflow: auto; } diff --git a/src/utils/StateLoader.js b/src/utils/StateLoader.js index ad1b78201..c9c3d11c6 100644 --- a/src/utils/StateLoader.js +++ b/src/utils/StateLoader.js @@ -31,7 +31,6 @@ export class StateLoader { }; try { let serializedState = JSON.stringify(newState); - // console.log(serializedState); localStorage.setItem("curState", serializedState); } catch (err) { diff --git a/src/utils/Tools.js b/src/utils/Tools.js index 2b4273abe..23cc51f42 100644 --- a/src/utils/Tools.js +++ b/src/utils/Tools.js @@ -299,3 +299,46 @@ export function groupByKey(xs, keyGetter) { }); return map; } +export function getSortCompareFunction(type, ascending){ + + // console.log("Here", a,b) + switch(type){ + case "name": + return (a, b) => { + let x,y; + x = a.Name.toLowerCase(); + y = b.Name.toLowerCase(); + if (x < y) {return ascending ? -1 : 1;} + if (x > y) {return ascending ? 1 : -1;} + return 0; + } + case "size": + return (a, b) => { + let x, y; + x = a.Size ? a.Size : 0; + y = b.Size ? b.Size : 0; + return ascending ? ( x - y ) : ( y - x ); + } + case "modified": + return (a, b) => { + let x, y; + x = new Date(a.ModTime); + y = new Date(b.ModTime); + return ascending ? (x - y) : (y - x); + } + default: + break; + + + } +} + +export function makeUniqueID(length) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} diff --git a/src/views/Base/BackendStatusCard/BackendStatusCard.js b/src/views/Base/BackendStatusCard/BackendStatusCard.js index 3b6944832..d68cf1922 100644 --- a/src/views/Base/BackendStatusCard/BackendStatusCard.js +++ b/src/views/Base/BackendStatusCard/BackendStatusCard.js @@ -66,7 +66,7 @@ class BackendStatusCard extends React.Component { - rclone status + Overview Not monitoring connectivity status. Tap the icon in navbar to start.

- } - if (connectivityStatus) { - return ( -

The rclone backend is connected and working as expected.
Current IP address is {ipAddress} -
Username: {userName}

- ); - } else { - return ( -

Cannot connect to rclone backend. There is a problem with connecting to {ipAddress}.

- - ) + + let statusText = ""; + if(!checkStatus){ + statusText = "Not monitoring connectivity status. Tap the icon in navbar to start."; + }else if(connectivityStatus){ + // Connected to backend + statusText = "rClone Backend is connected and working as expected"; + }else{ + statusText = "Cannot connect to rclone backend. There is a problem with connecting to {ipAddress}." } + + return ( + <> +

+ Status: {" "} + {statusText} +

+

+ Current IP Address: {" "} + {ipAddress} +

+

+ Username: {" "} + {userName} +

+ + ) } const propTypes = { diff --git a/src/views/Base/BackendStatusCard/__snapshots__/BackendStatusCard.test.js.snap b/src/views/Base/BackendStatusCard/__snapshots__/BackendStatusCard.test.js.snap index bd5c1bfbe..38ef8f342 100644 --- a/src/views/Base/BackendStatusCard/__snapshots__/BackendStatusCard.test.js.snap +++ b/src/views/Base/BackendStatusCard/__snapshots__/BackendStatusCard.test.js.snap @@ -8,7 +8,7 @@ exports[`Backend Status Card renders showChangeBandwidth: false should match sna - rclone status + Overview - rclone status + Overview - - - Current bandwidth - - - - - Change bandwidth - -
- - - - - - Keep empty to reset. - The bandwidth should be of the form 1M|2M|1G|1K|1.1K - etc - - - - - -
-
-
- - - - ) + + return ( + + + Bandwidth + + +

+ Current Max speed: {" "} + {(bandwidth.rate !== "off") ? bandwidth.rate : "Unlimited"} +

+
+ + + + + + Keep empty to reset. + The bandwidth should be of the form 1M|2M|1G|1K|1.1K + etc + + + + + +
+ +
+
) } } diff --git a/src/views/Base/BandwidthStatusCard/__snapshots__/BandwidthStatusCard.test.js.snap b/src/views/Base/BandwidthStatusCard/__snapshots__/BandwidthStatusCard.test.js.snap index baf53360c..21aa83d79 100644 --- a/src/views/Base/BandwidthStatusCard/__snapshots__/BandwidthStatusCard.test.js.snap +++ b/src/views/Base/BandwidthStatusCard/__snapshots__/BandwidthStatusCard.test.js.snap @@ -1,149 +1,104 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Bandwidth Status Card should match snapshot 1`] = ` - - - - Current bandwidth - - - - + + - - + + Current Max speed: + + + - Change bandwidth - - +

+
+ - + Enter new max speed + + - + - - - - - Keep empty to reset. - - - The bandwidth should be of the form 1M|2M|1G|1K|1.1K etc - - - - - -
-
- -
+ The bandwidth should be of the form 1M|2M|1G|1K|1.1K etc + + + + + +
+
`; diff --git a/src/views/Base/FileOperations/FileOperations.js b/src/views/Base/FileOperations/FileOperations.js index efb7421f4..b4a2cf1ea 100644 --- a/src/views/Base/FileOperations/FileOperations.js +++ b/src/views/Base/FileOperations/FileOperations.js @@ -1,34 +1,34 @@ import React from 'react'; import { - Button, - ButtonDropdown, - ButtonGroup, - Col, - DropdownItem, - DropdownMenu, - DropdownToggle, - Input, - InputGroup, - InputGroupAddon, - Modal, - ModalBody, - ModalFooter, - ModalHeader, - Row, - Spinner, - UncontrolledTooltip + Button, + ButtonDropdown, + ButtonGroup, + Col, + DropdownItem, + DropdownMenu, + DropdownToggle, + Form, + FormGroup, + Input, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + Row, + Spinner, + UncontrolledTooltip } from "reactstrap"; import NewFolder from "../NewFolder/NewFolder"; import PropTypes from "prop-types"; import {connect} from "react-redux"; import { - changeGridMode, - changeVisibilityFilter, - getFilesForContainerID, - navigateBack, - navigateFwd, - setLoadImages, - setSearchQuery + changeGridMode, + changeVisibilityFilter, + getFilesForContainerID, + navigateBack, + navigateFwd, + setLoadImages, + setSearchQuery } from "../../../actions/explorerStateActions"; import {visibilityFilteringOptions} from "../../../utils/Constants"; import {getAbout} from "../../../actions/providerStatusActions"; @@ -38,6 +38,7 @@ import axiosInstance from "../../../utils/API/API"; import {toast} from "react-toastify"; import {PROP_FS_INFO} from "../../../utils/RclonePropTypes"; import urls from "../../../utils/API/endpoint"; +import newFolderImg from '../../../assets/img/new-folder.png'; // with import /** * File Operations component which handles user actions for files in the remote.( Visibility, gridmode, back, forward etc) @@ -48,7 +49,8 @@ class FileOperations extends React.Component { this.state = { newFolderModalIsVisible: false, isAboutModalOpen: false, - dropdownOpen: false + dropdownOpen: false, + searchOpen: false }; this.filterOptions = visibilityFilteringOptions; } @@ -72,20 +74,13 @@ class FileOperations extends React.Component { const {changeVisibilityFilter} = this.props; changeVisibilityFilter(this.props.containerID, newFilter); - this.toggleDropDown(); }; - handleChangeGridMode = (e) => { - - const gridMode = e.target.value; - - const {changeGridMode} = this.props; - - changeGridMode(this.props.containerID, gridMode); - this.toggleDropDown(); - + handleChangeGridMode = () => { + const {gridMode, changeGridMode, containerID} = this.props; + changeGridMode(containerID, gridMode === "list" ? "card" : "list"); }; changeSearch = (e) => { @@ -162,106 +157,92 @@ class FileOperations extends React.Component { setLoadImages(containerID, !loadImages); }; + handleSearchOpen = () => { + const {containerID} = this.props; + this.setState((prevState) => { + if (prevState.searchOpen) { + // Clear Search Query if the search is about to close + this.props.setSearchQuery(containerID, ""); + } + + return {searchOpen: !prevState.searchOpen} + } + ); + }; + render() { - const {containerID, loadImages, getFilesForContainerID, visibilityFilter, gridMode, navigateFwd, navigateBack, searchQuery, currentPath, doughnutData} = this.props; - const {newFolderModalIsVisible, dropdownOpen, isAboutModalOpen} = this.state; + const {containerID, getFilesForContainerID, gridMode, navigateFwd, navigateBack, searchQuery, currentPath, doughnutData} = this.props; + const {newFolderModalIsVisible, dropdownOpen, isAboutModalOpen, searchOpen} = this.state; const {remoteName, remotePath} = currentPath; return ( - - ); } } @@ -331,17 +311,19 @@ FileOperations.propTypes = { */ searchQuery: PropTypes.string, /** - * A map which gives the information about the remote about. - */ - remoteAbout: PropTypes.object, - /** - * File system information and features about the current remote - */ - fsInfo: PROP_FS_INFO, - /** - * Map of data to be passed to the doughnutChart. - */ - doughnutData: PropTypes.object + * A map which gives the information about the remote about. + */ + remoteAbout: PropTypes.object, + /** + * File system information and features about the current remote + */ + fsInfo: PROP_FS_INFO, + /** + * Map of data to be passed to the doughnutChart. + */ + doughnutData: PropTypes.object, + + numCols: PropTypes.number.isRequired, }; const mapStateToProps = (state, ownProps) => { @@ -389,15 +371,16 @@ const mapStateToProps = (state, ownProps) => { } return { - visibilityFilter: state.explorer.visibilityFilters[ownProps.containerID], - loadImages: state.explorer.loadImages[ownProps.containerID], - currentPath: state.explorer.currentPaths[ownProps.containerID], - gridMode: state.explorer.gridMode[ownProps.containerID], - searchQuery: state.explorer.searchQueries[ownProps.containerID], - fsInfo, - doughnutData - - } + visibilityFilter: state.explorer.visibilityFilters[ownProps.containerID], + loadImages: state.explorer.loadImages[ownProps.containerID], + currentPath: state.explorer.currentPaths[ownProps.containerID], + gridMode: state.explorer.gridMode[ownProps.containerID], + searchQuery: state.explorer.searchQueries[ownProps.containerID], + fsInfo, + doughnutData, + numCols: state.remote.numCols, + + } }; diff --git a/src/views/Base/FileOperations/__snapshots__/FileOperations.test.js.snap b/src/views/Base/FileOperations/__snapshots__/FileOperations.test.js.snap index 6752cdb5f..c0a56b868 100644 --- a/src/views/Base/FileOperations/__snapshots__/FileOperations.test.js.snap +++ b/src/views/Base/FileOperations/__snapshots__/FileOperations.test.js.snap @@ -3,226 +3,226 @@ exports[`File Operations renders should match snapshot 1`] = ` `; diff --git a/src/views/Base/RcloneLoading/RcloneLoading.js b/src/views/Base/RcloneLoading/RcloneLoading.js new file mode 100644 index 000000000..04a453c22 --- /dev/null +++ b/src/views/Base/RcloneLoading/RcloneLoading.js @@ -0,0 +1,6 @@ +import React from 'react'; +import animatedLogo from '../../../assets/img/brand/animatedlogo.gif' + +function RcloneLoading() { + return ({"Loading"}/); +} diff --git a/src/views/Base/RunningJobs/RunningJobs.js b/src/views/Base/RunningJobs/RunningJobs.js index 57bb41fc2..fba1c9971 100644 --- a/src/views/Base/RunningJobs/RunningJobs.js +++ b/src/views/Base/RunningJobs/RunningJobs.js @@ -92,36 +92,36 @@ function GlobalStatus({stats}) { - - + + - - + + - - + + - - + + - - + + 0 ? "table-danger" : ""}> - - + + - - + + - - + + @@ -248,11 +248,11 @@ class RunningJobs extends React.Component { if (isConnected) { return ( - + - + Speed diff --git a/src/views/Base/RunningJobs/__snapshots__/RunningJobs.test.js.snap b/src/views/Base/RunningJobs/__snapshots__/RunningJobs.test.js.snap index 7c8fdc9e9..5d758161c 100644 --- a/src/views/Base/RunningJobs/__snapshots__/RunningJobs.test.js.snap +++ b/src/views/Base/RunningJobs/__snapshots__/RunningJobs.test.js.snap @@ -14,7 +14,7 @@ exports[`Running Jobs renders should match snapshot 1`] = ` } > - - diff --git a/src/views/Explorer/FilesView/FilesView.js b/src/views/Explorer/FilesView/FilesView.js index 7318432cc..3365ad2ac 100644 --- a/src/views/Explorer/FilesView/FilesView.js +++ b/src/views/Explorer/FilesView/FilesView.js @@ -1,15 +1,21 @@ import React from "react"; import axiosInstance from "../../../utils/API/API"; -import {Alert, Button, Col, Container, Row, Spinner, Table} from "reactstrap"; +import {Alert, Col, Container, Row, Spinner, Table} from "reactstrap"; import {DropTarget} from "react-dnd"; import FileComponent from "./FileComponent"; import {ItemTypes} from "./Constants"; import {toast} from "react-toastify"; -import {addColonAtLast, changeListVisibility, changeSearchFilter, isEmpty} from "../../../utils/Tools"; +import { + addColonAtLast, + changeListVisibility, + changeSearchFilter, + getSortCompareFunction, + isEmpty +} from "../../../utils/Tools"; import {connect} from "react-redux"; import {getFiles} from "../../../actions/explorerActions"; import {compose} from "redux"; -import {changePath, navigateUp} from "../../../actions/explorerStateActions"; +import {changePath, changeSortFilter, navigateUp} from "../../../actions/explorerStateActions"; import LinkShareModal from "../../Base/LinkShareModal/LinkShareModal"; import ScrollableDiv from "../../Base/ScrollableDiv/ScrollableDiv"; import {FILES_VIEW_HEIGHT} from "../../../utils/Constants"; @@ -90,21 +96,21 @@ function renderOverlay() { * */ // Provides the up button view in the files view -function UpButtonComponent({upButtonHandle, gridMode}) { - if (gridMode === "card") { - return ( - - - - ) - } else { - return ( - upButtonHandle()} className={"pointer-cursor"}> - - ); - } -} +// function UpButtonComponent({upButtonHandle, gridMode}) { +// if (gridMode === "card") { +// return ( +// +// +// +// ) +// } else { +// return ( +// upButtonHandle()} className={"pointer-cursor"}> +// +// ); +// } +// } /** * FilesView component renders files in the file explorer. @@ -122,6 +128,7 @@ class FilesView extends React.PureComponent { showLinkShareModal: false, generatedLink: "", + }; this.handleFileClick = this.handleFileClick.bind(this); this.downloadHandle = this.downloadHandle.bind(this); @@ -173,7 +180,7 @@ class FilesView extends React.PureComponent { } - getFilesList(showLoading = true) { + getFilesList() { const {remoteName, remotePath} = this.props.currentPath; this.props.getFiles(remoteName, remotePath); @@ -257,11 +264,11 @@ class FilesView extends React.PureComponent { updateHandler = () => { - const {remoteName, remotePath} = this.props.currentPath; - this.getFilesList(remoteName, remotePath); + // const {remoteName, remotePath} = this.props.currentPath; + this.getFilesList(); }; - dismissAlert = (e) => { + dismissAlert = (_) => { this.setState({isDownloadProgress: false}); }; @@ -294,41 +301,48 @@ class FilesView extends React.PureComponent { const {remoteName, remotePath} = this.props.currentPath; // console.log(fsInfo, files); if (fsInfo && !isEmpty(fsInfo)) { - return files.map((item, idx) => { + return files.reduce((result, item) => { let {ID, Name} = item; - // Using fallback as fileName when the ID is not available (for local file system) + // Using fallback as fileName when the ID is not available (especially for local file system) if (ID === undefined) { ID = Name; } if (item.IsDir === isDir) { - return ( - - - - - + result.push( + + + ); } - return null; - }); + return result; + }, []); } }; + applySortFilter = (sortFilter) => { + const {changeSortFilter, containerID} = this.props; + + if (this.props.sortFilter === sortFilter) { + return changeSortFilter(containerID, sortFilter, (this.props.sortFilterAscending !== true)); + } else { + return changeSortFilter(containerID, sortFilter, true); + } + + }; + render() { const {isLoading, isDownloadProgress, downloadingItems, generatedLink, showLinkShareModal} = this.state; - const {connectDropTarget, isOver, files, navigateUp, containerID, gridMode, canDrop} = this.props; + const {connectDropTarget, isOver, files, gridMode, canDrop, sortFilter, sortFilterAscending} = this.props; const {remoteName} = this.props.currentPath; - // console.log(this.props.searchQuery); - if (isLoading || !files) { return (
Loading
); } else { @@ -350,9 +364,7 @@ class FilesView extends React.PureComponent { renderElement = ( - - navigateUp(containerID)} gridMode={gridMode}/> - +

Directories

@@ -376,45 +388,45 @@ class FilesView extends React.PureComponent { ) } else { - - + let filterIconClass = "fa fa-lg fa-arrow-down"; + if(sortFilterAscending){ + filterIconClass = "fa fa-lg fa-arrow-up"; + } renderElement = ( + - -
Bytes Transferred:{formatBytes(bytes)}Bytes Transferred:{formatBytes(bytes)}
Average Speed:{formatBytes(speed)}PSAverage Speed:{formatBytes(speed)}PS
Checks:{checks}Checks:{checks}
Deletes:{deletes}Deletes:{deletes}
Running since:{secondsToStr(elapsedTime)}Running since:{secondsToStr(elapsedTime)}
Errors:{errors}Errors:{errors}
Transfers:{transfers}Transfers:{transfers}
Last Error:{lastError}Last Error:{lastError}
clickHandler(e, item)} id={"file" + itemIdx}> - {Name} - - + clickHandler(e, item)}> + {" "}{Name} {Size === -1 ? "-" : formatBytes(Size, 2)} {modTime.toLocaleDateString()}
- Go Up...
+// Go Up...
+
- - - - + + + - navigateUp(containerID)} gridMode={gridMode}/> {files.length > 0 ? ( - {dirComponentMap} - - {fileComponentMap} ) : - } @@ -468,11 +480,15 @@ FilesView.defaultProps = defaultProps; const mapStateToProps = (state, ownProps) => { - const currentPath = state.explorer.currentPaths[ownProps.containerID]; - const visibilityFilter = state.explorer.visibilityFilters[ownProps.containerID]; - const gridMode = state.explorer.gridMode[ownProps.containerID]; - const searchQuery = state.explorer.searchQueries[ownProps.containerID]; - const loadImages = state.explorer.loadImages[ownProps.containerID]; + const {currentPaths, visibilityFilters, gridMode, searchQueries, loadImages, sortFilters, sortFiltersAscending} = state.explorer; + const {containerID} = ownProps; + const currentPath = currentPaths[containerID]; + const visibilityFilter = visibilityFilters[containerID]; + const mgridMode = gridMode[containerID]; + const searchQuery = searchQueries[containerID]; + const mloadImages = loadImages[containerID]; + const sortFilter = sortFilters[containerID]; + const sortFilterAscending = sortFiltersAscending[containerID]; let fsInfo = {}; const {remoteName, remotePath} = currentPath; @@ -481,7 +497,6 @@ const mapStateToProps = (state, ownProps) => { const tempRemoteName = remoteName.split(':')[0]; if (state.remote.configs[tempRemoteName]) - fsInfo = state.remote.configs[tempRemoteName]; } @@ -492,29 +507,36 @@ const mapStateToProps = (state, ownProps) => { if (files) { files = files.files; // Filter according to visibility filters - if (visibilityFilter) { + if (visibilityFilter && visibilityFilter !== "") { files = changeListVisibility(files, visibilityFilter); } - //Filter according to search query, if ny + //Filter according to search query, if any if (searchQuery) { files = changeSearchFilter(files, searchQuery); } + files.sort(getSortCompareFunction(sortFilter, sortFilterAscending)); + } + // Sort the files + + return { files, currentPath, fsInfo, - gridMode, + gridMode: mgridMode, searchQuery, - loadImages + loadImages: mloadImages, + sortFilter, + sortFilterAscending } }; export default compose( connect( - mapStateToProps, {getFiles, navigateUp, changePath} + mapStateToProps, {getFiles, navigateUp, changePath, changeSortFilter} ), DropTarget(ItemTypes.FILECOMPONENT, filesTarget, collect) )(FilesView) diff --git a/src/views/Explorer/RemoteExplorer/RemoteExplorer.js b/src/views/Explorer/RemoteExplorer/RemoteExplorer.js index 1fb2d6dcf..f45d1dde7 100644 --- a/src/views/Explorer/RemoteExplorer/RemoteExplorer.js +++ b/src/views/Explorer/RemoteExplorer/RemoteExplorer.js @@ -1,106 +1,107 @@ import React from 'react'; -import {Card, CardBody, CardHeader} from "reactstrap"; -import RemotesList from "../RemotesList"; +import {Card, CardBody, Container} from "reactstrap"; import FilesView from "../FilesView/FilesView"; import {addColonAtLast} from "../../../utils/Tools"; import {connect} from "react-redux"; import * as PropTypes from 'prop-types'; import { - changePath, - changeRemoteName, - changeRemotePath, - createPath, - navigateBack, - navigateFwd, - navigateUp + changePath, + changeRemoteName, + changeRemotePath, + createPath, + navigateBack, + navigateFwd, + navigateUp } from "../../../actions/explorerStateActions"; import FileOperations from "../../Base/FileOperations/FileOperations"; import {PROP_CURRENT_PATH, PROP_FS_INFO} from "../../../utils/RclonePropTypes"; import ErrorBoundary from "../../../ErrorHandling/ErrorBoundary"; +import RemotesList from "../RemotesList"; class RemoteExplorer extends React.Component { - constructor(props) { - super(props); - this.state = { - remoteNameTemp: "" - }; - - this.updateRemoteName = this.updateRemoteName.bind(this); - this.updateRemotePath = this.updateRemotePath.bind(this); - } - - - updateRemoteName(remoteName) { - this.setState({remoteNameTemp: remoteName}); - } - - updateRemotePath(newRemotePath, IsDir, IsBucket) { - const {remoteName} = this.props.currentPath; - - let updateRemoteName = ""; - let updateRemotePath = ""; - - if (IsBucket) { - updateRemoteName = addColonAtLast(remoteName) + newRemotePath; - updateRemotePath = ""; - - } else if (IsDir) { - updateRemoteName = remoteName; - updateRemotePath = newRemotePath; - } - this.props.changePath(this.props.containerID, updateRemoteName, updateRemotePath); - } - - render() { - - - const {remoteName} = this.props.currentPath; - const {containerID, distractionFreeMode} = this.props; - - const isValidPath = remoteName && remoteName !== ""; - - return ( - - {/*Render remotes array*/} - {(!distractionFreeMode) && - - Remotes - - - - - } - - {/*Render the files in the selected remote*/} - {isValidPath && - - - - - - - } - - - ); - - } + constructor(props) { + super(props); + this.state = { + remoteNameTemp: "" + }; + + this.updateRemoteName = this.updateRemoteName.bind(this); + this.updateRemotePath = this.updateRemotePath.bind(this); + } + + + updateRemoteName(remoteName) { + this.setState({remoteNameTemp: remoteName}); + } + + updateRemotePath(newRemotePath, IsDir, IsBucket) { + const {remoteName} = this.props.currentPath; + + let updateRemoteName = ""; + let updateRemotePath = ""; + + if (IsBucket) { + updateRemoteName = addColonAtLast(remoteName) + newRemotePath; + updateRemotePath = ""; + + } else if (IsDir) { + updateRemoteName = remoteName; + updateRemotePath = newRemotePath; + } + this.props.changePath(this.props.containerID, updateRemoteName, updateRemotePath); + } + + render() { + + + const {remoteName} = this.props.currentPath; + const {containerID, className} = this.props; + + const isValidPath = remoteName && remoteName !== ""; + + return ( + + + + + + + {isValidPath ? ( + <> + + + + ) : ( + + )} + + + + + + + + ); + + + } } const propTypes = { - containerID: PropTypes.string.isRequired, - createPath: PropTypes.func.isRequired, - currentPath: PROP_CURRENT_PATH, - fsInfo: PROP_FS_INFO, - hasError: PropTypes.bool, - distractionFreeMode: PropTypes.bool.isRequired + containerID: PropTypes.string.isRequired, + createPath: PropTypes.func.isRequired, + currentPath: PROP_CURRENT_PATH, + fsInfo: PROP_FS_INFO, + hasError: PropTypes.bool, + distractionFreeMode: PropTypes.bool.isRequired, + className: PropTypes.string }; @@ -108,35 +109,36 @@ const defaultProps = {}; const mapStateToProps = (state, ownProps) => { - const currentPath = state.explorer.currentPaths[ownProps.containerID]; - let fsInfo = {}; + const currentPath = state.explorer.currentPaths[ownProps.containerID]; + let fsInfo = {}; + + const {remoteName} = currentPath; - const {remoteName} = currentPath; + if (currentPath && state.remote.configs) { - if (currentPath && state.remote.configs) { + const tempRemoteName = remoteName.split(':')[0]; + if (state.remote.configs[tempRemoteName]) - const tempRemoteName = remoteName.split(':')[0]; - if (state.remote.configs[tempRemoteName]) + fsInfo = state.remote.configs[tempRemoteName]; + } + return { + configs: state.remote.configs, + hasError: state.remote.hasError, + error: state.remote.error, + currentPath, + fsInfo, + } - fsInfo = state.remote.configs[tempRemoteName]; - } - return { - configs: state.remote.configs, - hasError: state.remote.hasError, - error: state.remote.error, - currentPath: state.explorer.currentPaths[ownProps.containerID], - fsInfo - } }; RemoteExplorer.propTypes = propTypes; RemoteExplorer.defaultProps = defaultProps; export default connect( - mapStateToProps, - { - createPath, changePath, - changeRemoteName, changeRemotePath, navigateUp, - navigateBack, navigateFwd - } + mapStateToProps, + { + createPath, changePath, + changeRemoteName, changeRemotePath, navigateUp, + navigateBack, navigateFwd + } )(RemoteExplorer); diff --git a/src/views/Explorer/RemoteExplorer/__snapshots__/RemoteExplorer.test.js.snap b/src/views/Explorer/RemoteExplorer/__snapshots__/RemoteExplorer.test.js.snap index ad8145ca8..b00117007 100644 --- a/src/views/Explorer/RemoteExplorer/__snapshots__/RemoteExplorer.test.js.snap +++ b/src/views/Explorer/RemoteExplorer/__snapshots__/RemoteExplorer.test.js.snap @@ -5,36 +5,20 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` - - Remotes - - - - - - - - - - + + + + diff --git a/src/views/Explorer/RemoteExplorerLayout/RemoteExplorerLayout.js b/src/views/Explorer/RemoteExplorerLayout/RemoteExplorerLayout.js index d4376df51..866021ecc 100644 --- a/src/views/Explorer/RemoteExplorerLayout/RemoteExplorerLayout.js +++ b/src/views/Explorer/RemoteExplorerLayout/RemoteExplorerLayout.js @@ -1,5 +1,5 @@ import React from "react"; -import {Button, Card, CardBody, CardHeader, Col, Row} from "reactstrap"; +import {Button, Col, Row} from "reactstrap"; import RemoteExplorer from "../RemoteExplorer"; import HTML5Backend from "react-dnd-html5-backend"; @@ -11,35 +11,20 @@ import * as PropTypes from 'prop-types'; import {changeDistractionFreeMode, changeNumCols} from "../../../actions/explorerActions"; import ErrorBoundary from "../../../ErrorHandling/ErrorBoundary"; +import singlePaneImg from '../../../assets/img/single-pane.png'; +import doublePaneImg from '../../../assets/img/double-pane1.png'; +import triplePaneImg from '../../../assets/img/triple-pane.png'; +import TabsLayout from "../TabsLayout/TabsLayout"; -function RemoteExplorerList({cols, distractionFreeMode}) { - let remoteExplorers = []; - const lgSize = 12 / cols; - for (let i = 0; i < cols; i++) { - - remoteExplorers.push(( - - - - - )); - } - return remoteExplorers; -} class RemoteExplorerLayout extends React.Component { - constructor(props) { - super(props); - this.changeLayout = this.changeLayout.bind(this); - } - changeLayout = (nos, mode) => { const {changeNumCols} = this.props; - // console.log("changing layout"); - if (mode === "side" && nos !== changeNumCols) { - changeNumCols(nos); + // Check if the current layout is not same as previous + if(nos !== changeNumCols){ + changeNumCols(nos, mode); } }; @@ -48,24 +33,55 @@ class RemoteExplorerLayout extends React.Component { const {numCols, changeNumCols} = this.props; if (numCols < 1) { - changeNumCols(1); + changeNumCols(1, "horizontal"); } } - toggleDistractionFreeMode = (e) => { - const {distractionFreeMode, changeDistractionFreeMode} = this.props; - // this.setState((prevState) => ({ - // distractionFreeMode: !prevState.distractionFreeMode - // })); - changeDistractionFreeMode(!distractionFreeMode); - + toggleDistractionFreeMode = (_) => { + const {distractionFreeMode, changeDistractionFreeMode} = this.props; + // this.setState((prevState) => ({ + // distractionFreeMode: !prevState.distractionFreeMode + // })); + changeDistractionFreeMode(!distractionFreeMode); + + }; + + Panes = ({numCols, activeRemoteContainerID, distractionFreeMode, containers}) => { + let returnData = []; + const lgSize = 12 / numCols; + for (let pane = 0; pane < numCols; pane++) { + returnData.push(( + + + + + + + { + containers.map(({ID, paneID}) => { + if (paneID === pane) { + return () + } else { + return null; + } + }) + } + {/*{activeRemoteContainerID && activeRemoteContainerID[pane] && activeRemoteContainerID[pane] !== "" &&*/} + {/*}*/} + + + )) + } + return returnData; }; render() { /*Divide the 12 bootstrap columns to fit number of explorers*/ - const {numCols, backStacks, distractionFreeMode} = this.props; - + const {numCols, distractionFreeMode, activeRemoteContainerID, containers} = this.props; return ( @@ -76,46 +92,43 @@ class RemoteExplorerLayout extends React.Component { } {(!distractionFreeMode) && - - - - - Choose Layout - - - - - - - - {/**/} - - + + + + + Choose Layout: {" "} + + + + + + + + {/**/} + } - - + - - ); } @@ -124,7 +137,10 @@ class RemoteExplorerLayout extends React.Component { const mapStateToProps = (state) => ({ backStacks: state.explorer.backStacks, numCols: state.remote.numCols, - distractionFreeMode: state.remote.distractionFreeMode + distractionFreeMode: state.remote.distractionFreeMode, + splitMode: state.remote.splitMode, + activeRemoteContainerID: state.remote.activeRemoteContainerID, + containers: state.remote.containers, }); RemoteExplorerLayout.propTypes = { @@ -137,4 +153,4 @@ RemoteExplorerLayout.propTypes = { export default compose( connect(mapStateToProps, {createPath, changeNumCols, changeDistractionFreeMode}), DragDropContext(HTML5Backend) -)(RemoteExplorerLayout); \ No newline at end of file +)(RemoteExplorerLayout); diff --git a/src/views/Explorer/RemoteExplorerLayout/__snapshots__/RemoteExplorerLayout.test.js.snap b/src/views/Explorer/RemoteExplorerLayout/__snapshots__/RemoteExplorerLayout.test.js.snap index 8916ab654..e63a87322 100644 --- a/src/views/Explorer/RemoteExplorerLayout/__snapshots__/RemoteExplorerLayout.test.js.snap +++ b/src/views/Explorer/RemoteExplorerLayout/__snapshots__/RemoteExplorerLayout.test.js.snap @@ -6,6 +6,7 @@ exports[`Remote Explorer Layout renders should match snapshot 1`] = ` data-test="remoteExplorerLayout" > - - - Choose Layout - - - - - - - - - + Choose Layout: + + + + + + `; diff --git a/src/views/Explorer/RemotesList/RemoteListAutoSuggest.js b/src/views/Explorer/RemotesList/RemoteListAutoSuggest.js index 5236910df..6fd18a61a 100644 --- a/src/views/Explorer/RemotesList/RemoteListAutoSuggest.js +++ b/src/views/Explorer/RemotesList/RemoteListAutoSuggest.js @@ -81,6 +81,7 @@ class RemoteListAutoSuggest extends React.Component { renderSuggestion={renderSuggestion} highlightFirstSuggestion={true} inputProps={inputProps} + style={{width:"100%"}} /> ); } diff --git a/src/views/Explorer/RemotesList/RemotesList.js b/src/views/Explorer/RemotesList/RemotesList.js index 6a20558b1..2971b4be1 100644 --- a/src/views/Explorer/RemotesList/RemotesList.js +++ b/src/views/Explorer/RemotesList/RemotesList.js @@ -4,7 +4,7 @@ import {connect} from "react-redux"; import {getFsInfo, getRemoteNames} from "../../../actions/explorerActions"; import PropTypes from 'prop-types' import {changeRemoteName} from "../../../actions/explorerStateActions"; -import {Button, Col, Form, Row} from "reactstrap"; +import {Button, Col, Form} from "reactstrap"; import {PROP_CURRENT_PATH} from "../../../utils/RclonePropTypes"; class RemotesList extends React.Component { @@ -72,19 +72,18 @@ class RemotesList extends React.Component { } else { return ( -
- - -
- - - - - - - - + + + + + + + + + + + ); @@ -104,9 +103,7 @@ const propTypes = { error: PropTypes.object, hasError: PropTypes.bool, containerID: PropTypes.string.isRequired, - currentPath: PROP_CURRENT_PATH - }; diff --git a/src/views/Explorer/TabsLayout/TabsLayout.js b/src/views/Explorer/TabsLayout/TabsLayout.js new file mode 100644 index 000000000..7dd103ffc --- /dev/null +++ b/src/views/Explorer/TabsLayout/TabsLayout.js @@ -0,0 +1,61 @@ +import React from 'react'; +import {Button, Nav, NavItem, NavLink} from "reactstrap"; +import {connect} from "react-redux"; +import {addRemoteContainer, changeActiveRemoteContainer, removeRemoteContainer} from "../../../actions/explorerActions"; + +function TabsLayout(props) { + const {addRemoteContainer, removeRemoteContainer, changeActiveRemoteContainer, activeRemoteContainerID} = props; + const {containers, currentPaths, paneID: currentPaneID} = props; + const activeContainerIDInPane = activeRemoteContainerID ? activeRemoteContainerID[currentPaneID] : ""; + return () +} + +const mapStateToProps = (state, _) => { + return { + containers: state.remote.containers, + numContainers: state.remote.numContainers, + currentPaths: state.explorer.currentPaths, + activeRemoteContainerID: state.remote.activeRemoteContainerID + }; +}; + + +export default connect( + mapStateToProps, + { + addRemoteContainer, removeRemoteContainer, changeActiveRemoteContainer + } +)(TabsLayout); + diff --git a/src/views/Home/Home.js b/src/views/Home/Home.js index 2c9fe0527..528caabab 100644 --- a/src/views/Home/Home.js +++ b/src/views/Home/Home.js @@ -15,15 +15,11 @@ class Home extends React.Component { const ipAddress = localStorage.getItem(IP_ADDRESS_KEY); return (
-

Welcome to Rclone dashboard.

-

Begin by creating a new remote config from the left sidebar.

- - -
+ - + diff --git a/src/views/Home/__snapshots__/Home.test.js.snap b/src/views/Home/__snapshots__/Home.test.js.snap index b7e851dc3..9ed89f648 100644 --- a/src/views/Home/__snapshots__/Home.test.js.snap +++ b/src/views/Home/__snapshots__/Home.test.js.snap @@ -4,12 +4,6 @@ exports[`Home Component renders should match snapshot 1`] = `
-

- Welcome to Rclone dashboard. -

-

- Begin by creating a new remote config from the left sidebar. -

{ this.setState({ username: e.target.value, - connectionSuccess: false }); }; changePassword = e => { this.setState({ password: e.target.value, - connectionSuccess: false }) }; @@ -79,7 +77,6 @@ class Login extends Component { this.setState({ ipAddress: e.target.value, - connectionSuccess: false }); }; @@ -89,59 +86,69 @@ class Login extends Component { onSubmit = e => { - if (e) - e.preventDefault(); - - const {ipAddress, username, password} = this.state; - - Promise.all([ - changeUserNamePassword(username, password), - changeIPAddress(ipAddress) - ]).then(() => { - this.redirectToDashboard() - }); - - }; - - checkConnection = (e) => { - e.preventDefault(); - - // Set the localStorage parameters temporarily. - const {ipAddress, username, password} = this.state; - const {changeUserNamePassword, changeIPAddress} = this.props; - - Promise.all([ - changeUserNamePassword(username, password), - changeIPAddress(ipAddress) - ]).then(() => { - axiosInstance.post(urls.noopAuth).then((data) => { - console.log("Connection successful."); - this.setState({ - connectionSuccess: true, - error: "" - }) - }, (error) => { - console.log(error); - this.setState({ - connectionSuccess: false, - error: "Error connecting. Please check username password and verify if rclone is working at the specified IP." - }) - }) - }) - - - }; - - componentDidMount() { - localStorage.clear(); - this.props.signOut(); - - - let url_string = window.location.href; - let url = new URL(url_string); - let loginToken = url.searchParams.get("login_token"); - let ipAddress = this.state.ipAddress; - if (url.searchParams.get("ip_address")) { + if (e) + e.preventDefault(); + + const {ipAddress, username, password} = this.state; + const {changeUserNamePassword, changeIPAddress} = this.props; + + + Promise.all([ + changeUserNamePassword(username, password), + changeIPAddress(ipAddress) + ]).then(() => { + axiosInstance.post(urls.noopAuth).then((data) => { + console.log("Connection successful."); + this.redirectToDashboard(); + }, (error) => { + console.log(error); + this.setState({ + error: "Error connecting. Please check username password and verify if rclone is working at the specified IP." + }) + }) + + }); + + }; + + // checkConnection = (e) => { + // e.preventDefault(); + // + // // Set the localStorage parameters temporarily. + // const {ipAddress, username, password} = this.state; + // const {changeUserNamePassword, changeIPAddress} = this.props; + // + // Promise.all([ + // changeUserNamePassword(username, password), + // changeIPAddress(ipAddress) + // ]).then(() => { + // axiosInstance.post(urls.noopAuth).then((data) => { + // console.log("Connection successful."); + // this.setState({ + // connectionSuccess: true, + // error: "" + // }) + // }, (error) => { + // console.log(error); + // this.setState({ + // connectionSuccess: false, + // error: "Error connecting. Please check username password and verify if rclone is working at the specified IP." + // }) + // }) + // }) + // + // + // }; + + componentDidMount() { + localStorage.clear(); + this.props.signOut(); + + let url_string = window.location.href; + let url = new URL(url_string); + let loginToken = url.searchParams.get("login_token"); + let ipAddress = this.state.ipAddress; + if (url.searchParams.get("ip_address")) { ipAddress = url.searchParams.get("ip_address"); } // console.log(loginToken); @@ -158,8 +165,9 @@ class Login extends Component { } + render() { - const {username, password, ipAddress, connectionSuccess, error} = this.state; + const {username, password, ipAddress, error} = this.state; return (
@@ -169,27 +177,25 @@ class Login extends Component { -
-

Login

-

Sign In to your account

- {error && } - {connectionSuccess && } - - - - - - - + +

Login

+

Sign In to your account

+ {error && } + + + + + + + - + - +
- - - - + - + -
-
+ RClone logo
diff --git a/src/views/Pages/Login/__snapshots__/Login.test.js.snap b/src/views/Pages/Login/__snapshots__/Login.test.js.snap index f5b8be83c..fbc9941de 100644 --- a/src/views/Pages/Login/__snapshots__/Login.test.js.snap +++ b/src/views/Pages/Login/__snapshots__/Login.test.js.snap @@ -160,42 +160,18 @@ exports[`Login Layout renders should match snapshot 1`] = ` className="px-4" color="primary" data-testid="LoginForm-BtnLogin" - disabled={true} tag="button" + type="submit" > Login -
- - -
+ RClone logo diff --git a/src/views/RemoteManagement/NewDrive/NewDrive.js b/src/views/RemoteManagement/NewDrive/NewDrive.js index 8b2430afe..6be562b7b 100644 --- a/src/views/RemoteManagement/NewDrive/NewDrive.js +++ b/src/views/RemoteManagement/NewDrive/NewDrive.js @@ -1,18 +1,5 @@ import React from 'react'; -import { - Button, - Card, - CardBody, - CardFooter, - CardHeader, - Col, - Collapse, - Form, - FormFeedback, - FormGroup, - Input, - Label -} from "reactstrap"; +import {Button, Card, CardBody, Col, Collapse, Container, FormFeedback, FormGroup, Input, Label, Row} from "reactstrap"; // import {config} from "./config.js"; import NewDriveAuthModal from "../../Base/NewDriveAuthModal"; import axiosInstance from "../../../utils/API/API"; @@ -192,6 +179,8 @@ class NewDrive extends React.Component { optionTypes: {}, isValid: {}, + currentStepNumber: 1 + }; this.configCheckInterval = null; // console.log("Params", this.props.match.params); @@ -428,7 +417,7 @@ class NewDrive extends React.Component { * Validate form and submit request. * */ async handleSubmit(e) { - e.preventDefault(); + e && e.preventDefault(); // console.log("Submitted form"); const {formValues, drivePrefix} = this.state; @@ -503,13 +492,13 @@ class NewDrive extends React.Component { } } } else { - if (!this.state.colSetup) { - this.openSetupDrive(); - } + // if (!this.state.colSetup) { + // this.openSetupDrive(); + // } - if (this.state.advancedOptions && !this.state.colAdvanced) { - this.openAdvancedSettings(); - } + // if (this.state.advancedOptions && !this.state.colAdvanced) { + // this.openAdvancedSettings(); + // } toast.warn(`Check for errors before submitting.`, { autoClose: false }); @@ -530,7 +519,6 @@ class NewDrive extends React.Component { * */ changeName = e => { const {driveNameIsEditable} = this.state; - console.log("changeName"); if (driveNameIsEditable) { const value = e.target.value; @@ -610,113 +598,194 @@ class NewDrive extends React.Component { this.configCheckInterval = null; } + gotoNextStep = () => { + const {currentStepNumber, advancedOptions} = this.state; + if((advancedOptions && currentStepNumber === 3) || (!advancedOptions && currentStepNumber === 2)){ + this.handleSubmit(null); + }else{ + this.setCurrentStep(currentStepNumber + 1); + } + } + + gotoPrevStep = () => { + const {currentStepNumber} = this.state; + this.setCurrentStep(currentStepNumber - 1); + } + + + setCurrentStep = (stepNo) => { + this.setState({currentStepNumber: stepNo}); + // const {currentStepNumber, advancedOptions} = this.state; + // if((advancedOptions && currentStepNumber === 3) || (!advancedOptions && currentStepNumber === 2)){ + // this.handleSubmit(null); + // }else{ + // this.setState({currentStepNumber: stepNo}); + // } + + } + + StepShowCase = ({currentStepNumber}) => { + const buttonActiveClassName = "step-active"; + const stepTitles = [ + "Set up Remote Config", + "Set up Drive", + "Advanced Config" + ]; + + return ( + + + {stepTitles.map((item, idx) => { + idx += 1; + return ( + +
+ +

{item}

+ + {idx !== stepTitles.length &&
+
+ + } + + ) + })} + + + + ) + + } + + /* return ( +
+ + +
+

Shift Created

+
+
+
+
  • +
    +

    Shift Created

    +
    +
  • +
  • +
    +

    Shift Created

    +
    +
  • +
    + ) */ + + + render() { - const {colRconfig, colSetup, colAdvanced, drivePrefix, advancedOptions, driveName, driveNameIsValid} = this.state; + const {drivePrefix, advancedOptions, driveName, driveNameIsValid, currentStepNumber} = this.state; const {providers} = this.props; - // console.log("config", config); return (

    This 3 step process will guide you through creating a new config. For auto config, leave the - parameters as is.

    -
    + parameters as it is.

    + + - -
    - -
    - -
    - - - - - - - -
    - - - - - - {' '} - Rclone Config - - - - - -
    - -
    -
    - - - - {/*div for Scrolling to here*/} -
    this.setupDriveDiv = el}/> - -
    - -
    - -
    - - - - - - -
    -
    - Edit Advanced Options - - -
    + + + + + +
    + + + + + + {' '} + Rclone Config + + +
    +
    + + +
    - +
    + - - - - -
    - - -
    - -
    - - - - - - -
    -
    { + + + + {/*div for Scrolling to here*/} + {/*
    this.setupDriveDiv = el}/> */} + + + +
    +
    + Edit Advanced Options + + + + +
    +
    +
    + + + + + {/* +
    + + +
    + +
    */} + + + + + +
    +
    + Edit Advanced Options + + + +
    +
    +
    + + + +
    +
    + {/*
    { this.configEndDiv = el }}>
    @@ -724,8 +793,7 @@ class NewDrive extends React.Component {
    -
    - +
    */}
    ); diff --git a/src/views/RemoteManagement/NewDrive/__snapshots__/NewDrive.test.js.snap b/src/views/RemoteManagement/NewDrive/__snapshots__/NewDrive.test.js.snap index 5abd74a3f..61f1752fd 100644 --- a/src/views/RemoteManagement/NewDrive/__snapshots__/NewDrive.test.js.snap +++ b/src/views/RemoteManagement/NewDrive/__snapshots__/NewDrive.test.js.snap @@ -6,161 +6,138 @@ exports[`Remote Explorer renders should match snapshot 1`] = ` >

    - This 3 step process will guide you through creating a new config. For auto config, leave the parameters as is. + This 3 step process will guide you through creating a new config. For auto config, leave the parameters as it is.

    -
    + - -
    - -
    -
    - - + - - + Select + +
    - - - upload cutoff) files", - "Hide": 0, - "IsPassword": false, - "Name": "disable_checksum", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "bool", - "Value": null, - "ValueStr": "false", - }, - Object { - "Advanced": true, - "Default": "", - "DefaultStr": "", - "Help": "Custom endpoint for downloads. + "Hide": 0, + "IsPassword": false, + "Name": "chunk_size", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "SizeSuffix", + "Value": null, + "ValueStr": "96M", + }, + Object { + "Advanced": true, + "Default": false, + "DefaultStr": "false", + "Help": "Disable checksums for large (> upload cutoff) files", + "Hide": 0, + "IsPassword": false, + "Name": "disable_checksum", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "bool", + "Value": null, + "ValueStr": "false", + }, + Object { + "Advanced": true, + "Default": "", + "DefaultStr": "", + "Help": "Custom endpoint for downloads. This is usually set to a Cloudflare CDN URL as Backblaze offers free egress for data downloaded through the Cloudflare network. This is probably only useful for a public bucket. Leave blank if you want to use the endpoint provided by Backblaze.", - "Hide": 0, - "IsPassword": false, - "Name": "download_url", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "string", - "Value": null, - "ValueStr": "", - }, - Object { - "Advanced": true, - "Default": 604800000000000, - "DefaultStr": "1w", - "Help": "Time before the authorization token will expire in s or suffix ms|s|m|h|d. + "Hide": 0, + "IsPassword": false, + "Name": "download_url", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "string", + "Value": null, + "ValueStr": "", + }, + Object { + "Advanced": true, + "Default": 604800000000000, + "DefaultStr": "1w", + "Help": "Time before the authorization token will expire in s or suffix ms|s|m|h|d. The duration before the download authorization token will expire. The minimum value is 1 second. The maximum value is one week.", - "Hide": 0, - "IsPassword": false, - "Name": "download_auth_duration", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "Duration", - "Value": null, - "ValueStr": "1w", - }, - ], - "Prefix": "b2", - }, - ] - } - value="" - /> - - - + + + + - - - - - Rclone Config - - - - - + + + Rclone Config + + + +
    - - +
    + + + -
    - -
    - -
    -
    - - - upload cutoff) files", - "Hide": 0, - "IsPassword": false, - "Name": "disable_checksum", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "bool", - "Value": null, - "ValueStr": "false", - }, - Object { - "Advanced": true, - "Default": "", - "DefaultStr": "", - "Help": "Custom endpoint for downloads. + "Hide": 0, + "IsPassword": false, + "Name": "chunk_size", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "SizeSuffix", + "Value": null, + "ValueStr": "96M", + }, + Object { + "Advanced": true, + "Default": false, + "DefaultStr": "false", + "Help": "Disable checksums for large (> upload cutoff) files", + "Hide": 0, + "IsPassword": false, + "Name": "disable_checksum", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "bool", + "Value": null, + "ValueStr": "false", + }, + Object { + "Advanced": true, + "Default": "", + "DefaultStr": "", + "Help": "Custom endpoint for downloads. This is usually set to a Cloudflare CDN URL as Backblaze offers free egress for data downloaded through the Cloudflare network. This is probably only useful for a public bucket. Leave blank if you want to use the endpoint provided by Backblaze.", - "Hide": 0, - "IsPassword": false, - "Name": "download_url", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "string", - "Value": null, - "ValueStr": "", - }, - Object { - "Advanced": true, - "Default": 604800000000000, - "DefaultStr": "1w", - "Help": "Time before the authorization token will expire in s or suffix ms|s|m|h|d. + "Hide": 0, + "IsPassword": false, + "Name": "download_url", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "string", + "Value": null, + "ValueStr": "", + }, + Object { + "Advanced": true, + "Default": 604800000000000, + "DefaultStr": "1w", + "Help": "Time before the authorization token will expire in s or suffix ms|s|m|h|d. The duration before the download authorization token will expire. The minimum value is 1 second. The maximum value is one week.", - "Hide": 0, - "IsPassword": false, - "Name": "download_auth_duration", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "Duration", - "Value": null, - "ValueStr": "1w", - }, - ], - "Prefix": "b2", - }, - ] + "Hide": 0, + "IsPassword": false, + "Name": "download_auth_duration", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "Duration", + "Value": null, + "ValueStr": "1w", + }, + ], + "Prefix": "b2", + }, + ] + } + currentValues={Object {}} + drivePrefix="" + errorsMap={ + Object { + "driveName": "", } - currentValues={Object {}} - drivePrefix="" - errorsMap={ - Object { - "driveName": "", - } - } - isValidMap={Object {}} - loadAdvanced={false} - /> - - +
    -
    + + Edit Advanced Options + + -
    + Go back + +
    - - +
    + +
    + - -
    - -
    -
    - - - upload cutoff) files", - "Hide": 0, - "IsPassword": false, - "Name": "disable_checksum", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "bool", - "Value": null, - "ValueStr": "false", - }, - Object { - "Advanced": true, - "Default": "", - "DefaultStr": "", - "Help": "Custom endpoint for downloads. + "Hide": 0, + "IsPassword": false, + "Name": "chunk_size", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "SizeSuffix", + "Value": null, + "ValueStr": "96M", + }, + Object { + "Advanced": true, + "Default": false, + "DefaultStr": "false", + "Help": "Disable checksums for large (> upload cutoff) files", + "Hide": 0, + "IsPassword": false, + "Name": "disable_checksum", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "bool", + "Value": null, + "ValueStr": "false", + }, + Object { + "Advanced": true, + "Default": "", + "DefaultStr": "", + "Help": "Custom endpoint for downloads. This is usually set to a Cloudflare CDN URL as Backblaze offers free egress for data downloaded through the Cloudflare network. This is probably only useful for a public bucket. Leave blank if you want to use the endpoint provided by Backblaze.", - "Hide": 0, - "IsPassword": false, - "Name": "download_url", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "string", - "Value": null, - "ValueStr": "", - }, - Object { - "Advanced": true, - "Default": 604800000000000, - "DefaultStr": "1w", - "Help": "Time before the authorization token will expire in s or suffix ms|s|m|h|d. + "Hide": 0, + "IsPassword": false, + "Name": "download_url", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "string", + "Value": null, + "ValueStr": "", + }, + Object { + "Advanced": true, + "Default": 604800000000000, + "DefaultStr": "1w", + "Help": "Time before the authorization token will expire in s or suffix ms|s|m|h|d. The duration before the download authorization token will expire. The minimum value is 1 second. The maximum value is one week.", - "Hide": 0, - "IsPassword": false, - "Name": "download_auth_duration", - "NoPrefix": false, - "Provider": "", - "Required": false, - "ShortOpt": "", - "Type": "Duration", - "Value": null, - "ValueStr": "1w", - }, - ], - "Prefix": "b2", - }, - ] - } - currentValues={Object {}} - drivePrefix="" - errorsMap={ - Object { - "driveName": "", - } + "Hide": 0, + "IsPassword": false, + "Name": "download_auth_duration", + "NoPrefix": false, + "Provider": "", + "Required": false, + "ShortOpt": "", + "Type": "Duration", + "Value": null, + "ValueStr": "1w", + }, + ], + "Prefix": "b2", + }, + ] + } + currentValues={Object {}} + drivePrefix="" + errorsMap={ + Object { + "driveName": "", } - isValidMap={Object {}} - loadAdvanced={true} - /> - - -
    -
    -
    - - -
    -
    - +
    + + + Edit Advanced Options + + + +
    +
    + +
    +
    {type}
    - ); } diff --git a/src/views/RemoteManagement/ShowConfig/ShowConfig.js b/src/views/RemoteManagement/ShowConfig/ShowConfig.js index 8d6d05d3d..1ae8a358c 100644 --- a/src/views/RemoteManagement/ShowConfig/ShowConfig.js +++ b/src/views/RemoteManagement/ShowConfig/ShowConfig.js @@ -32,27 +32,28 @@ class ShowConfig extends React.PureComponent { return (
    -
    - - + + + -
    xNameSizeModified this.applySortFilter("name")}>Name {sortFilter === "name" && + } this.applySortFilter("size")}>Size {sortFilter === "size" && + } this.applySortFilter("modified")}>Modified {sortFilter === "modified" && + } Actions
    Directories
    - Files - - + Files
    Files
    - + +
    +
    - - + - +
    No. Name TypeUpdateDeleteActions
    diff --git a/src/views/RemoteManagement/ShowConfig/__snapshots__/ConfigRow.test.js.snap b/src/views/RemoteManagement/ShowConfig/__snapshots__/ConfigRow.test.js.snap index 4638d20d7..37eeb3d16 100644 --- a/src/views/RemoteManagement/ShowConfig/__snapshots__/ConfigRow.test.js.snap +++ b/src/views/RemoteManagement/ShowConfig/__snapshots__/ConfigRow.test.js.snap @@ -17,15 +17,13 @@ exports[`Config Row renders should match snapshot 1`] = ` - - + - - + /> -
    - Update - - Delete + Actions