Merge branch 'preprocessor-transformations-rebased' into cli-invoc-2

This commit is contained in:
Jason Miller
2020-12-09 12:21:24 -05:00
9 changed files with 92 additions and 161 deletions

View File

@@ -1,13 +1,22 @@
import { h, cloneElement, Component, VNode, createRef, ComponentChildren, ComponentProps } from "preact"; import {
import { ClickOutsideDetector } from "../ClickOutsideDetector"; h,
cloneElement,
Component,
VNode,
createRef,
ComponentChildren,
ComponentProps,
} from 'preact';
import { ClickOutsideDetector } from '../ClickOutsideDetector';
import * as style from './style.css'; import * as style from './style.css';
import 'add-css:./style.css'; import 'add-css:./style.css';
type Anchor = 'left' | 'right' | 'top' | 'bottom'; type Anchor = 'left' | 'right' | 'top' | 'bottom';
type Direction = 'left' | 'right' | 'up' | 'down';
interface Props extends ComponentProps<'aside'> { interface Props extends ComponentProps<'aside'> {
showing?: boolean; showing?: boolean;
direction?: 'up' | 'down'; direction?: Direction | Direction[];
anchor?: Anchor | Anchor[]; anchor?: Anchor | Anchor[];
toggle?: VNode; toggle?: VNode;
children?: ComponentChildren; children?: ComponentChildren;
@@ -19,7 +28,7 @@ interface State {
export default class Flyout extends Component<Props, State> { export default class Flyout extends Component<Props, State> {
state = { state = {
showing: this.props.showing === true showing: this.props.showing === true,
}; };
private menu = createRef<HTMLElement>(); private menu = createRef<HTMLElement>();
@@ -55,12 +64,18 @@ export default class Flyout extends Component<Props, State> {
} }
} }
render({ direction, anchor, toggle, children, ...props }: Props, { showing }: State) { render(
{ direction, anchor, toggle, children, ...props }: Props,
{ showing }: State,
) {
const toggleProps = { const toggleProps = {
flyoutOpen: showing, flyoutOpen: showing,
onClick: this.toggle onClick: this.toggle,
}; };
const directionText = Array.isArray(direction)
? direction.join(' ')
: direction;
const anchorText = Array.isArray(anchor) ? anchor.join(' ') : anchor; const anchorText = Array.isArray(anchor) ? anchor.join(' ') : anchor;
return ( return (
@@ -73,7 +88,7 @@ export default class Flyout extends Component<Props, State> {
ref={this.menu} ref={this.menu}
hidden={!showing} hidden={!showing}
data-anchor={anchorText} data-anchor={anchorText}
data-direction={direction} data-direction={directionText}
> >
{children} {children}
</aside> </aside>

View File

@@ -32,6 +32,7 @@
&[data-direction*='left'] { &[data-direction*='left'] {
right: 0; right: 0;
left: auto; left: auto;
align-items: flex-end;
&[anchor*='right'] { &[anchor*='right'] {
right: 100%; right: 100%;
} }

View File

@@ -178,12 +178,6 @@ export default class Output extends Component<Props, State> {
// this.hideMenu(); // this.hideMenu();
}; };
private zoomTo2x = () => {
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
this.pinchZoomLeft.scaleTo(0.5, scaleToOpts);
this.recenter();
};
private recenter = () => { private recenter = () => {
const img = this.props.source?.preprocessed; const img = this.props.source?.preprocessed;
if (!img || !this.pinchZoomLeft) return; if (!img || !this.pinchZoomLeft) return;
@@ -342,10 +336,7 @@ export default class Output extends Component<Props, State> {
</two-up> </two-up>
</div> </div>
<div <div class={style.controls} hidden={hidden}>
class={style.controls}
hidden={hidden}
>
<div class={style.zoomControls}> <div class={style.zoomControls}>
<button class={style.button} onClick={this.zoomOut}> <button class={style.button} onClick={this.zoomOut}>
<RemoveIcon /> <RemoveIcon />
@@ -379,29 +370,27 @@ export default class Output extends Component<Props, State> {
class={style.menu} class={style.menu}
showing={hidden ? false : undefined} showing={hidden ? false : undefined}
anchor="right" anchor="right"
direction={mobileView ? 'down' : 'up'} direction={mobileView ? ['down', 'left'] : 'up'}
toggle={ toggle={
<button class={`${style.button} ${style.moreButton}`}> <button class={`${style.button} ${style.moreButton}`}>
<MoreIcon /> <MoreIcon />
</button> </button>
} }
> >
<button class={style.button} onClick={onShowPreprocessorTransforms}> <button
class={style.button}
onClick={onShowPreprocessorTransforms}
>
<RotateIcon /> Rotate & Transform <RotateIcon /> Rotate & Transform
</button> </button>
<button class={style.button} onClick={this.fitToViewport}> <button class={style.button} onClick={this.fitToViewport}>
Fit to viewport Fit to viewport
</button> </button>
<button class={style.button} onClick={this.zoomTo2x}>
Simulate retina
</button>
<button class={style.button} onClick={this.recenter}> <button class={style.button} onClick={this.recenter}>
Re-center Re-center
</button> </button>
<button class={style.button} onClick={onToggleBackground}> <button class={style.button} onClick={onToggleBackground}>
<ToggleBackgroundIcon /> <ToggleBackgroundIcon /> Change canvas color
{' '}
Change canvas color
</button> </button>
</Flyout> </Flyout>
</div> </div>

View File

@@ -83,10 +83,6 @@ export default class Cropper extends Component<Props, State> {
) { ) {
return; return;
} }
// crop.left = Math.max(0, crop.left) | 0;
// crop.top = Math.max(0, crop.top) | 0;
// crop.right = Math.max(0, crop.right) | 0;
// crop.bottom = Math.max(0, crop.bottom) | 0;
this.setState({ crop }); this.setState({ crop });
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(crop); this.props.onChange(crop);
@@ -261,10 +257,14 @@ export default class Cropper extends Component<Props, State> {
const y = crop.top; const y = crop.top;
const width = size.width - crop.left - crop.right; const width = size.width - crop.left - crop.right;
const height = size.height - crop.top - crop.bottom; const height = size.height - crop.top - crop.bottom;
// const x = crop.left.toFixed(2);
// const y = crop.top.toFixed(2); const s = (x: number) => x / (scale || 1);
// const width = (size.width - crop.left - crop.right).toFixed(2);
// const height = (size.height - crop.top - crop.bottom).toFixed(2); const clip = `polygon(0 0, 0 100%, 100% 100%, 100% 0, 0 0, ${s(x)}px ${s(
y,
)}px, ${s(x + width)}px ${s(y)}px, ${s(x + width)}px ${s(
y + height,
)}px, ${s(x)}px ${s(y + height)}px, ${s(x)}px ${s(y)}px)`;
return ( return (
<svg <svg
@@ -280,45 +280,11 @@ export default class Cropper extends Component<Props, State> {
onPointerMove={this.onPointerMove} onPointerMove={this.onPointerMove}
onPointerUp={this.onPointerUp} onPointerUp={this.onPointerUp}
> >
<defs>
{/*
<clipPath id="bg">
<rect x={x} y={y} width={width} height={height} />
</clipPath>
*/}
{/*
<filter id="shadow" x="-2" y="-2" width="4" height="4">
<feDropShadow
dx="0"
dy="0.5"
stdDeviation="1.5"
flood-color="#000"
/>
</filter>
<filter id="shadow2" x="-2" y="-2" width="4" height="4">
<feDropShadow
dx="0"
dy="0.25"
stdDeviation="0.5"
flood-color="rgba(0,0,0,0.5)"
/>
</filter>
*/}
</defs>
<rect <rect
class={style.background} class={style.background}
width={size.width} width={size.width}
height={size.height} height={size.height}
// mask="url(#bg)" clip-path={clip}
// clip-path="url(#bg)"
// style={{
// clipPath: `polygon(0 0, 0 100%, 100% 100%, 100% 0, 0 0, ${x}px ${y}px, ${x+width}px ${y}px, ${x+width}px ${y+height}px, ${x}px ${y+height}px, ${x}px ${y}px)`
// }}
clip-path={`polygon(0 0, 0 100%, 100% 100%, 100% 0, 0 0, ${x}px ${y}px, ${
x + width
}px ${y}px, ${x + width}px ${y + height}px, ${x}px ${
y + height
}px, ${x}px ${y}px)`}
/> />
<svg x={x} y={y} width={width} height={height}> <svg x={x} y={y} width={width} height={height}>
<Freezer> <Freezer>
@@ -328,7 +294,6 @@ export default class Cropper extends Component<Props, State> {
data-edge="left,right,top,bottom" data-edge="left,right,top,bottom"
width="100%" width="100%"
height="100%" height="100%"
// filter="url(#shadow2)"
/> />
<rect class={style.edge} data-edge="top" width="100%" /> <rect class={style.edge} data-edge="top" width="100%" />
@@ -336,71 +301,17 @@ export default class Cropper extends Component<Props, State> {
<rect class={style.edge} data-edge="left" height="100%" /> <rect class={style.edge} data-edge="left" height="100%" />
<rect class={style.edge} data-edge="right" height="100%" x="100%" /> <rect class={style.edge} data-edge="right" height="100%" x="100%" />
<circle <circle class={style.corner} data-edge="left,top" />
class={style.corner} <circle class={style.corner} data-edge="right,top" cx="100%" />
data-edge="left,top"
// filter="url(#shadow)"
/>
<circle
class={style.corner}
data-edge="right,top"
cx="100%"
// filter="url(#shadow)"
/>
<circle <circle
class={style.corner} class={style.corner}
data-edge="right,bottom" data-edge="right,bottom"
cx="100%" cx="100%"
cy="100%" cy="100%"
// filter="url(#shadow)"
/>
<circle
class={style.corner}
data-edge="left,bottom"
cy="100%"
// filter="url(#shadow)"
/> />
<circle class={style.corner} data-edge="left,bottom" cy="100%" />
</Freezer> </Freezer>
</svg> </svg>
{/*
<rect
id="box"
class={style.cropbox}
data-edge="left,right,top,bottom"
x={x}
y={y}
width={width}
height={height}
/>
<rect
class={`${style.edge} ${style.top}`}
data-edge="top"
x={x}
y={y}
width={width}
/>
<rect
class={`${style.edge} ${style.bottom}`}
data-edge="bottom"
x={x}
y={size.height - crop.bottom}
width={width}
/>
<rect
class={`${style.edge} ${style.left}`}
data-edge="left"
x={x}
y={y}
height={height}
/>
<rect
class={`${style.edge} ${style.right}`}
data-edge="right"
x={size.width - crop.right}
y={y}
height={height}
/>
*/}
</svg> </svg>
); );
} }

View File

@@ -35,8 +35,8 @@
.cropbox { .cropbox {
fill: none; fill: none;
stroke: white; stroke: white;
stroke-width: calc(1.5 / var(--scale, 1)); stroke-width: calc(1.5px / var(--scale, 1));
stroke-dasharray: calc(5 / var(--scale, 1)), calc(5 / var(--scale, 1)); stroke-dasharray: calc(5px / var(--scale, 1)), calc(5px / var(--scale, 1));
stroke-dashoffset: 50%; stroke-dashoffset: 50%;
/* Accept pointer input even though this is unpainted transparent */ /* Accept pointer input even though this is unpainted transparent */
pointer-events: all; pointer-events: all;
@@ -85,8 +85,8 @@
} }
.corner { .corner {
r: calc(4 / var(--scale, 1)); r: calc(4px / var(--scale, 1));
stroke-width: calc(4 / var(--scale, 1)); stroke-width: calc(4px / var(--scale, 1));
stroke: rgba(225, 225, 225, 0.01); stroke: rgba(225, 225, 225, 0.01);
fill: white; fill: white;
shape-rendering: geometricprecision; shape-rendering: geometricprecision;

View File

@@ -73,7 +73,7 @@
/* @TODO use grid */ /* @TODO use grid */
.cancel { .cancel {
fill: rgba(0, 0, 0, 0.4); fill: rgba(0, 0, 0, 0.7);
& > svg:not(.icon) { & > svg:not(.icon) {
display: block; display: block;
@@ -93,7 +93,7 @@
display: inline-block; display: inline-block;
padding: 4px 10px; padding: 4px 10px;
border-radius: 1rem; border-radius: 1rem;
background: rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.7);
font-size: 80%; font-size: 80%;
color: #fff; color: #fff;
} }
@@ -126,6 +126,11 @@
animation: slideInFromRight 500ms ease-out forwards 1; animation: slideInFromRight 500ms ease-out forwards 1;
--horizontal-padding: 15px; --horizontal-padding: 15px;
--main-theme-color: var(--blue); --main-theme-color: var(--blue);
/* Hide on mobile (for now) */
@media (max-width: 599px) {
display: none;
}
} }
@keyframes slideInFromRight { @keyframes slideInFromRight {
0% { 0% {

View File

@@ -11,6 +11,7 @@ import {
canDecodeImageType, canDecodeImageType,
abortable, abortable,
assertSignal, assertSignal,
shallowEqual,
} from '../util'; } from '../util';
import { import {
PreprocessorState, PreprocessorState,
@@ -447,19 +448,32 @@ export default class Compress extends Component<Props, State> {
const newRotate = preprocessorState.rotate.rotate; const newRotate = preprocessorState.rotate.rotate;
const orientationChanged = oldRotate % 180 !== newRotate % 180; const orientationChanged = oldRotate % 180 !== newRotate % 180;
const { crop } = preprocessorState;
const cropChanged = !shallowEqual(crop, this.state.preprocessorState.crop);
this.setState((state) => ({ this.setState((state) => ({
loading: true, loading: true,
preprocessorState, preprocessorState,
// Flip resize values if orientation has changed // Flip resize values if orientation has changed
sides: !orientationChanged sides:
!orientationChanged && !cropChanged
? state.sides ? state.sides
: (state.sides.map((side) => { : (state.sides.map((side) => {
const currentResizeSettings = const currentResizeSettings =
side.latestSettings.processorState.resize; side.latestSettings.processorState.resize;
const resizeSettings: Partial<ProcessorState['resize']> = { let resizeSettings: Partial<ProcessorState['resize']>;
if (cropChanged) {
const img = state.source?.decoded;
resizeSettings = {
width: img ? img.width - crop.left - crop.right : undefined,
height: img ? img.height - crop.top - crop.bottom : undefined,
};
} else {
resizeSettings = {
width: currentResizeSettings.height, width: currentResizeSettings.height,
height: currentResizeSettings.width, height: currentResizeSettings.width,
}; };
}
return cleanMerge( return cleanMerge(
side, side,
'latestSettings.processorState.resize', 'latestSettings.processorState.resize',

View File

@@ -44,6 +44,12 @@
transform: translateX(100%); transform: translateX(100%);
} }
@media (max-width: 599px) {
& > .options {
display: none;
}
}
& > .back { & > .back {
display: none; display: none;
} }

View File

@@ -103,16 +103,6 @@ export default class Intro extends Component<Props, State> {
); );
}); });
} }
// TODO: remove this
const demo = demos[3];
fetch(demo.url)
.then((r) => r.blob())
.then((blob) =>
this.props.onFile!(
new File([blob], demo.filename, { type: blob.type }),
),
);
} }
componentWillUnmount() { componentWillUnmount() {