diff --git a/changelog.md b/changelog.md index 82de393..f799e99 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## v2.1.4 + +- Added timestamp to file export. + - As requested in [#30](https://github.com/League-of-Foundry-Developers/foundryvtt-forien-copy-environment/issues/30) +- Re-added "Save as JSON" which was removed in v2.1.2 as requested in [#29](https://github.com/League-of-Foundry-Developers/foundryvtt-forien-copy-environment/issues/29) + - Have renamed this to `Copy as JSON` to help differentiate it from the `Export Settings` option. + ## v2.1.3 - Fixed issue where having a player other than GM in the world would prevent the importer from correctly importing anything. diff --git a/languages/de.json b/languages/de.json index 8006adf..79ad8b6 100644 --- a/languages/de.json +++ b/languages/de.json @@ -2,7 +2,7 @@ "forien-copy-environment": { "menu": { "copy": "Als Text kopieren", - "save": "Als JSON abspeichern", + "save": "Als JSON kopieren", "export": "Einstellungen exportieren", "import": "Einstellungen importieren" }, diff --git a/languages/en.json b/languages/en.json index cba463b..de1ab0f 100644 --- a/languages/en.json +++ b/languages/en.json @@ -2,7 +2,7 @@ "forien-copy-environment": { "menu": { "copy": "Copy as text", - "save": "Save as JSON", + "save": "Copy as JSON", "export": "Export Settings", "import": "Import Settings" }, diff --git a/languages/ja.json b/languages/ja.json index 152d970..9476daf 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -2,7 +2,7 @@ "forien-copy-environment": { "menu": { "copy": "クリップボードにコピー", - "save": "JSONに保存", + "save": "JSONとしてコピー", "export": "設定のエクスポート", "import": "設定のインポート" }, @@ -12,6 +12,7 @@ "copiedToClipboard": "環境データをクリップボードにコピーしました!", "updatedReloading": "ワールド設定を更新しました。5秒後に再読み込みします……", "import": { + "title": "ワールドの設定", "save": "インポート設定", "playerList": "次のプレイヤー設定をインポートします:", "existing": "既存のエクスポートをインポートします:", @@ -22,7 +23,8 @@ "existingValue": "次の値は変更がないためスキップされます:", "existingPlayerValues": "次のプレイヤーは変更がないためスキップされます:", "updatedPlayer": "次のプレイヤーの設定を更新しました:{name}", - "noChanges": "現在のワールド設定とインポートした設定に差異がありません。" + "noChanges": "現在のワールド設定とインポートした設定に差異がありません。", + "showSettings": "{count} の設定を表示" } } -} \ No newline at end of file +} diff --git a/module.json b/module.json index 3f59603..c564209 100644 --- a/module.json +++ b/module.json @@ -1,4 +1,5 @@ { + "id": "forien-copy-environment", "name": "forien-copy-environment", "title": "Forien's Copy Environment", "description": "Allows for copying list of system/modules and versions, and gives ability to export/import game and player settings", @@ -21,9 +22,13 @@ "flags": { "allowBugReporter": true }, - "version": "2.1.3", + "version": "2.1.4", "minimumCoreVersion": "0.6.0", "compatibleCoreVersion": "10", + "compatibility": { + "minimum": "0.6.0", + "verified": "10.270" + }, "scripts": [], "esmodules": [ "/scripts/module.js" diff --git a/scripts/core.js b/scripts/core.js index f8f0d0d..62c03b1 100755 --- a/scripts/core.js +++ b/scripts/core.js @@ -302,7 +302,7 @@ export default class Core extends FormApplication { return false; } - if (Object.keys(changes).length === 1 && isObjectEmpty(changes.flags)) { + if (Object.keys(changes).length === 1 && (typeof isEmpty === 'function' ? isEmpty(changes.flags) : isObjectEmpty(changes.flags))) { log(true, 'No changes selected for', targetUser?.name); return false; } @@ -331,16 +331,6 @@ export default class Core extends FormApplication { } static getText() { - const modules = (isV10orNewer() ? game.modules : game.data.modules).map(m => { - let mod; - if (isV10orNewer()) { - mod = m.toObject(); - } else { - mod = m.data.toObject(); - } - mod.active = m.active; - return mod; - }).filter((m) => m.active); const system = isV10orNewer() ? game.data.system : game.data.system.data; const core = game.version || game.data.version; @@ -355,7 +345,7 @@ export default class Core extends FormApplication { text += `System: ${(system.id ?? system.name)} ${system.version} (${Array.from(new Set(systemAuthors)).join(', ')}) \n\n`; text += `Modules: \n`; - modules.forEach((m) => { + Core.getModulesForExport().forEach((m) => { const moduleAuthors = m.authors.length ? m.authors.map(a => { if (typeof a === 'string') { return a; @@ -391,6 +381,56 @@ export default class Core extends FormApplication { ); } + static getModulesForExport() { + return (isV10orNewer() ? game.modules : game.data.modules).map(m => { + let mod; + if (isV10orNewer()) { + mod = m.toObject(); + } else { + mod = m.data.toObject(); + } + mod.active = m.active; + return mod; + }).filter((m) => m.active); + } + + static saveSummaryAsJSON() { + const system = isV10orNewer() ? game.data.system : game.data.system.data; + const systemAuthors = system.authors.length ? system.authors.map(a => { + if (typeof a === 'string') { + return a; + } + return a.name; + }) : [system.author]; + + const data = {}; + data.core = { + version: game.version || game.data.version, + }; + data.system = { + id: system.id, + version: system.version, + author: Array.from(new Set(systemAuthors)).join(', '), + manifest: system.manifest, + }; + data.modules = Core.getModulesForExport().map((m) => { + const moduleAuthors = m.authors.length ? m.authors.map(a => { + if (typeof a === 'string') { + return a; + } + return a.name; + }) : [m.author]; + return { + id: m.id || m.name, + version: m.version, + author: Array.from(new Set(moduleAuthors)).join(', '), + manifest: m.manifest, + }; + }); + + this.download(data, Core.getFilename('foundry-environment')); + } + static exportGameSettings() { const excludeModules = game.data.modules.filter((m) => m.flags?.noCopyEnvironmentSettings || m.data?.flags?.noCopyEnvironmentSettings).map((m) => m.id) || []; @@ -429,7 +469,22 @@ export default class Core extends FormApplication { }; }), ); - this.download(data, 'foundry-settings-export.json'); + this.download(data, Core.getFilename('foundry-settings-export')); + } + + static padNumber(number) { + return (number < 10 ? '0' : '') + number; + } + + static getFilename(filename) { + const now = new Date(); + const yyyy = now.getFullYear(); + const MM = Core.padNumber(now.getMonth() + 1); // getMonth() is zero-based + const dd = Core.padNumber(now.getDate()); + const hh = Core.padNumber(now.getHours()); + const mm = Core.padNumber(now.getMinutes()); + const ss = Core.padNumber(now.getSeconds()); + return `${filename}-${yyyy}-${MM}-${dd}-${hh}-${mm}-${ss}.json`; } static importGameSettingsQuick() { diff --git a/scripts/module.js b/scripts/module.js index 2769335..f3e420a 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -1,12 +1,12 @@ import {name} from './config.js'; import Core from './core.js'; -Hooks.once('init', function(){ +Hooks.once('init', function () { game.settings.register(name, 'selected-properties', { scope: 'client', config: false, type: Object, - default:{} + default: {}, }); }); @@ -28,6 +28,17 @@ Hooks.on('renderSettings', function (app, html, data) { } }, }, + { + name: game.i18n.localize('forien-copy-environment.menu.save'), + icon: '', + callback: () => { + try { + Core.saveSummaryAsJSON(); + } catch (e) { + console.error('Copy Environment | Error copying game settings to JSON', e); + } + }, + }, { name: game.i18n.localize('forien-copy-environment.menu.export'), icon: '', diff --git a/scripts/setting.js b/scripts/setting.js index 3a1b8c6..a7643b9 100755 --- a/scripts/setting.js +++ b/scripts/setting.js @@ -88,7 +88,7 @@ export class WorldSetting { } if (typeof existingSetting === 'object' && typeof newValue === 'object') { let diff = diffObject(existingSetting, newValue); - if (isObjectEmpty(diff)) { + if (typeof isEmpty === 'function' ? isEmpty(diff) : isObjectEmpty(diff)) { // No difference in the underlying object. return new Difference(this.key, null, null); } @@ -190,6 +190,10 @@ export class PlayerSetting { * @returns boolean */ hasDataChanges() { + if (typeof isEmpty === 'function') { + return !isEmpty(this.playerDifferences) || !isEmpty(this.playerFlagDifferences); + } + return ( !isObjectEmpty(this.playerDifferences) || !isObjectEmpty(this.playerFlagDifferences)