aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts1
-rw-r--r--src/client/views/nodes/trails/CubicBezierEditor.tsx134
-rw-r--r--src/client/views/nodes/trails/PresBox.scss14
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx47
4 files changed, 182 insertions, 14 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index e8bd35ac4..5f60e5f2a 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -644,6 +644,7 @@ const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number
};
export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) {
+ console.log('smooth scroll');
stopper?.();
const elements = element instanceof HTMLElement ? [element] : element;
const starts = elements.map(element => element.scrollTop);
diff --git a/src/client/views/nodes/trails/CubicBezierEditor.tsx b/src/client/views/nodes/trails/CubicBezierEditor.tsx
new file mode 100644
index 000000000..0744709aa
--- /dev/null
+++ b/src/client/views/nodes/trails/CubicBezierEditor.tsx
@@ -0,0 +1,134 @@
+import React, { useEffect, useState } from 'react';
+
+type Props = {
+ setFunc: (newPoints: { p1: number[]; p2: number[] }) => void;
+};
+
+const ANIMATION_DURATION = 750;
+// const ANIMATION_TIMING_FUNC = "cubic-bezier(.42,.97,.52,1.49)";
+const ANIMATION_TIMING_FUNC = 'cubic-bezier(0.3, .2, .2, 1.4)';
+const CONTAINER_WIDTH = 200;
+const EDITOR_WIDTH = 100;
+const OFFSET = (CONTAINER_WIDTH - EDITOR_WIDTH) / 2;
+
+const CubicBezierEditor = ({ setFunc }: Props) => {
+ const [animating, setAnimating] = useState(false);
+ const [cPoints, setCPoints] = useState({ p1: [0.67, 0.2], p2: [0.37, 0.88] });
+ const [c1Down, setC1Down] = useState(false);
+ const [c2Down, setC2Down] = useState(false);
+
+ const roundToHundredth = (num: number) => {
+ return Math.round(num * 100) / 100;
+ };
+
+ useEffect(() => {
+ if (animating) {
+ setTimeout(() => {
+ setAnimating(false);
+ }, ANIMATION_DURATION * 2);
+ }
+ }, [animating]);
+
+ useEffect(() => {
+ if (!c1Down) return;
+ window.addEventListener('pointerup', () => {
+ setC1Down(false);
+ });
+ const handlePointerMove = (e: PointerEvent) => {
+ const newX = cPoints.p1[0] + e.movementX / EDITOR_WIDTH;
+ if (newX < 0 || newX > 1) {
+ return;
+ }
+
+ setCPoints(prev => ({
+ ...prev,
+ p1: [roundToHundredth(prev.p1[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(prev.p1[1] - e.movementY / EDITOR_WIDTH)],
+ }));
+ setFunc({
+ ...cPoints,
+ p1: [roundToHundredth(cPoints.p1[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(cPoints.p1[1] - e.movementY / EDITOR_WIDTH)],
+ });
+ };
+
+ window.addEventListener('pointermove', handlePointerMove);
+
+ return () => window.removeEventListener('pointermove', handlePointerMove);
+ }, [c1Down, cPoints]);
+
+ useEffect(() => {
+ if (!c2Down) return;
+ window.addEventListener('pointerup', () => {
+ setC2Down(false);
+ });
+ const handlePointerMove = (e: PointerEvent) => {
+ const newX = cPoints.p2[0] + e.movementX / EDITOR_WIDTH;
+ if (newX < 0 || newX > 1) {
+ return;
+ }
+
+ setCPoints(prev => ({
+ ...prev,
+ p2: [roundToHundredth(prev.p2[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(prev.p2[1] - e.movementY / EDITOR_WIDTH)],
+ }));
+ setFunc({
+ ...cPoints,
+ p1: [roundToHundredth(cPoints.p2[0] + e.movementX / EDITOR_WIDTH), roundToHundredth(cPoints.p2[1] - e.movementY / EDITOR_WIDTH)],
+ });
+ };
+
+ window.addEventListener('pointermove', handlePointerMove);
+
+ return () => window.removeEventListener('pointermove', handlePointerMove);
+ }, [c2Down, cPoints]);
+
+ return (
+ <div>
+ <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg">
+ {/* Outlines */}
+ <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" />
+ {/* Box Outline */}
+ <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" />
+ {/* Editor */}
+ <path
+ d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${cPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${
+ cPoints.p2[0] * EDITOR_WIDTH + OFFSET
+ } ${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`}
+ stroke="#ffffff"
+ fill="transparent"
+ />
+ {/* Bottom left */}
+ <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${cPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
+ <circle
+ cx={`${cPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
+ cy={`${EDITOR_WIDTH - cPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
+ r="5"
+ fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`}
+ onPointerDown={e => {
+ e.stopPropagation();
+ setC1Down(true);
+ }}
+ onPointerUp={e => {
+ setC1Down(false);
+ }}
+ />
+ {/* Top right */}
+ <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${cPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
+ <circle
+ cx={`${cPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
+ cy={`${EDITOR_WIDTH - cPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
+ r="5"
+ fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`}
+ onPointerDown={e => {
+ e.stopPropagation();
+ setC2Down(true);
+ }}
+ onPointerUp={e => {
+ setC2Down(false);
+ }}
+ />
+ </svg>
+ </div>
+ );
+};
+
+export default CubicBezierEditor;
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index dd0007d82..0aceb1b28 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -28,6 +28,20 @@
overflow-y: hidden;
}
+// Bezier editor
+
+.presBox-bezier-editor {
+ border: 1px solid rgb(221, 221, 221);
+ border-radius: 4px;
+}
+
+.presBox-easing-function-options {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
.presBox-cont {
cursor: auto;
position: absolute;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index cd3114940..cc05a5c5f 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -43,6 +43,7 @@ import { BiMicrophone, BiX } from 'react-icons/bi';
import { AiOutlineSend } from 'react-icons/ai';
import { gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/customization';
import { DictationManager } from '../../../util/DictationManager';
+import CubicBezierEditor from './CubicBezierEditor';
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -91,6 +92,10 @@ const easeItems = [
text: 'Linear',
val: 'linear',
},
+ // {
+ // text: 'Custom',
+ // val: 'custom',
+ // },
];
@observer
@@ -162,6 +167,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.isRecording = isRecording;
};
+ // Easing function variables
+
+ @observable easeDropdownVal = 'ease';
+
+ // @observable bezierControlPoints: { p1: number[]; p2: number[] } = { p1: [0.67, 0.2], p2: [0.37, 0.88] };
+ @action setBezierControlPoints = (newPoints: { p1: number[]; p2: number[] }) => {
+ // this.bezierControlPoints = newPoints;
+ this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
+ };
+
@computed
get isTreeOrStack() {
return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any);
@@ -2021,20 +2036,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="slider-text">Slow</div>
</div>
{/* Easing function */}
- <Dropdown
- color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Easing Function'}
- closeOnSelect={true}
- items={easeItems}
- selectedVal={this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease'}
- setSelectedVal={val => {
- if (typeof val === 'string') this.setEaseFunc(this.activeItem, val);
- }}
- dropdownType={DropdownType.SELECT}
- type={Type.TERT}
- fillWidth
- />
- {/* Custom */}
+ <div className="presBox-easing-function-options">
+ <Dropdown
+ color={StrCast(Doc.UserDoc().userColor)}
+ formLabel={'Easing Function'}
+ closeOnSelect={true}
+ items={easeItems}
+ selectedVal={this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease'}
+ setSelectedVal={val => {
+ if (typeof val === 'string') this.setEaseFunc(this.activeItem, val);
+ }}
+ dropdownType={DropdownType.SELECT}
+ type={Type.TERT}
+ // fillWidth
+ />
+ {/* Custom */}
+ <p className="presBox-submenu-label">Custom Timing Function</p>
+ <CubicBezierEditor setFunc={this.setBezierControlPoints} />
+ </div>
</div>
<div className="ribbon-box">
Effects