Revealing sections

This commit is contained in:
Jake Archibald
2018-10-17 12:04:04 +01:00
parent 6ec0349f8f
commit 2138154ec5
8 changed files with 179 additions and 55 deletions

View File

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

View File

@@ -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"

View File

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

View File

@@ -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 {}

View File

@@ -1,7 +1,7 @@
.checkbox { .checkbox {
display: inline-block; display: inline-block;
position: relative; position: relative;
--size: 24px; --size: 17px;
} }
.real-checkbox { .real-checkbox {

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

View File

@@ -0,0 +1,10 @@
.expander {
overflow: hidden;
transition: height 200ms ease-in-out;
}
.children-exiting {
& > * {
pointer-events: none;
}
}

View File

@@ -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>
); );