Refactor make bucket and upload components (#5521)
parent
9bfa07ecf5
commit
44f8f7059c
@ -0,0 +1,41 @@ |
|||||||
|
/* |
||||||
|
* 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 Alert from "./Alert" |
||||||
|
import * as alertActions from "./actions" |
||||||
|
|
||||||
|
export const AlertContainer = ({ alert, clearAlert }) => { |
||||||
|
if (!alert.message) { |
||||||
|
return "" |
||||||
|
} |
||||||
|
return <Alert {...alert} onDismiss={clearAlert} /> |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
return { |
||||||
|
alert: state.alert |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
clearAlert: () => dispatch(alertActions.clear()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(AlertContainer) |
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* 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, mount } from "enzyme" |
||||||
|
import { AlertContainer } from "../AlertContainer" |
||||||
|
|
||||||
|
describe("Alert", () => { |
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow( |
||||||
|
<AlertContainer alert={{ show: true, type: "danger", message: "Test" }} /> |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should render nothing if message is empty", () => { |
||||||
|
const wrapper = shallow( |
||||||
|
<AlertContainer alert={{ show: true, type: "danger", message: "" }} /> |
||||||
|
) |
||||||
|
expect(wrapper.find("Alert").length).toBe(0) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,83 @@ |
|||||||
|
/* |
||||||
|
* 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 { Dropdown, OverlayTrigger, Tooltip } from "react-bootstrap" |
||||||
|
import * as actionsBuckets from "../buckets/actions" |
||||||
|
import * as uploadsActions from "../uploads/actions" |
||||||
|
|
||||||
|
export const MainActions = ({ uploadFile, showMakeBucketModal }) => { |
||||||
|
const uploadTooltip = <Tooltip id="tt-upload-file">Upload file</Tooltip> |
||||||
|
const makeBucketTooltip = ( |
||||||
|
<Tooltip id="tt-create-bucket">Create bucket</Tooltip> |
||||||
|
) |
||||||
|
const onFileUpload = e => { |
||||||
|
e.preventDefault() |
||||||
|
uploadFile(e.target.files[0]) |
||||||
|
e.target.value = null |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Dropdown dropup className="feb-actions" id="fe-action-toggle"> |
||||||
|
<Dropdown.Toggle noCaret className="feba-toggle"> |
||||||
|
<span> |
||||||
|
<i className="fa fa-plus" /> |
||||||
|
</span> |
||||||
|
</Dropdown.Toggle> |
||||||
|
<Dropdown.Menu> |
||||||
|
<OverlayTrigger placement="left" overlay={uploadTooltip}> |
||||||
|
<a href="#" className="feba-btn feba-upload"> |
||||||
|
<input |
||||||
|
type="file" |
||||||
|
onChange={onFileUpload} |
||||||
|
style={{ display: "none" }} |
||||||
|
id="file-input" |
||||||
|
/> |
||||||
|
<label htmlFor="file-input"> |
||||||
|
{" "} |
||||||
|
<i className="fa fa-cloud-upload" />{" "} |
||||||
|
</label> |
||||||
|
</a> |
||||||
|
</OverlayTrigger> |
||||||
|
<OverlayTrigger placement="left" overlay={makeBucketTooltip}> |
||||||
|
<a |
||||||
|
href="#" |
||||||
|
id="show-make-bucket" |
||||||
|
className="feba-btn feba-bucket" |
||||||
|
onClick={e => { |
||||||
|
e.preventDefault() |
||||||
|
showMakeBucketModal() |
||||||
|
}} |
||||||
|
> |
||||||
|
<i className="fa fa-hdd-o" /> |
||||||
|
</a> |
||||||
|
</OverlayTrigger> |
||||||
|
</Dropdown.Menu> |
||||||
|
</Dropdown> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => state |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
uploadFile: file => dispatch(uploadsActions.uploadFile(file)), |
||||||
|
showMakeBucketModal: () => dispatch(actionsBuckets.showMakeBucketModal()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(MainActions) |
@ -0,0 +1,47 @@ |
|||||||
|
/* |
||||||
|
* 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, mount } from "enzyme" |
||||||
|
import { MainActions } from "../MainActions" |
||||||
|
|
||||||
|
describe("MainActions", () => { |
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow(<MainActions />) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call showMakeBucketModal when create bucket icon is clicked", () => { |
||||||
|
const showMakeBucketModal = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<MainActions showMakeBucketModal={showMakeBucketModal} /> |
||||||
|
) |
||||||
|
wrapper |
||||||
|
.find("#show-make-bucket") |
||||||
|
.simulate("click", { preventDefault: jest.fn() }) |
||||||
|
expect(showMakeBucketModal).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call uploadFile when a file is selected for upload", () => { |
||||||
|
const uploadFile = jest.fn() |
||||||
|
const wrapper = shallow(<MainActions uploadFile={uploadFile} />) |
||||||
|
const file = new Blob(["file content"], { type: "text/plain" }) |
||||||
|
wrapper.find("#file-input").simulate("change", { |
||||||
|
preventDefault: jest.fn(), |
||||||
|
target: { files: [file] } |
||||||
|
}) |
||||||
|
expect(uploadFile).toHaveBeenCalledWith(file) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,90 @@ |
|||||||
|
/* |
||||||
|
* 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 { Modal, ModalBody } from "react-bootstrap" |
||||||
|
import * as actionsBuckets from "./actions" |
||||||
|
|
||||||
|
export class MakeBucketModal extends React.Component { |
||||||
|
constructor(props) { |
||||||
|
super(props) |
||||||
|
this.state = { |
||||||
|
bucketName: "" |
||||||
|
} |
||||||
|
} |
||||||
|
onSubmit(e) { |
||||||
|
e.preventDefault() |
||||||
|
const { makeBucket } = this.props |
||||||
|
const bucket = this.state.bucketName |
||||||
|
if (bucket) { |
||||||
|
makeBucket(bucket) |
||||||
|
this.hideModal() |
||||||
|
} |
||||||
|
} |
||||||
|
hideModal() { |
||||||
|
this.setState({ |
||||||
|
bucketName: "" |
||||||
|
}) |
||||||
|
this.props.hideMakeBucketModal() |
||||||
|
} |
||||||
|
render() { |
||||||
|
const { showMakeBucketModal } = this.props |
||||||
|
return ( |
||||||
|
<Modal |
||||||
|
className="modal-create-bucket" |
||||||
|
bsSize="small" |
||||||
|
animation={false} |
||||||
|
show={showMakeBucketModal} |
||||||
|
onHide={this.hideModal.bind(this)} |
||||||
|
> |
||||||
|
<button className="close close-alt" onClick={this.hideModal.bind(this)}> |
||||||
|
<span>×</span> |
||||||
|
</button> |
||||||
|
<ModalBody> |
||||||
|
<form onSubmit={this.onSubmit.bind(this)}> |
||||||
|
<div className="input-group"> |
||||||
|
<input |
||||||
|
className="ig-text" |
||||||
|
type="text" |
||||||
|
placeholder="Bucket Name" |
||||||
|
value={this.state.bucketName} |
||||||
|
onChange={e => this.setState({ bucketName: e.target.value })} |
||||||
|
autoFocus |
||||||
|
/> |
||||||
|
<i className="ig-helpers" /> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</ModalBody> |
||||||
|
</Modal> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
return { |
||||||
|
showMakeBucketModal: state.buckets.showMakeBucketModal |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
makeBucket: bucket => dispatch(actionsBuckets.makeBucket(bucket)), |
||||||
|
hideMakeBucketModal: () => dispatch(actionsBuckets.hideMakeBucketModal()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(MakeBucketModal) |
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* 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, mount } from "enzyme" |
||||||
|
import { MakeBucketModal } from "../MakeBucketModal" |
||||||
|
|
||||||
|
describe("MakeBucketModal", () => { |
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow(<MakeBucketModal />) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call hideMakeBucketModal when close button is clicked", () => { |
||||||
|
const hideMakeBucketModal = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<MakeBucketModal hideMakeBucketModal={hideMakeBucketModal} /> |
||||||
|
) |
||||||
|
wrapper.find("button").simulate("click") |
||||||
|
expect(hideMakeBucketModal).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
|
||||||
|
it("bucketName should be cleared before hiding the modal", () => { |
||||||
|
const hideMakeBucketModal = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<MakeBucketModal hideMakeBucketModal={hideMakeBucketModal} /> |
||||||
|
) |
||||||
|
wrapper.find("input").simulate("change", { |
||||||
|
target: { value: "test" } |
||||||
|
}) |
||||||
|
expect(wrapper.state("bucketName")).toBe("test") |
||||||
|
wrapper.find("button").simulate("click") |
||||||
|
expect(wrapper.state("bucketName")).toBe("") |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call makeBucket when the form is submitted", () => { |
||||||
|
const makeBucket = jest.fn() |
||||||
|
const hideMakeBucketModal = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<MakeBucketModal |
||||||
|
makeBucket={makeBucket} |
||||||
|
hideMakeBucketModal={hideMakeBucketModal} |
||||||
|
/> |
||||||
|
) |
||||||
|
wrapper.find("input").simulate("change", { |
||||||
|
target: { value: "test" } |
||||||
|
}) |
||||||
|
wrapper.find("form").simulate("submit", { preventDefault: jest.fn() }) |
||||||
|
expect(makeBucket).toHaveBeenCalledWith("test") |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call hideMakeBucketModal and clear bucketName after the form is submited", () => { |
||||||
|
const makeBucket = jest.fn() |
||||||
|
const hideMakeBucketModal = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<MakeBucketModal |
||||||
|
makeBucket={makeBucket} |
||||||
|
hideMakeBucketModal={hideMakeBucketModal} |
||||||
|
/> |
||||||
|
) |
||||||
|
wrapper.find("input").simulate("change", { |
||||||
|
target: { value: "test" } |
||||||
|
}) |
||||||
|
wrapper.find("form").simulate("submit", { preventDefault: jest.fn() }) |
||||||
|
expect(hideMakeBucketModal).toHaveBeenCalled() |
||||||
|
expect(wrapper.state("bucketName")).toBe("") |
||||||
|
}) |
||||||
|
}) |
@ -1,141 +0,0 @@ |
|||||||
/* |
|
||||||
* Minio Cloud Storage (C) 2016 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 humanize from 'humanize' |
|
||||||
import classNames from 'classnames' |
|
||||||
import connect from 'react-redux/lib/components/connect' |
|
||||||
|
|
||||||
import ProgressBar from 'react-bootstrap/lib/ProgressBar' |
|
||||||
import ConfirmModal from './ConfirmModal' |
|
||||||
|
|
||||||
import * as actions from '../actions' |
|
||||||
|
|
||||||
// UploadModal is a modal that handles multiple file uploads.
|
|
||||||
// During the upload, it displays a progress bar, and can transform into an
|
|
||||||
// abort modal if the user decides to abort the uploads.
|
|
||||||
class UploadModal extends React.Component { |
|
||||||
|
|
||||||
// Abort all the current uploads.
|
|
||||||
abortUploads(e) { |
|
||||||
e.preventDefault() |
|
||||||
const {dispatch, uploads} = this.props |
|
||||||
|
|
||||||
for (var slug in uploads) { |
|
||||||
let upload = uploads[slug] |
|
||||||
upload.xhr.abort() |
|
||||||
dispatch(actions.stopUpload({ |
|
||||||
slug |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
this.hideAbort(e) |
|
||||||
} |
|
||||||
|
|
||||||
// Show the abort modal instead of the progress modal.
|
|
||||||
showAbort(e) { |
|
||||||
e.preventDefault() |
|
||||||
const {dispatch} = this.props |
|
||||||
|
|
||||||
dispatch(actions.setShowAbortModal(true)) |
|
||||||
} |
|
||||||
|
|
||||||
// Show the progress modal instead of the abort modal.
|
|
||||||
hideAbort(e) { |
|
||||||
e.preventDefault() |
|
||||||
const {dispatch} = this.props |
|
||||||
|
|
||||||
dispatch(actions.setShowAbortModal(false)) |
|
||||||
} |
|
||||||
|
|
||||||
render() { |
|
||||||
const {uploads, showAbortModal} = this.props |
|
||||||
|
|
||||||
// Show the abort modal.
|
|
||||||
if (showAbortModal) { |
|
||||||
let baseClass = classNames({ |
|
||||||
'abort-upload': true |
|
||||||
}) |
|
||||||
let okIcon = classNames({ |
|
||||||
'fa': true, |
|
||||||
'fa-times': true |
|
||||||
}) |
|
||||||
let cancelIcon = classNames({ |
|
||||||
'fa': true, |
|
||||||
'fa-cloud-upload': true |
|
||||||
}) |
|
||||||
|
|
||||||
return ( |
|
||||||
<ConfirmModal show={ true } |
|
||||||
baseClass={ baseClass } |
|
||||||
text='Abort uploads in progress?' |
|
||||||
icon='fa fa-info-circle mci-amber' |
|
||||||
sub='This cannot be undone!' |
|
||||||
okText='Abort' |
|
||||||
okIcon={ okIcon } |
|
||||||
cancelText='Upload' |
|
||||||
cancelIcon={ cancelIcon } |
|
||||||
okHandler={ this.abortUploads.bind(this) } |
|
||||||
cancelHandler={ this.hideAbort.bind(this) }> |
|
||||||
</ConfirmModal> |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
// If we don't have any files uploading, don't show anything.
|
|
||||||
let numberUploading = Object.keys(uploads).length |
|
||||||
if (numberUploading == 0) |
|
||||||
return ( <noscript></noscript> ) |
|
||||||
|
|
||||||
let totalLoaded = 0 |
|
||||||
let totalSize = 0 |
|
||||||
|
|
||||||
// Iterate over each upload, adding together the total size and that
|
|
||||||
// which has been uploaded.
|
|
||||||
for (var slug in uploads) { |
|
||||||
let upload = uploads[slug] |
|
||||||
totalLoaded += upload.loaded |
|
||||||
totalSize += upload.size |
|
||||||
} |
|
||||||
|
|
||||||
let percent = (totalLoaded / totalSize) * 100 |
|
||||||
|
|
||||||
// If more than one: "Uploading files (5)..."
|
|
||||||
// If only one: "Uploading myfile.txt..."
|
|
||||||
let text = 'Uploading ' + (numberUploading == 1 ? `'${uploads[Object.keys(uploads)[0]].name}'` : `files (${numberUploading})`) + '...' |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="alert alert-info progress animated fadeInUp "> |
|
||||||
<button type="button" className="close" onClick={ this.showAbort.bind(this) }> |
|
||||||
<span>×</span> |
|
||||||
</button> |
|
||||||
<div className="text-center"> |
|
||||||
<small>{ text }</small> |
|
||||||
</div> |
|
||||||
<ProgressBar now={ percent } /> |
|
||||||
<div className="text-center"> |
|
||||||
<small>{ humanize.filesize(totalLoaded) } ({ percent.toFixed(2) } %)</small> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export default connect(state => { |
|
||||||
return { |
|
||||||
uploads: state.uploads, |
|
||||||
showAbortModal: state.showAbortModal |
|
||||||
} |
|
||||||
})(UploadModal) |
|
@ -0,0 +1,19 @@ |
|||||||
|
/* |
||||||
|
* 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 { createSelector } from "reselect" |
||||||
|
|
||||||
|
export const getCurrentPrefix = state => state.objects.currentPrefix |
@ -0,0 +1,75 @@ |
|||||||
|
/* |
||||||
|
* 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 classNames from "classnames" |
||||||
|
import { connect } from "react-redux" |
||||||
|
import ConfirmModal from "../browser/ConfirmModal" |
||||||
|
import * as uploadsActions from "./actions" |
||||||
|
|
||||||
|
export class AbortConfirmModal extends React.Component { |
||||||
|
abortUploads() { |
||||||
|
const { abort, uploads } = this.props |
||||||
|
for (var slug in uploads) { |
||||||
|
abort(slug) |
||||||
|
} |
||||||
|
} |
||||||
|
render() { |
||||||
|
const { hideAbort } = this.props |
||||||
|
let baseClass = classNames({ |
||||||
|
"abort-upload": true |
||||||
|
}) |
||||||
|
let okIcon = classNames({ |
||||||
|
fa: true, |
||||||
|
"fa-times": true |
||||||
|
}) |
||||||
|
let cancelIcon = classNames({ |
||||||
|
fa: true, |
||||||
|
"fa-cloud-upload": true |
||||||
|
}) |
||||||
|
|
||||||
|
return ( |
||||||
|
<ConfirmModal |
||||||
|
show={true} |
||||||
|
baseClass={baseClass} |
||||||
|
text="Abort uploads in progress?" |
||||||
|
icon="fa fa-info-circle mci-amber" |
||||||
|
sub="This cannot be undone!" |
||||||
|
okText="Abort" |
||||||
|
okIcon={okIcon} |
||||||
|
cancelText="Upload" |
||||||
|
cancelIcon={cancelIcon} |
||||||
|
okHandler={this.abortUploads.bind(this)} |
||||||
|
cancelHandler={hideAbort} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
return { |
||||||
|
uploads: state.uploads.files |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
abort: slug => dispatch(uploadsActions.abortUpload(slug)), |
||||||
|
hideAbort: () => dispatch(uploadsActions.hideAbortModal()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(AbortConfirmModal) |
@ -0,0 +1,91 @@ |
|||||||
|
/* |
||||||
|
* 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 humanize from "humanize" |
||||||
|
import classNames from "classnames" |
||||||
|
import { connect } from "react-redux" |
||||||
|
|
||||||
|
import { ProgressBar } from "react-bootstrap" |
||||||
|
import AbortConfirmModal from "./AbortConfirmModal" |
||||||
|
import * as uploadsActions from "./actions" |
||||||
|
|
||||||
|
export class UploadModal extends React.Component { |
||||||
|
render() { |
||||||
|
const { uploads, showAbort, showAbortModal } = this.props |
||||||
|
if (showAbort) { |
||||||
|
return <AbortConfirmModal /> |
||||||
|
} |
||||||
|
|
||||||
|
// If we don't have any files uploading, don't show anything.
|
||||||
|
let numberUploading = Object.keys(uploads).length |
||||||
|
if (numberUploading == 0) return <noscript /> |
||||||
|
|
||||||
|
let totalLoaded = 0 |
||||||
|
let totalSize = 0 |
||||||
|
|
||||||
|
// Iterate over each upload, adding together the total size and that
|
||||||
|
// which has been uploaded.
|
||||||
|
for (var slug in uploads) { |
||||||
|
let upload = uploads[slug] |
||||||
|
totalLoaded += upload.loaded |
||||||
|
totalSize += upload.size |
||||||
|
} |
||||||
|
|
||||||
|
let percent = totalLoaded / totalSize * 100 |
||||||
|
|
||||||
|
// If more than one: "Uploading files (5)..."
|
||||||
|
// If only one: "Uploading myfile.txt..."
|
||||||
|
let text = |
||||||
|
"Uploading " + |
||||||
|
(numberUploading == 1 |
||||||
|
? `'${uploads[Object.keys(uploads)[0]].name}'` |
||||||
|
: `files (${numberUploading})`) + |
||||||
|
"..." |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="alert alert-info progress animated fadeInUp "> |
||||||
|
<button type="button" className="close" onClick={showAbortModal}> |
||||||
|
<span>×</span> |
||||||
|
</button> |
||||||
|
<div className="text-center"> |
||||||
|
<small>{text}</small> |
||||||
|
</div> |
||||||
|
<ProgressBar now={percent} /> |
||||||
|
<div className="text-center"> |
||||||
|
<small> |
||||||
|
{humanize.filesize(totalLoaded)} ({percent.toFixed(2)} %) |
||||||
|
</small> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
return { |
||||||
|
uploads: state.uploads.files, |
||||||
|
showAbort: state.uploads.showAbortModal |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
showAbortModal: () => dispatch(uploadsActions.showAbortModal()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(UploadModal) |
@ -0,0 +1,49 @@ |
|||||||
|
/* |
||||||
|
* 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 { AbortConfirmModal } from "../AbortConfirmModal" |
||||||
|
|
||||||
|
describe("AbortConfirmModal", () => { |
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow(<AbortConfirmModal />) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call abort for every upload when Abort is clicked", () => { |
||||||
|
const abort = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<AbortConfirmModal |
||||||
|
uploads={{ |
||||||
|
"a-b/-test1": { size: 100, loaded: 50, name: "test1" }, |
||||||
|
"a-b/-test2": { size: 100, loaded: 50, name: "test2" } |
||||||
|
}} |
||||||
|
abort={abort} |
||||||
|
/> |
||||||
|
) |
||||||
|
wrapper.instance().abortUploads() |
||||||
|
expect(abort.mock.calls.length).toBe(2) |
||||||
|
expect(abort.mock.calls[0][0]).toBe("a-b/-test1") |
||||||
|
expect(abort.mock.calls[1][0]).toBe("a-b/-test2") |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call hideAbort when cancel is clicked", () => { |
||||||
|
const hideAbort = jest.fn() |
||||||
|
const wrapper = shallow(<AbortConfirmModal hideAbort={hideAbort} />) |
||||||
|
wrapper.find("ConfirmModal").prop("cancelHandler")() |
||||||
|
expect(hideAbort).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,56 @@ |
|||||||
|
/* |
||||||
|
* 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 { UploadModal } from "../UploadModal" |
||||||
|
|
||||||
|
describe("UploadModal", () => { |
||||||
|
it("should render without crashing", () => { |
||||||
|
shallow(<UploadModal uploads={{}} />) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should render AbortConfirmModal when showAbort is true", () => { |
||||||
|
const wrapper = shallow(<UploadModal uploads={{}} showAbort={true} />) |
||||||
|
expect(wrapper.find("Connect(AbortConfirmModal)").length).toBe(1) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should render nothing when there are no files being uploaded", () => { |
||||||
|
const wrapper = shallow(<UploadModal uploads={{}} />) |
||||||
|
expect(wrapper.find("noscript").length).toBe(1) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should show upload progress when one or more files are being uploaded", () => { |
||||||
|
const wrapper = shallow( |
||||||
|
<UploadModal |
||||||
|
uploads={{ "a-b/-test": { size: 100, loaded: 50, name: "test" } }} |
||||||
|
/> |
||||||
|
) |
||||||
|
expect(wrapper.find("ProgressBar").length).toBe(1) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should call showAbortModal when close button is clicked", () => { |
||||||
|
const showAbortModal = jest.fn() |
||||||
|
const wrapper = shallow( |
||||||
|
<UploadModal |
||||||
|
uploads={{ "a-b/-test": { size: 100, loaded: 50, name: "test" } }} |
||||||
|
showAbortModal={showAbortModal} |
||||||
|
/> |
||||||
|
) |
||||||
|
wrapper.find("button").simulate("click") |
||||||
|
expect(showAbortModal).toHaveBeenCalled() |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,166 @@ |
|||||||
|
/* |
||||||
|
* 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 configureStore from "redux-mock-store" |
||||||
|
import thunk from "redux-thunk" |
||||||
|
import * as uploadsActions from "../actions" |
||||||
|
|
||||||
|
const middlewares = [thunk] |
||||||
|
const mockStore = configureStore(middlewares) |
||||||
|
|
||||||
|
describe("Uploads actions", () => { |
||||||
|
it("creates uploads/ADD action", () => { |
||||||
|
const store = mockStore() |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "uploads/ADD", |
||||||
|
slug: "a-b-c", |
||||||
|
size: 100, |
||||||
|
name: "test" |
||||||
|
} |
||||||
|
] |
||||||
|
store.dispatch(uploadsActions.add("a-b-c", 100, "test")) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
|
||||||
|
it("creates uploads/UPDATE_PROGRESS action", () => { |
||||||
|
const store = mockStore() |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "uploads/UPDATE_PROGRESS", |
||||||
|
slug: "a-b-c", |
||||||
|
loaded: 50 |
||||||
|
} |
||||||
|
] |
||||||
|
store.dispatch(uploadsActions.updateProgress("a-b-c", 50)) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
|
||||||
|
it("creates uploads/STOP action", () => { |
||||||
|
const store = mockStore() |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "uploads/STOP", |
||||||
|
slug: "a-b-c" |
||||||
|
} |
||||||
|
] |
||||||
|
store.dispatch(uploadsActions.stop("a-b-c")) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
|
||||||
|
it("creates uploads/SHOW_ABORT_MODAL action", () => { |
||||||
|
const store = mockStore() |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "uploads/SHOW_ABORT_MODAL", |
||||||
|
show: true |
||||||
|
} |
||||||
|
] |
||||||
|
store.dispatch(uploadsActions.showAbortModal()) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
|
||||||
|
describe("uploadFile", () => { |
||||||
|
const file = new Blob(["file content"], { |
||||||
|
type: "text/plain" |
||||||
|
}) |
||||||
|
file.name = "file1" |
||||||
|
|
||||||
|
it("creates alerts/SET action when currentBucket is not present", () => { |
||||||
|
const store = mockStore({ |
||||||
|
buckets: { currentBucket: "" } |
||||||
|
}) |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "alert/SET", |
||||||
|
alert: { |
||||||
|
id: 0, |
||||||
|
type: "danger", |
||||||
|
message: "Please choose a bucket before trying to upload files." |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
const file = new Blob(["file content"], { type: "text/plain" }) |
||||||
|
store.dispatch(uploadsActions.uploadFile(file)) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
|
||||||
|
it("creates uploads/ADD action before uploading the file", () => { |
||||||
|
const store = mockStore({ |
||||||
|
buckets: { currentBucket: "test1" }, |
||||||
|
objects: { currentPrefix: "pre1/" } |
||||||
|
}) |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "uploads/ADD", |
||||||
|
slug: "test1-pre1/-file1", |
||||||
|
size: file.size, |
||||||
|
name: file.name |
||||||
|
} |
||||||
|
] |
||||||
|
store.dispatch(uploadsActions.uploadFile(file)) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should open and send XMLHttpRequest", () => { |
||||||
|
const open = jest.fn() |
||||||
|
const send = jest.fn() |
||||||
|
const xhrMockClass = () => ({ |
||||||
|
open: open, |
||||||
|
send: send, |
||||||
|
setRequestHeader: jest.fn(), |
||||||
|
upload: { |
||||||
|
addEventListener: jest.fn() |
||||||
|
} |
||||||
|
}) |
||||||
|
window.XMLHttpRequest = jest.fn().mockImplementation(xhrMockClass) |
||||||
|
const store = mockStore({ |
||||||
|
buckets: { currentBucket: "test1" }, |
||||||
|
objects: { currentPrefix: "pre1/" } |
||||||
|
}) |
||||||
|
store.dispatch(uploadsActions.uploadFile(file)) |
||||||
|
expect(open).toHaveBeenCalledWith( |
||||||
|
"PUT", |
||||||
|
"https://localhost:8080/upload/test1/pre1/file1", |
||||||
|
true |
||||||
|
) |
||||||
|
expect(send).toHaveBeenCalledWith(file) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("creates uploads/STOP and uploads/SHOW_ABORT_MODAL after abortUpload", () => { |
||||||
|
const store = mockStore() |
||||||
|
const expectedActions = [ |
||||||
|
{ |
||||||
|
type: "uploads/STOP", |
||||||
|
slug: "a-b/-c" |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: "uploads/SHOW_ABORT_MODAL", |
||||||
|
show: false |
||||||
|
} |
||||||
|
] |
||||||
|
store.dispatch(uploadsActions.abortUpload("a-b/-c")) |
||||||
|
const actions = store.getActions() |
||||||
|
expect(actions).toEqual(expectedActions) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,87 @@ |
|||||||
|
/* |
||||||
|
* 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 reducer from "../reducer" |
||||||
|
import * as actions from "../actions" |
||||||
|
|
||||||
|
describe("uploads reducer", () => { |
||||||
|
it("should return the initial state", () => { |
||||||
|
const initialState = reducer(undefined, {}) |
||||||
|
expect(initialState).toEqual({ |
||||||
|
files: {}, |
||||||
|
showAbortModal: false |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should handle ADD", () => { |
||||||
|
const newState = reducer(undefined, { |
||||||
|
type: actions.ADD, |
||||||
|
slug: "a-b-c", |
||||||
|
size: 100, |
||||||
|
name: "test" |
||||||
|
}) |
||||||
|
expect(newState.files).toEqual({ |
||||||
|
"a-b-c": { loaded: 0, size: 100, name: "test" } |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should handle UPDATE_PROGRESS", () => { |
||||||
|
const newState = reducer( |
||||||
|
{ |
||||||
|
files: { "a-b-c": { loaded: 0, size: 100, name: "test" } } |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: actions.UPDATE_PROGRESS, |
||||||
|
slug: "a-b-c", |
||||||
|
loaded: 50 |
||||||
|
} |
||||||
|
) |
||||||
|
expect(newState.files).toEqual({ |
||||||
|
"a-b-c": { loaded: 50, size: 100, name: "test" } |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should handle STOP", () => { |
||||||
|
const newState = reducer( |
||||||
|
{ |
||||||
|
files: { |
||||||
|
"a-b-c": { loaded: 70, size: 100, name: "test1" }, |
||||||
|
"x-y-z": { loaded: 50, size: 100, name: "test2" } |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: actions.STOP, |
||||||
|
slug: "a-b-c" |
||||||
|
} |
||||||
|
) |
||||||
|
expect(newState.files).toEqual({ |
||||||
|
"x-y-z": { loaded: 50, size: 100, name: "test2" } |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it("should handle SHOW_ABORT_MODAL", () => { |
||||||
|
const newState = reducer( |
||||||
|
{ |
||||||
|
showAbortModal: false |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: actions.SHOW_ABORT_MODAL, |
||||||
|
show: true |
||||||
|
} |
||||||
|
) |
||||||
|
expect(newState.showAbortModal).toBeTruthy() |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,172 @@ |
|||||||
|
/* |
||||||
|
* 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 Moment from "moment" |
||||||
|
import storage from "local-storage-fallback" |
||||||
|
import * as alertActions from "../alert/actions" |
||||||
|
import * as objectsActions from "../objects/actions" |
||||||
|
import { getCurrentBucket } from "../buckets/selectors" |
||||||
|
import { getCurrentPrefix } from "../objects/selectors" |
||||||
|
import { minioBrowserPrefix } from "../constants" |
||||||
|
|
||||||
|
export const ADD = "uploads/ADD" |
||||||
|
export const UPDATE_PROGRESS = "uploads/UPDATE_PROGRESS" |
||||||
|
export const STOP = "uploads/STOP" |
||||||
|
export const SHOW_ABORT_MODAL = "uploads/SHOW_ABORT_MODAL" |
||||||
|
|
||||||
|
export const add = (slug, size, name) => ({ |
||||||
|
type: ADD, |
||||||
|
slug, |
||||||
|
size, |
||||||
|
name |
||||||
|
}) |
||||||
|
|
||||||
|
export const updateProgress = (slug, loaded) => ({ |
||||||
|
type: UPDATE_PROGRESS, |
||||||
|
slug, |
||||||
|
loaded |
||||||
|
}) |
||||||
|
|
||||||
|
export const stop = slug => ({ |
||||||
|
type: STOP, |
||||||
|
slug |
||||||
|
}) |
||||||
|
|
||||||
|
export const showAbortModal = () => ({ |
||||||
|
type: SHOW_ABORT_MODAL, |
||||||
|
show: true |
||||||
|
}) |
||||||
|
|
||||||
|
export const hideAbortModal = () => ({ |
||||||
|
type: SHOW_ABORT_MODAL, |
||||||
|
show: false |
||||||
|
}) |
||||||
|
|
||||||
|
let requests = {} |
||||||
|
|
||||||
|
export const addUpload = (xhr, slug, size, name) => { |
||||||
|
return function(dispatch) { |
||||||
|
requests[slug] = xhr |
||||||
|
dispatch(add(slug, size, name)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const abortUpload = slug => { |
||||||
|
return function(dispatch) { |
||||||
|
const xhr = requests[slug] |
||||||
|
if (xhr) { |
||||||
|
xhr.abort() |
||||||
|
} |
||||||
|
dispatch(stop(slug)) |
||||||
|
dispatch(hideAbortModal()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const uploadFile = file => { |
||||||
|
return function(dispatch, getState) { |
||||||
|
const state = getState() |
||||||
|
const currentBucket = getCurrentBucket(state) |
||||||
|
if (!currentBucket) { |
||||||
|
dispatch( |
||||||
|
alertActions.set({ |
||||||
|
type: "danger", |
||||||
|
message: "Please choose a bucket before trying to upload files." |
||||||
|
}) |
||||||
|
) |
||||||
|
return |
||||||
|
} |
||||||
|
const currentPrefix = getCurrentPrefix(state) |
||||||
|
const objectName = `${currentPrefix}${file.name}` |
||||||
|
const uploadUrl = `${ |
||||||
|
window.location.origin |
||||||
|
}${minioBrowserPrefix}/upload/${currentBucket}/${objectName}` |
||||||
|
const slug = `${currentBucket}-${currentPrefix}-${file.name}` |
||||||
|
|
||||||
|
let xhr = new XMLHttpRequest() |
||||||
|
xhr.open("PUT", uploadUrl, true) |
||||||
|
xhr.withCredentials = false |
||||||
|
const token = storage.getItem("token") |
||||||
|
if (token) { |
||||||
|
xhr.setRequestHeader( |
||||||
|
"Authorization", |
||||||
|
"Bearer " + storage.getItem("token") |
||||||
|
) |
||||||
|
} |
||||||
|
xhr.setRequestHeader( |
||||||
|
"x-amz-date", |
||||||
|
Moment() |
||||||
|
.utc() |
||||||
|
.format("YYYYMMDDTHHmmss") + "Z" |
||||||
|
) |
||||||
|
|
||||||
|
dispatch(addUpload(xhr, slug, file.size, file.name)) |
||||||
|
|
||||||
|
xhr.onload = function(event) { |
||||||
|
if (xhr.status == 401 || xhr.status == 403) { |
||||||
|
dispatch(hideAbortModal()) |
||||||
|
dispatch(stop(slug)) |
||||||
|
dispatch( |
||||||
|
alertActions.set({ |
||||||
|
type: "danger", |
||||||
|
message: "Unauthorized request." |
||||||
|
}) |
||||||
|
) |
||||||
|
} |
||||||
|
if (xhr.status == 500) { |
||||||
|
dispatch(hideAbortModal()) |
||||||
|
dispatch(stop(slug)) |
||||||
|
dispatch( |
||||||
|
alertActions.set({ |
||||||
|
type: "danger", |
||||||
|
message: xhr.responseText |
||||||
|
}) |
||||||
|
) |
||||||
|
} |
||||||
|
if (xhr.status == 200) { |
||||||
|
dispatch(hideAbortModal()) |
||||||
|
dispatch(stop(slug)) |
||||||
|
dispatch( |
||||||
|
alertActions.set({ |
||||||
|
type: "success", |
||||||
|
message: "File '" + file.name + "' uploaded successfully." |
||||||
|
}) |
||||||
|
) |
||||||
|
dispatch(objectsActions.selectPrefix(currentPrefix)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
xhr.upload.addEventListener("error", event => { |
||||||
|
dispatch(stop(slug)) |
||||||
|
dispatch( |
||||||
|
alertActions.set({ |
||||||
|
type: "danger", |
||||||
|
message: "Error occurred uploading '" + file.name + "'." |
||||||
|
}) |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
xhr.upload.addEventListener("progress", event => { |
||||||
|
if (event.lengthComputable) { |
||||||
|
let loaded = event.loaded |
||||||
|
let total = event.total |
||||||
|
// Update the counter
|
||||||
|
dispatch(updateProgress(slug, loaded)) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
xhr.send(file) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* 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 * as uploadsActions from "./actions" |
||||||
|
|
||||||
|
const add = (files, action) => ({ |
||||||
|
...files, |
||||||
|
[action.slug]: { |
||||||
|
loaded: 0, |
||||||
|
size: action.size, |
||||||
|
name: action.name |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const updateProgress = (files, action) => ({ |
||||||
|
...files, |
||||||
|
[action.slug]: { |
||||||
|
...files[action.slug], |
||||||
|
loaded: action.loaded |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const stop = (files, action) => { |
||||||
|
const newFiles = Object.assign({}, files) |
||||||
|
delete newFiles[action.slug] |
||||||
|
return newFiles |
||||||
|
} |
||||||
|
|
||||||
|
export default (state = { files: {}, showAbortModal: false }, action) => { |
||||||
|
switch (action.type) { |
||||||
|
case uploadsActions.ADD: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
files: add(state.files, action) |
||||||
|
} |
||||||
|
case uploadsActions.UPDATE_PROGRESS: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
files: updateProgress(state.files, action) |
||||||
|
} |
||||||
|
case uploadsActions.STOP: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
files: stop(state.files, action) |
||||||
|
} |
||||||
|
case uploadsActions.SHOW_ABORT_MODAL: |
||||||
|
return { |
||||||
|
...state, |
||||||
|
showAbortModal: action.show |
||||||
|
} |
||||||
|
default: |
||||||
|
return state |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue