diff --git a/.gitignore b/.gitignore index 375ee0b..f7c19e4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ script static !static/package.json .eslintcache +.history diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..51cba30 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# 更新日志 +# 🚀 0.4.3 +`2021.01.06` +- 🚀 新增 + - 支持创建Bucket + - 文件列表操作hover提示 +- 🐞 修复 + - 文件列表调整 +# 🚀 0.4.2 +`2020.11.18` + +- 🚀 新增 + - 支持多AZ存储类型及归档存储类型展示 +- 🐞 修复 + - 修复滚动无法加载的问题 + - 选中多个文件状态下分享功能未禁用 + - 进入文件夹中当前选中文件数量未更新 + - 删除文件后当前选中文件数量未更新 + - 文件图标排列未对齐 + - 上传完成文件图标显示不正确 + - xmind, docx, gif, jpeg等扩展名文件的图标显示不正确 + - 点击文件/文件夹命令后错误选中文件/文件夹 + - 新建文件夹后成功后未清空上一次输入框中的文件夹名称 + - 搜索Bucket输入不存在的Bucket名称,错误进入上传页面 + - 左边sidebar首次进入后没有默认选中全部文件 + - 同步盘删除映射后选择框错误选中问题 + - 同步盘本地文件目录显示不全 + - 同步盘checkbox显示问题 + - windows环境下标题栏操作按钮缩放比例较小状态下显示不全 + - windows环境下同步盘选择框样式问题 + diff --git a/LICENSE b/LICENSE index e3ba941..3f9a20b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 木休大人 +Copyright (c) 2016 BAIDU Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4e69fef..44e54b8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ - [Releases](https://github.com/mudio/bce-client/releases) ## License -MIT © [木休大人](https://github.com/mudio) +MIT © BAIDU [travis-url]: https://travis-ci.org/mudio/bce-client [travis-image]: https://img.shields.io/travis/mudio/bce-client/master.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9Imljb24tbWFjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgMTUgMTUiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDE1IDE1OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6bm9uZTtzdHJva2U6IzlEOUQ5RDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7fQo8L3N0eWxlPgo8ZyBpZD0ibWFjIj4KCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yLjA2LDkuMDc1YzAtMC4xODUsMC0wLjM3MSwwLTAuNTU2QzIuMDYzLDguNSwyLjA2OCw4LjQ4MiwyLjA3LDguNDY0YzAuMDI2LTAuMjEzLDAuMDQtMC40MjgsMC4wODEtMC42MzgKCQljMC4xNDEtMC43MzEsMC40NTItMS4zNzksMC45OTEtMS45YzAuNTUzLTAuNTM0LDEuMjE2LTAuODI3LDEuOTktMC44NDdDNS40ODUsNS4wNyw1LjgxNyw1LjE3Myw2LjE0Niw1LjI4NwoJCWMwLjI4OSwwLjEsMC41NzgsMC4yMDMsMC44NjgsMC4zMDNjMC4xMzQsMC4wNDYsMC4yNjcsMC4wNDUsMC40MDItMC4wMDNjMC4yNzMtMC4wOTgsMC41NDgtMC4xOSwwLjgyMS0wLjI4OAoJCWMwLjMwNy0wLjExMSwwLjYyLTAuMjAxLDAuOTQ1LTAuMjQyYzAuMjgxLTAuMDM1LDAuNTU4LTAuMDA1LDAuODM1LDAuMDQ2YzAuNTc4LDAuMTA4LDEuMDkzLDAuMzQyLDEuNTE4LDAuNzU3CgkJYzAuMTMzLDAuMTI5LDAuMjUxLDAuMjcxLDAuMzUyLDAuNDI0Yy0wLjAwNiwwLjAwOC0wLjAwOCwwLjAxMi0wLjAxMSwwLjAxNGMtMC4wMTQsMC4wMS0wLjAyOCwwLjAxOS0wLjA0MiwwLjAyOAoJCWMtMC4zNSwwLjIyNy0wLjY1NywwLjUwMS0wLjg4MywwLjg1NWMtMC40MiwwLjY1Ny0wLjQ5LDEuMzcyLTAuMzMzLDIuMTE4YzAuMTMxLDAuNjIsMC40NzIsMS4xMTMsMC45NjEsMS41MDgKCQljMC4xOTQsMC4xNTYsMC40MDIsMC4yOSwwLjYzNiwwLjM4MWMwLDAuMDA4LDAsMC4wMTcsMCwwLjAyNWMtMC4wMDUsMC4wMDktMC4wMTEsMC4wMTctMC4wMTQsMC4wMjcKCQljLTAuMDY5LDAuMTgzLTAuMTMzLDAuMzY4LTAuMjA4LDAuNTQ4Yy0wLjIzNSwwLjU2Ny0wLjU1MywxLjA4Ni0wLjkyMSwxLjU3NmMtMC4yMTMsMC4yODMtMC40MzUsMC41NTgtMC43MTcsMC43NzgKCQljLTAuMzQ4LDAuMjcyLTAuNzM1LDAuNC0xLjE3OSwwLjMyM2MtMC4yNjMtMC4wNDUtMC41MTItMC4xMzMtMC43NTctMC4yMzRDOC4wODYsMTQuMDk0LDcuNzQyLDE0LDcuMzc5LDE0CgkJYy0wLjI3OCwwLTAuNTQ4LDAuMDUyLTAuODEsMC4xNDJjLTAuMjQyLDAuMDgzLTAuNDgsMC4xODEtMC43MjMsMC4yNmMtMC4xMzcsMC4wNDUtMC4yODEsMC4wNjctMC40MjIsMC4wOTkKCQljLTAuMDg0LDAtMC4xNjksMC0wLjI1MywwYy0wLjA5MS0wLjAyNC0wLjE4NC0wLjA0MS0wLjI3My0wLjA3MmMtMC4yOS0wLjEtMC41Mi0wLjI5MS0wLjczMy0wLjUwMQoJCWMtMC4zMy0wLjMyNS0wLjU5NS0wLjcwMy0wLjg1My0xLjA4NWMtMC40MTItMC42MTEtMC43MDQtMS4yNzktMC45MjMtMS45ODFDMi4yMzIsMTAuMzU5LDIuMTI0LDkuODQ2LDIuMDgzLDkuMzIKCQlDMi4wNzYsOS4yMzcsMi4wNjcsOS4xNTYsMi4wNiw5LjA3NXoiLz4KCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik05LjkzLDAuNWMwLjAyLDAuMjY1LDAuMDEyLDAuNTMtMC4wMzUsMC43OTJjLTAuMSwwLjU0Ni0wLjM0NCwxLjAyMy0wLjY5MiwxLjQ0OQoJCUM4LjksMy4xMTEsOC41NDMsMy40MTMsOC4wOTMsMy41OUM3LjgsMy43MDUsNy40OTYsMy43NTIsNy4xODIsMy43M2MtMC4wMTktMC4wMDEtMC4wMzctMC4wMDUtMC4wNi0wLjAwOAoJCWMwLTAuMTgxLTAuMDE1LTAuMzYxLDAuMDAyLTAuNTM5YzAuMTEtMS4xMjEsMS4wMjctMi4zLDIuNDM2LTIuNjMyQzkuNjUsMC41MzEsOS43NDEsMC41MTcsOS44MzEsMC41CgkJQzkuODY0LDAuNSw5Ljg5NywwLjUsOS45MywwLjV6Ii8%2BCjwvZz4KPC9zdmc%2BCg%3D%3D diff --git a/app/bce/components/common/SystemBar.css b/app/bce/components/common/SystemBar.css index 8282b0f..e20c5f2 100644 --- a/app/bce/components/common/SystemBar.css +++ b/app/bce/components/common/SystemBar.css @@ -7,11 +7,12 @@ .container div { color: #ccc; width: 35px; + height: 100%; display: flex; cursor: pointer; line-height: 24px; justify-content: center; - transition: 0.3s ease-in,color 0.3s ease-in; + transition: .3s ease-in, color .3s ease-in; } .container .exit, @@ -22,7 +23,7 @@ .container .exit:hover { color: #fff; - background-color: #f06a6a; + background-color: #ff4d4f; } .container .min:hover, diff --git a/app/bos/actions/context.js b/app/bos/actions/context.js index f0256d0..5fb976d 100644 --- a/app/bos/actions/context.js +++ b/app/bos/actions/context.js @@ -24,13 +24,13 @@ export const commandMap = { [MENU_UPLOAD_COMMAND]: {name: '上传', icon: 'cloud-upload', command: MENU_UPLOAD_COMMAND}, [MENU_UPLOAD_DIRECTORY_COMMAND]: {name: '上传目录', icon: 'cloud-upload', command: MENU_UPLOAD_DIRECTORY_COMMAND}, [MENU_REFRESH_COMMAND]: {name: '刷新', icon: 'refresh', command: MENU_REFRESH_COMMAND}, - [MENU_COPY_COMMAND]: {name: '复制到', icon: 'copy', command: MENU_COPY_COMMAND}, - [MENU_TRASH_COMMAND]: {name: '删除', icon: 'trash', command: MENU_TRASH_COMMAND}, - [MENU_SHARE_COMMAND]: {name: '分享', icon: 'chain', command: MENU_SHARE_COMMAND}, + [MENU_COPY_COMMAND]: {name: '复制到', icon: 'copy', title: '复制文件', command: MENU_COPY_COMMAND}, + [MENU_TRASH_COMMAND]: {name: '删除', icon: 'trash', title: '删除文件', command: MENU_TRASH_COMMAND}, + [MENU_SHARE_COMMAND]: {name: '分享', icon: 'chain', title: '复制链接', command: MENU_SHARE_COMMAND}, [MENU_MOVE_COMMAND]: {name: '移动到', icon: 'arrows', command: MENU_MOVE_COMMAND}, [MENU_VIEW_COMMAND]: {name: '查看', icon: 'eye', command: MENU_VIEW_COMMAND}, - [MENU_RENAME_COMMAND]: {name: '重命名', icon: 'pencil', command: MENU_RENAME_COMMAND}, - [MENU_DOWNLOAD_COMMAND]: {name: '下载', icon: 'cloud-download', command: MENU_DOWNLOAD_COMMAND}, + [MENU_RENAME_COMMAND]: {name: '重命名', icon: 'pencil', title: '重命名文件', command: MENU_RENAME_COMMAND}, + [MENU_DOWNLOAD_COMMAND]: {name: '下载', icon: 'cloud-download', title: '下载文件', command: MENU_DOWNLOAD_COMMAND}, [MENU_NEW_DIRECTORY_COMMAND]: {name: '新建文件夹', icon: 'plus', command: MENU_NEW_DIRECTORY_COMMAND}, [MENU_NEW_MAPPING_COMMAND]: {name: '创建同步盘', icon: 'plus', command: MENU_NEW_MAPPING_COMMAND} }; diff --git a/app/bos/actions/window.js b/app/bos/actions/window.js index c3dba63..deedc4e 100644 --- a/app/bos/actions/window.js +++ b/app/bos/actions/window.js @@ -51,7 +51,7 @@ export function listMoreObjects(bucketName, prefix = '', marker = '') { [API_TYPE]: { types: [LIST_MORE_REQUEST, LIST_MORE_SUCCESS, LIST_MORE_FAILURE], method: 'listObjects', - args: [bucketName, {delimiter: '/', prefix, marker, maxKeys: 200}] + args: [bucketName, {delimiter: '/', prefix, marker, maxKeys: 1000}] } }; } @@ -126,7 +126,13 @@ export function migrationObject(config, removeSource = false) { const objects = await client.listAllObjects(sourceBucket, sourceObject); // 控制一下copy速率,250ms最多执行5次 const throttledTask = throttle((item, targetKey) => dispatch( - copyObject(sourceBucket, item, targetBucket, targetKey) + copyObject( + sourceBucket, + item, + targetBucket, + targetKey, + {'x-bce-storage-class': item.storageClass} + ) ).then(res => { const {error, response} = res; diff --git a/app/bos/api/client.js b/app/bos/api/client.js index 49c453b..df4e050 100644 --- a/app/bos/api/client.js +++ b/app/bos/api/client.js @@ -5,6 +5,7 @@ * @author mudio(job.mudio@gmail.com) */ +import _ from 'lodash'; import url from 'url'; import {isString} from 'util'; import {BosClient} from '@baiducloud/sdk'; @@ -22,6 +23,18 @@ export class Client extends BosClient { this.credentials = credentials; } + createBucket(bucketName, options) { + return super.createBucket(bucketName, options); + } + + putBucketStorageclass(bucketName, storageClass, options) { + return super.putBucketStorageclass(bucketName, storageClass, options); + } + + putBucketAcl(bucketName, acl, options) { + return super.putBucketAcl(bucketName, acl, options); + } + listBuckets(config = {}) { const {forceUpdate, search} = config; @@ -42,9 +55,15 @@ export class Client extends BosClient { return super.listBuckets().then(res => { const {buckets, owner} = res.body; + const response = _.cloneDeep(res.body); + // hanle multi-az property + response.buckets = response.buckets.map(bucket => ({ + ...bucket, + enableMultiAz: !!bucket.enableMultiAz + })); try { - sessionStorage.setItem('buckets', JSON.stringify(res.body)); + sessionStorage.setItem('buckets', JSON.stringify(response)); } catch (ex) {} // eslint-disable-line if (search) { @@ -54,7 +73,10 @@ export class Client extends BosClient { }; } - return {owner, buckets}; + return { + owner, + buckets + }; }); } diff --git a/app/bos/components/app/SideBar.css b/app/bos/components/app/SideBar.css index cd82f64..27aa149 100644 --- a/app/bos/components/app/SideBar.css +++ b/app/bos/components/app/SideBar.css @@ -29,7 +29,7 @@ align-items: center; padding-left: 40px; text-decoration: blink; - transition: background 0.3s ease-in,color 0.3s ease-in; + transition: background .3s ease-in, color .3s ease-in; } .item:before { @@ -99,7 +99,7 @@ } .active .badge { - background: rgba(10, 84, 183, 0.5); + background: rgba(10, 84, 183, .5); color: #fff; } @@ -108,7 +108,7 @@ flex-flow: column nowrap; } -.tool>span { +.tool > span { cursor: pointer; font-size: 12px; color: #8692b2; @@ -116,6 +116,6 @@ line-height: 24px; } -.tool>span:hover { +.tool > span:hover { color: #2eacfc; } diff --git a/app/bos/components/app/SideBar.js b/app/bos/components/app/SideBar.js index cca647b..0b2c4db 100644 --- a/app/bos/components/app/SideBar.js +++ b/app/bos/components/app/SideBar.js @@ -43,6 +43,13 @@ class SideBar extends Component { { + if (!location || !location.pathname || !['/upload', '/download', '/complete', '/sync'].includes(location.pathname)) { + return true; + } + + return false; + }} > 全部文件 @@ -84,9 +91,6 @@ class SideBar extends Component { 开发者文档 - - Web Uploader - JavaScript SDK diff --git a/app/bos/components/common/Selection.css b/app/bos/components/common/Selection.css index 748a14a..af6f460 100644 --- a/app/bos/components/common/Selection.css +++ b/app/bos/components/common/Selection.css @@ -12,8 +12,8 @@ left: 0; border-radius: 3px; position: absolute; - background: rgba(68, 76, 99, 0.1); - border: 1px solid rgba(68, 76, 99, 0.2); + background: rgba(68, 76, 99, .1); + border: 1px solid rgba(68, 76, 99, .2); } :global .selectionItem { @@ -37,6 +37,7 @@ color: #3b8cff; visibility: hidden; position: absolute; + font-size: 16px; } :global .selectionItem:hover .checkbox { diff --git a/app/bos/components/common/Selection.js b/app/bos/components/common/Selection.js index cd99260..356dff2 100644 --- a/app/bos/components/common/Selection.js +++ b/app/bos/components/common/Selection.js @@ -11,6 +11,13 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import React, {Component} from 'react'; +import { + MENU_RENAME_COMMAND, + MENU_COPY_COMMAND, + MENU_SHARE_COMMAND, + MENU_DOWNLOAD_COMMAND, + MENU_TRASH_COMMAND +} from '../../actions/context'; import styles from './Selection.css'; export default class Selection extends Component { @@ -87,6 +94,19 @@ export default class Selection extends Component { evt.preventDefault(); evt.stopPropagation(); + const supportCommands = [ + MENU_RENAME_COMMAND, + MENU_COPY_COMMAND, + MENU_SHARE_COMMAND, + MENU_DOWNLOAD_COMMAND, + MENU_TRASH_COMMAND + ].map(item => item.toString()); + + // 如果点击文件命令,则不选中当前文件 + if (supportCommands.includes(_.get(evt, 'target.id'))) { + return; + } + const {enabled} = this.props; const {ctrlKey, shiftKey} = evt; const keys = Object.keys(this.__selectedCache); @@ -112,9 +132,9 @@ export default class Selection extends Component { } _onKeyDown = (evt) => { - const {keyCode, ctrlKey, metaKey} = evt; + const {key, ctrlKey, metaKey} = evt; - if (keyCode === 65 && (ctrlKey || metaKey)) { + if (key === 'a' && (ctrlKey || metaKey)) { evt.preventDefault(); this.selectAll(); } @@ -249,7 +269,8 @@ export default class Selection extends Component { ); return ( -
this._onSelectItem(evt, child.key)} onContextMenu={evt => this._onContextMenu(evt, child.key)} diff --git a/app/bos/components/common/SystemBar.css b/app/bos/components/common/SystemBar.css index d9e297f..1f0b12f 100644 --- a/app/bos/components/common/SystemBar.css +++ b/app/bos/components/common/SystemBar.css @@ -1,6 +1,6 @@ .container { - height: 25px; + min-height: 24px; display: flex; justify-content: flex-end; -webkit-app-region: drag; @@ -12,8 +12,9 @@ display: flex; cursor: pointer; line-height: 24px; + min-height: 24px; justify-content: center; - transition: 0.3s ease-in,color 0.3s ease-in; + transition: .3s ease-in,color .3s ease-in; } .title { diff --git a/app/bos/components/explorer/Bucket.css b/app/bos/components/explorer/Bucket.css index a2486e8..1da2ee8 100644 --- a/app/bos/components/explorer/Bucket.css +++ b/app/bos/components/explorer/Bucket.css @@ -2,7 +2,7 @@ .container { display: flex; width: 90px; - min-height: 80px; + height: 90px; color: #666; cursor: pointer; margin: 5px 0 0 5px; @@ -10,7 +10,7 @@ flex-flow: column wrap; align-self: flex-start; align-items: center; - justify-content: center; + justify-content: flex-start; user-select: none; } @@ -19,15 +19,16 @@ } .text { - display: inline-flex; - flex: auto; - width: 90%; + width: 95%; font-size: 12px; - overflow: hidden; text-align: center; - align-items: center; + overflow: hidden; + display: -webkit-box; + word-wrap: break-word; word-break: break-all; - justify-content: center; + -webkit-line-clamp: 2; + text-overflow: ellipsis; + -webkit-box-orient: vertical; } .bucketicon { diff --git a/app/bos/components/explorer/Bucket.js b/app/bos/components/explorer/Bucket.js index 506d681..fd4bfa7 100644 --- a/app/bos/components/explorer/Bucket.js +++ b/app/bos/components/explorer/Bucket.js @@ -7,6 +7,7 @@ import PropTypes from 'prop-types'; import React, {Component} from 'react'; +import {Tooltip} from 'antd'; import styles from './Bucket.css'; @@ -31,7 +32,9 @@ export default class Bucket extends Component { return (
- {item.name} + + {item.name} +
); } diff --git a/app/bos/components/explorer/BucketCreate.css b/app/bos/components/explorer/BucketCreate.css new file mode 100644 index 0000000..de10900 --- /dev/null +++ b/app/bos/components/explorer/BucketCreate.css @@ -0,0 +1,9 @@ + +.link { + color: #1890ff; + cursor: pointer; +} + +.tipFocus { + color: #f7a73f; +} diff --git a/app/bos/components/explorer/BucketCreate.js b/app/bos/components/explorer/BucketCreate.js new file mode 100644 index 0000000..5b72d5d --- /dev/null +++ b/app/bos/components/explorer/BucketCreate.js @@ -0,0 +1,220 @@ +/** + * Component - Bucket Create + * + * @file BucketCreate.js + * @author Vito(hanxiao_do@126.com) + */ + +import React from 'react'; +import {Modal, Form, Input, Button, Select, Radio, notification} from 'antd'; + +import {kRegions, getLocalText, REGION_BJ, REGION_GZ} from '../../../utils/region'; +import ErrorCode from '../../utils/ErrorCode'; +import { + storages, + storageTextMap, + STANDARD, + ARCHIVE, + accessDatasource, + accessHelp, + storageRegionMap +} from '../../../utils/enums'; +import BrowserLink from '../common/BrowserLink'; +import {ClientFactory} from '../../api/client'; +import styles from './BucketCreate.css'; + +const FormItem = Form.Item; +const SelectOption = Select.Option; +const RadioGroup = Radio.Group; +const RadioButton = Radio.Button; + +const layout = { + labelCol: {span: 4}, + wrapperCol: {span: 20}, +}; + + +class BucketCreate extends React.Component { + state = { + visible: false, + region: REGION_BJ, + storage: STANDARD, + access: accessDatasource[0].value, + bucketName: '' + } + + getStorageHelp() { + const {storage} = this.state; + return ( +
+

上传文件时如果未指定存储类型,则使用该默认存储类型

+ { + storage === ARCHIVE ? ( +

+ 归档存储类型针对较大文件的存储,且适用于平均3年访问一次的场景。 + 每位用户每天每PB归档存储文件最多支持取回35个文件,超出后当日无法取回,请根据自身访问情况酌情选择。 +

+ ) + : '' + } +

+ 不同存储类型的使用场景和计费策略不同,且具有不同的使用限制,请在使用前务必查看并了解 + + 分级存储使用说明 + +

+
+ ); + } + + handleInputChange = evt => { + const {name, value} = evt.target; + this.setState({[name]: value.trim()}); + } + + handleSelectChange = region => { + const {storage} = this.state; + this.setState({ + region, + storage: storageRegionMap[region].includes(storage) ? storage : STANDARD + }); + } + + cancelHandle = () => { + this.props.form.setFieldsValue({ + bucketName: '' + }); + this.setState({ + visible: false, + region: REGION_BJ, + storage: STANDARD, + access: accessDatasource[0].value + }); + } + + confirmHandle = async () => { + const form = this.props.form; + const state = this.state; + await form.validateFields(); + try { + const client = ClientFactory.fromRegion(state.region); + await client.createBucket(state.bucketName, {...state}); + await client.putBucketStorageclass(state.bucketName, state.storage); + await client.putBucketAcl(state.bucketName, state.access); + this.setState({ + visible: false + }); + notification.success({ + message: '创建成功', + description: state.bucketName + }); + this.cancelHandle(); + this.props.onSuccess(); + } catch (error) { + ErrorCode[error.code] + ? notification.error({message: ErrorCode[error.code]}) + : notification.error({message: error.code, description: error.message}); + } + } + + saveFormRef = form => { + this.form = form; + } + + render() { + const priceDoc = 'https://cloud.baidu.com/doc/BOS/s/Ok1rmtaow'; + const {visible, region, storage, access} = this.state; + const baseStorages = storageRegionMap[region]; + const storageHelp = this.getStorageHelp(); + const {getFieldDecorator} = this.props.form; + + return ( +
+ +
+ + { + getFieldDecorator('bucketName', { + rules: [ + {required: true, message: 'Bucket名称不能为空!'}, + {pattern: /^[a-z\d][a-z-\d]{1,61}[a-z\d]$/, message: 'Bucket名称不符合规则'} + ] + })( + + ) + } + + + + + + + + + {baseStorages.map(storage => { + return ( + + {storageTextMap[storage]} + + ); + })} + + + + + + { + accessDatasource.map(item => { + return ( + + {item.text} + + ); + }) + } + + + + +
+ 按用量收费 + 免费创建,使用阶段按照用量收费。 + + 了解计费详情 + +
+
+ +
+
+ +
+ + ); + } +} +export default Form.create()(BucketCreate); diff --git a/app/bos/components/explorer/Explorer.js b/app/bos/components/explorer/Explorer.js index 0657a79..0ac13f3 100644 --- a/app/bos/components/explorer/Explorer.js +++ b/app/bos/components/explorer/Explorer.js @@ -118,10 +118,7 @@ export default class Explorer extends Component { * @memberOf Explorer */ _onMigration = (config = {}, removeSource = false) => { - const { - sourceBucket, sourceObject, - targetBucket, targetObject - } = config; + const {sourceBucket, sourceObject, targetBucket, targetObject} = config; this.setState({visible: false}); @@ -161,6 +158,7 @@ export default class Explorer extends Component { notification.success({message: '创建成功', description: `成功创建文件夹${values.name}`}); this.setState({newFolder: false}); + form.resetFields(); this._onReresh(); } catch (ex) { notification.error({message: '创建失败', description: ex.message}); @@ -337,6 +335,7 @@ export default class Explorer extends Component { this._objectWindow = _objectWindow} region={region} bucket={bucket} prefix={prefix} diff --git a/app/bos/components/explorer/File.css b/app/bos/components/explorer/File.css index d7c2af1..97236d7 100644 --- a/app/bos/components/explorer/File.css +++ b/app/bos/components/explorer/File.css @@ -3,9 +3,9 @@ display: flex; width: 90px; color: #666; - min-height: 80px; + height: 90px; align-items: center; - justify-content: center; + justify-content: flex-start; flex-flow: column nowrap; user-select: none; } @@ -14,7 +14,7 @@ width: 95%; font-size: 12px; text-align: center; - overflow : hidden; + overflow: hidden; display: -webkit-box; word-wrap: break-word; word-break: break-all; @@ -39,6 +39,7 @@ line-height: 30px; user-select: none; padding-right: 10px; + margin-left: 10px; } .listLayout .icon { @@ -49,14 +50,26 @@ } .listLayout .text { - width: 0; + width: 310px; flex: auto; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; +} + +.listLayout .storage { + width: 100px; } .listLayout .extra { - width: 80px; + width: 120px; +} +.listLayout .time { + width: 126px; +} + +.listLayout .time { + width: 150px; } .listLayout:hover .commands span { diff --git a/app/bos/components/explorer/File.js b/app/bos/components/explorer/File.js index c93f437..8bc6b01 100644 --- a/app/bos/components/explorer/File.js +++ b/app/bos/components/explorer/File.js @@ -91,14 +91,17 @@ export default class File extends Component { renderCommands() { const {name, onCommand} = this.props; - return File.supportCommands.map(command => { - const {icon} = commandMap[command]; - + return File.supportCommands.map((command, index) => { + const {icon, title} = commandMap[command]; return ( - onCommand(command, {keys: [name]})} - /> + + onCommand(command, {keys: [name]})} + /> + ); }); } @@ -117,7 +120,7 @@ export default class File extends Component { {fileName} - +
); @@ -131,7 +134,7 @@ export default class File extends Component { {this.renderCommands()} - {BosCategory[storageClass]} + {BosCategory[storageClass] || '-'} {humanSize(size)} {utcToLocalTime(lastModified)} diff --git a/app/bos/components/explorer/Folder.js b/app/bos/components/explorer/Folder.js index 225d9a9..9eaf038 100644 --- a/app/bos/components/explorer/Folder.js +++ b/app/bos/components/explorer/Folder.js @@ -54,14 +54,16 @@ export default class Folder extends Component { renderCommands() { const {name, onCommand} = this.props; - return Folder.supportCommands.map(command => { - const {icon} = commandMap[command]; + return Folder.supportCommands.map((command, index) => { + const {icon, title} = commandMap[command]; return ( - onCommand(command, {keys: [name]})} - /> + + onCommand(command, {keys: [name]})} + /> + ); }); } @@ -91,9 +93,9 @@ export default class Folder extends Component { {this.renderCommands()} - - - - - - + -- + -- + -- ); } diff --git a/app/bos/components/explorer/Navigator.css b/app/bos/components/explorer/Navigator.css index 478dd6e..5f21b1d 100644 --- a/app/bos/components/explorer/Navigator.css +++ b/app/bos/components/explorer/Navigator.css @@ -2,6 +2,7 @@ .container { display: flex; height: 50px; + align-items: center; flex-shrink: 0; user-select: none; -webkit-app-region: drag; @@ -45,6 +46,10 @@ cursor: not-allowed; } +.createBtn { + margin-right: 10px; +} + .url { display: flex; flex: auto; @@ -139,4 +144,4 @@ font-size: 10px; padding: 2px; margin-right: 2px; -} \ No newline at end of file +} diff --git a/app/bos/components/explorer/Navigator.js b/app/bos/components/explorer/Navigator.js index 8c26d94..1678ea5 100644 --- a/app/bos/components/explorer/Navigator.js +++ b/app/bos/components/explorer/Navigator.js @@ -12,9 +12,14 @@ import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import classnames from 'classnames'; import React, {Component} from 'react'; +import { Lifecycle } from 'react-router' +import {Button} from 'antd'; +import {listBuckets} from '../../actions/window'; import styles from './Navigator.css'; import {redirect, query} from '../../actions/navigator'; +import {ClientFactory} from '../../api/client'; +import BucketCreate from './BucketCreate'; class Navigator extends Component { static propTypes = { @@ -23,6 +28,12 @@ class Navigator extends Component { dispatch: PropTypes.func.isRequired, }; + mixins = [Lifecycle] + + state = { + buckets: [] + } + constructor(props, ...args) { super(props, ...args); @@ -36,6 +47,26 @@ class Navigator extends Component { componentDidMount() { const {dispatch, bucket, prefix} = this.props; dispatch(query({bucket, prefix})); + + const client = ClientFactory.getDefault(); + // 获取buckets + client.listBuckets().then(res => { + const buckets = res.buckets.map( + bucket => ({ + label: bucket.name, + value: bucket.name, + isLeaf: false, + enableMultiAz: bucket.enableMultiAz + }) + ); + + this.setState({buckets}); + }); + } + + bucketCreated = () => { + const {dispatch} = this.props; + dispatch(listBuckets({forceUpdate: true})); } componentWillReceiveProps(nextProps) { @@ -45,6 +76,10 @@ class Navigator extends Component { } } + saveFormRef = form => { + this.form = form; + } + _onChange = evt => { this.setState({value: evt.target.value}); this.invokeQuery(); @@ -52,10 +87,15 @@ class Navigator extends Component { _onKeyDown = (event) => { const {key, target} = event; + const {buckets} = this.state; const {bucket} = this.props; const inputBucket = target.value.trim(); + // 当前输入的bucket名称是否合法, bucket值为空代表全部文件目录下,否则不做bucket校验 + const isValidBucket = !bucket + ? Array.isArray(buckets) && buckets.some(item => item.value === inputBucket) + : true; - if (key === 'Enter' && !bucket && inputBucket) { + if (key === 'Enter' && !bucket && inputBucket && isValidBucket) { event.preventDefault(); this._redirect(inputBucket); @@ -227,6 +267,13 @@ class Navigator extends Component { + { + !bucket && ( +
+ +
+ ) + }
{this.renderSearch()}
diff --git a/app/bos/components/explorer/NewFolder.js b/app/bos/components/explorer/NewFolder.js index a67b1da..7ef12ec 100644 --- a/app/bos/components/explorer/NewFolder.js +++ b/app/bos/components/explorer/NewFolder.js @@ -13,7 +13,7 @@ const FormItem = Form.Item; export default Form.create({})( (props) => { const {visible, onCancel, onConfirm, form} = props; - const {getFieldDecorator} = form; + const {getFieldDecorator, resetFields} = form; return ( { + _onScroll = _.throttle((evt) => { const {scrollTop, scrollHeight, clientHeight} = evt.target; + const {prevScrollTop} = this.state; const {bucket, prefix, nextMarker, isFetching, isTruncated, listMore} = this.props; - const allowListMore = scrollHeight - scrollTop - clientHeight <= clientHeight / 3; + // const allowListMore = scrollHeight - scrollTop - clientHeight <= clientHeight / 3; + + if (prevScrollTop <= scrollTop) { + this.setState({ prevScrollTop: scrollTop }); - if (!isFetching && isTruncated && bucket && allowListMore) { - listMore(bucket, prefix, nextMarker); + const allowListMore = scrollHeight - scrollTop - clientHeight > 0; + + if (!isFetching && isTruncated && bucket && allowListMore) { + listMore(bucket, prefix, nextMarker); + } } - } + }, 5000); _onCommand = (cmd, config) => { const {selectedItems} = this.state; @@ -170,6 +181,11 @@ class Window extends Component { } } + // 清空selection + clearSelection() { + this._selection.clearSelection(); + } + redirect = (prefix = '') => { const {bucket, dispatch} = this.props; @@ -195,7 +211,8 @@ class Window extends Component { if (!isFetching && !hasError && folders.length === 0 - && objects.length === 0) { + && objects.length === 0 + ) { return ( 文件夹为空,拖拽文件上传 @@ -253,12 +270,16 @@ class Window extends Component { return (
- - 名称 (已选{selectedItems.length}项) + + 名称 (已选{selectedItems.length}项) - 存储类型 + 存储类型 大小 - 修改时间 + 修改时间
); } @@ -287,7 +308,7 @@ class Window extends Component { // 重命名,只能操作一个文件 {type: MENU_RENAME_COMMAND, disable: selectedItems.length !== 1}, // 分享,只能分享文件 - {type: MENU_SHARE_COMMAND, disable: selectedItems.some(item => item.endsWith('/'))}, + {type: MENU_SHARE_COMMAND, disable: selectedItems.length !== 1 || selectedItems.some(item => /\/$/.test(item))}, {type: MENU_DOWNLOAD_COMMAND}, {type: MENU_TRASH_COMMAND} ]; @@ -334,7 +355,8 @@ class Window extends Component { onContextMenu={this._onContextMenu} > {this.renderListHead()} - this._selection = _selection} // eslint-disable-line + this._selection = _selection} // eslint-disable-line className={styleName} onSelectionChange={this._onSelectionChange} > diff --git a/app/bos/components/explorer/migration/Copy.js b/app/bos/components/explorer/migration/Copy.js index b3934ae..2c285d9 100644 --- a/app/bos/components/explorer/migration/Copy.js +++ b/app/bos/components/explorer/migration/Copy.js @@ -11,8 +11,10 @@ import {Modal, Cascader, Form} from 'antd'; const FormItem = Form.Item; export default Form.create()(props => { - const {visible, onCancel, onCopy, loadData, buckets, form} = props; + const {visible, onCancel, onCopy, loadData, buckets, form, bucket} = props; const {getFieldDecorator} = form; + const isMultiAzEnabled = !!(Array.isArray(buckets) && buckets.find(item => item.value === bucket && item.enableMultiAz)); + const filteredBucketList = buckets.filter(item => item.enableMultiAz === isMultiAzEnabled); return ( { message: '输入名称不能为空!' }] })( - + ) } diff --git a/app/bos/components/explorer/migration/Migration.js b/app/bos/components/explorer/migration/Migration.js index e799f3b..f24c00d 100644 --- a/app/bos/components/explorer/migration/Migration.js +++ b/app/bos/components/explorer/migration/Migration.js @@ -41,7 +41,8 @@ export default class Migration extends Component { bucket => ({ label: bucket.name, value: bucket.name, - isLeaf: false + isLeaf: false, + enableMultiAz: bucket.enableMultiAz }) ); diff --git a/app/bos/components/syncdisk/NewMapping.css b/app/bos/components/syncdisk/NewMapping.css index 7288419..c94db8b 100644 --- a/app/bos/components/syncdisk/NewMapping.css +++ b/app/bos/components/syncdisk/NewMapping.css @@ -7,7 +7,12 @@ white-space: nowrap; vertical-align: middle; margin-left: 10px; - line-height: 1; + line-height: 22px; +} + +.adaptor { + padding: 5px 0; + line-height: 30px; } .tooltip { @@ -26,7 +31,7 @@ display: block; padding: 5px 12px; overflow: hidden; - color: rgba(0,0,0,0.65); + color: rgba(0, 0, 0, .65); font-weight: normal; line-height: 22px; white-space: nowrap; diff --git a/app/bos/components/syncdisk/NewMapping.js b/app/bos/components/syncdisk/NewMapping.js index 3b32e67..7927f6e 100644 --- a/app/bos/components/syncdisk/NewMapping.js +++ b/app/bos/components/syncdisk/NewMapping.js @@ -16,6 +16,7 @@ import styles from './NewMapping.css'; import {ClientFactory} from '../../api/client'; import {toggleNewMapping, changeLocalPath, changeBosPath} from '../../actions/syncdisk'; import ErrorCode from '../../utils/ErrorCode'; +import {isWin} from '../../../utils'; const FormItem = Form.Item; @@ -200,6 +201,7 @@ class NewMapping extends Component { const {visible, localPath, bosPath, onConfirm} = this.props; const {loading, open} = this.state; const message = '同步映射建立完成后将自动开始数据同步,您可以在列表中使用操作来暂停自动同步'; + const pathStyle = `${styles.path} ${(isWin ? styles.adaptor : styles.adaptor)}`; return ( - {localPath} + {localPath} diff --git a/app/bos/components/syncdisk/SyncDisk.js b/app/bos/components/syncdisk/SyncDisk.js index 0aa10c9..40c3ec3 100644 --- a/app/bos/components/syncdisk/SyncDisk.js +++ b/app/bos/components/syncdisk/SyncDisk.js @@ -30,14 +30,15 @@ import {getLogPath} from '../../../utils'; class SyncDisk extends Component { state = { - selectedItems: [] + selectedRowKeys: [], + selectedRows: [], }; columns = [ { title: '序号', dataIndex: 'key', - width: 50 + width: 60 }, { title: '本地路径', @@ -70,7 +71,7 @@ class SyncDisk extends Component { { title: '操作', dataIndex: 'op', - width: 120, + width: 150, render: (value, item) => { let tmpHtml = (