From f04ded04bc955f1e1591399631f3c22b87778ebc Mon Sep 17 00:00:00 2001 From: vterentev Date: Tue, 6 Jan 2026 17:04:23 +0300 Subject: [PATCH 1/3] GML-14: Fix select java version for profile --- .../edit-profile-form/ui/EditProfileForm.tsx | 85 +++++- .../contracts/profiles/ProfileBaseEntity.ts | 4 + src/shared/api/contracts/profiles/zod.ts | 73 ++--- src/shared/enums/java-major-version.ts | 10 + src/shared/enums/profile-java-vendor.ts | 7 + src/shared/ui/select.tsx | 272 ++++++++++-------- .../client-hub/ui/DownloadClientHub.tsx | 108 +------ 7 files changed, 303 insertions(+), 256 deletions(-) create mode 100644 src/shared/enums/java-major-version.ts create mode 100644 src/shared/enums/profile-java-vendor.ts diff --git a/src/features/edit-profile-form/ui/EditProfileForm.tsx b/src/features/edit-profile-form/ui/EditProfileForm.tsx index 47c57a38..de26f73c 100644 --- a/src/features/edit-profile-form/ui/EditProfileForm.tsx +++ b/src/features/edit-profile-form/ui/EditProfileForm.tsx @@ -1,10 +1,14 @@ import React from 'react'; import { useRouter } from 'next/navigation'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useEditProfile } from '@/shared/hooks'; -import { EditProfileFormSchemaType, EditProfileSchema, ProfileExtendedBaseEntity } from '@/shared/api/contracts'; +import { + EditProfileFormSchemaType, + EditProfileSchema, + ProfileExtendedBaseEntity, +} from '@/shared/api/contracts'; import { Form, FormField, FormMessage } from '@/shared/ui/form'; import { Input } from '@/shared/ui/input'; import { Textarea } from '@/shared/ui/textarea'; @@ -13,6 +17,17 @@ import { Icons } from '@/shared/ui/icons'; import { DASHBOARD_PAGES } from '@/shared/routes'; import { Switch } from '@/shared/ui/switch'; import { Slider } from '@/shared/ui/slider'; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from '@/shared/ui/select'; +import { JavaMajorVersion } from '@/shared/enums/java-major-version'; +import { ProfileJavaVendor } from '@/shared/enums/profile-java-vendor'; interface EditProfileFormProps { profile?: ProfileExtendedBaseEntity; @@ -36,6 +51,8 @@ export const EditProfileForm = (props: EditProfileFormProps) => { recommendedRam: profile?.recommendedRam || 1024, background: profile?.background || '', isEnabled: profile?.isEnabled, + profileJavaVendor: profile?.profileJavaVendor, + javaMajorVersion: profile?.javaMajorVersion, }, resolver: zodResolver(EditProfileSchema), }); @@ -69,6 +86,8 @@ export const EditProfileForm = (props: EditProfileFormProps) => { formUpdate.append('enabled', body.isEnabled?.toString() ?? 'true'); formUpdate.append('priority', body.priority?.toString() ?? '0'); formUpdate.append('recommendedRam', body.recommendedRam?.toString() ?? '50'); + formUpdate.append('profileJavaVendor', body.profileJavaVendor?.toString() ?? '0'); + formUpdate.append('javaMajorVersion', body.javaMajorVersion?.toString() ?? ''); const backgroundFile = body.background && typeof body.background !== 'string' && body.background[0] instanceof File @@ -109,8 +128,10 @@ export const EditProfileForm = (props: EditProfileFormProps) => { form.reset(body); }; - // @ts-ignore - // @ts-ignore + console.log({ + error: form.formState.errors, + }); + return (
@@ -233,6 +254,62 @@ export const EditProfileForm = (props: EditProfileFormProps) => { )} + +
+
+
Версия Java
+

Версия запускаемой Java

+
+
+ ( + + )} + /> + + {form.formState.errors.profileJavaVendor && ( + + {form.formState.errors.profileJavaVendor.message?.toString()} + + )} +
+
+
Рекомендуемая оперативная память
diff --git a/src/shared/api/contracts/profiles/ProfileBaseEntity.ts b/src/shared/api/contracts/profiles/ProfileBaseEntity.ts index 1f04e822..6afa5bbd 100644 --- a/src/shared/api/contracts/profiles/ProfileBaseEntity.ts +++ b/src/shared/api/contracts/profiles/ProfileBaseEntity.ts @@ -1,5 +1,7 @@ import { EntityState } from '@/shared/enums'; import { PlayerBaseEntity } from '@/shared/api/contracts'; +import { ProfileJavaVendor } from '@/shared/enums/profile-java-vendor'; +import { JavaMajorVersion } from '@/shared/enums/java-major-version'; export type ProfileBaseEntity = { name: string; @@ -32,6 +34,8 @@ export type ProfileExtendedBaseEntity = { gameArguments: string; hasUpdate: boolean; isEnabled: boolean; + profileJavaVendor: ProfileJavaVendor; + javaMajorVersion: JavaMajorVersion; state: EntityState; loader: number; files: ProfileFileBaseEntity[]; diff --git a/src/shared/api/contracts/profiles/zod.ts b/src/shared/api/contracts/profiles/zod.ts index 66b27001..b3ed52be 100644 --- a/src/shared/api/contracts/profiles/zod.ts +++ b/src/shared/api/contracts/profiles/zod.ts @@ -1,37 +1,40 @@ import { z } from 'zod'; +import { ProfileJavaVendor } from '@/shared/enums/profile-java-vendor'; +import { JavaMajorVersion } from '@/shared/enums/java-major-version'; + export const ModDetailsEntitySchema = z.object({ title: z - .string() - .min(2, { message: 'Название мода должно быть больше 2 символов' }) - .max(100, { message: 'Название мода не должно быть больше 100 символов' }), + .string() + .min(2, { message: 'Название мода должно быть больше 2 символов' }) + .max(100, { message: 'Название мода не должно быть больше 100 символов' }), description: z - .string() - .min(2, { message: 'Описание мода должно быть больше 2 символов' }) - .max(1000, { message: 'Описание мода не должно быть больше 1000 символов' }), + .string() + .min(2, { message: 'Описание мода должно быть больше 2 символов' }) + .max(1000, { message: 'Описание мода не должно быть больше 1000 символов' }), }); export const CreateProfileSchema = z.object({ name: z - .string() - .min(1, { message: 'Обязательное поле' }) - .regex(/^[a-zA-Z0-9-]*$/, { - message: 'Название профиля может содержать только английские буквы, цифры и тире', - }) - .min(2, { message: 'Длина имени должна быть от 2 до 100 символов' }) - .max(100, { message: 'Длина имени должна быть от 2 до 100 символов' }), + .string() + .min(1, { message: 'Обязательное поле' }) + .regex(/^[a-zA-Z0-9-]*$/, { + message: 'Название профиля может содержать только английские буквы, цифры и тире', + }) + .min(2, { message: 'Длина имени должна быть от 2 до 100 символов' }) + .max(100, { message: 'Длина имени должна быть от 2 до 100 символов' }), displayName: z - .string() - .min(1, { message: 'Обязательное поле' }) - .min(2, { message: 'Длина имени должна быть больше 2 символов' }) - .max(100, { message: 'Длина имени не должна быть больше 100 символов' }), + .string() + .min(1, { message: 'Обязательное поле' }) + .min(2, { message: 'Длина имени должна быть больше 2 символов' }) + .max(100, { message: 'Длина имени не должна быть больше 100 символов' }), description: z - .string() - .min(1, { message: 'Обязательное поле' }) - .min(2, { message: 'Длина описания должна быть больше 2 символов' }) - .max(1000, { - message: 'Длина описания не должна быть больше 1000 символов', - }), + .string() + .min(1, { message: 'Обязательное поле' }) + .min(2, { message: 'Длина описания должна быть больше 2 символов' }) + .max(1000, { + message: 'Длина описания не должна быть больше 1000 символов', + }), version: z.string({ errorMap: () => ({ message: 'Не выбрана версия игры', @@ -44,22 +47,24 @@ export const CreateProfileSchema = z.object({ export const EditProfileSchema = z.object({ name: z - .string() - .min(2, { message: 'Длина имени должна быть больше 2 символов' }) - .max(100, { message: 'Длина имени не должна быть больше 100 символов' }), + .string() + .min(2, { message: 'Длина имени должна быть больше 2 символов' }) + .max(100, { message: 'Длина имени не должна быть больше 100 символов' }), displayName: z - .string() - .min(2, { message: 'Длина имени должна быть больше 2 символов' }) - .max(100, { message: 'Длина имени не должна быть больше 100 символов' }), + .string() + .min(2, { message: 'Длина имени должна быть больше 2 символов' }) + .max(100, { message: 'Длина имени не должна быть больше 100 символов' }), description: z - .string() - .min(2, { message: 'Длина описания должна быть больше 2 символов' }) - .max(255, { - message: 'Длина описания не должна быть больше 255 символов', - }), + .string() + .min(2, { message: 'Длина описания должна быть больше 2 символов' }) + .max(255, { + message: 'Длина описания не должна быть больше 255 символов', + }), jvmArguments: z.string().optional(), gameArguments: z.string().optional(), priority: z.coerce.number().optional(), + profileJavaVendor: z.nativeEnum(ProfileJavaVendor).optional(), + javaMajorVersion: z.union([z.nativeEnum(JavaMajorVersion), z.literal('0')]).optional(), recommendedRam: z.coerce.number().default(50).optional(), icon: z.any(), isEnabled: z.boolean().default(true).optional(), diff --git a/src/shared/enums/java-major-version.ts b/src/shared/enums/java-major-version.ts new file mode 100644 index 00000000..efb3ba96 --- /dev/null +++ b/src/shared/enums/java-major-version.ts @@ -0,0 +1,10 @@ +import { ValueOf } from '@/shared/lib/helpers'; + +export type JavaMajorVersion = ValueOf; +export const JavaMajorVersion = { + VERSION_8: '8', + VERSION_11: '11', + VERSION_17: '17', + VERSION_21: '21', + VERSION_25: '25', +} as const; diff --git a/src/shared/enums/profile-java-vendor.ts b/src/shared/enums/profile-java-vendor.ts new file mode 100644 index 00000000..a88070f3 --- /dev/null +++ b/src/shared/enums/profile-java-vendor.ts @@ -0,0 +1,7 @@ +import { ValueOf } from '@/shared/lib/helpers'; + +export type ProfileJavaVendor = ValueOf; +export const ProfileJavaVendor = { + DEFAULT: 0, + AZUL: 1, +} as const; diff --git a/src/shared/ui/select.tsx b/src/shared/ui/select.tsx index 765a1e01..409c8b4a 100644 --- a/src/shared/ui/select.tsx +++ b/src/shared/ui/select.tsx @@ -1,152 +1,174 @@ 'use client'; - -import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'; +import * as React from 'react'; import * as SelectPrimitive from '@radix-ui/react-select'; -import { Check, ChevronDown, ChevronUp } from 'lucide-react'; +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'; import { cn } from '@/shared/lib/utils'; -const Select = SelectPrimitive.Root; -const SelectGroup = SelectPrimitive.Group; -const SelectValue = SelectPrimitive.Value; - -const SelectTrigger = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - span]:line-clamp-1', - className, - )} - {...props} - > - {children} - - - - -)); -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; +function Select({ ...props }: React.ComponentProps) { + return ; +} -const SelectScrollUpButton = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; +function SelectGroup({ ...props }: React.ComponentProps) { + return ; +} -const SelectScrollDownButton = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - -)); -SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; +function SelectValue({ ...props }: React.ComponentProps) { + return ; +} -const SelectContent = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, children, position = 'popper', ...props }, ref) => ( - - & { + size?: 'sm' | 'default'; +}) { + return ( + - - + + + + ); +} + +function SelectContent({ + className, + children, + position = 'item-aligned', + align = 'center', + ...props +}: React.ComponentProps) { + return ( + + - {children} - - - - -)); -SelectContent.displayName = SelectPrimitive.Content.displayName; + + + {children} + + + + + ); +} -const SelectLabel = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectLabel.displayName = SelectPrimitive.Label.displayName; +function SelectLabel({ className, ...props }: React.ComponentProps) { + return ( + + ); +} -const SelectItem = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - - - - -
{children}
-
-
-)); -SelectItem.displayName = SelectPrimitive.Item.displayName; +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} -const SelectSeparator = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -SelectSeparator.displayName = SelectPrimitive.Separator.displayName; +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} export { Select, - SelectGroup, - SelectValue, - SelectTrigger, SelectContent, - SelectLabel, + SelectGroup, SelectItem, - SelectSeparator, - SelectScrollUpButton, + SelectLabel, SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, }; diff --git a/src/widgets/client-hub/ui/DownloadClientHub.tsx b/src/widgets/client-hub/ui/DownloadClientHub.tsx index 20c34a88..78e78e7c 100644 --- a/src/widgets/client-hub/ui/DownloadClientHub.tsx +++ b/src/widgets/client-hub/ui/DownloadClientHub.tsx @@ -3,20 +3,21 @@ import React, { useEffect, useRef, useState } from 'react'; import { Ubuntu_Mono } from 'next/font/google'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { ArrowBigDownDash, ChevronsUpDown, Package2Icon } from 'lucide-react'; +import { ArrowBigDownDash, Package2Icon } from 'lucide-react'; import { useConnectionHub } from '../lib/useConnectionHub'; -import { JavaVersionBaseEntity, ProfileExtendedBaseEntity, RestoreProfileSchemaType, } from '@/shared/api/contracts'; +import { + JavaVersionBaseEntity, + ProfileExtendedBaseEntity, + RestoreProfileSchemaType, +} from '@/shared/api/contracts'; import { cn } from '@/shared/lib/utils'; import { Button } from '@/shared/ui/button'; import { Progress } from '@/shared/ui/progress'; import { Textarea } from '@/shared/ui/textarea'; import { Icons } from '@/shared/ui/icons'; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/shared/ui/form'; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '@/shared/ui/command'; -import { Separator } from '@/shared/ui/separator'; -import { Popover, PopoverContent, PopoverTrigger } from '@/shared/ui/popover'; +import { Form } from '@/shared/ui/form'; interface DownloadClientHubProps { profile?: ProfileExtendedBaseEntity; @@ -76,8 +77,7 @@ export function DownloadClientHub(props: DownloadClientHubProps) { Управление {!isConnected && Подключение к консоли...} -
+
@@ -88,83 +88,6 @@ export function DownloadClientHub(props: DownloadClientHubProps) {

- ( - - Выберите версию Java - - - - - - - - - - Версия не найдена - - { - form.resetField('javaVersion'); - onOpenJavaVersionsChange(); - }} - > -
-
- По умолчанию - - На выбор сервера - -
-
-
- - {javaVersions.data && - javaVersions.data.map((version, i) => ( - { - field.onChange(JSON.stringify(version)); - onOpenJavaVersionsChange(); - }} - > -
- - {version.majorVersion} - -
- {version.name} - - {version.version} - -
-
-
- ))} -
-
-
-
-
-
- -
- )} - />
@@ -182,8 +105,7 @@ export function DownloadClientHub(props: DownloadClientHubProps) {
-
+
Шаг второй

@@ -196,8 +118,8 @@ export function DownloadClientHub(props: DownloadClientHubProps) { onClick={onBuildDistributive} disabled={!isConnected || isDisable || !props.profile || !props.profile.hasUpdate} > - {isDisable && } - + {isDisable && } + Собрать

@@ -239,8 +161,8 @@ export function DownloadClientHub(props: DownloadClientHubProps) { Общий прогресс: {percentAllStages}%
- - + +
From 2fd6ca8c0010f3a1843699c4f1d9df12656a0610 Mon Sep 17 00:00:00 2001 From: vterentev Date: Tue, 6 Jan 2026 17:06:06 +0300 Subject: [PATCH 2/3] GML-14: refactoring --- src/features/edit-profile-form/ui/EditProfileForm.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/features/edit-profile-form/ui/EditProfileForm.tsx b/src/features/edit-profile-form/ui/EditProfileForm.tsx index de26f73c..9e5d8035 100644 --- a/src/features/edit-profile-form/ui/EditProfileForm.tsx +++ b/src/features/edit-profile-form/ui/EditProfileForm.tsx @@ -128,10 +128,6 @@ export const EditProfileForm = (props: EditProfileFormProps) => { form.reset(body); }; - console.log({ - error: form.formState.errors, - }); - return (
From 73d47499f9bc046ec25da82174cff9af511acc2c Mon Sep 17 00:00:00 2001 From: vterentev Date: Tue, 6 Jan 2026 21:51:15 +0300 Subject: [PATCH 3/3] GML-14: added status with download --- src/shared/enums/entity-state.ts | 4 +++- src/widgets/client-hub/ui/ClientState.tsx | 1 + src/widgets/game-mods/ui/GameMods.tsx | 18 +++++++++--------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/shared/enums/entity-state.ts b/src/shared/enums/entity-state.ts index 859be476..471d4b6e 100644 --- a/src/shared/enums/entity-state.ts +++ b/src/shared/enums/entity-state.ts @@ -5,7 +5,8 @@ export enum EntityState { ENTITY_STATE_INITIALIZE = 3, // Подготовка профиля ENTITY_STATE_ERROR = 4, // Ошибка при работе с профилем ENTITY_STATE_NEED_COMPILE = 5, // Необходима сборка профиля - ENTITY_STATE_PACKING = 6, // Необходима сборка профиля + ENTITY_STATE_PACKING = 6, // Сборка профиля + ENTITY_STATE_NEED_DOWNLOAD = 7, // Упаковывается ENTITY_STATE_DISABLED = 99999, // Профиль выключен } @@ -18,5 +19,6 @@ export enum EntityStateOption { 'OPTION_4' = 'Ошибка', // Ошибка при работе с профилем 'OPTION_5' = 'Необходима сборка', // Необходима сборка профиля 'OPTION_6' = 'Сборка', // Сборка профиля + 'OPTION_7' = 'Необходимо дозагрузить', // Сборка профиля 'OPTION_99999' = 'Недоступен', // Профиль выключен } diff --git a/src/widgets/client-hub/ui/ClientState.tsx b/src/widgets/client-hub/ui/ClientState.tsx index 5bd45d46..e87d840c 100644 --- a/src/widgets/client-hub/ui/ClientState.tsx +++ b/src/widgets/client-hub/ui/ClientState.tsx @@ -15,6 +15,7 @@ const stateColorMap: Record = { [EntityState.ENTITY_STATE_ERROR]: 'bg-red-500', [EntityState.ENTITY_STATE_NEED_COMPILE]: 'bg-blue-500', [EntityState.ENTITY_STATE_PACKING]: 'bg-orange-500', + [EntityState.ENTITY_STATE_NEED_DOWNLOAD]: 'bg-blue-500', }; export function ClientState({ state }: ClientStateProps) { diff --git a/src/widgets/game-mods/ui/GameMods.tsx b/src/widgets/game-mods/ui/GameMods.tsx index 5a639dbd..c685d131 100644 --- a/src/widgets/game-mods/ui/GameMods.tsx +++ b/src/widgets/game-mods/ui/GameMods.tsx @@ -15,7 +15,7 @@ import { Button } from '@/shared/ui/button'; import { EntityState, ModType } from '@/shared/enums'; import { Card, CardContent, CardHeader } from '@/shared/ui/card'; import { GameModItem } from '@/widgets/game-mods/ui/GameModItem'; -import { useProfileCardStore } from "@/entities/ProfileCard/lib/store"; +import { useProfileCardStore } from '@/entities/ProfileCard/lib/store'; interface GameServersParams { profile: ProfileExtendedBaseEntity; @@ -64,6 +64,7 @@ export const GameMods = ({ profile }: GameServersParams) => { EntityState.ENTITY_STATE_ERROR, EntityState.ENTITY_STATE_PACKING, EntityState.ENTITY_STATE_DISABLED, + EntityState.ENTITY_STATE_NEED_DOWNLOAD, ].includes(state || EntityState.ENTITY_STATE_ACTIVE); const removeMod = async (fileName: string) => { @@ -101,7 +102,7 @@ export const GameMods = ({ profile }: GameServersParams) => {
Список модов
- + {
- + - + {mod?.name} - + Jar
@@ -156,7 +156,7 @@ export const GameMods = ({ profile }: GameServersParams) => { className="cursor-pointer inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground hover:bg-secondary/80 h-10 px-4 py-2 w-max gap-2" > Загрузить - + { {filteredOptionalMods && detailsMods && filteredOptionalMods.length > 0 ? ( filteredOptionalMods.map((mod, index) => ( - + )) ) : ( @@ -211,7 +211,7 @@ export const GameMods = ({ profile }: GameServersParams) => { className="cursor-pointer inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground hover:bg-secondary/80 h-10 px-4 py-2 w-max gap-2" > Загрузить - +