Compare commits
3 Commits
tiling-rus
...
copy-to-si
Author | SHA1 | Date | |
---|---|---|---|
7e91f4e5c5 | |||
854d20af12 | |||
97778c5ba6 |
@ -59,13 +59,11 @@ const encoderOptionsComponentMap = {
|
|||||||
interface Props {
|
interface Props {
|
||||||
mobileView: boolean;
|
mobileView: boolean;
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
imageIndex: number;
|
|
||||||
encoderState: EncoderState;
|
encoderState: EncoderState;
|
||||||
preprocessorState: PreprocessorState;
|
preprocessorState: PreprocessorState;
|
||||||
onEncoderTypeChange(newType: EncoderType): void;
|
onEncoderTypeChange(newType: EncoderType): void;
|
||||||
onEncoderOptionsChange(newOptions: EncoderOptions): void;
|
onEncoderOptionsChange(newOptions: EncoderOptions): void;
|
||||||
onPreprocessorOptionsChange(newOptions: PreprocessorState): void;
|
onPreprocessorOptionsChange(newOptions: PreprocessorState): void;
|
||||||
onCopyToOtherClick(): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -116,16 +114,9 @@ export default class Options extends Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
|
||||||
onCopyToOtherClick(event: Event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.props.onCopyToOtherClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
{
|
{
|
||||||
source,
|
source,
|
||||||
imageIndex,
|
|
||||||
encoderState,
|
encoderState,
|
||||||
preprocessorState,
|
preprocessorState,
|
||||||
onEncoderOptionsChange,
|
onEncoderOptionsChange,
|
||||||
@ -205,14 +196,6 @@ export default class Options extends Component<Props, State> {
|
|||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
</Expander>
|
</Expander>
|
||||||
|
|
||||||
<div class={style.optionsCopy}>
|
|
||||||
<button onClick={this.onCopyToOtherClick} class={style.copyButton}>
|
|
||||||
{imageIndex === 1 && '← '}
|
|
||||||
Copy settings across
|
|
||||||
{imageIndex === 0 && ' →'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -55,18 +55,3 @@ $horizontalPadding: 15px;
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-copy {
|
|
||||||
display: grid;
|
|
||||||
background: rgba(0, 0, 0, 0.9);
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-button {
|
|
||||||
composes: unbutton from '../../lib/util.scss';
|
|
||||||
background: #484848;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #fff;
|
|
||||||
text-align: left;
|
|
||||||
padding: 5px 10px;
|
|
||||||
}
|
|
||||||
|
@ -34,7 +34,7 @@ import Processor from '../../codecs/processor';
|
|||||||
import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta';
|
import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta';
|
||||||
import './custom-els/MultiPanel';
|
import './custom-els/MultiPanel';
|
||||||
import Results from '../results';
|
import Results from '../results';
|
||||||
import { ExpandIcon } from '../../lib/icons';
|
import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons';
|
||||||
|
|
||||||
export interface SourceImage {
|
export interface SourceImage {
|
||||||
file: File | Fileish;
|
file: File | Fileish;
|
||||||
@ -136,7 +136,7 @@ async function processSvg(blob: Blob): Promise<HTMLImageElement> {
|
|||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const text = await blobToText(blob);
|
const text = await blobToText(blob);
|
||||||
const document = parser.parseFromString(text, 'image/svg+xml');
|
const document = parser.parseFromString(text, 'image/svg+xml');
|
||||||
const svg = document.documentElement;
|
const svg = document.documentElement!;
|
||||||
|
|
||||||
if (svg.hasAttribute('width') && svg.hasAttribute('height')) {
|
if (svg.hasAttribute('width') && svg.hasAttribute('height')) {
|
||||||
return blobToImg(blob);
|
return blobToImg(blob);
|
||||||
@ -156,6 +156,9 @@ async function processSvg(blob: Blob): Promise<HTMLImageElement> {
|
|||||||
|
|
||||||
// These are only used in the mobile view
|
// These are only used in the mobile view
|
||||||
const resultTitles = ['Top', 'Bottom'];
|
const resultTitles = ['Top', 'Bottom'];
|
||||||
|
// These are only used in the desktop view
|
||||||
|
const buttonPositions =
|
||||||
|
['download-left', 'download-right'] as ('download-left' | 'download-right')[];
|
||||||
|
|
||||||
export default class Compress extends Component<Props, State> {
|
export default class Compress extends Component<Props, State> {
|
||||||
widthQuery = window.matchMedia('(max-width: 599px)');
|
widthQuery = window.matchMedia('(max-width: 599px)');
|
||||||
@ -405,26 +408,30 @@ export default class Compress extends Component<Props, State> {
|
|||||||
<Options
|
<Options
|
||||||
source={source}
|
source={source}
|
||||||
mobileView={mobileView}
|
mobileView={mobileView}
|
||||||
imageIndex={index}
|
|
||||||
preprocessorState={image.preprocessorState}
|
preprocessorState={image.preprocessorState}
|
||||||
encoderState={image.encoderState}
|
encoderState={image.encoderState}
|
||||||
onEncoderTypeChange={this.onEncoderTypeChange.bind(this, index)}
|
onEncoderTypeChange={this.onEncoderTypeChange.bind(this, index)}
|
||||||
onEncoderOptionsChange={this.onEncoderOptionsChange.bind(this, index)}
|
onEncoderOptionsChange={this.onEncoderOptionsChange.bind(this, index)}
|
||||||
onPreprocessorOptionsChange={this.onPreprocessorOptionsChange.bind(this, index)}
|
onPreprocessorOptionsChange={this.onPreprocessorOptionsChange.bind(this, index)}
|
||||||
onCopyToOtherClick={this.onCopyToOtherClick.bind(this, index)}
|
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
const results = images.map((image, i) => (
|
const copyDirections =
|
||||||
|
(mobileView ? ['down', 'up'] : ['right', 'left']) as CopyAcrossIconProps['copyDirection'][];
|
||||||
|
|
||||||
|
const results = images.map((image, index) => (
|
||||||
<Results
|
<Results
|
||||||
downloadUrl={image.downloadUrl}
|
downloadUrl={image.downloadUrl}
|
||||||
imageFile={image.file}
|
imageFile={image.file}
|
||||||
source={source}
|
source={source}
|
||||||
loading={loading || image.loading}
|
loading={loading || image.loading}
|
||||||
|
copyDirection={copyDirections[index]}
|
||||||
|
onCopyToOtherClick={this.onCopyToOtherClick.bind(this, index)}
|
||||||
|
buttonPosition={mobileView ? 'stack-right' : buttonPositions[index]}
|
||||||
>
|
>
|
||||||
{!mobileView ? null : [
|
{!mobileView ? null : [
|
||||||
<ExpandIcon class={style.expandIcon} key="expand-icon"/>,
|
<ExpandIcon class={style.expandIcon} key="expand-icon"/>,
|
||||||
`${resultTitles[i]} (${encoderMap[image.encoderState.type].label})`,
|
`${resultTitles[index]} (${encoderMap[image.encoderState.type].label})`,
|
||||||
]}
|
]}
|
||||||
</Results>
|
</Results>
|
||||||
));
|
));
|
||||||
|
@ -2,10 +2,10 @@ import { h, Component, ComponentChildren, ComponentChild } from 'preact';
|
|||||||
|
|
||||||
import * as style from './style.scss';
|
import * as style from './style.scss';
|
||||||
import FileSize from './FileSize';
|
import FileSize from './FileSize';
|
||||||
import { DownloadIcon } from '../../lib/icons';
|
import { DownloadIcon, CopyAcrossIcon, CopyAcrossIconProps } from '../../lib/icons';
|
||||||
import '../custom-els/LoadingSpinner';
|
import '../custom-els/LoadingSpinner';
|
||||||
import { SourceImage } from '../compress';
|
import { SourceImage } from '../compress';
|
||||||
import { Fileish } from '../../lib/initial-util';
|
import { Fileish, bind } from '../../lib/initial-util';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -13,12 +13,21 @@ interface Props {
|
|||||||
imageFile?: Fileish;
|
imageFile?: Fileish;
|
||||||
downloadUrl?: string;
|
downloadUrl?: string;
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
|
copyDirection: CopyAcrossIconProps['copyDirection'];
|
||||||
|
buttonPosition: keyof typeof buttonPositionClass;
|
||||||
|
onCopyToOtherClick(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
showLoadingState: boolean;
|
showLoadingState: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buttonPositionClass = {
|
||||||
|
'stack-right': style.stackRight,
|
||||||
|
'download-right': style.downloadRight,
|
||||||
|
'download-left': style.downloadLeft,
|
||||||
|
};
|
||||||
|
|
||||||
const loadingReactionDelay = 500;
|
const loadingReactionDelay = 500;
|
||||||
|
|
||||||
export default class Results extends Component<Props, State> {
|
export default class Results extends Component<Props, State> {
|
||||||
@ -43,9 +52,19 @@ export default class Results extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ source, imageFile, downloadUrl, children }: Props, { showLoadingState }: State) {
|
@bind
|
||||||
|
private onCopyToOtherClick(event: Event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.onCopyToOtherClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
{ source, imageFile, downloadUrl, children, copyDirection, buttonPosition }: Props,
|
||||||
|
{ showLoadingState }: State,
|
||||||
|
) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.results}>
|
<div class={`${style.results} ${buttonPositionClass[buttonPosition]}`}>
|
||||||
<div class={style.resultData}>
|
<div class={style.resultData}>
|
||||||
{(children as ComponentChild[])[0]
|
{(children as ComponentChild[])[0]
|
||||||
? <div class={style.resultTitle}>{children}</div>
|
? <div class={style.resultTitle}>{children}</div>
|
||||||
@ -59,6 +78,14 @@ export default class Results extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class={style.copyToOther}
|
||||||
|
title="Copy settings to other side"
|
||||||
|
onClick={this.onCopyToOtherClick}
|
||||||
|
>
|
||||||
|
<CopyAcrossIcon class={style.copyIcon} copyDirection={copyDirection} />
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class={style.download}>
|
<div class={style.download}>
|
||||||
{(downloadUrl && imageFile) && (
|
{(downloadUrl && imageFile) && (
|
||||||
<a
|
<a
|
||||||
|
@ -16,9 +16,17 @@
|
|||||||
|
|
||||||
.results {
|
.results {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: [text] 1fr [copy-button] auto [download-button] auto;
|
||||||
background: rgba(0, 0, 0, 0.9);
|
background: rgba(0, 0, 0, 0.9);
|
||||||
font-size: 1.4rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
|
@media (min-width: 400px) {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -26,13 +34,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.result-data {
|
.result-data {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 15px;
|
padding: 0 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.result-title {
|
.result-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -40,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.size-delta {
|
.size-delta {
|
||||||
font-size: 1.1rem;
|
font-size: 0.8em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
@ -56,6 +80,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.download {
|
.download {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: download-button;
|
||||||
background: #34B9EB;
|
background: #34B9EB;
|
||||||
--size: 38px;
|
--size: 38px;
|
||||||
width: var(--size);
|
width: var(--size);
|
||||||
@ -77,7 +103,8 @@
|
|||||||
animation: action-leave 0.2s;
|
animation: action-leave 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-icon {
|
.download-icon,
|
||||||
|
.copy-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: block;
|
display: block;
|
||||||
--size: 24px;
|
--size: 24px;
|
||||||
@ -93,3 +120,12 @@
|
|||||||
--size: 22px;
|
--size: 22px;
|
||||||
grid-area: 1/1;
|
grid-area: 1/1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-to-other {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: copy-button;
|
||||||
|
composes: unbutton from '../../lib/util.scss';
|
||||||
|
composes: download;
|
||||||
|
|
||||||
|
background: #656565;
|
||||||
|
}
|
||||||
|
@ -47,3 +47,28 @@ export const ExpandIcon = (props: JSX.HTMLAttributes) => (
|
|||||||
<path d="M16.6 8.6L12 13.2 7.4 8.6 6 10l6 6 6-6z"/>
|
<path d="M16.6 8.6L12 13.2 7.4 8.6 6 10l6 6 6-6z"/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const copyAcrossRotations = {
|
||||||
|
up: 90, right: 180, down: -90, left: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CopyAcrossIconProps extends 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user