Progress indicator

This commit is contained in:
Jake Archibald
2018-10-20 11:56:37 +01:00
parent 2b87fb3942
commit 720cb9da8b
3 changed files with 55 additions and 11 deletions

View File

@@ -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<Props, State> {
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<Props, State> {
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<Props, State> {
<div class={style.results}>
<div class={style.resultData}>
{!imageFile ? 'Compressing…' :
{!imageFile || showLoadingState ? 'Working…' :
<FileSize
blob={imageFile}
compareTo={(source && imageFile !== source.file) ? source.file : undefined}
@@ -232,11 +257,15 @@ export default class Options extends Component<Props, State> {
</div>
<div class={style.download}>
{(downloadUrl && imageFile) && (
<a href={downloadUrl} download={imageFile.name} title="Download">
<DownloadIcon class={style.downloadIcon} />
</a>
)}
{(downloadUrl && imageFile && !showLoadingState)
? (
<a href={downloadUrl} download={imageFile.name} title="Download">
<DownloadIcon class={style.downloadIcon} />
</a>
) : (
<loading-spinner class={style.spinner} />
)
}
</div>
</div>

View File

@@ -129,3 +129,9 @@ $horizontalPadding: 15px;
text-align: left;
padding: 5px 10px;
}
.spinner {
--color: #fff;
--delay: 0;
--size: 22px;
}

View File

@@ -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<Props, State> {
state: State = {
source: undefined,
loading: false,
loadingCounter: 0,
images: [
{
preprocessorState: defaultPreprocessorState,
@@ -252,7 +255,9 @@ export default class Compress extends Component<Props, State> {
@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<Props, State> {
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<Props, State> {
} 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<Props, State> {
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 (
<div class={style.compress}>
@@ -403,6 +412,7 @@ export default class Compress extends Component<Props, State> {
<div class={`${style.optionPair} ${style[orientation]}`}>
{images.map((image, index) => (
<Options
loading={loading || image.loading}
source={source}
orientation={orientation}
imageIndex={index}
@@ -417,7 +427,6 @@ export default class Compress extends Component<Props, State> {
/>
))}
</div>
{anyLoading && <span style={{ position: 'fixed', top: 0, left: 0 }}>Loading...</span>}
</div>
);
}