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

View File

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

View File

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

View File

@@ -83,10 +83,6 @@ export default class Cropper extends Component<Props, State> {
) {
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 });
if (this.props.onChange) {
this.props.onChange(crop);
@@ -261,10 +257,14 @@ export default class Cropper extends Component<Props, State> {
const y = crop.top;
const width = size.width - crop.left - crop.right;
const height = size.height - crop.top - crop.bottom;
// const x = crop.left.toFixed(2);
// const y = crop.top.toFixed(2);
// const width = (size.width - crop.left - crop.right).toFixed(2);
// const height = (size.height - crop.top - crop.bottom).toFixed(2);
const s = (x: number) => x / (scale || 1);
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 (
<svg
@@ -280,45 +280,11 @@ export default class Cropper extends Component<Props, State> {
onPointerMove={this.onPointerMove}
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
class={style.background}
width={size.width}
height={size.height}
// mask="url(#bg)"
// 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)`}
clip-path={clip}
/>
<svg x={x} y={y} width={width} height={height}>
<Freezer>
@@ -328,7 +294,6 @@ export default class Cropper extends Component<Props, State> {
data-edge="left,right,top,bottom"
width="100%"
height="100%"
// filter="url(#shadow2)"
/>
<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="right" height="100%" x="100%" />
<circle
class={style.corner}
data-edge="left,top"
// filter="url(#shadow)"
/>
<circle
class={style.corner}
data-edge="right,top"
cx="100%"
// filter="url(#shadow)"
/>
<circle class={style.corner} data-edge="left,top" />
<circle class={style.corner} data-edge="right,top" cx="100%" />
<circle
class={style.corner}
data-edge="right,bottom"
cx="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>
</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>
);
}

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ import {
canDecodeImageType,
abortable,
assertSignal,
shallowEqual,
} from '../util';
import {
PreprocessorState,
@@ -447,25 +448,38 @@ export default class Compress extends Component<Props, State> {
const newRotate = preprocessorState.rotate.rotate;
const orientationChanged = oldRotate % 180 !== newRotate % 180;
const { crop } = preprocessorState;
const cropChanged = !shallowEqual(crop, this.state.preprocessorState.crop);
this.setState((state) => ({
loading: true,
preprocessorState,
// Flip resize values if orientation has changed
sides: !orientationChanged
? state.sides
: (state.sides.map((side) => {
const currentResizeSettings =
side.latestSettings.processorState.resize;
const resizeSettings: Partial<ProcessorState['resize']> = {
width: currentResizeSettings.height,
height: currentResizeSettings.width,
};
return cleanMerge(
side,
'latestSettings.processorState.resize',
resizeSettings,
);
}) as [Side, Side]),
sides:
!orientationChanged && !cropChanged
? state.sides
: (state.sides.map((side) => {
const currentResizeSettings =
side.latestSettings.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,
height: currentResizeSettings.width,
};
}
return cleanMerge(
side,
'latestSettings.processorState.resize',
resizeSettings,
);
}) as [Side, Side]),
}));
};

View File

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