Dev cleanup (#32)
* fix(client): fix layout issues in mobile devices * feat(client/panes): add panes, settings, login pane * fix(client/dialog): move return button back to dialog * fix(client/panes): fix style issues * fix(client/browser): fix table style issues * fix(client): refresh list after login * chore(readme): update reademe
This commit is contained in:
parent
31e4850344
commit
1ff1e2024e
12 changed files with 653 additions and 331 deletions
45
README.md
45
README.md
|
@ -1,5 +1,5 @@
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
[WORKING IN PROGRESS!!!] Quickshare
|
Quickshare
|
||||||
</h1>
|
</h1>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Simple file sharing server built with Go/Golang, Typescript, Gin, React, Boltdb, etc.
|
Simple file sharing server built with Go/Golang, Typescript, Gin, React, Boltdb, etc.
|
||||||
|
@ -24,50 +24,21 @@ Choose Language: English | [简体中文](./docs/README_zh-cn.md)
|
||||||
|
|
||||||
Visit [Release Page](https://github.com/ihexxa/quickshare/releases) to get Linux | Mac | Windows distribution(s).
|
Visit [Release Page](https://github.com/ihexxa/quickshare/releases) to get Linux | Mac | Windows distribution(s).
|
||||||
|
|
||||||
## Features
|
## Main Features
|
||||||
|
|
||||||
- Upload and download in browser, no client
|
- Uploading and downloading in browser without client
|
||||||
- Share files among desktop and mobile devices
|
- Be compatible with Linux, Mac and Windows
|
||||||
- Portable software
|
- Sharing files among different devices (desktop & mobile)
|
||||||
- Add files from local
|
- Stopping and resuming uploading/downloading
|
||||||
- Add download limit for resource
|
|
||||||
- Download from interrupted point
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
2 steps are needed to start a quickshare: unzip it and start it.
|
Coming soon.
|
||||||
|
|
||||||
The first step, unzip and start quickshare
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
- Unzip the package: `unzip [package].` (`[package]` could be `quickshare_0.0.8_linux_x86_6 4.zip`)
|
|
||||||
- Start quickshare `./quickshare`
|
|
||||||
|
|
||||||
### Mac
|
|
||||||
|
|
||||||
- Unzip the package: `unzip [package].` (`[package]` could be `quickshare_0.0.8_macos_x86_64.zip`)
|
|
||||||
- Start quickshare `./quickshare`
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
- Unzip the package
|
|
||||||
- Go into folder and click `quickshare.exe`
|
|
||||||
|
|
||||||
Last step, meet quickshare in browser
|
|
||||||
|
|
||||||
- Quickshare will start and show `quickshare starts @ [URL]` in terminal (e.g. `URL` could be `192.168.0.1:8888`)
|
|
||||||
- Open `URL` in browser and login with `admin` and `quicksh@re`
|
|
||||||
- Enjoy (But don't forget to change password according to [FAQ document](./docs/FAQ_en-us.md))
|
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
Please refer [FAQ document](./docs/FAQ_en-us.md)
|
Please refer [FAQ document](./docs/FAQ_en-us.md)
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Please refer [Configuration document](./docs/CONFIG_en-us.md)
|
|
||||||
|
|
||||||
### Contribution
|
### Contribution
|
||||||
|
|
||||||
Will add it soon...
|
TODO
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { mock, instance } from "ts-mockito";
|
import { mock, instance } from "ts-mockito";
|
||||||
|
|
||||||
import { initWithWorker } from "../core_state";
|
import { initWithWorker } from "../core_state";
|
||||||
import { Updater } from "../auth_pane";
|
import { Updater } from "../pane_login";
|
||||||
import { MockUsersClient } from "../../client/users_mock";
|
import { MockUsersClient } from "../../client/users_mock";
|
||||||
import { Response } from "../../client";
|
import { Response } from "../../client";
|
||||||
import { MockWorker } from "../../worker/interface";
|
import { MockWorker } from "../../worker/interface";
|
|
@ -3,6 +3,7 @@ import * as ReactDOM from "react-dom";
|
||||||
import { List, Map } from "immutable";
|
import { List, Map } from "immutable";
|
||||||
import FileSize from "filesize";
|
import FileSize from "filesize";
|
||||||
|
|
||||||
|
import { Layouter } from "./layouter";
|
||||||
import { ICoreState } from "./core_state";
|
import { ICoreState } from "./core_state";
|
||||||
import {
|
import {
|
||||||
IUsersClient,
|
IUsersClient,
|
||||||
|
@ -14,7 +15,6 @@ import { FilesClient } from "../client/files";
|
||||||
import { UsersClient } from "../client/users";
|
import { UsersClient } from "../client/users";
|
||||||
import { UploadMgr } from "../worker/upload_mgr";
|
import { UploadMgr } from "../worker/upload_mgr";
|
||||||
import { UploadEntry } from "../worker/interface";
|
import { UploadEntry } from "../worker/interface";
|
||||||
// import { FileUploader } from "../worker/uploader";
|
|
||||||
|
|
||||||
export const uploadCheckCycle = 1000;
|
export const uploadCheckCycle = 1000;
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ export interface Props {
|
||||||
uploadFiles: List<File>;
|
uploadFiles: List<File>;
|
||||||
uploadValue: string;
|
uploadValue: string;
|
||||||
|
|
||||||
|
isVertical: boolean;
|
||||||
|
|
||||||
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,11 +158,6 @@ export class Updater {
|
||||||
return Updater.setItems(List<string>(dstDir.split("/")));
|
return Updater.setItems(List<string>(dstDir.split("/")));
|
||||||
};
|
};
|
||||||
|
|
||||||
static setPwd = async (oldPwd: string, newPwd: string): Promise<boolean> => {
|
|
||||||
const resp = await Updater.usersClient.setPwd(oldPwd, newPwd);
|
|
||||||
return resp.status === 200;
|
|
||||||
};
|
|
||||||
|
|
||||||
static addUploadFiles = (fileList: FileList, len: number) => {
|
static addUploadFiles = (fileList: FileList, len: number) => {
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
// do not wait for the promise
|
// do not wait for the promise
|
||||||
|
@ -179,11 +176,6 @@ export interface State {
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
selectedSrc: string;
|
selectedSrc: string;
|
||||||
selectedItems: Map<string, boolean>;
|
selectedItems: Map<string, boolean>;
|
||||||
|
|
||||||
show: boolean;
|
|
||||||
oldPwd: string;
|
|
||||||
newPwd1: string;
|
|
||||||
newPwd2: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Browser extends React.Component<Props, State, {}> {
|
export class Browser extends React.Component<Props, State, {}> {
|
||||||
|
@ -201,10 +193,6 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
inputValue: "",
|
inputValue: "",
|
||||||
selectedSrc: "",
|
selectedSrc: "",
|
||||||
selectedItems: Map<string, boolean>(),
|
selectedItems: Map<string, boolean>(),
|
||||||
show: false,
|
|
||||||
oldPwd: "",
|
|
||||||
newPwd1: "",
|
|
||||||
newPwd2: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.uploadInput = undefined;
|
this.uploadInput = undefined;
|
||||||
|
@ -227,18 +215,9 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showPane = () => {
|
// showPane = () => {
|
||||||
this.setState({ show: !this.state.show });
|
// this.setState({ show: !this.state.show });
|
||||||
};
|
// };
|
||||||
changeOldPwd = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
this.setState({ oldPwd: ev.target.value });
|
|
||||||
};
|
|
||||||
changeNewPwd1 = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
this.setState({ newPwd1: ev.target.value });
|
|
||||||
};
|
|
||||||
changeNewPwd2 = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
this.setState({ newPwd2: ev.target.value });
|
|
||||||
};
|
|
||||||
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ inputValue: ev.target.value });
|
this.setState({ inputValue: ev.target.value });
|
||||||
};
|
};
|
||||||
|
@ -352,31 +331,6 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
setPwd = () => {
|
|
||||||
if (this.state.newPwd1 !== this.state.newPwd2) {
|
|
||||||
alert("new passwords are not same");
|
|
||||||
} else if (this.state.newPwd1 == "") {
|
|
||||||
alert("new passwords can not be empty");
|
|
||||||
} else if (this.state.oldPwd == this.state.newPwd1) {
|
|
||||||
alert("old and new passwords are same");
|
|
||||||
} else {
|
|
||||||
Updater.setPwd(this.state.oldPwd, this.state.newPwd1).then(
|
|
||||||
(ok: boolean) => {
|
|
||||||
if (ok) {
|
|
||||||
alert("Password is updated");
|
|
||||||
} else {
|
|
||||||
alert("fail to update password");
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
oldPwd: "",
|
|
||||||
newPwd1: "",
|
|
||||||
newPwd2: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const breadcrumb = this.props.dirPath.map(
|
const breadcrumb = this.props.dirPath.map(
|
||||||
(pathPart: string, key: number) => {
|
(pathPart: string, key: number) => {
|
||||||
|
@ -395,96 +349,56 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const ops = (
|
const nameCellClass = `item-name item-name-${
|
||||||
<div>
|
this.props.isVertical ? "vertical" : "horizontal"
|
||||||
<div className="grey0-font">
|
} pointer`;
|
||||||
|
const sizeCellClass = this.props.isVertical ? `hidden margin-s` : ``;
|
||||||
|
const modTimeCellClass = this.props.isVertical ? `hidden margin-s` : ``;
|
||||||
|
|
||||||
|
const layoutChildren = [
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.delete()}
|
onClick={() => this.delete()}
|
||||||
className="red0-bg white-font margin-m"
|
className="red0-bg white-font margin-t-m margin-b-m"
|
||||||
>
|
>
|
||||||
Delete Selected
|
Delete Selected
|
||||||
</button>
|
</button>,
|
||||||
<span className="margin-s">-</span>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.moveHere()}
|
onClick={() => this.moveHere()}
|
||||||
className="grey1-bg white-font margin-m"
|
className="grey1-bg white-font margin-t-m margin-b-m"
|
||||||
>
|
>
|
||||||
Paste
|
Paste
|
||||||
</button>
|
</button>,
|
||||||
<span className="margin-s">-</span>
|
<span className="inline-block margin-t-m margin-b-m">
|
||||||
<button
|
|
||||||
onClick={this.onClickUpload}
|
|
||||||
className="green0-bg white-font margin-m"
|
|
||||||
>
|
|
||||||
Upload Files
|
|
||||||
</button>
|
|
||||||
<span className="margin-s">-</span>
|
|
||||||
<span className="margin-m">
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
onChange={this.onInputChange}
|
onChange={this.onInputChange}
|
||||||
value={this.state.inputValue}
|
value={this.state.inputValue}
|
||||||
className="margin-r-m black0-font"
|
className="black0-font margin-r-m"
|
||||||
placeholder="folder name"
|
placeholder="folder name"
|
||||||
/>
|
/>
|
||||||
<button onClick={this.onMkDir} className="grey1-bg white-font">
|
<button onClick={this.onMkDir} className="grey1-bg white-font">
|
||||||
Create Folder
|
Create Folder
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>,
|
||||||
|
<span className="inline-block margin-t-m margin-b-m">
|
||||||
|
<button onClick={this.onClickUpload} className="green0-bg white-font">
|
||||||
|
Upload Files
|
||||||
|
</button>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
onChange={this.addUploadFile}
|
onChange={this.addUploadFile}
|
||||||
multiple={true}
|
multiple={true}
|
||||||
value={this.props.uploadValue}
|
value={this.props.uploadValue}
|
||||||
ref={this.assignInput}
|
ref={this.assignInput}
|
||||||
className="black0-font hidden margin-m"
|
className="black0-font hidden"
|
||||||
/>
|
/>
|
||||||
<span className="margin-s">-</span>
|
</span>,
|
||||||
<button
|
];
|
||||||
onClick={this.showPane}
|
|
||||||
className="grey1-bg white-font margin-m"
|
const ops = (
|
||||||
>
|
<Layouter isHorizontal={false} elements={layoutChildren}></Layouter>
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{ display: this.state.show ? "inherit" : "none" }}
|
|
||||||
className="margin-t-m"
|
|
||||||
>
|
|
||||||
<h3 className="padding-l-s grey0-font">Update Password</h3>
|
|
||||||
<input
|
|
||||||
name="old_pwd"
|
|
||||||
type="password"
|
|
||||||
onChange={this.changeOldPwd}
|
|
||||||
value={this.state.oldPwd}
|
|
||||||
className="margin-m black0-font"
|
|
||||||
placeholder="old password"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
name="new_pwd1"
|
|
||||||
type="password"
|
|
||||||
onChange={this.changeNewPwd1}
|
|
||||||
value={this.state.newPwd1}
|
|
||||||
className="margin-m black0-font"
|
|
||||||
placeholder="new password"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
name="new_pwd2"
|
|
||||||
type="password"
|
|
||||||
onChange={this.changeNewPwd2}
|
|
||||||
value={this.state.newPwd2}
|
|
||||||
className="margin-m black0-font"
|
|
||||||
placeholder="new password again"
|
|
||||||
/>
|
|
||||||
<button onClick={this.setPwd} className="grey1-bg white-font">
|
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemList = this.props.items.map((item: MetadataResp) => {
|
const itemList = this.props.items.map((item: MetadataResp) => {
|
||||||
|
@ -497,59 +411,67 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
return item.isDir ? (
|
return item.isDir ? (
|
||||||
<tr
|
<tr
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className={`${isSelected ? "white0-bg selected" : ""}`}
|
// className={`${isSelected ? "white0-bg selected" : ""}`}
|
||||||
>
|
>
|
||||||
<td className="padding-l-l" style={{ width: "3rem" }}>
|
<td>
|
||||||
<span className="dot yellow0-bg"></span>
|
<span className="dot yellow0-bg"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
className="item-name pointer"
|
className={nameCellClass}
|
||||||
onClick={() => this.gotoChild(item.name)}
|
onClick={() => this.gotoChild(item.name)}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>--</td>
|
<td className={sizeCellClass}>--</td>
|
||||||
<td>{item.modTime.slice(0, item.modTime.indexOf("T"))}</td>
|
<td className={modTimeCellClass}>
|
||||||
|
{item.modTime.slice(0, item.modTime.indexOf("T"))}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<span className="item-op">
|
||||||
<button
|
<button
|
||||||
onClick={() => this.select(item.name)}
|
onClick={() => this.select(item.name)}
|
||||||
className="white-font margin-t-m margin-b-m"
|
className={`white-font ${isSelected ? "blue0-bg" : ""}`}
|
||||||
|
style={{width: "8rem", display: "inline-block"}}
|
||||||
>
|
>
|
||||||
{isSelected ? "Unselect" : "Select"}
|
{isSelected ? "Deselect" : "Select"}
|
||||||
</button>
|
</button>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
<tr
|
<tr
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className={`${isSelected ? "white0-bg selected" : ""}`}
|
// className={`${isSelected ? "white0-bg selected" : ""}`}
|
||||||
>
|
>
|
||||||
<td className="padding-l-l" style={{ width: "3rem" }}>
|
<td>
|
||||||
<span className="dot green0-bg"></span>
|
<span className="dot green0-bg"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
className="item-name"
|
className={nameCellClass}
|
||||||
href={`/v1/fs/files?fp=${itemPath}`}
|
href={`/v1/fs/files?fp=${itemPath}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{FileSize(item.size, { round: 0 })}</td>
|
<td className={sizeCellClass}>{FileSize(item.size, { round: 0 })}</td>
|
||||||
<td>{item.modTime.slice(0, item.modTime.indexOf("T"))}</td>
|
<td className={modTimeCellClass}>
|
||||||
|
{item.modTime.slice(0, item.modTime.indexOf("T"))}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<span className="item-op">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.select(item.name)}
|
onClick={() => this.select(item.name)}
|
||||||
className="white-font margin-t-m margin-b-m"
|
className={`white-font ${isSelected ? "blue0-bg" : ""}`}
|
||||||
|
style={{width: "8rem", display: "inline-block"}}
|
||||||
>
|
>
|
||||||
{isSelected ? "Unselect" : "Select"}
|
{isSelected ? "Deselect" : "Select"}
|
||||||
</button>
|
</button>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
@ -561,28 +483,28 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={fileName}>
|
<tr key={fileName}>
|
||||||
<td className="padding-l-l" style={{ width: "3rem" }}>
|
<td>
|
||||||
<span className="dot blue0-bg"></span>
|
<span className="dot blue0-bg"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className="item-name pointer">{fileName}</span>
|
<div className={nameCellClass}>{fileName}</div>
|
||||||
</td>
|
<div className="item-op">
|
||||||
<td>{FileSize(uploading.uploaded, { round: 0 })}</td>
|
|
||||||
<td>{FileSize(uploading.size, { round: 0 })}</td>
|
|
||||||
<td>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => this.stopUploading(uploading.realFilePath)}
|
onClick={() => this.stopUploading(uploading.realFilePath)}
|
||||||
className="white-font margin-m"
|
className="white-font margin-r-m"
|
||||||
>
|
>
|
||||||
Stop
|
Stop
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => this.deleteUploading(uploading.realFilePath)}
|
onClick={() => this.deleteUploading(uploading.realFilePath)}
|
||||||
className="white-font margin-m"
|
className="white-font"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{FileSize(uploading.uploaded, { round: 0 })}</td>
|
||||||
|
<td>{FileSize(uploading.size, { round: 0 })}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -593,19 +515,20 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<div className="margin-l-m margin-r-m">{ops}</div>
|
<div className="margin-l-m margin-r-m">{ops}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="item-list" className="">
|
<div id="item-list">
|
||||||
<div className="margin-b-l">{breadcrumb}</div>
|
<div className="margin-b-l">{breadcrumb}</div>
|
||||||
|
|
||||||
|
{this.props.uploadings.size === 0 ? null : (
|
||||||
|
<div className="container">
|
||||||
<table>
|
<table>
|
||||||
<thead style={{ fontWeight: "bold" }}>
|
<thead style={{ fontWeight: "bold" }}>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="padding-l-l" style={{ width: "3rem" }}>
|
<td>
|
||||||
<span className="dot black-bg"></span>
|
<span className="dot black-bg"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>Uploaded</td>
|
<td className={sizeCellClass}>Uploaded</td>
|
||||||
<td>Size</td>
|
<td className={modTimeCellClass}>Size</td>
|
||||||
<td>Action</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{uploadingList}</tbody>
|
<tbody>{uploadingList}</tbody>
|
||||||
|
@ -613,22 +536,24 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td className={sizeCellClass}></td>
|
||||||
<td></td>
|
<td className={modTimeCellClass}></td>
|
||||||
<td></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="container">
|
||||||
<table>
|
<table>
|
||||||
<thead style={{ fontWeight: "bold" }}>
|
<thead style={{ fontWeight: "bold" }}>
|
||||||
<tr>
|
<tr>
|
||||||
<td className="padding-l-l" style={{ width: "3rem" }}>
|
<td>
|
||||||
<span className="dot black-bg"></span>
|
<span className="dot black-bg"></span>
|
||||||
</td>
|
</td>
|
||||||
<td>Name</td>
|
<td>Name</td>
|
||||||
<td>File Size</td>
|
<td className={sizeCellClass}>File Size</td>
|
||||||
<td>Mod Time</td>
|
<td className={modTimeCellClass}>Mod Time</td>
|
||||||
<td>Edit</td>
|
<td>Edit</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -637,14 +562,15 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td className={sizeCellClass}></td>
|
||||||
<td></td>
|
<td className={modTimeCellClass}></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { List } from "immutable";
|
import { List, Set } from "immutable";
|
||||||
|
|
||||||
import BgWorker from "../worker/upload.bg.worker";
|
import BgWorker from "../worker/upload.bg.worker";
|
||||||
import { FgWorker } from "../worker/upload.fgworker";
|
import { FgWorker } from "../worker/upload.fgworker";
|
||||||
|
@ -15,6 +15,7 @@ export interface IContext {
|
||||||
export interface ICoreState {
|
export interface ICoreState {
|
||||||
ctx: IContext;
|
ctx: IContext;
|
||||||
panel: PanelProps;
|
panel: PanelProps;
|
||||||
|
isVertical: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initWithWorker(worker: IWorker): ICoreState {
|
export function initWithWorker(worker: IWorker): ICoreState {
|
||||||
|
@ -33,21 +34,34 @@ export function init(): ICoreState {
|
||||||
return initState();
|
return initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVertical(): boolean {
|
||||||
|
return window.innerWidth <= window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
export function initState(): ICoreState {
|
export function initState(): ICoreState {
|
||||||
return {
|
return {
|
||||||
ctx: null,
|
ctx: null,
|
||||||
|
isVertical: isVertical(),
|
||||||
panel: {
|
panel: {
|
||||||
displaying: "browser",
|
displaying: "browser",
|
||||||
authPane: {
|
authPane: {
|
||||||
authed: false,
|
authed: false,
|
||||||
},
|
},
|
||||||
browser: {
|
browser: {
|
||||||
|
isVertical: isVertical(),
|
||||||
dirPath: List<string>(["."]),
|
dirPath: List<string>(["."]),
|
||||||
items: List<Item>([]),
|
items: List<Item>([]),
|
||||||
uploadings: List<UploadInfo>([]),
|
uploadings: List<UploadInfo>([]),
|
||||||
uploadValue: "",
|
uploadValue: "",
|
||||||
uploadFiles: List<File>([]),
|
uploadFiles: List<File>([]),
|
||||||
},
|
},
|
||||||
|
panes: {
|
||||||
|
displaying: "",
|
||||||
|
paneNames: Set<string>(["settings", "login"]),
|
||||||
|
login: {
|
||||||
|
authed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
38
src/client/web/src/components/layouter.tsx
Normal file
38
src/client/web/src/components/layouter.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
isHorizontal: boolean;
|
||||||
|
elements: Array<JSX.Element>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State {}
|
||||||
|
export class Layouter extends React.Component<Props, State, {}> {
|
||||||
|
constructor(p: Props) {
|
||||||
|
super(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontalLayout = (children: Array<JSX.Element>): Array<JSX.Element> => {
|
||||||
|
return children.map((child: JSX.Element, idx: number) => {
|
||||||
|
// if (idx === 0) {
|
||||||
|
// return <span key={`layout=${idx}`}>{child}</span>;
|
||||||
|
// }
|
||||||
|
return (
|
||||||
|
<span key={`layout=${idx}`}>
|
||||||
|
{child}
|
||||||
|
<span className="margin-s"></span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
verticalLayout = (children: Array<JSX.Element>): Array<JSX.Element> => {
|
||||||
|
return this.horizontalLayout(children);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const elements = this.props.isHorizontal
|
||||||
|
? this.horizontalLayout(this.props.elements)
|
||||||
|
: this.verticalLayout(this.props.elements);
|
||||||
|
return <div className="layouter">{elements}</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { ICoreState } from "./core_state";
|
import { ICoreState } from "./core_state";
|
||||||
import { IUsersClient } from "../client";
|
import { IUsersClient } from "../client";
|
||||||
import { UsersClient } from "../client/users";
|
import { UsersClient } from "../client/users";
|
||||||
|
import { Updater as PanesUpdater } from "./panes";
|
||||||
|
import { Updater as BrowserUpdater } from "./browser";
|
||||||
|
import { Layouter } from "./layouter";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
authed: boolean;
|
authed: boolean;
|
||||||
|
@ -90,14 +94,29 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
login = () => {
|
login = () => {
|
||||||
Updater.login(this.state.user, this.state.pwd).then((ok: boolean) => {
|
Updater.login(this.state.user, this.state.pwd)
|
||||||
|
.then((ok: boolean) => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.update(Updater.setAuthPane);
|
this.update(Updater.setAuthPane);
|
||||||
this.setState({ user: "", pwd: "" });
|
this.setState({ user: "", pwd: "" });
|
||||||
|
// close all the panes
|
||||||
|
PanesUpdater.displayPane("");
|
||||||
|
this.update(PanesUpdater.updateState);
|
||||||
|
|
||||||
|
// refresh
|
||||||
|
return BrowserUpdater.setItems(
|
||||||
|
List<string>(["."])
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.setState({ user: "", pwd: "" });
|
this.setState({ user: "", pwd: "" });
|
||||||
alert("Failed to login.");
|
alert("Failed to login.");
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return BrowserUpdater.refreshUploadings();
|
||||||
|
})
|
||||||
|
.then((_: boolean) => {
|
||||||
|
this.update(BrowserUpdater.setBrowser);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,30 +131,38 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
const elements: Array<JSX.Element> = [
|
||||||
<span>
|
|
||||||
<span style={{ display: this.props.authed ? "none" : "inherit" }}>
|
|
||||||
<input
|
<input
|
||||||
name="user"
|
name="user"
|
||||||
type="text"
|
type="text"
|
||||||
onChange={this.changeUser}
|
onChange={this.changeUser}
|
||||||
value={this.state.user}
|
value={this.state.user}
|
||||||
className="margin-r-m black0-font"
|
className="black0-font margin-t-m margin-b-m"
|
||||||
style={{ width: "6rem" }}
|
// style={{ width: "80%" }}
|
||||||
placeholder="user name"
|
placeholder="user name"
|
||||||
/>
|
/>,
|
||||||
<input
|
<input
|
||||||
name="pwd"
|
name="pwd"
|
||||||
type="password"
|
type="password"
|
||||||
onChange={this.changePwd}
|
onChange={this.changePwd}
|
||||||
value={this.state.pwd}
|
value={this.state.pwd}
|
||||||
className="margin-r-m black0-font"
|
className="black0-font margin-t-m margin-b-m"
|
||||||
style={{ width: "6rem" }}
|
// style={{ width: "80%" }}
|
||||||
placeholder="password"
|
placeholder="password"
|
||||||
/>
|
/>,
|
||||||
<button onClick={this.login} className="green0-bg white-font">
|
<button
|
||||||
|
onClick={this.login}
|
||||||
|
className="green0-bg white-font margin-t-m margin-b-m"
|
||||||
|
>
|
||||||
Log in
|
Log in
|
||||||
</button>
|
</button>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<span style={{ display: this.props.authed ? "none" : "inherit" }}>
|
||||||
|
<h5 className="grey0-font">Login</h5>
|
||||||
|
<Layouter isHorizontal={false} elements={elements} />
|
||||||
</span>
|
</span>
|
||||||
<span style={{ display: this.props.authed ? "inherit" : "none" }}>
|
<span style={{ display: this.props.authed ? "inherit" : "none" }}>
|
||||||
<button
|
<button
|
132
src/client/web/src/components/pane_settings.tsx
Normal file
132
src/client/web/src/components/pane_settings.tsx
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { ICoreState } from "./core_state";
|
||||||
|
import { IUsersClient } from "../client";
|
||||||
|
import { AuthPane, Props as LoginProps } from "./pane_login";
|
||||||
|
import { Layouter } from "./layouter";
|
||||||
|
import { UsersClient } from "../client/users";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
login: LoginProps;
|
||||||
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Updater {
|
||||||
|
private static props: Props;
|
||||||
|
private static usersClient: IUsersClient;
|
||||||
|
|
||||||
|
static init = (props: Props) => (Updater.props = { ...props });
|
||||||
|
static setClient(usersClient: IUsersClient) {
|
||||||
|
Updater.usersClient = usersClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
static setPwd = async (oldPwd: string, newPwd: string): Promise<boolean> => {
|
||||||
|
const resp = await Updater.usersClient.setPwd(oldPwd, newPwd);
|
||||||
|
return resp.status === 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
static updateState = (prevState: ICoreState): ICoreState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
panel: { ...prevState.panel, ...Updater.props },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
oldPwd: string;
|
||||||
|
newPwd1: string;
|
||||||
|
newPwd2: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaneSettings extends React.Component<Props, State, {}> {
|
||||||
|
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
|
changeOldPwd = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ oldPwd: ev.target.value });
|
||||||
|
};
|
||||||
|
changeNewPwd1 = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ newPwd1: ev.target.value });
|
||||||
|
};
|
||||||
|
changeNewPwd2 = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ newPwd2: ev.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(p: Props) {
|
||||||
|
super(p);
|
||||||
|
Updater.init(p);
|
||||||
|
Updater.setClient(new UsersClient(""));
|
||||||
|
this.update = p.update;
|
||||||
|
this.state = {
|
||||||
|
oldPwd: "",
|
||||||
|
newPwd1: "",
|
||||||
|
newPwd2: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setPwd = () => {
|
||||||
|
if (this.state.newPwd1 !== this.state.newPwd2) {
|
||||||
|
alert("new passwords are not same");
|
||||||
|
} else if (this.state.newPwd1 == "") {
|
||||||
|
alert("new passwords can not be empty");
|
||||||
|
} else if (this.state.oldPwd == this.state.newPwd1) {
|
||||||
|
alert("old and new passwords are same");
|
||||||
|
} else {
|
||||||
|
Updater.setPwd(this.state.oldPwd, this.state.newPwd1).then(
|
||||||
|
(ok: boolean) => {
|
||||||
|
if (ok) {
|
||||||
|
alert("Password is updated");
|
||||||
|
} else {
|
||||||
|
alert("fail to update password");
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
oldPwd: "",
|
||||||
|
newPwd1: "",
|
||||||
|
newPwd2: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const inputs: Array<JSX.Element> = [
|
||||||
|
<input
|
||||||
|
name="old_pwd"
|
||||||
|
type="password"
|
||||||
|
onChange={this.changeOldPwd}
|
||||||
|
value={this.state.oldPwd}
|
||||||
|
className="black0-font margin-t-m margin-b-m"
|
||||||
|
placeholder="old password"
|
||||||
|
/>,
|
||||||
|
<input
|
||||||
|
name="new_pwd1"
|
||||||
|
type="password"
|
||||||
|
onChange={this.changeNewPwd1}
|
||||||
|
value={this.state.newPwd1}
|
||||||
|
className="black0-font margin-t-m margin-b-m"
|
||||||
|
placeholder="new password"
|
||||||
|
/>,
|
||||||
|
<input
|
||||||
|
name="new_pwd2"
|
||||||
|
type="password"
|
||||||
|
onChange={this.changeNewPwd2}
|
||||||
|
value={this.state.newPwd2}
|
||||||
|
className="black0-font margin-t-m margin-b-m"
|
||||||
|
placeholder="new password again"
|
||||||
|
/>,
|
||||||
|
<button onClick={this.setPwd} className="grey1-bg white-font">
|
||||||
|
Update
|
||||||
|
</button>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="grey0-font">Update Password</h5>
|
||||||
|
<Layouter isHorizontal={false} elements={inputs}></Layouter>
|
||||||
|
<div className="hr"></div>
|
||||||
|
|
||||||
|
<AuthPane authed={this.props.login.authed} update={this.update} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,14 @@ import * as React from "react";
|
||||||
|
|
||||||
import { ICoreState } from "./core_state";
|
import { ICoreState } from "./core_state";
|
||||||
import { Browser, Props as BrowserProps } from "./browser";
|
import { Browser, Props as BrowserProps } from "./browser";
|
||||||
import { AuthPane, Props as AuthPaneProps } from "./auth_pane";
|
import { AuthPane, Props as AuthPaneProps } from "./pane_login";
|
||||||
|
import { Panes, Props as PanesProps, Updater as PanesUpdater } from "./panes";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
displaying: string;
|
displaying: string;
|
||||||
browser: BrowserProps;
|
browser: BrowserProps;
|
||||||
authPane: AuthPaneProps;
|
authPane: AuthPaneProps;
|
||||||
|
panes: PanesProps;
|
||||||
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,26 +35,45 @@ export class Panel extends React.Component<Props, State, {}> {
|
||||||
this.update = p.update;
|
this.update = p.update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showSettings = () => {
|
||||||
|
PanesUpdater.displayPane("settings");
|
||||||
|
this.update(PanesUpdater.updateState);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="theme-white desktop">
|
<div className="theme-white desktop">
|
||||||
<div id="bg" className="bg bg-img font-m">
|
<div id="bg" className="bg bg-img font-m">
|
||||||
|
<Panes
|
||||||
|
displaying={this.props.panes.displaying}
|
||||||
|
paneNames={this.props.panes.paneNames}
|
||||||
|
login={this.props.authPane}
|
||||||
|
update={this.update}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="top-bar"
|
id="top-bar"
|
||||||
className="top-bar cyan1-font padding-t-m padding-b-m padding-l-l padding-r-l"
|
className="top-bar cyan1-font padding-t-m padding-b-m padding-l-l padding-r-l"
|
||||||
>
|
>
|
||||||
<div className="flex-2col-parent">
|
<div className="flex-2col-parent">
|
||||||
<a href="https://github.com/ihexxa/quickshare" className="flex-13col h5">Quickshare</a>
|
<a
|
||||||
|
href="https://github.com/ihexxa/quickshare"
|
||||||
|
className="flex-13col h5"
|
||||||
|
>
|
||||||
|
Quickshare
|
||||||
|
</a>
|
||||||
<span className="flex-23col text-right">
|
<span className="flex-23col text-right">
|
||||||
<AuthPane
|
<button
|
||||||
authed={this.props.authPane.authed}
|
onClick={this.showSettings}
|
||||||
update={this.update}
|
className="grey1-bg white-font margin-r-m"
|
||||||
/>
|
>
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="container-center">
|
<div className="container-center">
|
||||||
<Browser
|
<Browser
|
||||||
dirPath={this.props.browser.dirPath}
|
dirPath={this.props.browser.dirPath}
|
||||||
items={this.props.browser.items}
|
items={this.props.browser.items}
|
||||||
|
@ -60,8 +81,13 @@ export class Panel extends React.Component<Props, State, {}> {
|
||||||
update={this.update}
|
update={this.update}
|
||||||
uploadFiles={this.props.browser.uploadFiles}
|
uploadFiles={this.props.browser.uploadFiles}
|
||||||
uploadValue={this.props.browser.uploadValue}
|
uploadValue={this.props.browser.uploadValue}
|
||||||
|
isVertical={this.props.browser.isVertical}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="container-center black0-font tail margin-t-xl margin-b-xl">
|
||||||
|
<a href="https://github.com/ihexxa/quickshare">Quickshare</a> -
|
||||||
|
sharing in simple way.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
101
src/client/web/src/components/panes.tsx
Normal file
101
src/client/web/src/components/panes.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { Set, Map } from "immutable";
|
||||||
|
|
||||||
|
import { ICoreState } from "./core_state";
|
||||||
|
import { PaneSettings } from "./pane_settings";
|
||||||
|
import { AuthPane, Props as AuthPaneProps } from "./pane_login";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
displaying: string;
|
||||||
|
paneNames: Set<string>;
|
||||||
|
login: AuthPaneProps;
|
||||||
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Updater {
|
||||||
|
private static props: Props;
|
||||||
|
|
||||||
|
static init = (props: Props) => (Updater.props = { ...props });
|
||||||
|
|
||||||
|
static displayPane = (paneName: string) => {
|
||||||
|
if (paneName === "") {
|
||||||
|
// hide all panes
|
||||||
|
Updater.props.displaying = "";
|
||||||
|
} else {
|
||||||
|
const pane = Updater.props.paneNames.get(paneName);
|
||||||
|
if (pane != null) {
|
||||||
|
Updater.props.displaying = paneName;
|
||||||
|
} else {
|
||||||
|
alert(`dialgos: pane (${paneName}) not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static updateState = (prevState: ICoreState): ICoreState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
panel: {
|
||||||
|
...prevState.panel,
|
||||||
|
panes: { ...prevState.panel.panes, ...Updater.props },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State {}
|
||||||
|
export class Panes extends React.Component<Props, State, {}> {
|
||||||
|
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
|
constructor(p: Props) {
|
||||||
|
super(p);
|
||||||
|
Updater.init(p);
|
||||||
|
this.update = p.update;
|
||||||
|
}
|
||||||
|
|
||||||
|
closePane = () => {
|
||||||
|
if (this.props.displaying !== "login") {
|
||||||
|
Updater.displayPane("");
|
||||||
|
this.update(Updater.updateState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let displaying = this.props.displaying;
|
||||||
|
if (!this.props.login.authed) {
|
||||||
|
// TODO: use constant instead
|
||||||
|
displaying = "login";
|
||||||
|
}
|
||||||
|
|
||||||
|
const panesMap: Map<string, JSX.Element> = Map({
|
||||||
|
settings: <PaneSettings login={this.props.login} update={this.update} />,
|
||||||
|
login: <AuthPane authed={this.props.login.authed} update={this.update} />,
|
||||||
|
});
|
||||||
|
|
||||||
|
const panes = panesMap.keySeq().map(
|
||||||
|
(paneName: string): JSX.Element => {
|
||||||
|
const isDisplay = displaying === paneName ? "" : "hidden";
|
||||||
|
return (
|
||||||
|
<div key={paneName} className={`${isDisplay}`}>
|
||||||
|
{panesMap.get(paneName)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const btnClass = displaying === "login" ? "hidden" : "";
|
||||||
|
return (
|
||||||
|
<div id="panes" className={displaying === "" ? "hidden" : ""}>
|
||||||
|
<div className="container">
|
||||||
|
<div className="padding-l">
|
||||||
|
<div className={btnClass}>
|
||||||
|
<button onClick={this.closePane} className="black0-bg white-font">
|
||||||
|
Return
|
||||||
|
</button>
|
||||||
|
<div className="hr"></div>
|
||||||
|
</div>
|
||||||
|
{panes}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,12 +26,15 @@ export class StateMgr extends React.Component<Props, State, {}> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<Panel
|
<Panel
|
||||||
authPane = {this.state.panel.authPane}
|
authPane = {this.state.panel.authPane}
|
||||||
displaying={this.state.panel.displaying}
|
displaying={this.state.panel.displaying}
|
||||||
update={this.update}
|
update={this.update}
|
||||||
browser={this.state.panel.browser}
|
browser={this.state.panel.browser}
|
||||||
|
panes={this.state.panel.panes}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,54 +9,123 @@
|
||||||
|
|
||||||
#top-bar {
|
#top-bar {
|
||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
#container-center {
|
.container-center {
|
||||||
margin: 5rem auto auto auto;
|
margin: 2rem auto auto auto;
|
||||||
width: 96%;
|
width: 96%;
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layouter .vcell {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.5rem 2rem;
|
||||||
|
border-bottom: solid 1px #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panes {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
z-index: 100;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panes .container {
|
||||||
|
max-width: 960px;
|
||||||
|
width: 96%;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 101;
|
||||||
|
text-align: left;
|
||||||
|
margin: 3rem auto 8rem auto;
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panes .return-btn {
|
||||||
|
position: fixed;
|
||||||
|
max-width: 960px;
|
||||||
|
width: 96%;
|
||||||
|
margin: auto;
|
||||||
|
z-index: 101;
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#op-bar {
|
#op-bar {
|
||||||
width: 96%;
|
/* width: 96%; */
|
||||||
max-width: 960px;
|
/* max-width: 960px; */
|
||||||
|
|
||||||
position: fixed;
|
/* position: fixed;
|
||||||
top: 6rem;
|
top: 6rem;
|
||||||
right: auto;
|
right: auto;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
left: auto;
|
left: auto; */
|
||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
border-radius: 0.6rem;
|
border-radius: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#item-list {
|
#item-list {
|
||||||
margin-top: 12rem;
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#item-list .container {
|
||||||
|
width: 100%;
|
||||||
|
color: #34495e;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 5rem;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#item-list table {
|
#item-list table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #34495e;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
line-height: 4rem;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 0.8rem;
|
|
||||||
margin-bottom: 8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#item-list tr {
|
#item-list tr {
|
||||||
border-top: solid 1px transparent;
|
border-top: solid 1px transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#item-list .dot {
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#item-list .item-name-cell {
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#item-list .item-name-vertical {
|
||||||
|
width: 14rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#item-list .item-name-horizontal {
|
||||||
|
width: 48rem;
|
||||||
|
}
|
||||||
|
|
||||||
#item-list .item-name {
|
#item-list .item-name {
|
||||||
display: block;
|
|
||||||
max-width: 8rem;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#item-list tr button {
|
#item-list .item-op {
|
||||||
|
line-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr button {
|
||||||
background-color: #95a5a6;
|
background-color: #95a5a6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +161,7 @@
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
font-size: 2.0rem;
|
font-size: 2rem;
|
||||||
line-height: 3.9rem;
|
line-height: 3.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,3 +565,18 @@ div.hr {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
.inline-block {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hr {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #95a5a6;
|
||||||
|
margin: 1rem auto 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tail {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
-webkit-backdrop-filter: blur( 9.5px );
|
-webkit-backdrop-filter: blur( 9.5px );
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-white div.hr {
|
/* .theme-white div.hr {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.theme-white .op-bar {
|
.theme-white .op-bar {
|
||||||
background: rgba( 255, 255, 255, 0.9 );
|
background: rgba( 255, 255, 255, 0.9 );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue