149 lines
4.5 KiB
JavaScript
149 lines
4.5 KiB
JavaScript
/**
|
||
* Chroniques de l'Étrange — Système FoundryVTT
|
||
*
|
||
* Chroniques de l'Étrange est un jeu de rôle édité par Antre-Monde Éditions.
|
||
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
|
||
* affilié à Antre-Monde Éditions,
|
||
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
|
||
*
|
||
* @author LeRatierBretonnien
|
||
* @copyright 2024–2026 LeRatierBretonnien
|
||
* @license CC BY-NC-SA 4.0 – https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||
*/
|
||
|
||
import { parseLegacyJson } from "../../migration/migrator.js"
|
||
|
||
const MIGRATION_TEMPLATE = "systems/fvtt-chroniques-de-l-etrange/templates/apps/cde-migration-app.html"
|
||
|
||
/**
|
||
* Dialog for importing legacy CDE actor JSON files into the current system.
|
||
*
|
||
* Accessible via the System Settings menu (registerMenu).
|
||
* Supports multi-file selection and shows a preview table before importing.
|
||
*/
|
||
export class CDEMigrationApp extends foundry.applications.api.HandlebarsApplicationMixin(
|
||
foundry.applications.api.ApplicationV2
|
||
) {
|
||
static DEFAULT_OPTIONS = {
|
||
id: "cde-migration-app",
|
||
classes: ["cde-migration-app"],
|
||
tag: "div",
|
||
window: {
|
||
title: "CDE.MigrationTitle",
|
||
icon: "fas fa-file-import",
|
||
resizable: false,
|
||
},
|
||
position: { width: 560, height: "auto" },
|
||
actions: {
|
||
clearFiles: CDEMigrationApp.#clearFiles,
|
||
doImport: CDEMigrationApp.#doImport,
|
||
},
|
||
}
|
||
|
||
static PARTS = {
|
||
form: { template: MIGRATION_TEMPLATE },
|
||
}
|
||
|
||
/** @type {Array<{name: string, type: string, img: string, system: object, items: object[], _srcFile: string}>} */
|
||
#pending = []
|
||
|
||
/** @type {string[]} - error messages per file */
|
||
#errors = []
|
||
|
||
async _prepareContext(options) {
|
||
return {
|
||
pending: this.#pending,
|
||
errors: this.#errors,
|
||
hasPending: this.#pending.length > 0,
|
||
hasErrors: this.#errors.length > 0,
|
||
count: this.#pending.length,
|
||
}
|
||
}
|
||
|
||
/** After render, wire up the file input. */
|
||
_onRender(context, options) {
|
||
super._onRender(context, options)
|
||
const input = this.element.querySelector(".cde-migration-file-input")
|
||
input?.addEventListener("change", this.#onFileChange.bind(this))
|
||
|
||
const dropZone = this.element.querySelector(".cde-migration-drop-zone")
|
||
if (dropZone) {
|
||
dropZone.addEventListener("dragover", (e) => { e.preventDefault(); dropZone.classList.add("is-dragover") })
|
||
dropZone.addEventListener("dragleave", () => dropZone.classList.remove("is-dragover"))
|
||
dropZone.addEventListener("drop", (e) => {
|
||
e.preventDefault()
|
||
dropZone.classList.remove("is-dragover")
|
||
this.#processFiles(Array.from(e.dataTransfer.files))
|
||
})
|
||
}
|
||
}
|
||
|
||
async #onFileChange(event) {
|
||
const files = Array.from(event.target.files ?? [])
|
||
event.target.value = ""
|
||
await this.#processFiles(files)
|
||
}
|
||
|
||
async #processFiles(files) {
|
||
for (const file of files) {
|
||
if (!file.name.endsWith(".json")) {
|
||
this.#errors.push(game.i18n.format("CDE.MigrationErrorNotJson", { file: file.name }))
|
||
continue
|
||
}
|
||
try {
|
||
const text = await file.text()
|
||
const actors = parseLegacyJson(text)
|
||
for (const actor of actors) {
|
||
actor._srcFile = file.name
|
||
// Avoid duplicates by name
|
||
if (!this.#pending.some(p => p.name === actor.name)) {
|
||
this.#pending.push(actor)
|
||
}
|
||
}
|
||
} catch (err) {
|
||
this.#errors.push(game.i18n.format("CDE.MigrationErrorParse", { file: file.name, error: err.message }))
|
||
}
|
||
}
|
||
this.render()
|
||
}
|
||
|
||
static async #clearFiles() {
|
||
this.#pending = []
|
||
this.#errors = []
|
||
this.render()
|
||
}
|
||
|
||
static async #doImport() {
|
||
if (!this.#pending.length) return
|
||
|
||
const created = []
|
||
const failed = []
|
||
|
||
for (const data of this.#pending) {
|
||
try {
|
||
const { _srcFile, ...actorData } = data
|
||
const actor = await Actor.create(actorData)
|
||
created.push(actor.name)
|
||
} catch (err) {
|
||
failed.push(`${data.name}: ${err.message}`)
|
||
console.error(`CHRONIQUESDELETRANGE | Migration failed for "${data.name}":`, err)
|
||
}
|
||
}
|
||
|
||
this.#pending = []
|
||
this.#errors = failed
|
||
this.render()
|
||
|
||
if (created.length) {
|
||
ui.notifications.info(
|
||
game.i18n.format("CDE.MigrationSuccess", { count: created.length, names: created.join(", ") })
|
||
)
|
||
}
|
||
if (failed.length) {
|
||
ui.notifications.warn(
|
||
game.i18n.format("CDE.MigrationPartialError", { count: failed.length })
|
||
)
|
||
}
|
||
}
|
||
}
|