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:
Jake Archibald
2018-08-07 12:19:06 +01:00
committed by GitHub
parent c90db020b0
commit 44f0700332
4 changed files with 82 additions and 37 deletions

6
package-lock.json generated
View File

@@ -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
},

View File

@@ -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 });
}

View File

@@ -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
View 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);
}