mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 08:47:31 +00:00
Progress indicator
This commit is contained in:
@@ -41,6 +41,7 @@ import { SourceImage } from '../App';
|
|||||||
import Checkbox from '../checkbox';
|
import Checkbox from '../checkbox';
|
||||||
import Expander from '../expander';
|
import Expander from '../expander';
|
||||||
import Select from '../select';
|
import Select from '../select';
|
||||||
|
import '../custom-els/LoadingSpinner';
|
||||||
|
|
||||||
const encoderOptionsComponentMap = {
|
const encoderOptionsComponentMap = {
|
||||||
[identity.type]: undefined,
|
[identity.type]: undefined,
|
||||||
@@ -60,6 +61,7 @@ const encoderOptionsComponentMap = {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
orientation: 'horizontal' | 'vertical';
|
orientation: 'horizontal' | 'vertical';
|
||||||
|
loading: boolean;
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
imageIndex: number;
|
imageIndex: number;
|
||||||
imageFile?: Fileish;
|
imageFile?: Fileish;
|
||||||
@@ -74,16 +76,39 @@ interface Props {
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
encoderSupportMap?: EncoderSupportMap;
|
encoderSupportMap?: EncoderSupportMap;
|
||||||
|
showLoadingState: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingReactionDelay = 500;
|
||||||
|
|
||||||
export default class Options extends Component<Props, State> {
|
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() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
encodersSupported.then(encoderSupportMap => this.setState({ encoderSupportMap }));
|
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
|
@bind
|
||||||
onEncoderTypeChange(event: Event) {
|
onEncoderTypeChange(event: Event) {
|
||||||
const el = event.currentTarget as HTMLSelectElement;
|
const el = event.currentTarget as HTMLSelectElement;
|
||||||
@@ -135,7 +160,7 @@ export default class Options extends Component<Props, State> {
|
|||||||
preprocessorState,
|
preprocessorState,
|
||||||
onEncoderOptionsChange,
|
onEncoderOptionsChange,
|
||||||
}: Props,
|
}: Props,
|
||||||
{ encoderSupportMap }: State,
|
{ encoderSupportMap, showLoadingState }: State,
|
||||||
) {
|
) {
|
||||||
// tslint:disable variable-name
|
// tslint:disable variable-name
|
||||||
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
|
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
|
||||||
@@ -223,7 +248,7 @@ export default class Options extends Component<Props, State> {
|
|||||||
|
|
||||||
<div class={style.results}>
|
<div class={style.results}>
|
||||||
<div class={style.resultData}>
|
<div class={style.resultData}>
|
||||||
{!imageFile ? 'Compressing…' :
|
{!imageFile || showLoadingState ? 'Working…' :
|
||||||
<FileSize
|
<FileSize
|
||||||
blob={imageFile}
|
blob={imageFile}
|
||||||
compareTo={(source && imageFile !== source.file) ? source.file : undefined}
|
compareTo={(source && imageFile !== source.file) ? source.file : undefined}
|
||||||
@@ -232,11 +257,15 @@ export default class Options extends Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={style.download}>
|
<div class={style.download}>
|
||||||
{(downloadUrl && imageFile) && (
|
{(downloadUrl && imageFile && !showLoadingState)
|
||||||
<a href={downloadUrl} download={imageFile.name} title="Download">
|
? (
|
||||||
<DownloadIcon class={style.downloadIcon} />
|
<a href={downloadUrl} download={imageFile.name} title="Download">
|
||||||
</a>
|
<DownloadIcon class={style.downloadIcon} />
|
||||||
)}
|
</a>
|
||||||
|
) : (
|
||||||
|
<loading-spinner class={style.spinner} />
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -129,3 +129,9 @@ $horizontalPadding: 15px;
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
--color: #fff;
|
||||||
|
--delay: 0;
|
||||||
|
--size: 22px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ interface Props {
|
|||||||
interface State {
|
interface State {
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
images: [EncodedImage, EncodedImage];
|
images: [EncodedImage, EncodedImage];
|
||||||
|
/** Source image load */
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
loadingCounter: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
orientation: Orientation;
|
orientation: Orientation;
|
||||||
}
|
}
|
||||||
@@ -159,6 +161,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
state: State = {
|
state: State = {
|
||||||
source: undefined,
|
source: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
loadingCounter: 0,
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
preprocessorState: defaultPreprocessorState,
|
preprocessorState: defaultPreprocessorState,
|
||||||
@@ -252,7 +255,9 @@ export default class Compress extends Component<Props, State> {
|
|||||||
|
|
||||||
@bind
|
@bind
|
||||||
private async updateFile(file: File | Fileish) {
|
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.
|
// Abort any current encode jobs, as they're redundant now.
|
||||||
this.leftProcessor.abortCurrent();
|
this.leftProcessor.abortCurrent();
|
||||||
@@ -273,6 +278,9 @@ export default class Compress extends Component<Props, State> {
|
|||||||
data = await decodeImage(file, this.leftProcessor);
|
data = await decodeImage(file, this.leftProcessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Another file has been opened before this one processed.
|
||||||
|
if (this.state.loadingCounter !== loadingCounter) return;
|
||||||
|
|
||||||
let newState: State = {
|
let newState: State = {
|
||||||
...this.state,
|
...this.state,
|
||||||
source: { data, file, vectorImage },
|
source: { data, file, vectorImage },
|
||||||
@@ -303,6 +311,8 @@ export default class Compress extends Component<Props, State> {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'AbortError') return;
|
if (err.name === 'AbortError') return;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
// Another file has been opened before this one processed.
|
||||||
|
if (this.state.loadingCounter !== loadingCounter) return;
|
||||||
this.props.onError('Invalid image');
|
this.props.onError('Invalid image');
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
@@ -388,7 +398,6 @@ export default class Compress extends Component<Props, State> {
|
|||||||
render({ }: Props, { loading, images, source, orientation }: State) {
|
render({ }: Props, { loading, images, source, orientation }: State) {
|
||||||
const [leftImage, rightImage] = images;
|
const [leftImage, rightImage] = images;
|
||||||
const [leftImageData, rightImageData] = images.map(i => i.data);
|
const [leftImageData, rightImageData] = images.map(i => i.data);
|
||||||
const anyLoading = loading || images.some(image => image.loading);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.compress}>
|
<div class={style.compress}>
|
||||||
@@ -403,6 +412,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
<div class={`${style.optionPair} ${style[orientation]}`}>
|
<div class={`${style.optionPair} ${style[orientation]}`}>
|
||||||
{images.map((image, index) => (
|
{images.map((image, index) => (
|
||||||
<Options
|
<Options
|
||||||
|
loading={loading || image.loading}
|
||||||
source={source}
|
source={source}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
imageIndex={index}
|
imageIndex={index}
|
||||||
@@ -417,7 +427,6 @@ export default class Compress extends Component<Props, State> {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{anyLoading && <span style={{ position: 'fixed', top: 0, left: 0 }}>Loading...</span>}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user