Initial
This commit is contained in:
157
resources/app/client/data/documents/adventure.js
Normal file
157
resources/app/client/data/documents/adventure.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* @typedef {Object} AdventureImportData
|
||||
* @property {Record<string, object[]>} toCreate Arrays of document data to create, organized by document name
|
||||
* @property {Record<string, object[]>} toUpdate Arrays of document data to update, organized by document name
|
||||
* @property {number} documentCount The total count of documents to import
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} AdventureImportResult
|
||||
* @property {Record<string, Document[]>} created Documents created as a result of the import, organized by document name
|
||||
* @property {Record<string, Document[]>} updated Documents updated as a result of the import, organized by document name
|
||||
*/
|
||||
|
||||
/**
|
||||
* The client-side Adventure document which extends the common {@link foundry.documents.BaseAdventure} model.
|
||||
* @extends foundry.documents.BaseAdventure
|
||||
* @mixes ClientDocumentMixin
|
||||
*
|
||||
* ### Hook Events
|
||||
* {@link hookEvents.preImportAdventure} emitted by Adventure#import
|
||||
* {@link hookEvents.importAdventure} emitted by Adventure#import
|
||||
*/
|
||||
class Adventure extends ClientDocumentMixin(foundry.documents.BaseAdventure) {
|
||||
|
||||
/** @inheritdoc */
|
||||
static fromSource(source, options={}) {
|
||||
const pack = game.packs.get(options.pack);
|
||||
if ( pack && !pack.metadata.system ) {
|
||||
// Omit system-specific documents from this Adventure's data.
|
||||
source.actors = [];
|
||||
source.items = [];
|
||||
source.folders = source.folders.filter(f => !CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES.includes(f.type));
|
||||
}
|
||||
return super.fromSource(source, options);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Perform a full import workflow of this Adventure.
|
||||
* Create new and update existing documents within the World.
|
||||
* @param {object} [options] Options which configure and customize the import process
|
||||
* @param {boolean} [options.dialog=true] Display a warning dialog if existing documents would be overwritten
|
||||
* @returns {Promise<AdventureImportResult>} The import result
|
||||
*/
|
||||
async import({dialog=true, ...importOptions}={}) {
|
||||
const importData = await this.prepareImport(importOptions);
|
||||
|
||||
// Allow modules to preprocess adventure data or to intercept the import process
|
||||
const allowed = Hooks.call("preImportAdventure", this, importOptions, importData.toCreate, importData.toUpdate);
|
||||
if ( allowed === false ) {
|
||||
console.log(`"${this.name}" Adventure import was prevented by the "preImportAdventure" hook`);
|
||||
return {created: [], updated: []};
|
||||
}
|
||||
|
||||
// Warn the user if the import operation will overwrite existing World content
|
||||
if ( !foundry.utils.isEmpty(importData.toUpdate) && dialog ) {
|
||||
const confirm = await Dialog.confirm({
|
||||
title: game.i18n.localize("ADVENTURE.ImportOverwriteTitle"),
|
||||
content: `<h4><strong>${game.i18n.localize("Warning")}:</strong></h4>
|
||||
<p>${game.i18n.format("ADVENTURE.ImportOverwriteWarning", {name: this.name})}</p>`
|
||||
});
|
||||
if ( !confirm ) return {created: [], updated: []};
|
||||
}
|
||||
|
||||
// Perform the import
|
||||
const {created, updated} = await this.importContent(importData);
|
||||
|
||||
// Refresh the sidebar display
|
||||
ui.sidebar.render();
|
||||
|
||||
// Allow modules to perform additional post-import workflows
|
||||
Hooks.callAll("importAdventure", this, importOptions, created, updated);
|
||||
|
||||
// Update the imported state of the adventure.
|
||||
const imports = game.settings.get("core", "adventureImports");
|
||||
imports[this.uuid] = true;
|
||||
await game.settings.set("core", "adventureImports", imports);
|
||||
|
||||
return {created, updated};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Prepare Adventure data for import into the World.
|
||||
* @param {object} [options] Options passed in from the import dialog to configure the import
|
||||
* behavior.
|
||||
* @param {string[]} [options.importFields] A subset of adventure fields to import.
|
||||
* @returns {Promise<AdventureImportData>}
|
||||
*/
|
||||
async prepareImport({ importFields=[] }={}) {
|
||||
importFields = new Set(importFields);
|
||||
const adventureData = this.toObject();
|
||||
const toCreate = {};
|
||||
const toUpdate = {};
|
||||
let documentCount = 0;
|
||||
const importAll = !importFields.size || importFields.has("all");
|
||||
const keep = new Set();
|
||||
for ( const [field, cls] of Object.entries(Adventure.contentFields) ) {
|
||||
if ( !importAll && !importFields.has(field) ) continue;
|
||||
keep.add(cls.documentName);
|
||||
const collection = game.collections.get(cls.documentName);
|
||||
let [c, u] = adventureData[field].partition(d => collection.has(d._id));
|
||||
if ( (field === "folders") && !importAll ) {
|
||||
c = c.filter(f => keep.has(f.type));
|
||||
u = u.filter(f => keep.has(f.type));
|
||||
}
|
||||
if ( c.length ) {
|
||||
toCreate[cls.documentName] = c;
|
||||
documentCount += c.length;
|
||||
}
|
||||
if ( u.length ) {
|
||||
toUpdate[cls.documentName] = u;
|
||||
documentCount += u.length;
|
||||
}
|
||||
}
|
||||
return {toCreate, toUpdate, documentCount};
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Execute an Adventure import workflow, creating and updating documents in the World.
|
||||
* @param {AdventureImportData} data Prepared adventure data to import
|
||||
* @returns {Promise<AdventureImportResult>} The import result
|
||||
*/
|
||||
async importContent({toCreate, toUpdate, documentCount}={}) {
|
||||
const created = {};
|
||||
const updated = {};
|
||||
|
||||
// Display importer progress
|
||||
const importMessage = game.i18n.localize("ADVENTURE.ImportProgress");
|
||||
let nImported = 0;
|
||||
SceneNavigation.displayProgressBar({label: importMessage, pct: 1});
|
||||
|
||||
// Create new documents
|
||||
for ( const [documentName, createData] of Object.entries(toCreate) ) {
|
||||
const cls = getDocumentClass(documentName);
|
||||
const docs = await cls.createDocuments(createData, {keepId: true, keepEmbeddedId: true, renderSheet: false});
|
||||
created[documentName] = docs;
|
||||
nImported += docs.length;
|
||||
SceneNavigation.displayProgressBar({label: importMessage, pct: Math.floor(nImported * 100 / documentCount)});
|
||||
}
|
||||
|
||||
// Update existing documents
|
||||
for ( const [documentName, updateData] of Object.entries(toUpdate) ) {
|
||||
const cls = getDocumentClass(documentName);
|
||||
const docs = await cls.updateDocuments(updateData, {diff: false, recursive: false, noHook: true});
|
||||
updated[documentName] = docs;
|
||||
nImported += docs.length;
|
||||
SceneNavigation.displayProgressBar({label: importMessage, pct: Math.floor(nImported * 100 / documentCount)});
|
||||
}
|
||||
SceneNavigation.displayProgressBar({label: importMessage, pct: 100});
|
||||
return {created, updated};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user