Complete Story 3.3: Preset Import & Export
Implements FR-19: Preset import/export as JSON New Files: - src/core/PresetImportExportManager.js - Core logic for export/import with merge/replace - src/ui/gm/PresetExportDialog.js - Export dialog with file download - src/ui/gm/PresetImportDialog.js - Import dialog with file picker, preview, merge/replace - templates/preset-export.hbs - Export dialog template - templates/preset-import.hbs - Import dialog template - styles/components/_preset-import-export.less - Dialog styles - tests/unit/core/PresetImportExportManager.test.js - 38 unit tests - _bmad-output/implementation-artifacts/3-3-preset-import-and-export.md - Story file Modified Files: - src/ui/gm/DirectorsBoard.js - Added export/import button handlers - templates/directors-board.hbs - Added Export/Import buttons to footer - styles/scrying-pool.less - Added stylesheet import - lang/en.json - Added localization strings for new UI - _bmad-output/implementation-artifacts/sprint-status.yaml - Story status: review Features: - Export all presets from current scene as JSON file - Import presets with merge (add new, skip duplicates) or replace (overwrite all) modes - Preview of presets before import with validation status - Confirmation dialog for replace mode to prevent data loss - Comprehensive error handling and validation - All ACs satisfied (AC-9 deferred for README docs) Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -36,10 +36,16 @@
|
||||
</div>
|
||||
|
||||
<footer class="directors-board__footer">
|
||||
<button type="button" disabled>
|
||||
<button type="button" class="directors-board__footer-btn" data-action="save-preset">
|
||||
{{localize "video-view-manager.directorsBoard.footer.savePreset"}}
|
||||
</button>
|
||||
<button type="button" disabled>
|
||||
<button type="button" class="directors-board__footer-btn" data-action="load-preset" {{#unless hasPresets}}disabled{{/unless}}>
|
||||
{{localize "video-view-manager.directorsBoard.footer.loadPreset"}}
|
||||
</button>
|
||||
<button type="button" class="directors-board__footer-btn" data-action="export-presets">
|
||||
{{localize "video-view-manager.directorsBoard.footer.exportPresets"}}
|
||||
</button>
|
||||
<button type="button" class="directors-board__footer-btn" data-action="import-presets">
|
||||
{{localize "video-view-manager.directorsBoard.footer.importPresets"}}
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="sp-dialog-content">
|
||||
<p class="sp-export-description">
|
||||
{{localize "SCRYING_POOL.ExportPresetsDescription"}}
|
||||
</p>
|
||||
|
||||
<div class="sp-export-info">
|
||||
<span class="sp-info-label">{{localize "SCRYING_POOL.Scene"}}:</span>
|
||||
<span class="sp-info-value">{{sceneName}}</span>
|
||||
</div>
|
||||
|
||||
<div class="sp-export-info">
|
||||
<span class="sp-info-label">{{localize "SCRYING_POOL.PresetCount"}}:</span>
|
||||
<span class="sp-info-value">{{presetCount}}</span>
|
||||
</div>
|
||||
|
||||
<div class="sp-export-info">
|
||||
<span class="sp-info-label">{{localize "SCRYING_POOL.Filename"}}:</span>
|
||||
<span class="sp-info-value sp-filename">{{filename}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sp-dialog-buttons">
|
||||
<button type="button" class="sp-btn sp-btn-primary sp-export-btn">
|
||||
<i class="fas fa-download"></i> {{localize "SCRYING_POOL.Export"}}
|
||||
</button>
|
||||
<button type="button" class="sp-btn sp-btn-secondary" data-action="close">
|
||||
{{localize "SCRYING_POOL.Cancel"}}
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,90 @@
|
||||
<div class="sp-dialog-content">
|
||||
<p class="sp-import-description">
|
||||
{{localize "SCRYING_POOL.ImportPresetsDescription"}}
|
||||
</p>
|
||||
|
||||
{{#if hasExistingPresets}}
|
||||
<div class="sp-warning-box">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{{localize "SCRYING_POOL.ExistingPresetsWarning" existingPresetCount=existingPresetCount}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{!-- File Selection --}}
|
||||
<div class="sp-form-group">
|
||||
<label class="sp-form-label">{{localize "SCRYING_POOL.SelectFile"}}</label>
|
||||
<div class="sp-file-upload">
|
||||
<input type="file" class="sp-file-input" accept=".json" />
|
||||
<label class="sp-file-label">
|
||||
<i class="fas fa-upload"></i>
|
||||
<span class="sp-file-text">{{localize "SCRYING_POOL.ChooseFile"}}</span>
|
||||
</label>
|
||||
</div>
|
||||
{{#if selectedFileName}}
|
||||
<div class="sp-file-selected">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>{{selectedFileName}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{!-- Mode Selection --}}
|
||||
<div class="sp-form-group">
|
||||
<label class="sp-form-label">{{localize "SCRYING_POOL.ImportMode"}}</label>
|
||||
<div class="sp-radio-group">
|
||||
<label class="sp-radio-label">
|
||||
<input type="radio" name="import-mode" class="sp-mode-merge" value="merge" {{checked (eq mode "merge")}} />
|
||||
<span class="sp-radio-text">{{mergeLabel}}</span>
|
||||
<span class="sp-radio-hint">{{localize "SCRYING_POOL.ImportModeMergeHint"}}</span>
|
||||
</label>
|
||||
<label class="sp-radio-label">
|
||||
<input type="radio" name="import-mode" class="sp-mode-replace" value="replace" {{checked (eq mode "replace")}} />
|
||||
<span class="sp-radio-text">{{replaceLabel}}</span>
|
||||
<span class="sp-radio-hint">{{localize "SCRYING_POOL.ImportModeReplaceHint"}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Preview Section --}}
|
||||
{{#if previewItems.length}}
|
||||
<div class="sp-preview-section">
|
||||
<h3 class="sp-preview-title">{{localize "SCRYING_POOL.PreviewTitle"}}</h3>
|
||||
<ul class="sp-preview-list">
|
||||
{{#each previewItems as |item|}}
|
||||
<li class="sp-preview-item {{unless item.valid 'sp-preview-item--invalid'}}">
|
||||
<i class="fas {{if item.valid 'fa-check-circle sp-valid' 'fa-exclamation-circle sp-invalid'}}"></i>
|
||||
<span class="sp-preview-name">{{item.name}}</span>
|
||||
{{#if item.error}}
|
||||
<span class="sp-preview-error" title="{{item.error}}">{{item.error}}</span>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{!-- Confirmation Section (shown for replace mode) --}}
|
||||
{{#if requiresConfirmation}}
|
||||
<div class="sp-confirmation-section">
|
||||
<div class="sp-confirmation-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{{localize "SCRYING_POOL.ReplaceConfirmation" existingPresetCount=existingPresetCount}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="sp-dialog-buttons">
|
||||
{{#unless requiresConfirmation}}
|
||||
<button type="button" class="sp-btn sp-btn-primary sp-import-btn" {{disabled (not previewItems.length) }}>
|
||||
<i class="fas fa-file-import"></i> {{localize "SCRYING_POOL.Import"}}
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" class="sp-btn sp-btn-danger sp-confirm-btn">
|
||||
<i class="fas fa-check"></i> {{localize "SCRYING_POOL.ConfirmReplace"}}
|
||||
</button>
|
||||
{{/unless}}
|
||||
<button type="button" class="sp-btn sp-btn-secondary sp-cancel-btn">
|
||||
{{localize "SCRYING_POOL.Cancel"}}
|
||||
</button>
|
||||
</div>
|
||||
Reference in New Issue
Block a user