mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 00:37:19 +00:00
Merge branch 'plan-b' into dev
This commit is contained in:
@@ -13,11 +13,11 @@ import {
|
|||||||
encoderMap,
|
encoderMap,
|
||||||
} from '../../feature-meta';
|
} from '../../feature-meta';
|
||||||
import Expander from './Expander';
|
import Expander from './Expander';
|
||||||
import Checkbox from './Checkbox';
|
|
||||||
import Toggle from './Toggle';
|
import Toggle from './Toggle';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
||||||
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
||||||
|
import { CLIIcon, SwapIcon } from 'client/lazy-app/icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: 0 | 1;
|
index: 0 | 1;
|
||||||
@@ -28,6 +28,8 @@ interface Props {
|
|||||||
onEncoderTypeChange(index: 0 | 1, newType: OutputType): void;
|
onEncoderTypeChange(index: 0 | 1, newType: OutputType): void;
|
||||||
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
||||||
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
||||||
|
onCopyToOtherSideClick(index: 0 | 1): void;
|
||||||
|
onCopyCliClick(index: 0 | 1): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@@ -106,6 +108,14 @@ export default class Options extends Component<Props, State> {
|
|||||||
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onCopyCliClick = () => {
|
||||||
|
this.props.onCopyCliClick(this.props.index);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCopyToOtherSideClick = () => {
|
||||||
|
this.props.onCopyToOtherSideClick(this.props.index);
|
||||||
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
{ source, encoderState, processorState }: Props,
|
{ source, encoderState, processorState }: Props,
|
||||||
{ supportedEncoderMap }: State,
|
{ supportedEncoderMap }: State,
|
||||||
@@ -125,7 +135,25 @@ export default class Options extends Component<Props, State> {
|
|||||||
<Expander>
|
<Expander>
|
||||||
{!encoderState ? null : (
|
{!encoderState ? null : (
|
||||||
<div>
|
<div>
|
||||||
<h3 class={style.optionsTitle}>Edit</h3>
|
<h3 class={style.optionsTitle}>
|
||||||
|
<div class={style.titleAndButtons}>
|
||||||
|
Edit
|
||||||
|
<button
|
||||||
|
class={style.cliButton}
|
||||||
|
title="Copy npx command"
|
||||||
|
onClick={this.onCopyCliClick}
|
||||||
|
>
|
||||||
|
<CLIIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class={style.copyOverButton}
|
||||||
|
title="Copy settings to other side"
|
||||||
|
onClick={this.onCopyToOtherSideClick}
|
||||||
|
>
|
||||||
|
<SwapIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
<label class={style.sectionEnabler}>
|
<label class={style.sectionEnabler}>
|
||||||
Resize
|
Resize
|
||||||
<Toggle
|
<Toggle
|
||||||
|
|||||||
@@ -81,3 +81,37 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-and-buttons {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-auto-columns: max-content;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-button {
|
||||||
|
composes: unbutton from global;
|
||||||
|
svg {
|
||||||
|
--size: 20px;
|
||||||
|
display: block;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cli-button {
|
||||||
|
composes: title-button;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--header-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-over-button {
|
||||||
|
composes: title-button;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--header-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component, Fragment } from 'preact';
|
||||||
import type PinchZoom from './custom-els/PinchZoom';
|
import type PinchZoom from './custom-els/PinchZoom';
|
||||||
import type { ScaleToOpts } from './custom-els/PinchZoom';
|
import type { ScaleToOpts } from './custom-els/PinchZoom';
|
||||||
import './custom-els/PinchZoom';
|
import './custom-els/PinchZoom';
|
||||||
@@ -262,59 +262,60 @@ export default class Output extends Component<Props, State> {
|
|||||||
const originalImage = source && source.preprocessed;
|
const originalImage = source && source.preprocessed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Fragment>
|
||||||
class={`${style.output} ${altBackground ? style.altBackground : ''}`}
|
<div
|
||||||
>
|
class={`${style.output} ${altBackground ? style.altBackground : ''}`}
|
||||||
<two-up
|
|
||||||
legacy-clip-compat
|
|
||||||
class={style.twoUp}
|
|
||||||
orientation={mobileView ? 'vertical' : 'horizontal'}
|
|
||||||
// Event redirecting. See onRetargetableEvent.
|
|
||||||
onTouchStartCapture={this.onRetargetableEvent}
|
|
||||||
onTouchEndCapture={this.onRetargetableEvent}
|
|
||||||
onTouchMoveCapture={this.onRetargetableEvent}
|
|
||||||
onPointerDownCapture={this.onRetargetableEvent}
|
|
||||||
onMouseDownCapture={this.onRetargetableEvent}
|
|
||||||
onWheelCapture={this.onRetargetableEvent}
|
|
||||||
>
|
>
|
||||||
<pinch-zoom
|
<two-up
|
||||||
class={style.pinchZoom}
|
legacy-clip-compat
|
||||||
onChange={this.onPinchZoomLeftChange}
|
class={style.twoUp}
|
||||||
ref={linkRef(this, 'pinchZoomLeft')}
|
orientation={mobileView ? 'vertical' : 'horizontal'}
|
||||||
|
// Event redirecting. See onRetargetableEvent.
|
||||||
|
onTouchStartCapture={this.onRetargetableEvent}
|
||||||
|
onTouchEndCapture={this.onRetargetableEvent}
|
||||||
|
onTouchMoveCapture={this.onRetargetableEvent}
|
||||||
|
onPointerDownCapture={this.onRetargetableEvent}
|
||||||
|
onMouseDownCapture={this.onRetargetableEvent}
|
||||||
|
onWheelCapture={this.onRetargetableEvent}
|
||||||
>
|
>
|
||||||
<canvas
|
<pinch-zoom
|
||||||
class={style.pinchTarget}
|
class={style.pinchZoom}
|
||||||
ref={linkRef(this, 'canvasLeft')}
|
onChange={this.onPinchZoomLeftChange}
|
||||||
width={leftDraw && leftDraw.width}
|
ref={linkRef(this, 'pinchZoomLeft')}
|
||||||
height={leftDraw && leftDraw.height}
|
>
|
||||||
style={{
|
<canvas
|
||||||
width: originalImage ? originalImage.width : '',
|
class={style.pinchTarget}
|
||||||
height: originalImage ? originalImage.height : '',
|
ref={linkRef(this, 'canvasLeft')}
|
||||||
objectFit: leftImgContain ? 'contain' : '',
|
width={leftDraw && leftDraw.width}
|
||||||
}}
|
height={leftDraw && leftDraw.height}
|
||||||
/>
|
style={{
|
||||||
</pinch-zoom>
|
width: originalImage ? originalImage.width : '',
|
||||||
<pinch-zoom
|
height: originalImage ? originalImage.height : '',
|
||||||
class={style.pinchZoom}
|
objectFit: leftImgContain ? 'contain' : '',
|
||||||
ref={linkRef(this, 'pinchZoomRight')}
|
}}
|
||||||
>
|
/>
|
||||||
<canvas
|
</pinch-zoom>
|
||||||
class={style.pinchTarget}
|
<pinch-zoom
|
||||||
ref={linkRef(this, 'canvasRight')}
|
class={style.pinchZoom}
|
||||||
width={rightDraw && rightDraw.width}
|
ref={linkRef(this, 'pinchZoomRight')}
|
||||||
height={rightDraw && rightDraw.height}
|
>
|
||||||
style={{
|
<canvas
|
||||||
width: originalImage ? originalImage.width : '',
|
class={style.pinchTarget}
|
||||||
height: originalImage ? originalImage.height : '',
|
ref={linkRef(this, 'canvasRight')}
|
||||||
objectFit: rightImgContain ? 'contain' : '',
|
width={rightDraw && rightDraw.width}
|
||||||
}}
|
height={rightDraw && rightDraw.height}
|
||||||
/>
|
style={{
|
||||||
</pinch-zoom>
|
width: originalImage ? originalImage.width : '',
|
||||||
</two-up>
|
height: originalImage ? originalImage.height : '',
|
||||||
|
objectFit: rightImgContain ? 'contain' : '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</pinch-zoom>
|
||||||
|
</two-up>
|
||||||
|
</div>
|
||||||
<div class={style.controls}>
|
<div class={style.controls}>
|
||||||
<div class={style.zoomControls}>
|
<div class={style.buttonGroup}>
|
||||||
<button class={style.button} onClick={this.zoomOut}>
|
<button class={style.firstButton} onClick={this.zoomOut}>
|
||||||
<RemoveIcon />
|
<RemoveIcon />
|
||||||
</button>
|
</button>
|
||||||
{editingScale ? (
|
{editingScale ? (
|
||||||
@@ -338,23 +339,15 @@ export default class Output extends Component<Props, State> {
|
|||||||
<span class={style.zoomValue}>{Math.round(scale * 100)}</span>%
|
<span class={style.zoomValue}>{Math.round(scale * 100)}</span>%
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<button class={style.button} onClick={this.zoomIn}>
|
<button class={style.lastButton} onClick={this.zoomIn}>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class={style.buttonsNoWrap}>
|
<div class={style.buttonGroup}>
|
||||||
<button
|
<button class={style.firstButton} onClick={this.onRotateClick}>
|
||||||
class={style.button}
|
|
||||||
onClick={this.onRotateClick}
|
|
||||||
title="Rotate image"
|
|
||||||
>
|
|
||||||
<RotateIcon />
|
<RotateIcon />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class={style.lastButton} onClick={this.toggleBackground}>
|
||||||
class={`${style.button} ${altBackground ? style.active : ''}`}
|
|
||||||
onClick={this.toggleBackground}
|
|
||||||
title="Change canvas color"
|
|
||||||
>
|
|
||||||
{altBackground ? (
|
{altBackground ? (
|
||||||
<ToggleBackgroundActiveIcon />
|
<ToggleBackgroundActiveIcon />
|
||||||
) : (
|
) : (
|
||||||
@@ -363,7 +356,7 @@ export default class Output extends Component<Props, State> {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,20 +64,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-controls {
|
.button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
& :not(:first-child) {
|
z-index: 100;
|
||||||
border-top-left-radius: 0;
|
margin: 0 3px;
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
& :not(:last-child) {
|
|
||||||
margin-right: 0;
|
|
||||||
border-right-width: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button,
|
.button,
|
||||||
@@ -85,63 +76,80 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 4px;
|
background-color: rgba(29, 29, 29, 0.92);
|
||||||
background-color: #fff;
|
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
border-width: 1px 0 1px 1px;
|
||||||
border-radius: 5px;
|
line-height: 1.1;
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: 36px;
|
height: 39px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
font-size: 1.2rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
|
||||||
height: 48px;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 0 0 2px var(--button-fg);
|
/* box-shadow: 0 0 0 2px var(--hot-pink); */
|
||||||
|
box-shadow: 0 0 0 2px #fff;
|
||||||
outline: none;
|
outline: none;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
color: var(--button-fg);
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #eee;
|
background: rgba(50, 50, 50, 0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: #34b9eb;
|
background: rgba(72, 72, 72, 0.92);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #32a3ce;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.first-button {
|
||||||
|
composes: button;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-button {
|
||||||
|
composes: button;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.zoom {
|
.zoom {
|
||||||
color: #625e80;
|
|
||||||
cursor: text;
|
cursor: text;
|
||||||
width: 6em;
|
width: 7rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px var(--button-fg);
|
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
span.zoom {
|
||||||
|
color: #939393;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
input.zoom {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-indent: 3px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.zoom-value {
|
.zoom-value {
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
margin: 0 3px 0 0;
|
margin: 0 3px 0 0;
|
||||||
color: #888;
|
padding: 0 2px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
border-bottom: 1px dashed #999;
|
border-bottom: 1px dashed #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import WorkerBridge from '../worker-bridge';
|
|||||||
import { resize } from 'features/processors/resize/client';
|
import { resize } from 'features/processors/resize/client';
|
||||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
import { Arrow, ExpandIcon } from '../icons';
|
import { Arrow, ExpandIcon } from '../icons';
|
||||||
|
import { generateCliInvocation } from '../util/cli';
|
||||||
|
|
||||||
export type OutputType = EncoderType | 'identity';
|
export type OutputType = EncoderType | 'identity';
|
||||||
|
|
||||||
@@ -379,7 +380,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
this.queueUpdateImage();
|
this.queueUpdateImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onCopyToOtherClick(index: 0 | 1) {
|
private onCopyToOtherClick = async (index: 0 | 1) => {
|
||||||
const otherIndex = index ? 0 : 1;
|
const otherIndex = index ? 0 : 1;
|
||||||
const oldSettings = this.state.sides[otherIndex];
|
const oldSettings = this.state.sides[otherIndex];
|
||||||
const newSettings = { ...this.state.sides[index] };
|
const newSettings = { ...this.state.sides[index] };
|
||||||
@@ -404,7 +405,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
sides: cleanSet(this.state.sides, otherIndex, oldSettings),
|
sides: cleanSet(this.state.sides, otherIndex, oldSettings),
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
private onPreprocessorChange = async (
|
private onPreprocessorChange = async (
|
||||||
preprocessorState: PreprocessorState,
|
preprocessorState: PreprocessorState,
|
||||||
@@ -438,6 +439,29 @@ export default class Compress extends Component<Props, State> {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onCopyCliClick = async (index: 0 | 1) => {
|
||||||
|
try {
|
||||||
|
const cliInvocation = generateCliInvocation(
|
||||||
|
this.state.sides[index].latestSettings.encoderState!,
|
||||||
|
this.state.sides[index].latestSettings.processorState,
|
||||||
|
);
|
||||||
|
await navigator.clipboard.writeText(cliInvocation);
|
||||||
|
const result = await this.props.showSnack(
|
||||||
|
'CLI command copied to clipboard',
|
||||||
|
{
|
||||||
|
timeout: 8000,
|
||||||
|
actions: ['usage', 'dismiss'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result === 'usage') {
|
||||||
|
open('https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.props.showSnack(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounce the heavy lifting of updateImage.
|
* Debounce the heavy lifting of updateImage.
|
||||||
* Otherwise, the thrashing causes jank, and sometimes crashes iOS Safari.
|
* Otherwise, the thrashing causes jank, and sometimes crashes iOS Safari.
|
||||||
@@ -805,6 +829,8 @@ export default class Compress extends Component<Props, State> {
|
|||||||
onEncoderTypeChange={this.onEncoderTypeChange}
|
onEncoderTypeChange={this.onEncoderTypeChange}
|
||||||
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
||||||
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
||||||
|
onCopyCliClick={this.onCopyCliClick}
|
||||||
|
onCopyToOtherSideClick={this.onCopyToOtherClick}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -72,3 +72,20 @@ export const DownloadIcon = () => (
|
|||||||
<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" />
|
<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>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const CLIIcon = () => (
|
||||||
|
<svg viewBox="0 0 81.3 68.8">
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke-miterlimit="15.6"
|
||||||
|
stroke-width="6.3"
|
||||||
|
d="M3.1 3.1h75v62.5h-75zm18.8 43.8l12.5-12.5-12.5-12.5m18.7 25h18.8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SwapIcon = () => (
|
||||||
|
<svg viewBox="0 0 18 14">
|
||||||
|
<path d="M5.5 3.6v6.8L2.1 7l3.4-3.4M7 0L0 7l7 7V0zm4 0v14l7-7-7-7z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|||||||
51
src/client/lazy-app/util/cli.ts
Normal file
51
src/client/lazy-app/util/cli.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EncoderState, ProcessorState } from '../feature-meta';
|
||||||
|
|
||||||
|
// Maps our encoder.type values to CLI parameter names
|
||||||
|
const typeMap = new Map<string, string>([
|
||||||
|
['avif', '--avif'],
|
||||||
|
['jxl', '--jxl'],
|
||||||
|
['mozJPEG', '--mozjpeg'],
|
||||||
|
['oxiPNG', '--oxipng'],
|
||||||
|
['webP', '--webp'],
|
||||||
|
['wp2', '--wp2'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Same as JSON.stringify, but with single quotes around the entire value
|
||||||
|
// so that shells don’t do weird stuff.
|
||||||
|
function cliJson<T>(v: T): string {
|
||||||
|
return "'" + JSON.stringify(v) + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateCliInvocation(
|
||||||
|
encoder: EncoderState,
|
||||||
|
processor: ProcessorState,
|
||||||
|
): string {
|
||||||
|
if (!typeMap.has(encoder.type)) {
|
||||||
|
throw Error(`Encoder ${encoder.type} is unsupported in the CLI`);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'npx',
|
||||||
|
'@squoosh/cli',
|
||||||
|
...(processor.resize.enabled
|
||||||
|
? ['--resize', cliJson(processor.resize)]
|
||||||
|
: []),
|
||||||
|
...(processor.quantize.enabled
|
||||||
|
? ['--quant', cliJson(processor.quantize)]
|
||||||
|
: []),
|
||||||
|
typeMap.get(encoder.type)!,
|
||||||
|
cliJson(encoder.options),
|
||||||
|
].join(' ');
|
||||||
|
}
|
||||||
@@ -351,6 +351,12 @@ export default class Intro extends Component<Props, State> {
|
|||||||
>
|
>
|
||||||
Privacy
|
Privacy
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
class={style.footerLink}
|
||||||
|
href="https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli"
|
||||||
|
>
|
||||||
|
Squoosh CLI
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
class={style.footerLinkWithLogo}
|
class={style.footerLinkWithLogo}
|
||||||
href="https://github.com/GoogleChromeLabs/squoosh"
|
href="https://github.com/GoogleChromeLabs/squoosh"
|
||||||
|
|||||||
@@ -133,11 +133,17 @@
|
|||||||
|
|
||||||
.footer-items {
|
.footer-items {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: end;
|
|
||||||
grid-auto-columns: max-content;
|
grid-auto-rows: max-content;
|
||||||
grid-auto-flow: column;
|
justify-items: center;
|
||||||
align-items: center;
|
gap: 2rem;
|
||||||
gap: 4rem;
|
|
||||||
|
@media (min-width: 480px) {
|
||||||
|
justify-content: end;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-link {
|
.footer-link {
|
||||||
|
|||||||
Reference in New Issue
Block a user