-
+ {
+ checked ? uncheckObject(name) : checkObject(name)
+ }}
+ />
@@ -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)
diff --git a/browser/app/js/objects/ObjectsBulkActions.js b/browser/app/js/objects/ObjectsBulkActions.js
new file mode 100644
index 000000000..fc63fc5b9
--- /dev/null
+++ b/browser/app/js/objects/ObjectsBulkActions.js
@@ -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 (
+
0
+ })
+ }
+ >
+
+ {checkedObjectsCount} Objects
+ selected
+
+
+
+
+
+
+
+
+ {this.state.showDeleteConfirmation && (
+
+ )}
+
+ )
+ }
+}
+
+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)
diff --git a/browser/app/js/objects/__tests__/ObjectContainer.test.js b/browser/app/js/objects/__tests__/ObjectContainer.test.js
index 67d7cc074..edffdf09b 100644
--- a/browser/app/js/objects/__tests__/ObjectContainer.test.js
+++ b/browser/app/js/objects/__tests__/ObjectContainer.test.js
@@ -25,7 +25,25 @@ describe("ObjectContainer", () => {
it("should render ObjectItem with props", () => {
const wrapper = shallow(
)
- expect(wrapper.find("ObjectItem").length).toBe(1)
- expect(wrapper.find("ObjectItem").prop("name")).toBe("test1.jpg")
+ expect(wrapper.find("Connect(ObjectItem)").length).toBe(1)
+ expect(wrapper.find("Connect(ObjectItem)").prop("name")).toBe("test1.jpg")
+ })
+
+ it("should pass actions to ObjectItem", () => {
+ const wrapper = shallow(
+
+ )
+ 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(
+
+ )
+ expect(wrapper.find("Connect(ObjectItem)").prop("actionButtons")).toBe(
+ undefined
+ )
})
})
diff --git a/browser/app/js/objects/__tests__/ObjectItem.test.js b/browser/app/js/objects/__tests__/ObjectItem.test.js
index 507d52aa3..5a692b254 100644
--- a/browser/app/js/objects/__tests__/ObjectItem.test.js
+++ b/browser/app/js/objects/__tests__/ObjectItem.test.js
@@ -34,4 +34,27 @@ describe("ObjectItem", () => {
wrapper.find("a").simulate("click", { preventDefault: jest.fn() })
expect(onClick).toHaveBeenCalled()
})
+
+ it("should call checkObject when the object/prefix is checked", () => {
+ const checkObject = jest.fn()
+ const wrapper = shallow(
+
+ )
+ wrapper.find("input[type='checkbox']").simulate("change")
+ expect(checkObject).toHaveBeenCalledWith("test")
+ })
+
+ it("should render checked checkbox", () => {
+ const wrapper = shallow(
)
+ 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(
+
+ )
+ wrapper.find("input[type='checkbox']").simulate("change")
+ expect(uncheckObject).toHaveBeenCalledWith("test")
+ })
})
diff --git a/browser/app/js/objects/__tests__/ObjectsBulkActions.test.js b/browser/app/js/objects/__tests__/ObjectsBulkActions.test.js
new file mode 100644
index 000000000..9948bc1f2
--- /dev/null
+++ b/browser/app/js/objects/__tests__/ObjectsBulkActions.test.js
@@ -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(
)
+ })
+
+ it("should show actions when checkObjectsCount is more than 0", () => {
+ const wrapper = shallow(
)
+ expect(wrapper.hasClass("list-actions-toggled")).toBeTruthy()
+ })
+
+ it("should call downloadChecked when download button is clicked", () => {
+ const downloadChecked = jest.fn()
+ const wrapper = shallow(
+
+ )
+ 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(
+
+ )
+ wrapper.find("#close-bulk-actions").simulate("click")
+ expect(clearChecked).toHaveBeenCalled()
+ })
+
+ it("shoud show DeleteObjectConfirmModal when delete-checked button is clicked", () => {
+ const wrapper = shallow(
)
+ 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(
+
+ )
+ 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)
+ })
+})
diff --git a/browser/app/js/objects/__tests__/ObjectsList.test.js b/browser/app/js/objects/__tests__/ObjectsList.test.js
index 5d6609271..72bb3eed1 100644
--- a/browser/app/js/objects/__tests__/ObjectsList.test.js
+++ b/browser/app/js/objects/__tests__/ObjectsList.test.js
@@ -27,7 +27,7 @@ describe("ObjectsList", () => {
const wrapper = shallow(
)
- expect(wrapper.find("ObjectContainer").length).toBe(2)
+ expect(wrapper.find("Connect(ObjectContainer)").length).toBe(2)
})
it("should render PrefixContainer for every prefix", () => {
diff --git a/browser/app/js/objects/__tests__/PrefixContainer.test.js b/browser/app/js/objects/__tests__/PrefixContainer.test.js
index 87bc47ba3..7ae27a0bb 100644
--- a/browser/app/js/objects/__tests__/PrefixContainer.test.js
+++ b/browser/app/js/objects/__tests__/PrefixContainer.test.js
@@ -25,8 +25,8 @@ describe("PrefixContainer", () => {
it("should render ObjectItem with props", () => {
const wrapper = shallow(
)
- expect(wrapper.find("ObjectItem").length).toBe(1)
- expect(wrapper.find("ObjectItem").prop("name")).toBe("abc/")
+ expect(wrapper.find("Connect(ObjectItem)").length).toBe(1)
+ expect(wrapper.find("Connect(ObjectItem)").prop("name")).toBe("abc/")
})
it("should call selectPrefix when the prefix is clicked", () => {
@@ -38,7 +38,7 @@ describe("PrefixContainer", () => {
selectPrefix={selectPrefix}
/>
)
- wrapper.find("ObjectItem").prop("onClick")()
+ wrapper.find("Connect(ObjectItem)").prop("onClick")()
expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/")
})
})
diff --git a/browser/app/js/objects/__tests__/actions.test.js b/browser/app/js/objects/__tests__/actions.test.js
index 26d33a37c..7fa256895 100644
--- a/browser/app/js/objects/__tests__/actions.test.js
+++ b/browser/app/js/objects/__tests__/actions.test.js
@@ -18,8 +18,10 @@ import configureStore from "redux-mock-store"
import thunk from "redux-thunk"
import * as actionsObjects from "../actions"
import * as alertActions from "../../alert/actions"
+import { minioBrowserPrefix } from "../../constants"
jest.mock("../../web", () => ({
+ LoggedIn: jest.fn(() => true).mockReturnValueOnce(false),
ListObjects: jest.fn(() => {
return Promise.resolve({
objects: [{ name: "test1" }, { name: "test2" }],
@@ -38,7 +40,18 @@ jest.mock("../../web", () => ({
return Promise.reject({ message: "Invalid bucket" })
}
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]
@@ -169,13 +182,14 @@ describe("Objects actions", () => {
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({
buckets: { currentBucket: "test" },
objects: { currentPrefix: "" }
})
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/"))
const actions = store.getActions()
@@ -301,4 +315,132 @@ describe("Objects actions", () => {
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"]
+ })
+ )
+ })
+ })
})
diff --git a/browser/app/js/objects/__tests__/reducer.test.js b/browser/app/js/objects/__tests__/reducer.test.js
index 732b227ba..d3ea3ba07 100644
--- a/browser/app/js/objects/__tests__/reducer.test.js
+++ b/browser/app/js/objects/__tests__/reducer.test.js
@@ -31,7 +31,8 @@ describe("objects reducer", () => {
show: false,
object: "",
url: ""
- }
+ },
+ checkedList: []
})
})
@@ -135,4 +136,33 @@ describe("objects reducer", () => {
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([])
+ })
})
diff --git a/browser/app/js/objects/actions.js b/browser/app/js/objects/actions.js
index 3ae6fe606..788a4c91d 100644
--- a/browser/app/js/objects/actions.js
+++ b/browser/app/js/objects/actions.js
@@ -22,8 +22,9 @@ import {
sortObjectsByDate
} from "../utils"
import { getCurrentBucket } from "../buckets/selectors"
-import { getCurrentPrefix } from "./selectors"
+import { getCurrentPrefix, getCheckedList } from "./selectors"
import * as alertActions from "../alert/actions"
+import { minioBrowserPrefix } from "../constants"
export const SET_LIST = "objects/SET_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_CURRENT_PREFIX = "objects/SET_CURRENT_PREFIX"
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) => ({
type: SET_LIST,
@@ -119,6 +123,7 @@ export const selectPrefix = prefix => {
return function(dispatch, getState) {
dispatch(setCurrentPrefix(prefix))
dispatch(fetchObjects())
+ dispatch(resetCheckedList())
const currentBucket = getCurrentBucket(getState())
history.replace(`/${currentBucket}/${prefix}`)
}
@@ -160,6 +165,16 @@ export const removeObject = 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) => {
return function(dispatch, getState) {
const currentBucket = getCurrentBucket(getState())
@@ -206,3 +221,112 @@ export const hideShareObject = (object, url) => ({
object: "",
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))
+}
diff --git a/browser/app/js/objects/reducer.js b/browser/app/js/objects/reducer.js
index 301b9aa83..5d5941ebd 100644
--- a/browser/app/js/objects/reducer.js
+++ b/browser/app/js/objects/reducer.js
@@ -16,8 +16,8 @@
import * as actionsObjects from "./actions"
-const removeObject = (list, action) => {
- const idx = list.findIndex(object => object.name === action.object)
+const removeObject = (list, objectToRemove, lookup) => {
+ const idx = list.findIndex(object => lookup(object) === objectToRemove)
if (idx == -1) {
return list
}
@@ -36,7 +36,8 @@ export default (
show: false,
object: "",
url: ""
- }
+ },
+ checkedList: []
},
action
) => {
@@ -58,7 +59,7 @@ export default (
case actionsObjects.REMOVE:
return {
...state,
- list: removeObject(state.list, action)
+ list: removeObject(state.list, action.object, object => object.name)
}
case actionsObjects.SET_SORT_BY:
return {
@@ -86,6 +87,25 @@ export default (
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:
return state
}
diff --git a/browser/app/js/objects/selectors.js b/browser/app/js/objects/selectors.js
index 45809c582..1e9895a51 100644
--- a/browser/app/js/objects/selectors.js
+++ b/browser/app/js/objects/selectors.js
@@ -17,3 +17,5 @@
import { createSelector } from "reselect"
export const getCurrentPrefix = state => state.objects.currentPrefix
+
+export const getCheckedList = state => state.objects.checkedList