import { ANIMATIONS } from "../animation/Animations" ; import type { AnimationDeclaration } from "../animation/AnimationDeclaration" ; import type { Animationable } from "kittik-animation-basic" ; import { Canvas } from "terminal-canvas" ; import type { OrderDeclaration } from "./OrderDeclaration" ; import { SHAPES } from "../shape/Shapes" ; import type { ShapeDeclaration } from "../shape/ShapeDeclaration" ; import type { ShapeRenderable } from "kittik-shape-basic" ; import type { SlideDeclaration } from "./SlideDeclaration" ; export { AnimationBuilder } from "../animation/AnimationBuilder" ; export { AnimationDeclaration } from "../animation/AnimationDeclaration" ; export { AnimationType , AnimationObject , AnimationOptions , } from "../animation/Animations" ; export { OrderDeclaration } from "./OrderDeclaration" ; export { ShapeBuilder } from "../shape/ShapeBuilder" ; export { ShapeDeclaration } from "../shape/ShapeDeclaration" ; export { ShapeType , ShapeObject , ShapeOptions } from "../shape/Shapes" ; export { SlideBuilder } from "./SlideBuilder" ; export { SlideDeclaration } from "./SlideDeclaration" ; export class Slide { public canvas : Canvas = Canvas . create ( ) ; public name = `Untitled Slide #${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) }` ; public readonly shapes : Map < string , ShapeRenderable > = new Map < string , ShapeRenderable > ( ) ; public readonly animations : Map < string , Animationable > = new Map < string , Animationable > ( ) ; public readonly order : OrderDeclaration [ ] = [ ] ; public constructor ( declaration ? , canvas ? ) { if ( typeof canvas !== "undefined" ) { this . canvas = canvas ; } if ( typeof declaration ?. name !== "undefined" ) { this . name = declaration . name ; } if ( typeof declaration ?. shapes !== "undefined" ) { this . initShapes ( declaration . shapes ) ; } if ( typeof declaration ?. animations !== "undefined" ) { this . initAnimations ( declaration . animations ) ; } if ( typeof declaration ?. order !== "undefined" ) { this . order = declaration . order ; } } public static create ( declaration ? , canvas ? ) { return new this ( declaration , canvas ) ; } public static fromObject ( obj , canvas ? ) { return this . create ( obj , canvas ) ; } public static fromJSON ( json , canvas ? ) { return this . fromObject ( JSON . parse ( json ) , canvas ) ; } public addShape ( name , shape , toOverride = false ) { if ( this . shapes . has ( name ) && ! toOverride ) { throw new Error ( `You are trying to add shape with the name "${ name }" into the slide "${ this . name }". ` + `But this shape already exists in slide "${ this . name }".` ) ; } this . shapes . set ( name , shape ) ; } public addAnimation ( name , animation , toOverride = false ) { if ( this . animations . has ( name ) && ! toOverride ) { throw new Error ( `You are trying to add animation with the name "${ name }" into the slide "${ this . name }". ` + `But this animation already exists in slide "${ this . name }".` ) ; } this . animations . set ( name , animation ) ; } public addOrder ( shape , animations = [ ] ) { if ( this . order . some ( ( order ) => order . shape === shape ) ) { throw new Error ( `You already have specified an ordering for shape "${ shape }" in slide "${ this . name }". ` + "Adding another one with the same name does not make any sense. " + "Did you make a typo in shape name or forgot that you already added a shape to ordering?" ) ; } const unknownAnimations = animations . filter ( ( animation ) => ! this . animations . has ( animation ) ) ; if ( unknownAnimations . length > 0 ) { throw new Error ( `You have provided animations for the shape "${ shape }" in slide "${ this . name }". ` + `But, some of them could not be found in the slide "${ this . name }". ` + `These animations are: [${ unknownAnimations . join ( ", " ) }]. ` + `Please, check if the animations from the list are declared in slide "${ this . name }".` ) ; } this . order . push ( { animations , shape } ) ; } public async render ( ) { const { animations , shapes } = this ; const shapesToRender = [ ] ; const sequence = [ ] ; const onTick = ( ) : void => { this . renderShapes ( shapesToRender ) ; } ; animations . forEach ( ( animation ) => animation . on ( "tick" , onTick ) ) ; for ( const order of this . order ) { const shapeToRender = shapes . get ( order . shape ) ; if ( typeof shapeToRender === "undefined" ) { throw new Error ( `You specified shape "${ order . shape }" in slide "${ this . name }" ` + "as part of ordering, but it does not exist in shapes declaration for the slide. " + "Maybe you forgot to create a shape you want to order or it is a typo in ordering itself." ) ; } sequence . push ( ( ) => shapesToRender . push ( shapeToRender ) ) ; ( order . animations ?. map ( ( item ) => animations . get ( item ) ) ?? [ ] ) . forEach ( ( animation ) => sequence . push ( ( ) => animation ?. animate ( shapeToRender ) ) ) ; sequence . push ( ( ) => { this . renderShapes ( shapesToRender ) ; } ) ; } sequence . push ( ( ) => { animations . forEach ( ( animation ) => animation . removeListener ( "tick" , onTick ) ) ; } ) ; await sequence . reduce ( async ( promise , item ) => { await promise . then ( item ) ; } , Promise . resolve ( ) ) ; } public toObject ( ) { const { name , order } = this ; const shapes = [ ... this . shapes . entries ( ) ] . map ( ( [ shapeName , shape ] ) => ( { ... shape . toObject ( ) , name : shapeName } ) ) ; const animations = [ ... this . animations . entries ( ) ] . map ( ( [ animationName , animation ] ) => ( { ... animation . toObject ( ) , name : animationName } ) ) ; return { animations , name , order , shapes } ; } public toJSON ( ) { return JSON . stringify ( this . toObject ( ) ) ; } private initShapes ( declaration ) { declaration . forEach ( ( shapeDeclaration ) => { const ctor = SHAPES . get ( shapeDeclaration . type ) ; if ( typeof ctor === "undefined" ) { throw new Error ( `You have specified a shape with the name "${ shapeDeclaration . name }" in slide "${ this . name }". ` + `This shape has an unknown type "${ shapeDeclaration . type }". ` + 'Maybe you made a typo in "type" or tried to use a shape we do not have implemented.' ) ; } this . addShape ( shapeDeclaration . name , ctor . fromObject ( shapeDeclaration ) ) ; } ) ; } private initAnimations ( declaration ) { declaration . forEach ( ( animationDeclaration ) => { const ctor = ANIMATIONS . get ( animationDeclaration . type ) ; if ( typeof ctor === "undefined" ) { throw new Error ( `You have specified an animation with the name "${ animationDeclaration . name }" in slide "${ this . name }". ` + `This animation has an unknown type "${ animationDeclaration . type }". ` + 'Maybe you made a typo in "type" or tried to use an animation we do not have implemented.' ) ; } this . addAnimation ( animationDeclaration . name , ctor . fromObject ( animationDeclaration ) ) ; } ) ; } private renderShapes ( shapes ) { this . canvas . eraseScreen ( ) ; shapes . forEach ( ( shape ) => { shape . render ( this . canvas ) ; } ) ; this . canvas . flush ( ) ; } }