forked from external-repos/squoosh
Adding clean-set (#124)
* Adding clean-set * Moving to our own cleanSet and cleanMerge. * Oops, this can be simpler * Allow the path to be a number * Better typing
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -7904,7 +7904,7 @@
|
|||||||
},
|
},
|
||||||
"onetime": {
|
"onetime": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@@ -8058,7 +8058,7 @@
|
|||||||
},
|
},
|
||||||
"onetime": {
|
"onetime": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@@ -9414,7 +9414,7 @@
|
|||||||
},
|
},
|
||||||
"onetime": {
|
"onetime": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
} from '../../codecs/preprocessors';
|
} from '../../codecs/preprocessors';
|
||||||
|
|
||||||
import { decodeImage } from '../../codecs/decoders';
|
import { decodeImage } from '../../codecs/decoders';
|
||||||
|
import { cleanMerge } from '../../lib/clean-modify';
|
||||||
|
|
||||||
interface SourceImage {
|
interface SourceImage {
|
||||||
file: File;
|
file: File;
|
||||||
@@ -155,9 +156,6 @@ export default class App extends Component<Props, State> {
|
|||||||
type: EncoderType,
|
type: EncoderType,
|
||||||
options?: EncoderOptions,
|
options?: EncoderOptions,
|
||||||
): void {
|
): void {
|
||||||
const images = this.state.images.slice() as [EncodedImage, EncodedImage];
|
|
||||||
const oldImage = images[index];
|
|
||||||
|
|
||||||
// Some type cheating here.
|
// Some type cheating here.
|
||||||
// encoderMap[type].defaultOptions is always safe.
|
// encoderMap[type].defaultOptions is always safe.
|
||||||
// options should always be correct for the type, but TypeScript isn't smart enough.
|
// options should always be correct for the type, but TypeScript isn't smart enough.
|
||||||
@@ -166,12 +164,7 @@ export default class App extends Component<Props, State> {
|
|||||||
options: options || encoderMap[type].defaultOptions,
|
options: options || encoderMap[type].defaultOptions,
|
||||||
} as EncoderState;
|
} as EncoderState;
|
||||||
|
|
||||||
images[index] = {
|
const images = cleanMerge(this.state.images, index, { encoderState, preprocessorState });
|
||||||
...oldImage,
|
|
||||||
encoderState,
|
|
||||||
preprocessorState,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setState({ images });
|
this.setState({ images });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,19 +243,19 @@ export default class App extends Component<Props, State> {
|
|||||||
async updateImage(index: number): Promise<void> {
|
async updateImage(index: number): Promise<void> {
|
||||||
const { source } = this.state;
|
const { source } = this.state;
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
let images = this.state.images.slice() as [EncodedImage, EncodedImage];
|
|
||||||
|
|
||||||
// Each time we trigger an async encode, the counter changes.
|
// Each time we trigger an async encode, the counter changes.
|
||||||
const loadingCounter = images[index].loadingCounter + 1;
|
const loadingCounter = this.state.images[index].loadingCounter + 1;
|
||||||
|
|
||||||
const image = images[index] = {
|
let images = cleanMerge(this.state.images, index, {
|
||||||
...images[index],
|
|
||||||
loadingCounter,
|
loadingCounter,
|
||||||
loading: true,
|
loading: true,
|
||||||
};
|
});
|
||||||
|
|
||||||
this.setState({ images });
|
this.setState({ images });
|
||||||
|
|
||||||
|
const image = images[index];
|
||||||
|
|
||||||
let file;
|
let file;
|
||||||
try {
|
try {
|
||||||
source.preprocessed = await preprocessImage(source, image.preprocessorState);
|
source.preprocessed = await preprocessImage(source, image.preprocessorState);
|
||||||
@@ -286,16 +279,13 @@ export default class App extends Component<Props, State> {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
images = this.state.images.slice() as [EncodedImage, EncodedImage];
|
images = cleanMerge(this.state.images, '' + index, {
|
||||||
|
|
||||||
images[index] = {
|
|
||||||
...images[index],
|
|
||||||
file,
|
file,
|
||||||
bmp,
|
bmp,
|
||||||
downloadUrl: URL.createObjectURL(file),
|
downloadUrl: URL.createObjectURL(file),
|
||||||
loading: images[index].loadingCounter !== loadingCounter,
|
loading: images[index].loadingCounter !== loadingCounter,
|
||||||
loadedCounter: loadingCounter,
|
loadedCounter: loadingCounter,
|
||||||
};
|
});
|
||||||
|
|
||||||
this.setState({ images });
|
this.setState({ images });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
import * as style from './style.scss';
|
import * as style from './style.scss';
|
||||||
import { bind } from '../../lib/util';
|
import { bind } from '../../lib/util';
|
||||||
|
import { cleanSet, cleanMerge } from '../../lib/clean-modify';
|
||||||
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
||||||
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
||||||
import WebPEncoderOptions from '../../codecs/webp/options';
|
import WebPEncoderOptions from '../../codecs/webp/options';
|
||||||
@@ -80,27 +82,18 @@ export default class Options extends Component<Props, State> {
|
|||||||
@bind
|
@bind
|
||||||
onPreprocessorEnabledChange(event: Event) {
|
onPreprocessorEnabledChange(event: Event) {
|
||||||
const el = event.currentTarget as HTMLInputElement;
|
const el = event.currentTarget as HTMLInputElement;
|
||||||
|
|
||||||
const preprocessor = el.name.split('.')[0] as keyof PreprocessorState;
|
const preprocessor = el.name.split('.')[0] as keyof PreprocessorState;
|
||||||
|
|
||||||
this.props.onPreprocessorOptionsChange({
|
this.props.onPreprocessorOptionsChange(
|
||||||
...this.props.preprocessorState,
|
cleanSet(this.props.preprocessorState, `${preprocessor}.enabled`, el.checked),
|
||||||
[preprocessor]: {
|
);
|
||||||
...this.props.preprocessorState[preprocessor],
|
|
||||||
enabled: el.checked,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
onQuantizerOptionsChange(opts: QuantizeOptions) {
|
onQuantizerOptionsChange(opts: QuantizeOptions) {
|
||||||
this.props.onPreprocessorOptionsChange({
|
this.props.onPreprocessorOptionsChange(
|
||||||
...this.props.preprocessorState,
|
cleanMerge(this.props.preprocessorState, 'quantizer', opts),
|
||||||
quantizer: {
|
);
|
||||||
...opts,
|
|
||||||
enabled: this.props.preprocessorState.quantizer.enabled,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
|||||||
62
src/lib/clean-modify.ts
Normal file
62
src/lib/clean-modify.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
function cleanSetOrMerge<A extends any[] | object>(
|
||||||
|
source: A,
|
||||||
|
keys: string | number | string[],
|
||||||
|
toSetOrMerge: any[] | object,
|
||||||
|
merge: boolean,
|
||||||
|
): A {
|
||||||
|
const splitKeys = Array.isArray(keys) ? keys : ('' + keys).split('.');
|
||||||
|
|
||||||
|
// Going off road in terms of types, otherwise TypeScript doesn't like the access-by-index.
|
||||||
|
// The assumptions in this code break if the object contains things which aren't arrays or
|
||||||
|
// plain objects.
|
||||||
|
let last = copy(source) as any;
|
||||||
|
const newObject = last;
|
||||||
|
|
||||||
|
const lastIndex = splitKeys.length - 1;
|
||||||
|
|
||||||
|
for (const [i, key] of splitKeys.entries()) {
|
||||||
|
if (i !== lastIndex) {
|
||||||
|
// Copy everything along the path.
|
||||||
|
last = last[key] = copy(last[key]);
|
||||||
|
} else {
|
||||||
|
// Merge or set.
|
||||||
|
last[key] = merge ?
|
||||||
|
Object.assign(copy(last[key]), toSetOrMerge) :
|
||||||
|
toSetOrMerge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy<A extends any[] | object>(source: A): A {
|
||||||
|
// Some type cheating here, as TypeScript can't infer between generic types.
|
||||||
|
if (Array.isArray(source)) return [...source] as any;
|
||||||
|
return { ...(source as any) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param source Object to copy from.
|
||||||
|
* @param keys Path to modify, eg "foo.bar.baz".
|
||||||
|
* @param toMerge A value to merge into the value at the path.
|
||||||
|
*/
|
||||||
|
export function cleanMerge<A extends any[] | object>(
|
||||||
|
source: A,
|
||||||
|
keys: string | number | string[],
|
||||||
|
toMerge: any[] | object,
|
||||||
|
): A {
|
||||||
|
return cleanSetOrMerge(source, keys, toMerge, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param source Object to copy from.
|
||||||
|
* @param keys Path to modify, eg "foo.bar.baz".
|
||||||
|
* @param newValue A value to set at the path.
|
||||||
|
*/
|
||||||
|
export function cleanSet<A extends any[] | object>(
|
||||||
|
source: A,
|
||||||
|
keys: string | number | string[],
|
||||||
|
newValue: any,
|
||||||
|
): A {
|
||||||
|
return cleanSetOrMerge(source, keys, newValue, false);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user