Created
January 29, 2024 20:17
-
-
Save polooner/4adc157bbc6b9c51b442f47f1dfaba80 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useMakeKlips } from '@/lib/hooks/use-makeklips'; | |
import { createTimeframe } from '@/lib/utils'; | |
import { MakeKlipsRefType } from '@/types/makeklips'; | |
import { | |
ChangeEvent, | |
KeyboardEvent, | |
MouseEvent, | |
SyntheticEvent, | |
forwardRef, | |
useEffect, | |
useRef, | |
useState, | |
} from 'react'; | |
import { KeyFrame } from '@/types'; | |
interface CaptionContainerProps { | |
keyFrames: KeyFrame[]; | |
} | |
//TODO: transform frames to timestamps, use createTimeframe() | |
export const Captions = forwardRef<MakeKlipsRefType, CaptionContainerProps>( | |
({ keyFrames, ...rest }, ref) => { | |
//TODO: use the global hook to handle word change | |
const { getKeyFrames } = useMakeKlips(); | |
// setKeyFrames(keyFrames); | |
const frames = getKeyFrames(); | |
return ( | |
//TODO: set height to only be the height of player, scroll overflow | |
<div | |
{...rest} | |
ref={ref} | |
className='flex-1 gap-2 overflow-scroll flex flex-col bg-zinc-950 text-white p-4 overflow-y-auto max-h-full' | |
> | |
{frames | |
.sort((a, b) => a.timeFrom - b.timeFrom) // Sort keyFrames by timeFrom property | |
.map((keyFrame, index) => ( | |
<CaptionBox | |
key={index} | |
id={keyFrame.id} | |
timeFrom={keyFrame.timeFrom} | |
timeTo={keyFrame.timeTo} | |
text={keyFrame.text} | |
{...keyFrame.data} | |
/> | |
))} | |
</div> | |
); | |
} | |
); | |
Captions.displayName = 'Captions'; | |
function CaptionBox({ id, timeFrom, timeTo, text }: KeyFrame) { | |
console.log(text); | |
const [value, setValue] = useState(text); | |
const textareaRef = useRef<HTMLTextAreaElement>(null); | |
// const [cursorPosition, setCursorPosition] = useState(0); | |
const { getKeyFrames, updateKeyFrame, setKeyFrames } = useMakeKlips(); | |
// const handleCursorChange = (e: KeyboardEvent) => { | |
// const input = textareaRef.current; | |
// if (input) { | |
// const cursorPosition = input.selectionStart; | |
// const textBeforeCursor = input.value.substring(0, cursorPosition); | |
// const lineCount = textBeforeCursor.split('\n').length; | |
// setCursorPosition(lineCount); | |
// console.log(cursorPosition); | |
// } | |
// }; | |
//TODO: on backspace, pop the sentence and append to first if last character is the newline sequence (\r\n) | |
//TODO: on enter, sentence into another array element | |
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => { | |
//FIXME: text and other elements are a render behind | |
console.log(e.target.value); | |
setValue(e.target.value); | |
console.log(e.target.value.includes('\n\n')); | |
// if pushed a double new line, just cut at the end of double \n and create a new keyframe | |
if (e.target.value.includes('\n\n')) { | |
// can create an empty string. should this be prevented and alerted to the user? | |
//FIXME: we see a new object in console but no new CaptionBox appears | |
const valueCutIntoArray = e.target.value.split('\n\n'); | |
setValue(valueCutIntoArray[0]); | |
updateKeyFrame(String(id), { | |
text: valueCutIntoArray[0], | |
timeFrom, | |
timeTo, | |
id, | |
}); | |
//TODO: calculate % | |
//TODO: set timeFrom to receive the amount of frames corresponding the the % that the text is of the full text | |
//TODO: prevent user from infinitely pressing Enter | |
console.log(valueCutIntoArray[0].length + valueCutIntoArray[1].length); | |
setKeyFrames((prevKeyFrames) => { | |
return prevKeyFrames.concat({ | |
id: prevKeyFrames.length + 1, | |
text: valueCutIntoArray[1], | |
timeFrom: 160, | |
timeTo: 180, | |
}); | |
}); | |
console.log(valueCutIntoArray); | |
} else { | |
updateKeyFrame(String(id), { | |
text: value, | |
timeFrom, | |
timeTo, | |
id, | |
}); | |
} | |
const frames = getKeyFrames(); | |
console.log(frames); | |
}; | |
return ( | |
<div className='p-4 hover:bg-zinc-900 w-full rounded-2xl flex justify-between items-center !bg-zinc-900 flex-row'> | |
<div style={{ direction: 'ltr' }} className='w-full'> | |
<span | |
suppressHydrationWarning | |
className='text-xs px-2 font-sans block text-slate-500 font-display' | |
> | |
{timeFrom}-{timeTo} | |
</span> | |
{/* TODO: stretch textarea infinitely */} | |
<textarea | |
ref={textareaRef} | |
className='resize-none w-full text-white bg-transparent overflow-hidden p-2 font-inherit' | |
value={value} | |
onChange={(e) => handleChange(e)} | |
// onKeyUp={handleCursorChange} | |
// onClick={handleCursorChange} | |
// onSelect={handleCursorChange} | |
/> | |
</div> | |
</div> | |
); | |
} | |
export default Captions; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use client'; | |
import { useRef, type ReactNode } from 'react'; | |
import { type StoreApi } from 'zustand'; | |
import { UseBoundStoreWithEqualityFn } from 'zustand/traditional'; | |
import { Provider } from '../lib/contexts/MKStoreContext'; | |
import { createMKStore } from '../lib/store'; | |
import { KeyFrame, MakeKlipsState } from '@/types'; | |
export function MakeKlipsProvider({ | |
children, | |
initialKeyFrames, | |
}: { | |
children: ReactNode; | |
initialKeyFrames: KeyFrame[]; | |
}) { | |
const storeRef = useRef<UseBoundStoreWithEqualityFn< | |
StoreApi<MakeKlipsState> | |
> | null>(null); | |
if (!storeRef.current) { | |
storeRef.current = createMKStore({ | |
keyFrames: initialKeyFrames, | |
}); | |
} | |
return <Provider value={storeRef.current}>{children}</Provider>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useMemo, useRef } from 'react'; | |
import { useStoreApi } from './use-store'; | |
import type { MakeKlipsInstance, Instance, KeyFrame } from '../../types'; | |
/** | |
* Hook for accessing the captions instance. | |
* | |
* | |
* @returns ClipsInstance | |
*/ | |
export function useMakeKlips< | |
KeyFrameType extends KeyFrame = KeyFrame | |
>(): ClipsInstance<KeyFrameType> { | |
const store = useStoreApi(); | |
const getKeyFrames = useCallback<Instance.GetKeyFrames<KeyFrameType>>(() => { | |
return store.getState().keyFrames.map((k) => ({ ...k })) as KeyFrameType[]; | |
}, []); | |
const getKeyFrame = useCallback<Instance.GetKeyFrame<KeyFrameType>>((id) => { | |
return store.getState().frameLookup.get(id) as KeyFrameType; | |
}, []); | |
// this is used to handle multiple syncronous setNodes calls | |
const setKeyFramesData = useRef<KeyFrame[]>(); | |
const setKeyFramesTimeout = useRef<ReturnType<typeof setTimeout>>(); | |
const setKeyFrames = useCallback<Instance.SetKeyFrames<KeyFrameType>>( | |
(payload) => { | |
const { | |
keyFrames = [], | |
setKeyFrames, | |
} = store.getState(); | |
const nextKeyFrames = | |
typeof payload === 'function' | |
? payload((setKeyFramesData.current as KeyFrameType[]) || keyFrames) | |
: payload; | |
setKeyFramesData.current = nextKeyFrames; | |
if (setKeyFramesTimeout.current) { | |
clearTimeout(setKeyFramesTimeout.current); | |
} | |
setKeyFramesTimeout.current = setTimeout(() => { | |
setKeyFrames(nextKeyFrames); | |
setKeyFramesData.current = undefined; | |
}, 0); | |
}, | |
[] | |
); | |
const addKeyFrames = useCallback<Instance.AddKeyFrames<KeyFrame>>( | |
(payload) => { | |
const keyFrames = Array.isArray(payload) ? payload : [payload]; | |
const { | |
keyFrames: currentKeyFrames, | |
setKeyFrames, | |
} = store.getState(); | |
const nextKeyFrames = [...currentKeyFrames, ...keyFrames]; | |
setKeyFrames(nextKeyFrames); | |
}, | |
[] | |
); | |
const toObject = useCallback<Instance.ToObject<KeyFrameType>>(() => { | |
const { keyFrames = [] } = store.getState(); | |
return { | |
keyFrames: keyFrames.map((k) => ({ ...k })) as KeyFrameType[], | |
}; | |
}, []); | |
const updateKeyFrame = useCallback<Instance.UpdateKeyFrame<KeyFrameType>>( | |
(id, keyFrameUpdate, options = { replace: true }) => { | |
setKeyFrames((prevKeyFrames: KeyFrameType[]) => | |
prevKeyFrames.map((keyFrame) => { | |
if (String(keyFrame.id) === id) { | |
const nextKeyFrame = | |
typeof keyFrameUpdate === 'function' | |
? (keyFrame as KeyFrameType) | |
: keyFrameUpdate; | |
return options.replace | |
? (nextKeyFrame as KeyFrameType) | |
: { ...keyFrame, ...nextKeyFrame }; | |
} | |
return keyFrame; | |
}) | |
); | |
}, | |
[setKeyFrames] | |
); | |
return useMemo(() => { | |
return { | |
getKeyFrames, | |
getKeyFrame, | |
setKeyFrames, | |
addKeyFrames, | |
toObject, | |
updateKeyFrame, | |
// deleteElements, | |
// getIntersectingKeyFrames, | |
// isKeyFrameIntersecting, | |
// updateKeyFrameData, | |
}; | |
}, [ | |
getKeyFrames, | |
getKeyFrame, | |
setKeyFrames, | |
addKeyFrames, | |
toObject, | |
updateKeyFrame, | |
// deleteElements, | |
// getIntersectingKeyFrames, | |
// isKeyFrameIntersecting, | |
// updateKeyFrameData, | |
]); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useContext, useMemo } from 'react'; | |
import { useStoreWithEqualityFn as useZustandStore } from 'zustand/traditional'; | |
import type { StoreApi } from 'zustand'; | |
import StoreContext from '../contexts/MKStoreContext'; | |
import type { MakeKlipsState } from '@/types/store'; | |
type ExtractState = StoreApi<CaptionsState> extends { | |
getState: () => infer T; | |
} | |
? T | |
: never; | |
/** | |
* Hook for accessing the internal store. | |
* | |
* @public | |
* @param selector | |
* @param equalityFn | |
* @returns The selected state slice | |
*/ | |
function useStore<StateSlice = ExtractState>( | |
selector: (state: MakeKlipsState) => StateSlice, | |
equalityFn?: (a: StateSlice, b: StateSlice) => boolean | |
) { | |
const store = useContext(StoreContext); | |
//TODO: implement ErrorMessages and pass them into the Error | |
if (store === null) { | |
throw new Error('Store is null in useStore'); | |
} | |
return useZustandStore(store, selector, equalityFn); | |
} | |
const useStoreApi = () => { | |
const store = useContext(StoreContext); | |
//TODO: implement ErrorMessages and pass them into the Error | |
if (store === null) { | |
throw new Error('Store is null in useStoreApi'); | |
} | |
return useMemo( | |
() => ({ | |
getState: store.getState, | |
setState: store.setState, | |
subscribe: store.subscribe, | |
destroy: store.destroy, | |
}), | |
[store] | |
); | |
}; | |
export { useStore, useStoreApi }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment