680 lines
17 KiB
Markdown
680 lines
17 KiB
Markdown
# 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 GnommoEditor’s 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.
|