feat(fe): enable drag and drop to upload files

This commit is contained in:
hexxa 2022-04-30 21:40:45 +08:00 committed by Hexxa
parent 6034c56fe8
commit badd5ce65b
12 changed files with 152 additions and 22 deletions

View file

@ -59,7 +59,7 @@
"react-icons": "4.3.1",
"react-qr-code": "^2.0.3",
"react-svg": "^8.0.6",
"throttle-debounce": "^2.1.0",
"throttle-debounce": "^4.0.1",
"webpack-bundle-analyzer": "^4.4.2",
"worker-loader": "^3.0.7"
},

View file

@ -2,6 +2,7 @@ export const settingsTabsCtrl = "settingsTabs";
export const settingsDialogCtrl = "settingsDialog";
export const sharingCtrl = "sharingCtrl";
export const filesViewCtrl = "filesView";
export const dropAreaCtrl = "dropArea";
export const ctrlHidden = "hidden";
export const ctrlOn = "on";
export const ctrlOff = "off";

View file

@ -15,6 +15,7 @@ import {
ctrlOn,
ctrlOff,
loadingCtrl,
dropAreaCtrl,
} from "../common/controls";
import { LoginProps } from "./pane_login";
import { AdminProps } from "./pane_admin";
@ -134,6 +135,7 @@ export function initState(): ICoreState {
[sharingCtrl]: ctrlOff,
[filesViewCtrl]: "rows",
[loadingCtrl]: ctrlOff,
[dropAreaCtrl]: ctrlOff,
}),
options: Map<string, Set<string>>({
[panelTabs]: Set<string>([
@ -146,6 +148,7 @@ export function initState(): ICoreState {
[sharingCtrl]: Set<string>([ctrlOn, ctrlOff]),
[filesViewCtrl]: Set<string>(["rows", "table"]),
[loadingCtrl]: Set<string>([ctrlOn, ctrlOff]),
[dropAreaCtrl]: Set<string>([ctrlOn, ctrlOff]),
}),
},
},

View file

@ -1,5 +1,5 @@
import * as React from "react";
import { List } from "immutable";
import { throttle } from "throttle-debounce";
import { updater } from "./state_updater";
import { ICoreState, MsgProps, UIProps } from "./core_state";
@ -16,10 +16,12 @@ import {
loadingCtrl,
ctrlOn,
ctrlHidden,
dropAreaCtrl,
} from "../common/controls";
import { LoadingIcon } from "./visual/loading";
import { Title } from "./visual/title";
import { HotkeyHandler } from "../common/hotkeys";
import { getIcon } from "./visual/icons";
export interface Props {
filesInfo: FilesProps;
@ -65,6 +67,10 @@ export class Layers extends React.Component<Props, State, {}> {
(this.props.ui.control.controls.get(sharingCtrl) === ctrlOn &&
this.props.filesInfo.isSharing);
const loginPaneClass = hideLogin ? "hidden" : "";
const dropAreaClass =
this.props.ui.control.controls.get(dropAreaCtrl) === ctrlOn
? ""
: "hidden";
const showSettings =
this.props.ui.control.controls.get(settingsDialogCtrl) === ctrlOn
@ -82,15 +88,23 @@ export class Layers extends React.Component<Props, State, {}> {
</div>
<div id="login-layer" className={`layer ${loginPaneClass}`}>
{/* <div id="root-container"> */}
<AuthPane
login={this.props.login}
ui={this.props.ui}
update={this.props.update}
msg={this.props.msg}
enabled={!hideLogin}
/>
{/* </div> */}
<AuthPane
login={this.props.login}
ui={this.props.ui}
update={this.props.update}
msg={this.props.msg}
enabled={!hideLogin}
/>
</div>
{/* ${dropAreaClass} */}
<div id="drop-area-layer" className={`${dropAreaClass}`}>
<div className="drop-area-container">
<div className="drop-area major-bg focus-font">
<div>{getIcon("RiFolderUploadFill", "4rem", "focus")}</div>
<span>{this.props.msg.pkg.get("term.dropAnywhere")}</span>
</div>
</div>
</div>
<div id="settings-layer" className={`layer ${showSettings}`}>

View file

@ -156,15 +156,15 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}
};
addUploads = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files.length > 200) {
addFileList = (originalFileList: FileList) => {
if (originalFileList.length > 200) {
Env().alertMsg(this.props.msg.pkg.get("err.tooManyUploads"));
return;
}
let fileList = List<File>();
for (let i = 0; i < event.target.files.length; i++) {
fileList = fileList.push(event.target.files[i]);
for (let i = 0; i < originalFileList.length; i++) {
fileList = fileList.push(originalFileList[i]);
}
const status = updater().addUploads(fileList);
@ -174,6 +174,10 @@ export class FilesPanel extends React.Component<Props, State, {}> {
this.props.update(updater().updateUploadingsInfo);
};
addUploads = (event: React.ChangeEvent<HTMLInputElement>) => {
this.addFileList(event.target.files);
};
mkDirFromKb = async (
event: React.KeyboardEvent<HTMLInputElement>
): Promise<void> => {

View file

@ -1,5 +1,6 @@
import * as React from "react";
import { Map } from "immutable";
import { Map, List } from "immutable";
import { throttle } from "throttle-debounce";
import { ICoreState, MsgProps, UIProps } from "./core_state";
import { FilesPanel, FilesProps } from "./panel_files";
@ -13,8 +14,10 @@ import { AdminProps } from "./pane_admin";
import { TopBar } from "./topbar";
import { CronJobs } from "../common/cron";
import { updater } from "./state_updater";
import { dropAreaCtrl, ctrlOn, ctrlOff } from "../common/controls";
export const controlName = "panelTabs";
const dragOverthrottlePeriod = 200;
export interface Props {
filesInfo: FilesProps;
uploadingsInfo: UploadingsProps;
@ -26,10 +29,16 @@ export interface Props {
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
export interface State {}
export interface State {
lastDragOverTime: number;
}
export class RootFrame extends React.Component<Props, State, {}> {
private filesPanelRef: FilesPanel;
constructor(p: Props) {
super(p);
this.state = {
lastDragOverTime: 0,
};
}
componentDidMount(): void {
@ -38,12 +47,22 @@ export class RootFrame extends React.Component<Props, State, {}> {
args: [],
delay: 60 * 1000,
});
CronJobs().setInterval("endDrag", {
func: this.endDrag,
args: [],
delay: dragOverthrottlePeriod * 2,
});
}
componentWillUnmount() {
CronJobs().clearInterval("autoSwitchTheme");
}
private setFilesPanelRef = (ref: FilesPanel) => {
this.filesPanelRef = ref;
};
makeBgStyle = (): Object => {
if (this.props.ui.clientCfg.allowSetBg) {
if (
@ -78,6 +97,39 @@ export class RootFrame extends React.Component<Props, State, {}> {
return {};
};
onDragOver = (ev: React.DragEvent<HTMLDivElement>) => {
this.onDragOverImp();
ev.preventDefault();
};
onDragOverImp = throttle(dragOverthrottlePeriod, () => {
updater().setControlOption(dropAreaCtrl, ctrlOn);
this.props.update(updater().updateUI);
this.setState({ lastDragOverTime: Date.now() });
});
onDrop = (ev: React.DragEvent<HTMLDivElement>) => {
if (ev.dataTransfer?.files?.length > 0) {
this.filesPanelRef.addFileList(ev.dataTransfer.files);
}
ev.preventDefault();
};
endDrag = () => {
const now = Date.now();
const isDragOverOff =
this.props.ui.control.controls.get(dropAreaCtrl) === ctrlOff;
if (
now - this.state.lastDragOverTime < dragOverthrottlePeriod * 1.5 ||
isDragOverOff
) {
return;
}
updater().setControlOption(dropAreaCtrl, ctrlOff);
this.props.update(updater().updateUI);
};
render() {
const bgStyle = this.makeBgStyle();
const autoTheme =
@ -97,7 +149,12 @@ export class RootFrame extends React.Component<Props, State, {}> {
const sharingsPanelClass = displaying === "sharingsPanel" ? "" : "hidden";
return (
<div id="root-frame" className={`${theme} ${fontSizeClass}`}>
<div
id="root-frame"
className={`${theme} ${fontSizeClass}`}
onDragOver={this.onDragOver}
onDrop={this.onDrop}
>
<div id="bg" style={bgStyle}>
<div id="custom">
<Layers
@ -151,6 +208,7 @@ export class RootFrame extends React.Component<Props, State, {}> {
ui={this.props.ui}
enabled={displaying === "filesPanel"}
update={this.props.update}
ref={this.setFilesPanelRef}
/>
</span>

View file

@ -23,6 +23,7 @@ import { BiSortUp } from "@react-icons/all-files/bi/BiSortUp";
import { RiListSettingsFill } from "@react-icons/all-files/ri/RiListSettingsFill";
import { RiHardDriveFill } from "@react-icons/all-files/ri/RiHardDriveFill";
import { RiGridFill } from "@react-icons/all-files/ri/RiGridFill";
import { RiFolderUploadFill } from "@react-icons/all-files/ri/RiFolderUploadFill";
import { colorClass } from "./colors";
@ -54,6 +55,7 @@ const icons = Map<string, IconType>({
RiListSettingsFill: RiListSettingsFill,
RiHardDriveFill: RiHardDriveFill,
RiGridFill: RiGridFill,
RiFolderUploadFill: RiFolderUploadFill,
});
export function getIconWithProps(

View file

@ -153,4 +153,5 @@ export const msgs: Map<string, string> = Map({
"autoTheme": "Enable auto theme switching",
"term.enabled": "Enabled",
"term.disabled": "Disabled",
"term.dropAnywhere": "Drop files anywhere"
});

View file

@ -150,4 +150,5 @@ export const msgs: Map<string, string> = Map({
"autoTheme": "自动切换主题",
"term.enabled": "启用",
"term.disabled": "关闭",
"term.dropAnywhere": "把文件在任意处释放"
});

View file

@ -328,6 +328,22 @@
background-color: #333;
}
.theme-dark .drop-area-container {
position: relative;
width: 100%;
padding-top: 20rem;
}
.theme-dark .drop-area {
opacity: 0.8;
backdrop-filter: blur(9.5px);
text-align: center;
border-radius: 0.8rem;
padding: 2rem 0;
margin: auto;
width: 25rem;
}
/* +colors */
.theme-dark .major-font {
@ -354,6 +370,9 @@
.theme-dark .focus-bg {
background-color: #16a085;
}
.theme-dark .reverse-bg {
background-color: #fff;
}
.theme-dark .minor-bg {
background-color: #333;
}

View file

@ -330,6 +330,30 @@
background-color: #ecf0f1;
}
.theme-default .drop-area-container {
position: relative;
width: 100%;
padding-top: 20rem;
}
.theme-default .drop-area {
opacity: 0.8;
backdrop-filter: blur(9.5px);
text-align: center;
border-radius: 0.8rem;
padding: 2rem 0;
margin: auto;
width: 25rem;
}
.theme-default #login-layer {
z-index: 200;
}
.theme-default #drop-area-layer {
z-index: 4;
}
/* +colors */
.theme-default .minor-font {
@ -360,6 +384,9 @@
.theme-default .minor-bg {
background-color: #ecf0f6;
}
.theme-default .reverse-bg {
background-color: #000;
}
.theme-default ::placeholder {
color: #95a5a6;
}

View file

@ -4465,10 +4465,10 @@ throat@^6.0.1:
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
throttle-debounce@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2"
integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==
throttle-debounce@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-4.0.1.tgz#f86656fe9c8a6b8218952ef36c3bf225089b1baf"
integrity sha512-s3PedbXdZtr8v3J5Sxd5T/GmWG80BcK5GVpwDdvgEaUXsaMqQe4zxgmC4TA7B8luSDCPxo3CeSBS3F9rF1CZwg==
tmpl@1.0.5:
version "1.0.5"