diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 42304bc..992e4b8 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -18,6 +18,15 @@ module.exports = {
},
plugins: ['import', 'prettier', 'unused-imports'],
rules: {
+ '@typescript-eslint/consistent-type-imports': 'error',
+ '@typescript-eslint/consistent-type-exports': 'error',
+ '@typescript-eslint/member-ordering': [
+ 'error',
+ {
+ interfaces: { order: 'alphabetically-case-insensitive' },
+ typeLiterals: { order: 'alphabetically-case-insensitive' },
+ },
+ ],
'import/first': 'error',
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts
index 7b78301..ec60df6 100644
--- a/electron/electron-env.d.ts
+++ b/electron/electron-env.d.ts
@@ -1,3 +1,5 @@
+import type { IpcRenderer } from 'electron';
+
///
declare namespace NodeJS {
interface ProcessEnv {
@@ -22,5 +24,5 @@ declare namespace NodeJS {
// Used in Renderer process, expose in `preload.ts`
interface Window {
- ipcRenderer: import('electron').IpcRenderer;
+ ipcRenderer: IpcRenderer;
}
diff --git a/package.json b/package.json
index 3cf1ccd..f8562e7 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@radix-ui/react-label": "2.0.2",
"@radix-ui/react-navigation-menu": "1.1.4",
"@radix-ui/react-select": "2.0.0",
+ "@radix-ui/react-separator": "1.0.3",
"@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-tooltip": "1.0.7",
"class-variance-authority": "0.7.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 69318aa..267d78e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ dependencies:
'@radix-ui/react-select':
specifier: 2.0.0
version: 2.0.0(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
+ '@radix-ui/react-separator':
+ specifier: 1.0.3
+ version: 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot':
specifier: 1.0.2
version: 1.0.2(@types/react@18.2.31)(react@18.2.0)
@@ -1227,6 +1230,27 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.31)(react@18.2.0)
dev: false
+ /@radix-ui/react-separator@1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.23.2
+ '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@18.2.31)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react': 18.2.31
+ '@types/react-dom': 18.2.14
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/@radix-ui/react-slot@1.0.2(@types/react@18.2.31)(react@18.2.0):
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies:
diff --git a/src/components/ConversionSettings/BitrateSelect.tsx b/src/components/ConversionSettings/BitrateSelect.tsx
new file mode 100644
index 0000000..1477b27
--- /dev/null
+++ b/src/components/ConversionSettings/BitrateSelect.tsx
@@ -0,0 +1,44 @@
+import { useTranslation } from 'react-i18next';
+import { codecSchema } from 'src/schemas/conversionSettings.schema';
+import { FormControl, FormItem, FormLabel } from '../ui/Form';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/Select';
+import type { Bitrate, Codec } from 'src/schemas/conversionSettings.schema';
+
+type BitrateSelectProps = {
+ codec: Codec;
+ onChange: (value: Bitrate) => void;
+ value: Bitrate;
+};
+
+const bitrateByCodec: Record = {
+ default: ['default'],
+ aac: ['default', '32k', '64k', '96k', '128k', '192k', '256k', '320k'],
+ ac3: ['default', '128k', '192k', '256k', '320k', '384k', '448k', '512k', '640k'],
+ eac3: ['default', '192k', '320k', '448k', '640k', '1024k', '2048k', '4096k'],
+};
+
+export const BitrateSelect = ({ codec, value, onChange }: BitrateSelectProps) => {
+ const { t } = useTranslation();
+ const options = bitrateByCodec[codec];
+ const isDisabled = codec === codecSchema.enum.default;
+
+ return (
+
+ {t('conversionSettings.bitrate.label')}
+
+
+ );
+};
diff --git a/src/components/ConversionSettings/ChannelsSelect.tsx b/src/components/ConversionSettings/ChannelsSelect.tsx
new file mode 100644
index 0000000..ff47990
--- /dev/null
+++ b/src/components/ConversionSettings/ChannelsSelect.tsx
@@ -0,0 +1,44 @@
+import { useTranslation } from 'react-i18next';
+import { codecSchema } from 'src/schemas/conversionSettings.schema';
+import { FormControl, FormItem, FormLabel } from '../ui/Form';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/Select';
+import type { Channels, Codec } from 'src/schemas/conversionSettings.schema';
+
+type ChannelsSelectProps = {
+ codec: Codec;
+ onChange: (value: Channels) => void;
+ value: Channels;
+};
+
+const channelsByCodec: Record = {
+ default: ['default'],
+ aac: ['default', '1', '2', '3', '4', '5', '6', '7', '8'],
+ ac3: ['default', '1', '2', '3', '4', '5', '6'],
+ eac3: ['default', '1', '2', '3', '4', '5', '6'],
+};
+
+export const ChannelsSelect = ({ codec, onChange, value }: ChannelsSelectProps) => {
+ const { t } = useTranslation();
+ const options = channelsByCodec[codec];
+ const isDisabled = codec === codecSchema.enum.default;
+
+ return (
+
+ {t('conversionSettings.channels.label')}
+
+
+ );
+};
diff --git a/src/components/ConversionSettings/CodecSelect.tsx b/src/components/ConversionSettings/CodecSelect.tsx
new file mode 100644
index 0000000..98cb9eb
--- /dev/null
+++ b/src/components/ConversionSettings/CodecSelect.tsx
@@ -0,0 +1,35 @@
+import { useTranslation } from 'react-i18next';
+import { codecSchema } from 'src/schemas/conversionSettings.schema';
+import { FormControl, FormItem, FormLabel } from '../ui/Form';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/Select';
+import type { Codec } from 'src/schemas/conversionSettings.schema';
+
+type CodecSelectProps = {
+ onChange: (value: Codec) => void;
+ value: Codec;
+};
+
+export const CodecSelect = ({ onChange, value }: CodecSelectProps) => {
+ const { t } = useTranslation();
+ const options = codecSchema.options.map(option => option);
+
+ return (
+
+ {t('conversionSettings.codec.label')}
+
+
+ );
+};
diff --git a/src/components/ConversionSettings/ConversionSettings.tsx b/src/components/ConversionSettings/ConversionSettings.tsx
new file mode 100644
index 0000000..15f025c
--- /dev/null
+++ b/src/components/ConversionSettings/ConversionSettings.tsx
@@ -0,0 +1,57 @@
+import { useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useConversionSettingsForm } from 'src/hooks/useConversionSettingsForm';
+import { bitrateSchema, channelsSchema, codecSchema, sampleRateSchema } from 'src/schemas/conversionSettings.schema';
+import { Form, FormField } from '../ui/Form';
+import { BitrateSelect } from './BitrateSelect';
+import { ChannelsSelect } from './ChannelsSelect';
+import { CodecSelect } from './CodecSelect';
+import { SampleRateSelect } from './SampleRateSelect';
+
+export const ConversionSettings = () => {
+ const { t } = useTranslation();
+ const form = useConversionSettingsForm({
+ defaultValues: {
+ codec: codecSchema.enum.default,
+ bitrate: bitrateSchema.enum.default,
+ sampleRate: sampleRateSchema.enum.default,
+ channels: channelsSchema.enum.default,
+ },
+ });
+ const { control, setValue, watch } = form;
+ const codec = watch('codec');
+
+ useEffect(() => {
+ setValue('bitrate', bitrateSchema.enum.default);
+ setValue('sampleRate', sampleRateSchema.enum.default);
+ setValue('channels', channelsSchema.enum.default);
+ }, [codec, setValue]);
+
+ return (
+
+
+ );
+};
diff --git a/src/components/ConversionSettings/SampleRateSelect.tsx b/src/components/ConversionSettings/SampleRateSelect.tsx
new file mode 100644
index 0000000..14f6ac9
--- /dev/null
+++ b/src/components/ConversionSettings/SampleRateSelect.tsx
@@ -0,0 +1,44 @@
+import { useTranslation } from 'react-i18next';
+import { codecSchema } from 'src/schemas/conversionSettings.schema';
+import { FormControl, FormItem, FormLabel } from '../ui/Form';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/Select';
+import type { Codec, SampleRate } from 'src/schemas/conversionSettings.schema';
+
+type SampleRateSelectProps = {
+ codec: Codec;
+ onChange: (value: SampleRate) => void;
+ value: SampleRate;
+};
+
+const sampleRateByCodec: Record = {
+ default: ['default'],
+ aac: ['default', '8000', '22050', '32000', '44100', '48000', '96000'],
+ ac3: ['default', '8000', '22050', '32000', '44100', '48000'],
+ eac3: ['default', '32000', '44100', '48000'],
+};
+
+export const SampleRateSelect = ({ codec, onChange, value }: SampleRateSelectProps) => {
+ const { t } = useTranslation();
+ const options = sampleRateByCodec[codec];
+ const isDisabled = codec === codecSchema.enum.default;
+
+ return (
+
+ {t('conversionSettings.sampleRate.label')}
+
+
+ );
+};
diff --git a/src/components/ConversionSettings/index.ts b/src/components/ConversionSettings/index.ts
new file mode 100644
index 0000000..1d29261
--- /dev/null
+++ b/src/components/ConversionSettings/index.ts
@@ -0,0 +1 @@
+export { ConversionSettings } from './ConversionSettings';
diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx
index edcf1c2..4274a34 100644
--- a/src/components/ui/Card.tsx
+++ b/src/components/ui/Card.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from 'src/lib/utils';
-import { PropsWithClassName } from './shadcn.types';
+import type { PropsWithClassName } from './ui.types';
const Card = React.forwardRef & PropsWithClassName>(
({ className, ...props }, ref) => (
diff --git a/src/components/ui/Form.tsx b/src/components/ui/Form.tsx
index 05cecbd..9d76bf1 100644
--- a/src/components/ui/Form.tsx
+++ b/src/components/ui/Form.tsx
@@ -1,10 +1,11 @@
-import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';
-import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from 'react-hook-form';
+import { Controller, FormProvider, useFormContext } from 'react-hook-form';
import { Label } from 'src/components/ui/Label';
import { cn } from 'src/lib/utils';
-import { PropsWithClassName } from './shadcn.types';
+import type { PropsWithClassName } from './ui.types';
+import type * as LabelPrimitive from '@radix-ui/react-label';
+import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
const Form = FormProvider;
@@ -65,7 +66,7 @@ const FormItem = React.forwardRef
-
+
);
},
diff --git a/src/components/ui/NavigationMenu.tsx b/src/components/ui/NavigationMenu.tsx
index 2a98d6f..de398e7 100644
--- a/src/components/ui/NavigationMenu.tsx
+++ b/src/components/ui/NavigationMenu.tsx
@@ -3,7 +3,7 @@ import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from 'src/lib/utils';
-import { PropsWithClassName } from './shadcn.types';
+import type { PropsWithClassName } from './ui.types';
const NavigationMenu = React.forwardRef<
React.ElementRef,
diff --git a/src/components/ui/Select.tsx b/src/components/ui/Select.tsx
index 492f20b..db262cf 100644
--- a/src/components/ui/Select.tsx
+++ b/src/components/ui/Select.tsx
@@ -2,7 +2,7 @@ import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import * as React from 'react';
import { cn } from 'src/lib/utils';
-import { PropsWithClassName } from './shadcn.types';
+import type { PropsWithClassName } from './ui.types';
const Select = SelectPrimitive.Root;
diff --git a/src/components/ui/Separator.tsx b/src/components/ui/Separator.tsx
new file mode 100644
index 0000000..906debd
--- /dev/null
+++ b/src/components/ui/Separator.tsx
@@ -0,0 +1,20 @@
+import * as SeparatorPrimitive from '@radix-ui/react-separator';
+import * as React from 'react';
+import { cn } from 'src/lib/utils';
+import type { PropsWithClassName } from './ui.types';
+
+const Separator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & PropsWithClassName
+>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
+
+));
+Separator.displayName = SeparatorPrimitive.Root.displayName;
+
+export { Separator };
diff --git a/src/components/ui/Tooltip.tsx b/src/components/ui/Tooltip.tsx
index a1a2581..5a3c73f 100644
--- a/src/components/ui/Tooltip.tsx
+++ b/src/components/ui/Tooltip.tsx
@@ -1,7 +1,7 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import * as React from 'react';
import { cn } from 'src/lib/utils';
-import { PropsWithClassName } from './shadcn.types';
+import type { PropsWithClassName } from './ui.types';
const TooltipProvider = TooltipPrimitive.Provider;
diff --git a/src/components/ui/shadcn.types.ts b/src/components/ui/ui.types.ts
similarity index 100%
rename from src/components/ui/shadcn.types.ts
rename to src/components/ui/ui.types.ts
diff --git a/src/hooks/useConversionSettingsForm.ts b/src/hooks/useConversionSettingsForm.ts
new file mode 100644
index 0000000..0fa3b90
--- /dev/null
+++ b/src/hooks/useConversionSettingsForm.ts
@@ -0,0 +1,21 @@
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { bitrateSchema, channelsSchema, codecSchema, sampleRateSchema } from 'src/schemas/conversionSettings.schema';
+import { z } from 'zod';
+import type { UseFormProps } from 'react-hook-form';
+
+const formSchema = z.object({
+ codec: codecSchema,
+ bitrate: bitrateSchema,
+ sampleRate: sampleRateSchema,
+ channels: channelsSchema,
+});
+
+type FormData = z.infer;
+
+export const useConversionSettingsForm = (props?: UseFormProps) => {
+ return useForm({
+ ...props,
+ resolver: zodResolver(formSchema),
+ });
+};
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index a3408b1..9fc1305 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -4,6 +4,64 @@
"home": "Converter",
"settings": "Settings"
},
+ "conversionSettings": {
+ "bitrate": {
+ "label": "Bitrate",
+ "values": {
+ "default": "Default",
+ "32k": "32 kbps",
+ "64k": "64 kbps",
+ "96k": "96 kbps",
+ "128k": "128 kbps",
+ "192k": "192 kbps",
+ "256k": "256 kbps",
+ "320k": "320 kbps",
+ "384k": "384 kbps",
+ "448k": "448 kbps",
+ "512k": "512 kbps",
+ "640k": "640 kbps",
+ "1024k": "1024 kbps",
+ "2048k": "2048 kbps",
+ "4096k": "4096 kbps"
+ }
+ },
+ "channels": {
+ "label": "Channels",
+ "values": {
+ "default": "Same as source",
+ "1": "Mono",
+ "2": "Stereo",
+ "3": "2.1",
+ "4": "4.0",
+ "5": "5.0",
+ "6": "5.1",
+ "7": "7.0",
+ "8": "7.1"
+ }
+ },
+ "codec": {
+ "label": "Codec",
+ "values": {
+ "default": "Without reencoding - Copy audio",
+ "aac": "AAC",
+ "ac3": "AC3",
+ "eac3": "EAC3"
+ }
+ },
+ "sampleRate": {
+ "label": "Sample rate",
+ "values": {
+ "default": "Same as source",
+ "8000": "8000 Hz",
+ "22050": "22050 Hz",
+ "32000": "32000 Hz",
+ "44100": "44100 Hz",
+ "48000": "48000 Hz",
+ "96000": "96000 Hz"
+ }
+ },
+ "title": "Conversion settings"
+ },
"fileImport": {
"button": "Add files",
"description": "Add video files for conversion.\nYou can drag & drop your files here."
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index 0381dc6..9577bed 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -4,6 +4,64 @@
"home": "Convertisseur",
"settings": "Paramètres"
},
+ "conversionSettings": {
+ "bitrate": {
+ "label": "Débit",
+ "values": {
+ "default": "Par défaut",
+ "32k": "32 kbps",
+ "64k": "64 kbps",
+ "96k": "96 kbps",
+ "128k": "128 kbps",
+ "192k": "192 kbps",
+ "256k": "256 kbps",
+ "320k": "320 kbps",
+ "384k": "384 kbps",
+ "448k": "448 kbps",
+ "512k": "512 kbps",
+ "640k": "640 kbps",
+ "1024k": "1024 kbps",
+ "2048k": "2048 kbps",
+ "4096k": "4096 kbps"
+ }
+ },
+ "channels": {
+ "label": "Canaux",
+ "values": {
+ "default": "Identique à la source",
+ "1": "Mono",
+ "2": "Stéréo",
+ "3": "2.1",
+ "4": "4.0",
+ "5": "5.0",
+ "6": "5.1",
+ "7": "7.0",
+ "8": "7.1"
+ }
+ },
+ "codec": {
+ "label": "Codec",
+ "values": {
+ "default": "Sans réencodage - Copier l'audio",
+ "aac": "AAC",
+ "ac3": "AC3",
+ "eac3": "EAC3"
+ }
+ },
+ "sampleRate": {
+ "label": "Fréquence d'échantillonnage",
+ "values": {
+ "default": "Identique à la source",
+ "8000": "8000 Hz",
+ "22050": "22050 Hz",
+ "32000": "32000 Hz",
+ "44100": "44100 Hz",
+ "48000": "48000 Hz",
+ "96000": "96000 Hz"
+ }
+ },
+ "title": "Paramètres de conversion"
+ },
"fileImport": {
"button": "Ajouter des fichiers",
"description": "Ajouter des fichiers vidéo à convertir.\nVous pouvez glisser-déposer vos fichiers ici."
diff --git a/src/routes/Converter.tsx b/src/routes/Converter.tsx
index b1a7df8..3bf5cb7 100644
--- a/src/routes/Converter.tsx
+++ b/src/routes/Converter.tsx
@@ -1,3 +1,4 @@
+import { ConversionSettings } from 'src/components/ConversionSettings';
import { FileImport } from 'src/components/FileImport';
import { FileList } from 'src/components/FileList';
@@ -11,7 +12,7 @@ export const Converter = () => {
diff --git a/src/schemas/conversionSettings.schema.ts b/src/schemas/conversionSettings.schema.ts
new file mode 100644
index 0000000..085ad60
--- /dev/null
+++ b/src/schemas/conversionSettings.schema.ts
@@ -0,0 +1,29 @@
+import { z } from 'zod';
+
+export const codecSchema = z.enum(['default', 'aac', 'ac3', 'eac3']);
+export type Codec = z.infer;
+
+export const bitrateSchema = z.enum([
+ 'default',
+ '32k',
+ '64k',
+ '96k',
+ '128k',
+ '192k',
+ '256k',
+ '320k',
+ '384k',
+ '448k',
+ '512k',
+ '640k',
+ '1024k',
+ '2048k',
+ '4096k',
+]);
+export type Bitrate = z.infer;
+
+export const sampleRateSchema = z.enum(['default', '8000', '22050', '32000', '44100', '48000', '96000']);
+export type SampleRate = z.infer;
+
+export const channelsSchema = z.enum(['default', '1', '2', '3', '4', '5', '6', '7', '8']);
+export type Channels = z.infer;
diff --git a/src/store/selectors.ts b/src/store/selectors.ts
index 9d2b6e3..24af9f3 100644
--- a/src/store/selectors.ts
+++ b/src/store/selectors.ts
@@ -1,3 +1,3 @@
-import { State } from './store.types';
+import type { State } from './store.types';
export const getFiles = (state: State) => Object.values(state.files);
diff --git a/src/store/useStore.ts b/src/store/useStore.ts
index 6c297ad..3aee7c6 100644
--- a/src/store/useStore.ts
+++ b/src/store/useStore.ts
@@ -1,6 +1,6 @@
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
-import { Store } from './store.types';
+import type { Store } from './store.types';
export const useStore = create()(
immer(set => ({