Use strict TypeScript, enable TSLint, update all types to work in strict mode.

This commit is contained in:
Jason Miller
2018-03-29 15:42:07 -04:00
parent 9977e5b8c6
commit 5e6500d196
16 changed files with 396 additions and 265 deletions

View File

@@ -1,5 +1,5 @@
import { h, Component } from 'preact';
import { updater, toggle, When } from '../../lib/util';
import { When, bind } from '../../lib/util';
import Fab from '../fab';
import Header from '../header';
// import Drawer from 'async!../drawer';
@@ -8,105 +8,113 @@ import Home from '../home';
import * as style from './style.scss';
type Props = {
url?: String
}
url?: string
};
type FileObj = {
id: any,
data: any,
error: Error | DOMError | String,
file: File,
loading: Boolean
export type FileObj = {
id: number,
data?: string,
error?: Error | DOMError | String,
file: File,
loading: boolean
};
type State = {
showDrawer: Boolean,
showFab: Boolean,
files: FileObj[]
showDrawer: boolean,
showFab: boolean,
files: FileObj[]
};
let counter = 0;
export default class App extends Component<Props, State> {
state: State = {
showDrawer: false,
showFab: true,
files: []
};
state: State = {
showDrawer: false,
showFab: true,
files: []
};
loadFile = (file: File) => {
let fileObj = {
id: ++counter,
file,
error: null,
loading: true,
data: null
};
enableDrawer = false;
this.setState({
files: [fileObj]
});
@bind
openDrawer() {
this.setState({ showDrawer: true });
}
@bind
closeDrawer() {
this.setState({ showDrawer: false });
}
@bind
toggleDrawer() {
this.setState({ showDrawer: !this.state.showDrawer });
}
let fr = new FileReader();
// fr.readAsArrayBuffer();
fr.onerror = () => {
let files = this.state.files.slice();
files.splice(0, files.indexOf(fileObj), {
...fileObj,
error: fr.error,
loading: false
});
this.setState({ files });
};
fr.onloadend = () => {
let files = this.state.files.slice();
files.splice(0, files.indexOf(fileObj), {
...fileObj,
data: fr.result,
loading: false
});
this.setState({ files });
};
fr.readAsDataURL(file);
};
@bind
openFab() {
this.setState({ showFab: true });
}
@bind
closeFab() {
this.setState({ showFab: false });
}
@bind
toggleFab() {
this.setState({ showFab: !this.state.showFab });
}
enableDrawer = false;
@bind
loadFile(file: File) {
let fileObj: FileObj = {
id: ++counter,
file,
error: undefined,
loading: true,
data: undefined
};
openDrawer = updater(this, 'showDrawer', true);
closeDrawer = updater(this, 'showDrawer', false);
toggleDrawer = updater(this, 'showDrawer', toggle);
this.setState({
files: [fileObj]
});
openFab = updater(this, 'showFab', true);
closeFab = updater(this, 'showFab', false);
toggleFab = updater(this, 'showFab', toggle);
let done = () => {
let files = this.state.files.slice();
files[files.indexOf(fileObj)] = Object.assign({}, fileObj, {
error: fr.error,
loading: false,
data: fr.result
});
this.setState({ files });
};
render({ url }, { showDrawer, showFab, files }) {
if (showDrawer) this.enableDrawer = true;
let fr = new FileReader();
fr.onerror = fr.onloadend = done;
fr.readAsDataURL(file);
}
if (showFab===true) showFab = files.length>0;
render({ url }: Props, { showDrawer, showFab, files }: State) {
if (showDrawer) this.enableDrawer = true;
return (
<div id="app" class={style.app}>
<Fab showing={showFab} />
if (showFab === true) showFab = files.length > 0;
<Header toggleDrawer={this.toggleDrawer} loadFile={this.loadFile} />
{/* Avoid loading & rendering the drawer until the first time it is shown. */}
<When value={showDrawer}>
<Drawer showing={showDrawer} openDrawer={this.openDrawer} closeDrawer={this.closeDrawer} />
</When>
return (
<div id="app" class={style.app}>
<Fab showing={showFab} />
{/*
Note: this is normally where a <Router> with auto code-splitting goes.
Since we don't seem to need one (yet?), it's omitted.
*/}
<div class={style.content}>
<Home files={files} />
</div>
<Header class={style.header} toggleDrawer={this.toggleDrawer} loadFile={this.loadFile} />
{/* This ends up in the body when prerendered, which makes it load async. */}
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</div>
);
}
{/* Avoid loading & rendering the drawer until the first time it is shown. */}
<When value={showDrawer}>
<Drawer showing={showDrawer} openDrawer={this.openDrawer} closeDrawer={this.closeDrawer} />
</When>
{/*
Note: this is normally where a <Router> with auto code-splitting goes.
Since we don't seem to need one (yet?), it's omitted.
*/}
<div class={style.content}>
<Home files={files} />
</div>
</div>
);
}
}

View File

@@ -1,51 +0,0 @@
import { h, Component } from 'preact';
import MdlDrawer from 'preact-material-components-drawer';
import 'preact-material-components/Drawer/style.css';
import List from 'preact-material-components/List';
// import 'preact-material-components/List/style.css';
import { Text } from 'preact-i18n';
import style from './style';
export default class Drawer extends Component {
state = {
rendered: false
};
setRendered = () => {
this.setState({ rendered: true });
};
render({ showing, openDrawer, closeDrawer }, { rendered }) {
if (showing && !rendered) {
setTimeout(this.setRendered, 20);
showing = false;
}
return (
<MdlDrawer open={showing} onOpen={openDrawer} onClose={closeDrawer}>
<MdlDrawer.Header class="mdc-theme--primary-bg">
<img class={style.logo} alt="logo" src="/assets/icon.png" />
</MdlDrawer.Header>
<MdlDrawer.Content class={style.list}>
<List>
<List.LinkItem href="/">
<List.ItemIcon>verified_user</List.ItemIcon>
<Text id="SIGN_IN">Sign In</Text>
</List.LinkItem>
<List.LinkItem href="/register">
<List.ItemIcon>account_circle</List.ItemIcon>
<Text id="REGISTER">Register</Text>
</List.LinkItem>
</List>
</MdlDrawer.Content>
<div class={style.bottom}>
<List.LinkItem href="/preferences">
<List.ItemIcon>settings</List.ItemIcon>
<Text id="PREFERENCES">Preferences</Text>
</List.LinkItem>
</div>
</MdlDrawer>
);
}
}

View File

@@ -0,0 +1,63 @@
import { h, Component } from 'preact';
import MdlDrawer from 'preact-material-components-drawer';
import 'preact-material-components/Drawer/style.css';
import List from 'preact-material-components/List';
// import 'preact-material-components/List/style.css';
import { Text } from 'preact-i18n';
import * as style from './style.scss';
import { bind } from '../../lib/util';
type Props = {
showing: boolean,
openDrawer(): void,
closeDrawer(): void
};
type State = {
rendered: boolean
};
export default class Drawer extends Component<Props, State> {
state: State = {
rendered: false
};
@bind
setRendered() {
this.setState({ rendered: true });
}
render({ showing, openDrawer, closeDrawer }: Props, { rendered }: State) {
if (showing && !rendered) {
setTimeout(this.setRendered, 20);
showing = false;
}
return (
<MdlDrawer open={showing} onOpen={openDrawer} onClose={closeDrawer}>
<MdlDrawer.Header class="mdc-theme--primary-bg">
<img class={style.logo} alt="logo" src="/assets/icon.png" />
</MdlDrawer.Header>
<MdlDrawer.Content>
<List>
<List.LinkItem href="/">
<List.ItemIcon>verified_user</List.ItemIcon>
<Text id="SIGN_IN">Sign In</Text>
</List.LinkItem>
<List.LinkItem href="/register">
<List.ItemIcon>account_circle</List.ItemIcon>
<Text id="REGISTER">Register</Text>
</List.LinkItem>
</List>
</MdlDrawer.Content>
<div class={style.bottom}>
<List.LinkItem href="/preferences">
<List.ItemIcon>settings</List.ItemIcon>
<Text id="PREFERENCES">Preferences</Text>
</List.LinkItem>
</div>
</MdlDrawer>
);
}
}

View File

@@ -1,4 +1,5 @@
import { h, Component } from 'preact';
import { bind } from '../../lib/util';
import Icon from 'preact-material-components/Icon';
import 'preact-material-components/Icon/style.css';
import Fab from 'preact-material-components/Fab';
@@ -6,34 +7,41 @@ import RadialProgress from 'material-radial-progress';
import * as style from './style.scss';
type Props = {
showing: boolean
showing: boolean
};
type State = {
loading: boolean
loading: boolean
};
export default class AppFab extends Component<Props, State> {
state: State = {
loading: false
};
state: State = {
loading: false
};
handleClick = () => {
console.log('TODO: Save the file to disk.');
this.setState({ loading: true });
setTimeout( () => {
this.setState({ loading: false });
}, 1000);
};
@bind
setLoading(loading: boolean) {
this.setState({ loading });
}
render({ showing }, { loading }) {
return (
<Fab ripple secondary exited={showing===false} class={style.fab} onClick={this.handleClick}>
{ loading ? (
<RadialProgress primary class={style.progress} />
) : (
<Icon>file_download</Icon>
) }
</Fab>
);
}
}
@bind
handleClick() {
console.log('TODO: Save the file to disk.');
this.setState({ loading: true });
setTimeout(() => {
this.setState({ loading: false });
}, 1000);
}
render({ showing }: Props, { loading }: State) {
return (
<Fab ripple secondary exited={showing === false} class={style.fab} onClick={this.handleClick}>
{ loading ? (
<RadialProgress primary class={style.progress} />
) : (
<Icon>file_download</Icon>
) }
</Fab>
);
}
}

View File

@@ -2,50 +2,52 @@ import { h, Component } from 'preact';
import Toolbar from 'preact-material-components/Toolbar';
import cx from 'classnames';
import * as style from './style.scss';
import { bind } from '../../lib/util';
type Props = {
toggleDrawer?(),
showHeader?(),
showFab?(),
loadFile?(File)
'class'?: string,
showHeader?: boolean,
toggleDrawer?(): void,
showFab?(): void,
loadFile(f: File): void
};
type State = {
};
type State = {};
export default class Header extends Component<Props, State> {
input: HTMLInputElement;
input?: HTMLInputElement;
setInputRef = c => {
this.input = c;
};
@bind
setInputRef(c?: Element) {
this.input = c as HTMLInputElement;
}
upload = () => {
this.input.click();
};
@bind
upload() {
this.input!.click();
}
handleFiles = () => {
let files = this.input.files;
if (files.length) {
this.props.loadFile(files[0]);
}
};
@bind
handleFiles() {
let files = this.input!.files;
if (files && files.length) {
this.props.loadFile(files[0]);
}
}
render({ toggleDrawer, showHeader, showFab }) {
return (
<Toolbar fixed class={cx(style.toolbar, 'inert', showHeader===false && style.minimal)}>
<Toolbar.Row>
<Toolbar.Title class={style.title}>
<img class={style.logo} src="/assets/icon.png" />
<Toolbar.Icon ripple onClick={this.upload}>file_upload</Toolbar.Icon>
</Toolbar.Title>
<Toolbar.Section align-end>
<Toolbar.Icon ripple onClick={toggleDrawer}>menu</Toolbar.Icon>
</Toolbar.Section>
</Toolbar.Row>
<input class={style.fileInput} ref={this.setInputRef} type="file" onChange={this.handleFiles} />
</Toolbar>
);
}
}
render({ class: c, toggleDrawer, showHeader = false, showFab }: Props) {
return (
<Toolbar fixed class={cx(c, style.toolbar, 'inert', !showHeader && style.minimal)}>
<Toolbar.Row>
<Toolbar.Title class={style.title}>
<Toolbar.Icon title="Upload" ripple onClick={this.upload}>file_upload</Toolbar.Icon>
</Toolbar.Title>
<Toolbar.Section align-end>
<Toolbar.Icon ripple onClick={toggleDrawer}>menu</Toolbar.Icon>
</Toolbar.Section>
</Toolbar.Row>
<input class={style.fileInput} ref={this.setInputRef} type="file" onChange={this.handleFiles} />
</Toolbar>
);
}
}

View File

@@ -3,34 +3,34 @@ import { h, Component } from 'preact';
// import Switch from 'preact-material-components/Switch';
// import 'preact-material-components/Switch/style.css';
import * as style from './style.scss';
import { FileObj } from '../app';
type Props = {
files: {
data: any
}[]
files: FileObj[]
};
type State = {
active: boolean
active: boolean
};
export default class Home extends Component<Props, State> {
state: State = {
active: false
};
state: State = {
active: false
};
componentDidMount() {
setTimeout( () => {
this.setState({ active: true });
});
}
componentDidMount() {
setTimeout(() => {
this.setState({ active: true });
});
}
render({ files }, { active }) {
return (
<div class={style.home+' '+(active ? style.active : '')}>
{ files && files[0] && (
<img src={files[0].data} />
) }
</div>
);
}
render({ files }: Props, { active }: State) {
return (
<div class={style.home + ' ' + (active ? style.active : '')}>
{ files && files[0] && (
<img src={files[0].data} />
) }
</div>
);
}
}