diff --git a/assets/i18n/de.json b/assets/i18n/de.json index d340c76c..a95f4d85 100644 --- a/assets/i18n/de.json +++ b/assets/i18n/de.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Entlassen von", "logs": "Zugriff auf die Logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/en.json b/assets/i18n/en.json index b7535f58..8fdbc3e1 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Dismiss", "logs": "Access the logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/es.json b/assets/i18n/es.json index 25fcb496..acbca43d 100644 --- a/assets/i18n/es.json +++ b/assets/i18n/es.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Descartar", "logs": "Acceder a los logs", "version_current_version_editor": "Versión {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/fr.json b/assets/i18n/fr.json index 0bb13531..e95bc000 100644 --- a/assets/i18n/fr.json +++ b/assets/i18n/fr.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Nom de la forme de {{name}}", "translation_form_description": "Description de la forme de {{name}}", "translation_custom_objective": "Objectif personnalisé", + "translation_message": "Message", + "translation_narrator": "Narrateur", "dismiss": "Fermer", "logs": "Accéder aux logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Ajout du fichier CSV pour les noms de dossiers d'événements", "event_command_comment": "Commentaire", "event_command_script": "Script", + "event_command_narrator": "Narrateur", + "event_command_narrator_name": "Nom du narrateur", + "event_command_name_color": "Couleur du nom", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Chen", + "event_command_message": "Message", + "event_command_message_placeholder": "Bienvenue dans le monde des Pokémon !", + "event_command_allow_skipping": "Passer le message", + "event_command_format_options": "Options de formatage", + "event_command_edit_portraits": "Éditer les portraits", + "event_command_message_box": "Boîte de message", + "event_command_show_message_box": "Afficher la boîte", + "event_command_message_box_position": "Position de la boîte", + "event_command_message_box_appearance": "Apparence", + "event_command_other_options": "Options supplémentaires", + "event_command_look_at_this_event": "Regarder l'événement", + "event_command_look_at_this_event_info": "Le joueur se tourne vers l'événement à l'origine du message.", + "event_command_look_to_other_event": "Tourner vers un événement", + "event_command_minimap": "Carte miniature", + "event_command_add_portrait": "Ajouter un portrait", + "event_command_is_mirrored": "Afficher en miroir", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "Le portrait est positionné au dessus de la boite de message. Des coordonnées négatives positionnent le portrait par rapport à la droite de la fenêtre.", + "bottom": "En bas", + "top": "En haut", + "middle": "Au milieu", + "position": "Position", + "opacity": "Opacité", + "image": "Image", "create_event": "Ajouter un évènement", "invalid_data": "Données invalides", "event_title_empty_state": "Commencez à créer un événement !", diff --git a/assets/i18n/it.json b/assets/i18n/it.json index 3e02a7f7..9398c1a7 100644 --- a/assets/i18n/it.json +++ b/assets/i18n/it.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Chiudere", "logs": "Registri di Pokémon Studio", "version_current_version_editor": "Versione {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/ja.json b/assets/i18n/ja.json index c26e5e50..57e79d0d 100644 --- a/assets/i18n/ja.json +++ b/assets/i18n/ja.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Dismiss", "logs": "Access the logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/nl.json b/assets/i18n/nl.json index 946ebc21..5644e2d6 100644 --- a/assets/i18n/nl.json +++ b/assets/i18n/nl.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Negeer", "logs": "Access the logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/pt.json b/assets/i18n/pt.json index ff8309f0..37bdb341 100644 --- a/assets/i18n/pt.json +++ b/assets/i18n/pt.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Descrição da Forma de {{name}}", "translation_custom_objective": "Objetivo personalizado", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Fechar", "logs": "Acessar os registros", "version_current_version_editor": "Versão {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/zh_Hans.json b/assets/i18n/zh_Hans.json index b0769526..bf36db22 100644 --- a/assets/i18n/zh_Hans.json +++ b/assets/i18n/zh_Hans.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Dismiss", "logs": "Access the logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/i18n/zh_Hant.json b/assets/i18n/zh_Hant.json index 40c0594e..48e85f25 100644 --- a/assets/i18n/zh_Hant.json +++ b/assets/i18n/zh_Hant.json @@ -1227,6 +1227,8 @@ "translation_form_name": "Form name of {{name}}", "translation_form_description": "Form description of {{name}}", "translation_custom_objective": "Custom objective", + "translation_message": "Message", + "translation_narrator": "Narrator", "dismiss": "Dismiss", "logs": "Access the logs", "version_current_version_editor": "Version {{current_version_editor}}", @@ -1861,6 +1863,36 @@ "add_csv_for_event_folders": "Adding the CSV file for event folder names", "event_command_comment": "Comment", "event_command_script": "Script", + "event_command_narrator": "Narrator", + "event_command_narrator_name": "Narrator name", + "event_command_name_color": "Name color", + "event_command_name_color_placeholder": "#000000", + "event_command_narrator_placeholder": "Oak", + "event_command_message": "Message", + "event_command_message_placeholder": "Welcome to the world of Pokémon!", + "event_command_allow_skipping": "Allow skipping", + "event_command_format_options": "Formatting options", + "event_command_edit_portraits": "Edit portraits", + "event_command_message_box": "Message box", + "event_command_show_message_box": "Show the message box", + "event_command_message_box_position": "Message box position", + "event_command_message_box_appearance": "Appearance", + "event_command_other_options": "Other options", + "event_command_look_at_this_event": "Look at this event", + "event_command_look_at_this_event_info": "The player will turn to face the event currently showing the message.", + "event_command_look_to_other_event": "Turn to another event", + "event_command_minimap": "Minimap", + "event_command_add_portrait": "Add a portrait", + "event_command_is_mirrored": "Show mirrored", + "event_command_portrait": "Portrait #{{index}}", + "event_command_portrait_title": "Portraits", + "event_command_position_info": "The portrait is positioned above the message box. Negative coordinates position the portrait relative to the right side of the window.", + "bottom": "Bottom", + "top": "Top", + "middle": "Middle", + "position": "Position", + "opacity": "Opacity", + "image": "Image", "create_event": "Add event", "invalid_data": "Invalid data", "event_title_empty_state": "Start creating an event!", diff --git a/assets/icons/global/question-mark-icon.svg b/assets/icons/global/question-mark-icon.svg new file mode 100644 index 00000000..7eb279f8 --- /dev/null +++ b/assets/icons/global/question-mark-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/backendTasks/convertRMXPEventsToStudioEvents.ts b/src/backendTasks/convertRMXPEventsToStudioEvents.ts index b78ccaec..52a89fc0 100644 --- a/src/backendTasks/convertRMXPEventsToStudioEvents.ts +++ b/src/backendTasks/convertRMXPEventsToStudioEvents.ts @@ -1,13 +1,21 @@ -import { StudioMap } from '@modelEntities/map'; -import { readRMXPEvents, RMXPEvent } from './readRMXPEvents'; import { DbSymbol } from '@modelEntities/dbSymbol'; -import type { Appearance, CustomEvent, EventAppearance, EventTrigger, LinkParameter, MapEventLink } from '@modelEntities/event/event'; import type { CommandId, StudioEventCommand } from '@modelEntities/event/command'; +import { + EVENT_START_CSV_FILE_ID, + type Appearance, + type CustomEvent, + type EventAppearance, + type EventTrigger, + type LinkParameter, + type MapEventLink, +} from '@modelEntities/event/event'; +import { StudioMap } from '@modelEntities/map'; +import { findFirstAvailableCsvFileId, findFirstAvailableId } from '@utils/ModelUtils'; import log from 'electron-log'; -import { findFirstAvailableId } from '@utils/ModelUtils'; import { defineBackendServiceFunction } from './defineBackendServiceFunction'; +import { readRMXPEvents, RMXPEvent } from './readRMXPEvents'; -type PartialStudioEvent = { dbSymbol: DbSymbol; id: number }; +type PartialStudioEvent = { dbSymbol: DbSymbol; id: number; csvFileId: number }; export type RMXPEventsToStudioEventsInput = { projectPath: string; map: string; events: PartialStudioEvent[] }; export type RMXPEventsToStudioEventsOutput = {}; //export type RMXPEventsToStudioEventsOutput = { map: StudioMap, events: PartialStudioEvent[], newStudioEvents: unknown[]} @@ -127,8 +135,9 @@ const createLinkParameters = (rmxpEvent: RMXPEvent, pageIndex: number): LinkPara const createNewEventLink = (events: Record, rmxpEvent: RMXPEvent): MapEventLink => { const id = findFirstAvailableId(events, 0); + const csvFileId = findFirstAvailableCsvFileId(events, EVENT_START_CSV_FILE_ID); const dbSymbol = `event_${id}` as DbSymbol; - events[dbSymbol] = { dbSymbol, id }; + events[dbSymbol] = { dbSymbol, id, csvFileId }; return { conditions: [], // TODO: @@ -151,10 +160,12 @@ const getEventTriggers = (rmxpEvent: RMXPEvent): CustomEvent['triggers'] => { })); }; -const createCustomEvent = (rmxpEvent: RMXPEvent, eventIdentifier: PartialStudioEvent): CustomEvent => { +const createCustomEvent = (events: Record, rmxpEvent: RMXPEvent, eventIdentifier: PartialStudioEvent): CustomEvent => { + const csvFileId = findFirstAvailableCsvFileId(events, EVENT_START_CSV_FILE_ID); return { dbSymbol: eventIdentifier.dbSymbol, id: eventIdentifier.id, + csvFileId, type: 'custom', triggers: getEventTriggers(rmxpEvent), commands: {} as Record, // TODO: implement command lists @@ -174,7 +185,7 @@ export const convertRMXPEventsToStudioEvents = async (payload: RMXPEventsToStudi const newEventLink = createNewEventLink(events, rmxpEvent); newEventLinks.push(newEventLink); - const newCustomEvent = createCustomEvent(rmxpEvent, events[newEventLink.eventDbSymbol]); + const newCustomEvent = createCustomEvent(events, rmxpEvent, events[newEventLink.eventDbSymbol]); newStudioEvents.push(newCustomEvent); }, Promise.resolve()); diff --git a/src/hooks/useInputAttrs.tsx b/src/hooks/useInputAttrs.tsx index cb0cfb3d..16403ac0 100644 --- a/src/hooks/useInputAttrs.tsx +++ b/src/hooks/useInputAttrs.tsx @@ -1,5 +1,7 @@ import { + FileInput, Input, + InputWithColorLabelContainer, InputWithLeftLabelContainer, InputWithTopLabelContainer, Label, @@ -8,10 +10,12 @@ import { NodeMultiLineInput, Toggle, } from '@components/inputs'; +import { DropInput } from '@components/inputs/DropInput'; import { EmbeddedUnitInput } from '@components/inputs/EmbeddedUnitInput'; import { Select } from '@ds/Select'; import { inputAttrs } from '@utils/inputAttrs'; -import React, { useMemo } from 'react'; +import { basename } from '@utils/path'; +import React, { useMemo, useRef, useState } from 'react'; import { z } from 'zod'; type WithSchemaKeyAndName = { @@ -22,6 +26,50 @@ type WithSchemaKeyAndName = { // eslint-disable-next-line @typescript-eslint/no-explicit-any type ReactProps any> = Omit[0], 'name'> & WithSchemaKeyAndName; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ReactPropsWithLabel any> = Omit[0], 'name'> & + WithSchemaKeyAndName & { label?: string; labelLeft?: boolean }; + +type ResourceInputFieldProps = Omit, 'onFileChoosen' | 'onFileClear' | 'filePath'> & { + filename: string; +}; + +const createResourceInputField = (schema: z.ZodObject, defaults?: Record) => { + const ResourceInputField = ({ name, schemaKey, filename, ...props }: ResourceInputFieldProps) => { + const { type, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); + const inputRef = useRef(null); + const [value, setValue] = useState((defaults?.[name] as string) ?? ''); + const filePath = value ? `${props.destFolderToCopy}/${value}` : ''; + + const onFileChoosen = (filePath: string) => { + const newValue = basename(filePath); + if (!inputRef?.current) return; + + inputRef.current.value = newValue; + setValue(newValue); + }; + + const onFileClear = () => { + if (!inputRef?.current) return; + + inputRef.current.value = ''; + setValue(''); + }; + + return ( + <> + + {value ? ( + + ) : ( + + )} + + ); + }; + return ResourceInputField; +}; + export const useInputAttrs = (schema: z.ZodObject, defaults?: Record) => { return useMemo( () => ({ @@ -29,19 +77,27 @@ export const useInputAttrs = (schema: z.ZodObject, d EmbeddedUnitInput: ({ name, schemaKey, ...props }: ReactProps) => ( ), + MultiLineInput: ({ name, schemaKey, ...props }: ReactProps) => ( + + ), Select: ({ name, schemaKey, ...props }: ReactProps) => { const { type, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); return ; if (labelLeft) - return ( - - - - - ); + if (props.type === 'color') { + return ( + + + + + ); + } else { + return ( + + + + + ); + } return ( @@ -121,15 +186,26 @@ export const useInputAttrsWithLabel = (schema: z.ZodObj Toggle: ({ name, schemaKey, label, ...props }: ReactProps) => { const { type, required, ...attrs } = inputAttrs(schema, name, defaults, schemaKey); const defaultChecked = attrs.defaultValue === 'true'; - if (!label) return ; + if (!label) return ; return ( - + ); }, + ResourceInput: ({ label, ...props }: ResourceInputFieldProps & { label?: string }) => { + const ResourceInputField = useMemo(() => createResourceInputField(schema, defaults), []); + if (!label) return ; + + return ( + + + + + ); + }, }), [schema, defaults], ); diff --git a/src/models/entities/event/command.ts b/src/models/entities/event/command.ts index 8dd59480..e76ad6a6 100644 --- a/src/models/entities/event/command.ts +++ b/src/models/entities/event/command.ts @@ -1,5 +1,6 @@ -import type { StudioEventCommandCategory } from './category'; +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; @@ -7,10 +8,9 @@ export type CommandId = z.infer; export const COMMAND_CONNECTION_ID_VALIDATOR = z.string().brand('ConnectionId'); export type ConnectionId = z.infer; -// TODO: change for z.number().int() if we use snapToGrid in the event editor const EVENT_COMMAND_STUDIO_DATA_VALIDATOR = z.object({ - x: z.number(), - y: z.number(), + x: z.number().int(), + y: z.number().int(), comments: z.array(z.string()), }); @@ -22,6 +22,42 @@ export const EVENT_COMMAND_CONNECTION_VALIDATOR = z.object({ export type StudioEventCommandConnection = z.infer; +//#region Messages + +export const MESSAGE_BOX_POSITION_VALIDATOR = z.union([z.literal('top'), z.literal('middle'), z.literal('bottom')]); +export type StudioMessageBoxPosition = z.infer; + +export const PORTRAIT_VALIDATOR = z.object({ + image: z.string().default(''), + isMirrored: z.boolean().default(false), + position: z.number().int().default(0), + opacity: z.number().int().default(100), +}); +export type StudioPortrait = z.infer; + +export const EVENT_COMMAND_SHOW_MESSAGE_VALIDATOR = z.object({ + type: z.literal('show_message'), + message: POSITIVE_OR_ZERO_INT, + allowSkipping: z.boolean().default(false), + narrator: POSITIVE_OR_ZERO_INT, + nameColor: z.string().default('#000000'), + showMessageBox: z.boolean().default(true), + messageBoxPosition: MESSAGE_BOX_POSITION_VALIDATOR.default('bottom'), + messageBoxAppearance: z.string().default(''), + lookAtThisEvent: z.boolean().default(false), + lookToOtherEvent: z.string().default('__undef__'), + minimap: z.string().default(''), + portraits: z.array(PORTRAIT_VALIDATOR).default([]), + connections: z.record(COMMAND_CONNECTION_ID_VALIDATOR, EVENT_COMMAND_CONNECTION_VALIDATOR), + studioData: EVENT_COMMAND_STUDIO_DATA_VALIDATOR, +}); + +export type StudioEventCommandShowMessage = z.infer; + +//#endregion + +//#region Scripting + export const EVENT_COMMAND_INSERT_SCRIPT_VALIDATOR = z.object({ type: z.literal('insert_script'), script: z.string().default(''), @@ -31,6 +67,8 @@ export const EVENT_COMMAND_INSERT_SCRIPT_VALIDATOR = z.object({ export type StudioEventCommandInsertScript = z.infer; +//#endregion + const GENERIC_COMMAND = (type: T) => z.object({ type: z.literal(type), @@ -39,7 +77,7 @@ const GENERIC_COMMAND = (type: T) => }); export const EVENT_COMMAND_VALIDATOR = z.discriminatedUnion('type', [ - GENERIC_COMMAND('show_message'), + EVENT_COMMAND_SHOW_MESSAGE_VALIDATOR, GENERIC_COMMAND('narrator_settings'), GENERIC_COMMAND('manage_message_box'), GENERIC_COMMAND('show_choice'), diff --git a/src/models/entities/event/event.ts b/src/models/entities/event/event.ts index e0fe3fcf..0c4f780c 100644 --- a/src/models/entities/event/event.ts +++ b/src/models/entities/event/event.ts @@ -53,6 +53,7 @@ const EVENT_TRIGGER_VALIDATOR = z.object({ export const CUSTOM_EVENT_VALIDATOR = z.object({ dbSymbol: DB_SYMBOL_VALIDATOR, id: POSITIVE_OR_ZERO_INT, + csvFileId: POSITIVE_OR_ZERO_INT, type: z.literal('custom'), commands: z.record(COMMAND_ID_VALIDATOR, EVENT_COMMAND_VALIDATOR), triggers: z.array(EVENT_TRIGGER_VALIDATOR), @@ -118,3 +119,4 @@ export const EVENT_VALIDATOR = CUSTOM_EVENT_VALIDATOR.extend({ klass: z.literal( export type StudioEvent = z.infer; export const EVENT_NAME_TEXT_ID = 200005; +export const EVENT_START_CSV_FILE_ID = 500000; diff --git a/src/poc/ConvertEventsPage.tsx b/src/poc/ConvertEventsPage.tsx index c8748aaa..d12a1fd0 100644 --- a/src/poc/ConvertEventsPage.tsx +++ b/src/poc/ConvertEventsPage.tsx @@ -1,15 +1,21 @@ import { PrimaryButton } from '@components/buttons'; import { SelectMap } from '@components/selects'; -import { useProjectData } from '@src/hooks/useProjectData'; -import React, { useState } from 'react'; +import { useProjectData, useProjectEvents } from '@src/hooks/useProjectData'; +import React, { useMemo, useState } from 'react'; export const ConvertEventsPage = () => { const { projectDataValues: maps, state } = useProjectData('maps', 'map'); + const { projectDataValues: allEvents } = useProjectEvents(); const projectPath = state.projectPath!; const [mapDbSymbol, setMapDbSymbol] = useState('map001'); const [result, setResult] = useState(''); + const events = useMemo(() => Object.values(allEvents), [allEvents]); const map = maps[mapDbSymbol]; + // TODO: don't forget to create the new csv + // const setNewProjectText = useNewProjectText(); + // setNewProjectText(event.csvFileId); + return (
@@ -17,12 +23,12 @@ export const ConvertEventsPage = () => { window.api.convertRMXPEventsToStudioEvents( - { events: [], map: JSON.stringify(map), projectPath }, + { events, map: JSON.stringify(map), projectPath }, () => setResult(`Success! (Map id: ${map.id})`), ({ errorMessage }) => { console.error(errorMessage); setResult(`Error! (Map id: ${map.id}) Read the console log`); - } + }, ) } > diff --git a/src/utils/ModelUtils.ts b/src/utils/ModelUtils.ts index 3d85963f..865659a5 100644 --- a/src/utils/ModelUtils.ts +++ b/src/utils/ModelUtils.ts @@ -1,3 +1,4 @@ +import { StudioEvent } from '@modelEntities/event/event'; import { StudioTextInfo } from '@modelEntities/textInfo'; import { StudioTrainerAdditionalDialogs } from '@modelEntities/trainer'; import { ProjectData } from '@src/GlobalStateProvider'; @@ -8,7 +9,7 @@ import { ProjectData } from '@src/GlobalStateProvider'; * @returns The text id */ export const findFirstAvailableTextId = ( - allData: ProjectData['abilities'] | ProjectData['types'] | StudioTextInfo[] | StudioTrainerAdditionalDialogs[] + allData: ProjectData['abilities'] | ProjectData['types'] | StudioTextInfo[] | StudioTrainerAdditionalDialogs[], ) => { const textIdSet = Object.values(allData) .map(({ textId }) => textId) // Fetch all ids @@ -109,3 +110,37 @@ export const findFirstAvailableCustomObjectiveTextId = (allQuests: ProjectData[' return textIdSet[holeIndex - 1] + 1; }; + +export const findFirstAvailableCsvFileId = (allData: Record, startId: number) => { + const values = Object.values(allData); + if (values.length === 0) return startId; + + const idSet = values + .map(({ csvFileId }) => csvFileId) // Fetch all csvFileIds + .filter((id, index, array) => index === array.indexOf(id)) // reject all duplicates + .sort((a, b) => a - b); // sort id by ascending order + // Since ids are ordered, if the first isn't the startId that means we need to fill the beginning of the list ;) + if (idSet[0] > startId) return startId; + + const holeIndex = idSet.findIndex((id, index) => id !== index + startId); + if (holeIndex === -1) return idSet[idSet.length - 1] + 1; + + return idSet[holeIndex - 1] + 1; +}; + +export const findFirstAvailableTextIdEvent = (event: StudioEvent, startId: number) => { + const commands = Object.values(event.commands).filter((command) => !!command && command.type === 'show_message'); + if (commands.length === 0) return { messageId: startId, narratorId: startId + 1 }; + + const idSet = commands + .reduce((prev, { message, narrator }) => [...prev, message, narrator], []) + .filter((id, index, array) => index === array.indexOf(id)) // reject all duplicates + .sort((a, b) => a - b); // sort id by ascending order + // Since ids are ordered, if the first isn't the startId that means we need to fill the beginning of the list ;) + if (idSet[0] > startId && idSet[1] > startId + 1) return { messageId: startId, narratorId: startId + 1 }; + + const holeIndex = idSet.findIndex((id, index) => id !== index + startId); + if (holeIndex === -1) return { messageId: idSet[idSet.length - 1] + 1, narratorId: idSet[idSet.length - 1] + 2 }; + + return { messageId: idSet[holeIndex - 1] + 1, narratorId: idSet[holeIndex - 1] + 2 }; +}; diff --git a/src/utils/entityCreation.ts b/src/utils/entityCreation.ts index e8bc97b8..466afbd2 100644 --- a/src/utils/entityCreation.ts +++ b/src/utils/entityCreation.ts @@ -5,8 +5,11 @@ import { DEX_DEFAULT_NAME_TEXT_ID, StudioDex, StudioDexCreature } from '@modelEn import type { StudioCustomGroupCondition, StudioGroup, StudioGroupSystemTag, StudioGroupTool, StudioGroupVsType } from '@modelEntities/group'; import { createExpandPokemonSetup, StudioGroupEncounter } from '@modelEntities/groupEncounter'; import type { StudioItem, StudioItemStatusCondition } from '@modelEntities/item'; +import type { StudioMap, StudioMapAudio } from '@modelEntities/map'; +import type { StudioMapInfo, StudioMapInfoMap } from '@modelEntities/mapInfo'; import type { StudioMapLink } from '@modelEntities/mapLink'; import type { StudioMove, StudioMoveCategory, StudioMoveCondition } from '@modelEntities/move'; +import type { StudioNature } from '@modelEntities/nature'; import type { StudioCreatureQuestCondition, StudioCreatureQuestConditionType, @@ -17,21 +20,24 @@ import type { StudioQuestObjectiveType, StudioQuestResolution, } from '@modelEntities/quest'; +import type { StudioTextInfo } from '@modelEntities/textInfo'; import type { StudioTrainer, StudioTrainerVsType } from '@modelEntities/trainer'; import type { StudioType } from '@modelEntities/type'; import type { StudioZone } from '@modelEntities/zone'; import { ProjectData } from '@src/GlobalStateProvider'; +import { CommandId, StudioEventCommand } from '../models/entities/event/command'; +import { EVENT_START_CSV_FILE_ID, StudioEvent } from '../models/entities/event/event'; import { assertUnreachable } from './assertUnreachable'; -import { findFirstAvailableCustomObjectiveTextId, findFirstAvailableFormTextId, findFirstAvailableId, findFirstAvailableTextId } from './ModelUtils'; -import { padStr } from './PadStr'; -import type { StudioTextInfo } from '@modelEntities/textInfo'; -import type { StudioMap, StudioMapAudio } from '@modelEntities/map'; -import type { StudioMapInfo, StudioMapInfoMap } from '@modelEntities/mapInfo'; -import type { StudioNature } from '@modelEntities/nature'; -import { mapInfoFindFirstAvailableId, mapInfoFindFirstAvailableTextId } from './MapInfoUtils'; import { cloneEntity } from './cloneEntity'; -import { CommandId, StudioEventCommand } from '../models/entities/event/command'; -import { CustomEvent, StudioEvent } from '../models/entities/event/event'; +import { mapInfoFindFirstAvailableId, mapInfoFindFirstAvailableTextId } from './MapInfoUtils'; +import { + findFirstAvailableCsvFileId, + findFirstAvailableCustomObjectiveTextId, + findFirstAvailableFormTextId, + findFirstAvailableId, + findFirstAvailableTextId, +} from './ModelUtils'; +import { padStr } from './PadStr'; /** * Create a new ability with default values @@ -615,10 +621,15 @@ export const createNature = (allNatures: ProjectData['natures'], dbSymbol: DbSym }; }; -export const createEvent = (dbSymbol: DbSymbol, id: number): StudioEvent => { +export const createEvent = (allEvents: ProjectData['events']): StudioEvent => { + const id = findFirstAvailableId(allEvents, 1); + const dbSymbol = `event_${id}` as DbSymbol; + const csvFileId = findFirstAvailableCsvFileId(allEvents, EVENT_START_CSV_FILE_ID); + return { dbSymbol, id, + csvFileId, klass: 'Event', type: 'custom', triggers: [], diff --git a/src/utils/eventCommandCreation.ts b/src/utils/eventCommandCreation.ts index d7601508..622ab5ff 100644 --- a/src/utils/eventCommandCreation.ts +++ b/src/utils/eventCommandCreation.ts @@ -1,79 +1,101 @@ -import type { StudioEventCommandType, StudioEventCommand, StudioEventCommandData } from '@modelEntities/event/command'; +import type { StudioEventCommand, StudioEventCommandData, StudioEventCommandType } from '@modelEntities/event/command'; +import { StudioEvent } from '@modelEntities/event/event'; +import { findFirstAvailableTextIdEvent } from './ModelUtils'; -const insertScriptCommand = { script: '' }; - -export const EventCommandCreation: Record, 'type'>> = { - show_message: {}, - narrator_settings: {}, - manage_message_box: {}, - show_choice: {}, - wait_key_press: {}, - record_key_press: {}, - input_creature_name: {}, - input_character_name: {}, - ask_player_for_number: {}, - create_loop: {}, - exit_loop: {}, - manage_conditions: {}, - go_to: {}, - wait_for_set_time: {}, - stop_event_execution: {}, - call_event: {}, - trigger_event: {}, - change_event_parameters: {}, - move_event: {}, - teleport_event: {}, - teleport_player: {}, - wait_move_completion: {}, - manage_event_reappearance: {}, - manage_path_finding: {}, - manage_follow_me: {}, - manage_variables: {}, - manage_event_variables: {}, - manage_timer: {}, - change_character_name: {}, - start_trainer_battle: {}, - start_wild_encounter: {}, - start_scripted_battle: {}, - manage_random_encounters: {}, - manage_player_items: {}, - manage_player_money: {}, - manage_dex: {}, - set_active_dex: {}, - give_badge: {}, - manage_access_save_menu: {}, - open_save_menu: {}, - manage_autosave: {}, - force_autosave: {}, - force_save: {}, - open_scene: {}, - open_shop: {}, - open_custom_scene: {}, - manage_access_main_menu: {}, - trigger_game_over: {}, - return_to_title_screen: {}, - open_creature_shop: {}, - start_quest: {}, - display_hidden_objective: {}, - validate_quest_objectives: {}, - display_quest_progress: {}, - complete_quest: {}, - play_sound: {}, - stop_current_sound: {}, - change_default_sound: {}, - memorize_background_sounds: {}, - restore_background_sounds: {}, - play_creature_cry: {}, - change_screen_tone: {}, - display_animation: {}, - display_screen_animation: {}, - display_emotion: {}, - manage_image: {}, - manage_camera: {}, - manage_dynamic_light: {}, - change_weather: {}, - manage_map_fog: {}, - manage_map_panorama: {}, - change_battle_background: {}, - insert_script: insertScriptCommand, +const createShowMessageCommand = (event: StudioEvent) => { + const { messageId, narratorId } = findFirstAvailableTextIdEvent(event, 0); + return { + message: messageId, + allowSkipping: false, + narrator: narratorId, + nameColor: '#000000', + showMessageBox: true, + messageBoxPosition: 'bottom', + messageBoxAppearance: '', + lookAtThisEvent: false, + lookToOtherEvent: '__undef__', + minimap: '', + portraits: [], + }; }; + +const insertScriptCommand = () => ({ script: '' }); + +const dummy = () => ({}); + +export const EventCommandCreation: Record Omit, 'type'>> = + { + show_message: createShowMessageCommand, + narrator_settings: dummy, + manage_message_box: dummy, + show_choice: dummy, + wait_key_press: dummy, + record_key_press: dummy, + input_creature_name: dummy, + input_character_name: dummy, + ask_player_for_number: dummy, + create_loop: dummy, + exit_loop: dummy, + manage_conditions: dummy, + go_to: dummy, + wait_for_set_time: dummy, + stop_event_execution: dummy, + call_event: dummy, + trigger_event: dummy, + change_event_parameters: dummy, + move_event: dummy, + teleport_event: dummy, + teleport_player: dummy, + wait_move_completion: dummy, + manage_event_reappearance: dummy, + manage_path_finding: dummy, + manage_follow_me: dummy, + manage_variables: dummy, + manage_event_variables: dummy, + manage_timer: dummy, + change_character_name: dummy, + start_trainer_battle: dummy, + start_wild_encounter: dummy, + start_scripted_battle: dummy, + manage_random_encounters: dummy, + manage_player_items: dummy, + manage_player_money: dummy, + manage_dex: dummy, + set_active_dex: dummy, + give_badge: dummy, + manage_access_save_menu: dummy, + open_save_menu: dummy, + manage_autosave: dummy, + force_autosave: dummy, + force_save: dummy, + open_scene: dummy, + open_shop: dummy, + open_custom_scene: dummy, + manage_access_main_menu: dummy, + trigger_game_over: dummy, + return_to_title_screen: dummy, + open_creature_shop: dummy, + start_quest: dummy, + display_hidden_objective: dummy, + validate_quest_objectives: dummy, + display_quest_progress: dummy, + complete_quest: dummy, + play_sound: dummy, + stop_current_sound: dummy, + change_default_sound: dummy, + memorize_background_sounds: dummy, + restore_background_sounds: dummy, + play_creature_cry: dummy, + change_screen_tone: dummy, + display_animation: dummy, + display_screen_animation: dummy, + display_emotion: dummy, + manage_image: dummy, + manage_camera: dummy, + manage_dynamic_light: dummy, + change_weather: dummy, + manage_map_fog: dummy, + manage_map_panorama: dummy, + change_battle_background: dummy, + insert_script: insertScriptCommand, + }; diff --git a/src/utils/events/EventUtils.ts b/src/utils/events/EventUtils.ts index 0e49d661..ea8ea4a0 100644 --- a/src/utils/events/EventUtils.ts +++ b/src/utils/events/EventUtils.ts @@ -31,7 +31,7 @@ export const initCommandNodes = (event: StudioEvent, dialogsRef?: CommandDialogs id, type: command?.type, position: { x: command?.studioData.x || 0, y: command?.studioData.y || 0 }, - data: { dialogsRef, command, comments: command?.studioData.comments }, + data: { dialogsRef, command, comments: command?.studioData.comments, csvFileId: event.csvFileId }, })); }; diff --git a/src/utils/inputAttrs.ts b/src/utils/inputAttrs.ts index 89302e64..91682e32 100644 --- a/src/utils/inputAttrs.ts +++ b/src/utils/inputAttrs.ts @@ -10,7 +10,7 @@ type InputProps = Pick< export const inputAttrsSingle = ( singleAttributeValidator: z.ZodFirstPartySchemaTypes, name: string, - defaults?: Record + defaults?: Record, ): InputProps => { if (singleAttributeValidator instanceof z.ZodBranded) { return inputAttrsSingle(singleAttributeValidator.unwrap(), name, defaults); @@ -88,20 +88,27 @@ export const inputAttrsSingle = ( return attributes; }; +const unwrapValidator = (validator: z.ZodFirstPartySchemaTypes): z.ZodFirstPartySchemaTypes => { + if (validator instanceof z.ZodDefault) return unwrapValidator(validator._def.innerType as z.ZodFirstPartySchemaTypes); + if (validator instanceof z.ZodOptional) return unwrapValidator(validator._def.innerType as z.ZodFirstPartySchemaTypes); + if (validator instanceof z.ZodNullable) return unwrapValidator(validator._def.innerType as z.ZodFirstPartySchemaTypes); + return validator; +}; + const getValidatorFromSchema = (schema: z.ZodObject, schemaKey: string) => { const keys = schemaKey.split('.'); let validator: z.ZodFirstPartySchemaTypes = schema; for (const key of keys) { + const unwrapped = unwrapValidator(validator); if (isStringPositiveInteger(key)) { - if (validator instanceof z.ZodArray) { - const newValidator = validator._def.type as z.ZodFirstPartySchemaTypes; - validator = newValidator; + if (unwrapped instanceof z.ZodArray) { + validator = unwrapped._def.type as z.ZodFirstPartySchemaTypes; } else { throw new Error('Cannot have extract type from non array object with numeric key'); } } else { - if (validator instanceof z.ZodObject) { - validator = validator.shape[key]; + if (unwrapped instanceof z.ZodObject) { + validator = unwrapped.shape[key]; if (!validator) throw new Error(`Failed to extract ${key} from schema (${schemaKey})`); } else { throw new Error('Expect simple Zod object with string key, consider giving a non-opaque schema with a schemaKey instead.'); diff --git a/src/views/components/buttons/DarkButtonWithPlusIcon.tsx b/src/views/components/buttons/DarkButtonWithPlusIcon.tsx index fea200a7..f895a4fa 100644 --- a/src/views/components/buttons/DarkButtonWithPlusIcon.tsx +++ b/src/views/components/buttons/DarkButtonWithPlusIcon.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import PlusIcon from '@assets/icons/global/plus-icon.svg'; +import EditIcon from '@assets/icons/global/edit-icon.svg'; import ImportIcon from '@assets/icons/global/import-icon.svg'; +import PlusIcon from '@assets/icons/global/plus-icon.svg'; +import QuestionMarkIcon from '@assets/icons/global/question-mark-icon.svg'; import ReOrderIcon from '@assets/icons/global/reorder.svg'; -import { SecondaryButton, DarkButton } from './GenericButtons'; +import React from 'react'; import styled from 'styled-components'; +import { DarkButton, SecondaryButton } from './GenericButtons'; type DarkButtonWithPlusIconProps = Omit[0], 'theme'>; @@ -59,3 +61,17 @@ export const DarkButtonReOrderResponsive = ({ children, disabled, breakpoint, .. {children} ); + +export const DarkButtonEditResponsive = ({ children, disabled, ...props }: DarkButtonWithPlusIconResponsiveProps) => ( + + + {children} + +); + +export const DarkButtonQuestionMarkResponsive = ({ children, disabled, ...props }: DarkButtonWithPlusIconResponsiveProps) => ( + + + {children} + +); diff --git a/src/views/components/database/type/editors/TypeFrameEditor.tsx b/src/views/components/database/type/editors/TypeFrameEditor.tsx index 4adc0233..d244cdea 100644 --- a/src/views/components/database/type/editors/TypeFrameEditor.tsx +++ b/src/views/components/database/type/editors/TypeFrameEditor.tsx @@ -67,7 +67,7 @@ export const TypeFrameEditor = forwardRef((_, ref) => { - setColor(event.target.value)} /> + setColor(event.target.value)} /> diff --git a/src/views/components/editor/Editor.tsx b/src/views/components/editor/Editor.tsx index 9a71a467..26c110a0 100644 --- a/src/views/components/editor/Editor.tsx +++ b/src/views/components/editor/Editor.tsx @@ -1,3 +1,5 @@ +import ClearIcon from '@assets/icons/global/clear-tag-icon.svg'; +import { DarkButton } from '@components/buttons'; import { PaginationWithTitle, PaginationWithTitleProps } from '@components/PaginationWithTitle'; import React, { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; @@ -38,6 +40,31 @@ export const EditorTitle = styled.div` } `; +export const EditorTitleContainer = styled.div` + display: flex; + gap: 8px; + justify-content: space-between; + + ${DarkButton} { + padding: 0; + min-width: 32px; + height: 32px; + } + + ${EditorTitle} { + padding: 0; + + & > h3 { + padding: 0; + border: none; + } + } + + padding: 0 0 12px 0; + border-bottom: 1px solid ${({ theme }) => theme.colors.dark20}; + margin-bottom: 16px; +`; + type EditorProps = { type: | 'edit' @@ -55,31 +82,46 @@ type EditorProps = { | 'combo_moves'; title: string; children: ReactNode; + onClose?: () => void; }; -export const Editor = ({ type, title, children }: EditorProps) => { +export const Editor = ({ type, title, children, onClose }: EditorProps) => { const { t } = useTranslation(); return ( - -

{t(type)}

-

{title}

-
+ + +

{t(type)}

+

{title}

+
+ {onClose && ( + + + + )} +
{children}
); }; -export const EditorWithCollapse = ({ type, title, children }: EditorProps) => { +export const EditorWithCollapse = ({ type, title, children, onClose }: EditorProps) => { const { t } = useTranslation(); return ( - -

{t(type)}

-

{title}

-
+ + +

{t(type)}

+

{title}

+
+ {onClose && ( + + + + )} +
{children}
); @@ -87,7 +129,7 @@ export const EditorWithCollapse = ({ type, title, children }: EditorProps) => { type EditorWithPaginationProps = { paginationProps?: PaginationWithTitleProps; -} & EditorProps; +} & Omit; export const EditorWithPagination = ({ type, title, children, paginationProps }: EditorWithPaginationProps) => { const { t } = useTranslation(); diff --git a/src/views/components/editor/TranslationEditorWithCloseHandling.tsx b/src/views/components/editor/TranslationEditorWithCloseHandling.tsx index 083f12a5..b67de173 100644 --- a/src/views/components/editor/TranslationEditorWithCloseHandling.tsx +++ b/src/views/components/editor/TranslationEditorWithCloseHandling.tsx @@ -1,17 +1,17 @@ +import ClearIcon from '@assets/icons/global/clear-tag-icon.svg'; import { DarkButton } from '@components/buttons'; +import { Input, InputContainer, InputWithTopLabelContainer, Label, MultiLineInput } from '@components/inputs'; +import { SecondaryTag } from '@components/Tag'; +import { getProjectMultiLanguageTextChange } from '@hooks/updateProjectText'; +import { useGlobalState } from '@src/GlobalStateProvider'; +import { getText, useGetProjectText } from '@utils/ReadingProjectText'; +import { SavingTextMap } from '@utils/SavingUtils'; import React, { forwardRef, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; -import { EditorTitle } from './Editor'; +import { EditorTitle, EditorTitleContainer } from './Editor'; import { EditorContainer } from './EditorContainer'; -import ClearIcon from '@assets/icons/global/clear-tag-icon.svg'; -import { useGlobalState } from '@src/GlobalStateProvider'; -import { Input, InputContainer, InputWithTopLabelContainer, Label, MultiLineInput } from '@components/inputs'; -import { SecondaryTag } from '@components/Tag'; -import { getText, useGetProjectText } from '@utils/ReadingProjectText'; import { EditorHandlingClose, useEditorHandlingClose } from './useHandleCloseEditor'; -import { getProjectMultiLanguageTextChange } from '@hooks/updateProjectText'; -import { SavingTextMap } from '@utils/SavingUtils'; const TranslationEditorContainer = styled(EditorContainer)` position: absolute; @@ -31,31 +31,6 @@ const TranslationEditorContainer = styled(EditorContainer)` } `; -const TranslateEditorTitleContainer = styled.div` - display: flex; - gap: 8px; - justify-content: space-between; - - ${DarkButton} { - padding: 0; - min-width: 32px; - height: 32px; - } - - ${EditorTitle} { - padding: 0; - - & > h3 { - padding: 0; - border: none; - } - } - - padding: 0 0 12px 0; - border-bottom: 1px solid ${({ theme }) => theme.colors.dark20}; - margin-bottom: 16px; -`; - type TranslationInputProps = { defaultValue: string; name: string; @@ -83,6 +58,8 @@ export type TranslationEditorTitle = | 'translation_additional_dialog' | 'translation_form_name' | 'translation_form_description' + | 'translation_message' + | 'translation_narrator' | 'translation_custom_objective'; type InputRefsType = Record; @@ -111,16 +88,16 @@ const TranslationEditor = ({ title, name, textId, fileId, onClose, isMultiline, state.projectStudio.languagesTranslation .map<[string, number]>(({ code }, index) => [code, index]) .filter(([code]) => code !== defaultLanguageCode), - [state.projectStudio.languagesTranslation, defaultLanguageCode] + [state.projectStudio.languagesTranslation, defaultLanguageCode], ); const defaultLanguageName = useMemo( () => state.projectStudio.languagesTranslation.find(({ code }) => code === defaultLanguageCode)?.name || '???', - [defaultLanguageCode, state.projectStudio.languagesTranslation] + [defaultLanguageCode, state.projectStudio.languagesTranslation], ); return ( - +

{t('translation')}

{t(title, { name })}

@@ -128,7 +105,7 @@ const TranslationEditor = ({ title, name, textId, fileId, onClose, isMultiline, -
+