mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-15 01:59:57 +00:00
Rollup build
This commit is contained in:
23
src/client/lazy-app/Compress/Options/Checkbox/index.tsx
Normal file
23
src/client/lazy-app/Compress/Options/Checkbox/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { h, Component } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import { UncheckedIcon, CheckedIcon } from '../../../icons';
|
||||
|
||||
interface Props extends preact.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} />
|
||||
)}
|
||||
{/* @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019 */}
|
||||
<input class={style.realCheckbox} type="checkbox" {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
22
src/client/lazy-app/Compress/Options/Checkbox/style.css
Normal file
22
src/client/lazy-app/Compress/Options/Checkbox/style.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.checkbox {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
--size: 17px;
|
||||
}
|
||||
|
||||
.real-checkbox {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
}
|
||||
|
||||
.checked {
|
||||
fill: #34b9eb;
|
||||
}
|
||||
66
src/client/lazy-app/Compress/Options/Expander/index.tsx
Normal file
66
src/client/lazy-app/Compress/Options/Expander/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { h, Component, ComponentChild, ComponentChildren } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import { transitionHeight } from '../../../util';
|
||||
|
||||
interface Props {
|
||||
children: ComponentChildren;
|
||||
}
|
||||
interface State {
|
||||
children: ComponentChildren;
|
||||
outgoingChildren: ComponentChildren;
|
||||
}
|
||||
|
||||
export default class Expander extends Component<Props, State> {
|
||||
static getDerivedStateFromProps(
|
||||
props: Props,
|
||||
state: State,
|
||||
): Partial<State> | null {
|
||||
if (!props.children && state.children) {
|
||||
return { children: props.children, outgoingChildren: state.children };
|
||||
}
|
||||
|
||||
if (props.children !== state.children) {
|
||||
return { children: props.children, outgoingChildren: undefined };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async componentDidUpdate(_: Props, previousState: State) {
|
||||
let heightFrom: number;
|
||||
let heightTo: number;
|
||||
|
||||
if (previousState.children && !this.state.children) {
|
||||
heightFrom = (this.base as HTMLElement).getBoundingClientRect().height;
|
||||
heightTo = 0;
|
||||
} else if (!previousState.children && this.state.children) {
|
||||
heightFrom = 0;
|
||||
heightTo = (this.base as HTMLElement).getBoundingClientRect().height;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
(this.base as HTMLElement).style.overflow = 'hidden';
|
||||
|
||||
await transitionHeight(this.base as HTMLElement, {
|
||||
duration: 300,
|
||||
from: heightFrom,
|
||||
to: heightTo,
|
||||
});
|
||||
|
||||
// Unset the height & overflow, so element changes do the right thing.
|
||||
(this.base as HTMLElement).style.height = '';
|
||||
(this.base as HTMLElement).style.overflow = '';
|
||||
|
||||
this.setState({ outgoingChildren: undefined });
|
||||
}
|
||||
|
||||
render({}: Props, { children, outgoingChildren }: State) {
|
||||
return (
|
||||
<div class={outgoingChildren ? style.childrenExiting : ''}>
|
||||
{outgoingChildren || children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
5
src/client/lazy-app/Compress/Options/Expander/style.css
Normal file
5
src/client/lazy-app/Compress/Options/Expander/style.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.children-exiting {
|
||||
& > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import PointerTracker from 'pointer-tracker';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
|
||||
const RETARGETED_EVENTS = ['focus', 'blur'];
|
||||
const UPDATE_EVENTS = ['input', 'change'];
|
||||
const REFLECTED_PROPERTIES = [
|
||||
'name',
|
||||
'min',
|
||||
'max',
|
||||
'step',
|
||||
'value',
|
||||
'disabled',
|
||||
];
|
||||
const REFLECTED_ATTRIBUTES = [
|
||||
'name',
|
||||
'min',
|
||||
'max',
|
||||
'step',
|
||||
'value',
|
||||
'disabled',
|
||||
];
|
||||
|
||||
function getPrescision(value: string): number {
|
||||
const afterDecimal = value.split('.')[1];
|
||||
return afterDecimal ? afterDecimal.length : 0;
|
||||
}
|
||||
|
||||
class RangeInputElement extends HTMLElement {
|
||||
private _input: HTMLInputElement;
|
||||
private _valueDisplay?: HTMLDivElement;
|
||||
private _ignoreChange = false;
|
||||
|
||||
static get observedAttributes() {
|
||||
return REFLECTED_ATTRIBUTES;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._input = document.createElement('input');
|
||||
this._input.type = 'range';
|
||||
this._input.className = style.input;
|
||||
|
||||
const tracker = new PointerTracker(this._input, {
|
||||
start: (): boolean => {
|
||||
if (tracker.currentPointers.length !== 0) return false;
|
||||
this._input.classList.add(style.touchActive);
|
||||
return true;
|
||||
},
|
||||
end: () => {
|
||||
this._input.classList.remove(style.touchActive);
|
||||
},
|
||||
});
|
||||
|
||||
for (const event of RETARGETED_EVENTS) {
|
||||
this._input.addEventListener(event, this._retargetEvent, true);
|
||||
}
|
||||
|
||||
for (const event of UPDATE_EVENTS) {
|
||||
this._input.addEventListener(event, this._update, true);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.contains(this._input)) return;
|
||||
this.innerHTML =
|
||||
`<div class="${style.thumbWrapper}">` +
|
||||
`<div class="${style.thumb}"></div>` +
|
||||
`<div class="${style.valueDisplay}"></div>` +
|
||||
'</div>';
|
||||
|
||||
this.insertBefore(this._input, this.firstChild);
|
||||
this._valueDisplay = this.querySelector(
|
||||
'.' + style.valueDisplay,
|
||||
) as HTMLDivElement;
|
||||
// Set inline styles (this is useful when used with frameworks which might clear inline styles)
|
||||
this._update();
|
||||
}
|
||||
|
||||
get labelPrecision(): string {
|
||||
return this.getAttribute('label-precision') || '';
|
||||
}
|
||||
|
||||
set labelPrecision(precision: string) {
|
||||
this.setAttribute('label-precision', precision);
|
||||
}
|
||||
|
||||
attributeChangedCallback(
|
||||
name: string,
|
||||
oldValue: string,
|
||||
newValue: string | null,
|
||||
) {
|
||||
if (this._ignoreChange) return;
|
||||
if (newValue === null) {
|
||||
this._input.removeAttribute(name);
|
||||
} else {
|
||||
this._input.setAttribute(name, newValue);
|
||||
}
|
||||
this._reflectAttributes();
|
||||
this._update();
|
||||
}
|
||||
|
||||
private _retargetEvent = (event: Event) => {
|
||||
event.stopImmediatePropagation();
|
||||
const retargetted = new Event(event.type, event);
|
||||
this.dispatchEvent(retargetted);
|
||||
};
|
||||
|
||||
private _update = () => {
|
||||
// Not connected?
|
||||
if (!this._valueDisplay) return;
|
||||
const value = Number(this.value) || 0;
|
||||
const min = Number(this.min) || 0;
|
||||
const max = Number(this.max) || 100;
|
||||
const labelPrecision =
|
||||
Number(this.labelPrecision) || getPrescision(this.step) || 0;
|
||||
const percent = (100 * (value - min)) / (max - min);
|
||||
const displayValue = labelPrecision
|
||||
? value.toFixed(labelPrecision)
|
||||
: Math.round(value).toString();
|
||||
|
||||
this._valueDisplay!.textContent = displayValue;
|
||||
this.style.setProperty('--value-percent', percent + '%');
|
||||
this.style.setProperty('--value-width', '' + displayValue.length);
|
||||
};
|
||||
|
||||
private _reflectAttributes() {
|
||||
this._ignoreChange = true;
|
||||
for (const attributeName of REFLECTED_ATTRIBUTES) {
|
||||
if (this._input.hasAttribute(attributeName)) {
|
||||
this.setAttribute(
|
||||
attributeName,
|
||||
this._input.getAttribute(attributeName)!,
|
||||
);
|
||||
} else {
|
||||
this.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
this._ignoreChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
interface RangeInputElement {
|
||||
name: string;
|
||||
min: string;
|
||||
max: string;
|
||||
step: string;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
for (const prop of REFLECTED_PROPERTIES) {
|
||||
Object.defineProperty(RangeInputElement.prototype, prop, {
|
||||
get() {
|
||||
return this._input[prop];
|
||||
},
|
||||
set(val) {
|
||||
this._input[prop] = val;
|
||||
this._reflectAttributes();
|
||||
this._update();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default RangeInputElement;
|
||||
|
||||
customElements.define('range-input', RangeInputElement);
|
||||
9
src/client/lazy-app/Compress/Options/Range/custom-els/RangeInput/missing-types.d.ts
vendored
Normal file
9
src/client/lazy-app/Compress/Options/Range/custom-els/RangeInput/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare module 'preact' {
|
||||
namespace createElement.JSX {
|
||||
interface IntrinsicElements {
|
||||
'range-input': HTMLAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,98 @@
|
||||
range-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 18px;
|
||||
width: 130px;
|
||||
margin: 2px;
|
||||
font: inherit;
|
||||
line-height: 16px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Disabled inputs are greyed out */
|
||||
range-input[disabled] {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
range-input::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 -0.5px 0 rgba(0, 0, 0, 0.3),
|
||||
inset 0 0.5px 0 rgba(255, 255, 255, 0.2), 0 0.5px 0 rgba(255, 255, 255, 0.3);
|
||||
background: linear-gradient(#34b9eb, #218ab1) 0 / var(--value-percent, 0%)
|
||||
100% no-repeat #eee;
|
||||
}
|
||||
|
||||
.input {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.thumb {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: var(--value-percent, 0%);
|
||||
margin-left: -6px;
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><circle cx="5" cy="5" r="1" fill="%235D509E" /></svg>')
|
||||
center no-repeat #34b9eb;
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-shadow: 0 0.5px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.thumb-wrapper {
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
bottom: 0;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.value-display {
|
||||
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="62" fill="none"><path fill="%2334B9EB" d="M27.3 27.3C25 29.6 17 35.8 17 43v3c0 3 2.5 5 3.2 5.8a6 6 0 1 1-8.5 0C12.6 51 15 49 15 46v-3c0-7.2-8-13.4-10.3-15.7A16 16 0 0 1 16 0a16 16 0 0 1 11.3 27.3z"/><circle cx="16" cy="56" r="1" fill="%235D509E"/></svg>')
|
||||
top center no-repeat;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
left: var(--value-percent, 0%);
|
||||
bottom: 3px;
|
||||
width: 32px;
|
||||
height: 62px;
|
||||
text-align: center;
|
||||
padding: 8px 3px 0;
|
||||
margin: 0 0 0 -16px;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.3));
|
||||
transform-origin: 50% 90%;
|
||||
opacity: 0.0001;
|
||||
transform: scale(0.2);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-size: calc(100% - var(--value-width, 3) / 5 * 0.2em);
|
||||
text-overflow: clip;
|
||||
text-shadow: 0 -0.5px 0 rgba(0, 0, 0, 0.4);
|
||||
transition: all 200ms ease;
|
||||
transition-property: opacity, transform;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.touch-active + .thumb-wrapper .value-display {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.touch-active + .thumb-wrapper .thumb {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
55
src/client/lazy-app/Compress/Options/Range/index.tsx
Normal file
55
src/client/lazy-app/Compress/Options/Range/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { h, Component } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import RangeInputElement from './custom-els/RangeInput';
|
||||
import './custom-els/RangeInput';
|
||||
import { linkRef } from 'shared/initial-app/util';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {}
|
||||
interface State {}
|
||||
|
||||
export default class Range extends Component<Props, State> {
|
||||
rangeWc?: RangeInputElement;
|
||||
|
||||
private onTextInput = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const value = input.value.trim();
|
||||
if (!value) return;
|
||||
this.rangeWc!.value = input.value;
|
||||
this.rangeWc!.dispatchEvent(
|
||||
new InputEvent('input', {
|
||||
bubbles: event.bubbles,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
render(props: Props) {
|
||||
const { children, ...otherProps } = props;
|
||||
|
||||
const { value, min, max, step } = props;
|
||||
|
||||
return (
|
||||
<label class={style.range}>
|
||||
<span class={style.labelText}>{children}</span>
|
||||
{/* On interaction, Safari gives focus to the first element in the label, so the
|
||||
<range-input> is deliberately first. */}
|
||||
<div class={style.rangeWcContainer}>
|
||||
<range-input
|
||||
ref={linkRef(this, 'rangeWc')}
|
||||
class={style.rangeWc}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
class={style.textInput}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
onInput={this.onTextInput}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
55
src/client/lazy-app/Compress/Options/Range/style.css
Normal file
55
src/client/lazy-app/Compress/Options/Range/style.css
Normal file
@@ -0,0 +1,55 @@
|
||||
.range {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
color: #fff; /* TEMP */
|
||||
}
|
||||
|
||||
.range-wc-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
grid-row: 2 / 3;
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
.range-wc {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-input {
|
||||
grid-row: 1 / 2;
|
||||
grid-column: 2 / 3;
|
||||
|
||||
text-align: right;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
border: none;
|
||||
padding: 2px 5px;
|
||||
box-sizing: border-box;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-position: under;
|
||||
width: 48px;
|
||||
position: relative;
|
||||
left: 5px;
|
||||
|
||||
&:focus {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* Remove the number controls */
|
||||
-moz-appearance: textfield;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
27
src/client/lazy-app/Compress/Options/Select/index.tsx
Normal file
27
src/client/lazy-app/Compress/Options/Select/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { h, Component } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {
|
||||
large?: boolean;
|
||||
}
|
||||
interface State {}
|
||||
|
||||
export default class Select extends Component<Props, State> {
|
||||
render(props: Props) {
|
||||
const { large, ...otherProps } = props;
|
||||
|
||||
return (
|
||||
<div class={style.select}>
|
||||
{/* @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019 */}
|
||||
<select
|
||||
class={`${style.builtinSelect} ${large ? style.large : ''}`}
|
||||
{...otherProps}
|
||||
/>
|
||||
<svg class={style.arrow} viewBox="0 0 10 5">
|
||||
<path d="M0 0l5 5 5-5z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
33
src/client/lazy-app/Compress/Options/Select/style.css
Normal file
33
src/client/lazy-app/Compress/Options/Select/style.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.select {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.builtin-select {
|
||||
background: #2f2f2f;
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
padding: 4px 25px 4px 10px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
fill: #fff;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.large {
|
||||
padding: 10px 35px 10px 10px;
|
||||
background: #151515;
|
||||
|
||||
& .arrow {
|
||||
right: 13px;
|
||||
}
|
||||
}
|
||||
190
src/client/lazy-app/Compress/Options/index.tsx
Normal file
190
src/client/lazy-app/Compress/Options/index.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import { cleanSet, cleanMerge } from '../../util/clean-modify';
|
||||
|
||||
import type { SourceImage, OutputType } from '..';
|
||||
import {
|
||||
EncoderOptions,
|
||||
EncoderState,
|
||||
ProcessorState,
|
||||
ProcessorOptions,
|
||||
encoderMap,
|
||||
} from '../../feature-meta';
|
||||
import Expander from './Expander';
|
||||
import Checkbox from './Checkbox';
|
||||
import Select from './Select';
|
||||
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
||||
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
||||
|
||||
interface Props {
|
||||
mobileView: boolean;
|
||||
source?: SourceImage;
|
||||
encoderState?: EncoderState;
|
||||
processorState: ProcessorState;
|
||||
onEncoderTypeChange(newType: OutputType): void;
|
||||
onEncoderOptionsChange(newOptions: EncoderOptions): void;
|
||||
onProcessorOptionsChange(newOptions: ProcessorState): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
supportedEncoderMap?: PartialButNotUndefined<typeof encoderMap>;
|
||||
}
|
||||
|
||||
type PartialButNotUndefined<T> = {
|
||||
[P in keyof T]: T[P];
|
||||
};
|
||||
|
||||
const supportedEncoderMapP: Promise<PartialButNotUndefined<
|
||||
typeof encoderMap
|
||||
>> = (async () => {
|
||||
const supportedEncoderMap: PartialButNotUndefined<typeof encoderMap> = {
|
||||
...encoderMap,
|
||||
};
|
||||
|
||||
// Filter out entries where the feature test fails
|
||||
await Promise.all(
|
||||
Object.entries(encoderMap).map(async ([encoderName, details]) => {
|
||||
if ('featureTest' in details && !(await details.featureTest())) {
|
||||
delete supportedEncoderMap[encoderName as keyof typeof encoderMap];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return supportedEncoderMap;
|
||||
})();
|
||||
|
||||
export default class Options extends Component<Props, State> {
|
||||
state: State = {
|
||||
supportedEncoderMap: undefined,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
supportedEncoderMapP.then((supportedEncoderMap) =>
|
||||
this.setState({ supportedEncoderMap }),
|
||||
);
|
||||
}
|
||||
|
||||
private onEncoderTypeChange = (event: Event) => {
|
||||
const el = event.currentTarget as HTMLSelectElement;
|
||||
|
||||
// The select element only has values matching encoder types,
|
||||
// so 'as' is safe here.
|
||||
const type = el.value as OutputType;
|
||||
this.props.onEncoderTypeChange(type);
|
||||
};
|
||||
|
||||
private onProcessorEnabledChange = (event: Event) => {
|
||||
const el = event.currentTarget as HTMLInputElement;
|
||||
const processor = el.name.split('.')[0] as keyof ProcessorState;
|
||||
|
||||
this.props.onProcessorOptionsChange(
|
||||
cleanSet(this.props.processorState, `${processor}.enabled`, el.checked),
|
||||
);
|
||||
};
|
||||
|
||||
private onQuantizerOptionsChange = (opts: ProcessorOptions['quantize']) => {
|
||||
this.props.onProcessorOptionsChange(
|
||||
cleanMerge(this.props.processorState, 'quantize', opts),
|
||||
);
|
||||
};
|
||||
|
||||
private onResizeOptionsChange = (opts: ProcessorOptions['resize']) => {
|
||||
this.props.onProcessorOptionsChange(
|
||||
cleanMerge(this.props.processorState, 'resize', opts),
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
{ source, encoderState, processorState, onEncoderOptionsChange }: Props,
|
||||
{ supportedEncoderMap }: State,
|
||||
) {
|
||||
const encoder = encoderState && encoderMap[encoderState.type];
|
||||
const EncoderOptionComponent =
|
||||
encoder && 'Options' in encoder ? encoder.Options : undefined;
|
||||
|
||||
return (
|
||||
<div class={style.optionsScroller}>
|
||||
<Expander>
|
||||
{!encoderState ? null : (
|
||||
<div>
|
||||
<h3 class={style.optionsTitle}>Edit</h3>
|
||||
<label class={style.sectionEnabler}>
|
||||
<Checkbox
|
||||
name="resize.enable"
|
||||
checked={!!processorState.resize.enabled}
|
||||
onChange={this.onProcessorEnabledChange}
|
||||
/>
|
||||
Resize
|
||||
</label>
|
||||
<Expander>
|
||||
{processorState.resize.enabled ? (
|
||||
<ResizeOptionsComponent
|
||||
isVector={Boolean(source && source.vectorImage)}
|
||||
inputWidth={source ? source.preprocessed.width : 1}
|
||||
inputHeight={source ? source.preprocessed.height : 1}
|
||||
options={processorState.resize}
|
||||
onChange={this.onResizeOptionsChange}
|
||||
/>
|
||||
) : null}
|
||||
</Expander>
|
||||
|
||||
<label class={style.sectionEnabler}>
|
||||
<Checkbox
|
||||
name="quantize.enable"
|
||||
checked={!!processorState.quantize.enabled}
|
||||
onChange={this.onProcessorEnabledChange}
|
||||
/>
|
||||
Reduce palette
|
||||
</label>
|
||||
<Expander>
|
||||
{processorState.quantize.enabled ? (
|
||||
<QuantOptionsComponent
|
||||
options={processorState.quantize}
|
||||
onChange={this.onQuantizerOptionsChange}
|
||||
/>
|
||||
) : null}
|
||||
</Expander>
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
|
||||
<h3 class={style.optionsTitle}>Compress</h3>
|
||||
|
||||
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
|
||||
{supportedEncoderMap ? (
|
||||
<Select
|
||||
value={encoderState ? encoderState.type : 'identity'}
|
||||
onChange={this.onEncoderTypeChange}
|
||||
large
|
||||
>
|
||||
<option value="identity">Original Image</option>
|
||||
{Object.entries(supportedEncoderMap).map(([type, encoder]) => (
|
||||
<option value={type}>{encoder.meta.label}</option>
|
||||
))}
|
||||
</Select>
|
||||
) : (
|
||||
<Select large>
|
||||
<option>Loading…</option>
|
||||
</Select>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<Expander>
|
||||
{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}
|
||||
/>
|
||||
)}
|
||||
</Expander>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
58
src/client/lazy-app/Compress/Options/style.css
Normal file
58
src/client/lazy-app/Compress/Options/style.css
Normal file
@@ -0,0 +1,58 @@
|
||||
.options-scroller {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
--horizontal-padding: 15px;
|
||||
}
|
||||
|
||||
.options-title {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
margin: 0;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
font-weight: normal;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
.option-text-first {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 87px 1fr;
|
||||
grid-gap: 0.7em;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
}
|
||||
|
||||
.option-one-cell {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
}
|
||||
|
||||
.option-input-first,
|
||||
.section-enabler {
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-gap: 0.7em;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
}
|
||||
|
||||
.section-enabler {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.options-section {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.text-field {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
font: inherit;
|
||||
border: none;
|
||||
padding: 2px 0 2px 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
Reference in New Issue
Block a user