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' ;
2
3
import PropTypes from 'prop-types' ;
4
+
3
5
import GraphSpinner from '../fragments/Loading/spinners/GraphSpinner.jsx' ;
4
6
import DefaultSpinner from '../fragments/Loading/spinners/DefaultSpinner.jsx' ;
5
7
import CubeSpinner from '../fragments/Loading/spinners/CubeSpinner.jsx' ;
@@ -16,12 +18,51 @@ const spinnerComponentOptions = {
16
18
const getSpinner = spinnerType =>
17
19
spinnerComponentOptions [ spinnerType ] || DefaultSpinner ;
18
20
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 ( {
23
65
children,
24
- loading_state,
25
66
display = 'auto' ,
26
67
color = '#119DFF' ,
27
68
id,
@@ -38,91 +79,60 @@ const Loading = ({
38
79
delay_show = 0 ,
39
80
target_components,
40
81
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
+ ) ;
82
88
83
89
const [ showSpinner , setShowSpinner ] = useState ( show_initially ) ;
84
90
const dismissTimer = useRef ( ) ;
85
91
const showTimer = useRef ( ) ;
86
92
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
+
88
100
useEffect ( ( ) => {
89
101
if ( display === 'show' || display === 'hide' ) {
90
102
setShowSpinner ( display === 'show' ) ;
91
103
return ;
92
104
}
93
105
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 ) ;
123
133
}
124
134
}
125
- } , [ delay_hide , delay_show , loading_state , display , showSpinner ] ) ;
135
+ } , [ delay_hide , delay_show , loading , display , showSpinner ] ) ;
126
136
127
137
const Spinner = showSpinner && getSpinner ( spinnerType ) ;
128
138
@@ -131,18 +141,7 @@ const Loading = ({
131
141
style = { { position : 'relative' , ...parent_style } }
132
142
className = { parent_className }
133
143
>
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 } >
146
145
{ children }
147
146
</ div >
148
147
< div id = { id } style = { showSpinner ? coveringSpinner : { } } >
@@ -151,7 +150,7 @@ const Loading = ({
151
150
< Spinner
152
151
className = { className }
153
152
style = { style }
154
- status = { loading_state }
153
+ status = { loading }
155
154
color = { color }
156
155
debug = { debug }
157
156
fullscreen = { fullscreen }
@@ -160,9 +159,7 @@ const Loading = ({
160
159
</ div >
161
160
</ div >
162
161
) ;
163
- } ;
164
-
165
- Loading . _dashprivate_isLoadingComponent = true ;
162
+ }
166
163
167
164
Loading . propTypes = {
168
165
/**
@@ -227,24 +224,6 @@ Loading.propTypes = {
227
224
*/
228
225
color : PropTypes . string ,
229
226
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
-
248
227
/**
249
228
* Setting display to "show" or "hide" will override the loading state coming from dash-renderer
250
229
*/
0 commit comments