8#include "absl/strings/match.h"
9#include "absl/strings/str_format.h"
10#include "absl/strings/str_split.h"
16#include "imgui/imgui.h"
19#include "yaze_config.h"
31 const std::string& right) {
32 if (left.empty() || right.empty()) {
36 std::error_code equivalent_error;
37 if (std::filesystem::equivalent(left, right, equivalent_error)) {
41 std::error_code left_error;
42 std::error_code right_error;
43 const auto left_path =
44 std::filesystem::absolute(left, left_error).lexically_normal();
45 const auto right_path =
46 std::filesystem::absolute(right, right_error).lexically_normal();
47 return !left_error && !right_error && left_path == right_path;
53 const std::string& filepath) {
54 if (project !=
nullptr && project->
filepath == filepath) {
57 std::string key = std::filesystem::path(filepath).stem().string();
58 return key.empty() ?
"project" : key;
74 ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver);
75 if (!ImGui::Begin(absl::StrFormat(
"%s Project Editor###ProjectFileEditor",
84 if (ImGui::BeginTable(
"ProjectEditorToolbar", 10,
85 ImGuiTableFlags_SizingFixedFit)) {
86 ImGui::TableNextColumn();
94 ImGui::TableNextColumn();
102 std::string(status.message().data(), status.message().size()),
108 ImGui::TableNextColumn();
111 ImGui::BeginDisabled();
112 if (ImGui::Button(absl::StrFormat(
"%s Save",
ICON_MD_SAVE).c_str())) {
118 std::string(status.message().data(), status.message().size()),
123 ImGui::EndDisabled();
125 ImGui::TableNextColumn();
126 if (ImGui::Button(absl::StrFormat(
"%s Save As",
ICON_MD_SAVE_AS).c_str())) {
135 std::string(status.message().data(), status.message().size()),
141 ImGui::TableNextColumn();
144 ImGui::TableNextColumn();
147 absl::StrFormat(
"%s Import Labels",
ICON_MD_LABEL).c_str())) {
154 std::string(status.message().data(), status.message().size()),
158 if (ImGui::IsItemHovered()) {
159 ImGui::SetTooltip(tr(
"Import labels from ZScream DefaultNames.txt"));
162 ImGui::TableNextColumn();
169 ImGui::TableNextColumn();
172 ImGui::TableNextColumn();
174 ImGui::TextDisabled(
"%s",
filepath_.c_str());
176 ImGui::TextDisabled(tr(
"No file loaded"));
187 {.bg = ImVec4(0.3f, 0.2f, 0.2f, 0.5f)},
true);
189 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
192 ImGui::BulletText(
"%s", error.c_str());
198 ImVec2 editor_size = ImGui::GetContentRegionAvail();
246 auto storage_or = platform::WasmStorage::LoadProject(key);
247 if (storage_or.ok()) {
253 return absl::OkStatus();
258 if (!file.is_open()) {
259 return absl::InvalidArgumentError(
260 absl::StrFormat(
"Cannot open file: %s",
filepath));
263 std::stringstream buffer;
264 buffer << file.rdbuf();
274 return absl::OkStatus();
279 return absl::InvalidArgumentError(
"No file path specified");
288 if (!absl::EndsWith(final_path,
".yaze")) {
289 final_path +=
".yaze";
298 const std::string key = ProjectStorageKey(
project_, final_path);
299 const bool replace_existing =
301 auto storage_status =
302 platform::WasmStorage::SaveProject(key, contents, replace_existing);
303 if (!storage_status.ok()) {
304 return storage_status;
315 return absl::OkStatus();
317 const bool replace_existing =
318 PathsReferToSameProjectFile(
filepath_, final_path);
335 return absl::OkStatus();
341 return absl::FailedPreconditionError(
342 "Project-file draft has unsaved changes; use Save or Save As first");
344 return absl::OkStatus();
350 const std::string template_content = absl::StrFormat(
351 R
"(# yaze Project File
370ollama_host=http://localhost:11434
371use_custom_prompt=false
375rom_backup_folder=backups
378patches_folder=patches
379labels_filename=labels.txt
380symbols_filename=symbols.txt
382custom_objects_folder=
383# Optional: ASM integration (e.g. Oracle-of-Secrets hack_manifest.json)
393# REMOVED: kLogInstructions - DisassemblyViewer is always active
394kSaveOverworldMaps=true
395kSaveOverworldEntrances=true
396kSaveOverworldExits=true
397kSaveOverworldItems=true
398kSaveOverworldProperties=true
400kSaveDungeonObjects=true
401kSaveDungeonSprites=true
402kSaveDungeonRoomHeaders=true
403kSaveDungeonTorches=true
405kSaveDungeonBlocks=true
406kSaveDungeonCollision=true
407kSaveDungeonChests=true
408kSaveDungeonPotItems=true
409kSaveDungeonPalettes=true
410kSaveGraphicsSheet=true
414kLoadCustomOverworld=false
419autosave_interval_secs=300
421backup_retention_count=20
422backup_keep_daily=true
423backup_keep_daily_days=14
427# expanded_message_start=0x178000
428# expanded_message_end=0x17FFFF
429# expanded_music_hook=0x008919
430# expanded_music_main=0x1A9EF5
431# expanded_music_aux=0x1ACCA7
432# overworld_messages_expanded=0x1417F8
433# overworld_map16_expanded=0x1E8000
434# overworld_map32_tr_expanded=0x020000
435# overworld_map32_bl_expanded=0x1F0000
436# overworld_map32_br_expanded=0x1F8000
437# overworld_entrance_map_expanded=0x0DB55F
438# overworld_entrance_pos_expanded=0x0DB35F
439# overworld_entrance_id_expanded=0x0DB75F
440# overworld_entrance_flag_expanded=0x0DB895
441# overworld_ptr_marker_expanded=0x1423FF
442# overworld_ptr_magic_expanded=0xEA
443# overworld_gfx_ptr1=0x004F80
444# overworld_gfx_ptr2=0x00505F
445# overworld_gfx_ptr3=0x00513E
448# object_0x31=track_LR.bin,track_UD.bin,track_corner_TL.bin
449# object_0x32=furnace.bin,firewood.bin,ice_chair.bin
455asm_entry_point=asm/main.asm
461persist_custom_music=true
472 return absl::OkStatus();
484 std::vector<std::string> lines = absl::StrSplit(content,
'\n');
486 std::string current_section;
489 for (
const auto& line : lines) {
491 std::string trimmed = std::string(absl::StripAsciiWhitespace(line));
494 if (trimmed.empty() || trimmed[0] ==
'#')
498 if (trimmed[0] ==
'[' && trimmed[trimmed.size() - 1] ==
']') {
499 current_section = trimmed.substr(1, trimmed.size() - 2);
503 current_section !=
"workspace_settings") {
505 "Line %d: Unknown section [%s]", line_num, current_section));
511 size_t equals_pos = trimmed.find(
'=');
512 if (equals_pos == std::string::npos) {
514 "Line %d: Invalid format, expected key=value", line_num));
528 if (!text.empty() && text.back() ==
'\n') {
538 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
tr(
"Validation Errors:"));
540 ImGui::BulletText(
"%s", error.c_str());
546 return absl::UnimplementedError(
547 "File-based label import is not supported in the web build");
550 return absl::FailedPreconditionError(
551 "No project loaded. Open a project first.");
557 return absl::CancelledError(
"No file selected");
561 std::ifstream input_file(file);
562 if (!input_file.is_open()) {
563 return absl::InvalidArgumentError(
564 absl::StrFormat(
"Cannot open file: %s", file));
567 std::stringstream buffer;
568 buffer << input_file.rdbuf();
void SetShowWhitespaces(bool aValue)
bool IsTextChanged() const
std::string GetText() const
void Render(const char *aTitle, const ImVec2 &aSize=ImVec2(), bool aBorder=false)
void SetText(const std::string &aText)
void SetTabSize(int aValue)
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
SaveGuardCallback save_guard_callback_
void ApplySyntaxHighlighting()
ToastManager * toast_manager_
absl::Status SaveFileAs(const std::string &filepath)
Save to a new file path.
absl::Status LoadFile(const std::string &filepath)
Load a project file into the editor.
ProjectFileEditorState CaptureState() const
bool IsModified() const
Get whether the file has unsaved changes.
std::string GetDocumentText() const
void RestoreState(const ProjectFileEditorState &state, project::YazeProject *project)
absl::Status CanReplaceDocument() const
std::vector< std::string > validation_errors_
absl::Status SaveFile()
Save the current editor contents to disk.
absl::Status ImportLabelsFromZScream()
Import labels from a ZScream DefaultNames.txt file.
SaveCompleteCallback save_complete_callback_
project::YazeProject * project_
absl::Status NewFile()
Create a new empty project file.
void ResetForProject(project::YazeProject *project)
void ShowValidationErrors()
const std::string & filepath() const
Get the current filepath.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
RAII guard for ImGui child windows with optional styling.
static RecentFilesManager & GetInstance()
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define YAZE_VERSION_STRING
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_DESCRIPTION
bool PathsReferToSameProjectFile(const std::string &left, const std::string &right)
const char * tr(const char *source)
absl::Status WriteProjectFileAtomically(absl::string_view target_path, absl::string_view contents, bool replace_existing)
bool IsKnownProjectFileSection(absl::string_view name)
#define RETURN_IF_ERROR(expr)
static const LanguageDefinition & C()
std::vector< std::string > validation_errors
Modern project structure with comprehensive settings consolidation.
std::string MakeStorageKey(absl::string_view suffix) const
absl::Status ImportLabelsFromZScreamContent(const std::string &content)
Import labels from ZScream format content directly.