Refactor download object and bulk action components (#5546)

This commit is contained in:
Kanagaraj M 2018-02-21 07:30:43 +05:30 committed by Harshavardhana
parent da4558a8f7
commit 6a42727e00
14 changed files with 604 additions and 24 deletions

View File

@ -21,9 +21,11 @@ import ObjectsSection from "../objects/ObjectsSection"
import MainActions from "./MainActions" import MainActions from "./MainActions"
import MakeBucketModal from "../buckets/MakeBucketModal" import MakeBucketModal from "../buckets/MakeBucketModal"
import UploadModal from "../uploads/UploadModal" import UploadModal from "../uploads/UploadModal"
import ObjectsBulkActions from "../objects/ObjectsBulkActions"
export const MainContent = () => ( export const MainContent = () => (
<div className="fe-body"> <div className="fe-body">
<ObjectsBulkActions />
<MobileHeader /> <MobileHeader />
<Header /> <Header />
<ObjectsSection /> <ObjectsSection />

View File

@ -15,22 +15,41 @@
*/ */
import React from "react" import React from "react"
import { connect } from "react-redux"
import humanize from "humanize" import humanize from "humanize"
import Moment from "moment" import Moment from "moment"
import ObjectItem from "./ObjectItem" import ObjectItem from "./ObjectItem"
import ObjectActions from "./ObjectActions" import ObjectActions from "./ObjectActions"
import * as actionsObjects from "./actions" import * as actionsObjects from "./actions"
import { getCheckedList } from "./selectors"
export const ObjectContainer = ({ object }) => { export const ObjectContainer = ({
const actionButtons = <ObjectActions object={object} /> object,
const props = { checkedObjectsCount,
downloadObject
}) => {
let props = {
name: object.name, name: object.name,
contentType: object.contentType, contentType: object.contentType,
size: humanize.filesize(object.size), size: humanize.filesize(object.size),
lastModified: Moment(object.lastModified).format("lll"), lastModified: Moment(object.lastModified).format("lll")
actionButtons: actionButtons
} }
return <ObjectItem {...props} /> if (checkedObjectsCount == 0) {
props.actionButtons = <ObjectActions object={object} />
}
return <ObjectItem {...props} onClick={() => downloadObject(object.name)} />
} }
export default ObjectContainer const mapStateToProps = state => {
return {
checkedObjectsCount: getCheckedList(state).length
}
}
const mapDispatchToProps = dispatch => {
return {
downloadObject: object => dispatch(actionsObjects.downloadObject(object))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ObjectContainer)

View File

@ -19,12 +19,17 @@ import { connect } from "react-redux"
import humanize from "humanize" import humanize from "humanize"
import Moment from "moment" import Moment from "moment"
import { getDataType } from "../mime" import { getDataType } from "../mime"
import * as actions from "./actions"
import { getCheckedList } from "./selectors"
export const ObjectItem = ({ export const ObjectItem = ({
name, name,
contentType, contentType,
size, size,
lastModified, lastModified,
checked,
checkObject,
uncheckObject,
actionButtons, actionButtons,
onClick onClick
}) => { }) => {
@ -32,7 +37,14 @@ export const ObjectItem = ({
<div className={"fesl-row"} data-type={getDataType(name, contentType)}> <div className={"fesl-row"} data-type={getDataType(name, contentType)}>
<div className="fesl-item fesl-item-icon"> <div className="fesl-item fesl-item-icon">
<div className="fi-select"> <div className="fi-select">
<input type="checkbox" name={name} checked={false} /> <input
type="checkbox"
name={name}
checked={checked}
onChange={() => {
checked ? uncheckObject(name) : checkObject(name)
}}
/>
<i className="fis-icon" /> <i className="fis-icon" />
<i className="fis-helper" /> <i className="fis-helper" />
</div> </div>
@ -55,4 +67,17 @@ export const ObjectItem = ({
) )
} }
export default ObjectItem const mapStateToProps = (state, ownProps) => {
return {
checked: getCheckedList(state).indexOf(ownProps.name) >= 0
}
}
const mapDispatchToProps = dispatch => {
return {
checkObject: name => dispatch(actions.checkObject(name)),
uncheckObject: name => dispatch(actions.uncheckObject(name))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ObjectItem)

View File

@ -0,0 +1,101 @@
/*
* Minio Cloud Storage (C) 2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { connect } from "react-redux"
import classNames from "classnames"
import * as actions from "./actions"
import { getCheckedList } from "./selectors"
import DeleteObjectConfirmModal from "./DeleteObjectConfirmModal"
export class ObjectsBulkActions extends React.Component {
constructor(props) {
super(props)
this.state = {
showDeleteConfirmation: false
}
}
deleteChecked() {
const { deleteChecked } = this.props
deleteChecked()
this.hideDeleteConfirmModal()
}
hideDeleteConfirmModal() {
this.setState({
showDeleteConfirmation: false
})
}
render() {
const { checkedObjectsCount, downloadChecked, clearChecked } = this.props
return (
<div
className={
"list-actions" +
classNames({
" list-actions-toggled": checkedObjectsCount > 0
})
}
>
<span className="la-label">
<i className="fa fa-check-circle" /> {checkedObjectsCount} Objects
selected
</span>
<span className="la-actions pull-right">
<button id="download-checked" onClick={downloadChecked}>
{" "}
Download all as zip{" "}
</button>
</span>
<span className="la-actions pull-right">
<button
id="delete-checked"
onClick={() => this.setState({ showDeleteConfirmation: true })}
>
{" "}
Delete selected{" "}
</button>
</span>
<i
className="la-close fa fa-times"
id="close-bulk-actions"
onClick={clearChecked}
/>
{this.state.showDeleteConfirmation && (
<DeleteObjectConfirmModal
deleteObject={this.deleteChecked.bind(this)}
hideDeleteConfirmModal={this.hideDeleteConfirmModal.bind(this)}
/>
)}
</div>
)
}
}
const mapStateToProps = state => {
return {
checkedObjectsCount: getCheckedList(state).length
}
}
const mapDispatchToProps = dispatch => {
return {
downloadChecked: () => dispatch(actions.downloadCheckedObjects()),
clearChecked: () => dispatch(actions.resetCheckedList()),
deleteChecked: () => dispatch(actions.deleteCheckedObjects())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ObjectsBulkActions)

View File

@ -25,7 +25,25 @@ describe("ObjectContainer", () => {
it("should render ObjectItem with props", () => { it("should render ObjectItem with props", () => {
const wrapper = shallow(<ObjectContainer object={{ name: "test1.jpg" }} />) const wrapper = shallow(<ObjectContainer object={{ name: "test1.jpg" }} />)
expect(wrapper.find("ObjectItem").length).toBe(1) expect(wrapper.find("Connect(ObjectItem)").length).toBe(1)
expect(wrapper.find("ObjectItem").prop("name")).toBe("test1.jpg") expect(wrapper.find("Connect(ObjectItem)").prop("name")).toBe("test1.jpg")
})
it("should pass actions to ObjectItem", () => {
const wrapper = shallow(
<ObjectContainer object={{ name: "test1.jpg" }} checkedObjectsCount={0} />
)
expect(wrapper.find("Connect(ObjectItem)").prop("actionButtons")).not.toBe(
undefined
)
})
it("should pass empty actions to ObjectItem when checkedObjectCount is more than 0", () => {
const wrapper = shallow(
<ObjectContainer object={{ name: "test1.jpg" }} checkedObjectsCount={1} />
)
expect(wrapper.find("Connect(ObjectItem)").prop("actionButtons")).toBe(
undefined
)
}) })
}) })

View File

@ -34,4 +34,27 @@ describe("ObjectItem", () => {
wrapper.find("a").simulate("click", { preventDefault: jest.fn() }) wrapper.find("a").simulate("click", { preventDefault: jest.fn() })
expect(onClick).toHaveBeenCalled() expect(onClick).toHaveBeenCalled()
}) })
it("should call checkObject when the object/prefix is checked", () => {
const checkObject = jest.fn()
const wrapper = shallow(
<ObjectItem name={"test"} checked={false} checkObject={checkObject} />
)
wrapper.find("input[type='checkbox']").simulate("change")
expect(checkObject).toHaveBeenCalledWith("test")
})
it("should render checked checkbox", () => {
const wrapper = shallow(<ObjectItem name={"test"} checked={true} />)
expect(wrapper.find("input[type='checkbox']").prop("checked")).toBeTruthy()
})
it("should call uncheckObject when the object/prefix is unchecked", () => {
const uncheckObject = jest.fn()
const wrapper = shallow(
<ObjectItem name={"test"} checked={true} uncheckObject={uncheckObject} />
)
wrapper.find("input[type='checkbox']").simulate("change")
expect(uncheckObject).toHaveBeenCalledWith("test")
})
}) })

View File

@ -0,0 +1,74 @@
/*
* Minio Cloud Storage (C) 2018 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from "react"
import { shallow } from "enzyme"
import { ObjectsBulkActions } from "../ObjectsBulkActions"
describe("ObjectsBulkActions", () => {
it("should render without crashing", () => {
shallow(<ObjectsBulkActions checkedObjectsCount={0} />)
})
it("should show actions when checkObjectsCount is more than 0", () => {
const wrapper = shallow(<ObjectsBulkActions checkedObjectsCount={1} />)
expect(wrapper.hasClass("list-actions-toggled")).toBeTruthy()
})
it("should call downloadChecked when download button is clicked", () => {
const downloadChecked = jest.fn()
const wrapper = shallow(
<ObjectsBulkActions
checkedObjectsCount={1}
downloadChecked={downloadChecked}
/>
)
wrapper.find("#download-checked").simulate("click")
expect(downloadChecked).toHaveBeenCalled()
})
it("should call clearChecked when close button is clicked", () => {
const clearChecked = jest.fn()
const wrapper = shallow(
<ObjectsBulkActions checkedObjectsCount={1} clearChecked={clearChecked} />
)
wrapper.find("#close-bulk-actions").simulate("click")
expect(clearChecked).toHaveBeenCalled()
})
it("shoud show DeleteObjectConfirmModal when delete-checked button is clicked", () => {
const wrapper = shallow(<ObjectsBulkActions checkedObjectsCount={1} />)
wrapper.find("#delete-checked").simulate("click")
wrapper.update()
expect(wrapper.find("DeleteObjectConfirmModal").length).toBe(1)
})
it("shoud call deleteChecked when Delete is clicked on confirmation modal", () => {
const deleteChecked = jest.fn()
const wrapper = shallow(
<ObjectsBulkActions
checkedObjectsCount={1}
deleteChecked={deleteChecked}
/>
)
wrapper.find("#delete-checked").simulate("click")
wrapper.update()
wrapper.find("DeleteObjectConfirmModal").prop("deleteObject")()
expect(deleteChecked).toHaveBeenCalled()
wrapper.update()
expect(wrapper.find("DeleteObjectConfirmModal").length).toBe(0)
})
})

View File

@ -27,7 +27,7 @@ describe("ObjectsList", () => {
const wrapper = shallow( const wrapper = shallow(
<ObjectsList objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]} /> <ObjectsList objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]} />
) )
expect(wrapper.find("ObjectContainer").length).toBe(2) expect(wrapper.find("Connect(ObjectContainer)").length).toBe(2)
}) })
it("should render PrefixContainer for every prefix", () => { it("should render PrefixContainer for every prefix", () => {

View File

@ -25,8 +25,8 @@ describe("PrefixContainer", () => {
it("should render ObjectItem with props", () => { it("should render ObjectItem with props", () => {
const wrapper = shallow(<PrefixContainer object={{ name: "abc/" }} />) const wrapper = shallow(<PrefixContainer object={{ name: "abc/" }} />)
expect(wrapper.find("ObjectItem").length).toBe(1) expect(wrapper.find("Connect(ObjectItem)").length).toBe(1)
expect(wrapper.find("ObjectItem").prop("name")).toBe("abc/") expect(wrapper.find("Connect(ObjectItem)").prop("name")).toBe("abc/")
}) })
it("should call selectPrefix when the prefix is clicked", () => { it("should call selectPrefix when the prefix is clicked", () => {
@ -38,7 +38,7 @@ describe("PrefixContainer", () => {
selectPrefix={selectPrefix} selectPrefix={selectPrefix}
/> />
) )
wrapper.find("ObjectItem").prop("onClick")() wrapper.find("Connect(ObjectItem)").prop("onClick")()
expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/") expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/")
}) })
}) })

View File

@ -18,8 +18,10 @@ import configureStore from "redux-mock-store"
import thunk from "redux-thunk" import thunk from "redux-thunk"
import * as actionsObjects from "../actions" import * as actionsObjects from "../actions"
import * as alertActions from "../../alert/actions" import * as alertActions from "../../alert/actions"
import { minioBrowserPrefix } from "../../constants"
jest.mock("../../web", () => ({ jest.mock("../../web", () => ({
LoggedIn: jest.fn(() => true).mockReturnValueOnce(false),
ListObjects: jest.fn(() => { ListObjects: jest.fn(() => {
return Promise.resolve({ return Promise.resolve({
objects: [{ name: "test1" }, { name: "test2" }], objects: [{ name: "test1" }, { name: "test2" }],
@ -38,7 +40,18 @@ jest.mock("../../web", () => ({
return Promise.reject({ message: "Invalid bucket" }) return Promise.reject({ message: "Invalid bucket" })
} }
return Promise.resolve({ url: "https://test.com/bk1/pre1/b.txt" }) return Promise.resolve({ url: "https://test.com/bk1/pre1/b.txt" })
}) }),
CreateURLToken: jest
.fn()
.mockImplementationOnce(() => {
return Promise.resolve({ token: "test" })
})
.mockImplementationOnce(() => {
return Promise.reject({ message: "Error in creating token" })
})
.mockImplementationOnce(() => {
return Promise.resolve({ token: "test" })
})
})) }))
const middlewares = [thunk] const middlewares = [thunk]
@ -169,13 +182,14 @@ describe("Objects actions", () => {
expect(actions).toEqual(expectedActions) expect(actions).toEqual(expectedActions)
}) })
it("should update browser url and creates objects/SET_CURRENT_PREFIX action when selectPrefix is called", () => { it("should update browser url and creates objects/SET_CURRENT_PREFIX and objects/CHECKED_LIST_RESET actions when selectPrefix is called", () => {
const store = mockStore({ const store = mockStore({
buckets: { currentBucket: "test" }, buckets: { currentBucket: "test" },
objects: { currentPrefix: "" } objects: { currentPrefix: "" }
}) })
const expectedActions = [ const expectedActions = [
{ type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" } { type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" },
{ type: "objects/CHECKED_LIST_RESET" }
] ]
store.dispatch(actionsObjects.selectPrefix("abc/")) store.dispatch(actionsObjects.selectPrefix("abc/"))
const actions = store.getActions() const actions = store.getActions()
@ -301,4 +315,132 @@ describe("Objects actions", () => {
expect(actions).toEqual(expectedActions) expect(actions).toEqual(expectedActions)
}) })
}) })
describe("Download object", () => {
it("should download the object non-LoggedIn users", () => {
const setLocation = jest.fn()
Object.defineProperty(window, "location", {
set(url) {
setLocation(url)
}
})
const store = mockStore({
buckets: { currentBucket: "bk1" },
objects: { currentPrefix: "pre1/" }
})
store.dispatch(actionsObjects.downloadObject("obj1"))
const url = `${
window.location.origin
}${minioBrowserPrefix}/download/bk1/${encodeURI("pre1/obj1")}?token=''`
expect(setLocation).toHaveBeenCalledWith(url)
})
it("should download the object for LoggedIn users", () => {
const setLocation = jest.fn()
Object.defineProperty(window, "location", {
set(url) {
setLocation(url)
}
})
const store = mockStore({
buckets: { currentBucket: "bk1" },
objects: { currentPrefix: "pre1/" }
})
return store.dispatch(actionsObjects.downloadObject("obj1")).then(() => {
const url = `${
window.location.origin
}${minioBrowserPrefix}/download/bk1/${encodeURI(
"pre1/obj1"
)}?token=test`
expect(setLocation).toHaveBeenCalledWith(url)
})
})
it("create alert/SET action when CreateUrlToken fails", () => {
const store = mockStore({
buckets: { currentBucket: "bk1" },
objects: { currentPrefix: "pre1/" }
})
const expectedActions = [
{
type: "alert/SET",
alert: {
type: "danger",
message: "Error in creating token",
id: alertActions.alertId
}
}
]
return store.dispatch(actionsObjects.downloadObject("obj1")).then(() => {
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
})
})
it("creates objects/CHECKED_LIST_ADD action", () => {
const store = mockStore()
const expectedActions = [
{
type: "objects/CHECKED_LIST_ADD",
object: "obj1"
}
]
store.dispatch(actionsObjects.checkObject("obj1"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("creates objects/CHECKED_LIST_REMOVE action", () => {
const store = mockStore()
const expectedActions = [
{
type: "objects/CHECKED_LIST_REMOVE",
object: "obj1"
}
]
store.dispatch(actionsObjects.uncheckObject("obj1"))
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("creates objects/CHECKED_LIST_RESET action", () => {
const store = mockStore()
const expectedActions = [
{
type: "objects/CHECKED_LIST_RESET"
}
]
store.dispatch(actionsObjects.resetCheckedList())
const actions = store.getActions()
expect(actions).toEqual(expectedActions)
})
it("should download checked objects", () => {
const open = jest.fn()
const send = jest.fn()
const xhrMockClass = () => ({
open: open,
send: send
})
window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass)
const store = mockStore({
buckets: { currentBucket: "bk1" },
objects: { currentPrefix: "pre1/", checkedList: ["obj1"] }
})
return store.dispatch(actionsObjects.downloadCheckedObjects()).then(() => {
const requestUrl = `${
location.origin
}${minioBrowserPrefix}/zip?token=test`
expect(open).toHaveBeenCalledWith("POST", requestUrl, true)
expect(send).toHaveBeenCalledWith(
JSON.stringify({
bucketName: "bk1",
prefix: "pre1/",
objects: ["obj1"]
})
)
})
})
}) })

View File

@ -31,7 +31,8 @@ describe("objects reducer", () => {
show: false, show: false,
object: "", object: "",
url: "" url: ""
} },
checkedList: []
}) })
}) })
@ -135,4 +136,33 @@ describe("objects reducer", () => {
url: "test" url: "test"
}) })
}) })
it("should handle CHECKED_LIST_ADD", () => {
const newState = reducer(undefined, {
type: actions.CHECKED_LIST_ADD,
object: "obj1"
})
expect(newState.checkedList).toEqual(["obj1"])
})
it("should handle SELECTED_LIST_REMOVE", () => {
const newState = reducer(
{ checkedList: ["obj1", "obj2"] },
{
type: actions.CHECKED_LIST_REMOVE,
object: "obj1"
}
)
expect(newState.checkedList).toEqual(["obj2"])
})
it("should handle CHECKED_LIST_RESET", () => {
const newState = reducer(
{ checkedList: ["obj1", "obj2"] },
{
type: actions.CHECKED_LIST_RESET
}
)
expect(newState.checkedList).toEqual([])
})
}) })

View File

@ -22,8 +22,9 @@ import {
sortObjectsByDate sortObjectsByDate
} from "../utils" } from "../utils"
import { getCurrentBucket } from "../buckets/selectors" import { getCurrentBucket } from "../buckets/selectors"
import { getCurrentPrefix } from "./selectors" import { getCurrentPrefix, getCheckedList } from "./selectors"
import * as alertActions from "../alert/actions" import * as alertActions from "../alert/actions"
import { minioBrowserPrefix } from "../constants"
export const SET_LIST = "objects/SET_LIST" export const SET_LIST = "objects/SET_LIST"
export const APPEND_LIST = "objects/APPEND_LIST" export const APPEND_LIST = "objects/APPEND_LIST"
@ -32,6 +33,9 @@ export const SET_SORT_BY = "objects/SET_SORT_BY"
export const SET_SORT_ORDER = "objects/SET_SORT_ORDER" export const SET_SORT_ORDER = "objects/SET_SORT_ORDER"
export const SET_CURRENT_PREFIX = "objects/SET_CURRENT_PREFIX" export const SET_CURRENT_PREFIX = "objects/SET_CURRENT_PREFIX"
export const SET_SHARE_OBJECT = "objects/SET_SHARE_OBJECT" export const SET_SHARE_OBJECT = "objects/SET_SHARE_OBJECT"
export const CHECKED_LIST_ADD = "objects/CHECKED_LIST_ADD"
export const CHECKED_LIST_REMOVE = "objects/CHECKED_LIST_REMOVE"
export const CHECKED_LIST_RESET = "objects/CHECKED_LIST_RESET"
export const setList = (objects, marker, isTruncated) => ({ export const setList = (objects, marker, isTruncated) => ({
type: SET_LIST, type: SET_LIST,
@ -119,6 +123,7 @@ export const selectPrefix = prefix => {
return function(dispatch, getState) { return function(dispatch, getState) {
dispatch(setCurrentPrefix(prefix)) dispatch(setCurrentPrefix(prefix))
dispatch(fetchObjects()) dispatch(fetchObjects())
dispatch(resetCheckedList())
const currentBucket = getCurrentBucket(getState()) const currentBucket = getCurrentBucket(getState())
history.replace(`/${currentBucket}/${prefix}`) history.replace(`/${currentBucket}/${prefix}`)
} }
@ -160,6 +165,16 @@ export const removeObject = object => ({
object object
}) })
export const deleteCheckedObjects = () => {
return function(dispatch, getState) {
const checkedObjects = getCheckedList(getState())
for (let i = 0; i < checkedObjects.length; i++) {
dispatch(deleteObject(checkedObjects[i]))
}
dispatch(resetCheckedList())
}
}
export const shareObject = (object, days, hours, minutes) => { export const shareObject = (object, days, hours, minutes) => {
return function(dispatch, getState) { return function(dispatch, getState) {
const currentBucket = getCurrentBucket(getState()) const currentBucket = getCurrentBucket(getState())
@ -206,3 +221,112 @@ export const hideShareObject = (object, url) => ({
object: "", object: "",
url: "" url: ""
}) })
export const downloadObject = object => {
return function(dispatch, getState) {
const currentBucket = getCurrentBucket(getState())
const currentPrefix = getCurrentPrefix(getState())
const objectName = `${currentPrefix}${object}`
const encObjectName = encodeURI(objectName)
if (web.LoggedIn()) {
return web
.CreateURLToken()
.then(res => {
const url = `${
window.location.origin
}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=${
res.token
}`
window.location = url
})
.catch(err => {
dispatch(
alertActions.set({
type: "danger",
message: err.message
})
)
})
} else {
const url = `${
window.location.origin
}${minioBrowserPrefix}/download/${currentBucket}/${encObjectName}?token=''`
window.location = url
}
}
}
export const checkObject = object => ({
type: CHECKED_LIST_ADD,
object
})
export const uncheckObject = object => ({
type: CHECKED_LIST_REMOVE,
object
})
export const resetCheckedList = () => ({
type: CHECKED_LIST_RESET
})
export const downloadCheckedObjects = () => {
return function(dispatch, getState) {
const state = getState()
const req = {
bucketName: getCurrentBucket(state),
prefix: getCurrentPrefix(state),
objects: getCheckedList(state)
}
if (!web.LoggedIn()) {
const requestUrl = location.origin + "/minio/zip?token=''"
downloadZip(requestUrl, req, dispatch)
} else {
return web
.CreateURLToken()
.then(res => {
const requestUrl = `${
location.origin
}${minioBrowserPrefix}/zip?token=${res.token}`
downloadZip(requestUrl, req, dispatch)
})
.catch(err =>
dispatch(
alertActions.set({
type: "danger",
message: err.message
})
)
)
}
}
}
const downloadZip = (url, req, dispatch) => {
var anchor = document.createElement("a")
document.body.appendChild(anchor)
var xhr = new XMLHttpRequest()
xhr.open("POST", url, true)
xhr.responseType = "blob"
xhr.onload = function(e) {
if (this.status == 200) {
dispatch(resetCheckedList())
var blob = new Blob([this.response], {
type: "octet/stream"
})
var blobUrl = window.URL.createObjectURL(blob)
var separator = req.prefix.length > 1 ? "-" : ""
anchor.href = blobUrl
anchor.download =
req.bucketName + separator + req.prefix.slice(0, -1) + ".zip"
anchor.click()
window.URL.revokeObjectURL(blobUrl)
anchor.remove()
}
}
xhr.send(JSON.stringify(req))
}

View File

@ -16,8 +16,8 @@
import * as actionsObjects from "./actions" import * as actionsObjects from "./actions"
const removeObject = (list, action) => { const removeObject = (list, objectToRemove, lookup) => {
const idx = list.findIndex(object => object.name === action.object) const idx = list.findIndex(object => lookup(object) === objectToRemove)
if (idx == -1) { if (idx == -1) {
return list return list
} }
@ -36,7 +36,8 @@ export default (
show: false, show: false,
object: "", object: "",
url: "" url: ""
} },
checkedList: []
}, },
action action
) => { ) => {
@ -58,7 +59,7 @@ export default (
case actionsObjects.REMOVE: case actionsObjects.REMOVE:
return { return {
...state, ...state,
list: removeObject(state.list, action) list: removeObject(state.list, action.object, object => object.name)
} }
case actionsObjects.SET_SORT_BY: case actionsObjects.SET_SORT_BY:
return { return {
@ -86,6 +87,25 @@ export default (
url: action.url url: action.url
} }
} }
case actionsObjects.CHECKED_LIST_ADD:
return {
...state,
checkedList: [...state.checkedList, action.object]
}
case actionsObjects.CHECKED_LIST_REMOVE:
return {
...state,
checkedList: removeObject(
state.checkedList,
action.object,
object => object
)
}
case actionsObjects.CHECKED_LIST_RESET:
return {
...state,
checkedList: []
}
default: default:
return state return state
} }

View File

@ -17,3 +17,5 @@
import { createSelector } from "reselect" import { createSelector } from "reselect"
export const getCurrentPrefix = state => state.objects.currentPrefix export const getCurrentPrefix = state => state.objects.currentPrefix
export const getCheckedList = state => state.objects.checkedList