# 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.js` - `backend/src/db/models/project_ui_control_settings.js` - `backend/src/db/api/global_ui_control_defaults.ts` - `backend/src/db/api/project_ui_control_settings.ts` - `backend/src/routes/global_ui_control_defaults.ts` - `backend/src/routes/project_ui_control_settings.ts` - `backend/src/services/global_ui_control_defaults.ts` - `backend/src/services/project_ui_control_settings.ts` Frontend: - `frontend/src/types/uiControls.ts` - `frontend/src/components/UiControls/UiControlsSettingsForm.tsx` - `frontend/src/components/Runtime/RuntimeControls.tsx` - `frontend/src/components/Constructor/ElementEditorPanel.tsx` - `frontend/src/stores/global_ui_control_defaults/globalUiControlDefaultsSlice.ts` - `frontend/src/stores/project_ui_control_settings/projectUiControlSettingsSlice.ts` - `frontend/src/components/RuntimePresentation.tsx` - `frontend/src/pages/constructor.tsx` - `frontend/src/pages/global-ui-control-defaults.tsx` - `frontend/src/pages/global-ui-control-defaults/[controlType].tsx` - `frontend/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`. ```html ``` 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-defaults` - `PUT /api/global-ui-control-defaults/:id` - `GET /api/project-ui-control-settings/project/:projectId/env/:environment` - `PUT /api/project-ui-control-settings/project/:projectId/env/:environment` - `DELETE /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.js` creates the two DB models, adds `tour_pages.global_ui_controls_settings_json`, creates the project/env unique index, and seeds the initial global defaults. - `20260628000005-snapshot-existing-project-ui-controls.js` snapshots 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).