|
|
@@ -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 && ( |
|
|
|