Skip to content

Commit 5424fe0

Browse files
committed
Rework loading component
1 parent e1cbc68 commit 5424fe0

File tree

21 files changed

+265
-338
lines changed

21 files changed

+265
-338
lines changed

components/dash-core-components/src/components/Loading.react.js

Lines changed: 91 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import React, {useEffect, useRef, useState} from 'react';
1+
import React, {useState, useRef, useMemo, useEffect} from 'react';
2+
import {equals, concat, includes, toPairs} from 'ramda';
23
import PropTypes from 'prop-types';
4+
35
import GraphSpinner from '../fragments/Loading/spinners/GraphSpinner.jsx';
46
import DefaultSpinner from '../fragments/Loading/spinners/DefaultSpinner.jsx';
57
import CubeSpinner from '../fragments/Loading/spinners/CubeSpinner.jsx';
@@ -16,12 +18,51 @@ const spinnerComponentOptions = {
1618
const getSpinner = spinnerType =>
1719
spinnerComponentOptions[spinnerType] || DefaultSpinner;
1820

19-
/**
20-
* A Loading component that wraps any other component and displays a spinner until the wrapped component has rendered.
21-
*/
22-
const Loading = ({
21+
const coveringSpinner = {
22+
visibility: 'visible',
23+
position: 'absolute',
24+
top: '0',
25+
height: '100%',
26+
width: '100%',
27+
display: 'flex',
28+
justifyContent: 'center',
29+
alignItems: 'center',
30+
};
31+
32+
const loadingSelector = (componentPath, targetComponents) => state => {
33+
let stringPath = JSON.stringify(componentPath);
34+
// Remove the last ] for easy match
35+
stringPath = stringPath.substring(0, stringPath.length - 1);
36+
const loadingChildren = toPairs(state.loading).reduce(
37+
(acc, [path, load]) => {
38+
if (path.startsWith(stringPath)) {
39+
if (targetComponents) {
40+
const target = targetComponents[load.id];
41+
if (!target) {
42+
return acc;
43+
}
44+
if (Array.isArray(target)) {
45+
if (!includes(load.property, target)) {
46+
return acc;
47+
}
48+
} else if (load.property !== target) {
49+
return acc;
50+
}
51+
}
52+
return concat(acc, load);
53+
}
54+
return acc;
55+
},
56+
[]
57+
);
58+
if (loadingChildren.length) {
59+
return loadingChildren;
60+
}
61+
return null;
62+
};
63+
64+
function Loading({
2365
children,
24-
loading_state,
2566
display = 'auto',
2667
color = '#119DFF',
2768
id,
@@ -38,91 +79,60 @@ const Loading = ({
3879
delay_show = 0,
3980
target_components,
4081
custom_spinner,
41-
}) => {
42-
const coveringSpinner = {
43-
visibility: 'visible',
44-
position: 'absolute',
45-
top: '0',
46-
height: '100%',
47-
width: '100%',
48-
display: 'flex',
49-
justifyContent: 'center',
50-
alignItems: 'center',
51-
};
52-
53-
/* Overrides default Loading behavior if target_components is set. By default,
54-
* Loading fires when any recursive child enters loading state. This makes loading
55-
* opt-in: Loading animation only enabled when one of target components enters loading state.
56-
*/
57-
const isTarget = () => {
58-
if (!target_components) {
59-
return true;
60-
}
61-
const isMatchingComponent = () => {
62-
return Object.entries(target_components).some(
63-
([component_name, prop_names]) => {
64-
// Convert prop_names to an array if it's not already
65-
const prop_names_array = Array.isArray(prop_names)
66-
? prop_names
67-
: [prop_names];
68-
69-
return (
70-
loading_state.component_name === component_name &&
71-
(prop_names_array.includes('*') ||
72-
prop_names_array.some(
73-
prop_name =>
74-
loading_state.prop_name === prop_name
75-
))
76-
);
77-
}
78-
);
79-
};
80-
return isMatchingComponent;
81-
};
82+
}) {
83+
const ctx = window.dash_component_api.useDashContext();
84+
const loading = ctx.useSelector(
85+
loadingSelector(ctx.componentPath, target_components),
86+
equals
87+
);
8288

8389
const [showSpinner, setShowSpinner] = useState(show_initially);
8490
const dismissTimer = useRef();
8591
const showTimer = useRef();
8692

87-
// delay_hide and delay_show is from dash-bootstrap-components dbc.Spinner
93+
const containerStyle = useMemo(() => {
94+
if (showSpinner) {
95+
return {visibility: 'hidden', ...overlay_style, ...parent_style};
96+
}
97+
return parent_style;
98+
}, [showSpinner, parent_style]);
99+
88100
useEffect(() => {
89101
if (display === 'show' || display === 'hide') {
90102
setShowSpinner(display === 'show');
91103
return;
92104
}
93105

94-
if (loading_state) {
95-
if (loading_state.is_loading) {
96-
// if component is currently loading and there's a dismiss timer active
97-
// we need to clear it.
98-
if (dismissTimer.current) {
99-
dismissTimer.current = clearTimeout(dismissTimer.current);
100-
}
101-
// if component is currently loading but the spinner is not showing and
102-
// there is no timer set to show, then set a timeout to show
103-
if (!showSpinner && !showTimer.current) {
104-
showTimer.current = setTimeout(() => {
105-
setShowSpinner(isTarget());
106-
showTimer.current = null;
107-
}, delay_show);
108-
}
109-
} else {
110-
// if component is not currently loading and there's a show timer
111-
// active we need to clear it
112-
if (showTimer.current) {
113-
showTimer.current = clearTimeout(showTimer.current);
114-
}
115-
// if component is not currently loading and the spinner is showing and
116-
// there's no timer set to dismiss it, then set a timeout to hide it
117-
if (showSpinner && !dismissTimer.current) {
118-
dismissTimer.current = setTimeout(() => {
119-
setShowSpinner(false);
120-
dismissTimer.current = null;
121-
}, delay_hide);
122-
}
106+
if (loading) {
107+
// if component is currently loading and there's a dismiss timer active
108+
// we need to clear it.
109+
if (dismissTimer.current) {
110+
dismissTimer.current = clearTimeout(dismissTimer.current);
111+
}
112+
// if component is currently loading but the spinner is not showing and
113+
// there is no timer set to show, then set a timeout to show
114+
if (!showSpinner && !showTimer.current) {
115+
showTimer.current = setTimeout(() => {
116+
setShowSpinner(true);
117+
showTimer.current = null;
118+
}, delay_show);
119+
}
120+
} else {
121+
// if component is not currently loading and there's a show timer
122+
// active we need to clear it
123+
if (showTimer.current) {
124+
showTimer.current = clearTimeout(showTimer.current);
125+
}
126+
// if component is not currently loading and the spinner is showing and
127+
// there's no timer set to dismiss it, then set a timeout to hide it
128+
if (showSpinner && !dismissTimer.current) {
129+
dismissTimer.current = setTimeout(() => {
130+
setShowSpinner(false);
131+
dismissTimer.current = null;
132+
}, delay_hide);
123133
}
124134
}
125-
}, [delay_hide, delay_show, loading_state, display, showSpinner]);
135+
}, [delay_hide, delay_show, loading, display, showSpinner]);
126136

127137
const Spinner = showSpinner && getSpinner(spinnerType);
128138

@@ -131,18 +141,7 @@ const Loading = ({
131141
style={{position: 'relative', ...parent_style}}
132142
className={parent_className}
133143
>
134-
<div
135-
className={parent_className}
136-
style={
137-
showSpinner
138-
? {
139-
visibility: 'hidden',
140-
...overlay_style,
141-
...parent_style,
142-
}
143-
: parent_style
144-
}
145-
>
144+
<div className={parent_className} style={containerStyle}>
146145
{children}
147146
</div>
148147
<div id={id} style={showSpinner ? coveringSpinner : {}}>
@@ -151,7 +150,7 @@ const Loading = ({
151150
<Spinner
152151
className={className}
153152
style={style}
154-
status={loading_state}
153+
status={loading}
155154
color={color}
156155
debug={debug}
157156
fullscreen={fullscreen}
@@ -160,9 +159,7 @@ const Loading = ({
160159
</div>
161160
</div>
162161
);
163-
};
164-
165-
Loading._dashprivate_isLoadingComponent = true;
162+
}
166163

167164
Loading.propTypes = {
168165
/**
@@ -227,24 +224,6 @@ Loading.propTypes = {
227224
*/
228225
color: PropTypes.string,
229226

230-
/**
231-
* Object that holds the loading state object coming from dash-renderer
232-
*/
233-
loading_state: PropTypes.shape({
234-
/**
235-
* Determines if the component is loading or not
236-
*/
237-
is_loading: PropTypes.bool,
238-
/**
239-
* Holds which property is loading
240-
*/
241-
prop_name: PropTypes.string,
242-
/**
243-
* Holds the name of the component that is loading
244-
*/
245-
component_name: PropTypes.string,
246-
}),
247-
248227
/**
249228
* Setting display to "show" or "hide" will override the loading state coming from dash-renderer
250229
*/

components/dash-core-components/src/fragments/Loading/spinners/CircleSpinner.jsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4+
import DebugTitle from './DebugTitle.jsx';
5+
6+
47
/**
58
* Spinner created by Tobias Ahlin, https://github.com/tobiasahlin/SpinKit
69
*/
@@ -14,12 +17,7 @@ const CircleSpinner = ({
1417
}) => {
1518
let debugTitle;
1619
if (debug) {
17-
debugTitle = (
18-
<h3 className="dash-loading-title">
19-
Loading {status.component_name}
20-
's {status.prop_name}
21-
</h3>
22-
);
20+
debugTitle = status.map((s) => <DebugTitle {...s} />);
2321
}
2422
let spinnerClass = fullscreen ? 'dash-spinner-container' : '';
2523
if (className) {
@@ -187,7 +185,7 @@ const CircleSpinner = ({
187185
};
188186

189187
CircleSpinner.propTypes = {
190-
status: PropTypes.object,
188+
status: PropTypes.array,
191189
color: PropTypes.string,
192190
className: PropTypes.string,
193191
fullscreen: PropTypes.bool,

components/dash-core-components/src/fragments/Loading/spinners/CubeSpinner.jsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import changeColor from 'color';
44

5+
import DebugTitle from './DebugTitle.jsx';
6+
7+
58
const CubeSpinner = ({status, color, fullscreen, debug, className, style}) => {
69
let debugTitle;
710
if (debug) {
8-
debugTitle = (
9-
<h3 className="dash-loading-title">
10-
Loading {status.component_name}
11-
's {status.prop_name}
12-
</h3>
13-
);
11+
debugTitle = status.map((s) => <DebugTitle {...s} />);
1412
}
1513
let spinnerClass = fullscreen ? 'dash-spinner-container' : '';
1614
if (className) {
@@ -189,7 +187,7 @@ const CubeSpinner = ({status, color, fullscreen, debug, className, style}) => {
189187
};
190188

191189
CubeSpinner.propTypes = {
192-
status: PropTypes.object,
190+
status: PropTypes.array,
193191
color: PropTypes.string,
194192
className: PropTypes.string,
195193
fullscreen: PropTypes.bool,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
3+
export default function DebugTitle({id, property}) {
4+
return (
5+
<h3 className="dash-loading-title">
6+
Loading #{id}
7+
's {property}
8+
</h3>
9+
)
10+
}

components/dash-core-components/src/fragments/Loading/spinners/DefaultSpinner.jsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4+
import DebugTitle from './DebugTitle.jsx';
5+
46
/**
57
* Spinner created by Tobias Ahlin, https://github.com/tobiasahlin/SpinKit
68
*/
@@ -14,12 +16,7 @@ const DefaultSpinner = ({
1416
}) => {
1517
let debugTitle;
1618
if (debug) {
17-
debugTitle = (
18-
<h3 className="dash-loading-title">
19-
Loading {status.component_name}
20-
's {status.prop_name}
21-
</h3>
22-
);
19+
debugTitle = status.map((s) => <DebugTitle {...s} />);
2320
}
2421
let spinnerClass = fullscreen ? 'dash-spinner-container' : '';
2522
if (className) {
@@ -112,7 +109,7 @@ const DefaultSpinner = ({
112109
};
113110

114111
DefaultSpinner.propTypes = {
115-
status: PropTypes.object,
112+
status: PropTypes.array,
116113
color: PropTypes.string,
117114
className: PropTypes.string,
118115
fullscreen: PropTypes.bool,

components/dash-core-components/src/fragments/Loading/spinners/DotSpinner.jsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4+
import DebugTitle from './DebugTitle.jsx';
5+
46
/**
57
* Spinner created by Tobias Ahlin, https://github.com/tobiasahlin/SpinKit
68
*/
79
const DotSpinner = ({status, color, fullscreen, debug, className, style}) => {
810
let debugTitle;
911
if (debug) {
10-
debugTitle = (
11-
<h3 className="dash-loading-title">
12-
Loading {status.component_name}
13-
's {status.prop_name}
14-
</h3>
15-
);
12+
debugTitle = status.map((s) => <DebugTitle {...s} />);
1613
}
1714
let spinnerClass = fullscreen ? 'dash-spinner-container' : '';
1815
if (className) {
@@ -91,7 +88,7 @@ const DotSpinner = ({status, color, fullscreen, debug, className, style}) => {
9188
};
9289

9390
DotSpinner.propTypes = {
94-
status: PropTypes.object,
91+
status: PropTypes.array,
9592
color: PropTypes.string,
9693
className: PropTypes.string,
9794
fullscreen: PropTypes.bool,

0 commit comments

Comments
 (0)