mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-14 01:37:26 +00:00
(Almost the) rest of the redesign (#880)
* Load demo img * two-up styles * Back button * Button size tweak * Move back btn * Move options and back button into a single grid * Simpler max height * Responsive grid * Feed index into options * Option heading themes * More option styles * Changing checkbox position * Theme range input & use transforms * Range input underline theme * Checkbox color * Add toggle * Reorder * Arrow revealer * Round two-up thumb * Don't bundle CSS urls starting # * Results in progress * Fix Safari bugs * Download blobs * Loading spinner * Hook up download button * Different style for original image * Mobile design for results * Remove demo auto-loader * Remove redundant colors * Sticky headings
This commit is contained in:
@@ -90,7 +90,7 @@ export default function (resolveFileUrl) {
|
||||
}),
|
||||
postCSSUrl({
|
||||
url: ({ relativePath, url }) => {
|
||||
if (/^(https?|data):/.test(url)) return url;
|
||||
if (/^((https?|data):|#)/.test(url)) return url;
|
||||
const parsedPath = parsePath(relativePath);
|
||||
const source = readFileSync(
|
||||
resolvePath(dirname(path), relativePath),
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -6056,12 +6056,6 @@
|
||||
"integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-bytes": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz",
|
||||
"integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"preact": "^10.5.5",
|
||||
"preact-render-to-string": "^5.1.11",
|
||||
"prettier": "^2.1.2",
|
||||
"pretty-bytes": "^5.4.1",
|
||||
"rollup": "^2.33.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"serve": "^11.3.2",
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
}
|
||||
|
||||
.checked {
|
||||
fill: #34b9eb;
|
||||
fill: var(--main-theme-color);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import PointerTracker from 'pointer-tracker';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
|
||||
@@ -28,7 +27,7 @@ function getPrescision(value: string): number {
|
||||
|
||||
class RangeInputElement extends HTMLElement {
|
||||
private _input: HTMLInputElement;
|
||||
private _valueDisplay?: HTMLDivElement;
|
||||
private _valueDisplay?: HTMLSpanElement;
|
||||
private _ignoreChange = false;
|
||||
|
||||
static get observedAttributes() {
|
||||
@@ -41,15 +40,22 @@ class RangeInputElement extends HTMLElement {
|
||||
this._input.type = 'range';
|
||||
this._input.className = style.input;
|
||||
|
||||
const tracker = new PointerTracker(this._input, {
|
||||
start: (): boolean => {
|
||||
if (tracker.currentPointers.length !== 0) return false;
|
||||
let activePointer: number | undefined;
|
||||
|
||||
// Not using pointer-tracker here due to https://bugs.webkit.org/show_bug.cgi?id=219636.
|
||||
this.addEventListener('pointerdown', (event) => {
|
||||
if (activePointer) return;
|
||||
activePointer = event.pointerId;
|
||||
this._input.classList.add(style.touchActive);
|
||||
return true;
|
||||
},
|
||||
end: () => {
|
||||
const pointerUp = (event: PointerEvent) => {
|
||||
if (event.pointerId !== activePointer) return;
|
||||
activePointer = undefined;
|
||||
this._input.classList.remove(style.touchActive);
|
||||
},
|
||||
window.removeEventListener('pointerup', pointerUp);
|
||||
window.removeEventListener('pointercancel', pointerUp);
|
||||
};
|
||||
window.addEventListener('pointerup', pointerUp);
|
||||
window.addEventListener('pointercancel', pointerUp);
|
||||
});
|
||||
|
||||
for (const event of RETARGETED_EVENTS) {
|
||||
@@ -66,13 +72,13 @@ class RangeInputElement extends HTMLElement {
|
||||
this.innerHTML =
|
||||
`<div class="${style.thumbWrapper}">` +
|
||||
`<div class="${style.thumb}"></div>` +
|
||||
`<div class="${style.valueDisplay}"></div>` +
|
||||
`<div class="${style.valueDisplay}"><svg width="32" height="62"><path 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"/></svg><span></span></div>` +
|
||||
'</div>';
|
||||
|
||||
this.insertBefore(this._input, this.firstChild);
|
||||
this._valueDisplay = this.querySelector(
|
||||
'.' + style.valueDisplay,
|
||||
) as HTMLDivElement;
|
||||
'.' + style.valueDisplay + ' > span',
|
||||
) as HTMLSpanElement;
|
||||
// Set inline styles (this is useful when used with frameworks which might clear inline styles)
|
||||
this._update();
|
||||
}
|
||||
|
||||
@@ -23,10 +23,8 @@ range-input::before {
|
||||
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;
|
||||
background: linear-gradient(var(--main-theme-color), var(--main-theme-color))
|
||||
0 / var(--value-percent, 0%) 100% no-repeat var(--medium-light-gray);
|
||||
}
|
||||
|
||||
.input {
|
||||
@@ -41,14 +39,12 @@ range-input::before {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: var(--value-percent, 0%);
|
||||
left: 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;
|
||||
background: var(--main-theme-color);
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-shadow: 0 0.5px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.thumb-wrapper {
|
||||
@@ -58,21 +54,19 @@ range-input::before {
|
||||
bottom: 0;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
transform: translate(var(--value-percent, 0%), 0);
|
||||
}
|
||||
|
||||
.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%);
|
||||
left: 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);
|
||||
@@ -86,6 +80,19 @@ range-input::before {
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
|
||||
> svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: var(--main-theme-color);
|
||||
}
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.touch-active + .thumb-wrapper .value-display {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
box-sizing: border-box;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-decoration-color: var(--main-theme-color);
|
||||
text-underline-position: under;
|
||||
width: 48px;
|
||||
position: relative;
|
||||
|
||||
21
src/client/lazy-app/Compress/Options/Revealer/index.tsx
Normal file
21
src/client/lazy-app/Compress/Options/Revealer/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { h, Component } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import { Arrow } from '../../../icons';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {}
|
||||
interface State {}
|
||||
|
||||
export default class Revealer extends Component<Props, State> {
|
||||
render(props: Props) {
|
||||
return (
|
||||
<div class={style.checkbox}>
|
||||
{/* @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019 */}
|
||||
<input class={style.realCheckbox} type="checkbox" {...props} />
|
||||
<div class={style.arrow}>
|
||||
<Arrow />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
29
src/client/lazy-app/Compress/Options/Revealer/style.css
Normal file
29
src/client/lazy-app/Compress/Options/Revealer/style.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.checkbox {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
fill: var(--white);
|
||||
transition: transform 200ms ease;
|
||||
transform: rotate(-90deg);
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.real-checkbox {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&:checked + .arrow {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { h, Component } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import { Arrow } from 'client/lazy-app/icons';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {
|
||||
large?: boolean;
|
||||
@@ -18,9 +19,9 @@ export default class Select extends Component<Props, State> {
|
||||
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 class={style.arrow}>
|
||||
<Arrow />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
}
|
||||
|
||||
.builtin-select {
|
||||
background: #2f2f2f;
|
||||
background: var(--black);
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
padding: 4px 25px 4px 10px;
|
||||
padding: 7px 0;
|
||||
padding-right: 25px;
|
||||
padding-left: 10px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border: none;
|
||||
@@ -21,11 +23,12 @@
|
||||
transform: translateY(-50%);
|
||||
fill: #fff;
|
||||
width: 10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.large {
|
||||
padding: 10px 35px 10px 10px;
|
||||
background: #151515;
|
||||
background: var(--dark-gray);
|
||||
|
||||
& .arrow {
|
||||
right: 13px;
|
||||
|
||||
22
src/client/lazy-app/Compress/Options/Toggle/index.tsx
Normal file
22
src/client/lazy-app/Compress/Options/Toggle/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { h, Component } from 'preact';
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {}
|
||||
interface State {}
|
||||
|
||||
export default class Toggle extends Component<Props, State> {
|
||||
render(props: Props) {
|
||||
return (
|
||||
<div class={style.checkbox}>
|
||||
{/* @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019 */}
|
||||
<input class={style.realCheckbox} type="checkbox" {...props} />
|
||||
<div class={style.track}>
|
||||
<div class={style.thumbTrack}>
|
||||
<div class={style.thumb}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
55
src/client/lazy-app/Compress/Options/Toggle/style.css
Normal file
55
src/client/lazy-app/Compress/Options/Toggle/style.css
Normal file
@@ -0,0 +1,55 @@
|
||||
.checkbox {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.track {
|
||||
--thumb-size: 14px;
|
||||
background: var(--black);
|
||||
border-radius: 1000px;
|
||||
width: 24px;
|
||||
padding: 3px calc(var(--thumb-size) / 2 + 3px);
|
||||
}
|
||||
|
||||
.thumb {
|
||||
position: relative;
|
||||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
background: var(--less-light-gray);
|
||||
border-radius: 100%;
|
||||
transform: translateX(calc(var(--thumb-size) / -2));
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--main-theme-color);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
}
|
||||
|
||||
.thumb-track {
|
||||
transition: transform 200ms ease;
|
||||
}
|
||||
|
||||
.real-checkbox {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&:checked + .track {
|
||||
.thumb-track {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.thumb::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,20 @@ import {
|
||||
} from '../../feature-meta';
|
||||
import Expander from './Expander';
|
||||
import Checkbox from './Checkbox';
|
||||
import Toggle from './Toggle';
|
||||
import Select from './Select';
|
||||
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
||||
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
||||
|
||||
interface Props {
|
||||
index: 0 | 1;
|
||||
mobileView: boolean;
|
||||
source?: SourceImage;
|
||||
encoderState?: EncoderState;
|
||||
processorState: ProcessorState;
|
||||
onEncoderTypeChange(newType: OutputType): void;
|
||||
onEncoderOptionsChange(newOptions: EncoderOptions): void;
|
||||
onProcessorOptionsChange(newOptions: ProcessorState): void;
|
||||
onEncoderTypeChange(index: 0 | 1, newType: OutputType): void;
|
||||
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
||||
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -73,7 +75,7 @@ export default class Options extends Component<Props, State> {
|
||||
// The select element only has values matching encoder types,
|
||||
// so 'as' is safe here.
|
||||
const type = el.value as OutputType;
|
||||
this.props.onEncoderTypeChange(type);
|
||||
this.props.onEncoderTypeChange(this.props.index, type);
|
||||
};
|
||||
|
||||
private onProcessorEnabledChange = (event: Event) => {
|
||||
@@ -81,24 +83,31 @@ export default class Options extends Component<Props, State> {
|
||||
const processor = el.name.split('.')[0] as keyof ProcessorState;
|
||||
|
||||
this.props.onProcessorOptionsChange(
|
||||
this.props.index,
|
||||
cleanSet(this.props.processorState, `${processor}.enabled`, el.checked),
|
||||
);
|
||||
};
|
||||
|
||||
private onQuantizerOptionsChange = (opts: ProcessorOptions['quantize']) => {
|
||||
this.props.onProcessorOptionsChange(
|
||||
this.props.index,
|
||||
cleanMerge(this.props.processorState, 'quantize', opts),
|
||||
);
|
||||
};
|
||||
|
||||
private onResizeOptionsChange = (opts: ProcessorOptions['resize']) => {
|
||||
this.props.onProcessorOptionsChange(
|
||||
this.props.index,
|
||||
cleanMerge(this.props.processorState, 'resize', opts),
|
||||
);
|
||||
};
|
||||
|
||||
private onEncoderOptionsChange = (newOptions: EncoderOptions) => {
|
||||
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
||||
};
|
||||
|
||||
render(
|
||||
{ source, encoderState, processorState, onEncoderOptionsChange }: Props,
|
||||
{ source, encoderState, processorState }: Props,
|
||||
{ supportedEncoderMap }: State,
|
||||
) {
|
||||
const encoder = encoderState && encoderMap[encoderState.type];
|
||||
@@ -106,18 +115,24 @@ export default class Options extends Component<Props, State> {
|
||||
encoder && 'Options' in encoder ? encoder.Options : undefined;
|
||||
|
||||
return (
|
||||
<div class={style.optionsScroller}>
|
||||
<div
|
||||
class={
|
||||
style.optionsScroller +
|
||||
' ' +
|
||||
(encoderState ? '' : style.originalImage)
|
||||
}
|
||||
>
|
||||
<Expander>
|
||||
{!encoderState ? null : (
|
||||
<div>
|
||||
<h3 class={style.optionsTitle}>Edit</h3>
|
||||
<label class={style.sectionEnabler}>
|
||||
<Checkbox
|
||||
Resize
|
||||
<Toggle
|
||||
name="resize.enable"
|
||||
checked={!!processorState.resize.enabled}
|
||||
onChange={this.onProcessorEnabledChange}
|
||||
/>
|
||||
Resize
|
||||
</label>
|
||||
<Expander>
|
||||
{processorState.resize.enabled ? (
|
||||
@@ -132,12 +147,12 @@ export default class Options extends Component<Props, State> {
|
||||
</Expander>
|
||||
|
||||
<label class={style.sectionEnabler}>
|
||||
<Checkbox
|
||||
Reduce palette
|
||||
<Toggle
|
||||
name="quantize.enable"
|
||||
checked={!!processorState.quantize.enabled}
|
||||
onChange={this.onProcessorEnabledChange}
|
||||
/>
|
||||
Reduce palette
|
||||
</label>
|
||||
<Expander>
|
||||
{processorState.quantize.enabled ? (
|
||||
@@ -180,7 +195,7 @@ export default class Options extends Component<Props, State> {
|
||||
// the correct type, but typescript isn't smart enough.
|
||||
encoderState!.options as any
|
||||
}
|
||||
onChange={onEncoderOptionsChange}
|
||||
onChange={this.onEncoderOptionsChange}
|
||||
/>
|
||||
)}
|
||||
</Expander>
|
||||
|
||||
@@ -1,58 +1,83 @@
|
||||
.options-scroller {
|
||||
--horizontal-padding: 15px;
|
||||
border-radius: var(--scroller-radius);
|
||||
|
||||
/* At smaller widths, the multi-panel handles the scrolling */
|
||||
@media (min-width: 600px) {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
--horizontal-padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.options-title {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
background-color: var(--main-theme-color);
|
||||
color: var(--header-text-color);
|
||||
margin: 0;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
font-weight: normal;
|
||||
font-weight: bold;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid #000;
|
||||
border-bottom: 1px solid var(--off-black);
|
||||
transition: all 300ms ease-in-out;
|
||||
transition-property: background-color, color;
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.original-image .options-title {
|
||||
background-color: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.option-text-first {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 87px 1fr;
|
||||
grid-gap: 0.7em;
|
||||
gap: 0.7em;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
}
|
||||
|
||||
.option-toggle {
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 0.7em;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
}
|
||||
|
||||
.option-reveal {
|
||||
composes: option-toggle;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.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);
|
||||
composes: option-toggle;
|
||||
background: var(--dark-gray);
|
||||
padding: 15px var(--horizontal-padding);
|
||||
border-bottom: 1px solid var(--off-black);
|
||||
}
|
||||
|
||||
.options-section {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
background: var(--off-black);
|
||||
}
|
||||
|
||||
.text-field {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
background: var(--white);
|
||||
color: var(--black);
|
||||
font: inherit;
|
||||
border: none;
|
||||
padding: 2px 0 2px 10px;
|
||||
padding: 6px 0 6px 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -73,9 +73,14 @@ export default class TwoUp extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this._childrenChange();
|
||||
|
||||
this._handle.innerHTML = `<div class="${
|
||||
styles.scrubber
|
||||
}">${`<svg viewBox="0 0 27 20" fill="currentColor">${'<path d="M17 19.2l9.5-9.6L16.9 0zM9.6 0L0 9.6l9.6 9.6z"/>'}</svg>`}</div>`;
|
||||
// prettier-ignore
|
||||
this._handle.innerHTML =
|
||||
`<div class="${styles.scrubber}">${
|
||||
`<svg viewBox="0 0 27 20">${
|
||||
`<path class="${styles.arrowLeft}" d="M9.6 0L0 9.6l9.6 9.6z"/>` +
|
||||
`<path class="${styles.arrowRight}" d="M17 19.2l9.5-9.6L16.9 0z"/>`
|
||||
}</svg>
|
||||
`}</div>`;
|
||||
|
||||
if (!this._everConnected) {
|
||||
this._resetPosition();
|
||||
|
||||
@@ -2,12 +2,11 @@ two-up {
|
||||
display: grid;
|
||||
position: relative;
|
||||
--split-point: 0;
|
||||
--accent-color: #777;
|
||||
--track-color: var(--accent-color);
|
||||
--thumb-background: #fff;
|
||||
--track-color: rgb(0 0 0 / 0.6);
|
||||
--thumb-background: var(--black);
|
||||
--thumb-color: var(--accent-color);
|
||||
--thumb-size: 62px;
|
||||
--bar-size: 6px;
|
||||
--bar-size: 9px;
|
||||
--bar-touch-size: 30px;
|
||||
}
|
||||
|
||||
@@ -37,8 +36,6 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
|
||||
height: 100%;
|
||||
width: var(--bar-size);
|
||||
margin: 0 auto;
|
||||
box-shadow: inset calc(var(--bar-size) / 2) 0 0 rgba(0, 0, 0, 0.1),
|
||||
0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
background: var(--track-color);
|
||||
}
|
||||
|
||||
@@ -47,14 +44,11 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform-origin: 50% 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: var(--thumb-size);
|
||||
height: calc(var(--thumb-size) * 0.9);
|
||||
height: var(--thumb-size);
|
||||
background: var(--thumb-background);
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: var(--thumb-size);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
color: var(--thumb-color);
|
||||
box-sizing: border-box;
|
||||
padding: 0 calc(var(--thumb-size) * 0.24);
|
||||
@@ -64,6 +58,14 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.arrow-left {
|
||||
fill: var(--pink);
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
fill: var(--blue);
|
||||
}
|
||||
|
||||
two-up[orientation='vertical'] .two-up-handle {
|
||||
width: auto;
|
||||
height: var(--bar-touch-size);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
ToggleBackgroundIcon,
|
||||
AddIcon,
|
||||
RemoveIcon,
|
||||
BackIcon,
|
||||
ToggleBackgroundActiveIcon,
|
||||
RotateIcon,
|
||||
} from '../../icons';
|
||||
@@ -28,7 +27,6 @@ interface Props {
|
||||
rightCompressed?: ImageData;
|
||||
leftImgContain: boolean;
|
||||
rightImgContain: boolean;
|
||||
onBack: () => void;
|
||||
onPreprocessorChange: (newState: PreprocessorState) => void;
|
||||
}
|
||||
|
||||
@@ -255,7 +253,7 @@ export default class Output extends Component<Props, State> {
|
||||
};
|
||||
|
||||
render(
|
||||
{ mobileView, leftImgContain, rightImgContain, source, onBack }: Props,
|
||||
{ mobileView, leftImgContain, rightImgContain, source }: Props,
|
||||
{ scale, editingScale, altBackground }: State,
|
||||
) {
|
||||
const leftDraw = this.leftDrawable();
|
||||
@@ -314,12 +312,6 @@ export default class Output extends Component<Props, State> {
|
||||
</pinch-zoom>
|
||||
</two-up>
|
||||
|
||||
<div class={style.back}>
|
||||
<button class={style.button} onClick={onBack}>
|
||||
<BackIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class={style.controls}>
|
||||
<div class={style.zoomControls}>
|
||||
<button class={style.button} onClick={this.zoomOut}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.output {
|
||||
composes: abs-fill from global;
|
||||
display: contents;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -9,18 +9,17 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #000;
|
||||
opacity: 0;
|
||||
opacity: 0.8;
|
||||
transition: opacity 500ms ease;
|
||||
}
|
||||
|
||||
&.alt-background::before {
|
||||
opacity: 0.6;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.two-up {
|
||||
composes: abs-fill from global;
|
||||
--accent-color: var(--button-fg);
|
||||
}
|
||||
|
||||
.pinch-zoom {
|
||||
@@ -41,16 +40,15 @@
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 9px 84px;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
contain: content;
|
||||
grid-area: header;
|
||||
align-self: center;
|
||||
padding: 9px 66px;
|
||||
position: relative;
|
||||
|
||||
/* Allow clicks to fall through to the pinch zoom area */
|
||||
pointer-events: none;
|
||||
@@ -60,11 +58,9 @@
|
||||
|
||||
@media (min-width: 860px) {
|
||||
padding: 9px;
|
||||
top: auto;
|
||||
left: 320px;
|
||||
right: 320px;
|
||||
bottom: 0;
|
||||
flex-wrap: wrap-reverse;
|
||||
grid-area: viewportOpts;
|
||||
align-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,13 +145,6 @@
|
||||
border-bottom: 1px dashed #999;
|
||||
}
|
||||
|
||||
.back {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.buttons-no-wrap {
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { h, Component } from 'preact';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import * as style from './style.css';
|
||||
|
||||
interface Props {
|
||||
blob: Blob;
|
||||
compareTo?: Blob;
|
||||
}
|
||||
|
||||
interface State {}
|
||||
|
||||
export default class FileSize extends Component<Props, State> {
|
||||
render({ blob, compareTo }: Props) {
|
||||
let comparison: preact.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>
|
||||
{prettyBytes(blob.size)} {comparison}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,25 @@
|
||||
import { h, Component, ComponentChildren, ComponentChild } from 'preact';
|
||||
import { h, Component, Fragment } from 'preact';
|
||||
|
||||
import * as style from './style.css';
|
||||
import 'add-css:./style.css';
|
||||
import FileSize from './FileSize';
|
||||
import {
|
||||
DownloadIcon,
|
||||
CopyAcrossIcon,
|
||||
CopyAcrossIconProps,
|
||||
} from 'client/lazy-app/icons';
|
||||
import 'shared/custom-els/loading-spinner';
|
||||
import { SourceImage } from '../';
|
||||
import prettyBytes from './pretty-bytes';
|
||||
import { Arrow, DownloadIcon } from 'client/lazy-app/icons';
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
source?: SourceImage;
|
||||
imageFile?: File;
|
||||
downloadUrl?: string;
|
||||
children: ComponentChildren;
|
||||
copyDirection: CopyAcrossIconProps['copyDirection'];
|
||||
buttonPosition: keyof typeof buttonPositionClass;
|
||||
onCopyToOtherClick(): void;
|
||||
flipSide: boolean;
|
||||
typeLabel: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showLoadingState: boolean;
|
||||
}
|
||||
|
||||
const buttonPositionClass = {
|
||||
'stack-right': style.stackRight,
|
||||
'download-right': style.downloadRight,
|
||||
'download-left': style.downloadLeft,
|
||||
};
|
||||
|
||||
const loadingReactionDelay = 500;
|
||||
|
||||
export default class Results extends Component<Props, State> {
|
||||
@@ -56,11 +44,6 @@ export default class Results extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private onCopyToOtherClick = (event: Event) => {
|
||||
event.preventDefault();
|
||||
this.props.onCopyToOtherClick();
|
||||
};
|
||||
|
||||
private onDownload = () => {
|
||||
// GA can’t do floats. So we round to ints. We're deliberately rounding to nearest kilobyte to
|
||||
// avoid cases where exact image sizes leak something interesting about the user.
|
||||
@@ -76,59 +59,83 @@ export default class Results extends Component<Props, State> {
|
||||
};
|
||||
|
||||
render(
|
||||
{
|
||||
source,
|
||||
imageFile,
|
||||
downloadUrl,
|
||||
children,
|
||||
copyDirection,
|
||||
buttonPosition,
|
||||
}: Props,
|
||||
{ source, imageFile, downloadUrl, flipSide, typeLabel }: Props,
|
||||
{ showLoadingState }: State,
|
||||
) {
|
||||
return (
|
||||
<div class={`${style.results} ${buttonPositionClass[buttonPosition]}`}>
|
||||
<div class={style.resultData}>
|
||||
{children ? <div class={style.resultTitle}>{children}</div> : null}
|
||||
{!imageFile || showLoadingState ? (
|
||||
'Working…'
|
||||
) : (
|
||||
<FileSize
|
||||
blob={imageFile}
|
||||
compareTo={
|
||||
source && imageFile !== source.file ? source.file : undefined
|
||||
const prettySize = imageFile && prettyBytes(imageFile.size);
|
||||
const isOriginal = !source || !imageFile || source.file === imageFile;
|
||||
let diff;
|
||||
let percent;
|
||||
|
||||
if (source && imageFile) {
|
||||
diff = imageFile.size / source.file.size;
|
||||
const absolutePercent = Math.round(Math.abs(diff) * 100);
|
||||
percent = diff > 1 ? absolutePercent - 100 : 100 - absolutePercent;
|
||||
}
|
||||
/>
|
||||
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
(flipSide ? style.resultsRight : style.resultsLeft) +
|
||||
' ' +
|
||||
(isOriginal ? style.isOriginal : '')
|
||||
}
|
||||
>
|
||||
<div class={style.expandArrow}>
|
||||
<Arrow />
|
||||
</div>
|
||||
<div class={style.bubble}>
|
||||
<div class={style.bubbleInner}>
|
||||
<div class={style.sizeInfo}>
|
||||
<div class={style.fileSize}>
|
||||
{prettySize ? (
|
||||
<Fragment>
|
||||
{prettySize.value}{' '}
|
||||
<span class={style.unit}>{prettySize.unit}</span>
|
||||
<span class={style.typeLabel}> {typeLabel}</span>
|
||||
</Fragment>
|
||||
) : (
|
||||
'…'
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class={style.copyToOther}
|
||||
title="Copy settings to other side"
|
||||
onClick={this.onCopyToOtherClick}
|
||||
</div>
|
||||
<div class={style.percentInfo}>
|
||||
<svg
|
||||
viewBox="0 0 1 2"
|
||||
class={style.bigArrow}
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<CopyAcrossIcon
|
||||
class={style.copyIcon}
|
||||
copyDirection={copyDirection}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div class={style.download}>
|
||||
{downloadUrl && imageFile && (
|
||||
<path d="M1 0v2L0 1z" />
|
||||
</svg>
|
||||
<div class={style.percentOutput}>
|
||||
{diff && diff !== 1 && (
|
||||
<span class={style.sizeDirection}>
|
||||
{diff < 1 ? '↓' : '↑'}
|
||||
</span>
|
||||
)}
|
||||
<span class={style.sizeValue}>{percent || 0}</span>
|
||||
<span class={style.percentChar}>%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
class={`${style.downloadLink} ${
|
||||
showLoadingState ? style.downloadLinkDisable : ''
|
||||
}`}
|
||||
class={showLoadingState ? style.downloadDisable : style.download}
|
||||
href={downloadUrl}
|
||||
download={imageFile.name}
|
||||
download={imageFile ? imageFile.name : ''}
|
||||
title="Download"
|
||||
onClick={this.onDownload}
|
||||
>
|
||||
<DownloadIcon class={style.downloadIcon} />
|
||||
</a>
|
||||
)}
|
||||
{showLoadingState && <loading-spinner class={style.spinner} />}
|
||||
<svg class={style.downloadBlobs} viewBox="0 0 89.6 86.9">
|
||||
<title>Download</title>
|
||||
<path d="M27.3 72c-8-4-15.6-12.3-16.9-21-1.2-8.7 4-17.8 10.5-26s14.4-15.6 24-16 21.2 6 28.6 16.5c7.4 10.5 10.8 25 6.6 34S64.1 71.8 54 73.6c-10.2 2-18.7 2.3-26.7-1.6z" />
|
||||
<path d="M19.8 24.8c4.3-7.8 13-15 21.8-15.7 8.7-.8 17.5 4.8 25.4 11.8 7.8 6.9 14.8 15.2 14.7 24.9s-7.1 20.7-18 27.6c-10.8 6.8-25.5 9.5-34.2 4.8S18.1 61.6 16.7 51.4c-1.3-10.3-1.3-18.8 3-26.6z" />
|
||||
</svg>
|
||||
<div class={style.downloadIcon}>
|
||||
<DownloadIcon />
|
||||
</div>
|
||||
{showLoadingState && <loading-spinner />}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
27
src/client/lazy-app/Compress/Results/pretty-bytes.ts
Normal file
27
src/client/lazy-app/Compress/Results/pretty-bytes.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Based on https://www.npmjs.com/package/pretty-bytes
|
||||
// Modified so the units are returned separately.
|
||||
|
||||
const UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
interface PrettyBytesResult {
|
||||
value: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export default function prettyBytes(number: number): PrettyBytesResult {
|
||||
const isNegative = number < 0;
|
||||
const prefix = isNegative ? '-' : '';
|
||||
|
||||
if (isNegative) number = -number;
|
||||
if (number < 1) return { value: prefix + number, unit: UNITS[0] };
|
||||
|
||||
const exponent = Math.min(
|
||||
Math.floor(Math.log10(number) / 3),
|
||||
UNITS.length - 1,
|
||||
);
|
||||
|
||||
return {
|
||||
unit: UNITS[exponent],
|
||||
value: prefix + (number / Math.pow(1000, exponent)).toPrecision(3),
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono Numbers';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
/* Just 0132456789. https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@700&text=0123456789 */
|
||||
src: url('data:font/woff;base64,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')
|
||||
format('woff');
|
||||
}
|
||||
|
||||
@keyframes action-enter {
|
||||
from {
|
||||
transform: rotate(-90deg);
|
||||
@@ -15,117 +24,336 @@
|
||||
}
|
||||
|
||||
.results {
|
||||
--download-overflow-size: 9px;
|
||||
background: rgba(0, 0, 0, 0.67);
|
||||
border-radius: 5px;
|
||||
display: grid;
|
||||
grid-template-columns: [text] 1fr [copy-button] auto [download-button] auto;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
font-size: 1rem;
|
||||
|
||||
@media (min-width: 400px) {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
grid-template-columns: max-content [bubble] 1fr [download] max-content;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.result-data {
|
||||
grid-row: 1;
|
||||
grid-column: text;
|
||||
display: flex;
|
||||
--download-overflow-size: 30px;
|
||||
background: none;
|
||||
border-radius: none;
|
||||
grid-template-columns: [download] auto [bubble] 1fr;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.download-right {
|
||||
grid-template-columns: [copy-button] auto [text] 1fr [download-button] auto;
|
||||
}
|
||||
|
||||
.download-left {
|
||||
grid-template-columns: [download-button] auto [text] 1fr [copy-button] auto;
|
||||
}
|
||||
|
||||
.stack-right {
|
||||
& .result-data {
|
||||
padding: 0 15px;
|
||||
margin-bottom: calc(var(--download-overflow-size) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.result-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 0.4em;
|
||||
.expand-arrow {
|
||||
fill: var(--white);
|
||||
transform: rotate(180deg);
|
||||
margin: 0 1rem;
|
||||
align-self: center;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:focus & {
|
||||
fill: var(--main-theme-color);
|
||||
}
|
||||
|
||||
[content-expanded] & {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
--size: 15px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
}
|
||||
}
|
||||
|
||||
.size-delta {
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
.file-size {
|
||||
}
|
||||
|
||||
.bubble {
|
||||
align-self: center;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-left: 0.3em;
|
||||
width: max-content;
|
||||
grid-row: 1;
|
||||
grid-column: bubble;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-image-source: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='186.5' height='280.3' viewBox='0 0 186.5 280.3'%3E%3Cpath fill='rgba(30,31,29,0.69)' d='M181.5 0H16.4a5 5 0 00-5 5v134L0 146.5h11.4v128.8a5 5 0 005 5h165.1a5 5 0 005-5V5a5 5 0 00-5-5z'/%3E%3Cpath fill='rgba(0,0,0,0.23)' d='M16.4 1a4 4 0 00-4 4v134.5l-.5.3-8.6 5.7h9v129.8a4 4 0 004 4h165.2a4 4 0 004-4V5a4 4 0 00-4-4H16.4m0-1h165.1a5 5 0 015 5v270.3a5 5 0 01-5 5H16.4a5 5 0 01-5-5V146.5H0l11.4-7.5V5a5 5 0 015-5z'/%3E%3C/svg%3E");
|
||||
border-image-slice: 12 12 12 17 fill;
|
||||
border-image-width: 12px 12px 12px 17px;
|
||||
border-image-repeat: repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.size-increase {
|
||||
color: #e35050;
|
||||
.bubble-inner {
|
||||
display: grid;
|
||||
grid-template-columns: [size-info] 1fr [percent-info] auto;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
position: relative;
|
||||
--main-padding: 1px;
|
||||
--speech-padding: 2.1rem;
|
||||
padding: var(--main-padding) var(--main-padding) var(--main-padding)
|
||||
var(--speech-padding);
|
||||
gap: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.size-decrease {
|
||||
color: #50e3c2;
|
||||
.unit {
|
||||
color: var(--main-theme-color);
|
||||
}
|
||||
|
||||
.type-label {
|
||||
@media (min-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.size-info {
|
||||
background: var(--dark-gray);
|
||||
border-radius: 19px;
|
||||
align-self: center;
|
||||
justify-self: start;
|
||||
grid-column: size-info;
|
||||
grid-row: 1;
|
||||
justify-self: start;
|
||||
padding: 0.6rem 1.2rem;
|
||||
margin: 0.4rem 0;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
border-radius: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.percent-info {
|
||||
align-self: center;
|
||||
margin-left: 1rem;
|
||||
margin-right: 0.3rem;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
--arrow-width: 16px;
|
||||
grid-template-columns: [arrow] var(--arrow-width) [data] auto;
|
||||
grid-column: percent-info;
|
||||
grid-row: 1;
|
||||
--shadow-direction: -1px;
|
||||
filter: drop-shadow(var(--shadow-direction) 0 0 rgba(0, 0, 0, 0.67));
|
||||
}
|
||||
}
|
||||
|
||||
.big-arrow {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
fill: var(--main-theme-color);
|
||||
grid-column: arrow;
|
||||
grid-row: 1;
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.percent-output {
|
||||
grid-column: data;
|
||||
grid-row: 1;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
line-height: 1;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
background: var(--main-theme-color);
|
||||
--radius: 4px;
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
--padding-arrow-side: 0.6rem;
|
||||
--padding-other-side: 1.1rem;
|
||||
padding: 0.7rem var(--padding-other-side);
|
||||
padding-left: var(--padding-arrow-side);
|
||||
}
|
||||
}
|
||||
|
||||
.size-direction {
|
||||
font-weight: 700;
|
||||
align-self: center;
|
||||
font-family: sans-serif;
|
||||
opacity: 0.76;
|
||||
text-shadow: 0 2px rgba(0, 0, 0, 0.3);
|
||||
font-size: 1.5rem;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.size-value {
|
||||
font-family: 'Roboto Mono Numbers';
|
||||
font-size: 2.6rem;
|
||||
text-shadow: 0 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.percent-char {
|
||||
align-self: start;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
opacity: 0.76;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
|
||||
.download {
|
||||
--size: 59px;
|
||||
width: calc(var(--size) + var(--download-overflow-size));
|
||||
height: calc(var(--size) + var(--download-overflow-size));
|
||||
position: relative;
|
||||
grid-row: 1;
|
||||
grid-column: download-button;
|
||||
background: #34b9eb;
|
||||
--size: 38px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
grid-column: download;
|
||||
margin: calc(var(--download-overflow-size) / -2) 0;
|
||||
margin-right: calc(var(--download-overflow-size) / -3);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
align-self: center;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
--size: 63px;
|
||||
}
|
||||
|
||||
loading-spinner {
|
||||
grid-area: 1 / 1;
|
||||
position: relative;
|
||||
--color: var(--white);
|
||||
--size: 21px;
|
||||
top: 0px;
|
||||
left: 1px;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
top: -1px;
|
||||
left: 2px;
|
||||
--size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download-link {
|
||||
.download-blobs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
path {
|
||||
fill: var(--hot-theme-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.download-icon {
|
||||
grid-area: 1 / 1;
|
||||
|
||||
svg {
|
||||
--size: 19px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
fill: var(--white);
|
||||
position: relative;
|
||||
top: 3px;
|
||||
left: 1px;
|
||||
animation: action-enter 0.2s;
|
||||
grid-area: 1/1;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
--size: 27px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download-link-disable {
|
||||
.download-disable {
|
||||
composes: download;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
.download-icon svg {
|
||||
opacity: 0;
|
||||
transform: rotate(90deg);
|
||||
animation: action-leave 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.download-icon,
|
||||
.copy-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));
|
||||
.results-left {
|
||||
composes: results;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
--color: #fff;
|
||||
--delay: 0;
|
||||
--size: 22px;
|
||||
grid-area: 1/1;
|
||||
.results-right {
|
||||
composes: results;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
grid-template-columns: [bubble] 1fr [download] auto;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
@media (min-width: 600px) {
|
||||
justify-self: end;
|
||||
|
||||
&::before {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download {
|
||||
margin-left: calc(var(--download-overflow-size) / -3);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.bubble-inner {
|
||||
@media (min-width: 600px) {
|
||||
padding: var(--main-padding) var(--speech-padding) var(--main-padding)
|
||||
var(--main-padding);
|
||||
grid-template-columns: [percent-info] auto [size-info] 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.percent-info {
|
||||
@media (min-width: 600px) {
|
||||
grid-template-columns: [data] auto [arrow] var(--arrow-width);
|
||||
--shadow-direction: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.percent-output {
|
||||
@media (min-width: 600px) {
|
||||
border-radius: var(--radius) 0 0 var(--radius);
|
||||
padding-left: var(--padding-other-side);
|
||||
padding-right: var(--padding-arrow-side);
|
||||
}
|
||||
}
|
||||
|
||||
.big-arrow {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
.copy-to-other {
|
||||
grid-row: 1;
|
||||
grid-column: copy-button;
|
||||
composes: unbutton from global;
|
||||
composes: download;
|
||||
|
||||
background: #656565;
|
||||
.is-original {
|
||||
.big-arrow {
|
||||
fill: transparent;
|
||||
}
|
||||
.percent-output {
|
||||
background: none;
|
||||
}
|
||||
.download-blobs path {
|
||||
fill: var(--black);
|
||||
}
|
||||
.unit {
|
||||
color: var(--white);
|
||||
opacity: 0.76;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.panel-heading {
|
||||
background: gray;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
height: 0px;
|
||||
overflow: auto;
|
||||
|
||||
@@ -31,7 +31,7 @@ import Results from './Results';
|
||||
import WorkerBridge from '../worker-bridge';
|
||||
import { resize } from 'features/processors/resize/client';
|
||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||
import { CopyAcrossIconProps, ExpandIcon } from '../icons';
|
||||
import { Arrow, ExpandIcon } from '../icons';
|
||||
|
||||
export type OutputType = EncoderType | 'identity';
|
||||
|
||||
@@ -319,7 +319,7 @@ export default class Compress extends Component<Props, State> {
|
||||
this.setState({ mobileView: this.widthQuery.matches });
|
||||
};
|
||||
|
||||
private onEncoderTypeChange(index: 0 | 1, newType: OutputType): void {
|
||||
private onEncoderTypeChange = (index: 0 | 1, newType: OutputType): void => {
|
||||
this.setState({
|
||||
sides: cleanSet(
|
||||
this.state.sides,
|
||||
@@ -332,12 +332,12 @@ export default class Compress extends Component<Props, State> {
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onProcessorOptionsChange(
|
||||
private onProcessorOptionsChange = (
|
||||
index: 0 | 1,
|
||||
options: ProcessorState,
|
||||
): void {
|
||||
): void => {
|
||||
this.setState({
|
||||
sides: cleanSet(
|
||||
this.state.sides,
|
||||
@@ -345,9 +345,12 @@ export default class Compress extends Component<Props, State> {
|
||||
options,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onEncoderOptionsChange(index: 0 | 1, options: EncoderOptions): void {
|
||||
private onEncoderOptionsChange = (
|
||||
index: 0 | 1,
|
||||
options: EncoderOptions,
|
||||
): void => {
|
||||
this.setState({
|
||||
sides: cleanSet(
|
||||
this.state.sides,
|
||||
@@ -355,7 +358,7 @@ export default class Compress extends Component<Props, State> {
|
||||
options,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps: Props): void {
|
||||
if (nextProps.file !== this.props.file) {
|
||||
@@ -794,50 +797,30 @@ export default class Compress extends Component<Props, State> {
|
||||
|
||||
const options = sides.map((side, index) => (
|
||||
<Options
|
||||
index={index as 0 | 1}
|
||||
source={source}
|
||||
mobileView={mobileView}
|
||||
processorState={side.latestSettings.processorState}
|
||||
encoderState={side.latestSettings.encoderState}
|
||||
onEncoderTypeChange={this.onEncoderTypeChange.bind(
|
||||
this,
|
||||
index as 0 | 1,
|
||||
)}
|
||||
onEncoderOptionsChange={this.onEncoderOptionsChange.bind(
|
||||
this,
|
||||
index as 0 | 1,
|
||||
)}
|
||||
onProcessorOptionsChange={this.onProcessorOptionsChange.bind(
|
||||
this,
|
||||
index as 0 | 1,
|
||||
)}
|
||||
onEncoderTypeChange={this.onEncoderTypeChange}
|
||||
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
||||
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
||||
/>
|
||||
));
|
||||
|
||||
const copyDirections = (mobileView
|
||||
? ['down', 'up']
|
||||
: ['right', 'left']) as CopyAcrossIconProps['copyDirection'][];
|
||||
|
||||
const results = sides.map((side, index) => (
|
||||
<Results
|
||||
downloadUrl={side.downloadUrl}
|
||||
imageFile={side.file}
|
||||
source={source}
|
||||
loading={loading || side.loading}
|
||||
copyDirection={copyDirections[index]}
|
||||
onCopyToOtherClick={this.onCopyToOtherClick.bind(this, index as 0 | 1)}
|
||||
buttonPosition={mobileView ? 'stack-right' : buttonPositions[index]}
|
||||
>
|
||||
{!mobileView
|
||||
? null
|
||||
: [
|
||||
<ExpandIcon class={style.expandIcon} key="expand-icon" />,
|
||||
`${resultTitles[index]} (${
|
||||
flipSide={mobileView || index === 1}
|
||||
typeLabel={
|
||||
side.latestSettings.encoderState
|
||||
? encoderMap[side.latestSettings.encoderState.type].meta.label
|
||||
: 'Original Image'
|
||||
})`,
|
||||
]}
|
||||
</Results>
|
||||
}
|
||||
/>
|
||||
));
|
||||
|
||||
// For rendering, we ideally want the settings that were used to create the
|
||||
@@ -862,26 +845,38 @@ export default class Compress extends Component<Props, State> {
|
||||
rightCompressed={rightImageData}
|
||||
leftImgContain={leftImgContain}
|
||||
rightImgContain={rightImgContain}
|
||||
onBack={onBack}
|
||||
preprocessorState={preprocessorState}
|
||||
onPreprocessorChange={this.onPreprocessorChange}
|
||||
/>
|
||||
<button class={style.back} onClick={onBack}>
|
||||
<svg viewBox="0 0 61 53.3">
|
||||
<title>Back</title>
|
||||
<path
|
||||
class={style.backBlob}
|
||||
d="M0 25.6c-.5-7.1 4.1-14.5 10-19.1S23.4.1 32.2 0c8.8 0 19 1.6 24.4 8s5.6 17.8 1.7 27a29.7 29.7 0 01-20.5 18c-8.4 1.5-17.3-2.6-24.5-8S.5 32.6.1 25.6z"
|
||||
/>
|
||||
<path
|
||||
class={style.backX}
|
||||
d="M41.6 17.1l-2-2.1-8.3 8.2-8.2-8.2-2 2 8.2 8.3-8.3 8.2 2.1 2 8.2-8.1 8.3 8.2 2-2-8.2-8.3z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{mobileView ? (
|
||||
<div class={style.options}>
|
||||
<multi-panel class={style.multiPanel} open-one-only>
|
||||
{results[0]}
|
||||
{options[0]}
|
||||
{results[1]}
|
||||
{options[1]}
|
||||
<div class={style.options1Theme}>{results[0]}</div>
|
||||
<div class={style.options1Theme}>{options[0]}</div>
|
||||
<div class={style.options2Theme}>{results[1]}</div>
|
||||
<div class={style.options2Theme}>{options[1]}</div>
|
||||
</multi-panel>
|
||||
</div>
|
||||
) : (
|
||||
[
|
||||
<div class={style.options} key="options0">
|
||||
<div class={style.options1} key="options1">
|
||||
{options[0]}
|
||||
{results[0]}
|
||||
</div>,
|
||||
<div class={style.options} key="options1">
|
||||
<div class={style.options2} key="options2">
|
||||
{options[1]}
|
||||
{results[1]}
|
||||
</div>,
|
||||
|
||||
@@ -3,39 +3,77 @@
|
||||
height: 100%;
|
||||
contain: strict;
|
||||
display: grid;
|
||||
align-items: end;
|
||||
align-content: end;
|
||||
grid-template-rows: 1fr auto;
|
||||
grid-template-rows: max-content 1fr;
|
||||
grid-template-areas:
|
||||
'header'
|
||||
'opts';
|
||||
|
||||
--options-radius: 7px;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: 100%;
|
||||
grid-template-rows: max-content 1fr;
|
||||
grid-template-columns: max-content 1fr max-content;
|
||||
grid-template-areas:
|
||||
'header header header'
|
||||
'optsLeft viewportOpts optsRight';
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
position: relative;
|
||||
color: #fff;
|
||||
opacity: 0.9;
|
||||
font-size: 1.2rem;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
width: calc(100% - 60px);
|
||||
max-height: calc(100% - 104px);
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
grid-area: opts;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr max-content;
|
||||
align-content: end;
|
||||
align-self: end;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
max-height: calc(100% - 75px);
|
||||
width: 300px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
max-height: calc(100% - 40px);
|
||||
.options-1-theme {
|
||||
--main-theme-color: var(--pink);
|
||||
--hot-theme-color: var(--hot-pink);
|
||||
--header-text-color: var(--white);
|
||||
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
--scroller-radius: 0 var(--options-radius) var(--options-radius) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.options-2-theme {
|
||||
--main-theme-color: var(--blue);
|
||||
--hot-theme-color: var(--deep-blue);
|
||||
--header-text-color: var(--dark-text);
|
||||
--scroller-radius: var(--options-radius) var(--options-radius) 0 0;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
--scroller-radius: var(--options-radius) 0 0 var(--options-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.options-1 {
|
||||
composes: options;
|
||||
composes: options-1-theme;
|
||||
grid-area: optsLeft;
|
||||
}
|
||||
|
||||
.options-2 {
|
||||
composes: options;
|
||||
composes: options-2-theme;
|
||||
grid-area: optsRight;
|
||||
}
|
||||
|
||||
.multi-panel {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -61,15 +99,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
transform: rotate(180deg);
|
||||
margin-left: -12px;
|
||||
.back {
|
||||
composes: unbutton from global;
|
||||
position: relative;
|
||||
grid-area: header;
|
||||
margin: 9px;
|
||||
justify-self: start;
|
||||
align-self: start;
|
||||
|
||||
& > svg {
|
||||
width: 47px;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
margin: 14px;
|
||||
|
||||
& > svg {
|
||||
width: 58px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[content-expanded] .expand-icon {
|
||||
transform: none;
|
||||
.back-blob {
|
||||
fill: var(--hot-pink);
|
||||
opacity: 0.77;
|
||||
}
|
||||
|
||||
:focus .expand-icon {
|
||||
fill: #34b9eb;
|
||||
.back-x {
|
||||
fill: var(--white);
|
||||
}
|
||||
|
||||
@@ -11,12 +11,6 @@ const Icon = (props: preact.JSX.HTMLAttributes) => (
|
||||
/>
|
||||
);
|
||||
|
||||
export const DownloadIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
<Icon {...props}>
|
||||
<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>
|
||||
);
|
||||
|
||||
export const ToggleBackgroundIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
<Icon {...props}>
|
||||
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.9 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h10a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z" />
|
||||
@@ -67,42 +61,14 @@ export const ExpandIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
</Icon>
|
||||
);
|
||||
|
||||
export const BackIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
<Icon {...props}>
|
||||
<path d="M20 11H7.8l5.6-5.6L12 4l-8 8 8 8 1.4-1.4L7.8 13H20v-2z" />
|
||||
</Icon>
|
||||
export const Arrow = () => (
|
||||
<svg viewBox="0 -1.95 9.8 9.8">
|
||||
<path d="M8.2.2a1 1 0 011.4 1.4l-4 4a1 1 0 01-1.4 0l-4-4A1 1 0 011.6.2l3.3 3.3L8.2.2z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const copyAcrossRotations = {
|
||||
up: 90,
|
||||
right: 180,
|
||||
down: -90,
|
||||
left: 0,
|
||||
};
|
||||
|
||||
export interface CopyAcrossIconProps extends preact.JSX.HTMLAttributes {
|
||||
copyDirection: keyof typeof copyAcrossRotations;
|
||||
}
|
||||
|
||||
export const CopyAcrossIcon = (props: CopyAcrossIconProps) => {
|
||||
const { copyDirection, ...otherProps } = props;
|
||||
const id = 'point-' + copyDirection;
|
||||
const rotation = copyAcrossRotations[copyDirection];
|
||||
|
||||
return (
|
||||
<Icon {...otherProps}>
|
||||
<defs>
|
||||
<clipPath id={id}>
|
||||
<path
|
||||
d="M-12-12v24h24v-24zM4.5 2h-4v3l-5-5 5-5v3h4z"
|
||||
transform={`translate(12 13) rotate(${rotation})`}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path
|
||||
clip-path={`url(#${id})`}
|
||||
d="M19 3h-4.2c-.4-1.2-1.5-2-2.8-2s-2.4.8-2.8 2H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm-7 0a1 1 0 0 1 0 2c-.6 0-1-.4-1-1s.4-1 1-1z"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
};
|
||||
export const DownloadIcon = () => (
|
||||
<svg viewBox="0 0 23.9 24.9">
|
||||
<path d="M6.6 2.7h-4v13.2h2.7A2.7 2.7 0 018 18.6a2.7 2.7 0 002.6 2.6h2.7a2.7 2.7 0 002.6-2.6 2.7 2.7 0 012.7-2.7h2.6V2.7h-4a1.3 1.3 0 110-2.7h4A2.7 2.7 0 0124 2.7v18.5a2.7 2.7 0 01-2.7 2.7H2.7A2.7 2.7 0 010 21.2V2.7A2.7 2.7 0 012.7 0h4a1.3 1.3 0 010 2.7zm4 7.4V1.3a1.3 1.3 0 112.7 0v8.8L15 8.4a1.3 1.3 0 011.9 1.8l-4 4a1.3 1.3 0 01-1.9 0l-4-4A1.3 1.3 0 019 8.4z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||
import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
import linkState from 'linkstate';
|
||||
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
|
||||
|
||||
export const encode = (
|
||||
signal: AbortSignal,
|
||||
@@ -209,12 +210,12 @@ export class Options extends Component<Props, State> {
|
||||
) {
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Lossless
|
||||
<Checkbox
|
||||
checked={lossless}
|
||||
onChange={this._inputChange('lossless', 'boolean')}
|
||||
/>
|
||||
Lossless
|
||||
</label>
|
||||
<Expander>
|
||||
{!lossless && (
|
||||
@@ -242,22 +243,22 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Separate alpha quality
|
||||
<Checkbox
|
||||
checked={separateAlpha}
|
||||
onChange={this._inputChange('separateAlpha', 'boolean')}
|
||||
/>
|
||||
Separate alpha quality
|
||||
</label>
|
||||
<Expander>
|
||||
{separateAlpha && (
|
||||
<div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Lossless alpha
|
||||
<Checkbox
|
||||
checked={losslessAlpha}
|
||||
onChange={this._inputChange('losslessAlpha', 'boolean')}
|
||||
/>
|
||||
Lossless alpha
|
||||
</label>
|
||||
<Expander>
|
||||
{!losslessAlpha && (
|
||||
@@ -288,23 +289,23 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
<label class={style.optionReveal}>
|
||||
<Revealer
|
||||
checked={showAdvanced}
|
||||
onChange={linkState(this, 'showAdvanced')}
|
||||
/>
|
||||
Show advanced settings
|
||||
Advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced && (
|
||||
<div>
|
||||
{/*<label class={style.optionInputFirst}>
|
||||
{/*<label class={style.optionToggle}>
|
||||
Grayscale
|
||||
<Checkbox
|
||||
data-set-state="grayscale"
|
||||
checked={grayscale}
|
||||
onChange={this._inputChange('grayscale', 'boolean')}
|
||||
/>
|
||||
Grayscale
|
||||
</label>*/}
|
||||
<Expander>
|
||||
{!grayscale && !lossless && (
|
||||
|
||||
@@ -123,23 +123,23 @@ export class Options extends Component<Props, State> {
|
||||
// gathering the data.
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Lossless
|
||||
<Checkbox
|
||||
name="lossless"
|
||||
checked={lossless}
|
||||
onChange={this._inputChange('lossless', 'boolean')}
|
||||
/>
|
||||
Lossless
|
||||
</label>
|
||||
<Expander>
|
||||
{lossless && (
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Slight loss
|
||||
<Checkbox
|
||||
name="slightLoss"
|
||||
checked={slightLoss}
|
||||
onChange={this._inputChange('slightLoss', 'boolean')}
|
||||
/>
|
||||
Slight loss
|
||||
</label>
|
||||
)}
|
||||
</Expander>
|
||||
@@ -157,7 +157,8 @@ export class Options extends Component<Props, State> {
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Auto edge filter
|
||||
<Checkbox
|
||||
name="autoEdgeFilter"
|
||||
checked={autoEdgePreservingFilter}
|
||||
@@ -166,7 +167,6 @@ export class Options extends Component<Props, State> {
|
||||
'boolean',
|
||||
)}
|
||||
/>
|
||||
Auto edge filter
|
||||
</label>
|
||||
<Expander>
|
||||
{!autoEdgePreservingFilter && (
|
||||
@@ -188,13 +188,13 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Progressive rendering
|
||||
<Checkbox
|
||||
name="progressive"
|
||||
checked={progressive}
|
||||
onChange={this._inputChange('progressive', 'boolean')}
|
||||
/>
|
||||
Progressive rendering
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
|
||||
@@ -12,6 +12,7 @@ import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
|
||||
|
||||
export function encode(
|
||||
signal: AbortSignal,
|
||||
@@ -116,12 +117,12 @@ export class Options extends Component<Props, State> {
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
<label class={style.optionReveal}>
|
||||
<Revealer
|
||||
checked={showAdvanced}
|
||||
onChange={linkState(this, 'showAdvanced')}
|
||||
/>
|
||||
Show advanced settings
|
||||
Advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced ? (
|
||||
@@ -141,13 +142,13 @@ export class Options extends Component<Props, State> {
|
||||
<Expander>
|
||||
{options.color_space === MozJpegColorSpace.YCbCr ? (
|
||||
<div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Auto subsample chroma
|
||||
<Checkbox
|
||||
name="auto_subsample"
|
||||
checked={options.auto_subsample}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Auto subsample chroma
|
||||
</label>
|
||||
<Expander>
|
||||
{options.auto_subsample ? null : (
|
||||
@@ -164,13 +165,13 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Separate chroma quality
|
||||
<Checkbox
|
||||
name="separate_chroma_quality"
|
||||
checked={options.separate_chroma_quality}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Separate chroma quality
|
||||
</label>
|
||||
<Expander>
|
||||
{options.separate_chroma_quality ? (
|
||||
@@ -190,35 +191,35 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
) : null}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Pointless spec compliance
|
||||
<Checkbox
|
||||
name="baseline"
|
||||
checked={options.baseline}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Pointless spec compliance
|
||||
</label>
|
||||
<Expander>
|
||||
{options.baseline ? null : (
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Progressive rendering
|
||||
<Checkbox
|
||||
name="progressive"
|
||||
checked={options.progressive}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Progressive rendering
|
||||
</label>
|
||||
)}
|
||||
</Expander>
|
||||
<Expander>
|
||||
{options.baseline ? (
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Optimize Huffman table
|
||||
<Checkbox
|
||||
name="optimize_coding"
|
||||
checked={options.optimize_coding}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Optimize Huffman table
|
||||
</label>
|
||||
) : null}
|
||||
</Expander>
|
||||
@@ -251,33 +252,33 @@ export class Options extends Component<Props, State> {
|
||||
<option value="8">Peterson et al</option>
|
||||
</Select>
|
||||
</label>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Trellis multipass
|
||||
<Checkbox
|
||||
name="trellis_multipass"
|
||||
checked={options.trellis_multipass}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Trellis multipass
|
||||
</label>
|
||||
<Expander>
|
||||
{options.trellis_multipass ? (
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Optimize zero block runs
|
||||
<Checkbox
|
||||
name="trellis_opt_zero"
|
||||
checked={options.trellis_opt_zero}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Optimize zero block runs
|
||||
</label>
|
||||
) : null}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Optimize after trellis quantization
|
||||
<Checkbox
|
||||
name="trellis_opt_table"
|
||||
checked={options.trellis_opt_table}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Optimize after trellis quantization
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
|
||||
@@ -12,6 +12,7 @@ import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
|
||||
|
||||
export const encode = (
|
||||
signal: AbortSignal,
|
||||
@@ -179,7 +180,8 @@ export class Options extends Component<Props, State> {
|
||||
Slight loss:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Discrete tone image
|
||||
{/*
|
||||
Although there are 3 different kinds of image hint, webp only
|
||||
seems to do something with the 'graph' type, and I don't really
|
||||
@@ -190,7 +192,6 @@ export class Options extends Component<Props, State> {
|
||||
checked={options.image_hint === WebPImageHint.WEBP_HINT_GRAPH}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Discrete tone image
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
@@ -224,23 +225,23 @@ export class Options extends Component<Props, State> {
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
<label class={style.optionReveal}>
|
||||
<Revealer
|
||||
checked={showAdvanced}
|
||||
onChange={linkState(this, 'showAdvanced')}
|
||||
/>
|
||||
Show advanced settings
|
||||
Advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced ? (
|
||||
<div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Compress alpha
|
||||
<Checkbox
|
||||
name="alpha_compression"
|
||||
checked={!!options.alpha_compression}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Compress alpha
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
@@ -264,13 +265,13 @@ export class Options extends Component<Props, State> {
|
||||
Alpha filter quality:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Auto adjust filter strength
|
||||
<Checkbox
|
||||
name="autofilter"
|
||||
checked={!!options.autofilter}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Auto adjust filter strength
|
||||
</label>
|
||||
<Expander>
|
||||
{options.autofilter ? null : (
|
||||
@@ -287,13 +288,13 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Strong filter
|
||||
<Checkbox
|
||||
name="filter_type"
|
||||
checked={!!options.filter_type}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Strong filter
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
@@ -306,13 +307,13 @@ export class Options extends Component<Props, State> {
|
||||
Filter sharpness:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Sharp RGB→YUV conversion
|
||||
<Checkbox
|
||||
name="use_sharp_yuv"
|
||||
checked={!!options.use_sharp_yuv}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Sharp RGB→YUV conversion
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
@@ -382,24 +383,24 @@ export class Options extends Component<Props, State> {
|
||||
// gathering the data.
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Lossless
|
||||
<Checkbox
|
||||
name="lossless"
|
||||
checked={!!options.lossless}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Lossless
|
||||
</label>
|
||||
{options.lossless
|
||||
? this._losslessSpecificOptions(options)
|
||||
: this._lossySpecificOptions(options)}
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Preserve transparent data
|
||||
<Checkbox
|
||||
name="exact"
|
||||
checked={!!options.exact}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Preserve transparent data
|
||||
</label>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import Select from 'client/lazy-app/Compress/Options/Select';
|
||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
import linkState from 'linkstate';
|
||||
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
|
||||
|
||||
export const encode = (
|
||||
signal: AbortSignal,
|
||||
@@ -154,12 +155,12 @@ export class Options extends Component<Props, State> {
|
||||
) {
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Lossless
|
||||
<Checkbox
|
||||
checked={lossless}
|
||||
onChange={this._inputChange('lossless', 'boolean')}
|
||||
/>
|
||||
Lossless
|
||||
</label>
|
||||
<Expander>
|
||||
{lossless && (
|
||||
@@ -190,12 +191,12 @@ export class Options extends Component<Props, State> {
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Separate alpha quality
|
||||
<Checkbox
|
||||
checked={separateAlpha}
|
||||
onChange={this._inputChange('separateAlpha', 'boolean')}
|
||||
/>
|
||||
Separate alpha quality
|
||||
</label>
|
||||
<Expander>
|
||||
{separateAlpha && (
|
||||
@@ -212,12 +213,12 @@ export class Options extends Component<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
<label class={style.optionReveal}>
|
||||
<Revealer
|
||||
checked={showAdvanced}
|
||||
onChange={linkState(this, 'showAdvanced')}
|
||||
/>
|
||||
Show advanced settings
|
||||
Advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced && (
|
||||
@@ -278,7 +279,8 @@ export class Options extends Component<Props, State> {
|
||||
<option value={Csp.kYIQ}>YIQ</option>
|
||||
</Select>
|
||||
</label>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Random matrix
|
||||
<Checkbox
|
||||
checked={useRandomMatrix}
|
||||
onChange={this._inputChange(
|
||||
@@ -286,7 +288,6 @@ export class Options extends Component<Props, State> {
|
||||
'boolean',
|
||||
)}
|
||||
/>
|
||||
Random matrix
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -285,33 +285,33 @@ export class Options extends Component<Props, State> {
|
||||
</label>
|
||||
<Expander>
|
||||
{isWorkerOptions(options) ? (
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Premultiply alpha channel
|
||||
<Checkbox
|
||||
name="premultiply"
|
||||
checked={options.premultiply}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Premultiply alpha channel
|
||||
</label>
|
||||
) : null}
|
||||
{isWorkerOptions(options) ? (
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Linear RGB
|
||||
<Checkbox
|
||||
name="linearRGB"
|
||||
checked={options.linearRGB}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
Linear RGB
|
||||
</label>
|
||||
) : null}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<label class={style.optionToggle}>
|
||||
Maintain aspect ratio
|
||||
<Checkbox
|
||||
name="maintainAspect"
|
||||
checked={maintainAspect}
|
||||
onChange={linkState(this, 'maintainAspect')}
|
||||
/>
|
||||
Maintain aspect ratio
|
||||
</label>
|
||||
<Expander>
|
||||
{maintainAspect ? null : (
|
||||
|
||||
@@ -46,7 +46,7 @@ const demos = [
|
||||
url: logo,
|
||||
iconUrl: logoIcon,
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
const blobAnimImport =
|
||||
!__PRERENDER__ && matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
display: grid;
|
||||
grid-template-rows: 1fr max-content max-content;
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark-text);
|
||||
color: var(--dim-text);
|
||||
}
|
||||
|
||||
.blob-canvas {
|
||||
|
||||
@@ -2,18 +2,19 @@ html {
|
||||
--pink: #ff3385;
|
||||
--hot-pink: #ff0066;
|
||||
--white: #fff;
|
||||
--black: #000;
|
||||
--off-black: #1d1d1d;
|
||||
--blue: #5fb4e4;
|
||||
--dim-blue: #0a7bcc;
|
||||
--deep-blue: #09f;
|
||||
--light-blue: #76c8ff;
|
||||
--less-light-gray: #bcbcbc;
|
||||
--medium-light-gray: #d1d1d1;
|
||||
--light-gray: #eaeaea;
|
||||
--dark-text: #343a3e;
|
||||
--dark-gray: #333;
|
||||
--dim-text: #343a3e;
|
||||
--dark-text: #142630;
|
||||
|
||||
/* Old stuff: */
|
||||
--gray-dark: rgba(0, 0, 0, 0.8);
|
||||
|
||||
--button-fg-color: 95, 180, 228;
|
||||
--button-fg: rgb(95, 180, 228);
|
||||
|
||||
--negative: rgb(207, 113, 127);
|
||||
--positive: rgb(149, 212, 159);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user