290 lines
14 KiB
C#
290 lines
14 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.ViewModels;
|
|
|
|
public sealed class ImportIndexViewModel
|
|
{
|
|
public string JsonText { get; set; } = string.Empty;
|
|
public IFormFile? JsonFile { get; set; }
|
|
public bool AllowDuplicateProjectTitle { get; set; }
|
|
public bool ConfirmImport { get; set; }
|
|
public bool AcknowledgeWarnings { get; set; }
|
|
public ImportPreview? Preview { get; set; }
|
|
public string? StatusMessage { get; set; }
|
|
public string? TechnicalDetail { get; set; }
|
|
public bool CanImport => Preview is { Validation.IsValid: true }
|
|
&& (!Preview.Validation.Warnings.Any() || AcknowledgeWarnings)
|
|
&& (!Preview.HasDuplicateProjectTitle || AllowDuplicateProjectTitle)
|
|
&& ConfirmImport;
|
|
|
|
public static string SampleJson => """
|
|
{
|
|
"packageVersion": "1.0",
|
|
"source": "JSON Import Phase 1D sample",
|
|
"project": {
|
|
"title": "The Glass Meridian",
|
|
"description": "A placeholder two-book fantasy outline.",
|
|
"notes": "Imported sample data for testing."
|
|
},
|
|
"characters": [
|
|
{ "name": "Mara", "displayName": "Mara Vale", "shortName": "Mara", "role": "Protagonist", "importance": 10, "description": "Courier drawn into the hidden city.", "notes": "Primary POV for book one." },
|
|
{ "name": "Ilen", "displayName": "Ilen Or", "shortName": "Ilen", "role": "Guide", "importance": 8, "description": "Watchman with partial knowledge of the old routes.", "notes": "Carries secrets into book two." },
|
|
{ "name": "Sera", "displayName": "Sera Quill", "shortName": "Sera", "role": "Antagonist", "importance": 7, "description": "Rival mapmaker.", "notes": "Opposes the expedition." },
|
|
{ "name": "Tovin", "displayName": "Tovin Reed", "shortName": "Tovin", "role": "Supporting", "importance": 5, "description": "Mechanic and signal reader.", "notes": "Useful for technical scenes." }
|
|
],
|
|
"locations": [
|
|
{ "name": "Old Arcade", "displayName": "Old Arcade", "type": "Building", "description": "Covered shopping arcade above the hidden route.", "notes": "Opening location." },
|
|
{ "name": "Service stair", "displayName": "Service Stair", "type": "Room", "parentLocationName": "Old Arcade", "description": "Narrow stair behind a locked panel.", "notes": "Connects to the lower city." },
|
|
{ "name": "Lantern Court", "displayName": "Lantern Court", "type": "Landmark", "description": "Underground civic court lit by glass lamps.", "notes": "Central discovery location." },
|
|
{ "name": "Clockwork Shore", "displayName": "Clockwork Shore", "type": "Outdoor Area", "description": "Mechanical coastline beyond the city.", "notes": "Book two setting." },
|
|
{ "name": "Tidal causeway", "displayName": "Tidal Causeway", "type": "Outdoor Area", "parentLocationName": "Clockwork Shore", "description": "Crossing exposed at low tide.", "notes": "Final sample scene location." }
|
|
],
|
|
"plotLines": [
|
|
{
|
|
"name": "Hidden City Mystery",
|
|
"displayName": "Hidden City Mystery",
|
|
"type": "Mystery",
|
|
"importance": 10,
|
|
"description": "The central question of what lies beneath the city.",
|
|
"notes": "Sample main mystery line.",
|
|
"threads": [
|
|
{
|
|
"name": "The Brass Key",
|
|
"displayName": "The Brass Key",
|
|
"type": "Clue",
|
|
"status": "Developing",
|
|
"importance": 8,
|
|
"description": "The key points toward the lower city.",
|
|
"notes": "Thread imported from sample JSON.",
|
|
"events": [
|
|
{ "bookOrder": 1, "chapterOrder": 1, "sceneOrder": 1, "title": "Key discovered", "eventType": "Introduced", "summary": "Mara finds the brass key.", "impact": "Opens the route beneath the arcade.", "importance": 8, "notes": "First marker." },
|
|
{ "bookOrder": 1, "chapterOrder": 2, "sceneOrder": 1, "title": "Map gap revealed", "eventType": "Developed", "summary": "The key does not match the visible map.", "impact": "Expands the mystery.", "importance": 7, "notes": "Shows there is more below." }
|
|
]
|
|
},
|
|
{
|
|
"name": "Missing Captain",
|
|
"displayName": "Missing Captain",
|
|
"type": "Question",
|
|
"status": "Introduced",
|
|
"importance": 6,
|
|
"description": "A name on the signal plate points to a missing captain.",
|
|
"events": [
|
|
{ "bookOrder": 2, "chapterOrder": 2, "sceneOrder": 1, "title": "Captain named", "eventType": "Question Raised", "summary": "The signal names a missing captain.", "impact": "Sets up the next search.", "importance": 6, "notes": "Book two escalation." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "Rival Expedition",
|
|
"displayName": "Rival Expedition",
|
|
"type": "Threat",
|
|
"importance": 7,
|
|
"description": "Sera's group races the protagonists.",
|
|
"threads": [
|
|
{
|
|
"name": "Sera's Pressure",
|
|
"displayName": "Sera's Pressure",
|
|
"type": "Threat",
|
|
"status": "Developing",
|
|
"importance": 7,
|
|
"description": "Sera appears as active opposition.",
|
|
"events": [
|
|
{ "bookOrder": 2, "chapterOrder": 2, "sceneOrder": 2, "title": "Camp sighted", "eventType": "Introduced", "summary": "Sera's camp is visible across the causeway.", "impact": "Turns the crossing into a race.", "importance": 7, "notes": "Visible antagonist pressure." }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"books": [
|
|
{
|
|
"title": "Book One",
|
|
"subtitle": "The Map Beneath the City",
|
|
"seriesOrder": 1,
|
|
"dependsOnPreviousBook": false,
|
|
"description": "The opening movement of the story.",
|
|
"notes": "Keep this book focused on discovery.",
|
|
"chapters": [
|
|
{
|
|
"chapterNumber": 1,
|
|
"title": "A Door in the Rain",
|
|
"order": 1,
|
|
"summary": "Mara finds a hidden route under the old district.",
|
|
"notes": "Establish the central mystery.",
|
|
"scenes": [
|
|
{
|
|
"sceneNumber": 1,
|
|
"title": "The Locked Arcade",
|
|
"order": 1,
|
|
"summary": "A late delivery leads to an impossible doorway.",
|
|
"povCharacterName": "Mara",
|
|
"locationName": "Old Arcade",
|
|
"dateTimeText": "Rainy evening",
|
|
"purposeText": "Introduce the mystery",
|
|
"outcomeText": "Mara keeps the brass key",
|
|
"notes": "Quiet, tense opening.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Mara", "roleInScene": "POV", "importance": 10, "notes": "Finds the key." },
|
|
{ "characterName": "Tovin", "roleInScene": "Supporting", "importance": 4, "notes": "Warns Mara not to linger." }
|
|
]
|
|
},
|
|
{
|
|
"sceneNumber": 2,
|
|
"title": "Footsteps Below",
|
|
"order": 2,
|
|
"summary": "Mara hears someone moving beneath the street.",
|
|
"povCharacterName": "Mara",
|
|
"locationName": "Service stair",
|
|
"dateTimeText": "Same evening",
|
|
"purposeText": "Escalate the discovery",
|
|
"outcomeText": "She chooses to descend",
|
|
"notes": "End on a decision.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Mara", "roleInScene": "POV", "importance": 10, "notes": "Chooses to descend." },
|
|
{ "characterName": "Ilen", "roleInScene": "Present", "importance": 6, "notes": "Unseen at first." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"chapterNumber": 2,
|
|
"title": "The Lantern Court",
|
|
"order": 2,
|
|
"summary": "The route opens into a forgotten civic space.",
|
|
"notes": "Add wonder, but keep danger near.",
|
|
"scenes": [
|
|
{
|
|
"sceneNumber": 1,
|
|
"title": "A City Under Glass",
|
|
"order": 1,
|
|
"summary": "Mara sees the underground court for the first time.",
|
|
"povCharacterName": "Mara",
|
|
"locationName": "Lantern Court",
|
|
"dateTimeText": "Later that night",
|
|
"purposeText": "Reveal the hidden setting",
|
|
"outcomeText": "Mara realizes the map is incomplete",
|
|
"notes": "Use strong visual contrast.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Mara", "roleInScene": "POV", "importance": 10, "notes": "First glimpse of the court." },
|
|
{ "characterName": "Ilen", "roleInScene": "Supporting", "importance": 7, "notes": "Explains only part of the truth." }
|
|
]
|
|
},
|
|
{
|
|
"sceneNumber": 2,
|
|
"title": "The Watchman's Offer",
|
|
"order": 2,
|
|
"summary": "Ilen offers help at a cost.",
|
|
"povCharacterName": "Ilen",
|
|
"locationName": "Lantern Court",
|
|
"dateTimeText": "Later that night",
|
|
"purposeText": "Introduce an uneasy ally",
|
|
"outcomeText": "Mara accepts a temporary guide",
|
|
"notes": "Trust should feel provisional.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Ilen", "roleInScene": "POV", "importance": 8, "notes": "Frames the bargain." },
|
|
{ "characterName": "Mara", "roleInScene": "Present", "importance": 8, "notes": "Accepts with reservations." }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"title": "Book Two",
|
|
"subtitle": "The Clockwork Shore",
|
|
"seriesOrder": 2,
|
|
"dependsOnPreviousBook": true,
|
|
"description": "The story widens beyond the city.",
|
|
"notes": "Seed consequences from book one.",
|
|
"chapters": [
|
|
{
|
|
"chapterNumber": 1,
|
|
"title": "Tide Tables",
|
|
"order": 1,
|
|
"summary": "The route points toward a mechanical coastline.",
|
|
"notes": "Reorient the cast after the first book.",
|
|
"scenes": [
|
|
{
|
|
"sceneNumber": 1,
|
|
"title": "Departure Bell",
|
|
"order": 1,
|
|
"summary": "The expedition leaves before dawn.",
|
|
"povCharacterName": "Ilen",
|
|
"locationName": "Clockwork Shore",
|
|
"dateTimeText": "Early morning",
|
|
"purposeText": "Start the next journey",
|
|
"outcomeText": "The group reaches the shore",
|
|
"notes": "Keep momentum high.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Ilen", "roleInScene": "POV", "importance": 8, "notes": "Knows the tide table." },
|
|
{ "characterName": "Mara", "roleInScene": "Present", "importance": 8, "notes": "Questions the plan." },
|
|
{ "characterName": "Tovin", "roleInScene": "Supporting", "importance": 5, "notes": "Carries tools." }
|
|
]
|
|
},
|
|
{
|
|
"sceneNumber": 2,
|
|
"title": "Salt in the Gears",
|
|
"order": 2,
|
|
"summary": "The first coastal machine fails in a revealing way.",
|
|
"povCharacterName": "Ilen",
|
|
"locationName": "Clockwork Shore",
|
|
"dateTimeText": "Midday",
|
|
"purposeText": "Show the new world's rules",
|
|
"outcomeText": "They find a damaged signal plate",
|
|
"notes": "Make the mechanism readable.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Ilen", "roleInScene": "POV", "importance": 8, "notes": "Recognizes sabotage." },
|
|
{ "characterName": "Tovin", "roleInScene": "Supporting", "importance": 6, "notes": "Repairs enough to continue." }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"chapterNumber": 2,
|
|
"title": "The Signal Plate",
|
|
"order": 2,
|
|
"summary": "The signal points to a rival expedition.",
|
|
"notes": "Bring in pressure from outside the group.",
|
|
"scenes": [
|
|
{
|
|
"sceneNumber": 1,
|
|
"title": "A Message in Brass",
|
|
"order": 1,
|
|
"summary": "The plate reveals a warning.",
|
|
"povCharacterName": "Mara",
|
|
"locationName": "Clockwork Shore",
|
|
"dateTimeText": "Afternoon",
|
|
"purposeText": "Translate the clue",
|
|
"outcomeText": "The warning names a missing captain",
|
|
"notes": "Make the clue actionable.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Mara", "roleInScene": "POV", "importance": 10, "notes": "Interprets the warning." },
|
|
{ "characterName": "Sera", "roleInScene": "Mentioned", "importance": 6, "notes": "Named by the signal." }
|
|
]
|
|
},
|
|
{
|
|
"sceneNumber": 2,
|
|
"title": "Smoke on the Causeway",
|
|
"order": 2,
|
|
"summary": "Sera's camp appears across the water.",
|
|
"povCharacterName": "Mara",
|
|
"locationName": "Tidal causeway",
|
|
"dateTimeText": "Sunset",
|
|
"purposeText": "Introduce opposition",
|
|
"outcomeText": "The group prepares to cross",
|
|
"notes": "End with forward motion.",
|
|
"sceneCharacters": [
|
|
{ "characterName": "Mara", "roleInScene": "POV", "importance": 10, "notes": "Chooses to cross." },
|
|
{ "characterName": "Sera", "roleInScene": "Antagonist", "importance": 7, "notes": "Visible across the causeway." },
|
|
{ "characterName": "Ilen", "roleInScene": "Supporting", "importance": 6, "notes": "Warns about the tide." }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
}
|