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:
Hexxa 2021-01-29 21:50:42 +08:00 committed by GitHub
parent 31e4850344
commit 1ff1e2024e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 653 additions and 331 deletions

View file

@ -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

View file

@ -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";

View file

@ -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>
); );
} }
} }

View file

@ -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,
},
},
}, },
}; };
} }

View 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>;
}
}

View file

@ -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

View 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>
);
}
}

View file

@ -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>
); );

View 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>
);
}
}

View file

@ -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>
); );
} }
} }

View file

@ -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;
}

View file

@ -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 );