From 5990efa650ab3f934713028ce78bd2349c988ca8 Mon Sep 17 00:00:00 2001 From: Menta Date: Sun, 7 Jun 2026 01:08:41 +0200 Subject: [PATCH 1/2] feat(wait-movement-command): add movement command - Remove circular dependecies - Make export command file possible - Create folder to rearrange the flow - display the wait completion node --- assets/i18n/de.json | 10 +- assets/i18n/en.json | 9 ++ assets/i18n/es.json | 10 +- assets/i18n/fr.json | 9 ++ assets/i18n/it.json | 10 +- assets/i18n/ja.json | 10 +- assets/i18n/nl.json | 10 +- assets/i18n/pt.json | 10 +- assets/i18n/zh_Hans.json | 10 +- assets/i18n/zh_Hant.json | 10 +- src/designSystem/MultiSelect/MultiSelect.tsx | 9 +- src/designSystem/Select/RenderOptions.tsx | 2 +- src/designSystem/Select/useSelect.ts | 4 +- src/hooks/useInputAttrs.tsx | 97 +++++++++++++-- src/hooks/useZodForm.ts | 2 +- src/models/entities/event/command.ts | 22 +--- src/models/entities/event/event.ts | 3 +- src/models/entities/event/globalCommand.ts | 19 +++ .../waitCommand/waitMovementCompletion.ts | 23 ++++ src/utils/eventCommandCreation.ts | 3 +- .../components/editor/EditorContainer.tsx | 5 + .../world/event/commands/DefaultCommand.tsx | 10 +- .../commands/editors/CommandEditorOverlay.tsx | 5 +- .../commands/editors/EventEditorProps.ts | 2 +- .../WaitMovementCompletion.tsx | 111 +++++++++++++++++ .../WaitMovementCompletion.tsx | 113 ++++++++++++++++++ .../event/commands/sharedSelectOptions.ts | 10 ++ .../world/event/common/CommandToNodes.ts | 3 +- .../world/event/hooks/useCommandNode.tsx | 5 +- 29 files changed, 492 insertions(+), 54 deletions(-) create mode 100644 src/models/entities/event/globalCommand.ts create mode 100644 src/models/entities/event/waitCommand/waitMovementCompletion.ts create mode 100644 src/views/components/world/event/commands/editors/movementCommands/WaitMovementCompletion.tsx create mode 100644 src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx create mode 100644 src/views/components/world/event/commands/sharedSelectOptions.ts diff --git a/assets/i18n/de.json b/assets/i18n/de.json index 2946e9a58..ade214a39 100644 --- a/assets/i18n/de.json +++ b/assets/i18n/de.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 9a6fabd5b..7767fbf64 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -1812,6 +1812,15 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "The player", + "event_command_wait_move_completion_multiselect_static_this_event": "This event", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", diff --git a/assets/i18n/es.json b/assets/i18n/es.json index 46e80a02c..065aa9edf 100644 --- a/assets/i18n/es.json +++ b/assets/i18n/es.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/fr.json b/assets/i18n/fr.json index 83e9fe1a0..541991c11 100644 --- a/assets/i18n/fr.json +++ b/assets/i18n/fr.json @@ -1812,6 +1812,15 @@ "event_command_teleport_event": "Téléporter un événement", "event_command_teleport_player": "Téléporter le joueur", "event_command_wait_move_completion": "Attendre la fin des déplacements", + "event_command_wait_move_completion_select": "Attendre", + "event_command_wait_move_completion_select_all": "Tous les événements", + "event_command_wait_move_completion_select_some": "Des événements spécifiques", + "event_command_wait_move_completion_multiselect_label": "Événements à attendre", + "event_command_wait_move_completion_multiselect_static_player": "Le joueur", + "event_command_wait_move_completion_multiselect_static_this_event": "Cet événement", + "event_editor_wait_move_completion_wait_all": "Attendre tous les événements", + "event_editor_wait_move_completion_timeout": "Délai d'attente max", + "event_editor_wait_move_completion_timeout_info": "L’événement continue son exécution si les déplacements prennent plus de temps.", "event_command_manage_event_reappearance": "Gérer la réapparition de l'événement", "event_command_manage_path_finding": "Gérer le path finding", "event_command_manage_follow_me": "Gérer le Follow Me", diff --git a/assets/i18n/it.json b/assets/i18n/it.json index f99f14d6e..3f761c196 100644 --- a/assets/i18n/it.json +++ b/assets/i18n/it.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/ja.json b/assets/i18n/ja.json index 76c221ce7..3b209f117 100644 --- a/assets/i18n/ja.json +++ b/assets/i18n/ja.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/nl.json b/assets/i18n/nl.json index 6d56e2006..1e5bc0afa 100644 --- a/assets/i18n/nl.json +++ b/assets/i18n/nl.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/pt.json b/assets/i18n/pt.json index 9eb2d65cb..b58c4c803 100644 --- a/assets/i18n/pt.json +++ b/assets/i18n/pt.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleportar evento", "event_command_teleport_player": "Teleportar jogador", "event_command_wait_move_completion": "Aguardar a conclusão do movimento", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Gerenciar reaparecimento do evento", "event_command_manage_path_finding": "Gerenciar busca de caminhos", "event_command_manage_follow_me": "Gerenciar siga-me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/zh_Hans.json b/assets/i18n/zh_Hans.json index 659098d73..ed11e77e3 100644 --- a/assets/i18n/zh_Hans.json +++ b/assets/i18n/zh_Hans.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/assets/i18n/zh_Hant.json b/assets/i18n/zh_Hant.json index 8f37a9b6a..053bb1e92 100644 --- a/assets/i18n/zh_Hant.json +++ b/assets/i18n/zh_Hant.json @@ -1812,6 +1812,14 @@ "event_command_teleport_event": "Teleport event", "event_command_teleport_player": "Teleport player", "event_command_wait_move_completion": "Wait for move completion", + "event_command_wait_move_completion_select": "Waiting", + "event_command_wait_move_completion_select_all": "All event", + "event_command_wait_move_completion_select_some": "Wait spécifics events", + "event_command_wait_move_completion_multiselect_label": "Event to wait", + "event_command_wait_move_completion_multiselect_static_player": "Player", + "event_editor_wait_move_completion_wait_all": "Wait all events", + "event_editor_wait_move_completion_timeout": "Maximum waiting time", + "event_editor_wait_move_completion_timeout_info": "The event continues to run if travel takes longer.", "event_command_manage_event_reappearance": "Manage event reappearance", "event_command_manage_path_finding": "Manage path finding", "event_command_manage_follow_me": "Manage follow me", @@ -1920,4 +1928,4 @@ "assigning_tiled_maps_error": "Error while assigning Tiled maps", "max_trainer_party_size": "Max trainer party size", "add_trainer_party_max_size_to_settings": "Add trainer party max size to settings" -} +} \ No newline at end of file diff --git a/src/designSystem/MultiSelect/MultiSelect.tsx b/src/designSystem/MultiSelect/MultiSelect.tsx index 484e32c50..12e147e4f 100644 --- a/src/designSystem/MultiSelect/MultiSelect.tsx +++ b/src/designSystem/MultiSelect/MultiSelect.tsx @@ -1,8 +1,8 @@ +import DownIcon from '@assets/icons/global/down-icon.svg'; import React from 'react'; +import { MultiSelectContainer, MultiSelectTextArea } from './MultiSelectContainer'; import { RenderOptions } from './RenderOptions'; import { MultiSelectProps, useMultiSelect } from './useMultiSelect'; -import { MultiSelectContainer, MultiSelectTextArea } from './MultiSelectContainer'; -import DownIcon from '@assets/icons/global/down-icon.svg'; import { ValueType } from './useRenderOptions'; export const MultiSelect = (props: MultiSelectProps) => { @@ -11,9 +11,12 @@ export const MultiSelect = ( chooseValue: (props.chooseValue as Value[]) ?? [], }); + const { name, ...textAreaProps } = inputProps; + return ( - + + {name && currentValues.map((val, i) => )} diff --git a/src/designSystem/Select/RenderOptions.tsx b/src/designSystem/Select/RenderOptions.tsx index 63d62a1f8..c82be13d7 100644 --- a/src/designSystem/Select/RenderOptions.tsx +++ b/src/designSystem/Select/RenderOptions.tsx @@ -1,7 +1,7 @@ +import React, { MouseEventHandler } from 'react'; import List, { ListRowProps } from 'react-virtualized/dist/es/List'; import { SelectOption } from './types'; import { RenderOptionsProps, useRenderOptions } from './useRenderOptions'; -import React, { MouseEventHandler } from 'react'; const CLASSES = ['option', 'option highlighted', 'option current', 'option highlighted current']; diff --git a/src/designSystem/Select/useSelect.ts b/src/designSystem/Select/useSelect.ts index 1cef65259..8b5a4de23 100644 --- a/src/designSystem/Select/useSelect.ts +++ b/src/designSystem/Select/useSelect.ts @@ -1,3 +1,4 @@ +import { normalize } from '@utils/normalize'; import { ChangeEventHandler, FocusEventHandler, @@ -9,10 +10,9 @@ import { useRef, useState, } from 'react'; +import type { List } from 'react-virtualized/dist/es/List'; import { RenderOptionRef, SelectOption } from './types'; import { getNotFoundExclusionPattern, getSelectDefaultLabel, positionAndShowPopover } from './utils'; -import type { List } from 'react-virtualized/dist/es/List'; -import { normalize } from '@utils/normalize'; export type SelectProps = { options: Readonly[]>; diff --git a/src/hooks/useInputAttrs.tsx b/src/hooks/useInputAttrs.tsx index 16403ac06..08413899f 100644 --- a/src/hooks/useInputAttrs.tsx +++ b/src/hooks/useInputAttrs.tsx @@ -17,6 +17,7 @@ import { inputAttrs } from '@utils/inputAttrs'; import { basename } from '@utils/path'; import React, { useMemo, useRef, useState } from 'react'; import { z } from 'zod'; +import { MultiSelect } from '../designSystem/MultiSelect'; type WithSchemaKeyAndName = { name: string; @@ -80,9 +81,9 @@ export const useInputAttrs = (schema: z.ZodObject, d MultiLineInput: ({ name, schemaKey, ...props }: ReactProps) => ( ), - Select: ({ name, schemaKey, ...props }: ReactProps) => { - const { type, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); - return ; }, Toggle: ({ name, schemaKey, ...props }: ReactProps) => { const { type, required, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); @@ -164,22 +165,56 @@ export const useInputAttrsWithLabel = (schema: z.ZodObj ); }, - Select: ({ name, schemaKey, label, labelLeft, ...props }: ReactPropsWithLabel) => { - const { type, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); - if (!label) return ; if (labelLeft) return ( - ); return ( - + + ); + }, + MultiSelect: ({ + name, + schemaKey, + label, + defaultValue, + selectAllOption, + whenAllOptionSelected, + ...props + }: ReactPropsWithLabel) => { + const { type, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); + if (!label) + return ( + + ); + + return ( + + + ); }, @@ -232,6 +267,52 @@ export const useNodeInputAttrsWithLabel = (schema: z.Zo ); }, + Select: ({ name, schemaKey, label, defaultValue, ...props }: ReactPropsWithLabel) => { + const { type, defaultValue: _defaultValue, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); + + if (!label) return + + ); + }, + MultiSelect: ({ + name, + schemaKey, + label, + defaultValue, + selectAllOption, + whenAllOptionSelected, + ...props + }: ReactPropsWithLabel) => { + const { type, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); + if (!label) + return ( + + ); + + return ( + + + + + ); + }, MultiLineInput: ({ name, schemaKey, label, labelLeft, ...props }: ReactPropsWithLabel) => { if (!label) return ; diff --git a/src/hooks/useZodForm.ts b/src/hooks/useZodForm.ts index 453672172..1ae00cfc2 100644 --- a/src/hooks/useZodForm.ts +++ b/src/hooks/useZodForm.ts @@ -148,7 +148,7 @@ export const useZodForm = ( ) => { const formRef = useRef(null); const touchedInputValidity = useRef({}); - const d = useMemo(() => formData(formRef, schema, defaults, fixturesBeforeValidation), [schema]); + const d = useMemo(() => formData(formRef, schema, defaults, fixturesBeforeValidation), [schema, defaults]); const [isValid, setIsValid] = useState(d.defaultValid); const onTouched = (inputName: string, isValid: boolean, value: string) => { diff --git a/src/models/entities/event/command.ts b/src/models/entities/event/command.ts index e76ad6a66..bddd3472c 100644 --- a/src/models/entities/event/command.ts +++ b/src/models/entities/event/command.ts @@ -1,24 +1,8 @@ import { POSITIVE_OR_ZERO_INT } from '@modelEntities/common'; import { z } from 'zod'; import type { StudioEventCommandCategory } from './category'; - -export const COMMAND_ID_VALIDATOR = z.string().brand('CommandId'); -export type CommandId = z.infer; - -export const COMMAND_CONNECTION_ID_VALIDATOR = z.string().brand('ConnectionId'); -export type ConnectionId = z.infer; - -const EVENT_COMMAND_STUDIO_DATA_VALIDATOR = z.object({ - x: z.number().int(), - y: z.number().int(), - comments: z.array(z.string()), -}); - -export const EVENT_COMMAND_CONNECTION_VALIDATOR = z.object({ - sourceHandle: z.string(), - target: COMMAND_ID_VALIDATOR, - targetHandle: z.string(), -}); +import { COMMAND_CONNECTION_ID_VALIDATOR, EVENT_COMMAND_CONNECTION_VALIDATOR, EVENT_COMMAND_STUDIO_DATA_VALIDATOR } from './globalCommand'; +import { EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR } from './waitCommand/waitMovementCompletion'; export type StudioEventCommandConnection = z.infer; @@ -98,7 +82,7 @@ export const EVENT_COMMAND_VALIDATOR = z.discriminatedUnion('type', [ GENERIC_COMMAND('move_event'), GENERIC_COMMAND('teleport_event'), GENERIC_COMMAND('teleport_player'), - GENERIC_COMMAND('wait_move_completion'), + EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR, GENERIC_COMMAND('manage_event_reappearance'), GENERIC_COMMAND('manage_path_finding'), GENERIC_COMMAND('manage_follow_me'), diff --git a/src/models/entities/event/event.ts b/src/models/entities/event/event.ts index 0c4f780c2..e2485ded7 100644 --- a/src/models/entities/event/event.ts +++ b/src/models/entities/event/event.ts @@ -1,7 +1,8 @@ import { z } from 'zod'; import { POSITIVE_OR_ZERO_INT } from '../common'; import { DB_SYMBOL_VALIDATOR } from '../dbSymbol'; -import { COMMAND_ID_VALIDATOR, EVENT_COMMAND_VALIDATOR } from './command'; +import { EVENT_COMMAND_VALIDATOR } from './command'; +import { COMMAND_ID_VALIDATOR } from './globalCommand'; const TEMPLATE_PARAMETER_NAME_VALIDATOR = z.string().brand('TemplateParameterName'); diff --git a/src/models/entities/event/globalCommand.ts b/src/models/entities/event/globalCommand.ts new file mode 100644 index 000000000..eda369a63 --- /dev/null +++ b/src/models/entities/event/globalCommand.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +export const COMMAND_ID_VALIDATOR = z.string().brand('CommandId'); +export type CommandId = z.infer; + +export const COMMAND_CONNECTION_ID_VALIDATOR = z.string().brand('ConnectionId'); +export type ConnectionId = z.infer; + +export const EVENT_COMMAND_STUDIO_DATA_VALIDATOR = z.object({ + x: z.number().int(), + y: z.number().int(), + comments: z.array(z.string()), +}); + +export const EVENT_COMMAND_CONNECTION_VALIDATOR = z.object({ + sourceHandle: z.string(), + target: COMMAND_ID_VALIDATOR, + targetHandle: z.string(), +}); diff --git a/src/models/entities/event/waitCommand/waitMovementCompletion.ts b/src/models/entities/event/waitCommand/waitMovementCompletion.ts new file mode 100644 index 000000000..01d6a5cd2 --- /dev/null +++ b/src/models/entities/event/waitCommand/waitMovementCompletion.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; +import { POSITIVE_OR_ZERO_INT } from '../../common'; +import { COMMAND_CONNECTION_ID_VALIDATOR, EVENT_COMMAND_CONNECTION_VALIDATOR, EVENT_COMMAND_STUDIO_DATA_VALIDATOR } from '../globalCommand'; + +export const EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR = z.object({ + waitAllevent: z.boolean().default(false), + waitById: z.array(z.string()).default([]), + timeout: POSITIVE_OR_ZERO_INT, + + type: z.literal('wait_move_completion'), + connections: z.record(COMMAND_CONNECTION_ID_VALIDATOR, EVENT_COMMAND_CONNECTION_VALIDATOR), + studioData: EVENT_COMMAND_STUDIO_DATA_VALIDATOR, +}); + +export type StudioEventWaitMovementCompletion = z.infer; + +export const createWaitMovementCompletionCommand = () => { + return { + waitAllevent: false, + waitById: [], + timeout: 0, + }; +}; diff --git a/src/utils/eventCommandCreation.ts b/src/utils/eventCommandCreation.ts index 622ab5ffd..2d52a2a60 100644 --- a/src/utils/eventCommandCreation.ts +++ b/src/utils/eventCommandCreation.ts @@ -1,6 +1,7 @@ import type { StudioEventCommand, StudioEventCommandData, StudioEventCommandType } from '@modelEntities/event/command'; import { StudioEvent } from '@modelEntities/event/event'; import { findFirstAvailableTextIdEvent } from './ModelUtils'; +import { createWaitMovementCompletionCommand } from '../models/entities/event/waitCommand/waitMovementCompletion'; const createShowMessageCommand = (event: StudioEvent) => { const { messageId, narratorId } = findFirstAvailableTextIdEvent(event, 0); @@ -46,7 +47,7 @@ export const EventCommandCreation: Record props.theme.colors.dark15}; border: 1px solid ${(props) => props.theme.colors.text400}; } + + .params { + color: ${(props) => props.theme.colors.text400}; + ${(props) => props.theme.fonts.normalSmall}; + } `; export const EditorWithCollapseContainer = styled(EditorContainer)` diff --git a/src/views/components/world/event/commands/DefaultCommand.tsx b/src/views/components/world/event/commands/DefaultCommand.tsx index 4a4269e61..3f0b9e58f 100644 --- a/src/views/components/world/event/commands/DefaultCommand.tsx +++ b/src/views/components/world/event/commands/DefaultCommand.tsx @@ -1,10 +1,10 @@ -import { CommandNodeProps } from './CommandNodeProps'; -import { useHandleConnectionState } from '../hooks/useHandleConnectionState'; -import { IconsFromCommand } from '../common/EventIcon'; -import { CommandNode } from '../common/CommandNode'; -import { CustomHandle } from '../common/CustomHandle'; import { Position } from '@xyflow/react'; import React from 'react'; +import { CommandNode } from '../common/CommandNode'; +import { CustomHandle } from '../common/CustomHandle'; +import { IconsFromCommand } from '../common/EventIcon'; +import { useHandleConnectionState } from '../hooks/useHandleConnectionState'; +import { CommandNodeProps } from './CommandNodeProps'; export const DefaultCommand = ({ id, data: { dialogsRef, command }, selected }: CommandNodeProps) => { //const date = useMemo(() => new Date().toLocaleString(), []); diff --git a/src/views/components/world/event/commands/editors/CommandEditorOverlay.tsx b/src/views/components/world/event/commands/editors/CommandEditorOverlay.tsx index b669cec30..dec9b5919 100644 --- a/src/views/components/world/event/commands/editors/CommandEditorOverlay.tsx +++ b/src/views/components/world/event/commands/editors/CommandEditorOverlay.tsx @@ -1,12 +1,14 @@ import { defineEditorOverlay } from '@components/editor/EditorOverlayV2'; import { DialogRefData } from '@hooks/useDialogsRef'; -import type { CommandId, StudioEventCommandType } from '@modelEntities/event/command'; +import type { StudioEventCommandType } from '@modelEntities/event/command'; import type { StudioEvent } from '@modelEntities/event/event'; import { assertUnreachable } from '@utils/assertUnreachable'; import React from 'react'; +import { CommandId } from '../../../../../../models/entities/event/globalCommand'; import { DefaultEditor } from './DefaultEditor'; import { InsertScriptEditor } from './InsertScriptEditor'; import { ShowMessageEditor } from './ShowMessageEditor'; +import { WaitMovementCompletionEditor } from './movementCommands/WaitMovementCompletion'; export type CommandEditorAndDeletionKeys = StudioEventCommandType; export type CommandDialogsRef = React.RefObject | null>; @@ -42,6 +44,7 @@ export const CommandEditorOverlay = defineEditorOverlay; case 'manage_event_reappearance': case 'manage_path_finding': case 'manage_follow_me': diff --git a/src/views/components/world/event/commands/editors/EventEditorProps.ts b/src/views/components/world/event/commands/editors/EventEditorProps.ts index b32bc2a30..238464970 100644 --- a/src/views/components/world/event/commands/editors/EventEditorProps.ts +++ b/src/views/components/world/event/commands/editors/EventEditorProps.ts @@ -1,5 +1,5 @@ -import type { CommandId } from '@modelEntities/event/command'; import type { StudioEvent } from '@modelEntities/event/event'; +import { CommandId } from '../../../../../../models/entities/event/globalCommand'; export type EventEditorProps = { commandId?: CommandId; diff --git a/src/views/components/world/event/commands/editors/movementCommands/WaitMovementCompletion.tsx b/src/views/components/world/event/commands/editors/movementCommands/WaitMovementCompletion.tsx new file mode 100644 index 000000000..e3cdc072b --- /dev/null +++ b/src/views/components/world/event/commands/editors/movementCommands/WaitMovementCompletion.tsx @@ -0,0 +1,111 @@ +import { Editor } from '@components/editor'; +import { EditorHandlingClose, useEditorHandlingClose } from '@components/editor/useHandleCloseEditor'; +import { InputFormContainer } from '@components/inputs/InputContainer'; +import { useInputAttrsWithLabel } from '@src/hooks/useInputAttrs'; +import { useZodForm } from '@src/hooks/useZodForm'; +import React, { forwardRef, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import z from 'zod'; +import { + EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR, + StudioEventWaitMovementCompletion, +} from '../../../../../../../models/entities/event/waitCommand/waitMovementCompletion'; +import { InputWithLeftLabelContainer, InputWithTopLabelContainer } from '../../../../../inputs'; +import { useCommandEditor } from '../../../hooks/useCommandEditor'; +import { useSharedOptions } from '../../sharedSelectOptions'; +import { EventEditorProps } from '../EventEditorProps'; + +const TimeoutInfo = styled.div` + ${({ theme }) => theme.fonts.normalSmall}; + color: ${({ theme }) => theme.colors.text400}; + user-select: none; +`; + +const WAIT_MOVEMENT_COMPLETION_EDITOR_SCHEMA = EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR.pick({ + timeout: true, +}).extend({ + waitAllevent: z.enum(['all', 'some']), + waitById: z.array(z.string()).optional(), +}); + +export const WaitMovementCompletionEditor = forwardRef(({ commandId: defaultCommandId, event }, ref) => { + const { command, updateCommand } = useCommandEditor(event, defaultCommandId); + const commandDataForForm = useMemo( + () => ({ + ...command, + waitAllevent: (command.waitAllevent ? 'all' : 'some') as 'all' | 'some', + }), + [command.waitAllevent, command.waitById, command.timeout], + ); + const { globalStaticEvent } = useSharedOptions(); + const { canClose, getFormData, defaults, formRef } = useZodForm(WAIT_MOVEMENT_COMPLETION_EDITOR_SCHEMA, commandDataForForm); + const { Input, Select, MultiSelect } = useInputAttrsWithLabel(WAIT_MOVEMENT_COMPLETION_EDITOR_SCHEMA, defaults); + const timeoutRef = useRef(null); + const { t } = useTranslation(); + + const [waitEvent, setWaitEvent] = useState(command.waitAllevent ? 'all' : 'some'); + + const waitEventOptions = useMemo( + () => [ + { label: t('event_command_wait_move_completion_select_all'), value: 'all' }, + { label: t('event_command_wait_move_completion_select_some'), value: 'some' }, + ], + [t], + ); + + const eventToWaitOptions = useMemo( + () => [ + ...globalStaticEvent(), + { + label: t('event_command_wait_move_completion_multiselect_static_this_event'), + value: event?.dbSymbol as string, + }, + ], + [event?.dbSymbol], + ); + + const onClose = () => { + const result = canClose() && getFormData(); + if (!result || !result.success) return; + + updateCommand({ + ...result.data, + waitAllevent: waitEvent === 'all', + timeout: timeoutRef.current ? Number(timeoutRef.current.value) : result.data.timeout, + }); + }; + useEditorHandlingClose(ref, onClose, canClose); + + return ( + + + + + {t('event_editor_wait_move_completion_timeout_info')} + + + + ); +}); + +WaitMovementCompletionEditor.displayName = 'WaitMovementCompletionEditor'; diff --git a/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx b/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx new file mode 100644 index 000000000..05100c3b3 --- /dev/null +++ b/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import z from 'zod'; +import { useNodeInputAttrsWithLabel } from '../../../../../../../hooks/useInputAttrs'; +import { useZodForm } from '../../../../../../../hooks/useZodForm'; +import { StudioEventCommandData } from '../../../../../../../models/entities/event/command'; +import { + EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR, + StudioEventWaitMovementCompletion, +} from '../../../../../../../models/entities/event/waitCommand/waitMovementCompletion'; +import { InputFormContainer } from '../../../../../inputs/InputContainer'; +import { useEventData } from '../../../common/EventContext'; +import { useCommandNode } from '../../../hooks/useCommandNode'; +import { CommandNodeProps } from '../../CommandNodeProps'; +import { useSharedOptions } from '../../sharedSelectOptions'; + +const WAIT_EDITOR_SCHEMA = EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR.pick({ + waitAllevent: true, +}).extend({ + waitAllevent: z.enum(['all', 'some']), + waitById: z.array(z.string()).optional(), +}); + +export const WaitMovementCompletionCommand = ({ id, data: { dialogsRef, command, comments }, selected }: CommandNodeProps) => { + const { CommandNode, updateCommand } = useCommandNode(id); + const { globalStaticEvent } = useSharedOptions(); + const { type: commandType, ...commandData } = command as StudioEventCommandData; + const { event } = useEventData(); + + const commandDataForForm = useMemo( + () => ({ + ...commandData, + waitAllevent: (commandData.waitAllevent ? 'all' : 'some') as 'all' | 'some', + }), + [commandData], + ); + + const { canClose, getFormData, defaults, formRef } = useZodForm(WAIT_EDITOR_SCHEMA, commandDataForForm); + const { Select, MultiSelect } = useNodeInputAttrsWithLabel(WAIT_EDITOR_SCHEMA, defaults); + const { t } = useTranslation(); + + const [waitEvent, setWaitEvent] = useState(commandData.waitAllevent ? 'all' : 'some'); + + const waitEventOptions = useMemo(() => { + return [ + { label: t('event_command_wait_move_completion_select_all'), value: 'all' }, + { label: t('event_command_wait_move_completion_select_some'), value: 'some' }, + ]; + }, [t]); + + const eventToWaitOptions = useMemo( + () => [ + ...globalStaticEvent(), + { + label: t('event_command_wait_move_completion_multiselect_static_this_event'), + value: event?.dbSymbol as string, + }, + ], + [event?.dbSymbol], + ); + + const onBlur = () => { + const result = canClose() && getFormData(); + if (!result || !result.success) return; + console.log('save', result.data, getFormData); + + updateCommand({ + ...result.data, + waitAllevent: result.data.waitAllevent === 'all', + }); + }; + + const onWaitAllEventChange = (value: string) => { + setWaitEvent(value); + + const result = canClose() && getFormData(); + if (!result || !result.success) return; + + updateCommand({ + ...result.data, + waitAllevent: value === 'all', + }); + }; + + useEffect(() => { + setWaitEvent(commandData.waitAllevent ? 'all' : 'some'); + }, [commandData.waitAllevent]); + + return ( + + + )} - + + {t('event_editor_wait_move_completion_timeout_info')} diff --git a/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx b/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx index 05100c3b3..e24fd3b94 100644 --- a/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx +++ b/src/views/components/world/event/commands/nodes/movementCommands/WaitMovementCompletion.tsx @@ -7,7 +7,7 @@ import { StudioEventCommandData } from '../../../../../../../models/entities/eve import { EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR, StudioEventWaitMovementCompletion, -} from '../../../../../../../models/entities/event/waitCommand/waitMovementCompletion'; +} from '../../../../../../../models/entities/event/movement/waitMovementCompletion'; import { InputFormContainer } from '../../../../../inputs/InputContainer'; import { useEventData } from '../../../common/EventContext'; import { useCommandNode } from '../../../hooks/useCommandNode'; @@ -15,9 +15,9 @@ import { CommandNodeProps } from '../../CommandNodeProps'; import { useSharedOptions } from '../../sharedSelectOptions'; const WAIT_EDITOR_SCHEMA = EVENT_COMMAND_WAIT_MOVEMENT_COMPLETION_VALIDATOR.pick({ - waitAllevent: true, + waitAllEvents: true, }).extend({ - waitAllevent: z.enum(['all', 'some']), + waitAllEvents: z.enum(['all', 'some']), waitById: z.array(z.string()).optional(), }); @@ -30,7 +30,7 @@ export const WaitMovementCompletionCommand = ({ id, data: { dialogsRef, command, const commandDataForForm = useMemo( () => ({ ...commandData, - waitAllevent: (commandData.waitAllevent ? 'all' : 'some') as 'all' | 'some', + waitAllEvents: (commandData.waitAllEvents ? 'all' : 'some') as 'all' | 'some', }), [commandData], ); @@ -39,7 +39,7 @@ export const WaitMovementCompletionCommand = ({ id, data: { dialogsRef, command, const { Select, MultiSelect } = useNodeInputAttrsWithLabel(WAIT_EDITOR_SCHEMA, defaults); const { t } = useTranslation(); - const [waitEvent, setWaitEvent] = useState(commandData.waitAllevent ? 'all' : 'some'); + const [waitEvent, setWaitEvent] = useState(commandData.waitAllEvents ? 'all' : 'some'); const waitEventOptions = useMemo(() => { return [ @@ -62,11 +62,10 @@ export const WaitMovementCompletionCommand = ({ id, data: { dialogsRef, command, const onBlur = () => { const result = canClose() && getFormData(); if (!result || !result.success) return; - console.log('save', result.data, getFormData); updateCommand({ ...result.data, - waitAllevent: result.data.waitAllevent === 'all', + waitAllEvents: result.data.waitAllEvents === 'all', }); }; @@ -78,23 +77,23 @@ export const WaitMovementCompletionCommand = ({ id, data: { dialogsRef, command, updateCommand({ ...result.data, - waitAllevent: value === 'all', + waitAllEvents: value === 'all', }); }; useEffect(() => { - setWaitEvent(commandData.waitAllevent ? 'all' : 'some'); - }, [commandData.waitAllevent]); + setWaitEvent(commandData.waitAllEvents ? 'all' : 'some'); + }, [commandData.waitAllEvents]); return (