From 219f023202658414b09c5245c807c1bb6cdc4285 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 25 Apr 2024 11:17:35 -0400 Subject: ui framework for effect animations --- src/client/apis/gpt/customization.ts | 26 ++- src/client/views/nodes/DocumentView.tsx | 12 +- src/client/views/nodes/trails/PresBox.scss | 29 +++ src/client/views/nodes/trails/PresBox.tsx | 241 ++++++++++++++----------- src/client/views/nodes/trails/SlideEffect.scss | 2 +- src/client/views/nodes/trails/SlideEffect.tsx | 86 ++++++--- src/client/views/nodes/trails/SpringUtils.ts | 9 + 7 files changed, 259 insertions(+), 146 deletions(-) diff --git a/src/client/apis/gpt/customization.ts b/src/client/apis/gpt/customization.ts index 8960bc651..a1ab2bd5f 100644 --- a/src/client/apis/gpt/customization.ts +++ b/src/client/apis/gpt/customization.ts @@ -39,8 +39,27 @@ const setupPresSlideCustomization = () => { setupPresSlideCustomization(); -export const gptTrailSlideCustomization = async (inputText: string, properties: any) => { - console.log('properties', properties); +export const getSlideTransitionSuggestions = async (inputText: string, properties: any) => { + /** + * Prompt: Generate an entrance animations from slower and gentler + * to bouncier and more high energy + * + * Format: + * { + * name: Slow Fade, Quick Flip, Springy + * effect: BOUNCE + * effectDirection: LEFT + * timingConfig: { + * + * + * + * } + * } + */ + const res = await new Promise(resolve => resolve('result')); +}; + +export const gptTrailSlideCustomization = async (inputText: string, properties: any | any[], applyToWhole?: boolean) => { let prompt = prompts.trails.description; prompts.trails.features.forEach(feature => { @@ -50,9 +69,6 @@ export const gptTrailSlideCustomization = async (inputText: string, properties: } }); - // prompt += - // 'title is the title/name of the slide. presentation_transition is a number in milliseconds for how long it should take to transition/move to a slide. presentation_effect is an effect applied to the slide when we transition to it. Its only possible values are: [None, Fade in, Flip, Rotate, Bounce, Roll]. presentation_effectDirection is what direction the effect is applied. Its only possible values are: [Enter from left, Enter from right, Enter from bottom, Enter from Top, Enter from center]. config_zoom is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.'; - prompt += 'Set unchanged values to null. Please only return the json with these keys and their values.'; console.log('messages', [ diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 801d96b35..b372c1927 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1031,12 +1031,12 @@ export class DocumentViewInternal extends DocComponent{renderDoc} - case PresEffect.Fade: return {renderDoc} - case PresEffect.Flip: return {renderDoc} - case PresEffect.Rotate: return {renderDoc} - case PresEffect.Bounce: return {renderDoc} - case PresEffect.Roll: return {renderDoc} + case PresEffect.Zoom: return {renderDoc} + case PresEffect.Fade: return {renderDoc} + case PresEffect.Flip: return {renderDoc} + case PresEffect.Rotate: return {renderDoc} + case PresEffect.Bounce: return {renderDoc} + case PresEffect.Roll: return {renderDoc} // keep as preset, doesn't really make sense with spring config case PresEffect.Lightspeed: return {renderDoc}; } diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss index adc59d812..09de4b0f4 100644 --- a/src/client/views/nodes/trails/PresBox.scss +++ b/src/client/views/nodes/trails/PresBox.scss @@ -36,6 +36,35 @@ overflow-y: hidden; } +// Effect Animations + +.presBox-effect-row { + display: flex; + gap: 4px; + margin: 4px; +} + +.presBox-effect-container { + overflow: hidden; + position: relative; + width: 80px; + height: 80px; + /* background-color: #1f2028; */ + border: 1px solid rgb(118, 118, 118); + border-radius: 8px; +} + +.presBox-effect-demo-box { + width: 40px; + height: 40px; + position: absolute; + top: 20px; + left: 20px; + border-radius: 4px; + // default bg + background-color: rgb(37, 161, 255); +} + // Bezier editor .presBox-show-hide-dropdown { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 5ce010cf7..9644935b2 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -49,6 +49,7 @@ import Slider from '@mui/material/Slider'; import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaCompressArrowsAlt } from 'react-icons/fa'; import SpringAnimationPreview from './SlideEffectPreview'; import { effectTimings, SpringType, springMappings, effectItems, easeItems, movementItems, SpringSettings, presEffectDefaultTimings } from './SpringUtils'; +import SlideEffect from './SlideEffect'; export interface pinDataTypes { scrollable?: boolean; @@ -1322,7 +1323,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.updateCurrentPresentation(DocCast(doc.embedContainer)); }; - //Command click + // Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { if (!this.selectedArray.has(doc)) { @@ -1666,6 +1667,7 @@ export class PresBox extends ViewBoxBaseComponent() { PresBox._sliderBatch.end(); document.removeEventListener('pointerup', PresBox.endBatch, true); }; + public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => { return (
() { ); }; + // Applies the slide transiiton settings to all docs in the array @undoBatch applyTo = (array: Doc[]) => { this.updateMovement(this.activeItem.presentation_movement as PresMovement, true); @@ -2145,16 +2148,6 @@ export class PresBox extends ViewBoxBaseComponent() { onClick={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} color={SettingsManager.userColor} /> - {/*
-
Play Audio Annotation
- (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} - checked={BoolCast(activeItem.presPlayAudio)} - /> -
*/} () { onClick={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} color={SettingsManager.userColor} /> - {/*
-
Zoom Text Selections
- (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} - checked={BoolCast(activeItem.presentation_zoomText)} - /> -
*/} {/* Effect dropdown */} () { dropdownType={DropdownType.SELECT} type={Type.TERT} /> - {/*
{ - e.stopPropagation(); - this._openEffectDropdown = !this._openEffectDropdown; - })} - style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, - borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, - border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, - }}> - {effect?.toString()} - -
e.stopPropagation()}> - {Object.values(PresEffect) - .filter(v => isNaN(Number(v))) - .map(effect => preseEffect(effect))} + {/* Effect generations */} +
+ {/* Chat for idea generation */} +
+

Customize with GPT

+
+
+ (this._inputref = r)} + minRows={1} + placeholder="Customize..." + className="pres-chatbox" + autoFocus={true} + value={this.chatInput} + onChange={e => { + this.setChatInput(e.target.value); + }} + onKeyDown={e => { + this.stopDictation(true); + e.stopPropagation(); + }} + /> + } + onClick={() => { + if (!this.isRecording) { + this.recordDictation(); + } else { + this.stopDictation(true); + } + }} + /> +
+
-
*/} + {/* Preview Animations */} +
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
{/* Effect direction */} {/* Only applies to certain effects */} - {effect === PresEffect.Flip || - effect === PresEffect.Bounce || - (effect === PresEffect.Roll && ( - <> -
-
Effect direction
-
- {StrCast(this.activeItem.presentation_effectDirection)} -
+ {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && ( + <> +
+
Effect direction
+
+ {StrCast(this.activeItem.presentation_effectDirection)}
-
- +
+ {/* } onClick={() => this.updateEffectDirection(PresEffectDirection.Center)} - /> - } - onClick={() => this.updateEffectDirection(PresEffectDirection.Left)} - /> - } - onClick={() => this.updateEffectDirection(PresEffectDirection.Right)} - /> - {effect !== PresEffect.Roll && ( - <> - } - onClick={() => this.updateEffectDirection(PresEffectDirection.Top)} - /> - } - onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)} - /> - - )} -
- - ))} + /> */} + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Left)} + /> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Right)} + /> + {effect !== PresEffect.Roll && ( + <> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Top)} + /> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)} + /> + + )} +
+ + )} {/* Spring settings */} {/* No spring settings for jackinthebox (lightspeed) */} -
{ - e.stopPropagation(); - this.setSpringEditorVisibility(!this.showSpringEditor); - }}> - {`${this.showSpringEditor ? 'Hide' : 'Show'} Spring Settings`} - -
{effect !== PresEffect.Lightspeed && ( <> () { dropdownType={DropdownType.SELECT} type={Type.TERT} /> +
{ + e.stopPropagation(); + this.setSpringEditorVisibility(!this.showSpringEditor); + }}> + {`${this.showSpringEditor ? 'Hide' : 'Show'} Spring Settings`} + +
{this.showSpringEditor && ( <>
Tension
@@ -2363,11 +2391,12 @@ export class PresBox extends ViewBoxBaseComponent() { )} {/* Effect spring settings */}
-
+
*/}
); diff --git a/src/client/views/nodes/trails/SlideEffect.scss b/src/client/views/nodes/trails/SlideEffect.scss index 144052c1f..cadda0ccc 100644 --- a/src/client/views/nodes/trails/SlideEffect.scss +++ b/src/client/views/nodes/trails/SlideEffect.scss @@ -20,5 +20,5 @@ .flip-back { // Get the background color of node instead - background-color: rgb(223, 223, 223); + // background-color: rgb(223, 223, 223); } diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx index bd667b925..251649d6a 100644 --- a/src/client/views/nodes/trails/SlideEffect.tsx +++ b/src/client/views/nodes/trails/SlideEffect.tsx @@ -7,20 +7,26 @@ import { NumCast } from '../../../../fields/Types'; interface Props { // pass in doc to extract width, height, bg - doc: Doc; - // wait for the transition movement to end before starting effect - delay: number; + doc?: Doc; dir: PresEffectDirection; presEffect: PresEffect; friction: number; tension: number; mass: number; children: React.ReactNode; + infinite?: boolean; } +const DEFAULT_WIDTH = 40; +const PREVIEW_OFFSET = 60; +const ACTUAL_OFFSET = 200; +const infiniteOptions = { + loop: true, + delay: 500, +}; + // TODO: add visibility detector when the slide comes into view -export default function SpringAnimation({ doc, delay, dir, friction, tension, mass, presEffect, children }: Props) { - console.log('delay:', delay); +export default function SpringAnimation({ doc, dir, friction, tension, mass, presEffect, children, infinite }: Props) { const [springs, api] = useSpring( () => ({ from: { @@ -34,12 +40,8 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma friction: friction, mass: mass, }, - onStart: () => { - console.log('started'); - }, - onRest: () => { - console.log('resting'); - }, + onStart: () => {}, + onRest: () => {}, }), [tension, friction, mass] ); @@ -110,7 +112,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma return { from: { opacity: 0, - x: -200, + x: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, y: 0, }, }; @@ -118,7 +120,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma return { from: { opacity: 0, - x: 200, + x: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, y: 0, }, }; @@ -127,7 +129,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma from: { opacity: 0, x: 0, - y: -200, + y: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, }, }; case PresEffectDirection.Bottom: @@ -135,7 +137,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma from: { opacity: 0, x: 0, - y: 200, + y: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, }, }; default: @@ -164,6 +166,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma }, }, ], + loop: true, }; const flipConfig = { @@ -196,7 +199,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma from: { opacity: 0, x: 100, - y: -120, + y: 120, }, }; case PresEffectDirection.Top: @@ -246,35 +249,41 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma opacity: 0, }, to: [], - loop: true, }; // Switch animation depending on slide effect const startAnimation = () => { + let config: any = zoomConfig; switch (presEffect) { case PresEffect.Bounce: - api.start(bounceConfig); + config = bounceConfig; break; case PresEffect.Zoom: - api.start(zoomConfig); + config = zoomConfig; break; case PresEffect.Rotate: - api.start(rotateConfig); + config = rotateConfig; break; case PresEffect.Fade: - api.start(fadeConfig); + config = fadeConfig; break; case PresEffect.Flip: - api.start(flipConfig); + config = flipConfig; break; case PresEffect.Roll: - api.start(rollConfig); + config = rollConfig; break; case PresEffect.Lightspeed: break; default: break; } + + if (infinite) { + config = { ...config, ...infiniteOptions }; + } + + api.start(config); }; // const flipRender = () => { @@ -308,15 +317,30 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma
{dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? ( <> - `perspective(600px) rotateX(${val}deg)`), width: NumCast(doc.width), height: NumCast(doc.height) }} /> - `perspective(600px) rotateX(${val}deg)`), rotateX: '180deg', width: NumCast(doc.width), height: NumCast(doc.height) }}> + `perspective(600px) rotateX(${val}deg)`), + width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, + height: doc ? NumCast(doc.height) : DEFAULT_WIDTH, + backgroundColor: infinite ? '#a825ff' : 'rgb(223, 223, 223);', + }} + /> + `perspective(600px) rotateX(${val}deg)`), rotateX: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> {children} ) : ( <> - `perspective(600px) rotateY(${val}deg)`), width: NumCast(doc.width), height: NumCast(doc.height) }} /> - `perspective(600px) rotateY(${val}deg)`), rotateY: '180deg', width: NumCast(doc.width), height: NumCast(doc.height) }}> + `perspective(600px) rotateY(${val}deg)`), width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }} + /> + `perspective(600px) rotateY(${val}deg)`), rotateY: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> {children} @@ -350,7 +374,7 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma // }, []); useEffect(() => { - if (!inView) return; + if (infinite || !inView) return; console.log('in view'); // TODO: Control start with slide visibility instead setTimeout(() => { @@ -358,5 +382,11 @@ export default function SpringAnimation({ doc, delay, dir, friction, tension, ma }, 100); }, [inView]); + useEffect(() => { + if (infinite) { + startAnimation(); + } + }, []); + return
{getRenderDoc()}
; } diff --git a/src/client/views/nodes/trails/SpringUtils.ts b/src/client/views/nodes/trails/SpringUtils.ts index 0518e0623..42a0add84 100644 --- a/src/client/views/nodes/trails/SpringUtils.ts +++ b/src/client/views/nodes/trails/SpringUtils.ts @@ -16,6 +16,15 @@ export interface SpringSettings { mass: number; } +// Overall config + +export interface AnimationSettings { + effect: PresEffect; + stiffness: number; + damping: number; + mass: number; +} + // Options in the movement easing dropdown export const easeItems = [ { -- cgit v1.2.3-70-g09d2