Skip to content

Commit

Permalink
Merge branch 'ui' into pinch-zoom-features
Browse files Browse the repository at this point in the history
  • Loading branch information
developit authored Aug 22, 2018
2 parents 3b2513c + 78b7197 commit 982bca2
Show file tree
Hide file tree
Showing 18 changed files with 2,390 additions and 3,993 deletions.
6 changes: 0 additions & 6 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ declare namespace JSX {
interface IntrinsicElements { }
}

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

declare module 'pako/lib/deflate';
5,957 changes: 2,215 additions & 3,742 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,7 @@
"classnames": "^2.2.6",
"comlink": "^3.0.3",
"comlink-loader": "^1.0.0",
"material-components-web": "^0.32.0",
"pako": "^1.0.6",
"preact": "^8.2.9",
"preact-i18n": "^1.2.2",
"preact-material-components": "^1.4.7",
"preact-router": "^2.6.1",
"pretty-bytes": "^5.1.0"
}
}
31 changes: 25 additions & 6 deletions src/components/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
import { decodeImage } from '../../codecs/decoders';
import { cleanMerge, cleanSet } from '../../lib/clean-modify';

type Orientation = 'horizontal' | 'vertical';

interface SourceImage {
file: File;
bmp: ImageBitmap;
Expand Down Expand Up @@ -63,6 +65,7 @@ interface State {
images: [EncodedImage, EncodedImage];
loading: boolean;
error?: string;
orientation: Orientation;
}

interface UpdateImageOptions {
Expand Down Expand Up @@ -110,6 +113,8 @@ async function compressImage(
}

export default class App extends Component<Props, State> {
widthQuery = window.matchMedia('(min-width: 500px)');

state: State = {
loading: false,
images: [
Expand All @@ -128,6 +133,7 @@ export default class App extends Component<Props, State> {
loading: false,
},
],
orientation: this.widthQuery.matches ? 'horizontal' : 'vertical',
};

private snackbar?: SnackBarElement;
Expand All @@ -143,6 +149,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 {
Expand Down Expand Up @@ -284,15 +297,19 @@ export default class App extends Component<Props, State> {
this.snackbar.showSnackbar({ message: error });
}

render({ }: Props, { loading, images, source }: 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>Drop, paste or select an image</h1>
Expand All @@ -301,9 +318,11 @@ export default class App extends Component<Props, State> {
)}
{(leftImageBmp && rightImageBmp) && images.map((image, index) => (
<Options
sourceImage={source}
image={image}
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)}
Expand Down
28 changes: 8 additions & 20 deletions src/components/App/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,20 @@ Note: These styles are temporary. They will be replaced before going live.
overflow: hidden;
contain: strict;
display: flex;
justify-content: flex-end;



.leftOptions,
.rightOptions {
position: fixed;
bottom: 1px;
&.horizontal {
justify-content: space-between;
align-items: flex-end;
}

.leftOptions { left: 0px; }
.rightOptions { right: 0px; }
&.vertical {
flex-direction: column;
}
}

.welcome {
flex: 1;
align-self: center;
justify-self: center;
margin: auto;
text-align: center;

h1 {
Expand Down Expand Up @@ -90,12 +87,3 @@ Note: These styles are temporary. They will be replaced before going live.
}
}
}

@keyframes drop-pop {
from {
transform: scale(0.9);
}
to {
transform: scale(1);
}
}
108 changes: 42 additions & 66 deletions src/components/FileSize.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,83 @@
import { h, Component, ClassAttributes } from 'preact';
import { h, Component } from 'preact';
import * as prettyBytes from 'pretty-bytes';
import { blobToArrayBuffer } from '../lib/util';
import GzipSizeWorker from '../lib/gzip-size.worker';

type FileContents = string | ArrayBuffer | File | Blob;
type FileContents = ArrayBuffer | Blob;

interface Props extends ClassAttributes<HTMLSpanElement> {
compress?: boolean;
data?: FileContents;
interface Props extends Pick<JSX.HTMLAttributes, Exclude<keyof JSX.HTMLAttributes, 'data'>> {
file?: FileContents;
compareTo?: FileContents;
class?: string;
increaseClass?: string;
decreaseClass?: string;
}

interface State {
size?: number;
sizeFormatted?: string;
compareSize?: number;
compareSizeFormatted?: string;
}

let gzipSizeWorker: GzipSizeWorker;

const CACHE = new WeakMap();

async function calculateSize(rawData: FileContents, compress = false): Promise<number | void> {
let data = rawData;

if (typeof data === 'string') {
data = new Blob([data]);
}
if (data instanceof Blob || data instanceof File) {
data = await blobToArrayBuffer(data);
}

if (!compress) {
return data.byteLength;
}

if (!gzipSizeWorker) {
gzipSizeWorker = await new GzipSizeWorker();
}

if (CACHE.has(data)) {
return CACHE.get(data);
}

const size = gzipSizeWorker.gzipSize(data);
// cache the Promise so we dedupe in-flight requests.
CACHE.set(data, size);
return await size;
function calculateSize(data: FileContents): number {
return data instanceof ArrayBuffer ? data.byteLength : data.size;
}

export default class FileSize extends Component<Props, State> {
// "lock" counters for computed state properties
counters: Partial<State> = {};

componentDidMount() {
const { compress, data, compareTo } = this.props;
if (data) {
this.computeSize('size', compress, data);
constructor(props: Props) {
super(props);
if (props.file) {
this.computeSize('size', props.file);
}
if (compareTo) {
this.computeSize('compareSize', compress, compareTo);
if (props.compareTo) {
this.computeSize('compareSize', props.compareTo);
}
}

componentWillReceiveProps({ compress, data, compareTo }: Props) {
if (compress !== this.props.compress || data !== this.props.data) {
this.computeSize('size', compress, data);
componentWillReceiveProps({ file, compareTo }: Props) {
if (file !== this.props.file) {
this.computeSize('size', file);
}
if (compress !== this.props.compress || compareTo !== this.props.compareTo) {
this.computeSize('compareSize', compress, compareTo);
if (compareTo !== this.props.compareTo) {
this.computeSize('compareSize', compareTo);
}
this.componentDidUpdate();
}

componentDidMount() {
this.applyStyles();
}

componentDidUpdate() {
const { size, compareSize= 0 } = this.state;
this.applyStyles();
}

applyStyles() {
const { size, compareSize = 0 } = this.state;
if (size != null && this.base) {
const delta = Math.round(size && compareSize ? (size - compareSize) / compareSize * 100 : 0);
const delta = size && compareSize ? (size - compareSize) / compareSize : 0;
this.base.style.setProperty('--size', '' + size);
this.base.style.setProperty('--size-delta', '' + Math.abs(delta));
this.base.style.setProperty('--size-delta', '' + Math.round(Math.abs(delta * 100)));
}
}

async computeSize(prop: keyof State, compress = false, data?: FileContents) {
const id = this.counters[prop] = (this.counters[prop] || 0) + 1;
const size = data ? await calculateSize(data, compress) : 0;
if (this.counters[prop] !== id) return;
this.setState({ [prop]: size });
computeSize(prop: keyof State, data?: FileContents) {
const size = data ? calculateSize(data) : 0;
const pretty = prettyBytes(size);
this.setState({
[prop]: size,
[prop + 'Formatted']: pretty,
});
}

render(
{ data, compareTo, increaseClass, decreaseClass, ...props }: Props,
{ size, compareSize }: State,
{ file, compareTo, increaseClass, decreaseClass, ...props }: Props,
{ size, sizeFormatted = '', compareSize }: State,
) {
const sizeFormatted = size ? prettyBytes(size) : '';
const delta = size && compareSize ? (size - compareSize) / compareSize : 0;
return (
<span title={sizeFormatted} {...props}>
<span {...props}>
{sizeFormatted}
{Math.abs(delta) >= 0.01 && (
{compareTo && (
<span class={delta > 0 ? increaseClass : decreaseClass}>
{delta > 0 && '+'}
{Math.round(delta * 100)}%
</span>
)}
Expand Down
46 changes: 23 additions & 23 deletions src/components/Options/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { QuantizeOptions } from '../../codecs/imagequant/quantizer';
import { PreprocessorState } from '../../codecs/preprocessors';

import FileSize from '../FileSize';
import DownloadIcon from '../../lib/icons/Download';
import { DownloadIcon } from '../../lib/icons';

const encoderOptionsComponentMap = {
[identity.type]: undefined,
Expand All @@ -52,15 +52,17 @@ const encoderOptionsComponentMap = {
[browserPDF.type]: undefined,
};

const titles = {
horizontal: ['Left Image', 'Right Image'],
vertical: ['Top Image', 'Bottom Image'],
};

interface Props {
class?: string;
image: {
downloadUrl?: string;
file?: File;
};
sourceImage?: {
file?: File;
};
orientation: 'horizontal' | 'vertical';
imageIndex: number;
sourceImageFile?: File;
imageFile?: File;
downloadUrl?: string;
encoderState: EncoderState;
preprocessorState: PreprocessorState;
onEncoderTypeChange(newType: EncoderType): void;
Expand Down Expand Up @@ -109,9 +111,11 @@ export default class Options extends Component<Props, State> {

render(
{
image,
sourceImage,
class: className,
sourceImageFile,
imageIndex,
imageFile,
downloadUrl,
orientation,
encoderState,
preprocessorState,
onEncoderOptionsChange,
Expand All @@ -122,17 +126,17 @@ export default class Options extends Component<Props, State> {
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];

return (
<div class={`${style.options}${className ? (' ' + className) : ''}`}>
<div class={`${style.options} ${style[orientation]}`}>
<h2 class={style.title}>
{className && className.match(/left/) ? 'Left Image' : 'Right Image'}
{titles[orientation][imageIndex]}
{', '}
{encoderMap[encoderState.type].label}

{(image.downloadUrl && image.file) && (
{(downloadUrl && imageFile) && (
<a
class={style.download}
href={image.downloadUrl}
download={image.file.name}
href={downloadUrl}
download={imageFile.name}
title="Download"
>
<DownloadIcon />
Expand Down Expand Up @@ -188,12 +192,8 @@ export default class Options extends Component<Props, State> {
class={style.size}
increaseClass={style.increase}
decreaseClass={style.decrease}
// @todo: once we have a nice way to pass down the original image
// (image size?), pass compareTo prop here to show size delta.
data={image.file}
compareTo={sourceImage && sourceImage.file}
// @todo determine cases for compressing and pass here
compress={false}
file={imageFile}
compareTo={imageFile === sourceImageFile ? undefined : sourceImageFile}
/>
</div>
</div>
Expand Down
Loading

0 comments on commit 982bca2

Please sign in to comment.