This commit is contained in:
Jake Archibald
2018-10-16 15:17:42 +01:00
parent 0cec90c7ca
commit ae904cd07f
8 changed files with 131 additions and 299 deletions

View File

@@ -1,7 +1,3 @@
/*
Note: These styles are temporary. They will be replaced before going live.
*/
.app { .app {
position: absolute; position: absolute;
left: 0; left: 0;

View File

@@ -0,0 +1,20 @@
import { h, Component } from 'preact';
import * as style from './style.scss';
import { UncheckedIcon, CheckedIcon } from '../../../lib/icons';
interface Props extends JSX.HTMLAttributes {}
interface State {}
export default class Checkbox extends Component<Props, State> {
render(props: Props) {
return (
<div class={style.checkbox}>
{props.checked
? <CheckedIcon class={`${style.icon} ${style.checked}`} />
: <UncheckedIcon class={style.icon} />
}
<input class={style.realCheckbox} type="checkbox" {...props}/>
</div>
);
}
}

View File

@@ -0,0 +1,21 @@
.checkbox {
display: inline-block;
position: relative;
--size: 24px;
}
.real-checkbox {
position: absolute;
opacity: 0;
pointer-events: none;
}
.icon {
display: block;
width: var(--size);
height: var(--size);
}
.checked {
fill: #34B9EB;
}

View File

@@ -31,7 +31,6 @@ import {
encoders, encoders,
encodersSupported, encodersSupported,
EncoderSupportMap, EncoderSupportMap,
encoderMap,
} 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';
@@ -39,6 +38,7 @@ 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';
const encoderOptionsComponentMap = { const encoderOptionsComponentMap = {
[identity.type]: undefined, [identity.type]: undefined,
@@ -56,11 +56,6 @@ const encoderOptionsComponentMap = {
[browserPDF.type]: undefined, [browserPDF.type]: undefined,
}; };
const titles = {
horizontal: ['Left Image', 'Right Image'],
vertical: ['Top Image', 'Bottom Image'],
};
interface Props { interface Props {
orientation: 'horizontal' | 'vertical'; orientation: 'horizontal' | 'vertical';
source?: SourceImage; source?: SourceImage;
@@ -144,74 +139,64 @@ export default class Options extends Component<Props, State> {
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type]; const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
return ( return (
<div class={`${style.options} ${style[orientation]}`}> <div class={style.options}>
<h2 class={style.title}> <h2 class={style.optionsTitle}>Process</h2>
{titles[orientation][imageIndex]} <label class={style.sectionEnabler}>
{', '} <Checkbox
{encoderMap[encoderState.type].label} name="resize.enable"
</h2> checked={!!preprocessorState.resize.enabled}
onChange={this.onPreprocessorEnabledChange}
/>
<span class={style.sectionEnablerLabel}>Resize</span>
</label>
{/*
{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}>
<input
name="quantizer.enable"
type="checkbox"
checked={!!preprocessorState.quantizer.enabled}
onChange={this.onPreprocessorEnabledChange}
/>
Quantize
</label>
{preprocessorState.quantizer.enabled &&
<QuantizerOptionsComponent
options={preprocessorState.quantizer}
onChange={this.onQuantizerOptionsChange}
/>
}
*/}
<div class={style.content}> {/*<section class={style.picker}>
<section class={style.picker}> {encoderSupportMap ?
{encoderSupportMap ? <select value={encoderState.type} onChange={this.onEncoderTypeChange}>
<select value={encoderState.type} onChange={this.onEncoderTypeChange}> {encoders.filter(encoder => encoderSupportMap[encoder.type]).map(encoder => (
{encoders.filter(encoder => encoderSupportMap[encoder.type]).map(encoder => ( <option value={encoder.type}>{encoder.label}</option>
<option value={encoder.type}>{encoder.label}</option> ))}
))} </select>
</select> :
: <select><option>Loading…</option></select>
<select><option>Loading</option></select>
}
</section>
{encoderState.type !== 'identity' && (
<div key="preprocessors" class={style.preprocessors}>
<label class={style.toggle}>
<input
name="resize.enable"
type="checkbox"
checked={!!preprocessorState.resize.enabled}
onChange={this.onPreprocessorEnabledChange}
/>
Resize
</label>
{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}>
<input
name="quantizer.enable"
type="checkbox"
checked={!!preprocessorState.quantizer.enabled}
onChange={this.onPreprocessorEnabledChange}
/>
Quantize
</label>
{preprocessorState.quantizer.enabled &&
<QuantizerOptionsComponent
options={preprocessorState.quantizer}
onChange={this.onQuantizerOptionsChange}
/>
}
</div>
)}
{EncoderOptionComponent &&
<EncoderOptionComponent
options={
// Casting options, as encoderOptionsComponentMap[encodeData.type] ensures
// the correct type, but typescript isn't smart enough.
encoderState.options as any
}
onChange={onEncoderOptionsChange}
/>
} }
</div> </section>
{EncoderOptionComponent &&
<EncoderOptionComponent
options={
// Casting options, as encoderOptionsComponentMap[encodeData.type] ensures
// the correct type, but typescript isn't smart enough.
encoderState.options as any
}
onChange={onEncoderOptionsChange}
/>
}
<div class={style.row}> <div class={style.row}>
<button onClick={this.onCopyToOtherClick}>Copy settings to other side</button> <button onClick={this.onCopyToOtherClick}>Copy settings to other side</button>
@@ -237,6 +222,7 @@ export default class Options extends Component<Props, State> {
</a> </a>
)} )}
</div> </div>
*/}
</div> </div>
); );
} }

View File

@@ -1,225 +1,30 @@
/* $horizontalPadding: 15px;
Note: These styles are temporary. They will be replaced before going live.
*/
.row {
padding: 5px;
margin: 0 10px;
}
.options { .options {
box-sizing: border-box; color: #fff;
padding: 0; width: 300px;
background: rgba(40,40,40,0.8); opacity: 0.8;
box-shadow: 0 1px 3px rgba(0,0,0,0.5); font-size: 1.2rem;
color: #eee;
overflow: auto;
z-index: 1;
opacity: 0.9;
transform-origin: 50% 140%;
transition: opacity 300ms linear;
animation: options-open 500ms cubic-bezier(.6,1.6,.6,1) forwards 1;
&.horizontal {
border-radius: 1px 1px 5px 5px;
width: 230px;
> .inner {
max-height: 80vh;
overflow: auto;
-webkit-overflow-scrolling: touch;
-ms-touch-action: pan-y;
touch-action: pan-y;
}
}
&.vertical {
opacity: 1;
margin: 0 5px 10px;
border-radius: 0 0 5px 5px;
}
&:hover, &:focus, &:focus-within {
opacity: 1;
}
@keyframes options-open {
from {
transform: translateY(100px) scale(.8);
}
}
.content {
max-height: calc(75vh - 100px);
overflow: auto;
touch-action: pan-y;
-webkit-overflow-scrolling: touch;
}
.picker {
margin: 5px 15px;
select {
display: block;
width: 100%;
box-sizing: border-box;
-webkit-appearance: none;
appearance: none;
padding: 10px 30px 10px 10px;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="25" height="5"><polygon fill="#fff" points="10,0 5,5 0,0"/></svg>') right center no-repeat;
background-color: var(--gray-dark);
opacity: 0.9;
border: none;
font: inherit;
color: white;
transition: box-shadow 150ms ease;
&:hover {
opacity: 1;
}
&:focus {
opacity: 1;
outline: none;
box-shadow: 0 0 0 2px var(--button-fg, #ccc);
}
}
}
.title {
display: flex;
align-items: center;
padding: 10px 15px;
margin: 0 0 12px;
background: rgba(0,0,0,0.9);
font: inherit;
}
label {
display: block;
padding: 5px;
margin: 0 10px;
display: flex;
flex-wrap: wrap;
// prevent labels from wrapping below checkboxes
> span {
flex: 1;
}
input[type=checkbox],
input[type=radio] {
flex: 0;
margin: 2px 8px 0 0;
}
range-input {
display: block;
flex: 1 0 100%;
margin: 2px 0;
}
}
hr {
height: 1px;
border: none;
margin: 5px 0;
box-shadow: inset 0 0.5px 0 rgba(0, 0, 0, 0.4), inset 0 -0.5px 0 rgba(255, 255, 255, 0.2);
}
} }
.picker { .options-title {
margin: 5px 15px; background: rgba(0, 0, 0, 0.9);
margin: 0;
select { padding: 10px $horizontalPadding;
display: block;
width: 100%;
box-sizing: border-box;
-webkit-appearance: none;
appearance: none;
padding: 10px 30px 10px 10px;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="25" height="5"><polygon fill="#fff" points="10,0 5,5 0,0"/></svg>') right center no-repeat;
background-color: var(--gray-dark);
opacity: 0.9;
border: none;
font: inherit;
color: white;
}
hr {
height: 1px;
border: none;
margin: 5px 0;
box-shadow: inset 0 0.5px 0 rgba(0, 0, 0, 0.4), inset 0 -0.5px 0 rgba(255, 255, 255, 0.2);
}
}
.size-details {
display: flex;
align-items: center;
padding: 5px 15px;
background: rgba(0,0,0,0.5);
}
.download {
flex: 0;
margin: 0 0 0 auto;
background: rgba(255,255,255,0.1);
border-radius: 50%;
padding: 5px;
width: 16px;
height: 16px;
text-decoration: none;
> svg {
width: 16px;
height: 16px;
fill: #fff;
}
&:hover {
background-color: rgba(255,255,255,0.3);
}
}
.size-details {
padding: 5px 15px;
background: rgba(0,0,0,0.5);
}
.size {
font-weight: normal; font-weight: normal;
font-size: 1.4rem;
border-bottom: 1px solid #000;
} }
.increase, .section-enabler {
.decrease { cursor: pointer;
font-style: italic; background: rgba(0, 0, 0, 0.8);
filter: #{"grayscale(calc(50% - var(--size-delta, 50) * 0.5%))"}; display: grid;
grid-template-columns: auto 1fr;
&:before { grid-gap: 0.5em;
content: ' ('; padding: 6px $horizontalPadding;
}
&:after {
content: ')';
}
} }
.increase { .section-enabler-label {
color: var(--negative); padding-top: 3px;
}
.decrease {
color: var(--positive);
}
.preprocessors {
padding: 5px 0;
margin: 5px 0;
box-shadow: inset 0 -.5px 0 rgba(0,0,0,0.4), 0 .5px 0 rgba(255,255,255,0.2);
}
.toggle {
display: flex;
position: relative;
align-content: center;
font-size: 14px;
box-shadow: inset 0 -.5px 0 rgba(0,0,0,0.4), 0 .5px 0 rgba(255,255,255,0.2);
} }

View File

@@ -29,3 +29,15 @@ export const RemoveIcon = (props: JSX.HTMLAttributes) => (
<path d="M19 13H5v-2h14v2z"/> <path d="M19 13H5v-2h14v2z"/>
</Icon> </Icon>
); );
export const UncheckedIcon = (props: JSX.HTMLAttributes) => (
<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"/>
</Icon>
);
export const CheckedIcon = (props: JSX.HTMLAttributes) => (
<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"/>
</Icon>
);

View File

@@ -1,7 +1,3 @@
/*
Note: These styles are temporary. They will be replaced before going live.
*/
@import './reset.scss'; @import './reset.scss';
html, body { html, body {

View File

@@ -1,7 +1,3 @@
/*
Note: These styles are temporary. They will be replaced before going live.
*/
button, a, img, input, select, textarea { button, a, img, input, select, textarea {
-webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-tap-highlight-color: rgba(0,0,0,0);
} }