8.7 KiB
Global UI Controls
Overview
Global UI controls are system-owned presentation buttons for fullscreen, global
sound mute, and offline mode. They are not regular canvas elements and are not
stored in tour_pages.ui_schema_json.elements.
They behave like runtime chrome:
- button dimensions use canvas-relative percentages
- button positions are relative to the visible canvas using
xPercent/yPercent - constructor edit mode renders controls even when hidden
- controls can be disabled or hidden, but cannot be deleted
- each control can use custom default and active icons from Assets
Implementation Files
Backend:
backend/src/db/models/global_ui_control_defaults.jsbackend/src/db/models/project_ui_control_settings.jsbackend/src/db/api/global_ui_control_defaults.tsbackend/src/db/api/project_ui_control_settings.tsbackend/src/routes/global_ui_control_defaults.tsbackend/src/routes/project_ui_control_settings.tsbackend/src/services/global_ui_control_defaults.tsbackend/src/services/project_ui_control_settings.ts
Frontend:
frontend/src/types/uiControls.tsfrontend/src/components/UiControls/UiControlsSettingsForm.tsxfrontend/src/components/Runtime/RuntimeControls.tsxfrontend/src/components/Constructor/ElementEditorPanel.tsxfrontend/src/stores/global_ui_control_defaults/globalUiControlDefaultsSlice.tsfrontend/src/stores/project_ui_control_settings/projectUiControlSettingsSlice.tsfrontend/src/components/RuntimePresentation.tsxfrontend/src/pages/constructor.tsxfrontend/src/pages/global-ui-control-defaults.tsxfrontend/src/pages/global-ui-control-defaults/[controlType].tsxfrontend/src/pages/project-ui-control-settings.tsx
Cascade
Settings resolve field-by-field:
global_ui_control_defaults
-> project_ui_control_settings (project + environment)
-> tour_pages.global_ui_controls_settings_json
Missing fields inherit from the previous cascade level: page settings override project settings, project settings override global defaults, and frontend hardcoded defaults are only a safety fallback.
Data Shape
Each control (fullscreen, sound, offline) supports:
| Field | Purpose |
|---|---|
enabled |
Disable action while keeping the control selectable in edit mode |
hidden |
Hide in runtime/preview; still render as ghost in constructor edit mode |
xPercent, yPercent |
Canvas-relative position |
anchor |
Which button point is placed at the coordinate |
buttonSizePercent, iconSizePercent, borderRadiusPercent |
Canvas-width-relative dimensions |
defaultIconUrl, activeIconUrl |
Optional custom asset URL/storage key per state |
defaultBackgroundColor, activeBackgroundColor |
Button background color per state |
defaultBorderColor, activeBorderColor |
Button border color per state |
hoverBackgroundColor, color |
Shared hover background and icon color |
opacity, boxShadow, zIndex, order |
Presentation and initial ordering |
Active state means downloaded/offline for the offline button, muted for the sound button, and fullscreen for the fullscreen button.
When runtime is embedded in an iframe, the fullscreen control first tries the
browser Fullscreen API for the presentation document, then tries to fullscreen
the embedding iframe when same-origin access is available. For cross-origin
wrappers it posts tour-builder:request-fullscreen to window.parent; exit
attempts post tour-builder:exit-fullscreen.
<iframe id="tour-frame" src="https://example.com/p/project" allow="fullscreen" allowfullscreen></iframe>
<script>
window.addEventListener('message', async (event) => {
const frame = document.getElementById('tour-frame');
if (event.data?.type === 'tour-builder:request-fullscreen') {
await frame?.requestFullscreen?.();
}
if (event.data?.type === 'tour-builder:exit-fullscreen' && document.fullscreenElement) {
await document.exitFullscreen();
}
});
</script>
Dimensions are stored as percentages of canvas width. buttonSizePercent and
iconSizePercent resolve from the displayed canvas width. Vertical bounds use
the canvas aspect ratio, so buttons remain fully inside the visible canvas for
non-16:9 projects as well.
Default global values:
| Control | X | Y | Size | Icon | Radius | Order |
|---|---|---|---|---|---|---|
offline |
89.5 |
6 |
2.6 |
1.35 |
0.42 |
1 |
fullscreen |
92.75 |
6 |
2.6 |
1.35 |
0.42 |
2 |
sound |
96 |
6 |
2.6 |
1.35 |
0.42 |
3 |
APIs
GET /api/global-ui-control-defaultsPUT /api/global-ui-control-defaults/:idGET /api/project-ui-control-settings/project/:projectId/env/:environmentPUT /api/project-ui-control-settings/project/:projectId/env/:environmentDELETE /api/project-ui-control-settings/project/:projectId/env/:environment
Production project settings reads follow the same public/private access rules as runtime transition settings. Dev/stage and writes require JWT.
GET /api/global-ui-control-defaults is public-readable for runtime. Updates
require JWT and UPDATE_PAGE_ELEMENTS; these settings are authored with the
same permission family as element defaults. Project-level production reads
are public only for public production presentations; private production
presentations require JWT and presentation access.
Project override writes require UPDATE_PAGE_ELEMENTS. The project
environment reset endpoint is implemented as DELETE because it removes the
override row, but authorization treats it as an update operation: it also
requires UPDATE_PAGE_ELEMENTS, not DELETE_PAGE_ELEMENTS.
Constructor Behavior
Constructor renders controls through RuntimeControls with editMode=true.
Selecting a system control opens the element editor in system-control mode.
System controls:
- can be dragged to update
xPercent/yPercent - can be edited with coordinate inputs
- can choose separate default and active custom icons
- can edit default/active background colors and border colors
- can edit hidden/disabled state, order, z-index, opacity, shadow, size, radius, and anchor
- cannot be removed, copied, or pasted
Constructor system-control opacity is edited as a clamped percentage from 0
to 100 and converted to the stored runtime opacity number from 0 to 1.
Constructor edit mode blocks runtime actions. Clicking or dragging system controls does not toggle fullscreen, sound, or offline mode. Hidden controls are rendered as ghost controls so authors can reselect and unhide them.
Admin Editing UI
Global defaults are listed at /global-ui-control-defaults and edited per
control:
/global-ui-control-defaults/offline/global-ui-control-defaults/fullscreen/global-ui-control-defaults/sound
Project-level overrides are edited from
/project-ui-control-settings?projectId=....
The per-control global pages and project page use UiControlsSettingsForm, a
typed editor with the same tab model used by element defaults:
- General Settings: enabled/hidden state, canvas-relative position, anchor, order, and default/active icon URLs
- CSS Styles: button/icon sizing, radius, icon color, background colors, and border colors
- Effects: opacity, z-index, and box shadow
Opacity inputs in the constructor/editor UI are percentage-based for authors;
stored settings_json.opacity remains a numeric CSS opacity value.
Project settings can be cleared with Use Global Defaults, which deletes the project/environment override so the project inherits global defaults again.
Publishing
project_ui_control_settings is environment-aware and is copied by the publish
workflow:
dev -> stage -> production
Page-level overrides live on tour_pages and are copied with page records.
Project clone copies project UI-control settings for each environment, and page
duplication copies page-level global_ui_controls_settings_json.
Migrations
20260628000001-create-ui-control-settings.jscreates the two DB models, addstour_pages.global_ui_controls_settings_json, creates the project/env unique index, and seeds the initial global defaults.20260628000005-snapshot-existing-project-ui-controls.jssnapshots project-level settings for existing projects that do not already have project UI-control overrides.
Existing Projects
Migration 20260628000005-snapshot-existing-project-ui-controls.js snapshots
the previous runtime chrome layout into project-level settings for existing
projects in dev, stage, and production when no project UI-control override
already exists. The snapshot keeps the old top-right grouped placement offsets
and previous runtime z-index, but stores dimensions with the same
canvas-relative defaults used by new projects (2.6% button size, 1.35% icon
size, 0.42% radius).