This repository has been archived by the owner on Nov 30, 2022. It is now read-only.
/
guide.jsx
185 lines (171 loc) · 5.68 KB
/
guide.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import React,
{
useState,
useCallback,
forwardRef,
useImperativeHandle,
} from 'react';
import PropTypes from 'prop-types';
import Joyride, { ACTIONS, EVENTS, STATUS } from 'react-joyride';
import steps from './guide-steps';
import './guide.pcss';
// The component needs to be wrapped in `forwardRef` to give access to the
// ref object assigned using the `ref` prop.
const Guide = forwardRef(({ isLocalStorage }, ref) => {
const [runGuide, setRunGuide] = useState(!isLocalStorage ? true : !localStorage.getItem('catwalkGuide'));
const [stepIndex, setStepIndex] = useState(0);
// Any instance of the component is extended with what is returned from the
// callback passed as the second argument.
useImperativeHandle(ref, () => ({
startGuideFunc() {
if (!runGuide) {
setRunGuide(true);
}
},
isGuideRunning() {
return runGuide;
},
}));
const getNbrOfColumnsInCube = () => {
let nbrOfColumns = 0;
const table = document.getElementsByClassName('hypercube-table');
if (table.length > 0) {
const virtTable = table[table.length - 1].getElementsByClassName('ReactVirtualized__Table');
if (virtTable.length > 0) {
nbrOfColumns = virtTable[0].getAttribute('aria-colcount');
}
}
return nbrOfColumns;
};
const setStep = (stepName) => {
setRunGuide(false);
setStepIndex(steps.findIndex((s) => s.step === stepName));
setTimeout(() => setRunGuide(true), 300);
};
const onClick = useCallback((evt) => {
let parentElemName = evt.target.parentElement.className;
if (parentElemName) {
parentElemName = parentElemName.trim();
}
if (parentElemName === 'field') {
// a click in the field (to open the filterbox).
// stop and start the guide in order to highlight the opened filterbox.
setRunGuide(false);
setRunGuide(true);
}
if (parentElemName === 'add-button') {
// a click on the big hypercube builder button.
setStep('selectEntity');
} else if (parentElemName === 'expression' || parentElemName === 'expression-list') {
// a click on an expression in the hypercube builder.
const nbrOfColumns = getNbrOfColumnsInCube();
if (nbrOfColumns > 0) {
setStep('cubeFinished');
} else {
setStep('addAnotherColumn');
}
} else if (parentElemName === 'column-add-button') {
// a click on the little add button in the hypercube builder.
setStep('selectAnotherEntity');
}
}, []);
const endGuide = () => {
setRunGuide(false);
setStepIndex(0);
if (isLocalStorage) {
localStorage.setItem('catwalkGuide', 'catwalk');
}
document.removeEventListener('mouseup', onClick);
};
const readyToProceed = (newStepIndex) => {
if (newStepIndex < steps.length) {
const stepName = steps[newStepIndex].step;
if (stepName === 'selections') {
const selections = document.querySelector('.selections-inner li');
if (!selections) return false;
} else if (stepName === 'selectEntity' || stepName === 'selectAnotherEntity') {
const overlay = document.getElementsByClassName('cube-column-chooser');
if (overlay.length <= 0) {
return false;
}
} else if (stepName === 'addAnotherColumn') {
// we need to have a hypercube with a column in order for the step to be valid.
const nbrOfColumns = getNbrOfColumnsInCube();
if (nbrOfColumns <= 0) {
return false;
}
} else if (stepName === 'cubeFinished') {
const nbrOfColumns = getNbrOfColumnsInCube();
if (nbrOfColumns > 0) {
return false;
}
}
}
// we are ready to proceed with this step.
return true;
};
const handleJoyrideCallback = (data) => {
const {
action, index, type, status,
} = data;
if ([EVENTS.TOUR_START].includes(type)) {
document.addEventListener('mouseup', onClick);
} else if ([ACTIONS.START].includes(action) && index === 0) {
// if the guide is restarted there will be no EVENTS.TOUR_START, the EventListener must
// be added on ACTION.START and step 0.
document.addEventListener('mouseup', onClick);
} else if ([ACTIONS.CLOSE].includes(action) || [STATUS.FINISHED, STATUS.SKIPPED].includes(status)) {
if (runGuide) {
endGuide();
}
} else if ([EVENTS.STEP_AFTER].includes(type)) {
const newStepIndex = index + (action === ACTIONS.PREV ? -1 : 1);
if (action !== ACTIONS.PREV && !readyToProceed(newStepIndex)) {
// we are not ready to proceed to the next step. Reset the current step.
// The stop/start of the guide is needed to reset the Joyride events, or it will
// be somewhere in between steps, statewise.
setRunGuide(false);
setStepIndex(index);
setTimeout(() => setRunGuide(true), 100);
} else {
// Update state to advance the guide
setStepIndex(newStepIndex);
}
} else if ([EVENTS.TARGET_NOT_FOUND].includes(type)) {
// The target could not be found. Go to next step.
const newStepIndex = index + 1;
setStepIndex(newStepIndex);
}
};
return (
<Joyride
continuous
stepIndex={stepIndex}
showProgress
showSkipButton
disableBeacon
disableOverlayClose
run={runGuide}
callback={handleJoyrideCallback}
styles={{
options: {
primaryColor: '#398ab5',
},
buttonClose: {
display: 'none',
},
buttonSkip: {
color: '#398ab5',
},
}}
steps={steps}
/>
);
});
Guide.propTypes = {
isLocalStorage: PropTypes.bool,
};
Guide.defaultProps = {
isLocalStorage: false,
};
export default Guide;