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": {
|
||||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -8058,7 +8058,7 @@
|
||||
},
|
||||
"onetime": {
|
||||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
@@ -9414,7 +9414,7 @@
|
||||
},
|
||||
"onetime": {
|
||||
"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=",
|
||||
"dev": true
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from '../../codecs/preprocessors';
|
||||
|
||||
import { decodeImage } from '../../codecs/decoders';
|
||||
import { cleanMerge } from '../../lib/clean-modify';
|
||||
|
||||
interface SourceImage {
|
||||
file: File;
|
||||
@@ -155,9 +156,6 @@ export default class App extends Component<Props, State> {
|
||||
type: EncoderType,
|
||||
options?: EncoderOptions,
|
||||
): void {
|
||||
const images = this.state.images.slice() as [EncodedImage, EncodedImage];
|
||||
const oldImage = images[index];
|
||||
|
||||
// Some type cheating here.
|
||||
// encoderMap[type].defaultOptions is always safe.
|
||||
// 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,
|
||||
} as EncoderState;
|
||||
|
||||
images[index] = {
|
||||
...oldImage,
|
||||
encoderState,
|
||||
preprocessorState,
|
||||
};
|
||||
|
||||
const images = cleanMerge(this.state.images, index, { encoderState, preprocessorState });
|
||||
this.setState({ images });
|
||||
}
|
||||
|
||||
@@ -250,19 +243,19 @@ export default class App extends Component<Props, State> {
|
||||
async updateImage(index: number): Promise<void> {
|
||||
const { source } = this.state;
|
||||
if (!source) return;
|
||||
let images = this.state.images.slice() as [EncodedImage, EncodedImage];
|
||||
|
||||
// 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] = {
|
||||
...images[index],
|
||||
let images = cleanMerge(this.state.images, index, {
|
||||
loadingCounter,
|
||||
loading: true,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ images });
|
||||
|
||||
const image = images[index];
|
||||
|
||||
let file;
|
||||
try {
|
||||
source.preprocessed = await preprocessImage(source, image.preprocessorState);
|
||||
@@ -286,16 +279,13 @@ export default class App extends Component<Props, State> {
|
||||
throw err;
|
||||
}
|
||||
|
||||
images = this.state.images.slice() as [EncodedImage, EncodedImage];
|
||||
|
||||
images[index] = {
|
||||
...images[index],
|
||||
images = cleanMerge(this.state.images, '' + index, {
|
||||
file,
|
||||
bmp,
|
||||
downloadUrl: URL.createObjectURL(file),
|
||||
loading: images[index].loadingCounter !== loadingCounter,
|
||||
loadedCounter: loadingCounter,
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ images });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import * as style from './style.scss';
|
||||
import { bind } from '../../lib/util';
|
||||
import { cleanSet, cleanMerge } from '../../lib/clean-modify';
|
||||
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
||||
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
||||
import WebPEncoderOptions from '../../codecs/webp/options';
|
||||
@@ -80,27 +82,18 @@ export default class Options extends Component<Props, State> {
|
||||
@bind
|
||||
onPreprocessorEnabledChange(event: Event) {
|
||||
const el = event.currentTarget as HTMLInputElement;
|
||||
|
||||
const preprocessor = el.name.split('.')[0] as keyof PreprocessorState;
|
||||
|
||||
this.props.onPreprocessorOptionsChange({
|
||||
...this.props.preprocessorState,
|
||||
[preprocessor]: {
|
||||
...this.props.preprocessorState[preprocessor],
|
||||
enabled: el.checked,
|
||||
},
|
||||
});
|
||||
this.props.onPreprocessorOptionsChange(
|
||||
cleanSet(this.props.preprocessorState, `${preprocessor}.enabled`, el.checked),
|
||||
);
|
||||
}
|
||||
|
||||
@bind
|
||||
onQuantizerOptionsChange(opts: QuantizeOptions) {
|
||||
this.props.onPreprocessorOptionsChange({
|
||||
...this.props.preprocessorState,
|
||||
quantizer: {
|
||||
...opts,
|
||||
enabled: this.props.preprocessorState.quantizer.enabled,
|
||||
},
|
||||
});
|
||||
this.props.onPreprocessorOptionsChange(
|
||||
cleanMerge(this.props.preprocessorState, 'quantizer', opts),
|
||||
);
|
||||
}
|
||||
|
||||
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