diff --git a/.gitignore b/.gitignore index 3c50e04f..552a128a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ test/bgBuilder/* .VSCodeCounter TEST_OUTPUT.md .deepl_key +jsdoc_server/ diff --git a/.jsdoc.json b/.jsdoc.json new file mode 100644 index 00000000..86ae87c7 --- /dev/null +++ b/.jsdoc.json @@ -0,0 +1,22 @@ +{ + "tags": { + "allowUnknownTags": false + }, + "source": { + "include": ["./modAssist_main.js", "./lib"] + }, + "plugins": [ + "plugins/markdown" + ], + "opts": { + "template": "node_modules/docdash/", + "encoding": "utf8", + "destination": "jsdoc_server/", + "recurse": true, + "verbose": true + }, + "templates": { + "cleverLinks": false, + "monospaceLinks": false + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index fd14b60a..8edbfeda 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -68,6 +68,7 @@ "deepl", "DESCVERSION", "digestate", + "docdash", "dollys", "drygrass", "DXGI", @@ -123,6 +124,7 @@ "iconfilename", "IMAGEFILENAME", "IMAGENAME", + "importjson", "incomeperhour", "ingame", "inputbinding", @@ -205,6 +207,7 @@ "progressbar", "pycache", "reallights", + "redcabbage", "Rescan", "roadsalt", "Rottne", @@ -214,6 +217,7 @@ "savedate", "savegame", "savegamename", + "savegames", "savemanage", "savetrack", "SCCS", @@ -255,6 +259,7 @@ "Vicon", "wheelconfiguration", "wheelconfigurations", + "whitecabbage", "windrowers", "woodchips", "workingwidth", diff --git a/.yarn/cache/@babel-code-frame-npm-7.24.2-e104352cc7-70e867340c.zip b/.yarn/cache/@babel-code-frame-npm-7.24.2-e104352cc7-70e867340c.zip new file mode 100644 index 00000000..46e6debb Binary files /dev/null and b/.yarn/cache/@babel-code-frame-npm-7.24.2-e104352cc7-70e867340c.zip differ diff --git a/.yarn/cache/@babel-core-npm-7.23.9-4987baf09b-634a511f74.zip b/.yarn/cache/@babel-core-npm-7.23.9-4987baf09b-634a511f74.zip deleted file mode 100644 index c5f94726..00000000 Binary files a/.yarn/cache/@babel-core-npm-7.23.9-4987baf09b-634a511f74.zip and /dev/null differ diff --git a/.yarn/cache/@babel-core-npm-7.24.3-1ba98ae816-1a33460794.zip b/.yarn/cache/@babel-core-npm-7.24.3-1ba98ae816-1a33460794.zip new file mode 100644 index 00000000..ac130723 Binary files /dev/null and b/.yarn/cache/@babel-core-npm-7.24.3-1ba98ae816-1a33460794.zip differ diff --git a/.yarn/cache/@babel-eslint-parser-npm-7.23.10-7be7518fee-81249edee1.zip b/.yarn/cache/@babel-eslint-parser-npm-7.23.10-7be7518fee-81249edee1.zip deleted file mode 100644 index cd953876..00000000 Binary files a/.yarn/cache/@babel-eslint-parser-npm-7.23.10-7be7518fee-81249edee1.zip and /dev/null differ diff --git a/.yarn/cache/@babel-eslint-parser-npm-7.24.1-9aae2c34b4-962ffa9862.zip b/.yarn/cache/@babel-eslint-parser-npm-7.24.1-9aae2c34b4-962ffa9862.zip new file mode 100644 index 00000000..8d2e5d3e Binary files /dev/null and b/.yarn/cache/@babel-eslint-parser-npm-7.24.1-9aae2c34b4-962ffa9862.zip differ diff --git a/.yarn/cache/@babel-generator-npm-7.23.6-817ef5e591-1a1a1c4eac.zip b/.yarn/cache/@babel-generator-npm-7.23.6-817ef5e591-1a1a1c4eac.zip deleted file mode 100644 index 0ec9b50d..00000000 Binary files a/.yarn/cache/@babel-generator-npm-7.23.6-817ef5e591-1a1a1c4eac.zip and /dev/null differ diff --git a/.yarn/cache/@babel-generator-npm-7.24.1-649d75bd2a-98c6ce5ec7.zip b/.yarn/cache/@babel-generator-npm-7.24.1-649d75bd2a-98c6ce5ec7.zip new file mode 100644 index 00000000..9f206879 Binary files /dev/null and b/.yarn/cache/@babel-generator-npm-7.24.1-649d75bd2a-98c6ce5ec7.zip differ diff --git a/.yarn/cache/@babel-helpers-npm-7.23.9-703579a363-2678231192.zip b/.yarn/cache/@babel-helpers-npm-7.23.9-703579a363-2678231192.zip deleted file mode 100644 index dac0d2c0..00000000 Binary files a/.yarn/cache/@babel-helpers-npm-7.23.9-703579a363-2678231192.zip and /dev/null differ diff --git a/.yarn/cache/@babel-helpers-npm-7.24.1-665db13190-0643b8ccf3.zip b/.yarn/cache/@babel-helpers-npm-7.24.1-665db13190-0643b8ccf3.zip new file mode 100644 index 00000000..6fcdc37d Binary files /dev/null and b/.yarn/cache/@babel-helpers-npm-7.24.1-665db13190-0643b8ccf3.zip differ diff --git a/.yarn/cache/@babel-highlight-npm-7.24.2-d2e9453f0c-5f17b131cc.zip b/.yarn/cache/@babel-highlight-npm-7.24.2-d2e9453f0c-5f17b131cc.zip new file mode 100644 index 00000000..6897036d Binary files /dev/null and b/.yarn/cache/@babel-highlight-npm-7.24.2-d2e9453f0c-5f17b131cc.zip differ diff --git a/.yarn/cache/@babel-parser-npm-7.24.1-8b30631d26-a1068941dd.zip b/.yarn/cache/@babel-parser-npm-7.24.1-8b30631d26-a1068941dd.zip new file mode 100644 index 00000000..d440b09e Binary files /dev/null and b/.yarn/cache/@babel-parser-npm-7.24.1-8b30631d26-a1068941dd.zip differ diff --git a/.yarn/cache/@babel-template-npm-7.23.9-d3df10ecd2-6e67414c0f.zip b/.yarn/cache/@babel-template-npm-7.24.0-674650c96c-f257b003c0.zip similarity index 63% rename from .yarn/cache/@babel-template-npm-7.23.9-d3df10ecd2-6e67414c0f.zip rename to .yarn/cache/@babel-template-npm-7.24.0-674650c96c-f257b003c0.zip index bf341ec4..b8200beb 100644 Binary files a/.yarn/cache/@babel-template-npm-7.23.9-d3df10ecd2-6e67414c0f.zip and b/.yarn/cache/@babel-template-npm-7.24.0-674650c96c-f257b003c0.zip differ diff --git a/.yarn/cache/@babel-traverse-npm-7.23.9-5506c369f7-a932f7aa85.zip b/.yarn/cache/@babel-traverse-npm-7.23.9-5506c369f7-a932f7aa85.zip deleted file mode 100644 index c2aa4e7a..00000000 Binary files a/.yarn/cache/@babel-traverse-npm-7.23.9-5506c369f7-a932f7aa85.zip and /dev/null differ diff --git a/.yarn/cache/@babel-traverse-npm-7.24.1-8b235322a8-92a5ca906a.zip b/.yarn/cache/@babel-traverse-npm-7.24.1-8b235322a8-92a5ca906a.zip new file mode 100644 index 00000000..ff8797d6 Binary files /dev/null and b/.yarn/cache/@babel-traverse-npm-7.24.1-8b235322a8-92a5ca906a.zip differ diff --git a/.yarn/cache/@babel-types-npm-7.23.6-4e68ac9e9b-68187dbec0.zip b/.yarn/cache/@babel-types-npm-7.23.6-4e68ac9e9b-68187dbec0.zip deleted file mode 100644 index cb120e92..00000000 Binary files a/.yarn/cache/@babel-types-npm-7.23.6-4e68ac9e9b-68187dbec0.zip and /dev/null differ diff --git a/.yarn/cache/@babel-types-npm-7.23.9-c32aeb5f36-0a9b008e9b.zip b/.yarn/cache/@babel-types-npm-7.24.0-a0508cb308-4b574a37d4.zip similarity index 73% rename from .yarn/cache/@babel-types-npm-7.23.9-c32aeb5f36-0a9b008e9b.zip rename to .yarn/cache/@babel-types-npm-7.24.0-a0508cb308-4b574a37d4.zip index 4571d0ca..282e944e 100644 Binary files a/.yarn/cache/@babel-types-npm-7.23.9-c32aeb5f36-0a9b008e9b.zip and b/.yarn/cache/@babel-types-npm-7.24.0-a0508cb308-4b574a37d4.zip differ diff --git a/.yarn/cache/@eslint-js-npm-8.56.0-b1de08cbff-5804130574.zip b/.yarn/cache/@eslint-js-npm-8.57.0-00ead3710a-315dc65b0e.zip similarity index 88% rename from .yarn/cache/@eslint-js-npm-8.56.0-b1de08cbff-5804130574.zip rename to .yarn/cache/@eslint-js-npm-8.57.0-00ead3710a-315dc65b0e.zip index 0bb729d5..82eab16e 100644 Binary files a/.yarn/cache/@eslint-js-npm-8.56.0-b1de08cbff-5804130574.zip and b/.yarn/cache/@eslint-js-npm-8.57.0-00ead3710a-315dc65b0e.zip differ diff --git a/.yarn/cache/@humanwhocodes-config-array-npm-0.11.13-12314014f2-f8ea57b0d7.zip b/.yarn/cache/@humanwhocodes-config-array-npm-0.11.14-94a02fcc87-861ccce9ea.zip similarity index 53% rename from .yarn/cache/@humanwhocodes-config-array-npm-0.11.13-12314014f2-f8ea57b0d7.zip rename to .yarn/cache/@humanwhocodes-config-array-npm-0.11.14-94a02fcc87-861ccce9ea.zip index 4a8665e8..166fee4b 100644 Binary files a/.yarn/cache/@humanwhocodes-config-array-npm-0.11.13-12314014f2-f8ea57b0d7.zip and b/.yarn/cache/@humanwhocodes-config-array-npm-0.11.14-94a02fcc87-861ccce9ea.zip differ diff --git a/.yarn/cache/@humanwhocodes-object-schema-npm-2.0.1-c23364bbfc-24929487b1.zip b/.yarn/cache/@humanwhocodes-object-schema-npm-2.0.2-77b42018f9-2fc1150336.zip similarity index 64% rename from .yarn/cache/@humanwhocodes-object-schema-npm-2.0.1-c23364bbfc-24929487b1.zip rename to .yarn/cache/@humanwhocodes-object-schema-npm-2.0.2-77b42018f9-2fc1150336.zip index ddd36cec..cf6847cf 100644 Binary files a/.yarn/cache/@humanwhocodes-object-schema-npm-2.0.1-c23364bbfc-24929487b1.zip and b/.yarn/cache/@humanwhocodes-object-schema-npm-2.0.2-77b42018f9-2fc1150336.zip differ diff --git a/.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip b/.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip new file mode 100644 index 00000000..ab69f33c Binary files /dev/null and b/.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip differ diff --git a/.yarn/cache/@jridgewell-resolve-uri-npm-3.1.2-5bc4245992-83b85f72c5.zip b/.yarn/cache/@jridgewell-resolve-uri-npm-3.1.2-5bc4245992-83b85f72c5.zip new file mode 100644 index 00000000..a5724228 Binary files /dev/null and b/.yarn/cache/@jridgewell-resolve-uri-npm-3.1.2-5bc4245992-83b85f72c5.zip differ diff --git a/.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip b/.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip new file mode 100644 index 00000000..8a72fc72 Binary files /dev/null and b/.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip differ diff --git a/.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip b/.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip new file mode 100644 index 00000000..fc42ef59 Binary files /dev/null and b/.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip differ diff --git a/.yarn/cache/@jsdoc-salty-npm-0.2.7-e225cff537-020bc5a7f7.zip b/.yarn/cache/@jsdoc-salty-npm-0.2.7-e225cff537-020bc5a7f7.zip new file mode 100644 index 00000000..7dafcfec Binary files /dev/null and b/.yarn/cache/@jsdoc-salty-npm-0.2.7-e225cff537-020bc5a7f7.zip differ diff --git a/.yarn/cache/@types-linkify-it-npm-3.0.5-ff4a6ef3bd-fac28f41a6.zip b/.yarn/cache/@types-linkify-it-npm-3.0.5-ff4a6ef3bd-fac28f41a6.zip new file mode 100644 index 00000000..9435a15a Binary files /dev/null and b/.yarn/cache/@types-linkify-it-npm-3.0.5-ff4a6ef3bd-fac28f41a6.zip differ diff --git a/.yarn/cache/@types-markdown-it-npm-12.2.3-ef47108ac6-868824a3e4.zip b/.yarn/cache/@types-markdown-it-npm-12.2.3-ef47108ac6-868824a3e4.zip new file mode 100644 index 00000000..844294ce Binary files /dev/null and b/.yarn/cache/@types-markdown-it-npm-12.2.3-ef47108ac6-868824a3e4.zip differ diff --git a/.yarn/cache/@types-mdurl-npm-1.0.5-924b60d7d4-e8e872e8da.zip b/.yarn/cache/@types-mdurl-npm-1.0.5-924b60d7d4-e8e872e8da.zip new file mode 100644 index 00000000..6912667e Binary files /dev/null and b/.yarn/cache/@types-mdurl-npm-1.0.5-924b60d7d4-e8e872e8da.zip differ diff --git a/.yarn/cache/@types-node-npm-18.17.1-4d5cf864b8-56201bda9a.zip b/.yarn/cache/@types-node-npm-18.17.1-4d5cf864b8-56201bda9a.zip deleted file mode 100644 index 9b87da48..00000000 Binary files a/.yarn/cache/@types-node-npm-18.17.1-4d5cf864b8-56201bda9a.zip and /dev/null differ diff --git a/.yarn/cache/@types-node-npm-20.11.30-b20dd3f11f-7597767aa3.zip b/.yarn/cache/@types-node-npm-20.11.30-b20dd3f11f-7597767aa3.zip new file mode 100644 index 00000000..70717b85 Binary files /dev/null and b/.yarn/cache/@types-node-npm-20.11.30-b20dd3f11f-7597767aa3.zip differ diff --git a/.yarn/cache/abort-controller-npm-3.0.0-2f3a9a2bcb-170bdba9b4.zip b/.yarn/cache/abort-controller-npm-3.0.0-2f3a9a2bcb-170bdba9b4.zip new file mode 100644 index 00000000..c9b02732 Binary files /dev/null and b/.yarn/cache/abort-controller-npm-3.0.0-2f3a9a2bcb-170bdba9b4.zip differ diff --git a/.yarn/cache/adm-zip-npm-0.5.10-17a872f2fd-07ed91cf64.zip b/.yarn/cache/adm-zip-npm-0.5.10-17a872f2fd-07ed91cf64.zip deleted file mode 100644 index c1fad788..00000000 Binary files a/.yarn/cache/adm-zip-npm-0.5.10-17a872f2fd-07ed91cf64.zip and /dev/null differ diff --git a/.yarn/cache/app-builder-lib-npm-24.13.0-ae2148d80d-7e9baa6881.zip b/.yarn/cache/app-builder-lib-npm-24.13.3-86a66c0bf3-68ea3295ef.zip similarity index 69% rename from .yarn/cache/app-builder-lib-npm-24.13.0-ae2148d80d-7e9baa6881.zip rename to .yarn/cache/app-builder-lib-npm-24.13.3-86a66c0bf3-68ea3295ef.zip index 19b0733c..d6ce3f2b 100644 Binary files a/.yarn/cache/app-builder-lib-npm-24.13.0-ae2148d80d-7e9baa6881.zip and b/.yarn/cache/app-builder-lib-npm-24.13.3-86a66c0bf3-68ea3295ef.zip differ diff --git a/.yarn/cache/archiver-npm-6.0.1-427b6af3f3-20549eef73.zip b/.yarn/cache/archiver-npm-7.0.1-3d250dfaf3-f93bcc00f9.zip similarity index 94% rename from .yarn/cache/archiver-npm-6.0.1-427b6af3f3-20549eef73.zip rename to .yarn/cache/archiver-npm-7.0.1-3d250dfaf3-f93bcc00f9.zip index 8d902efe..4fdf5962 100644 Binary files a/.yarn/cache/archiver-npm-6.0.1-427b6af3f3-20549eef73.zip and b/.yarn/cache/archiver-npm-7.0.1-3d250dfaf3-f93bcc00f9.zip differ diff --git a/.yarn/cache/archiver-utils-npm-4.0.1-6d05f300c9-2917cdf63a.zip b/.yarn/cache/archiver-utils-npm-5.0.2-a19b25b4fd-7dc4f3001d.zip similarity index 61% rename from .yarn/cache/archiver-utils-npm-4.0.1-6d05f300c9-2917cdf63a.zip rename to .yarn/cache/archiver-utils-npm-5.0.2-a19b25b4fd-7dc4f3001d.zip index 07ce8854..a73df9d7 100644 Binary files a/.yarn/cache/archiver-utils-npm-4.0.1-6d05f300c9-2917cdf63a.zip and b/.yarn/cache/archiver-utils-npm-5.0.2-a19b25b4fd-7dc4f3001d.zip differ diff --git a/.yarn/cache/buffer-crc32-npm-1.0.0-3a0d1f8f40-bc114c0e02.zip b/.yarn/cache/buffer-crc32-npm-1.0.0-3a0d1f8f40-bc114c0e02.zip new file mode 100644 index 00000000..f2e0c050 Binary files /dev/null and b/.yarn/cache/buffer-crc32-npm-1.0.0-3a0d1f8f40-bc114c0e02.zip differ diff --git a/.yarn/cache/buffer-npm-6.0.3-cd90dfedfe-5ad23293d9.zip b/.yarn/cache/buffer-npm-6.0.3-cd90dfedfe-5ad23293d9.zip new file mode 100644 index 00000000..dbf2748b Binary files /dev/null and b/.yarn/cache/buffer-npm-6.0.3-cd90dfedfe-5ad23293d9.zip differ diff --git a/.yarn/cache/builder-util-npm-24.13.1-8df62f958e-2991ee7ce2.zip b/.yarn/cache/builder-util-npm-24.13.1-8df62f958e-2991ee7ce2.zip new file mode 100644 index 00000000..3114f727 Binary files /dev/null and b/.yarn/cache/builder-util-npm-24.13.1-8df62f958e-2991ee7ce2.zip differ diff --git a/.yarn/cache/builder-util-npm-24.9.4-46437d9b72-33a7df457a.zip b/.yarn/cache/builder-util-npm-24.9.4-46437d9b72-33a7df457a.zip deleted file mode 100644 index 6ba52032..00000000 Binary files a/.yarn/cache/builder-util-npm-24.9.4-46437d9b72-33a7df457a.zip and /dev/null differ diff --git a/.yarn/cache/builder-util-runtime-npm-9.2.3-3905baa738-f25d6f1235.zip b/.yarn/cache/builder-util-runtime-npm-9.2.3-3905baa738-f25d6f1235.zip deleted file mode 100644 index cd74cc0b..00000000 Binary files a/.yarn/cache/builder-util-runtime-npm-9.2.3-3905baa738-f25d6f1235.zip and /dev/null differ diff --git a/.yarn/cache/builder-util-runtime-npm-9.2.4-d04e702c06-7d02b7f57a.zip b/.yarn/cache/builder-util-runtime-npm-9.2.4-d04e702c06-7d02b7f57a.zip new file mode 100644 index 00000000..df7021c9 Binary files /dev/null and b/.yarn/cache/builder-util-runtime-npm-9.2.4-d04e702c06-7d02b7f57a.zip differ diff --git a/.yarn/cache/catharsis-npm-0.9.0-208a98dbe1-da867df1fd.zip b/.yarn/cache/catharsis-npm-0.9.0-208a98dbe1-da867df1fd.zip new file mode 100644 index 00000000..fb2b4e44 Binary files /dev/null and b/.yarn/cache/catharsis-npm-0.9.0-208a98dbe1-da867df1fd.zip differ diff --git a/.yarn/cache/compress-commons-npm-5.0.1-28fafeb552-65a68e5621.zip b/.yarn/cache/compress-commons-npm-6.0.2-f21623b848-37d79a54f9.zip similarity index 55% rename from .yarn/cache/compress-commons-npm-5.0.1-28fafeb552-65a68e5621.zip rename to .yarn/cache/compress-commons-npm-6.0.2-f21623b848-37d79a54f9.zip index 632c05e3..995a4c40 100644 Binary files a/.yarn/cache/compress-commons-npm-5.0.1-28fafeb552-65a68e5621.zip and b/.yarn/cache/compress-commons-npm-6.0.2-f21623b848-37d79a54f9.zip differ diff --git a/.yarn/cache/crc32-stream-npm-5.0.0-6050675417-8e5dd04f22.zip b/.yarn/cache/crc32-stream-npm-6.0.0-fcf8d3f1b3-e6edc2f81b.zip similarity index 65% rename from .yarn/cache/crc32-stream-npm-5.0.0-6050675417-8e5dd04f22.zip rename to .yarn/cache/crc32-stream-npm-6.0.0-fcf8d3f1b3-e6edc2f81b.zip index 83d50875..e14e314e 100644 Binary files a/.yarn/cache/crc32-stream-npm-5.0.0-6050675417-8e5dd04f22.zip and b/.yarn/cache/crc32-stream-npm-6.0.0-fcf8d3f1b3-e6edc2f81b.zip differ diff --git a/.yarn/cache/deepl-node-npm-1.11.1-4f5aae070e-bd261a44fd.zip b/.yarn/cache/deepl-node-npm-1.12.0-0d06ce1023-a2e5100d83.zip similarity index 67% rename from .yarn/cache/deepl-node-npm-1.11.1-4f5aae070e-bd261a44fd.zip rename to .yarn/cache/deepl-node-npm-1.12.0-0d06ce1023-a2e5100d83.zip index 19ba7a01..2aab9c91 100644 Binary files a/.yarn/cache/deepl-node-npm-1.11.1-4f5aae070e-bd261a44fd.zip and b/.yarn/cache/deepl-node-npm-1.12.0-0d06ce1023-a2e5100d83.zip differ diff --git a/.yarn/cache/dmg-builder-npm-24.13.0-9dfb64f4f3-725e22042e.zip b/.yarn/cache/dmg-builder-npm-24.13.3-2047ba9716-5c25293d79.zip similarity index 81% rename from .yarn/cache/dmg-builder-npm-24.13.0-9dfb64f4f3-725e22042e.zip rename to .yarn/cache/dmg-builder-npm-24.13.3-2047ba9716-5c25293d79.zip index e0feae0e..36862acf 100644 Binary files a/.yarn/cache/dmg-builder-npm-24.13.0-9dfb64f4f3-725e22042e.zip and b/.yarn/cache/dmg-builder-npm-24.13.3-2047ba9716-5c25293d79.zip differ diff --git a/.yarn/cache/docdash-npm-2.0.2-cf0e0b30a6-de802483b5.zip b/.yarn/cache/docdash-npm-2.0.2-cf0e0b30a6-de802483b5.zip new file mode 100644 index 00000000..21f56d5a Binary files /dev/null and b/.yarn/cache/docdash-npm-2.0.2-cf0e0b30a6-de802483b5.zip differ diff --git a/.yarn/cache/electron-builder-npm-24.13.0-417603cfc1-de371c1187.zip b/.yarn/cache/electron-builder-npm-24.13.0-417603cfc1-de371c1187.zip deleted file mode 100644 index eb8d4727..00000000 Binary files a/.yarn/cache/electron-builder-npm-24.13.0-417603cfc1-de371c1187.zip and /dev/null differ diff --git a/.yarn/cache/electron-builder-npm-24.13.3-765a27e19b-8d7943d990.zip b/.yarn/cache/electron-builder-npm-24.13.3-765a27e19b-8d7943d990.zip new file mode 100644 index 00000000..a9b3e70c Binary files /dev/null and b/.yarn/cache/electron-builder-npm-24.13.3-765a27e19b-8d7943d990.zip differ diff --git a/.yarn/cache/electron-npm-28.2.2-bc12ea7fe4-0e44bf2dd0.zip b/.yarn/cache/electron-npm-28.2.2-bc12ea7fe4-0e44bf2dd0.zip deleted file mode 100644 index ef6ef101..00000000 Binary files a/.yarn/cache/electron-npm-28.2.2-bc12ea7fe4-0e44bf2dd0.zip and /dev/null differ diff --git a/.yarn/cache/electron-npm-29.1.6-383238c372-d37229ee3c.zip b/.yarn/cache/electron-npm-29.1.6-383238c372-d37229ee3c.zip new file mode 100644 index 00000000..ac972373 Binary files /dev/null and b/.yarn/cache/electron-npm-29.1.6-383238c372-d37229ee3c.zip differ diff --git a/.yarn/cache/electron-publish-npm-24.13.0-b10dc85b0b-fa8d27e56a.zip b/.yarn/cache/electron-publish-npm-24.13.0-b10dc85b0b-fa8d27e56a.zip deleted file mode 100644 index 27f56e68..00000000 Binary files a/.yarn/cache/electron-publish-npm-24.13.0-b10dc85b0b-fa8d27e56a.zip and /dev/null differ diff --git a/.yarn/cache/electron-publish-npm-24.13.1-40acfab2ad-7cd9924c96.zip b/.yarn/cache/electron-publish-npm-24.13.1-40acfab2ad-7cd9924c96.zip new file mode 100644 index 00000000..4d80ed00 Binary files /dev/null and b/.yarn/cache/electron-publish-npm-24.13.1-40acfab2ad-7cd9924c96.zip differ diff --git a/.yarn/cache/electron-store-npm-8.1.0-eb7ce7c6e3-7036f6d91c.zip b/.yarn/cache/electron-store-npm-8.1.0-eb7ce7c6e3-7036f6d91c.zip deleted file mode 100644 index 4e4a455e..00000000 Binary files a/.yarn/cache/electron-store-npm-8.1.0-eb7ce7c6e3-7036f6d91c.zip and /dev/null differ diff --git a/.yarn/cache/electron-store-npm-8.2.0-293941df14-ee2484ff9b.zip b/.yarn/cache/electron-store-npm-8.2.0-293941df14-ee2484ff9b.zip new file mode 100644 index 00000000..c98a3433 Binary files /dev/null and b/.yarn/cache/electron-store-npm-8.2.0-293941df14-ee2484ff9b.zip differ diff --git a/.yarn/cache/electron-updater-npm-6.1.8-72cb7c369e-250371f0e1.zip b/.yarn/cache/electron-updater-npm-6.1.8-72cb7c369e-250371f0e1.zip deleted file mode 100644 index c83df0a3..00000000 Binary files a/.yarn/cache/electron-updater-npm-6.1.8-72cb7c369e-250371f0e1.zip and /dev/null differ diff --git a/.yarn/cache/electron-updater-npm-6.2.1-3a4fa2432f-92a064610a.zip b/.yarn/cache/electron-updater-npm-6.2.1-3a4fa2432f-92a064610a.zip new file mode 100644 index 00000000..a09c78a6 Binary files /dev/null and b/.yarn/cache/electron-updater-npm-6.2.1-3a4fa2432f-92a064610a.zip differ diff --git a/.yarn/cache/entities-npm-2.1.0-b27b8aebc6-a10a877e48.zip b/.yarn/cache/entities-npm-2.1.0-b27b8aebc6-a10a877e48.zip new file mode 100644 index 00000000..50499cb0 Binary files /dev/null and b/.yarn/cache/entities-npm-2.1.0-b27b8aebc6-a10a877e48.zip differ diff --git a/.yarn/cache/escape-string-regexp-npm-2.0.0-aef69d2a25-9f8a2d5743.zip b/.yarn/cache/escape-string-regexp-npm-2.0.0-aef69d2a25-9f8a2d5743.zip new file mode 100644 index 00000000..5150d4e5 Binary files /dev/null and b/.yarn/cache/escape-string-regexp-npm-2.0.0-aef69d2a25-9f8a2d5743.zip differ diff --git a/.yarn/cache/eslint-npm-8.56.0-6eec398a41-883436d1e8.zip b/.yarn/cache/eslint-npm-8.57.0-4286e12a3a-3a48d7ff85.zip similarity index 92% rename from .yarn/cache/eslint-npm-8.56.0-6eec398a41-883436d1e8.zip rename to .yarn/cache/eslint-npm-8.57.0-4286e12a3a-3a48d7ff85.zip index 1b83bdbf..73f8f9df 100644 Binary files a/.yarn/cache/eslint-npm-8.56.0-6eec398a41-883436d1e8.zip and b/.yarn/cache/eslint-npm-8.57.0-4286e12a3a-3a48d7ff85.zip differ diff --git a/.yarn/cache/event-target-shim-npm-5.0.1-cb48709025-1ffe3bb22a.zip b/.yarn/cache/event-target-shim-npm-5.0.1-cb48709025-1ffe3bb22a.zip new file mode 100644 index 00000000..565cdc67 Binary files /dev/null and b/.yarn/cache/event-target-shim-npm-5.0.1-cb48709025-1ffe3bb22a.zip differ diff --git a/.yarn/cache/events-npm-3.3.0-c280bc7e48-f6f487ad21.zip b/.yarn/cache/events-npm-3.3.0-c280bc7e48-f6f487ad21.zip new file mode 100644 index 00000000..6f643482 Binary files /dev/null and b/.yarn/cache/events-npm-3.3.0-c280bc7e48-f6f487ad21.zip differ diff --git a/.yarn/cache/fast-xml-parser-npm-4.3.4-471ce88e5f-ab88177343.zip b/.yarn/cache/fast-xml-parser-npm-4.3.4-471ce88e5f-ab88177343.zip deleted file mode 100644 index 3ac345fc..00000000 Binary files a/.yarn/cache/fast-xml-parser-npm-4.3.4-471ce88e5f-ab88177343.zip and /dev/null differ diff --git a/.yarn/cache/fast-xml-parser-npm-4.3.6-b71efbeb6c-12795c55f4.zip b/.yarn/cache/fast-xml-parser-npm-4.3.6-b71efbeb6c-12795c55f4.zip new file mode 100644 index 00000000..01c8bd14 Binary files /dev/null and b/.yarn/cache/fast-xml-parser-npm-4.3.6-b71efbeb6c-12795c55f4.zip differ diff --git a/.yarn/cache/glob-npm-10.3.12-f2e90133a0-2b0949d636.zip b/.yarn/cache/glob-npm-10.3.12-f2e90133a0-2b0949d636.zip new file mode 100644 index 00000000..ab0edb15 Binary files /dev/null and b/.yarn/cache/glob-npm-10.3.12-f2e90133a0-2b0949d636.zip differ diff --git a/.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip b/.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip deleted file mode 100644 index 3fc76b57..00000000 Binary files a/.yarn/cache/glob-npm-8.1.0-65f64af8b1-92fbea3221.zip and /dev/null differ diff --git a/.yarn/cache/html-validate-npm-8.18.1-c5271a0fb9-53479bf75b.zip b/.yarn/cache/html-validate-npm-8.18.1-c5271a0fb9-53479bf75b.zip new file mode 100644 index 00000000..b2f855af Binary files /dev/null and b/.yarn/cache/html-validate-npm-8.18.1-c5271a0fb9-53479bf75b.zip differ diff --git a/.yarn/cache/html-validate-npm-8.9.1-b3199a37b5-31d46f3779.zip b/.yarn/cache/html-validate-npm-8.9.1-b3199a37b5-31d46f3779.zip deleted file mode 100644 index eef93cd5..00000000 Binary files a/.yarn/cache/html-validate-npm-8.9.1-b3199a37b5-31d46f3779.zip and /dev/null differ diff --git a/.yarn/cache/ignore-npm-5.3.0-fb0f5fa062-2736da6621.zip b/.yarn/cache/ignore-npm-5.3.0-fb0f5fa062-2736da6621.zip deleted file mode 100644 index 87f79cff..00000000 Binary files a/.yarn/cache/ignore-npm-5.3.0-fb0f5fa062-2736da6621.zip and /dev/null differ diff --git a/.yarn/cache/ignore-npm-5.3.1-f6947c5df7-71d7bb4c1d.zip b/.yarn/cache/ignore-npm-5.3.1-f6947c5df7-71d7bb4c1d.zip new file mode 100644 index 00000000..75ba53a2 Binary files /dev/null and b/.yarn/cache/ignore-npm-5.3.1-f6947c5df7-71d7bb4c1d.zip differ diff --git a/.yarn/cache/is-stream-npm-2.0.1-c802db55e7-b8e05ccdf9.zip b/.yarn/cache/is-stream-npm-2.0.1-c802db55e7-b8e05ccdf9.zip new file mode 100644 index 00000000..c5699a4e Binary files /dev/null and b/.yarn/cache/is-stream-npm-2.0.1-c802db55e7-b8e05ccdf9.zip differ diff --git a/.yarn/cache/js2xmlparser-npm-4.0.2-8dc434a23c-55e3af71dc.zip b/.yarn/cache/js2xmlparser-npm-4.0.2-8dc434a23c-55e3af71dc.zip new file mode 100644 index 00000000..1556bc76 Binary files /dev/null and b/.yarn/cache/js2xmlparser-npm-4.0.2-8dc434a23c-55e3af71dc.zip differ diff --git a/.yarn/cache/jsdoc-npm-4.0.2-9d339d556b-04bf5ab005.zip b/.yarn/cache/jsdoc-npm-4.0.2-9d339d556b-04bf5ab005.zip new file mode 100644 index 00000000..14ddda17 Binary files /dev/null and b/.yarn/cache/jsdoc-npm-4.0.2-9d339d556b-04bf5ab005.zip differ diff --git a/.yarn/cache/klaw-npm-3.0.0-74149fbd53-1bf9de2239.zip b/.yarn/cache/klaw-npm-3.0.0-74149fbd53-1bf9de2239.zip new file mode 100644 index 00000000..fe53a5f5 Binary files /dev/null and b/.yarn/cache/klaw-npm-3.0.0-74149fbd53-1bf9de2239.zip differ diff --git a/.yarn/cache/linkify-it-npm-3.0.3-6880fe29c1-31367a4bb7.zip b/.yarn/cache/linkify-it-npm-3.0.3-6880fe29c1-31367a4bb7.zip new file mode 100644 index 00000000..23145f33 Binary files /dev/null and b/.yarn/cache/linkify-it-npm-3.0.3-6880fe29c1-31367a4bb7.zip differ diff --git a/.yarn/cache/lru-cache-npm-10.2.0-b9f6b44740-eee7ddda4a.zip b/.yarn/cache/lru-cache-npm-10.2.0-b9f6b44740-eee7ddda4a.zip new file mode 100644 index 00000000..3b579617 Binary files /dev/null and b/.yarn/cache/lru-cache-npm-10.2.0-b9f6b44740-eee7ddda4a.zip differ diff --git a/.yarn/cache/markdown-it-anchor-npm-8.6.7-698cb24368-828236768a.zip b/.yarn/cache/markdown-it-anchor-npm-8.6.7-698cb24368-828236768a.zip new file mode 100644 index 00000000..29b09ebc Binary files /dev/null and b/.yarn/cache/markdown-it-anchor-npm-8.6.7-698cb24368-828236768a.zip differ diff --git a/.yarn/cache/markdown-it-npm-12.3.2-6c66b716e8-890555711c.zip b/.yarn/cache/markdown-it-npm-12.3.2-6c66b716e8-890555711c.zip new file mode 100644 index 00000000..be179a7f Binary files /dev/null and b/.yarn/cache/markdown-it-npm-12.3.2-6c66b716e8-890555711c.zip differ diff --git a/.yarn/cache/marked-npm-4.3.0-e7ef9e874f-0db6817893.zip b/.yarn/cache/marked-npm-4.3.0-e7ef9e874f-0db6817893.zip new file mode 100644 index 00000000..3c26c0a5 Binary files /dev/null and b/.yarn/cache/marked-npm-4.3.0-e7ef9e874f-0db6817893.zip differ diff --git a/.yarn/cache/mdurl-npm-1.0.1-054d974269-71731ecba9.zip b/.yarn/cache/mdurl-npm-1.0.1-054d974269-71731ecba9.zip new file mode 100644 index 00000000..e8e8256e Binary files /dev/null and b/.yarn/cache/mdurl-npm-1.0.1-054d974269-71731ecba9.zip differ diff --git a/.yarn/cache/minipass-npm-7.0.4-eacb4e042e-87585e258b.zip b/.yarn/cache/minipass-npm-7.0.4-eacb4e042e-87585e258b.zip new file mode 100644 index 00000000..472202f6 Binary files /dev/null and b/.yarn/cache/minipass-npm-7.0.4-eacb4e042e-87585e258b.zip differ diff --git a/.yarn/cache/node-stream-zip-npm-1.15.0-47adb9fcfb-0b73ffbb09.zip b/.yarn/cache/node-stream-zip-npm-1.15.0-47adb9fcfb-0b73ffbb09.zip new file mode 100644 index 00000000..c953145b Binary files /dev/null and b/.yarn/cache/node-stream-zip-npm-1.15.0-47adb9fcfb-0b73ffbb09.zip differ diff --git a/.yarn/cache/path-scurry-npm-1.10.2-676482c764-6739b4290f.zip b/.yarn/cache/path-scurry-npm-1.10.2-676482c764-6739b4290f.zip new file mode 100644 index 00000000..64413d2e Binary files /dev/null and b/.yarn/cache/path-scurry-npm-1.10.2-676482c764-6739b4290f.zip differ diff --git a/.yarn/cache/process-npm-0.11.10-aeb3b641ae-bfcce49814.zip b/.yarn/cache/process-npm-0.11.10-aeb3b641ae-bfcce49814.zip new file mode 100644 index 00000000..1bb27202 Binary files /dev/null and b/.yarn/cache/process-npm-0.11.10-aeb3b641ae-bfcce49814.zip differ diff --git a/.yarn/cache/readable-stream-npm-4.5.2-4a1062e2a4-c4030ccff0.zip b/.yarn/cache/readable-stream-npm-4.5.2-4a1062e2a4-c4030ccff0.zip new file mode 100644 index 00000000..eaa87656 Binary files /dev/null and b/.yarn/cache/readable-stream-npm-4.5.2-4a1062e2a4-c4030ccff0.zip differ diff --git a/.yarn/cache/requizzle-npm-0.2.4-a970961c26-fceaa448b2.zip b/.yarn/cache/requizzle-npm-0.2.4-a970961c26-fceaa448b2.zip new file mode 100644 index 00000000..bd8b0e78 Binary files /dev/null and b/.yarn/cache/requizzle-npm-0.2.4-a970961c26-fceaa448b2.zip differ diff --git a/.yarn/cache/uc.micro-npm-1.0.6-36f3dc2fc4-6898bb5563.zip b/.yarn/cache/uc.micro-npm-1.0.6-36f3dc2fc4-6898bb5563.zip new file mode 100644 index 00000000..dc93b68a Binary files /dev/null and b/.yarn/cache/uc.micro-npm-1.0.6-36f3dc2fc4-6898bb5563.zip differ diff --git a/.yarn/cache/underscore-npm-1.13.6-3ebe9d92fb-d5cedd14a9.zip b/.yarn/cache/underscore-npm-1.13.6-3ebe9d92fb-d5cedd14a9.zip new file mode 100644 index 00000000..6aeb59d0 Binary files /dev/null and b/.yarn/cache/underscore-npm-1.13.6-3ebe9d92fb-d5cedd14a9.zip differ diff --git a/.yarn/cache/wasm-vips-npm-0.0.7-66a9115b07-3185046f39.zip b/.yarn/cache/wasm-vips-npm-0.0.7-66a9115b07-3185046f39.zip deleted file mode 100644 index 9e104d8f..00000000 Binary files a/.yarn/cache/wasm-vips-npm-0.0.7-66a9115b07-3185046f39.zip and /dev/null differ diff --git a/.yarn/cache/wasm-vips-npm-0.0.8-c54ccf5554-aac38c77b3.zip b/.yarn/cache/wasm-vips-npm-0.0.8-c54ccf5554-aac38c77b3.zip new file mode 100644 index 00000000..6f51939c Binary files /dev/null and b/.yarn/cache/wasm-vips-npm-0.0.8-c54ccf5554-aac38c77b3.zip differ diff --git a/.yarn/cache/xmlcreate-npm-2.0.4-340367eeae-b8dd52668b.zip b/.yarn/cache/xmlcreate-npm-2.0.4-340367eeae-b8dd52668b.zip new file mode 100644 index 00000000..2f5d5ce2 Binary files /dev/null and b/.yarn/cache/xmlcreate-npm-2.0.4-340367eeae-b8dd52668b.zip differ diff --git a/.yarn/cache/zip-stream-npm-5.0.1-f19d836b38-116cee5a2c.zip b/.yarn/cache/zip-stream-npm-6.0.1-21da293d4a-aa5abd6a89.zip similarity index 55% rename from .yarn/cache/zip-stream-npm-5.0.1-f19d836b38-116cee5a2c.zip rename to .yarn/cache/zip-stream-npm-6.0.1-21da293d4a-aa5abd6a89.zip index 683a33f0..ee7b6b7d 100644 Binary files a/.yarn/cache/zip-stream-npm-5.0.1-f19d836b38-116cee5a2c.zip and b/.yarn/cache/zip-stream-npm-6.0.1-21da293d4a-aa5abd6a89.zip differ diff --git a/docs/img340/context-collection.png b/docs/img340/context-collection.png index 2834f2ff..fc856e5f 100644 Binary files a/docs/img340/context-collection.png and b/docs/img340/context-collection.png differ diff --git a/docs/img340/import-step-0.png b/docs/img340/import-step-0.png new file mode 100644 index 00000000..39bb4b31 Binary files /dev/null and b/docs/img340/import-step-0.png differ diff --git a/docs/img340/import-step-1.png b/docs/img340/import-step-1.png new file mode 100644 index 00000000..5348e86c Binary files /dev/null and b/docs/img340/import-step-1.png differ diff --git a/docs/img340/import-step-2.png b/docs/img340/import-step-2.png new file mode 100644 index 00000000..b5f8a883 Binary files /dev/null and b/docs/img340/import-step-2.png differ diff --git a/docs/importexport.md b/docs/importexport.md index e2e30cd5..caeec548 100644 --- a/docs/importexport.md +++ b/docs/importexport.md @@ -61,3 +61,65 @@ Next to each collection is a small `CSV` icon You can drag single mods from the collection area to any valid target in windows. Valid targets are the desktop, an explorer window, and any number of third party apps that support dragging a file directly to them (for example, Discord) ![Alt text](img340/drag-drop-export.png) + +## Import & Export of Collections + +There is support for importing and exporting collection settings. + +### Export Collection + +Right click on the collection, and choose "Export Settings (JSON)" + +![context](img340/context-collection.png) + +--- + +### Altering Export Details + +The import / export JSON format is defined below. Most of the options are directly from the collection detail settings, the two additional fields are available for single mod download (no unzip) and mod pack download (unzip) at import time. + +```json +{ + "collection_color": 6, + "collection_description": "Test Import Server", + "download_direct": [ + "https://example.net/my_mod_collection.zip" + ], + "download_unzip": [ + "https://example.net/FS22_SingleMod.zip" + ], + "force_frozen": true, + "game_version": 22, + "server_downloads": false, + "server_id": "1-2-3-4", + "server_name": "server-name", + "server_password": "server-join-password", + "server_website": "http://example.net/" +} +``` + +--- + +### Import Collection + +To import a collection drag-and-drop the JSON file to the Mod Assistant window. A New window will appear + +The first section of the window will give you an overview of the collection being imported. + +![overview](img340/import-step-0.png) + +#### Step 1 + +First, select where on disk you would like to store the collection. You can use an existing folder (for instance, if updating a collection, or you already made the folder), or, you can create a new folder in the dialog that appears. + +![step 1](img340/import-step-1.png) + +Once you have selected the folder, press the "Apply Settings" button to apply the included settings. The button becomes enabled after selecting a location, and turns green when it is finished. + +#### Step 2 (optional) + +Optionally, you can include one-time downloads in an exported collection JSON file - if you do, they are available to download here. The buttons become available once you have selected a location on disk to store the collection. The buttons turn green when they are finished. Please note that these downloads will overwrite any existing files. + +![step 2](img340/import-step-2.png) + +Mod pack style downloads will be unzipped after download, single mods will just be stored in the collection folder. diff --git a/lib/jsdoc_global_types.js b/lib/jsdoc_global_types.js new file mode 100644 index 00000000..996d62aa --- /dev/null +++ b/lib/jsdoc_global_types.js @@ -0,0 +1,412 @@ +/** + * @typedef modRecord + * @type {Object} + * @description full mod record + * @property {modRecord_modDesc} modDesc Basic data from modDesc.xml + * @property {modRecord_md5Sum} md5Sum MD5 hash from location and date + * @property {modRecord_uuid} uuid MD5 hash from full path + * @property {modRecord_collectKey} currentCollection Current collection of mod + * @property {modRecord_fileDetail} fileDetail mod file details + * @property {boolean} canNotUse Mod cannot be used in game + * @property {modRecord_badges} badgeArray Array of badges + * @property {Object} l10n localization from mod + * @property {Array.} issues Array of issue string keys + */ + +/** + * @typedef modRecord_badges + * @type {Array.} + * @description modRecord badges + * @property {string} broken mod is broken + * @property {string} folder mod is a folder + * @property {string} malware mod may be malware + * @property {string} noMP mod can't do MP + * @property {string} notmod mod isn't a mod + * @property {string} pconly mod is PC only + * @property {string} problem mod has a problem + * @property {string} savegame mod is a savegame, not a mod + */ + +/** + * @typedef modRecord_md5Sum + * @type {string} + * @description MD5 sum from location and date + */ + +/** + * @typedef modRecord_storable + * @type {Object} + * @property {Array.} log Log lines generated from processing + * @property {modRecord} record the mod record + */ + +/** + * @typedef modRecord_collectKey + * @type {string} + * @description special string col_[collection UUID from path] + */ + +/** + * @typedef modRecord_col_uuid + * @type {string} + * @description special string col_[collection UUID]__[mod UUID] + */ + +/** + * @typedef modRecord_uuid + * @type {string} + * @description MD5 hash from full path of file + */ + +/** + * @typedef modRecord_shortName + * @type {string} + * @description Part of the mod filename before the .ZIP + */ + +/** + * @typedef modRecord_modDesc + * @type {Object} + * @description modDesc portion of modRecord + * @property {Object} actions Possible keyboard actions + * @property {string} author Mod author + * @property {Object} binds Keybinds + * @property {modRecord_cropInfo} cropInfo crop info or false + * @property {modRecord_cropWeather} cropWeather crop weather + * @property {Array.} depend Dependents + * @property {number} descVersion descVersion + * @property {(string|boolean)} iconFileName icon filename or false + * @property {?string} iconImageCache icon base64 or null + * @property {?string} mapConfigFile map config file + * @property {boolean} mapIsSouth map is in southern hemisphere + * @property {boolean} multiPlayer can be used multiplayer + * @property {number} scriptFiles number of script files + * @property {number} storeItems number of store items + * @property {string} version version of mod + */ + +/** + * @typedef modRecord_fileDetail + * @type {Object} + * @description file detail portion of modRecord + * @property {(string|boolean)} copyName if suspected copy, original name suggestion + * @property {Array} extraFiles Out-of-spec files in archive + * @property {string} fileDate ISO date of file + * @property {number} fileSize mod size + * @property {string} fullPath full path to file + * @property {Array.} i3dFiles List of I3D files + * @property {Array.} imageDDS List of DDS files + * @property {Array.} imageNonDDS List of non-DDS images + * @property {boolean} isFolder mod is a folder + * @property {boolean} isSaveGame is actually a savegame + * @property {Array.} pngTexture List of PNG files + * @property {modRecord_shortName} shortName short name of mod + * @property {Array.} spaceFiles List of files with a space in them + * @property {Array.} tooBigFiles Files out of spec for size + */ + +/** + * @typedef cropData_crop + * @type {Object} + * @description a single crop from the crop calendar + * @property {number} growthTime periods of growth + * @property {Array.} harvestPeriods periods allowing harvest + * @property {string} name l10n key + * @property {Array.} plantPeriods periods allowing planting + */ + +/** + * @typedef cropData_weather + * @type {Object} + * @description full weather from the crop calendar + * @property {cropData_weather_minmax} spring spring period + * @property {cropData_weather_minmax} summer summer period + * @property {cropData_weather_minmax} autumn autumn period + * @property {cropData_weather_minmax} winter winter period + */ + +/** + * @typedef cropData_weather_minmax + * @type {Object} + * @description a single period of the crop calendar weather + * @property {number} min Min temperature for period + * @property {number} max Max temperature for period + */ + +/** + * @typedef lookRecord_return + * @type {Object} + * @description Child process modLook return + * @property {Array.} log Log lines generated from processing + * @property {lookRecord} record the look record + */ + +/** + * @typedef lookRecord + * @type {Object} + * @description full lookRecord returned by modLook + * @property {Object.} brands key is brand ID + * @property {Object.} icon key is xml storeitem, value is base64 icon + * @property {Object.<(lookRecord_item_vehicle|lookRecord_item_place)>} items key is xml storeitem + * @property {?string} mapImage base64 map image if available + */ + +/** + * @typedef lookRecord_brand + * @type {Object} + * @description lookRecord part, brand + * @property {string} title Name of brand, translated + * @property {string} icon Base64 webp icon + */ + +/** + * @typedef lookRecord_item_joints + * @type {Object} + * @description modLook record part - attacher joints + * @property {Array.} canUse types this can haul + * @property {Array.} needs types this connects to + */ + +/** + * @typedef lookRecord_item_motor + * @type {Object} + * @description modLook record part - motor info + * @property {Array} hp chart.js data for HP graph + * @property {Array} kph chart.js data for KPH graph + * @property {Array} mph chart.js data for MPH graph + * @property {Array} speed fallback speed + */ + +/** + * @typedef lookRecord_item_vehicle + * @type {Object} + * @description modLook record for a vehicle + * @property {string} brand Brand ID, uppercase + * @property {string} category Category Name + * @property {number} fillLevel Fill capacity + * @property {Array.} fillTypes Valid fill types + * @property {(string|boolean)} fuelType Type of fuel + * @property {Array.} functions Functions of item + * @property {boolean} hasBeacons Has beacon lights + * @property {boolean} hasColor Color can be changed + * @property {boolean} hasLights Has lighting + * @property {boolean} hasWheelChoice Has multiple wheel sets + * @property {?string} icon Base64 icon + * @property {boolean} isEnterable can be entered by player + * @property {boolean} isMotorized has a motor + * @property {lookRecord_item_joints} joints attacher joints + * @property {string} masterType type of record, will be "vehicle" + * @property {lookRecord_item_motor} motorInfo Motor graph data + * @property {string} name name of item + * @property {number} price price of item + * @property {Object} specs direct info from XML specs, includes combinations if any + * @property {number} speedLimit max speed of item + * @property {?Object} sprayTypes spraytypes by width + * @property {(string|boolean)} transType transmission type + * @property {string} typeDesc type description + * @property {number} weight weight of item (kg) + * @property {number} year year of item + * @property {string} uuid_name name safe for use as html ID + */ + +/** + * @typedef lookRecord_item_beehive + * @type {Object} + * @description modLook record part for beehives + * @property {boolean} exist is a beehive + * @property {number} radius area of effect + * @property {number} liters liters per hour production + */ + +/** + * @typedef lookRecord_item_husbandry + * @type {Object} + * @description modLook record part for husbandry + * @property {boolean} exists is a husbandry + * @property {string} type animal type + * @property {number} capacity number of animals + */ + +/** + * @typedef lookRecord_item_silo + * @type {Object} + * @description modLook record portion for silo + * @property {boolean} exists is a silo + * @property {Array.} types fill types for silo + * @property {number} capacity capacity of silo + */ + +/** + * @typedef lookRecord_item_place + * @type {Object} + * @description modLook Record for a placeable + * @property {string} category Category Name + * @property {Array.} functions Functions of item + * @property {boolean} hasColor Color can be changed + * @property {?string} icon icon KEY + * @property {string} masterType type of record, will be "placable" + * @property {string} name name of item + * @property {number} price price of item + * @property {string} type full internal type of item + * @property {lookRecord_item_beehive} beehive beehive details + * @property {lookRecord_item_husbandry} husbandry husbandry details + * @property {?number} incomePerHour income + * @property {?number} objectStorage number of objects storable + * @property {Array.} productions production recipe info + * @property {lookRecord_item_silo} silo silo details + * + */ + +/** + * @typedef badgeHandleList + * @type {Array} + * @property {string} [0] key + * @property {Array} [1] [class list, translated title] + */ + +/** + * @typedef saveManageRecord_return + * @type {Object} + * @property {Array.} active Active saves + * @property {Array.} backups Backup saves + */ +/** + * @typedef saveManageRecord + * @type {Object} + * @description Record of a savegame + * @property {boolean} error true if error + * @property {Array.} farms farms in save + * @property {string} map map name in use + * @property {number} modCount number of mods + * @property {number} playTime play time + * @property {string} saveDate save date, YYYY-MM-DD + * @property {string} uuid HTML id safe ID + * @property {string} fileDate file mtime + * @property {string} fullName name of save folder + * @property {string} fullPath path and folder for save + */ + +/** + * @typedef saveTrackRecord + * @type {Object} + * @description tracking of a savegame + * @property {Array.} mods mods in active + * @property {string} saveID name of savegame + * @property {Array.} byDate backups by date + */ + +/** + * @typedef saveTrack_byDate + * @type {Object} + * @description Each backup of a save + * @property {string} date date of backup + * @property {boolean} duplicate mods the same as active save + * @property {Array.} mods mods in save + * @property {Array.} onlyBackup mods only in this backup + * @property {Array.} onlyOriginal mods not in this backup + */ + +/** + * @typedef saveManageRecord_farm + * @type {Object} + * @description Farm in the save + * @property {number} color index + * @property {number} id index + * @property {number} loan amount + * @property {number} money amount + * @property {string} name name of the farm + */ + +/** + * @typedef saveFileCheckerRecord + * @type {Object} + * @description save file check record + * @property {Array} errorList Array of errors + * @property {Object} farms Farms { id : farm Name } + * @property {?string} mapMod Map name, null on error + * @property {Object.} mods Mods found in save, key is shortName + * @property {boolean} singleFarm Single farm save + */ + +/** + * @typedef saveFileCheckerRecord_mods + * @type {Object} + * @description Mod record from save + * @property {string} version + * @property {string} title + * @property {Set} farms farms this is used on (by index) + */ + +/** + * @typedef modCollect_mods + * @type {Object.} + * @description List of mods + */ + +/** + * @typedef modCollect_collection + * @type {Object} + * @description Individual collection + * @property {modCollect_mods} mods all mod records + * @property {Set.} modSet list of uuids + * @property {number} folderSize size on disk + * @property {Set.} dependSet list of shortNames + * @property {Array.} alphaSort suitable for sorting shortName + uuid + * @property {Array.} requireArr array of all required mods + * @property {Array.>>} requireBy array of individual requires + */ + +/** + * @typedef modHub_list + * @type {Object} + * @description ModHub data + * @property {Object.} mods name and modhub id + * @property {Array.} last recent updates + */ + +/** + * @typedef modHub_version + * @type {Object.} + * @description Last ModHub version for mods by modhub ID + */ + +/** + * @typedef modHub_record + * @type {Object} + * @description ModHub record for a mod + * @property {number} id ModHub ID + * @property {string} version last seen version + * @property {boolean} recent Mod was recently updated + */ + +/** + * @typedef subWindow_def + * @type {Object} + * @description Set up for a sub window + * @property {string} winName Window name, no spaces + * @property {string} HTMLFile file to load + * @property {subWindow_args} subWindowArgs + * @property {function} callback called on load and reload + * @property {boolean} refocusCallback fire callback when re-focused or "re-opened" + * @property {function} extraCloseFunc callback to run after window close + * @property {boolean} handleURLinWin intercept external href, redirect to system default browser + */ + +/** + * @typedef subWindow_args + * @type {Object} + * @description Arguments for a sub window + * @property {boolean} sizeable can be resized, true + * @property {boolean} noChrome disable window chrome, false + * @property {boolean} useCustomTitle use custom title bar, true + * @property {boolean} skipTaskbar hide from taskbar, false + * @property {boolean} noSelect do not allow user selection, true + * @property {boolean} show show immediately, true + * @property {boolean} parent parent window, null + * @property {boolean} title title of window, null + * @property {boolean} fixed position is fixed, false + * @property {boolean} frame show frame, true + * @property {boolean} move can be moved, true + * @property {boolean} preload preload file, null + * @property {boolean} fixedOnTop always on top when fixed, true + */ \ No newline at end of file diff --git a/lib/modAssist_func_lib.js b/lib/modAssist_func_lib.js index e60c9752..8a42817e 100644 --- a/lib/modAssist_func_lib.js +++ b/lib/modAssist_func_lib.js @@ -233,6 +233,41 @@ const general = { csvLog.warning('Could not save csv file', err) }) }, + exportJSON : (collection) => { + const jsonLog = serveIPC.log.group('json-export') + + dialog.showSaveDialog(serveIPC.windowLib.win.main, { + defaultPath : path.join(app.getPath('desktop'), `${serveIPC.modCollect.mapCollectionToName(collection)}.json`), + filters : [{ name : 'JSON', extensions : ['json'] }], + }).then(async (result) => { + if ( result.canceled ) { + jsonLog.debug('Save JSON Cancelled') + } else { + try { + fs.writeFileSync(result.filePath, JSON.stringify({ + collection_color : parseInt(serveIPC.modCollect.getSafeNote(collection, 'color')), + collection_description : serveIPC.modCollect.getSafeNote(collection, 'tagline'), + download_direct : [], + download_unzip : [], + force_frozen : serveIPC.modCollect.getSafeNote(collection, 'frozen'), + game_version : serveIPC.modCollect.getSafeNote(collection, 'version'), + server_downloads : serveIPC.modCollect.getSafeNote(collection, 'websiteDL'), + server_id : serveIPC.modCollect.getSafeNote(collection, 'fsg_bot'), + server_name : serveIPC.modCollect.getSafeNote(collection, 'server'), + server_password : serveIPC.modCollect.getSafeNote(collection, 'password'), + server_website : serveIPC.modCollect.getSafeNote(collection, 'website'), + }, null, 4)) + app.addRecentDocument(result.filePath) + serveIPC.windowLib.doDialogBox('main', { messageL10n : 'save_json_worked' }) + } catch (err) { + jsonLog.warning('Could not save json file', err) + serveIPC.windowLib.doDialogBox('main', { type : 'warning', messageL10n : 'save_json_failed' }) + } + } + }).catch((err) => { + jsonLog.warning('Could not save json file', err) + }) + }, exportZIP : (selectedMods) => { const filePaths = [] const zipLog = serveIPC.log.group('zip-export') @@ -438,6 +473,186 @@ const general = { }) }, + importJSON_process : (filename) => { + const jsonLog = serveIPC.log.group('json-import') + try { + const import_json = JSON.parse(fs.readFileSync(filename)) + const [isValid, errors] = general.importJSON_check(import_json) + if ( !isValid ) { + serveIPC.windowLib.doDialogBox('main', { type : 'warning', messageL10n : 'import_json_read_failed' }) + const errorList = errors.map((x) => x.message).join(' -- ') + jsonLog.warning('Could not validate JSON file', errorList) + return + } + serveIPC.windowLib.createNamedWindow('importjson', { + thisImport : import_json, + }) + } catch (err) { + jsonLog.warning('Could not load JSON file', err) + serveIPC.windowLib.doDialogBox('main', { type : 'warning', messageL10n : 'import_json_load_failed' }) + } + }, + + importJSON_check : (parsed_json) => { + const Ajv = require('ajv') + const addFormats = require('ajv-formats') + const ajv = new Ajv({strict : false}) + addFormats(ajv) + const import_schema = { + properties : { + collection_color : { type : 'integer', maximum : 8, minimum : 0 }, + collection_description : { type : 'string', maxLength : 255 }, + download_direct : { + type : 'array', + items : { type : 'string', format : 'uri' }, + }, + download_unzip : { + type : 'array', + items : { type : 'string', format : 'uri' }, + }, + force_frozen : { type : 'boolean', default : false }, + game_version : { + type : 'integer', + enum : [22, 19, 17, 15, 13], + }, + server_downloads : { type : 'boolean', default : false }, + server_id : { type : 'string', maxLength : 255 }, + server_name : { type : 'string', maxLength : 255 }, + server_password : { type : 'string', maxLength : 100 }, + server_website : { anyOf : [ + { type : 'string', maxLength : 255, format : 'uri' }, + { type : 'string', maxLength : 0 }, + ]}, + }, + required : [ + 'collection_color', + 'collection_description', + 'force_frozen', + 'game_version', + 'server_downloads', + 'server_id', + 'server_name', + 'server_password', + 'server_website' + ], + type : 'object', + } + const isValid = ajv.validate(import_schema, parsed_json) + return [isValid, ajv.errors] + }, + + importJSON_download : (uri, unpack, collection) => { + if ( serveIPC.isDownloading ) { return } + const modDLLog = serveIPC.log.group('mod-download') + + serveIPC.isDownloading = true + modDLLog.info('Downloading Collection', collection) + modDLLog.debug('Download Link', uri) + + serveIPC.dlRequest = net.request(uri) + + serveIPC.dlRequest.on('response', (response) => { + modDLLog.info('Got download', response.statusCode) + + if ( response.statusCode < 200 || response.statusCode >= 400 ) { + serveIPC.windowLib.doDialogBox('importjson', { + type : 'error', + titleL10n : 'download_title', + message : `${serveIPC.__('download_failed')} :: ${serveIPC.modCollect.mapCollectionToName(collection)}`, + }) + serveIPC.isDownloading = false + } else { + serveIPC.loadJSON.open('download', true) + + serveIPC.loadJSON.total(response.headers['content-length'] || 0, true, true) + serveIPC.loadJSON.current(0, true, true) + + const dlFile = new URL(uri).pathname.split('/').filter(Boolean).pop() + + const dlPath = unpack ? + path.join(app.getPath('temp'), `${collection}.zip`) : + path.join(serveIPC.modCollect.mapCollectionToFolder(collection), dlFile) + + if ( !unpack && fs.existsSync(dlPath) ) { fs.rmSync(dlPath) } + + const writeStream = fs.createWriteStream(dlPath) + + response.pipe(writeStream) + response.on('data', (chunk) => { serveIPC.loadJSON.current(chunk.length, false, true) }) + + writeStream.on('finish', () => { + writeStream.close() + if ( !unpack ) { + modDLLog.info('Download complete, finished!') + modDLLog.info('Unzipping complete') + serveIPC.isDownloading = false + serveIPC.loadJSON.hide() + serveIPC.refFunc.processModFolders(true) + return + } + + modDLLog.info('Download complete, unzipping') + try { + let zipBytesSoFar = 0 + const zipBytesTotal = fs.statSync(dlPath).size + + serveIPC.loadJSON.open('zip') + serveIPC.loadJSON.total(100, true) + + const zipReadStream = fs.createReadStream(dlPath) + + zipReadStream.on('data', (chunk) => { + zipBytesSoFar += chunk.length + serveIPC.loadJSON.current(((zipBytesSoFar/zipBytesTotal)*100).toFixed(2), true) + }) + + zipReadStream.on('error', (err) => { + serveIPC.loadJSON.hide() + serveIPC.isDownloading = false + modDLLog.danger('Download unzip failed', err) + }) + + zipReadStream.on('end', () => { + modDLLog.info('Unzipping complete') + zipReadStream.close() + fs.unlinkSync(dlPath) + serveIPC.isDownloading = false + serveIPC.loadJSON.hide() + serveIPC.refFunc.processModFolders(true) + }) + const extract = unzip.Extract({ path : serveIPC.modCollect.mapCollectionToFolder(collection) }) + + zipReadStream.pipe(extract) + + extract.on('error', (err) => { + serveIPC.loadJSON.hide() + serveIPC.isDownloading = false + modDLLog.danger('Download unzip failed', err) + }) + } catch (err) { + modDLLog.danger('Download failed', response.statusCode, err) + serveIPC.loadJSON.hide() + } + }) + } + }) + serveIPC.dlRequest.on('abort', () => { + modDLLog.notice('Download canceled') + serveIPC.isDownloading = false + serveIPC.loadJSON.hide() + }) + serveIPC.dlRequest.on('error', (err) => { + serveIPC.windowLib.doDialogBox('importjson', { + type : 'error', + titleL10n : 'download_title', + message : `${serveIPC.__('download_failed')} :: ${err}`, + }) + modDLLog.warning('Network error', err) + serveIPC.isDownloading = false + serveIPC.loadJSON.hide() + }) + serveIPC.dlRequest.end() + }, } const discord = { @@ -708,12 +923,20 @@ const menu = { click : () => { shell.openPath(serveIPC.modCollect.mapCollectionToFolder(collection))}, icon : serveIPC.windowLib.contextIcons.openExplorer, label : serveIPC.__('open_folder'), + }, + menu.sep, + { + click : () => { general.exportJSON(collection) }, + icon : serveIPC.windowLib.contextIcons.save, + label : serveIPC.__('save_json'), } ] }, snip_copy : () => [menu.iconL10n('context_copy', null, 'copy', { role : 'copy' })], snip_cut_copy_paste : () => [ + menu.iconL10n('context_select_all', null, 'active', { role : 'selectAll' } ), + menu.sep, menu.iconL10n('context_cut', null, 'cut', { role : 'cut' }), ...menu.snip_copy(), menu.iconL10n('context_paste', null, 'paste', { role : 'paste' }), @@ -976,9 +1199,10 @@ const processor = { serveIPC.modCollect.processMods() }, - addFolderTracking : (folder) => { + addFolderTracking : (folder, json_import = false) => { const collectKey = serveIPC.modCollect.getFolderHash(folder) if ( serveIPC.modCollect.collections.has(collectKey) ) { + if ( json_import ) { return } serveIPC.windowLib.doDialogBox('main', { type : 'error', messageL10n : 'drop_folder_exists', @@ -1231,6 +1455,40 @@ const gameSet = { } ) }, + read_broken : (version, backName, origName, backExist) => { + gameSet.log.warning('gameSettings.xml is empty - trying to fix') + const fixGameXML = dialog.showMessageBoxSync(serveIPC.windowLib.win.main, { + cancelId : 1, + defaultId : 1, + message : backExist ? serveIPC.__('bad_game_set_xml_message_back') : serveIPC.__('bad_game_set_xml_message_no_back'), + title : `${serveIPC.__('bad_game_set_xml_title')} ${version}`, + type : 'question', + + buttons : [ + backExist ? serveIPC.__('bad_game_xml_restore') : serveIPC.__('bad_game_xml_delete'), + serveIPC.__('bad_game_xml_nothing'), + ], + }) + + switch (fixGameXML) { + case 0 : { + if ( backExist ) { // restore backup + try { + fs.copyFileSync(backName, origName) + general.toggleFolderDirty(true) + } catch { + gameSet.log.warning('gameSettings.xml is empty - backup restore failed!') + } + } else { + fs.rmSync(origName) + } + break + } + default: // do nothing + break + } + }, + read : ( newCollectKey = null ) => { gameSet.init() @@ -1241,49 +1499,118 @@ const gameSet = { const filename = prefs.verGet('game_settings', currentVersion) const XMLDoc = gameSet._getXMLDoc(filename) - + + const backupFileName = path.join(app.getPath('userData'), `gameSettings_${currentVersion}_xml.bak`) + const backupExists = fs.existsSync(backupFileName) + + if ( XMLDoc !== null && Object.keys(XMLDoc).length === 0 ) { + gameSet.read_broken(currentVersion, backupFileName, filename, backupExists) + return + } + try { serveIPC.gameSetOverride.active = XMLDoc.gameSettings.modsDirectoryOverride['@_active'] === 'true' serveIPC.gameSetOverride.folder = XMLDoc.gameSettings.modsDirectoryOverride['@_directory'] serveIPC.gameSetOverride.xml = gameSet._parse_xml(XMLDoc) serveIPC.gameSetOverride.index = ( !serveIPC.gameSetOverride.active ) ? '0' : serveIPC.modCollect.mapFolderToCollection(serveIPC.gameSetOverride.folder) || '999' + + try { + fs.copyFileSync(filename, backupFileName) + } catch { + gameSet.log.warning('gameSettings.xml - backup create/update failed!') + } } catch (err) { gameSet.log.danger('Could not parse game settings', err) } }, + gameXML_broken : (version, backName, origName, backExist) => { + gameSet.log.warning('game.xml is empty - trying to fix') + const fixGameXML = dialog.showMessageBoxSync(serveIPC.windowLib.win.main, { + cancelId : 1, + defaultId : 1, + message : backExist ? serveIPC.__('bad_game_xml_message_back') : serveIPC.__('bad_game_xml_message_no_back'), + title : `${serveIPC.__('bad_game_xml_title')} ${version}`, + type : 'question', + + buttons : [ + backExist ? serveIPC.__('bad_game_xml_restore') : serveIPC.__('bad_game_xml_delete'), + serveIPC.__('bad_game_xml_nothing'), + ], + }) + + switch (fixGameXML) { + case 0 : { + if ( backExist ) { // restore backup + try { + fs.copyFileSync(backName, origName) + general.toggleFolderDirty(true) + } catch { + gameSet.log.warning('game.xml is empty - backup restore failed!') + } + } else { + fs.rmSync(origName) + } + break + } + default: // do nothing + break + } + }, + gameXML : (version = 22, setDevMode = null) => { const gameEnabledValue = version === 22 ? true : (serveIPC.storeSet.get(`game_enabled_${version}`) && serveIPC.storeSet.get('multi_version', false)) const thisGameSettingsXML = prefs.verGet('game_settings', version) const gameXMLFile = thisGameSettingsXML.replace('gameSettings.xml', 'game.xml') + const backupFileName = path.join(app.getPath('userData'), `game_${version}_xml.bak`) + const backupExists = fs.existsSync(backupFileName) if ( !gameEnabledValue ) { return } const XMLDoc = gameSet._getXMLDoc(gameXMLFile) - if ( XMLDoc !== null ) { - serveIPC.devControls[version] = XMLDoc.game.development.controls + if ( XMLDoc !== null && Object.keys(XMLDoc).length === 0) { + gameSet.gameXML_broken(version, backupFileName, gameXMLFile, backupExists) + return } + + try { + if ( XMLDoc !== null && Object.keys(XMLDoc).length !== 0 ) { + serveIPC.devControls[version] = XMLDoc.game.development.controls + // Good version, we should create a backup (only if we're not altering it) + if ( setDevMode === null ) { + try { + fs.copyFileSync(gameXMLFile, backupFileName) + } catch { + gameSet.log.warning('game.xml - backup create/update failed!') + } + } + } + + if ( setDevMode !== null && XMLDoc !== null && Object.keys(XMLDoc).length !== 0 ) { + XMLDoc.game.development.controls = setDevMode - if ( setDevMode !== null && XMLDoc !== null ) { - XMLDoc.game.development.controls = setDevMode - - const builder = new fxml.XMLBuilder({ - commentPropName : '#comment', - format : true, - ignoreAttributes : false, - indentBy : ' ', - suppressBooleanAttributes : false, - suppressEmptyNode : true, - }) - - try { - fs.writeFileSync(gameXMLFile, builder.build(XMLDoc)) - } catch (err) { - gameSet.log.danger('Could not write game xml', err) + const builder = new fxml.XMLBuilder({ + commentPropName : '#comment', + format : true, + ignoreAttributes : false, + indentBy : ' ', + suppressBooleanAttributes : false, + suppressEmptyNode : true, + }) + + try { + // Good version saved, we should update/create a backup + fs.writeFileSync(gameXMLFile, builder.build(XMLDoc)) + fs.writeFileSync(backupFileName, builder.build(XMLDoc)) + } catch (err) { + gameSet.log.danger('Could not write game xml', err) + } + + gameSet.gameXML(version) } - - gameSet.gameXML(version) + } catch (err) { + gameSet.log.danger('Could not read game xml', err) } }, @@ -1592,6 +1919,7 @@ const saveManage = { if ( results.errorList.length === 0 ) { serveIPC.windowLib.sendToValidWindow('save_manage', 'fromMain_saveImport', result.filePaths[0]) } else { + serveIPC.windowLib.doDialogBox('save_manage', { type : 'warning', titleL10n : 'load_save_import_title', messageL10n : 'load_save_import_failed' }) saveManage.log.danger('Invalid Save File') } }) diff --git a/lib/modAssist_window_lib.js b/lib/modAssist_window_lib.js index 97737544..34b9a8e9 100644 --- a/lib/modAssist_window_lib.js +++ b/lib/modAssist_window_lib.js @@ -4,13 +4,17 @@ |__|_|__||_____|_____|___|___||_____|_____||__||_____||____| (c) 2022-present FSG Modding. MIT License. */ -// Window Control Library and Config +/** + * Window Control Library and Config + * @module modAssist_window_lib + */ const { screen, app, nativeTheme, dialog, shell, BrowserWindow, nativeImage, Menu } = require('electron') const path = require('node:path') const { funcLib } = require('./modAssist_func_lib.js') const { serveIPC } = require('./modUtilLib.js') + module.exports.windowLib = class { #dev = new Set([ 'basegame', @@ -20,6 +24,8 @@ module.exports.windowLib = class { 'detail', 'find', // 'gamelog', + // 'import', + 'importjson', // 'load', 'main', // 'mini', @@ -34,6 +40,7 @@ module.exports.windowLib = class { ]) /* eslint-disable sort-keys */ + /** @type {Object.} */ winDefs = { basegame : { winName : 'basegame', @@ -155,6 +162,13 @@ module.exports.windowLib = class { callback : () => { return }, refocusCallback : true, }, + importjson : { + winName : 'importjson', + HTMLFile : 'importjson.html', + subWindowArgs : { preload : 'importjsonWindow' }, + callback : (windowArgs) => { this.sendModList(windowArgs, 'fromMain_importData', 'importjson', false ) }, + handleURLinWin : true, + }, /* eslint-enable sort-keys */ } @@ -174,6 +188,7 @@ module.exports.windowLib = class { find : null, gamelog : null, import : null, + importjson : null, load : null, main : null, notes : null, @@ -207,9 +222,13 @@ module.exports.windowLib = class { render : path.join(app.getAppPath(), 'renderer'), } + /** + * create new windowing library + */ constructor() { this.loading = new loadingWindow(this) serveIPC.loadWindow = this.loading + serveIPC.loadJSON = new loadingWindow(this, 'importjson') this.populateContextIcons() @@ -224,16 +243,19 @@ module.exports.windowLib = class { }) } - #getNativeImage(icon, noDark) { + #getNativeImage(icon, noDark = false) { const basePath = !app.isPackaged ? path.join(app.getAppPath(), 'renderer', 'img', 'context-icons') : path.join(process.resourcesPath, 'app.asar', 'renderer', 'img', 'context-icons') - const isDark = !noDark || nativeTheme.shouldUseDarkColors ? '' : 'blk-' + const isDark = noDark || nativeTheme.shouldUseDarkColors ? '' : 'blk-' return new nativeImage.createFromPath(path.join(basePath, `${isDark}${icon}.png`)) } + /** + * populate nativeImage context icons for later use + */ populateContextIcons() { this.contextIcons.active = this.#getNativeImage('ui-icon-active') this.contextIcons.collection = this.#getNativeImage('ui-icon-collection') @@ -262,6 +284,10 @@ module.exports.windowLib = class { this.contextIcons.launch = this.#getNativeImage(`ui-icon-launch-${serveIPC.storeSet?.get?.('game_version', 22) || 22}`, true) } + /** + * Run the tray icon context menu + * @returns null + */ trayContextMenu() { if ( !this.tray || this.tray.isDestroyed() ) { return } this.tray.setContextMenu(Menu.buildFromTemplate([ @@ -273,30 +299,57 @@ module.exports.windowLib = class { ])) } + /** + * Check if a window name exist, the window exists, and it's not destroyed + * @param {string} name window + * @returns {boolean} + */ isValid(name) { return ( Object.hasOwn(this.win, name) && this.win[name] !== null && !this.win[name].isDestroyed() ) } + /** + * Check if a window is always on top + * @param {string} name window + * @returns {boolean} + */ isAlwaysOnTop(name) { return this.isValid(name) && this.win[name].isAlwaysOnTop() } + /** + * Toggle window always on top status (or force true) + * @param {string} name window + * @param {boolean} force force on top + */ toggleAlwaysOnTop(name, force = false) { if ( this.isValid(name) ) { this.win[name].setAlwaysOnTop(force ? true : !this.win[name].isAlwaysOnTop(), 'pop-up-menu') } } + /** + * Check if window is visible + * @param {string} name window + * @returns {boolean} + */ isVisible(name) { return this.win[name] !== null && this.win[name]?.isVisible() } + /** + * Safely close a window + * @param {string} name window + */ safeClose(name) { if ( this.isValid(name) && this.isVisible(name) ) { this.win[name].close() } } + /** + * Safely close all sub windows + */ closeAllSubWin() { for ( const thisWin in this.win ) { if ( thisWin !== 'main' && this.isValid(thisWin) ) { @@ -305,12 +358,21 @@ module.exports.windowLib = class { } } + /** + * Force a window to be focused + * @param {string} name window + */ forceFocus (name) { if ( this.isValid(name) ) { this.win[name].focus() } } + /** + * Raise or create the named window, run the callback on completion + * @param {string} name window + * @param {function} callback + */ raiseOrOpen(name, callback) { let timeoutAmount = 0 if ( !this.isValid(name) ) { @@ -322,6 +384,10 @@ module.exports.windowLib = class { setTimeout(() => { callback() }, timeoutAmount) } + /** + * Destroy named window, focus mini or main window after + * @param {string} name window + */ destroyAndFocus (name) { this.win[name] = null if ( this.isValid('main') ) { @@ -333,14 +399,32 @@ module.exports.windowLib = class { } } + /** + * Send a command to a window (unsafe, no checks) + * @param {string} name window + * @param {string} command + * @param {...any} args + */ sendToWindow(name, command, ...args) { this.win[name].webContents.send(command, ...args) } + /** + * Send a command to a window if it is valid + * @param {string} name window + * @param {string} command + * @param {...any} args + */ sendToValidWindow(name, command, ...args) { if ( this.isValid(name) ) { this.sendToWindow(name, command, ...args) } } + /** + * Send a command to a window if it is valid, and focus it + * @param {string} name window + * @param {string} command + * @param {...any} args + */ sendAndFocusValid(name, command, ...args) { if ( this.isValid(name) ) { this.win[name].focus() @@ -348,6 +432,13 @@ module.exports.windowLib = class { } } + /** + * Send the mod list to a window + * @param {Object} extraArgs extra data + * @param {string} eventName command + * @param {string} toWindow window + * @param {boolean} closeLoader close loader if open + */ sendModList(extraArgs = {}, eventName = 'fromMain_modList', toWindow = 'main', closeLoader = true) { serveIPC.modCollect.toRenderer(extraArgs).then((modCollection) => { this.sendToValidWindow(toWindow, eventName, modCollection) @@ -362,6 +453,9 @@ module.exports.windowLib = class { }) } + /** + * load window settings, theme, tray menu + */ loadSettings() { this.#settings = serveIPC.storeSet this.changeTheme() @@ -369,9 +463,14 @@ module.exports.windowLib = class { this.trayContextMenu() } - set themeCurrentColor(name) { this.#themeCurrentColor = name } + /** @type {string} */ + set themeCurrentColor(name) { this.changeTheme(name) } get themeCurrentColor() { return this.#themeCurrentColor } + /** + * Change the current theme + * @param {string} newTheme + */ changeTheme(newTheme = null ) { if (newTheme !== null) { this.#settings.set('color_theme', newTheme) @@ -387,9 +486,18 @@ module.exports.windowLib = class { (nativeTheme.shouldUseDarkColors ? 'dark' : 'light') : currentSetting - if (newTheme !== null) { this.themeUpdater() } + if (newTheme !== null) { + this.populateContextIcons() + this.trayContextMenu() + this.themeUpdater() + } } + /** + * Safely center a window + * @param {string} name window + * @returns {Object} x,y + */ getRealCenter (name) { const realCenter = { x : null, y : null } const winSettings = this.#settings.get(`wins.${name}`) @@ -410,6 +518,9 @@ module.exports.windowLib = class { return realCenter } + /** + * reset all window positions + */ resetPositions() { this.#settings.reset('wins') const mainBounds = this.#settings.get('wins.main') @@ -422,6 +533,11 @@ module.exports.windowLib = class { this.win.main.center() } + /** + * Refresh a single window or all windows with new a new language code + * @param {string} newLocale lang code + * @param {?string} name window + */ refreshL10n(newLocale, name = null) { if ( name !== null ) { this.sendToValidWindow(name, 'fromMain_l10n_refresh', newLocale) @@ -432,6 +548,9 @@ module.exports.windowLib = class { } } + /** + * change font size + */ fontUpdater() { for ( const thisWinKey in this.win ) { if ( this.isVisible(thisWinKey) && thisWinKey !== 'mini' ) { @@ -450,6 +569,9 @@ module.exports.windowLib = class { } } + /** + * update window theme + */ themeUpdater() { for ( const thisWinKey in this.win ) { if ( this.isVisible(thisWinKey) ) { @@ -462,13 +584,32 @@ module.exports.windowLib = class { } } + /** + * toggle debug red flag + */ toggleMainDangerFlag() { this.sendToValidWindow('main', 'fromMain_debugLogDangerFlag', true) } + + /** + * send dirty folders flag + * @param {boolean} foldersDirty + */ + toggleMainDirtyFlag(foldersDirty) { this.sendToValidWindow('main', 'fromMain_dirtyUpdate', foldersDirty) } + /** + * Show a dialog box (sync) + * @param {string} attachTo window to attach + * @param {Object} options options + * @param {string} options.type type of dialog + * @param {?string} options.message message of dialog + * @param {?string} options.messageL10n l10n key of dialog message + * @param {?string} options.title title of dialog + * @param {?string} options.titleL10n l10n key of title + */ doDialogBox(attachTo, {type = 'info', message = null, messageL10n = null, title = null, titleL10n = null }) { const attachWin = ( attachTo === null ) ? null : this.win[attachTo] const thisTitle = ( title !== null ) ? title : this.l10n.syncStringLookup(( titleL10n === null ) ? 'app_name' : titleL10n) @@ -481,6 +622,12 @@ module.exports.windowLib = class { }) } + /** + * Create a named window + * @param {string} winName window + * @param {Object} windowArgs arguments for window callback + * @param {?function} extraCallback additional callback + */ createNamedWindow(winName, windowArgs, extraCallback = null) { const subWinDef = this.winDefs[winName] const thisWinName = subWinDef.winName @@ -553,6 +700,11 @@ module.exports.windowLib = class { } } + /** + * Correct window placement x,y to be on a existing monitor + * @param {Object} winSettings + * @returns {Object} + */ checkValidPlacement(winSettings) { const newWinSettings = winSettings @@ -573,6 +725,12 @@ module.exports.windowLib = class { return newWinSettings } + /** + * Create a sub window + * @param {string} winName window + * @param {subWindow_args} options window arguments + * @returns {Electron.BrowserWindow} + */ createSubWindow(winName, { sizeable = true, noChrome = false, useCustomTitle = true, skipTaskbar = false, noSelect = true, show = true, parent = null, title = null, fixed = false, frame = true, move = true, preload = null, fixedOnTop = true} = {}) { const realCenter = this.getRealCenter(winName) const winSettings = this.checkValidPlacement(this.#settings.get(`wins.${winName}`)) @@ -667,6 +825,10 @@ module.exports.windowLib = class { return thisWindow } + /** + * Create main window + * @param {function} openCallback + */ createMainWindow (openCallback) { this.win.main = this.createSubWindow('main', { noSelect : false, show : this.#debug, preload : 'mainWindow' }) @@ -766,6 +928,9 @@ module.exports.windowLib = class { }) } + /** + * send main window to tray icon + */ sendToTray () { if ( this.tray ) { if ( serveIPC.isFirstMinimize ) { @@ -786,61 +951,101 @@ module.exports.windowLib = class { } } +/** + * loadingWindow class + * @class + */ class loadingWindow { #win = null + #parent = null + #total = 0 #current = 0 - constructor(win) { + /** + * Create loading window interface + * @param {module:modAssist_window_lib.windowLib} win + */ + constructor(win, parent = 'main') { + this.#parent = parent this.#win = win } + /** + * Increase (or reset) counter current value + * @param {number} amount amount to increase + * @param {boolean} reset use amount as new value + * @param {boolean} inMB use megabytes + */ current (amount = 1, reset = false, inMB = false) { this.#doCount('current', amount, reset, inMB) } + /** + * Increase (or reset) counter total + * @param {number} amount amount to increase + * @param {boolean} reset use amount as new value + * @param {boolean} inMB use megabytes + */ total (amount, reset = false, inMB = false) { this.#doCount('total', amount, reset, inMB) } + /** + * Run callback when ready + * @param {function} callback + */ doReady (callback) { if ( this.isReady ) { callback(); return } setTimeout(() => { this.doReady(callback) }, 250) } - get isReady () { return this.#win.isVisible('main') } + /** @type {boolean} */ + get isReady () { return this.#win.isVisible(this.#parent) } #doCount (whichCount, amount, reset, inMB) { const thisCounter = `#${whichCount}` this[thisCounter] = ( reset ) ? amount : amount + this[thisCounter] - if ( whichCount === 'current' && this.#win.isVisible('main') ) { + if ( whichCount === 'current' && this.#win.isVisible(this.#parent) ) { this.#win.win.main.setProgressBar(Math.max(0, Math.min(1, this.#current / this.#total))) } - this.#win.sendToValidWindow('main', `fromMain_loading_${whichCount}`, this[thisCounter], inMB) + this.#win.sendToValidWindow(this.#parent, `fromMain_loading_${whichCount}`, this[thisCounter], inMB) } + /** + * Hide the loading window + * @param {number} time + */ hide (time = 1250) { setTimeout(() => { - if ( this.#win.isValid('main') ) { this.#win.win.main.setProgressBar(-1)} - this.#win.sendToValidWindow('main', 'formMain_loading_hide') + if ( this.#win.isValid(this.#parent) ) { this.#win.win.main.setProgressBar(-1)} + this.#win.sendToValidWindow(this.#parent, 'formMain_loading_hide') }, time) } + /** + * Do not display the count in the loading window + */ noCount () { - this.#win.sendToValidWindow('main', 'fromMain_loadingNoCount') + this.#win.sendToValidWindow(this.#parent, 'fromMain_loadingNoCount') } + /** + * Open the loading window + * @param {string} l10nKey localization key for text + * @param {boolean} isDownload show download cancel button + */ open ( l10nKey, isDownload = false ) { const winTitle = this.#win.l10n.syncStringLookup((l10nKey) !== 'launch' ? `loading_${l10nKey}_title` : 'app_name') const winSubTitle = this.#win.l10n.syncStringLookup((l10nKey) !== 'launch' ? `loading_${l10nKey}_subtitle` : 'launch_game') const dlCancel = this.#win.l10n.syncStringLookup('cancel_download') - this.#win.sendToValidWindow('main', 'formMain_loadingTitles', winTitle, winSubTitle, dlCancel) + this.#win.sendToValidWindow(this.#parent, 'formMain_loadingTitles', winTitle, winSubTitle, dlCancel) if ( isDownload ) { - this.#win.sendToValidWindow('main', 'fromMain_loadingDownload') + this.#win.sendToValidWindow(this.#parent, 'fromMain_loadingDownload') } } } @@ -880,8 +1085,8 @@ module.exports.defaultSettings = class { use_one_drive : { type : 'boolean', default : false }, game_args : { type : 'string', default : '' }, - game_path : { type : 'string', default : serveIPC.path.game }, - game_settings : { type : 'string', default : serveIPC.path.setFile }, + game_path : { type : 'string', default : serveIPC.path.game || '' }, + game_settings : { type : 'string', default : serveIPC.path.setFile || '' }, game_args_19 : { type : 'string', default : '' }, game_enabled_19 : { type : 'boolean', default : false}, @@ -914,6 +1119,7 @@ module.exports.defaultSettings = class { folder : this.#winDef(800, 500), gamelog : this.#winDef(1000, 500), import : this.#winDef(750, 500), + importjson : this.#winDef(900, 700), main : this.#winDef(1000, 700), mini : this.#winDef(400, 80), notes : this.#winDef(800, 500), diff --git a/lib/modCheckLib.js b/lib/modCheckLib.js index 2cd59e1e..f9ed6ddf 100644 --- a/lib/modCheckLib.js +++ b/lib/modCheckLib.js @@ -4,20 +4,24 @@ |__|_|__||_____|_____|___|___||_____|_____||__||_____||____| (c) 2022-present FSG Modding. MIT License. */ -// Mod Collection Class, File Handler Class, Save File Reader, Save Tracker - -const fs = require('node:fs') -const fsPromise = require('node:fs/promises') -const path = require('node:path') -const cp = require('node:child_process') -const crypto = require('node:crypto') -const admZip = require('adm-zip') -const {XMLParser} = require('fast-xml-parser') -const alwaysArray = require('./modCheckLib_static.js').alwaysArrays -const { globSync } = require('glob') -const { serveIPC } = require('./modUtilLib') - - +/** + * Mod Collection Class, File Handler Class, Save File Reader, Save Tracker + * @module modCheckLib + */ + +const fs = require('node:fs') +const fsPromise = require('node:fs/promises') +const path = require('node:path') +const cp = require('node:child_process') +const crypto = require('node:crypto') +const {fileHandlerAsync} = require('./workerThreadLib.js') +const { globSync } = require('glob') +const { serveIPC } = require('./modUtilLib') + +/** + * Main Collection Class for modRecords + * @class + */ class modFileCollection { #ignoreList = [ '^npm-debug\\.log$', @@ -82,6 +86,35 @@ class modFileCollection { #botMinInterval = 1000 * 60 * 5 #botDisabled = false + #noteDefaults = { + notes_add_date : new Date(1900, 1, 1), + notes_admin : null, + notes_color : '0', + notes_favorite : false, + notes_frozen : false, + notes_fsg_bot : '', + notes_game_admin : null, + notes_holding : false, + notes_last : new Date(1900, 1, 1), + notes_notes : null, + notes_password : null, + notes_removable : false, + notes_server : null, + notes_tagline : null, + notes_unit_acre : null, + notes_unit_mile : null, + notes_unit_money : null, + notes_unit_temp : null, + notes_username : null, + notes_version : 22, + notes_website : null, + notes_websiteDL : false, + } + /** + * + * @param {string} homeDir User's home folder + * @param {EventEmitter} emitter emitter to use + */ constructor(homeDir, emitter) { this.#userHome = homeDir this.#skipCache = serveIPC.isModCacheDisabled @@ -89,6 +122,10 @@ class modFileCollection { this.#botDisabled = serveIPC.isBotDisabled } + /** + * Start the bot if it's not running + * @param {boolean} forceRunNow Force run it once now + */ startBotInterval(forceRunNow = false) { if ( this.#botDisabled ) { return } if ( forceRunNow ) { this.#botRunner() } @@ -153,28 +190,71 @@ class modFileCollection { } } + /** + * Map a real folder to a collection key + * @param {string} folder Folder Path + * @returns {?modRecord_collectKey} + */ mapFolderToCollection(folder) { return this.#map_FolderToCollection[folder] || null } + + /** + * Map collection key to real folder + * @param {modRecord_collectKey} collectKey + * @returns {string} Path to folder + */ mapCollectionToFolder(collectKey) { return this.#map_CollectionToFolder[collectKey] } + + /** + * Map collection key to (short) name + * @param {modRecord_collectKey} collectKey + * @returns {string} Collection name + */ mapCollectionToName(collectKey) { return this.#map_CollectionToName[collectKey] } + + /** + * Map collection to full name + * @param {modRecord_collectKey} collectKey + * @returns {string} Full collection name + */ mapCollectionToFullName(collectKey) { return this.#map_CollectionToFullName[collectKey] } + + /** + * Map comboKey to folder + * @param {modRecord_col_uuid} comboKey + * @returns {string} Collection folder + */ mapDashedToFolder(comboKey) { return this.#map_CollectionToFolder[comboKey.split('--')[0]] } + + /** + * Map comboKey to mod path + * @param {modRecord_col_uuid} comboKey + * @returns {string} Full path to mod + */ mapDashedToFullPath(comboKey) { const entry = this.modColUUIDToFolderAndRecord(comboKey) return path.join(entry.folder, path.basename(entry.mod.fileDetail.fullPath)) } + /** + * @type {Array} + * @description Set a new order for collection display + */ set newCollectionOrder(newSetOrder) { this.#set_Collections = newSetOrder } + /** + * Remove a collection from list + * @param {modRecord_collectKey} collectKey + */ removeCollection(collectKey) { const thisRealPath = this.#map_CollectionToFolder[collectKey] @@ -190,16 +270,34 @@ class modFileCollection { delete this.#map_CollectionToStatus[collectKey] } + /** + * @type {Object} + * @description bind conflicts + */ get bindConflict() { return this.#bindConflict } + + /** @type {Set.} */ get collections() { return this.#set_Collections } + + /** @type {Object.} */ get shortNames() { return this.#map_ShortName } + /** @type {number} */ get totalModCount() { return Object.keys(this.#map_ModUUIDToShortName).length } + /** + * Is this a collection of favorites? + * @param {modRecord_collectKey} collectKey + * @returns {boolean} + */ isFavorite(collectKey) { return serveIPC.storeNote.get(`${collectKey}.notes_favorite`, false) } + /** + * + * @returns {Array} [[all files], [collectKeys]] + */ getFavoriteCollectionFiles() { const files = [] @@ -213,52 +311,109 @@ class modFileCollection { } return [files, collections] } + + /** + * Get mod list from a collectKey + * @param {string} collectKey + * @returns {modCollect_mods} { uuid : modRecord } + */ getModListFromCollection(collectKey) { return Object.values(this.#list_allMods[collectKey].mods) } + + /** + * Get mod list from a comboKey + * @param {string} comboKey + * @returns {modCollect_mods} { uuid : modRecord } + */ getModCollectionFromDashed(comboKey) { return this.#list_allMods[comboKey.split('--')[0]] } + + /** + * Get a full collection by collectKey + * @param {string} collectKey + * @returns {modCollect_collection} + */ getModCollection(collectKey) { return this.#list_allMods[collectKey] } + /** @type {modHub_list} */ get modHubList() { return this.#modHubList } set modHubList(newList) { this.#modHubList = newList } + + /** @type {modHub_version} */ get modHubVersion() { return this.#modHubVersion } set modHubVersion(newList) { this.#modHubVersion = newList } - modHubFullRecord(thisMod, asArray = true) { - /* Return [ID, Version, Recent] */ + /** + * Get full modhub record by shortname + * @param {modRecord_shortName} thisMod + * @returns {modHub_record} + */ + modHubFullRecord(thisMod) { const modHubID = this.modHubModRecord(thisMod) - return ( asArray ) ? - [ - modHubID, - this.modHubVersionModHubId(modHubID), - this.#modHubList.last.includes(parseInt(modHubID)) - ] : { - id : modHubID, - version : this.modHubVersionModHubId(modHubID), - recent : this.#modHubList.last.includes(parseInt(modHubID)), - } + return { + id : modHubID, + version : this.modHubVersionModHubId(modHubID), + recent : this.#modHubList.last.includes(parseInt(modHubID)), + } } + + /** + * Get modHub ID by shortname + * @param {modRecord_shortName} thisMod + * @returns {?number} + */ modHubModRecord(thisMod) { return this.#modHubList.mods[thisMod.fileDetail.shortName] || null } + + /** + * Get modHub ID by UUID + * @param {modRecord_uuid} modUUID + * @returns {?number} + */ modHubModUUID(modUUID) { return this.#modHubList.mods[this.#map_ModUUIDToShortName[modUUID]] || null } + + /** + * Get modHub ID by UUID + * @param {modRecord_uuid} modUUID + * @returns {?string} + */ modHubVersionUUID(modUUID) { return this.#modHubVersion[this.modHubModUUID(modUUID)] || null } + + /** + * Get modHub ID by shortname + * @param {modRecord_shortName} thisMod + * @returns {?number} + */ modHubVersionModRecord(thisMod) { return this.#modHubVersion[this.modHubModRecord(thisMod)] || null } + + /** + * Get modHub version by modHub ID + * @param {number} modHubID + * @returns {?string} + */ modHubVersionModHubId(modHubID) { return this.#modHubVersion[modHubID] || null } + /** + * + * @param {modRecord_col_uuid} ID + * @returns {Object} record + * @returns {string} record.folder full path + * @returns {?modRecord} record.mod + */ modColUUIDToFolderAndRecord(ID) { const idParts = ID.split('--') return { @@ -267,23 +422,47 @@ class modFileCollection { } } + /** + * Get a modRecord from a comboKey + * @param {modRecord_col_uuid} ID + * @returns {?modRecord} + */ modColUUIDToRecord(ID) { const idParts = ID.split('--') return this.#list_allMods?.[idParts[0]]?.mods?.[idParts[1]] || null } + /** + * Get folder path from comboKey + * @param {modRecord_col_uuid} ID + * @returns {string} Full path + */ modColUUIDToFolder(ID) { return this.#map_CollectionToFolder[ID.split('--')[0]] } + /** + * Get multiple mod records from comboKeys + * @param {Array.} IDs + * @returns {Array.} + */ modColUUIDsToRecords(IDs) { return IDs.map((thisColUUID) => this.modColUUIDToRecord(thisColUUID)) } + /** + * Get single mod from collectKey and UUID + * @param {modRecord_collectKey} collectKey + * @param {modRecord_uuid} modKey + * @returns {modRecord} + */ modColAndUUID(collectKey, modKey) { return this.#list_allMods[collectKey].mods[modKey] } + /** + * Clear all collection storage + */ clearAll() { this.#botRequestList = new Set() this.#scanPromise = [] @@ -301,10 +480,17 @@ class modFileCollection { this.#set_Collections.clear() } + /** @type {boolean} */ set syncSafe(mode) { this.#useSyncSafeMode = typeof mode === 'boolean' ? mode : false } + /** + * Return new collection key from folder + * @param {string} folder + * @returns {modRecord_collectKey} col_[HASH] + */ getMD5FromFolder(folder) { return this.#getMD5Hash(folder, 'col_') } + /** @type {number} */ get modFullCount() { let fullCount = 0 for ( const thisList in this.#list_allMods ) { @@ -312,6 +498,20 @@ class modFileCollection { } return fullCount } + + /** @type {number} */ + get fullCollectSize() { + return JSON.stringify(this.#list_allMods).length + } + + /** + * Add a collection + * @param {string} folder + * @param {boolean} offline collection is currently offline + * @returns {Object} newCollection + * @returns {modRecord_collectKey} newCollection.collectKey + * @returns {number} newCollection.fileCount number of files + */ addCollection(folder, offline = false) { const goodFolderContents = [] const thisFolderKey = this.#getMD5Hash(folder, 'col_') @@ -438,6 +638,9 @@ class modFileCollection { return [...this.#threadPoolIdx][this.#threadCurrent % this.#threadPoolIdx.size] } + /** + * Process mods on disk (multi-threaded) + */ async processMods() { this.#startThreads() @@ -449,6 +652,9 @@ class modFileCollection { this.#exitThreads() } + /** + * Post-process mods, run after processMods, but before returning to caller + */ processModsPost() { this.#modCache.saveFile() this.#doAlphaSort() @@ -457,6 +663,7 @@ class modFileCollection { this.#doBadges() } + /** @type {Promise} */ get processPromise() { return Promise.all(this.#scanPromise) } #doRequired() { @@ -482,7 +689,7 @@ class modFileCollection { } const newBadge = new badgeHandle(modRecord.badgeArray, this.#badgeNamed) - const thisMHRec = this.modHubFullRecord(modRecord, false) + const thisMHRec = this.modHubFullRecord(modRecord) if ( this.#list_newMods.has(modRecord.uuid) ) { newBadge.add('new') @@ -550,6 +757,12 @@ class modFileCollection { return newBadge.keyList } + /** + * Get conflicts in a given collection + * @param {modRecord_collectKey} collectKey + * @param {modRecord} modRecord + * @returns {Array.} + */ getConflictInCollection(collectKey, modRecord) { const conflictList = this.#bindConflict[collectKey][modRecord.fileDetail.shortName] @@ -571,6 +784,12 @@ class modFileCollection { return [...confMods] } + /** + * Get if depended on mods are in a collection + * @param {modRecord_collectKey} collectKey + * @param {modRecord} modRecord + * @returns {(boolean|number)} number not met + */ getDependInCollection(collectKey, modRecord) { if ( modRecord.modDesc.depend.length === 0 ) { return false } const metSet = this.getModCollection(collectKey).dependSet @@ -706,7 +925,7 @@ class modFileCollection { thisModRecord.currentCollection = collectKey thisModRecord.colUUID = `${collectKey}--${thisModRecord.uuid}` - thisModRecord.modHub = this.modHubFullRecord(thisModRecord, false) + thisModRecord.modHub = this.modHubFullRecord(thisModRecord) thisModRecord.gameVersion = this.#util_gameVersion(thisModRecord.modDesc.descVersion) if ( thisModRecord.issues.includes('MALICIOUS_CODE') ) { @@ -785,7 +1004,6 @@ class modFileCollection { } } - async #addMod(collectKey, thisFile) { const thisFileStats = this.#fileStats(collectKey, thisFile) @@ -823,47 +1041,61 @@ class modFileCollection { }) } + /** + * Get version of a collection + * @param {modRecord_collectKey} collectKey + * @returns {number} + */ version(collectKey) { return this.#map_CollectionNotes.get(`${collectKey}.notes_version`, 22) } + + /** + * Check if collection version is the same + * @param {modRecord_collectKey} collectKey + * @param {number} verCheck + * @returns {boolean} + */ versionSame(collectKey, verCheck) { return verCheck === this.version(collectKey) } + + /** + * Check if collection version is the different + * @param {modRecord_collectKey} collectKey + * @param {number} verCheck + * @returns {boolean} + */ versionNotSame(collectKey, verCheck) { return verCheck !== this.version(collectKey) } + + /** + * Check if collection version is the currently selected version + * @param {modRecord_collectKey} collectKey + * @returns {boolean} + */ versionCurrent(collectKey) { const current_version = serveIPC.storeSet.get('game_version') return this.versionSame(collectKey, current_version) } + /** + * Get note with safe default + * @param {modRecord_collectKey} collectKey + * @param {string} noteKey + * @returns {any} + */ + getSafeNote(collectKey, noteKey) { + const fullNoteKey = `notes_${noteKey}` + const realDefault = this.#noteDefaults[fullNoteKey] + return this.#map_CollectionNotes.get(`${collectKey}.${fullNoteKey}`, realDefault) + } + #doNotesDefault() { const currentOptions = this.#map_CollectionNotes.store - const defaults = { - notes_add_date : new Date(1900, 1, 1), - notes_admin : null, - notes_color : '0', - notes_favorite : false, - notes_frozen : false, - notes_fsg_bot : '', - notes_game_admin : null, - notes_holding : false, - notes_last : new Date(1900, 1, 1), - notes_notes : null, - notes_password : null, - notes_removable : false, - notes_server : null, - notes_tagline : null, - notes_unit_acre : null, - notes_unit_mile : null, - notes_unit_money : null, - notes_unit_temp : null, - notes_username : null, - notes_version : 22, - notes_website : null, - notes_websiteDL : false, - } - const safeOptions = {} + const defaults = this.#noteDefaults + const safeOptions = {} for ( const collectKey of this.#set_Collections ) { safeOptions[collectKey] = {} @@ -875,9 +1107,11 @@ class modFileCollection { return safeOptions } + /** @type {boolean} */ set updateIsReady(newValue = true) { this.#updateReady = newValue } get updateIsReady() { return this.#updateReady } + /** @type {Object} */ get botDetails() { return { l10nMap : { @@ -891,11 +1125,18 @@ class modFileCollection { } } + /** @type {Array.} */ get dangerMods() { return this.#dangerMods } + + /** @type {boolean} */ get isDangerMods() { return this.#dangerMods.size !== 0 } + /** + * Send mod list to renderer + * @param {Object} extra more data to include in opts + * @returns {Object} see code for full list of keys + */ async toRenderer(extra = null) { - // DATA STRUCT : send mod list toRenderer() return Promise.all(this.#scanPromise).then(() => { return { appSettings : this.#settings.store, @@ -943,6 +1184,9 @@ class modFileCollection { } } +/** + * notModFileChecker - null(ish) class that mimics modRecord + */ class notModFileChecker { modDesc = { actions : {}, @@ -1023,6 +1267,7 @@ class notModFileChecker { } } + class csvFileChecker { #fileName = null #fileContents = null @@ -1087,6 +1332,10 @@ class csvFileChecker { } +/** + * Save file checker + * @class + */ class saveFileChecker { #fileName = null #isFolder = false @@ -1102,6 +1351,11 @@ class saveFileChecker { #placeables = {} #vehicles = {} + /** + * + * @param {string} fileName full file and path of save + * @param {boolean} isFolder Expect a folder + */ constructor( fileName, isFolder ) { this.#fileName = fileName this.#isFolder = isFolder @@ -1110,6 +1364,10 @@ class saveFileChecker { this.#log.info(`Adding Save: ${fileName}`) } + /** + * @type {saveFileCheckerRecord} + * @description get generated data + */ get dataReturn() { return { errorList : this.errorList, @@ -1120,19 +1378,25 @@ class saveFileChecker { } } + /** + * Process data + * @returns {saveFileCheckerRecord} + */ async getInfo() { - this.#fileHandle = new fileHandler(this.#fileName, this.#isFolder, this.#log) + this.#fileHandle = new fileHandlerAsync(this.#fileName, this.#isFolder, this.#log) + + const couldOpen = await this.#fileHandle.open() - if ( ! this.#fileHandle.isOpen ) { + if ( ! couldOpen ) { this.#log.danger(`Save open fail: ${this.#fileName}`) this.errorList.push(['SAVEGAME_UNREADABLE', this.#fileName]) return this.dataReturn } - this.#doStep_farms() - this.#doStep_placeables() - this.#doStep_vehicles() - this.#doStep_career() + await this.#doStep_farms() + await this.#doStep_placeables() + await this.#doStep_vehicles() + await this.#doStep_career() this.#fileHandle.close() this.#fileHandle = null @@ -1158,8 +1422,8 @@ class saveFileChecker { return returnObj } - #doStep_farms() { - const thisXML = this.#fileHandle.readXML('farms.xml', 'savegame', 'farms') + async #doStep_farms() { + const thisXML = await this.#fileHandle.readXML('farms.xml', 'savegame', 'farms') if ( thisXML === null || !Array.isArray(thisXML.farm) ) { this.#util_readError('farms') @@ -1174,8 +1438,8 @@ class saveFileChecker { this.farms[0] = '--unowned--' } - #doStep_placeables() { - const thisXML = this.#fileHandle.readXML('placeables.xml', 'savegame', 'placeables') + async #doStep_placeables() { + const thisXML = await this.#fileHandle.readXML('placeables.xml', 'savegame', 'placeables') if ( thisXML === null || typeof thisXML.placeable !== 'object' ) { this.#util_readError('placeables') @@ -1185,8 +1449,8 @@ class saveFileChecker { this.#placeables = this.#util_iterateXML(thisXML.placeable) } - #doStep_vehicles() { - const thisXML = this.#fileHandle.readXML('vehicles.xml', 'savegame', 'vehicles') + async #doStep_vehicles() { + const thisXML = await this.#fileHandle.readXML('vehicles.xml', 'savegame', 'vehicles') if ( thisXML === null || typeof thisXML.vehicle !== 'object' ) { this.#util_readError('vehicles') @@ -1196,8 +1460,8 @@ class saveFileChecker { this.#vehicles = this.#util_iterateXML(thisXML.vehicle) } - #doStep_career() { - const thisXML = this.#fileHandle.readXML('careerSavegame.xml', 'savegame', 'careersavegame') + async #doStep_career() { + const thisXML = await this.#fileHandle.readXML('careerSavegame.xml', 'savegame', 'careersavegame') if ( thisXML === null ) { this.#util_readError('careerSavegame') @@ -1234,6 +1498,10 @@ class saveFileChecker { } } +/** + * Savegame tracker + * @class + */ class savegameTrack { #log = null #savePath = null @@ -1244,13 +1512,21 @@ class savegameTrack { byDate : [], } + /** + * + * @param {string} savePath Full path to the save game + */ constructor(savePath) { this.#savePath = savePath this.#log = serveIPC.log.group(`savegame_track_${path.basename(savePath)}`) - this.#xmlParser = fileHandler.getParser('savegame') + this.#xmlParser = fileHandlerAsync.getParser('savegame') } + /** + * Process savegame + * @returns {saveTrackRecord} + */ async getInfo() { try { const backupFileList = this.#doStep_findFiles() @@ -1324,12 +1600,20 @@ class savegameTrack { } } +/** + * Manage local save games + * @class + */ class saveGameManager { #allSaves = {} #backPath = null #fullPath = null #log = null + /** + * + * @param {string} gameSettingsXMLFile Path and name of game settings file + */ constructor(gameSettingsXMLFile) { this.#fullPath = path.dirname(gameSettingsXMLFile) this.#log = serveIPC.log.group('savegame-manager') @@ -1342,6 +1626,10 @@ class saveGameManager { } } + /** + * Process saves on disk + * @returns {saveManageRecord_return} + */ async getInfo() { await this.#findActiveSaves() await this.#findBackupSaves() @@ -1349,10 +1637,14 @@ class saveGameManager { return this.#allSaves } - #processSave(fullPath) { - const thisSaveFile = new fileHandler(fullPath, true, this.#log) - const thisCareer = thisSaveFile.readXML('careerSavegame.xml', 'savegame', 'careersavegame') - const thisFarms = thisSaveFile.readXML('farms.xml', 'savegame', 'farms') + async #processSave(fullPath) { + const thisSaveFile = new fileHandlerAsync(fullPath, true, this.#log) + const couldOpen = await thisSaveFile.open() + + if ( !couldOpen ) { return { error : true } } + + const thisCareer = await thisSaveFile.readXML('careerSavegame.xml', 'savegame', 'careersavegame') + const thisFarms = await thisSaveFile.readXML('farms.xml', 'savegame', 'farms') if ( thisCareer === null || thisCareer === false || thisFarms === null || thisFarms === false ) { return { error : true } @@ -1385,7 +1677,7 @@ class saveGameManager { } async #findActiveSaves() { - return fsPromise.readdir(this.#fullPath, {withFileTypes : true}).then((folderContents) => { + return fsPromise.readdir(this.#fullPath, {withFileTypes : true}).then(async (folderContents) => { for ( const thisFolder of folderContents ) { if ( !thisFolder.isDirectory() ) { continue @@ -1396,7 +1688,9 @@ class saveGameManager { fileDate : thisStats.mtime, fullName : thisFolder.name, fullPath : path.join(this.#fullPath, thisFolder.name), - ...this.#processSave(path.join(this.#fullPath, thisFolder.name)), + /* eslint-disable no-await-in-loop */ + ...await this.#processSave(path.join(this.#fullPath, thisFolder.name)), + /* eslint-enable no-await-in-loop */ } } else if ( thisFolder.name === 'savegameBackup' ) { this.#backPath = path.join(this.#fullPath, 'savegameBackup') @@ -1408,7 +1702,7 @@ class saveGameManager { async #findBackupSaves() { if ( this.#backPath === null ) { return } - return fsPromise.readdir(path.join(this.#backPath), { withFileTypes : true }).then((folderContents) => { + return fsPromise.readdir(path.join(this.#backPath), { withFileTypes : true }).then(async (folderContents) => { for ( const thisFolder of folderContents ) { if ( !thisFolder.isDirectory() ) { continue @@ -1422,7 +1716,9 @@ class saveGameManager { fileDate : folderDateObj, fullName : thisFolder.name, fullPath : path.join(this.#backPath, thisFolder.name), - ...this.#processSave(path.join(this.#backPath, thisFolder.name)), + /* eslint-disable no-await-in-loop */ + ...await this.#processSave(path.join(this.#backPath, thisFolder.name)), + /* eslint-enable no-await-in-loop */ }) } } @@ -1430,117 +1726,19 @@ class saveGameManager { } } -class fileHandler { - #ZIPFile = null - #isFolder = false - #log = null - #folderName = null - - #isOpen = false - - constructor(fileName, isFolder, log) { - this.#isFolder = isFolder - this.#isOpen = true - this.#log = log - - if ( ! this.#isFolder ) { - try { - if ( ! fileName.endsWith('.zip') ) { throw new Error('Not a ZIP File') } - this.#ZIPFile = new admZip(fileName) - this.#isOpen = true - } catch (_) { - this.#log.warning(`File-Manager ZIP File Error: ${fileName}`) - this.#isOpen = false - } - } else { - this.#folderName = fileName - this.#isOpen = true - } - } - - get isOpen() { return this.#isOpen } - - close() { if ( ! this.#isFolder ) { this.#ZIPFile = null } } - - exists(fileName) { - if ( fileName === null ) { return false } - if ( this.#isFolder ) { - return fs.existsSync(path.join(this.#folderName, fileName)) - } - return ( this.#ZIPFile.getEntry(fileName) !== null ) - } - - listFiles() { - return ( this.#isFolder ) ? - globSync('**', { cwd : this.#folderName, follow : true, mark : true, stat : true, withFileTypes : true }) : - this.#ZIPFile.getEntries() - } - - static getParser(type) { - return new XMLParser({ - attributeNamePrefix : '', - attributesGroupName : '$', - ignoreAttributes : false, - ignoreDeclaration : true, - ignorePiTags : true, - isArray : (_, jPath) => alwaysArray[type].has(jPath), - parseAttributeValue : true, - parseTagValue : true, - processEntities : false, - stopNodes : require('./modCheckLib_static.js').stopNodes, - transformAttributeName : (name) => name.toUpperCase(), - transformTagName : (name) => name.toLowerCase(), - trimValues : true, - }) - } - - readText(fileName) { return this.#readFile(fileName, true) } - readBin(fileName) { return this.#readFile(fileName, false) } - readXML(fileName, type = 'moddesc', defaultKey = null) { - - const fileContents = this.readText(fileName) - - if ( fileContents === null ) { return null } - - const thisXMLParser = fileHandler.getParser(type) - - try { - - const thisParsedXML = (defaultKey === null ? thisXMLParser.parse(fileContents) : thisXMLParser.parse(fileContents)?.[defaultKey] ) ?? null - - if ( thisParsedXML === null ) { - this.#log.warning(`XML Parse error or default key not found ${fileName} :: ${defaultKey}`) - } - - return thisParsedXML - } catch (_) { - this.#log.warning(`Caught unrecoverable XML Parse error ${fileName}`) - return false - } - } - - #readFile(fileName, text = true) { - try { - if ( ! this.exists(fileName) ) { throw new Error('Non-existent') } - - if ( this.#isFolder ) { - return text ? - fs.readFileSync(path.join(this.#folderName, fileName), 'utf8') : - fs.readFileSync(path.join(this.#folderName, fileName), null).buffer - } - return text ? this.#ZIPFile.readAsText(fileName) : this.#ZIPFile.readFile(fileName).buffer - } catch (err) { - this.#log.debug(`File-Manager file read error ${fileName} :: ${err.message}`) - return null - } - } -} - +/** + * Check if a ZIP is a mod pack + * @class + */ class modPackChecker { #fileName = null #fileHandle = null #log = null + /** + * + * @param {string} fileName path and name of ZIP + */ constructor( fileName ) { this.#fileName = fileName this.#log = serveIPC.log.group(`mod_pack_check-${path.basename(fileName)}`) @@ -1548,31 +1746,45 @@ class modPackChecker { this.#log.info(`Checking Possible ModPack: ${fileName}`) } - getInfo() { + /** + * Process the ZIP file + * @returns {boolean} true if it is a mod pack + */ + async getInfo() { let allowedNonZIP = 2 - this.#fileHandle = new fileHandler(this.#fileName, false, this.#log) + this.#fileHandle = new fileHandlerAsync(this.#fileName, false, this.#log) + const couldOpen = await this.#fileHandle.open() - if ( ! this.#fileHandle.isOpen ) { + if ( ! couldOpen ) { this.#log.danger(`File fail: ${this.#fileName}`) return false } - for ( const thisFile of this.#fileHandle.listFiles() ) { - if ( thisFile.isDirectory ) { return false } - if ( thisFile.entryName.endsWith('.xml') ) { return false } - if ( !thisFile.entryName.endsWith('.zip') ) { allowedNonZIP-- } - if ( allowedNonZIP <= 0 ) { return false } + for ( const thisFile of this.#fileHandle.list ) { + const fileInfo = this.#fileHandle.fileInfo(thisFile) + if ( fileInfo.isFolder ) { return false } + if ( thisFile.endsWith('.xml') ) { return false } + if ( !thisFile.endsWith('.zip') ) { allowedNonZIP-- } + if ( allowedNonZIP <= 0 ) { return false } } return true } } +/** + * badge handler + * @class + */ class badgeHandle { #baseSet = null #l10nNames = {} #extraData = {} - + /** + * + * @param {Array} initArray original simple array of badges + * @param {Object} transBadges localization for badges + */ constructor(initArray = [], transBadges) { this.#baseSet = new Set() this.#l10nNames = transBadges @@ -1582,6 +1794,10 @@ class badgeHandle { } } + /** + * @type {Array.} + * @description Get list of badges + */ get keyList() { const sorted = [...this.#baseSet].sort((a, b) => Intl.Collator().compare(this.#l10nNames[a], this.#l10nNames[b])) const returnArray = [] @@ -1595,10 +1811,19 @@ class badgeHandle { return returnArray } + /** + * Remove badge if another exists + * @param {string} checkKey badge id to find + * @param {string} removeKey badge id to remove + */ pruneOn(checkKey, removeKey) { if ( this.#baseSet.has(checkKey) ) { this.del(removeKey) } } + /** + * Add a badge + * @param {string} key badge id to add + */ add(key) { this.#baseSet.add(key) this.#extraData[key] ??= { cls : new Set(), title : new Set() } @@ -1610,21 +1835,48 @@ class badgeHandle { ]) } if ( key === 'nonmh' ) { this.addClass(key, 'bdg_no_fs13') } } + + /** + * Remove a badge + * @param {string} key badge id to remove + */ del(key) { this.#baseSet.delete(key) delete this.#extraData[key] } + /** + * Add a CSS class to a badge + * @param {string} key badge id + * @param {string} cls CSS class to add + */ addClass(key, cls) { this.#addExtra(key, 'cls', cls) } + + /** + * Remove a CSS class from a badge + * @param {string} key badge id + * @param {string} cls CSS class to remove + */ delClass(key, cls) { this.#extraData[key].cls.delete(cls) } + /** + * Add a title to a badge + * @param {string} key badge id + * @param {string} cls title to add + */ addTitle(key, title) { this.#addExtra(key, 'title', title) } + + /** + * Remove a title from a badge + * @param {string} key badge id + * @param {string} cls title to add + */ delTitle(key, title) { this.#extraData[key].title.delete(title) } diff --git a/lib/modUtilLib.js b/lib/modUtilLib.js index 7b1ac9fe..be665f07 100644 --- a/lib/modUtilLib.js +++ b/lib/modUtilLib.js @@ -9,12 +9,20 @@ // and // https://github.com/Jam3/parse-dds +/** + * Utility libraries for MA + * @module modUtilLib + */ const fs = require('node:fs') const path = require('node:path') const { keyMap, localKeys } = require('../renderer/renderJS/util/key_lookup_table.js') const baseGameLang = require('./modLookerLang.json') +/** + * Logger Class + * @class +*/ class ma_logger { #isPacked = false #dataPath = null @@ -35,6 +43,13 @@ class ma_logger { warning : 'fw-bold text-warning', } + /** + * Create a new log instance + * @param {string} defaultProcess Default process string + * @param {object} app Electron app api + * @param {string} fileName File to output + * @param {boolean} clearFile Clear file when starting + */ constructor( defaultProcess, app = null, fileName = null, clearFile = true) { this.#process = defaultProcess @@ -54,8 +69,15 @@ class ma_logger { } } + /** + * Set danger callback function + * @param {function} newCallback Function to call + */ set dangerCallBack(newCallback) { this.#dangerEvent = newCallback } + /** + * Turn off console writes + */ forceNoConsole() { this.#isPacked = true } #trans = { @@ -64,6 +86,15 @@ class ma_logger { file : (text) => { if ( this.#fullFileName !== null ) { this.#fileHandle.write(`${text}\n`) } }, } + /** + * Add a log line + * @param {string} level Level, one of 'danger', 'warning', 'notice', 'info', or 'debug' + * @param {string} process Process to log + * @param {...string} text Text to add to log + * @private + * @instance + * @memberof ma_logger + */ #addToLog(level, process, ...text) { const inputText = [...text].join(' :: ') if ( level === 'danger' && typeof this.#dangerEvent === 'function' ) { this.#dangerEvent() } @@ -82,12 +113,47 @@ class ma_logger { this.#textLog.push(simpleText) } + /** + * Add danger level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ danger (process, ...text) { this.#addToLog('danger', process, ...text) } + /** + * Add debug level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ debug (process, ...text) { this.#addToLog('debug', process, ...text) } + /** + * Add danger level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ error (process, ...text) { this.#addToLog('danger', process, ...text) } + /** + * Add info level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ info (process, ...text) { this.#addToLog('info', process, ...text) } + /** + * Add notice level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ notice (process, ...text) { this.#addToLog('notice', process, ...text) } + /** + * Add warning level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ warn (process, ...text) { this.#addToLog('warning', process, ...text) } + /** + * Add warning level error + * @param {string} process Process to log + * @param {...string} text Text to log + */ warning (process, ...text) { this.#addToLog('warning', process, ...text) } log = { @@ -100,6 +166,11 @@ class ma_logger { warning : (text, process) => { this.danger(process, 'WARNING DEPRECIATED', text) }, } + /** + * Create grouped process + * @param {string} process Process to log + * @returns {Object} Object of standard log level functions + */ group = (process) => { return { danger : (...text) => { this.danger(process, ...text) }, debug : (...text) => { this.debug(process, ...text) }, @@ -110,11 +181,24 @@ class ma_logger { warning : (...text) => { this.warning(process, ...text) }, } } + /** + * @property {string} pathToLog Path to log on disk + */ get pathToLog() { return this.#fullFileName } + /** + * @property {string} textLog Full log as text + */ get textLog() { return this.#textLog.join('\n') } + /** + * @property {string} htmlLog Full log as html + */ get htmlLog() { return this.#htmlLog.join('\n') } } +/** + * + * @returns 2 letter system locale, constrained to the set Giants uses + */ const getSystemLocale = function () { const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale const spanishEA = new Set([ @@ -146,6 +230,10 @@ const getSystemLocale = function () { } } +/** + * l10n Library + * @class + */ class translator { #log = null #debug = false @@ -153,6 +241,9 @@ class translator { #translatorStrings = new Map() #langPromise = null mcVersion = null + /** + * @property {Object} iconOverrides Preset string->icon + */ iconOverrides = { admin_button : 'globe2', admin_pass_button : 'key', @@ -195,6 +286,11 @@ class translator { search_all__title : ['KEY_f3'], } + /** + * Create a new translator instance + * @param {string} locale 2 letter locale + * @param {boolean} debug Show debug information + */ constructor(locale = null, debug = false) { this.#currentLocale = locale === null ? getSystemLocale() : locale this.#debug = debug @@ -202,10 +298,17 @@ class translator { this.#log = serveIPC.log.group('translate-lib') this.#log.info(`Starting i18n Library :: ${this.#currentLocale}`) - this.#langPromise = this.loadLanguages() + this.#langPromise = this.#loadLanguages() } + /** + * Useful for passing a later-called function around + * @returns {string} 2 letter locale + */ deferCurrentLocale = () => { return this.#currentLocale } + /** + * @property {string} currentLocale 2 letter locale + */ get currentLocale() { return this.#currentLocale } set currentLocale(value) { if ( this.#translatorStrings.has(value) ) { @@ -213,6 +316,9 @@ class translator { } } + /** + * @property {Object} currentUnits Get current measurement units + */ get currentUnits() { return { hp : this.syncStringLookup('unit_hp'), @@ -226,7 +332,13 @@ class translator { } } - async loadLanguages() { + /** + * load Language files + * @private + * @instance + * @memberof translator + */ + async #loadLanguages() { const langJSON = fs.readdirSync(path.join(__dirname, '..', 'translations')) for ( const thisFile of langJSON ) { @@ -242,6 +354,10 @@ class translator { } } + /** + * Get loaded language list + * @returns {Array.} [[langCode, Language Title],...] + */ async getLangList() { return this.#langPromise.then(() => { const returnArray = [] @@ -255,13 +371,18 @@ class translator { }) } - iconOverride(stringID) { + #iconOverride(stringID) { const className = this.iconOverrides[stringID] ?? null return ( className !== null ) ? `` : null } + /** + * Synchronous lookup + * @param {string} stringID + * @returns {string} Translated text + */ syncStringLookup(stringID) { // Note: this could fail depending on when it's used. if ( stringID === null ) { return null } @@ -269,7 +390,7 @@ class translator { let possibleValue = null const lcStringID = stringID.toLowerCase() - possibleValue ??= this.iconOverride(lcStringID) + possibleValue ??= this.#iconOverride(lcStringID) possibleValue ??= this.#translatorStrings.get(this.#currentLocale)?.get(lcStringID) possibleValue ??= this.#translatorStrings.get('en')?.get(lcStringID) possibleValue ??= `__${lcStringID}__` @@ -281,22 +402,48 @@ class translator { return possibleValue } + /** + * Retrieve MA string + * @param {string} stringID + * @returns {string} Translated string + */ async stringLookup(stringID) { return this.#langPromise.then(() => { return this.syncStringLookup(stringID) }) } + /** + * Retrieve multiple MA strings + * @param {Array.} stringIDs Array of stringIDs + * @returns {Array.} [[stringID, Translated String], ...] + */ async stringGroup(stringIDs) { return this.#langPromise.then(() => [...stringIDs].map((x) => [x, this.syncStringLookup(x)]) ) } - async baseStringGroup(stringIDs) { - return this.#langPromise.then(() => [...stringIDs].map((x) => [x, this.syncBaseStringLookup(x)]) ) - } + /** + * Retrieve base game string + * @param {string} stringID + * @returns {string} Translated string + */ async baseStringLookup(stringID) { return this.#langPromise.then(() => this.syncBaseStringLookup(stringID)) } + /** + * Retrieve multiple base game strings + * @param {Array.} stringIDs + * @returns {string} [[stringID, Translated String], ...] + */ + async baseStringGroup(stringIDs) { + return this.#langPromise.then(() => [...stringIDs].map((x) => [x, this.syncBaseStringLookup(x)]) ) + } + + /** + * Retrieve base game string + * @param {string} stringID + * @returns {string} Translated string + */ syncBaseStringLookup(stringID) { const lookupID = stringID.replace('$l10n_', '') let thisAnswer = baseGameLang?.[this.#currentLocale]?.[lookupID] @@ -325,6 +472,11 @@ class translator { return ` [${bindArr.join('+')}]` } + /** + * Retrieve MA title string + * @param {string} stringID + * @returns {string} Translated string + */ async stringTitleLookup(stringID) { return this.#langPromise.then(() => { if ( stringID === null ) { return null } @@ -362,17 +514,29 @@ function getDeferPromise() { return promise } +/** + * Mod Cache Manager + * @class +*/ class modCacheManager { #cacheMap = new Map() #saveFileData = null #saveFileIcon = null + /** + * Create new instance + * @param {string} filePath Path to save to on disk + */ constructor(filePath) { this.#saveFileData = path.join(filePath, 'mod_cache.json') this.#saveFileIcon = path.join(filePath, 'mod_icons.json') this.loadFile() } + /** + * Load current cache + * @returns null + */ loadFile() { if ( !fs.existsSync(this.#saveFileData) || !fs.existsSync(this.#saveFileIcon) ) { serveIPC.log.debug('mod-cache-manager', 'Cache Not Found, Creating') @@ -395,6 +559,9 @@ class modCacheManager { } } + /** + * Save current cache + */ saveFile() { try { const writeData = fs.createWriteStream(this.#saveFileData) @@ -423,20 +590,89 @@ class modCacheManager { } } + /** + * @property {Array.} keys Array of mod uuid keys + */ get keys() { return this.#cacheMap.keys() } + /** + * Clear the cache + */ clearAll() { this.#cacheMap.clear() this.saveFile() } + /** + * Test is mod exists in cache + * @param {string} uuid Mod UUID + * @returns {boolean} UUID exists in cache + */ hasMod(uuid) { return this.#cacheMap.has(uuid) } + /** + * Get mod from cache + * @param {string} uuid Mod UUID + * @returns {(Object|boolean)} Mod record or false + */ getMod(uuid) { return this.hasMod(uuid) ? this.#cacheMap.get(uuid) : false } + /** + * Remove mod from cache + * @param {string} uuid Mod UUID + */ remMod(uuid) { this.#cacheMap.delete(uuid) } + /** + * Add mod to cache (or update) + * @param {string} uuid Mod UUID + * @param {Object} data Mod Record + */ setMod(uuid, data) { this.#cacheMap.set(uuid, data) } } + +/** + * IPC Communication + * @typedef serveIPC + * @type {Object} + * @property {Object} dlRequest NET download request object + * @property {module:modCheckLib~modFileCollection} modCollect mod collection + * @property {Set} modFolders mod folders as full paths + * @property {module:modAssist_window_lib.windowLib} windowLib Window handling library + * @property {module:modUtilLib~modCacheManager} storeCache Mod Cache + * @property {electron-store} storeCacheDetail Mod Details Cache + * @property {electron-store} storeNote Collection Settings + * @property {electron-store} storeSet Application Settings + * @property {electron-store} storeSites Mod External Sites + * @property {saveFileCheckerRecord} cacheGameSave Loaded save game compare + * @property {module:modUtilLib~ma_logger} log Logging Class + * @property {module:modUtilLib~translator} l10n Localization class + * @property {Set} ignoreMalwareList Malware files to ignore in this session + * @property {Set} whiteMalwareList Malware files globally white-listed + * @property {boolean} isBotDisabled use FSG Bot + * @property {boolean} isDownloading Currently downloading mods + * @property {boolean} isFirstMinimize Has been minimized this session + * @property {boolean} isFirstRun Has been opened before + * @property {boolean} isFoldersDirty Folders need re-scanned + * @property {boolean} isFoldersEdit Folders are being edited + * @property {boolean} isGamePolling Poll game for running status + * @property {boolean} isGameRunning Is the game running + * @property {boolean} isModCacheDisabled Don't cache mods + * @property {boolean} isProcessing Mod processor running + * @property {module:modAssist_window_lib~loadingWindow} loadWindow Loading window + * @property {Object} devControls Dev controls status { version : boolean } + * @property {module:modUtilLib~gameSetOverride} gameSetOverride Game settings overrides + * @property {module:modUtilLib~intervals} interval Running intervals + * @property {referenceFunctions} refFunc Reference functions + * @property {Object} icon Icons needed elsewhere + * @property {module:modUtilLib~ipc-path} path Reusable paths + * @property {Object} watch Watched files +*/ const serveIPC = { + /** + * Get translated string (sync!!) + * @param {string} x String ID + * @returns {string} Translated string + * @method + */ __ : (x) => serveIPC.l10n.syncStringLookup(x), dlRequest : null, @@ -469,10 +705,19 @@ const serveIPC = { isModCacheDisabled : false, isProcessing : false, + loadJSON : { current : () => {} }, loadWindow : { current : () => {} }, devControls : { 13 : false, 15 : false, 17 : false, 19 : false, 22 : false }, + /** + * @typedef gameSetOverride + * @property {boolean} active Override is active + * @property {?string} folder Folder for override + * @property {(number|string)} index Collection for override, 999 - disabled, 0 - unknown + * @property {Object} xml XML tree of override + */ + gameSetOverride : { active : false, folder : null, @@ -480,6 +725,13 @@ const serveIPC = { xml : null, }, + /** + * @typedef intervals + * @property {?interval} gameLog game log reloader + * @property {?interval} gamePoll game status poll + * @property {?interval} modHub modhub downloader + * @property {?interval} update auto-updater + */ interval : { gameLog : null, gamePoll : null, @@ -493,6 +745,13 @@ const serveIPC = { tray : null, }, + /** + * @typedef ipc-path + * @property {string} game Path to game + * @property {?string} last Last path used to open a file or folder + * @property {string} setFile Settings file (current version) + * @property {string} setFolder Settings folder (current version) + */ path : { game : '', last : null, diff --git a/lib/workerThreadLib.js b/lib/workerThreadLib.js index 3d3f7e9d..84daf4b2 100644 --- a/lib/workerThreadLib.js +++ b/lib/workerThreadLib.js @@ -4,19 +4,24 @@ |__|_|__||_____|_____|___|___||_____|_____||__||_____||____| (c) 2022-present FSG Modding. MIT License. */ -// Mod Parser, Mod Look Parser, Crop Data Reader, File Handler Class +/** + * Mod Parser, Mod Look Parser, Crop Data Reader, File Handler Class + * @module workerThreadLib + */ + // -- This is part of the detached thread processes const path = require('node:path') const fs = require('node:fs') +const fsPromise = require('node:fs/promises') const crypto = require('node:crypto') const cp = require('node:child_process') -const admZip = require('adm-zip') const {XMLParser} = require('fast-xml-parser') +const StreamZip = require('node-stream-zip') const allLang = require('./modLookerLang.json') const alwaysArray = require('./modCheckLib_static.js').alwaysArrays const malwareFalse = require('./modCheckLib_static.js').malwareFalse -const { globSync } = require('glob') +const { glob } = require('glob') const Vips = require('wasm-vips') const vipsPromise = Vips() @@ -26,22 +31,47 @@ const requiredItems = { l10n_hp : null, } +/** + * Log collector class, for child process communication + * @class + */ class logCollector { #items = [] #group = null + /** + * + * @param {string} group Process group text + */ constructor(group) { this.#group = group } + /** @param {string} text Text with danger level */ danger (text) { this.#items.push(['danger', text]) } + + /** @param {string} text Text with debug level */ debug (text) { this.#items.push(['debug', text]) } + + /** @param {string} text Text with danger level */ error (text) { this.#items.push(['danger', text]) } + + /** @param {string} text Text with info level */ info (text) { this.#items.push(['info', text]) } + + /** @param {string} text Text with notice level */ notice (text) { this.#items.push(['notice', text]) } + + /** @param {string} text Text with warning level */ warn (text) { this.#items.push(['warning', text]) } + + /** @param {string} text Text with warning level */ warning (text) { this.#items.push(['warning', text]) } + /** + * Get log lines for transmission + * @returns {Object} {group, items} + */ get lines() { return { group : this.#group, @@ -50,6 +80,10 @@ class logCollector { } } +/** + * Mod file parser + * @class + */ class modFileChecker { #maxFilesType = { grle : 10, pdf : 1, png : 128, txt : 2 } #fileSizeMap = { cache : 10485760, dds : 12582912, gdm : 18874368, shapes : 268435456, xml : 262144 } @@ -146,6 +180,14 @@ class modFileChecker { #modHandle = null + /** + * Create new mod parsing instance + * @param {string} filePath Path to mod file/folder + * @param {boolean} isFolder Mod is a folder + * @param {number} size Size of mod + * @param {Date} date Date of mod + * @param {modRecord_md5Sum} md5Pre MD5 hash from location and date + */ constructor(filePath, isFolder, size, date, md5Pre = null) { this.fileDetail.fullPath = filePath this.fileDetail.isFolder = isFolder @@ -160,6 +202,10 @@ class modFileChecker { this.#flag_info.INFO_NO_MULTIPLAYER_UNZIPPED = this.fileDetail.isFolder } + /** + * Actually parse the mod + * @returns {modRecord_storable} + */ async getInfo() { this.uuid = crypto.createHash('md5').update(this.fileDetail.fullPath).digest('hex') this.#log = new logCollector(`mod-${this.uuid}`) @@ -172,9 +218,11 @@ class modFileChecker { this.#util_raiseFlag_broken('FILE_ERROR_NAME_INVALID') } - this.#modHandle = new fileHandler(this.fileDetail.fullPath, this.fileDetail.isFolder, this.#log) + this.#modHandle = new fileHandlerAsync(this.fileDetail.fullPath, this.fileDetail.isFolder, this.#log) + + const couldOpen = await this.#modHandle.open() - if ( ! this.#modHandle.isOpen ) { + if ( !couldOpen ) { if ( !isValidMod ) { throw new Error('Invalid Mod') } this.#util_raiseFlag_broken('FILE_ERROR_UNREADABLE_ZIP') throw new Error('Unreadable ZIP File') @@ -191,25 +239,25 @@ class modFileChecker { throw new Error('Invalid Mod') } - if ( this.#modHandle.exists('modDesc.xml') === null ) { + if ( ! this.#modHandle.exists('modDesc.xml') ) { this.#util_raiseFlag_broken('NOT_MOD_MODDESC_MISSING') this.md5Sum = null throw new Error('ModDesc Missing, Invalid, or Un-Readable') } - this.#doStep_fileCounts() - this.#doStep_parseModDesc() + await this.#doStep_fileCounts() + await this.#doStep_parseModDesc() if ( this.modDesc.mapConfigFile !== null ) { try { if (! this.#modHandle.exists(this.modDesc.mapConfigFile) ) { throw new Error('Config file does not Exist')} - const cropConfigFiles = this.#doStep_parseMapXML(this.modDesc.mapConfigFile) + const cropConfigFiles = await this.#doStep_parseMapXML(this.modDesc.mapConfigFile) const cropInfo = new cropDataReader( - this.#modHandle.readText(cropConfigFiles[0]), - this.#modHandle.readText(cropConfigFiles[1]), - this.#modHandle.readText(cropConfigFiles[2]), + await this.#modHandle.readText(cropConfigFiles[0]), + await this.#modHandle.readText(cropConfigFiles[1]), + await this.#modHandle.readText(cropConfigFiles[2]), cropConfigFiles[3] ) @@ -227,7 +275,7 @@ class modFileChecker { } this.modDesc.iconImageCache = await this.#iconParser.parseDDS( - this.#modHandle.readBin(this.modDesc.iconFileName), + await this.#modHandle.readBin(this.modDesc.iconFileName), false ) } catch (err) { @@ -247,6 +295,9 @@ class modFileChecker { return this.storable } + /** + * @type {modRecord_storable} + */ get storable() { return { log : this.#log.lines, @@ -264,26 +315,28 @@ class modFileChecker { } } - #doStep_fileCounts() { - for ( const checkFile of this.#modHandle.listFiles() ) { - const fileName = this.fileDetail.isFolder ? checkFile.name : checkFile.entryName - const fullName = this.fileDetail.isFolder ? this.#modHandle.relativeFolder(checkFile.fullpath()) : checkFile.entryName + async #doStep_fileCounts() { + for ( const checkFile of this.#modHandle.list ) { + + const fileInfo = this.#modHandle.fileInfo(checkFile) + const fileName = checkFile + const fullName = this.fileDetail.isFolder ? this.#modHandle.relativeFolder(checkFile) : checkFile if ( fileName.includes(' ') ) { this.fileDetail.spaceFiles.push(fileName) this.#util_raiseFlag_problem('PERF_SPACE_IN_FILE') } - if ( this.fileDetail.isFolder && checkFile.isDirectory() ) { continue } + if ( fileInfo.isFolder ) { continue } - if ( fileName.endsWith('/') || fileName.endsWith('\\') ) { continue } - - this.#util_countFile( + /* eslint-disable no-await-in-loop */ + await this.#util_countFile( path.extname(fileName), fileName, - this.fileDetail.isFolder ? checkFile.size : checkFile.header.size, + fileInfo.size, fullName ) + /* eslint-enable no-await-in-loop */ } this.#flag_problem.PERF_GRLE_TOO_MANY = ( this.#maxFilesType.grle < 1 ) @@ -372,9 +425,9 @@ class modFileChecker { #modDesc_default(key, fallback = null) { return key ?? fallback } - #doStep_parseModDesc() { - this.modDescParsed = this.#modHandle.readXML('modDesc.xml', 'moddesc', 'moddesc') - + async #doStep_parseModDesc() { + this.modDescParsed = await this.#modHandle.readXML('modDesc.xml', 'moddesc', 'moddesc') + if ( this.modDescParsed === false ) { this.#util_raiseFlag_broken('NOT_MOD_MODDESC_PARSE_ERROR') return @@ -450,9 +503,8 @@ class modFileChecker { } } - #doStep_parseMapXML(fileName) { - const mapConfigParsed = this.#modHandle.readXML(fileName, 'moddesc', 'map') - + async #doStep_parseMapXML(fileName) { + const mapConfigParsed = await this.#modHandle.readXML(fileName, 'moddesc', 'map') if ( mapConfigParsed === null ) { this.#log.warning('Map XML Files not found') @@ -491,13 +543,14 @@ class modFileChecker { } return false } - #util_checkLUA(fileName) { + + async #util_checkLUA(fileName) { if ( malwareFalse.has(this.fileDetail.shortName)) { return } - const luaContents = this.#modHandle.readText(fileName) + const luaContents = await this.#modHandle.readText(fileName) + + if ( typeof luaContents !== 'string' ) { return } - if ( luaContents === null ) { return } - if ( luaContents.match(/\.deleteFolder/) ) { this.#util_raiseFlag_info('MALICIOUS_CODE') } else if ( luaContents.match(/\.deleteFile/) ) { @@ -505,7 +558,8 @@ class modFileChecker { } // console.log(luaContents) } - #util_countFile(suffix, fileName, size, fullFileName) { + + async #util_countFile(suffix, fileName, size, fullFileName) { const shortSuffix = suffix.substring(1) if ( this.#util_countUnknown(shortSuffix, fileName) ) { return } @@ -528,7 +582,7 @@ class modFileChecker { break case 'lua' : this.modDesc.scriptFiles++ - this.#util_checkLUA(fullFileName) + await this.#util_checkLUA(fullFileName) break case 'cache' : this.#util_size_check(size, shortSuffix, fileName, 'I3D') @@ -552,6 +606,7 @@ class modFileChecker { #util_nullBaseGameFile(fileName) { return ( typeof fileName === 'string' && !fileName.startsWith('$') ) ? fileName : null } + #util_getBaseGameFile(fileName) { try { return ( typeof fileName === 'string' && !fileName.startsWith('$') ) ? null : path.normalize(path.dirname(fileName.replace('$data', ''))).split(path.sep).slice(-1) @@ -559,6 +614,10 @@ class modFileChecker { } } +/** + * Crop Data Reader + * @class + */ class cropDataReader { #cropData = {} #skipFruits = new Set(['meadow']) @@ -701,11 +760,18 @@ class cropDataReader { #mapIsSouth = false #cropOutput = [] + /** + * Create new crop parser + * @param {?string} typesFile String contents of fruitTypes.xml + * @param {?string} growthFile String contents of growth.xml + * @param {?string} envFile String contents of environment.xml + * @param {?string} baseEnvFile Use base game weather from this map name + */ constructor(typesFile, growthFile, envFile, baseEnvFile) { let didReadTypes = false let didReadGrowth = false - const cropParser = fileHandler.getParser('crop') + const cropParser = fileHandlerAsync.getParser('crop') if ( baseEnvFile !== null ) { this.#weather = this.#baseGameWeather[baseEnvFile] ?? null @@ -871,11 +937,29 @@ class cropDataReader { return true } + /** + * @type {boolean} + * @description Map is in southern hemisphere + */ get isSouth() { return this.#mapIsSouth } + + /** + * @type {Array.} + * @description Crops found + */ get crops() { return this.#cropOutput } + + /** + * @type {cropData_weather} + * @description Map weather + */ get weather() { return this.#weather } } +/** + * ModLook class - gets internal store items from a mod + * @class + */ class modLooker { #iconParser = requiredItems.iconDecoder #fullFileName = null @@ -901,6 +985,12 @@ class modLooker { #modHandle = null + /** + * Get internal store items from mod + * @param {modRecord} modRecord Original mod record + * @param {string} modCollectFolder Folder of mod + * @param {boolean} skipIcons Do not process icons + */ constructor( modRecord, modCollectFolder, skipIcons = false ) { // modRecord from collection, folder for collection, skip Icons bool this.#log = new logCollector(`modLook-${modRecord.fileDetail.shortName}`) @@ -915,12 +1005,18 @@ class modLooker { this.#langData = Object.hasOwn(allLang, this.#locale) ? allLang[this.#locale] : allLang.en } + /** + * Process the mod for store items + * @returns {lookRecord_return} + */ async getInfo() { - this.#modHandle = new fileHandler(this.#fullFileName, this.#modRecord.fileDetail.isFolder, this.#log) + this.#modHandle = new fileHandlerAsync(this.#fullFileName, this.#modRecord.fileDetail.isFolder, this.#log) - if ( ! this.#modHandle.isOpen ) { return { log : this.#log.lines, record : null } } + const couldOpen = await this.#modHandle.open() - const modDescTree = this.#modHandle.readXML('modDesc.xml') + if ( ! couldOpen ) { return { log : this.#log.lines, record : null } } + + const modDescTree = await this.#modHandle.readXML('modDesc.xml') const storeItemFiles = this.#getStoreItems(modDescTree) this.#infoData.mapImage = await this.#getMapImage(modDescTree) @@ -931,8 +1027,10 @@ class modLooker { const iconLoads = [] for ( const thisItem of storeItemFiles ) { - const thisItemTree = this.#modHandle.readXML(thisItem, 'modlook') + /* eslint-disable no-await-in-loop */ + const thisItemTree = await this.#modHandle.readXML(thisItem, 'modlook') const thisItemInfo = this.#parseStoreItem(thisItemTree) + /* eslint-enable no-await-in-loop */ if ( thisItemInfo !== null ) { this.#infoData.items[thisItem] = thisItemInfo @@ -947,10 +1045,9 @@ class modLooker { } } - this.#modHandle.close() - this.#modHandle = null - return Promise.allSettled(iconLoads).then(() => { + this.#modHandle.close() + this.#modHandle = null return { log : this.#log.lines, record : this.#infoData } }) } @@ -1024,9 +1121,10 @@ class modLooker { async #getMapImage(modDescTree) { if ( modDescTree !== null ) { const mapFile = modDescTree.moddesc?.maps?.map?.[0]?.$?.CONFIGFILENAME ?? null + if ( mapFile === null ) { return null } - const mapXMLTree = this.#modHandle.readXML(mapFile, 'modlook') + const mapXMLTree = await this.#modHandle.readXML(mapFile, 'modlook') const mapImageName = mapXMLTree?.map?.$?.IMAGEFILENAME return this.#loadIcon(mapImageName, true) @@ -1072,7 +1170,7 @@ class modLooker { try { if ( this.#modHandle.exists(fileName) ) { return this.#iconParser.parseDDS( - this.#modHandle.readBin(fileName), + await this.#modHandle.readBin(fileName), true, isMap ) @@ -1489,58 +1587,170 @@ class modLooker { } } -class fileHandler { +/** + * Asynchronous File Handler, ZIP or Folder + * @class + */ +class fileHandlerAsync { + #fileName = null #ZIPFile = null #isFolder = false #log = null #folderName = null - #isOpen = false + #isOpen = false + #allFiles = {} + #contents = new Set() + /** + * + * @param {string} fileName path on disk + * @param {boolean} isFolder is a Folder + * @param {module:modUtilLib~ma_logger} log Logger Class + */ constructor(fileName, isFolder, log) { + this.#fileName = fileName this.#isFolder = isFolder this.#isOpen = true this.#log = log + } - if ( ! fs.existsSync(fileName) ) { + /** + * Open the archive or folder + * @returns {Promise} If the file/folder could be opened + */ + async open() { + if ( ! fs.existsSync(this.#fileName) ) { this.#isOpen = false - this.#log.warning(`File-Manager File-Not-Found: ${fileName}`) - } else if ( ! this.#isFolder ) { - try { - if ( ! fileName.endsWith('.zip') ) { throw new Error('Not a ZIP File') } - this.#ZIPFile = new admZip(fileName) - this.#isOpen = true - } catch (_) { - this.#log.warning(`File-Manager ZIP File Error: ${fileName}`) - this.#isOpen = false - } - } else { - this.#folderName = fileName + this.#log.warning(`File-Manager File-Not-Found :: ${this.#fileName}`) + return false + } + + + if ( this.#isFolder ) { this.#isOpen = true + this.#folderName = this.#fileName + return this.#readList().then(() => { return this.#isOpen }) + } + + if ( !this.#fileName.endsWith('.zip') ) { + this.#isOpen = false + this.#log.warning(`File-Manager Not-ZIP-File :: ${this.#fileName}`) + return false } + + this.#ZIPFile = new StreamZip.async({ storeEntries : true, file : this.#fileName }) + + return this.#readList().then(() => { return this.#isOpen }) } + /** + * @property {boolean} isOpen If the ZIP/Folder is open + */ get isOpen() { return this.#isOpen } - close() { if ( ! this.#isFolder ) { this.#ZIPFile = null } } - - exists(fileName) { - if ( fileName === null ) { return false } + /** + * Read and populate the full file list + * @private + * @instance + * @memberof fileHandlerAsync + * @returns null + */ + async #readList () { if ( this.#isFolder ) { - return fs.existsSync(path.join(this.#folderName, fileName)) + return glob('**', { + cwd : this.#folderName, + follow : true, + mark : true, + stat : true, + withFileTypes : true, + }) + .then((list) => { + for ( const thisItem of list ) { + const normName = path.relative(this.#folderName, path.join(thisItem.path, thisItem.name)).split(path.sep).join(path.posix.sep).concat(thisItem.isDirectory() ? '/' : '') + if ( normName === '' || normName === '/' ) { continue } + this.#contents.add(normName) + this.#allFiles[normName] = { + isFolder : thisItem.isDirectory(), + size : thisItem.size, + } + } + } ) + .catch((err) => { + this.#isOpen = false + this.#log.warning(`File-Manager File-List-Failed :: ${this.#fileName} :: ${err}`) + }) } - return ( this.#ZIPFile.getEntry(fileName) !== null ) + return this.#ZIPFile.entries() + .then((list) => { + for ( const thisItem of Object.keys(list) ) { + this.#contents.add(thisItem) + this.#allFiles[thisItem] = { + isFolder : list[thisItem].isDirectory, + size : list[thisItem].size, + } + } + } ) + .catch((err) => { + this.#isOpen = false + this.#log.warning(`File-Manager File-List-Failed :: ${this.#fileName} :: ${err}`) + }) } - relativeFolder(fileName) { - return path.relative(this.#folderName, fileName) + + /** + * @property {Object} listFiles Object of files and file info + */ + get listFiles() { return this.#allFiles } + /** + * @property {Array} list Array of internal file paths + */ + get list() { return this.#contents } + + /** + * Get extended file info + * @param {string} fileName + * @returns {boolean} fileInfo.isFolder - fileName is a folder + * @returns {number} fileInfo.size - fileName size on disk (0 for folders) + */ + fileInfo(fileName) { return this.#allFiles[fileName] || null } + + /** + * Close the file/folder + */ + close() { + if ( ! this.#isFolder && this.#isOpen ) { + this.#ZIPFile.close().catch((err) => { this.#log.warning(`File-Manager File-Close-Failed :: ${this.#fileName} :: ${err}`) }) + } } - listFiles() { - return ( this.#isFolder ) ? - globSync('**', { cwd : this.#folderName, follow : true, mark : true, stat : true, withFileTypes : true }) : - this.#ZIPFile.getEntries() + /** + * Check for internal file existence + * @param {string} fileName + * @returns {boolean} + */ + exists(fileName) { + if ( fileName === null || !this.#isOpen ) { return false } + + return this.#contents.has(path.normalize(fileName).split(path.sep).join(path.posix.sep)) } + /** + * Get path relative to container (zip or folder) + * @param {string} fileName + * @returns {string} + */ + relativeFolder(fileName) { + if ( this.#isFolder ) { return path.relative(this.#folderName, path.join(this.#folderName, fileName))} + + return path.relative(this.#folderName, fileName) + } + + /** + * Get a new XML Parser + * @static + * @param {string} type + * @returns {XMLParser} + */ static getParser(type) { return new XMLParser({ attributeNamePrefix : '', @@ -1559,45 +1769,74 @@ class fileHandler { }) } - readText(fileName) { return this.#readFile(fileName, true) } - readBin(fileName) { return this.#readFile(fileName, false) } - readXML(fileName, type = 'moddesc', defaultKey = null) { - - const fileContents = this.readText(fileName) - - if ( fileContents === null ) { return null } - - const thisXMLParser = fileHandler.getParser(type) + /** + * Get a file as text + * @param {string} fileName + * @returns {string} + */ + async readText(fileName) { return this.#readFile(fileName, true) } + + /** + * Get a file as a buffer + * @param {string} fileName + * @returns {buffer} + */ + async readBin(fileName) { return this.#readFile(fileName, false) } + /** + * Get a file as an XML tree + * @param {string} fileName + * @param {string} type Named type of XML + * @param {string} defaultKey Default XML key for "root" + * @returns {Object} + */ + async readXML(fileName, type = 'moddesc', defaultKey = null) { + return this.#readFile(fileName, true).then((fileContents) => { + if ( fileContents === null ) { return null } + + const thisXMLParser = fileHandlerAsync.getParser(type) - try { + try { - const thisParsedXML = (defaultKey === null ? thisXMLParser.parse(fileContents) : thisXMLParser.parse(fileContents)?.[defaultKey] ) ?? null - - if ( thisParsedXML === null ) { - this.#log.warning(`XML Parse error or default key not found ${fileName} :: ${defaultKey}`) + const thisParsedXML = (defaultKey === null ? thisXMLParser.parse(fileContents) : thisXMLParser.parse(fileContents)?.[defaultKey] ) ?? null + + if ( thisParsedXML === null ) { + this.#log.warning(`XML Parse error or default key not found ${fileName} :: ${defaultKey}`) + } + + return thisParsedXML + } catch (_) { + this.#log.warning(`Caught unrecoverable XML Parse error : ${fileName}`) + return false } - - return thisParsedXML - } catch (_) { - this.#log.warning(`Caught unrecoverable XML Parse error ${fileName}`) - return false - } + }).catch((err) => { + this.#log.warning(`Could Not Open XML File :: ${fileName} :: ${err}`) + return null + }) } - #readFile(fileName, text = true) { - try { - if ( ! this.exists(fileName) ) { throw new Error('Non-existent') } - - if ( this.#isFolder ) { - return text ? - fs.readFileSync(path.join(this.#folderName, fileName), 'utf8') : - fs.readFileSync(path.join(this.#folderName, fileName), null).buffer - } - return text ? this.#ZIPFile.readAsText(fileName) : this.#ZIPFile.readFile(fileName).buffer - } catch (err) { - this.#log.debug(`File-Manager file read error ${fileName} :: ${err.message}`) - return null + /** + * Get file contents + * @private + * @instance + * @memberof fileHandlerAsync + * @param {string} fileName + * @param {boolean} text Return as string + * @returns {(string|buffer|null)} + */ + async #readFile(fileName, text = true) { + if ( fileName === null ) { return null } + if ( ! this.#isOpen ) { throw new Error('File-Manager Mod-Not-Open')} + if ( ! this.exists(fileName) ) { throw new Error('File-Manager File-Not-Found') } + + if ( this.#isFolder ) { + return fsPromise.readFile(path.join(this.#folderName, fileName)) + .then((fileBuffer) => { return text ? fileBuffer.toString() : fileBuffer.buffer }) + .catch((err) => { this.#log.debug(`File-Manager File-Read-Error :: ${fileName} :: ${err}`) }) } + + return this.#ZIPFile.entryData(fileName) + .then((fileBuffer) => { return text ? fileBuffer.toString() : fileBuffer.buffer }) + .catch((err) => { this.#log.debug(`File-Manager File-Read-Error :: ${fileName} :: ${err}`) }) } } @@ -2110,10 +2349,10 @@ class ddsDecoder { } module.exports = { - ddsDecoder : ddsDecoder, - fileHandler : fileHandler, - logCollector : logCollector, - modFileChecker : modFileChecker, - modLooker : modLooker, - requiredItems : requiredItems, + ddsDecoder : ddsDecoder, + fileHandlerAsync : fileHandlerAsync, + logCollector : logCollector, + modFileChecker : modFileChecker, + modLooker : modLooker, + requiredItems : requiredItems, } \ No newline at end of file diff --git a/modAssist_main.js b/modAssist_main.js index 9b6977c3..76ba5e0b 100644 --- a/modAssist_main.js +++ b/modAssist_main.js @@ -32,6 +32,16 @@ const __ = (x) => serveIPC.l10n.syncStringLookup(x) class queueEmitter extends EventEmitter {} const modQueueRunner = new queueEmitter() +/** + * @typedef referenceFunctions + * @property {function} gameLauncher launch the game + * @property {function} processModFolders process mod folders + * @property {function} readGameLog read the game log + * @property {function} refreshClientModList refresh mod list in client + * @property {function} refreshTransientStatus refresh status flags in client + * @property {function} toggleMiniWindow toggle mini window on and off + */ + serveIPC.refFunc = { gameLauncher : funcLib.gameLauncher, processModFolders : processModFolders, @@ -108,16 +118,24 @@ ipcMain.on('toMain_addFolder_direct', (event, potentialFolder) => { funcLib.processor.addFolderTracking(potentialFolder) event.sender.send( 'fromMain_allSettings', ...funcLib.commonSend.settings() ) }) -ipcMain.on('toMain_addFolder', () => { +ipcMain.on('toMain_addFolder', (notify_import = false) => { funcLib.general.showFileDialog({ defaultPath : serveIPC.path.last ?? app.getPath('home'), filterAll : false, + parent : notify_import ? 'importjson' : 'main', callback : (result) => { const potentialFolder = result.filePaths[0] serveIPC.path.last = path.resolve(path.join(potentialFolder, '..')) - funcLib.processor.addFolderTracking(potentialFolder) + funcLib.processor.addFolderTracking(potentialFolder, notify_import) + if ( notify_import ) { + serveIPC.windowLib.sendToValidWindow('importjson', 'fromMain_importFolder', { + folder : result.filePaths[0], + collectKey : serveIPC.modCollect.getFolderHash(result.filePaths[0]), + contents : fs.readdirSync(result.filePaths[0]).length, + }) + } processModFolders() }, }) @@ -160,7 +178,10 @@ ipcMain.on('toMain_dropFolder', (_, newFolder) => { funcLib.processor.addFolderTracking(newFolder) processModFolders() }) -ipcMain.on('toMain_dropFiles', (_, files) => { +ipcMain.on('toMain_import_json_download', (_, collectKey, uri, unpack) => { + funcLib.general.importJSON_download(uri, unpack, collectKey) +}) +ipcMain.on('toMain_dropFiles', async (_, files) => { if ( files.length === 1 && files[0].endsWith('.csv') ) { new csvFileChecker(files[0]).getInfo().then((results) => { serveIPC.windowLib.createNamedWindow('save', { @@ -169,8 +190,10 @@ ipcMain.on('toMain_dropFiles', (_, files) => { }) }) return + } else if ( files.length === 1 && files[0].endsWith('.json') ) { + funcLib.general.importJSON_process(files[0]) } else if ( files.length === 1 && files[0].endsWith('.zip') ) { - sendCopyMoveDelete('import', null, null, files, new modPackChecker(files[0]).getInfo()) + sendCopyMoveDelete('import', null, null, files, await new modPackChecker(files[0]).getInfo()) } else { sendCopyMoveDelete('import', null, null, files, false) } @@ -642,7 +665,7 @@ ipcMain.on('toMain_cleanCacheFile', () => { setTimeout(() => { serveIPC.windowLib.sendToValidWindow('main', 'fromMain_l10n_refresh', serveIPC.l10n.currentLocale) - }, 1000) + }, 500) }) ipcMain.on('toMain_setPrefFile', (_, version) => { funcLib.prefs.changeFilePath(version, false) }) ipcMain.on('toMain_setGamePath', (_, version) => { funcLib.prefs.changeFilePath(version, true) }) @@ -671,12 +694,13 @@ function openWizard() { // Collection Settings Operation (notes) function openNotesWindow(collectKey) { serveIPC.windowLib.createNamedWindow('notes', { - collectKey : collectKey, - lastGameSettings : serveIPC.gameSetOverride.xml, + collectKey : collectKey, + isActiveCollection : serveIPC.gameSetOverride.index === collectKey, + lastGameSettings : serveIPC.gameSetOverride.xml, }) } ipcMain.on('toMain_openNotes', (_, collectKey) => { openNotesWindow(collectKey) }) -ipcMain.on('toMain_setNote', (_, id, value, collectKey) => { +ipcMain.on('toMain_setNote', (_, id, value, collectKey, json_import = false) => { const cleanValue = ( id === 'notes_version' ) ? parseInt(value, 10) : value if ( cleanValue === '' ) { @@ -685,10 +709,16 @@ ipcMain.on('toMain_setNote', (_, id, value, collectKey) => { serveIPC.storeNote.set(`${collectKey}.${id}`, cleanValue) } - openNotesWindow(collectKey) + if ( ! json_import ) { + openNotesWindow(collectKey) + } }) ipcMain.on('toMain_setModInfo', (_, mod, site) => { - serveIPC.storeSites.set(mod, site) + if ( site === '' || site === null ) { + serveIPC.storeSites.delete(mod) + } else { + serveIPC.storeSites.set(mod, site) + } refreshClientModList() }) // END : Collection Settings Operation (notes) @@ -874,6 +904,7 @@ function refreshClientModList(closeLoader = true) { foldersEdit : serveIPC.isFoldersEdit, gameRunning : serveIPC.isGameRunning, gameRunningEnable : serveIPC.isGamePolling, + isDev : !app.isPackaged, l10n : { disable : __('override_disabled'), unknown : __('override_unknown'), diff --git a/package.json b/package.json index f4d54777..de561027 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fsg-mod-assistant", - "version": "3.5.2", + "version": "3.9.81", "description": "FSG Farm Sim Mod Assistant", "main": "modAssist_main.js", "homepage": "https://github.com/FSGModding/FSG_Mod_Assistant#readme", @@ -19,30 +19,35 @@ "pack": "electron-builder --dir", "dist": "electron-builder", "test": "node ./test/test.js", - "depends": "node ./test/outdated-deps.js" + "depends": "node ./test/outdated-deps.js", + "generate-docs": "node_modules\\.bin\\jsdoc -c .jsdoc.json" }, "devDependencies": { - "@babel/core": "^7.23.9", - "@babel/eslint-parser": "^7.23.10", + "@babel/core": "^7.24.3", + "@babel/eslint-parser": "^7.24.1", "ansi-colors": "^4.1.3", "clean-css": "^5.3.3", - "deepl-node": "^1.11.1", - "electron": "^28.2.2", - "electron-builder": "^24.13.0", - "eslint": "^8.56.0", + "deepl-node": "^1.12.0", + "docdash": "^2.0.2", + "electron": "^29.1.6", + "electron-builder": "^24.13.3", + "eslint": "^8.57.0", "eslint-plugin-unicorn": "^51.0.1", - "html-validate": "^8.9.1" + "html-validate": "^8.18.1", + "jsdoc": "^4.0.2" }, "dependencies": { - "adm-zip": "^0.5.10", - "archiver": "^6.0.1", + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "archiver": "^7.0.1", "discord-rpc": "^4.0.1", - "electron-store": "^8.1.0", - "electron-updater": "^6.1.8", - "fast-xml-parser": "^4.3.4", - "glob": "^10.3.10", + "electron-store": "^8.2.0", + "electron-updater": "^6.2.1", + "fast-xml-parser": "^4.3.6", + "glob": "^10.3.12", + "node-stream-zip": "^1.15.0", "unzip-stream": "^0.3.1", - "wasm-vips": "^0.0.7" + "wasm-vips": "^0.0.8" }, "build": { "appId": "jtsage.fsmodassist", diff --git a/renderer/img/fsg_back_3.svg b/renderer/img/fsg_back_3.svg new file mode 100644 index 00000000..2fc3f1f1 --- /dev/null +++ b/renderer/img/fsg_back_3.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/renderer/importjson.html b/renderer/importjson.html new file mode 100644 index 00000000..610327b3 --- /dev/null +++ b/renderer/importjson.html @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + +
+
+
+
+

+
+
+
+
+
+
+ +
+
+ +
+ + +
+
+ +
+
+ +
+ +
20
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+ + +
+
+ +
+
+
+ +
+
+
+
+
+ +
+ + + + + \ No newline at end of file diff --git a/renderer/inc/fsico.css b/renderer/inc/fsico.css index 0e146b60..4761c133 100644 --- a/renderer/inc/fsico.css +++ b/renderer/inc/fsico.css @@ -12,6 +12,7 @@ .fsico-fill-alfalfa-windrow::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-anhydrous::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-armoire::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-bakedpoppy::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-balenet::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-baletwine::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-barley::before { background-image: url("data:image/svg+xml,"); } @@ -24,10 +25,14 @@ .fsico-fill-bowl::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-bread::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-bucket::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-bun::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-butter::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cake::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-canola::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-carp::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-carrot::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-carrotjuice::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-carrotsalad::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cartonroll::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cattree::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cereals::before { background-image: url("data:image/svg+xml,"); } @@ -35,14 +40,19 @@ .fsico-fill-chair::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cheese::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-chicken::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-chickenfeed::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-chips::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-chocolate::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-chocolateicecream::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-clothes::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-clover::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-clover-windrow::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-coleslaw::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-compost::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cornstalks::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cotton::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-cow::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-cream::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-def::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-diesel::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-digestate::before { background-image: url("data:image/svg+xml,"); } @@ -52,22 +62,30 @@ .fsico-fill-dryclover-windrow::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-drygrass-windrow::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-drymaize2::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-duck::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-easel::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-egg::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-electriccharge::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-emptypallet::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-ethanol::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-fabric::before { background-image: url("data:image/svg+xml,"); } -.fsico-fill-fertilizer::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-fertilizer::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-fishfeed::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-fishflour::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-floortiles::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-flour::before { background-image: url("data:image/svg+xml,"); } -.fsico-fill-forage::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-forage::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-forage-mixing::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-frenchfries::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-furniture::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-goat::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-goatcheese::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-goatmilk::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-grape::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-grapejuice::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-grass::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-grass-windrow::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-greensalad::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-haypellets::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-herbicide::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-honey::before { background-image: url("data:image/svg+xml,"); } @@ -79,19 +97,24 @@ .fsico-fill-liquidmanure::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-maize::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-manure::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-mashedpotato::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-metal::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-methane::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-milk::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-mineralfeed::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-mohn::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-molasses::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-oat::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-oatdrink::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-oatmeal::before { background-image: url("data:image/svg+xml,"); } -.fsico-fill-oatmilk::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-oatmilk::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-oilradish::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-oil-canola::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-oil-olive::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-oil-sunflower::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-olive::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-onion::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-onionjuice::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-paperroll::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-parsnip::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-pasta::before { background-image: url("data:image/svg+xml,"); } @@ -102,15 +125,23 @@ .fsico-fill-pictureframe::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-pig::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-pigfood::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-pike::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-pizza::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-plankslong::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-pomace::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-poplar::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-poppyseedbun::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-potato::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-potatopancake::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-potatosalad::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-praline::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-prefabwall::before { background-image: url("data:image/svg+xml,"); } -.fsico-fill-pressurewasheradditive::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-pressurewasheradditive::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-product::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-propane::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-raisins::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-redbeet::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-redcabbage::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-roadsalt::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-rockpowder::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-roundbalecotton::before { background-image: url("data:image/svg+xml,"); } @@ -119,6 +150,9 @@ .fsico-fill-roundbalesilage::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-roundbalestraw::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-roundbalewood::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-rye::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-salmon::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-salt::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-seeds::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-seedtreatingliquid::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sheep::before { background-image: url("data:image/svg+xml,"); } @@ -126,6 +160,7 @@ .fsico-fill-silage::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-silageadditive::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-snow::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-sodiumchloride::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sorghum::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-soup-carrot::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-soup-parsnip::before { background-image: url("data:image/svg+xml,"); } @@ -133,7 +168,10 @@ .fsico-fill-soup-redbeet::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-soup-triple::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-soybean::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-soydrink::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-soyflour::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-soymilk::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-spelt::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-squarebalecotton::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-squarebalegrass::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-squarebalehay::before { background-image: url("data:image/svg+xml,"); } @@ -144,20 +182,30 @@ .fsico-fill-stone::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-straw::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-strawberry::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-strawberrycreamcake::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-strawberryicecream::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-strawberryjuice::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-strawpellets::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sugar::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sugarbeet::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sugarbeetcut::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sugarcane::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-sun::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-sunflower::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-table::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-tarp::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-tomato::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-tomatojuice::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-tomatosalad::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-unknown::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-vinegar::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-vitamins::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-water::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-weed::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-wheat::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-wheatsemolina::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-whiskey::before { background-image: url("data:image/svg+xml,"); } +.fsico-fill-whitecabbage::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-wine::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-wood::before { background-image: url("data:image/svg+xml,"); } .fsico-fill-woodbeam::before { background-image: url("data:image/svg+xml,"); } diff --git a/renderer/main.html b/renderer/main.html index d0d89368..bb6c6215 100644 --- a/renderer/main.html +++ b/renderer/main.html @@ -149,6 +149,12 @@ background-position: bottom 0px left 30px !important; background-blend-mode: multiply !important; } + .fsg-back-3 { + background-image: url(img/fsg_back_3.svg); + background-size: 70vh !important; + background-position: bottom 35px left 30px !important; + background-blend-mode: multiply !important; + } .fake-disable { opacity: .65;} @@ -769,9 +775,9 @@
- - -
+ + +
@@ -918,20 +924,20 @@
{{mapMultiButton}} - - - + + + - open admin - - - +   + + +
- +
diff --git a/renderer/notes.html b/renderer/notes.html index 88a1769b..3164e174 100644 --- a/renderer/notes.html +++ b/renderer/notes.html @@ -59,12 +59,13 @@

-
- -
+ +
diff --git a/renderer/preload/preload-importjsonWindow.js b/renderer/preload/preload-importjsonWindow.js new file mode 100644 index 00000000..7e5eb314 --- /dev/null +++ b/renderer/preload/preload-importjsonWindow.js @@ -0,0 +1,106 @@ +/* _______ __ _______ __ __ + | | |.-----.--| | _ |.-----.-----.|__|.-----.| |_ + | || _ | _ | ||__ --|__ --|| ||__ --|| _| + |__|_|__||_____|_____|___|___||_____|_____||__||_____||____| + (c) 2022-present FSG Modding. MIT License. */ + +// Main window preLoad + +const {contextBridge, ipcRenderer} = require('electron') + +contextBridge.exposeInMainWorld( + 'log', { + danger : (text, process) => { ipcRenderer.send('toMain_log', 'danger', `render-${process}`, text) }, + debug : (text, process) => { ipcRenderer.send('toMain_log', 'debug', `render-${process}`, text) }, + info : (text, process) => { ipcRenderer.send('toMain_log', 'info', `render-${process}`, text) }, + log : (text, process) => { ipcRenderer.send('toMain_log', 'debug', `render-${process}`, text) }, + notice : (text, process) => { ipcRenderer.send('toMain_log', 'notice', `render-${process}`, text) }, + warning : (text, process) => { ipcRenderer.send('toMain_log', 'warning', `render-${process}`, text) }, + } +) + +contextBridge.exposeInMainWorld( + 'l10n', { + langList_change : ( lang ) => { ipcRenderer.send('toMain_langList_change', lang) }, + langList_send : () => { ipcRenderer.send('toMain_langList_send') }, + themeList_change : ( theme ) => { ipcRenderer.send('toMain_themeList_change', theme) }, + themeList_send : () => { ipcRenderer.send('toMain_themeList_send') }, + + getLocale : () => { return ipcRenderer.sendSync('toMain_getText_locale') }, + getText_send : ( text ) => { ipcRenderer.send('toMain_getText_send', text) }, + getText_sync : ( items ) => { return ipcRenderer.sendSync('toMain_getText_sync', items) }, + getTextBase_send : ( text ) => { ipcRenderer.send('toMain_getTextBase_send', text) }, + receive : ( channel, func ) => { + const validChannels = new Set([ + 'fromMain_getText_return', + 'fromMain_getText_return_base', + 'fromMain_getText_return_title', + 'fromMain_langList_return', + 'fromMain_themeList_return', + 'fromMain_l10n_refresh' + ]) + + if ( validChannels.has( channel ) ) { + ipcRenderer.on( channel, ( _, ...args ) => func( ...args )) + } + }, + } +) + +contextBridge.exposeInMainWorld( + 'loader', { + receive : ( channel, func ) => { + const validChannels = new Set([ + 'formMain_loading_show', + 'formMain_loading_hide', + 'formMain_loadingTitles', + 'fromMain_loadingDownload', + 'fromMain_loadingNoCount', + 'fromMain_loading_total', + 'fromMain_loading_current', + ]) + + if ( validChannels.has( channel ) ) { + ipcRenderer.on( channel, ( _, ...args ) => func( ...args )) + } + }, + } +) + +contextBridge.exposeInMainWorld( + 'mods', { + cancelDownload : () => { ipcRenderer.send('toMain_cancelDownload') }, + + doDownload : ( collectKey, uri, unpack ) => { ipcRenderer.send('toMain_import_json_download', collectKey, uri, unpack) }, + doReload : ( ) => { ipcRenderer.send('toMain_refreshFolders') }, + setFolder : ( ) => { ipcRenderer.send('toMain_addFolder', true) }, + setNote : ( id, value, collection ) => ipcRenderer.send('toMain_setNote', id, value, collection, true), + + receive : ( channel, func ) => { + const validChannels = new Set([ + 'fromMain_importData', + 'fromMain_importFolder', + ]) + + if ( validChannels.has( channel ) ) { + ipcRenderer.on( channel, ( _, ...args ) => func( ...args )) + } + }, + } +) + +contextBridge.exposeInMainWorld( + 'win_ops', { + closeWindow : () => { ipcRenderer.send('toMain_closeSubWindow') }, + receive : ( channel, func ) => { + const validChannels = new Set([ + 'fromMain_clearTooltips', + 'fromMain_themeSetting', + ]) + + if ( validChannels.has( channel ) ) { + ipcRenderer.on( channel, ( _, ...args ) => func( ...args )) + } + }, + } +) diff --git a/renderer/renderJS/importjson_ui.js b/renderer/renderJS/importjson_ui.js new file mode 100644 index 00000000..c37eb300 --- /dev/null +++ b/renderer/renderJS/importjson_ui.js @@ -0,0 +1,163 @@ +/* _______ __ _______ __ __ + | | |.-----.--| | _ |.-----.-----.|__|.-----.| |_ + | || _ | _ | ||__ --|__ --|| ||__ --|| _| + |__|_|__||_____|_____|___|___||_____|_____||__||_____||____| + (c) 2022-present FSG Modding. MIT License. */ + +// Import Collection window UI + +/* global processL10N, fsgUtil, __, bootstrap */ + + +const formatEmpty = (text) => text !== '' ? text : `${__('import_json_not_set')}` +const formatBool = (value) => value ? + ` ${__('import_json_yes')}`: + ` ${__('import_json_no')}` + +const downloadButton = (uri, isPack = true) => { + const thisID = crypto.randomUUID() + return [ + `
`, + isPack ? __('import_json_step_2_download_unpack') : __('import_json_step_2_download'), + ' :: ', + uri, + '
' + ].join('') +} + +let importJSON = null +let importKey = null + +window.mods.receive('fromMain_importFolder', (folderData) => { + importKey = folderData.collectKey + fsgUtil.setById('folder_location', folderData.folder) + fsgUtil.clsShowTrue('folder_location_empty', folderData.contents === 0 ) + fsgUtil.clsShowTrue('folder_location_not_empty', folderData.contents !== 0 ) + fsgUtil.clsEnable('apply_button') + fsgUtil.clsRemoveFromAll('.btn-download', 'disabled') +}) + +window.mods.receive('fromMain_importData', (modCollect) => { + importJSON = modCollect.opts.thisImport + fsgUtil.setById('notes_collection_color', fsgUtil.getIconSVG('folder', false, false, false, importJSON.collection_color )) + fsgUtil.setById('notes_collection_description', formatEmpty(importJSON.collection_description)) + fsgUtil.setById('notes_game_version', formatEmpty(importJSON.game_version)) + fsgUtil.setById('notes_server_name', formatEmpty(importJSON.server_name)) + fsgUtil.setById('notes_server_id', formatEmpty(importJSON.server_id)) + fsgUtil.setById('notes_server_password', formatEmpty(importJSON.server_password)) + fsgUtil.setById('notes_server_website', formatEmpty(importJSON.server_website)) + fsgUtil.setById('notes_server_downloads', formatBool(importJSON.server_downloads)) + fsgUtil.setById('notes_force_frozen', formatBool(importJSON.force_frozen)) + + const packDL = importJSON.download_unzip.map((x) => downloadButton(x, true) ) + const singleDL = importJSON.download_direct.map((x) => downloadButton(x, false) ) + fsgUtil.setById('import_mod_packs', packDL.join('')) + fsgUtil.setById('import_mod_singles', singleDL.join('')) + + processL10N() +}) + +function clientDoDownload(id, uri, isPack) { + fsgUtil.clsDelId(`but_${id}`, 'btn-primary') + fsgUtil.clsAddId(`but_${id}`, 'btn-success', 'disabled') + window.mods.doDownload(importKey, uri, isPack) +} + +function clientDoSettings() { + window.mods.setNote('notes_color', importJSON.collection_color.toString(), importKey) + window.mods.setNote('notes_tagline', importJSON.collection_description, importKey) + window.mods.setNote('notes_version', importJSON.game_version, importKey) + window.mods.setNote('notes_server', importJSON.server_name, importKey) + window.mods.setNote('notes_fsg_bot', importJSON.server_id, importKey) + window.mods.setNote('notes_password', importJSON.server_password, importKey) + window.mods.setNote('notes_website', importJSON.server_website, importKey) + window.mods.setNote('notes_websiteDL', importJSON.server_downloads, importKey) + window.mods.setNote('notes_frozen', importJSON.force_frozen, importKey) + fsgUtil.clsDelId('apply_button', 'btn-primary') + fsgUtil.clsAddId('apply_button', 'btn-success') + window.mods.doReload() +} + +const loaderLib = { + overlay : null, + + lastTotal : 1, + startTime : Date.now(), + + hide : () => { loaderLib.overlay?.hide() }, + show : () => { loaderLib.overlay?.show() }, + + hideCount : () => { + fsgUtil.clsHide('loadOverlay_statusCount') + fsgUtil.clsHide('loadOverlay_statusProgBar') + }, + startDownload : () => { + loaderLib.startTime = Date.now() + fsgUtil.clsShow('loadOverlay_downloadCancel') + fsgUtil.clsShow('loadOverlay_speed') + }, + updateCount : (count, inMB = false) => { + const thisCount = inMB ? fsgUtil.bytesToMB(count, false) : count + const thisElement = fsgUtil.byId('loadOverlay_statusCurrent') + const thisProg = fsgUtil.byId('loadOverlay_statusProgBarInner') + const thisPercent = `${Math.ceil((count / loaderLib.lastTotal) * 100)}%` || '0%' + + if ( thisProg !== null ) { thisProg.style.width = thisPercent } + + if ( thisElement !== null ) { thisElement.innerHTML = thisCount } + + if ( inMB ) { + const perDone = Math.max(1, Math.ceil((count / loaderLib.lastTotal) * 100)) + const perRem = 100 - perDone + const elapsedSec = (Date.now() - loaderLib.startTime) / 1000 + const estSpeed = fsgUtil.bytesToMBCalc(count, false) / elapsedSec // MB/sec + const secRemain = elapsedSec / perDone * perRem + + const prettyMinRemain = Math.floor(secRemain / 60) + const prettySecRemain = secRemain % 60 + + fsgUtil.setById('loadOverlay_speed_speed', `${estSpeed.toFixed(1)} MB/s`) + fsgUtil.setById('loadOverlay_speed_time', `~ ${prettyMinRemain.toFixed(0).padStart(2, '0')}:${prettySecRemain.toFixed(0).padStart(2, '0')}`) + } + }, + updateText : (mainTitle, subTitle, dlCancel) => { + fsgUtil.setById('loadOverlay_statusMessage', mainTitle) + fsgUtil.setById('loadOverlay_statusDetail', subTitle) + fsgUtil.setById('loadOverlay_statusTotal', '0') + fsgUtil.setById('loadOverlay_statusCurrent', '0') + fsgUtil.setById('loadOverlay_downloadCancelButton', dlCancel) + + fsgUtil.clsShow('loadOverlay_statusCount') + fsgUtil.clsShow('loadOverlay_statusProgBar') + + fsgUtil.clsHide('loadOverlay_downloadCancel') + fsgUtil.clsHide('loadOverlay_speed') + + loaderLib.show() + }, + updateTotal : (count, inMB = false) => { + if ( inMB ) { loaderLib.startTime = Date.now() } + const thisCount = inMB ? fsgUtil.bytesToMB(count) : count + fsgUtil.setById('loadOverlay_statusTotal', thisCount) + loaderLib.lastTotal = ( count < 1 ) ? 1 : count + }, +} + +window.addEventListener('DOMContentLoaded', () => { + loaderLib.overlay = new bootstrap.Modal('#loadOverlay', { backdrop : 'static', keyboard : false }) +}) + +// Loader Overlay +window.loader.receive('formMain_loading_show', () => { loaderLib.show() }) +window.loader.receive('formMain_loading_hide', () => { loaderLib.hide() }) +window.loader.receive('fromMain_loadingDownload', () => { loaderLib.startDownload() }) +window.loader.receive('fromMain_loadingNoCount', () => { loaderLib.hideCount() }) +window.loader.receive('formMain_loadingTitles', (mainTitle, subTitle, dlCancel) => { + loaderLib.updateText(mainTitle, subTitle, dlCancel) +}) +window.loader.receive('fromMain_loading_total', (count, inMB) => { + loaderLib.updateTotal(count, inMB) +}) +window.loader.receive('fromMain_loading_current', (count, inMB ) => { + loaderLib.updateCount(count, inMB) +}) diff --git a/renderer/renderJS/main_ui.js b/renderer/renderJS/main_ui.js index 6ed173fd..346cc9b9 100644 --- a/renderer/renderJS/main_ui.js +++ b/renderer/renderJS/main_ui.js @@ -70,6 +70,9 @@ window.mods.receive('fromMain_allSettings', (allSettings, devControls) => { }) window.mods.receive('fromMain_modList', (modCollect) => { + if ( modCollect.opts.isDev ) { + fsgUtil.clsAddId('drag_target', 'fsg-back-3') + } mainState.isMultiVersion = modCollect.appSettings.multi_version mainState.currentGameVersion = modCollect.appSettings.game_version mainState.modCollect = modCollect @@ -210,7 +213,12 @@ window.mods.receive('fromMain_modList', (modCollect) => { select_lib.new_tag_reset() select_lib.clear_range() - openCurrentTable(lastOpenID, lastOpenQ, scrollStart, modCollect.opts.cacheGameSave?.collectKey) + openCurrentTable( + lastOpenID, + lastOpenQ, + modCollect.opts.foldersEdit ? mainState.lastFolderScroll : scrollStart, + modCollect.opts.cacheGameSave?.collectKey + ) select_lib.filter_begin() processL10N() @@ -352,7 +360,7 @@ window.addEventListener('DOMContentLoaded', () => { }) fsgUtil.byId('prefcanvas').addEventListener('hide.bs.offcanvas', () => { dragLib.preventRun = false - fsgUtil.clearTooltips() + fsgUtil.clearTooltipsXX() }) fsgUtil.byId('prefcanvas').addEventListener('show.bs.offcanvas', () => { fsgUtil.byId('prefcanvas').querySelector('.offcanvas-body').scrollTop = 0 diff --git a/renderer/renderJS/main_ui_lib.js b/renderer/renderJS/main_ui_lib.js index cd633731..2eb0fad6 100644 --- a/renderer/renderJS/main_ui_lib.js +++ b/renderer/renderJS/main_ui_lib.js @@ -17,6 +17,7 @@ const mainState = { all : {}, }, isMultiVersion : false, + lastFolderScroll : 0, modCollect : null, searchStringMap : {}, searchTagMap : {}, @@ -290,13 +291,20 @@ const mainLib = { } return null }, + removeItemOrder : (collectKey) => { + fsgUtil.clearTooltipsXX() + window.mods.removeFolder(collectKey) + }, setItemOrder : (collectKey, moveUpInList, forceLast = false) => { const curIndex = mainState.collectOrder.map[collectKey] const newIndex = forceLast ? moveUpInList ? 0 : mainState.collectOrder.max : moveUpInList ? mainLib.getOrderPrev(collectKey) : mainLib.getOrderNext(collectKey) - + + fsgUtil.clearTooltipsXX() + if ( curIndex !== null && newIndex !== null ) { + mainState.lastFolderScroll = fsgUtil.byId('mod-collections').offsetParent.scrollTop window.mods.reorderFolder(curIndex, newIndex) } }, @@ -335,6 +343,7 @@ const mainLib = { fsgUtil.valueById('collectionSelect', mainState.gameSetCollect.selected) window.mods.startFarmSim() }, + } const actionLib = { @@ -371,6 +380,12 @@ const actionLib = { ) mainState.win.modInfo.hide() }, + + doCacheClean : () => { + fsgUtil.setById('clean_cache_size', '') + fsgUtil.setById('clean_detail_cache_size', '') + window.mods.cleanCache() + }, } const prefLib = { diff --git a/renderer/renderJS/notes_ui.js b/renderer/renderJS/notes_ui.js index 24a0b7d0..e5004caa 100644 --- a/renderer/renderJS/notes_ui.js +++ b/renderer/renderJS/notes_ui.js @@ -13,10 +13,9 @@ let thisCollection = null window.mods.receive('fromMain_collectionName', (modCollect) => { thisCollection = modCollect.opts.collectKey - if ( !modCollect.appSettings.multi_version ) { - fsgUtil.clsHide('multi_version') - } - + fsgUtil.clsHideFalse('is_active_collect', modCollect.opts.isActiveCollection) + fsgUtil.clsHideFalse('multi_version', modCollect.appSettings.multi_version) + fsgUtil.setById('collection_name', modCollect.collectionToName[thisCollection]) for ( const element of fsgUtil.query('input') ) { @@ -157,4 +156,12 @@ window.addEventListener('DOMContentLoaded', () => { const thisNum = parseInt(thisLabel.getAttribute('for').replace('notes_color', '')) thisLabel.innerHTML = fsgUtil.getIconSVG('folder', false, false, false, thisNum, true) } -}) \ No newline at end of file +}) + +window.onbeforeunload = (e) => { + if ( document.activeElement.tagName === 'INPUT' ) { + e.returnValue = false + document.activeElement.blur() + setTimeout(() => { window.close()}, 250) + } +} \ No newline at end of file diff --git a/renderer/renderJS/savemanage_ui.js b/renderer/renderJS/savemanage_ui.js index 0e656a76..96b618ff 100644 --- a/renderer/renderJS/savemanage_ui.js +++ b/renderer/renderJS/savemanage_ui.js @@ -20,8 +20,8 @@ window.mods.receive('fromMain_saveInfo', (modCollect) => { uuidMap = {} const theseSaves = modCollect.opts.saveInfo - const activeIDS = Object.keys(theseSaves).filter((x) => theseSaves[x].active !== false && theseSaves[x].active.error === false) - const backIDS = Object.keys(theseSaves).filter((x) => theseSaves[x].backups.length !== 0 ) + const activeIDS = Object.keys(theseSaves).filter((x) => typeof theseSaves[x].active === 'object' && theseSaves[x].active?.error === false) + const backIDS = Object.keys(theseSaves).filter((x) => Array.isArray(theseSaves[x].backups) && theseSaves[x].backups.length !== 0 ) const activeGameHTML = [] const backGameHTML = [] diff --git a/renderer/renderJS/util/cropcal.js b/renderer/renderJS/util/cropcal.js index a34768b6..46e949fb 100644 --- a/renderer/renderJS/util/cropcal.js +++ b/renderer/renderJS/util/cropcal.js @@ -28,6 +28,13 @@ const knownCrops = { olive : { name : 'croptype_olive', icon : 'olive' }, clover : { name : 'croptype_clover', icon : 'clover' }, alfalfa : { name : 'croptype_alfalfa', icon : 'alfalfa' }, + onion : { name : 'croptype_onion', icon : 'onion' }, + carrot : { name : 'croptype_carrot', icon : 'carrot' }, + whitecabbage : { name : 'croptype_whitecabbage', icon : 'whitecabbage' }, + redcabbage : { name : 'croptype_redcabbage', icon : 'redcabbage' }, + rye : { name : 'croptype_rye', icon : 'rye' }, + poppy : { name : 'croptype_poppy', icon : 'poppy' }, + spelt : { name : 'croptype_spelt', icon : 'spelt' }, } /* eslint-enable sort-keys */ diff --git a/renderer/renderJS/util/fillType.js b/renderer/renderJS/util/fillType.js index 9e92aea7..887549ee 100644 --- a/renderer/renderJS/util/fillType.js +++ b/renderer/renderJS/util/fillType.js @@ -10,13 +10,14 @@ /* cSpell:disable */ /* eslint-disable comma-spacing, quotes */ -const ft_known = new Set(["cat-attach-has","cat-attach-need","cat-brand","cat-object","cat-placeable","cat-tool","cat-vehicle","fill-air","fill-alfalfa","fill-alfalfa-windrow","fill-anhydrous","fill-armoire","fill-balenet","fill-baletwine","fill-barley","fill-barrel","fill-bathtub","fill-beanstraw","fill-beer","fill-birdhouse","fill-boards","fill-bowl","fill-bread","fill-bucket","fill-butter","fill-cake","fill-canola","fill-carrot","fill-cartonroll","fill-cattree","fill-cereals","fill-chaff","fill-chair","fill-cheese","fill-chicken","fill-chips","fill-chocolate","fill-clothes","fill-clover","fill-clover-windrow","fill-cornstalks","fill-cotton","fill-cow","fill-def","fill-diesel","fill-digestate","fill-dog","fill-doghouse","fill-dryalfalfa-windrow","fill-dryclover-windrow","fill-drygrass-windrow","fill-drymaize2","fill-easel","fill-egg","fill-electriccharge","fill-emptypallet","fill-ethanol","fill-fabric","fill-fertilizer","fill-floortiles","fill-flour","fill-forage","fill-forage-mixing","fill-furniture","fill-grape","fill-grapejuice","fill-grass","fill-grass-windrow","fill-haypellets","fill-herbicide","fill-honey","fill-horse","fill-ironore","fill-lettuce","fill-lime","fill-liquidfertilizer","fill-liquidmanure","fill-maize","fill-manure","fill-metal","fill-methane","fill-milk","fill-mineralfeed","fill-molasses","fill-oat","fill-oatmeal","fill-oatmilk","fill-oilradish","fill-oil-canola","fill-oil-olive","fill-oil-sunflower","fill-olive","fill-paperroll","fill-parsnip","fill-pasta","fill-peppergrinder","fill-pickle-carrot","fill-pickle-parsnip","fill-pickle-redbeet","fill-pictureframe","fill-pig","fill-pigfood","fill-plankslong","fill-poplar","fill-potato","fill-prefabwall","fill-pressurewasheradditive","fill-product","fill-propane","fill-raisins","fill-redbeet","fill-roadsalt","fill-rockpowder","fill-roundbalecotton","fill-roundbalegrass","fill-roundbalehay","fill-roundbalesilage","fill-roundbalestraw","fill-roundbalewood","fill-seeds","fill-seedtreatingliquid","fill-sheep","fill-shingle","fill-silage","fill-silageadditive","fill-snow","fill-sorghum","fill-soup-carrot","fill-soup-parsnip","fill-soup-potato","fill-soup-redbeet","fill-soup-triple","fill-soybean","fill-soymilk","fill-squarebalecotton","fill-squarebalegrass","fill-squarebalehay","fill-squarebalesilage","fill-squarebalestraw","fill-squarebalewood","fill-staircaserailing","fill-stone","fill-straw","fill-strawberry","fill-strawpellets","fill-sugar","fill-sugarbeet","fill-sugarbeetcut","fill-sugarcane","fill-sunflower","fill-table","fill-tarp","fill-tomato","fill-unknown","fill-water","fill-weed","fill-wheat","fill-whiskey","fill-wine","fill-wood","fill-woodbeam","fill-woodchips","fill-wool","look-beacons","look-diesel","look-electric","look-engine","look-fillunit","look-income","look-info","look-key","look-lights","look-methane","look-objects","look-paintable","look-price","look-prod-cycle","look-prod-input","look-prod-output","look-speed","look-speedlimit","look-timer","look-transmission","look-weight","look-wheels","look-width","look-year","ma-large","ma-small","season-fall","season-spring","season-summer","season-winter","sort-down","sort-none","sort-up","ver-13","ver-15","ver-17","ver-19","ver-22","weather-1","weather-2","weather-3","weather-4","weather-5","weather-6","weather-7","weather-8","weather-9"]) +const ft_known = new Set(["cat-attach-has","cat-attach-need","cat-brand","cat-object","cat-placeable","cat-tool","cat-vehicle","fill-air","fill-alfalfa","fill-alfalfa-windrow","fill-anhydrous","fill-armoire","fill-bakedpoppy","fill-balenet","fill-baletwine","fill-barley","fill-barrel","fill-bathtub","fill-beanstraw","fill-beer","fill-birdhouse","fill-boards","fill-bowl","fill-bread","fill-bucket","fill-bun","fill-butter","fill-cake","fill-canola","fill-carp","fill-carrot","fill-carrotjuice","fill-carrotsalad","fill-cartonroll","fill-cattree","fill-cereals","fill-chaff","fill-chair","fill-cheese","fill-chicken","fill-chickenfeed","fill-chips","fill-chocolate","fill-chocolateicecream","fill-clothes","fill-clover","fill-clover-windrow","fill-coleslaw","fill-compost","fill-cornstalks","fill-cotton","fill-cow","fill-cream","fill-def","fill-diesel","fill-digestate","fill-dog","fill-doghouse","fill-dryalfalfa-windrow","fill-dryclover-windrow","fill-drygrass-windrow","fill-drymaize2","fill-duck","fill-easel","fill-egg","fill-electriccharge","fill-emptypallet","fill-ethanol","fill-fabric","fill-fertilizer","fill-fishfeed","fill-fishflour","fill-floortiles","fill-flour","fill-forage","fill-forage-mixing","fill-frenchfries","fill-furniture","fill-goat","fill-goatcheese","fill-goatmilk","fill-grape","fill-grapejuice","fill-grass","fill-grass-windrow","fill-greensalad","fill-haypellets","fill-herbicide","fill-honey","fill-horse","fill-ironore","fill-lettuce","fill-lime","fill-liquidfertilizer","fill-liquidmanure","fill-maize","fill-manure","fill-mashedpotato","fill-metal","fill-methane","fill-milk","fill-mineralfeed","fill-mohn","fill-molasses","fill-oat","fill-oatdrink","fill-oatmeal","fill-oatmilk","fill-oilradish","fill-oil-canola","fill-oil-olive","fill-oil-sunflower","fill-olive","fill-onion","fill-onionjuice","fill-paperroll","fill-parsnip","fill-pasta","fill-peppergrinder","fill-pickle-carrot","fill-pickle-parsnip","fill-pickle-redbeet","fill-pictureframe","fill-pig","fill-pigfood","fill-pike","fill-pizza","fill-plankslong","fill-pomace","fill-poplar","fill-poppyseedbun","fill-potato","fill-potatopancake","fill-potatosalad","fill-praline","fill-prefabwall","fill-pressurewasheradditive","fill-product","fill-propane","fill-raisins","fill-redbeet","fill-redcabbage","fill-roadsalt","fill-rockpowder","fill-roundbalecotton","fill-roundbalegrass","fill-roundbalehay","fill-roundbalesilage","fill-roundbalestraw","fill-roundbalewood","fill-rye","fill-salmon","fill-salt","fill-seeds","fill-seedtreatingliquid","fill-sheep","fill-shingle","fill-silage","fill-silageadditive","fill-snow","fill-sodiumchloride","fill-sorghum","fill-soup-carrot","fill-soup-parsnip","fill-soup-potato","fill-soup-redbeet","fill-soup-triple","fill-soybean","fill-soydrink","fill-soyflour","fill-soymilk","fill-spelt","fill-squarebalecotton","fill-squarebalegrass","fill-squarebalehay","fill-squarebalesilage","fill-squarebalestraw","fill-squarebalewood","fill-staircaserailing","fill-stone","fill-straw","fill-strawberry","fill-strawberrycreamcake","fill-strawberryicecream","fill-strawberryjuice","fill-strawpellets","fill-sugar","fill-sugarbeet","fill-sugarbeetcut","fill-sugarcane","fill-sun","fill-sunflower","fill-table","fill-tarp","fill-tomato","fill-tomatojuice","fill-tomatosalad","fill-unknown","fill-vinegar","fill-vitamins","fill-water","fill-weed","fill-wheat","fill-wheatsemolina","fill-whiskey","fill-whitecabbage","fill-wine","fill-wood","fill-woodbeam","fill-woodchips","fill-wool","look-beacons","look-diesel","look-electric","look-engine","look-fillunit","look-income","look-info","look-key","look-lights","look-methane","look-objects","look-paintable","look-price","look-prod-cycle","look-prod-input","look-prod-output","look-speed","look-speedlimit","look-timer","look-transmission","look-weight","look-wheels","look-width","look-year","ma-large","ma-small","season-fall","season-spring","season-summer","season-winter","sort-down","sort-none","sort-up","ver-13","ver-15","ver-17","ver-19","ver-22","weather-1","weather-2","weather-3","weather-4","weather-5","weather-6","weather-7","weather-8","weather-9"]) /* eslint-enable comma-spacing, quotes */ const ft_map = { 'barley-flour' : 'flour', 'canola-oil' : 'oil-canola', 'canolaoil' : 'oil-canola', + 'cardboard' : 'cartonroll', 'carton-roll' : 'cartonroll', 'cereal' : 'cereals', 'chaff-silage' : 'silage', @@ -36,6 +37,8 @@ const ft_map = { 'factory-power' : 'electriccharge', 'fermenter-slurry' : 'liquidmanure', 'firtree' : 'wood', + 'fish-feed' : 'fishfeed', + 'fish-flour' : 'fishflour', 'foragemix' : 'forage-mixing', 'grape-juice' : 'grapejuice', 'grapes' : 'grape', @@ -68,6 +71,8 @@ const ft_map = { 'pigfood-mixer' : 'pigfood', 'planks' : 'boards', 'poplartree' : 'poplar', + 'poppy' : 'mohn', + 'potatochips' : 'chips', 'potatoe' : 'potato', 'potatoes' : 'potato', 'potatos' : 'potato', @@ -77,13 +82,16 @@ const ft_map = { 'silage-additive' : 'silageadditive', 'silage-in' : 'silage', 'slurry' : 'liquidmanure', + 'sodiumchlorid' : 'sodiumchloride', 'soldable-electricity' : 'electriccharge', 'soldable-methane' : 'methane', 'solid-fertilizer' : 'fertilizer', 'solidfertilizer' : 'fertilizer', 'sorghum-flour' : 'flour', + 'soy-flour' : 'soyflour', 'soybeans' : 'soybean', 'soybeanstraw' : 'beanstraw', + 'spaghetti' : 'pasta', 'square-bale-grass' : 'squarebalegrass', 'square-bale-silage' : 'squarebalesilage', 'square-bale-straw' : 'squarebalestraw', @@ -110,6 +118,7 @@ const ft_map = { 'tree-saplings' : 'wood', 'treesaplings' : 'wood', 'wheat-flour' : 'flour', + 'wind' : 'air', 'wood-wood-beams' : 'woodbeam', 'woodchipsroundbale' : 'roundbalewood', 'woodchipssquarebale' : 'squarebalewood', diff --git a/renderer/renderJS/util/general_lib.js b/renderer/renderJS/util/general_lib.js index 596133bc..e3b3bdec 100644 --- a/renderer/renderJS/util/general_lib.js +++ b/renderer/renderJS/util/general_lib.js @@ -9,8 +9,8 @@ /* global l10n, bootstrap, ft_doReplace */ /* exported tableBuilder */ -const getText = (text, extraTitle = null) => `` -const getTextBase = (text, extraTitle = null) => `` +const getText = (text, extraTitle = null) => ` ` +const getTextBase = (text, extraTitle = null) => ` ` const _l = () => l10n.getLocale() const __ = (text, { skipIfNotBase = false, skipOnSpace = true, skipAlways = false, title = null } = {} ) => { if ( skipAlways || typeof text !== 'string' || text.length === 0 ) { return text } @@ -28,7 +28,7 @@ const __ = (text, { skipIfNotBase = false, skipOnSpace = true, skipAlways = fals } catch { /* Ignore Problems Here */ } return newName } - return text.startsWith('$l10n') ? getTextBase(text, title) : skipIfNotBase || (text.indexOf(' ') !== -1 && skipOnSpace) ? text :getText(text, title) + return text.startsWith('$l10n') ? getTextBase(text, title) : skipIfNotBase || (text.indexOf(' ') !== -1 && skipOnSpace) ? text : getText(text, title) } const fsgUtil = { @@ -333,7 +333,8 @@ const fsgUtil = { } }, - clearTooltips : () => { for ( const tooltip of fsgUtil.query('.tooltip') ) { tooltip.remove() } }, + clearTooltips : () => { for ( const tooltip of fsgUtil.query('.tooltip') ) { tooltip?.dispose?.() } }, + clearTooltipsXX : () => { for ( const tooltip of fsgUtil.query('.tooltip') ) { tooltip?.remove?.() } }, setTheme : (theme) => { document.body.setAttribute('data-bs-theme', theme) }, windowCheckAll : () => { fsgUtil.windowCheckOp(true) }, windowCheckInv : () => { @@ -393,14 +394,12 @@ function processL10N() { } function clientGetL10NEntries() { - const l10nSendArray = fsgUtil.queryA('l10n').map((element) => fsgUtil.getAttribNullEmpty(element, 'name')) - + const l10nSendArray = fsgUtil.queryA('l10n:not([data-done="true"]').map((element) => fsgUtil.getAttribNullEmpty(element, 'name')) l10n.getText_send(new Set(l10nSendArray)) } function clientGetL10NEntriesBase() { - const l10nSendArray = fsgUtil.queryA('l10nBase').map((element) => fsgUtil.getAttribNullEmpty(element, 'name')) - + const l10nSendArray = fsgUtil.queryA('l10nBase:not([data-done="true"]').map((element) => fsgUtil.getAttribNullEmpty(element, 'name')) l10n.getTextBase_send(new Set(l10nSendArray)) } @@ -408,12 +407,18 @@ window?.l10n?.receive('fromMain_getText_return', (data) => { if ( data[0] === '__currentLocale__' ) { document.body.setAttribute('data-i18n', data[1]) } else { - for ( const item of fsgUtil.query(`l10n[name="${data[0]}"]`) ) { item.innerHTML = data[1] } + for ( const item of fsgUtil.query(`l10n[name="${data[0]}"]`) ) { + item.innerHTML = data[1] + item.setAttribute('data-done', 'true') + } } }) window?.l10n?.receive('fromMain_getText_return_base', (data) => { - for ( const item of fsgUtil.query(`l10nBase[name="${data[0]}"]`) ) { item.innerHTML = data[1] } + for ( const item of fsgUtil.query(`l10nBase[name="${data[0]}"]`) ) { + item.innerHTML = data[1] + item.setAttribute('data-done', 'true') + } }) const currentTooltips = [] @@ -423,6 +428,7 @@ window?.l10n?.receive('fromMain_getText_return_title', (data) => { let thisTitle = item.closest('button') thisTitle ??= item.closest('span') thisTitle ??= item.closest('label') + thisTitle ??= item.closest('a') if ( data[0] === 'game_icon_lg' ) { thisTitle = item.closest('#multi_version_button') } @@ -439,6 +445,10 @@ window?.l10n?.receive('fromMain_l10n_refresh', (newLang) => { } currentTooltips.length = 0 document.body.setAttribute('data-i18n', newLang) + + for ( const element of fsgUtil.query('l10n[data-done]') ) { + element.removeAttribute('data-done') + } processL10N() }) diff --git a/test/generateBaseGame_lib.js b/test/generateBaseGame_lib.js index c0df2064..1909671a 100644 --- a/test/generateBaseGame_lib.js +++ b/test/generateBaseGame_lib.js @@ -6,7 +6,7 @@ // Base Game generator library -const { requiredItems, logCollector, fileHandler } = require('../lib/workerThreadLib.js') +const { requiredItems, logCollector, fileHandlerAsync } = require('../lib/workerThreadLib.js') const path = require('node:path') const allLang = require('../lib/modLookerLang.json') const {XMLParser} = require('fast-xml-parser') @@ -40,7 +40,9 @@ class baseLooker { } async getInfo() { - this.#modHandle = new fileHandler(path.dirname(this.#fullFileName), true, this.#log) + this.#modHandle = new fileHandlerAsync(path.dirname(this.#fullFileName), true, this.#log) + + await this.#modHandle.open() const thisItem = path.basename(this.#fullFileName) const thisItemName = path.relative(this.#dataPath, this.#fullFileName).replaceAll('\\', '_').replace('.xml', '') @@ -48,7 +50,7 @@ class baseLooker { const iconLoads = [] - const thisItemTree = this.#modHandle.readXML(thisItem, 'modlook') + const thisItemTree = await this.#modHandle.readXML(thisItem, 'modlook') const thisItemInfo = this.#parseStoreItem(thisItemTree) if ( thisItemInfo !== null && thisItemInfo.icon !== null ) { @@ -60,10 +62,9 @@ class baseLooker { })) } - this.#modHandle.close() - this.#modHandle = null - return Promise.allSettled(iconLoads).then(() => { + this.#modHandle.close() + this.#modHandle = null return { shortname : thisItemName, log : this.#log.lines, record : thisItemInfo } }) } @@ -97,7 +98,7 @@ class baseLooker { try { if ( this.#modHandle.exists(fileName) ) { return this.#iconParser.parseDDS( - this.#modHandle.readBin(fileName), + await this.#modHandle.readBin(fileName), true, isMap ) diff --git a/test/test.js b/test/test.js index 8ea4ef8d..10c8cc8c 100644 --- a/test/test.js +++ b/test/test.js @@ -171,7 +171,7 @@ if (require.main === module) { } else { rootTest.end(true) } - + // console.log(serveIPC.log.textLog) }) } \ No newline at end of file diff --git a/test/tests/csvcheck.js b/test/tests/csvcheck.js index 5d254424..996e1498 100644 --- a/test/tests/csvcheck.js +++ b/test/tests/csvcheck.js @@ -37,7 +37,7 @@ const testGood = (test) => { } if ( Object.keys(results.farms).length === 2 ) { - test.step('Got expected number of farms (2)') + test.step('Got expected number of farms (2 [default])') } else { test.error('Got unexpected number of farms') } diff --git a/test/tests/modcheck.js b/test/tests/modcheck.js index 2d9bc269..8fba3239 100644 --- a/test/tests/modcheck.js +++ b/test/tests/modcheck.js @@ -26,7 +26,7 @@ module.exports.test = async () => { return Promise.allSettled([ testSingleFlag( 'EXAMPLE_Bad_ModDesc_CRC.zip', - ['NOT_MOD_MODDESC_MISSING', 'MOD_ERROR_NO_MOD_ICON', 'PERF_L10N_NOT_SET'], + ['NOT_MOD_MODDESC_PARSE_ERROR', 'MOD_ERROR_NO_MOD_ICON', 'PERF_L10N_NOT_SET'], new testLib('Mod Checker - Broken ZIP (Bad CRC) File') ), @@ -74,7 +74,7 @@ module.exports.test = async () => { return Promise.allSettled([ testSingleFlag( 'EXAMPLE_Missing_ModDesc.zip', - ['NOT_MOD_MODDESC_MISSING', 'MOD_ERROR_NO_MOD_ICON', 'PERF_L10N_NOT_SET'], + ['NOT_MOD_MODDESC_MISSING', 'PERF_L10N_NOT_SET'], new testLib('Mod Checker - Missing modDesc') ), diff --git a/test/tests/modcollectbench.js b/test/tests/modcollectbench.js index f11b043a..1438cfc3 100644 --- a/test/tests/modcollectbench.js +++ b/test/tests/modcollectbench.js @@ -25,7 +25,7 @@ const modCollect = new modFileCollection( require('node:os').homedir, queueDoneE module.exports.test = () => { return Promise.allSettled([ - testGood(new testLib('Mod Collection - Valid')) + testGood(new testLib('Mod Collection - Benchmark All')) ]) } @@ -78,6 +78,7 @@ const testGood = (test) => { test.error(`Got unexpected ${actualSize} bytes of data, outside range`) } + test.step(`Size of All : ${modCollect.fullCollectSize} bytes`) test.step(`Total Mods Processed : ${modCollect.totalModCount}`) test.step(`Collection Process took ${Date.now() - startTime}ms`) diff --git a/test/tests/sourcecode.js b/test/tests/sourcecode.js index 5d4bcad6..021f1772 100644 --- a/test/tests/sourcecode.js +++ b/test/tests/sourcecode.js @@ -27,7 +27,7 @@ async function tester (test) { for ( const file of jsFiles ) { const thisFile = path.relative(rootPath, file) - if ( thisFile.startsWith('node_modules') || thisFile.includes('inc') ) { + if ( thisFile.startsWith('node_modules') || thisFile.startsWith('jsdoc_server') || thisFile.includes('inc') ) { continue } diff --git a/translations/cs.json b/translations/cs.json index 62949e7c..bdb6c399 100644 --- a/translations/cs.json +++ b/translations/cs.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "该收藏有与之相关的其他信息", "bad_folder_folder" : "文件夹位置:", "bad_folder_title" : "藏品文件夹丢失", + "bad_game_set_xml_message_back" : "您的 gameSettings.xml 文件存在,但为空或无法读取。 该文件有备份,您想恢复吗?", + "bad_game_set_xml_message_no_back" : "您的 gameSettings.xml 文件存在,但为空或不可读。 该文件的备份不存在 - 建议删除该文件,然后启动游戏自动创建一个新文件", + "bad_game_set_xml_title" : "gameSettings.xml 发现问题 - 版本 :", + "bad_game_xml_delete" : "删除坏文件(需要启动游戏)", + "bad_game_xml_message_back" : "您的 game.xml 文件存在,但为空或无法读取。 该文件已有备份,您想恢复它吗?", + "bad_game_xml_message_no_back" : "您的 game.xml 文件存在,但为空或无法读取。 该文件的备份不存在 - 建议删除该文件,然后启动游戏自动创建一个新文件", + "bad_game_xml_nothing" : "什么也别做,我自己来解决", + "bad_game_xml_restore" : "恢复备份", + "bad_game_xml_title" : "检测到 game.xml 问题 - 版本 :", "base_game_data__title" : "浏览基地游戏项目", "basegame_attach" : "附件", "basegame_attach_has" : "接受带有", @@ -80,6 +89,7 @@ "context_mod_detail" : "Show Details", "context_open_website" : "Open Source Website", "context_paste" : "Paste", + "context_select_all" : "全部选择", "context_set_website" : "Set Source Website", "copy" : "是的,复制模组", "copy_cropcal" : "将作物日历 JSON 复制到剪贴板", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Alfalfa", "croptype_barley" : "Barley", "croptype_canola" : "Canola", + "croptype_carrot" : "胡萝卜", "croptype_clover" : "Clover", "croptype_cotton" : "Cotton", "croptype_grape" : "Grapes", @@ -112,14 +123,20 @@ "croptype_oat" : "Oat", "croptype_oilseedradish" : "Oilseed Radish", "croptype_olive" : "Olives", + "croptype_onion" : "洋葱", "croptype_poplar" : "Poplar", + "croptype_poppy" : "罂粟花", "croptype_potato" : "Potato", + "croptype_redcabbage" : "红甘蓝", + "croptype_rye" : "黑麦", "croptype_sorghum" : "Sorghum", "croptype_soybean" : "Soybean", + "croptype_spelt" : "斯佩耳特", "croptype_sugarbeet" : "Sugarbeet", "croptype_sugarcane" : "Sugar Cane", "croptype_sunflower" : "Sunflower", "croptype_wheat" : "Wheat", + "croptype_whitecabbage" : "白椰菜", "delete" : "是的,删除模组", "destination" : "目标集合", "destination_clear" : "目标文件不存在,可以继续", @@ -196,6 +213,32 @@ "game_running_warning" : "游戏当前正在运行 - 更改活动模式集需要重启游戏!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Open Documentation", + "import_json_blurb" : "该工具可让您导入带有以下设置的收藏集", + "import_json_folder_empty" : "在磁盘上找到文件夹,按 \"应用设置 \"按钮完成操作", + "import_json_folder_not_empty" : "文件夹已包含文件,如果确定这是您想要的,请按 \"应用设置 \"按钮完成操作", + "import_json_load_failed" : "无法从磁盘读取 JSON 收集导入文件。", + "import_json_no" : "未启用", + "import_json_not_set" : "未提供数值", + "import_json_read_failed" : "JSON 集合导入文件无效。", + "import_json_step_0_blurb" : "以下是导入文件中包含的设置", + "import_json_step_0_title" : "包括设置", + "import_json_step_1_blurb" : "选择一个地方来存放这些模型集", + "import_json_step_1_folder" : "选择文件夹位置", + "import_json_step_1_folder_blurb" : "为该文件集选择磁盘位置。您可以在弹出的对话框中使用现有文件夹或创建新文件夹", + "import_json_step_1_folder_button" : "设置地点", + "import_json_step_1_folder_return" : "收藏地点", + "import_json_step_1_options" : "应用采集设置", + "import_json_step_1_options_blurb" : "将包含的设置应用到此收藏", + "import_json_step_1_options_button" : "应用设置", + "import_json_step_1_title" : "第 1 步:选择位置并导入", + "import_json_step_2_blurb" : "本合集包含以下修改器和修改器包,您可以选择在此处下载。 请注意,任何现有文件都将被下载副本覆盖。", + "import_json_step_2_download" : "下载", + "import_json_step_2_download_unpack" : "下载和解压", + "import_json_step_2_pack" : "模块包", + "import_json_step_2_single" : "单一模式", + "import_json_step_2_title" : "第 2 步:下载修改器(可选)", + "import_json_title" : "进口收藏", + "import_json_yes" : "启用", "info_might_be_piracy" : "这个模组似乎是一个盗版的付费DLC。这个测试比较简单,所以可能是一个误报,但请注意,使用盗版DLC将阻止您得到 Giants 支持团队的帮助,并且可能会导致在官方 Discord 和论坛上被封禁。请支持开发者!", "info_no_multiplayer_unzipped" : "未压缩的模组无法在多人游戏中使用。如果您不打算在多人游戏中使用此模组,您可以忽略此消息。", "language_code" : "cs", @@ -210,6 +253,8 @@ "list-active__title" : "请在 gameSettings.xml 文件中将此收藏夹设置为激活状态", "list-none" : "取消激活", "list-none__title" : "请在 gameSettings.xml 文件中取消激活覆盖。", + "load_save_import_failed" : "无法读取指定的保存文件,无法继续导入。", + "load_save_import_title" : "无效保存文件", "loading_cache_subtitle" : "清理额外的缓存条目 (较慢)", "loading_cache_title" : "清除缓存", "loading_download_subtitle" : "Please wait, this could take a minute", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "用 \"英亩 \"表示面积", "notes_units_use_fahrenheit" : "使用华氏温度表示温度", "notes_units_use_miles" : "用 \"英里 \"表示距离", - "notes_usage_blurb" : "Please Note: To activate any changes here you must press the \"Set Active\" button for this collection again!", + "notes_usage_blurb_2" : "请注意:这是当前激活的收藏集--要激活此处的任何更改(密码、筛选器、用户名),必须再次按下该收藏集的 \"设置激活 \"按钮!", "offcanvas_blurb" : "Select which version of Farming Simulator you wish to be active", "open_debug_log" : "查看调试日志", "open_folder" : "在资源管理器中打开", @@ -409,6 +454,9 @@ "save_csv_failed" : "Unable to export CSV collection list (check debug log)", "save_csv_worked" : "Collection Exported to CSV List", "save_import_slot" : "目的地插槽", + "save_json" : "导出设置(JSON)", + "save_json_failed" : "无法将集合导出为 JSON(检查调试日志)", + "save_json_worked" : "导出为 JSON 格式的收藏集", "save_log_failed" : "调试日志保存失败", "save_log_worked" : "调试日志保存成功", "save_manage_active" : "主动保存游戏", diff --git a/translations/cz.json b/translations/cz.json index d02260d3..3d278475 100644 --- a/translations/cz.json +++ b/translations/cz.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "K této kolekci jsou přiřazeny další informace", "bad_folder_folder" : "Umístění složky:", "bad_folder_title" : "Chybějící složka sbírky", + "bad_game_set_xml_message_back" : "Váš soubor gameSettings.xml existuje, ale je prázdný nebo nečitelný. Záloha tohoto souboru existuje, chcete ji obnovit?", + "bad_game_set_xml_message_no_back" : "Váš soubor gameSettings.xml existuje, ale je prázdný nebo nečitelný. Záloha tohoto souboru neexistuje - doporučeným postupem je jeho odstranění, poté je třeba spustit hru, aby se automaticky vytvořil nový soubor.", + "bad_game_set_xml_title" : "gameSettings.xml Zjištěn problém - Verze :", + "bad_game_xml_delete" : "Odstranění špatného souboru (je nutné spustit hru!)", + "bad_game_xml_message_back" : "Váš soubor game.xml existuje, ale je prázdný nebo nečitelný. Záloha tohoto souboru existuje, chcete ji obnovit?", + "bad_game_xml_message_no_back" : "Váš soubor game.xml existuje, ale je prázdný nebo nečitelný. Záloha tohoto souboru neexistuje - doporučená akce je smazat jej, pak je třeba spustit hru, aby se automaticky vytvořil nový.", + "bad_game_xml_nothing" : "Nedělej nic, opravím to sám", + "bad_game_xml_restore" : "Obnovení zálohy", + "bad_game_xml_title" : "game.xml Zjištěn problém - Verze :", "base_game_data__title" : "Procházet položky základní hry", "basegame_attach" : "Příloha", "basegame_attach_has" : "Přijímá přílohy s", @@ -80,6 +89,7 @@ "context_mod_detail" : "Zobrazit podrobnosti", "context_open_website" : "Nastavit zdrojový web", "context_paste" : "Vložit", + "context_select_all" : "Vybrat vše", "context_set_website" : "Nastavit zdrojový web", "copy" : "Ano, kopírovat režimy", "copy_cropcal" : "Kopírování JSON kalendáře oříznutí do schránky", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Vojtěška", "croptype_barley" : "Ječmen", "croptype_canola" : "Řepka", + "croptype_carrot" : "Mrkev", "croptype_clover" : "Jetel", "croptype_cotton" : "Bavlna", "croptype_grape" : "Hroznové víno", @@ -112,14 +123,20 @@ "croptype_oat" : "Oves", "croptype_oilseedradish" : "Ředkev olejná", "croptype_olive" : "Olivy", + "croptype_onion" : "Cibule", "croptype_poplar" : "Topol", + "croptype_poppy" : "Poppy", "croptype_potato" : "Brambory", + "croptype_redcabbage" : "Červené zelí", + "croptype_rye" : "Žito", "croptype_sorghum" : "Čirok", "croptype_soybean" : "Sójové boby", + "croptype_spelt" : "Špalda", "croptype_sugarbeet" : "Cukrová řepa", "croptype_sugarcane" : "Cukrová třtina", "croptype_sunflower" : "Slunečnice", "croptype_wheat" : "Pšenice", + "croptype_whitecabbage" : "Bílé zelí", "delete" : "Ano, odstranit módy", "destination" : "Cílová kolekce:", "destination_clear" : "Cílový soubor neexistuje, je dobré jít", @@ -196,6 +213,32 @@ "game_running_warning" : "Hra je právě spuštěna - změny v aktivní kolekci modů budou vyžadovat restart hry!", "game_title_farming_simulator" : "Farmářský simulátor", "help_button__title" : "Otevřít dokumentaci", + "import_json_blurb" : "Tento nástroj vám umožní importovat kolekci s nastavením", + "import_json_folder_empty" : "Složka nalezena na disku, stiskněte tlačítko \"Použít nastavení\" pro dokončení operace.", + "import_json_folder_not_empty" : "Složka již obsahuje soubory, pokud jste si jisti, že je to to, co chcete, stiskněte tlačítko \"Použít nastavení\" pro dokončení operace.", + "import_json_load_failed" : "Importní soubor kolekce JSON se nepodařilo načíst z disku.", + "import_json_no" : "není povoleno", + "import_json_not_set" : "není uvedena žádná hodnota", + "import_json_read_failed" : "Importní soubor kolekce JSON byl neplatný.", + "import_json_step_0_blurb" : "Toto jsou nastavení obsažená v importním souboru", + "import_json_step_0_title" : "Zahrnutá nastavení", + "import_json_step_1_blurb" : "Zvolte místo pro uložení této sbírky modifikací.", + "import_json_step_1_folder" : "Výběr umístění složky", + "import_json_step_1_folder_blurb" : "Vyberte umístění této sbírky na disku. Můžete použít existující složku nebo vytvořit novou ve vyskakovacím dialogovém okně.", + "import_json_step_1_folder_button" : "Nastavit umístění", + "import_json_step_1_folder_return" : "Místo sběru", + "import_json_step_1_options" : "Použít nastavení sbírky", + "import_json_step_1_options_blurb" : "Použijte přiložená nastavení na tuto kolekci", + "import_json_step_1_options_button" : "Použít nastavení", + "import_json_step_1_title" : "Krok 1 : Výběr umístění a import", + "import_json_step_2_blurb" : "Tato kolekce obsahuje následující mody a balíčky modů - volitelně si je můžete stáhnout zde. Upozorňujeme, že všechny existující soubory budou přepsány staženými kopiemi.", + "import_json_step_2_download" : "Stáhnout", + "import_json_step_2_download_unpack" : "Stažení a rozbalení", + "import_json_step_2_pack" : "Balíčky modifikací", + "import_json_step_2_single" : "Jednotlivé mody", + "import_json_step_2_title" : "Krok 2 : Stáhněte si mody (volitelné)", + "import_json_title" : "Import kolekce", + "import_json_yes" : "povoleno", "info_might_be_piracy" : "Zdá se, že tento mod je trhlina / pirátská verze placené DLC. Tento test je zjednodušující, takže by mohl být falešně pozitivní, ale mějte na paměti, že používání prasklých DLC vám pomůže získat pomoc od Giants podpory, a je důvodem pro zákaz oficiálních neshod a fór. Podpořte prosím vývojáře!", "info_no_multiplayer_unzipped" : "Rozbalené mody nemohou být použity ve hře pro více hráčů. Pokud nemáte v úmyslu použít tento mod ve hře pro více hráčů, můžete tuto zprávu ignorovat.", "language_code" : "cz", @@ -210,6 +253,8 @@ "list-active__title" : "Nastavte tuto kolekci Aktivní v herním Settings.xml", "list-none" : "Deaktivovat", "list-none__title" : "Deaktivovat přepsání v herním Settings.xml", + "load_save_import_failed" : "Nelze přečíst zadaný uložený soubor, nelze pokračovat v importu.", + "load_save_import_title" : "Neplatný soubor pro uložení", "loading_cache_subtitle" : "Čištění extra položek mezipaměti (pomalé!)", "loading_cache_title" : "Vyčistit mezipaměť", "loading_download_subtitle" : "Počkejte prosím, může to chvíli trvat", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Použijte akry pro plochu", "notes_units_use_fahrenheit" : "Použití Fahrenheita pro teplotu", "notes_units_use_miles" : "Použití mil pro vzdálenost", - "notes_usage_blurb" : "Upozornění: Chcete-li zde aktivovat změny, musíte pro tuto kolekci znovu stisknout tlačítko \"Nastavit aktivní\"!", + "notes_usage_blurb_2" : "Upozornění: Jedná se o aktuálně aktivní kolekci - pro aktivaci jakýchkoli změn (heslo, filtry, uživatelské jméno) je nutné znovu stisknout tlačítko \"Nastavit aktivní\" pro tuto kolekci!", "offcanvas_blurb" : "Vyberte, která verze farmařicího simulátoru chcete být aktivní", "open_debug_log" : "Zobrazit protokol ladění", "open_folder" : "Otevřít v Průzkumníku", @@ -409,6 +454,9 @@ "save_csv_failed" : "Nelze exportovat seznam CSV kolekce (zkontrolujte protokol ladění)", "save_csv_worked" : "Kolekce exportována do seznamu CSV", "save_import_slot" : "Cílový slot", + "save_json" : "Export nastavení (JSON)", + "save_json_failed" : "Nelze exportovat kolekci do JSON (zkontrolujte protokol ladění)", + "save_json_worked" : "Kolekce exportovaná do JSON", "save_log_failed" : "Uložení ladicího protokolu se nezdařilo", "save_log_worked" : "Ladící log byl úspěšně uložen", "save_manage_active" : "Aktivní ukládání her", diff --git a/translations/de.json b/translations/de.json index 635b36a6..4c8dfe35 100644 --- a/translations/de.json +++ b/translations/de.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Dieser Sammlung sind zusätzliche Informationen zugeordnet", "bad_folder_folder" : "Ordnerspeicherorte:", "bad_folder_title" : "Sammlungsverzeichnis fehlt", + "bad_game_set_xml_message_back" : "Deine gameSettings.xml existiert bereits, ist aber leer oder unlesbar. Eine Sicherung dieser Datei existiert bereits, möchtest du sie wiederherstellen?", + "bad_game_set_xml_message_no_back" : "Deine gameSettings.xml existiert bereits, ist aber leer oder unlesbar. Eine Sicherung dieser Datei existiert nicht - die empfohlene Aktion ist sie zu löschen. Anschließend musst du das Spiel starten, um automatisch eine neue zu erstellen", + "bad_game_set_xml_title" : "gameSettings.xml Problem erkannt - Version:", + "bad_game_xml_delete" : "Ungültige Datei löschen (Spielstart erforderlich!)", + "bad_game_xml_message_back" : "Deine gameSettings.xml existiert bereits, ist aber leer oder unlesbar. Eine Sicherung dieser Datei existiert bereits, möchtest du sie wiederherstellen?", + "bad_game_xml_message_no_back" : "Deine gameSettings.xml existiert bereits, ist aber leer oder unlesbar. Eine Sicherung dieser Datei existiert nicht - die empfohlene Aktion ist sie zu löschen. Anschließend musst du das Spiel starten, um automatisch eine neue zu erstellen", + "bad_game_xml_nothing" : "Nichts unternehmen, ich werde das selbst beheben ", + "bad_game_xml_restore" : "Das Backup wiederherstellen", + "bad_game_xml_title" : "gameSettings.xml Problem erkannt - Version:", "base_game_data__title" : "Basis-Spielelemente durchsuchen", "basegame_attach" : "Anhang", "basegame_attach_has" : "Anhänge akzeptieren mit", @@ -78,8 +87,9 @@ "context_main_copy" : "Kopieren", "context_main_title" : "Sammlungsoptionen", "context_mod_detail" : "Details anzeigen", - "context_open_website" : "Quellseite festlegen", + "context_open_website" : "Quellseite öffnen", "context_paste" : "Einfügen", + "context_select_all" : "Alle auswählen", "context_set_website" : "Quellseite festlegen", "copy" : "Ja, Mod(s) kopieren", "copy_cropcal" : "Kopiere Wachstumskalender JSON in die Zwischenablage", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Luzerne", "croptype_barley" : "Gerste", "croptype_canola" : "Raps", + "croptype_carrot" : "Karotte", "croptype_clover" : "Klee", "croptype_cotton" : "Baumwolle", "croptype_grape" : "Trauben", @@ -112,14 +123,20 @@ "croptype_oat" : "Hafer", "croptype_oilseedradish" : "Ölrettich", "croptype_olive" : "Oliven", + "croptype_onion" : "Zwiebel", "croptype_poplar" : "Pappeln", + "croptype_poppy" : "Mohn", "croptype_potato" : "Kartoffeln", + "croptype_redcabbage" : "Rotkohl", + "croptype_rye" : "Roggen", "croptype_sorghum" : "Sorghum", "croptype_soybean" : "Sojabohnen", + "croptype_spelt" : "Dinkel", "croptype_sugarbeet" : "Zuckerrüben", "croptype_sugarcane" : "Zuckerrohr", "croptype_sunflower" : "Sonnenblume", "croptype_wheat" : "Weizen", + "croptype_whitecabbage" : "Weißkohl", "delete" : "Ja, Mod löschen", "destination" : "Bestimmungsort:", "destination_clear" : "Die Zieldatei existiert nicht, alles in Ordnung", @@ -196,6 +213,32 @@ "game_running_warning" : "Das Spiel läuft gerade - Änderungen an der aktiven Mod-Sammlung erfordern einen Neustart des Spiels!", "game_title_farming_simulator" : "Landwirtschafts-Simulator", "help_button__title" : "Dokumentation öffnen", + "import_json_blurb" : "Mit diesem Tool können Sie eine Sammlung mit Einstellungen importieren", + "import_json_folder_empty" : "Ordner auf der Festplatte gefunden, drücken Sie die Schaltfläche \"Einstellungen übernehmen\", um den Vorgang abzuschließen", + "import_json_folder_not_empty" : "Der Ordner enthält bereits Dateien. Wenn Sie sicher sind, dass Sie dies wünschen, klicken Sie auf die Schaltfläche \"Einstellungen übernehmen\", um den Vorgang abzuschließen.", + "import_json_load_failed" : "Die JSON-Sammlungsimportdatei konnte nicht von der Festplatte gelesen werden.", + "import_json_no" : "nicht aktiviert", + "import_json_not_set" : "kein Wert angegeben", + "import_json_read_failed" : "JSON-Sammlungsimportdatei war ungültig.", + "import_json_step_0_blurb" : "Die folgenden Einstellungen sind in der Importdatei enthalten", + "import_json_step_0_title" : "Enthaltene Einstellungen", + "import_json_step_1_blurb" : "Wählen Sie einen Ort, an dem Sie diese Sammlung von Mods aufbewahren können", + "import_json_step_1_folder" : "Speicherort des Ordners wählen", + "import_json_step_1_folder_blurb" : "Wählen Sie den Speicherort auf der Festplatte für diese Sammlung. Sie können einen bestehenden Ordner verwenden oder einen neuen im Popup-Dialog erstellen", + "import_json_step_1_folder_button" : "Standort festlegen", + "import_json_step_1_folder_return" : "Standort der Sammlung", + "import_json_step_1_options" : "Sammlungseinstellungen anwenden", + "import_json_step_1_options_blurb" : "Wenden Sie die enthaltenen Einstellungen auf diese Sammlung an", + "import_json_step_1_options_button" : "Einstellungen anwenden", + "import_json_step_1_title" : "Schritt 1: Wählen Sie einen Standort und importieren Sie", + "import_json_step_2_blurb" : "Die folgenden Mods und Mod Packs sind in dieser Sammlung enthalten - Sie können sie optional hier herunterladen. Beachten Sie, dass bereits vorhandene Dateien durch die heruntergeladenen Kopien überschrieben werden.", + "import_json_step_2_download" : "Herunterladen", + "import_json_step_2_download_unpack" : "Herunterladen und Entpacken", + "import_json_step_2_pack" : "Mod Packs", + "import_json_step_2_single" : "Einzelne Mods", + "import_json_step_2_title" : "Schritt 2: Mods herunterladen (optional)", + "import_json_title" : "Sammlung importieren", + "import_json_yes" : "aktiviert", "info_might_be_piracy" : "Dies scheint eine Raubkopie von einem kostenpflichtigen DLC zu sein. Dieser Test ist simpel, es könnte also ein Fehlalarm sein. Dennoch sei dir bewusst, dass die Nutzung von solchen Raubkopien zum Verlust von Support durch GIANTS und zur Sperrung vom GIANTS Discord server und dem Forum führen kann. Bitte unterstützt die Entwickler!", "info_no_multiplayer_unzipped" : "Entpackte Mods können nicht im Multiplayer verwendet werden. Wenn du diesen Mod nicht in einem Multiplayer verwenden möchtest, kannst du diese Nachricht ignorieren.", "language_code" : "de", @@ -210,6 +253,8 @@ "list-active__title" : "Diese Sammlung in gameSettings.xml auf Aktiv setzen", "list-none" : "Deaktivieren", "list-none__title" : "Deaktivieren der Überschreibung in gameSettings.xml", + "load_save_import_failed" : "Die angegebene Speicherdatei konnte nicht gelesen werden, der Import konnte nicht fortgesetzt werden.", + "load_save_import_title" : "Ungültiger Speicherstand", "loading_cache_subtitle" : "Bereinigung zusätzlicher Cache-Einträge (langsam!)", "loading_cache_title" : "Cache reinigen", "loading_download_subtitle" : "Bitte warten. Dies kann einige Minuten dauern", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Acker für Fläche verwenden", "notes_units_use_fahrenheit" : "Fahrenheit für Temperatur verwenden", "notes_units_use_miles" : "Meilen für Distanz verwenden", - "notes_usage_blurb" : "Bitte beachten: Um Änderungen hier zu aktivieren, drücken Sie erneut die Schaltfläche \"Aktivieren\" für diese Sammlung!", + "notes_usage_blurb_2" : "Bitte beachten Sie: Dies ist die derzeit aktive Sammlung - um hier Änderungen (Passwort, Filter, Benutzername) zu aktivieren, müssen Sie die Schaltfläche \"Aktiv setzen\" für diese Sammlung erneut drücken!", "offcanvas_blurb" : "Wählen Sie die Version des Landwirtschafts-Simulators aus, die Sie aktivieren möchten", "open_debug_log" : "Debug-Log anzeigen", "open_folder" : "Im Explorer öffnen", @@ -409,6 +454,9 @@ "save_csv_failed" : "Exportieren der CSV-Sammlungsliste nicht möglich (Debug-Log überprüfen)", "save_csv_worked" : "Sammlung in CSV-Liste exportiert", "save_import_slot" : "Ziel Slot", + "save_json" : "Export-Einstellungen (JSON)", + "save_json_failed" : "Kann Sammlung nicht nach JSON exportieren (Debug-Log überprüfen)", + "save_json_worked" : "Sammlung in CSV-Liste exportiert", "save_log_failed" : "Debug-Log konnte nicht gespeichert werden", "save_log_worked" : "Debug-Log wurde gespeichert", "save_manage_active" : "Aktive Spielstände", diff --git a/translations/en.json b/translations/en.json index f795b673..d020cc56 100644 --- a/translations/en.json +++ b/translations/en.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "This collection has additional information associated with it", "bad_folder_folder" : "Folder Location:", "bad_folder_title" : "Collection Folder Missing", + "bad_game_set_xml_message_back" : "Your gameSettings.xml exists but is empty or unreadable. A backup of this file exists, would you like to restore it?", + "bad_game_set_xml_message_no_back" : "Your gameSettings.xml exists but is empty or unreadable. A backup of this file does not exist - the recommended action is to delete it, then you need to start the game to automatically create a new one", + "bad_game_set_xml_title" : "gameSettings.xml Problem Detected - Version :", + "bad_game_xml_delete" : "Delete Bad File (game start required!)", + "bad_game_xml_message_back" : "Your game.xml exists but is empty or unreadable. A backup of this file exists, would you like to restore it?", + "bad_game_xml_message_no_back" : "Your game.xml exists but is empty or unreadable. A backup of this file does not exist - the recommended action is to delete it, then you need to start the game to automatically create a new one", + "bad_game_xml_nothing" : "Do Nothing, I'll fix this myself", + "bad_game_xml_restore" : "Restore the backup", + "bad_game_xml_title" : "game.xml Problem Detected - Version :", "base_game_data__title" : "Browse Base Game Items", "basegame_attach" : "Attachment", "basegame_attach_has" : "Accepts attachments with", @@ -80,6 +89,7 @@ "context_mod_detail" : "Show Details", "context_open_website" : "Open Source Website", "context_paste" : "Paste", + "context_select_all" : "Select All", "context_set_website" : "Set Source Website", "copy" : "Yes, Copy Mod(s)", "copy_cropcal" : "Copy Crop Calender JSON to Clipboard", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Alfalfa", "croptype_barley" : "Barley", "croptype_canola" : "Canola", + "croptype_carrot" : "Carrot", "croptype_clover" : "Clover", "croptype_cotton" : "Cotton", "croptype_grape" : "Grapes", @@ -112,14 +123,20 @@ "croptype_oat" : "Oat", "croptype_oilseedradish" : "Oilseed Radish", "croptype_olive" : "Olives", + "croptype_onion" : "Onion", "croptype_poplar" : "Poplar", + "croptype_poppy" : "Poppy", "croptype_potato" : "Potato", + "croptype_redcabbage" : "Red Cabbage", + "croptype_rye" : "Rye", "croptype_sorghum" : "Sorghum", "croptype_soybean" : "Soybean", + "croptype_spelt" : "Spelt", "croptype_sugarbeet" : "Sugarbeet", "croptype_sugarcane" : "Sugar Cane", "croptype_sunflower" : "Sunflower", "croptype_wheat" : "Wheat", + "croptype_whitecabbage" : "White Cabbage", "delete" : "Yes, Delete Mods", "destination" : "Destination Collection", "destination_clear" : "Destination file does not exist, good to go", @@ -196,6 +213,32 @@ "game_running_warning" : "Game is currently running - changes to the active mod collection will require a game restart!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Open Documentation", + "import_json_blurb" : "This tool will let you import a collection with settings", + "import_json_folder_empty" : "Folder found on disk, press 'Apply Settings' button to complete the operation", + "import_json_folder_not_empty" : "Folder already contains files, if you are sure this is what you want, press 'Apply Settings' button to complete the operation", + "import_json_load_failed" : "JSON collection import file was unable to be read from disk.", + "import_json_no" : "not enabled", + "import_json_not_set" : "no value provided", + "import_json_read_failed" : "JSON collection import file was invalid.", + "import_json_step_0_blurb" : "These are the settings included in the import file", + "import_json_step_0_title" : "Included Settings", + "import_json_step_1_blurb" : "Choose a place to store this collection of mods", + "import_json_step_1_folder" : "Choose Folder Location", + "import_json_step_1_folder_blurb" : "Choose the location on disk for this collection. You can use an existing folder or create a new one in the popup dialog", + "import_json_step_1_folder_button" : "Set Location", + "import_json_step_1_folder_return" : "Collection Location", + "import_json_step_1_options" : "Apply Collection Settings", + "import_json_step_1_options_blurb" : "Apply the included settings to this collection", + "import_json_step_1_options_button" : "Apply Settings", + "import_json_step_1_title" : "Step 1 : Choose A Location & Import", + "import_json_step_2_blurb" : "The following mods and mod packs are included in this collection - you can optionally download them here. Note that any existing files will be overwritten by the downloaded copies.", + "import_json_step_2_download" : "Download", + "import_json_step_2_download_unpack" : "Download and Unpack", + "import_json_step_2_pack" : "Mod Packs", + "import_json_step_2_single" : "Single Mods", + "import_json_step_2_title" : "Step 2 : Download Mods (optional)", + "import_json_title" : "Import Collection", + "import_json_yes" : "enabled", "info_might_be_piracy" : "This mod appears to be a cracked / pirated version of a paid DLC. This test is simplistic, so this could be a false positive, however be aware that using cracked DLC will prevent you from receiving help from Giants support, and is grounds for a ban from the official discord and forums. Please support the developers!", "info_no_multiplayer_unzipped" : "Unzipped mods cannot be used in multiplayer. If you don't intend to use this mod in a multiplayer game, you can ignore this message.", "language_code" : "en", @@ -210,6 +253,8 @@ "list-active__title" : "Set this Collection Active in gameSettings.xml", "list-none" : "Deactivate", "list-none__title" : "Deactivate override in gameSettings.xml", + "load_save_import_failed" : "Unable to read specified save file, unable to continue import.", + "load_save_import_title" : "Invalid Save File", "loading_cache_subtitle" : "Cleaning extra cache entries (slow!)", "loading_cache_title" : "Cleaning Cache", "loading_download_subtitle" : "Please wait, this could take a minute", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Use Acres for Area", "notes_units_use_fahrenheit" : "Use Fahrenheit for Temperature", "notes_units_use_miles" : "Use Miles For Distance", - "notes_usage_blurb" : "Please Note: To activate any changes here you must press the \"Set Active\" button for this collection again!", + "notes_usage_blurb_2" : "Please Note: This is the currently active collection - to activate any changes here (password, filters, user name) you must press the \"Set Active\" button for this collection again!", "offcanvas_blurb" : "Select which version of Farming Simulator you wish to be active", "open_debug_log" : "View Debug Log", "open_folder" : "Open in Explorer", @@ -409,6 +454,9 @@ "save_csv_failed" : "Unable to export CSV collection list (check debug log)", "save_csv_worked" : "Collection Exported to CSV List", "save_import_slot" : "Destination Slot", + "save_json" : "Export Settings (JSON)", + "save_json_failed" : "Unable to export collection to JSON (check debug log)", + "save_json_worked" : "Collection Exported to JSON", "save_log_failed" : "Debug log save failed", "save_log_worked" : "Debug log saved successfully", "save_manage_active" : "Active Save Games", diff --git a/translations/es.json b/translations/es.json index 3c0230a8..6c9d4b14 100644 --- a/translations/es.json +++ b/translations/es.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Esta colección tiene información adicional asociada", "bad_folder_folder" : "Ubicación de la carpeta:", "bad_folder_title" : "Falta la carpeta de recogida", + "bad_game_set_xml_message_back" : "Tu gameSettings.xml existe pero está vacío o es ilegible. Existe una copia de seguridad de este archivo, ¿te gustaría restaurarla?", + "bad_game_set_xml_message_no_back" : "Tu gameSettings.xml existe pero está vacío o es ilegible. Una copia de seguridad de este archivo no existe - la acción recomendada es eliminarlo, entonces usted necesita para iniciar el juego para crear automáticamente una nueva", + "bad_game_set_xml_title" : "gameSettings.xml Problema detectado - Versión :", + "bad_game_xml_delete" : "Eliminar archivo defectuoso (es necesario iniciar el juego)", + "bad_game_xml_message_back" : "Tu game.xml existe pero está vacío o es ilegible. Existe una copia de seguridad de este archivo, ¿te gustaría restaurarla?", + "bad_game_xml_message_no_back" : "Tu game.xml existe pero está vacío o es ilegible. No existe una copia de seguridad de este archivo - la acción recomendada es eliminarlo, entonces usted necesita para iniciar el juego para crear automáticamente una nueva", + "bad_game_xml_nothing" : "No hagas nada, lo arreglaré yo mismo", + "bad_game_xml_restore" : "Restaurar la copia de seguridad", + "bad_game_xml_title" : "game.xml Problema detectado - Versión :", "base_game_data__title" : "Examinar artículos de juego base", "basegame_attach" : "Adjunto", "basegame_attach_has" : "Acepta accesorios con", @@ -80,6 +89,7 @@ "context_mod_detail" : "Mostrar detalles", "context_open_website" : "Sitio web de código abierto", "context_paste" : "Pegar", + "context_select_all" : "Seleccionar todo", "context_set_website" : "Establecer sitio web de origen", "copy" : "Sí, Copiar Mod(s)", "copy_cropcal" : "Copiar JSON de Calendario de Recortes al Portapapeles", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Alfalfa", "croptype_barley" : "Cebada", "croptype_canola" : "Colza", + "croptype_carrot" : "Zanahoria", "croptype_clover" : "Trébol", "croptype_cotton" : "Algodón", "croptype_grape" : "Uva", @@ -112,14 +123,20 @@ "croptype_oat" : "Avena", "croptype_oilseedradish" : "Rábano oleífero", "croptype_olive" : "Aceitunas", + "croptype_onion" : "Cebolla", "croptype_poplar" : "Álamo", + "croptype_poppy" : "Amapola", "croptype_potato" : "Patatas", + "croptype_redcabbage" : "Lombarda", + "croptype_rye" : "Centeno", "croptype_sorghum" : "Sorgo", "croptype_soybean" : "Habas de soja", + "croptype_spelt" : "Escanda", "croptype_sugarbeet" : "Remolacha azuc.", "croptype_sugarcane" : "Caña de azúcar", "croptype_sunflower" : "Girasol", "croptype_wheat" : "Trigo", + "croptype_whitecabbage" : "Col blanca", "delete" : "Sí, eliminar el mod.", "destination" : "Destino previsto:", "destination_clear" : "El archivo de destino no existe, listo para funcionar", @@ -196,6 +213,32 @@ "game_running_warning" : "El juego se está ejecutando actualmente: ¡los cambios en la colección de mods activa requerirán reiniciar el juego!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Documentación abierta", + "import_json_blurb" : "Esta herramienta le permitirá importar una colección con ajustes", + "import_json_folder_empty" : "Carpeta encontrada en el disco, pulse el botón \"Aplicar configuración\" para completar la operación.", + "import_json_folder_not_empty" : "La carpeta ya contiene archivos, si está seguro de que esto es lo que desea, pulse el botón \"Aplicar configuración\" para completar la operación.", + "import_json_load_failed" : "No se ha podido leer del disco el archivo de importación de la colección JSON.", + "import_json_no" : "no activado", + "import_json_not_set" : "no se proporciona ningún valor", + "import_json_read_failed" : "El archivo de importación de la colección JSON no era válido.", + "import_json_step_0_blurb" : "Estos son los ajustes incluidos en el archivo de importación", + "import_json_step_0_title" : "Ajustes incluidos", + "import_json_step_1_blurb" : "Elige un lugar para guardar esta colección de mods", + "import_json_step_1_folder" : "Elija la ubicación de la carpeta", + "import_json_step_1_folder_blurb" : "Elija la ubicación en disco para esta colección. Puede utilizar una carpeta existente o crear una nueva en el cuadro de diálogo emergente", + "import_json_step_1_folder_button" : "Establecer ubicación", + "import_json_step_1_folder_return" : "Ubicación de la colección", + "import_json_step_1_options" : "Aplicar ajustes de recogida", + "import_json_step_1_options_blurb" : "Aplique los ajustes incluidos a esta colección", + "import_json_step_1_options_button" : "Aplicar ajustes", + "import_json_step_1_title" : "Paso 1: Elija una ubicación e importe", + "import_json_step_2_blurb" : "Los siguientes mods y packs de mods están incluidos en esta colección - puedes descargarlos opcionalmente aquí. Ten en cuenta que los archivos existentes se sobrescribirán con las copias descargadas.", + "import_json_step_2_download" : "Descargar", + "import_json_step_2_download_unpack" : "Descargar y desempaquetar", + "import_json_step_2_pack" : "Mod Packs", + "import_json_step_2_single" : "Mods individuales", + "import_json_step_2_title" : "Paso 2: Descarga de mods (opcional)", + "import_json_title" : "Importar colección", + "import_json_yes" : "habilitado", "info_might_be_piracy" : "Este mod parece ser una versión pirateada de un DLC de pago. Esta prueba es simple, por lo que podría ser un falso positivo. Sin embargo, tenga en cuenta que el uso de tales copias pirateadas puede provocar la pérdida de soporte de GIANTS y ​​el bloqueo del servidor en Discord de GIANTS y el foro. Apoye a los desarrolladores! Hombre por favor...", "info_no_multiplayer_unzipped" : "Los mods descomprimidos no se pueden usar en multiplayer. Si no tiene la intención de utilizar este mod en un juego multijugador, puede ignorar este mensaje.", "language_code" : "es", @@ -210,6 +253,8 @@ "list-active__title" : "Establece esta colección como activa en gameSettings.xml", "list-none" : "Desactivar", "list-none__title" : "Desactivar la anulación en gameSettings.xml", + "load_save_import_failed" : "No se ha podido leer el archivo guardado especificado, no se puede continuar con la importación.", + "load_save_import_title" : "Archivo de almacenamiento no válido", "loading_cache_subtitle" : "Limpieza de entradas de caché adicionales (¡lento!)", "loading_cache_title" : "Limpieza de la caché", "loading_download_subtitle" : "Por favor, espere, esto puede tardar un minuto", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Utilizar acres para superficie", "notes_units_use_fahrenheit" : "Utilice Fahrenheit para la temperatura", "notes_units_use_miles" : "Utilizar millas para la distancia", - "notes_usage_blurb" : "Nota: ¡Para activar cualquier cambio aquí debe pulsar el botón de \"Establecer Activo\" para esta colección de nuevo!", + "notes_usage_blurb_2" : "¡Nota: Esta es la colección actualmente activa - para activar cualquier cambio aquí (contraseña, filtros, nombre de usuario) debe pulsar de nuevo el botón \"Establecer activo\" para esta colección!", "offcanvas_blurb" : "Selecciona qué versión de Farming Simulator deseas que esté activa", "open_debug_log" : "Ver Log Depuración", "open_folder" : "Abrir en el Explorador", @@ -409,6 +454,9 @@ "save_csv_failed" : "No se puede exportar la lista de recogida CSV (compruebe el registro de depuración)", "save_csv_worked" : "Colección exportada a lista CSV", "save_import_slot" : "Ranura de destino", + "save_json" : "Exportar configuración (JSON)", + "save_json_failed" : "No se puede exportar la colección a JSON (compruebe el registro de depuración)", + "save_json_worked" : "Colección exportada a JSON", "save_log_failed" : "Ha fallado el guardado del Log de Depuración", "save_log_worked" : "Log de Depuración guardado satisfactoriamente", "save_manage_active" : "Juegos de guardar activos", diff --git a/translations/fr.json b/translations/fr.json index 8a7f2319..ed321c40 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Des informations complémentaires sont associées à cette collection", "bad_folder_folder" : "Emplacement du dossier :", "bad_folder_title" : "Dossier de collection manquant", + "bad_game_set_xml_message_back" : "Votre fichier gameSettings.xml existe mais est vide ou illisible. Une sauvegarde de ce fichier existe, souhaitez-vous la restaurer ?", + "bad_game_set_xml_message_no_back" : "Votre fichier gameSettings.xml existe mais il est vide ou illisible. Une sauvegarde de ce fichier n'existe pas - l'action recommandée est de le supprimer, puis de démarrer le jeu pour en créer automatiquement un nouveau.", + "bad_game_set_xml_title" : "gameSettings.xml Problème détecté - Version :", + "bad_game_xml_delete" : "Supprimer le fichier défectueux (démarrage du jeu requis !)", + "bad_game_xml_message_back" : "Votre fichier game.xml existe mais il est vide ou illisible. Une sauvegarde de ce fichier existe, souhaitez-vous la restaurer ?", + "bad_game_xml_message_no_back" : "Votre fichier game.xml existe mais il est vide ou illisible. Une sauvegarde de ce fichier n'existe pas - l'action recommandée est de le supprimer, puis de lancer le jeu pour en créer automatiquement un nouveau.", + "bad_game_xml_nothing" : "Ne rien faire, je me débrouillerai tout seul", + "bad_game_xml_restore" : "Restaurer la sauvegarde", + "bad_game_xml_title" : "game.xml Problème détecté - Version :", "base_game_data__title" : "Parcourir les articles de jeu de base", "basegame_attach" : "Pièce jointe", "basegame_attach_has" : "Accepte les pièces jointes avec", @@ -80,6 +89,7 @@ "context_mod_detail" : "Afficher les détails", "context_open_website" : "Site web à source ouverte", "context_paste" : "Coller", + "context_select_all" : "Sélectionner tout", "context_set_website" : "Set Source Website", "copy" : "Oui, copier le(s) mod(s)", "copy_cropcal" : "Copier le JSON du calendrier des récoltes dans le presse-papiers", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Luzerne", "croptype_barley" : "Orge", "croptype_canola" : "Colza", + "croptype_carrot" : "Carotte", "croptype_clover" : "Trèfle", "croptype_cotton" : "Coton", "croptype_grape" : "Raisin", @@ -112,14 +123,20 @@ "croptype_oat" : "Avoine", "croptype_oilseedradish" : "Radis oléagineux", "croptype_olive" : "Olives", + "croptype_onion" : "Oignon", "croptype_poplar" : "Peuplier", + "croptype_poppy" : "Coquelicot", "croptype_potato" : "P. de terre", + "croptype_redcabbage" : "Chou rouge", + "croptype_rye" : "Seigle", "croptype_sorghum" : "Sorgho", "croptype_soybean" : "Soja", + "croptype_spelt" : "Épeautre", "croptype_sugarbeet" : "B. sucrières", "croptype_sugarcane" : "Canne à sucre", "croptype_sunflower" : "Tournesols", "croptype_wheat" : "Blé", + "croptype_whitecabbage" : "Chou blanc", "delete" : "Oui, Delete Mod", "destination" : "Destination prévue :", "destination_clear" : "Le fichier de destination n'existe pas, c'est bon à prendre", @@ -196,6 +213,32 @@ "game_running_warning" : "Le jeu est en cours d'exécution - les modifications apportées à la collection active de mods nécessiteront un redémarrage du jeu !", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Ouvrir Documentation", + "import_json_blurb" : "Cet outil vous permet d'importer une collection avec des paramètres", + "import_json_folder_empty" : "Dossier trouvé sur le disque, appuyez sur le bouton \"Appliquer les paramètres\" pour terminer l'opération.", + "import_json_folder_not_empty" : "Le dossier contient déjà des fichiers, si vous êtes sûr que c'est ce que vous voulez, appuyez sur le bouton \"Appliquer les paramètres\" pour terminer l'opération.", + "import_json_load_failed" : "Le fichier d'importation de la collection JSON n'a pas pu être lu à partir du disque.", + "import_json_no" : "non activé", + "import_json_not_set" : "aucune valeur fournie", + "import_json_read_failed" : "Le fichier d'importation de la collection JSON n'était pas valide.", + "import_json_step_0_blurb" : "Voici les paramètres inclus dans le fichier d'importation", + "import_json_step_0_title" : "Paramètres inclus", + "import_json_step_1_blurb" : "Choisir un endroit pour stocker cette collection de mods", + "import_json_step_1_folder" : "Choisir l'emplacement du dossier", + "import_json_step_1_folder_blurb" : "Choisissez l'emplacement de cette collection sur le disque. Vous pouvez utiliser un dossier existant ou en créer un nouveau dans la boîte de dialogue.", + "import_json_step_1_folder_button" : "Lieu d'implantation", + "import_json_step_1_folder_return" : "Emplacement de la collection", + "import_json_step_1_options" : "Appliquer les paramètres de la collection", + "import_json_step_1_options_blurb" : "Appliquer les paramètres inclus à cette collection", + "import_json_step_1_options_button" : "Appliquer les paramètres", + "import_json_step_1_title" : "Étape 1 : Choisir un lieu et importer", + "import_json_step_2_blurb" : "Les mods et packs de mods suivants sont inclus dans cette collection - vous pouvez les télécharger ici. Notez que tous les fichiers existants seront écrasés par les copies téléchargées.", + "import_json_step_2_download" : "Télécharger", + "import_json_step_2_download_unpack" : "Télécharger et décompresser", + "import_json_step_2_pack" : "Mod Packs", + "import_json_step_2_single" : "Mods uniques", + "import_json_step_2_title" : "Étape 2 : Télécharger les mods (facultatif)", + "import_json_title" : "Collection d'importations", + "import_json_yes" : "activée", "info_might_be_piracy" : "Ce mod semble être une version craquée / piratée d'un DLC payant. Ce test est sommaire, il peut donc s'agir d'un faux positif, mais sachez que l'utilisation d'un DLC piraté vous empêchera de recevoir de l'aide de l'assistance Giants, et constitue un motif de bannissement du discord et des forums officiels. Soutenez les développeurs !", "info_no_multiplayer_unzipped" : "Les mods décompressés ne peuvent pas être utilisés en mode multijoueur. Si vous n'avez pas l'intention d'utiliser ce mod dans une partie multijoueur, vous pouvez ignorer ce message.", "language_code" : "fr", @@ -210,6 +253,8 @@ "list-active__title" : "Définissez cette collection active dans gameSettings.xml.", "list-none" : "Désactiver", "list-none__title" : "Désactiver la surcharge dans gameSettings.xml", + "load_save_import_failed" : "Impossible de lire le fichier d'enregistrement spécifié, impossible de poursuivre l'importation.", + "load_save_import_title" : "Fichier d'enregistrement invalide", "loading_cache_subtitle" : "Nettoyage des entrées supplémentaires dans le cache (lent !)", "loading_cache_title" : "Nettoyage du cache", "loading_download_subtitle" : "Veuillez patienter. Cette action peut prendre quelques minutes", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Utiliser les acres pour la superficie", "notes_units_use_fahrenheit" : "Utiliser Fahrenheit pour la température", "notes_units_use_miles" : "Utiliser les miles pour la distance", - "notes_usage_blurb" : "Veuillez noter : Pour activer tout changement ici, vous devez appuyer sur le bouton \"Définir actif\" pour cette collection à nouveau !", + "notes_usage_blurb_2" : "Notez svp: Il s'agit de la collection actuellement active - pour activer toute modification (mot de passe, filtres, nom d'utilisateur), vous devez appuyer à nouveau sur le bouton \"Définir actif\" pour cette collection !", "offcanvas_blurb" : "Sélectionnez la version de Farming Simulator que vous souhaitez activer", "open_debug_log" : "Voir le log de débogage", "open_folder" : "Ouvrir dans Explorer", @@ -409,6 +454,9 @@ "save_csv_failed" : "Impossible d'exporter la liste des collections CSV (vérifiez le journal de débogage)", "save_csv_worked" : "Collection exportée vers la liste CSV", "save_import_slot" : "Emplacement de destination", + "save_json" : "Exporter les paramètres (JSON)", + "save_json_failed" : "Impossible d'exporter la collection vers JSON (vérifier le journal de débogage)", + "save_json_worked" : "Collection exportée en JSON", "save_log_failed" : "La sauvegarde du log de débogage a échoué", "save_log_worked" : "Log de débogage sauvegardé avec succès", "save_manage_active" : "Active Save Games", diff --git a/translations/lv.json b/translations/lv.json index 69617b6d..6af42bc6 100644 --- a/translations/lv.json +++ b/translations/lv.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Šai kolekcijai ir pievienota papildu informācija", "bad_folder_folder" : "Mapes atrašanās vieta:", "bad_folder_title" : "Trūkst kolekcijas mapes", + "bad_game_set_xml_message_back" : "Jūsu gameSettings.xml pastāv, bet ir tukšs vai nelasāms. Šī faila dublējums pastāv, vai vēlaties to atjaunot?", + "bad_game_set_xml_message_no_back" : "Jūsu gameSettings.xml pastāv, bet ir tukšs vai nelasāms. Šī faila rezerves kopija neeksistē - ieteicamā darbība ir to izdzēst, pēc tam jums ir jāsāk spēle, lai automātiski izveidotu jaunu failu.", + "bad_game_set_xml_title" : "gameSettings.xml Atklāta problēma - versija :", + "bad_game_xml_delete" : "Izdzēst sliktu failu (nepieciešama spēles sākšana!)", + "bad_game_xml_message_back" : "Jūsu game.xml pastāv, bet ir tukšs vai nelasāms. Šī faila dublējums pastāv, vai vēlaties to atjaunot?", + "bad_game_xml_message_no_back" : "Jūsu game.xml pastāv, bet ir tukšs vai nelasāms. Šī faila rezerves kopija neeksistē - ieteicamā darbība ir to izdzēst, pēc tam jums ir jāsāk spēle, lai automātiski tiktu izveidots jauns fails.", + "bad_game_xml_nothing" : "Nedariet neko, es pats to salaboju", + "bad_game_xml_restore" : "Rezerves kopijas atjaunošana", + "bad_game_xml_title" : "game.xml Atklāta problēma - versija :", "base_game_data__title" : "Pārlūkot bāzes spēļu preces", "basegame_attach" : "Pielikums", "basegame_attach_has" : "Pieņem pielikumus ar", @@ -80,6 +89,7 @@ "context_mod_detail" : "Rādīt informāciju", "context_open_website" : "Atvērtā koda tīmekļa vietne", "context_paste" : "Ielīmēt", + "context_select_all" : "Atlasiet visus", "context_set_website" : "Iestatīt avota vietni", "copy" : "Jā, kopēt Mod(s)", "copy_cropcal" : "Apcirpšanas kalendāra JSON kopēšana uz starpliktuvi", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Lucerna", "croptype_barley" : "Mieži", "croptype_canola" : "Kanolas", + "croptype_carrot" : "Burkāni", "croptype_clover" : "Clover", "croptype_cotton" : "Kokvilna", "croptype_grape" : "Vīnogas", @@ -112,14 +123,20 @@ "croptype_oat" : "Auzas", "croptype_oilseedradish" : "Eļļas redīsi", "croptype_olive" : "Olīvas", + "croptype_onion" : "Sīpols", "croptype_poplar" : "Topols", + "croptype_poppy" : "Magones", "croptype_potato" : "Kartupelis", + "croptype_redcabbage" : "Sarkanie kāposti", + "croptype_rye" : "Rudzu", "croptype_sorghum" : "Sorgo", "croptype_soybean" : "Sojas pupas", + "croptype_spelt" : "Speltas pākstis", "croptype_sugarbeet" : "Cukurbietes", "croptype_sugarcane" : "Cukurniedres", "croptype_sunflower" : "Saulespuķe", "croptype_wheat" : "Kvieši", + "croptype_whitecabbage" : "Baltie kāposti", "delete" : "Jā, dzēst Mods", "destination" : "Galamērķa kolekcija:", "destination_clear" : "Galamērķa fails neeksistē, var sākt", @@ -196,6 +213,32 @@ "game_running_warning" : "Spēle pašlaik darbojas - lai veiktu izmaiņas aktīvajā modu kolekcijā, būs nepieciešams spēli restartēt!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Atvērt dokumentāciju", + "import_json_blurb" : "Šis rīks ļauj importēt kolekciju ar iestatījumiem.", + "import_json_folder_empty" : "Mape atrasta diskā, nospiediet pogu \"Piemērot iestatījumus\", lai pabeigtu darbību.", + "import_json_folder_not_empty" : "Mapē jau ir faili, ja esat pārliecināts, ka tas ir tas, ko vēlaties, nospiediet pogu \"Apply Settings\", lai pabeigtu darbību.", + "import_json_load_failed" : "JSON kolekcijas importa failu nav iespējams nolasīt no diska.", + "import_json_no" : "nav iespējots", + "import_json_not_set" : "vērtība nav norādīta", + "import_json_read_failed" : "JSON kolekcijas importa fails bija nederīgs.", + "import_json_step_0_blurb" : "Šie ir iestatījumi, kas iekļauti importa failā", + "import_json_step_0_title" : "Iekļautie iestatījumi", + "import_json_step_1_blurb" : "Izvēlieties vietu, kur glabāt šo modu kolekciju", + "import_json_step_1_folder" : "Izvēlēties mapes atrašanās vietu", + "import_json_step_1_folder_blurb" : "Izvēlieties šīs kolekcijas atrašanās vietu uz diska. Dialoglodziņā var izmantot esošo mapi vai izveidot jaunu mapi.", + "import_json_step_1_folder_button" : "Iestatīt atrašanās vietu", + "import_json_step_1_folder_return" : "Kolekcijas atrašanās vieta", + "import_json_step_1_options" : "Kolekcijas iestatījumu piemērošana", + "import_json_step_1_options_blurb" : "Piemērot iekļautos iestatījumus šai kolekcijai", + "import_json_step_1_options_button" : "Piemērot iestatījumus", + "import_json_step_1_title" : "1. solis : Izvēlieties atrašanās vietu un importējiet", + "import_json_step_2_blurb" : "Šajā kolekcijā ir iekļauti šādi modi un modu paketes - tos pēc izvēles varat lejupielādēt šeit. Ņemiet vērā, ka visi esošie faili tiks pārrakstīti ar lejupielādētajām kopijām.", + "import_json_step_2_download" : "Lejupielādēt", + "import_json_step_2_download_unpack" : "Lejupielādēt un izpakot", + "import_json_step_2_pack" : "Modu paketes", + "import_json_step_2_single" : "Atsevišķi modeļi", + "import_json_step_2_title" : "2. solis : Lejupielādēt Mods (pēc izvēles)", + "import_json_title" : "Importa kolekcija", + "import_json_yes" : "iespējota", "info_might_be_piracy" : "Šķiet, ka šis mod veids ir krekinga / pirātiska apmaksāta DLC versija. Šis tests ir vienkāršots, tāpēc tas var būt viltus pozitīvs rezultāts, tomēr ņemiet vērā, ka, izmantojot krekinga DLC, jūs nevarēsiet saņemt palīdzību no Giants atbalsta un tas ir pamats oficiālā discord un forumu aizliegumam. Lūdzu, atbalstiet izstrādātājus!", "info_no_multiplayer_unzipped" : "Neizplēstos modus nevar izmantot multiplayer režīmā. Ja jūs neplānojat izmantot šo modi spēlē vairākiem spēlētājiem, varat ignorēt šo ziņojumu.", "language_code" : "lv", @@ -210,6 +253,8 @@ "list-active__title" : "Iestatiet šo kolekciju Active in gameSettings.xml", "list-none" : "Deaktivizēt", "list-none__title" : "Deaktivizēt override in gameSettings.xml", + "load_save_import_failed" : "Nevar nolasīt norādīto saglabāšanas failu, nevar turpināt importēšanu.", + "load_save_import_title" : "Nederīgs saglabāšanas fails", "loading_cache_subtitle" : "Papildu kešatmiņas ierakstu tīrīšana (lēni!)", "loading_cache_title" : "Cache tīrīšana", "loading_download_subtitle" : "Lūdzu, pagaidiet, tas var aizņemt minūti", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Izmantojiet akrus platībai", "notes_units_use_fahrenheit" : "Temperatūras apzīmēšanai izmantojiet Fārenheita grādu", "notes_units_use_miles" : "Attāluma apzīmēšanai izmantojiet jūdzes", - "notes_usage_blurb" : "Pievērsiet uzmanību: Lai aktivizētu šeit veiktās izmaiņas, vēlreiz nospiediet šīs kolekcijas pogu \"Iestatīt aktīvo\"!", + "notes_usage_blurb_2" : " Lūdzu, ņemiet vērā: Šī ir pašreiz aktīvā kolekcija - lai aktivizētu jebkādas izmaiņas (parole, filtri, lietotājvārds), jums vēlreiz jānospiež šīs kolekcijas poga \"Iestatīt aktīvu\"!", "offcanvas_blurb" : "Izvēlieties, kuru Farming Simulator versiju vēlaties, lai tā būtu aktīva", "open_debug_log" : "Skatīt atkļūdošanas žurnālu", "open_folder" : "Atvērt pārlūkā Explorer", @@ -409,6 +454,9 @@ "save_csv_failed" : "Nav iespējams eksportēt CSV kolekciju sarakstu (pārbaudiet atkļūdošanas žurnālu)", "save_csv_worked" : "Kolekcija eksportēta uz CSV sarakstu", "save_import_slot" : "Galamērķa slots", + "save_json" : "Eksportēt iestatījumus (JSON)", + "save_json_failed" : "Nav iespējams eksportēt kolekciju uz JSON (pārbaudiet atkļūdošanas žurnālu)", + "save_json_worked" : "Kolekcija eksportēta uz JSON", "save_log_failed" : "Debug log save failed", "save_log_worked" : "Veiksmīgi saglabāts atkļūdošanas žurnāls", "save_manage_active" : "Aktīvās saglabāšanas spēles", diff --git a/translations/nl.json b/translations/nl.json index c9d3c32c..e92cfc57 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Aan deze collectie is aanvullende informatie gekoppeld", "bad_folder_folder" : "Map Locatie:", "bad_folder_title" : "Collectie Map ontbreekt", + "bad_game_set_xml_message_back" : "Je gameSettings.xml bestaat, maar is leeg of onleesbaar. Er bestaat een back-up van dit bestand, wil je deze herstellen?", + "bad_game_set_xml_message_no_back" : "Je gameSettings.xml bestaat, maar is leeg of onleesbaar. Een back-up van dit bestand bestaat niet - de aanbevolen actie is om het te verwijderen, daarna moet je het spel starten om automatisch een nieuwe aan te maken.", + "bad_game_set_xml_title" : "gameSettings.xml Probleem gedetecteerd - Versie :", + "bad_game_xml_delete" : "Slecht bestand verwijderen (spel starten vereist!)", + "bad_game_xml_message_back" : "Je game.xml bestaat, maar is leeg of onleesbaar. Er bestaat een back-up van dit bestand, wil je deze herstellen?", + "bad_game_xml_message_no_back" : "Je game.xml bestaat, maar is leeg of onleesbaar. Een back-up van dit bestand bestaat niet - de aanbevolen actie is om het te verwijderen, daarna moet je het spel starten om automatisch een nieuwe aan te maken.", + "bad_game_xml_nothing" : "Niets doen, ik los het zelf wel op", + "bad_game_xml_restore" : "De back-up herstellen", + "bad_game_xml_title" : "game.xml Probleem gedetecteerd - Versie :", "base_game_data__title" : "Bladeren door basisspelartikelen", "basegame_attach" : "Bijlage", "basegame_attach_has" : "Accepteert bevestigingen met", @@ -80,6 +89,7 @@ "context_mod_detail" : "Details weergeven", "context_open_website" : "Open bronwebsite", "context_paste" : "Plakken", + "context_select_all" : "Alles selecteren", "context_set_website" : "Bronwebsite instellen", "copy" : "Ja, Copy Mod(s)", "copy_cropcal" : "Kopieer Crop Calender JSON naar Klembord", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Alfalfa", "croptype_barley" : "Gerst", "croptype_canola" : "Koolzaad", + "croptype_carrot" : "Wortel", "croptype_clover" : "Klaver", "croptype_cotton" : "Katoen", "croptype_grape" : "Druiven", @@ -112,14 +123,20 @@ "croptype_oat" : "Haver", "croptype_oilseedradish" : "Oliezaad Radijs", "croptype_olive" : "Olijven", + "croptype_onion" : "Ui", "croptype_poplar" : "Populier", + "croptype_poppy" : "Poppy", "croptype_potato" : "Aardappel", + "croptype_redcabbage" : "Rode kool", + "croptype_rye" : "Rogge", "croptype_sorghum" : "Sorgo", "croptype_soybean" : "Sojaboon", + "croptype_spelt" : "Spelt", "croptype_sugarbeet" : "Suikerbiet", "croptype_sugarcane" : "Suiker riet", "croptype_sunflower" : "Zonnebloem", "croptype_wheat" : "Tarwe", + "croptype_whitecabbage" : "Witte kool", "delete" : "Ja, Delete Mod", "destination" : "Bestemming:", "destination_clear" : "Bestemmingsbestand bestaat niet, goed om te gaan.", @@ -196,6 +213,32 @@ "game_running_warning" : "Het spel is momenteel actief - wijzigingen aan de actieve modverzameling vereisen een herstart van het spel!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Open documentatie", + "import_json_blurb" : "Met dit hulpmiddel kun je een verzameling met instellingen importeren", + "import_json_folder_empty" : "Map gevonden op schijf, druk op de knop 'Instellingen toepassen' om de bewerking te voltooien", + "import_json_folder_not_empty" : "De map bevat al bestanden. Als u zeker weet dat dit is wat u wilt, drukt u op de knop 'Instellingen toepassen' om de bewerking te voltooien.", + "import_json_load_failed" : "JSON collectie importbestand kon niet van schijf gelezen worden.", + "import_json_no" : "niet ingeschakeld", + "import_json_not_set" : "geen waarde opgegeven", + "import_json_read_failed" : "JSON collectie importbestand was ongeldig.", + "import_json_step_0_blurb" : "Dit zijn de instellingen in het importbestand", + "import_json_step_0_title" : "Inbegrepen instellingen", + "import_json_step_1_blurb" : "Kies een plaats om deze verzameling mods op te slaan", + "import_json_step_1_folder" : "Maplocatie kiezen", + "import_json_step_1_folder_blurb" : "Kies de locatie op schijf voor deze collectie. Je kunt een bestaande map gebruiken of een nieuwe map aanmaken in het pop-upvenster", + "import_json_step_1_folder_button" : "Locatie instellen", + "import_json_step_1_folder_return" : "Verzamellocatie", + "import_json_step_1_options" : "Collectie-instellingen toepassen", + "import_json_step_1_options_blurb" : "Pas de meegeleverde instellingen toe op deze collectie", + "import_json_step_1_options_button" : "Instellingen toepassen", + "import_json_step_1_title" : "Stap 1: Kies een locatie en importeer", + "import_json_step_2_blurb" : "De volgende mods en mod packs zijn opgenomen in deze verzameling - je kunt ze optioneel hier downloaden. Bestaande bestanden worden overschreven door de gedownloade exemplaren.", + "import_json_step_2_download" : "Downloaden", + "import_json_step_2_download_unpack" : "Downloaden en uitpakken", + "import_json_step_2_pack" : "Mod Packs", + "import_json_step_2_single" : "Losse mods", + "import_json_step_2_title" : "Stap 2: Mods downloaden (optioneel)", + "import_json_title" : "Collectie importeren", + "import_json_yes" : "ingeschakeld", "info_might_be_piracy" : "Deze mod lijkt een gekraakte versie van een betaalde DLC te zijn. Deze test is simplistisch, dus dit zou een valse positieve uitslag kunnen zijn. Wees er echter van bewust dat het gebruik van een gekraakte DLC je ervan weerhoudt hulp te ontvangen van Giants en reden is voor een ban van de officiële Discord en fora. Ondersteun de ontwikkelaars!", "info_no_multiplayer_unzipped" : "Uitgepakte mods kunnen niet worden gebruikt in multiplayer. Als je niet van plan bent om deze mod te gebruiken in een multiplayer spel, dan kun je dit bericht negeren.", "language_code" : "nl", @@ -210,6 +253,8 @@ "list-active__title" : "Stel deze collectie actief in gameSettings.xml", "list-none" : "Deactiveer", "list-none__title" : "Deactiveer override in gameSettings.xml", + "load_save_import_failed" : "Kan opgegeven opslagbestand niet lezen, kan import niet voortzetten.", + "load_save_import_title" : "Ongeldig bestand opslaan", "loading_cache_subtitle" : "Opschonen van extra cache entries (traag!)", "loading_cache_title" : "Cache schoonmaken", "loading_download_subtitle" : "Geduld a.u.b., dit kan enkele minuten duren", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Acres gebruiken voor oppervlakte", "notes_units_use_fahrenheit" : "Gebruik Fahrenheit voor temperatuur", "notes_units_use_miles" : "Gebruik mijlen voor afstand", - "notes_usage_blurb" : "Notitie: Als je de wijzigingen hier wilt activeren, moet je opnieuw op de \"Activieren\" knop voor deze collectie drukken!", + "notes_usage_blurb_2" : "Let op: Dit is de collectie die op dit moment actief is - om hier wijzigingen te activeren (wachtwoord, filters, gebruikersnaam) moet je opnieuw op de knop \"Actief instellen\" voor deze collectie drukken!", "offcanvas_blurb" : "Selecteer welke versie van Farming Simulator je wilt dat actief is", "open_debug_log" : "Bekijk Debug Log", "open_folder" : "Openen in Verkenner", @@ -409,6 +454,9 @@ "save_csv_failed" : "Kan CSV niet exporteren (controleer de error log)", "save_csv_worked" : "Collectie geëxporteerd naar CSV", "save_import_slot" : "Bestemming Slot", + "save_json" : "Instellingen exporteren (JSON)", + "save_json_failed" : "Kan verzameling niet exporteren naar JSON (controleer debug-logboek)", + "save_json_worked" : "Collectie geëxporteerd naar JSON", "save_log_failed" : "Opslaan debug log mislukt", "save_log_worked" : "Debug log succesvol opgeslagen", "save_manage_active" : "Actief opslaan Spelletjes", diff --git a/translations/pl.json b/translations/pl.json index d499d491..37832b4b 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Z tą kolekcją powiązane są dodatkowe informacje", "bad_folder_folder" : "Lokalizacja folderu:", "bad_folder_title" : "Brakujący folder kolekcji", + "bad_game_set_xml_message_back" : "Plik gameSettings.xml istnieje, ale jest pusty lub nieczytelny. Istnieje kopia zapasowa tego pliku, czy chcesz ją przywrócić?", + "bad_game_set_xml_message_no_back" : "Plik gameSettings.xml istnieje, ale jest pusty lub nieczytelny. Kopia zapasowa tego pliku nie istnieje - zalecanym działaniem jest usunięcie go, a następnie uruchomienie gry w celu automatycznego utworzenia nowego pliku.", + "bad_game_set_xml_title" : "gameSettings.xml Wykryto problem - Wersja :", + "bad_game_xml_delete" : "Usuń zły plik (wymagane uruchomienie gry!)", + "bad_game_xml_message_back" : "Plik game.xml istnieje, ale jest pusty lub nieczytelny. Istnieje kopia zapasowa tego pliku, czy chcesz ją przywrócić?", + "bad_game_xml_message_no_back" : "Plik game.xml istnieje, ale jest pusty lub nieczytelny. Kopia zapasowa tego pliku nie istnieje - zalecanym działaniem jest usunięcie go, a następnie uruchomienie gry w celu automatycznego utworzenia nowego pliku.", + "bad_game_xml_nothing" : "Nic nie rób, sam to naprawię", + "bad_game_xml_restore" : "Przywracanie kopii zapasowej", + "bad_game_xml_title" : "game.xml Wykryto problem - Wersja :", "base_game_data__title" : "Przeglądaj bazowe elementy gier", "basegame_attach" : "Załącznik", "basegame_attach_has" : "Akceptuje załączniki z", @@ -80,6 +89,7 @@ "context_mod_detail" : "Pokaż szczegóły", "context_open_website" : "Otwórz stronę źródłową", "context_paste" : "Wklej", + "context_select_all" : "Wybierz wszystko", "context_set_website" : "Ustaw stronę źródłową", "copy" : "Tak, skopiuj", "copy_cropcal" : "Kopiowanie Crop Calender JSON do schowka", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Lucerna", "croptype_barley" : "Jęczmień", "croptype_canola" : "Rzepak", + "croptype_carrot" : "Marchewka", "croptype_clover" : "Koniczyna", "croptype_cotton" : "Bawełna", "croptype_grape" : "Winogrona", @@ -112,14 +123,20 @@ "croptype_oat" : "Owies", "croptype_oilseedradish" : "Rzodkiew oleista", "croptype_olive" : "Oliwki", + "croptype_onion" : "Cebula", "croptype_poplar" : "Topola", + "croptype_poppy" : "Mak", "croptype_potato" : "Ziemniaki", + "croptype_redcabbage" : "Czerwona kapusta", + "croptype_rye" : "Żyto", "croptype_sorghum" : "Sorgo", "croptype_soybean" : "Soja", + "croptype_spelt" : "Orkisz", "croptype_sugarbeet" : "Burak cukrowy", "croptype_sugarcane" : "Trzcina cukrowa", "croptype_sunflower" : "Słoneczniki", "croptype_wheat" : "Pszenica", + "croptype_whitecabbage" : "Biała kapusta", "delete" : "Tak, usuń", "destination" : "Folder docelowy:", "destination_clear" : "Plik docelowy nie istnieje, można kontynuować.", @@ -196,6 +213,32 @@ "game_running_warning" : "Gra jest obecnie uruchomiona - zmiany w aktywnej kolekcji modów będą wymagały ponownego uruchomienia gry!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Otwórz dokumentację", + "import_json_blurb" : "Narzędzie to umożliwia importowanie kolekcji z ustawieniami", + "import_json_folder_empty" : "Folder znaleziony na dysku, naciśnij przycisk \"Zastosuj ustawienia\", aby zakończyć operację.", + "import_json_folder_not_empty" : "Folder zawiera już pliki, jeśli jesteś pewien, że tego właśnie chcesz, naciśnij przycisk \"Zastosuj ustawienia\", aby zakończyć operację.", + "import_json_load_failed" : "Nie można było odczytać pliku importu kolekcji JSON z dysku.", + "import_json_no" : "nie włączony", + "import_json_not_set" : "nie podano wartości", + "import_json_read_failed" : "Plik importu kolekcji JSON był nieprawidłowy.", + "import_json_step_0_blurb" : "Oto ustawienia zawarte w pliku importu", + "import_json_step_0_title" : "Dołączone ustawienia", + "import_json_step_1_blurb" : "Wybierz miejsce do przechowywania tej kolekcji modów", + "import_json_step_1_folder" : "Wybierz lokalizację folderu", + "import_json_step_1_folder_blurb" : "Wybierz lokalizację na dysku dla tej kolekcji. Możesz użyć istniejącego folderu lub utworzyć nowy w wyskakującym oknie dialogowym", + "import_json_step_1_folder_button" : "Ustaw lokalizację", + "import_json_step_1_folder_return" : "Lokalizacja kolekcji", + "import_json_step_1_options" : "Zastosuj ustawienia kolekcji", + "import_json_step_1_options_blurb" : "Zastosuj dołączone ustawienia do tej kolekcji", + "import_json_step_1_options_button" : "Zastosuj ustawienia", + "import_json_step_1_title" : "Krok 1: Wybierz lokalizację i importuj", + "import_json_step_2_blurb" : "Poniższe mody i pakiety modów są zawarte w tej kolekcji - można je opcjonalnie pobrać tutaj. Należy pamiętać, że wszelkie istniejące pliki zostaną nadpisane przez pobrane kopie.", + "import_json_step_2_download" : "Pobierz", + "import_json_step_2_download_unpack" : "Pobierz i rozpakuj", + "import_json_step_2_pack" : "Pakiety modów", + "import_json_step_2_single" : "Pojedyncze mody", + "import_json_step_2_title" : "Krok 2: Pobierz mody (opcjonalnie)", + "import_json_title" : "Kolekcja importu", + "import_json_yes" : "włączony", "info_might_be_piracy" : "Ten mod wygląda na piracką wersję DLC. Może to być błędne rozpoznanie ze względu na sposób działania programu, ale używanie pirackich wersji DLC uniemożliwi pomoc w problemach z grą od oficjalnego kanału wsparcia Giantsa oraz zostaniesz zbanowany z oficjalnego serwera Discord i oficjalnego forum gry. Wspieraj twórców gier!", "info_no_multiplayer_unzipped" : "Rozpakowane mody nie mogą być używane w trybie wieloosobowym. Jeśli nie zamierzasz używać tego modu w grze wieloosobowej, możesz zignorować tę wiadomość.", "language_code" : "pl", @@ -210,6 +253,8 @@ "list-active__title" : "Ustaw ten folder jako aktywny w gameSettings.xml", "list-none" : "Dezaktywuj", "list-none__title" : "Dezaktywuj nadpisywanie w gameSettings.xml", + "load_save_import_failed" : "Nie można odczytać określonego pliku zapisu, nie można kontynuować importu.", + "load_save_import_title" : "Nieprawidłowy plik zapisu", "loading_cache_subtitle" : "Czyszczenie dodatkowych wpisów w pamięci podręcznej (wolno!)", "loading_cache_title" : "Czyszczenie pamięci podręcznej", "loading_download_subtitle" : "Proszę czekać, to może potrwać minutę", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Użyj akrów dla obszaru", "notes_units_use_fahrenheit" : "Temperatura w stopniach Fahrenheita", "notes_units_use_miles" : "Użyj mil jako odległości", - "notes_usage_blurb" : "UWAGA: Aby aktywować wszelkie zmiany tutaj, musisz ponownie nacisnąć przycisk \"Aktywuj\" dla tego folderu!", + "notes_usage_blurb_2" : "Uwaga: Jest to aktualnie aktywna kolekcja - aby aktywować jakiekolwiek zmiany tutaj (hasło, filtry, nazwa użytkownika), musisz ponownie nacisnąć przycisk \"Ustaw aktywną\" dla tej kolekcji!", "offcanvas_blurb" : "Wybierz, która odsłona Farming Simulator ma być aktywna", "open_debug_log" : "Zobacz LOG", "open_folder" : "Otwórz w Eksploratorze", @@ -409,6 +454,9 @@ "save_csv_failed" : "Nie można wyeksportować listy folderów CSV (sprawdź dziennik debugowania)", "save_csv_worked" : "Foldery wyeksportowane do listy CSV", "save_import_slot" : "Miejsce docelowe", + "save_json" : "Eksportuj ustawienia (JSON)", + "save_json_failed" : "Nie można wyeksportować kolekcji do formatu JSON (sprawdź dziennik debugowania)", + "save_json_worked" : "Kolekcja wyeksportowana do JSON", "save_log_failed" : "Zapisanie LOGa nie powiodło się", "save_log_worked" : "Zapisano LOG poprawnie", "save_manage_active" : "Gry z aktywnym zapisem", diff --git a/translations/pt.json b/translations/pt.json index 898ca43b..fd850b53 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Esta coleção tem informações adicionais associadas", "bad_folder_folder" : "Localização da pasta:", "bad_folder_title" : "Pasta de coleção em falta", + "bad_game_set_xml_message_back" : "O teu ficheiro gameSettings.xml existe mas está vazio ou ilegível. Existe uma cópia de segurança deste ficheiro, gostaria de a restaurar?", + "bad_game_set_xml_message_no_back" : "O teu ficheiro gameSettings.xml existe mas está vazio ou ilegível. Não existe uma cópia de segurança deste ficheiro - a ação recomendada é apagá-lo, depois é necessário iniciar o jogo para criar automaticamente um novo ficheiro", + "bad_game_set_xml_title" : "gameSettings.xml Problema detectado - Versão :", + "bad_game_xml_delete" : "Eliminar o ficheiro danificado (é necessário iniciar o jogo!)", + "bad_game_xml_message_back" : "O teu game.xml existe mas está vazio ou ilegível. Existe uma cópia de segurança deste ficheiro, gostaria de a restaurar?", + "bad_game_xml_message_no_back" : "O teu game.xml existe mas está vazio ou ilegível. Não existe uma cópia de segurança deste ficheiro - a ação recomendada é apagá-lo, depois é necessário iniciar o jogo para criar automaticamente um novo ficheiro", + "bad_game_xml_nothing" : "Não fazer nada, eu próprio resolvo isto", + "bad_game_xml_restore" : "Restaurar a cópia de segurança", + "bad_game_xml_title" : "game.xml Problema detectado - Versão :", "base_game_data__title" : "Procurar itens de Base Game", "basegame_attach" : "Anexo", "basegame_attach_has" : "Aceita anexos com", @@ -80,6 +89,7 @@ "context_mod_detail" : "Mostrar Detalhes", "context_open_website" : "Abrir Website de Origem", "context_paste" : "Colar", + "context_select_all" : "Selecionar tudo", "context_set_website" : "Definir Website de Origem", "copy" : "Sim, Copiar Mod(s)", "copy_cropcal" : "Copiar JSON do calendário de corte para a área de transferência", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Luzerna", "croptype_barley" : "Cevada", "croptype_canola" : "Canola", + "croptype_carrot" : "Cenoura", "croptype_clover" : "Trevo", "croptype_cotton" : "Algodão", "croptype_grape" : "Uva", @@ -112,14 +123,20 @@ "croptype_oat" : "Aveia", "croptype_oilseedradish" : "Rabanete japonês", "croptype_olive" : "Azeitonas", + "croptype_onion" : "Cebola", "croptype_poplar" : "Choupo", + "croptype_poppy" : "Papoila", "croptype_potato" : "Batatas", + "croptype_redcabbage" : "Couve roxa", + "croptype_rye" : "Centeio", "croptype_sorghum" : "Sorgo", "croptype_soybean" : "Soja", + "croptype_spelt" : "Espelta", "croptype_sugarbeet" : "Beterraba", "croptype_sugarcane" : "Cana-de-açucar", "croptype_sunflower" : "Girassóis", "croptype_wheat" : "Trigo", + "croptype_whitecabbage" : "Couve branca", "delete" : "Sim, remova o mod.", "destination" : "Destino pretendido:", "destination_clear" : "O arquivo de destino não existe, pronto para ir", @@ -196,6 +213,32 @@ "game_running_warning" : "O jogo está a decorrer - as alterações à coleção de mods activos requerem um reinício do jogo!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Abrir Documentação", + "import_json_blurb" : "Esta ferramenta permite-lhe importar uma coleção com definições", + "import_json_folder_empty" : "Pasta encontrada no disco, prima o botão \"Aplicar definições\" para concluir a operação", + "import_json_folder_not_empty" : "A pasta já contém ficheiros, se tiver a certeza de que é isto que pretende, prima o botão \"Aplicar definições\" para concluir a operação", + "import_json_load_failed" : "Não foi possível ler o ficheiro de importação de colecções JSON a partir do disco.", + "import_json_no" : "não ativado", + "import_json_not_set" : "nenhum valor fornecido", + "import_json_read_failed" : "O ficheiro de importação de colecções JSON era inválido.", + "import_json_step_0_blurb" : "Estas são as definições incluídas no ficheiro de importação", + "import_json_step_0_title" : "Definições incluídas", + "import_json_step_1_blurb" : "Escolha um local para guardar esta coleção de mods", + "import_json_step_1_folder" : "Escolher a localização da pasta", + "import_json_step_1_folder_blurb" : "Escolha a localização no disco para esta coleção. Pode utilizar uma pasta existente ou criar uma nova na caixa de diálogo pop-up", + "import_json_step_1_folder_button" : "Definir localização", + "import_json_step_1_folder_return" : "Localização da coleção", + "import_json_step_1_options" : "Aplicar definições de coleção", + "import_json_step_1_options_blurb" : "Aplicar as definições incluídas a esta coleção", + "import_json_step_1_options_button" : "Aplicar definições", + "import_json_step_1_title" : "Passo 1 : Escolher um local e importar", + "import_json_step_2_blurb" : "Os seguintes mods e pacotes de mods estão incluídos nesta coleção - pode descarregá-los opcionalmente aqui. Note que os ficheiros existentes serão substituídos pelas cópias descarregadas.", + "import_json_step_2_download" : "Descarregar", + "import_json_step_2_download_unpack" : "Descarregar e descompactar", + "import_json_step_2_pack" : "Pacotes de mods", + "import_json_step_2_single" : "Mods individuais", + "import_json_step_2_title" : "Passo 2: Descarregar Mods (opcional)", + "import_json_title" : "Coleção de importação", + "import_json_yes" : "ativado", "info_might_be_piracy" : "Este mod parece ser uma versão hackeada de um DLC pago. Este teste é simples, então pode ser um falso positivo. No entanto, observe que o uso de tais cópias piratas pode resultar em perda de suporte do GIANTS e falha do servidor no Discord e no fórum do GIANTS. Por favor, apoie os desenvolvedores!", "info_no_multiplayer_unzipped" : "Mods descompactados não podem ser usados ​​no multiplayer. Se você não pretende usar este mod em um jogo multiplayer, pode ignorar esta mensagem.", "language_code" : "pt", @@ -210,6 +253,8 @@ "list-active__title" : "Definir esta coleção como ativa em gameSettings.xml", "list-none" : "Desativar", "list-none__title" : "Desativar substituição em gameSettings.xml", + "load_save_import_failed" : "Não foi possível ler o ficheiro guardado especificado, não é possível continuar a importação.", + "load_save_import_title" : "Ficheiro de gravação inválido", "loading_cache_subtitle" : "Limpando entradas de cache adicionais (lento!)", "loading_cache_title" : "limpeza de cache", "loading_download_subtitle" : "Aguarde, isso pode levar um minuto.", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Utilizar hectares para a área", "notes_units_use_fahrenheit" : "Utilizar Fahrenheit para a temperatura", "notes_units_use_miles" : "Utilizar milhas como distância", - "notes_usage_blurb" : "Nota: Para ativar quaisquer alterações aqui, você deve pressionar o botão \"Definir ativo\" para esta coleção novamente!", + "notes_usage_blurb_2" : "Nota: Esta é a coleção atualmente ativa - para ativar quaisquer alterações aqui (palavra-passe, filtros, nome de utilizador), tem de premir novamente o botão \"Definir como ativa\" para esta coleção!", "offcanvas_blurb" : "Selecione qual versão do Farming Simulator deseja que esteja ativa", "open_debug_log" : "Exibir registro de Log", "open_folder" : "Abrir no Explorer", @@ -409,6 +454,9 @@ "save_csv_failed" : "Não é possível exportar a lista de coleta CSV (verifique o registro de depuração)", "save_csv_worked" : "Coleção exportada para lista CSV", "save_import_slot" : "Ranhura de destino", + "save_json" : "Exportar definições (JSON)", + "save_json_failed" : "Não é possível exportar a coleção para JSON (verificar o registo de depuração)", + "save_json_worked" : "Coleção exportada para JSON", "save_log_failed" : "Falha ao salvar log de depuração", "save_log_worked" : "Log de depuração salvo com sucesso", "save_manage_active" : "Jogos de poupança ativa", diff --git a/translations/ru.json b/translations/ru.json index 286796ce..0398c382 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -16,6 +16,15 @@ "bad_folder_extra" : "Для этой коллекции есть дополнительная информация", "bad_folder_folder" : "Расположение папки:", "bad_folder_title" : "Папка коллекции отсутствует", + "bad_game_set_xml_message_back" : "Ваш файл gameSettings.xml существует, но пуст или нечитаем. Существует резервная копия этого файла. Восстановить её?", + "bad_game_set_xml_message_no_back" : "Ваш файл gameSettings.xml существует, но пуст или нечитаем. Резервная копия этого файла не существует - рекомендуемое действие - удалить его, затем вам нужно запустить игру, чтобы автоматически создать новый.", + "bad_game_set_xml_title" : "В файле gameSettings.xml обнаружена проблема — Версия:", + "bad_game_xml_delete" : "Удалить повреждённый файл (требуется запуск игры!)", + "bad_game_xml_message_back" : "Ваш файл game.xml существует, но пуст или нечитаем. Существует резервная копия этого файла. Восстановить её?", + "bad_game_xml_message_no_back" : "Ваш файл game.xml существует, но пуст или нечитаем. Резервная копия этого файла не существует - рекомендуемое действие - удалить его, затем вам нужно запустить игру, чтобы автоматически создать новый.", + "bad_game_xml_nothing" : "Ничего не делать, я исправлю это сам", + "bad_game_xml_restore" : "Восстановить из резервной копии", + "bad_game_xml_title" : "В файле game.xml обнаружена проблема — Версия:", "base_game_data__title" : "Обзор объектов базовой игры", "basegame_attach" : "Сочетания", "basegame_attach_has" : "Крепление для", @@ -80,6 +89,7 @@ "context_mod_detail" : "Показать подробности", "context_open_website" : "Открыть исходный веб-сайт", "context_paste" : "Вставить", + "context_select_all" : "Выбрать всё", "context_set_website" : "Задать исходный веб-сайт", "copy" : "Да, скопировать мод(ы)", "copy_cropcal" : "Копировать Календарь урожая в формате JSON в буфер обмена", @@ -104,6 +114,7 @@ "croptype_alfalfa" : "Люцерна", "croptype_barley" : "Ячмень", "croptype_canola" : "Канола", + "croptype_carrot" : "Морковь", "croptype_clover" : "Клевер", "croptype_cotton" : "Хлопок", "croptype_grape" : "Виноград", @@ -112,14 +123,20 @@ "croptype_oat" : "Овёс", "croptype_oilseedradish" : "Посевная редька", "croptype_olive" : "Оливки", + "croptype_onion" : "Лук", "croptype_poplar" : "Тополь", + "croptype_poppy" : "Мак", "croptype_potato" : "Картофель", + "croptype_redcabbage" : "Краснокочанная капуста", + "croptype_rye" : "Рожь", "croptype_sorghum" : "Сорго", "croptype_soybean" : "Соевые бобы", + "croptype_spelt" : "Полба", "croptype_sugarbeet" : "Свёкла", "croptype_sugarcane" : "Сахарный тростник", "croptype_sunflower" : "Подсолнухи", "croptype_wheat" : "Пшеница", + "croptype_whitecabbage" : "Белокочанная Капуста", "delete" : "Да, удалить мод(ы)", "destination" : "Коллекция назначения:", "destination_clear" : "Файл назначения не существует, перезапись не возможна", @@ -196,6 +213,32 @@ "game_running_warning" : "Игра в данный момент запущена — для внесения изменений, в активную коллекцию модов, требуется перезапустить игру!", "game_title_farming_simulator" : "Farming Simulator", "help_button__title" : "Открыть документацию", + "import_json_blurb" : "Этот инструмент позволит вам импортировать коллекцию с настройками", + "import_json_folder_empty" : "Папка найдена на диске. Нажмите кнопку «Применить настройки», чтобы завершить операцию.", + "import_json_folder_not_empty" : "Папка уже содержит файлы. Если вы уверены, что это то, что вам нужно, нажмите кнопку «Применить настройки», чтобы завершить операцию.", + "import_json_load_failed" : "Не удалось прочитать файл импорта коллекции JSON с диска.", + "import_json_no" : "не включено", + "import_json_not_set" : "не указано", + "import_json_read_failed" : "Недопустимое разрешение файла JSON для импорта коллекции.", + "import_json_step_0_blurb" : "Настройки файла импорта", + "import_json_step_0_title" : "Включённые настройки", + "import_json_step_1_blurb" : "Выберите место для хранения этой коллекции модов", + "import_json_step_1_folder" : "Выберите расположение папки", + "import_json_step_1_folder_blurb" : "Выберите место на диске для этой коллекции. Вы можете использовать существующую папку или создать новую во всплывающем диалоговом окне.", + "import_json_step_1_folder_button" : "Установить расположение", + "import_json_step_1_folder_return" : "Расположение коллекции", + "import_json_step_1_options" : "Применить настройки коллекции", + "import_json_step_1_options_blurb" : "Применить включённые настройки к этой коллекции", + "import_json_step_1_options_button" : "Применить настройки", + "import_json_step_1_title" : "Шаг 1: Выберите расположение и импортируйте", + "import_json_step_2_blurb" : "В эту коллекцию включены следующие моды и пакеты модов — при желании вы можете скачать их здесь. Обратите внимание, что любые существующие файлы будут перезаписаны загруженными копиями.", + "import_json_step_2_download" : "Скачать", + "import_json_step_2_download_unpack" : "Скачать и распаковать", + "import_json_step_2_pack" : "Пакеты модов", + "import_json_step_2_single" : "Моды", + "import_json_step_2_title" : "Шаг 2: Скачать моды (опционально)", + "import_json_title" : "Импорт коллекции", + "import_json_yes" : "включено", "info_might_be_piracy" : "Этот мод выглядит как взломанная/пиратская версия платного DLC. Этот тест упрощен, поэтому он может быть ложноположительным, однако имейте в виду, что использование взломанного DLC не позволит вам получить помощь от службы поддержки Giants и является основанием для запрета на официальном Discord и форумах. Пожалуйста, поддерживайте разработчиков!", "info_no_multiplayer_unzipped" : "Разархивированные моды нельзя использовать в многопользовательской игре. Если вы не собираетесь использовать этот мод в многопользовательской игре, вы можете проигнорировать это сообщение.", "language_code" : "ru", @@ -210,6 +253,8 @@ "list-active__title" : "Активировать эту коллекцию в файле gameSettings.xml.", "list-none" : "Деактивировать", "list-none__title" : "Деактивировать переопределение в gameSettings.xml", + "load_save_import_failed" : "Невозможно прочитать указанный файл сохранения, невозможно продолжить импорт.", + "load_save_import_title" : "Недопустимый файл сохранения", "loading_cache_subtitle" : "Очистка лишних записей в кеше (медленно!)", "loading_cache_title" : "Очистка кеша", "loading_download_subtitle" : "Пожалуйста, подождите, это может занять минуту", @@ -364,7 +409,7 @@ "notes_units_use_acres" : "Использовать акры для измерения площади", "notes_units_use_fahrenheit" : "Использовать градус Фаренгейта для измерения температуры", "notes_units_use_miles" : "Использовать мили для измерения расстояния", - "notes_usage_blurb" : "Пожалуйста, обратите внимание: Чтобы активировать любые изменения здесь, вы должны снова нажать кнопку \"Установить активным\" для этой коллекции!", + "notes_usage_blurb_2" : "Обратите внимание: это активная в данный момент коллекция. Чтобы активировать любые изменения здесь (пароль, фильтры, имя пользователя), вам необходимо еще раз нажать кнопку «Сделать активной» для этой коллекции!", "offcanvas_blurb" : "Выберите, какую версию Farming Simulator вы хотите активировать", "open_debug_log" : "Просмотр журнала отладки", "open_folder" : "Открыть в Проводнике", @@ -409,6 +454,9 @@ "save_csv_failed" : "Не удалось экспортировать список коллекции в формате CSV (проверьте журнал отладки)", "save_csv_worked" : "Коллекция экспортирована в список в формате CSV", "save_import_slot" : "Слот назначения", + "save_json" : "Настройки экспорта (JSON)", + "save_json_failed" : "Невозможно экспортировать коллекцию в JSON (проверьте журнал отладки)", + "save_json_worked" : "Коллекция экспортирована в JSON", "save_log_failed" : "Ошибка сохранения журнала отладки", "save_log_worked" : "Журнал отладки успешно сохранён", "save_manage_active" : "Активные сохранения игры", diff --git a/yarn.lock b/yarn.lock index 9bb97542..8ae5098b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -58,6 +58,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.24.1, @babel/code-frame@npm:^7.24.2": + version: 7.24.2 + resolution: "@babel/code-frame@npm:7.24.2" + dependencies: + "@babel/highlight": ^7.24.2 + picocolors: ^1.0.0 + checksum: 70e867340cfe09ca5488b2f36372c45cabf43c79a5b6426e6df5ef0611ff5dfa75a57dda841895693de6008f32c21a7c97027a8c7bcabd63a7d17416cbead6f8 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.23.5": version: 7.23.5 resolution: "@babel/compat-data@npm:7.23.5" @@ -65,32 +75,32 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/core@npm:7.23.9" +"@babel/core@npm:^7.24.3": + version: 7.24.3 + resolution: "@babel/core@npm:7.24.3" dependencies: "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.23.5 - "@babel/generator": ^7.23.6 + "@babel/code-frame": ^7.24.2 + "@babel/generator": ^7.24.1 "@babel/helper-compilation-targets": ^7.23.6 "@babel/helper-module-transforms": ^7.23.3 - "@babel/helpers": ^7.23.9 - "@babel/parser": ^7.23.9 - "@babel/template": ^7.23.9 - "@babel/traverse": ^7.23.9 - "@babel/types": ^7.23.9 + "@babel/helpers": ^7.24.1 + "@babel/parser": ^7.24.1 + "@babel/template": ^7.24.0 + "@babel/traverse": ^7.24.1 + "@babel/types": ^7.24.0 convert-source-map: ^2.0.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.3 semver: ^6.3.1 - checksum: 634a511f74db52a5f5a283c1121f25e2227b006c095b84a02a40a9213842489cd82dc7d61cdc74e10b5bcd9bb0a4e28bab47635b54c7e2256d47ab57356e2a76 + checksum: 1a33460794f4122cf255b656f4d6586913f41078a1afdf1bcf0365ddbd99c1ddb68f904062f9079445ab26b507c36bc297055192bc26e5c8e6e3def42195f9ab languageName: node linkType: hard -"@babel/eslint-parser@npm:^7.23.10": - version: 7.23.10 - resolution: "@babel/eslint-parser@npm:7.23.10" +"@babel/eslint-parser@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/eslint-parser@npm:7.24.1" dependencies: "@nicolo-ribaudo/eslint-scope-5-internals": 5.1.1-v1 eslint-visitor-keys: ^2.1.0 @@ -98,19 +108,19 @@ __metadata: peerDependencies: "@babel/core": ^7.11.0 eslint: ^7.5.0 || ^8.0.0 - checksum: 81249edee14f95720044f393b5b0a681a230ac2bde3d656b0c55b1cec4c5cb99ce0584ef6acd2e5413acc7905daee1b2e1db8e3fab18a3a74c508098a584ec9a + checksum: 962ffa98629f76234d7fd75134848bea040137c8534c602c73ed9ad6bdd3be0d2e7eaebd2ad496e81ab87220176170fd805e6fdc98464af6907ac66e1da7fc9a languageName: node linkType: hard -"@babel/generator@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/generator@npm:7.23.6" +"@babel/generator@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/generator@npm:7.24.1" dependencies: - "@babel/types": ^7.23.6 - "@jridgewell/gen-mapping": ^0.3.2 - "@jridgewell/trace-mapping": ^0.3.17 + "@babel/types": ^7.24.0 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 jsesc: ^2.5.1 - checksum: 1a1a1c4eac210f174cd108d479464d053930a812798e09fee069377de39a893422df5b5b146199ead7239ae6d3a04697b45fc9ac6e38e0f6b76374390f91fc6c + checksum: 98c6ce5ec7a1cba2bdf35cdf607273b90cf7cf82bbe75cd0227363fb84d7e1bd8efa74f40247d5900c8c009123f10132ad209a05283757698de918278c3c6700 languageName: node linkType: hard @@ -230,14 +240,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/helpers@npm:7.23.9" +"@babel/helpers@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/helpers@npm:7.24.1" dependencies: - "@babel/template": ^7.23.9 - "@babel/traverse": ^7.23.9 - "@babel/types": ^7.23.9 - checksum: 2678231192c0471dbc2fc403fb19456cc46b1afefcfebf6bc0f48b2e938fdb0fef2e0fe90c8c8ae1f021dae5012b700372e4b5d15867f1d7764616532e4a6324 + "@babel/template": ^7.24.0 + "@babel/traverse": ^7.24.1 + "@babel/types": ^7.24.0 + checksum: 0643b8ccf3358682303aea65f0798e482b2c3642040d32ffe130a245f4a46d0d23fe575a5e06e3cda4e8ec4af89d52b94ff1c444a74465d47ccc27da6ddbbb9f languageName: node linkType: hard @@ -274,6 +284,27 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.24.2": + version: 7.24.2 + resolution: "@babel/highlight@npm:7.24.2" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: 5f17b131cc3ebf3ab285a62cf98a404aef1bd71a6be045e748f8d5bf66d6a6e1aefd62f5972c84369472e8d9f22a614c58a89cd331eb60b7ba965b31b1bbeaf5 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.20.15": + version: 7.23.9 + resolution: "@babel/parser@npm:7.23.9" + bin: + parser: ./bin/babel-parser.js + checksum: e7cd4960ac8671774e13803349da88d512f9292d7baa952173260d3e8f15620a28a3701f14f709d769209022f9e7b79965256b8be204fc550cfe783cdcabe7c7 + languageName: node + linkType: hard + "@babel/parser@npm:^7.22.15": version: 7.23.0 resolution: "@babel/parser@npm:7.23.0" @@ -283,12 +314,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/parser@npm:7.23.9" +"@babel/parser@npm:^7.24.0, @babel/parser@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/parser@npm:7.24.1" bin: parser: ./bin/babel-parser.js - checksum: e7cd4960ac8671774e13803349da88d512f9292d7baa952173260d3e8f15620a28a3701f14f709d769209022f9e7b79965256b8be204fc550cfe783cdcabe7c7 + checksum: a1068941dddf82ffdf572565b8b7b2cddb963ff9ddf97e6e28f50e843d820b4285e6def8f59170104a94e2a91ae2e3b326489886d77a57ea29d468f6a5e79bf9 languageName: node linkType: hard @@ -303,32 +334,32 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/template@npm:7.23.9" +"@babel/template@npm:^7.24.0": + version: 7.24.0 + resolution: "@babel/template@npm:7.24.0" dependencies: "@babel/code-frame": ^7.23.5 - "@babel/parser": ^7.23.9 - "@babel/types": ^7.23.9 - checksum: 6e67414c0f7125d7ecaf20c11fab88085fa98a96c3ef10da0a61e962e04fdf3a18a496a66047005ddd1bb682a7cc7842d556d1db2f3f3f6ccfca97d5e445d342 + "@babel/parser": ^7.24.0 + "@babel/types": ^7.24.0 + checksum: f257b003c071a0cecdbfceca74185f18fe62c055469ab5c1d481aab12abeebed328e67e0a19fd978a2a8de97b28953fa4bc3da6d038a7345fdf37923b9fcdec8 languageName: node linkType: hard -"@babel/traverse@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/traverse@npm:7.23.9" +"@babel/traverse@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/traverse@npm:7.24.1" dependencies: - "@babel/code-frame": ^7.23.5 - "@babel/generator": ^7.23.6 + "@babel/code-frame": ^7.24.1 + "@babel/generator": ^7.24.1 "@babel/helper-environment-visitor": ^7.22.20 "@babel/helper-function-name": ^7.23.0 "@babel/helper-hoist-variables": ^7.22.5 "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/parser": ^7.23.9 - "@babel/types": ^7.23.9 + "@babel/parser": ^7.24.1 + "@babel/types": ^7.24.0 debug: ^4.3.1 globals: ^11.1.0 - checksum: a932f7aa850e158c00c97aad22f639d48c72805c687290f6a73e30c5c4957c07f5d28310c9bf59648e2980fe6c9d16adeb2ff92a9ca0f97fa75739c1328fc6c3 + checksum: 92a5ca906abfba9df17666d2001ab23f18600035f706a687055a0e392a690ae48d6fec67c8bd4ef19ba18699a77a5b7f85727e36b83f7d110141608fe0c24fe9 languageName: node linkType: hard @@ -354,25 +385,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/types@npm:7.23.6" +"@babel/types@npm:^7.24.0": + version: 7.24.0 + resolution: "@babel/types@npm:7.24.0" dependencies: "@babel/helper-string-parser": ^7.23.4 "@babel/helper-validator-identifier": ^7.22.20 to-fast-properties: ^2.0.0 - checksum: 68187dbec0d637f79bc96263ac95ec8b06d424396678e7e225492be866414ce28ebc918a75354d4c28659be6efe30020b4f0f6df81cc418a2d30645b690a8de0 - languageName: node - linkType: hard - -"@babel/types@npm:^7.23.9": - version: 7.23.9 - resolution: "@babel/types@npm:7.23.9" - dependencies: - "@babel/helper-string-parser": ^7.23.4 - "@babel/helper-validator-identifier": ^7.22.20 - to-fast-properties: ^2.0.0 - checksum: 0a9b008e9bfc89beb8c185e620fa0f8ed6c771f1e1b2e01e1596870969096fec7793898a1d64a035176abf1dd13e2668ee30bf699f2d92c210a8128f4b151e65 + checksum: 4b574a37d490f621470ff36a5afaac6deca5546edcb9b5e316d39acbb20998e9c2be42f3fc0bf2b55906fc49ff2a5a6a097e8f5a726ee3f708a0b0ca93aed807 languageName: node linkType: hard @@ -497,10 +517,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.56.0": - version: 8.56.0 - resolution: "@eslint/js@npm:8.56.0" - checksum: 5804130574ef810207bdf321c265437814e7a26f4e6fac9b496de3206afd52f533e09ec002a3be06cd9adcc9da63e727f1883938e663c4e4751c007d5b58e539 +"@eslint/js@npm:8.57.0": + version: 8.57.0 + resolution: "@eslint/js@npm:8.57.0" + checksum: 315dc65b0e9893e2bff139bddace7ea601ad77ed47b4550e73da8c9c2d2766c7a575c3cddf17ef85b8fd6a36ff34f91729d0dcca56e73ca887c10df91a41b0bb languageName: node linkType: hard @@ -513,14 +533,14 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.11.13": - version: 0.11.13 - resolution: "@humanwhocodes/config-array@npm:0.11.13" +"@humanwhocodes/config-array@npm:^0.11.14": + version: 0.11.14 + resolution: "@humanwhocodes/config-array@npm:0.11.14" dependencies: - "@humanwhocodes/object-schema": ^2.0.1 - debug: ^4.1.1 + "@humanwhocodes/object-schema": ^2.0.2 + debug: ^4.3.1 minimatch: ^3.0.5 - checksum: f8ea57b0d7ed7f2d64cd3944654976829d9da91c04d9c860e18804729a33f7681f78166ef4c761850b8c324d362f7d53f14c5c44907a6b38b32c703ff85e4805 + checksum: 861ccce9eaea5de19546653bccf75bf09fe878bc39c3aab00aeee2d2a0e654516adad38dd1098aab5e3af0145bbcbf3f309bdf4d964f8dab9dcd5834ae4c02f2 languageName: node linkType: hard @@ -531,10 +551,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.1": - version: 2.0.1 - resolution: "@humanwhocodes/object-schema@npm:2.0.1" - checksum: 24929487b1ed48795d2f08346a0116cc5ee4634848bce64161fb947109352c562310fd159fc64dda0e8b853307f5794605191a9547f7341158559ca3c8262a45 +"@humanwhocodes/object-schema@npm:^2.0.2": + version: 2.0.2 + resolution: "@humanwhocodes/object-schema@npm:2.0.2" + checksum: 2fc11503361b5fb4f14714c700c02a3f4c7c93e9acd6b87a29f62c522d90470f364d6161b03d1cc618b979f2ae02aed1106fd29d302695d8927e2fc8165ba8ee languageName: node linkType: hard @@ -552,7 +572,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": +"@jridgewell/gen-mapping@npm:^0.3.0": version: 0.3.3 resolution: "@jridgewell/gen-mapping@npm:0.3.3" dependencies: @@ -563,6 +583,17 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": ^1.2.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.24 + checksum: ff7a1764ebd76a5e129c8890aa3e2f46045109dabde62b0b6c6a250152227647178ff2069ea234753a690d8f3c4ac8b5e7b267bbee272bffb7f3b0a370ab6e52 + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:3.1.0": version: 3.1.0 resolution: "@jridgewell/resolve-uri@npm:3.1.0" @@ -570,6 +601,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 83b85f72c59d1c080b4cbec0fef84528963a1b5db34e4370fa4bd1e3ff64a0d80e0cee7369d11d73c704e0286fb2865b530acac7a871088fbe92b5edf1000870 + languageName: node + linkType: hard + "@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" @@ -577,6 +615,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 + languageName: node + linkType: hard + "@jridgewell/sourcemap-codec@npm:1.4.14": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" @@ -584,14 +629,24 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": ^3.1.0 + "@jridgewell/sourcemap-codec": ^1.4.14 + checksum: 9d3c40d225e139987b50c48988f8717a54a8c994d8a948ee42e1412e08988761d0754d7d10b803061cc3aebf35f92a5dbbab493bd0e1a9ef9e89a2130e83ba34 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.18 resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: @@ -601,6 +656,15 @@ __metadata: languageName: node linkType: hard +"@jsdoc/salty@npm:^0.2.1": + version: 0.2.7 + resolution: "@jsdoc/salty@npm:0.2.7" + dependencies: + lodash: ^4.17.21 + checksum: 020bc5a7f7270c281b854c73ca989c3a8947f0a520cd5142d3d0532ecc54cff05efef56ec2b04ee7628f605776d054033aa7948cd605963c406fe4c6cd4285df + languageName: node + linkType: hard + "@malept/cross-spawn-promise@npm:^1.1.0": version: 1.1.1 resolution: "@malept/cross-spawn-promise@npm:1.1.1" @@ -755,6 +819,30 @@ __metadata: languageName: node linkType: hard +"@types/linkify-it@npm:*": + version: 3.0.5 + resolution: "@types/linkify-it@npm:3.0.5" + checksum: fac28f41a6e576282300a459d70ea0d33aab70dbb77c3d09582bb0335bb00d862b6de69585792a4d590aae4173fbab0bf28861e2d90ca7b2b1439b52688e9ff6 + languageName: node + linkType: hard + +"@types/markdown-it@npm:^12.2.3": + version: 12.2.3 + resolution: "@types/markdown-it@npm:12.2.3" + dependencies: + "@types/linkify-it": "*" + "@types/mdurl": "*" + checksum: 868824a3e4d00718ba9cd4762cf16694762a670860f4b402e6e9f952b6841a2027488bdc55d05c2b960bf5078df21a9d041270af7e8949514645fe88fdb722ac + languageName: node + linkType: hard + +"@types/mdurl@npm:*": + version: 1.0.5 + resolution: "@types/mdurl@npm:1.0.5" + checksum: e8e872e8da8f517a9c748b06cec61c947cb73fd3069e8aeb0926670ec5dfac5d30549b3d0f1634950401633e812f9b7263f2d5dbe7e98fce12bcb2c659aa4b21 + languageName: node + linkType: hard + "@types/ms@npm:*": version: 0.7.31 resolution: "@types/ms@npm:0.7.31" @@ -778,10 +866,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.11.18": - version: 18.17.1 - resolution: "@types/node@npm:18.17.1" - checksum: 56201bda9a2d05d68602df63b4e67b0545ac8c6d0280bd5fb31701350a978a577a027501fbf49db99bf177f2242ebd1244896bfd35e89042d5bd7dfebff28d4e +"@types/node@npm:^20.9.0": + version: 20.11.30 + resolution: "@types/node@npm:20.11.30" + dependencies: + undici-types: ~5.26.4 + checksum: 7597767aa3e44b0f1bf62efa522dd17741135f283c11de6a20ead8bb7016fb4999cc30adcd8f2bb29ebb216906c92894346ccd187de170927dc1e212d2c07c81 languageName: node linkType: hard @@ -848,6 +938,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -866,13 +965,6 @@ __metadata: languageName: node linkType: hard -"adm-zip@npm:^0.5.10": - version: 0.5.10 - resolution: "adm-zip@npm:0.5.10" - checksum: 07ed91cf6423bf5dca4ee63977bc7635e91b8d21829c00829d48dce4c6932e1b19e6cfcbe44f1931c956e68795ae97183fc775913883fa48ce88a1ac11fb2034 - languageName: node - linkType: hard - "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -938,7 +1030,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.6.3": +"ajv@npm:^8.0.0, ajv@npm:^8.12.0, ajv@npm:^8.6.3": version: 8.12.0 resolution: "ajv@npm:8.12.0" dependencies: @@ -1003,9 +1095,9 @@ __metadata: languageName: node linkType: hard -"app-builder-lib@npm:24.13.0": - version: 24.13.0 - resolution: "app-builder-lib@npm:24.13.0" +"app-builder-lib@npm:24.13.3": + version: 24.13.3 + resolution: "app-builder-lib@npm:24.13.3" dependencies: "@develar/schema-utils": ~2.6.5 "@electron/notarize": 2.2.1 @@ -1015,12 +1107,12 @@ __metadata: "@types/fs-extra": 9.0.13 async-exit-hook: ^2.0.1 bluebird-lst: ^1.0.9 - builder-util: 24.9.4 - builder-util-runtime: 9.2.3 + builder-util: 24.13.1 + builder-util-runtime: 9.2.4 chromium-pickle-js: ^0.2.0 debug: ^4.3.4 ejs: ^3.1.8 - electron-publish: 24.13.0 + electron-publish: 24.13.1 form-data: ^4.0.0 fs-extra: ^10.1.0 hosted-git-info: ^4.1.0 @@ -1034,7 +1126,10 @@ __metadata: semver: ^7.3.8 tar: ^6.1.12 temp-file: ^3.4.0 - checksum: 7e9baa688103731e0eb13d4b38ae16ea5ddcd9b2a4176b7398c6ad7dc8144eb2e13fcb3c4306542bb956eff8cf9a42b8395cd8a04788f9221a65227cbce29883 + peerDependencies: + dmg-builder: 24.13.3 + electron-builder-squirrel-windows: 24.13.3 + checksum: 68ea3295efe99b8e8d4f9a1e77f3eae34de01b9829f8907e467d658b9406aa04c95baa2c06142b29bd8184d4efdc69f176a53d62fec36e7eba80024c46ce5adc languageName: node linkType: hard @@ -1045,32 +1140,33 @@ __metadata: languageName: node linkType: hard -"archiver-utils@npm:^4.0.1": - version: 4.0.1 - resolution: "archiver-utils@npm:4.0.1" +"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": + version: 5.0.2 + resolution: "archiver-utils@npm:5.0.2" dependencies: - glob: ^8.0.0 + glob: ^10.0.0 graceful-fs: ^4.2.0 + is-stream: ^2.0.1 lazystream: ^1.0.0 lodash: ^4.17.15 normalize-path: ^3.0.0 - readable-stream: ^3.6.0 - checksum: 2917cdf63a912c74002a4a1e6de3076a4691030b4e722efdd6d862447b61cd64c8b7688d331b1d35f8d4fc661d6e34f91bc1ffc79478fca2e48ad060acece18c + readable-stream: ^4.0.0 + checksum: 7dc4f3001dc373bd0fa7671ebf08edf6f815cbc539c78b5478a2eaa67e52e3fc0e92f562cdef2ba016c4dcb5468d3d069eb89535c6844da4a5bb0baf08ad5720 languageName: node linkType: hard -"archiver@npm:^6.0.1": - version: 6.0.1 - resolution: "archiver@npm:6.0.1" +"archiver@npm:^7.0.1": + version: 7.0.1 + resolution: "archiver@npm:7.0.1" dependencies: - archiver-utils: ^4.0.1 + archiver-utils: ^5.0.2 async: ^3.2.4 - buffer-crc32: ^0.2.1 - readable-stream: ^3.6.0 + buffer-crc32: ^1.0.0 + readable-stream: ^4.0.0 readdir-glob: ^1.1.2 tar-stream: ^3.0.0 - zip-stream: ^5.0.1 - checksum: 20549eef7366173440a86873387412226568744a410626f826998b0dda85fe84e739c542d9db9aca3923b772436eb795eafdff29c2983e683355fdd9faaa0fdb + zip-stream: ^6.0.1 + checksum: f93bcc00f919e0bbb6bf38fddf111d6e4d1ed34721b73cc073edd37278303a7a9f67aa4abd6fd2beb80f6c88af77f2eb4f60276343f67605e3aea404e5ad93ea languageName: node linkType: hard @@ -1200,7 +1296,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:^3.5.5": +"bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef @@ -1247,7 +1343,14 @@ __metadata: languageName: node linkType: hard -"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:~0.2.3": +"buffer-crc32@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-crc32@npm:1.0.0" + checksum: bc114c0e02fe621249e0b5093c70e6f12d4c2b1d8ddaf3b1b7bbe3333466700100e6b1ebdc12c050d0db845bc582c4fce8c293da487cc483f97eea027c480b23 + languageName: node + linkType: hard + +"buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" checksum: 06252347ae6daca3453b94e4b2f1d3754a3b146a111d81c68924c22d91889a40623264e95e67955b1cb4a68cbedf317abeabb5140a9766ed248973096db5ce1c @@ -1278,6 +1381,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.2.1 + checksum: 5ad23293d9a731e4318e420025800b42bf0d264004c0286c8cc010af7a270c7a0f6522e84f54b9ad65cbd6db20b8badbfd8d2ebf4f80fa03dab093b89e68c3f9 + languageName: node + linkType: hard + "buffers@npm:~0.1.1": version: 0.1.1 resolution: "buffers@npm:0.1.1" @@ -1285,25 +1398,25 @@ __metadata: languageName: node linkType: hard -"builder-util-runtime@npm:9.2.3": - version: 9.2.3 - resolution: "builder-util-runtime@npm:9.2.3" +"builder-util-runtime@npm:9.2.4": + version: 9.2.4 + resolution: "builder-util-runtime@npm:9.2.4" dependencies: debug: ^4.3.4 sax: ^1.2.4 - checksum: f25d6f12352be946785f0c6b8000902f3d8bd22921b7b0c7c9256829a4e3a6cf0c81d88e85cb73ffa00c7293b60573cacb533383d8ffce5b9b734b93d83553a8 + checksum: 7d02b7f57a10ac0d65a6dac08c7048d8e4a2bbbaa6025423fa0c08b6d629c2fedf6c712f4807f5c3480cabe1a721b5eccc21bcccb6211ce660e067945fd016cc languageName: node linkType: hard -"builder-util@npm:24.9.4": - version: 24.9.4 - resolution: "builder-util@npm:24.9.4" +"builder-util@npm:24.13.1": + version: 24.13.1 + resolution: "builder-util@npm:24.13.1" dependencies: 7zip-bin: ~5.2.0 "@types/debug": ^4.1.6 app-builder-bin: 4.0.0 bluebird-lst: ^1.0.9 - builder-util-runtime: 9.2.3 + builder-util-runtime: 9.2.4 chalk: ^4.1.2 cross-spawn: ^7.0.3 debug: ^4.3.4 @@ -1315,7 +1428,7 @@ __metadata: source-map-support: ^0.5.19 stat-mode: ^1.0.0 temp-file: ^3.4.0 - checksum: 33a7df457a319b3130f159ecb37790ce80bdb9992cdf6315e51bd70453d0224a105ee5cbfd7464bff86b3d15917f1febf943f62cc5536c2f19358d7b66a335d5 + checksum: 2991ee7ce2677736ca918d408180f93f2178decd17951164e31b90f01b7165a7e30d3d4d2a552978ec67b66be5cbe7a858deb581ff2aa9c4ba18fc1e72bf057d languageName: node linkType: hard @@ -1382,6 +1495,15 @@ __metadata: languageName: node linkType: hard +"catharsis@npm:^0.9.0": + version: 0.9.0 + resolution: "catharsis@npm:0.9.0" + dependencies: + lodash: ^4.17.15 + checksum: da867df1fd01823ea5a7283886ba382f6eb5b1fe5af356e00fd944a02d9b867f4ea2fc7f61416c53427f62760fdbd41614f6e8ae37686d2c3a4696871526df20 + languageName: node + linkType: hard + "chainsaw@npm:~0.1.0": version: 0.1.0 resolution: "chainsaw@npm:0.1.0" @@ -1559,15 +1681,16 @@ __metadata: languageName: node linkType: hard -"compress-commons@npm:^5.0.1": - version: 5.0.1 - resolution: "compress-commons@npm:5.0.1" +"compress-commons@npm:^6.0.2": + version: 6.0.2 + resolution: "compress-commons@npm:6.0.2" dependencies: crc-32: ^1.2.0 - crc32-stream: ^5.0.0 + crc32-stream: ^6.0.0 + is-stream: ^2.0.1 normalize-path: ^3.0.0 - readable-stream: ^3.6.0 - checksum: 65a68e56211a8d1dbe9dab0d35f1bd60a4df27aa01e6c3f0883080263e228c460758bab4f083637a380d4a96d2326722972a42ea1951360cc69728a3915f209f + readable-stream: ^4.0.0 + checksum: 37d79a54f91344ecde352588e0a128f28ce619b085acd4f887defd76978a0640e3454a42c7dcadb0191bb3f971724ae4b1f9d6ef9620034aa0427382099ac946 languageName: node linkType: hard @@ -1652,13 +1775,13 @@ __metadata: languageName: node linkType: hard -"crc32-stream@npm:^5.0.0": - version: 5.0.0 - resolution: "crc32-stream@npm:5.0.0" +"crc32-stream@npm:^6.0.0": + version: 6.0.0 + resolution: "crc32-stream@npm:6.0.0" dependencies: crc-32: ^1.2.0 - readable-stream: ^3.4.0 - checksum: 8e5dd04f22f3fbecc623492395107fbed2114f225bd606e39e8ed338f2fc1c454ac02a05741243620ab526473cb867fa86411a44a7ffcd88457cc1c2af82d0bc + readable-stream: ^4.0.0 + checksum: e6edc2f81bc387daef6d18b2ac18c2ffcb01b554d3b5c7d8d29b177505aafffba574658fdd23922767e8dab1183d1962026c98c17e17fb272794c33293ef607c languageName: node linkType: hard @@ -1719,15 +1842,15 @@ __metadata: languageName: node linkType: hard -"deepl-node@npm:^1.11.1": - version: 1.11.1 - resolution: "deepl-node@npm:1.11.1" +"deepl-node@npm:^1.12.0": + version: 1.12.0 + resolution: "deepl-node@npm:1.12.0" dependencies: "@types/node": ">=12.0" axios: ^1.6.4 form-data: ^3.0.0 loglevel: ">=1.6.2" - checksum: bd261a44fdc4e5432a2f3e8f1a0816f67bd5fe3c9aa8b3cc0486d61719b0e2196849d09bc21386c30263bfb1be0152d590be733880abd516bde6e0265896c5d3 + checksum: a2e5100d837d95ed91abc4036ab148bfcdbbf4bd66608a0025f2940a8f8b4d00616e46b54647255722787d6ed60bdb6b2e0778bc0593e20b112b2d38303dc4bd languageName: node linkType: hard @@ -1807,13 +1930,13 @@ __metadata: languageName: node linkType: hard -"dmg-builder@npm:24.13.0": - version: 24.13.0 - resolution: "dmg-builder@npm:24.13.0" +"dmg-builder@npm:24.13.3": + version: 24.13.3 + resolution: "dmg-builder@npm:24.13.3" dependencies: - app-builder-lib: 24.13.0 - builder-util: 24.9.4 - builder-util-runtime: 9.2.3 + app-builder-lib: 24.13.3 + builder-util: 24.13.1 + builder-util-runtime: 9.2.4 dmg-license: ^1.0.11 fs-extra: ^10.1.0 iconv-lite: ^0.6.2 @@ -1821,7 +1944,7 @@ __metadata: dependenciesMeta: dmg-license: optional: true - checksum: 725e22042e7102a6a4b22bce4c11107220c521f746a336ff153fba70e2851aa085d294109c1a4c6650836344767f78239b0059d6f0189fa204af7c4cb7949284 + checksum: 5c25293d795bb3326baee9d911d797a1ec703ad78ba57b60c6e6ce672582fe820590c59913b6800885e8303c853b3797ce518e304aa83f568caab147e1e8979a languageName: node linkType: hard @@ -1843,6 +1966,15 @@ __metadata: languageName: node linkType: hard +"docdash@npm:^2.0.2": + version: 2.0.2 + resolution: "docdash@npm:2.0.2" + dependencies: + "@jsdoc/salty": ^0.2.1 + checksum: de802483b5f72458c488d430b17de7cf0ad86b8e1dea38bae14607d820e5860f18f61d11578280f5fc2051bf31d294b7ca8c8192d0a9b159df801e2b56778875 + languageName: node + linkType: hard + "doctrine@npm:^3.0.0": version: 3.0.0 resolution: "doctrine@npm:3.0.0" @@ -1893,15 +2025,15 @@ __metadata: languageName: node linkType: hard -"electron-builder@npm:^24.13.0": - version: 24.13.0 - resolution: "electron-builder@npm:24.13.0" +"electron-builder@npm:^24.13.3": + version: 24.13.3 + resolution: "electron-builder@npm:24.13.3" dependencies: - app-builder-lib: 24.13.0 - builder-util: 24.9.4 - builder-util-runtime: 9.2.3 + app-builder-lib: 24.13.3 + builder-util: 24.13.1 + builder-util-runtime: 9.2.4 chalk: ^4.1.2 - dmg-builder: 24.13.0 + dmg-builder: 24.13.3 fs-extra: ^10.1.0 is-ci: ^3.0.0 lazy-val: ^1.0.5 @@ -1911,32 +2043,32 @@ __metadata: bin: electron-builder: cli.js install-app-deps: install-app-deps.js - checksum: de371c1187855c3d77a010c36681286901ca09d6c8888c004357573e079c0ed685e28bdc722ee0685b6f38a73b734b42889107ce6e8d80e66cdf5328c2124c68 + checksum: 8d7943d990363e547f1fbe391fee6b94d5e35e78c355645399f1f9b6709b6c167f0781abf8926c984c8a92475e6647f863f5e6a6938101a8a3a18ca85559810b languageName: node linkType: hard -"electron-publish@npm:24.13.0": - version: 24.13.0 - resolution: "electron-publish@npm:24.13.0" +"electron-publish@npm:24.13.1": + version: 24.13.1 + resolution: "electron-publish@npm:24.13.1" dependencies: "@types/fs-extra": ^9.0.11 - builder-util: 24.9.4 - builder-util-runtime: 9.2.3 + builder-util: 24.13.1 + builder-util-runtime: 9.2.4 chalk: ^4.1.2 fs-extra: ^10.1.0 lazy-val: ^1.0.5 mime: ^2.5.2 - checksum: fa8d27e56ac786c0391473712b566b870ccd90396f3255dcfea28f6bf785b6f107085667d3db02c08d805b4255c6f62643d65d6251849b36180397a42a4c4db3 + checksum: 7cd9924c967418074126f090404265efd93108a5ece7a5fe053df6ae647da9da264991f98a2463f5ac06c56e2e8f58f0d44ada04ad7a6374d3b870e95198117e languageName: node linkType: hard -"electron-store@npm:^8.1.0": - version: 8.1.0 - resolution: "electron-store@npm:8.1.0" +"electron-store@npm:^8.2.0": + version: 8.2.0 + resolution: "electron-store@npm:8.2.0" dependencies: conf: ^10.2.0 type-fest: ^2.17.0 - checksum: 7036f6d91cdcf6e08b10e24df9b144b9a04fe6cb1d7a7cc009277c1f6ad206d96cfbc3ada8ead47206fc5dadec0d34ff1beb8345bb3696e200f071f66b0abd8e + checksum: ee2484ff9b7f7e34609dfb17d9049386451730e8607422f9efb7e7c96c966f6a2b6d59a7982d555fc23bebd9e9dac245690b74be9411ad1e6d5c5192f78b2eab languageName: node linkType: hard @@ -1947,11 +2079,11 @@ __metadata: languageName: node linkType: hard -"electron-updater@npm:^6.1.8": - version: 6.1.8 - resolution: "electron-updater@npm:6.1.8" +"electron-updater@npm:^6.2.1": + version: 6.2.1 + resolution: "electron-updater@npm:6.2.1" dependencies: - builder-util-runtime: 9.2.3 + builder-util-runtime: 9.2.4 fs-extra: ^10.1.0 js-yaml: ^4.1.0 lazy-val: ^1.0.5 @@ -1959,20 +2091,20 @@ __metadata: lodash.isequal: ^4.5.0 semver: ^7.3.8 tiny-typed-emitter: ^2.1.0 - checksum: 250371f0e12ce5eccb5a596932cbf51dcb7f862270bdd23d26ad88ca8c0d83c52ad310fb36fd77150070424535cee3e4d5630b5f484393bf20af8556795ce8ed + checksum: 92a064610a3c9df747dce9c3eccd69c48adb2c8b37b9d7c13d1c39f7e2a9ffaef4e909ab50e6973d557566bde6de7ec9c64e2cc3a2cdef18b3220c5d91333e1c languageName: node linkType: hard -"electron@npm:^28.2.2": - version: 28.2.2 - resolution: "electron@npm:28.2.2" +"electron@npm:^29.1.6": + version: 29.1.6 + resolution: "electron@npm:29.1.6" dependencies: "@electron/get": ^2.0.0 - "@types/node": ^18.11.18 + "@types/node": ^20.9.0 extract-zip: ^2.0.1 bin: electron: cli.js - checksum: 0e44bf2dd0ad14ee16ed9696f9b4959b48c3db9b5e0b48f1b7aa0d8be80ae58663ccad692560721b22c787d24e784cdb36d60311f302e6552653dc446d89c3f1 + checksum: d37229ee3c6aaffcb773f22959eb56ec5a70fb5bfaffd6300963a9bd0b4e3117d7cdb8d030b4ef854eb6bd9c3ee150d08765bc3c98d7c3a97fea95c1f95a3aa0 languageName: node linkType: hard @@ -2008,6 +2140,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:~2.1.0": + version: 2.1.0 + resolution: "entities@npm:2.1.0" + checksum: a10a877e489586a3f6a691fe49bf3fc4e58f06c8e80522f08214a5150ba457e7017b447d4913a3fa041bda06ee4c92517baa4d8d75373eaa79369e9639225ffd + languageName: node + linkType: hard + "env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -2052,6 +2191,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 9f8a2d5743677c16e85c810e3024d54f0c8dea6424fad3c79ef6666e81dd0846f7437f5e729dfcdac8981bc9e5294c39b4580814d114076b8d36318f46ae4395 + languageName: node + linkType: hard + "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -2126,15 +2272,15 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.56.0": - version: 8.56.0 - resolution: "eslint@npm:8.56.0" +"eslint@npm:^8.57.0": + version: 8.57.0 + resolution: "eslint@npm:8.57.0" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/regexpp": ^4.6.1 "@eslint/eslintrc": ^2.1.4 - "@eslint/js": 8.56.0 - "@humanwhocodes/config-array": ^0.11.13 + "@eslint/js": 8.57.0 + "@humanwhocodes/config-array": ^0.11.14 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 "@ungap/structured-clone": ^1.2.0 @@ -2170,7 +2316,7 @@ __metadata: text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 883436d1e809b4a25d9eb03d42f584b84c408dbac28b0019f6ea07b5177940bf3cca86208f749a6a1e0039b63e085ee47aca1236c30721e91f0deef5cc5a5136 + checksum: 3a48d7ff85ab420a8447e9810d8087aea5b1df9ef68c9151732b478de698389ee656fd895635b5f2871c89ee5a2652b3f343d11e9db6f8486880374ebc74a2d9 languageName: node linkType: hard @@ -2224,6 +2370,20 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 + languageName: node + linkType: hard + +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -2283,14 +2443,14 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.3.4": - version: 4.3.4 - resolution: "fast-xml-parser@npm:4.3.4" +"fast-xml-parser@npm:^4.3.6": + version: 4.3.6 + resolution: "fast-xml-parser@npm:4.3.6" dependencies: strnum: ^1.0.5 bin: fxparser: src/cli/cli.js - checksum: ab88177343f6d3d971d53462db3011003a83eb8a8db704840127ddaaf27105ea90cdf7903a0f9b2e1279ccc4adfca8dfc0277b33bae6262406f10c16bd60ccf9 + checksum: 12795c55f4564699c3cee13f7e892423244ac1125775e9b85bf948a1d4b65352da8f688d334bad530972288bb7ee0cf3d2605088d475123fce40d95003f045fa languageName: node linkType: hard @@ -2488,25 +2648,29 @@ __metadata: version: 0.0.0-use.local resolution: "fsg-mod-assistant@workspace:." dependencies: - "@babel/core": ^7.23.9 - "@babel/eslint-parser": ^7.23.10 - adm-zip: ^0.5.10 + "@babel/core": ^7.24.3 + "@babel/eslint-parser": ^7.24.1 + ajv: ^8.12.0 + ajv-formats: ^2.1.1 ansi-colors: ^4.1.3 - archiver: ^6.0.1 + archiver: ^7.0.1 clean-css: ^5.3.3 - deepl-node: ^1.11.1 + deepl-node: ^1.12.0 discord-rpc: ^4.0.1 - electron: ^28.2.2 - electron-builder: ^24.13.0 - electron-store: ^8.1.0 - electron-updater: ^6.1.8 - eslint: ^8.56.0 + docdash: ^2.0.2 + electron: ^29.1.6 + electron-builder: ^24.13.3 + electron-store: ^8.2.0 + electron-updater: ^6.2.1 + eslint: ^8.57.0 eslint-plugin-unicorn: ^51.0.1 - fast-xml-parser: ^4.3.4 - glob: ^10.3.10 - html-validate: ^8.9.1 + fast-xml-parser: ^4.3.6 + glob: ^10.3.12 + html-validate: ^8.18.1 + jsdoc: ^4.0.2 + node-stream-zip: ^1.15.0 unzip-stream: ^0.3.1 - wasm-vips: ^0.0.7 + wasm-vips: ^0.0.8 languageName: unknown linkType: soft @@ -2577,7 +2741,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.3.10": +"glob@npm:^10.0.0": version: 10.3.10 resolution: "glob@npm:10.3.10" dependencies: @@ -2607,6 +2771,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.3.12": + version: 10.3.12 + resolution: "glob@npm:10.3.12" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.3.6 + minimatch: ^9.0.1 + minipass: ^7.0.4 + path-scurry: ^1.10.2 + bin: + glob: dist/esm/bin.mjs + checksum: 2b0949d6363021aaa561b108ac317bf5a97271b8a5d7a5fac1a176e40e8068ecdcccc992f8a7e958593d501103ac06d673de92adc1efcbdab45edefe35f8d7c6 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -2621,19 +2800,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.0": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 - languageName: node - linkType: hard - "global-agent@npm:^3.0.0": version: 3.0.0 resolution: "global-agent@npm:3.0.0" @@ -2692,7 +2858,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -2775,9 +2941,9 @@ __metadata: languageName: node linkType: hard -"html-validate@npm:^8.9.1": - version: 8.9.1 - resolution: "html-validate@npm:8.9.1" +"html-validate@npm:^8.18.1": + version: 8.18.1 + resolution: "html-validate@npm:8.18.1" dependencies: "@babel/code-frame": ^7.10.0 "@html-validate/stylish": ^4.1.0 @@ -2785,7 +2951,7 @@ __metadata: ajv: ^8.0.0 deepmerge: 4.3.1 glob: ^10.0.0 - ignore: 5.3.0 + ignore: 5.3.1 kleur: ^4.1.0 minimist: ^1.2.0 prompts: ^2.0.0 @@ -2806,7 +2972,7 @@ __metadata: optional: true bin: html-validate: bin/html-validate.js - checksum: 31d46f3779635beefe90caafc64a8ddd404232355acaf9d6fc8809781b078fac91b5ff5770391b8392d52cd62bcb522c68f035d72b7491fb13b52e6cbc99ca8f + checksum: 53479bf75bcb6ad748a6543583de6a26bfb55d85c0ae793bd6619c0079795f482c01b4168a7dea2584219f31b8a05c3ea2a0d5ebfd639099caf623263d3ac536 languageName: node linkType: hard @@ -2876,17 +3042,17 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.1.13": +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e languageName: node linkType: hard -"ignore@npm:5.3.0": - version: 5.3.0 - resolution: "ignore@npm:5.3.0" - checksum: 2736da6621f14ced652785cb05d86301a66d70248597537176612bd0c8630893564bd5f6421f8806b09e8472e75c591ef01672ab8059c07c6eb2c09cefe04bf9 +"ignore@npm:5.3.1": + version: 5.3.1 + resolution: "ignore@npm:5.3.1" + checksum: 71d7bb4c1dbe020f915fd881108cbe85a0db3d636a0ea3ba911393c53946711d13a9b1143c7e70db06d571a5822c0a324a6bcde5c9904e7ca5047f01f1bf8cd3 languageName: node linkType: hard @@ -3025,6 +3191,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^2.0.1": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 + languageName: node + linkType: hard + "isarray@npm:~1.0.0": version: 1.0.0 resolution: "isarray@npm:1.0.0" @@ -3066,7 +3239,7 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5": +"jackspeak@npm:^2.3.5, jackspeak@npm:^2.3.6": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" dependencies: @@ -3111,6 +3284,40 @@ __metadata: languageName: node linkType: hard +"js2xmlparser@npm:^4.0.2": + version: 4.0.2 + resolution: "js2xmlparser@npm:4.0.2" + dependencies: + xmlcreate: ^2.0.4 + checksum: 55e3af71dc0104941dfc3e85452230db42ff3870a5777d1ea26bc0c68743f49113a517a7b305421a932b29f10058a012a7da8f5ba07860a05a1dce9fe5b62962 + languageName: node + linkType: hard + +"jsdoc@npm:^4.0.2": + version: 4.0.2 + resolution: "jsdoc@npm:4.0.2" + dependencies: + "@babel/parser": ^7.20.15 + "@jsdoc/salty": ^0.2.1 + "@types/markdown-it": ^12.2.3 + bluebird: ^3.7.2 + catharsis: ^0.9.0 + escape-string-regexp: ^2.0.0 + js2xmlparser: ^4.0.2 + klaw: ^3.0.0 + markdown-it: ^12.3.2 + markdown-it-anchor: ^8.4.1 + marked: ^4.0.10 + mkdirp: ^1.0.4 + requizzle: ^0.2.3 + strip-json-comments: ^3.1.0 + underscore: ~1.13.2 + bin: + jsdoc: jsdoc.js + checksum: 04bf5ab005349b7581bd0e72ed99933eb71a41dcb47235b486b7d9146fbdf212a53e0cc044abe48ccf46012bd812dc1dfc007c6d679660ebdd053cd000242515 + languageName: node + linkType: hard + "jsesc@npm:^2.5.1": version: 2.5.2 resolution: "jsesc@npm:2.5.2" @@ -3230,6 +3437,15 @@ __metadata: languageName: node linkType: hard +"klaw@npm:^3.0.0": + version: 3.0.0 + resolution: "klaw@npm:3.0.0" + dependencies: + graceful-fs: ^4.1.9 + checksum: 1bf9de22392c80d28de8a2babd6f0de29fa52fcdc1654838fd35174b3641c168ec32b8b03022191e3c190efd535c31fce23f85e29cb260245571da7263ef418e + languageName: node + linkType: hard + "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -3277,6 +3493,15 @@ __metadata: languageName: node linkType: hard +"linkify-it@npm:^3.0.1": + version: 3.0.3 + resolution: "linkify-it@npm:3.0.3" + dependencies: + uc.micro: ^1.0.1 + checksum: 31367a4bb70c5bbc9703246236b504b0a8e049bcd4e0de4291fa50f0ebdebf235b5eb54db6493cb0b1319357c6eeafc4324c9f4aa34b0b943d9f2e11a1268fbc + languageName: node + linkType: hard + "locate-path@npm:^3.0.0": version: 3.0.0 resolution: "locate-path@npm:3.0.0" @@ -3326,7 +3551,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15": +"lodash@npm:^4.17.15, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -3347,6 +3572,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^10.2.0": + version: 10.2.0 + resolution: "lru-cache@npm:10.2.0" + checksum: eee7ddda4a7475deac51ac81d7dd78709095c6fa46e8350dc2d22462559a1faa3b81ed931d5464b13d48cbd7e08b46100b6f768c76833912bc444b99c37e25db + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -3402,6 +3634,40 @@ __metadata: languageName: node linkType: hard +"markdown-it-anchor@npm:^8.4.1": + version: 8.6.7 + resolution: "markdown-it-anchor@npm:8.6.7" + peerDependencies: + "@types/markdown-it": "*" + markdown-it: "*" + checksum: 828236768ac7f61ed5591393c1b1bfc5dbf2b6d0c58a3deec606c61dddaa12658a34450cbef37ab50a04453e618ce1efd47d86e4e52595024334898fd306225b + languageName: node + linkType: hard + +"markdown-it@npm:^12.3.2": + version: 12.3.2 + resolution: "markdown-it@npm:12.3.2" + dependencies: + argparse: ^2.0.1 + entities: ~2.1.0 + linkify-it: ^3.0.1 + mdurl: ^1.0.1 + uc.micro: ^1.0.5 + bin: + markdown-it: bin/markdown-it.js + checksum: 890555711c1c00fa03b936ca2b213001a3b9b37dea140d8445ae4130ce16628392aad24b12e2a0a9935336ca5951f2957a38f4e5309a2e38eab44e25ff32a41e + languageName: node + linkType: hard + +"marked@npm:^4.0.10": + version: 4.3.0 + resolution: "marked@npm:4.3.0" + bin: + marked: bin/marked.js + checksum: 0db6817893952c3ec710eb9ceafb8468bf5ae38cb0f92b7b083baa13d70b19774674be04db5b817681fa7c5c6a088f61300815e4dd75a59696f4716ad69f6260 + languageName: node + linkType: hard + "matcher@npm:^3.0.0": version: 3.0.0 resolution: "matcher@npm:3.0.0" @@ -3411,6 +3677,13 @@ __metadata: languageName: node linkType: hard +"mdurl@npm:^1.0.1": + version: 1.0.1 + resolution: "mdurl@npm:1.0.1" + checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b + languageName: node + linkType: hard + "mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" @@ -3579,6 +3852,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^7.0.4": + version: 7.0.4 + resolution: "minipass@npm:7.0.4" + checksum: 87585e258b9488caf2e7acea242fd7856bbe9a2c84a7807643513a338d66f368c7d518200ad7b70a508664d408aa000517647b2930c259a8b1f9f0984f344a21 + languageName: node + linkType: hard + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -3600,7 +3880,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3": +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" bin: @@ -3688,6 +3968,13 @@ __metadata: languageName: node linkType: hard +"node-stream-zip@npm:^1.15.0": + version: 1.15.0 + resolution: "node-stream-zip@npm:1.15.0" + checksum: 0b73ffbb09490e479c8f47038d7cba803e6242618fbc1b71c26782009d388742ed6fb5ce6e9d31f528b410249e7eb1c6e7534e9d3792a0cafd99813ac5a35107 + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -3910,6 +4197,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.10.2": + version: 1.10.2 + resolution: "path-scurry@npm:1.10.2" + dependencies: + lru-cache: ^10.2.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: 6739b4290f7d1a949c61c758b481c07ac7d1a841964c68cf5e1fa153d7e18cbde4872b37aadf9c5173c800d627f219c47945859159de36c977dd82419997b9b8 + languageName: node + linkType: hard + "pend@npm:~1.2.0": version: 1.2.0 resolution: "pend@npm:1.2.0" @@ -3965,6 +4262,13 @@ __metadata: languageName: node linkType: hard +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3 + languageName: node + linkType: hard + "progress@npm:^2.0.3": version: 2.0.3 resolution: "progress@npm:2.0.3" @@ -4089,7 +4393,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -4100,6 +4404,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: c4030ccff010b83e4f33289c535f7830190773e274b3fcb6e2541475070bdfd69c98001c3b0cb78763fc00c8b62f514d96c2b10a8bd35d5ce45203a25fa1d33a + languageName: node + linkType: hard + "readdir-glob@npm:^1.1.2": version: 1.1.3 resolution: "readdir-glob@npm:1.1.3" @@ -4153,6 +4470,15 @@ __metadata: languageName: node linkType: hard +"requizzle@npm:^0.2.3": + version: 0.2.4 + resolution: "requizzle@npm:0.2.4" + dependencies: + lodash: ^4.17.21 + checksum: fceaa448b235f9ed111aa58360129225a3cec1a897a23293dc08d2a00f001756c042a62df0a9d4d1e2669ace52dec960aea73437f407b30c51bfba2e9da208b7 + languageName: node + linkType: hard + "resolve-alpn@npm:^1.0.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" @@ -4541,7 +4867,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -4586,7 +4912,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 @@ -4795,6 +5121,20 @@ __metadata: languageName: node linkType: hard +"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": + version: 1.0.6 + resolution: "uc.micro@npm:1.0.6" + checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a + languageName: node + linkType: hard + +"underscore@npm:~1.13.2": + version: 1.13.6 + resolution: "underscore@npm:1.13.6" + checksum: d5cedd14a9d0d91dd38c1ce6169e4455bb931f0aaf354108e47bd46d3f2da7464d49b2171a5cf786d61963204a42d01ea1332a903b7342ad428deaafaf70ec36 + languageName: node + linkType: hard + "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" @@ -4902,10 +5242,10 @@ __metadata: languageName: node linkType: hard -"wasm-vips@npm:^0.0.7": - version: 0.0.7 - resolution: "wasm-vips@npm:0.0.7" - checksum: 3185046f391a39af5c9943b3cbc7c6c77a0a78dad84127389e1653d10d6fab1402ad33e8d90770f7d056f0fa35b8132a59940c1723b32e8b3daa004e7649cb4c +"wasm-vips@npm:^0.0.8": + version: 0.0.8 + resolution: "wasm-vips@npm:0.0.8" + checksum: aac38c77b34ce7ae3ad8d2f54c720ec080d9724c4d2ea086530651665c7ae6fb72519e076ade36e0269b9e3fc471f96cc1e1da38ae3ebc6cef063f7203c1e349 languageName: node linkType: hard @@ -4997,6 +5337,13 @@ __metadata: languageName: node linkType: hard +"xmlcreate@npm:^2.0.4": + version: 2.0.4 + resolution: "xmlcreate@npm:2.0.4" + checksum: b8dd52668b9aea77cd1408fa85538c14bb8dcc98b4e7bb51e76696c9c115d59eba7240298d0c4fd2caf8f1a8e283ab4e5c7b9a6bcfcf23a8b48f5068b677b748 + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" @@ -5057,13 +5404,13 @@ __metadata: languageName: node linkType: hard -"zip-stream@npm:^5.0.1": - version: 5.0.1 - resolution: "zip-stream@npm:5.0.1" +"zip-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "zip-stream@npm:6.0.1" dependencies: - archiver-utils: ^4.0.1 - compress-commons: ^5.0.1 - readable-stream: ^3.6.0 - checksum: 116cee5a2c1ecce7aa440b665470653f58ef56670c6aafa1b5491c9f9335992352145502af5fa865ac82f46336905e37fb7cbc649c2be72e2152c6b91802995c + archiver-utils: ^5.0.0 + compress-commons: ^6.0.2 + readable-stream: ^4.0.0 + checksum: aa5abd6a89590eadeba040afbc375f53337f12637e5e98330012a12d9886cde7a3ccc28bd91aafab50576035bbb1de39a9a316eecf2411c8b9009c9f94f0db27 languageName: node linkType: hard