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,
...etcProps
}, ref) => (
<label>
<label className="contents">
<input
{...etcProps}
ref={ref}


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

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

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

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

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

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

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

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

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

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

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

const focusAnimationIdInput: FocusEventHandler<HTMLInputElement> = (e) => {
@@ -123,40 +124,7 @@ const useAnimationForm = () => {
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;
if (!svgPreviewWrapper) {
return;
@@ -178,82 +146,26 @@ const useAnimationForm = () => {
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 changedIndex = changed.indexOf(group.id);
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 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})`);
}

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

const updateTime: ChangeEventHandler<HTMLInputElement> = (e) => {
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 hasKeyframeRange = (previousKeyframe || nextKeyframe);
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),
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;
}

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

return (
<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">
<option value="-360">-360</option>
<option value="-270">-270</option>
@@ -507,12 +437,24 @@ const IndexPage = () => {
</div>
<div
style={{
width: 560
width: 700
}}
>
{
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) => {
return (
<fieldset
@@ -522,88 +464,84 @@ const IndexPage = () => {
<legend className="sr-only">
{group.name}
</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
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>
</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>
)
})}
</>
</div>
)
}
</div>


Loading…
Cancel
Save