mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-11 16:26:20 +00:00
Initial Options Flyout
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
.wrap {
|
||||
display: inline;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.wrap > aside:last-of-type {
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'add-css:./style.css';
|
||||
import { cleanSet, cleanMerge } from '../../util/clean-modify';
|
||||
|
||||
import type { SourceImage, OutputType } from '..';
|
||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
||||
import {
|
||||
EncoderOptions,
|
||||
EncoderState,
|
||||
@@ -14,17 +13,15 @@ import {
|
||||
encoderMap,
|
||||
} from '../../feature-meta';
|
||||
import Expander from './Expander';
|
||||
import Checkbox from './Checkbox';
|
||||
import Toggle from './Toggle';
|
||||
import Select from './Select';
|
||||
import Flyout from '../Flyout';
|
||||
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
||||
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
||||
|
||||
import { generateCliInvocation } from '../../util/cli-invocation-generator';
|
||||
import { CLIIcon, MoreIcon, SwapIcon } from 'client/lazy-app/icons';
|
||||
|
||||
interface Props {
|
||||
index: 0 | 1;
|
||||
showSnack: SnackBarElement['showSnackbar'];
|
||||
mobileView: boolean;
|
||||
source?: SourceImage;
|
||||
encoderState?: EncoderState;
|
||||
@@ -32,6 +29,8 @@ interface Props {
|
||||
onEncoderTypeChange(index: 0 | 1, newType: OutputType): void;
|
||||
onEncoderOptionsChange(index: 0 | 1, newOptions: EncoderOptions): void;
|
||||
onProcessorOptionsChange(index: 0 | 1, newOptions: ProcessorState): void;
|
||||
onCopyToOtherSideClick(index: 0 | 1): void;
|
||||
onCopyCliClick(index: 0 | 1): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -110,20 +109,12 @@ export default class Options extends Component<Props, State> {
|
||||
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
||||
};
|
||||
|
||||
private onCreateCLIInvocation = () => {
|
||||
if (!this.props.encoderState) {
|
||||
return;
|
||||
}
|
||||
private onCopyCliClick = () => {
|
||||
this.props.onCopyCliClick(this.props.index);
|
||||
};
|
||||
|
||||
try {
|
||||
const cliInvocation = generateCliInvocation(
|
||||
this.props.encoderState,
|
||||
this.props.processorState,
|
||||
);
|
||||
navigator.clipboard.writeText(cliInvocation);
|
||||
} catch (e) {
|
||||
this.props.showSnack(e);
|
||||
}
|
||||
private onCopyToOtherSideClick = () => {
|
||||
this.props.onCopyToOtherSideClick(this.props.index);
|
||||
};
|
||||
|
||||
render(
|
||||
@@ -146,7 +137,31 @@ export default class Options extends Component<Props, State> {
|
||||
{!encoderState ? null : (
|
||||
<div>
|
||||
<h3 class={style.optionsTitle}>
|
||||
<button onClick={this.onCreateCLIInvocation}>CLI</button>Edit
|
||||
Edit
|
||||
<Flyout
|
||||
direction={['up', 'left']}
|
||||
anchor="right"
|
||||
toggle={
|
||||
<button class={style.titleButton}>
|
||||
<MoreIcon />
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button
|
||||
class={style.menuButton}
|
||||
onClick={this.onCopyCliClick}
|
||||
>
|
||||
<CLIIcon />
|
||||
Copy npx command
|
||||
</button>
|
||||
<button
|
||||
class={style.menuButton}
|
||||
onClick={this.onCopyToOtherSideClick}
|
||||
>
|
||||
<SwapIcon />
|
||||
Copy settings to other side
|
||||
</button>
|
||||
</Flyout>
|
||||
</h3>
|
||||
<label class={style.sectionEnabler}>
|
||||
Resize
|
||||
|
||||
@@ -14,13 +14,21 @@
|
||||
background-color: var(--main-theme-color);
|
||||
color: var(--header-text-color);
|
||||
margin: 0;
|
||||
padding: 10px var(--horizontal-padding);
|
||||
height: 38px;
|
||||
padding: 0 0 0 var(--horizontal-padding);
|
||||
font-weight: bold;
|
||||
font-size: 1.4rem;
|
||||
border-bottom: 1px solid var(--off-black);
|
||||
transition: all 300ms ease-in-out;
|
||||
transition-property: background-color, color;
|
||||
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr;
|
||||
grid-auto-columns: max-content;
|
||||
grid-auto-flow: column;
|
||||
gap: 0.8rem 0;
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
@@ -81,3 +89,63 @@
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.options-title aside {
|
||||
right: 10px;
|
||||
bottom: calc(100% + 10px);
|
||||
}
|
||||
|
||||
.title-button {
|
||||
composes: unbutton from global;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0);
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
svg {
|
||||
--size: 24px;
|
||||
fill: var(--header-text-color);
|
||||
display: block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
margin: 8px 0;
|
||||
background-color: rgba(29, 29, 29, 0.92);
|
||||
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||
border-radius: 2rem;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
height: 39px;
|
||||
padding: 0 16px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background: rgba(50, 50, 50, 0.92);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px #fff;
|
||||
outline: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
& > svg {
|
||||
position: relative;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 12px;
|
||||
color: var(--main-theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ input.zoom {
|
||||
}
|
||||
}
|
||||
|
||||
[data-flyout-open] {
|
||||
.controls [data-flyout-open] {
|
||||
.moreButton {
|
||||
background: rgba(82, 82, 82, 0.92);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import WorkerBridge from '../worker-bridge';
|
||||
import { resize } from 'features/processors/resize/client';
|
||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||
import Transform from './Transform';
|
||||
import { generateCliInvocation } from '../util/cli';
|
||||
|
||||
export type OutputType = EncoderType | 'identity';
|
||||
|
||||
@@ -411,7 +412,7 @@ export default class Compress extends Component<Props, State> {
|
||||
this.queueUpdateImage();
|
||||
}
|
||||
|
||||
private async onCopyToOtherClick(index: 0 | 1) {
|
||||
private onCopyToOtherClick = async (index: 0 | 1) => {
|
||||
const otherIndex = index ? 0 : 1;
|
||||
const oldSettings = this.state.sides[otherIndex];
|
||||
const newSettings = { ...this.state.sides[index] };
|
||||
@@ -436,7 +437,7 @@ export default class Compress extends Component<Props, State> {
|
||||
this.setState({
|
||||
sides: cleanSet(this.state.sides, otherIndex, oldSettings),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onPreprocessorChange = async (
|
||||
preprocessorState: PreprocessorState,
|
||||
@@ -483,6 +484,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.
|
||||
@@ -853,7 +877,6 @@ export default class Compress extends Component<Props, State> {
|
||||
const options = sides.map((side, index) => (
|
||||
<Options
|
||||
index={index as 0 | 1}
|
||||
showSnack={showSnack}
|
||||
source={source}
|
||||
mobileView={mobileView}
|
||||
processorState={side.latestSettings.processorState}
|
||||
@@ -861,6 +884,8 @@ export default class Compress extends Component<Props, State> {
|
||||
onEncoderTypeChange={this.onEncoderTypeChange}
|
||||
onEncoderOptionsChange={this.onEncoderOptionsChange}
|
||||
onProcessorOptionsChange={this.onProcessorOptionsChange}
|
||||
onCopyCliClick={this.onCopyCliClick}
|
||||
onCopyToOtherSideClick={this.onCopyToOtherClick}
|
||||
/>
|
||||
));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { h } from 'preact';
|
||||
|
||||
const Icon = (props: preact.JSX.HTMLAttributes) => (
|
||||
const Icon = (props: preact.JSX.HTMLAttributes | preact.JSX.SVGAttributes) => (
|
||||
// @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019
|
||||
<svg
|
||||
width="24"
|
||||
@@ -11,9 +11,15 @@ const Icon = (props: preact.JSX.HTMLAttributes) => (
|
||||
/>
|
||||
);
|
||||
|
||||
export const CLIIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
<Icon {...props}>
|
||||
<path d="M1 2.7H23v18.5H1zm5.5 13l3.7-3.7-3.7-3.7m5.5 7.4h5.6" />
|
||||
</Icon>
|
||||
);
|
||||
|
||||
export const SwapIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
<Icon {...props}>
|
||||
<path d="M9.01 14H2v2h7.01v3L13 15l-3.99-4zm5.98-1v-3H22V8h-7.01V5L11 9z" />
|
||||
<path d="M8.5 8.6v6.8L5.1 12l3.4-3.4M10 5l-7 7 7 7V5zm4 0v14l7-7-7-7z" />
|
||||
</Icon>
|
||||
);
|
||||
|
||||
@@ -77,9 +83,9 @@ export const RotateIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
|
||||
export const MoreIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="12" cy="6" r="2" fill="#fff" />
|
||||
<circle cx="12" cy="12" r="2" fill="#fff" />
|
||||
<circle cx="12" cy="18" r="2" fill="#fff" />
|
||||
<circle cx="12" cy="6" r="2" />
|
||||
<circle cx="12" cy="12" r="2" />
|
||||
<circle cx="12" cy="18" r="2" />
|
||||
</Icon>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user