Options UI (#135)

* Move gzipped size calculations into a worker and wrap it up in a `<GzipSize />` component that will also handle showing % of original size once that info is plumbed

* A couple tweaks for the app welcome (drop files) screen. We don't have mocks for this one, but this is at least a minor improvement.

* Prettier "pop" effect and styling for the drop zone/indicator.

* Styling for the quantization toggle to make it look like a disclosure triangle/button.

* Add controls bar (zoom in/out/to, background toggle). @todo: extract into its own component.

* When clicking/tapping the image area, give it focus.

* Utilities used by this PR

* Add a `two-up-handle` attribute to the handle for easier styling (classname gets mangled so it doesn't make for a good public API)

* Add a dummy comment to test netlify deploy

* Remove commented-out code.

* Fix styling of vertical split (which as it turns out is slightly different in the mocks anyway)

* Use a composited overlay for the dark background instead of animating background-color

* Move grayscale styling into `<two-up>` by default, then set colors via custom properties

* Remove commented-out svg fill

* Remove dummy comment

* Change `<GzipSize>` to be `<FileSize>`, add `compress` option that lets us show gzipped sizes later if we need. Defaults to `false`, and the gzip worker is only lazily instantiated the first time a compressed size calculation is requested.

* Dependency updates

* Remove color animations from dnd overlay

* Don't use a cyclical import for EncodedImage, instead just specify the types of the properties we Options actually uses.

* Pass source image through to FileSize component so it can compute delta

* Stylize size display with colors based on delta amount/direction

* Remove box-shadow animation.

* Simplify font stack

* Remove commented out code

* Remove gzip compression from size component

* Remove memoization bits

* Use specific flattend props instead of passing large context objects around.

* Remove unused packages.

* Remove unreachable String case in FileSize, and omit redundant File type

* Simplify calculateSize()

* Fix types for FileSize!

* Remove FileSize title

* Make delta variable consistent.

* Skip passing compareTo value for original image

* Remove manual focus

* Fix whitespace

* remove unused keyframes

* remove pointless flex-wrap property

* Remove unused resetZoom() method

* Remove pointless flex properties

* Use `on` prefix for event handling

* Remove pointless justify-self property

* Use an inline SVG for TwoUp's handle icon so it can be colored from outside the component..

* Move orientation state up from `<Output>` into `<App>` and share it with `<Options>`.

* Make the options panels responsive :)

* Show a plus sign for size increases `(+8%)`

* Use inline SVG for the zoom +/- icons, collect SVG icons into one file now that I've verified they get tree-shaken properly.

* Fix top/bottom options panels being reversed

* remove commented out code

* lockfile

* Revert quanitzation toggle styles so it's just a checkbox.

* Remove minimum delta for compare size

* Rename data prop to file.

* scale int -> float

* remove tabIndex

* Remove old icon files

* Add width to options panels

* Add vertical scrolling when options are taller than 80% of the screen height.
This commit is contained in:
Jason Miller
2018-09-05 03:21:54 -04:00
committed by Jake Archibald
parent 54ad30a7ed
commit 32f6f8b941
15 changed files with 766 additions and 1757 deletions

View File

@@ -1,5 +1,4 @@
import { h, Component } from 'preact';
import { partial } from 'filesize';
import { bind, linkRef, bitmapToImageData } from '../../lib/util';
import * as style from './style.scss';
@@ -38,6 +37,8 @@ import {
import { decodeImage } from '../../codecs/decoders';
import { cleanMerge, cleanSet } from '../../lib/clean-modify';
type Orientation = 'horizontal' | 'vertical';
interface SourceImage {
file: File;
bmp: ImageBitmap;
@@ -65,14 +66,13 @@ interface State {
images: [EncodedImage, EncodedImage];
loading: boolean;
error?: string;
orientation: Orientation;
}
interface UpdateImageOptions {
skipPreprocessing?: boolean;
}
const filesize = partial({});
async function preprocessImage(
source: SourceImage,
preprocessData: PreprocessorState,
@@ -115,6 +115,8 @@ async function compressImage(
}
export default class App extends Component<Props, State> {
widthQuery = window.matchMedia('(min-width: 500px)');
state: State = {
loading: false,
images: [
@@ -133,6 +135,7 @@ export default class App extends Component<Props, State> {
loading: false,
},
],
orientation: this.widthQuery.matches ? 'horizontal' : 'vertical',
};
private snackbar?: SnackBarElement;
@@ -148,6 +151,13 @@ export default class App extends Component<Props, State> {
window.STATE = this.state;
};
}
this.widthQuery.addListener(this.onMobileWidthChange);
}
@bind
onMobileWidthChange() {
this.setState({ orientation: this.widthQuery.matches ? 'horizontal' : 'vertical' });
}
onEncoderTypeChange(index: 0 | 1, newType: EncoderType): void {
@@ -289,33 +299,32 @@ export default class App extends Component<Props, State> {
this.snackbar.showSnackbar({ message: error });
}
render({ }: Props, { loading, images }: State) {
render({ }: Props, { loading, images, source, orientation }: State) {
const [leftImageBmp, rightImageBmp] = images.map(i => i.bmp);
const anyLoading = loading || images.some(image => image.loading);
return (
<file-drop accept="image/*" onfiledrop={this.onFileDrop}>
<div id="app" class={style.app}>
<div id="app" class={`${style.app} ${style[orientation]}`}>
{(leftImageBmp && rightImageBmp) ? (
<Output leftImg={leftImageBmp} rightImg={rightImageBmp} />
<Output
orientation={orientation}
leftImg={leftImageBmp}
rightImg={rightImageBmp}
/>
) : (
<div class={style.welcome}>
<h1>Select an image</h1>
<input type="file" onChange={this.onFileChange} />
</div>
)}
{images.map((image, index) => (
<span class={index ? style.rightLabel : style.leftLabel}>
{encoderMap[image.encoderState.type].label}
{(image.downloadUrl && image.file) && (
<a href={image.downloadUrl} download={image.file.name}>🔻</a>
)}
{image.file && ` - ${filesize(image.file.size)}`}
</span>
))}
{images.map((image, index) => (
<div class={style.welcome}>
<h1>Drop, paste or select an image</h1>
<input type="file" onChange={this.onFileChange} />
</div>
)}
{(leftImageBmp && rightImageBmp) && images.map((image, index) => (
<Options
class={index ? style.rightOptions : style.leftOptions}
orientation={orientation}
imageIndex={index}
imageFile={image.file}
sourceImageFile={source && source.file}
downloadUrl={image.downloadUrl}
preprocessorState={image.preprocessorState}
encoderState={image.encoderState}
onEncoderTypeChange={this.onEncoderTypeChange.bind(this, index)}