@vibecuting/video-project-core 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -3
- package/src/composer/index.ts +2 -0
- package/src/composer/resolve-video-plugin-timeline.ts +103 -0
- package/src/composer/video-plugin-composer.tsx +54 -0
- package/src/core/scene/2dscene/default-2d-scene.tsx +62 -38
- package/src/core/scene/3dscene/default-3d-scene.tsx +64 -40
- package/src/core/scene/slide/default-slide-scene.tsx +61 -37
- package/src/index.ts +9 -2
- package/src/layouts/SceneSlots.tsx +19 -0
- package/src/layouts/index.ts +1 -0
- package/src/plugins/scene-component.tsx +111 -0
- package/src/plugins/scene-plugin-registry.ts +11 -0
- package/src/runtime/use-scene-root-props.ts +43 -0
- package/src/scenes/default-scene-props.ts +27 -0
- package/src/scenes/index.ts +1 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/scene-theme-registry.ts +57 -0
- package/src/themes/scene-theme.ts +41 -0
- package/src/themes/scene-themes.ts +44 -0
- package/src/transitions/index.ts +2 -0
- package/src/transitions/transition-plugin-registry.ts +10 -0
- package/src/transitions/transition-plugin.tsx +90 -0
- package/tsconfig.json +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecuting/video-project-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
"test": "node ../../../node_modules/vitest/vitest.mjs run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vibecuting/component-project-helper": "0.1.
|
|
22
|
-
"
|
|
21
|
+
"@vibecuting/component-project-helper": "0.1.17",
|
|
22
|
+
"@remotion/transitions": "4.0.473",
|
|
23
|
+
"react": "^19.0.0",
|
|
24
|
+
"zod": "4.1.12"
|
|
23
25
|
},
|
|
24
26
|
"peerDependencies": {
|
|
25
27
|
"remotion": "^4.0.0"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { SceneThemeRegistry } from '../themes'
|
|
2
|
+
import type { SceneTheme } from '../themes'
|
|
3
|
+
import type { DefaultSceneProps } from '../scenes/default-scene-props'
|
|
4
|
+
import type { ScenePluginRegistryEntry } from '../plugins/scene-component'
|
|
5
|
+
import type { TransitionPlugin } from '../transitions/transition-plugin'
|
|
6
|
+
|
|
7
|
+
export type VideoSceneSelection = {
|
|
8
|
+
id: string
|
|
9
|
+
chapterId: string
|
|
10
|
+
pluginKey: string
|
|
11
|
+
durationInFrames: number
|
|
12
|
+
themePreset?: string
|
|
13
|
+
props: unknown
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type VideoBoundarySelection = {
|
|
17
|
+
afterSceneId: string
|
|
18
|
+
pluginKey: string
|
|
19
|
+
props: unknown
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResolvedVideoTimeline = {
|
|
23
|
+
durationInFrames: number
|
|
24
|
+
scenes: Array<{
|
|
25
|
+
id: string
|
|
26
|
+
chapterId: string
|
|
27
|
+
durationInFrames: number
|
|
28
|
+
pluginKey: string
|
|
29
|
+
props: unknown
|
|
30
|
+
theme?: SceneTheme
|
|
31
|
+
plugin: ScenePluginRegistryEntry<DefaultSceneProps>
|
|
32
|
+
}>
|
|
33
|
+
boundaries: Array<{
|
|
34
|
+
afterSceneId: string
|
|
35
|
+
pluginKey: string
|
|
36
|
+
props: unknown
|
|
37
|
+
plugin: TransitionPlugin<unknown>
|
|
38
|
+
}>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ResolveVideoPluginTimelineInput = {
|
|
42
|
+
fps: number
|
|
43
|
+
scenes: VideoSceneSelection[]
|
|
44
|
+
boundaries: VideoBoundarySelection[]
|
|
45
|
+
scenePluginRegistry: { get(key: string): ScenePluginRegistryEntry<DefaultSceneProps> }
|
|
46
|
+
transitionPluginRegistry: { get(key: string): TransitionPlugin<unknown> }
|
|
47
|
+
sceneThemeRegistry: SceneThemeRegistry
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveVideoPluginTimeline(
|
|
51
|
+
input: ResolveVideoPluginTimelineInput,
|
|
52
|
+
): ResolvedVideoTimeline {
|
|
53
|
+
const scenes = input.scenes.map((scene) => ({
|
|
54
|
+
...scene,
|
|
55
|
+
plugin: input.scenePluginRegistry.get(scene.pluginKey),
|
|
56
|
+
theme: scene.themePreset ? input.sceneThemeRegistry.get(scene.themePreset) : undefined,
|
|
57
|
+
}))
|
|
58
|
+
|
|
59
|
+
const boundaries = input.boundaries.map((boundary) => ({
|
|
60
|
+
...boundary,
|
|
61
|
+
plugin: input.transitionPluginRegistry.get(boundary.pluginKey),
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
const boundaryBySceneId = new Map<string, typeof boundaries[number]>()
|
|
65
|
+
for (const boundary of boundaries) {
|
|
66
|
+
if (boundaryBySceneId.has(boundary.afterSceneId)) {
|
|
67
|
+
throw new Error(`duplicate boundary after scene: ${boundary.afterSceneId}`)
|
|
68
|
+
}
|
|
69
|
+
boundaryBySceneId.set(boundary.afterSceneId, boundary)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let durationInFrames = 0
|
|
73
|
+
scenes.forEach((scene, index) => {
|
|
74
|
+
const boundary = boundaryBySceneId.get(scene.id)
|
|
75
|
+
durationInFrames += scene.durationInFrames
|
|
76
|
+
|
|
77
|
+
if (!boundary) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const boundaryDuration = boundary.plugin.getBoundaryDurationInFrames(boundary.props, input.fps)
|
|
82
|
+
const overlap = boundary.plugin.getTimelineOverlapInFrames(boundary.props, input.fps)
|
|
83
|
+
|
|
84
|
+
if (boundary.plugin.kind === 'transition') {
|
|
85
|
+
durationInFrames -= overlap
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (index === scenes.length - 1) {
|
|
89
|
+
throw new Error(`boundary cannot follow last scene: ${scene.id}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const nextScene = scenes[index + 1]
|
|
93
|
+
if (boundaryDuration > scene.durationInFrames || boundaryDuration > nextScene.durationInFrames) {
|
|
94
|
+
throw new Error(`transition duration exceeds adjacent scenes: ${boundary.afterSceneId}`)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
durationInFrames,
|
|
100
|
+
scenes,
|
|
101
|
+
boundaries,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { createElement, Fragment } from 'react'
|
|
3
|
+
|
|
4
|
+
import type { ResolvedVideoTimeline } from './resolve-video-plugin-timeline'
|
|
5
|
+
|
|
6
|
+
type TransitionSeriesProps = {
|
|
7
|
+
children: ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type TransitionSeriesSequenceProps = {
|
|
11
|
+
durationInFrames: number
|
|
12
|
+
premountFor?: number
|
|
13
|
+
children: ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type TransitionSeriesBoundaryProps = {
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function TransitionSeries({ children }: TransitionSeriesProps) {
|
|
21
|
+
return createElement(Fragment, null, children)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
TransitionSeries.Sequence = function TransitionSeriesSequence({
|
|
25
|
+
children,
|
|
26
|
+
}: TransitionSeriesSequenceProps) {
|
|
27
|
+
return createElement(Fragment, null, children)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
TransitionSeries.Transition = function TransitionSeriesBoundary({
|
|
31
|
+
children,
|
|
32
|
+
}: TransitionSeriesBoundaryProps) {
|
|
33
|
+
return createElement(Fragment, null, children)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function VideoPluginComposer({
|
|
37
|
+
timeline,
|
|
38
|
+
}: {
|
|
39
|
+
timeline: ResolvedVideoTimeline
|
|
40
|
+
}) {
|
|
41
|
+
return (
|
|
42
|
+
<TransitionSeries>
|
|
43
|
+
{timeline.scenes.map((scene) => (
|
|
44
|
+
<TransitionSeries.Sequence
|
|
45
|
+
key={scene.id}
|
|
46
|
+
durationInFrames={scene.durationInFrames}
|
|
47
|
+
premountFor={Math.min(30, scene.durationInFrames)}
|
|
48
|
+
>
|
|
49
|
+
{scene.plugin.render(scene.props)}
|
|
50
|
+
</TransitionSeries.Sequence>
|
|
51
|
+
))}
|
|
52
|
+
</TransitionSeries>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -1,52 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type BaseRemotionSceneProps,
|
|
4
|
-
useRemotionSceneRuntime,
|
|
5
|
-
} from '../../../base'
|
|
6
|
-
import {
|
|
7
|
-
VideoComponent,
|
|
8
|
-
defineComponentProjectComponentMetadata,
|
|
9
|
-
} from '@vibecuting/component-project-helper'
|
|
1
|
+
import { AbsoluteFill } from 'remotion'
|
|
2
|
+
import { z } from 'zod'
|
|
10
3
|
|
|
11
|
-
|
|
4
|
+
import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
|
|
5
|
+
|
|
6
|
+
import { defineSceneComponent } from '../../../plugins/scene-component'
|
|
7
|
+
import { useRemotionSceneRuntime } from '../../../base'
|
|
8
|
+
import { useSceneRootProps } from '../../../runtime/use-scene-root-props'
|
|
9
|
+
|
|
10
|
+
export interface Default2DSceneProps {
|
|
11
|
+
sceneId?: string
|
|
12
|
+
sceneName?: string
|
|
12
13
|
title: string
|
|
13
14
|
subtitle?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const componentMetadata =
|
|
17
|
+
export const componentMetadata = defineScenePluginMetadata({
|
|
18
|
+
resourceKind: 'scene',
|
|
17
19
|
name: 'Default2DScene',
|
|
18
20
|
description: 'Default remotion 2D scene',
|
|
19
21
|
sourceFile: 'src/core/scene/2dscene/default-2d-scene.tsx',
|
|
22
|
+
pluginKey: 'core.canvas-2d',
|
|
23
|
+
tags: ['scene', '2d'],
|
|
20
24
|
aspectRatio: '16:9',
|
|
21
25
|
sceneType: 'scene',
|
|
22
|
-
|
|
26
|
+
sceneFamily: 'canvas-2d',
|
|
27
|
+
rootLayout: 'absolute-fill',
|
|
23
28
|
propsTypeName: 'Default2DSceneProps',
|
|
24
29
|
})
|
|
25
30
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
}
|
|
31
|
+
export const Default2DScene = VideoComponent(componentMetadata)(
|
|
32
|
+
defineSceneComponent({
|
|
33
|
+
family: 'canvas-2d',
|
|
34
|
+
propsSchema: z.object({
|
|
35
|
+
sceneId: z.string().optional(),
|
|
36
|
+
sceneName: z.string().optional(),
|
|
37
|
+
title: z.string().min(1),
|
|
38
|
+
subtitle: z.string().optional(),
|
|
39
|
+
}),
|
|
40
|
+
component: function Default2DScene({
|
|
41
|
+
sceneId,
|
|
42
|
+
sceneName,
|
|
43
|
+
title,
|
|
44
|
+
subtitle,
|
|
45
|
+
}: Default2DSceneProps) {
|
|
46
|
+
const runtime = useRemotionSceneRuntime()
|
|
47
|
+
const rootProps = useSceneRootProps({
|
|
48
|
+
pluginKey: componentMetadata.pluginKey,
|
|
49
|
+
sceneId,
|
|
50
|
+
sceneName: sceneName ?? title,
|
|
51
|
+
})
|
|
52
|
+
const titleFontSize = Math.round(runtime.height * 0.062)
|
|
53
|
+
const subtitleFontSize = Math.round(runtime.height * 0.024)
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
return (
|
|
56
|
+
<AbsoluteFill
|
|
57
|
+
{...rootProps}
|
|
58
|
+
style={{
|
|
59
|
+
...rootProps.style,
|
|
60
|
+
backgroundColor: '#111827',
|
|
61
|
+
color: '#f8fafc',
|
|
62
|
+
display: 'flex',
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
justifyContent: 'flex-start',
|
|
65
|
+
padding: Math.round(runtime.height * 0.06),
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<div style={{ width: '100%', maxWidth: Math.round(runtime.width * 0.72) }}>
|
|
69
|
+
<h1 style={{ margin: 0, fontSize: titleFontSize, lineHeight: 1.05 }}>{title}</h1>
|
|
70
|
+
{subtitle ? <p style={{ margin: '16px 0 0', fontSize: subtitleFontSize }}>{subtitle}</p> : null}
|
|
71
|
+
</div>
|
|
72
|
+
</AbsoluteFill>
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
@@ -1,54 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type BaseRemotionSceneProps,
|
|
4
|
-
useRemotionSceneRuntime,
|
|
5
|
-
} from '../../../base'
|
|
6
|
-
import {
|
|
7
|
-
VideoComponent,
|
|
8
|
-
defineComponentProjectComponentMetadata,
|
|
9
|
-
} from '@vibecuting/component-project-helper'
|
|
1
|
+
import { AbsoluteFill } from 'remotion'
|
|
2
|
+
import { z } from 'zod'
|
|
10
3
|
|
|
11
|
-
|
|
4
|
+
import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
|
|
5
|
+
|
|
6
|
+
import { defineSceneComponent } from '../../../plugins/scene-component'
|
|
7
|
+
import { useRemotionSceneRuntime } from '../../../base'
|
|
8
|
+
import { useSceneRootProps } from '../../../runtime/use-scene-root-props'
|
|
9
|
+
|
|
10
|
+
export interface Default3DSceneProps {
|
|
11
|
+
sceneId?: string
|
|
12
|
+
sceneName?: string
|
|
12
13
|
title: string
|
|
13
14
|
depth?: number
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const componentMetadata =
|
|
17
|
+
export const componentMetadata = defineScenePluginMetadata({
|
|
18
|
+
resourceKind: 'scene',
|
|
17
19
|
name: 'Default3DScene',
|
|
18
20
|
description: 'Default remotion 3D scene',
|
|
19
21
|
sourceFile: 'src/core/scene/3dscene/default-3d-scene.tsx',
|
|
22
|
+
pluginKey: 'core.three-3d',
|
|
23
|
+
tags: ['scene', '3d'],
|
|
20
24
|
aspectRatio: '16:9',
|
|
21
25
|
sceneType: 'scene',
|
|
22
|
-
|
|
26
|
+
sceneFamily: 'three-3d',
|
|
27
|
+
rootLayout: 'absolute-fill',
|
|
23
28
|
propsTypeName: 'Default3DSceneProps',
|
|
24
29
|
})
|
|
25
30
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
</p>
|
|
49
|
-
</div>
|
|
50
|
-
</BaseRemotionScene>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
31
|
+
export const Default3DScene = VideoComponent(componentMetadata)(
|
|
32
|
+
defineSceneComponent({
|
|
33
|
+
family: 'three-3d',
|
|
34
|
+
propsSchema: z.object({
|
|
35
|
+
sceneId: z.string().optional(),
|
|
36
|
+
sceneName: z.string().optional(),
|
|
37
|
+
title: z.string().min(1),
|
|
38
|
+
depth: z.number().optional(),
|
|
39
|
+
}),
|
|
40
|
+
component: function Default3DScene({
|
|
41
|
+
sceneId,
|
|
42
|
+
sceneName,
|
|
43
|
+
title,
|
|
44
|
+
depth = 240,
|
|
45
|
+
}: Default3DSceneProps) {
|
|
46
|
+
const runtime = useRemotionSceneRuntime()
|
|
47
|
+
const rootProps = useSceneRootProps({
|
|
48
|
+
pluginKey: componentMetadata.pluginKey,
|
|
49
|
+
sceneId,
|
|
50
|
+
sceneName: sceneName ?? title,
|
|
51
|
+
})
|
|
52
|
+
const titleFontSize = Math.round(runtime.height * 0.062)
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
return (
|
|
55
|
+
<AbsoluteFill
|
|
56
|
+
{...rootProps}
|
|
57
|
+
style={{
|
|
58
|
+
...rootProps.style,
|
|
59
|
+
backgroundColor: '#020617',
|
|
60
|
+
color: '#e2e8f0',
|
|
61
|
+
display: 'flex',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
justifyContent: 'flex-start',
|
|
64
|
+
padding: Math.round(runtime.height * 0.06),
|
|
65
|
+
perspective: 1200,
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<div style={{ transform: `translateZ(${depth / 8}px) rotateY(-18deg)` }}>
|
|
69
|
+
<h1 style={{ margin: 0, fontSize: titleFontSize, lineHeight: 1.05 }}>{title}</h1>
|
|
70
|
+
<p style={{ margin: '16px 0 0', fontSize: Math.round(runtime.height * 0.024), opacity: 0.8 }}>
|
|
71
|
+
Depth {depth}
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
</AbsoluteFill>
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
@@ -1,51 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type BaseRemotionSceneProps,
|
|
4
|
-
useRemotionSceneRuntime,
|
|
5
|
-
} from '../../../base'
|
|
6
|
-
import {
|
|
7
|
-
VideoComponent,
|
|
8
|
-
defineComponentProjectComponentMetadata,
|
|
9
|
-
} from '@vibecuting/component-project-helper'
|
|
1
|
+
import { AbsoluteFill } from 'remotion'
|
|
2
|
+
import { z } from 'zod'
|
|
10
3
|
|
|
11
|
-
|
|
4
|
+
import { VideoComponent, defineScenePluginMetadata } from '@vibecuting/component-project-helper'
|
|
5
|
+
|
|
6
|
+
import { defineSceneComponent } from '../../../plugins/scene-component'
|
|
7
|
+
import { useRemotionSceneRuntime } from '../../../base'
|
|
8
|
+
import { useSceneRootProps } from '../../../runtime/use-scene-root-props'
|
|
9
|
+
|
|
10
|
+
export interface DefaultSlideSceneProps {
|
|
11
|
+
sceneId?: string
|
|
12
|
+
sceneName?: string
|
|
12
13
|
title: string
|
|
13
14
|
body?: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const componentMetadata =
|
|
17
|
+
export const componentMetadata = defineScenePluginMetadata({
|
|
18
|
+
resourceKind: 'scene',
|
|
17
19
|
name: 'DefaultSlideScene',
|
|
18
20
|
description: 'Default remotion slide scene',
|
|
19
21
|
sourceFile: 'src/core/scene/slide/default-slide-scene.tsx',
|
|
22
|
+
pluginKey: 'core.slide',
|
|
23
|
+
tags: ['scene', 'slide'],
|
|
20
24
|
aspectRatio: '16:9',
|
|
21
25
|
sceneType: 'scene',
|
|
22
|
-
|
|
26
|
+
sceneFamily: 'slide',
|
|
27
|
+
rootLayout: 'absolute-fill',
|
|
23
28
|
propsTypeName: 'DefaultSlideSceneProps',
|
|
24
29
|
})
|
|
25
30
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
49
|
-
}
|
|
31
|
+
export const DefaultSlideScene = VideoComponent(componentMetadata)(
|
|
32
|
+
defineSceneComponent({
|
|
33
|
+
family: 'slide',
|
|
34
|
+
propsSchema: z.object({
|
|
35
|
+
sceneId: z.string().optional(),
|
|
36
|
+
sceneName: z.string().optional(),
|
|
37
|
+
title: z.string().min(1),
|
|
38
|
+
body: z.string().optional(),
|
|
39
|
+
}),
|
|
40
|
+
component: function DefaultSlideScene({
|
|
41
|
+
sceneId,
|
|
42
|
+
sceneName,
|
|
43
|
+
title,
|
|
44
|
+
body,
|
|
45
|
+
}: DefaultSlideSceneProps) {
|
|
46
|
+
const runtime = useRemotionSceneRuntime()
|
|
47
|
+
const rootProps = useSceneRootProps({
|
|
48
|
+
pluginKey: componentMetadata.pluginKey,
|
|
49
|
+
sceneId,
|
|
50
|
+
sceneName: sceneName ?? title,
|
|
51
|
+
})
|
|
52
|
+
const titleFontSize = Math.round(runtime.height * 0.062)
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
return (
|
|
55
|
+
<AbsoluteFill
|
|
56
|
+
{...rootProps}
|
|
57
|
+
style={{
|
|
58
|
+
...rootProps.style,
|
|
59
|
+
backgroundColor: '#f8fafc',
|
|
60
|
+
color: '#0f172a',
|
|
61
|
+
display: 'flex',
|
|
62
|
+
alignItems: 'flex-start',
|
|
63
|
+
justifyContent: 'flex-start',
|
|
64
|
+
padding: Math.round(runtime.height * 0.06),
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<aside style={{ maxWidth: Math.round(runtime.width * 0.72) }}>
|
|
68
|
+
<h1 style={{ margin: 0, fontSize: titleFontSize, lineHeight: 1.05 }}>{title}</h1>
|
|
69
|
+
{body ? <p style={{ margin: '16px 0 0', fontSize: Math.round(runtime.height * 0.024) }}>{body}</p> : null}
|
|
70
|
+
</aside>
|
|
71
|
+
</AbsoluteFill>
|
|
72
|
+
)
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
)
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
1
|
+
export * from './base'
|
|
2
|
+
export * from './core'
|
|
3
|
+
export * from './composer'
|
|
4
|
+
export * from './layouts'
|
|
5
|
+
export * from './plugins/scene-component'
|
|
6
|
+
export * from './plugins/scene-plugin-registry'
|
|
7
|
+
export * from './scenes'
|
|
8
|
+
export * from './themes'
|
|
9
|
+
export * from './transitions'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export type SceneSlotsProps = {
|
|
4
|
+
header?: ReactNode
|
|
5
|
+
body?: ReactNode
|
|
6
|
+
footer?: ReactNode
|
|
7
|
+
debug?: ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function SceneSlots({ header, body, footer, debug }: SceneSlotsProps) {
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
{header}
|
|
14
|
+
{body}
|
|
15
|
+
{footer}
|
|
16
|
+
{debug}
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SceneSlots'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement } from 'react'
|
|
2
|
+
import { createElement } from 'react'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
VideoComponent,
|
|
7
|
+
defineScenePluginMetadata,
|
|
8
|
+
getScenePluginMetadata,
|
|
9
|
+
type ScenePluginMetadata,
|
|
10
|
+
} from '@vibecuting/component-project-helper'
|
|
11
|
+
|
|
12
|
+
import type { DefaultSceneProps } from '../scenes/default-scene-props'
|
|
13
|
+
|
|
14
|
+
export type SceneFamily = 'slide' | 'canvas-2d' | 'game-2d' | 'three-3d' | 'custom'
|
|
15
|
+
|
|
16
|
+
export type SceneComponentDefinition<TProps> = {
|
|
17
|
+
family: SceneFamily
|
|
18
|
+
propsSchema: z.ZodType<TProps>
|
|
19
|
+
component: ComponentType<TProps>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SCENE_COMPONENT_DEFINITION_KEY = Symbol.for(
|
|
23
|
+
'@vibecuting/video-project-core/scene-component-definition',
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
export type RegisteredSceneComponent<TProps extends DefaultSceneProps> = ComponentType<TProps> & {
|
|
27
|
+
readonly [SCENE_COMPONENT_DEFINITION_KEY]: SceneComponentDefinition<TProps>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defineSceneComponent<TProps extends DefaultSceneProps>(
|
|
31
|
+
definition: SceneComponentDefinition<TProps>,
|
|
32
|
+
): RegisteredSceneComponent<TProps> {
|
|
33
|
+
const component = definition.component as RegisteredSceneComponent<TProps>
|
|
34
|
+
Object.defineProperty(component, SCENE_COMPONENT_DEFINITION_KEY, {
|
|
35
|
+
value: definition,
|
|
36
|
+
enumerable: false,
|
|
37
|
+
})
|
|
38
|
+
return component
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ScenePluginRegistryEntry<TProps extends DefaultSceneProps> = {
|
|
42
|
+
key: string
|
|
43
|
+
family: SceneFamily
|
|
44
|
+
metadata: ScenePluginMetadata
|
|
45
|
+
component: RegisteredSceneComponent<TProps>
|
|
46
|
+
parseProps(input: unknown): TProps
|
|
47
|
+
render(input: unknown): ReactElement
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createScenePluginRegistry<TProps extends DefaultSceneProps>(
|
|
51
|
+
plugins: readonly RegisteredSceneComponent<TProps>[],
|
|
52
|
+
) {
|
|
53
|
+
const entries = new Map<string, ScenePluginRegistryEntry<TProps>>()
|
|
54
|
+
|
|
55
|
+
for (const component of plugins) {
|
|
56
|
+
const metadata = getScenePluginMetadata(component)
|
|
57
|
+
if (!metadata) {
|
|
58
|
+
throw new Error(`scene plugin is missing VideoComponent annotation: ${component.name}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const definition = component[SCENE_COMPONENT_DEFINITION_KEY]
|
|
62
|
+
if (!definition) {
|
|
63
|
+
throw new Error(`scene plugin is missing scene definition: ${component.name}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (definition.family !== metadata.sceneFamily) {
|
|
67
|
+
throw new Error(`scene family mismatch: ${metadata.pluginKey}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (entries.has(metadata.pluginKey)) {
|
|
71
|
+
throw new Error(`duplicate scene plugin key: ${metadata.pluginKey}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
entries.set(metadata.pluginKey, {
|
|
75
|
+
key: metadata.pluginKey,
|
|
76
|
+
family: definition.family,
|
|
77
|
+
metadata,
|
|
78
|
+
component,
|
|
79
|
+
parseProps(input) {
|
|
80
|
+
return definition.propsSchema.parse(input) as TProps
|
|
81
|
+
},
|
|
82
|
+
render(input) {
|
|
83
|
+
return createElement(component, definition.propsSchema.parse(input) as TProps)
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
get(key: string) {
|
|
90
|
+
const entry = entries.get(key)
|
|
91
|
+
if (!entry) {
|
|
92
|
+
throw new Error(`unknown scene plugin: ${key}`)
|
|
93
|
+
}
|
|
94
|
+
return entry
|
|
95
|
+
},
|
|
96
|
+
list() {
|
|
97
|
+
return [...entries.values()]
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function annotateSceneComponent<TProps extends DefaultSceneProps>(
|
|
103
|
+
metadata: ScenePluginMetadata,
|
|
104
|
+
component: ComponentType<TProps>,
|
|
105
|
+
) {
|
|
106
|
+
return VideoComponent(metadata)(component)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function defineScenePluginMetadataForComponent(metadata: ScenePluginMetadata) {
|
|
110
|
+
return defineScenePluginMetadata(metadata)
|
|
111
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createScenePluginRegistry,
|
|
3
|
+
defineSceneComponent,
|
|
4
|
+
annotateSceneComponent,
|
|
5
|
+
defineScenePluginMetadataForComponent as defineScenePluginMetadata,
|
|
6
|
+
SCENE_COMPONENT_DEFINITION_KEY,
|
|
7
|
+
type RegisteredSceneComponent,
|
|
8
|
+
type SceneComponentDefinition,
|
|
9
|
+
type SceneFamily,
|
|
10
|
+
type ScenePluginRegistryEntry,
|
|
11
|
+
} from './scene-component'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react'
|
|
2
|
+
import { useId } from 'react'
|
|
3
|
+
|
|
4
|
+
import type { SceneTheme, SceneThemeStyle } from '../themes'
|
|
5
|
+
|
|
6
|
+
export type SceneRootOptions = {
|
|
7
|
+
pluginKey: string
|
|
8
|
+
sceneId?: string
|
|
9
|
+
sceneName?: string
|
|
10
|
+
theme?: SceneTheme
|
|
11
|
+
className?: string
|
|
12
|
+
style?: SceneThemeStyle
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useSceneRootProps(options: SceneRootOptions): {
|
|
16
|
+
id: string
|
|
17
|
+
'data-scene-plugin': string
|
|
18
|
+
'data-scene-name'?: string
|
|
19
|
+
className: string
|
|
20
|
+
style: CSSProperties
|
|
21
|
+
} {
|
|
22
|
+
const reactId = useId()
|
|
23
|
+
const id = options.sceneId ?? `${options.pluginKey}-${reactId.replace(/:/g, '')}`
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
'data-scene-plugin': options.pluginKey,
|
|
28
|
+
'data-scene-name': options.sceneName,
|
|
29
|
+
className: [
|
|
30
|
+
'box-border flex h-full w-full flex-col overflow-hidden',
|
|
31
|
+
'bg-[var(--scene-bg)] text-[var(--scene-fg)]',
|
|
32
|
+
'px-[var(--scene-page-x)] py-[var(--scene-page-y)]',
|
|
33
|
+
'gap-[var(--scene-section-gap)]',
|
|
34
|
+
options.className ?? '',
|
|
35
|
+
]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(' '),
|
|
38
|
+
style: {
|
|
39
|
+
...(options.theme?.variables ?? {}),
|
|
40
|
+
...(options.style ?? {}),
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SceneTheme } from '../themes'
|
|
2
|
+
|
|
3
|
+
export type SceneItem = {
|
|
4
|
+
title: string
|
|
5
|
+
description?: string
|
|
6
|
+
value?: string
|
|
7
|
+
imageSrc?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type SceneMedia = {
|
|
11
|
+
type: 'image' | 'video'
|
|
12
|
+
src: string
|
|
13
|
+
alt?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DefaultSceneProps = {
|
|
17
|
+
sceneId?: string
|
|
18
|
+
sceneName?: string
|
|
19
|
+
title?: string
|
|
20
|
+
subtitle?: string
|
|
21
|
+
description?: string
|
|
22
|
+
eyebrow?: string
|
|
23
|
+
items?: SceneItem[]
|
|
24
|
+
media?: SceneMedia
|
|
25
|
+
accent?: string
|
|
26
|
+
theme?: SceneTheme
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './default-scene-props'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getThemePluginMetadata, type ThemePluginMetadata } from '@vibecuting/component-project-helper'
|
|
2
|
+
|
|
3
|
+
import type { SceneTheme } from './scene-theme'
|
|
4
|
+
|
|
5
|
+
export type SceneThemeRegistryEntry = SceneTheme & {
|
|
6
|
+
metadata: ThemePluginMetadata
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type SceneThemeRegistry = {
|
|
10
|
+
get(key: string): SceneThemeRegistryEntry
|
|
11
|
+
list(): SceneThemeRegistryEntry[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createSceneThemeRegistry(themes: readonly SceneTheme[]): SceneThemeRegistry {
|
|
15
|
+
const entries = new Map<string, SceneThemeRegistryEntry>()
|
|
16
|
+
|
|
17
|
+
for (const theme of themes) {
|
|
18
|
+
const metadata = getThemePluginMetadata(theme)
|
|
19
|
+
if (!metadata) {
|
|
20
|
+
throw new Error(`theme is missing ThemeComponent annotation: ${theme.key}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (theme.key !== metadata.pluginKey) {
|
|
24
|
+
throw new Error(`theme key mismatch: ${theme.key}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (entries.has(theme.key)) {
|
|
28
|
+
throw new Error(`duplicate scene theme key: ${theme.key}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const declaredVariables = [...Object.keys(theme.variables)].sort()
|
|
32
|
+
const metadataVariables = [...metadata.cssVariables].sort()
|
|
33
|
+
|
|
34
|
+
if (declaredVariables.join(',') !== metadataVariables.join(',')) {
|
|
35
|
+
throw new Error(`theme cssVariables mismatch: ${theme.key}`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
entries.set(theme.key, {
|
|
39
|
+
...theme,
|
|
40
|
+
metadata,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
get(key: string) {
|
|
46
|
+
const entry = entries.get(key)
|
|
47
|
+
if (!entry) {
|
|
48
|
+
throw new Error(`unknown scene theme: ${key}`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return entry
|
|
52
|
+
},
|
|
53
|
+
list() {
|
|
54
|
+
return [...entries.values()]
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ThemeComponent,
|
|
5
|
+
defineThemePluginMetadata,
|
|
6
|
+
type ThemePluginMetadata,
|
|
7
|
+
} from '@vibecuting/component-project-helper'
|
|
8
|
+
|
|
9
|
+
export type SceneThemeVariables = {
|
|
10
|
+
'--scene-bg': string
|
|
11
|
+
'--scene-fg': string
|
|
12
|
+
'--scene-muted': string
|
|
13
|
+
'--scene-accent': string
|
|
14
|
+
'--scene-panel': string
|
|
15
|
+
'--scene-border': string
|
|
16
|
+
'--scene-page-x': string
|
|
17
|
+
'--scene-page-y': string
|
|
18
|
+
'--scene-content-max': string
|
|
19
|
+
'--scene-radius': string
|
|
20
|
+
'--scene-section-gap': string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type SceneTheme = {
|
|
24
|
+
key: string
|
|
25
|
+
name: string
|
|
26
|
+
variables: SceneThemeVariables
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SceneThemeStyle = CSSProperties & Partial<SceneThemeVariables>
|
|
30
|
+
|
|
31
|
+
export function defineSceneTheme(theme: SceneTheme): SceneTheme {
|
|
32
|
+
return theme
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function annotateSceneTheme(theme: SceneTheme, metadata: ThemePluginMetadata) {
|
|
36
|
+
return ThemeComponent(metadata)(theme)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function defineSceneThemeMetadata(input: ThemePluginMetadata): ThemePluginMetadata {
|
|
40
|
+
return defineThemePluginMetadata(input)
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ThemeComponent, defineThemePluginMetadata } from '@vibecuting/component-project-helper'
|
|
2
|
+
|
|
3
|
+
import { defineSceneTheme } from './scene-theme'
|
|
4
|
+
|
|
5
|
+
export const editorialDarkTheme = ThemeComponent(
|
|
6
|
+
defineThemePluginMetadata({
|
|
7
|
+
resourceKind: 'theme',
|
|
8
|
+
name: 'Editorial Dark',
|
|
9
|
+
description: 'Default dark theme for slide scenes',
|
|
10
|
+
sourceFile: 'src/themes/scene-themes.ts',
|
|
11
|
+
pluginKey: 'core.editorial-dark',
|
|
12
|
+
supportedSceneFamilies: ['slide', 'custom'],
|
|
13
|
+
cssVariables: [
|
|
14
|
+
'--scene-bg',
|
|
15
|
+
'--scene-fg',
|
|
16
|
+
'--scene-muted',
|
|
17
|
+
'--scene-accent',
|
|
18
|
+
'--scene-panel',
|
|
19
|
+
'--scene-border',
|
|
20
|
+
'--scene-page-x',
|
|
21
|
+
'--scene-page-y',
|
|
22
|
+
'--scene-content-max',
|
|
23
|
+
'--scene-radius',
|
|
24
|
+
'--scene-section-gap',
|
|
25
|
+
],
|
|
26
|
+
tags: ['builtin', 'theme'],
|
|
27
|
+
}),
|
|
28
|
+
)(defineSceneTheme({
|
|
29
|
+
key: 'core.editorial-dark',
|
|
30
|
+
name: 'Editorial Dark',
|
|
31
|
+
variables: {
|
|
32
|
+
'--scene-bg': '#020617',
|
|
33
|
+
'--scene-fg': '#f8fafc',
|
|
34
|
+
'--scene-muted': '#94a3b8',
|
|
35
|
+
'--scene-accent': '#38bdf8',
|
|
36
|
+
'--scene-panel': '#0f172a',
|
|
37
|
+
'--scene-border': '#1e293b',
|
|
38
|
+
'--scene-page-x': '64px',
|
|
39
|
+
'--scene-page-y': '56px',
|
|
40
|
+
'--scene-content-max': '1200px',
|
|
41
|
+
'--scene-radius': '24px',
|
|
42
|
+
'--scene-section-gap': '24px',
|
|
43
|
+
},
|
|
44
|
+
}))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export {
|
|
2
|
+
annotateTransitionPlugin,
|
|
3
|
+
createTransitionPluginRegistry,
|
|
4
|
+
defineTransitionPlugin,
|
|
5
|
+
defineTransitionPluginMetadataForPlugin as defineTransitionPluginMetadata,
|
|
6
|
+
type TransitionPlugin,
|
|
7
|
+
type TransitionPluginDefinition,
|
|
8
|
+
type TransitionPluginKind,
|
|
9
|
+
type TransitionPluginRegistryEntry,
|
|
10
|
+
} from './transition-plugin'
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ReactElement } from 'react'
|
|
2
|
+
import { createElement } from 'react'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
TransitionComponent,
|
|
7
|
+
defineTransitionPluginMetadata,
|
|
8
|
+
getTransitionPluginMetadata,
|
|
9
|
+
type TransitionPluginMetadata,
|
|
10
|
+
} from '@vibecuting/component-project-helper'
|
|
11
|
+
|
|
12
|
+
export type TransitionPluginKind = 'transition' | 'overlay'
|
|
13
|
+
|
|
14
|
+
export type TransitionPluginDefinition<TProps> = {
|
|
15
|
+
key: string
|
|
16
|
+
kind: TransitionPluginKind
|
|
17
|
+
metadata: TransitionPluginMetadata
|
|
18
|
+
propsSchema: z.ZodType<TProps>
|
|
19
|
+
renderBoundary: (props: TProps) => ReactElement
|
|
20
|
+
getBoundaryDurationInFrames: (props: TProps, fps: number) => number
|
|
21
|
+
getTimelineOverlapInFrames: (props: TProps, fps: number) => number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type TransitionPlugin<TProps> = TransitionPluginDefinition<TProps> & {
|
|
25
|
+
parseProps(input: unknown): TProps
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function defineTransitionPlugin<TProps>(
|
|
29
|
+
definition: TransitionPluginDefinition<TProps>,
|
|
30
|
+
): TransitionPlugin<TProps> {
|
|
31
|
+
return {
|
|
32
|
+
...definition,
|
|
33
|
+
parseProps(input) {
|
|
34
|
+
return definition.propsSchema.parse(input) as TProps
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type TransitionPluginRegistryEntry<TProps> = TransitionPlugin<TProps>
|
|
40
|
+
|
|
41
|
+
export function createTransitionPluginRegistry<TProps>(
|
|
42
|
+
plugins: readonly TransitionPlugin<TProps>[],
|
|
43
|
+
) {
|
|
44
|
+
const entries = new Map<string, TransitionPlugin<TProps>>()
|
|
45
|
+
|
|
46
|
+
for (const plugin of plugins) {
|
|
47
|
+
const metadata = getTransitionPluginMetadata(plugin)
|
|
48
|
+
if (!metadata) {
|
|
49
|
+
throw new Error(`transition plugin is missing TransitionComponent annotation: ${plugin.key}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (plugin.key !== metadata.pluginKey) {
|
|
53
|
+
throw new Error(`transition plugin key mismatch: ${plugin.key}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (plugin.kind !== metadata.transitionKind) {
|
|
57
|
+
throw new Error(`transition plugin kind mismatch: ${plugin.key}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (entries.has(plugin.key)) {
|
|
61
|
+
throw new Error(`duplicate transition plugin key: ${plugin.key}`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
entries.set(plugin.key, plugin)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
get(key: string) {
|
|
69
|
+
const entry = entries.get(key)
|
|
70
|
+
if (!entry) {
|
|
71
|
+
throw new Error(`unknown transition plugin: ${key}`)
|
|
72
|
+
}
|
|
73
|
+
return entry
|
|
74
|
+
},
|
|
75
|
+
list() {
|
|
76
|
+
return [...entries.values()]
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function annotateTransitionPlugin<TProps>(
|
|
82
|
+
metadata: TransitionPluginMetadata,
|
|
83
|
+
plugin: TransitionPlugin<TProps>,
|
|
84
|
+
) {
|
|
85
|
+
return TransitionComponent(metadata)(plugin)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function defineTransitionPluginMetadataForPlugin(metadata: TransitionPluginMetadata) {
|
|
89
|
+
return defineTransitionPluginMetadata(metadata)
|
|
90
|
+
}
|