Adding Steps to the Guided Tour
Global Guided Tour Step IDs
The IDs of the guided tour steps live in frontend/src/features/map_planning/utils/GuidedTourStepIds.ts.
If you want to add a new step to the tour first add the ID of your new step to this file.
The new ID can be any string but should describe what the step with this ID does.
The new ID should follow the style of the IDs that are already present.
Example:
If you wanted to add a step for selecting the measuring tool the step ID could be measuringToolSelect.
Important:
When using the ID of any step for any action in the guided tour instead of typing it out always use the imported ID from GuidedTourStepIds.ts!
New Step Basics
The actual steps of the guided tour are defined in frontend/src/features/map_planning/utils/GuidedTour.ts.
export const guidedTourSteps returns an array of ShepherdOptionsWithType objects which are transformed to Step objects during runtime.
This array determines what steps are contained in the guided tour and the order in which they appear.
To change the tour you have to edit what is included in the steps array of guidedTourSteps.
What the title and the text of a new step are should be defined in our i18 config in German and in English.
Adding a Simple Step
After adding ID, title and text to the correct files a simple new step could look like this:
{
id: GuidedTourStepIds.MeasuringToolSelect,
title: t('guidedTour:mapEditor.measuring_tool_select_title'),
text: t('guidedTour:mapEditor.measuring_tool_select_text'),
buttons: getStandardButtons(GuidedTourStepIds.MeasuringToolSelect),
attachTo: {
element: '[data-tourid="toolbox"]',
on: 'right',
},
}
When this new step is displayed depends on its position in the steps array (the top step in the array is shown first).
Adding buttons to the step using getStandardButtons(GuidedTourStepIds.MeasuringToolSelect) will add a back and a next button.
The back button goes back to the step before the one you just added.
The next button goes to the step after the one you just added and tells the backend that the step was completed so it will not be shown the next time the guided tour appears.
You don't have to use the standard buttons for your step though.
Many actions can be bound to a button, like skipping ahead to another step using its ID or making a backend call.
The action of a StepOptionsButton which is the interface used for buttons in steps is defined like this:
/**
* A function executed when the button is clicked on
* It is automatically bound to the `tour` the step is associated with, so things like `this.next` will
* work inside the action.
* You can use action to skip steps or navigate to specific steps, with something like:
* ```js
* action() {
* return this.show('some_step_name');
* }
* ```
*/
action?: ((this: Tour) => void);
This is an example implementation of a simple skip button that doesn't persist step completion to the backend.
const getSkipButton = (): ShepherdButtonWithType[] => [
{
secondary: true,
text: t('guidedTour:skip'),
type: 'next',
},
];
This can be used to add a skip button to any new step which does the same thing as the next button except it doesn't notify the backend that the step was completed (the step will be shown again the next time the guided tour appears).
Adding an Action Step
Besides simple steps that are completed by simply pressing the next button there are also steps where the user has to complete an action to proceed.
For example, a step where the user has to click the plant search button:
{
id: GuidedTourStepIds.PlantsLayerFirstPlantingSearch,
title: `${t('guidedTour:mapEditor.plants_layer_title')} (4/16)`,
text: actionText(
t('guidedTour:mapEditor.plants_layer_first_planting_search_action'),
t('guidedTour:mapEditor.plants_layer_first_planting_search_text'),
),
buttons: [
{
secondary: true,
text: t('guidedTour:back'),
type: 'back',
},
],
attachTo: {
element: '[data-tourid="search_button"]',
on: 'left',
},
when: makeAdvanceOnCompleteWhen({
selector: '[data-tourid="search_button"]',
}),
classes: 'action-step',
canClickTarget: true,
},
As you can see for this type of step the back button was added manually and to advance
when: makeAdvanceOnCompleteWhen({
selector: '[data-tourid="search_button"]',
}),
is added to the step.
In the definition of makeAdvanceOnCompleteWhen() you can see that when only the selector is provided all the user has to do is click on the element with this selector to proceed.
When using makeAdvanceOnCompleteWhen() step completion is also persisted to the backend.
You can also use more involved events in makeAdvanceOnCompleteWhen() as can be seen in this step:
{
id: GuidedTourStepIds.PlantLayerChangeDate,
title: `${t('guidedTour:mapEditor.plants_layer_title')} (7/16)`,
text: actionText(
t('guidedTour:mapEditor.plants_layer_change_date_action'),
t('guidedTour:mapEditor.plants_layer_change_date_text'),
),
attachTo: {
element: '[data-tourid="timeline"]',
on: 'top',
},
when: makeAdvanceOnCompleteWhen({
selector: '[data-tourid="timeline"]',
event: 'dateChanged',
}),
classes: 'action-step',
canClickTarget: true,
},
Here makeAdvanceOnCompleteWhen() is provided with the selector parameter of [data-tourid="timeline"] as well as the event parameter of dateChanged.
And in frontend/src/features/map_planning/components/MapOverlay.tsx this event is triggered when changing the date on the timeline.
const triggerDateChangedInGuidedTour = () => {
const changeDateEvent = new Event('dateChanged');
document.getElementById('timeline')?.dispatchEvent(changeDateEvent);
};
const handleTimeLineDateChanged = useCallback(
(date: string) => {
// skip if date did not actually change
if (date === timelineDate) return;
triggerDateChangedInGuidedTour();
onTimeLineStateChanged(TimelineState.idle);
updateTimelineDate(date);
},
[onTimeLineStateChanged, timelineDate, updateTimelineDate],
);
If you add events like this the guided tour can be progressed on basically anything.
Since makeAdvanceOnCompleteWhen() is utilized this step completion is also persisted in the database.