diff --git a/src/components/Options/index.tsx b/src/components/Options/index.tsx index def0ff32..d86dd128 100644 --- a/src/components/Options/index.tsx +++ b/src/components/Options/index.tsx @@ -41,6 +41,7 @@ import { SourceImage } from '../App'; import Checkbox from '../checkbox'; import Expander from '../expander'; import Select from '../select'; +import '../custom-els/LoadingSpinner'; const encoderOptionsComponentMap = { [identity.type]: undefined, @@ -60,6 +61,7 @@ const encoderOptionsComponentMap = { interface Props { orientation: 'horizontal' | 'vertical'; + loading: boolean; source?: SourceImage; imageIndex: number; imageFile?: Fileish; @@ -74,16 +76,39 @@ interface Props { interface State { encoderSupportMap?: EncoderSupportMap; + showLoadingState: boolean; } +const loadingReactionDelay = 500; + export default class Options extends Component { - typeSelect?: HTMLSelectElement; + state: State = { + encoderSupportMap: undefined, + showLoadingState: false, + }; + + /** The timeout ID between entering the loading state, and changing UI */ + private loadingTimeoutId: number = 0; constructor() { super(); encodersSupported.then(encoderSupportMap => this.setState({ encoderSupportMap })); } + componentDidUpdate(prevProps: Props, prevState: State) { + if (prevProps.loading && !this.props.loading) { + // Just stopped loading + clearTimeout(this.loadingTimeoutId); + this.setState({ showLoadingState: false }); + } else if (!prevProps.loading && this.props.loading) { + // Just started loading + this.loadingTimeoutId = self.setTimeout( + () => this.setState({ showLoadingState: true }), + loadingReactionDelay, + ); + } + } + @bind onEncoderTypeChange(event: Event) { const el = event.currentTarget as HTMLSelectElement; @@ -135,7 +160,7 @@ export default class Options extends Component { preprocessorState, onEncoderOptionsChange, }: Props, - { encoderSupportMap }: State, + { encoderSupportMap, showLoadingState }: State, ) { // tslint:disable variable-name const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type]; @@ -223,7 +248,7 @@ export default class Options extends Component {
- {!imageFile ? 'Compressing…' : + {!imageFile || showLoadingState ? 'Working…' : {
- {(downloadUrl && imageFile) && ( - - - - )} + {(downloadUrl && imageFile && !showLoadingState) + ? ( + + + + ) : ( + + ) + }
diff --git a/src/components/Options/style.scss b/src/components/Options/style.scss index c06f57c1..4247abfa 100644 --- a/src/components/Options/style.scss +++ b/src/components/Options/style.scss @@ -129,3 +129,9 @@ $horizontalPadding: 15px; text-align: left; padding: 5px 10px; } + +.spinner { + --color: #fff; + --delay: 0; + --size: 22px; +} diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index 0568ff41..56c8e5b1 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -65,7 +65,9 @@ interface Props { interface State { source?: SourceImage; images: [EncodedImage, EncodedImage]; + /** Source image load */ loading: boolean; + loadingCounter: number; error?: string; orientation: Orientation; } @@ -159,6 +161,7 @@ export default class Compress extends Component { state: State = { source: undefined, loading: false, + loadingCounter: 0, images: [ { preprocessorState: defaultPreprocessorState, @@ -252,7 +255,9 @@ export default class Compress extends Component { @bind private async updateFile(file: File | Fileish) { - this.setState({ loading: true }); + const loadingCounter = this.state.loadingCounter + 1; + + this.setState({ loadingCounter, loading: true }); // Abort any current encode jobs, as they're redundant now. this.leftProcessor.abortCurrent(); @@ -273,6 +278,9 @@ export default class Compress extends Component { data = await decodeImage(file, this.leftProcessor); } + // Another file has been opened before this one processed. + if (this.state.loadingCounter !== loadingCounter) return; + let newState: State = { ...this.state, source: { data, file, vectorImage }, @@ -303,6 +311,8 @@ export default class Compress extends Component { } catch (err) { if (err.name === 'AbortError') return; console.error(err); + // Another file has been opened before this one processed. + if (this.state.loadingCounter !== loadingCounter) return; this.props.onError('Invalid image'); this.setState({ loading: false }); } @@ -388,7 +398,6 @@ export default class Compress extends Component { render({ }: Props, { loading, images, source, orientation }: State) { const [leftImage, rightImage] = images; const [leftImageData, rightImageData] = images.map(i => i.data); - const anyLoading = loading || images.some(image => image.loading); return (
@@ -403,6 +412,7 @@ export default class Compress extends Component {
{images.map((image, index) => ( { /> ))}
- {anyLoading && Loading...}
); }