Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
2a1b6dc9da | |||
129c33fa12 | |||
3245987113 | |||
593ad62cbb | |||
a625a76e9e | |||
c2a305304b | |||
7389c507fb | |||
68f0f23016 |
2
_redirects.ejs
Normal file
2
_redirects.ejs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/index.html / 301
|
||||||
|
/* /index.html 301
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"license": "apache-2.0",
|
"license": "apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --host 0.0.0.0 --hot",
|
"start": "webpack-dev-server --host 0.0.0.0 --hot",
|
||||||
|
@ -20,7 +20,7 @@ import * as browserPDF from './browser-pdf/encoder';
|
|||||||
type ProcessorWorkerApi = import('./processor-worker').ProcessorWorkerApi;
|
type ProcessorWorkerApi = import('./processor-worker').ProcessorWorkerApi;
|
||||||
|
|
||||||
/** How long the worker should be idle before terminating. */
|
/** How long the worker should be idle before terminating. */
|
||||||
const workerTimeout = 1000;
|
const workerTimeout = 10000;
|
||||||
|
|
||||||
interface ProcessingJobOptions {
|
interface ProcessingJobOptions {
|
||||||
needsWorker?: boolean;
|
needsWorker?: boolean;
|
||||||
|
@ -4,10 +4,6 @@ const bpp = 4;
|
|||||||
|
|
||||||
export function rotate(data: ImageData, opts: RotateOptions): ImageData {
|
export function rotate(data: ImageData, opts: RotateOptions): ImageData {
|
||||||
const { rotate } = opts;
|
const { rotate } = opts;
|
||||||
|
|
||||||
// Early exit if there's no transform.
|
|
||||||
if (rotate === 0) return data;
|
|
||||||
|
|
||||||
const flipDimensions = rotate % 180 !== 0;
|
const flipDimensions = rotate % 180 !== 0;
|
||||||
const { width: inputWidth, height: inputHeight } = data;
|
const { width: inputWidth, height: inputHeight } = data;
|
||||||
const outputWidth = flipDimensions ? inputHeight : inputWidth;
|
const outputWidth = flipDimensions ? inputHeight : inputWidth;
|
||||||
|
@ -9,6 +9,8 @@ import '../../lib/SnackBar';
|
|||||||
import Intro from '../intro';
|
import Intro from '../intro';
|
||||||
import '../custom-els/LoadingSpinner';
|
import '../custom-els/LoadingSpinner';
|
||||||
|
|
||||||
|
const ROUTE_EDITOR = '/editor';
|
||||||
|
|
||||||
const compressPromise = import(
|
const compressPromise = import(
|
||||||
/* webpackChunkName: "main-app" */
|
/* webpackChunkName: "main-app" */
|
||||||
'../compress',
|
'../compress',
|
||||||
@ -18,15 +20,21 @@ const offlinerPromise = import(
|
|||||||
'../../lib/offliner',
|
'../../lib/offliner',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function back() {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {}
|
interface Props {}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
file?: File | Fileish;
|
file?: File | Fileish;
|
||||||
|
isEditorOpen: Boolean;
|
||||||
Compress?: typeof import('../compress').default;
|
Compress?: typeof import('../compress').default;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class App extends Component<Props, State> {
|
export default class App extends Component<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
|
isEditorOpen: false,
|
||||||
file: undefined,
|
file: undefined,
|
||||||
Compress: undefined,
|
Compress: undefined,
|
||||||
};
|
};
|
||||||
@ -53,17 +61,20 @@ export default class App extends Component<Props, State> {
|
|||||||
window.STATE = this.state;
|
window.STATE = this.state;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', this.onPopState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private onFileDrop(event: FileDropEvent) {
|
private onFileDrop({ file }: FileDropEvent) {
|
||||||
const { file } = event;
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
this.openEditor();
|
||||||
this.setState({ file });
|
this.setState({ file });
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private onIntroPickFile(file: File | Fileish) {
|
private onIntroPickFile(file: File | Fileish) {
|
||||||
|
this.openEditor();
|
||||||
this.setState({ file });
|
this.setState({ file });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,18 +85,25 @@ export default class App extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
private onBack() {
|
private onPopState() {
|
||||||
this.setState({ file: undefined });
|
this.setState({ isEditorOpen: location.pathname === ROUTE_EDITOR });
|
||||||
}
|
}
|
||||||
|
|
||||||
render({}: Props, { file, Compress }: State) {
|
@bind
|
||||||
|
private openEditor() {
|
||||||
|
if (this.state.isEditorOpen) return;
|
||||||
|
history.pushState(null, '', ROUTE_EDITOR);
|
||||||
|
this.setState({ isEditorOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
render({}: Props, { file, isEditorOpen, Compress }: State) {
|
||||||
return (
|
return (
|
||||||
<div id="app" class={style.app}>
|
<div id="app" class={style.app}>
|
||||||
<file-drop accept="image/*" onfiledrop={this.onFileDrop} class={style.drop}>
|
<file-drop accept="image/*" onfiledrop={this.onFileDrop} class={style.drop}>
|
||||||
{(!file)
|
{!isEditorOpen
|
||||||
? <Intro onFile={this.onIntroPickFile} showSnack={this.showSnack} />
|
? <Intro onFile={this.onIntroPickFile} showSnack={this.showSnack} />
|
||||||
: (Compress)
|
: (Compress)
|
||||||
? <Compress file={file} showSnack={this.showSnack} onBack={this.onBack} />
|
? <Compress file={file!} showSnack={this.showSnack} onBack={back} />
|
||||||
: <loading-spinner class={style.appLoader}/>
|
: <loading-spinner class={style.appLoader}/>
|
||||||
}
|
}
|
||||||
<snack-bar ref={linkRef(this, 'snackbar')} />
|
<snack-bar ref={linkRef(this, 'snackbar')} />
|
||||||
|
@ -43,6 +43,7 @@ $horizontalPadding: 15px;
|
|||||||
|
|
||||||
.text-field {
|
.text-field {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 2px 0 2px 10px;
|
padding: 2px 0 2px 10px;
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
// We should try to remove this once the issue is fixed.
|
// We should try to remove this once the issue is fixed.
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=870222#c10
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=870222#c10
|
||||||
will-change: auto;
|
will-change: auto;
|
||||||
|
// Prevent the image becoming misshapen due to default flexbox layout.
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
@ -85,12 +85,18 @@ interface UpdateImageOptions {
|
|||||||
skipPreprocessing?: boolean;
|
skipPreprocessing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processInput(
|
async function processInput(
|
||||||
data: ImageData,
|
data: ImageData,
|
||||||
inputProcessData: InputProcessorState,
|
inputProcessData: InputProcessorState,
|
||||||
processor: Processor,
|
processor: Processor,
|
||||||
) {
|
) {
|
||||||
return processor.rotate(data, inputProcessData.rotate);
|
let processedData = data;
|
||||||
|
|
||||||
|
if (inputProcessData.rotate.rotate !== 0) {
|
||||||
|
processedData = await processor.rotate(processedData, inputProcessData.rotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preprocessImage(
|
async function preprocessImage(
|
||||||
@ -154,7 +160,7 @@ function stateForNewSourceData(state: State, newSource: SourceImage): State {
|
|||||||
for (const i of [0, 1]) {
|
for (const i of [0, 1]) {
|
||||||
// Ditch previous encodings
|
// Ditch previous encodings
|
||||||
const downloadUrl = state.sides[i].downloadUrl;
|
const downloadUrl = state.sides[i].downloadUrl;
|
||||||
if (downloadUrl) URL.revokeObjectURL(downloadUrl!);
|
if (downloadUrl) URL.revokeObjectURL(downloadUrl);
|
||||||
|
|
||||||
newState = cleanMerge(state, `sides.${i}`, {
|
newState = cleanMerge(state, `sides.${i}`, {
|
||||||
preprocessed: undefined,
|
preprocessed: undefined,
|
||||||
@ -313,9 +319,14 @@ export default class Compress extends Component<Props, State> {
|
|||||||
private async onCopyToOtherClick(index: 0 | 1) {
|
private async onCopyToOtherClick(index: 0 | 1) {
|
||||||
const otherIndex = (index + 1) % 2;
|
const otherIndex = (index + 1) % 2;
|
||||||
const oldSettings = this.state.sides[otherIndex];
|
const oldSettings = this.state.sides[otherIndex];
|
||||||
|
const newSettings = { ...this.state.sides[index] };
|
||||||
|
|
||||||
|
// Create a new object URL for the new settings. This avoids both sides sharing a URL, which
|
||||||
|
// means it can be safely revoked without impacting the other side.
|
||||||
|
if (newSettings.file) newSettings.downloadUrl = URL.createObjectURL(newSettings.file);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
sides: cleanSet(this.state.sides, otherIndex, this.state.sides[index]),
|
sides: cleanSet(this.state.sides, otherIndex, newSettings),
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await this.props.showSnack('Settings copied across', {
|
const result = await this.props.showSnack('Settings copied across', {
|
||||||
|
@ -251,6 +251,11 @@ module.exports = async function (_, env) {
|
|||||||
filename: '_headers',
|
filename: '_headers',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
isProd && new AssetTemplatePlugin({
|
||||||
|
template: path.join(__dirname, '_redirects.ejs'),
|
||||||
|
filename: '_redirects',
|
||||||
|
}),
|
||||||
|
|
||||||
new ScriptExtHtmlPlugin({
|
new ScriptExtHtmlPlugin({
|
||||||
inline: ['first']
|
inline: ['first']
|
||||||
}),
|
}),
|
||||||
|
Reference in New Issue
Block a user