Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0531566
chore: add new directory for PsdCharacter
aqiq-marine Mar 7, 2026
a226a1f
chore: add index.ts
aqiq-marine Mar 7, 2026
3a32d9b
feature: add ast
aqiq-marine Mar 7, 2026
d89cce5
feature: add psd character component
aqiq-marine Mar 7, 2026
97b4747
fix: export components
aqiq-marine Mar 7, 2026
ed7ec45
fix: fix DeclareVariable
aqiq-marine Mar 7, 2026
e5b9068
feature: add parser
aqiq-marine Mar 8, 2026
519773c
fix: fix fetch url
aqiq-marine Mar 8, 2026
7dabdd9
feature: add runtime
aqiq-marine Mar 8, 2026
186f19a
feature: add voice volume tune
aqiq-marine Mar 8, 2026
07926ce
feature: fix voice volume
aqiq-marine Mar 8, 2026
8653b8f
fix: fix register cache
aqiq-marine Mar 8, 2026
e4cd0fd
feature: add blink
aqiq-marine Mar 8, 2026
8dc4c22
fix: performance tune
aqiq-marine Mar 8, 2026
d658b9d
fix: animation error
aqiq-marine Mar 8, 2026
62f2452
fix: add package for psd
aqiq-marine Mar 8, 2026
88bf1e0
add: add comment
aqiq-marine Mar 8, 2026
0a0c5c3
chore: delete duplicated package
aqiq-marine Mar 8, 2026
2eb640b
fix: duplicated register
aqiq-marine Mar 10, 2026
fcbae2a
fix: add className
aqiq-marine Mar 12, 2026
a2358da
fix: add sound props
aqiq-marine Mar 13, 2026
2236022
init character manager branch
aqiq-marine Mar 14, 2026
f2143a5
feature: add ast
aqiq-marine Mar 14, 2026
3ee8456
feature: add character-manager-component
aqiq-marine Mar 14, 2026
139c413
feature: add dialogSenario
aqiq-marine Mar 14, 2026
94a14bc
fix: type validate
aqiq-marine Mar 14, 2026
43392a8
fix: add className
aqiq-marine Mar 14, 2026
cdec21e
feature: non-speaker component in chapter
aqiq-marine Mar 15, 2026
5642b44
fix: render first frame
aqiq-marine Mar 15, 2026
51aa617
fix: export manager component
aqiq-marine Mar 15, 2026
d7df7a3
fix: lint
aqiq-marine Mar 15, 2026
0286cf5
feature: add option for merge implicit characters
aqiq-marine Mar 15, 2026
77bf9ad
fix: type DeclareVariable
aqiq-marine Mar 20, 2026
bf11c88
feature: add declareVariables
aqiq-marine Mar 20, 2026
51c0f66
fix: type AnimationContext
aqiq-marine Mar 20, 2026
3bb8604
fix: delete debug log
aqiq-marine Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
372 changes: 240 additions & 132 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@ffprobe-installer/ffprobe": "^2.1.2",
"@types/opentype.js": "^1.3.4",
"ag-psd": "^30.1.0",
"ag-psd-psdtool": "^1.1.10",
"mathjax-full": "^3.2.1",
"opentype.js": "^1.3.4",
"prismjs": "^1.30.0",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ type MoveController<T> = {
to: (value: T, durationFrames: number, easing?: Easing) => AnimationHandle
}

type AnimationContext = {
export type AnimationContext = {
sleep: (frames: number) => AnimationHandle
waitUntil: (frame: number) => AnimationHandle
waitUntilClip: (label: string) => AnimationHandle
Expand Down
55 changes: 55 additions & 0 deletions src/lib/character/character-manager/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { ReactNode } from "react"

export const CharacterManagerElement = {
CharacterManager: "CharacterManager",
DeclareCharacters: "DeclareCharacters",
Senario: "Senario",
DeclareCharacter: "DeclareCharacter",
Chapter: "Chapter",
Speaker: "Speaker",
} as const

export type ChapterChild =
| { kind: "speaker", node: SpeakerNode }
| { kind: "other", node: ReactNode }


/* =========================
Nodes
========================= */

export interface CharacterManagerNode {
type: typeof CharacterManagerElement.CharacterManager
characters: DeclareCharactersNode
senario: SenarioNode
}

export interface DeclareCharactersNode {
type: typeof CharacterManagerElement.DeclareCharacters
children: DeclareCharacterNode[]
}

export interface SenarioNode {
type: typeof CharacterManagerElement.Senario
children: ChapterNode[]
}

export interface DeclareCharacterNode {
type: typeof CharacterManagerElement.DeclareCharacter
className?: string
name: string
psd: string
children: ReactNode
}

export interface ChapterNode {
type: typeof CharacterManagerElement.Chapter
children: ChapterChild[]
}

export interface SpeakerNode {
type: typeof CharacterManagerElement.Speaker
className?: string
name: string
children: ReactNode
}
31 changes: 31 additions & 0 deletions src/lib/character/character-manager/character-manager-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ReactElement } from "react"
import { defineDSL } from "../utils/defineDSL"
import { CharacterManagerElement } from "./ast"
import type { OneOrMany } from "../utils/util-types"

type ChildrenOf<T> = OneOrMany<ReactElement<T>>

export const DeclareCharacters = defineDSL<{
children: ChildrenOf<typeof DeclareCharacter>
}>(CharacterManagerElement.DeclareCharacters)

export const Senario = defineDSL<{
children?: ChildrenOf<typeof Chapter>
}>(CharacterManagerElement.Senario)

export const DeclareCharacter = defineDSL<{
className?: string
name: string
psd: string
children: React.ReactNode
}>(CharacterManagerElement.DeclareCharacter)

export const Chapter = defineDSL<{
children: React.ReactNode
}>(CharacterManagerElement.Chapter)

export const Speaker = defineDSL<{
className?: string
name: string
children: React.ReactNode
}>(CharacterManagerElement.Speaker)
75 changes: 75 additions & 0 deletions src/lib/character/character-manager/character-manager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { ReactElement, ReactNode } from "react"
import { parseCharacterManager } from "./parser"
import { PsdCharacter } from "../character-unit"
import { DeclareCharacters, Senario } from "./character-manager-component"
import { Clip, ClipSequence } from "../../clip"
import type { OneOrMany } from "../utils/util-types"

export type ImplicitCharacterPlacement = "front" | "back"

type DialogueSenarioProps = {
implicitPlacement?: ImplicitCharacterPlacement
children: OneOrMany<ReactElement<typeof DeclareCharacters> | ReactElement<typeof Senario>>
}

export const DialogueSenario = ({
implicitPlacement = "back",
children
}: DialogueSenarioProps) => {
const ast = parseCharacterManager(children)

const characters = new Map(ast.characters.children.map(character => {
return [
character.name,
{
psd: character.psd,
waitingState: <PsdCharacter
key={character.name}
className={character.className}
psd={character.psd}
>
{character.children}
</PsdCharacter>
}
]
}))

const senario = ast.senario.children.map(chapter => {
const explicitSpeakers = chapter.children.filter(child => child.kind == "speaker").map(s => s.node.name)
const implicitCharacters = Array.from(characters.entries()).filter(([key, _]) => !explicitSpeakers.includes(key))

const explicits = chapter.children.map(elm => {
if (elm.kind == "speaker") {
return (
<PsdCharacter key={elm.node.name} className={elm.node.className} psd={characters.get(elm.node.name)?.psd!}>
{elm.node.children}
</PsdCharacter>
)
} else {
return elm.node
}
})
const implicits = implicitCharacters.map(([_, character]) => character.waitingState)

const merged = mergeImplicitCharacters(implicitPlacement, explicits, implicits)

return <Clip> {merged} </Clip>
})

return (
<ClipSequence>
{senario}
</ClipSequence>
)
}

const mergeImplicitCharacters = (implicitPlacement: ImplicitCharacterPlacement, explicits: ReactNode[], implicits: ReactNode[]) => {
switch (implicitPlacement) {
case "front":
return [...explicits, ...implicits]
case "back":
return [...implicits, ...explicits]
default:
throw `unknown merge option: {implicitPlacement}`
}
}
2 changes: 2 additions & 0 deletions src/lib/character/character-manager/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./character-manager"
export * from "./character-manager-component"
146 changes: 146 additions & 0 deletions src/lib/character/character-manager/parser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { isValidElement, type ReactElement, type ReactNode } from "react"
import type { CharacterManagerNode, DeclareCharactersNode, SenarioNode, DeclareCharacterNode, ChapterNode, SpeakerNode, ChapterChild } from "./ast"
import { CharacterManagerElement as ManagerElm} from "./ast"


type AnyElement = ReactElement<any, any>


export const parseCharacterManager = (
children: ReactNode,
): CharacterManagerNode => {
const childrenArray = React.Children.toArray(children)
if (childrenArray.length != 2) {
throw "CharacterManager need DeclareCharacters and Senario."
}

if (!isValidElement(childrenArray[0]) || !isValidElement(childrenArray[1])) {
throw new Error(`Invalid Element in ${ManagerElm.CharacterManager}`)
}

const characters = parseDeclareCharacters(childrenArray[0])
const senario = parseSenario(childrenArray[1])

return {
type: ManagerElm.CharacterManager,
characters: characters,
senario: senario,
}
}

const parseDeclareCharacters = (
self: AnyElement
): DeclareCharactersNode => {
const { children } = self.props
const body = parseDeclareCharactersChildren(children)
return {
type: ManagerElm.DeclareCharacters,
children: body,
}
}

const parseDeclareCharactersChildren = (
children: ReactNode
): DeclareCharacterNode[] => {
return React.Children.map(children, (child) => {
if (!isValidElement(child)) return

const type = getDslType(child)
if (type == ManagerElm.DeclareCharacter) {
return parseDeclareCharacter(child)
} else {
throw `Invalid DSL type in ${ManagerElm.DeclareCharacters}: ${type}`
}
}) ?? []

}

const parseSenario = (
self: AnyElement
): SenarioNode => {
const { children } = self.props
const body = parseSenarioChildren(children)
return {
type: ManagerElm.Senario,
children: body,
}
}

const parseSenarioChildren = (
children: ReactNode
): ChapterNode[] => {
return React.Children.map(children, (child) => {
if (!isValidElement(child)) return

const type = getDslType(child)
if (type == ManagerElm.Chapter) {
return parseChapter(child)
} else {
throw `Invalid DSL type in ${ManagerElm.DeclareCharacters}: ${type}`
}
}) ?? []

}

const parseDeclareCharacter = (
self: AnyElement
): DeclareCharacterNode => {
const { name, psd, className, children } = self.props
return {
type: ManagerElm.DeclareCharacter,
name,
psd,
className,
children,
}
}

const parseChapter = (
self: AnyElement
): ChapterNode => {
const { children } = self.props
const body = parseChapterChildren(children)
return {
type: ManagerElm.Chapter,
children: body,
}
}

const parseChapterChildren = (
children: ReactNode
): ChapterChild[] => {
return React.Children.map(children, child => {
if (!isValidElement(child)) return

const type = getDslType(child)
if (type == ManagerElm.Speaker) {
return { kind: "speaker", node: parseSpeaker(child) }
} else {
return { kind: "other", node: child }
}
}) ?? []

}

const parseSpeaker = (
self: AnyElement
): SpeakerNode => {
const { className, name, children } = self.props
return {
type: ManagerElm.Speaker,
className,
name,
children,
}
}


const getDslType = (el: AnyElement): string | undefined => {
const type = el.type as any

if (type?.__dslType) {
return type.__dslType
}

return undefined
}
Loading