소스 검색

Extract some components from animation form

Define separate components and modules for reducing side of form source code.
master
부모
커밋
54e0ad6da4
5개의 변경된 파일104개의 추가작업 그리고 59개의 파일을 삭제
  1. +1
    -1
      tools/animation-workbench/app/components/Button/index.tsx
  2. +52
    -0
      tools/animation-workbench/app/components/ComboBox/index.tsx
  3. +2
    -2
      tools/animation-workbench/app/components/FileButton/index.tsx
  4. +20
    -0
      tools/animation-workbench/app/modules/file.ts
  5. +29
    -56
      tools/animation-workbench/app/routes/index.tsx

+ 1
- 1
tools/animation-workbench/app/components/Button/index.tsx 파일 보기

@@ -5,7 +5,7 @@ export interface ButtonProps extends Omit<HTMLProps<HTMLButtonElement>, 'type'>
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(({
className,
className = '',
...etcProps
}, ref) => (
<button


+ 52
- 0
tools/animation-workbench/app/components/ComboBox/index.tsx 파일 보기

@@ -0,0 +1,52 @@
import {FormEventHandler, forwardRef, HTMLProps, ReactNode, useId} from 'react';

export interface ComboBoxProps extends Omit<HTMLProps<HTMLInputElement>, 'list'> {
onOptionSelect?: FormEventHandler<HTMLInputElement>;
onCustomValueInput?: FormEventHandler<HTMLInputElement>;
children?: ReactNode;
}

export const ComboBox = forwardRef<HTMLInputElement, ComboBoxProps>(({
onOptionSelect,
children,
className = '',
onInput,
onCustomValueInput,
...etcProps
}, ref) => {
const id = useId();
const handleInput: FormEventHandler<HTMLInputElement> = (e) => {
onInput?.(e);
const nativeEvent = e.nativeEvent as unknown as { inputType?: string };
switch (nativeEvent.inputType) {
case 'deleteContentBackward':
case 'deleteContentForward':
case 'insertText':
// we type into the input
onCustomValueInput?.(e);
return;
default:
break;
// we select from the datalist

}
onOptionSelect?.(e);
}

return (
<>
<datalist
id={id}
>
{children}
</datalist>
<input
{...etcProps}
ref={ref}
list={id}
className={`w-full block h-12 px-4 ${className}`}
onInput={handleInput}
/>
</>
)
})

+ 2
- 2
tools/animation-workbench/app/components/FileButton/index.tsx 파일 보기

@@ -1,11 +1,11 @@
import {forwardRef, HTMLProps, ReactNode} from 'react';

export interface FileButtonProps extends Omit<HTMLProps<HTMLInputElement>, 'type'> {
children: ReactNode;
children?: ReactNode;
}

export const FileButton = forwardRef<HTMLInputElement, FileButtonProps>(({
className,
className = '',
children,
...etcProps
}, ref) => (


+ 20
- 0
tools/animation-workbench/app/modules/file.ts 파일 보기

@@ -0,0 +1,20 @@
export const readFile = async (file: File) => {
const fileReader = new FileReader()

return new Promise<string>((resolve, reject) => {
fileReader.addEventListener('load', (e) => {
if (!e.target) {
reject(new Error('Invalid target'))
return;
}

resolve(e.target.result as string);
})

fileReader.addEventListener('error', () => {
reject(new Error('Error reading file'))
});

fileReader.readAsText(file);
});
}

+ 29
- 56
tools/animation-workbench/app/routes/index.tsx 파일 보기

@@ -9,7 +9,9 @@ import {
} from 'react';
import { getFormValues, setFormValues } from '@theoryofnekomata/formxtra';
import { Button } from '~/components/Button';
import {FileButton} from '~/components/FileButton';
import { FileButton } from '~/components/FileButton';
import * as FileModule from '~/modules/file';
import {ComboBox} from '~/components/ComboBox';

type SvgGroup = {
id: string;
@@ -39,30 +41,8 @@ const useAnimationForm = () => {
const [savedSvgKeyframes, setSavedSvgKeyframes] = useState<Keyframe[]>();

const svgAnimationIdsListId = useId();

const svgPreviewRef = useRef<HTMLDivElement>(null);

const readFile = async (file: File) => {
const fileReader = new FileReader()

return new Promise<string>((resolve, reject) => {
fileReader.addEventListener('load', (e) => {
if (!e.target) {
reject(new Error('Invalid target'))
return;
}

resolve(e.target.result as string);
})

fileReader.addEventListener('error', () => {
reject(new Error('Error reading file'))
});

fileReader.readAsText(file);
});
}

const loadSvgFile: ChangeEventHandler<HTMLInputElement> = async (e) => {
const filePicker = e.currentTarget;
if (!filePicker.files) {
@@ -74,29 +54,22 @@ const useAnimationForm = () => {
}
setLoadedFileName(file.name);
setIsNewAnimationId(false);
const result = await readFile(file);
const result = await FileModule.readFile(file);
setLoadedFileContents(result);
filePicker.value = ''
}

const handleAnimationIdInput: FormEventHandler<HTMLInputElement> = (e) => {
const nativeEvent = e.nativeEvent as unknown as { inputType?: string };
switch (nativeEvent.inputType) {
case 'deleteContentBackward':
case 'deleteContentForward':
case 'insertText':
// we type into the input
setIsNewAnimationId(true);
break;
default:
// we select from the datalist
const target = e.currentTarget;
setIsNewAnimationId(false);
setCurrentAnimationId(e.currentTarget.value);
setTimeout(() => {
target.blur();
});
}
const selectNewAnimationId: FormEventHandler<HTMLInputElement> = (e) => {
const target = e.currentTarget;
setIsNewAnimationId(false);
setCurrentAnimationId(e.currentTarget.value);
setTimeout(() => {
target.blur();
});
}

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

const cancelAnimationIdChange: MouseEventHandler<HTMLButtonElement> = (e) => {
@@ -325,6 +298,8 @@ const useAnimationForm = () => {
default:
break;
}

console.log(values);
}

const updateTime: ChangeEventHandler<HTMLInputElement> = (e) => {
@@ -364,7 +339,8 @@ const useAnimationForm = () => {
svgPreviewRef,
svgGroups,
svgAnimationIdsListId,
handleAnimationIdInput,
selectNewAnimationId,
proposeNewAnimationId,
cancelAnimationIdChange,
focusAnimationIdInput,
blurAnimationIdInput,
@@ -389,7 +365,8 @@ const IndexPage = () => {
svgGroups,
svgAnimations,
svgAnimationIdsListId,
handleAnimationIdInput,
selectNewAnimationId,
proposeNewAnimationId,
isNewAnimationId,
cancelAnimationIdChange,
focusAnimationIdInput,
@@ -464,27 +441,23 @@ const IndexPage = () => {
&& (
<div className="h-full flex flex-col">
<div className="flex gap-4">
<datalist
id={svgAnimationIdsListId}
>
{svgAnimations?.map(s => (
<option key={s}>{s}</option>
))}
</datalist>
<label className="block w-full">
<span className="sr-only">
Animation ID
</span>
<input
list={svgAnimationIdsListId}
<ComboBox
name="animationId"
className="w-full block h-full px-4 h-12"
defaultValue={svgAnimations?.[0] ?? ''}
onInput={handleAnimationIdInput}
onOptionSelect={selectNewAnimationId}
onCustomValueInput={proposeNewAnimationId}
onFocus={focusAnimationIdInput}
onBlur={blurAnimationIdInput}
autoComplete="off"
/>
>
{svgAnimations?.map(s => (
<option key={s}>{s}</option>
))}
</ComboBox>
</label>
{
isNewAnimationId && (


불러오는 중...
취소
저장