210 lines
8.7 KiB
Markdown
210 lines
8.7 KiB
Markdown
# 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
|
|
<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-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).
|