From d3dc1fe2522afd0a96a9895238774099410e5775 Mon Sep 17 00:00:00 2001 From: wangchao Date: Sun, 30 Jul 2017 10:27:54 +0800 Subject: [PATCH 01/12] first version of inspector --- .gitignore | 3 + js/rendererjs/plugins.js | 3 + package.json | 3 +- plugins/Inspector/assets/button.png | Bin 0 -> 906 bytes plugins/Inspector/css/files.css | 626 ++++++++++++++++++ plugins/Inspector/index.html | 19 + plugins/Inspector/js/actions/files.js | 222 +++++++ .../js/components/addfolderbutton.js | 13 + .../js/components/addfolderdialog.js | 30 + .../js/components/allowanceconfirmation.js | 42 ++ .../js/components/allowancedialog.js | 65 ++ plugins/Inspector/js/components/app.js | 17 + .../js/components/contractorstatus.js | 22 + .../Inspector/js/components/deletedialog.js | 31 + .../js/components/directoryinfobar.js | 42 ++ .../Inspector/js/components/downloadlist.js | 20 + .../Inspector/js/components/dragoverlay.js | 12 + plugins/Inspector/js/components/file.js | 67 ++ .../Inspector/js/components/filebrowser.js | 59 ++ .../Inspector/js/components/filecontrols.js | 50 ++ plugins/Inspector/js/components/filedetail.js | 117 ++++ plugins/Inspector/js/components/filelist.js | 155 +++++ .../Inspector/js/components/filetransfers.js | 35 + .../Inspector/js/components/progressbar.js | 22 + .../js/components/redundancystatus.js | 44 ++ .../Inspector/js/components/renamedialog.js | 35 + .../Inspector/js/components/searchbutton.js | 16 + .../Inspector/js/components/searchfield.js | 18 + .../js/components/setallowancebutton.js | 13 + plugins/Inspector/js/components/transfer.js | 22 + .../Inspector/js/components/transferlist.js | 24 + .../js/components/transfersbutton.js | 21 + .../Inspector/js/components/unlockwarning.js | 17 + .../Inspector/js/components/uploadbutton.js | 47 ++ .../Inspector/js/components/uploaddialog.js | 36 + plugins/Inspector/js/components/uploadlist.js | 18 + plugins/Inspector/js/components/usagestats.js | 15 + plugins/Inspector/js/constants/files.js | 62 ++ .../js/containers/addfolderbutton.js | 13 + .../js/containers/addfolderdialog.js | 13 + .../js/containers/allowancedialog.js | 19 + plugins/Inspector/js/containers/app.js | 9 + .../js/containers/contractorstatus.js | 11 + .../Inspector/js/containers/deletedialog.js | 14 + .../Inspector/js/containers/filebrowser.js | 22 + .../Inspector/js/containers/filecontrols.js | 14 + plugins/Inspector/js/containers/filelist.js | 21 + .../Inspector/js/containers/filetransfers.js | 15 + .../Inspector/js/containers/renamedialog.js | 14 + .../Inspector/js/containers/searchbutton.js | 14 + .../Inspector/js/containers/searchfield.js | 15 + .../js/containers/setallowancebutton.js | 13 + .../js/containers/transfersbutton.js | 14 + .../Inspector/js/containers/uploadbutton.js | 14 + .../Inspector/js/containers/uploaddialog.js | 15 + plugins/Inspector/js/containers/usagestats.js | 10 + plugins/Inspector/js/index.js | 33 + .../Inspector/js/reducers/allowancedialog.js | 28 + plugins/Inspector/js/reducers/deletedialog.js | 15 + plugins/Inspector/js/reducers/files.js | 165 +++++ plugins/Inspector/js/reducers/index.js | 16 + plugins/Inspector/js/reducers/renamedialog.js | 15 + plugins/Inspector/js/reducers/wallet.js | 21 + plugins/Inspector/js/sagas/files.js | 324 +++++++++ plugins/Inspector/js/sagas/helpers.js | 271 ++++++++ plugins/Inspector/js/sagas/index.js | 7 + 66 files changed, 3217 insertions(+), 1 deletion(-) create mode 100644 plugins/Inspector/assets/button.png create mode 100644 plugins/Inspector/css/files.css create mode 100644 plugins/Inspector/index.html create mode 100644 plugins/Inspector/js/actions/files.js create mode 100644 plugins/Inspector/js/components/addfolderbutton.js create mode 100644 plugins/Inspector/js/components/addfolderdialog.js create mode 100644 plugins/Inspector/js/components/allowanceconfirmation.js create mode 100644 plugins/Inspector/js/components/allowancedialog.js create mode 100644 plugins/Inspector/js/components/app.js create mode 100644 plugins/Inspector/js/components/contractorstatus.js create mode 100644 plugins/Inspector/js/components/deletedialog.js create mode 100644 plugins/Inspector/js/components/directoryinfobar.js create mode 100644 plugins/Inspector/js/components/downloadlist.js create mode 100644 plugins/Inspector/js/components/dragoverlay.js create mode 100644 plugins/Inspector/js/components/file.js create mode 100644 plugins/Inspector/js/components/filebrowser.js create mode 100644 plugins/Inspector/js/components/filecontrols.js create mode 100644 plugins/Inspector/js/components/filedetail.js create mode 100644 plugins/Inspector/js/components/filelist.js create mode 100644 plugins/Inspector/js/components/filetransfers.js create mode 100644 plugins/Inspector/js/components/progressbar.js create mode 100644 plugins/Inspector/js/components/redundancystatus.js create mode 100644 plugins/Inspector/js/components/renamedialog.js create mode 100644 plugins/Inspector/js/components/searchbutton.js create mode 100644 plugins/Inspector/js/components/searchfield.js create mode 100644 plugins/Inspector/js/components/setallowancebutton.js create mode 100644 plugins/Inspector/js/components/transfer.js create mode 100644 plugins/Inspector/js/components/transferlist.js create mode 100644 plugins/Inspector/js/components/transfersbutton.js create mode 100644 plugins/Inspector/js/components/unlockwarning.js create mode 100644 plugins/Inspector/js/components/uploadbutton.js create mode 100644 plugins/Inspector/js/components/uploaddialog.js create mode 100644 plugins/Inspector/js/components/uploadlist.js create mode 100644 plugins/Inspector/js/components/usagestats.js create mode 100644 plugins/Inspector/js/constants/files.js create mode 100644 plugins/Inspector/js/containers/addfolderbutton.js create mode 100644 plugins/Inspector/js/containers/addfolderdialog.js create mode 100644 plugins/Inspector/js/containers/allowancedialog.js create mode 100644 plugins/Inspector/js/containers/app.js create mode 100644 plugins/Inspector/js/containers/contractorstatus.js create mode 100644 plugins/Inspector/js/containers/deletedialog.js create mode 100644 plugins/Inspector/js/containers/filebrowser.js create mode 100644 plugins/Inspector/js/containers/filecontrols.js create mode 100644 plugins/Inspector/js/containers/filelist.js create mode 100644 plugins/Inspector/js/containers/filetransfers.js create mode 100644 plugins/Inspector/js/containers/renamedialog.js create mode 100644 plugins/Inspector/js/containers/searchbutton.js create mode 100644 plugins/Inspector/js/containers/searchfield.js create mode 100644 plugins/Inspector/js/containers/setallowancebutton.js create mode 100644 plugins/Inspector/js/containers/transfersbutton.js create mode 100644 plugins/Inspector/js/containers/uploadbutton.js create mode 100644 plugins/Inspector/js/containers/uploaddialog.js create mode 100644 plugins/Inspector/js/containers/usagestats.js create mode 100644 plugins/Inspector/js/index.js create mode 100644 plugins/Inspector/js/reducers/allowancedialog.js create mode 100644 plugins/Inspector/js/reducers/deletedialog.js create mode 100644 plugins/Inspector/js/reducers/files.js create mode 100644 plugins/Inspector/js/reducers/index.js create mode 100644 plugins/Inspector/js/reducers/renamedialog.js create mode 100644 plugins/Inspector/js/reducers/wallet.js create mode 100644 plugins/Inspector/js/sagas/files.js create mode 100644 plugins/Inspector/js/sagas/helpers.js create mode 100644 plugins/Inspector/js/sagas/index.js diff --git a/.gitignore b/.gitignore index 1f438a47..68365c88 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ package-lock.json # IntelliJ .idea *.iml + +#MAC +.DS* diff --git a/js/rendererjs/plugins.js b/js/rendererjs/plugins.js index d6b416ca..2d9d5b50 100644 --- a/js/rendererjs/plugins.js +++ b/js/rendererjs/plugins.js @@ -150,6 +150,9 @@ export const pushToTop = (plugins, target) => export const getOrderedPlugins = (path, homePlugin) => { let plugins = scanFolder(path) + // Push the Terminal plugin to the bottom + plugins = pushToBottom(plugins, 'Inspector') + // Push the Terminal plugin to the bottom plugins = pushToBottom(plugins, 'Terminal') diff --git a/package.json b/package.json index d5b1c16c..a30eed27 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "spectron": "^3.6.0", "uglify-js": "git://github.com/mishoo/UglifyJS2.git#harmony-v2.8.22", "uglifyjs-webpack-plugin": "^0.4.3", - "webpack": "^2.5.1" + "webpack": "^2.5.1", + "rc-tooltip": "^3.4.7" }, "dependencies": { "babel-polyfill": "^6.9.1", diff --git a/plugins/Inspector/assets/button.png b/plugins/Inspector/assets/button.png new file mode 100644 index 0000000000000000000000000000000000000000..56e6343aefc7b51569a5addcc76dbd0f32e94445 GIT binary patch literal 906 zcmV;519kj~P)MeM{*EhSq-K@8Za2r4!rSZD+b z5i9~KYAy&0u~5+11k{VM7!QxV*~{+SeDme8KbYcXcEA1p|DB!Pot*=}v;miZH^2xm z=2%97B5>4mequBMcY!~+i%u&00_t$04H&||@N(%7ro@4*z*pqNj{(0V1d8}t&A=DNKYK1jp5mmU;IPjo;4!esb1o`mHDCj#+b_1X4cDEG z0$B`L5Ay>rIxmc zNiH608_ejjHT(_MN}1l#T@sWdzXZaM6?A4{Gsm}|jz z_ZoFb5a4!-*u9Rit1i<5X5OvAF<>nQ9rig3booxC76qVFDE7sS+n-6;$HmA@{J`(@ zRX^D$GEXMND`FnT#4Z6W6=?=MpB%U3P$}M)qt`J&lViXYOZ_0Q12fw!ilzaFfooJx zcoqpH9`p?80`@pPN{Q%~gR}l|k3}^xF0kdQE>f#~HAb1t~WJW6iNBbnCd%!ke zmq6U_z5+=ba8M|2pRYjD26PC->;)eB3MM@U#HZL^052V54Fk_GQ+M8wHegQd_XFU9 z@G&!?{WhphZh&?I+T;dkC!kGkfOZ1f .badge { + background: #CC0033; + border-radius: 50%; + position: absolute; + top: 5px; + right: 25px; + width: 20px; + height: 20px; + vertical-align: center; + display: flex !important; + align-items: center; + justify-content: center; + font-size: 11px; + animation-name: notify-animation; + animation-duration: 0.5s; +} +.files-toolbar .buttons div[class$="button"]:hover { + opacity: 0.5; +} +.files-usage-info { + margin-left: 15px; +} +.files-toolbar .buttons div span { + display: block; + padding: 0; + margin-top: 2px; + margin-bottom: 2px; + font-size: 12px; + color: #f5f5f5; + user-select: none; +} +.files-toolbar .buttons div i { + color: #00CBA0; +} +.file-transfers .close-button { + position: absolute; + top: 12px; + right: 25px; + cursor: pointer; +} +.file-list h2 { + text-align: center; +} +.file-list { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + overflow-y: auto; + background-color: #C5C5C5; + margin: 0; + cursor: default; + -webkit-user-select: none; + font-size: 14px; +} +.file-list ul { + margin: 0; + list-style-type: none; + justify-content: flex-start; + width: 100%; + padding: 0; +} +.file-list .directory-infobar { + height: 35px; +} +.filesize { + font-size: 12px; +} +.file-list li { + display: flex; + height: 22px; + align-items: center; + justify-content: space-between; + background-color: #ECECEC; + padding-left: 10px; + padding-right: 10px; +} +.file-list .file-info { + display: flex; + align-items: center; + justify-content: center; +} +.file-buttons { + margin-bottom: 5px; + cursor: pointer; +} +.filename { + display: flex; + align-items: center; + justify-content: flex-start; +} +.filebrowser-file:hover { + background-color: #F5F5F5; +} +.file-list li i { + color: #00CBA0; + margin-left: 10px; + margin-right: 10px; +} +.search-field { + margin-top: 15px; + margin-bottom: 15px; + width: 80%; + display: flex; + align-items: center; + justify-content: center; +} +.search-field i { + display: inline-block; + margin-left: 5px; + margin-right: 5px; +} +.search-field input { + display: inline-block; + width: 80%; + height: 25px; + border: 2px solid #00CBA0; +} +.drag-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + display: flex; + align-items: center; + justify-content: center; +} + +.drag-overlay span { + color: #FFFFFF; + text-align: center; +} +.drag-overlay i { + color: #00CBA0; +} +.drag-overlay h3 { + margin-top: .2em; + margin-bottom: 0em; +} + +.upload-dialog { + padding-left: 25px; + padding-right: 25px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #ECECEC; + width: 280px; + height: 190px; +} +.addfolder-dialog { + padding: 25px 25px 25px 25px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #ECECEC; + width: 300px; + height: 200px; + border-radius: 4px; +} +.upload-dialog-buttons { + margin-top: 20px; +} +.upload-dialog-buttons > * { + margin-left: 5px; + margin-right: 5px; +} +.upload-dialog h1 { + margin: 0; + padding: 0; + margin-top: 10px; + margin-bottom: 10px; +} +.storage-plans { + display: flex; + justify-content: center; + align-items: center; + flex-direction: row; + margin-top: 10px; + margin-bottom: 10px; + padding-bottom: 10px; + padding-top: 10px; +} +.plan { + margin: 5px 5px 5px 5px; + width: 100px; + height: 80px; + text-align: center; +} +.allowance-dialog h3 { + padding: 0; + margin: 0; + margin-bottom: 15px; +} +.allowance-dialog.unlock-warning { + justify-content: center; +} +.unlock-warning-head { + margin-top: 10px; +} +.allowance-message > .footnote { + font-size: 14px; +} +.allowance-message { + margin-left: 20px; + margin-right: 20px; + margin-bottom: 25px; +} +.allowance-dialog p { + margin-top: 10px; + text-align: left; +} +.allowance-warning { + color: #A71A18; +} +.allowance-buttons button { + width: 100px; + height: 25px; + margin-left: 5px; + margin-right: 5px; +} +.allowance-dialog form { + width: 400px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} +.allowance-dialog form input { + display: inline-block; + text-align: center; + margin-top: 10px; + margin-bottom: 10px; + margin-right: 4px; + width: 187px; +} +.estimates { + margin-top: 35px; + width: 230px; +} +.allowance-dialog form { + margin-top: 10px; + margin-bottom: 10px; +} +.allowance-input { + padding-right: 80px; +} + +@keyframes allowance-dialog-popin { + from { transform: scale(0.2) } + to { transform: scale(1) } +} + +.allowance-dialog { + padding: 40px 40px 40px 40px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + background-color: #ECECEC; + width: 50%; + min-width: 700px; + height: 400px; + z-index: 10; + + animation-name: allowance-dialog-popin; + animation-duration: 0.2s; + + border-radius: 4px; +} + +.allowance-input span { + display: inline-block; +} +.estimate-label { + text-align: left; +} +.estimate-content { + text-align: right; +} +.estimates td { + padding-left: 5px; + padding-right: 5px; +} +.delete-dialog { + margin-left: 15px; + margin-right: 15px; + padding-left: 15px; + padding-right: 15px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #ECECEC; + width: 50%; + height: 50%; +} +.delete-buttons { + margin-top: 35px; +} +.delete-buttons button { + margin-left: 10px; + margin-right: 10px; +} +.rename-dialog { + margin-left: 15px; + margin-right: 15px; + padding-left: 15px; + padding-right: 15px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #ECECEC; + width: 50%; + height: 50%; +} +.rename-buttons { + margin: auto; + margin-top: 35px; + text-align: center; +} +.rename-buttons button { + margin-left: 10px; + margin-right: 10px; +} +.file-buttons { + display: flex; +} +.file-buttons i { + color: #00CBA0; +} +.unlock-dialog { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #C5C5C5; +} +.file-buttons div { + margin: 10px 5px 5px 10px; +} +.rename-field { + margin: auto; + margin-top: 15px; + margin-bottom: 15px; + width: 80%; + display: flex; + align-items: center; + justify-content: center; +} +.rename-field input { + display: inline-block; + width: 80%; + height: 25px; + border: 2px solid #00CBA0; +} +.rename-form { + display: inline-block; + width: 80%; +} +.buttons { + width: 500px; + display: flex; +} diff --git a/plugins/Inspector/index.html b/plugins/Inspector/index.html new file mode 100644 index 00000000..adedf608 --- /dev/null +++ b/plugins/Inspector/index.html @@ -0,0 +1,19 @@ + + + + Files + + + + + + + + + +
+ + + + + diff --git a/plugins/Inspector/js/actions/files.js b/plugins/Inspector/js/actions/files.js new file mode 100644 index 00000000..f0af749b --- /dev/null +++ b/plugins/Inspector/js/actions/files.js @@ -0,0 +1,222 @@ +import * as constants from '../constants/files.js' + +export const getWalletLockstate = () => ({ + type: constants.GET_WALLET_LOCKSTATE, +}) +export const receiveWalletLockstate = (unlocked) => ({ + type: constants.RECEIVE_WALLET_LOCKSTATE, + unlocked, +}) +export const getWalletSyncstate = () => ({ + type: constants.GET_WALLET_SYNCSTATE, +}) +export const setWalletSyncstate = (synced) => ({ + type: constants.SET_WALLET_SYNCSTATE, + synced, +}) +export const getFiles = () => ({ + type: constants.GET_FILES, +}) +export const receiveFiles = (files) => ({ + type: constants.RECEIVE_FILES, + files, +}) +export const getAllowance = () => ({ + type: constants.GET_ALLOWANCE, +}) +export const receiveAllowance = (allowance) => ({ + type: constants.RECEIVE_ALLOWANCE, + allowance, +}) +export const receiveSpending = (spending) => ({ + type: constants.RECEIVE_SPENDING, + spending, +}) +export const getStorageEstimate = (funds) => ({ + type: constants.GET_STORAGE_ESTIMATE, + funds, +}) +export const setStorageEstimate = (estimate) => ({ + type: constants.SET_STORAGE_ESTIMATE, + estimate, +}) +export const setFeeEstimate = (estimate) => ({ + type: constants.SET_FEE_ESTIMATE, + estimate, +}) +export const setAllowanceCompleted = () => ({ + type: constants.SET_ALLOWANCE_COMPLETED, +}) +export const setAllowance = (funds) => ({ + type: constants.SET_ALLOWANCE, + funds, +}) +export const getWalletBalance = () => ({ + type: constants.GET_WALLET_BALANCE, +}) +export const receiveWalletBalance = (balance) => ({ + type: constants.RECEIVE_WALLET_BALANCE, + balance, +}) +export const showAllowanceDialog = () => ({ + type: constants.SHOW_ALLOWANCE_DIALOG, +}) +export const closeAllowanceDialog = () => ({ + type: constants.CLOSE_ALLOWANCE_DIALOG, +}) +export const setPath = (path) => ({ + type: constants.SET_PATH, + path, +}) +export const setSearchText = (text, path) => ({ + type: constants.SET_SEARCH_TEXT, + text, + path, +}) +export const toggleSearchField = () => ({ + type: constants.TOGGLE_SEARCH_FIELD, +}) +export const setDragging = () => ({ + type: constants.SET_DRAGGING, +}) +export const setNotDragging = () => ({ + type: constants.SET_NOT_DRAGGING, +}) +export const showUploadDialog = (source) => ({ + type: constants.SHOW_UPLOAD_DIALOG, + source, +}) +export const hideUploadDialog = () => ({ + type: constants.HIDE_UPLOAD_DIALOG, +}) +export const downloadFile = (file, downloadpath) => ({ + type: constants.DOWNLOAD_FILE, + file, + downloadpath, +}) +export const uploadFile = (siapath, source) => ({ + type: constants.UPLOAD_FILE, + siapath, + source, +}) +export const deleteFile = (file) => ({ + type: constants.DELETE_FILE, + file, +}) +export const uploadFolder = (siapath, source) => ({ + type: constants.UPLOAD_FOLDER, + siapath, + source, +}) +export const getDownloads = () => ({ + type: constants.GET_DOWNLOADS, +}) +export const getUploads = () => ({ + type: constants.GET_UPLOADS, +}) +export const receiveUploads = (uploads) => ({ + type: constants.RECEIVE_UPLOADS, + uploads, +}) +export const receiveDownloads = (downloads) => ({ + type: constants.RECEIVE_DOWNLOADS, + downloads, +}) +export const showFileTransfers = () => ({ + type: constants.SHOW_FILE_TRANSFERS, +}) +export const hideFileTransfers = () => ({ + type: constants.HIDE_FILE_TRANSFERS, +}) +export const toggleFileTransfers = () => ({ + type: constants.TOGGLE_FILE_TRANSFERS, +}) +export const showDeleteDialog = (files) => ({ + type: constants.SHOW_DELETE_DIALOG, + files, +}) +export const hideDeleteDialog = () => ({ + type: constants.HIDE_DELETE_DIALOG, +}) +export const getContractCount = () => ({ + type: constants.GET_CONTRACT_COUNT, +}) +export const setContractCount = (count) => ({ + type: constants.SET_CONTRACT_COUNT, + count, +}) +export const renameFile = (file, newsiapath) => ({ + type: constants.RENAME_FILE, + file, + newsiapath, +}) +export const showRenameDialog = (file) => ({ + type: constants.SHOW_RENAME_DIALOG, + file, +}) +export const hideRenameDialog = () => ({ + type: constants.HIDE_RENAME_DIALOG, +}) +export const selectFile = (file) => ({ + type: constants.SELECT_FILE, + file, +}) +export const selectUpTo = (file) => ({ + type: constants.SELECT_UP_TO, + file, +}) +export const deselectAll = () => ({ + type: constants.DESELECT_ALL, +}) +export const deselectFile = (file) => ({ + type: constants.DESELECT_FILE, + file, +}) +export const clearDownloads = () => ({ + type: constants.CLEAR_DOWNLOADS, +}) +export const showAllowanceConfirmation = (allowance) => ({ + type: constants.SHOW_ALLOWANCE_CONFIRMATION, + allowance, +}) +export const hideAllowanceConfirmation = () => ({ + type: constants.HIDE_ALLOWANCE_CONFIRMATION, +}) +export const fetchData = () => ({ + type: constants.FETCH_DATA, +}) +export const addFolder = (name) => ({ + type: constants.ADD_FOLDER, + name, +}) +export const showAddFolderDialog = () => ({ + type: constants.SHOW_ADD_FOLDER_DIALOG, +}) +export const hideAddFolderDialog = () => ({ + type: constants.HIDE_ADD_FOLDER_DIALOG, +}) +export const setDragUploadEnabled = (enabled) => ({ + type: constants.SET_DRAG_UPLOAD_ENABLED, + enabled, +}) +export const setDragFolderTarget = (target) => ({ + type: constants.SET_DRAG_FOLDER_TARGET, + target, +}) +export const setDragFileOrigin = (origin) => ({ + type: constants.SET_DRAG_FILE_ORIGIN, + origin, +}) +export const renameSiaUIFolder = (source, dest) => ({ + type: constants.RENAME_SIA_UI_FOLDER, + source, + dest, +}) +export const deleteSiaUIFolder = (siapath) => ({ + type: constants.DELETE_SIA_UI_FOLDER, + siapath, +}) +export const showFileDetail = (siapath) => ({ + type: constants.SHOW_FILE_DETAIL, + siapath, +}) diff --git a/plugins/Inspector/js/components/addfolderbutton.js b/plugins/Inspector/js/components/addfolderbutton.js new file mode 100644 index 00000000..8fd0e0bd --- /dev/null +++ b/plugins/Inspector/js/components/addfolderbutton.js @@ -0,0 +1,13 @@ +import React from 'react' + +const AddFolderButton = ({actions}) => { + const handleClick = () => actions.showAddFolderDialog() + return ( +
+ + New Folder +
+ ) +} + +export default AddFolderButton diff --git a/plugins/Inspector/js/components/addfolderdialog.js b/plugins/Inspector/js/components/addfolderdialog.js new file mode 100644 index 00000000..c931e527 --- /dev/null +++ b/plugins/Inspector/js/components/addfolderdialog.js @@ -0,0 +1,30 @@ +import React from 'react' + +const AddFolderDialog = ({actions}) => { + const onConfirmClick = (e) => { + e.preventDefault() + actions.addFolder(e.target.name.value) + actions.hideAddFolderDialog() + } + const onCancelClick = () => actions.hideAddFolderDialog() + return ( +
+
+
+ Enter a name for the new folder: +
+
+
+ +
+
+ + +
+
+
+
+ ) +} + +export default AddFolderDialog diff --git a/plugins/Inspector/js/components/allowanceconfirmation.js b/plugins/Inspector/js/components/allowanceconfirmation.js new file mode 100644 index 00000000..f68e0e5d --- /dev/null +++ b/plugins/Inspector/js/components/allowanceconfirmation.js @@ -0,0 +1,42 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const ConfirmationDialog = ({allowance, onConfirmClick, onCancelClick}) => { + const confirmationStyle = { + 'padding': '40px 40px 40px 40px', + 'display': 'flex', + 'flexDirection': 'column', + 'alignItems': 'center', + 'justifyContent': 'space-around', + 'backgroundColor': '#ececec', + 'width': '50%', + 'height': '400px', + } + const buttonStyle = { + 'marginLeft': '5px', + 'marginRight': '5px', + } + const confirmationTextStyle = { + 'marginBottom': '20px', + 'fontSize': '24px', + } + return ( +
+

+ Please confirm that you would like to set aside {allowance} SC for storage on the Sia network. +

+
+ + +
+
+ ) +} + +ConfirmationDialog.propTypes = { + allowance: PropTypes.string.isRequired, + onConfirmClick: PropTypes.func.isRequired, + onCancelClick: PropTypes.func.isRequired, +} + +export default ConfirmationDialog diff --git a/plugins/Inspector/js/components/allowancedialog.js b/plugins/Inspector/js/components/allowancedialog.js new file mode 100644 index 00000000..baf22ed6 --- /dev/null +++ b/plugins/Inspector/js/components/allowancedialog.js @@ -0,0 +1,65 @@ +import PropTypes from 'prop-types' +import React from 'react' +import UnlockWarning from './unlockwarning.js' +import ConfirmationDialog from './allowanceconfirmation.js' +import BigNumber from 'bignumber.js' + +const AllowanceDialog = ({confirming, confirmationAllowance, unlocked, synced, feeEstimate, storageEstimate, actions}) => { + const onCancelClick = () => actions.closeAllowanceDialog() + const onConfirmationCancel = () => actions.hideAllowanceConfirmation() + const onConfirmClick = () => actions.setAllowance(confirmationAllowance) + const onAcceptClick = (e) => { + e.preventDefault() + actions.showAllowanceConfirmation(e.target.allowance.value) + } + const onAllowanceChange = (e) => actions.getStorageEstimate(e.target.value) + const dialogContents = confirming ? ( + + ) : ( +
+

Buy storage on the Sia Decentralized Network

+
+

You need to allocate funds to upload and download on Sia. Your allowance remains locked for 3 months. Unspent funds are then refunded*. You can increase your allowance at any time.

+

Your storage allowance automatically refills every 6 weeks. Your computer must be online with your wallet unlocked to complete the refill. If Sia fails to refill the allowance by the end of the lock-in period, your data may be lost.

+

*contract fees are non-refundable. They will be subtracted from the allowance that you set.

+
+
+
+ + SC +
+
+ + +
+ + + + + + + + + +
Estimated Fees{new BigNumber(feeEstimate).round(2).toString()} SC
Estimated Storage{storageEstimate}
+
+
+ ) + + return ( +
+ {unlocked && synced ? dialogContents : } +
+ ) +} + +AllowanceDialog.propTypes = { + confirmationAllowance: PropTypes.string.isRequired, + confirming: PropTypes.bool.isRequired, + unlocked: PropTypes.bool.isRequired, + synced: PropTypes.bool.isRequired, + feeEstimate: PropTypes.number.isRequired, + storageEstimate: PropTypes.string.isRequired, +} + +export default AllowanceDialog diff --git a/plugins/Inspector/js/components/app.js b/plugins/Inspector/js/components/app.js new file mode 100644 index 00000000..7b727581 --- /dev/null +++ b/plugins/Inspector/js/components/app.js @@ -0,0 +1,17 @@ +import PropTypes from 'prop-types' +import React from 'react' +import FileBrowser from '../containers/filebrowser.js' +import AllowanceDialog from '../containers/allowancedialog.js' + +const FilesApp = ({showAllowanceDialog}) => ( +
+ {showAllowanceDialog ? : null} + +
+) + +FilesApp.propTypes = { + showAllowanceDialog: PropTypes.bool.isRequired, +} + +export default FilesApp diff --git a/plugins/Inspector/js/components/contractorstatus.js b/plugins/Inspector/js/components/contractorstatus.js new file mode 100644 index 00000000..8748e07d --- /dev/null +++ b/plugins/Inspector/js/components/contractorstatus.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const ContractorStatus = ({settingAllowance, contractCount}) => ( +
+ { settingAllowance ? ( +
+ + Forming Contracts... +
+ ) : ( {contractCount} contracts ) + } +
+) + +ContractorStatus.propTypes = { + settingAllowance: PropTypes.bool.isRequired, + contractCount: PropTypes.number.isRequired, +} + +export default ContractorStatus + diff --git a/plugins/Inspector/js/components/deletedialog.js b/plugins/Inspector/js/components/deletedialog.js new file mode 100644 index 00000000..e2c159b8 --- /dev/null +++ b/plugins/Inspector/js/components/deletedialog.js @@ -0,0 +1,31 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { List } from 'immutable' + +const DeleteDialog = ({files, actions}) => { + const onYesClick = () => { + files.map(actions.deleteFile) + actions.hideDeleteDialog() + } + const onNoClick = () => actions.hideDeleteDialog() + return ( +
+
+

Confirm Deletion

+
+ Are you sure you want to delete {files.size} {files.size === 1 ? ' file' : ' files'} +
+
+ + +
+
+
+ ) +} + +DeleteDialog.propTypes = { + files: PropTypes.instanceOf(List).isRequired, +} + +export default DeleteDialog diff --git a/plugins/Inspector/js/components/directoryinfobar.js b/plugins/Inspector/js/components/directoryinfobar.js new file mode 100644 index 00000000..128c6b0e --- /dev/null +++ b/plugins/Inspector/js/components/directoryinfobar.js @@ -0,0 +1,42 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const colorBackDisabled = '#C5C5C5' +const colorBackEnabled = '#00CBA0' + +const DirectoryInfoBar = ({path, nfiles, onBackClick, setDragFolderTarget}) => { + const backButtonStyle = { + color: (() => { + if (path === '') { + return colorBackDisabled + } + return colorBackEnabled + })(), + } + // handle file drag onto the info bar: move the file into the parent + // directory + const handleDragOver = () => { + setDragFolderTarget('../') + } + return ( +
  • +
    + + Back +
    +
    + {path} + {nfiles} {nfiles === 1 ? 'file' : 'files' } +
    +
  • + ) +} + +DirectoryInfoBar.propTypes = { + path: PropTypes.string.isRequired, + nfiles: PropTypes.number.isRequired, + onBackClick: PropTypes.func.isRequired, + setDragFolderTarget: PropTypes.func.isRequired, +} + +export default DirectoryInfoBar diff --git a/plugins/Inspector/js/components/downloadlist.js b/plugins/Inspector/js/components/downloadlist.js new file mode 100644 index 00000000..07027679 --- /dev/null +++ b/plugins/Inspector/js/components/downloadlist.js @@ -0,0 +1,20 @@ +import PropTypes from 'prop-types' +import React from 'react' +import TransferList from './transferlist.js' +import { List } from 'immutable' + +const DownloadList = ({downloads, onDownloadClick, onClearClick}) => ( +
    +

    Downloads

    + + +
    +) + +DownloadList.propTypes = { + downloads: PropTypes.instanceOf(List).isRequired, + onDownloadClick: PropTypes.func, + onClearClick: PropTypes.func, +} + +export default DownloadList diff --git a/plugins/Inspector/js/components/dragoverlay.js b/plugins/Inspector/js/components/dragoverlay.js new file mode 100644 index 00000000..ac673569 --- /dev/null +++ b/plugins/Inspector/js/components/dragoverlay.js @@ -0,0 +1,12 @@ +import React from 'react' + +const DragOverlay = () => ( +
    + + +

    Drag to Upload

    +
    +
    +) + +export default DragOverlay diff --git a/plugins/Inspector/js/components/file.js b/plugins/Inspector/js/components/file.js new file mode 100644 index 00000000..64923514 --- /dev/null +++ b/plugins/Inspector/js/components/file.js @@ -0,0 +1,67 @@ +import PropTypes from 'prop-types' +import React from 'react' +import RedundancyStatus from './redundancystatus.js' + +const File = ({filename, type, selected, isDragTarget, filesize, available, redundancy, uploadprogress, onDoubleClick, onClick, setDragUploadEnabled, setDragFolderTarget, setDragFileOrigin, handleDragRename, isSiaUIFolder }) => { + const handleDrag = () => { + } + const handleDragStart = () => { + setDragUploadEnabled(false) + setDragFileOrigin({type: type, name: filename, isSiaUIFolder: isSiaUIFolder}) + setDragFolderTarget('') + } + const handleDragEnd = () => { + setDragUploadEnabled(true) + handleDragRename() + } + const handleDragOver = () => { + if (type === 'directory') { + setDragFolderTarget(filename) + } else { + setDragFolderTarget('') + } + } + const fileClass = (() => { + if (isDragTarget) { + return 'filebrowser-file dragtarget' + } + if (selected) { + return 'filebrowser-file selected' + } + return 'filebrowser-file' + })() + return ( +
  • +
    + {type === 'file' ? : } +
    {filename}
    +
    +
    + {filesize} + +
    +
  • + ) +} + +File.propTypes = { + filename: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + filesize: PropTypes.string.isRequired, + available: PropTypes.bool.isRequired, + redundancy: PropTypes.number, + uploadprogress: PropTypes.number, + selected: PropTypes.bool.isRequired, + onDoubleClick: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, +} + +export default File diff --git a/plugins/Inspector/js/components/filebrowser.js b/plugins/Inspector/js/components/filebrowser.js new file mode 100644 index 00000000..3cd9918c --- /dev/null +++ b/plugins/Inspector/js/components/filebrowser.js @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types' +import React from 'react' +import FileList from '../containers/filelist.js' +import TransfersButton from '../containers/transfersbutton.js' +import FileTransfers from '../containers/filetransfers.js' +import DragOverlay from './dragoverlay.js' + +const FileBrowser = ({dragging, dragUploadEnabled, settingAllowance, showAddFolderDialog, showRenameDialog, showUploadDialog, showDeleteDialog, showFileTransfers, actions}) => { + const onDragOver = (e) => { + if (!dragUploadEnabled) { + return + } + e.preventDefault() + actions.setDragging() + } + const onDrop = (e) => { + if (!dragUploadEnabled) { + return + } + e.preventDefault() + actions.setNotDragging() + // Convert file list into a list of file paths. + actions.showUploadDialog(Array.from(e.dataTransfer.files, (file) => file.path)) + } + const onDragLeave = (e) => { + if (!dragUploadEnabled) { + return + } + e.preventDefault() + actions.setNotDragging() + } + const onKeyDown = (e) => { + // Deselect all files when ESC is pressed. + if (e.keyCode === 27) { + actions.deselectAll() + } + } + return ( +
    +
    + {dragging ? : null} + +
    + {showFileTransfers ? : null} +
    + ) +} + +FileBrowser.propTypes = { + dragging: PropTypes.bool.isRequired, + settingAllowance: PropTypes.bool.isRequired, + showRenameDialog: PropTypes.bool.isRequired, + showUploadDialog: PropTypes.bool.isRequired, + showDeleteDialog: PropTypes.bool.isRequired, + showFileTransfers: PropTypes.bool.isRequired, + dragUploadEnabled: PropTypes.bool.isRequired, +} + +export default FileBrowser diff --git a/plugins/Inspector/js/components/filecontrols.js b/plugins/Inspector/js/components/filecontrols.js new file mode 100644 index 00000000..78c078c2 --- /dev/null +++ b/plugins/Inspector/js/components/filecontrols.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Set } from 'immutable' +import Path from 'path' + +const FileControls = ({files, actions}) => { + const onDownloadClick = () => { + const downloadpath = SiaAPI.openFile({ + title: 'Where should we download?', + properties: ['openDirectory', 'createDirectories'], + }) + if (downloadpath.length === 0) { + // No files selected, nop + return + } + files.forEach(async (file) => { + actions.downloadFile(file, Path.join(downloadpath[0], Path.basename(file.siapath))) + await new Promise((resolve) => setTimeout(resolve, 300)) + }) + } + const onDeleteClick = () => { + actions.showDeleteDialog(files.toList()) + } + const onRenameClick = () => { + actions.showRenameDialog(files.first()) + } + return ( +
    + {files.size} {files.size === 1 ? ' item' : ' items' } selected +
    + +
    + {files.size === 1 ? ( +
    + +
    + ) : null} +
    + +
    +
    + ) +} + +FileControls.propTypes = { + files: PropTypes.instanceOf(Set).isRequired, +} + +export default FileControls + diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js new file mode 100644 index 00000000..d8668dd1 --- /dev/null +++ b/plugins/Inspector/js/components/filedetail.js @@ -0,0 +1,117 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Tooltip from 'rc-tooltip'; + +const FileDetail = ({showDetailFile}) => { + const getChunkTooltip = chunk => chunk.map( + (piece, i) => { + var s; + if(!piece.host) { + s = pieceEmpty + } else if(piece.isoffline) { + s = pieceOffline + } else { + s = pieceOnline + } + return (
    {piece.host ? piece.host : "Empty"}
    ) + } + ) + const getChunkStyle = chunk => { + const onlineChunk = chunk.reduce((sum, piece) => { + if(piece.host && !piece.isoffline) { + return sum+1 + } + return sum + }, 0) + if(onlineChunk == chunk.length) { + return repairChunkRepaired + } + if(onlineChunk == 0) { + return repairChunkQueued + } + return repairChunkRepairing + } + const details = showDetailFile.details + return ( +
    +

    + {showDetailFile.name} +

    +
    + {details.map((v,i) => ( + + + + ) + )} +
    +
    +
    + ) +} + +FileDetail.propTypes = { + showDetailFile: PropTypes.object.isRequired, +} + +export default FileDetail + +const logViewStyle = { + position: 'absolute', + top: '55px', + bottom: '0', + left: '20px', + right: '20px', + margin: '0', + padding: '0', + overflowY: 'scroll', + whiteSpace: 'pre', + fontSize: '12px', + fontFamily: 'monospace', +} + +const liViewStyle = { + cursor: 'pointer', +} + +const reapirFileView = { + margin: '10px 20px', +} + +const repaireChunk = { + width: '8px', + height: '8px', + float: 'left', + border: 'solid 1px #001f3f', +} + +const repairChunkRepaired = { + ...repaireChunk, + background: '#01FF70', +} + +const repairChunkRepairing = { + ...repaireChunk, + background: '#FF851B', +} + +const repairChunkQueued = { + ...repaireChunk, + background: 'white', +} + +const clearBoth = { + clear: 'both', +} + +const pieceOnline = { + color: '#01FF70' +} + +const pieceOffline = { + color: '#dc143c' +} + +const pieceEmpty = { + color: 'white' +} \ No newline at end of file diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js new file mode 100644 index 00000000..fff22534 --- /dev/null +++ b/plugins/Inspector/js/components/filelist.js @@ -0,0 +1,155 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { List, Set } from 'immutable' +import File from './file.js' +import Path from 'path' +import SearchField from '../containers/searchfield.js' +import FileControls from '../containers/filecontrols.js' +import DirectoryInfoBar from './directoryinfobar.js' +import FileDetail from './filedetail.js' + +const FileList = ({files, selected, searchResults, path, showSearchField, dragFileOrigin, dragFolderTarget, showDetailPath, actions}) => { + const onBackClick = () => { + // remove a trailing slash if it exists + const cleanPath = path.replace(/\/$/, '') + + if (cleanPath === '') { + return + } + + // find the parent directory and set the new path + const pathComponents = cleanPath.split('/') + if (pathComponents.length < 2) { + actions.setPath('') + } else { + pathComponents.pop() + actions.setPath(pathComponents.join('/')) + } + } + + if (files === null) { + return ( +
    +
      +

      Loading files...

      +
    +
    + ) + } + + if(showDetailPath != null) { + const showDetailFile = files.find(file => file.siapath == showDetailPath) + return ( +
    + +
    + ) + } + + + let filelistFiles + if (showSearchField) { + filelistFiles = searchResults + } else { + filelistFiles = files + } + const fileElements = filelistFiles.map((file, key) => { + const isSelected = selected.map((selectedfile) => selectedfile.name).includes(file.name) + const onFileClick = (e) => { + // a show file detail action + actions.showFileDetail(file.siapath) + + // const shouldMultiSelect = e.ctrlKey || e.metaKey + // const shouldRangeSelect = e.shiftKey + // if (!shouldMultiSelect && !shouldRangeSelect) { + // actions.deselectAll() + // } + // if (shouldRangeSelect) { + // actions.selectUpTo(file) + // } + // if (shouldMultiSelect && isSelected) { + // actions.deselectFile(file) + // } else { + // actions.selectFile(file) + // } + } + const onDoubleClick = (e) => { + e.stopPropagation() + if (file.type === 'directory') { + actions.setPath(file.siapath) + } + } + const handleDragRename = () => { + if (typeof dragFileOrigin === 'undefined' || dragFolderTarget === '') { + return + } + if (dragFileOrigin.name === dragFolderTarget) { + return + } + if (dragFolderTarget === '../' && path === '') { + return + } + if (selected.size > 0) { + selected.forEach((selectedfile) => { + const destSiapath = Path.posix.join(path, dragFolderTarget, selectedfile.name) + actions.renameFile(selectedfile, destSiapath) + if (selected.type === 'directory' && !selected.isSiaUIFolder) { + actions.deleteSiaUIFolder(sourceSiapath) + } + }) + } else { + const sourceSiapath = Path.posix.join(path, dragFileOrigin.name) + const destSiapath = Path.posix.join(path, dragFolderTarget, dragFileOrigin.name) + actions.renameFile({type: dragFileOrigin.type, siapath: sourceSiapath, isSiaUIFolder: dragFileOrigin.isSiaUIFolder}, destSiapath) + if (dragFileOrigin.type === 'directory' && !dragFileOrigin.isSiaUIFolder) { + actions.deleteSiaUIFolder(sourceSiapath) + } + } + actions.getFiles() + actions.setDragFolderTarget('') + actions.setDragFileOrigin({}) + } + return ( + + ) + }) + + return ( +
    +
      + + { fileElements.size > 0 ? fileElements :

      No files uploaded

      } +
    + {selected.size > 0 ? : null} +
    + ) +} + +FileList.propTypes = { + files: PropTypes.instanceOf(List), + selected: PropTypes.instanceOf(Set).isRequired, + searchResults: PropTypes.instanceOf(List), + path: PropTypes.string.isRequired, + showSearchField: PropTypes.bool.isRequired, + dragFileOrigin: PropTypes.object.isRequired, + dragFolderTarget: PropTypes.string.isRequired, +} + +export default FileList diff --git a/plugins/Inspector/js/components/filetransfers.js b/plugins/Inspector/js/components/filetransfers.js new file mode 100644 index 00000000..6a5c5d29 --- /dev/null +++ b/plugins/Inspector/js/components/filetransfers.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { List } from 'immutable' +import UploadList from './uploadlist.js' +import DownloadList from './downloadlist.js' +import { shell } from 'electron' + +const FileTransfers = ({uploads, downloads, actions}) => { + const onCloseClick = () => actions.hideFileTransfers() + const onDownloadClick = (download) => () => shell.showItemInFolder(download.destination) + const onDownloadsClearClick = () => { + actions.clearDownloads() + actions.getDownloads() + } + return ( +
    +
    + +
    + {downloads.size === 0 && uploads.size === 0 ? ( +

    No file transfers in progress.

    + ) : null + } + {downloads.size > 0 ? : null} + {uploads.size > 0 ? : null} +
    + ) +} + +FileTransfers.propTypes = { + uploads: PropTypes.instanceOf(List).isRequired, + downloads: PropTypes.instanceOf(List).isRequired, +} + +export default FileTransfers diff --git a/plugins/Inspector/js/components/progressbar.js b/plugins/Inspector/js/components/progressbar.js new file mode 100644 index 00000000..85d41ed9 --- /dev/null +++ b/plugins/Inspector/js/components/progressbar.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const ProgressBar = ({progress}) => { + const style = { + width: progress.toString() + '%', + height: '100%', + transition: 'width 200ms', + backgroundColor: '#00CBA0', + } + return ( +
    +
    +
    + ) +} + +ProgressBar.propTypes = { + progress: PropTypes.number.isRequired, +} + +export default ProgressBar diff --git a/plugins/Inspector/js/components/redundancystatus.js b/plugins/Inspector/js/components/redundancystatus.js new file mode 100644 index 00000000..b88fb314 --- /dev/null +++ b/plugins/Inspector/js/components/redundancystatus.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const colorNotAvailable = '#FF8080' +const colorGoodRedundancy = '#00CBA0' +const colorNegativeRedundancy = '#b7afaf' + +const RedundancyStatus = ({available, redundancy, uploadprogress}) => { + const indicatorStyle = { + opacity: (() => { + if (!available || redundancy < 1.0) { + return 1 + } + if (uploadprogress > 100) { + return 1 + } + return uploadprogress/100 + })(), + color: (() => { + if (redundancy < 0) { + return colorNegativeRedundancy + } + if (!available || redundancy < 1.0) { + return colorNotAvailable + } + return colorGoodRedundancy + })(), + } + return ( +
    + + {redundancy > 0 ? redundancy + 'x' : '--'} +
    + ) +} + +RedundancyStatus.propTypes = { + available: PropTypes.bool.isRequired, + redundancy: PropTypes.number.isRequired, + uploadprogress: PropTypes.number.isRequired, +} + +export default RedundancyStatus + diff --git a/plugins/Inspector/js/components/renamedialog.js b/plugins/Inspector/js/components/renamedialog.js new file mode 100644 index 00000000..9f3a7fd9 --- /dev/null +++ b/plugins/Inspector/js/components/renamedialog.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Path from 'path' + +const RenameDialog = ({file, actions}) => { + const onYesClick = (e) => { + e.preventDefault() + actions.renameFile(file, Path.posix.join(Path.posix.dirname(file.siapath), e.target.newname.value)) + } + const onNoClick = () => actions.hideRenameDialog() + return ( +
    +
    +
    + Enter a new name for {Path.basename(file.siapath)}: +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + ) +} + +RenameDialog.propTypes = { + file: PropTypes.object.isRequired, +} + +export default RenameDialog diff --git a/plugins/Inspector/js/components/searchbutton.js b/plugins/Inspector/js/components/searchbutton.js new file mode 100644 index 00000000..fa41b9f2 --- /dev/null +++ b/plugins/Inspector/js/components/searchbutton.js @@ -0,0 +1,16 @@ +import React from 'react' + +const SearchButton = ({path, actions}) => { + const handleClick = () => { + actions.toggleSearchField() + actions.setSearchText('', path) + } + return ( +
    + + Search Files +
    + ) +} + +export default SearchButton diff --git a/plugins/Inspector/js/components/searchfield.js b/plugins/Inspector/js/components/searchfield.js new file mode 100644 index 00000000..1f2d5638 --- /dev/null +++ b/plugins/Inspector/js/components/searchfield.js @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const SearchField = ({searchText, path, actions}) => { + const onSearchChange = (e) => actions.setSearchText(e.target.value, path) + return ( +
    + + +
    + ) +} + +SearchField.propTypes = { + searchText: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, +} +export default SearchField diff --git a/plugins/Inspector/js/components/setallowancebutton.js b/plugins/Inspector/js/components/setallowancebutton.js new file mode 100644 index 00000000..e30750f2 --- /dev/null +++ b/plugins/Inspector/js/components/setallowancebutton.js @@ -0,0 +1,13 @@ +import React from 'react' + +const SetAllowanceButton = ({actions}) => { + const handleClick = () => actions.showAllowanceDialog() + return ( +
    + + Create Allowance +
    + ) +} + +export default SetAllowanceButton diff --git a/plugins/Inspector/js/components/transfer.js b/plugins/Inspector/js/components/transfer.js new file mode 100644 index 00000000..b65d88e6 --- /dev/null +++ b/plugins/Inspector/js/components/transfer.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import ProgressBar from './progressbar.js' + +const Transfer = ({name, progress, status, onClick}) => ( +
  • +
    +
    {name}
    + + {status} +
    +
  • +) + +Transfer.propTypes = { + name: PropTypes.string.isRequired, + progress: PropTypes.number.isRequired, + status: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, +} + +export default Transfer diff --git a/plugins/Inspector/js/components/transferlist.js b/plugins/Inspector/js/components/transferlist.js new file mode 100644 index 00000000..67529fe3 --- /dev/null +++ b/plugins/Inspector/js/components/transferlist.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { List } from 'immutable' +import Transfer from './transfer.js' + +const defaultTransferClick = () => () => {} + +const TransferList = ({transfers, onTransferClick = defaultTransferClick}) => { + const transferComponents = transfers.map((transfer, key) => ( + + )) + return ( +
      + {transferComponents} +
    + ) +} + +TransferList.propTypes = { + transfers: PropTypes.instanceOf(List).isRequired, + onTransferClick: PropTypes.func, +} + +export default TransferList diff --git a/plugins/Inspector/js/components/transfersbutton.js b/plugins/Inspector/js/components/transfersbutton.js new file mode 100644 index 00000000..559d0fb1 --- /dev/null +++ b/plugins/Inspector/js/components/transfersbutton.js @@ -0,0 +1,21 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const FileTransfersButton = ({unread, actions}) => { + const onTransfersClick = () => actions.toggleFileTransfers() + return ( +
    + + {unread > 0 ? ( + {unread > 10 ? '10+' : unread} + ) : null} + File Transfers +
    + ) +} + +FileTransfersButton.propTypes = { + unread: PropTypes.number.isRequired, +} + +export default FileTransfersButton diff --git a/plugins/Inspector/js/components/unlockwarning.js b/plugins/Inspector/js/components/unlockwarning.js new file mode 100644 index 00000000..618d76ea --- /dev/null +++ b/plugins/Inspector/js/components/unlockwarning.js @@ -0,0 +1,17 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const UnlockWarning = ({onClick}) => ( +
    +

    Your wallet must be unlocked and synchronized to buy storage.

    +
    + +
    +
    +) + +UnlockWarning.propTypes = { + onClick: PropTypes.func.isRequired, +} + +export default UnlockWarning diff --git a/plugins/Inspector/js/components/uploadbutton.js b/plugins/Inspector/js/components/uploadbutton.js new file mode 100644 index 00000000..c9cc3b5c --- /dev/null +++ b/plugins/Inspector/js/components/uploadbutton.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const minimumContracts = 14 + +const UploadButton = ({contracts = minimumContracts, actions}) => { + const onUploadClick = (type) => () => { + if (contracts < minimumContracts) { + SiaAPI.showError({ + title: 'Sia-UI files error', + content: 'Not enough contracts to upload. You must buy storage before uploading, or wait for contracts to form.', + }) + return + } + let dialogProperties + if (type === 'folder') { + dialogProperties = ['openDirectory'] + } else if (type === 'file') { + dialogProperties = ['openFile', 'multiSelections'] + } + const filepaths = SiaAPI.openFile({ + title: 'Choose a ' + type + ' to upload', + properties: dialogProperties, + }) + if (filepaths) { + actions.showUploadDialog(filepaths) + } + } + return ( +
    +
    + + Upload Files +
    +
    + + Upload Folder +
    +
    + ) +} + +UploadButton.propTypes = { + contracts: PropTypes.number, +} + +export default UploadButton diff --git a/plugins/Inspector/js/components/uploaddialog.js b/plugins/Inspector/js/components/uploaddialog.js new file mode 100644 index 00000000..a4e3f0c5 --- /dev/null +++ b/plugins/Inspector/js/components/uploaddialog.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React from 'react' +import fs from 'graceful-fs' + +const UploadDialog = ({source, path, actions}) => { + const onUploadClick = () => { + source.forEach((file) => { + if (fs.statSync(file).isDirectory()) { + actions.uploadFolder(path, file) + } else { + actions.uploadFile(path, file) + } + }) + actions.hideUploadDialog() + } + const onCancelClick = () => actions.hideUploadDialog() + return ( +
    +
    +

    Confirm Upload

    +
    Would you like to upload {source.length} {source.length === 1 ? 'item' : 'items'}?
    +
    + + +
    +
    +
    + ) +} + +UploadDialog.propTypes = { + source: PropTypes.array.isRequired, + path: PropTypes.string.isRequired, +} + +export default UploadDialog diff --git a/plugins/Inspector/js/components/uploadlist.js b/plugins/Inspector/js/components/uploadlist.js new file mode 100644 index 00000000..d4070477 --- /dev/null +++ b/plugins/Inspector/js/components/uploadlist.js @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types' +import React from 'react' +import TransferList from './transferlist.js' +import { List } from 'immutable' + +const UploadList = ({uploads, onUploadClick}) => ( +
    +

    Uploads

    + +
    +) + +UploadList.propTypes = { + uploads: PropTypes.instanceOf(List).isRequired, + onUploadClick: PropTypes.func, +} + +export default UploadList diff --git a/plugins/Inspector/js/components/usagestats.js b/plugins/Inspector/js/components/usagestats.js new file mode 100644 index 00000000..59b63bd6 --- /dev/null +++ b/plugins/Inspector/js/components/usagestats.js @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const UsageStats = ({allowance, spending}) => ( +
    + {spending} SC Spent / {allowance} SC Allocated +
    +) + +UsageStats.propTypes = { + allowance: PropTypes.string.isRequired, + spending: PropTypes.string.isRequired, +} + +export default UsageStats diff --git a/plugins/Inspector/js/constants/files.js b/plugins/Inspector/js/constants/files.js new file mode 100644 index 00000000..c64da61d --- /dev/null +++ b/plugins/Inspector/js/constants/files.js @@ -0,0 +1,62 @@ +export const GET_WALLET_LOCKSTATE = 'GET_WALLET_LOCKSTATE' +export const RECEIVE_WALLET_LOCKSTATE = 'SET_WALLET_LOCKSTATE' +export const GET_STORAGE_ESTIMATE = 'GET_STORAGE_ESTIMATE' +export const SET_STORAGE_ESTIMATE = 'SET_STORAGE_ESTIMATE' +export const SET_FEE_ESTIMATE = 'SET_FEE_ESTIMATE' +export const GET_ALLOWANCE = 'GET_ALLOWANCE' +export const RECEIVE_ALLOWANCE = 'RECEIVE_ALLOWANCE' +export const RECEIVE_SPENDING = 'RECEIVE_SPENDING' +export const SET_ALLOWANCE = 'SET_ALLOWANCE' +export const SET_ALLOWANCE_COMPLETED = 'SET_ALLOWANCE_COMPLETED' +export const SET_PATH = 'SET_PATH' +export const GET_FILES = 'GET_FILES' +export const RECEIVE_FILES = 'RECEIVE_FILES' +export const GET_WALLET_BALANCE = 'GET_WALLET_BALANCE' +export const RECEIVE_WALLET_BALANCE = 'RECEIVE_WALLET_BALANCE' +export const SHOW_ALLOWANCE_DIALOG = 'SHOW_ALLOWANCE_DIALOG' +export const CLOSE_ALLOWANCE_DIALOG = 'CLOSE_ALLOWANCE_DIALOG' +export const SET_SEARCH_TEXT = 'SET_SEARCH_TEXT' +export const TOGGLE_SEARCH_FIELD = 'TOGGLE_SEARCH_FIELD' +export const UPLOAD_FILE = 'UPLOAD_FILE' +export const UPLOAD_FOLDER = 'UPLOAD_FOLDER' +export const DELETE_FILE = 'DELETE_FILE' +export const SHOW_DELETE_DIALOG = 'SHOW_DELETE_DIALOG' +export const HIDE_DELETE_DIALOG = 'HIDE_DELETE_DIALOG' +export const DOWNLOAD_FILE = 'DOWNLOAD_FILE' +export const SET_DRAGGING = 'SET_DRAGGING' +export const SET_NOT_DRAGGING = 'SET_NOT_DRAGGING' +export const SHOW_UPLOAD_DIALOG = 'SHOW_UPLOAD_DIALOG' +export const HIDE_UPLOAD_DIALOG = 'HIDE_UPLOAD_DIALOG' +export const GET_DOWNLOADS = 'GET_DOWNLOADS' +export const GET_UPLOADS = 'GET_UPLOADS' +export const RECEIVE_DOWNLOADS = 'RECEIVE_DOWNLOADS' +export const RECEIVE_UPLOADS = 'RECEIVE_UPLOADS' +export const CALCULATE_STORAGE_COST = 'CALCULATE_STORAGE_COST' +export const SHOW_FILE_TRANSFERS = 'SHOW_FILE_TRANSFERS' +export const HIDE_FILE_TRANSFERS = 'HIDE_FILE_TRANSFERS' +export const TOGGLE_FILE_TRANSFERS = 'TOGGLE_FILE_TRANSFERS' +export const SET_CONTRACT_COUNT = 'SET_CONTRACT_COUNT' +export const GET_CONTRACT_COUNT = 'GET_CONTRACT_COUNT' +export const RENAME_FILE = 'RENAME_FILE' +export const SHOW_RENAME_DIALOG = 'SHOW_RENAME_DIALOG' +export const HIDE_RENAME_DIALOG = 'HIDE_RENAME_DIALOG' +export const SELECT_FILE = 'SELECT_FILE' +export const SELECT_UP_TO = 'SELECT_UP_TO' +export const DESELECT_ALL = 'DESELECT_ALL' +export const DESELECT_FILE = 'DESELECT_FILE' +export const CLEAR_DOWNLOADS = 'CLEAR_DOWNLOADS' +export const SHOW_ALLOWANCE_CONFIRMATION = 'SHOW_ALLOWANCE_CONFIRMATION' +export const HIDE_ALLOWANCE_CONFIRMATION = 'HIDE_ALLOWANCE_CONFIRMATION' +export const GET_WALLET_SYNCSTATE = 'GET_WALLET_SYNCSTATE' +export const SET_WALLET_SYNCSTATE = 'SET_WALLET_SYNCSTATE' +export const FETCH_DATA = 'FETCH_DATA' +export const ADD_FOLDER = 'ADD_FOLDER' +export const SHOW_ADD_FOLDER_DIALOG = 'SHOW_ADD_FOLDER_DIALOG' +export const HIDE_ADD_FOLDER_DIALOG = 'HIDE_ADD_FOLDER_DIALOG' +export const SET_DRAG_UPLOAD_ENABLED = 'SET_DRAG_UPLOAD_ENABLED' +export const SET_DRAG_FOLDER_TARGET = 'SET_DRAG_FOLDER_TARGET' +export const SET_DRAG_FILE_ORIGIN = 'SET_DRAG_FILE_ORIGIN' +export const RENAME_SIA_UI_FOLDER = 'RENAME_SIA_UI_FOLDER' +export const DELETE_SIA_UI_FOLDER = 'DELETE_SIA_UI_FOLDER' + +export const SHOW_FILE_DETAIL = 'SHOW_FILE_DETAIL' diff --git a/plugins/Inspector/js/containers/addfolderbutton.js b/plugins/Inspector/js/containers/addfolderbutton.js new file mode 100644 index 00000000..33d052ca --- /dev/null +++ b/plugins/Inspector/js/containers/addfolderbutton.js @@ -0,0 +1,13 @@ +import AddFolderButtonView from '../components/addfolderbutton.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { showAddFolderDialog } from '../actions/files.js' + +const mapStateToProps = () => ({ +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ showAddFolderDialog }, dispatch), +}) + +const AddFolderButton = connect(mapStateToProps, mapDispatchToProps)(AddFolderButtonView) +export default AddFolderButton diff --git a/plugins/Inspector/js/containers/addfolderdialog.js b/plugins/Inspector/js/containers/addfolderdialog.js new file mode 100644 index 00000000..158b28f2 --- /dev/null +++ b/plugins/Inspector/js/containers/addfolderdialog.js @@ -0,0 +1,13 @@ +import AddFolderDialogView from '../components/addfolderdialog.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { hideAddFolderDialog, addFolder } from '../actions/files.js' + +const mapStateToProps = () => ({ +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ hideAddFolderDialog, addFolder}, dispatch), +}) + +const AddFolderDialog = connect(mapStateToProps, mapDispatchToProps)(AddFolderDialogView) +export default AddFolderDialog diff --git a/plugins/Inspector/js/containers/allowancedialog.js b/plugins/Inspector/js/containers/allowancedialog.js new file mode 100644 index 00000000..c634f04c --- /dev/null +++ b/plugins/Inspector/js/containers/allowancedialog.js @@ -0,0 +1,19 @@ +import AllowanceDialogView from '../components/allowancedialog.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { showAllowanceConfirmation, hideAllowanceConfirmation, closeAllowanceDialog, setAllowance, setFeeEstimate, getStorageEstimate } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + unlocked: state.wallet.get('unlocked'), + synced: state.wallet.get('synced'), + storageEstimate: state.allowancedialog.get('storageEstimate'), + feeEstimate: state.allowancedialog.get('feeEstimate'), + confirmationAllowance: state.allowancedialog.get('confirmationAllowance'), + confirming: state.allowancedialog.get('confirming'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ getStorageEstimate, setFeeEstimate, showAllowanceConfirmation, setAllowance, hideAllowanceConfirmation, closeAllowanceDialog}, dispatch), +}) + +const AllowanceDialog = connect(mapStateToProps, mapDispatchToProps)(AllowanceDialogView) +export default AllowanceDialog diff --git a/plugins/Inspector/js/containers/app.js b/plugins/Inspector/js/containers/app.js new file mode 100644 index 00000000..dde468fb --- /dev/null +++ b/plugins/Inspector/js/containers/app.js @@ -0,0 +1,9 @@ +import AppView from '../components/app.js' +import { connect } from 'react-redux' + +const mapStateToProps = (state) => ({ + showAllowanceDialog: state.files.get('showAllowanceDialog'), +}) + +const App = connect(mapStateToProps)(AppView) +export default App diff --git a/plugins/Inspector/js/containers/contractorstatus.js b/plugins/Inspector/js/containers/contractorstatus.js new file mode 100644 index 00000000..2cef4f7f --- /dev/null +++ b/plugins/Inspector/js/containers/contractorstatus.js @@ -0,0 +1,11 @@ +import ContractorStatusView from '../components/contractorstatus.js' +import { connect } from 'react-redux' + +const mapStateToProps = (state) => ({ + settingAllowance: state.files.get('settingAllowance'), + contractCount: state.files.get('contractCount'), +}) + +const ContractorStatus = connect(mapStateToProps)(ContractorStatusView) +export default ContractorStatus + diff --git a/plugins/Inspector/js/containers/deletedialog.js b/plugins/Inspector/js/containers/deletedialog.js new file mode 100644 index 00000000..53b4e4f4 --- /dev/null +++ b/plugins/Inspector/js/containers/deletedialog.js @@ -0,0 +1,14 @@ +import DeleteDialogView from '../components/deletedialog.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { hideDeleteDialog, deleteFile } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + files: state.deletedialog.get('files'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ hideDeleteDialog, deleteFile }, dispatch), +}) + +const DeleteDialog = connect(mapStateToProps, mapDispatchToProps)(DeleteDialogView) +export default DeleteDialog diff --git a/plugins/Inspector/js/containers/filebrowser.js b/plugins/Inspector/js/containers/filebrowser.js new file mode 100644 index 00000000..a3032fdf --- /dev/null +++ b/plugins/Inspector/js/containers/filebrowser.js @@ -0,0 +1,22 @@ +import FileBrowserView from '../components/filebrowser.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { setDragging, deselectAll, setNotDragging, showUploadDialog } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + dragging: state.files.get('dragging'), + settingAllowance: state.files.get('settingAllowance'), + showRenameDialog: state.files.get('showRenameDialog'), + showUploadDialog: state.files.get('showUploadDialog'), + showFileTransfers: state.files.get('showFileTransfers'), + showDeleteDialog: state.files.get('showDeleteDialog'), + showAddFolderDialog: state.files.get('showAddFolderDialog'), + dragUploadEnabled: state.files.get('dragUploadEnabled'), +}) + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ setDragging, deselectAll, setNotDragging, showUploadDialog }, dispatch), +}) + +const FileBrowser = connect(mapStateToProps, mapDispatchToProps)(FileBrowserView) +export default FileBrowser diff --git a/plugins/Inspector/js/containers/filecontrols.js b/plugins/Inspector/js/containers/filecontrols.js new file mode 100644 index 00000000..9f2a2743 --- /dev/null +++ b/plugins/Inspector/js/containers/filecontrols.js @@ -0,0 +1,14 @@ +import FileControlsView from '../components/filecontrols.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { downloadFile, showRenameDialog, showDeleteDialog } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + files: state.files.get('selected'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ downloadFile, showRenameDialog, showDeleteDialog }, dispatch), +}) + +const FileControls = connect(mapStateToProps, mapDispatchToProps)(FileControlsView) +export default FileControls diff --git a/plugins/Inspector/js/containers/filelist.js b/plugins/Inspector/js/containers/filelist.js new file mode 100644 index 00000000..a8acc786 --- /dev/null +++ b/plugins/Inspector/js/containers/filelist.js @@ -0,0 +1,21 @@ +import FileListView from '../components/filelist.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { renameSiaUIFolder, deleteSiaUIFolder, renameFile, getFiles, setDragFolderTarget, setDragFileOrigin, setDragUploadEnabled, setPath, selectUpTo, deselectFile, deselectAll, selectFile, downloadFile, showDeleteDialog, showRenameDialog, showFileDetail } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + files: state.files.get('workingDirectoryFiles'), + selected: state.files.get('selected'), + searchResults: state.files.get('searchResults'), + path: state.files.get('path'), + showSearchField: state.files.get('showSearchField'), + dragFolderTarget: state.files.get('dragFolderTarget'), + dragFileOrigin: state.files.get('dragFileOrigin'), + showDetailPath: state.files.get('showDetailPath') +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ renameSiaUIFolder, deleteSiaUIFolder, getFiles, renameFile, setDragFileOrigin, setDragFolderTarget, setDragUploadEnabled, selectUpTo, setPath, deselectFile, deselectAll, selectFile, showRenameDialog, downloadFile, showDeleteDialog, showFileDetail }, dispatch), +}) + +const FileList = connect(mapStateToProps, mapDispatchToProps)(FileListView) +export default FileList diff --git a/plugins/Inspector/js/containers/filetransfers.js b/plugins/Inspector/js/containers/filetransfers.js new file mode 100644 index 00000000..93dc740a --- /dev/null +++ b/plugins/Inspector/js/containers/filetransfers.js @@ -0,0 +1,15 @@ +import FileTransfersView from '../components/filetransfers.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { getDownloads, clearDownloads, hideFileTransfers } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + uploads: state.files.get('uploading'), + downloads: state.files.get('downloading'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ getDownloads, clearDownloads, hideFileTransfers }, dispatch), +}) + +const FileTransfers = connect(mapStateToProps, mapDispatchToProps)(FileTransfersView) +export default FileTransfers diff --git a/plugins/Inspector/js/containers/renamedialog.js b/plugins/Inspector/js/containers/renamedialog.js new file mode 100644 index 00000000..f74eb832 --- /dev/null +++ b/plugins/Inspector/js/containers/renamedialog.js @@ -0,0 +1,14 @@ +import RenameDialogView from '../components/renamedialog.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { hideRenameDialog, renameFile } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + file: state.renamedialog.get('file'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ hideRenameDialog, renameFile }, dispatch), +}) + +const RenameDialog = connect(mapStateToProps, mapDispatchToProps)(RenameDialogView) +export default RenameDialog diff --git a/plugins/Inspector/js/containers/searchbutton.js b/plugins/Inspector/js/containers/searchbutton.js new file mode 100644 index 00000000..d3b2f90a --- /dev/null +++ b/plugins/Inspector/js/containers/searchbutton.js @@ -0,0 +1,14 @@ +import SearchButtonView from '../components/searchbutton.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { toggleSearchField, setSearchText } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + path: state.files.get('path'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ toggleSearchField, setSearchText }, dispatch), +}) + +const SearchButton = connect(mapStateToProps, mapDispatchToProps)(SearchButtonView) +export default SearchButton diff --git a/plugins/Inspector/js/containers/searchfield.js b/plugins/Inspector/js/containers/searchfield.js new file mode 100644 index 00000000..58935857 --- /dev/null +++ b/plugins/Inspector/js/containers/searchfield.js @@ -0,0 +1,15 @@ +import SearchFieldView from '../components/searchfield.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { setSearchText } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + searchText: state.files.get('searchText'), + path: state.files.get('path'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ setSearchText }, dispatch), +}) + +const SearchField = connect(mapStateToProps, mapDispatchToProps)(SearchFieldView) +export default SearchField diff --git a/plugins/Inspector/js/containers/setallowancebutton.js b/plugins/Inspector/js/containers/setallowancebutton.js new file mode 100644 index 00000000..a48dff8d --- /dev/null +++ b/plugins/Inspector/js/containers/setallowancebutton.js @@ -0,0 +1,13 @@ +import SetAllowanceButtonView from '../components/setallowancebutton.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { showAllowanceDialog } from '../actions/files.js' + +const mapStateToProps = () => ({ +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ showAllowanceDialog }, dispatch), +}) + +const SetAllowanceButton = connect(mapStateToProps, mapDispatchToProps)(SetAllowanceButtonView) +export default SetAllowanceButton diff --git a/plugins/Inspector/js/containers/transfersbutton.js b/plugins/Inspector/js/containers/transfersbutton.js new file mode 100644 index 00000000..d7af509c --- /dev/null +++ b/plugins/Inspector/js/containers/transfersbutton.js @@ -0,0 +1,14 @@ +import TransfersButtonView from '../components/transfersbutton.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { toggleFileTransfers } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + unread: state.files.get('unreadUploads').size + state.files.get('unreadDownloads').size, +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ toggleFileTransfers }, dispatch), +}) + +const TransfersButton = connect(mapStateToProps, mapDispatchToProps)(TransfersButtonView) +export default TransfersButton diff --git a/plugins/Inspector/js/containers/uploadbutton.js b/plugins/Inspector/js/containers/uploadbutton.js new file mode 100644 index 00000000..59df9a1e --- /dev/null +++ b/plugins/Inspector/js/containers/uploadbutton.js @@ -0,0 +1,14 @@ +import UploadButtonView from '../components/uploadbutton.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { showUploadDialog } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + contracts: state.files.get('contractCount'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ showUploadDialog }, dispatch), +}) + +const UploadButton = connect(mapStateToProps, mapDispatchToProps)(UploadButtonView) +export default UploadButton diff --git a/plugins/Inspector/js/containers/uploaddialog.js b/plugins/Inspector/js/containers/uploaddialog.js new file mode 100644 index 00000000..a122cc86 --- /dev/null +++ b/plugins/Inspector/js/containers/uploaddialog.js @@ -0,0 +1,15 @@ +import UploadDialogView from '../components/uploaddialog.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { hideUploadDialog, uploadFile, uploadFolder } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + source: state.files.get('uploadSource'), + path: state.files.get('path'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ hideUploadDialog, uploadFile, uploadFolder }, dispatch), +}) + +const UploadDialog = connect(mapStateToProps, mapDispatchToProps)(UploadDialogView) +export default UploadDialog diff --git a/plugins/Inspector/js/containers/usagestats.js b/plugins/Inspector/js/containers/usagestats.js new file mode 100644 index 00000000..18c202ac --- /dev/null +++ b/plugins/Inspector/js/containers/usagestats.js @@ -0,0 +1,10 @@ +import UsageStatsView from '../components/usagestats.js' +import { connect } from 'react-redux' + +const mapStateToProps = (state) => ({ + allowance: state.files.get('allowance'), + spending: state.files.get('spending'), +}) + +const UsageStats = connect(mapStateToProps)(UsageStatsView) +export default UsageStats diff --git a/plugins/Inspector/js/index.js b/plugins/Inspector/js/index.js new file mode 100644 index 00000000..2ff975b3 --- /dev/null +++ b/plugins/Inspector/js/index.js @@ -0,0 +1,33 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import createSagaMiddleware from 'redux-saga' +import { createStore, applyMiddleware } from 'redux' +import { Provider } from 'react-redux' +import rootReducer from './reducers/index.js' +import rootSaga from './sagas/index.js' +import App from './containers/app.js' +import { fetchData } from './actions/files.js' + +const sagaMiddleware = createSagaMiddleware() +const store = createStore( + rootReducer, + applyMiddleware(sagaMiddleware) +) +sagaMiddleware.run(rootSaga) + +setInterval(() => { + store.dispatch(fetchData()) +}, 2000) + +const rootElement = ( + + + +) +ReactDOM.render(rootElement, document.getElementById('react-root')) + +// update state when plugin is focused +window.onfocus = () => { + store.dispatch(fetchData()) +} + diff --git a/plugins/Inspector/js/reducers/allowancedialog.js b/plugins/Inspector/js/reducers/allowancedialog.js new file mode 100644 index 00000000..99382403 --- /dev/null +++ b/plugins/Inspector/js/reducers/allowancedialog.js @@ -0,0 +1,28 @@ +import { Map } from 'immutable' +import * as constants from '../constants/files.js' + +const initialState = Map({ + storageEstimate: '0 B', + feeEstimate: 0, + confirming: false, + confirmationAllowance: '0', +}) + +export default function allowancedialogReduceR(state = initialState, action) { + switch (action.type) { + case constants.SHOW_ALLOWANCE_CONFIRMATION: + return state.set('confirming', true) + .set('confirmationAllowance', action.allowance) + case constants.HIDE_ALLOWANCE_CONFIRMATION: + return state.set('confirming', false) + case constants.CLOSE_ALLOWANCE_DIALOG: + return state.set('confirming', false) + case constants.SET_FEE_ESTIMATE: + return state.set('feeEstimate', action.estimate) + case constants.SET_STORAGE_ESTIMATE: + return state.set('storageEstimate', action.estimate) + default: + return state + } +} + diff --git a/plugins/Inspector/js/reducers/deletedialog.js b/plugins/Inspector/js/reducers/deletedialog.js new file mode 100644 index 00000000..0a4d61c9 --- /dev/null +++ b/plugins/Inspector/js/reducers/deletedialog.js @@ -0,0 +1,15 @@ +import { Map, List } from 'immutable' +import * as constants from '../constants/files.js' + +const initialState = Map({ + files: List(), +}) + +export default function deletedialogReducer(state = initialState, action) { + switch (action.type) { + case constants.SHOW_DELETE_DIALOG: + return state.set('files', action.files) + default: + return state + } +} diff --git a/plugins/Inspector/js/reducers/files.js b/plugins/Inspector/js/reducers/files.js new file mode 100644 index 00000000..48082c7f --- /dev/null +++ b/plugins/Inspector/js/reducers/files.js @@ -0,0 +1,165 @@ +import { Map, Set, OrderedSet, List } from 'immutable' +import * as constants from '../constants/files.js' +import { ls, searchFiles, allFiles, rangeSelect } from '../sagas/helpers.js' +import Path from 'path' + +const initialState = Map({ + files: List(), + folders: List(), + workingDirectoryFiles: null, + searchResults: List(), + uploading: List(), + downloading: List(), + selected: OrderedSet(), + path: '', + searchText: '', + uploadSource: '', + showAllowanceDialog: false, + showAddFolderDialog: false, + showUploadDialog: false, + showSearchField: false, + showFileTransfers: false, + showDeleteDialog: false, + showRenameDialog: false, + settingAllowance: false, + dragging: false, + dragUploadEnabled: true, + dragFolderTarget: '', + dragFileOrigin: {}, + contractCount: 0, + allowance: '0', + spending: '0', + showDownloadsSince: Date.now(), + unreadUploads: Set(), + unreadDownloads: Set(), + showDetailPath: null, +}) + + +export default function filesReducer(state = initialState, action) { + switch (action.type) { + case constants.SET_DRAG_FILE_ORIGIN: + return state.set('dragFileOrigin', action.origin) + case constants.SET_DRAG_FOLDER_TARGET: + return state.set('dragFolderTarget', action.target) + case constants.SET_DRAG_UPLOAD_ENABLED: + return state.set('dragUploadEnabled', action.enabled) + case constants.SET_ALLOWANCE_COMPLETED: + return state.set('settingAllowance', false) + case constants.RECEIVE_ALLOWANCE: + return state.set('allowance', action.allowance) + case constants.RECEIVE_SPENDING: + return state.set('spending', action.spending) + case constants.DOWNLOAD_FILE: + return state.set('unreadDownloads', state.get('unreadDownloads').add(action.file.siapath)) + case constants.UPLOAD_FILE: + return state.set('unreadUploads', state.get('unreadUploads').add(action.siapath)) + case constants.RECEIVE_FILES: { + const workingDirectoryFiles = ls(action.files.concat(state.get('folders')), state.get('path')) + const workingDirectorySiapaths = workingDirectoryFiles.map((file) => file.siapath) + // filter out selected files that are no longer in the working directory + const selected = state.get('selected').filter((file) => workingDirectorySiapaths.includes(file.siapath)) + return state.set('files', action.files) + .set('workingDirectoryFiles', workingDirectoryFiles) + .set('selected', selected) + } + case constants.ADD_FOLDER: { + const folder = { + filesize: 0, + siapath: Path.join(state.get('path'), action.name), + available: false, + redundancy: -1, + uploadprogress: 100, + siaUIFolder: true, + } + const folders = state.get('folders').push(folder) + return state.set('folders', folders) + .set('workingDirectoryFiles', ls(state.get('files').concat(folders), state.get('path')), folders, state.get('path')) + } + case constants.DELETE_SIA_UI_FOLDER: { + return state.set('folders', state.get('folders').filter((folder) => { + const cleanSiapath = action.siapath.replace(/\/$/, '') + const cleanSource = folder.siapath.replace(/\/$/, '') + return cleanSiapath !== cleanSource + })) + } + case constants.RENAME_SIA_UI_FOLDER: + return state.set('folders', state.get('folders').map((folder) => { + const cleanSource = action.source.replace(/\/$/, '') + if (folder.siapath.indexOf(cleanSource) === 0) { + folder.siapath = action.dest + } + return folder + })) + case constants.SET_ALLOWANCE: + return state.set('allowance', action.funds) + .set('settingAllowance', true) + case constants.CLEAR_DOWNLOADS: + return state.set('showDownloadsSince', Date.now()) + case constants.SET_SEARCH_TEXT: { + const results = searchFiles(allFiles(state), action.text, state.get('path')) + return state.set('searchResults', results) + .set('searchText', action.text) + } + case constants.SET_PATH: { + const workingDirFiles = ls(allFiles(state), action.path) + return state.set('path', action.path) + .set('selected', OrderedSet()) + .set('workingDirectoryFiles', workingDirFiles) + .set('searchResults', searchFiles(allFiles(state), state.get('searchText'), action.path)) + } + case constants.DESELECT_FILE: + return state.set('selected', state.get('selected').filter((file) => file.siapath !== action.file.siapath)) + case constants.SELECT_FILE: + return state.set('selected', state.get('selected').add(action.file)) + case constants.DESELECT_ALL: + return state.set('selected', OrderedSet()) + case constants.SELECT_UP_TO: + return state.set('selected', rangeSelect(action.file, state.get('workingDirectoryFiles'), state.get('selected'))) + case constants.SHOW_ALLOWANCE_DIALOG: + return state.set('showAllowanceDialog', true) + case constants.CLOSE_ALLOWANCE_DIALOG: + return state.set('showAllowanceDialog', false) + case constants.TOGGLE_SEARCH_FIELD: + return state.set('showSearchField', !state.get('showSearchField')) + case constants.SET_DRAGGING: + return state.set('dragging', true) + case constants.SET_NOT_DRAGGING: + return state.set('dragging', false) + case constants.SHOW_DELETE_DIALOG: + return state.set('showDeleteDialog', true) + case constants.HIDE_DELETE_DIALOG: + return state.set('showDeleteDialog', false) + case constants.SHOW_UPLOAD_DIALOG: + return state.set('showUploadDialog', true) + .set('uploadSource', action.source) + case constants.HIDE_UPLOAD_DIALOG: + return state.set('showUploadDialog', false) + case constants.RECEIVE_UPLOADS: + return state.set('uploading', action.uploads) + case constants.RECEIVE_DOWNLOADS: + return state.set('downloading', action.downloads.filter((download) => Date.parse(download.starttime) > state.get('showDownloadsSince'))) + case constants.SHOW_FILE_TRANSFERS: + return state.set('showFileTransfers', true) + case constants.HIDE_FILE_TRANSFERS: + return state.set('showFileTransfers', false) + case constants.TOGGLE_FILE_TRANSFERS: + return state.set('showFileTransfers', !state.get('showFileTransfers')) + .set('unreadDownloads', Set()) + .set('unreadUploads', Set()) + case constants.SET_CONTRACT_COUNT: + return state.set('contractCount', action.count) + case constants.SHOW_RENAME_DIALOG: + return state.set('showRenameDialog', true) + case constants.HIDE_RENAME_DIALOG: + return state.set('showRenameDialog', false) + case constants.SHOW_ADD_FOLDER_DIALOG: + return state.set('showAddFolderDialog', true) + case constants.HIDE_ADD_FOLDER_DIALOG: + return state.set('showAddFolderDialog', false) + case constants.SHOW_FILE_DETAIL: + return state.set('showDetailPath', action.siapath) + default: + return state + } +} diff --git a/plugins/Inspector/js/reducers/index.js b/plugins/Inspector/js/reducers/index.js new file mode 100644 index 00000000..c68892d3 --- /dev/null +++ b/plugins/Inspector/js/reducers/index.js @@ -0,0 +1,16 @@ +import { combineReducers } from 'redux' +import wallet from './wallet.js' +import files from './files.js' +import deletedialog from './deletedialog.js' +import renamedialog from './renamedialog.js' +import allowancedialog from './allowancedialog.js' + +const rootReducer = combineReducers({ + wallet, + files, + deletedialog, + renamedialog, + allowancedialog, +}) + +export default rootReducer diff --git a/plugins/Inspector/js/reducers/renamedialog.js b/plugins/Inspector/js/reducers/renamedialog.js new file mode 100644 index 00000000..8ef997a6 --- /dev/null +++ b/plugins/Inspector/js/reducers/renamedialog.js @@ -0,0 +1,15 @@ +import { Map } from 'immutable' +import * as constants from '../constants/files.js' + +const initialState = Map({ + file: {}, +}) + +export default function renamedialogReducer(state = initialState, action) { + switch (action.type) { + case constants.SHOW_RENAME_DIALOG: + return state.set('file', action.file) + default: + return state + } +} diff --git a/plugins/Inspector/js/reducers/wallet.js b/plugins/Inspector/js/reducers/wallet.js new file mode 100644 index 00000000..d468fa8f --- /dev/null +++ b/plugins/Inspector/js/reducers/wallet.js @@ -0,0 +1,21 @@ +import { Map } from 'immutable' +import * as constants from '../constants/files.js' + +const initialState = Map({ + unlocked: false, + synced: false, + balance: '', +}) + +export default function walletReducer(state = initialState, action) { + switch (action.type) { + case constants.RECEIVE_WALLET_LOCKSTATE: + return state.set('unlocked', action.unlocked) + case constants.RECEIVE_WALLET_BALANCE: + return state.set('balance', action.balance) + case constants.SET_WALLET_SYNCSTATE: + return state.set('synced', action.synced) + default: + return state + } +} diff --git a/plugins/Inspector/js/sagas/files.js b/plugins/Inspector/js/sagas/files.js new file mode 100644 index 00000000..43d355a0 --- /dev/null +++ b/plugins/Inspector/js/sagas/files.js @@ -0,0 +1,324 @@ +import { takeEvery, delay } from 'redux-saga' +import { fork, join, put, race, take, call } from 'redux-saga/effects' +import Path from 'path' +import fs from 'graceful-fs' +import * as actions from '../actions/files.js' +import * as constants from '../constants/files.js' +import { List } from 'immutable' +import BigNumber from 'bignumber.js' +import { ls, uploadDirectory, sendError, allowancePeriod, readableFilesize, siadCall, readdirRecursive, parseDownloads, parseUploads } from './helpers.js' + +// Query siad for the state of the wallet. +// dispatch `unlocked` in receiveWalletLockstate +function* getWalletLockstateSaga() { + try { + const response = yield siadCall('/wallet') + yield put(actions.receiveWalletLockstate(response.unlocked)) + } catch (e) { + console.error('error fetching wallet lock state: ' + e.toString()) + } +} + +// Query siad for the sync state of the wallet. +function* getWalletSyncstateSaga() { + try { + const response = yield siadCall('/consensus') + yield put(actions.setWalletSyncstate(response.synced)) + } catch (e) { + console.error('error fetching wallet sync state: ' + e.toString()) + } +} + +// Query siad for the user's files. +function* getFilesSaga() { + try { + const response = yield siadCall('/renter/filesdetail') + const files = List(response.files) + yield put(actions.receiveFiles(files)) + } catch (e) { + console.error('error fetching files: ' + e.toString()) + } +} + +function* getStorageEstimateSaga(action) { + try { + const response = yield siadCall('/renter/prices') + if (response.storageterabytemonth === '0') { + yield put(actions.setStorageEstimate('No Hosts')) + return + } + const estimate = new BigNumber(SiaAPI.siacoinsToHastings(action.funds)).dividedBy(response.storageterabytemonth).times(1e12) + + yield put(actions.setStorageEstimate('~' + readableFilesize(estimate.toPrecision(1)))) + yield put(actions.setFeeEstimate(SiaAPI.hastingsToSiacoins(response.formcontracts).toString())) + } catch (e) { + console.error(e) + } +} + +// Get the renter's current allowance and spending. +function* getAllowanceSaga() { + try { + const response = yield siadCall('/renter') + const allowance = SiaAPI.hastingsToSiacoins(response.settings.allowance.funds) + + // compute allowance spending. Set the spending to zero if it is negative, + // since negative spending is confusing to the user. + let spending = allowance.minus(SiaAPI.hastingsToSiacoins(response.financialmetrics.unspent)) + if (spending.isNegative()) { + spending = new BigNumber(0) + } + + yield put(actions.receiveAllowance(allowance.round(0).toString())) + yield put(actions.receiveSpending(spending.round(0).toString())) + } catch (e) { + console.error('error getting allowance: ' + e.toString()) + } +} + +// Set the user's renter allowance. +function* setAllowanceSaga(action) { + try { + const newAllowance = SiaAPI.siacoinsToHastings(action.funds) + yield put(actions.closeAllowanceDialog()) + yield siadCall({ + url: '/renter', + method: 'POST', + timeout: 7.2e6, // 120 minute timeout for setting allowance + qs: { + funds: newAllowance.toString(), + period: allowancePeriod, + }, + }) + yield put(actions.setAllowanceCompleted()) + } catch (e) { + sendError(e) + yield put(actions.setAllowanceCompleted()) + yield put(actions.closeAllowanceDialog()) + } +} + +// Query Siad for the current wallet balance. +function* getWalletBalanceSaga() { + try { + const response = yield siadCall('/wallet') + const confirmedBalance = SiaAPI.hastingsToSiacoins(response.confirmedsiacoinbalance).round(2).toString() + yield put(actions.receiveWalletBalance(confirmedBalance)) + } catch (e) { + console.error('error fetching wallet balance: ' + e.toString()) + } +} + +// UploadFileSaga uploads a file to the Sia network. +// action.siapath: the working directory to upload the file to +// action.source: the path to the file to upload. +// The full siapath is computed as Path.join(action.siapath, Path.basename(action.source)) +function* uploadFileSaga(action) { + try { + const filename = Path.basename(action.source) + const destpath = Path.posix.join(action.siapath, filename) + yield siadCall({ + url: '/renter/upload/' + encodeURI(destpath), + timeout: 20000, // 20 second timeout for upload calls + method: 'POST', + qs: { + source: action.source, + }, + }) + } catch (e) { + sendError(e) + } +} + +// uploadFolderSaga uploads a folder to the Sia network. +// action.source: the source path of the folder +// action.siapath: the working directory to upload the folder inside +function *uploadFolderSaga(action) { + try { + const files = readdirRecursive(action.source) + const uploads = uploadDirectory(action.source, files, action.siapath) + for (const upload in uploads.toArray()) { + yield put(uploads.get(upload)) + } + } catch (e) { + sendError(e) + } +} + +function* downloadFileSaga(action) { + try { + if (action.file.type === 'file') { + yield siadCall({ + url: '/renter/download/' + encodeURI(action.file.siapath), + timeout: 6e8, + method: 'GET', + qs: { + destination: action.downloadpath, + }, + }) + } + if (action.file.type === 'directory') { + fs.mkdirSync(action.downloadpath) + const response = yield siadCall('/renter/files') + const siafiles = ls(List(response.files), action.file.siapath) + for (const siafile in siafiles.toArray()) { + const file = siafiles.get(siafile) + yield put(actions.downloadFile(file, Path.join(action.downloadpath, file.name))) + yield new Promise((resolve) => setTimeout(resolve, 300)) + } + } + } catch (e) { + sendError(e) + } +} + +function* getDownloadsSaga() { + try { + const response = yield siadCall('/renter/downloads') + const downloads = parseDownloads(response.downloads) + yield put(actions.receiveDownloads(downloads)) + } catch (e) { + console.error('error fetching downloads: ' + e.toString()) + } +} + +function* getUploadsSaga() { + try { + const response = yield siadCall('/renter/files') + const uploads = parseUploads(response.files) + yield put(actions.receiveUploads(uploads)) + } catch (e) { + console.error('error fetching uploads: ' + e.toString()) + } +} + +function* deleteFileSaga(action) { + try { + if (action.file.siaUIFolder) { + yield put(actions.deleteSiaUIFolder(action.file.siapath)) + } else if (action.file.type === 'file') { + yield siadCall({ + url: '/renter/delete/' + encodeURI(action.file.siapath), + timeout: 3.6e6, // 60 minute timeout for deleting files + method: 'POST', + }) + yield put(actions.getFiles()) + } else if (action.file.type === 'directory') { + const response = yield siadCall('/renter/files') + const siafiles = ls(List(response.files), action.file.siapath) + for (const siafile in siafiles.toArray()) { + const file = siafiles.get(siafile) + yield siadCall({ + url: '/renter/delete/' + encodeURI(file.siapath), + timeout: 3.6e6, // 60 minute timeout for deleting files + method: 'POST', + }) + } + } + } catch (e) { + sendError(e) + } +} + +function* getContractCountSaga() { + try { + const response = yield siadCall('/renter/contracts') + yield put(actions.setContractCount(response.contracts.length)) + } catch (e) { + console.error('error getting contract count: ' + e.toString()) + } +} + +function* renameFileSaga(action) { + try { + if (action.file.siaUIFolder) { + yield put(actions.renameSiaUIFolder(action.file.siapath, action.newsiapath)) + } else if (action.file.type === 'file') { + yield siadCall({ + url: '/renter/rename/' + encodeURI(action.file.siapath), + method: 'POST', + qs: { + newsiapath: action.newsiapath, + }, + }) + yield put(actions.getFiles()) + } else if (action.file.type === 'directory') { + const directorypath = action.file.siapath + const response = yield siadCall('/renter/files') + const siafiles = ls(List(response.files), directorypath) + for (const i in siafiles.toArray()) { + const file = siafiles.get(i) + const newfilepath = Path.posix.join(action.newsiapath, file.siapath.split(directorypath)[1]) + yield put(actions.renameFile(file, newfilepath)) + } + } + yield put(actions.hideRenameDialog()) + } catch (e) { + sendError(e) + } +} + +export function* dataFetcher() { + while (true) { + let tasks = [] + // tasks = tasks.concat(yield fork(getDownloadsSaga)) + tasks = tasks.concat(yield fork(getFilesSaga)) + // tasks = tasks.concat(yield fork(getUploadsSaga)) + // tasks = tasks.concat(yield fork(getContractCountSaga)) + // tasks = tasks.concat(yield fork(getWalletBalanceSaga)) + // tasks = tasks.concat(yield fork(getWalletSyncstateSaga)) + // tasks = tasks.concat(yield fork(getWalletLockstateSaga)) + // tasks = tasks.concat(yield fork(getAllowanceSaga)) + + yield join(...tasks) + yield race({ + task: call(delay, 8000), + cancel: take(constants.FETCH_DATA), + }) + } +} +export function* watchSetAllowance() { + yield *takeEvery(constants.SET_ALLOWANCE, setAllowanceSaga) +} +export function* watchGetAllowance() { + yield *takeEvery(constants.GET_ALLOWANCE, getAllowanceSaga) +} +export function* watchGetDownloads() { + yield *takeEvery(constants.GET_DOWNLOADS, getDownloadsSaga) +} +export function* watchGetUploads() { + yield *takeEvery(constants.GET_UPLOADS, getUploadsSaga) +} +export function* watchGetWalletLockstate() { + yield *takeEvery(constants.GET_WALLET_LOCKSTATE, getWalletLockstateSaga) +} +export function* watchGetFiles() { + yield *takeEvery(constants.GET_FILES, getFilesSaga) +} +export function* watchDeleteFile() { + yield *takeEvery(constants.DELETE_FILE, deleteFileSaga) +} +export function* watchGetWalletBalance() { + yield *takeEvery(constants.GET_WALLET_BALANCE, getWalletBalanceSaga) +} +export function* watchUploadFolder() { + yield *takeEvery(constants.UPLOAD_FOLDER, uploadFolderSaga) +} +export function* watchGetContractCount() { + yield *takeEvery(constants.GET_CONTRACT_COUNT, getContractCountSaga) +} +export function* watchUploadFile() { + yield *takeEvery(constants.UPLOAD_FILE, uploadFileSaga) +} +export function* watchDownloadFile() { + yield *takeEvery(constants.DOWNLOAD_FILE, downloadFileSaga) +} +export function* watchRenameFile() { + yield *takeEvery(constants.RENAME_FILE, renameFileSaga) +} +export function* watchGetStorageEstimate() { + yield *takeEvery(constants.GET_STORAGE_ESTIMATE, getStorageEstimateSaga) +} +export function* watchGetWalletSyncstate() { + yield *takeEvery(constants.GET_WALLET_SYNCSTATE, getWalletSyncstateSaga) +} diff --git a/plugins/Inspector/js/sagas/helpers.js b/plugins/Inspector/js/sagas/helpers.js new file mode 100644 index 00000000..6f7d1ee6 --- /dev/null +++ b/plugins/Inspector/js/sagas/helpers.js @@ -0,0 +1,271 @@ +// Helper functions for the Files sagas. +import { List, Map } from 'immutable' +import Path from 'path' +import fs from 'graceful-fs' +import * as actions from '../actions/files.js' + +export const blockMonth = 4320 +export const allowanceMonths = 3 +export const allowancePeriod = blockMonth*allowanceMonths +export const ncontracts = 24 +export const baseRedundancy = 6 +export const baseFee = 240 +export const siafundRate = 0.12 + +// sendError sends the error given by e to the ui for display. +export const sendError = (e) => { + SiaAPI.showError({ + title: 'Sia-UI Files Error', + content: typeof e.message !== 'undefined' ? e.message : e.toString(), + }) +} + +// siadCall: promisify Siad API calls. Resolve the promise with `response` if the call was successful, +// otherwise reject the promise with `err`. +export const siadCall = (uri) => new Promise((resolve, reject) => { + SiaAPI.call(uri, (err, response) => { + if (err) { + reject(err) + } else { + resolve(response) + } + }) +}) + +// Take a number of bytes and return a sane, human-readable size. +export const readableFilesize = (bytes) => { + const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + let readableunit = 'B' + let readablesize = bytes + for (const unit in units) { + if (readablesize < 1000) { + readableunit = units[unit] + break + } + readablesize /= 1000 + } + return readablesize.toFixed().toString() + ' ' + readableunit +} + +// minRedundancy takes a list of files and returns the minimum redundancy that +// occurs in the list. +export const minRedundancy = (files) => { + if (files.size === 0) { + return 0 + } + const redundantFiles = files.filter((file) => file.redundancy >= 0) + + // if all the provided files have -1 redundancy, return -1. + if (redundantFiles.size === 0) { + return -1 + } + + // return the minimum redundancy of all the files with redundancy >= 0 + return redundantFiles.min((a, b) => { + if (a.redundancy > b.redundancy) { + return 1 + } + return -1 + }).redundancy +} + +// minUpload takes a list of files and returns the minimum upload progress that +// occurs in the list. +export const minUpload = (files) => { + if (files.size === 0) { + return 0 + } + + return files.map((f) => f.uploadprogress).min() +} +// directoriesFirst is a comparator function used to sort files by type, where +// the directories will always come first. +const directoriesFirst = (file1, file2) => { + if (file1.type === 'directory' && file2.type === 'file') { + return -1 + } + if (file1.type === 'file' && file2.type === 'directory') { + return 1 + } + return 0 +} + + +// return a list of files filtered with path. +// ... it's ls. +export const ls = (files, path) => { + const fileList = files.filter((file) => file.siapath.includes(path) && file.siapath !== path) + let parsedFiles = Map() + fileList.forEach((file) => { + let type = 'file' + const relativePath = Path.posix.relative(path, file.siapath) + let filename = Path.posix.basename(relativePath) + let uploadprogress = Math.floor(file.uploadprogress) + let siapath = file.siapath + let filesize = readableFilesize(file.filesize) + let redundancy = file.redundancy + if (relativePath.indexOf('/') !== -1 || file.siaUIFolder === true) { + type = 'directory' + filename = relativePath.split('/')[0] + } + if (parsedFiles.has(filename) && parsedFiles.get(filename).type === type) { + return + } + if (type === 'directory') { + // directories cannot be named '..'. + if (filename === '..') { + return + } + + siapath = Path.posix.join(path, filename) + '/' + const subfiles = files.filter((subfile) => subfile.siapath.includes(siapath)) + const totalFilesize = subfiles.reduce((sum, subfile) => sum + subfile.filesize, 0) + filesize = readableFilesize(totalFilesize) + if (!file.siaUIFolder) { + redundancy = minRedundancy(subfiles) + } else { + redundancy = -1 + } + uploadprogress = minUpload(subfiles) + } + parsedFiles = parsedFiles.set(filename, { + size: filesize, + name: filename, + siapath: siapath, + available: file.available, + redundancy: redundancy, + uploadprogress: uploadprogress, + siaUIFolder: file.siaUIFolder === true, + type, + details: file.details, + }) + }) + return parsedFiles.toList().sortBy((file) => file.name).sort(directoriesFirst) +} + +// recursive version of readdir +export const readdirRecursive = (path, files) => { + const dirfiles = fs.readdirSync(path) + let filelist + if (typeof files === 'undefined') { + filelist = List() + } else { + filelist = files + } + dirfiles.forEach((file) => { + const filepath = Path.join(path, file) + const stat = fs.statSync(filepath) + if (stat.isDirectory()) { + filelist = readdirRecursive(filepath, filelist) + } else if (stat.isFile()) { + filelist = filelist.push(filepath) + } + }) + return filelist +} + +// uploadDirectory takes a `directory`, a list of files inside the directory, +// and a destination siapath and returns a List of upload actions that will +// upload each file to `destpath/directoryname/`. +export const uploadDirectory = (directory, files, destpath) => + files.map((file) => { + const relativePath = Path.dirname(file.substring(directory.length + 1)) + const siapath = Path.posix.join(destpath, Path.basename(directory), relativePath) + return actions.uploadFile(siapath, file) + }) + +// Parse a response from `/renter/downloads` +// return a list of file downloads +export const parseDownloads = (downloads) => + List(downloads) + .map((download) => ({ + status: (() => { + if (Math.floor(download.received / download.filesize) === 1) { + return 'Completed' + } + return 'Downloading' + })(), + siapath: download.siapath, + name: Path.basename(download.siapath), + progress: Math.floor((download.received / download.filesize) * 100), + destination: download.destination, + type: 'download', + starttime: download.starttime, + })) + .sortBy((download) => -download.starttime) + +// Parse a list of files and return the total filesize +export const totalUsage = (files) => readableFilesize(files.reduce((sum, file) => sum + file.filesize, 0)) + +// Parse a list of files from `/renter/files` +// return a list of file uploads +export const parseUploads = (files) => + List(files) + .filter((file) => file.redundancy >= 0) + .filter((file) => file.uploadprogress < 100) + .map((upload) => ({ + status: (() => { + if (upload.redundancy < 1.0) { + return 'Uploading' + } + return 'Boosting Redundancy' + })(), + siapath: upload.siapath, + name: Path.basename(upload.siapath), + progress: Math.floor(upload.uploadprogress), + type: 'upload', + })) + .sortBy((upload) => upload.name) + .sortBy((upload) => -upload.progress) + +// Search `files` for `text`, excluding directories not in `path` +export const searchFiles = (files, text, path) => { + const filteredFiles = List(files) + .filter((file) => file.siapath.indexOf(path) === 0 && file.siapath !== path) + .filter((file) => file.siapath.toLowerCase().includes(text.toLowerCase())) + + let parsedFiles = Map() + filteredFiles.forEach((file) => { + let type = 'file' + let name = Path.posix.basename(file.siapath) + let siapath = file.siapath + const pathComponents = file.siapath.split('/') + if (!Path.posix.basename(file.siapath).toLowerCase().includes(text.toLowerCase()) || file.siaUIFolder) { + type = 'directory' + pathComponents.forEach((component, idx) => { + if (component.toLowerCase().includes(text.toLowerCase())) { + name = component + siapath = pathComponents.slice(0, idx+1).join('/') + '/' + } + }) + } + if (!parsedFiles.has(siapath) && siapath !== path) { + const parsedFile = Object.assign({}, file) + parsedFile.siapath = siapath + parsedFile.type = type + parsedFile.name = name + parsedFiles = parsedFiles.set(siapath, parsedFile) + } + }) + + return parsedFiles.toList() +} + +// rangeSelect takes a file to select, a list of files, and a set of selected +// files and returns a new set of selected files consisting of all the files +// between the last selected file and the clicked `file`. +export const rangeSelect = (file, files, selectedFiles) => { + const siapaths = files.map((f) => f.siapath) + const selectedSiapaths = selectedFiles.map((selectedfile) => selectedfile.siapath) + + const endSelectionIndex = siapaths.indexOf(file.siapath) + const startSelectionIndex = siapaths.indexOf(selectedSiapaths.first()) + if (startSelectionIndex > endSelectionIndex) { + return files.slice(endSelectionIndex, startSelectionIndex + 1).toOrderedSet().reverse() + } + return files.slice(startSelectionIndex, endSelectionIndex + 1).toOrderedSet() +} + + +// allFiles returns all the files in the state, including Sia-UI folders +export const allFiles = (state) => state.get('files').concat(state.get('folders')) diff --git a/plugins/Inspector/js/sagas/index.js b/plugins/Inspector/js/sagas/index.js new file mode 100644 index 00000000..8a61e030 --- /dev/null +++ b/plugins/Inspector/js/sagas/index.js @@ -0,0 +1,7 @@ +import * as sagas from './files.js' +import { fork } from 'redux-saga/effects' + +export default function* rootSaga() { + const watchers = Object.values(sagas).map(fork) + yield watchers +} From 850302fc4337ff4cdbf11f818aa725893bbe78dd Mon Sep 17 00:00:00 2001 From: wangchao Date: Sun, 30 Jul 2017 11:45:18 +0800 Subject: [PATCH 02/12] add close button --- package.json | 3 +- plugins/Inspector/js/actions/files.js | 3 + plugins/Inspector/js/components/filedetail.js | 65 +++++++++++-------- plugins/Inspector/js/components/filelist.js | 2 +- plugins/Inspector/js/constants/files.js | 1 + plugins/Inspector/js/containers/filelist.js | 4 +- plugins/Inspector/js/reducers/files.js | 2 + 7 files changed, 49 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index a30eed27..57aad6ff 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "uglify-js": "git://github.com/mishoo/UglifyJS2.git#harmony-v2.8.22", "uglifyjs-webpack-plugin": "^0.4.3", "webpack": "^2.5.1", - "rc-tooltip": "^3.4.7" + "rc-tooltip": "^3.4.7", + "react-svg-buttons": "^0.4.0" }, "dependencies": { "babel-polyfill": "^6.9.1", diff --git a/plugins/Inspector/js/actions/files.js b/plugins/Inspector/js/actions/files.js index f0af749b..fe067c2d 100644 --- a/plugins/Inspector/js/actions/files.js +++ b/plugins/Inspector/js/actions/files.js @@ -220,3 +220,6 @@ export const showFileDetail = (siapath) => ({ type: constants.SHOW_FILE_DETAIL, siapath, }) +export const closeFileDetail = () => ({ + type: constants.CLOSE_FILE_DETAIL, +}) \ No newline at end of file diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index d8668dd1..278a5892 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -1,17 +1,19 @@ import PropTypes from 'prop-types' import React from 'react' -import Tooltip from 'rc-tooltip'; +import Tooltip from 'rc-tooltip' +import { CloseButton } from 'react-svg-buttons' -const FileDetail = ({showDetailFile}) => { +const FileDetail = ({showDetailFile, actions}) => { + const closeDetail = actions.closeFileDetail const getChunkTooltip = chunk => chunk.map( (piece, i) => { var s; if(!piece.host) { - s = pieceEmpty + s = pieceEmptyStyle } else if(piece.isoffline) { - s = pieceOffline + s = pieceOfflineStyle } else { - s = pieceOnline + s = pieceOnlineStyle } return (
    {piece.host ? piece.host : "Empty"}
    ) } @@ -24,27 +26,31 @@ const FileDetail = ({showDetailFile}) => { return sum }, 0) if(onlineChunk == chunk.length) { - return repairChunkRepaired + return repairChunkRepairedStyle } if(onlineChunk == 0) { - return repairChunkQueued + return repairChunkQueuedStyle } - return repairChunkRepairing + return repairChunkRepairingStyle } const details = showDetailFile.details return (
    +
    + +
    +

    {showDetailFile.name}

    -
    +
    {details.map((v,i) => ( ) )} -
    +
    ) @@ -58,7 +64,7 @@ export default FileDetail const logViewStyle = { position: 'absolute', - top: '55px', + top: '20px', bottom: '0', left: '20px', right: '20px', @@ -74,44 +80,49 @@ const liViewStyle = { cursor: 'pointer', } -const reapirFileView = { +const reapirFileViewStyle = { margin: '10px 20px', } -const repaireChunk = { +const repaireChunkStyle = { width: '8px', height: '8px', float: 'left', border: 'solid 1px #001f3f', } -const repairChunkRepaired = { - ...repaireChunk, +const repairChunkRepairedStyle = { + ...repaireChunkStyle, background: '#01FF70', } -const repairChunkRepairing = { - ...repaireChunk, +const repairChunkRepairingStyle = { + ...repaireChunkStyle, background: '#FF851B', } -const repairChunkQueued = { - ...repaireChunk, +const repairChunkQueuedStyle = { + ...repaireChunkStyle, background: 'white', } -const clearBoth = { +const clearBothStyle = { clear: 'both', } -const pieceOnline = { - color: '#01FF70' +const pieceOnlineStyle = { + color: '#01FF70', +} + +const pieceOfflineStyle = { + color: '#dc143c', } -const pieceOffline = { - color: '#dc143c' +const pieceEmptyStyle = { + color: 'white', } -const pieceEmpty = { - color: 'white' -} \ No newline at end of file +const closeButtonStyle = { + float: 'right', + cursor: 'pointer', +} diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js index fff22534..794496c3 100644 --- a/plugins/Inspector/js/components/filelist.js +++ b/plugins/Inspector/js/components/filelist.js @@ -41,7 +41,7 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi const showDetailFile = files.find(file => file.siapath == showDetailPath) return (
    - +
    ) } diff --git a/plugins/Inspector/js/constants/files.js b/plugins/Inspector/js/constants/files.js index c64da61d..6dc6ffb4 100644 --- a/plugins/Inspector/js/constants/files.js +++ b/plugins/Inspector/js/constants/files.js @@ -60,3 +60,4 @@ export const RENAME_SIA_UI_FOLDER = 'RENAME_SIA_UI_FOLDER' export const DELETE_SIA_UI_FOLDER = 'DELETE_SIA_UI_FOLDER' export const SHOW_FILE_DETAIL = 'SHOW_FILE_DETAIL' +export const CLOSE_FILE_DETAIL = 'CLOSE_FILE_DETAIL' \ No newline at end of file diff --git a/plugins/Inspector/js/containers/filelist.js b/plugins/Inspector/js/containers/filelist.js index a8acc786..551f91d5 100644 --- a/plugins/Inspector/js/containers/filelist.js +++ b/plugins/Inspector/js/containers/filelist.js @@ -1,7 +1,7 @@ import FileListView from '../components/filelist.js' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import { renameSiaUIFolder, deleteSiaUIFolder, renameFile, getFiles, setDragFolderTarget, setDragFileOrigin, setDragUploadEnabled, setPath, selectUpTo, deselectFile, deselectAll, selectFile, downloadFile, showDeleteDialog, showRenameDialog, showFileDetail } from '../actions/files.js' +import { renameSiaUIFolder, deleteSiaUIFolder, renameFile, getFiles, setDragFolderTarget, setDragFileOrigin, setDragUploadEnabled, setPath, selectUpTo, deselectFile, deselectAll, selectFile, downloadFile, showDeleteDialog, showRenameDialog, showFileDetail, closeFileDetail } from '../actions/files.js' const mapStateToProps = (state) => ({ files: state.files.get('workingDirectoryFiles'), @@ -14,7 +14,7 @@ const mapStateToProps = (state) => ({ showDetailPath: state.files.get('showDetailPath') }) const mapDispatchToProps = (dispatch) => ({ - actions: bindActionCreators({ renameSiaUIFolder, deleteSiaUIFolder, getFiles, renameFile, setDragFileOrigin, setDragFolderTarget, setDragUploadEnabled, selectUpTo, setPath, deselectFile, deselectAll, selectFile, showRenameDialog, downloadFile, showDeleteDialog, showFileDetail }, dispatch), + actions: bindActionCreators({ renameSiaUIFolder, deleteSiaUIFolder, getFiles, renameFile, setDragFileOrigin, setDragFolderTarget, setDragUploadEnabled, selectUpTo, setPath, deselectFile, deselectAll, selectFile, showRenameDialog, downloadFile, showDeleteDialog, showFileDetail, closeFileDetail }, dispatch), }) const FileList = connect(mapStateToProps, mapDispatchToProps)(FileListView) diff --git a/plugins/Inspector/js/reducers/files.js b/plugins/Inspector/js/reducers/files.js index 48082c7f..a7ef9a85 100644 --- a/plugins/Inspector/js/reducers/files.js +++ b/plugins/Inspector/js/reducers/files.js @@ -159,6 +159,8 @@ export default function filesReducer(state = initialState, action) { return state.set('showAddFolderDialog', false) case constants.SHOW_FILE_DETAIL: return state.set('showDetailPath', action.siapath) + case constants.CLOSE_FILE_DETAIL: + return state.delete('showDetailPath') default: return state } From 344d0963d3e4c9b6010a487bddf7d6f27cdf319b Mon Sep 17 00:00:00 2001 From: wangchao Date: Sun, 30 Jul 2017 12:17:50 +0800 Subject: [PATCH 03/12] fix directory --- plugins/Inspector/js/components/filelist.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js index 794496c3..ef01e8d7 100644 --- a/plugins/Inspector/js/components/filelist.js +++ b/plugins/Inspector/js/components/filelist.js @@ -57,6 +57,9 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi const isSelected = selected.map((selectedfile) => selectedfile.name).includes(file.name) const onFileClick = (e) => { // a show file detail action + if (file.type === 'directory') { + return + } actions.showFileDetail(file.siapath) // const shouldMultiSelect = e.ctrlKey || e.metaKey From 1b62c034b524875d9b093d5a0de61787000f9d23 Mon Sep 17 00:00:00 2001 From: wangchao Date: Sun, 30 Jul 2017 22:26:46 +0800 Subject: [PATCH 04/12] fix all eslint error --- plugins/Inspector/js/actions/files.js | 2 +- .../Inspector/js/components/filebrowser.js | 3 +- plugins/Inspector/js/components/filedetail.js | 178 +++++++++--------- plugins/Inspector/js/components/filelist.js | 21 +-- plugins/Inspector/js/constants/files.js | 2 +- plugins/Inspector/js/containers/filelist.js | 2 +- 6 files changed, 94 insertions(+), 114 deletions(-) diff --git a/plugins/Inspector/js/actions/files.js b/plugins/Inspector/js/actions/files.js index fe067c2d..5a28bdfe 100644 --- a/plugins/Inspector/js/actions/files.js +++ b/plugins/Inspector/js/actions/files.js @@ -222,4 +222,4 @@ export const showFileDetail = (siapath) => ({ }) export const closeFileDetail = () => ({ type: constants.CLOSE_FILE_DETAIL, -}) \ No newline at end of file +}) diff --git a/plugins/Inspector/js/components/filebrowser.js b/plugins/Inspector/js/components/filebrowser.js index 3cd9918c..5ff35c7c 100644 --- a/plugins/Inspector/js/components/filebrowser.js +++ b/plugins/Inspector/js/components/filebrowser.js @@ -1,11 +1,10 @@ import PropTypes from 'prop-types' import React from 'react' import FileList from '../containers/filelist.js' -import TransfersButton from '../containers/transfersbutton.js' import FileTransfers from '../containers/filetransfers.js' import DragOverlay from './dragoverlay.js' -const FileBrowser = ({dragging, dragUploadEnabled, settingAllowance, showAddFolderDialog, showRenameDialog, showUploadDialog, showDeleteDialog, showFileTransfers, actions}) => { +const FileBrowser = ({dragging, dragUploadEnabled, showFileTransfers, actions}) => { const onDragOver = (e) => { if (!dragUploadEnabled) { return diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 278a5892..5e6c79f0 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -3,126 +3,122 @@ import React from 'react' import Tooltip from 'rc-tooltip' import { CloseButton } from 'react-svg-buttons' -const FileDetail = ({showDetailFile, actions}) => { - const closeDetail = actions.closeFileDetail - const getChunkTooltip = chunk => chunk.map( - (piece, i) => { - var s; - if(!piece.host) { - s = pieceEmptyStyle - } else if(piece.isoffline) { - s = pieceOfflineStyle - } else { - s = pieceOnlineStyle - } - return (
    {piece.host ? piece.host : "Empty"}
    ) - } - ) - const getChunkStyle = chunk => { - const onlineChunk = chunk.reduce((sum, piece) => { - if(piece.host && !piece.isoffline) { - return sum+1 - } - return sum - }, 0) - if(onlineChunk == chunk.length) { - return repairChunkRepairedStyle - } - if(onlineChunk == 0) { - return repairChunkQueuedStyle - } - return repairChunkRepairingStyle - } - const details = showDetailFile.details - return ( -
    -
    - -
    -
    -

    - {showDetailFile.name} -

    -
    - {details.map((v,i) => ( - - - - ) - )} -
    -
    -
    - ) -} - -FileDetail.propTypes = { - showDetailFile: PropTypes.object.isRequired, -} - -export default FileDetail - const logViewStyle = { - position: 'absolute', - top: '20px', - bottom: '0', - left: '20px', - right: '20px', - margin: '0', - padding: '0', - overflowY: 'scroll', - whiteSpace: 'pre', - fontSize: '12px', - fontFamily: 'monospace', -} - -const liViewStyle = { - cursor: 'pointer', + position: 'absolute', + top: '20px', + bottom: '0', + left: '20px', + right: '20px', + margin: '0', + padding: '0', + overflowY: 'scroll', + whiteSpace: 'pre', + fontSize: '12px', + fontFamily: 'monospace', } const reapirFileViewStyle = { - margin: '10px 20px', + margin: '10px 20px', } const repaireChunkStyle = { - width: '8px', - height: '8px', - float: 'left', - border: 'solid 1px #001f3f', + width: '8px', + height: '8px', + float: 'left', + border: 'solid 1px #001f3f', } const repairChunkRepairedStyle = { - ...repaireChunkStyle, - background: '#01FF70', + ...repaireChunkStyle, + background: '#01FF70', } const repairChunkRepairingStyle = { - ...repaireChunkStyle, - background: '#FF851B', + ...repaireChunkStyle, + background: '#FF851B', } const repairChunkQueuedStyle = { - ...repaireChunkStyle, - background: 'white', + ...repaireChunkStyle, + background: 'white', } const clearBothStyle = { - clear: 'both', + clear: 'both', } const pieceOnlineStyle = { - color: '#01FF70', + color: '#01FF70', } const pieceOfflineStyle = { - color: '#dc143c', + color: '#dc143c', } const pieceEmptyStyle = { - color: 'white', + color: 'white', } const closeButtonStyle = { - float: 'right', - cursor: 'pointer', + float: 'right', + cursor: 'pointer', } + +const FileDetail = ({showDetailFile, actions}) => { + const closeDetail = actions.closeFileDetail + const getChunkTooltip = (chunk) => chunk.map( + (piece, i) => { + let s + if (!piece.host) { + s = pieceEmptyStyle + } else if (piece.isoffline) { + s = pieceOfflineStyle + } else { + s = pieceOnlineStyle + } + return (
    {piece.host ? piece.host : 'Empty'}
    ) + } + ) + const getChunkStyle = (chunk) => { + const onlineChunk = chunk.reduce((sum, piece) => { + if (piece.host && !piece.isoffline) { + return sum+1 + } + return sum + }, 0) + if (onlineChunk === chunk.length) { + return repairChunkRepairedStyle + } + if (onlineChunk === 0) { + return repairChunkQueuedStyle + } + return repairChunkRepairingStyle + } + const details = showDetailFile.details + return ( +
    +
    + +
    +
    +

    + {showDetailFile.name} +

    +
    + {details.map((v, i) => ( + + + ) + )} +
    +
    +
    + ) +} + +FileDetail.propTypes = { + showDetailFile: PropTypes.object.isRequired, +} + +export default FileDetail + diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js index ef01e8d7..8d275b20 100644 --- a/plugins/Inspector/js/components/filelist.js +++ b/plugins/Inspector/js/components/filelist.js @@ -3,7 +3,6 @@ import React from 'react' import { List, Set } from 'immutable' import File from './file.js' import Path from 'path' -import SearchField from '../containers/searchfield.js' import FileControls from '../containers/filecontrols.js' import DirectoryInfoBar from './directoryinfobar.js' import FileDetail from './filedetail.js' @@ -37,8 +36,8 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi ) } - if(showDetailPath != null) { - const showDetailFile = files.find(file => file.siapath == showDetailPath) + if (showDetailPath !== null) { + const showDetailFile = files.find((file) => file.siapath === showDetailPath) return (
    @@ -55,26 +54,12 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi } const fileElements = filelistFiles.map((file, key) => { const isSelected = selected.map((selectedfile) => selectedfile.name).includes(file.name) - const onFileClick = (e) => { + const onFileClick = () => { // a show file detail action if (file.type === 'directory') { return } actions.showFileDetail(file.siapath) - - // const shouldMultiSelect = e.ctrlKey || e.metaKey - // const shouldRangeSelect = e.shiftKey - // if (!shouldMultiSelect && !shouldRangeSelect) { - // actions.deselectAll() - // } - // if (shouldRangeSelect) { - // actions.selectUpTo(file) - // } - // if (shouldMultiSelect && isSelected) { - // actions.deselectFile(file) - // } else { - // actions.selectFile(file) - // } } const onDoubleClick = (e) => { e.stopPropagation() diff --git a/plugins/Inspector/js/constants/files.js b/plugins/Inspector/js/constants/files.js index 6dc6ffb4..8c7097f3 100644 --- a/plugins/Inspector/js/constants/files.js +++ b/plugins/Inspector/js/constants/files.js @@ -60,4 +60,4 @@ export const RENAME_SIA_UI_FOLDER = 'RENAME_SIA_UI_FOLDER' export const DELETE_SIA_UI_FOLDER = 'DELETE_SIA_UI_FOLDER' export const SHOW_FILE_DETAIL = 'SHOW_FILE_DETAIL' -export const CLOSE_FILE_DETAIL = 'CLOSE_FILE_DETAIL' \ No newline at end of file +export const CLOSE_FILE_DETAIL = 'CLOSE_FILE_DETAIL' diff --git a/plugins/Inspector/js/containers/filelist.js b/plugins/Inspector/js/containers/filelist.js index 551f91d5..5a93df8d 100644 --- a/plugins/Inspector/js/containers/filelist.js +++ b/plugins/Inspector/js/containers/filelist.js @@ -11,7 +11,7 @@ const mapStateToProps = (state) => ({ showSearchField: state.files.get('showSearchField'), dragFolderTarget: state.files.get('dragFolderTarget'), dragFileOrigin: state.files.get('dragFileOrigin'), - showDetailPath: state.files.get('showDetailPath') + showDetailPath: state.files.get('showDetailPath'), }) const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators({ renameSiaUIFolder, deleteSiaUIFolder, getFiles, renameFile, setDragFileOrigin, setDragFolderTarget, setDragUploadEnabled, selectUpTo, setPath, deselectFile, deselectAll, selectFile, showRenameDialog, downloadFile, showDeleteDialog, showFileDetail, closeFileDetail }, dispatch), From c330ba465cbc8608b5989bffc6b9b6de3fbe06c2 Mon Sep 17 00:00:00 2001 From: wangchao Date: Wed, 2 Aug 2017 10:53:34 +0800 Subject: [PATCH 05/12] bugfix showDetailPath undefined error --- plugins/Inspector/js/components/filelist.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js index 8d275b20..398787ec 100644 --- a/plugins/Inspector/js/components/filelist.js +++ b/plugins/Inspector/js/components/filelist.js @@ -35,8 +35,7 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi
    ) } - - if (showDetailPath !== null) { + if (showDetailPath) { const showDetailFile = files.find((file) => file.siapath === showDetailPath) return (
    From 87fb2f2412f40ae37c9331b8d47694b545e460f9 Mon Sep 17 00:00:00 2001 From: wangchao Date: Wed, 2 Aug 2017 23:07:14 +0800 Subject: [PATCH 06/12] new filesdetail structure --- plugins/Inspector/js/components/filedetail.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 5e6c79f0..64d6ac71 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -66,30 +66,30 @@ const closeButtonStyle = { const FileDetail = ({showDetailFile, actions}) => { const closeDetail = actions.closeFileDetail - const getChunkTooltip = (chunk) => chunk.map( - (piece, i) => { - let s - if (!piece.host) { - s = pieceEmptyStyle - } else if (piece.isoffline) { - s = pieceOfflineStyle - } else { - s = pieceOnlineStyle + const getChunkTooltip = (chunk) => chunk.map((piece, i) => { + if (piece.length == 0) { + return (
    Empty
    ) } - return (
    {piece.host ? piece.host : 'Empty'}
    ) + const line = piece.map((ele, j) => { + let s + if (ele.isoffline) { + s = pieceOfflineStyle + } else { + s = pieceOnlineStyle + } + return (
    {ele.host};
    ) + }) + return (
    {line}
    ) } ) const getChunkStyle = (chunk) => { - const onlineChunk = chunk.reduce((sum, piece) => { - if (piece.host && !piece.isoffline) { - return sum+1 - } - return sum + const onlinePiece = chunk.reduce((sum, piece) => { + return piece.some((ele) => ele.host && !ele.isoffline) ? sum+1: sum }, 0) - if (onlineChunk === chunk.length) { + if (onlinePiece === chunk.length) { return repairChunkRepairedStyle } - if (onlineChunk === 0) { + if (onlinePiece === 0) { return repairChunkQueuedStyle } return repairChunkRepairingStyle From 22d23622dcb3133724a27d916d31d860249b0b36 Mon Sep 17 00:00:00 2001 From: wangchao Date: Wed, 2 Aug 2017 23:16:52 +0800 Subject: [PATCH 07/12] fix eslint, fix test plugin num --- plugins/Inspector/js/components/filedetail.js | 31 +++++++++---------- test/plugins.unit.js | 2 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 64d6ac71..ba377795 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -67,25 +67,22 @@ const closeButtonStyle = { const FileDetail = ({showDetailFile, actions}) => { const closeDetail = actions.closeFileDetail const getChunkTooltip = (chunk) => chunk.map((piece, i) => { - if (piece.length == 0) { - return (
    Empty
    ) - } - const line = piece.map((ele, j) => { - let s - if (ele.isoffline) { - s = pieceOfflineStyle - } else { - s = pieceOnlineStyle - } - return (
    {ele.host};
    ) - }) - return (
    {line}
    ) + if (piece.length === 0) { + return (
    Empty
    ) + } + const line = piece.map((ele, j) => { + let s + if (ele.isoffline) { + s = pieceOfflineStyle + } else { + s = pieceOnlineStyle } - ) + return (
    {ele.host};
    ) + }) + return (
    {line}
    ) + }) const getChunkStyle = (chunk) => { - const onlinePiece = chunk.reduce((sum, piece) => { - return piece.some((ele) => ele.host && !ele.isoffline) ? sum+1: sum - }, 0) + const onlinePiece = chunk.reduce((sum, piece) => piece.some((ele) => ele.host && !ele.isoffline) ? sum+1: sum, 0) if (onlinePiece === chunk.length) { return repairChunkRepairedStyle } diff --git a/test/plugins.unit.js b/test/plugins.unit.js index fc1fcd26..358f2156 100644 --- a/test/plugins.unit.js +++ b/test/plugins.unit.js @@ -3,7 +3,7 @@ import { getPluginName, getOrderedPlugins } from '../js/rendererjs/plugins.js' import { expect } from 'chai' const pluginDir = Path.join(__dirname, '../plugins') -const nPlugins = 6 +const nPlugins = 7 describe('plugin system', () => { describe('getOrderedPlugins', () => { From c45c5d91546d1280ac3d55e254f8c38a353b7f4e Mon Sep 17 00:00:00 2001 From: wangchao Date: Thu, 3 Aug 2017 10:29:17 +0800 Subject: [PATCH 08/12] one pieces one line, show piece index --- plugins/Inspector/js/components/filedetail.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index ba377795..04561ec9 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -77,9 +77,9 @@ const FileDetail = ({showDetailFile, actions}) => { } else { s = pieceOnlineStyle } - return (
    {ele.host};
    ) + return ({ele.host};) }) - return (
    {line}
    ) + return (
    {i}: {line}
    ) }) const getChunkStyle = (chunk) => { const onlinePiece = chunk.reduce((sum, piece) => piece.some((ele) => ele.host && !ele.isoffline) ? sum+1: sum, 0) From 6fed95e34f4bf95a40d71a97c1046c99a90a33d7 Mon Sep 17 00:00:00 2001 From: wangchao Date: Mon, 21 Aug 2017 07:54:54 +0800 Subject: [PATCH 09/12] change file structure, still have some bug --- package.json | 4 +- .../css/{files.css => inspector.css} | 20 +- plugins/Inspector/index.html | 4 +- plugins/Inspector/js/actions/files.js | 218 +------------ .../Inspector/js/components/filebrowser.js | 2 - plugins/Inspector/js/components/filedetail.js | 57 +++- plugins/Inspector/js/components/filelist.js | 9 +- plugins/Inspector/js/constants/files.js | 3 + plugins/Inspector/js/containers/filelist.js | 5 +- plugins/Inspector/js/index.js | 5 - plugins/Inspector/js/reducers/files.js | 7 + plugins/Inspector/js/sagas/files.js | 301 ++---------------- 12 files changed, 122 insertions(+), 513 deletions(-) rename plugins/Inspector/css/{files.css => inspector.css} (97%) diff --git a/package.json b/package.json index 57aad6ff..8514b678 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,9 @@ "uglifyjs-webpack-plugin": "^0.4.3", "webpack": "^2.5.1", "rc-tooltip": "^3.4.7", - "react-svg-buttons": "^0.4.0" + "react-svg-buttons": "^0.4.0", + "rc-pagination": "^1.11.0", + "rc-select": "^6.8.9" }, "dependencies": { "babel-polyfill": "^6.9.1", diff --git a/plugins/Inspector/css/files.css b/plugins/Inspector/css/inspector.css similarity index 97% rename from plugins/Inspector/css/files.css rename to plugins/Inspector/css/inspector.css index 9381cecb..8a10b823 100644 --- a/plugins/Inspector/css/files.css +++ b/plugins/Inspector/css/inspector.css @@ -1,6 +1,6 @@ -/* Style Guide: +/* Style Guide: * Transparent: 70% Opacity - * + * * White: #FFFFFF * Grey-White: #F5F5F5 * Faint-Grey: #ECECEC @@ -223,7 +223,7 @@ div[tabindex="1"]:focus { .upload-button-container:hover .upload-button:last-of-type { max-height: 100px; padding: 23px 0px 5px; -} +} .upload-button-container { display: inline-block; position: relative; @@ -624,3 +624,17 @@ div[tabindex="1"]:focus { width: 500px; display: flex; } + +#react-paginate ul { + display: inline-block; + padding-left: 15px; + padding-right: 15px; +} + +#react-paginate li { + display: inline-block; +} + +#react-paginate .break a { + cursor: default; +} diff --git a/plugins/Inspector/index.html b/plugins/Inspector/index.html index adedf608..878cb371 100644 --- a/plugins/Inspector/index.html +++ b/plugins/Inspector/index.html @@ -6,8 +6,10 @@ + - + + diff --git a/plugins/Inspector/js/actions/files.js b/plugins/Inspector/js/actions/files.js index 5a28bdfe..51b2b43d 100644 --- a/plugins/Inspector/js/actions/files.js +++ b/plugins/Inspector/js/actions/files.js @@ -1,19 +1,5 @@ import * as constants from '../constants/files.js' -export const getWalletLockstate = () => ({ - type: constants.GET_WALLET_LOCKSTATE, -}) -export const receiveWalletLockstate = (unlocked) => ({ - type: constants.RECEIVE_WALLET_LOCKSTATE, - unlocked, -}) -export const getWalletSyncstate = () => ({ - type: constants.GET_WALLET_SYNCSTATE, -}) -export const setWalletSyncstate = (synced) => ({ - type: constants.SET_WALLET_SYNCSTATE, - synced, -}) export const getFiles = () => ({ type: constants.GET_FILES, }) @@ -21,205 +7,27 @@ export const receiveFiles = (files) => ({ type: constants.RECEIVE_FILES, files, }) -export const getAllowance = () => ({ - type: constants.GET_ALLOWANCE, -}) -export const receiveAllowance = (allowance) => ({ - type: constants.RECEIVE_ALLOWANCE, - allowance, -}) -export const receiveSpending = (spending) => ({ - type: constants.RECEIVE_SPENDING, - spending, -}) -export const getStorageEstimate = (funds) => ({ - type: constants.GET_STORAGE_ESTIMATE, - funds, -}) -export const setStorageEstimate = (estimate) => ({ - type: constants.SET_STORAGE_ESTIMATE, - estimate, -}) -export const setFeeEstimate = (estimate) => ({ - type: constants.SET_FEE_ESTIMATE, - estimate, -}) -export const setAllowanceCompleted = () => ({ - type: constants.SET_ALLOWANCE_COMPLETED, -}) -export const setAllowance = (funds) => ({ - type: constants.SET_ALLOWANCE, - funds, -}) -export const getWalletBalance = () => ({ - type: constants.GET_WALLET_BALANCE, -}) -export const receiveWalletBalance = (balance) => ({ - type: constants.RECEIVE_WALLET_BALANCE, - balance, -}) -export const showAllowanceDialog = () => ({ - type: constants.SHOW_ALLOWANCE_DIALOG, -}) -export const closeAllowanceDialog = () => ({ - type: constants.CLOSE_ALLOWANCE_DIALOG, -}) -export const setPath = (path) => ({ - type: constants.SET_PATH, - path, -}) -export const setSearchText = (text, path) => ({ - type: constants.SET_SEARCH_TEXT, - text, - path, -}) -export const toggleSearchField = () => ({ - type: constants.TOGGLE_SEARCH_FIELD, -}) -export const setDragging = () => ({ - type: constants.SET_DRAGGING, -}) -export const setNotDragging = () => ({ - type: constants.SET_NOT_DRAGGING, -}) -export const showUploadDialog = (source) => ({ - type: constants.SHOW_UPLOAD_DIALOG, - source, -}) -export const hideUploadDialog = () => ({ - type: constants.HIDE_UPLOAD_DIALOG, -}) -export const downloadFile = (file, downloadpath) => ({ - type: constants.DOWNLOAD_FILE, - file, - downloadpath, -}) -export const uploadFile = (siapath, source) => ({ - type: constants.UPLOAD_FILE, - siapath, - source, -}) -export const deleteFile = (file) => ({ - type: constants.DELETE_FILE, - file, -}) -export const uploadFolder = (siapath, source) => ({ - type: constants.UPLOAD_FOLDER, - siapath, - source, -}) -export const getDownloads = () => ({ - type: constants.GET_DOWNLOADS, -}) -export const getUploads = () => ({ - type: constants.GET_UPLOADS, -}) -export const receiveUploads = (uploads) => ({ - type: constants.RECEIVE_UPLOADS, - uploads, -}) -export const receiveDownloads = (downloads) => ({ - type: constants.RECEIVE_DOWNLOADS, - downloads, -}) -export const showFileTransfers = () => ({ - type: constants.SHOW_FILE_TRANSFERS, -}) -export const hideFileTransfers = () => ({ - type: constants.HIDE_FILE_TRANSFERS, -}) -export const toggleFileTransfers = () => ({ - type: constants.TOGGLE_FILE_TRANSFERS, -}) -export const showDeleteDialog = (files) => ({ - type: constants.SHOW_DELETE_DIALOG, - files, -}) -export const hideDeleteDialog = () => ({ - type: constants.HIDE_DELETE_DIALOG, -}) -export const getContractCount = () => ({ - type: constants.GET_CONTRACT_COUNT, -}) -export const setContractCount = (count) => ({ - type: constants.SET_CONTRACT_COUNT, - count, -}) -export const renameFile = (file, newsiapath) => ({ - type: constants.RENAME_FILE, - file, - newsiapath, -}) -export const showRenameDialog = (file) => ({ - type: constants.SHOW_RENAME_DIALOG, - file, -}) -export const hideRenameDialog = () => ({ - type: constants.HIDE_RENAME_DIALOG, -}) -export const selectFile = (file) => ({ - type: constants.SELECT_FILE, - file, -}) -export const selectUpTo = (file) => ({ - type: constants.SELECT_UP_TO, - file, -}) -export const deselectAll = () => ({ - type: constants.DESELECT_ALL, -}) -export const deselectFile = (file) => ({ - type: constants.DESELECT_FILE, - file, -}) -export const clearDownloads = () => ({ - type: constants.CLEAR_DOWNLOADS, -}) -export const showAllowanceConfirmation = (allowance) => ({ - type: constants.SHOW_ALLOWANCE_CONFIRMATION, - allowance, -}) -export const hideAllowanceConfirmation = () => ({ - type: constants.HIDE_ALLOWANCE_CONFIRMATION, +export const receiveFileDetail = (detail) => ({ + type: constants.RECEIVE_FILE_DETAIL, + ...detail, }) export const fetchData = () => ({ type: constants.FETCH_DATA, }) -export const addFolder = (name) => ({ - type: constants.ADD_FOLDER, - name, -}) -export const showAddFolderDialog = () => ({ - type: constants.SHOW_ADD_FOLDER_DIALOG, +export const showFileDetail = (siapath) => ({ + type: constants.SHOW_FILE_DETAIL, + siapath }) -export const hideAddFolderDialog = () => ({ - type: constants.HIDE_ADD_FOLDER_DIALOG, +export const closeFileDetail = () => ({ + type: constants.CLOSE_FILE_DETAIL, }) -export const setDragUploadEnabled = (enabled) => ({ - type: constants.SET_DRAG_UPLOAD_ENABLED, - enabled, +export const fetchFileDetail = (siapath, pagingNum, current) => ({ + type: constants.GET_FILE_DETAIL, + siapath, + pagingNum, + current, }) export const setDragFolderTarget = (target) => ({ type: constants.SET_DRAG_FOLDER_TARGET, target, }) -export const setDragFileOrigin = (origin) => ({ - type: constants.SET_DRAG_FILE_ORIGIN, - origin, -}) -export const renameSiaUIFolder = (source, dest) => ({ - type: constants.RENAME_SIA_UI_FOLDER, - source, - dest, -}) -export const deleteSiaUIFolder = (siapath) => ({ - type: constants.DELETE_SIA_UI_FOLDER, - siapath, -}) -export const showFileDetail = (siapath) => ({ - type: constants.SHOW_FILE_DETAIL, - siapath, -}) -export const closeFileDetail = () => ({ - type: constants.CLOSE_FILE_DETAIL, -}) diff --git a/plugins/Inspector/js/components/filebrowser.js b/plugins/Inspector/js/components/filebrowser.js index 5ff35c7c..ff10d149 100644 --- a/plugins/Inspector/js/components/filebrowser.js +++ b/plugins/Inspector/js/components/filebrowser.js @@ -17,7 +17,6 @@ const FileBrowser = ({dragging, dragUploadEnabled, showFileTransfers, actions}) return } e.preventDefault() - actions.setNotDragging() // Convert file list into a list of file paths. actions.showUploadDialog(Array.from(e.dataTransfer.files, (file) => file.path)) } @@ -26,7 +25,6 @@ const FileBrowser = ({dragging, dragUploadEnabled, showFileTransfers, actions}) return } e.preventDefault() - actions.setNotDragging() } const onKeyDown = (e) => { // Deselect all files when ESC is pressed. diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 04561ec9..3ff4887b 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -1,7 +1,10 @@ import PropTypes from 'prop-types' import React from 'react' +import * as constants from '../constants/files.js' import Tooltip from 'rc-tooltip' import { CloseButton } from 'react-svg-buttons' +import Pagination from 'react-paginate' +import Select from 'rc-select' const logViewStyle = { position: 'absolute', @@ -64,14 +67,41 @@ const closeButtonStyle = { cursor: 'pointer', } -const FileDetail = ({showDetailFile, actions}) => { +const FileDetail = ({showDetailPath, showDetailFile, actions}) => { const closeDetail = actions.closeFileDetail + const onChangeOrSizeChange = (current, pageSize) => { + actions.fetchFileDetail(showDetailPath, pageSize, current) + } + const onChange = (current) => { + actions.fetchFileDetail(showDetailPath, constants.DEFAULT_PAGE_SIZE, current) + } + + if (!showDetailFile) { + actions.fetchFileDetail(showDetailPath, constants.DEFAULT_PAGE_SIZE, 1) + return ( +
    +
    + +
    +
    +
      +

      Loading file detail...

      +
    +
    + ) + } + + const getHost = (idx) => { + return showDetailFile.details.hosts[idx] + } + const getChunkTooltip = (chunk) => chunk.map((piece, i) => { - if (piece.length === 0) { + if (!piece || piece.length === 0) { return (
    Empty
    ) } const line = piece.map((ele, j) => { let s + ele = getHost(ele) if (ele.isoffline) { s = pieceOfflineStyle } else { @@ -82,7 +112,7 @@ const FileDetail = ({showDetailFile, actions}) => { return (
    {i}: {line}
    ) }) const getChunkStyle = (chunk) => { - const onlinePiece = chunk.reduce((sum, piece) => piece.some((ele) => ele.host && !ele.isoffline) ? sum+1: sum, 0) + const onlinePiece = chunk.reduce((sum, piece) => piece.some((ele) => getHost(ele).host && !getHost(ele).isoffline) ? sum+1: sum, 0) if (onlinePiece === chunk.length) { return repairChunkRepairedStyle } @@ -91,7 +121,7 @@ const FileDetail = ({showDetailFile, actions}) => { } return repairChunkRepairingStyle } - const details = showDetailFile.details + return (
    @@ -101,10 +131,20 @@ const FileDetail = ({showDetailFile, actions}) => {

    {showDetailFile.name}

    +
    - {details.map((v, i) => ( - - + {showDetailFile.details.chunks.map((chunk, i) => ( + + ) )}
    @@ -114,8 +154,7 @@ const FileDetail = ({showDetailFile, actions}) => { } FileDetail.propTypes = { - showDetailFile: PropTypes.object.isRequired, + showDetailPath: PropTypes.string.isRequired, } export default FileDetail - diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js index 398787ec..b9dc1826 100644 --- a/plugins/Inspector/js/components/filelist.js +++ b/plugins/Inspector/js/components/filelist.js @@ -7,7 +7,7 @@ import FileControls from '../containers/filecontrols.js' import DirectoryInfoBar from './directoryinfobar.js' import FileDetail from './filedetail.js' -const FileList = ({files, selected, searchResults, path, showSearchField, dragFileOrigin, dragFolderTarget, showDetailPath, actions}) => { +const FileList = ({files, selected, searchResults, path, showSearchField, dragFileOrigin, dragFolderTarget, showDetailPath, showDetailFile, actions}) => { const onBackClick = () => { // remove a trailing slash if it exists const cleanPath = path.replace(/\/$/, '') @@ -35,16 +35,15 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi
    ) } + if (showDetailPath) { - const showDetailFile = files.find((file) => file.siapath === showDetailPath) return ( -
    - +
    +
    ) } - let filelistFiles if (showSearchField) { filelistFiles = searchResults diff --git a/plugins/Inspector/js/constants/files.js b/plugins/Inspector/js/constants/files.js index 8c7097f3..9621e801 100644 --- a/plugins/Inspector/js/constants/files.js +++ b/plugins/Inspector/js/constants/files.js @@ -61,3 +61,6 @@ export const DELETE_SIA_UI_FOLDER = 'DELETE_SIA_UI_FOLDER' export const SHOW_FILE_DETAIL = 'SHOW_FILE_DETAIL' export const CLOSE_FILE_DETAIL = 'CLOSE_FILE_DETAIL' +export const GET_FILE_DETAIL = 'GET_FILE_DETAIL' +export const RECEIVE_FILE_DETAIL = 'RECEIVE_FILE_DETAIL' +export const DEFAULT_PAGE_SIZE = 40 diff --git a/plugins/Inspector/js/containers/filelist.js b/plugins/Inspector/js/containers/filelist.js index 5a93df8d..d8800990 100644 --- a/plugins/Inspector/js/containers/filelist.js +++ b/plugins/Inspector/js/containers/filelist.js @@ -1,7 +1,7 @@ import FileListView from '../components/filelist.js' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import { renameSiaUIFolder, deleteSiaUIFolder, renameFile, getFiles, setDragFolderTarget, setDragFileOrigin, setDragUploadEnabled, setPath, selectUpTo, deselectFile, deselectAll, selectFile, downloadFile, showDeleteDialog, showRenameDialog, showFileDetail, closeFileDetail } from '../actions/files.js' +import { renameSiaUIFolder, deleteSiaUIFolder, renameFile, getFiles, setDragFolderTarget, setDragFileOrigin, setDragUploadEnabled, setPath, selectUpTo, deselectFile, deselectAll, selectFile, downloadFile, showDeleteDialog, showRenameDialog, showFileDetail, closeFileDetail, fetchFileDetail } from '../actions/files.js' const mapStateToProps = (state) => ({ files: state.files.get('workingDirectoryFiles'), @@ -12,9 +12,10 @@ const mapStateToProps = (state) => ({ dragFolderTarget: state.files.get('dragFolderTarget'), dragFileOrigin: state.files.get('dragFileOrigin'), showDetailPath: state.files.get('showDetailPath'), + showDetailFile: state.files.get('showDetailFile'), }) const mapDispatchToProps = (dispatch) => ({ - actions: bindActionCreators({ renameSiaUIFolder, deleteSiaUIFolder, getFiles, renameFile, setDragFileOrigin, setDragFolderTarget, setDragUploadEnabled, selectUpTo, setPath, deselectFile, deselectAll, selectFile, showRenameDialog, downloadFile, showDeleteDialog, showFileDetail, closeFileDetail }, dispatch), + actions: bindActionCreators({ renameSiaUIFolder, deleteSiaUIFolder, getFiles, renameFile, setDragFileOrigin, setDragFolderTarget, setDragUploadEnabled, selectUpTo, setPath, deselectFile, deselectAll, selectFile, showRenameDialog, downloadFile, showDeleteDialog, showFileDetail, closeFileDetail, fetchFileDetail }, dispatch), }) const FileList = connect(mapStateToProps, mapDispatchToProps)(FileListView) diff --git a/plugins/Inspector/js/index.js b/plugins/Inspector/js/index.js index 2ff975b3..f5abdb45 100644 --- a/plugins/Inspector/js/index.js +++ b/plugins/Inspector/js/index.js @@ -15,10 +15,6 @@ const store = createStore( ) sagaMiddleware.run(rootSaga) -setInterval(() => { - store.dispatch(fetchData()) -}, 2000) - const rootElement = ( @@ -30,4 +26,3 @@ ReactDOM.render(rootElement, document.getElementById('react-root')) window.onfocus = () => { store.dispatch(fetchData()) } - diff --git a/plugins/Inspector/js/reducers/files.js b/plugins/Inspector/js/reducers/files.js index a7ef9a85..11cde8ff 100644 --- a/plugins/Inspector/js/reducers/files.js +++ b/plugins/Inspector/js/reducers/files.js @@ -159,8 +159,15 @@ export default function filesReducer(state = initialState, action) { return state.set('showAddFolderDialog', false) case constants.SHOW_FILE_DETAIL: return state.set('showDetailPath', action.siapath) + case constants.RECEIVE_FILE_DETAIL: + return state.set('pagingNum', action.pagingNum) + .set('current', action.current) + .set('showDetailFile', action.file) case constants.CLOSE_FILE_DETAIL: return state.delete('showDetailPath') + .delete('showDetailFile') + .delete('pagingNum') + .delete('current') default: return state } diff --git a/plugins/Inspector/js/sagas/files.js b/plugins/Inspector/js/sagas/files.js index 43d355a0..9313eaa6 100644 --- a/plugins/Inspector/js/sagas/files.js +++ b/plugins/Inspector/js/sagas/files.js @@ -1,5 +1,5 @@ import { takeEvery, delay } from 'redux-saga' -import { fork, join, put, race, take, call } from 'redux-saga/effects' +import { fork, join, put, race, take, call, select } from 'redux-saga/effects' import Path from 'path' import fs from 'graceful-fs' import * as actions from '../actions/files.js' @@ -8,31 +8,10 @@ import { List } from 'immutable' import BigNumber from 'bignumber.js' import { ls, uploadDirectory, sendError, allowancePeriod, readableFilesize, siadCall, readdirRecursive, parseDownloads, parseUploads } from './helpers.js' -// Query siad for the state of the wallet. -// dispatch `unlocked` in receiveWalletLockstate -function* getWalletLockstateSaga() { - try { - const response = yield siadCall('/wallet') - yield put(actions.receiveWalletLockstate(response.unlocked)) - } catch (e) { - console.error('error fetching wallet lock state: ' + e.toString()) - } -} - -// Query siad for the sync state of the wallet. -function* getWalletSyncstateSaga() { - try { - const response = yield siadCall('/consensus') - yield put(actions.setWalletSyncstate(response.synced)) - } catch (e) { - console.error('error fetching wallet sync state: ' + e.toString()) - } -} - // Query siad for the user's files. function* getFilesSaga() { try { - const response = yield siadCall('/renter/filesdetail') + const response = yield siadCall('/renter/files') const files = List(response.files) yield put(actions.receiveFiles(files)) } catch (e) { @@ -40,235 +19,36 @@ function* getFilesSaga() { } } -function* getStorageEstimateSaga(action) { - try { - const response = yield siadCall('/renter/prices') - if (response.storageterabytemonth === '0') { - yield put(actions.setStorageEstimate('No Hosts')) - return - } - const estimate = new BigNumber(SiaAPI.siacoinsToHastings(action.funds)).dividedBy(response.storageterabytemonth).times(1e12) - - yield put(actions.setStorageEstimate('~' + readableFilesize(estimate.toPrecision(1)))) - yield put(actions.setFeeEstimate(SiaAPI.hastingsToSiacoins(response.formcontracts).toString())) - } catch (e) { - console.error(e) - } -} - -// Get the renter's current allowance and spending. -function* getAllowanceSaga() { - try { - const response = yield siadCall('/renter') - const allowance = SiaAPI.hastingsToSiacoins(response.settings.allowance.funds) - - // compute allowance spending. Set the spending to zero if it is negative, - // since negative spending is confusing to the user. - let spending = allowance.minus(SiaAPI.hastingsToSiacoins(response.financialmetrics.unspent)) - if (spending.isNegative()) { - spending = new BigNumber(0) - } - - yield put(actions.receiveAllowance(allowance.round(0).toString())) - yield put(actions.receiveSpending(spending.round(0).toString())) - } catch (e) { - console.error('error getting allowance: ' + e.toString()) - } -} - -// Set the user's renter allowance. -function* setAllowanceSaga(action) { - try { - const newAllowance = SiaAPI.siacoinsToHastings(action.funds) - yield put(actions.closeAllowanceDialog()) - yield siadCall({ - url: '/renter', - method: 'POST', - timeout: 7.2e6, // 120 minute timeout for setting allowance - qs: { - funds: newAllowance.toString(), - period: allowancePeriod, - }, - }) - yield put(actions.setAllowanceCompleted()) - } catch (e) { - sendError(e) - yield put(actions.setAllowanceCompleted()) - yield put(actions.closeAllowanceDialog()) - } -} - -// Query Siad for the current wallet balance. -function* getWalletBalanceSaga() { - try { - const response = yield siadCall('/wallet') - const confirmedBalance = SiaAPI.hastingsToSiacoins(response.confirmedsiacoinbalance).round(2).toString() - yield put(actions.receiveWalletBalance(confirmedBalance)) - } catch (e) { - console.error('error fetching wallet balance: ' + e.toString()) - } -} - -// UploadFileSaga uploads a file to the Sia network. -// action.siapath: the working directory to upload the file to -// action.source: the path to the file to upload. -// The full siapath is computed as Path.join(action.siapath, Path.basename(action.source)) -function* uploadFileSaga(action) { - try { - const filename = Path.basename(action.source) - const destpath = Path.posix.join(action.siapath, filename) - yield siadCall({ - url: '/renter/upload/' + encodeURI(destpath), - timeout: 20000, // 20 second timeout for upload calls - method: 'POST', - qs: { - source: action.source, - }, - }) - } catch (e) { - sendError(e) - } -} - -// uploadFolderSaga uploads a folder to the Sia network. -// action.source: the source path of the folder -// action.siapath: the working directory to upload the folder inside -function *uploadFolderSaga(action) { - try { - const files = readdirRecursive(action.source) - const uploads = uploadDirectory(action.source, files, action.siapath) - for (const upload in uploads.toArray()) { - yield put(uploads.get(upload)) - } - } catch (e) { - sendError(e) - } -} - -function* downloadFileSaga(action) { - try { - if (action.file.type === 'file') { - yield siadCall({ - url: '/renter/download/' + encodeURI(action.file.siapath), - timeout: 6e8, - method: 'GET', - qs: { - destination: action.downloadpath, - }, - }) - } - if (action.file.type === 'directory') { - fs.mkdirSync(action.downloadpath) - const response = yield siadCall('/renter/files') - const siafiles = ls(List(response.files), action.file.siapath) - for (const siafile in siafiles.toArray()) { - const file = siafiles.get(siafile) - yield put(actions.downloadFile(file, Path.join(action.downloadpath, file.name))) - yield new Promise((resolve) => setTimeout(resolve, 300)) - } - } - } catch (e) { - sendError(e) - } -} +function* getFileDetailSaga(action) { + let siapath, pagingNum, current -function* getDownloadsSaga() { - try { - const response = yield siadCall('/renter/downloads') - const downloads = parseDownloads(response.downloads) - yield put(actions.receiveDownloads(downloads)) - } catch (e) { - console.error('error fetching downloads: ' + e.toString()) - } -} - -function* getUploadsSaga() { - try { - const response = yield siadCall('/renter/files') - const uploads = parseUploads(response.files) - yield put(actions.receiveUploads(uploads)) - } catch (e) { - console.error('error fetching uploads: ' + e.toString()) - } -} - -function* deleteFileSaga(action) { - try { - if (action.file.siaUIFolder) { - yield put(actions.deleteSiaUIFolder(action.file.siapath)) - } else if (action.file.type === 'file') { - yield siadCall({ - url: '/renter/delete/' + encodeURI(action.file.siapath), - timeout: 3.6e6, // 60 minute timeout for deleting files - method: 'POST', - }) - yield put(actions.getFiles()) - } else if (action.file.type === 'directory') { - const response = yield siadCall('/renter/files') - const siafiles = ls(List(response.files), action.file.siapath) - for (const siafile in siafiles.toArray()) { - const file = siafiles.get(siafile) - yield siadCall({ - url: '/renter/delete/' + encodeURI(file.siapath), - timeout: 3.6e6, // 60 minute timeout for deleting files - method: 'POST', - }) - } + if (action) { + ({ siapath, pagingNum, current } = action) + } else { + ({ siapath, pagingNum, current } = yield select((state) => ({ + siapath: state.files.get('showDetailPath'), pagingNum: state.files.get('pagingNum'), current: state.files.get('current') + }))) + if (!siapath) { + return } - } catch (e) { - sendError(e) } -} -function* getContractCountSaga() { try { - const response = yield siadCall('/renter/contracts') - yield put(actions.setContractCount(response.contracts.length)) + const response = yield siadCall('/renter/filedetail/' + + encodeURI(siapath) + + "?pagingNum=" + pagingNum + + "¤t=" + current) + yield put(actions.receiveFileDetail({ pagingNum, current, file: response })) } catch (e) { - console.error('error getting contract count: ' + e.toString()) - } -} - -function* renameFileSaga(action) { - try { - if (action.file.siaUIFolder) { - yield put(actions.renameSiaUIFolder(action.file.siapath, action.newsiapath)) - } else if (action.file.type === 'file') { - yield siadCall({ - url: '/renter/rename/' + encodeURI(action.file.siapath), - method: 'POST', - qs: { - newsiapath: action.newsiapath, - }, - }) - yield put(actions.getFiles()) - } else if (action.file.type === 'directory') { - const directorypath = action.file.siapath - const response = yield siadCall('/renter/files') - const siafiles = ls(List(response.files), directorypath) - for (const i in siafiles.toArray()) { - const file = siafiles.get(i) - const newfilepath = Path.posix.join(action.newsiapath, file.siapath.split(directorypath)[1]) - yield put(actions.renameFile(file, newfilepath)) - } - } - yield put(actions.hideRenameDialog()) - } catch (e) { - sendError(e) + console.error('error fetching file: ' + e.toString()) } } export function* dataFetcher() { while (true) { let tasks = [] - // tasks = tasks.concat(yield fork(getDownloadsSaga)) tasks = tasks.concat(yield fork(getFilesSaga)) - // tasks = tasks.concat(yield fork(getUploadsSaga)) - // tasks = tasks.concat(yield fork(getContractCountSaga)) - // tasks = tasks.concat(yield fork(getWalletBalanceSaga)) - // tasks = tasks.concat(yield fork(getWalletSyncstateSaga)) - // tasks = tasks.concat(yield fork(getWalletLockstateSaga)) - // tasks = tasks.concat(yield fork(getAllowanceSaga)) + tasks = tasks.concat(yield fork(getFileDetailSaga)) yield join(...tasks) yield race({ @@ -277,48 +57,9 @@ export function* dataFetcher() { }) } } -export function* watchSetAllowance() { - yield *takeEvery(constants.SET_ALLOWANCE, setAllowanceSaga) -} -export function* watchGetAllowance() { - yield *takeEvery(constants.GET_ALLOWANCE, getAllowanceSaga) -} -export function* watchGetDownloads() { - yield *takeEvery(constants.GET_DOWNLOADS, getDownloadsSaga) -} -export function* watchGetUploads() { - yield *takeEvery(constants.GET_UPLOADS, getUploadsSaga) -} -export function* watchGetWalletLockstate() { - yield *takeEvery(constants.GET_WALLET_LOCKSTATE, getWalletLockstateSaga) -} export function* watchGetFiles() { yield *takeEvery(constants.GET_FILES, getFilesSaga) } -export function* watchDeleteFile() { - yield *takeEvery(constants.DELETE_FILE, deleteFileSaga) -} -export function* watchGetWalletBalance() { - yield *takeEvery(constants.GET_WALLET_BALANCE, getWalletBalanceSaga) -} -export function* watchUploadFolder() { - yield *takeEvery(constants.UPLOAD_FOLDER, uploadFolderSaga) -} -export function* watchGetContractCount() { - yield *takeEvery(constants.GET_CONTRACT_COUNT, getContractCountSaga) -} -export function* watchUploadFile() { - yield *takeEvery(constants.UPLOAD_FILE, uploadFileSaga) -} -export function* watchDownloadFile() { - yield *takeEvery(constants.DOWNLOAD_FILE, downloadFileSaga) -} -export function* watchRenameFile() { - yield *takeEvery(constants.RENAME_FILE, renameFileSaga) -} -export function* watchGetStorageEstimate() { - yield *takeEvery(constants.GET_STORAGE_ESTIMATE, getStorageEstimateSaga) -} -export function* watchGetWalletSyncstate() { - yield *takeEvery(constants.GET_WALLET_SYNCSTATE, getWalletSyncstateSaga) +export function* watchGetFileDetail() { + yield *takeEvery(constants.GET_FILE_DETAIL, getFileDetailSaga) } From d3fda40966dcee926071b10fbd69a6ec0a7e71f1 Mon Sep 17 00:00:00 2001 From: wangchao Date: Mon, 21 Aug 2017 16:00:11 +0800 Subject: [PATCH 10/12] use paged, per file detail --- package.json | 4 +- plugins/Inspector/index.html | 4 +- plugins/Inspector/js/actions/files.js | 6 +- plugins/Inspector/js/components/filedetail.js | 84 +++++++------------ .../Inspector/js/components/filedetailtip.js | 49 +++++++++++ plugins/Inspector/js/components/filelist.js | 6 +- plugins/Inspector/js/constants/files.js | 2 +- plugins/Inspector/js/containers/filedetail.js | 16 ++++ plugins/Inspector/js/reducers/files.js | 6 +- plugins/Inspector/js/sagas/files.js | 11 +-- 10 files changed, 114 insertions(+), 74 deletions(-) create mode 100644 plugins/Inspector/js/components/filedetailtip.js create mode 100644 plugins/Inspector/js/containers/filedetail.js diff --git a/package.json b/package.json index 8514b678..57aad6ff 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,7 @@ "uglifyjs-webpack-plugin": "^0.4.3", "webpack": "^2.5.1", "rc-tooltip": "^3.4.7", - "react-svg-buttons": "^0.4.0", - "rc-pagination": "^1.11.0", - "rc-select": "^6.8.9" + "react-svg-buttons": "^0.4.0" }, "dependencies": { "babel-polyfill": "^6.9.1", diff --git a/plugins/Inspector/index.html b/plugins/Inspector/index.html index 878cb371..95cd46bc 100644 --- a/plugins/Inspector/index.html +++ b/plugins/Inspector/index.html @@ -7,9 +7,7 @@ - - - + diff --git a/plugins/Inspector/js/actions/files.js b/plugins/Inspector/js/actions/files.js index 51b2b43d..a4597299 100644 --- a/plugins/Inspector/js/actions/files.js +++ b/plugins/Inspector/js/actions/files.js @@ -16,7 +16,7 @@ export const fetchData = () => ({ }) export const showFileDetail = (siapath) => ({ type: constants.SHOW_FILE_DETAIL, - siapath + siapath, }) export const closeFileDetail = () => ({ type: constants.CLOSE_FILE_DETAIL, @@ -31,3 +31,7 @@ export const setDragFolderTarget = (target) => ({ type: constants.SET_DRAG_FOLDER_TARGET, target, }) +export const setPath = (path) => ({ + type: constants.SET_PATH, + path, +}) diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 3ff4887b..82486a37 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -1,10 +1,9 @@ import PropTypes from 'prop-types' import React from 'react' +import FileDetailTip from './filedetailtip.js' import * as constants from '../constants/files.js' import Tooltip from 'rc-tooltip' import { CloseButton } from 'react-svg-buttons' -import Pagination from 'react-paginate' -import Select from 'rc-select' const logViewStyle = { position: 'absolute', @@ -25,8 +24,8 @@ const reapirFileViewStyle = { } const repaireChunkStyle = { - width: '8px', - height: '8px', + width: '10px', + height: '10px', float: 'left', border: 'solid 1px #001f3f', } @@ -50,31 +49,13 @@ const clearBothStyle = { clear: 'both', } -const pieceOnlineStyle = { - color: '#01FF70', -} - -const pieceOfflineStyle = { - color: '#dc143c', -} - -const pieceEmptyStyle = { - color: 'white', -} - const closeButtonStyle = { float: 'right', cursor: 'pointer', } -const FileDetail = ({showDetailPath, showDetailFile, actions}) => { +const FileDetail = ({showDetailPath, showDetailFile, current, actions}) => { const closeDetail = actions.closeFileDetail - const onChangeOrSizeChange = (current, pageSize) => { - actions.fetchFileDetail(showDetailPath, pageSize, current) - } - const onChange = (current) => { - actions.fetchFileDetail(showDetailPath, constants.DEFAULT_PAGE_SIZE, current) - } if (!showDetailFile) { actions.fetchFileDetail(showDetailPath, constants.DEFAULT_PAGE_SIZE, 1) @@ -91,26 +72,8 @@ const FileDetail = ({showDetailPath, showDetailFile, actions}) => { ) } - const getHost = (idx) => { - return showDetailFile.details.hosts[idx] - } + const getHost = (idx) => showDetailFile.details.hosts[idx] - const getChunkTooltip = (chunk) => chunk.map((piece, i) => { - if (!piece || piece.length === 0) { - return (
    Empty
    ) - } - const line = piece.map((ele, j) => { - let s - ele = getHost(ele) - if (ele.isoffline) { - s = pieceOfflineStyle - } else { - s = pieceOnlineStyle - } - return ({ele.host};) - }) - return (
    {i}: {line}
    ) - }) const getChunkStyle = (chunk) => { const onlinePiece = chunk.reduce((sum, piece) => piece.some((ele) => getHost(ele).host && !getHost(ele).isoffline) ? sum+1: sum, 0) if (onlinePiece === chunk.length) { @@ -121,6 +84,15 @@ const FileDetail = ({showDetailPath, showDetailFile, actions}) => { } return repairChunkRepairingStyle } + const links = Array.from(new Array(showDetailFile.totalpages), (val, index) => { + if (index+1 === current) { + return (
    {index+1} ) + } + const onChange = () => { + actions.fetchFileDetail(showDetailPath, constants.DEFAULT_PAGE_SIZE, index+1) + } + return ( {index+1} ) + }) return (
    @@ -131,19 +103,25 @@ const FileDetail = ({showDetailPath, showDetailFile, actions}) => {

    {showDetailFile.name}

    - + +
    + {links} +
    +
    {showDetailFile.details.chunks.map((chunk, i) => ( - + + } + > ) )} diff --git a/plugins/Inspector/js/components/filedetailtip.js b/plugins/Inspector/js/components/filedetailtip.js new file mode 100644 index 00000000..d0154295 --- /dev/null +++ b/plugins/Inspector/js/components/filedetailtip.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types' +import React from 'react' +import * as constants from '../constants/files.js' + +const pieceOnlineStyle = { + color: '#01FF70', +} + +const pieceOfflineStyle = { + color: '#dc143c', +} + +const pieceEmptyStyle = { + color: 'white', +} + +const FileDetailTip = ({chunk, index, current, getHost}) => { + const tip = chunk.map((piece, i) => { + if (!piece || piece.length === 0) { + return (
    Empty
    ) + } + const line = piece.map((ele, j) => { + let s + if (getHost(ele).isoffline) { + s = pieceOfflineStyle + } else { + s = pieceOnlineStyle + } + return ({getHost(ele).host};) + }) + return (
    {i}: {line}
    ) + }) + + return ( +
    +
    ChunkId : {(current - 1) * constants.DEFAULT_PAGE_SIZE + index}
    + {tip} +
    + ) +} + +FileDetailTip.propTypes = { + chunk: PropTypes.array.isRequired, + index: PropTypes.number.isRequired, + current: PropTypes.number.isRequired, + getHost: PropTypes.func.isRequired, +} + +export default FileDetailTip diff --git a/plugins/Inspector/js/components/filelist.js b/plugins/Inspector/js/components/filelist.js index b9dc1826..1fb35116 100644 --- a/plugins/Inspector/js/components/filelist.js +++ b/plugins/Inspector/js/components/filelist.js @@ -5,9 +5,9 @@ import File from './file.js' import Path from 'path' import FileControls from '../containers/filecontrols.js' import DirectoryInfoBar from './directoryinfobar.js' -import FileDetail from './filedetail.js' +import FileDetail from '../containers/filedetail.js' -const FileList = ({files, selected, searchResults, path, showSearchField, dragFileOrigin, dragFolderTarget, showDetailPath, showDetailFile, actions}) => { +const FileList = ({files, selected, searchResults, path, showSearchField, dragFileOrigin, dragFolderTarget, showDetailPath, actions}) => { const onBackClick = () => { // remove a trailing slash if it exists const cleanPath = path.replace(/\/$/, '') @@ -39,7 +39,7 @@ const FileList = ({files, selected, searchResults, path, showSearchField, dragFi if (showDetailPath) { return (
    - +
    ) } diff --git a/plugins/Inspector/js/constants/files.js b/plugins/Inspector/js/constants/files.js index 9621e801..5e70f858 100644 --- a/plugins/Inspector/js/constants/files.js +++ b/plugins/Inspector/js/constants/files.js @@ -63,4 +63,4 @@ export const SHOW_FILE_DETAIL = 'SHOW_FILE_DETAIL' export const CLOSE_FILE_DETAIL = 'CLOSE_FILE_DETAIL' export const GET_FILE_DETAIL = 'GET_FILE_DETAIL' export const RECEIVE_FILE_DETAIL = 'RECEIVE_FILE_DETAIL' -export const DEFAULT_PAGE_SIZE = 40 +export const DEFAULT_PAGE_SIZE = 200 diff --git a/plugins/Inspector/js/containers/filedetail.js b/plugins/Inspector/js/containers/filedetail.js new file mode 100644 index 00000000..26b12c70 --- /dev/null +++ b/plugins/Inspector/js/containers/filedetail.js @@ -0,0 +1,16 @@ +import FileDetailView from '../components/filedetail.js' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { showFileDetail, closeFileDetail, fetchFileDetail } from '../actions/files.js' + +const mapStateToProps = (state) => ({ + showDetailPath: state.files.get('showDetailPath'), + showDetailFile: state.files.get('showDetailFile'), + current: state.files.get('current'), +}) +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ showFileDetail, closeFileDetail, fetchFileDetail }, dispatch), +}) + +const FileDetail = connect(mapStateToProps, mapDispatchToProps)(FileDetailView) +export default FileDetail diff --git a/plugins/Inspector/js/reducers/files.js b/plugins/Inspector/js/reducers/files.js index 11cde8ff..c35274df 100644 --- a/plugins/Inspector/js/reducers/files.js +++ b/plugins/Inspector/js/reducers/files.js @@ -162,12 +162,12 @@ export default function filesReducer(state = initialState, action) { case constants.RECEIVE_FILE_DETAIL: return state.set('pagingNum', action.pagingNum) .set('current', action.current) - .set('showDetailFile', action.file) + .set('showDetailFile', action.file) case constants.CLOSE_FILE_DETAIL: return state.delete('showDetailPath') .delete('showDetailFile') - .delete('pagingNum') - .delete('current') + .delete('pagingNum') + .delete('current') default: return state } diff --git a/plugins/Inspector/js/sagas/files.js b/plugins/Inspector/js/sagas/files.js index 9313eaa6..809d1dbd 100644 --- a/plugins/Inspector/js/sagas/files.js +++ b/plugins/Inspector/js/sagas/files.js @@ -1,12 +1,9 @@ import { takeEvery, delay } from 'redux-saga' import { fork, join, put, race, take, call, select } from 'redux-saga/effects' -import Path from 'path' -import fs from 'graceful-fs' import * as actions from '../actions/files.js' import * as constants from '../constants/files.js' import { List } from 'immutable' -import BigNumber from 'bignumber.js' -import { ls, uploadDirectory, sendError, allowancePeriod, readableFilesize, siadCall, readdirRecursive, parseDownloads, parseUploads } from './helpers.js' +import { siadCall } from './helpers.js' // Query siad for the user's files. function* getFilesSaga() { @@ -26,7 +23,7 @@ function* getFileDetailSaga(action) { ({ siapath, pagingNum, current } = action) } else { ({ siapath, pagingNum, current } = yield select((state) => ({ - siapath: state.files.get('showDetailPath'), pagingNum: state.files.get('pagingNum'), current: state.files.get('current') + siapath: state.files.get('showDetailPath'), pagingNum: state.files.get('pagingNum'), current: state.files.get('current'), }))) if (!siapath) { return @@ -36,8 +33,8 @@ function* getFileDetailSaga(action) { try { const response = yield siadCall('/renter/filedetail/' + encodeURI(siapath) - + "?pagingNum=" + pagingNum - + "¤t=" + current) + + '?pagingNum=' + pagingNum + + '¤t=' + current) yield put(actions.receiveFileDetail({ pagingNum, current, file: response })) } catch (e) { console.error('error fetching file: ' + e.toString()) From 9458445debe6b80321f298ed7e50a794ae219e8e Mon Sep 17 00:00:00 2001 From: wangchao Date: Mon, 21 Aug 2017 16:01:24 +0800 Subject: [PATCH 11/12] remove useless class --- plugins/Inspector/js/components/filedetail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 82486a37..0d2c165f 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -60,7 +60,7 @@ const FileDetail = ({showDetailPath, showDetailFile, current, actions}) => { if (!showDetailFile) { actions.fetchFileDetail(showDetailPath, constants.DEFAULT_PAGE_SIZE, 1) return ( -
    +
    From 5f05bf2091ed9dc658d569b7862c88c6a6f26bc8 Mon Sep 17 00:00:00 2001 From: wangchao Date: Mon, 21 Aug 2017 16:05:25 +0800 Subject: [PATCH 12/12] use siapath instead of filename --- plugins/Inspector/js/components/filedetail.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Inspector/js/components/filedetail.js b/plugins/Inspector/js/components/filedetail.js index 0d2c165f..071640ff 100644 --- a/plugins/Inspector/js/components/filedetail.js +++ b/plugins/Inspector/js/components/filedetail.js @@ -100,12 +100,12 @@ const FileDetail = ({showDetailPath, showDetailFile, current, actions}) => {
    -

    - {showDetailFile.name} -

    +

    + file: {showDetailPath} +

    - {links} + Pages: {links}