• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15811185418

22 Jun 2025 09:57PM UTC coverage: 76.901% (-0.03%) from 76.934%
15811185418

Pull #11130

github

web-flow
Merge 0b5467418 into 7cde38ac9
Pull Request #11130: #10839: Allow printing by freely setting the scale factor

31173 of 48566 branches covered (64.19%)

39 of 103 new or added lines in 7 files covered. (37.86%)

1644 existing lines in 149 files now uncovered.

38769 of 50414 relevant lines covered (76.9%)

36.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.28
/web/client/components/tutorial/Tutorial.jsx
1
/*
2
 * Copyright 2017, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8
import PropTypes from 'prop-types';
9

10
import React from 'react';
11
import Joyride from 'react-joyride';
12
import I18N from '../I18N/I18N';
13
import { head } from 'lodash';
14
import Portal from '../misc/Portal';
15
import 'react-joyride/lib/react-joyride-compiled.css';
16
import './style/tutorial.css';
17

18
const defaultIntroStyle = {
1✔
19
    backgroundColor: 'transparent',
20
    color: '#fff',
21
    mainColor: '#fff',
22
    textAlign: 'center',
23
    header: {
24
        padding: 5,
25
        fontFamily: 'Georgia, serif',
26
        fontSize: '2.8em'
27
    },
28
    main: {
29
        fontSize: '1.0em',
30
        padding: 5
31
    },
32
    footer: {
33
        padding: 10
34
    },
35
    button: {
36
        color: '#fff'
37
    },
38
    close: {
39
        display: 'none'
40
    },
41
    skip: {
42
        color: '#fff'
43
    }
44
};
45

46
class Tutorial extends React.Component {
47
    static propTypes = {
1✔
48
        actions: PropTypes.object,
49
        allowClicksThruHole: PropTypes.bool,
50
        autoStart: PropTypes.bool,
51
        defaultStep: PropTypes.object,
52
        disableOverlay: PropTypes.bool,
53
        holePadding: PropTypes.number,
54
        intro: PropTypes.bool,
55
        introPosition: PropTypes.number,
56
        introStyle: PropTypes.object,
57
        keyboardNavigation: PropTypes.bool,
58
        preset: PropTypes.string,
59
        presetList: PropTypes.object,
60
        run: PropTypes.bool,
61
        resizeDebounce: PropTypes.bool,
62
        resizeDebounceDelay: PropTypes.number,
63
        scrollIntoViewOptions: PropTypes.object,
64
        scrollOffset: PropTypes.number,
65
        scrollToFirstStep: PropTypes.bool,
66
        scrollToSteps: PropTypes.bool,
67
        showBackButton: PropTypes.bool,
68
        showCheckbox: PropTypes.bool,
69
        showOverlay: PropTypes.bool,
70
        showSkipButton: PropTypes.bool,
71
        showStepsProgress: PropTypes.bool,
72
        status: PropTypes.string,
73
        steps: PropTypes.array,
74
        stepIndex: PropTypes.number,
75
        toggle: PropTypes.bool,
76
        tooltipOffset: PropTypes.number,
77
        tourAction: PropTypes.string
78
    };
79

80
    static defaultProps = {
1✔
81
        toggle: false,
82
        status: 'run',
83
        preset: 'default_tutorial',
84
        presetList: {},
85
        introPosition: (window.innerHeight - 348) / 2,
86
        showCheckbox: true,
87
        defaultStep: {
88
            title: '',
89
            text: '',
90
            position: 'bottom',
91
            type: 'click'
92
        },
93
        introStyle: defaultIntroStyle,
94
        tourAction: 'next',
95
        stepIndex: 0,
96
        steps: [],
97
        run: true,
98
        autoStart: true,
99
        keyboardNavigation: false,
100
        resizeDebounce: false,
101
        resizeDebounceDelay: 200,
102
        holePadding: 0,
103
        scrollOffset: 20,
104
        scrollToSteps: true,
105
        scrollToFirstStep: true,
106
        showBackButton: true,
107
        showOverlay: true,
108
        allowClicksThruHole: true,
109
        showSkipButton: true,
110
        showStepsProgress: false,
111
        tooltipOffset: 10,
112
        disableOverlay: false,
113
        actions: {
114
            onSetup: () => {},
115
            onStart: () => {},
116
            onUpdate: () => {},
117
            onDisable: () => {},
118
            onReset: () => {},
119
            onClose: () => {}
120
        },
121
        scrollIntoViewOptions: {
122
            block: "end"
123
        }
124
    };
125

126
    state = {
7✔
127
        checkAction: {}
128
    }
129

130
    componentDidMount() {
131
        let defaultSteps = this.props.presetList[this.props.preset] || [];
7✔
132
        let checkbox = this.props.showCheckbox ? <div id="tutorial-intro-checkbox-container"><input type="checkbox" id="tutorial-intro-checkbox" className="tutorial-tooltip-intro-checkbox" onChange={this.props.actions.onDisable}/><span><I18N.Message msgId={"tutorial.checkbox"}/></span></div> : <div id="tutorial-intro-checkbox-container"/>;
7✔
133
        this.props.actions.onSetup('default', defaultSteps, this.props.introStyle, checkbox, this.props.defaultStep, Object.assign({}, this.props.presetList, {default_tutorial: defaultSteps}));
7✔
134
    }
135

136
    UNSAFE_componentWillUpdate(newProps, newState) {
137
        if (this.props.steps.length > 0) {
13✔
138
            if (!this.props.toggle && newProps.toggle) {
12✔
139
                this.props.actions.onStart();
1✔
140
                this.setState({checkAction: {}});
1✔
141
            } else if (!this.state.checkAction[newProps.stepIndex] &&
11✔
142
                (this.props.status === 'run' && newProps.status === 'error'
143
            || this.props.status === 'error' && newProps.status === 'error')) {
144

145
                const newStep = newProps.steps && head(newProps.steps.filter((step, id) => id === newProps.stepIndex));
11✔
146
                const isActionStep = newStep && newStep.action && !this.state.checkAction[newProps.stepIndex] ? newProps.stepIndex : null;
5✔
147
                const index = isActionStep || this.checkFirstValidStep(newProps.stepIndex, newProps.tourAction);
5✔
148
                this.restartTour(index, {[index]: true});
5✔
149

150
            } else if (this.props.status === 'run' && newProps.status === 'close') {
6✔
151
                this.closeTour();
1✔
152
                this.setState({checkAction: {}});
1✔
153
            }
154
            if (this.state.checkAction && newState.checkAction && !this.state.checkAction[newProps.stepIndex] && newState.checkAction[newProps.stepIndex]) {
12✔
155
                // retry to find a valid step when it has action
156
                const newStep = head(newProps.steps.filter((step, id) => id === newProps.stepIndex));
5✔
157
                const isValid = newStep && newStep.selector && document.querySelector(newStep.selector) ? true : false;
2✔
158
                if (!isValid) {
2✔
159
                    const firstValidStep = this.checkFirstValidStep(newProps.stepIndex, newProps.tourAction);
1✔
160
                    this.restartTour(firstValidStep, {});
1✔
161
                }
162
            }
163
        }
164
    }
165

166
    componentWillUnmount() {
167
        this.props.actions.onClose();
7✔
168
        this.props.actions.onReset();
7✔
169
    }
170

171
    onTour = (tour) => {
7✔
172
        if (this.props.steps.length > 0 && tour && tour.type) {
20✔
173
            document.querySelector(tour?.step?.selector)?.scrollIntoView(this.props.scrollIntoViewOptions);
19✔
174
            const type = tour.type.split(':');
19✔
175
            if (type[0] !== 'tooltip' && type[1] === 'before'
19✔
176
            || tour.action === 'start'
177
            || type[1] === 'target_not_found'
178
            || tour.type === 'finished') {
179
                this.props.actions.onUpdate(tour, this.props.steps);
14✔
180

181
            }
182
        }
183
    };
184

185
    render() {
186
        let joy;
187
        if (this.props.steps.length > 0) {
20✔
188
            joy =
17✔
189
                (<Joyride
190
                    ref={c => (this.joyride = c)}
34✔
191
                    steps={this.props.steps}
192
                    stepIndex={this.props.stepIndex}
193
                    run={this.props.run}
194
                    autoStart={this.props.autoStart}
195
                    keyboardNavigation={this.props.keyboardNavigation}
196
                    locale={{
197
                        back: <I18N.Message msgId="tutorial.back"/>,
198
                        close: <I18N.Message msgId="tutorial.close"/>,
199
                        last: <I18N.Message msgId="tutorial.last"/>,
200
                        next: <I18N.Message msgId={'tutorial.next'}/>,
201
                        skip: <I18N.Message msgId="tutorial.skip"/>
202
                    }}
203
                    resizeDebounce={this.props.resizeDebounce}
204
                    resizeDebounceDelay={this.props.resizeDebounceDelay}
205
                    holePadding={this.props.holePadding}
206
                    scrollOffset={this.props.scrollOffset}
207
                    scrollToSteps={this.props.scrollToSteps}
208
                    scrollToFirstStep={this.props.scrollToFirstStep}
209
                    showBackButton={this.props.showBackButton}
210
                    showOverlay={this.props.showOverlay}
211
                    allowClicksThruHole={this.props.allowClicksThruHole}
212
                    showSkipButton={this.props.showSkipButton}
213
                    showStepsProgress={this.props.showStepsProgress}
214
                    tooltipOffset={this.props.tooltipOffset}
215
                    disableOverlay={this.props.disableOverlay}
216
                    type={'continuous'}
217
                    debug={false}
218
                    callback={this.onTour}
219
                />)
220
            ;
221
        } else {
222
            joy = <div className="tutorial-joyride-placeholder" />;
3✔
223
        }
224
        return (
20✔
225
            <Portal>
226
                <div>
227
                    {joy}
228
                    <div id="intro-tutorial" className="tutorial-presentation-position" style={{top: this.props.introPosition}}></div>
229
                </div>
230
            </Portal>
231

232
        );
233
    }
234

235
    checkFirstValidStep(index, action) {
236
        let steps = [].concat(this.props.steps);
4✔
237

238
        if (action === 'back') {
4✔
239
            steps = steps.slice(0, index);
1✔
240
            steps.sort((a, b) => b.index - a.index);
1✔
241
        } else {
242
            steps = steps.slice(index + 1, this.props.steps.length);
3✔
243
            steps.sort((a, b) => a.index - b.index);
3✔
244
        }
245

246
        steps = steps.filter((step) => {
4✔
247
            return document.querySelector(step.selector);
1✔
248
        }).map((step) => {
249
            return step.index;
1✔
250
        });
251

252
        return steps && steps.length > 0 ? steps[0] : -1;
4✔
253
    }
254

255
    closeTour() {
256
        const index = document.querySelector(this.props.steps[0].selector) ? 0 : this.checkFirstValidStep(0, 'next');
2!
257

258
        if (index === -1) {
2!
UNCOV
259
            this.props.actions.onReset();
×
260
        } else {
261
            this.joyride.setState({
2✔
262
                index,
263
                shouldRedraw: true
264
            });
265
        }
266

267
        this.props.actions.onClose();
2✔
268
    }
269

270
    restartTour(index, checkAction) {
271
        if (index === -1) {
6✔
272
            this.closeTour();
1✔
273
        } else {
274
            this.joyride.setState({
5✔
275
                index,
276
                isRunning: true,
277
                shouldRedraw: true,
278
                shouldRenderTooltip: true
279
            });
280
            this.props.actions.onStart();
5✔
281
            this.setState({checkAction});
5✔
282
        }
283
    }
284
}
285

286
export default Tutorial;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc