@model ImportIndexViewModel
@{
ViewData["Title"] = "JSON Import";
var preview = Model.Preview;
}
Tools
JSON Import
Paste or upload a legacy JSON package or a PlotWeaver backup file, preview it, dry-run if needed, then import a new project.
@if (!string.IsNullOrWhiteSpace(Model.StatusMessage))
{
Import paused
@Model.StatusMessage
@if (preview is { Package: not null, HasDuplicateProjectTitle: true })
{
Duplicate imports will be renamed to @preview.DuplicateImportProjectTitle.
}
@if (!string.IsNullOrWhiteSpace(Model.TechnicalDetail))
{
Technical detail
@Model.TechnicalDetail
}
}
Preview
@(preview.Package?.Project.Title ?? "Import package")
@preview.BookCount books
@preview.ChapterCount chapters
@preview.SceneCount scenes
@preview.CharacterCount characters
@preview.LocationCount locations
@preview.SceneCharacterAppearanceCount appearances
@preview.PlotLineCount plot lines
@preview.PlotThreadCount threads
@preview.ResolvedThreadEventCount events
@preview.StoryAssetCount assets
@preview.ResolvedAssetStateCount asset states
@preview.ResolvedAssetEventCount asset events
@preview.ResolvedAssetCustodyCount custody links
@preview.ResolvedSceneDependencyCount dependencies
@preview.ErrorCount errors
@preview.WarningCount warnings
@preview.UnresolvedReferenceCount unresolved
@preview.AutoCorrectionCount corrections
@preview.SkippedItemCount skipped
Import health
@preview.ErrorCount errors
@preview.WarningCount warnings
@preview.AutoCorrectionCount auto-corrections
@preview.SkippedItemCount skipped items
@preview.UnresolvedReferenceCount unresolved references
@if (preview.Package is not null && (!string.IsNullOrWhiteSpace(preview.Package.Source) || !string.IsNullOrWhiteSpace(preview.Package.CreatedBy) || !string.IsNullOrWhiteSpace(preview.Package.CreatedDate) || !string.IsNullOrWhiteSpace(preview.Package.Description)))
{
Source: @(preview.Package.Source ?? "Not supplied") /
Created by: @(preview.Package.CreatedBy ?? "Not supplied") /
Created date: @(preview.Package.CreatedDate ?? "Not supplied")
@if (!string.IsNullOrWhiteSpace(preview.Package.Description))
{
/ @preview.Package.Description
}
}
@if (preview.Validation.Errors.Any())
{
Errors block import
@foreach (var error in preview.Validation.Errors)
{
- @error
}
}
@if (preview.Validation.Warnings.Any())
{
Warnings require confirmation
@foreach (var warning in preview.Validation.Warnings)
{
- @warning
}
}
@if (preview.AutoCorrections.Any() || preview.Validation.AutoCorrections.Any() || preview.SkippedItems.Any() || preview.Validation.SkippedItems.Any() || preview.AliasWarnings.Any())
{
Health details
@if (preview.AutoCorrections.Any() || preview.Validation.AutoCorrections.Any())
{
Auto-corrections: @string.Join("; ", preview.AutoCorrections.Concat(preview.Validation.AutoCorrections).Take(12))
}
@if (preview.SkippedItems.Any() || preview.Validation.SkippedItems.Any())
{
Skipped items: @string.Join("; ", preview.SkippedItems.Concat(preview.Validation.SkippedItems).Take(12))
}
@if (preview.AliasWarnings.Any())
{
Alias warnings: @string.Join("; ", preview.AliasWarnings.Take(12))
}
}
@if (preview.Package is not null)
{
@if (preview.UnmatchedPovNames.Any() || preview.UnmatchedLocationNames.Any() || preview.UnmatchedSceneCharacterNames.Any() || preview.UnresolvedThreadEventReferences.Any() || preview.UnresolvedAssetReferences.Any() || preview.UnmatchedAssetOwnerNames.Any() || preview.UnmatchedAssetLocationNames.Any() || preview.UnresolvedSceneDependencyReferences.Any())
{
@if (preview.UnmatchedPovNames.Any())
{
Unmatched POV: @string.Join(", ", preview.UnmatchedPovNames)
}
@if (preview.UnmatchedLocationNames.Any())
{
Unmatched locations: @string.Join(", ", preview.UnmatchedLocationNames)
}
@if (preview.UnmatchedSceneCharacterNames.Any())
{
Unmatched scene characters: @string.Join(", ", preview.UnmatchedSceneCharacterNames)
}
@if (preview.UnresolvedThreadEventReferences.Any())
{
Unresolved thread events: @string.Join(", ", preview.UnresolvedThreadEventReferences)
}
@if (preview.UnresolvedAssetReferences.Any())
{
Unresolved asset references: @string.Join(", ", preview.UnresolvedAssetReferences)
}
@if (preview.UnmatchedAssetOwnerNames.Any())
{
Unmatched asset owners/events: @string.Join(", ", preview.UnmatchedAssetOwnerNames)
}
@if (preview.UnmatchedAssetLocationNames.Any())
{
Unmatched asset locations: @string.Join(", ", preview.UnmatchedAssetLocationNames)
}
@if (preview.UnresolvedSceneDependencyReferences.Any())
{
Unresolved dependencies: @string.Join(", ", preview.UnresolvedSceneDependencyReferences)
}
}
@foreach (var bookPreview in preview.BookPreviews)
{
var book = bookPreview.Book;
Book @book.SeriesOrder: @(string.IsNullOrWhiteSpace(book.Subtitle) ? book.Title : $"{book.Title}: {book.Subtitle}")
@bookPreview.ChapterCount chapters / @bookPreview.SceneCount scenes
@foreach (var chapterPreview in bookPreview.ChapterPreviews)
{
var chapter = chapterPreview.Chapter;
Chapter @chapter.ChapterNumber: @chapter.Title
@chapterPreview.SceneCount scenes
@foreach (var scene in chapter.Scenes.OrderBy(x => x.Order))
{
-
Scene @scene.SceneNumber: @scene.Title
POV: @(scene.PovCharacterName ?? "Not set") /
Location: @(scene.LocationName ?? "Not set") /
Characters: @scene.SceneCharacters.Count
}
}
}
@if (preview.Package.PlotLines.Any())
{
@foreach (var plotLine in preview.Package.PlotLines)
{
Plot line: @(string.IsNullOrWhiteSpace(plotLine.DisplayName) ? plotLine.Name : plotLine.DisplayName)
@plotLine.Threads.Count threads / @plotLine.Threads.Sum(x => x.Events.Count) events
@foreach (var thread in plotLine.Threads)
{
@(string.IsNullOrWhiteSpace(thread.DisplayName) ? thread.Name : thread.DisplayName)
@thread.Events.Count events
}
}
}
@if (preview.Package.SceneDependencies.Any())
{
@foreach (var group in preview.Package.SceneDependencies.Select((dependency, index) => new { Dependency = dependency, Index = index + 1 }).GroupBy(x => $"{x.Dependency.FromScene?.BookOrder?.ToString() ?? "?"}/{x.Dependency.FromScene?.ChapterOrder?.ToString() ?? "?"}").OrderBy(x => x.Key))
{
Dependencies from @group.Key
@group.Count() links
@foreach (var item in group.Take(12))
{
var dependency = item.Dependency;
var fromScene = $"{dependency.FromScene?.BookOrder?.ToString() ?? "?"}/{dependency.FromScene?.ChapterOrder?.ToString() ?? "?"}/{dependency.FromScene?.SceneOrder?.ToString() ?? "?"}";
var toScene = $"{dependency.ToScene?.BookOrder?.ToString() ?? "?"}/{dependency.ToScene?.ChapterOrder?.ToString() ?? "?"}/{dependency.ToScene?.SceneOrder?.ToString() ?? "?"}";
-
@fromScene -> @toScene
@((dependency.DependencyType ?? "Dependency")) / @(dependency.Strength ?? "soft")
}
@if (group.Count() > 12)
{
- Showing first 12 links in this group.
}
}
}
@if (preview.Package.StoryAssets.Any())
{
@foreach (var asset in preview.Package.StoryAssets)
{
Asset: @(string.IsNullOrWhiteSpace(asset.DisplayName) ? asset.Name : asset.DisplayName)
@asset.States.Count states / @asset.Events.Count events
- Type: @(asset.Type ?? "Not set") / Initial owner: @(asset.InitialOwnerCharacterName ?? "Not set") / Initial location: @(asset.InitialLocationName ?? "Not set")
}
}
Confirm import
This will create @preview.Package.Project.Title with @preview.BookCount books, @preview.ChapterCount chapters, @preview.SceneCount scenes, @preview.CharacterCount characters, @preview.LocationCount locations, @preview.SceneCharacterAppearanceCount scene character appearances, @preview.PlotLineCount plot lines, @preview.PlotThreadCount plot threads, @preview.ResolvedThreadEventCount resolved thread events, @preview.StoryAssetCount story assets, @preview.ResolvedAssetStateCount resolved asset states, @preview.ResolvedAssetEventCount resolved asset events, @preview.ResolvedAssetCustodyCount custody links, and @preview.ResolvedSceneDependencyCount scene dependencies.
@if (preview.HasDuplicateProjectTitle)
{
Because this title already exists, the created project will be named @preview.DuplicateImportProjectTitle if you continue.
}
}
}
@section Scripts {
}