Download icon

This commit is contained in:
Jake Archibald
2018-10-19 16:24:58 +01:00
parent e1e44157bc
commit 46998c473d
5 changed files with 153 additions and 116 deletions

View File

@@ -1,87 +0,0 @@
import { h, Component } from 'preact';
import * as prettyBytes from 'pretty-bytes';
type FileContents = ArrayBuffer | Blob;
interface Props extends Pick<JSX.HTMLAttributes, Exclude<keyof JSX.HTMLAttributes, 'data'>> {
file?: FileContents;
compareTo?: FileContents;
increaseClass?: string;
decreaseClass?: string;
}
interface State {
size?: number;
sizeFormatted?: string;
compareSize?: number;
compareSizeFormatted?: string;
}
function calculateSize(data: FileContents): number {
return data instanceof ArrayBuffer ? data.byteLength : data.size;
}
export default class FileSize extends Component<Props, State> {
constructor(props: Props) {
super(props);
if (props.file) {
this.computeSize('size', props.file);
}
if (props.compareTo) {
this.computeSize('compareSize', props.compareTo);
}
}
componentWillReceiveProps({ file, compareTo }: Props) {
if (file !== this.props.file) {
this.computeSize('size', file);
}
if (compareTo !== this.props.compareTo) {
this.computeSize('compareSize', compareTo);
}
}
componentDidMount() {
this.applyStyles();
}
componentDidUpdate() {
this.applyStyles();
}
applyStyles() {
const { size, compareSize = 0 } = this.state;
if (size != null && this.base) {
const delta = size && compareSize ? (size - compareSize) / compareSize : 0;
this.base.style.setProperty('--size', '' + size);
this.base.style.setProperty('--size-delta', '' + Math.round(Math.abs(delta * 100)));
}
}
computeSize(prop: keyof State, data?: FileContents) {
const size = data ? calculateSize(data) : 0;
const pretty = prettyBytes(size);
this.setState({
[prop]: size,
[prop + 'Formatted']: pretty,
});
}
render(
{ file, compareTo, increaseClass, decreaseClass, ...props }: Props,
{ size, sizeFormatted = '', compareSize }: State,
) {
const delta = size && compareSize ? (size - compareSize) / compareSize : 0;
return (
<span {...props}>
{sizeFormatted}
{compareTo && (
<span class={delta > 0 ? increaseClass : decreaseClass}>
{delta > 0 && '+'}
{Math.round(delta * 100)}%
</span>
)}
</span>
);
}
}

View File

@@ -0,0 +1,63 @@
import { h, Component } from 'preact';
import * as prettyBytes from 'pretty-bytes';
import * as style from './style.scss';
interface Props {
blob: Blob;
compareTo?: Blob;
}
interface State {
sizeFormatted?: string;
}
export default class FileSize extends Component<Props, State> {
constructor(props: Props) {
super(props);
if (props.blob) {
this.setState({
sizeFormatted: prettyBytes(props.blob.size),
});
}
}
componentWillReceiveProps({ blob }: Props) {
if (blob) {
this.setState({
sizeFormatted: prettyBytes(blob.size),
});
}
}
render(
{ blob, compareTo }: Props,
{ sizeFormatted }: State,
) {
let comparison: JSX.Element | undefined;
if (compareTo) {
const delta = blob.size / compareTo.size;
if (delta > 1) {
const percent = Math.round((delta - 1) * 100) + '%';
comparison = (
<span class={`${style.sizeDelta} ${style.sizeIncrease}`}>
{percent === '0%' ? 'slightly' : percent} bigger
</span>
);
} else if (delta < 1) {
const percent = Math.round((1 - delta) * 100) + '%';
comparison = (
<span class={`${style.sizeDelta} ${style.sizeDecrease}`}>
{percent === '0%' ? 'slightly' : percent} smaller
</span>
);
} else {
comparison = (
<span class={style.sizeDelta}>no change</span>
);
}
}
return <span>{sizeFormatted} {comparison}</span>;
}
}

View File

@@ -35,7 +35,7 @@ import {
import { QuantizeOptions } from '../../codecs/imagequant/processor-meta';
import { ResizeOptions } from '../../codecs/resize/processor-meta';
import { PreprocessorState } from '../../codecs/preprocessors';
import FileSize from '../FileSize';
import FileSize from './FileSize';
import { DownloadIcon } from '../../lib/icons';
import { SourceImage } from '../App';
import Checkbox from '../checkbox';
@@ -145,7 +145,7 @@ export default class Options extends Component<Props, State> {
<Expander>
{encoderState.type === identity.type ? null :
<div>
<h2 class={style.optionsTitle}>Edit</h2>
<h3 class={style.optionsTitle}>Edit</h3>
<label class={style.sectionEnabler}>
<Checkbox
name="resize.enable"
@@ -184,7 +184,7 @@ export default class Options extends Component<Props, State> {
}
</Expander>
<h2 class={style.optionsTitle}>Compress</h2>
<h3 class={style.optionsTitle}>Compress</h3>
<div class={style.optionsScroller}>
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
@@ -214,32 +214,35 @@ export default class Options extends Component<Props, State> {
</div>
{/*
<div class={style.row}>
<button onClick={this.onCopyToOtherClick}>Copy settings to other side</button>
<div class={style.optionsTitle}>
<button onClick={this.onCopyToOtherClick} class={style.button}>
{imageIndex === 1 && '← '}
Copy settings to other side
{imageIndex === 0 && ' →'}
</button>
</div>
*/}
<div class={style.sizeDetails}>
<div class={style.results}>
<div class={style.resultData}>
{!imageFile ? 'Compressing…' :
<FileSize
class={style.size}
increaseClass={style.increase}
decreaseClass={style.decrease}
file={imageFile}
blob={imageFile}
compareTo={(source && imageFile !== source.file) ? source.file : undefined}
/>
}
</div>
<div class={style.download}>
{(downloadUrl && imageFile) && (
<a
class={style.download}
href={downloadUrl}
download={imageFile.name}
title="Download"
>
<DownloadIcon />
<a href={downloadUrl} download={imageFile.name} title="Download">
<DownloadIcon class={style.downloadIcon} />
</a>
)}
</div>
*/}
</div>
</div>
);
}

View File

@@ -3,7 +3,7 @@ $horizontalPadding: 15px;
.options {
color: #fff;
width: 300px;
opacity: 0.8;
opacity: 0.9;
font-size: 1.2rem;
max-height: 100%;
display: flex;
@@ -28,6 +28,8 @@ $horizontalPadding: 15px;
}
.option-one-cell {
display: grid;
grid-template-columns: 1fr;
padding: 10px $horizontalPadding;
}
@@ -63,3 +65,59 @@ $horizontalPadding: 15px;
overflow-x: hidden;
overflow-y: auto;
}
.button {
composes: unbutton from '../../lib/util.scss';
background: #151515;
border-radius: 4px;
color: #fff;
}
.results {
display: grid;
grid-template-columns: 1fr auto;
background: rgba(0, 0, 0, 0.9);
font-size: 1.4rem;
}
.result-data {
display: flex;
align-items: center;
padding: 0 $horizontalPadding;
}
.download {
background: #34B9EB;
--size: 38px;
width: var(--size);
height: var(--size);
display: flex;
align-items: center;
justify-content: center;
}
.download-icon {
color: #fff;
display: block;
--size: 24px;
width: var(--size);
height: var(--size);
padding: 7px;
filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.7));
}
.size-delta {
font-size: 1.1rem;
font-style: italic;
position: relative;
top: -1px;
margin-left: 0.3em;
}
.size-increase {
color: #e35050;
}
.size-decrease {
color: #50e3c2;
}

View File

@@ -8,7 +8,7 @@ const Icon = (props: JSX.HTMLAttributes) => (
export const DownloadIcon = (props: JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M19.35 10.04A7.49 7.49 0 0 0 12 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 0 0 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z" />
<path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-7h-2zm-6 .7l2.6-2.6 1.4 1.4-5 5-5-5 1.4-1.4 2.6 2.6V3h2z"/>
</Icon>
);