Plan document export success messages

This commit is contained in:
2026-06-05 09:31:11 +02:00
parent f46839bf5c
commit 808a084ee3
8 changed files with 575 additions and 34 deletions

View File

@@ -649,6 +649,10 @@ Known local toolchain state:
for image file exports, layer/frame collection directories, picked-directory
stems, and MP4 suggested names as JSON and is covered for file, collection,
and suggested-name states.
- `pano_cli plan-export-message` exposes `pp_app_core` export completion
dialog metadata for equirectangular image, layer/frame collection,
depth/cube, animation MP4, and timelapse success paths as JSON, including
platform-style destinations and suppressed/no-message paths.
- `pano_cli plan-export-start` exposes `pp_app_core` export availability
planning for license-gated, demo-blocked, and missing-canvas states as JSON;
the live image, layer, animation-frame, depth, and cube-face export dialogs

File diff suppressed because one or more lines are too long

View File

@@ -519,6 +519,10 @@ stems, and MP4 suggested names used by the live export dialogs.
used by live image, layer, animation-frame, depth, and cube-face export dialogs
plus MP4 animation and timelapse export dialogs before they call legacy
canvas/recording export execution.
`pano_cli plan-export-message` exposes the app-core export completion dialog
metadata now consumed by the live legacy export bridge for equirectangular,
layer/frame, depth/cube, animation MP4, and timelapse success reporting,
including platform-style destinations and no-message/suppressed branches.
`pano_cli plan-recording-session` exposes the app-core recording start, stop,
clear, platform recorded-file cleanup, frame reset, and export progress-total
decisions used by the live recording controls. Recording lifecycle and MP4
@@ -655,6 +659,9 @@ choices, including image, layer, cube-face, depth, animation-frame, MP4, and
timelapse dialog routing plus license/canvas gating. Export menu commands now
dispatch through `DocumentExportMenuServices` in the shared app-shell bridge
before legacy export dialogs and renderer/video execution continue.
Export success-message metadata now also comes from `pp_app_core` through
`pano_cli plan-export-message` and the legacy document-export bridge, reducing
the bridge to export execution, platform handoff, and retained threading.
`pano_cli plan-grid-operation` exposes app-core planning for grid heightmap
pick/load/reload/clear, lightmap render capability/limit checks, and heightmap
commit used by the live grid panel. Grid execution now dispatches through

View File

@@ -1,5 +1,6 @@
#pragma once
#include "app_core/app_dialog.h"
#include "foundation/result.h"
#include <string>
@@ -71,6 +72,26 @@ enum class DocumentVideoExportKind {
timelapse,
};
enum class DocumentExportSuccessKind {
equirectangular,
layers,
animation_frames,
depth,
cube_faces,
animation_mp4,
timelapse,
};
enum class DocumentExportSuccessDestination {
suppressed,
photos,
pictures_panopainter,
files_panopainter,
work_directory,
path,
generic_success,
};
struct DocumentExportMenuPlan {
DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg;
DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog;
@@ -83,6 +104,13 @@ struct DocumentExportCollectionTargetPlan {
std::string_view suffix;
};
struct DocumentExportSuccessDialogPlan {
DocumentExportSuccessKind kind = DocumentExportSuccessKind::equirectangular;
DocumentExportSuccessDestination destination = DocumentExportSuccessDestination::suppressed;
AppMessageDialogPlan dialog;
bool show_dialog = false;
};
class DocumentExportMenuServices {
public:
virtual ~DocumentExportMenuServices() = default;
@@ -180,6 +208,32 @@ public:
return DocumentExportMenuAction::show_jpeg_dialog;
}
[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_equirectangular_platform_destination() noexcept
{
#if defined(__IOS__)
return DocumentExportSuccessDestination::photos;
#elif defined(__OSX__)
return DocumentExportSuccessDestination::pictures_panopainter;
#elif defined(_WIN32)
return DocumentExportSuccessDestination::work_directory;
#else
return DocumentExportSuccessDestination::suppressed;
#endif
}
[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_media_platform_destination() noexcept
{
#if defined(__IOS__)
return DocumentExportSuccessDestination::files_panopainter;
#elif defined(__OSX__)
return DocumentExportSuccessDestination::pictures_panopainter;
#elif defined(_WIN32)
return DocumentExportSuccessDestination::work_directory;
#else
return DocumentExportSuccessDestination::suppressed;
#endif
}
[[nodiscard]] constexpr DocumentExportMenuPlan plan_document_export_menu_action(
DocumentExportMenuKind kind,
bool has_canvas,
@@ -230,6 +284,99 @@ public:
};
}
[[nodiscard]] inline DocumentExportSuccessDialogPlan plan_document_export_success_dialog(
DocumentExportSuccessKind kind,
DocumentExportSuccessDestination destination,
std::string_view detail = {})
{
DocumentExportSuccessDialogPlan plan;
plan.kind = kind;
plan.destination = destination;
if (destination == DocumentExportSuccessDestination::suppressed) {
return plan;
}
std::string message;
switch (kind) {
case DocumentExportSuccessKind::equirectangular:
plan.dialog.title = "Export Equirectangular";
switch (destination) {
case DocumentExportSuccessDestination::photos:
message = "Image exported to Photos";
break;
case DocumentExportSuccessDestination::pictures_panopainter:
message = "Image exported to Pictures/PanoPainter folder";
break;
case DocumentExportSuccessDestination::work_directory:
message = "Image exported to ";
message += detail;
break;
case DocumentExportSuccessDestination::suppressed:
case DocumentExportSuccessDestination::files_panopainter:
case DocumentExportSuccessDestination::path:
case DocumentExportSuccessDestination::generic_success:
break;
}
break;
case DocumentExportSuccessKind::layers:
case DocumentExportSuccessKind::animation_frames:
plan.dialog.title = "Export Layers";
if (destination == DocumentExportSuccessDestination::files_panopainter) {
message = "Image layers exported to Files/PanoPainter";
} else if (destination == DocumentExportSuccessDestination::path) {
message = "Layers exported to: ";
message += detail;
}
break;
case DocumentExportSuccessKind::depth:
plan.dialog.title = "Export 3D View + Depth";
if (destination == DocumentExportSuccessDestination::files_panopainter) {
message = "Image and depth exported to Files/PanoPainter";
} else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
message = "Image and depth exported to Pictures/PanoPainter folder";
} else if (destination == DocumentExportSuccessDestination::work_directory) {
message = "Image and depth exported to ";
message += detail;
}
break;
case DocumentExportSuccessKind::cube_faces:
plan.dialog.title = "Export Cube Faces";
if (destination == DocumentExportSuccessDestination::files_panopainter) {
message = "Image and depth exported to Files/PanoPainter";
} else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
message = "Image and depth exported to Pictures/PanoPainter folder";
} else if (destination == DocumentExportSuccessDestination::work_directory) {
message = "Image and depth exported to ";
message += detail;
}
break;
case DocumentExportSuccessKind::animation_mp4:
plan.dialog.title = "Export Animation";
if (destination == DocumentExportSuccessDestination::path) {
message = "Animation exported to: ";
message += detail;
} else if (destination == DocumentExportSuccessDestination::generic_success) {
message = "Animation exported successfully.";
}
break;
case DocumentExportSuccessKind::timelapse:
plan.dialog.title = "Export Timelapse";
if (destination == DocumentExportSuccessDestination::path) {
message = "Timelapse exported to: ";
message += detail;
} else if (destination == DocumentExportSuccessDestination::generic_success) {
message = "Timelapse exported successfully.";
}
break;
}
if (!plan.dialog.title.empty() && !message.empty()) {
plan.dialog.message = std::move(message);
plan.show_dialog = true;
}
return plan;
}
[[nodiscard]] inline pp::foundation::Result<DocumentExportFileTarget> make_document_export_file_target(
std::string_view work_directory,
std::string_view document_name,

View File

@@ -10,6 +10,15 @@
namespace pp::panopainter {
namespace {
void show_export_success_dialog(
App& app,
const pp::app::DocumentExportSuccessDialogPlan& plan)
{
if (plan.show_dialog) {
app.message_box(plan.dialog.title, plan.dialog.message, plan.dialog.show_cancel);
}
}
class LegacyDocumentExportServices final : public pp::app::DocumentExportServices {
public:
explicit LegacyDocumentExportServices(App& app) noexcept
@@ -26,20 +35,18 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_equirectangular(target.path, [app, target] {
#if defined(__IOS__)
app->message_box("Export Equirectangular", "Image exported to Photos");
#elif defined(__OSX__)
app->message_box("Export Equirectangular", "Image exported to Pictures/PanoPainter folder");
#elif defined(_WIN32)
app->message_box("Export Equirectangular", "Image exported to " + app->work_path);
#elif defined(__QUEST__)
(void)target;
#elif __WEB__
#if __WEB__
app->ui_task([app, target] {
app->save_prepared_file(target.path, target.suggested_name, [](const std::string&, bool) { });
});
#else
(void)target;
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::equirectangular,
pp::app::document_export_equirectangular_platform_destination(),
app->work_path));
#endif
});
}
@@ -48,7 +55,12 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_layers(target.stem_path, [app, target] {
app->message_box("Export Layers", "Layers exported to: " + target.stem_path);
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::layers,
pp::app::DocumentExportSuccessDestination::path,
target.stem_path));
});
}
@@ -56,7 +68,11 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_layers(target.stem_path, [app] {
app->message_box("Export Layers", "Image layers exported to Files/PanoPainter");
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::layers,
pp::app::DocumentExportSuccessDestination::files_panopainter));
});
}
@@ -64,7 +80,12 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_anim_frames(target.stem_path, [app, target] {
app->message_box("Export Layers", "Layers exported to: " + target.stem_path);
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_frames,
pp::app::DocumentExportSuccessDestination::path,
target.stem_path));
});
}
@@ -72,7 +93,11 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_anim_frames(target.stem_path, [app] {
app->message_box("Export Layers", "Image layers exported to Files/PanoPainter");
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_frames,
pp::app::DocumentExportSuccessDestination::files_panopainter));
});
}
@@ -80,13 +105,12 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_depth(std::string(document_name), [app] {
#if defined(__IOS__)
app->message_box("Export 3D View + Depth", "Image and depth exported to Files/PanoPainter");
#elif defined(__OSX__)
app->message_box("Export 3D View + Depth", "Image and depth exported to Pictures/PanoPainter folder");
#elif defined(_WIN32)
app->message_box("Export 3D View + Depth", "Image and depth exported to " + app->work_path);
#endif
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::depth,
pp::app::document_export_media_platform_destination(),
app->work_path));
});
}
@@ -94,13 +118,12 @@ public:
{
auto* app = &app_;
app_.canvas->m_canvas->export_cube_faces(std::string(document_name), [app] {
#if defined(__IOS__)
app->message_box("Export Cube Faces", "Image and depth exported to Files/PanoPainter");
#elif defined(__OSX__)
app->message_box("Export Cube Faces", "Image and depth exported to Pictures/PanoPainter folder");
#elif defined(_WIN32)
app->message_box("Export Cube Faces", "Image and depth exported to " + app->work_path);
#endif
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::cube_faces,
pp::app::document_export_media_platform_destination(),
app->work_path));
});
}
@@ -122,13 +145,22 @@ public:
auto path_string = std::string(path);
if (asynchronous_) {
Canvas::I->export_anim_mp4(path_string, [app, path_string] {
app->message_box("Export Animation", "Animation exported to: " + path_string);
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_mp4,
pp::app::DocumentExportSuccessDestination::path,
path_string));
});
return;
}
Canvas::I->export_anim_mp4(path_string, [app] {
app->message_box("Export Animation", "Animation exported successfully.");
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_mp4,
pp::app::DocumentExportSuccessDestination::generic_success));
});
}
@@ -140,7 +172,12 @@ public:
std::thread([app, path_string] {
BT_SetTerminate();
app->rec_export(path_string);
app->message_box("Export Timelapse", "Timelapse exported to: " + path_string);
show_export_success_dialog(
*app,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::timelapse,
pp::app::DocumentExportSuccessDestination::path,
path_string));
}).detach();
return;
}
@@ -158,7 +195,12 @@ public:
if (asynchronous_) {
(void)path;
} else {
app_.message_box("Export Timelapse", "Timelapse exported successfully.");
(void)path;
show_export_success_dialog(
app_,
pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::timelapse,
pp::app::DocumentExportSuccessDestination::generic_success));
}
}

View File

@@ -899,6 +899,37 @@ if(TARGET pano_cli)
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-target\".*\"kind\":\"name\".*\"suggestedName\":\"demo-timelapse\"")
add_test(NAME pano_cli_plan_export_message_equirect_work_smoke
COMMAND pano_cli plan-export-message --kind equirectangular --destination work --detail D:/Paint)
set_tests_properties(pano_cli_plan_export_message_equirect_work_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-message\".*\"kind\":\"equirectangular\".*\"destination\":\"work-directory\".*\"showDialog\":true.*\"title\":\"Export Equirectangular\".*\"message\":\"Image exported to D:/Paint\"")
add_test(NAME pano_cli_plan_export_message_layers_files_smoke
COMMAND pano_cli plan-export-message --kind layers --destination files)
set_tests_properties(pano_cli_plan_export_message_layers_files_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-message\".*\"kind\":\"layers\".*\"destination\":\"files-panopainter\".*\"showDialog\":true.*\"message\":\"Image layers exported to Files/PanoPainter\"")
add_test(NAME pano_cli_plan_export_message_timelapse_success_smoke
COMMAND pano_cli plan-export-message --kind timelapse --destination success)
set_tests_properties(pano_cli_plan_export_message_timelapse_success_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-message\".*\"kind\":\"timelapse\".*\"destination\":\"generic-success\".*\"showDialog\":true.*\"message\":\"Timelapse exported successfully\\.\"")
add_test(NAME pano_cli_plan_export_message_suppressed_smoke
COMMAND pano_cli plan-export-message --kind depth --destination suppressed)
set_tests_properties(pano_cli_plan_export_message_suppressed_smoke PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-message\".*\"kind\":\"depth\".*\"destination\":\"suppressed\".*\"showDialog\":false.*\"title\":\"\".*\"message\":\"\"")
add_test(NAME pano_cli_plan_export_message_rejects_unknown
COMMAND pano_cli plan-export-message --kind nope)
set_tests_properties(pano_cli_plan_export_message_rejects_unknown PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
WILL_FAIL TRUE
PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-message\".*\"message\":\"unknown export message kind\"")
add_test(NAME pano_cli_plan_cloud_upload_clean_smoke
COMMAND pano_cli plan-cloud-upload)
set_tests_properties(pano_cli_plan_cloud_upload_clean_smoke PROPERTIES

View File

@@ -218,6 +218,115 @@ void collection_export_target_plan_selects_platform_destination(pp::tests::Harne
PP_EXPECT(harness, picked_layers.suffix == "_layers");
}
void export_success_dialog_plans_image_destinations(pp::tests::Harness& harness)
{
const auto photos = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::equirectangular,
pp::app::DocumentExportSuccessDestination::photos);
const auto pictures = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::equirectangular,
pp::app::DocumentExportSuccessDestination::pictures_panopainter);
const auto work = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::equirectangular,
pp::app::DocumentExportSuccessDestination::work_directory,
"D:/Paint");
PP_EXPECT(harness, photos.show_dialog);
PP_EXPECT(harness, photos.dialog.title == "Export Equirectangular");
PP_EXPECT(harness, photos.dialog.message == "Image exported to Photos");
PP_EXPECT(harness, pictures.dialog.message == "Image exported to Pictures/PanoPainter folder");
PP_EXPECT(harness, work.dialog.message == "Image exported to D:/Paint");
}
void export_success_dialog_plans_collection_destinations(pp::tests::Harness& harness)
{
const auto layers_path = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::layers,
pp::app::DocumentExportSuccessDestination::path,
"D:/Paint/demo_layers/demo");
const auto frames_path = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_frames,
pp::app::DocumentExportSuccessDestination::path,
"D:/Paint/demo_frames/demo");
const auto layers_collection = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::layers,
pp::app::DocumentExportSuccessDestination::files_panopainter);
const auto frames_collection = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_frames,
pp::app::DocumentExportSuccessDestination::files_panopainter);
PP_EXPECT(harness, layers_path.dialog.title == "Export Layers");
PP_EXPECT(harness, layers_path.dialog.message == "Layers exported to: D:/Paint/demo_layers/demo");
PP_EXPECT(harness, frames_path.dialog.message == "Layers exported to: D:/Paint/demo_frames/demo");
PP_EXPECT(harness, layers_collection.dialog.message == "Image layers exported to Files/PanoPainter");
PP_EXPECT(harness, frames_collection.dialog.message == "Image layers exported to Files/PanoPainter");
}
void export_success_dialog_plans_depth_and_cube_destinations(pp::tests::Harness& harness)
{
const auto depth_files = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::depth,
pp::app::DocumentExportSuccessDestination::files_panopainter);
const auto depth_pictures = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::depth,
pp::app::DocumentExportSuccessDestination::pictures_panopainter);
const auto depth_work = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::depth,
pp::app::DocumentExportSuccessDestination::work_directory,
"D:/Paint");
const auto cube_work = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::cube_faces,
pp::app::DocumentExportSuccessDestination::work_directory,
"D:/Paint");
PP_EXPECT(harness, depth_files.dialog.title == "Export 3D View + Depth");
PP_EXPECT(harness, depth_files.dialog.message == "Image and depth exported to Files/PanoPainter");
PP_EXPECT(harness, depth_pictures.dialog.message == "Image and depth exported to Pictures/PanoPainter folder");
PP_EXPECT(harness, depth_work.dialog.message == "Image and depth exported to D:/Paint");
PP_EXPECT(harness, cube_work.dialog.title == "Export Cube Faces");
PP_EXPECT(harness, cube_work.dialog.message == "Image and depth exported to D:/Paint");
}
void export_success_dialog_plans_video_destinations(pp::tests::Harness& harness)
{
const auto animation_path = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_mp4,
pp::app::DocumentExportSuccessDestination::path,
"D:/Paint/animation.mp4");
const auto animation_success = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::animation_mp4,
pp::app::DocumentExportSuccessDestination::generic_success);
const auto timelapse_path = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::timelapse,
pp::app::DocumentExportSuccessDestination::path,
"D:/Paint/timelapse.mp4");
const auto timelapse_success = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::timelapse,
pp::app::DocumentExportSuccessDestination::generic_success);
PP_EXPECT(harness, animation_path.dialog.title == "Export Animation");
PP_EXPECT(harness, animation_path.dialog.message == "Animation exported to: D:/Paint/animation.mp4");
PP_EXPECT(harness, animation_success.dialog.message == "Animation exported successfully.");
PP_EXPECT(harness, timelapse_path.dialog.title == "Export Timelapse");
PP_EXPECT(harness, timelapse_path.dialog.message == "Timelapse exported to: D:/Paint/timelapse.mp4");
PP_EXPECT(harness, timelapse_success.dialog.message == "Timelapse exported successfully.");
}
void export_success_dialog_suppresses_unsupported_destinations(pp::tests::Harness& harness)
{
const auto suppressed = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::depth,
pp::app::DocumentExportSuccessDestination::suppressed);
const auto invalid_combo = pp::app::plan_document_export_success_dialog(
pp::app::DocumentExportSuccessKind::equirectangular,
pp::app::DocumentExportSuccessDestination::files_panopainter);
PP_EXPECT(harness, !suppressed.show_dialog);
PP_EXPECT(harness, suppressed.dialog.title.empty());
PP_EXPECT(harness, !invalid_combo.show_dialog);
PP_EXPECT(harness, invalid_combo.dialog.message.empty());
}
void export_start_allows_valid_canvas_state(pp::tests::Harness& harness)
{
PP_EXPECT(
@@ -552,6 +661,11 @@ int main()
harness.run("picked directory export builds stem", picked_directory_export_builds_stem);
harness.run("video export builds suggested name", video_export_builds_suggested_name);
harness.run("collection export target plan selects platform destination", collection_export_target_plan_selects_platform_destination);
harness.run("export success dialog plans image destinations", export_success_dialog_plans_image_destinations);
harness.run("export success dialog plans collection destinations", export_success_dialog_plans_collection_destinations);
harness.run("export success dialog plans depth and cube destinations", export_success_dialog_plans_depth_and_cube_destinations);
harness.run("export success dialog plans video destinations", export_success_dialog_plans_video_destinations);
harness.run("export success dialog suppresses unsupported destinations", export_success_dialog_suppresses_unsupported_destinations);
harness.run("export start allows valid canvas state", export_start_allows_valid_canvas_state);
harness.run("export start blocks demo only when license required", export_start_blocks_demo_only_when_license_required);
harness.run("export start reports missing canvas after license gate", export_start_reports_missing_canvas_after_license_gate);

View File

@@ -168,6 +168,12 @@ struct PlanExportTargetArgs {
std::string suffix;
};
struct PlanExportMessageArgs {
std::string kind = "equirectangular";
std::string destination = "work";
std::string detail = "D:/Paint";
};
struct PlanExportStartArgs {
bool requires_license = false;
bool license_valid = true;
@@ -1943,6 +1949,123 @@ pp::foundation::Result<pp::app::DocumentExportMenuKind> parse_document_export_me
pp::foundation::Status::invalid_argument("unknown export menu kind"));
}
const char* document_export_success_kind_name(pp::app::DocumentExportSuccessKind kind) noexcept
{
switch (kind) {
case pp::app::DocumentExportSuccessKind::equirectangular:
return "equirectangular";
case pp::app::DocumentExportSuccessKind::layers:
return "layers";
case pp::app::DocumentExportSuccessKind::animation_frames:
return "animation-frames";
case pp::app::DocumentExportSuccessKind::depth:
return "depth";
case pp::app::DocumentExportSuccessKind::cube_faces:
return "cube-faces";
case pp::app::DocumentExportSuccessKind::animation_mp4:
return "animation-mp4";
case pp::app::DocumentExportSuccessKind::timelapse:
return "timelapse";
}
return "equirectangular";
}
const char* document_export_success_destination_name(
pp::app::DocumentExportSuccessDestination destination) noexcept
{
switch (destination) {
case pp::app::DocumentExportSuccessDestination::suppressed:
return "suppressed";
case pp::app::DocumentExportSuccessDestination::photos:
return "photos";
case pp::app::DocumentExportSuccessDestination::pictures_panopainter:
return "pictures-panopainter";
case pp::app::DocumentExportSuccessDestination::files_panopainter:
return "files-panopainter";
case pp::app::DocumentExportSuccessDestination::work_directory:
return "work-directory";
case pp::app::DocumentExportSuccessDestination::path:
return "path";
case pp::app::DocumentExportSuccessDestination::generic_success:
return "generic-success";
}
return "suppressed";
}
pp::foundation::Result<pp::app::DocumentExportSuccessKind> parse_document_export_success_kind(
std::string_view kind)
{
if (kind == "equirectangular" || kind == "equirect" || kind == "image") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::equirectangular);
}
if (kind == "layers") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::layers);
}
if (kind == "animation-frames" || kind == "frames") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::animation_frames);
}
if (kind == "depth") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::depth);
}
if (kind == "cube-faces" || kind == "cube") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::cube_faces);
}
if (kind == "animation-mp4" || kind == "mp4") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::animation_mp4);
}
if (kind == "timelapse") {
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::success(
pp::app::DocumentExportSuccessKind::timelapse);
}
return pp::foundation::Result<pp::app::DocumentExportSuccessKind>::failure(
pp::foundation::Status::invalid_argument("unknown export message kind"));
}
pp::foundation::Result<pp::app::DocumentExportSuccessDestination> parse_document_export_success_destination(
std::string_view destination)
{
if (destination == "suppressed" || destination == "none") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::suppressed);
}
if (destination == "photos") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::photos);
}
if (destination == "pictures" || destination == "pictures-panopainter") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::pictures_panopainter);
}
if (destination == "files" || destination == "files-panopainter") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::files_panopainter);
}
if (destination == "work" || destination == "work-directory") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::work_directory);
}
if (destination == "path") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::path);
}
if (destination == "success" || destination == "generic-success") {
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::success(
pp::app::DocumentExportSuccessDestination::generic_success);
}
return pp::foundation::Result<pp::app::DocumentExportSuccessDestination>::failure(
pp::foundation::Status::invalid_argument("unknown export message destination"));
}
const char* cloud_upload_action_name(pp::app::CloudUploadAction action) noexcept
{
switch (action) {
@@ -2234,6 +2357,7 @@ void print_help()
<< " plan-export-start [--requires-license] [--demo] [--no-canvas]\n"
<< " plan-export-menu --kind jpeg|png|layers|cube-faces|depth|animation-frames|animation-mp4|timelapse [--demo] [--no-canvas]\n"
<< " plan-export-target --kind file|collection|stem|name --doc-name NAME [--work-dir DIR] [--directory DIR] [--extension EXT] [--suffix SUFFIX]\n"
<< " plan-export-message --kind equirectangular|layers|animation-frames|depth|cube-faces|animation-mp4|timelapse --destination photos|pictures|files|work|path|success|suppressed [--detail TEXT]\n"
<< " plan-cloud-upload [--no-canvas] [--new-document] [--unsaved]\n"
<< " plan-cloud-browse [--no-canvas] [--selected-file FILE]\n"
<< " plan-cloud-upload-all [--file-count N] [--no-progress-ui]\n"
@@ -3588,6 +3712,74 @@ int plan_export_menu(int argc, char** argv)
return 0;
}
pp::foundation::Status parse_plan_export_message_args(
int argc,
char** argv,
PlanExportMessageArgs& args)
{
for (int i = 2; i < argc; ++i) {
const std::string_view key(argv[i]);
if (key == "--kind") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
args.kind = argv[++i];
} else if (key == "--destination") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
args.destination = argv[++i];
} else if (key == "--detail") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
args.detail = argv[++i];
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
return pp::foundation::Status::success();
}
int plan_export_message(int argc, char** argv)
{
PlanExportMessageArgs args;
const auto status = parse_plan_export_message_args(argc, argv, args);
if (!status.ok()) {
print_error("plan-export-message", status.message);
return 2;
}
const auto kind = parse_document_export_success_kind(args.kind);
if (!kind) {
print_error("plan-export-message", kind.status().message);
return 2;
}
const auto destination = parse_document_export_success_destination(args.destination);
if (!destination) {
print_error("plan-export-message", destination.status().message);
return 2;
}
const auto plan = pp::app::plan_document_export_success_dialog(
kind.value(),
destination.value(),
args.detail);
std::cout << "{\"ok\":true,\"command\":\"plan-export-message\""
<< ",\"state\":{\"kind\":\"" << json_escape(args.kind)
<< "\",\"destination\":\"" << json_escape(args.destination)
<< "\",\"detail\":\"" << json_escape(args.detail)
<< "\"},\"plan\":{\"kind\":\"" << document_export_success_kind_name(plan.kind)
<< "\",\"destination\":\"" << document_export_success_destination_name(plan.destination)
<< "\",\"showDialog\":" << json_bool(plan.show_dialog)
<< ",\"title\":\"" << json_escape(plan.dialog.title)
<< "\",\"message\":\"" << json_escape(plan.dialog.message)
<< "\"}}\n";
return 0;
}
pp::foundation::Status parse_plan_cloud_upload_args(
int argc,
char** argv,
@@ -11187,6 +11379,10 @@ int main(int argc, char** argv)
return plan_export_target(argc, argv);
}
if (command == "plan-export-message") {
return plan_export_message(argc, argv);
}
if (command == "plan-cloud-upload") {
return plan_cloud_upload(argc, argv);
}