diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 24 | ||||
-rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 173 | ||||
-rw-r--r-- | src/client/views/nodes/trails/SlideEffect.tsx | 55 | ||||
-rw-r--r-- | src/client/views/nodes/trails/SlideEffectPreview.tsx | 134 |
4 files changed, 373 insertions, 13 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3c1896474..92f66f2cd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -47,7 +47,7 @@ import { FieldViewProps, FieldViewSharedProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { PresEffect, PresEffectDirection } from './trails'; +import { PresEffect, PresEffectDirection, SpringSettings, SpringType } from './trails'; import SlideEffect, { EffectType } from './trails/SlideEffect'; interface Window { MediaRecorder: MediaRecorder; @@ -996,6 +996,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document */ public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) { const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection; + console.log(dir); const effectProps = { left: dir === PresEffectDirection.Left, right: dir === PresEffectDirection.Right, @@ -1005,19 +1006,32 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document delay: 0, duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)), }; - console.log('dir', dir); - console.log('effect', StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))); + + let timing = StrCast(presEffectDoc?.presEffectTiming); + let timingConfig: SpringSettings | undefined; + if (timing) { + timingConfig = JSON.parse(timing); + } + + if (!timingConfig) { + timingConfig = { + type: SpringType.DEFAULT, + stiffness: 600, + damping: 15, + mass: 1, + }; + } //prettier-ignore switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) { default: // package used: react-awesome-reveal case PresEffect.None: return renderDoc; - case PresEffect.Zoom: return <SlideEffect effectType={EffectType.ZOOM} tension={300} friction={12} mass={2}>{renderDoc}</SlideEffect> + case PresEffect.Zoom: return <SlideEffect dir={dir as PresEffectDirection} effectType={EffectType.ZOOM} tension={timingConfig.stiffness} friction={timingConfig.damping} mass={timingConfig.mass}>{renderDoc}</SlideEffect> case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>; case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>; case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>; - case PresEffect.Bounce: return <SlideEffect effectType={EffectType.BOUNCE} tension={300} friction={12} mass={2}>{renderDoc}</SlideEffect> + case PresEffect.Bounce: return <SlideEffect dir={dir as PresEffectDirection} effectType={EffectType.BOUNCE} tension={timingConfig.stiffness} friction={timingConfig.damping} mass={timingConfig.mass}>{renderDoc}</SlideEffect> case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>; case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>; } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index d330c8157..deb59d03f 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -46,6 +46,7 @@ import { DictationManager } from '../../../util/DictationManager'; import CubicBezierEditor, { TIMING_DEFAULT_MAPPINGS } from './CubicBezierEditor'; import Slider from '@mui/material/Slider'; import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaCompressArrowsAlt } from 'react-icons/fa'; +import SpringAnimationPreview from './SlideEffectPreview'; export interface pinDataTypes { scrollable?: boolean; @@ -74,6 +75,23 @@ export interface PinProps { pinData?: pinDataTypes; } +// the type of slide effect timing (spring-driven) +export enum SpringType { + DEFAULT = 'default', + GENTLE = 'gentle', + BOUNCY = 'bouncy', + CUSTOM = 'custom', + QUICK = 'quick', +} + +// settings that control slide effect spring settings +export interface SpringSettings { + type: SpringType; + stiffness: number; + damping: number; + mass: number; +} + const easeItems = [ { text: 'Ease', @@ -116,6 +134,59 @@ const effectItems = Object.values(PresEffect) val: effect, })); +const effectTimings = [ + { text: 'Default', val: SpringType.DEFAULT }, + { + text: 'Gentle', + val: SpringType.GENTLE, + }, + { + text: 'Quick', + val: SpringType.QUICK, + }, + { + text: 'Bouncy', + val: SpringType.BOUNCY, + }, + { + text: 'Custom', + val: SpringType.CUSTOM, + }, +]; + +const springMappings: { + [key: string]: { stiffness: number; damping: number; mass: number }; +} = { + default: { + // stiffness: 300, + // damping: 12, + // mass: 2, + stiffness: 600, + damping: 15, + mass: 1, + }, + gentle: { + stiffness: 100, + damping: 15, + mass: 1, + }, + quick: { + stiffness: 300, + damping: 20, + mass: 1, + }, + bouncy: { + stiffness: 600, + damping: 15, + mass: 1, + }, + custom: { + stiffness: 100, + damping: 10, + mass: 1, + }, +}; + @observer export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { @@ -1689,6 +1760,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @undoBatch updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect))); + @undoBatch + updateEffectTiming = (activeItem: Doc, timing: SpringSettings) => { + activeItem.presEffectTiming = JSON.stringify(timing); + this.selectedArray.forEach(doc => (doc.presEffectTiming = activeItem.presEffectTiming)); + }; + static _sliderBatch: any; static endBatch = () => { PresBox._sliderBatch.end(); @@ -1935,6 +2012,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get transitionDropdown() { const activeItem = this.activeItem; + // Retrieving spring timing properties + let timing = StrCast(activeItem.presEffectTiming); + let timingConfig: SpringSettings | undefined; + if (timing) { + timingConfig = JSON.parse(timing); + } + + if (!timingConfig) { + timingConfig = { + type: SpringType.DEFAULT, + // stiffness: 300, + // damping: 12, + // mass: 2, + stiffness: 600, + damping: 15, + mass: 1, + }; + } + const preseEffect = (effect: PresEffect) => ( <div className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`} @@ -2105,7 +2201,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text">Slow</div> </div> {/* Easing function */} - <div className="presBox-option-block presBox-option-cente"> + <div className="presBox-option-block presBox-option-center"> <Dropdown color={StrCast(Doc.UserDoc().userColor)} formLabel={'Easing Function'} @@ -2129,7 +2225,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} easeFunc={StrCast(this.activeItem.presEaseFunc)} /> </div> </div> - <div className="presBox-option-block"> + <div className="presBox-option-block" style={{ padding: '16px' }}> Effects <Toggle formLabel={'Play Audio Annotation'} @@ -2258,6 +2354,79 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { {presDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })} </div> */} {/* Effect spring settings */} + <Dropdown + color={StrCast(Doc.UserDoc().userColor)} + formLabel={'Effect Timing'} + closeOnSelect={true} + items={effectTimings} + selectedVal={timingConfig.type} + setSelectedVal={val => { + console.log('effect timing', val); + this.updateEffectTiming(activeItem, { + type: val as SpringType, + ...springMappings[val], + }); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + <div>Tension</div> + <div + onPointerDown={e => { + e.stopPropagation(); + }}> + <Slider + min={1} + max={1000} + step={5} + size="small" + value={timingConfig.stiffness} + onChange={(e, val) => { + if (!timingConfig) return; + this.updateEffectTiming(activeItem, { ...timingConfig, stiffness: val as number }); + }} + valueLabelDisplay="auto" + /> + </div> + <div>Damping</div> + <div + onPointerDown={e => { + e.stopPropagation(); + }}> + <Slider + min={1} + max={100} + step={1} + size="small" + value={timingConfig.damping} + onChange={(e, val) => { + if (!timingConfig) return; + this.updateEffectTiming(activeItem, { ...timingConfig, damping: val as number }); + }} + valueLabelDisplay="auto" + /> + </div> + <div>Mass</div> + <div + onPointerDown={e => { + e.stopPropagation(); + }}> + <Slider + min={1} + max={10} + step={1} + size="small" + value={timingConfig.mass} + onChange={(e, val) => { + if (!timingConfig) return; + this.updateEffectTiming(activeItem, { ...timingConfig, mass: val as number }); + }} + valueLabelDisplay="auto" + /> + </div> + <SpringAnimationPreview tension={timingConfig.stiffness} friction={timingConfig.damping} mass={timingConfig.mass}> + <div style={{ width: '40px', height: '40px', backgroundColor: '#2e96ff', borderRadius: '4px' }}></div> + </SpringAnimationPreview> </div> <div className="ribbon-final-box"> <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}> diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx index 918ea93bd..ae25bfb90 100644 --- a/src/client/views/nodes/trails/SlideEffect.tsx +++ b/src/client/views/nodes/trails/SlideEffect.tsx @@ -1,6 +1,7 @@ import { Button } from '@mui/material'; import { useSpring, animated, easings } from '@react-spring/web'; import React, { useEffect, useState } from 'react'; +import { PresEffectDirection } from './PresEnums'; export enum EffectType { ZOOM = 'zoom', @@ -9,6 +10,7 @@ export enum EffectType { } interface Props { + dir: PresEffectDirection; effectType: EffectType; friction: number; tension: number; @@ -16,7 +18,7 @@ interface Props { children: React.ReactNode; } -export default function SpringAnimation({ friction, tension, mass, effectType, children }: Props) { +export default function SpringAnimation({ dir, friction, tension, mass, effectType, children }: Props) { const [springs, api] = useSpring( () => ({ from: { @@ -63,12 +65,53 @@ export default function SpringAnimation({ friction, tension, mass, effectType, c }, }; + const getBounceConfigFrom = () => { + switch (dir) { + case PresEffectDirection.Left: + return { + from: { + opacity: 0, + x: -200, + y: 0, + }, + }; + case PresEffectDirection.Right: + return { + from: { + opacity: 0, + x: 200, + y: 0, + }, + }; + case PresEffectDirection.Top: + return { + from: { + opacity: 0, + x: 0, + y: -200, + }, + }; + case PresEffectDirection.Bottom: + return { + from: { + opacity: 0, + x: 0, + y: 200, + }, + }; + default: + return { + from: { + opacity: 0, + x: 0, + y: 0, + }, + }; + } + }; + const bounceConfig = { - from: { - opacity: 0, - x: -200, - y: 0, - }, + ...getBounceConfigFrom(), to: [ { opacity: 1, diff --git a/src/client/views/nodes/trails/SlideEffectPreview.tsx b/src/client/views/nodes/trails/SlideEffectPreview.tsx new file mode 100644 index 000000000..aacb37b48 --- /dev/null +++ b/src/client/views/nodes/trails/SlideEffectPreview.tsx @@ -0,0 +1,134 @@ +import { Button } from '@mui/material'; +import { useSpring, animated, easings } from '@react-spring/web'; +import React, { useEffect, useState } from 'react'; + +interface Props { + friction: number; + tension: number; + mass: number; + children: React.ReactNode; +} + +export default function SpringAnimationPreview({ friction, tension, mass, children }: Props) { + const [springs, api] = useSpring( + () => ({ + from: { + x: 0, + y: 0, + opacity: 1, + 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, + }, + }, + { + opacity: 0, + scale: 0, + x: 0, + y: 0, + config: { + duration: 500, + easing: easings.easeInOutCubic, + }, + }, + ], + }; + + const bounceConfig = { + from: { + x: -50, + y: 0, + }, + to: [ + { + x: 50, + y: 0, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + { + x: -50, + y: 0, + config: { + duration: 500, + easing: easings.easeInOutCubic, + }, + }, + ], + }; + + const animate = () => { + api.start(bounceConfig); + }; + + useEffect(() => { + animate(); + }, []); + + return ( + <div + style={{ + width: '200px', + height: '150px', + borderRadius: '4px', + border: '1px solid #696969', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }} + onPointerEnter={() => { + animate(); + }}> + {/* style={{ + width: "50px", + height: "50px", + backgroundColor: "#ff6d6d", + borderRadius: "4px", + ...springs, + }} */} + <animated.div + style={{ + ...springs, + }}> + {children} + </animated.div> + </div> + ); +} |