Browse Source

Update values handling for animation workbench

Refactor codebase regarding animation workbench.
master
TheoryOfNekomata 1 year ago
parent
commit
d37d61bdd3
2 changed files with 148 additions and 210 deletions
  1. +1
    -1
      tools/animation-workbench/app/components/FileButton/index.tsx
  2. +147
    -209
      tools/animation-workbench/app/routes/index.tsx

+ 1
- 1
tools/animation-workbench/app/components/FileButton/index.tsx View File

@@ -9,7 +9,7 @@ export const FileButton = forwardRef<HTMLInputElement, FileButtonProps>(({
children, children,
...etcProps ...etcProps
}, ref) => ( }, ref) => (
<label>
<label className="contents">
<input <input
{...etcProps} {...etcProps}
ref={ref} ref={ref}


+ 147
- 209
tools/animation-workbench/app/routes/index.tsx View File

@@ -3,7 +3,6 @@ import {
FormEventHandler, FormEventHandler,
MouseEventHandler, MouseEventHandler,
useEffect, useEffect,
useId,
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
@@ -40,7 +39,6 @@ const useAnimationForm = () => {
const [enabledGroupIds, setEnabledGroupIds] = useState<string[]>(); const [enabledGroupIds, setEnabledGroupIds] = useState<string[]>();
const [savedSvgKeyframes, setSavedSvgKeyframes] = useState<Keyframe[]>(); const [savedSvgKeyframes, setSavedSvgKeyframes] = useState<Keyframe[]>();


const svgAnimationIdsListId = useId();
const svgPreviewRef = useRef<HTMLDivElement>(null); const svgPreviewRef = useRef<HTMLDivElement>(null);


const loadSvgFile: ChangeEventHandler<HTMLInputElement> = async (e) => { const loadSvgFile: ChangeEventHandler<HTMLInputElement> = async (e) => {
@@ -68,7 +66,7 @@ const useAnimationForm = () => {
}); });
} }


const proposeNewAnimationId: FormEventHandler<HTMLInputElement> = (e) => {
const proposeNewAnimationId: FormEventHandler<HTMLInputElement> = () => {
setIsNewAnimationId(true); setIsNewAnimationId(true);
} }


@@ -80,17 +78,16 @@ const useAnimationForm = () => {
}) })
} }


useEffect(() => {
const onLoaded = (loadedFileContents?: string, svgPreviewRefCurrent?: HTMLElement | null) => {
if (!loadedFileContents) { if (!loadedFileContents) {
return return
} }


if (!svgPreviewRef.current) {
if (!svgPreviewRefCurrent) {
return return
} }


const svgPreviewRoot = svgPreviewRef.current.children[0];

const svgPreviewRoot = svgPreviewRefCurrent.children[0];
setSvgAnimations(['default']); setSvgAnimations(['default']);
setSvgGroups( setSvgGroups(
Array Array
@@ -103,6 +100,10 @@ const useAnimationForm = () => {
setCurrentAnimationId('default'); setCurrentAnimationId('default');
setEnabledGroupIds([]); // TODO - compute enabled groupIds based on current animation id setEnabledGroupIds([]); // TODO - compute enabled groupIds based on current animation id
setSavedSvgKeyframes([]); // TODO - load keyframes from file setSavedSvgKeyframes([]); // TODO - load keyframes from file
}

useEffect(() => {
onLoaded(loadedFileContents, svgPreviewRef.current);
}, [loadedFileContents, svgPreviewRef.current]); }, [loadedFileContents, svgPreviewRef.current]);


const focusAnimationIdInput: FocusEventHandler<HTMLInputElement> = (e) => { const focusAnimationIdInput: FocusEventHandler<HTMLInputElement> = (e) => {
@@ -123,40 +124,7 @@ const useAnimationForm = () => {
setEnabledGroupIds((oldEnabledGroupIds = []) => oldEnabledGroupIds.filter((i) => i !== currentTarget.value)); setEnabledGroupIds((oldEnabledGroupIds = []) => oldEnabledGroupIds.filter((i) => i !== currentTarget.value));
} }


const translateXGroup = (group: SvgGroup): ChangeEventHandler<HTMLInputElement> => (e) => {
const { current: svgPreviewWrapper } = svgPreviewRef;
if (!svgPreviewWrapper) {
return;
}

const svgPreviewRoot = svgPreviewWrapper.children[0] as SVGSVGElement | null;
if (!svgPreviewRoot) {
return;
}

const targetGroup = Array.from(svgPreviewRoot.children).find(g => g.id === group.id) as SVGGraphicsElement;
if (!targetGroup) {
return;
}

const rotationCenterReference = Array.from(targetGroup.children).find(e => e.tagName === 'circle') as SVGCircleElement | undefined
const rotationCenter = {
cx: rotationCenterReference?.getAttribute?.('cx') ?? 0,
cy: rotationCenterReference?.getAttribute?.('cy') ?? 0,
};

const values = getFormValues(e.currentTarget.form!, { forceNumberValues: true });
const changed = Array.isArray(values.changed) ? values.changed : [values.changed];
const changedIndex = changed.indexOf(group.id);
const rotate = Array.isArray(values.rotate) ? values.rotate : [values.rotate];
const translateY = Array.isArray(values.translateY) ? values.translateY : [values.translateY];
const currentRotate = rotate[changedIndex];
const currentTranslateX = e.currentTarget.value;
const currentTranslateY = translateY[changedIndex];
targetGroup.setAttribute('transform', `translate(${currentTranslateX} ${currentTranslateY}) rotate(${currentRotate} ${rotationCenter.cx} ${rotationCenter.cy})`);
}

const translateYGroup = (group: SvgGroup): ChangeEventHandler<HTMLInputElement> => (e) => {
const handleAttributeValueChange = (group: SvgGroup): ChangeEventHandler<HTMLInputElement> => (e) => {
const { current: svgPreviewWrapper } = svgPreviewRef; const { current: svgPreviewWrapper } = svgPreviewRef;
if (!svgPreviewWrapper) { if (!svgPreviewWrapper) {
return; return;
@@ -178,82 +146,26 @@ const useAnimationForm = () => {
cy: rotationCenterReference?.getAttribute?.('cy') ?? 0, cy: rotationCenterReference?.getAttribute?.('cy') ?? 0,
}; };


const values = getFormValues(e.currentTarget.form!, { forceNumberValues: true });
const form = e.currentTarget.form as HTMLFormElement
const values = getFormValues(form, { forceNumberValues: true });
const changed = Array.isArray(values.changed) ? values.changed : [values.changed]; const changed = Array.isArray(values.changed) ? values.changed : [values.changed];
const changedIndex = changed.indexOf(group.id); const changedIndex = changed.indexOf(group.id);
const rotate = Array.isArray(values.rotate) ? values.rotate : [values.rotate]; const rotate = Array.isArray(values.rotate) ? values.rotate : [values.rotate];
const translateX = Array.isArray(values.translateX) ? values.translateX : [values.translateX];
const currentRotate = rotate[changedIndex];
const currentTranslateX = translateX[changedIndex];
const currentTranslateY = e.currentTarget.value;
targetGroup.setAttribute('transform', `translate(${currentTranslateX} ${currentTranslateY}) rotate(${currentRotate} ${rotationCenter.cx} ${rotationCenter.cy})`);
}

const rotateGroup = (group: SvgGroup): ChangeEventHandler<HTMLInputElement> => (e) => {
const { current: svgPreviewWrapper } = svgPreviewRef;
if (!svgPreviewWrapper) {
return;
}

const svgPreviewRoot = svgPreviewWrapper.children[0] as SVGSVGElement | null;
if (!svgPreviewRoot) {
return;
}

const targetGroup = Array.from(svgPreviewRoot.children).find(g => g.id === group.id) as SVGGraphicsElement;
if (!targetGroup) {
return;
}

const rotationCenterReference = Array.from(targetGroup.children).find(e => e.tagName === 'circle') as SVGCircleElement | undefined
const rotationCenter = {
cx: rotationCenterReference?.getAttribute?.('cx') ?? 0,
cy: rotationCenterReference?.getAttribute?.('cy') ?? 0,
};

const values = getFormValues(e.currentTarget.form!, { forceNumberValues: true });
setFormValues(e.currentTarget.form!, { rotateNumber: values.rotate });
const changed = Array.isArray(values.changed) ? values.changed : [values.changed];
const changedIndex = changed.indexOf(group.id);
const rotateNumber = Array.isArray(values.rotateNumber) ? values.rotateNumber : [values.rotateNumber];
const translateX = Array.isArray(values.translateX) ? values.translateX : [values.translateX]; const translateX = Array.isArray(values.translateX) ? values.translateX : [values.translateX];
const translateY = Array.isArray(values.translateY) ? values.translateY : [values.translateY]; const translateY = Array.isArray(values.translateY) ? values.translateY : [values.translateY];
const currentRotate = e.currentTarget.value;
const currentTranslateX = translateX[changedIndex];
const currentTranslateY = translateY[changedIndex];
targetGroup.setAttribute('transform', `translate(${currentTranslateX} ${currentTranslateY}) rotate(${currentRotate} ${rotationCenter.cx} ${rotationCenter.cy})`);
}

const rotateGroupThroughNumber = (group: SvgGroup): ChangeEventHandler<HTMLInputElement> => (e) => {
const { current: svgPreviewWrapper } = svgPreviewRef;
if (!svgPreviewWrapper) {
return;
}

const svgPreviewRoot = svgPreviewWrapper.children[0] as SVGSVGElement | null;
if (!svgPreviewRoot) {
return;
}

const targetGroup = Array.from(svgPreviewRoot.children).find(g => g.id === group.id) as SVGGraphicsElement;
if (!targetGroup) {
return;
const currentRotate = e.currentTarget.name.startsWith('rotate') ? e.currentTarget.valueAsNumber : rotate[changedIndex];
const currentTranslateX = e.currentTarget.name.startsWith('translateX') ? e.currentTarget.valueAsNumber : translateX[changedIndex];
const currentTranslateY = e.currentTarget.name.startsWith('translateY') ? e.currentTarget.valueAsNumber : translateY[changedIndex];
if (e.currentTarget.name === 'rotate') {
setFormValues(form, {
rotateNumber: rotate,
})
} else if (e.currentTarget.name === 'rotateNumber') {
setFormValues(form, {
rotate: rotateNumber,
})
} }

const rotationCenterReference = Array.from(targetGroup.children).find(e => e.tagName === 'circle') as SVGCircleElement | undefined
const rotationCenter = {
cx: rotationCenterReference?.getAttribute?.('cx') ?? 0,
cy: rotationCenterReference?.getAttribute?.('cy') ?? 0,
};

const values = getFormValues(e.currentTarget.form!, { forceNumberValues: true });
setFormValues(e.currentTarget.form!, { rotate: values.rotateNumber });
const changed = Array.isArray(values.changed) ? values.changed : [values.changed];
const changedIndex = changed.indexOf(group.id);
const translateX = Array.isArray(values.translateX) ? values.translateX : [values.translateX];
const translateY = Array.isArray(values.translateY) ? values.translateY : [values.translateY];
const currentRotate = e.currentTarget.value;
const currentTranslateX = translateX[changedIndex];
const currentTranslateY = translateY[changedIndex];
targetGroup.setAttribute('transform', `translate(${currentTranslateX} ${currentTranslateY}) rotate(${currentRotate} ${rotationCenter.cx} ${rotationCenter.cy})`); targetGroup.setAttribute('transform', `translate(${currentTranslateX} ${currentTranslateY}) rotate(${currentRotate} ${rotationCenter.cx} ${rotationCenter.cy})`);
} }


@@ -304,19 +216,48 @@ const useAnimationForm = () => {


const updateTime: ChangeEventHandler<HTMLInputElement> = (e) => { const updateTime: ChangeEventHandler<HTMLInputElement> = (e) => {
const value = e.currentTarget.valueAsNumber; const value = e.currentTarget.valueAsNumber;
const form = e.currentTarget.form as HTMLFormElement
const form = e.currentTarget.form as HTMLFormElement;
const theSavedSvgKeyframes = savedSvgKeyframes ?? [];
const sortedSavedSvgKeyframes = theSavedSvgKeyframes.sort((k1, k2) => k1.time - k2.time);
const previousKeyframe =
[...sortedSavedSvgKeyframes].reverse().find((k) => k.time <= value)
?? sortedSavedSvgKeyframes[0];
const nextKeyframe =
sortedSavedSvgKeyframes.find((k) => k.time > value)
?? sortedSavedSvgKeyframes.slice(-1)[0];
const keyframe = savedSvgKeyframes?.find((k) => k.time === value); const keyframe = savedSvgKeyframes?.find((k) => k.time === value);
const hasKeyframeRange = (previousKeyframe || nextKeyframe);
if (!keyframe) { if (!keyframe) {
setEnabledGroupIds([]);
setFormValues(form, {
setEnabledGroupIds(svgGroups?.map(g => g.id));
// todo interpolate across individual keyframes
// get previous keyframe with respect to group
// get next keyframe with respect to group
// |----------------|
// |-----|

const interpolatedKeyframe = previousKeyframe;
const interpolatedValues = {
changed: svgGroups?.map(() => false), changed: svgGroups?.map(() => false),
translateX: svgGroups?.map(() => 0),
translateY: svgGroups?.map(() => 0),
rotate: svgGroups?.map(() => 0),
rotateNumber: svgGroups?.map(() => 0),
})
translateX: svgGroups?.map((g) => {
if (!hasKeyframeRange) {
return 0;
}
})
}
setTimeout(() => {
// todo set interpolation
setFormValues(form, {
changed: svgGroups?.map(() => false),
translateX: svgGroups?.map(() => 0),
translateY: svgGroups?.map(() => 0),
rotate: svgGroups?.map(() => 0),
rotateNumber: svgGroups?.map(() => 0),
});
setEnabledGroupIds([]);
});
return; return;
} }

const changed = keyframe.attributes.map((a) => a.group.id); const changed = keyframe.attributes.map((a) => a.group.id);
setEnabledGroupIds(changed); setEnabledGroupIds(changed);
setTimeout(() => { setTimeout(() => {
@@ -338,7 +279,6 @@ const useAnimationForm = () => {
loadSvgFile, loadSvgFile,
svgPreviewRef, svgPreviewRef,
svgGroups, svgGroups,
svgAnimationIdsListId,
selectNewAnimationId, selectNewAnimationId,
proposeNewAnimationId, proposeNewAnimationId,
cancelAnimationIdChange, cancelAnimationIdChange,
@@ -346,10 +286,7 @@ const useAnimationForm = () => {
blurAnimationIdInput, blurAnimationIdInput,
addKeyframeAttribute, addKeyframeAttribute,
enabledGroupIds, enabledGroupIds,
rotateGroup,
rotateGroupThroughNumber,
translateXGroup,
translateYGroup,
handleAttributeValueChange,
handleFormSubmit, handleFormSubmit,
savedSvgKeyframes, savedSvgKeyframes,
updateTime, updateTime,
@@ -364,7 +301,6 @@ const IndexPage = () => {
svgPreviewRef, svgPreviewRef,
svgGroups, svgGroups,
svgAnimations, svgAnimations,
svgAnimationIdsListId,
selectNewAnimationId, selectNewAnimationId,
proposeNewAnimationId, proposeNewAnimationId,
isNewAnimationId, isNewAnimationId,
@@ -373,10 +309,7 @@ const IndexPage = () => {
blurAnimationIdInput, blurAnimationIdInput,
addKeyframeAttribute, addKeyframeAttribute,
enabledGroupIds, enabledGroupIds,
rotateGroup,
rotateGroupThroughNumber,
translateXGroup,
translateYGroup,
handleAttributeValueChange,
handleFormSubmit, handleFormSubmit,
savedSvgKeyframes, savedSvgKeyframes,
updateTime, updateTime,
@@ -384,9 +317,6 @@ const IndexPage = () => {


return ( return (
<form className="lg:fixed lg:w-full lg:h-full flex flex-col" onSubmit={handleFormSubmit}> <form className="lg:fixed lg:w-full lg:h-full flex flex-col" onSubmit={handleFormSubmit}>
<datalist id="translationTickMarks">
<option value="0">0</option>
</datalist>
<datalist id="rotationTickMarks"> <datalist id="rotationTickMarks">
<option value="-360">-360</option> <option value="-360">-360</option>
<option value="-270">-270</option> <option value="-270">-270</option>
@@ -507,12 +437,24 @@ const IndexPage = () => {
</div> </div>
<div <div
style={{ style={{
width: 560
width: 700
}} }}
> >
{ {
Array.isArray(svgGroups) && ( Array.isArray(svgGroups) && (
<>
<div className="grid grid-cols-10">
<div className="col-span-4">
Group
</div>
<div>
X
</div>
<div>
Y
</div>
<div className="col-span-4">
Rotate
</div>
{svgGroups.map((group) => { {svgGroups.map((group) => {
return ( return (
<fieldset <fieldset
@@ -522,88 +464,84 @@ const IndexPage = () => {
<legend className="sr-only"> <legend className="sr-only">
{group.name} {group.name}
</legend> </legend>
<div className="flex">
<div className="flex-auto">
<label
className="whitespace-nowrap"
>
<input type="checkbox" name="changed" value={group.id} onChange={addKeyframeAttribute} />
{' '}
<span>
{group.name}
</span>
</label>
</div>
<label
className="w-16 block"
>
<span className="sr-only">Translate X</span>
<input
className="w-full block text-right"
type="number"
name="translateX"
min="-360" max="360"
defaultValue="0"
step="any"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={translateXGroup(group)}
list="translationTickMarks"
/>
</label>
<label
className="w-16 block"
>
<span className="sr-only">Translate Y</span>
<input
className="w-full block text-right"
type="number"
name="translateY"
min="-360" max="360"
defaultValue="0"
step="any"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={translateYGroup(group)}
list="translationTickMarks"
/>
</label>
<label
className="w-40 block"
>
<span className="sr-only">Rotate</span>
<input
className="w-full block"
type="range"
name="rotate"
min="-360"
max="360"
defaultValue="0"
step="5"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={rotateGroup(group)}
list="rotationTickMarks"
/>
</label>
<div className="col-span-4">
<label <label
className="w-12 block"
className="whitespace-nowrap"
> >
<span className="sr-only">Rotate</span>
<input
className="w-full block text-right"
type="number"
name="rotateNumber"
min="-360"
max="360"
defaultValue="0"
step="5"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={rotateGroupThroughNumber(group)}
/>
<input type="checkbox" name="changed" value={group.id} onChange={addKeyframeAttribute} />
{' '}
<span>
{group.name}
</span>
</label> </label>
</div> </div>
<label
className="block"
>
<span className="sr-only">Translate X</span>
<input
className="w-full block text-right"
type="number"
name="translateX"
min="-360" max="360"
defaultValue="0"
step="any"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={handleAttributeValueChange(group)}
/>
</label>
<label
className="block"
>
<span className="sr-only">Translate Y</span>
<input
className="w-full block text-right"
type="number"
name="translateY"
min="-360" max="360"
defaultValue="0"
step="any"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={handleAttributeValueChange(group)}
/>
</label>
<label
className="block col-span-3"
>
<span className="sr-only">Rotate</span>
<input
className="w-full block"
type="range"
name="rotate"
min="-360"
max="360"
defaultValue="0"
step="5"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={handleAttributeValueChange(group)}
list="rotationTickMarks"
/>
</label>
<label
className="block"
>
<span className="sr-only">Rotate</span>
<input
className="w-full block text-right"
type="number"
name="rotateNumber"
min="-360"
max="360"
defaultValue="0"
step="5"
disabled={!enabledGroupIds?.includes(group.name)}
onChange={handleAttributeValueChange(group)}
/>
</label>
</fieldset> </fieldset>
) )
})} })}
</>
</div>
) )
} }
</div> </div>


Loading…
Cancel
Save