mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 08:47:31 +00:00
Download icon
This commit is contained in:
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
src/components/Options/FileSize.tsx
Normal file
63
src/components/Options/FileSize.tsx
Normal 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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
import { QuantizeOptions } from '../../codecs/imagequant/processor-meta';
|
import { QuantizeOptions } from '../../codecs/imagequant/processor-meta';
|
||||||
import { ResizeOptions } from '../../codecs/resize/processor-meta';
|
import { ResizeOptions } from '../../codecs/resize/processor-meta';
|
||||||
import { PreprocessorState } from '../../codecs/preprocessors';
|
import { PreprocessorState } from '../../codecs/preprocessors';
|
||||||
import FileSize from '../FileSize';
|
import FileSize from './FileSize';
|
||||||
import { DownloadIcon } from '../../lib/icons';
|
import { DownloadIcon } from '../../lib/icons';
|
||||||
import { SourceImage } from '../App';
|
import { SourceImage } from '../App';
|
||||||
import Checkbox from '../checkbox';
|
import Checkbox from '../checkbox';
|
||||||
@@ -145,7 +145,7 @@ export default class Options extends Component<Props, State> {
|
|||||||
<Expander>
|
<Expander>
|
||||||
{encoderState.type === identity.type ? null :
|
{encoderState.type === identity.type ? null :
|
||||||
<div>
|
<div>
|
||||||
<h2 class={style.optionsTitle}>Edit</h2>
|
<h3 class={style.optionsTitle}>Edit</h3>
|
||||||
<label class={style.sectionEnabler}>
|
<label class={style.sectionEnabler}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
name="resize.enable"
|
name="resize.enable"
|
||||||
@@ -184,7 +184,7 @@ export default class Options extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
</Expander>
|
</Expander>
|
||||||
|
|
||||||
<h2 class={style.optionsTitle}>Compress</h2>
|
<h3 class={style.optionsTitle}>Compress</h3>
|
||||||
|
|
||||||
<div class={style.optionsScroller}>
|
<div class={style.optionsScroller}>
|
||||||
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
|
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
|
||||||
@@ -214,32 +214,35 @@ export default class Options extends Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
|
<div class={style.optionsTitle}>
|
||||||
<div class={style.row}>
|
<button onClick={this.onCopyToOtherClick} class={style.button}>
|
||||||
<button onClick={this.onCopyToOtherClick}>Copy settings to other side</button>
|
{imageIndex === 1 && '← '}
|
||||||
</div>
|
Copy settings to other side
|
||||||
|
{imageIndex === 0 && ' →'}
|
||||||
<div class={style.sizeDetails}>
|
</button>
|
||||||
<FileSize
|
|
||||||
class={style.size}
|
|
||||||
increaseClass={style.increase}
|
|
||||||
decreaseClass={style.decrease}
|
|
||||||
file={imageFile}
|
|
||||||
compareTo={(source && imageFile !== source.file) ? source.file : undefined}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{(downloadUrl && imageFile) && (
|
|
||||||
<a
|
|
||||||
class={style.download}
|
|
||||||
href={downloadUrl}
|
|
||||||
download={imageFile.name}
|
|
||||||
title="Download"
|
|
||||||
>
|
|
||||||
<DownloadIcon />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
|
<div class={style.results}>
|
||||||
|
<div class={style.resultData}>
|
||||||
|
{!imageFile ? 'Compressing…' :
|
||||||
|
<FileSize
|
||||||
|
blob={imageFile}
|
||||||
|
compareTo={(source && imageFile !== source.file) ? source.file : undefined}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={style.download}>
|
||||||
|
{(downloadUrl && imageFile) && (
|
||||||
|
<a href={downloadUrl} download={imageFile.name} title="Download">
|
||||||
|
<DownloadIcon class={style.downloadIcon} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ $horizontalPadding: 15px;
|
|||||||
.options {
|
.options {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
opacity: 0.8;
|
opacity: 0.9;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -28,6 +28,8 @@ $horizontalPadding: 15px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.option-one-cell {
|
.option-one-cell {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
padding: 10px $horizontalPadding;
|
padding: 10px $horizontalPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,3 +65,59 @@ $horizontalPadding: 15px;
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const Icon = (props: JSX.HTMLAttributes) => (
|
|||||||
|
|
||||||
export const DownloadIcon = (props: JSX.HTMLAttributes) => (
|
export const DownloadIcon = (props: JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<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>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user