From ac093e7e4f483a7d5c15d72b4f203d4ff0a39927 Mon Sep 17 00:00:00 2001 From: Sophie Zhang Date: Thu, 21 Mar 2024 00:50:53 -0400 Subject: preliminary react-spring anims --- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 23 ++- .../views/nodes/trails/CubicBezierEditor.tsx | 69 ++++++- src/client/views/nodes/trails/PresBox.scss | 52 +++-- src/client/views/nodes/trails/PresBox.tsx | 216 +++++++++++++++------ src/client/views/nodes/trails/SlideEffect.tsx | 131 +++++++++++++ 6 files changed, 410 insertions(+), 84 deletions(-) create mode 100644 src/client/views/nodes/trails/SlideEffect.tsx (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a62ea2eb9..5cc921d85 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -10,7 +10,6 @@ import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; @@ -57,8 +56,8 @@ import { MarqueeView } from './MarqueeView'; import { PropertiesView } from '../../PropertiesView'; import { ExtractColors } from '../../ExtractColors'; import { smartLayout, smartLayout2, StyleInputDocument } from '../../../apis/gpt/customization'; -import { RichTextField } from '../../../../fields/RichTextField'; import { extname } from 'path'; +import { RichTextField } from '../../../../fields/RichTextField'; export interface collectionFreeformViewProps { NativeWidth?: () => number; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d131f72d5..3c1896474 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; +import SlideEffect, { EffectType } from './trails/SlideEffect'; interface Window { MediaRecorder: MediaRecorder; } @@ -1004,18 +1005,34 @@ export class DocumentViewInternal extends DocComponent{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.Bounce: return {renderDoc} case PresEffect.Roll: return {renderDoc}; case PresEffect.Lightspeed: return {renderDoc}; } + // switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { + // default: + // // package used: react-awesome-reveal + // case PresEffect.None: 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}; + // case PresEffect.Lightspeed: return {renderDoc}; + // } } public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { let gumStream: any; diff --git a/src/client/views/nodes/trails/CubicBezierEditor.tsx b/src/client/views/nodes/trails/CubicBezierEditor.tsx index 06a246675..d6ce9db1f 100644 --- a/src/client/views/nodes/trails/CubicBezierEditor.tsx +++ b/src/client/views/nodes/trails/CubicBezierEditor.tsx @@ -1,8 +1,10 @@ import React, { useEffect, useState } from 'react'; +import { StrCast } from '../../../../fields/Types'; type Props = { setFunc: (newPoints: { p1: number[]; p2: number[] }) => void; currPoints: { p1: number[]; p2: number[] }; + easeFunc: string; }; const ANIMATION_DURATION = 750; @@ -20,7 +22,7 @@ export const TIMING_DEFAULT_MAPPINGS = { 'ease-in-out': 'cubic-bezier(0.42, 0, 0.58, 1.0)', }; -const CubicBezierEditor = ({ setFunc, currPoints }: Props) => { +const CubicBezierEditor = ({ setFunc, currPoints, easeFunc }: Props) => { const [animating, setAnimating] = useState(false); // Should let parent control state const [cPoints, setCPoints] = useState( @@ -38,6 +40,49 @@ const CubicBezierEditor = ({ setFunc, currPoints }: Props) => { return Math.round(num * 100) / 100; }; + const convertToPoints = (func: string) => { + console.log('getting curr c points'); + let strPoints = func ? func : 'ease'; + if (!strPoints.startsWith('cubic')) { + switch (func) { + case 'linear': + strPoints = 'cubic-bezier(0.0, 0.0, 1.0, 1.0)'; + break; + case 'ease': + strPoints = 'cubic-bezier(0.25, 0.1, 0.25, 1.0)'; + break; + case 'ease-in': + strPoints = 'cubic-bezier(0.42, 0, 1.0, 1.0)'; + break; + case 'ease-out': + strPoints = 'cubic-bezier(0, 0, 0.58, 1.0)'; + break; + case 'ease-in-out': + strPoints = 'cubic-bezier(0.42, 0, 0.58, 1.0)'; + break; + default: + strPoints = 'cubic-bezier(0.25, 0.1, 0.25, 1.0)'; + } + } + console.log('str points', strPoints); + const components = strPoints + .split('(')[1] + .split(')')[0] + .split(',') + .map(elem => parseFloat(elem)); + console.log('bezier components', components); + return { + p1: [components[0], components[1]], + p2: [components[2], components[3]], + }; + }; + + useEffect(() => { + if (!easeFunc.startsWith('cubic')) { + setCPoints(convertToPoints(easeFunc)); + } + }, [easeFunc]); + useEffect(() => { if (animating) { setTimeout(() => { @@ -124,6 +169,20 @@ const CubicBezierEditor = ({ setFunc, currPoints }: Props) => { fill="transparent" /> {/* Bottom left */} + { + setC1Down(true); + }} + onPointerUp={() => { + setC1Down(false); + }} + x1={`${0 + OFFSET}`} + y1={`${EDITOR_WIDTH + OFFSET}`} + x2={`${cPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} + y2={`${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} + stroke="#00000000" + strokeWidth="5" + /> { }} /> {/* Top right */} + { + e.stopPropagation(); + setC2Down(true); + }} + onPointerUp={e => { + setC2Down(false); + }} x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${cPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#00000000" + strokeWidth="5" /> isNaN(Number(v))) + .map(effect => ({ + text: effect, + val: effect, + })); + @observer export class PresBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { @@ -962,6 +980,7 @@ export class PresBox extends ViewBoxBaseComponent() { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); return; } + console.log('pres_effect_dir', StrCast(activeItem.presentation_effectDirection)); const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined; // default with effect: 750ms else 500ms const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500); @@ -1665,7 +1684,7 @@ export class PresBox extends ViewBoxBaseComponent() { }; @undoBatch - updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect)); + updateEffectDirection = (effectDir: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effectDir)); @undoBatch updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect))); @@ -1677,25 +1696,46 @@ export class PresBox extends ViewBoxBaseComponent() { }; public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => { return ( - { PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); document.addEventListener('pointerup', PresBox.endBatch, true); e.stopPropagation(); - }} - onChange={e => { - e.stopPropagation(); - change(e.target.value); - }} - /> + }}> + { + e.stopPropagation(); + change(val.toString()); + }} + step={parseFloat(step)} + min={parseFloat(min)} + max={parseFloat(max)} + value={value} + size="small" + defaultValue={value} + aria-label="Small" + valueLabelDisplay="auto" + /> + + // { + // PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); + // document.addEventListener('pointerup', PresBox.endBatch, true); + // e.stopPropagation(); + // }} + // onChange={e => { + // e.stopPropagation(); + // change(e.target.value); + // }} + // /> ); }; @@ -1920,10 +1960,12 @@ export class PresBox extends ViewBoxBaseComponent() { ); }; + if (activeItem && this.targetDoc) { const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5; const zoom = NumCast(activeItem.config_zoom, 1) * 100; const effect = activeItem.presentation_effect ? activeItem.presentation_effect : PresMovement.None; + return ( <> {/* GPT Component */} @@ -1971,16 +2013,6 @@ export class PresBox extends ViewBoxBaseComponent() { this.customizeWithGPT(this.chatInput); }} /> - - {/* } - onClick={() => { - this.customizeWithGPT(this.chatInput); - }} - /> */}
() { this._openEffectDropdown = false; this._openBulletEffectDropdown = false; })}> -
+
Movement - {/* { - if (typeof val === 'string') this.setFormData({ ...this.formData, type: val as BugType }); - }} - dropdownType={DropdownType.SELECT} - type={Type.TERT} - fillWidth - /> */} -
{ e.stopPropagation(); @@ -2037,20 +2056,32 @@ export class PresBox extends ViewBoxBaseComponent() { {presMovement(PresMovement.Pan)} {presMovement(PresMovement.Jump)}
-
+
*/} + { + this.updateMovement(val as PresMovement); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + />
Zoom (% screen filled)
this.updateZoom(e.target.value)} />%
-
+ {/*
this.updateZoom(String(zoom), 0.1)}>
this.updateZoom(String(zoom), -0.1)}>
-
+
*/}
{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
@@ -2058,23 +2089,23 @@ export class PresBox extends ViewBoxBaseComponent() {
e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
-
+ {/*
this.updateTransitionTime(String(transitionSpeed), 1000)}>
this.updateTransitionTime(String(transitionSpeed), -1000)}>
-
+
*/}
- {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)} + {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)}
Fast
Medium
Slow
{/* Easing function */} -
+
() { }} dropdownType={DropdownType.SELECT} type={Type.TERT} - // fillWidth /> {/* Custom */}

Custom Timing Function

- +
-
+
Effects -
+ (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} + color={SettingsManager.userColor} + /> + {/*
Play Audio Annotation
() { onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} checked={BoolCast(activeItem.presPlayAudio)} /> -
-
+
*/} + (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} + color={SettingsManager.userColor} + /> + {/*
Zoom Text Selections
() { onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} checked={BoolCast(activeItem.presentation_zoomText)} /> -
-
*/} + {/* Effect dropdown */} + { + this.updateEffect(val as PresEffect, false); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + {/*
{ e.stopPropagation(); @@ -2148,20 +2205,59 @@ export class PresBox extends ViewBoxBaseComponent() { .filter(v => isNaN(Number(v))) .map(effect => preseEffect(effect))}
-
+
*/} + {/* Effect direction */}
Effect direction
{StrCast(this.activeItem.presentation_effectDirection)}
-
+
+ } + onClick={() => this.updateEffectDirection(PresEffectDirection.Center)} + /> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Left)} + /> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Right)} + /> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Top)} + /> + } + onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)} + /> +
+ {/*
{presDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} {presDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} {presDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} {presDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} {presDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} -
+
*/} + {/* Effect spring settings */}
this.applyTo(this.childDocs)}> diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx new file mode 100644 index 000000000..918ea93bd --- /dev/null +++ b/src/client/views/nodes/trails/SlideEffect.tsx @@ -0,0 +1,131 @@ +import { Button } from '@mui/material'; +import { useSpring, animated, easings } from '@react-spring/web'; +import React, { useEffect, useState } from 'react'; + +export enum EffectType { + ZOOM = 'zoom', + FADE = 'fade', + BOUNCE = 'bounce', +} + +interface Props { + effectType: EffectType; + friction: number; + tension: number; + mass: number; + children: React.ReactNode; +} + +export default function SpringAnimation({ friction, tension, mass, effectType, children }: Props) { + const [springs, api] = useSpring( + () => ({ + from: { + x: 0, + y: 0, + opacity: 0, + scale: 1, + }, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + onStart: () => { + console.log('started'); + }, + onRest: () => { + console.log('resting'); + }, + }), + [tension, friction, mass] + ); + + // Whether the animation is currently playing + const [animating, setAnimating] = useState(false); + + const zoomConfig = { + from: { + x: 0, + y: 0, + opacity: 0, + scale: 0, + }, + to: { + x: 0, + y: 0, + opacity: 1, + scale: 1, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + }; + + const bounceConfig = { + from: { + opacity: 0, + x: -200, + y: 0, + }, + to: [ + { + opacity: 1, + x: 0, + y: 0, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + ], + }; + + const handleClick = () => { + if (effectType === EffectType.BOUNCE) { + api.start(bounceConfig); + } else if (effectType === EffectType.ZOOM) { + api.start(zoomConfig); + } + }; + + useEffect(() => { + setTimeout(() => { + handleClick(); + }, 200); + }, []); + + return ( +
{ + // handleClick(); + // }} + > + {/* style={{ + width: "50px", + height: "50px", + backgroundColor: "#ff6d6d", + borderRadius: "4px", + ...springs, + }} */} + + {children} + + {/* */} +
+ ); +} -- cgit v1.2.3-70-g09d2