Initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@goodgrief/shared-types",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
export type SubmissionSource = "live" | "pre_show" | "invite" | "admin_upload" | "library_import";
|
||||
|
||||
export type SubmissionStatus =
|
||||
| "uploaded"
|
||||
| "processing"
|
||||
| "pending_moderation"
|
||||
| "approved_partial"
|
||||
| "approved_all"
|
||||
| "rejected"
|
||||
| "archived";
|
||||
|
||||
export type ProcessingStatus = "queued" | "ready" | "failed";
|
||||
export type ModerationStatus = "pending" | "approved" | "hold" | "rejected" | "archived";
|
||||
export type ModerationDecisionType = "approved" | "hold" | "rejected" | "archive_only";
|
||||
export type CollectionKind = "bank" | "playlist" | "moment" | "favorites" | "archive_set";
|
||||
export type SceneRenderMode = "2d" | "3d" | "hybrid" | "shader_overlay";
|
||||
export type CueTriggerMode = "manual" | "follow" | "hold" | "armed";
|
||||
export type OperatorSessionMode = "rehearsal" | "tech" | "show" | "archive_review";
|
||||
export type OutputSurfaceRole = "program" | "preview" | "aux";
|
||||
export type SceneTier = "mvp" | "v1" | "stretch";
|
||||
export type SceneFamily = "hero" | "chorus" | "floor_paint" | "arrival" | "rupture" | "safe";
|
||||
export type TextTreatmentMode = "off" | "edge_whispers" | "relay_ticker" | "anchor_caption";
|
||||
export type SceneCategory =
|
||||
| "memory_elegy"
|
||||
| "humor_rupture"
|
||||
| "choir_swell"
|
||||
| "abstract_grief"
|
||||
| "photo_collage"
|
||||
| "immersive_3d"
|
||||
| "transition"
|
||||
| "audience_reactive";
|
||||
|
||||
export interface ContributorConsent {
|
||||
id: string;
|
||||
submissionId: string;
|
||||
hasRights: boolean;
|
||||
allowProjection: boolean;
|
||||
acknowledgePublicPerformance: boolean;
|
||||
agreedAt: string;
|
||||
allowArchive?: boolean;
|
||||
contactEmail?: string;
|
||||
guardianConfirmed?: boolean;
|
||||
}
|
||||
|
||||
export interface Submission {
|
||||
id: string;
|
||||
source: SubmissionSource;
|
||||
submittedAt: string;
|
||||
status: SubmissionStatus;
|
||||
consentId: string;
|
||||
displayName?: string;
|
||||
caption?: string;
|
||||
promptAnswer?: string;
|
||||
sessionToken?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface QualityFlags {
|
||||
tooSmall?: boolean;
|
||||
blurry?: boolean;
|
||||
lowContrast?: boolean;
|
||||
unusualAspectRatio?: boolean;
|
||||
}
|
||||
|
||||
export interface PhotoAsset {
|
||||
id: string;
|
||||
submissionId: string;
|
||||
originalKey: string;
|
||||
thumbKey?: string;
|
||||
previewKey?: string;
|
||||
renderKey?: string;
|
||||
mimeType: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
orientation?: "portrait" | "landscape" | "square";
|
||||
sha256?: string;
|
||||
pHash?: string;
|
||||
dominantColor?: string;
|
||||
processingStatus: ProcessingStatus;
|
||||
moderationStatus: ModerationStatus;
|
||||
createdAt: string;
|
||||
qualityFlags?: QualityFlags;
|
||||
approvedAt?: string;
|
||||
rejectionReason?: string;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: string;
|
||||
label: string;
|
||||
category: "tone" | "era" | "subject" | "palette" | "scene_fit" | "quality" | "show_moment";
|
||||
color?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Collection {
|
||||
id: string;
|
||||
name: string;
|
||||
kind: CollectionKind;
|
||||
createdAt: string;
|
||||
description?: string;
|
||||
coverAssetId?: string;
|
||||
locked?: boolean;
|
||||
assetIds: string[];
|
||||
tagIds: string[];
|
||||
}
|
||||
|
||||
export interface PhotoTreatmentParams {
|
||||
contrast: number;
|
||||
saturation: number;
|
||||
blackPoint: number;
|
||||
whitePoint: number;
|
||||
paletteMix: number;
|
||||
clarity: number;
|
||||
edgeLight: number;
|
||||
}
|
||||
|
||||
export interface ScenicTreatmentParams {
|
||||
washIntensity: number;
|
||||
spill: number;
|
||||
floorMix: number;
|
||||
paletteBias: number;
|
||||
vignette: number;
|
||||
fillHue: number;
|
||||
fillSaturation: number;
|
||||
fillLightness: number;
|
||||
}
|
||||
|
||||
export interface CompositionParams {
|
||||
motion: number;
|
||||
density: number;
|
||||
depth: number;
|
||||
focus: number;
|
||||
crop: number;
|
||||
emphasis: number;
|
||||
bands?: number;
|
||||
columns?: number;
|
||||
shutters?: number;
|
||||
tiles?: number;
|
||||
lanes?: number;
|
||||
edge?: "left" | "right";
|
||||
}
|
||||
|
||||
export interface TextTreatmentParams {
|
||||
mode: TextTreatmentMode;
|
||||
opacity: number;
|
||||
density: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export interface SceneParamGroups {
|
||||
photoTreatment: PhotoTreatmentParams;
|
||||
scenicTreatment: ScenicTreatmentParams;
|
||||
composition: CompositionParams;
|
||||
textTreatment: TextTreatmentParams;
|
||||
}
|
||||
|
||||
export interface SceneParamPatch {
|
||||
photoTreatment?: Partial<PhotoTreatmentParams>;
|
||||
scenicTreatment?: Partial<ScenicTreatmentParams>;
|
||||
composition?: Partial<CompositionParams>;
|
||||
textTreatment?: Partial<TextTreatmentParams>;
|
||||
}
|
||||
|
||||
export interface PhotoTreatmentPreset {
|
||||
id: string;
|
||||
name: string;
|
||||
params: Partial<PhotoTreatmentParams>;
|
||||
}
|
||||
|
||||
export interface ScenicTreatmentPreset {
|
||||
id: string;
|
||||
name: string;
|
||||
params: Partial<ScenicTreatmentParams>;
|
||||
}
|
||||
|
||||
export interface SceneDefinition {
|
||||
id: string;
|
||||
sceneKey: string;
|
||||
sceneFamily: SceneFamily;
|
||||
name: string;
|
||||
category: SceneCategory;
|
||||
tier: SceneTier;
|
||||
visualDescription: string;
|
||||
emotionalUseCase: string;
|
||||
renderMode: SceneRenderMode;
|
||||
complexity: "low" | "medium" | "high";
|
||||
performanceRisk: "low" | "medium" | "high";
|
||||
inputRules: {
|
||||
minAssets: number;
|
||||
maxAssets?: number;
|
||||
recommendedTags: string[];
|
||||
};
|
||||
defaultParams: SceneParamGroups;
|
||||
defaultPresetId: string;
|
||||
supportedPresetIds: string[];
|
||||
operatorControls: string[];
|
||||
metadataHints: string[];
|
||||
}
|
||||
|
||||
export interface CueTransition {
|
||||
style: "cut" | "dissolve" | "veil_wipe" | "luma_hold" | "rupture_offset";
|
||||
durationMs: number;
|
||||
}
|
||||
|
||||
export interface Cue {
|
||||
id: string;
|
||||
showConfigId: string;
|
||||
orderIndex: number;
|
||||
sceneDefinitionId: string;
|
||||
triggerMode: CueTriggerMode;
|
||||
transitionIn: CueTransition;
|
||||
transitionOut: CueTransition;
|
||||
collectionId?: string;
|
||||
assetIds?: string[];
|
||||
durationMs?: number;
|
||||
effectPresetId?: string;
|
||||
parameterOverrides?: SceneParamPatch;
|
||||
notes?: string;
|
||||
nextCueId?: string;
|
||||
}
|
||||
|
||||
export interface CueUpsertPayload {
|
||||
id?: string;
|
||||
showConfigId?: string;
|
||||
orderIndex?: number;
|
||||
sceneDefinitionId: string;
|
||||
triggerMode: CueTriggerMode;
|
||||
transitionIn: CueTransition;
|
||||
transitionOut: CueTransition;
|
||||
collectionId?: string;
|
||||
assetIds?: string[];
|
||||
durationMs?: number;
|
||||
effectPresetId?: string;
|
||||
parameterOverrides?: SceneParamPatch;
|
||||
notes?: string;
|
||||
nextCueId?: string;
|
||||
}
|
||||
|
||||
export interface CueMovePayload {
|
||||
direction: "up" | "down";
|
||||
}
|
||||
|
||||
export interface CueGeneratePayload {
|
||||
sceneDefinitionId?: string;
|
||||
preferredAssetIds?: string[];
|
||||
includeRupture?: boolean;
|
||||
}
|
||||
|
||||
export interface EffectPreset {
|
||||
id: string;
|
||||
modeKey: string;
|
||||
compatibleSceneFamilies: SceneFamily[];
|
||||
name: string;
|
||||
category:
|
||||
| "compositing"
|
||||
| "temporal"
|
||||
| "spatial"
|
||||
| "color"
|
||||
| "depth"
|
||||
| "particles"
|
||||
| "reveal"
|
||||
| "audio"
|
||||
| "performer"
|
||||
| "projection";
|
||||
artisticPurpose: string;
|
||||
operatorControls: string[];
|
||||
implementationLevel: "css" | "canvas" | "webgl" | "custom_shader";
|
||||
performanceNotes: string;
|
||||
paramDefaults: SceneParamPatch;
|
||||
safeRanges: Record<string, { min: number; max: number }>;
|
||||
}
|
||||
|
||||
export interface OperatorSession {
|
||||
id: string;
|
||||
startedAt: string;
|
||||
mode: OperatorSessionMode;
|
||||
operatorName: string;
|
||||
showConfigId: string;
|
||||
endedAt?: string;
|
||||
venueName?: string;
|
||||
incidentNotes?: string;
|
||||
}
|
||||
|
||||
export interface ModerationDecision {
|
||||
id: string;
|
||||
assetId: string;
|
||||
operatorSessionId: string;
|
||||
decision: ModerationDecisionType;
|
||||
decidedAt: string;
|
||||
reasonCode?: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface OutputSurface {
|
||||
id: string;
|
||||
name: string;
|
||||
role: OutputSurfaceRole;
|
||||
width: number;
|
||||
height: number;
|
||||
aspectRatio: string;
|
||||
screenIndex?: number;
|
||||
maskShape?: "full_frame" | "letterbox" | "pillarbox" | "custom";
|
||||
safeMargin?: number;
|
||||
colorProfile?: string;
|
||||
fullscreenBounds?: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ShowConfig {
|
||||
id: string;
|
||||
showName: string;
|
||||
venueName: string;
|
||||
defaultOutputSurfaceId: string;
|
||||
safeSceneCueId: string;
|
||||
retentionDays: number;
|
||||
ingestPolicy: "fully_live" | "pre_show_plus_live" | "pre_show_only";
|
||||
theme?: string;
|
||||
projectionNotes?: string;
|
||||
operatorShortcuts?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface SessionEvent {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
timestamp: string;
|
||||
type:
|
||||
| "cue_fired"
|
||||
| "cue_skipped"
|
||||
| "blackout"
|
||||
| "safe_scene"
|
||||
| "submission_received"
|
||||
| "asset_approved"
|
||||
| "asset_rejected";
|
||||
payload: Record<string, string | number | boolean | null>;
|
||||
}
|
||||
|
||||
export interface SubmissionPayload {
|
||||
displayName?: string;
|
||||
caption?: string;
|
||||
promptAnswer?: string;
|
||||
allowArchive?: boolean;
|
||||
hasRights: boolean;
|
||||
allowProjection: boolean;
|
||||
acknowledgePublicPerformance: boolean;
|
||||
source?: SubmissionSource;
|
||||
}
|
||||
|
||||
export interface ModerationActionPayload {
|
||||
decision: ModerationDecisionType;
|
||||
reasonCode?: string;
|
||||
note?: string;
|
||||
tagIds?: string[];
|
||||
collectionIds?: string[];
|
||||
}
|
||||
|
||||
export interface RepositoryState {
|
||||
submissions: Submission[];
|
||||
consents: ContributorConsent[];
|
||||
photoAssets: PhotoAsset[];
|
||||
tags: Tag[];
|
||||
collections: Collection[];
|
||||
scenes: SceneDefinition[];
|
||||
cues: Cue[];
|
||||
effectPresets: EffectPreset[];
|
||||
operatorSessions: OperatorSession[];
|
||||
moderationDecisions: ModerationDecision[];
|
||||
outputSurfaces: OutputSurface[];
|
||||
showConfig: ShowConfig;
|
||||
sessionEvents: SessionEvent[];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { ModerationDecisionType } from "./entities";
|
||||
|
||||
export type ApiEvent =
|
||||
| {
|
||||
type: "submission.received";
|
||||
submissionId: string;
|
||||
assetId: string;
|
||||
}
|
||||
| {
|
||||
type: "asset.moderated";
|
||||
assetId: string;
|
||||
decision: ModerationDecisionType;
|
||||
}
|
||||
| {
|
||||
type: "cue.fired";
|
||||
cueId: string;
|
||||
}
|
||||
| {
|
||||
type: "cue.safe";
|
||||
cueId: string;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./entities";
|
||||
export * from "./events";
|
||||
export * from "./mock";
|
||||
export * from "./scene-params";
|
||||
export * from "./scenes";
|
||||
@@ -0,0 +1,113 @@
|
||||
import type {
|
||||
Collection,
|
||||
OperatorSession,
|
||||
OutputSurface,
|
||||
RepositoryState,
|
||||
ShowConfig,
|
||||
Tag
|
||||
} from "./entities";
|
||||
import { defaultCueStack, defaultEffectPresets, defaultSceneDefinitions } from "./scenes";
|
||||
|
||||
export const defaultTags: Tag[] = [
|
||||
{ id: "tag-quiet", label: "quiet", category: "tone", color: "#9ea29f" },
|
||||
{ id: "tag-family", label: "family", category: "subject", color: "#d09d74" },
|
||||
{ id: "tag-portrait", label: "portrait", category: "subject", color: "#6f8579" },
|
||||
{ id: "tag-live", label: "live", category: "show_moment", color: "#e27f66" },
|
||||
{ id: "tag-choir", label: "choir", category: "show_moment", color: "#7d7098" },
|
||||
{ id: "tag-archive", label: "archive", category: "scene_fit", color: "#c8c0b3" }
|
||||
];
|
||||
|
||||
export const defaultCollections: Collection[] = [
|
||||
{
|
||||
id: "collection-curated-library",
|
||||
name: "Curated Library",
|
||||
kind: "bank",
|
||||
createdAt: new Date().toISOString(),
|
||||
description: "Imported operator-managed seed and rehearsal media.",
|
||||
locked: true,
|
||||
assetIds: [],
|
||||
tagIds: ["tag-archive", "tag-portrait"]
|
||||
},
|
||||
{
|
||||
id: "collection-favorites",
|
||||
name: "Favorites",
|
||||
kind: "favorites",
|
||||
createdAt: new Date().toISOString(),
|
||||
description: "Operator-trusted images for flexible live use.",
|
||||
locked: false,
|
||||
assetIds: [],
|
||||
tagIds: ["tag-portrait", "tag-quiet"]
|
||||
},
|
||||
{
|
||||
id: "collection-choir-swell",
|
||||
name: "Choir Swell",
|
||||
kind: "moment",
|
||||
createdAt: new Date().toISOString(),
|
||||
description: "Assets suitable for collective or musical lift.",
|
||||
locked: false,
|
||||
assetIds: [],
|
||||
tagIds: ["tag-choir", "tag-family"]
|
||||
}
|
||||
];
|
||||
|
||||
export const defaultOutputSurfaces: OutputSurface[] = [
|
||||
{
|
||||
id: "surface-program",
|
||||
name: "Program",
|
||||
role: "program",
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
aspectRatio: "16:9",
|
||||
safeMargin: 0.06
|
||||
},
|
||||
{
|
||||
id: "surface-preview",
|
||||
name: "Preview",
|
||||
role: "preview",
|
||||
width: 1280,
|
||||
height: 720,
|
||||
aspectRatio: "16:9"
|
||||
}
|
||||
];
|
||||
|
||||
export const defaultShowConfig: ShowConfig = {
|
||||
id: "show-good-grief",
|
||||
showName: "Good Grief",
|
||||
venueName: "Studio Black Box",
|
||||
defaultOutputSurfaceId: "surface-program",
|
||||
safeSceneCueId: "cue-safe-hold",
|
||||
retentionDays: 21,
|
||||
ingestPolicy: "fully_live",
|
||||
theme: "Tender collage for live projection.",
|
||||
projectionNotes: "Maintain center-safe composition and low white clip.",
|
||||
operatorShortcuts: {
|
||||
Space: "take cue",
|
||||
KeyB: "blackout",
|
||||
KeyS: "safe scene",
|
||||
ArrowDown: "next cue"
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultOperatorSession: OperatorSession = {
|
||||
id: "session-default",
|
||||
startedAt: new Date().toISOString(),
|
||||
mode: "rehearsal",
|
||||
operatorName: "Operator",
|
||||
showConfigId: "show-good-grief"
|
||||
};
|
||||
|
||||
export const createEmptyRepositoryState = (): RepositoryState => ({
|
||||
submissions: [],
|
||||
consents: [],
|
||||
photoAssets: [],
|
||||
tags: defaultTags,
|
||||
collections: defaultCollections,
|
||||
scenes: defaultSceneDefinitions,
|
||||
cues: defaultCueStack,
|
||||
effectPresets: defaultEffectPresets,
|
||||
operatorSessions: [defaultOperatorSession],
|
||||
moderationDecisions: [],
|
||||
outputSurfaces: defaultOutputSurfaces,
|
||||
showConfig: defaultShowConfig,
|
||||
sessionEvents: []
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
import type {
|
||||
CompositionParams,
|
||||
PhotoTreatmentParams,
|
||||
ScenicTreatmentParams,
|
||||
SceneParamGroups,
|
||||
SceneParamPatch,
|
||||
TextTreatmentParams
|
||||
} from "./entities";
|
||||
|
||||
const defaultTextTreatment = (): TextTreatmentParams => ({
|
||||
mode: "off",
|
||||
opacity: 0.2,
|
||||
density: 0.35,
|
||||
scale: 0.8
|
||||
});
|
||||
|
||||
export type SceneParamScalar = number | string | boolean;
|
||||
|
||||
export const createSceneParams = (input: {
|
||||
photoTreatment: PhotoTreatmentParams;
|
||||
scenicTreatment: ScenicTreatmentParams;
|
||||
composition: CompositionParams;
|
||||
textTreatment?: TextTreatmentParams;
|
||||
}): SceneParamGroups => ({
|
||||
photoTreatment: { ...input.photoTreatment },
|
||||
scenicTreatment: { ...input.scenicTreatment },
|
||||
composition: { ...input.composition },
|
||||
textTreatment: {
|
||||
...defaultTextTreatment(),
|
||||
...input.textTreatment
|
||||
}
|
||||
});
|
||||
|
||||
export const mergeSceneParams = (base: SceneParamGroups, ...patches: Array<SceneParamPatch | undefined>): SceneParamGroups => {
|
||||
const merged = createSceneParams(base);
|
||||
|
||||
for (const patch of patches) {
|
||||
if (!patch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (patch.photoTreatment) {
|
||||
merged.photoTreatment = {
|
||||
...merged.photoTreatment,
|
||||
...patch.photoTreatment
|
||||
};
|
||||
}
|
||||
|
||||
if (patch.scenicTreatment) {
|
||||
merged.scenicTreatment = {
|
||||
...merged.scenicTreatment,
|
||||
...patch.scenicTreatment
|
||||
};
|
||||
}
|
||||
|
||||
if (patch.composition) {
|
||||
merged.composition = {
|
||||
...merged.composition,
|
||||
...patch.composition
|
||||
};
|
||||
}
|
||||
|
||||
if (patch.textTreatment) {
|
||||
merged.textTreatment = {
|
||||
...merged.textTreatment,
|
||||
...patch.textTreatment
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
};
|
||||
|
||||
export const getSceneParamValue = (params: SceneParamGroups | SceneParamPatch, path: string): SceneParamScalar | undefined => {
|
||||
const [group, key] = path.split(".");
|
||||
if (!group || !key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const container = params[group as keyof SceneParamGroups] as Record<string, SceneParamScalar> | undefined;
|
||||
return container?.[key];
|
||||
};
|
||||
|
||||
export const setSceneParamValue = (params: SceneParamGroups, path: string, value: SceneParamScalar): SceneParamGroups => {
|
||||
const [group, key] = path.split(".");
|
||||
if (!group || !key) {
|
||||
return params;
|
||||
}
|
||||
|
||||
if (!(group in params)) {
|
||||
return params;
|
||||
}
|
||||
|
||||
return {
|
||||
...params,
|
||||
[group]: {
|
||||
...((params[group as keyof SceneParamGroups] as unknown as Record<string, SceneParamScalar>) ?? {}),
|
||||
[key]: value
|
||||
}
|
||||
} as SceneParamGroups;
|
||||
};
|
||||
|
||||
export const flattenSceneParams = (params: SceneParamGroups | SceneParamPatch) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(params).flatMap(([group, values]) =>
|
||||
Object.entries(values ?? {}).map(([key, value]) => [`${group}.${key}`, value] as const)
|
||||
)
|
||||
) as Record<string, SceneParamScalar>;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user