Files
gnommoplayer/deployment.md
T

680 lines
17 KiB
Markdown
Raw Normal View History

2026-04-11 09:21:22 +02:00
# Deploying GlitchPlayer Into Another React + Vite App
This guide explains how to include `GlitchPlayer` inside another user interface that is also built with React and Vite.
The target mental model is:
- your host app owns routing, fetching, auth, persistence, and editor UI
- `GlitchPlayer` owns presentation playback, responsive layout, slide rendering, gesture handling, pause/resume behavior, and voting UI
In other words, `GlitchPlayer` should behave like an embeddable runtime surface inside a larger product such as GnommoEditor.
For editor integrations, this package also exposes:
- `GlitchSlideRenderer`
- `GlitchSlideThumbnail`
For template pickers, prefer the normalized availability exports:
- `slideTemplateAvailabilityList`
- `slideTemplateAvailabilityByDisplayMode`
These expand templates by display mode. If a template supports both `square` and `fullscreen`, it appears once for each supported format.
## 1. What The Host App Must Provide
To render `GlitchPlayer`, the host app needs to provide:
- a `presentation` object
- a `slideRegistry`
- an optional player mode
- an optional target slide id
- an optional vote handler
- an optional player event handler
- an optional session id
Today, the public player surface is exported from:
- [src/player/index.ts](/Users/jenstandstad/Projects/gnommoplayer/src/player/index.ts)
The primary component is:
- `GlitchPlayer`
- `GlitchSlideRenderer`
- `GlitchSlideThumbnail`
The key types are:
- `PresentationDefinition`
- `SlideRegistry`
- `VoteRequest`
- `ClientEvent`
For editor-driven slide forms and template pickers, this package also exports:
- `slideTemplateDefinitions`
- `slideTemplateDefinitionList`
- `slideTemplateAvailabilityList`
- `slideTemplateAvailabilityByDisplayMode`
- `slideTemplateDrafts`
- `slideTemplateBudgets`
For hosted interactive Glitch components, the player also supports a base URL convention:
- `VITE_GLITCH_COMPONENT_BASE_URL`
If a `GlitchComponentFrame` slide omits `componentUrl`, the player resolves the iframe source as:
- `${VITE_GLITCH_COMPONENT_BASE_URL || "/glitch"}/glitch_${componentId.replace(/-/g, "_")}/index.html`
That matches the pattern where built Glitch component assets are copied into `public/glitch/...`.
## 2. Public API
Current host usage looks like this:
```tsx
import {
GlitchPlayer,
GlitchSlideThumbnail,
createSessionId,
defaultSlideRegistry,
type PresentationDefinition,
type Slide,
type VoteRequest,
type ClientEvent
} from "gnommoplayer";
import "gnommoplayer/player/styles.css";
interface PlayerHostProps {
presentation: PresentationDefinition;
}
export function PlayerHost({ presentation }: PlayerHostProps) {
async function handleVoteCommit(vote: VoteRequest) {
await fetch(`/api/presentations/${presentation.id}/votes`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(vote)
});
}
function handlePlayerEvent(event: ClientEvent) {
console.log("[glitch-player-event]", event);
}
return (
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
mode="playback"
initialSessionId={createSessionId()}
onVoteCommit={handleVoteCommit}
onEvent={handlePlayerEvent}
/>
);
}
```
## 2.1 Rendering Slide Thumbnails In An Editor
Use `GlitchSlideThumbnail` for slide lists, cards, and inspector previews. It renders the actual slide component without video playback or player chrome.
```tsx
function SlideCard({
presentationId,
slide,
}: {
presentationId: string;
slide: Slide;
}) {
return (
<GlitchSlideThumbnail
presentationId={presentationId}
slide={slide}
slideRegistry={defaultSlideRegistry}
viewportMode="desktop"
/>
);
}
```
Use `GlitchSlideRenderer` when you want a larger slide-only render surface without the surrounding player.
## 2.1 Previewing A Specific Slide
For editor workflows, `GlitchPlayer` can start in a slide-targeted preview mode.
Use:
- `mode="slide-preview"`
- `targetSlideId="some-slide-id"`
Example:
```tsx
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
mode="slide-preview"
targetSlideId="slide-05-chart-single"
initialSessionId={createSessionId()}
/>
```
That mode is useful when:
- an editor wants to preview one slide in context
- a template author wants to inspect a specific candidate
- GnommoEditor wants to jump directly to the currently selected slide in the timeline
## 3. Installation Strategy
There are two practical ways to use `GlitchPlayer` inside another Vite app.
### Option A: Local workspace dependency
Use this when the host app and `gnommoplayer` live side-by-side during active development.
Example:
```json
{
"dependencies": {
"gnommoplayer": "file:../gnommoplayer"
}
}
```
Then install as usual:
```bash
npm install
```
This is the easiest path while GlitchPlayer is still evolving rapidly.
### Option B: Internal package
Use this when the player should be versioned and consumed more formally.
That usually means:
- adding a proper package export map
- producing a distributable build
- publishing to a private npm registry or workspace package feed
This repo is not fully packaged for that yet, but the code is now organized in a way that makes that a straightforward next step.
### Option C: Built Files Only
Use this when another agent or deployment process is only allowed to fetch compiled assets from this repo.
Build the vendor assets with:
```bash
npm run build:lib
```
Or simply run:
```bash
npm run build
```
which now builds both the demo app and the vendor assets in one pass.
That produces these stable files:
- `dist/vendor/gnommoplayer.js`
- `dist/vendor/gnommoplayer.css`
This is the best path for GnommoEditor if the integration process is only allowed to pull built artifacts rather than source files.
## 3.1 Local Deployment Commands
This repo includes a deployment helper that copies the latest built vendor bundle into sibling apps.
Available commands:
```bash
npm run deploy:editor
npm run deploy:web
npm run deploy:all
./deploy.sh editor
./deploy.sh web
./deploy.sh all
```
The shell wrapper is useful when you just want a short deploy command from the repo root:
```bash
./deploy.sh
```
That defaults to:
```bash
npm run deploy:all
```
The npm commands are still available too:
```bash
npm run deploy:editor
npm run deploy:web
npm run deploy:all
```
These commands:
- run `npm run build:lib`
- ensure the vendor artifacts exist
- copy the latest bundle into the target app
Current target paths:
- `../gnommoeditor/src/vendor/gnommoplayer`
- `../gnommoweb/src/vendor/gnommoplayer`
The underlying script lives at:
- [scripts/deploy-vendor.mjs](/Users/jenstandstad/Projects/gnommoplayer/scripts/deploy-vendor.mjs)
You can also call it directly:
```bash
node scripts/deploy-vendor.mjs editor
node scripts/deploy-vendor.mjs web
node scripts/deploy-vendor.mjs all
node scripts/deploy-vendor.mjs all --skip-build
```
Use `--skip-build` only when you know `dist/vendor/` already contains the bundle you want to publish.
## 4. Styling Requirements
Import the player stylesheet once in the host app:
```tsx
import "gnommoplayer/player/styles.css";
```
If the host app is consuming built files directly instead of the source package, import the compiled vendor assets instead:
```tsx
import "../vendor/gnommoplayer/gnommoplayer.css";
import {
GlitchPlayer,
GlitchSlideThumbnail,
defaultSlideRegistry
} from "../vendor/gnommoplayer/gnommoplayer.js";
```
Inside this repo, that stylesheet entry currently lives at:
- [src/player/styles.css](/Users/jenstandstad/Projects/gnommoplayer/src/player/styles.css)
Important notes:
- The player expects to own its own visual surface.
- The host should avoid wrapping it in containers that impose additional padding, border radius, or clipping unless that is intentional.
- The player uses viewport-aware layout logic and expects enough room to center its stage within the available host surface.
Recommended host wrapper:
```tsx
export function PlayerScreen({ children }: { children: React.ReactNode }) {
return (
<div
style={{
width: "100%",
minHeight: "100vh",
background: "#6c6c6c"
}}
>
{children}
</div>
);
}
```
If the host app has its own page chrome, toolbars, or inspector panels, mount the player inside a dedicated content region rather than inside an arbitrary card component.
## 4.1 Built-File Vendor Workflow
For a sibling React + Vite app that is only allowed to fetch built files, the cleanest integration flow is:
1. Run `npm run build:lib` in `gnommoplayer`
2. Copy:
- `dist/vendor/gnommoplayer.js`
- `dist/vendor/gnommoplayer.css`
3. Place them in a vendor folder inside the host app, for example:
- `src/vendor/gnommoplayer/gnommoplayer.js`
- `src/vendor/gnommoplayer/gnommoplayer.css`
4. Import the CSS once near the app root
5. Import the JS module where the host renders the player or thumbnails
The built JS bundle already inlines the dynamic slide imports, so the host does not need to manage extra chunk files for the standard slide set.
## 5. Presentation Data Contract
`GlitchPlayer` expects a `PresentationDefinition`.
That type is currently defined in:
- [src/types/lecture.ts](/Users/jenstandstad/Projects/gnommoplayer/src/types/lecture.ts)
At the moment, `PresentationDefinition` is an alias for the existing internal `LectureDefinition` shape.
The important fields are:
```ts
interface PresentationDefinition {
id: string;
title: string;
version: string;
durationSec: number;
segments: VideoSegment[];
slides: Slide[];
}
```
Each presentation needs:
- ordered video segments
- timed slides
- one or more `glitchSlides` per logical slide
- valid `componentKey` values that exist in the registry supplied by the host
## 6. Loading Presentations In The Host App
The host app should fetch presentation data before rendering `GlitchPlayer`.
Example host loader:
```tsx
import { useEffect, useState } from "react";
import { GlitchPlayer, type PresentationDefinition } from "gnommoplayer";
export function PresentationRoute({
presentationId
}: {
presentationId: string;
}) {
const [presentation, setPresentation] =
useState<PresentationDefinition | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/presentations/${presentationId}`)
.then(async (response) => {
if (!response.ok) {
throw new Error(`Failed to load presentation ${presentationId}`);
}
return (await response.json()) as PresentationDefinition;
})
.then((data) => {
if (!cancelled) {
setPresentation(data);
}
})
.catch((caughtError: unknown) => {
if (!cancelled) {
setError(
caughtError instanceof Error
? caughtError.message
: "Could not load presentation."
);
}
});
return () => {
cancelled = true;
};
}, [presentationId]);
if (error) {
return <div>{error}</div>;
}
if (!presentation) {
return <div>Loading</div>;
}
return (
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
/>
);
}
```
## 7. Slide Registry Integration
The registry maps `componentKey` strings from presentation data to lazily-loaded React modules.
It looks like this:
```ts
type SlideRegistry = Record<
string,
() => Promise<{ default: React.ComponentType<SlideRuntimeProps> }>
>;
```
In this repo, the built-in demo registry lives at:
- [src/lib/slideRegistry.ts](/Users/jenstandstad/Projects/gnommoplayer/src/lib/slideRegistry.ts)
Example host registry:
```tsx
import type { SlideRegistry } from "gnommoplayer";
export const gnommoEditorSlideRegistry: SlideRegistry = {
SquareYellow: () => import("./slides/SquareYellow"),
FullscreenSplit: () => import("./slides/FullscreenSplit"),
RevenueChart: () => import("./slides/RevenueChart"),
OrbitalModel3D: () => import("./slides/OrbitalModel3D")
};
```
Rules to keep in mind:
- Every `componentKey` referenced by the presentation must exist in the registry.
- Components should be deterministic and reasonably lightweight.
- Prefer `componentKey + props` over arbitrary runtime code from a database.
- If the host supports custom submitted code, that code should be reviewed or sandboxed before entering the public registry.
## 8. Vote Handling
The player can emit vote requests through `onVoteCommit`.
That callback receives a `VoteRequest`:
```ts
interface VoteRequest {
lectureId: string;
slideId: string;
previousGlitchSlideId: string;
selectedGlitchSlideId: string;
voteDirection?: "up" | "down";
sessionId: string;
timestamp: string;
deviceContext: DeviceContext;
}
```
Even though the field is still named `lectureId` internally, treat it as the presentation id for integration purposes.
Recommended host behavior:
- send the vote request to your own API
- include auth/session context on the server side
- do not block the UI longer than necessary
- log failures, but keep playback usable
Example:
```tsx
async function handleVoteCommit(vote: VoteRequest) {
const response = await fetch(`/api/presentations/${vote.lectureId}/votes`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(vote)
});
if (!response.ok) {
throw new Error("Vote request failed.");
}
}
```
## 9. Player Events
The host can also listen to runtime events via `onEvent`.
Current events include things like:
- `lecture_loaded`
- `segment_changed`
- `slide_activated`
- `soft_pause_started`
- `soft_pause_ended`
- `glitch_slide_changed`
- `vote_committed`
This is useful for:
- analytics
- debugging
- editor preview tooling
- QA capture
Example:
```tsx
function handlePlayerEvent(event: ClientEvent) {
if (event.type === "slide_activated") {
console.log("Active slide:", event.slideId, event.glitchSlideId);
}
}
```
## 10. Recommended Host Layout
For a host app like GnommoEditor, a strong integration pattern is:
- app shell owns toolbar, navigation, inspector, and editor panels
- central preview region owns the player mount
- the player fills that preview region
Example:
```tsx
export function EditorPreviewPane({
presentation
}: {
presentation: PresentationDefinition;
}) {
return (
<section
style={{
flex: 1,
minWidth: 0,
minHeight: 0,
background: "#5d5d5d"
}}
>
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
/>
</section>
);
}
```
Important:
- make sure the containing region allows height to collapse properly with `min-height: 0` when used inside flex or grid shells
- avoid nesting inside containers with accidental `overflow: hidden` unless intended
- avoid wrapping the player in padded cards, because the player already frames its own stage
## 11. Vite Considerations
If the host app is also using Vite:
- dynamic `import()` for slide components will work well
- CSS import from the player should be straightforward
- local workspace dependency is usually the easiest during development
Potential issues to watch:
- duplicated React versions between host and player
- CSS ordering conflicts if the host imports many global styles after the player stylesheet
- path alias assumptions if you later move this from repo-local import to packaged dependency
Recommended safeguards:
- keep React and ReactDOM deduped in the workspace
- import player styles at the app shell level
- keep slide modules in the host app, not hidden inside the player package, if GnommoEditor should own template evolution
## 12. Minimal GnommoEditor Integration Plan
If the target is GnommoEditor, the simplest inclusion path is:
1. Add `gnommoplayer` as a workspace or file dependency.
2. Import `GlitchPlayer` and `player/styles.css`.
3. Fetch the current presentation from GnommoEditors backend.
4. Build a host-owned slide registry matching the templates GnommoEditor supports.
5. Pass vote commits into a GnommoEditor API endpoint.
6. Optionally use `onEvent` to power preview analytics or editor debugging.
That gives GnommoEditor responsibility for:
- data
- registry
- persistence
- product shell
And leaves GlitchPlayer responsible for:
- playback
- timeline
- responsive stage layout
- slide rendering
- drag browsing
- voting interaction UI
## 13. Suggested Next Steps Before Production Embedding
Before using this as a stable shared dependency, I recommend:
1. Rename remaining internal `lecture*` event/type fields to `presentation*` where possible.
2. Add a package export map for `src/player/index.ts`.
3. Add a small integration example app or Storybook-like preview host.
4. Add a few host-level visual regression tests for portrait and landscape framing.
5. Decide whether the player package or the host app owns built-in default slide templates.
## 14. Current Reality Check
Right now, GlitchPlayer is reusable enough to embed in another React + Vite app, especially in a workspace-style setup.
It is not yet a polished published library, but it already has the right architectural boundary:
- host app provides presentation + registry + persistence
- GlitchPlayer provides runtime playback and interaction
That is the correct seam for moving this into GnommoEditor.