Chapter 5: Bookmarks#
Welcome back to the loaders.gl-showcases
tutorial! In Chapter 4: Global State Management, we learned about the central "control room" of the application, where important information like the camera view and visible layers is stored and kept consistent. Now, we'll explore a feature that directly uses this powerful concept: Bookmarks.
What are Bookmarks?#
Imagine you're exploring a large 3D city model on the map, and you find a really interesting viewpoint β maybe a specific angle showing a cluster of buildings, with certain layers visible (like the terrain and building footprints) and maybe even some debug options turned on. You might want to save this exact view and setup so you can easily return to it later, or share it with someone else.
Manually recreating the camera position, turning on the right layers, and setting the same options each time would be tedious. This is where Bookmarks come in.
As the concept description says:
This feature allows users to save the current state of the application, including the camera position, loaded layers, and active debug or comparison settings. Saved states can be reloaded later, providing quick access to specific viewpoints or configurations. It's like saving different camera angles and scene setups in a film production to revisit them later.
Bookmarks capture a snapshot of the application's configuration at a specific moment, allowing you to jump back to it instantly.
The Central Use Case: Saving and Restoring a View#
The core task is straightforward: 1. Find a view on the map that you like. 2. Click a button to save it. 3. Later, select the saved bookmark from a list. 4. The application immediately changes the map view and settings back to how they were when you saved it.
This feature relies heavily on the application's ability to read and write its Global State.
Key Concepts#
Bookmarks involve a few core ideas:
- Capturing State: A bookmark needs to record the critical parts of the application's Global State β at minimum, the View State (camera position) and the list of currently visible Layers. It can also include debug settings, comparison mode state, etc.
- Visual Representation: Bookmarks are often shown as a list of small images (thumbnails) that give you a preview of the saved view.
- Storage: The saved state and thumbnail need to be stored temporarily within the application while it's running, and potentially exported/imported to a file for saving across sessions or sharing.
- Actions: The user needs controls to:
- Add (Save) a new bookmark.
- Select (Load) an existing bookmark.
- Delete a bookmark.
- Export/Import bookmarks.
How to Use (The Bookmarks Panel)#
The main way you interact with bookmarks is through the Bookmarks Panel. You'll typically find a button in the application's interface to open this panel. The component responsible for the main panel structure is BookmarksPanel
(src/components/bookmarks-panel/bookmarks-panel.tsx
).
Inside the panel, you'll see:
- A button to add a new bookmark (often a "+" icon).
- A list or "slider" showing your saved bookmarks, usually with their thumbnails.
- Options for managing bookmarks (like editing, deleting, exporting, importing).
Let's look at how saving, selecting, and managing bookmarks work from the user's perspective.
1. Saving a Bookmark#
When you have the view and settings you want to save:
- Open the Bookmarks Panel.
- Click the "Insert layer" button (or similar button with a "+").
The application will capture the current state and add a new entry to the bookmark list, often with a thumbnail screenshot of the map view.
2. Selecting/Loading a Bookmark#
To return to a saved view:
- Open the Bookmarks Panel.
- Find the bookmark you want in the list (using the thumbnail as a guide).
- Click on the bookmark's entry in the list.
The application will instantly change the camera view, update the visible layers in the Layers Panel, and restore any other settings that were saved with that bookmark.
3. Deleting a Bookmark#
If you want to remove a bookmark:
- Open the Bookmarks Panel.
- Access the editing mode (often via an options menu).
- Click the trash/delete icon associated with the bookmark you want to remove.
- Confirm the deletion if prompted.
The bookmark will be removed from the list.
4. Exporting and Importing Bookmarks#
Bookmarks can often be saved to a file (exported) and loaded from a file (imported). This is useful for persistence or sharing.
- Open the Bookmarks Panel.
- Click the "Options" icon (three dots).
- Choose "Download bookmarks" to save them to a file (typically JSON).
- Choose "Upload bookmarks" to load bookmarks from a previously saved file.
This allows you to keep your bookmarks even after closing and reopening the application, or to share collections of bookmarks with others.
Under the Hood: How Bookmarks Work#
Bookmarks rely heavily on the Global State Management we discussed in the previous chapter.
When you save a bookmark:
- The application reads the current state from the Redux store. This includes:
- The current View State (camera position, zoom, pitch, etc.) from the
viewState
slice. - The list of active/visible layers from the layers/sublayers state slices.
- Potentially the state of other panels, like debug options or comparison settings.
- The current View State (camera position, zoom, pitch, etc.) from the
- It takes a screenshot of the current map view to create a thumbnail image. This often uses libraries like
html2canvas
(src/utils/deck-thumbnail-utils.ts
). - It bundles this captured state data (camera position, layer IDs, settings, thumbnail) into a single object, often confirming it matches a defined structure like the one in
src/constants/json-schemas/bookmarks.ts
. - It dispatches an action (using
useAppDispatch
) to add this new bookmark object to a list of bookmarks stored in the Global State (likely within a dedicatedbookmarks
slice).
When you select a bookmark:
- You click on a bookmark entry in the UI (
SliderListItem.tsx
withinSlider.tsx
). - The UI component calls the
onSelect
handler provided by theBookmarksPanel
(src/components/bookmarks-panel/bookmarks-panel.tsx
). - The
BookmarksPanel
dispatches actions (usinguseAppDispatch
) to update the application's Global State. These actions restore the state values saved in the selected bookmark:- An action updates the
viewState
slice with the saved camera position. - An action updates the layers/sublayers state slices to match the saved list of visible layers.
- Actions update other state slices (debug options, etc.) if saved.
- An action updates the
- Because the Global State has changed, the components that read from these state slices automatically re-render:
- The Map Visualization component (
DeckGlWrapper.tsx
orArcgisWrapper.tsx
) updates the camera view and redraws with the newly selected layers. - The Layers Panel (
LayersPanel.tsx
) updates the checkboxes and lists to show the layers currently active according to the restored state. - Other panels (like Debug) also update to reflect their saved settings.
- The Map Visualization component (
Here's a simplified flow for saving and loading:
sequenceDiagram
Participant User
Participant BookmarksPanelUI
Participant GlobalState
Participant MapScreenshotUtil
Participant MapVisualization
Participant LayersPanelUI
User->>BookmarksPanelUI: Clicks Save Bookmark (+)
BookmarksPanelUI->>GlobalState: Reads current View State, visible Layers, etc.
BookmarksPanelUI->>MapScreenshotUtil: Requests screenshot for thumbnail
MapScreenshotUtil-->>BookmarksPanelUI: Provides thumbnail image
BookmarksPanelUI->>GlobalState: Dispatches action (ADD_BOOKMARK) with captured state + thumbnail
GlobalState->>GlobalState: Stores new bookmark in state list
GlobalState-->>BookmarksPanelUI: State change notifies UI
BookmarksPanelUI->>User: Bookmark list updates with new entry
User->>BookmarksPanelUI: Clicks on a saved bookmark
BookmarksPanelUI->>GlobalState: Dispatches actions (SET_VIEW_STATE, SET_VISIBLE_LAYERS, etc.) with bookmark's saved state
GlobalState->>GlobalState: Updates View State, Layer visibility, etc.
GlobalState-->>MapVisualization: State change notifies map
GlobalState-->>LayersPanelUI: State change notifies layers panel
MapVisualization->>User: Map view changes to bookmark's view state, shows saved layers
LayersPanelUI->>User: Layers list updates to show saved layer visibility
Code Snippet Examples#
The BookmarksPanel.tsx
component manages the overall display and handles the user interactions by calling functions passed as props (onAddBookmark
, onSelectBookmark
, onDeleteBookmark
, etc.). These functions are where the actual state updates happen via Redux actions, likely defined outside the component in a Redux slice file for bookmarks (conceptually, similar to view-state-slice.ts
or flattened-sublayers-slice.ts
).
Let's look at a very simplified view of how a bookmark is displayed in the list using SliderListItem
:
// Simplified snippet from src/components/slider/slider-list-item.tsx
export const SliderListItem = ({
id,
selected,
url, // This is the URL for the thumbnail image
editingMode,
onSelect, // Function to call when clicked
onDelete, // Function to call when delete is clicked
// ... other props
}) => {
// ... state for deleting confirmation ...
const layout = useAppLayout();
const isMobileLayout = layout !== Layout.Desktop;
return (
<ListItem
id={id}
url={url} // Use the thumbnail URL as background image
selected={selected}
editingMode={editingMode}
isMobile={isMobileLayout}
onClick={onSelect} // Call onSelect when the item is clicked
// ... event handlers for hover, layout props ...
>
{/* ... Conditional rendering for delete button based on editingMode ... */}
{editingMode && (
<TrashIconContainer onClick={onDeleteBookmarkClickHandler}>
<TrashIcon /> {/* The delete button */}
</TrashIconContainer>
)}
{/* ... potentially bookmark title/number for non-image bookmarks ... */}
</ListItem>
{/* ... Delete Confirmation modal ... */}
);
};
This snippet shows that SliderListItem
receives the bookmark's id
, url
(for the thumbnail), its selection status (selected
), and functions for onSelect
and onDelete
. When the user clicks on the list item, the onClick
handler triggers onSelect
, signaling to the parent component (Slider
and ultimately BookmarksPanel
) that this bookmark should be loaded. If editingMode
is on and the user clicks the trash icon, onDeleteBookmarkClickHandler
is called, which might trigger the delete confirmation and eventually call onDelete
.
The actual Slider
component (src/components/slider/slider.tsx
) is responsible for rendering the list of SliderListItem
s horizontally (or vertically for other slider types like floors), managing the scrolling, and handling the left/right arrow button navigation for desktop layouts. It maps over the data
(the array of bookmark objects) and renders a SliderListItem
for each one, passing the relevant data and handlers.
The structure of the data saved in a bookmark is defined by the JSON schema in src/constants/json-schemas/bookmarks.ts
. This schema specifies that each bookmark object should include an id
, imageUrl
(the thumbnail), viewState
(which itself contains main
and minimap
view states, matching the structure in the Redux viewState
slice), and lists of layersLeftSide
/layersRightSide
and activeLayersIdsLeftSide
/activeLayersIdsRightSide
(for comparison mode, linked to Layer Management and Comparison Mode).
// Simplified snippet from src/constants/json-schemas/bookmarks.ts
export const bookmarksSchemaJson: Draft202012Schema = {
// ... schema metadata ...
type: "array", // The main structure is an array of bookmarks
items: { // Each item in the array is a bookmark object
type: "object",
properties: {
id: { type: "string" },
imageUrl: { type: "string" }, // Base64 or URL for thumbnail
viewState: { // Nested object matching the Redux ViewStateState structure
type: "object",
properties: {
main: { $ref: "#/$defs/ViewState" }, // Reference to ViewState definition
minimap: { $ref: "#/$defs/ViewState" },
},
// ... required viewState properties ...
},
layersLeftSide: { $ref: "#/$defs/LayerExample" }, // Reference to LayerExample definition
layersRightSide: { $ref: "#/$defs/LayerExample" },
activeLayersIdsLeftSide: { // Array of IDs for active layers
type: "array",
items: { type: "string" },
},
activeLayersIdsRightSide: { // Array of IDs for active layers in comparison mode
type: "array",
items: { type: "string" },
},
// ... other optional properties ...
},
required: ["id", "imageUrl"], // Minimum required properties
},
// ... $defs for ViewState, LayerExample structures ...
};
This schema acts as a blueprint, ensuring that when bookmarks are saved or loaded from a file, they have the expected format. Functions in bookmarks-utils.ts
are used to parse and validate JSON files against this schema when importing.
Functions in deck-thumbnail-utils.ts
handle the technical details of capturing the image, potentially combining views for Comparison Mode.
// Simplified snippet from src/utils/deck-thumbnail-utils.ts
import html2canvas from "html2canvas";
// Function to create a thumbnail from a DOM element (like the map canvas)
export const createViewerBookmarkThumbnail = async (
containerId // ID of the HTML element containing the map
): Promise<string | null> => {
const domElement = document.querySelector(containerId); // Find the map element
if (!domElement) {
return null;
}
// Use html2canvas to take a screenshot of the element
const canvas = await html2canvas(domElement as HTMLElement);
// Create a thumbnail of the desired size from the screenshot
const thumbnail = createThumbnail(canvas, 144, 81); // Defined thumbnail size
if (!thumbnail) {
return null;
}
// Return the thumbnail as a base64 data URL string
return thumbnail.toDataURL("image/png");
};
// Helper to crop/resize the canvas
const createThumbnail = (canvas: HTMLCanvasElement, width: number, height: number): HTMLCanvasElement | null => {
// ... logic to calculate crop area and draw to new canvas ...
return outputCanvas;
};
// ... function to merge thumbnails for comparison mode ...
This demonstrates how html2canvas
is used to grab a visual representation of the map, which is then resized to create the small thumbnail image stored with the bookmark data.
In summary, bookmarks work by serializing (converting to data) key parts of the application's Global State along with a visual thumbnail. When a bookmark is selected, the saved data is deserialized and used to update the Global State, triggering updates throughout the application's UI and the map visualization.
Conclusion#
In this chapter, we learned about Bookmarks, a convenient feature for saving and restoring specific views and configurations of the application. We saw that bookmarks achieve this by capturing and storing a snapshot of the application's Global State, including the View State (camera position) and visible Layers. The Bookmarks Panel provides the user interface for adding, selecting, deleting, exporting, and importing these saved states, often using visual thumbnails generated by capturing the map view.
Understanding bookmarks reinforces the importance of Global State Management β by having a central source of truth, features like bookmarks can easily capture and restore the entire application's setup.
Next, we'll explore Comparison Mode, another feature that utilizes the application's state management to display and compare different views or datasets side-by-side.
Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/bookmarks-panel/bookmarks-panel.tsx), 2(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/slider/slider-list-item.tsx), 3(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/components/slider/slider.tsx), 4(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/constants/json-schemas/bookmarks.ts), 5(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/utils/bookmarks-utils.ts), 6(https://github.com/visgl/loaders.gl-showcases/blob/3403a56b6839455092211a95c5cd695f20ea6c7e/src/utils/deck-thumbnail-utils.ts)