Skip to content

Commit

Permalink
fix: Update values from outer state in form component
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Dec 20, 2024
1 parent 540e0e7 commit 4e7acda
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 68 deletions.
32 changes: 16 additions & 16 deletions packages/app/src/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Button, Input } from "@nextui-org/react";
import cn from "clsx";
import { forwardRef, useImperativeHandle } from "react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { Controller } from "react-hook-form";
import type { InputProps } from "@nextui-org/input";
import type { ForwardedRef } from "react";
import type { Control } from "react-hook-form";

export interface FormRef {
setValue(key: string, value: string | number): void;
}

interface FormProps<T extends FieldRecord> {
className?: string;
fields: T;
Expand All @@ -28,20 +23,29 @@ type FieldMap<T extends FieldRecord = FieldRecord> = {
[K in keyof T]: T[K] extends { value: infer V } ? V : never;
};

function FormComponent<T extends FieldRecord>(
{ className, fields, onSubmit, submit }: FormProps<T>,
ref: ForwardedRef<FormRef>,
) {
export function Form<T extends FieldRecord>({
className,
fields,
onSubmit,
submit,
}: FormProps<T>) {
const entries = Object.entries(fields);

const { handleSubmit, setValue, control } = useForm<FieldMap>({
const { handleSubmit, setValue, control, getValues } = useForm<FieldMap>({
defaultValues: entries.reduce<FieldMap>((acc, [key, field]) => {
acc[key] = field.value;
return acc;
}, {}),
});

useImperativeHandle(ref, () => ({ setValue }), [setValue]);
useEffect(() => {
const currentValues = getValues();
Object.entries(fields).forEach(([name, field]) => {
if (currentValues[name] !== field.value) {
setValue(name, field.value);
}
});
}, [fields]);

return (
<form
Expand Down Expand Up @@ -78,10 +82,6 @@ function FormComponent<T extends FieldRecord>(
);
}

export const Form = forwardRef(FormComponent) as <T extends FieldRecord>(
props: FormProps<T> & { ref?: ForwardedRef<FormRef> },
) => ReturnType<typeof FormComponent>;

export function FormInput({
name,
control,
Expand Down
25 changes: 12 additions & 13 deletions packages/app/src/components/PlayerControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ import { useSeekbar } from "../hooks/useSeekbar";
import type { ReactNode, RefObject } from "react";

export function PlayerControls() {
const ready = usePlayerSelector((player) => player.ready);
if (!ready) {
return null;
}
return (
<div className="flex flex-col gap-4 overflow-hidden">
<div className="flex">
<PlayButton />
</div>
<Seekbar />
<Time />
<div className="p-3 rounded-md bg-default-100">
<Time />
<Seekbar />
</div>
<Tracks />
</div>
);
Expand Down Expand Up @@ -59,11 +57,11 @@ function Seekbar() {
>
{hms(seekbar.value)}
</Tooltip>
<div className="flex items-center rounded-lg overflow-hidden mb-2">
<div className="h-2 bg-gray-100 w-full" />
<div className="relative flex items-center">
<div className="h-2 bg-default-200 w-full" />
<div
className={cn(
"h-2 absolute left-0 right-0 bg-gray-200 origin-left opacity-0 transition-opacity",
"h-2 absolute left-0 right-0 bg-default-300 origin-left opacity-0 transition-opacity",
seekbar.hover && "opacity-100",
)}
style={{
Expand Down Expand Up @@ -126,17 +124,18 @@ function CuePoints() {
const seekableStart = usePlayerSelector((player) => player.seekableStart);

return (
<div className="relative h-2 bg-gray-100 rounded-lg">
<div className="relative h-4 bg-gray-100 rounded-lg">
{cuePoints.map((cuePoint) => {
return (
<div
key={cuePoint}
style={{
left: `${((cuePoint - seekableStart) / (duration - seekableStart)) * 100}%`,
}}
className="absolute -translate-x-1/2 top-0 w-2 h-2 rounded-full bg-yellow-500"
className="absolute -translate-x-1/2 flex items-center flex-col"
>
<div className="absolute -translate-x-1/2 w-[2px] left-1/2 h-4 bottom-0 bg-yellow-500" />
<div className="w-[2px] h-2 bg-yellow-500" />
<div className="w-2 h-2 rounded-full bg-yellow-500" />
</div>
);
})}
Expand All @@ -151,7 +150,7 @@ function Time() {
const live = usePlayerSelector((player) => player.live);

return (
<div className="flex text-sm">
<div className="flex text-sm mb-2">
{hms(time)}
<div className="grow" />
{live ? `${hms(seekableStart)} - ${hms(duration)}` : `${hms(duration)}`}
Expand Down
101 changes: 62 additions & 39 deletions packages/app/src/routes/(dashboard)/_layout/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@ import {
Tabs,
} from "@nextui-org/react";
import { createFileRoute } from "@tanstack/react-router";
import { useRef, useState } from "react";
import { useEffect, useState } from "react";
import { CodeEditor } from "../../../components/CodeEditor";
import { Form } from "../../../components/Form";
import { Player } from "../../../components/Player";
import { PlayerControls } from "../../../components/PlayerControls";
import { PlayerStats } from "../../../components/PlayerStats";
import { ScrollCard } from "../../../components/ScrollCard";
import { PlayerProvider, WithPlayer } from "../../../context/PlayerContext";
import {
PlayerProvider,
usePlayerSelector,
WithPlayer,
} from "../../../context/PlayerContext";
import { useSwaggerSchema } from "../../../hooks/useSwaggerSchema";
import type { FormRef } from "../../../components/Form";

export const Route = createFileRoute("/(dashboard)/_layout/player")({
component: RouteComponent,
});

function RouteComponent() {
const formRef = useRef<FormRef>(null);
const [url, setUrl] = useState("");
const [error, setError] = useState<string | null>(null);

Expand All @@ -48,7 +50,6 @@ function RouteComponent() {

const data = await response.json();
if (response.ok) {
formRef.current?.setValue("url", data.url);
setUrl(data.url);
} else {
setError(data);
Expand All @@ -65,40 +66,7 @@ function RouteComponent() {
</div>
</div>
<WithPlayer>
<Tabs
classNames={{
panel: "grow p-0",
}}
>
<Tab title="Config">
<ScrollCard>
<Form
ref={formRef}
submit="Play"
fields={{
url: {
label: "URL",
type: "string",
value: url,
},
}}
onSubmit={async (values) => {
setUrl(values.url);
}}
/>
</ScrollCard>
</Tab>
<Tab title="Stats">
<ScrollCard>
<PlayerStats />
</ScrollCard>
</Tab>
<Tab title="Controls">
<ScrollCard>
<PlayerControls />
</ScrollCard>
</Tab>
</Tabs>
<PlayerTabs url={url} setUrl={setUrl} />
</WithPlayer>
</div>
</PlayerProvider>
Expand Down Expand Up @@ -126,3 +94,58 @@ function RouteComponent() {
</div>
);
}

function PlayerTabs({
url,
setUrl,
}: {
url: string;
setUrl: (value: string) => void;
}) {
const [selected, setSelected] = useState<string | number>("config");
const ready = usePlayerSelector((player) => player.ready);

useEffect(() => {
if (ready) {
setSelected("controls");
}
}, [ready]);

return (
<Tabs
classNames={{
panel: "grow p-0",
}}
selectedKey={selected}
onSelectionChange={setSelected}
>
<Tab key="config" title="Config">
<ScrollCard>
<Form
submit="Play"
fields={{
url: {
label: "URL",
type: "string",
value: url,
},
}}
onSubmit={async (values) => {
setUrl(values.url);
}}
/>
</ScrollCard>
</Tab>
<Tab key="stats" title="Stats">
<ScrollCard>
<PlayerStats />
</ScrollCard>
</Tab>
<Tab key="controls" title="Controls" isDisabled={!ready}>
<ScrollCard>
<PlayerControls />
</ScrollCard>
</Tab>
</Tabs>
);
}

0 comments on commit 4e7acda

Please sign in to comment.