mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-11 16:26:20 +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 { 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user