mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-14 09:39:15 +00:00
Styled viewport controls & cli copy
This commit is contained in:
@@ -13,11 +13,11 @@ import {
|
||||
encoderMap,
|
||||
} 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';
|
||||
import { CLIIcon } from 'client/lazy-app/icons';
|
||||
|
||||
interface Props {
|
||||
index: 0 | 1;
|
||||
@@ -28,6 +28,7 @@ interface Props {
|
||||
onEncoderTypeChange(index: 0 | 1, newType: OutputType): void;
|
||||
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
||||
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
||||
onCopyCliClick(index: 0 | 1): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -106,6 +107,10 @@ export default class Options extends Component<Props, State> {
|
||||
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
||||
};
|
||||
|
||||
private onCopyCliClick = () => {
|
||||
this.props.onCopyCliClick(this.props.index);
|
||||
};
|
||||
|
||||
render(
|
||||
{ source, encoderState, processorState }: Props,
|
||||
{ supportedEncoderMap }: State,
|
||||
@@ -125,7 +130,18 @@ export default class Options extends Component<Props, State> {
|
||||
<Expander>
|
||||
{!encoderState ? null : (
|
||||
<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>
|
||||
</div>
|
||||
</h3>
|
||||
<label class={style.sectionEnabler}>
|
||||
Resize
|
||||
<Toggle
|
||||
|
||||
@@ -81,3 +81,28 @@
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title-and-buttons {
|
||||
grid-template-columns: 1fr;
|
||||
grid-auto-columns: max-content;
|
||||
grid-auto-flow: column;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { h, Component } from 'preact';
|
||||
import { h, Component, Fragment } from 'preact';
|
||||
import type PinchZoom from './custom-els/PinchZoom';
|
||||
import type { ScaleToOpts } from './custom-els/PinchZoom';
|
||||
import './custom-els/PinchZoom';
|
||||
@@ -262,59 +262,60 @@ export default class Output extends Component<Props, State> {
|
||||
const originalImage = source && source.preprocessed;
|
||||
|
||||
return (
|
||||
<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}
|
||||
<Fragment>
|
||||
<div
|
||||
class={`${style.output} ${altBackground ? style.altBackground : ''}`}
|
||||
>
|
||||
<pinch-zoom
|
||||
class={style.pinchZoom}
|
||||
onChange={this.onPinchZoomLeftChange}
|
||||
ref={linkRef(this, 'pinchZoomLeft')}
|
||||
<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}
|
||||
>
|
||||
<canvas
|
||||
class={style.pinchTarget}
|
||||
ref={linkRef(this, 'canvasLeft')}
|
||||
width={leftDraw && leftDraw.width}
|
||||
height={leftDraw && leftDraw.height}
|
||||
style={{
|
||||
width: originalImage ? originalImage.width : '',
|
||||
height: originalImage ? originalImage.height : '',
|
||||
objectFit: leftImgContain ? 'contain' : '',
|
||||
}}
|
||||
/>
|
||||
</pinch-zoom>
|
||||
<pinch-zoom
|
||||
class={style.pinchZoom}
|
||||
ref={linkRef(this, 'pinchZoomRight')}
|
||||
>
|
||||
<canvas
|
||||
class={style.pinchTarget}
|
||||
ref={linkRef(this, 'canvasRight')}
|
||||
width={rightDraw && rightDraw.width}
|
||||
height={rightDraw && rightDraw.height}
|
||||
style={{
|
||||
width: originalImage ? originalImage.width : '',
|
||||
height: originalImage ? originalImage.height : '',
|
||||
objectFit: rightImgContain ? 'contain' : '',
|
||||
}}
|
||||
/>
|
||||
</pinch-zoom>
|
||||
</two-up>
|
||||
|
||||
<pinch-zoom
|
||||
class={style.pinchZoom}
|
||||
onChange={this.onPinchZoomLeftChange}
|
||||
ref={linkRef(this, 'pinchZoomLeft')}
|
||||
>
|
||||
<canvas
|
||||
class={style.pinchTarget}
|
||||
ref={linkRef(this, 'canvasLeft')}
|
||||
width={leftDraw && leftDraw.width}
|
||||
height={leftDraw && leftDraw.height}
|
||||
style={{
|
||||
width: originalImage ? originalImage.width : '',
|
||||
height: originalImage ? originalImage.height : '',
|
||||
objectFit: leftImgContain ? 'contain' : '',
|
||||
}}
|
||||
/>
|
||||
</pinch-zoom>
|
||||
<pinch-zoom
|
||||
class={style.pinchZoom}
|
||||
ref={linkRef(this, 'pinchZoomRight')}
|
||||
>
|
||||
<canvas
|
||||
class={style.pinchTarget}
|
||||
ref={linkRef(this, 'canvasRight')}
|
||||
width={rightDraw && rightDraw.width}
|
||||
height={rightDraw && rightDraw.height}
|
||||
style={{
|
||||
width: originalImage ? originalImage.width : '',
|
||||
height: originalImage ? originalImage.height : '',
|
||||
objectFit: rightImgContain ? 'contain' : '',
|
||||
}}
|
||||
/>
|
||||
</pinch-zoom>
|
||||
</two-up>
|
||||
</div>
|
||||
<div class={style.controls}>
|
||||
<div class={style.zoomControls}>
|
||||
<button class={style.button} onClick={this.zoomOut}>
|
||||
<div class={style.buttonGroup}>
|
||||
<button class={style.firstButton} onClick={this.zoomOut}>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
{editingScale ? (
|
||||
@@ -338,23 +339,15 @@ export default class Output extends Component<Props, State> {
|
||||
<span class={style.zoomValue}>{Math.round(scale * 100)}</span>%
|
||||
</span>
|
||||
)}
|
||||
<button class={style.button} onClick={this.zoomIn}>
|
||||
<button class={style.lastButton} onClick={this.zoomIn}>
|
||||
<AddIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div class={style.buttonsNoWrap}>
|
||||
<button
|
||||
class={style.button}
|
||||
onClick={this.onRotateClick}
|
||||
title="Rotate image"
|
||||
>
|
||||
<div class={style.buttonGroup}>
|
||||
<button class={style.firstButton} onClick={this.onRotateClick}>
|
||||
<RotateIcon />
|
||||
</button>
|
||||
<button
|
||||
class={`${style.button} ${altBackground ? style.active : ''}`}
|
||||
onClick={this.toggleBackground}
|
||||
title="Change canvas color"
|
||||
>
|
||||
<button class={style.lastButton} onClick={this.toggleBackground}>
|
||||
{altBackground ? (
|
||||
<ToggleBackgroundActiveIcon />
|
||||
) : (
|
||||
@@ -363,7 +356,7 @@ export default class Output extends Component<Props, State> {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,20 +64,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
.button-group {
|
||||
display: flex;
|
||||
|
||||
& :not(:first-child) {
|
||||
border-top-left-radius: 0;
|
||||
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;
|
||||
}
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.button,
|
||||
@@ -85,63 +76,80 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
margin: 4px;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
line-height: 1;
|
||||
background-color: rgba(29, 29, 29, 0.92);
|
||||
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||
border-width: 1px 0 1px 1px;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
height: 36px;
|
||||
height: 39px;
|
||||
padding: 0 8px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
&: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;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
color: var(--button-fg);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
background: rgba(50, 50, 50, 0.92);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #34b9eb;
|
||||
background: rgba(72, 72, 72, 0.92);
|
||||
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 {
|
||||
color: #625e80;
|
||||
cursor: text;
|
||||
width: 6em;
|
||||
width: 7rem;
|
||||
font: inherit;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
|
||||
&: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 {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import WorkerBridge from '../worker-bridge';
|
||||
import { resize } from 'features/processors/resize/client';
|
||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||
import { Arrow, ExpandIcon } from '../icons';
|
||||
import { generateCliInvocation } from '../util/cli';
|
||||
|
||||
export type OutputType = EncoderType | 'identity';
|
||||
|
||||
@@ -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.
|
||||
* Otherwise, the thrashing causes jank, and sometimes crashes iOS Safari.
|
||||
@@ -805,6 +829,7 @@ export default class Compress extends Component<Props, State> {
|
||||
onEncoderTypeChange={this.onEncoderTypeChange}
|
||||
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
||||
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
||||
onCopyCliClick={this.onCopyCliClick}
|
||||
/>
|
||||
));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user