mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 17:27:09 +00:00
Revealing sections
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
import linkState from 'linkstate';
|
import linkState from 'linkstate';
|
||||||
import { bind } from '../../lib/initial-util';
|
import { bind, linkRef } from '../../lib/initial-util';
|
||||||
import { inputFieldValueAsNumber } from '../../lib/util';
|
import { inputFieldValueAsNumber } from '../../lib/util';
|
||||||
import { ResizeOptions } from './processor-meta';
|
import { ResizeOptions } from './processor-meta';
|
||||||
|
import * as style from '../../components/options/style.scss';
|
||||||
|
import Checkbox from '../../components/checkbox';
|
||||||
|
import Expander from '../../components/expander';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isVector: Boolean;
|
isVector: Boolean;
|
||||||
@@ -32,7 +35,7 @@ export default class ResizerOptions extends Component<Props, State> {
|
|||||||
width: inputFieldValueAsNumber(width),
|
width: inputFieldValueAsNumber(width),
|
||||||
height: inputFieldValueAsNumber(height),
|
height: inputFieldValueAsNumber(height),
|
||||||
method: this.form!.resizeMethod.value,
|
method: this.form!.resizeMethod.value,
|
||||||
fitMethod: this.form!.fitMethod.value,
|
fitMethod: this.form!.fitMethod ? this.form!.fitMethod.value : this.props.options.fitMethod,
|
||||||
};
|
};
|
||||||
this.props.onChange(options);
|
this.props.onChange(options);
|
||||||
}
|
}
|
||||||
@@ -65,10 +68,10 @@ export default class ResizerOptions extends Component<Props, State> {
|
|||||||
this.form!.width.value = Math.round(height * this.props.aspect);
|
this.form!.width.value = Math.round(height * this.props.aspect);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ options, aspect, isVector }: Props, { maintainAspect }: State) {
|
render({ options, isVector }: Props, { maintainAspect }: State) {
|
||||||
return (
|
return (
|
||||||
<form ref={el => this.form = el}>
|
<form ref={linkRef(this, 'form')} class={style.optionsSection}>
|
||||||
<label>
|
<label class={style.optionTextFirst}>
|
||||||
Method:
|
Method:
|
||||||
<select
|
<select
|
||||||
name="resizeMethod"
|
name="resizeMethod"
|
||||||
@@ -82,7 +85,7 @@ export default class ResizerOptions extends Component<Props, State> {
|
|||||||
<option value="browser-high">Browser high quality</option>
|
<option value="browser-high">Browser high quality</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label class={style.optionTextFirst}>
|
||||||
Width:
|
Width:
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
@@ -94,7 +97,7 @@ export default class ResizerOptions extends Component<Props, State> {
|
|||||||
onInput={this.onWidthInput}
|
onInput={this.onWidthInput}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label class={style.optionTextFirst}>
|
||||||
Height:
|
Height:
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
@@ -105,26 +108,29 @@ export default class ResizerOptions extends Component<Props, State> {
|
|||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label class={style.optionInputFirst}>
|
||||||
<input
|
<Checkbox
|
||||||
name="maintainAspect"
|
name="maintainAspect"
|
||||||
type="checkbox"
|
|
||||||
checked={maintainAspect}
|
checked={maintainAspect}
|
||||||
onChange={linkState(this, 'maintainAspect')}
|
onChange={linkState(this, 'maintainAspect')}
|
||||||
/>
|
/>
|
||||||
Maintain aspect ratio
|
Maintain aspect ratio
|
||||||
</label>
|
</label>
|
||||||
<label style={{ display: maintainAspect ? 'none' : '' }}>
|
<Expander>
|
||||||
Fit method:
|
{maintainAspect ? null :
|
||||||
<select
|
<label class={style.optionTextFirst}>
|
||||||
name="fitMethod"
|
Fit method:
|
||||||
value={options.fitMethod}
|
<select
|
||||||
onChange={this.onChange}
|
name="fitMethod"
|
||||||
>
|
value={options.fitMethod}
|
||||||
<option value="stretch">Stretch</option>
|
onChange={this.onChange}
|
||||||
<option value="cover">Cover</option>
|
>
|
||||||
</select>
|
<option value="stretch">Stretch</option>
|
||||||
</label>
|
<option value="cover">Cover</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
</Expander>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { h, Component } from 'preact';
|
|||||||
import * as style from './style.scss';
|
import * as style from './style.scss';
|
||||||
import { bind, Fileish } from '../../lib/initial-util';
|
import { bind, Fileish } from '../../lib/initial-util';
|
||||||
import { cleanSet, cleanMerge } from '../../lib/clean-modify';
|
import { cleanSet, cleanMerge } from '../../lib/clean-modify';
|
||||||
/*import OptiPNGEncoderOptions from '../../codecs/optipng/options';
|
import OptiPNGEncoderOptions from '../../codecs/optipng/options';
|
||||||
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
||||||
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
||||||
import WebPEncoderOptions from '../../codecs/webp/options';
|
import WebPEncoderOptions from '../../codecs/webp/options';
|
||||||
@@ -23,24 +23,24 @@ import * as browserGIF from '../../codecs/browser-gif/encoder-meta';
|
|||||||
import * as browserTIFF from '../../codecs/browser-tiff/encoder-meta';
|
import * as browserTIFF from '../../codecs/browser-tiff/encoder-meta';
|
||||||
import * as browserJP2 from '../../codecs/browser-jp2/encoder-meta';
|
import * as browserJP2 from '../../codecs/browser-jp2/encoder-meta';
|
||||||
import * as browserBMP from '../../codecs/browser-bmp/encoder-meta';
|
import * as browserBMP from '../../codecs/browser-bmp/encoder-meta';
|
||||||
import * as browserPDF from '../../codecs/browser-pdf/encoder-meta';*/
|
import * as browserPDF from '../../codecs/browser-pdf/encoder-meta';
|
||||||
import {
|
import {
|
||||||
EncoderState,
|
EncoderState,
|
||||||
EncoderType,
|
EncoderType,
|
||||||
EncoderOptions,
|
EncoderOptions,
|
||||||
// encoders,
|
encoders,
|
||||||
encodersSupported,
|
encodersSupported,
|
||||||
EncoderSupportMap,
|
EncoderSupportMap,
|
||||||
} from '../../codecs/encoders';
|
} from '../../codecs/encoders';
|
||||||
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';
|
||||||
|
import Expander from '../expander';
|
||||||
|
|
||||||
/*
|
|
||||||
const encoderOptionsComponentMap = {
|
const encoderOptionsComponentMap = {
|
||||||
[identity.type]: undefined,
|
[identity.type]: undefined,
|
||||||
[optiPNG.type]: OptiPNGEncoderOptions,
|
[optiPNG.type]: OptiPNGEncoderOptions,
|
||||||
@@ -55,7 +55,7 @@ const encoderOptionsComponentMap = {
|
|||||||
[browserTIFF.type]: undefined,
|
[browserTIFF.type]: undefined,
|
||||||
[browserJP2.type]: undefined,
|
[browserJP2.type]: undefined,
|
||||||
[browserPDF.type]: undefined,
|
[browserPDF.type]: undefined,
|
||||||
};*/
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
orientation: 'horizontal' | 'vertical';
|
orientation: 'horizontal' | 'vertical';
|
||||||
@@ -125,19 +125,19 @@ export default class Options extends Component<Props, State> {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
{
|
{
|
||||||
/*source,
|
source,
|
||||||
imageIndex,
|
imageIndex,
|
||||||
imageFile,
|
imageFile,
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
orientation,
|
orientation,
|
||||||
encoderState,*/
|
encoderState,
|
||||||
preprocessorState,
|
preprocessorState,
|
||||||
// onEncoderOptionsChange,
|
onEncoderOptionsChange,
|
||||||
}: Props,
|
}: Props,
|
||||||
// { encoderSupportMap }: State,
|
{ encoderSupportMap }: State,
|
||||||
) {
|
) {
|
||||||
// tslint:disable variable-name
|
// tslint:disable variable-name
|
||||||
// const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
|
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.options}>
|
<div class={style.options}>
|
||||||
@@ -148,17 +148,21 @@ export default class Options extends Component<Props, State> {
|
|||||||
checked={!!preprocessorState.resize.enabled}
|
checked={!!preprocessorState.resize.enabled}
|
||||||
onChange={this.onPreprocessorEnabledChange}
|
onChange={this.onPreprocessorEnabledChange}
|
||||||
/>
|
/>
|
||||||
<span class={style.sectionEnablerLabel}>Resize</span>
|
Resize
|
||||||
</label>
|
</label>
|
||||||
|
<Expander>
|
||||||
|
{preprocessorState.resize.enabled
|
||||||
|
?
|
||||||
|
<ResizeOptionsComponent
|
||||||
|
isVector={Boolean(source && source.vectorImage)}
|
||||||
|
aspect={source ? (source.data.width / source.data.height) : 1}
|
||||||
|
options={preprocessorState.resize}
|
||||||
|
onChange={this.onResizeOptionsChange}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Expander>
|
||||||
{/*
|
{/*
|
||||||
{preprocessorState.resize.enabled &&
|
|
||||||
<ResizeOptionsComponent
|
|
||||||
isVector={Boolean(source && source.vectorImage)}
|
|
||||||
aspect={source ? (source.data.width / source.data.height) : 1}
|
|
||||||
options={preprocessorState.resize}
|
|
||||||
onChange={this.onResizeOptionsChange}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<label class={style.toggle}>
|
<label class={style.toggle}>
|
||||||
<input
|
<input
|
||||||
name="quantizer.enable"
|
name="quantizer.enable"
|
||||||
|
|||||||
@@ -16,15 +16,29 @@ $horizontalPadding: 15px;
|
|||||||
border-bottom: 1px solid #000;
|
border-bottom: 1px solid #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-enabler {
|
.option-text-first {
|
||||||
cursor: pointer;
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: 87px 1fr;
|
||||||
grid-gap: 0.5em;
|
grid-gap: 0.7em;
|
||||||
padding: 6px $horizontalPadding;
|
padding: 10px $horizontalPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-enabler-label {
|
.option-input-first,
|
||||||
padding-top: 3px;
|
.section-enabler {
|
||||||
|
cursor: pointer;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-gap: 0.7em;
|
||||||
|
padding: 10px $horizontalPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-enabler {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-section {
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
import * as style from './style.scss';
|
import * as style from './style.scss';
|
||||||
import { UncheckedIcon, CheckedIcon } from '../../../lib/icons';
|
import { UncheckedIcon, CheckedIcon } from '../../lib/icons';
|
||||||
|
|
||||||
interface Props extends JSX.HTMLAttributes {}
|
interface Props extends JSX.HTMLAttributes {}
|
||||||
interface State {}
|
interface State {}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
.checkbox {
|
.checkbox {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
--size: 24px;
|
--size: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.real-checkbox {
|
.real-checkbox {
|
||||||
90
src/components/expander/index.tsx
Normal file
90
src/components/expander/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { h, Component, ComponentChild, ComponentChildren } from 'preact';
|
||||||
|
import * as style from './style.scss';
|
||||||
|
import { linkRef } from '../../lib/initial-util';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ComponentChildren;
|
||||||
|
}
|
||||||
|
interface State {
|
||||||
|
outgoingChildren: ComponentChild[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Expander extends Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
outgoingChildren: [],
|
||||||
|
};
|
||||||
|
private el?: HTMLDivElement;
|
||||||
|
private lastElHeight: number = 0;
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: Props) {
|
||||||
|
const children = this.props.children as ComponentChild[];
|
||||||
|
const nextChildren = nextProps.children as ComponentChild[];
|
||||||
|
|
||||||
|
if (!nextChildren[0] && children[0]) {
|
||||||
|
// Cache the current children for the shrink animation.
|
||||||
|
this.setState({ outgoingChildren: children });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate(nextProps: Props) {
|
||||||
|
const children = this.props.children as ComponentChild[];
|
||||||
|
const nextChildren = nextProps.children as ComponentChild[];
|
||||||
|
|
||||||
|
// Only interested if going from empty to not-empty, or not-empty to empty.
|
||||||
|
if ((children[0] && nextChildren[0]) || (!children[0] && !nextChildren[0])) return;
|
||||||
|
this.lastElHeight = this.el!.getBoundingClientRect().height;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(previousProps: Props) {
|
||||||
|
const children = this.props.children as ComponentChild[];
|
||||||
|
const previousChildren = previousProps.children as ComponentChild[];
|
||||||
|
|
||||||
|
// Only interested if going from empty to not-empty, or not-empty to empty.
|
||||||
|
if ((children[0] && previousChildren[0]) || (!children[0] && !previousChildren[0])) return;
|
||||||
|
|
||||||
|
// What height do we need to transition to?
|
||||||
|
this.el!.style.transition = 'none';
|
||||||
|
this.el!.style.height = '';
|
||||||
|
const newHeight = children[0] ? this.el!.getBoundingClientRect().height : 0;
|
||||||
|
|
||||||
|
if (this.lastElHeight === newHeight) {
|
||||||
|
this.el!.style.transition = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the currently rendered height absolutely.
|
||||||
|
this.el!.style.height = this.lastElHeight + 'px';
|
||||||
|
this.el!.style.transition = '';
|
||||||
|
// Force a style calc so the browser picks up the start value.
|
||||||
|
getComputedStyle(this.el!).height;
|
||||||
|
// Animate to the new height.
|
||||||
|
this.el!.style.height = newHeight + 'px';
|
||||||
|
|
||||||
|
const listener = () => {
|
||||||
|
// Unset the height, so element changes do the right thing.
|
||||||
|
this.el!.style.height = '';
|
||||||
|
this.el!.removeEventListener('transitionend', listener);
|
||||||
|
this.el!.removeEventListener('transitioncancel', listener);
|
||||||
|
if (this.state.outgoingChildren[0]) {
|
||||||
|
this.setState({ outgoingChildren: [] });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.el!.addEventListener('transitionend', listener);
|
||||||
|
this.el!.addEventListener('transitioncancel', listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(props: Props, { outgoingChildren }: State) {
|
||||||
|
const children = props.children as ComponentChild[];
|
||||||
|
const childrenExiting = !children[0] && outgoingChildren[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={linkRef(this, 'el')}
|
||||||
|
class={`${style.expander} ${childrenExiting ? style.childrenExiting : ''}`}
|
||||||
|
>
|
||||||
|
{children[0] ? children : outgoingChildren}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/components/expander/style.scss
Normal file
10
src/components/expander/style.scss
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.expander {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.children-exiting {
|
||||||
|
& > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,12 +32,12 @@ export const RemoveIcon = (props: JSX.HTMLAttributes) => (
|
|||||||
|
|
||||||
export const UncheckedIcon = (props: JSX.HTMLAttributes) => (
|
export const UncheckedIcon = (props: JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<Icon {...props}>
|
||||||
<path d="M19 5v14H5V5h14m0-2H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z"/>
|
<path d="M21.3 2.7v18.6H2.7V2.7h18.6m0-2.7H2.7A2.7 2.7 0 0 0 0 2.7v18.6A2.7 2.7 0 0 0 2.7 24h18.6a2.7 2.7 0 0 0 2.7-2.7V2.7A2.7 2.7 0 0 0 21.3 0z"/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const CheckedIcon = (props: JSX.HTMLAttributes) => (
|
export const CheckedIcon = (props: JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<Icon {...props}>
|
||||||
<path d="M19 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm-9 14l-5-5 1.4-1.4 3.6 3.6 7.6-7.6L19 8l-9 9z"/>
|
<path d="M21.3 0H2.7A2.7 2.7 0 0 0 0 2.7v18.6A2.7 2.7 0 0 0 2.7 24h18.6a2.7 2.7 0 0 0 2.7-2.7V2.7A2.7 2.7 0 0 0 21.3 0zm-12 18.7L2.7 12l1.8-1.9L9.3 15 19.5 4.8l1.8 1.9z"/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user