Files
fvtt-chroniques-de-l-etrange/src/ui/apps/migration-app.js
T

149 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 20242026 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 })
)
}
}
}