mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-11 16:26:20 +00:00
Use strict TypeScript, enable TSLint, update all types to work in strict mode.
This commit is contained in:
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
18
global.d.ts
vendored
18
global.d.ts
vendored
@@ -0,0 +1,18 @@
|
||||
declare const __webpack_public_path__: string;
|
||||
|
||||
interface NodeModule {
|
||||
hot: any;
|
||||
}
|
||||
|
||||
declare namespace JSX {
|
||||
interface Element { }
|
||||
interface IntrinsicElements { div: any; }
|
||||
}
|
||||
|
||||
declare module 'preact-i18n';
|
||||
declare module 'preact-material-components-drawer';
|
||||
declare module 'material-radial-progress';
|
||||
|
||||
declare module 'classnames' {
|
||||
export default function classnames(...args: any[]): string;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
"eslintConfig": {
|
||||
"extends": "eslint-config-developit",
|
||||
"rules": {
|
||||
"indent": [
|
||||
2,
|
||||
2
|
||||
],
|
||||
"react/prefer-stateless-function": 0
|
||||
}
|
||||
},
|
||||
@@ -37,6 +41,8 @@
|
||||
"eslint": "^4.18.2",
|
||||
"eslint-config-developit": "^1.1.1",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"fork-ts-checker-notifier-webpack-plugin": "^0.4.0",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.1",
|
||||
"html-webpack-plugin": "^3.0.6",
|
||||
"if-env": "^1.0.4",
|
||||
"mini-css-extract-plugin": "^0.2.0",
|
||||
@@ -48,6 +54,9 @@
|
||||
"script-ext-html-webpack-plugin": "^2.0.0",
|
||||
"style-loader": "^0.20.3",
|
||||
"ts-loader": "^4.0.1",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-config-semistandard": "^7.0.0",
|
||||
"tslint-react": "^3.5.1",
|
||||
"typescript": "^2.7.2",
|
||||
"typescript-loader": "^1.1.3",
|
||||
"typings-for-css-modules-loader": "^1.7.0",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
63
src/components/drawer/index.tsx
Normal file
63
src/components/drawer/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
18
src/index.js
18
src/index.js
@@ -1,18 +0,0 @@
|
||||
import './style';
|
||||
import './lib/fix-pmc';
|
||||
import App from './components/app';
|
||||
|
||||
export default App;
|
||||
|
||||
if (typeof window!=='undefined') {
|
||||
addEventListener('click', e => {
|
||||
let { target } = e;
|
||||
do {
|
||||
if (target.nodeName === 'A') {
|
||||
history.pushState(null, null, target.pathname);
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
} while ((target = target.parentNode));
|
||||
});
|
||||
}
|
||||
32
src/index.tsx
Normal file
32
src/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { h, render } from 'preact';
|
||||
import './lib/fix-pmc';
|
||||
import './style';
|
||||
import App from './components/app';
|
||||
|
||||
// Find the outermost Element in our server-rendered HTML structure.
|
||||
let root = document.querySelector('[prerender]') || undefined;
|
||||
|
||||
// "attach" the client-side rendering to it, updating the DOM in-place instead of replacing:
|
||||
root = render(<App />, document.body, root);
|
||||
|
||||
// In production, this entire condition is removed.
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Enable support for React DevTools and some helpful console warnings:
|
||||
require('preact/debug');
|
||||
|
||||
// When an update to any module is received, re-import the app and trigger a full re-render:
|
||||
module.hot.accept('./components/app', () => {
|
||||
import('./components/app').then(({ default: App }) => {
|
||||
root = render(<App />, document.body, root);
|
||||
});
|
||||
});
|
||||
} else if ('serviceWorker' in navigator && location.protocol === 'https:') {
|
||||
addEventListener('load', () => {
|
||||
navigator.serviceWorker.register(__webpack_public_path__ + 'sw.js');
|
||||
});
|
||||
}
|
||||
|
||||
/** @todo SSR */
|
||||
// if (typeof module==='object') {
|
||||
// module.exports = app;
|
||||
// }
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Component } from 'preact';
|
||||
|
||||
export function updater(obj, property, value) {
|
||||
return e => {
|
||||
let update = {};
|
||||
update[property] = typeof value === 'function' ? value(obj.state[property], e) : value;
|
||||
obj.setState(update);
|
||||
};
|
||||
}
|
||||
|
||||
export const toggle = value => !value;
|
||||
|
||||
export class When extends Component {
|
||||
state = { ready: !!this.props.value };
|
||||
render({ value, children: [child] }, { ready }) {
|
||||
if (value && !ready) this.setState({ ready: true });
|
||||
return ready ? (typeof child === 'function' ? child() : child) : null;
|
||||
}
|
||||
}
|
||||
39
src/lib/util.ts
Normal file
39
src/lib/util.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Component, ComponentProps } from 'preact';
|
||||
|
||||
type WhenProps = ComponentProps<When> & {
|
||||
value: boolean,
|
||||
children?: (JSX.Element | (() => JSX.Element))[]
|
||||
};
|
||||
|
||||
type WhenState = {
|
||||
ready: boolean
|
||||
};
|
||||
|
||||
export class When extends Component<WhenProps, WhenState> {
|
||||
state: WhenState = {
|
||||
ready: !!this.props.value
|
||||
};
|
||||
|
||||
render({ value, children = [] }: WhenProps, { ready }: WhenState) {
|
||||
let child = children[0];
|
||||
if (value && !ready) this.setState({ ready: true });
|
||||
return ready ? (typeof child === 'function' ? child() : child) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorator that binds values to their class instance.
|
||||
* @example
|
||||
* class C {
|
||||
* @bind
|
||||
* foo () {
|
||||
* return this;
|
||||
* }
|
||||
* }
|
||||
* let f = new C().foo;
|
||||
* f() instanceof C; // true
|
||||
*/
|
||||
export function bind(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
descriptor.value = descriptor.value.bind(target);
|
||||
return descriptor;
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h",
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"async!*": ["*"]
|
||||
}
|
||||
}
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "es2017",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"noUnusedLocals": true,
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h",
|
||||
"allowJs": false,
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
16
tslint.json
Normal file
16
tslint.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint-config-semistandard",
|
||||
"tslint-react"
|
||||
],
|
||||
"rules": {
|
||||
"quotemark": [true, "single", "jsx-double", "avoid-escape"],
|
||||
"no-use-before-declare": false,
|
||||
"no-floating-promises": false,
|
||||
"space-before-function-paren": [true, false],
|
||||
"jsx-boolean-value": [true, "never"],
|
||||
"jsx-no-multiline-js": false,
|
||||
"jsx-no-bind": true,
|
||||
"jsx-no-lambda": true
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
const ForkTsCheckerNotifierWebpackPlugin = require('fork-ts-checker-notifier-webpack-plugin');
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
@@ -54,6 +56,10 @@ module.exports = function(_, env) {
|
||||
loader: 'ts-loader',
|
||||
// Don't transpile anything in node_modules:
|
||||
exclude: nodeModules,
|
||||
options: {
|
||||
// Offload type checking to ForkTsCheckerWebpackPlugin for better performance:
|
||||
transpileOnly: true
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(tsx?|jsx?)$/,
|
||||
@@ -112,6 +118,15 @@ module.exports = function(_, env) {
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
// Runs tslint & type checking in a worker pool
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
tslint: true,
|
||||
// wait for type chec
|
||||
async: !isProd,
|
||||
formatter: 'codeframe'
|
||||
}),
|
||||
new ForkTsCheckerNotifierWebpackPlugin({ excludeWarnings: true }),
|
||||
|
||||
// Pretty progressbar showing build progress:
|
||||
new ProgressBarPlugin({
|
||||
format: '\u001b[90m\u001b[44mBuild\u001b[49m\u001b[39m [:bar] \u001b[32m\u001b[1m:percent\u001b[22m\u001b[39m (:elapseds) \u001b[2m:msg\u001b[22m',
|
||||
|
||||
Reference in New Issue
Block a user