mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 08:47:31 +00:00
Initial Options Flyout
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
.wrap {
|
.wrap {
|
||||||
display: inline;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrap > aside:last-of-type {
|
.wrap > aside:last-of-type {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'add-css:./style.css';
|
|||||||
import { cleanSet, cleanMerge } from '../../util/clean-modify';
|
import { cleanSet, cleanMerge } from '../../util/clean-modify';
|
||||||
|
|
||||||
import type { SourceImage, OutputType } from '..';
|
import type { SourceImage, OutputType } from '..';
|
||||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
|
||||||
import {
|
import {
|
||||||
EncoderOptions,
|
EncoderOptions,
|
||||||
EncoderState,
|
EncoderState,
|
||||||
@@ -14,17 +13,15 @@ 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 Flyout from '../Flyout';
|
||||||
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, MoreIcon, SwapIcon } from 'client/lazy-app/icons';
|
||||||
import { generateCliInvocation } from '../../util/cli-invocation-generator';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: 0 | 1;
|
index: 0 | 1;
|
||||||
showSnack: SnackBarElement['showSnackbar'];
|
|
||||||
mobileView: boolean;
|
mobileView: boolean;
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
encoderState?: EncoderState;
|
encoderState?: EncoderState;
|
||||||
@@ -32,6 +29,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 {
|
||||||
@@ -110,20 +109,12 @@ export default class Options extends Component<Props, State> {
|
|||||||
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
this.props.onEncoderOptionsChange(this.props.index, newOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCreateCLIInvocation = () => {
|
private onCopyCliClick = () => {
|
||||||
if (!this.props.encoderState) {
|
this.props.onCopyCliClick(this.props.index);
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
private onCopyToOtherSideClick = () => {
|
||||||
const cliInvocation = generateCliInvocation(
|
this.props.onCopyToOtherSideClick(this.props.index);
|
||||||
this.props.encoderState,
|
|
||||||
this.props.processorState,
|
|
||||||
);
|
|
||||||
navigator.clipboard.writeText(cliInvocation);
|
|
||||||
} catch (e) {
|
|
||||||
this.props.showSnack(e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@@ -146,7 +137,31 @@ export default class Options extends Component<Props, State> {
|
|||||||
{!encoderState ? null : (
|
{!encoderState ? null : (
|
||||||
<div>
|
<div>
|
||||||
<h3 class={style.optionsTitle}>
|
<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>
|
</h3>
|
||||||
<label class={style.sectionEnabler}>
|
<label class={style.sectionEnabler}>
|
||||||
Resize
|
Resize
|
||||||
|
|||||||
@@ -14,13 +14,21 @@
|
|||||||
background-color: var(--main-theme-color);
|
background-color: var(--main-theme-color);
|
||||||
color: var(--header-text-color);
|
color: var(--header-text-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px var(--horizontal-padding);
|
height: 38px;
|
||||||
|
padding: 0 0 0 var(--horizontal-padding);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
border-bottom: 1px solid var(--off-black);
|
border-bottom: 1px solid var(--off-black);
|
||||||
transition: all 300ms ease-in-out;
|
transition: all 300ms ease-in-out;
|
||||||
transition-property: background-color, color;
|
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;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -81,3 +89,63 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 4px;
|
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 {
|
.moreButton {
|
||||||
background: rgba(82, 82, 82, 0.92);
|
background: rgba(82, 82, 82, 0.92);
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,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 Transform from './Transform';
|
import Transform from './Transform';
|
||||||
|
import { generateCliInvocation } from '../util/cli';
|
||||||
|
|
||||||
export type OutputType = EncoderType | 'identity';
|
export type OutputType = EncoderType | 'identity';
|
||||||
|
|
||||||
@@ -411,7 +412,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] };
|
||||||
@@ -436,7 +437,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,
|
||||||
@@ -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.
|
* 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.
|
||||||
@@ -853,7 +877,6 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const options = sides.map((side, index) => (
|
const options = sides.map((side, index) => (
|
||||||
<Options
|
<Options
|
||||||
index={index as 0 | 1}
|
index={index as 0 | 1}
|
||||||
showSnack={showSnack}
|
|
||||||
source={source}
|
source={source}
|
||||||
mobileView={mobileView}
|
mobileView={mobileView}
|
||||||
processorState={side.latestSettings.processorState}
|
processorState={side.latestSettings.processorState}
|
||||||
@@ -861,6 +884,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}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { h } from 'preact';
|
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
|
// @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
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) => (
|
export const SwapIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<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>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -77,9 +83,9 @@ export const RotateIcon = (props: preact.JSX.HTMLAttributes) => (
|
|||||||
|
|
||||||
export const MoreIcon = (props: preact.JSX.HTMLAttributes) => (
|
export const MoreIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<Icon {...props}>
|
||||||
<circle cx="12" cy="6" r="2" fill="#fff" />
|
<circle cx="12" cy="6" r="2" />
|
||||||
<circle cx="12" cy="12" r="2" fill="#fff" />
|
<circle cx="12" cy="12" r="2" />
|
||||||
<circle cx="12" cy="18" r="2" fill="#fff" />
|
<circle cx="12" cy="18" r="2" />
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user