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">
[WORKING IN PROGRESS!!!] Quickshare
Quickshare
</h1>
<p align="center">
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).
## Features
## Main Features
- Upload and download in browser, no client
- Share files among desktop and mobile devices
- Portable software
- Add files from local
- Add download limit for resource
- Download from interrupted point
- Uploading and downloading in browser without client
- Be compatible with Linux, Mac and Windows
- Sharing files among different devices (desktop & mobile)
- Stopping and resuming uploading/downloading
## Installation
2 steps are needed to start a quickshare: unzip it and start it.
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))
Coming soon.
### FAQ
Please refer [FAQ document](./docs/FAQ_en-us.md)
### Configuration
Please refer [Configuration document](./docs/CONFIG_en-us.md)
### Contribution
Will add it soon...
TODO

View file

@ -1,7 +1,7 @@
import { mock, instance } from "ts-mockito";
import { initWithWorker } from "../core_state";
import { Updater } from "../auth_pane";
import { Updater } from "../pane_login";
import { MockUsersClient } from "../../client/users_mock";
import { Response } from "../../client";
import { MockWorker } from "../../worker/interface";

View file

@ -3,6 +3,7 @@ import * as ReactDOM from "react-dom";
import { List, Map } from "immutable";
import FileSize from "filesize";
import { Layouter } from "./layouter";
import { ICoreState } from "./core_state";
import {
IUsersClient,
@ -14,7 +15,6 @@ import { FilesClient } from "../client/files";
import { UsersClient } from "../client/users";
import { UploadMgr } from "../worker/upload_mgr";
import { UploadEntry } from "../worker/interface";
// import { FileUploader } from "../worker/uploader";
export const uploadCheckCycle = 1000;
@ -34,6 +34,8 @@ export interface Props {
uploadFiles: List<File>;
uploadValue: string;
isVertical: boolean;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -156,11 +158,6 @@ export class Updater {
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) => {
for (let i = 0; i < len; i++) {
// do not wait for the promise
@ -179,11 +176,6 @@ export interface State {
inputValue: string;
selectedSrc: string;
selectedItems: Map<string, boolean>;
show: boolean;
oldPwd: string;
newPwd1: string;
newPwd2: string;
}
export class Browser extends React.Component<Props, State, {}> {
@ -201,10 +193,6 @@ export class Browser extends React.Component<Props, State, {}> {
inputValue: "",
selectedSrc: "",
selectedItems: Map<string, boolean>(),
show: false,
oldPwd: "",
newPwd1: "",
newPwd2: "",
};
this.uploadInput = undefined;
@ -227,18 +215,9 @@ export class Browser extends React.Component<Props, State, {}> {
});
}
showPane = () => {
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 });
};
// showPane = () => {
// this.setState({ show: !this.state.show });
// };
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
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() {
const breadcrumb = this.props.dirPath.map(
(pathPart: string, key: number) => {
@ -395,96 +349,56 @@ export class Browser extends React.Component<Props, State, {}> {
}
);
const nameCellClass = `item-name item-name-${
this.props.isVertical ? "vertical" : "horizontal"
} pointer`;
const sizeCellClass = this.props.isVertical ? `hidden margin-s` : ``;
const modTimeCellClass = this.props.isVertical ? `hidden margin-s` : ``;
const layoutChildren = [
<button
type="button"
onClick={() => this.delete()}
className="red0-bg white-font margin-t-m margin-b-m"
>
Delete Selected
</button>,
<button
type="button"
onClick={() => this.moveHere()}
className="grey1-bg white-font margin-t-m margin-b-m"
>
Paste
</button>,
<span className="inline-block margin-t-m margin-b-m">
<input
type="text"
onChange={this.onInputChange}
value={this.state.inputValue}
className="black0-font margin-r-m"
placeholder="folder name"
/>
<button onClick={this.onMkDir} className="grey1-bg white-font">
Create Folder
</button>
</span>,
<span className="inline-block margin-t-m margin-b-m">
<button onClick={this.onClickUpload} className="green0-bg white-font">
Upload Files
</button>
<input
type="file"
onChange={this.addUploadFile}
multiple={true}
value={this.props.uploadValue}
ref={this.assignInput}
className="black0-font hidden"
/>
</span>,
];
const ops = (
<div>
<div className="grey0-font">
<button
type="button"
onClick={() => this.delete()}
className="red0-bg white-font margin-m"
>
Delete Selected
</button>
<span className="margin-s">-</span>
<button
type="button"
onClick={() => this.moveHere()}
className="grey1-bg white-font margin-m"
>
Paste
</button>
<span className="margin-s">-</span>
<button
onClick={this.onClickUpload}
className="green0-bg white-font margin-m"
>
Upload Files
</button>
<span className="margin-s">-</span>
<span className="margin-m">
<input
type="text"
onChange={this.onInputChange}
value={this.state.inputValue}
className="margin-r-m black0-font"
placeholder="folder name"
/>
<button onClick={this.onMkDir} className="grey1-bg white-font">
Create Folder
</button>
</span>
<input
type="file"
onChange={this.addUploadFile}
multiple={true}
value={this.props.uploadValue}
ref={this.assignInput}
className="black0-font hidden margin-m"
/>
<span className="margin-s">-</span>
<button
onClick={this.showPane}
className="grey1-bg white-font margin-m"
>
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>
<Layouter isHorizontal={false} elements={layoutChildren}></Layouter>
);
const itemList = this.props.items.map((item: MetadataResp) => {
@ -497,59 +411,67 @@ export class Browser extends React.Component<Props, State, {}> {
return item.isDir ? (
<tr
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>
</td>
<td>
<span
className="item-name pointer"
className={nameCellClass}
onClick={() => this.gotoChild(item.name)}
>
{item.name}
</span>
</td>
<td>--</td>
<td>{item.modTime.slice(0, item.modTime.indexOf("T"))}</td>
<td className={sizeCellClass}>--</td>
<td className={modTimeCellClass}>
{item.modTime.slice(0, item.modTime.indexOf("T"))}
</td>
<td>
<button
onClick={() => this.select(item.name)}
className="white-font margin-t-m margin-b-m"
>
{isSelected ? "Unselect" : "Select"}
</button>
<span className="item-op">
<button
onClick={() => this.select(item.name)}
className={`white-font ${isSelected ? "blue0-bg" : ""}`}
style={{width: "8rem", display: "inline-block"}}
>
{isSelected ? "Deselect" : "Select"}
</button>
</span>
</td>
</tr>
) : (
<tr
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>
</td>
<td>
<a
className="item-name"
className={nameCellClass}
href={`/v1/fs/files?fp=${itemPath}`}
target="_blank"
>
{item.name}
</a>
</td>
<td>{FileSize(item.size, { round: 0 })}</td>
<td>{item.modTime.slice(0, item.modTime.indexOf("T"))}</td>
<td className={sizeCellClass}>{FileSize(item.size, { round: 0 })}</td>
<td className={modTimeCellClass}>
{item.modTime.slice(0, item.modTime.indexOf("T"))}
</td>
<td>
<button
type="button"
onClick={() => this.select(item.name)}
className="white-font margin-t-m margin-b-m"
>
{isSelected ? "Unselect" : "Select"}
</button>
<span className="item-op">
<button
type="button"
onClick={() => this.select(item.name)}
className={`white-font ${isSelected ? "blue0-bg" : ""}`}
style={{width: "8rem", display: "inline-block"}}
>
{isSelected ? "Deselect" : "Select"}
</button>
</span>
</td>
</tr>
);
@ -561,28 +483,28 @@ export class Browser extends React.Component<Props, State, {}> {
return (
<tr key={fileName}>
<td className="padding-l-l" style={{ width: "3rem" }}>
<td>
<span className="dot blue0-bg"></span>
</td>
<td>
<span className="item-name pointer">{fileName}</span>
<div className={nameCellClass}>{fileName}</div>
<div className="item-op">
<button
onClick={() => this.stopUploading(uploading.realFilePath)}
className="white-font margin-r-m"
>
Stop
</button>
<button
onClick={() => this.deleteUploading(uploading.realFilePath)}
className="white-font"
>
Delete
</button>
</div>
</td>
<td>{FileSize(uploading.uploaded, { round: 0 })}</td>
<td>{FileSize(uploading.size, { round: 0 })}</td>
<td>
<button
onClick={() => this.stopUploading(uploading.realFilePath)}
className="white-font margin-m"
>
Stop
</button>
<button
onClick={() => this.deleteUploading(uploading.realFilePath)}
className="white-font margin-m"
>
Delete
</button>
</td>
</tr>
);
});
@ -593,56 +515,60 @@ export class Browser extends React.Component<Props, State, {}> {
<div className="margin-l-m margin-r-m">{ops}</div>
</div>
<div id="item-list" className="">
<div id="item-list">
<div className="margin-b-l">{breadcrumb}</div>
<table>
<thead style={{ fontWeight: "bold" }}>
<tr>
<td className="padding-l-l" style={{ width: "3rem" }}>
<span className="dot black-bg"></span>
</td>
<td>Name</td>
<td>Uploaded</td>
<td>Size</td>
<td>Action</td>
</tr>
</thead>
<tbody>{uploadingList}</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tfoot>
</table>
{this.props.uploadings.size === 0 ? null : (
<div className="container">
<table>
<thead style={{ fontWeight: "bold" }}>
<tr>
<td>
<span className="dot black-bg"></span>
</td>
<td>Name</td>
<td className={sizeCellClass}>Uploaded</td>
<td className={modTimeCellClass}>Size</td>
</tr>
</thead>
<tbody>{uploadingList}</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td className={sizeCellClass}></td>
<td className={modTimeCellClass}></td>
</tr>
</tfoot>
</table>
</div>
)}
<table>
<thead style={{ fontWeight: "bold" }}>
<tr>
<td className="padding-l-l" style={{ width: "3rem" }}>
<span className="dot black-bg"></span>
</td>
<td>Name</td>
<td>File Size</td>
<td>Mod Time</td>
<td>Edit</td>
</tr>
</thead>
<tbody>{itemList}</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tfoot>
</table>
<div className="container">
<table>
<thead style={{ fontWeight: "bold" }}>
<tr>
<td>
<span className="dot black-bg"></span>
</td>
<td>Name</td>
<td className={sizeCellClass}>File Size</td>
<td className={modTimeCellClass}>Mod Time</td>
<td>Edit</td>
</tr>
</thead>
<tbody>{itemList}</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td className={sizeCellClass}></td>
<td className={modTimeCellClass}></td>
<td></td>
</tr>
</tfoot>
</table>
</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 { FgWorker } from "../worker/upload.fgworker";
@ -15,6 +15,7 @@ export interface IContext {
export interface ICoreState {
ctx: IContext;
panel: PanelProps;
isVertical: boolean;
}
export function initWithWorker(worker: IWorker): ICoreState {
@ -33,21 +34,34 @@ export function init(): ICoreState {
return initState();
}
export function isVertical(): boolean {
return window.innerWidth <= window.innerHeight;
}
export function initState(): ICoreState {
return {
ctx: null,
isVertical: isVertical(),
panel: {
displaying: "browser",
authPane: {
authed: false,
},
browser: {
isVertical: isVertical(),
dirPath: List<string>(["."]),
items: List<Item>([]),
uploadings: List<UploadInfo>([]),
uploadValue: "",
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 { List } from "immutable";
import { ICoreState } from "./core_state";
import { IUsersClient } from "../client";
import { UsersClient } from "../client/users";
import { Updater as PanesUpdater } from "./panes";
import { Updater as BrowserUpdater } from "./browser";
import { Layouter } from "./layouter";
export interface Props {
authed: boolean;
@ -90,15 +94,30 @@ export class AuthPane extends React.Component<Props, State, {}> {
};
login = () => {
Updater.login(this.state.user, this.state.pwd).then((ok: boolean) => {
if (ok) {
this.update(Updater.setAuthPane);
this.setState({ user: "", pwd: "" });
} else {
this.setState({ user: "", pwd: "" });
alert("Failed to login.");
}
});
Updater.login(this.state.user, this.state.pwd)
.then((ok: boolean) => {
if (ok) {
this.update(Updater.setAuthPane);
this.setState({ user: "", pwd: "" });
// close all the panes
PanesUpdater.displayPane("");
this.update(PanesUpdater.updateState);
// refresh
return BrowserUpdater.setItems(
List<string>(["."])
);
} else {
this.setState({ user: "", pwd: "" });
alert("Failed to login.");
}
})
.then(() => {
return BrowserUpdater.refreshUploadings();
})
.then((_: boolean) => {
this.update(BrowserUpdater.setBrowser);
});
};
logout = () => {
@ -112,30 +131,38 @@ export class AuthPane extends React.Component<Props, State, {}> {
};
render() {
const elements: Array<JSX.Element> = [
<input
name="user"
type="text"
onChange={this.changeUser}
value={this.state.user}
className="black0-font margin-t-m margin-b-m"
// style={{ width: "80%" }}
placeholder="user name"
/>,
<input
name="pwd"
type="password"
onChange={this.changePwd}
value={this.state.pwd}
className="black0-font margin-t-m margin-b-m"
// style={{ width: "80%" }}
placeholder="password"
/>,
<button
onClick={this.login}
className="green0-bg white-font margin-t-m margin-b-m"
>
Log in
</button>,
];
return (
<span>
<span style={{ display: this.props.authed ? "none" : "inherit" }}>
<input
name="user"
type="text"
onChange={this.changeUser}
value={this.state.user}
className="margin-r-m black0-font"
style={{ width: "6rem" }}
placeholder="user name"
/>
<input
name="pwd"
type="password"
onChange={this.changePwd}
value={this.state.pwd}
className="margin-r-m black0-font"
style={{ width: "6rem" }}
placeholder="password"
/>
<button onClick={this.login} className="green0-bg white-font">
Log in
</button>
<h5 className="grey0-font">Login</h5>
<Layouter isHorizontal={false} elements={elements} />
</span>
<span style={{ display: this.props.authed ? "inherit" : "none" }}>
<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 { 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 {
displaying: string;
browser: BrowserProps;
authPane: AuthPaneProps;
panes: PanesProps;
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}
@ -33,26 +35,45 @@ export class Panel extends React.Component<Props, State, {}> {
this.update = p.update;
}
showSettings = () => {
PanesUpdater.displayPane("settings");
this.update(PanesUpdater.updateState);
};
render() {
return (
<div className="theme-white desktop">
<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
id="top-bar"
className="top-bar cyan1-font padding-t-m padding-b-m padding-l-l padding-r-l"
>
<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">
<AuthPane
authed={this.props.authPane.authed}
update={this.update}
/>
<button
onClick={this.showSettings}
className="grey1-bg white-font margin-r-m"
>
Settings
</button>
</span>
</div>
</div>
<div id="container-center">
<div className="container-center">
<Browser
dirPath={this.props.browser.dirPath}
items={this.props.browser.items}
@ -60,8 +81,13 @@ export class Panel extends React.Component<Props, State, {}> {
update={this.update}
uploadFiles={this.props.browser.uploadFiles}
uploadValue={this.props.browser.uploadValue}
isVertical={this.props.browser.isVertical}
/>
</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>
);

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() {
return (
<Panel
authPane = {this.state.panel.authPane}
displaying={this.state.panel.displaying}
update={this.update}
browser={this.state.panel.browser}
/>
<div>
<Panel
authPane = {this.state.panel.authPane}
displaying={this.state.panel.displaying}
update={this.update}
browser={this.state.panel.browser}
panes={this.state.panel.panes}
/>
</div>
);
}
}

View file

@ -9,58 +9,127 @@
#top-bar {
line-height: 3rem;
z-index: 10;
}
#container-center {
margin: 5rem auto auto auto;
.container-center {
margin: 2rem auto auto auto;
width: 96%;
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 {
width: 96%;
max-width: 960px;
/* width: 96%; */
/* max-width: 960px; */
position: fixed;
/* position: fixed;
top: 6rem;
right: auto;
bottom: auto;
left: auto;
left: auto; */
line-height: 3rem;
border-radius: 0.6rem;
}
#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 {
width: 100%;
color: #34495e;
font-size: 1.4rem;
line-height: 4rem;
background-color: white;
border-radius: 0.8rem;
margin-bottom: 8rem;
}
#item-list tr {
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 {
display: block;
max-width: 8rem;
overflow: hidden;
white-space: nowrap;
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;
}
#item-list tr.selected button {
#item-list tr.selected button {
background-color: #e74c3c;
}
@ -92,7 +161,7 @@
bottom: auto;
left: 0;
font-size: 2.0rem;
font-size: 2rem;
line-height: 3.9rem;
}
@ -496,3 +565,18 @@ div.hr {
text-align: left;
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 );
}
.theme-white div.hr {
/* .theme-white div.hr {
background-color: white;
}
} */
.theme-white .op-bar {
background: rgba( 255, 255, 255, 0.9 );