|
|
- import { cacheImages } from '../scripts/search.js';
- import { TVA_CONFIG, updateSettings } from '../scripts/settings.js';
- import { getFileName } from '../scripts/utils.js';
- import EffectMappingForm from './effectMappingForm.js';
- import { showPathSelectCategoryDialog, showPathSelectConfigForm } from './dialogs.js';
-
- export default class ConfigureSettings extends FormApplication {
- constructor(
- dummySettings,
- {
- searchPaths = true,
- searchFilters = true,
- searchAlgorithm = true,
- randomizer = true,
- popup = true,
- permissions = true,
- worldHud = true,
- misc = true,
- activeEffects = true,
- features = false,
- } = {}
- ) {
- super({}, {});
- this.enabledTabs = {
- searchPaths,
- searchFilters,
- searchAlgorithm,
- randomizer,
- features,
- popup,
- permissions,
- worldHud,
- misc,
- activeEffects,
- };
- this.settings = foundry.utils.deepClone(TVA_CONFIG);
- if (dummySettings) {
- this.settings = mergeObject(this.settings, dummySettings, { insertKeys: false });
- this.dummySettings = dummySettings;
- }
- }
-
- static get defaultOptions() {
- return mergeObject(super.defaultOptions, {
- id: 'token-variants-configure-settings',
- classes: ['sheet'],
- template: 'modules/token-variants/templates/configureSettings.html',
- resizable: false,
- minimizable: false,
- title: 'Configure Settings',
- width: 700,
- height: 'auto',
- tabs: [{ navSelector: '.sheet-tabs', contentSelector: '.content', initial: 'searchPaths' }],
- });
- }
-
- async getData(options) {
- const data = super.getData(options);
- const settings = this.settings;
-
- data.enabledTabs = this.enabledTabs;
-
- // === Search Paths ===
- const paths = settings.searchPaths.map((path) => {
- const r = {};
- r.text = path.text;
- r.icon = this._pathIcon(path.source || '');
- r.cache = path.cache;
- r.source = path.source || '';
- r.types = path.types.join(',');
- r.config = JSON.stringify(path.config ?? {});
- r.hasConfig = path.config && !isEmpty(path.config);
- return r;
- });
- data.searchPaths = paths;
-
- // === Search Filters ===
- data.searchFilters = settings.searchFilters;
- for (const filter in data.searchFilters) {
- data.searchFilters[filter].label = filter;
- }
-
- // === Algorithm ===
- data.algorithm = deepClone(settings.algorithm);
- data.algorithm.fuzzyThreshold = 100 - data.algorithm.fuzzyThreshold * 100;
-
- // === Randomizer ===
- // Get all actor types defined by the game system
- data.randomizer = deepClone(settings.randomizer);
- const actorTypes = (game.system.entityTypes ?? game.system.documentTypes)['Actor'];
- data.randomizer.actorTypes = actorTypes.reduce((obj, t) => {
- const label = CONFIG['Actor']?.typeLabels?.[t] ?? t;
- obj[t] = {
- label: game.i18n.has(label) ? game.i18n.localize(label) : t,
- disable: settings.randomizer[`${t}Disable`] ?? false,
- };
- return obj;
- }, {});
-
- data.randomizer.tokenToPortraitDisabled =
- !(settings.randomizer.tokenCreate || settings.randomizer.tokenCopyPaste) || data.randomizer.diffImages;
-
- // === Pop-up ===
- data.popup = deepClone(settings.popup);
- // Get all actor types defined by the game system
- data.popup.actorTypes = actorTypes.reduce((obj, t) => {
- const label = CONFIG['Actor']?.typeLabels?.[t] ?? t;
- obj[t] = {
- type: t,
- label: game.i18n.has(label) ? game.i18n.localize(label) : t,
- disable: settings.popup[`${t}Disable`] ?? false,
- };
- return obj;
- }, {});
-
- // Split into arrays of max length 3
- let allTypes = [];
- let tempTypes = [];
- let i = 0;
- for (const [key, value] of Object.entries(data.popup.actorTypes)) {
- tempTypes.push(value);
- i++;
- if (i % 3 == 0) {
- allTypes.push(tempTypes);
- tempTypes = [];
- }
- }
- if (tempTypes.length > 0) allTypes.push(tempTypes);
- data.popup.actorTypes = allTypes;
-
- // === Permissions ===
- data.permissions = settings.permissions;
-
- // === Token HUD ===
- data.worldHud = deepClone(settings.worldHud);
- data.worldHud.tokenHUDWildcardActive = game.modules.get('token-hud-wildcard')?.active;
-
- // === Internal Effects ===
- data.internalEffects = deepClone(settings.internalEffects);
-
- // === Misc ===
- data.keywordSearch = settings.keywordSearch;
- data.excludedKeywords = settings.excludedKeywords;
- data.systemHpPath = settings.systemHpPath;
- data.runSearchOnPath = settings.runSearchOnPath;
- data.imgurClientId = settings.imgurClientId;
- data.enableStatusConfig = settings.enableStatusConfig;
- data.disableNotifs = settings.disableNotifs;
- data.staticCache = settings.staticCache;
- data.staticCacheFile = settings.staticCacheFile;
- data.stackStatusConfig = settings.stackStatusConfig;
- data.mergeGroup = settings.mergeGroup;
- data.customImageCategories = settings.customImageCategories.join(',');
- data.disableEffectIcons = settings.disableEffectIcons;
- data.displayEffectIconsOnHover = settings.displayEffectIconsOnHover;
- data.filterEffectIcons = settings.filterEffectIcons;
- data.hideElevationTooltip = settings.hideElevationTooltip;
- data.hideTokenBorder = settings.hideTokenBorder;
- data.filterCustomEffectIcons = settings.filterCustomEffectIcons;
- data.filterIconList = settings.filterIconList.join(',');
- data.tilesEnabled = settings.tilesEnabled;
- data.updateTokenProto = settings.updateTokenProto;
- data.imgNameContainsDimensions = settings.imgNameContainsDimensions;
- data.imgNameContainsFADimensions = settings.imgNameContainsFADimensions;
- data.playVideoOnHover = settings.playVideoOnHover;
- data.pauseVideoOnHoverOut = settings.pauseVideoOnHoverOut;
- data.disableImageChangeOnPolymorphed = settings.disableImageChangeOnPolymorphed;
- data.disableImageUpdateOnNonPrototype = settings.disableImageUpdateOnNonPrototype;
- data.disableTokenUpdateAnimation = settings.disableTokenUpdateAnimation;
- data.mappingsCurrentSceneOnly = settings.mappingsCurrentSceneOnly;
- data.evaluateOverlayOnHover = settings.evaluateOverlayOnHover;
-
- // Controls
- data.pathfinder = ['pf1e', 'pf2e'].includes(game.system.id);
- data.dnd5e = game.system.id === 'dnd5e';
-
- return data;
- }
-
- /**
- * @param {JQuery} html
- */
- activateListeners(html) {
- super.activateListeners(html);
-
- // Search Paths
- super.activateListeners(html);
- html.find('a.create-path').click(this._onCreatePath.bind(this));
- html.on('input', '.searchSource', this._onSearchSourceTextChange.bind(this));
- $(html).on('click', 'a.delete-path', this._onDeletePath.bind(this));
- $(html).on('click', 'a.convert-imgur', this._onConvertImgurPath.bind(this));
- $(html).on('click', 'a.convert-json', this._onConvertJsonPath.bind(this));
- $(html).on('click', '.path-image.source-icon a', this._onBrowseFolder.bind(this));
- $(html).on('click', 'a.select-category', showPathSelectCategoryDialog.bind(this));
- $(html).on('click', 'a.select-config', showPathSelectConfigForm.bind(this));
-
- // Search Filters
- html.on('input', 'input.filterRegex', this._validateRegex.bind(this));
-
- // Active Effects
- const disableEffectIcons = html.find('[name="disableEffectIcons"]');
- const filterEffectIcons = html.find('[name="filterEffectIcons"]');
- disableEffectIcons
- .on('change', (e) => {
- if (e.target.checked) filterEffectIcons.prop('checked', false);
- })
- .trigger('change');
- filterEffectIcons.on('change', (e) => {
- if (e.target.checked) disableEffectIcons.prop('checked', false);
- });
-
- // Algorithm
- const algorithmTab = $(html).find('div[data-tab="searchAlgorithm"]');
- algorithmTab.find(`input[name="algorithm.exact"]`).change((e) => {
- $(e.target).closest('form').find('input[name="algorithm.fuzzy"]').prop('checked', !e.target.checked);
- });
- algorithmTab.find(`input[name="algorithm.fuzzy"]`).change((e) => {
- $(e.target).closest('form').find('input[name="algorithm.exact"]').prop('checked', !e.target.checked);
- });
- algorithmTab.find('input[name="algorithm.fuzzyThreshold"]').change((e) => {
- $(e.target).siblings('.token-variants-range-value').html(`${e.target.value}%`);
- });
-
- // Randomizer
- const tokenCreate = html.find('input[name="randomizer.tokenCreate"]');
- const tokenCopyPaste = html.find('input[name="randomizer.tokenCopyPaste"]');
- const tokenToPortrait = html.find('input[name="randomizer.tokenToPortrait"]');
- const _toggle = () => {
- tokenToPortrait.prop('disabled', !(tokenCreate.is(':checked') || tokenCopyPaste.is(':checked')));
- };
- tokenCreate.change(_toggle);
- tokenCopyPaste.change(_toggle);
-
- const diffImages = html.find('input[name="randomizer.diffImages"]');
- const syncImages = html.find('input[name="randomizer.syncImages"]');
- diffImages.change(() => {
- syncImages.prop('disabled', !diffImages.is(':checked'));
- tokenToPortrait.prop('disabled', diffImages.is(':checked'));
- });
-
- // Token HUD
- html.find('input[name="worldHud.updateActorImage"]').change((event) => {
- $(event.target)
- .closest('form')
- .find('input[name="worldHud.useNameSimilarity"]')
- .prop('disabled', !event.target.checked);
- });
-
- // Static Cache
- html.find('button.token-variants-cache-images').click((event) => {
- const tab = $(event.target).closest('.tab');
- const staticOn = tab.find('input[name="staticCache"]');
- const staticFile = tab.find('input[name="staticCacheFile"]');
- cacheImages({ staticCache: staticOn.is(':checked'), staticCacheFile: staticFile.val() });
- });
-
- // Global Mappings
- html.find('button.token-variants-global-mapping').click(() => {
- const setting = game.settings.get('core', DefaultTokenConfig.SETTING);
- const data = new foundry.data.PrototypeToken(setting);
- const token = new TokenDocument(data, { actor: null });
- new EffectMappingForm(token, { globalMappings: true }).render(true);
- });
- }
-
- /**
- * Validates regex entered into Search Filter's RegEx input field
- */
- async _validateRegex(event) {
- if (this._validRegex(event.target.value)) {
- event.target.style.backgroundColor = '';
- } else {
- event.target.style.backgroundColor = '#ff7066';
- }
- }
-
- _validRegex(val) {
- if (val) {
- try {
- new RegExp(val);
- } catch (e) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Open a FilePicker so the user can select a local folder to use as an image source
- */
- async _onBrowseFolder(event) {
- const pathInput = $(event.target).closest('.table-row').find('.path-text input');
- const sourceInput = $(event.target).closest('.table-row').find('.path-source input');
-
- let activeSource = sourceInput.val() || 'data';
- let current = pathInput.val();
-
- if (activeSource.startsWith('s3:')) {
- const bucketName = activeSource.replace('s3:', '');
- current = `${game.data.files.s3?.endpoint.protocol}//${bucketName}.${game.data.files.s3?.endpoint.host}/${current}`;
- } else if (activeSource.startsWith('rolltable')) {
- let content = `<select style="width: 100%;" name="table-name" id="output-tableKey">`;
-
- game.tables.forEach((rollTable) => {
- content += `<option value='${rollTable.name}'>${rollTable.name}</option>`;
- });
-
- content += `</select>`;
-
- new Dialog({
- title: `Select a Rolltable`,
- content: content,
- buttons: {
- yes: {
- icon: "<i class='fas fa-check'></i>",
- label: 'Select',
- callback: (html) => {
- pathInput.val();
- const tableName = html.find("select[name='table-name']").val();
- pathInput.val(tableName);
- },
- },
- },
- default: 'yes',
- }).render(true);
- return;
- }
-
- if (activeSource === 'json') {
- new FilePicker({
- type: 'text',
- activeSource: 'data',
- current: current,
- callback: (path, fp) => {
- pathInput.val(path);
- },
- }).render(true);
- } else {
- new FilePicker({
- type: 'folder',
- activeSource: activeSource,
- current: current,
- callback: (path, fp) => {
- pathInput.val(fp.result.target);
- if (fp.activeSource === 's3') {
- sourceInput.val(`s3:${fp.result.bucket}`);
- } else {
- sourceInput.val(fp.activeSource);
- }
- },
- }).render(true);
- }
- }
-
- /**
- * Converts Imgur path to a rolltable
- */
- async _onConvertImgurPath(event) {
- event.preventDefault();
-
- const pathInput = $(event.target).closest('.table-row').find('.path-text input');
- const sourceInput = $(event.target).closest('.table-row').find('.path-source input');
-
- const albumHash = pathInput.val();
- const imgurClientId = TVA_CONFIG.imgurClientId === '' ? 'df9d991443bb222' : TVA_CONFIG.imgurClientId;
-
- fetch('https://api.imgur.com/3/gallery/album/' + albumHash, {
- headers: {
- Authorization: 'Client-ID ' + imgurClientId,
- Accept: 'application/json',
- },
- })
- .then((response) => response.json())
- .then(
- async function (result) {
- if (!result.success && location.hostname === 'localhost') {
- ui.notifications.warn(game.i18n.format('token-variants.notifications.warn.imgur-localhost'));
- return;
- }
-
- const data = result.data;
-
- let resultsArray = [];
- data.images.forEach((img, i) => {
- resultsArray.push({
- type: 0,
- text: img.title ?? img.description ?? '',
- weight: 1,
- range: [i + 1, i + 1],
- collection: 'Text',
- drawn: false,
- img: img.link,
- });
- });
-
- await RollTable.create({
- name: data.title,
- description: 'Token Variant Art auto generated RollTable: https://imgur.com/gallery/' + albumHash,
- results: resultsArray,
- replacement: true,
- displayRoll: true,
- img: 'modules/token-variants/img/token-images.svg',
- });
-
- pathInput.val(data.title);
- sourceInput.val('rolltable').trigger('input');
- }.bind(this)
- )
- .catch((error) => console.warn('TVA | ', error));
- }
-
- /**
- * Converts Json path to a rolltable
- */
- async _onConvertJsonPath(event) {
- event.preventDefault();
-
- const pathInput = $(event.target).closest('.table-row').find('.path-text input');
- const sourceInput = $(event.target).closest('.table-row').find('.path-source input');
-
- const jsonPath = pathInput.val();
-
- fetch(jsonPath, {
- headers: {
- Accept: 'application/json',
- },
- })
- .then((response) => response.json())
- .then(
- async function (result) {
- if (!result.length > 0) {
- ui.notifications.warn(game.i18n.format('token-variants.notifications.warn.json-localhost'));
- return;
- }
-
- const data = result;
- data.title = getFileName(jsonPath);
-
- let resultsArray = [];
- data.forEach((img, i) => {
- resultsArray.push({
- type: 0,
- text: img.name ?? '',
- weight: 1,
- range: [i + 1, i + 1],
- collection: 'Text',
- drawn: false,
- img: img.path,
- });
- });
-
- await RollTable.create({
- name: data.title,
- description: 'Token Variant Art auto generated RollTable: ' + jsonPath,
- results: resultsArray,
- replacement: true,
- displayRoll: true,
- img: 'modules/token-variants/img/token-images.svg',
- });
-
- pathInput.val(data.title);
- sourceInput.val('rolltable').trigger('input');
- }.bind(this)
- )
- .catch((error) => console.warn('TVA | ', error));
- }
-
- /**
- * Generates a new search path row
- */
- async _onCreatePath(event) {
- event.preventDefault();
- const table = $(event.currentTarget).closest('.token-variant-table');
- let row = `
- <li class="table-row flexrow">
- <div class="path-image source-icon">
- <a><i class="${this._pathIcon('')}"></i></a>
- </div>
- <div class="path-source">
- <input class="searchSource" type="text" name="searchPaths.source" value="" placeholder="data"/>
- </div>
- <div class="path-text">
- <input class="searchPath" type="text" name="searchPaths.text" value="" placeholder="Path to folder"/>
- </div>
- <div class="imgur-control">
- <a class="convert-imgur" title="Convert to Rolltable"><i class="fas fa-angle-double-left"></i></a>
- </div>
- <div class="json-control">
- <a class="convert-json" title="Convert to Rolltable"><i class="fas fa-angle-double-left"></i></a>
- </div>
- <div class="path-category">
- <a class="select-category" title="Select image categories/filters"><i class="fas fa-swatchbook"></i></a>
- <input type="hidden" name="searchPaths.types" value="Portrait,Token,PortraitAndToken">
- </div>
- <div class="path-config">
- <a class="select-config" title="Apply configuration to images under this path."><i class="fas fa-cog fa-lg"></i></a>
- <input type="hidden" name="searchPaths.config" value="{}">
- </div>
- <div class="path-cache">
- <input type="checkbox" name="searchPaths.cache" data-dtype="Boolean" checked/>
- </div>
- <div class="path-controls">
- <a class="delete-path" title="Delete path"><i class="fas fa-trash"></i></a>
- </div>
- </li>
- `;
- table.append(row);
-
- this._reIndexPaths(table);
-
- this.setPosition(); // Auto-resize window
- }
-
- async _reIndexPaths(table) {
- table
- .find('.path-source')
- .find('input')
- .each(function (index) {
- $(this).attr('name', `searchPaths.${index}.source`);
- });
-
- table
- .find('.path-text')
- .find('input')
- .each(function (index) {
- $(this).attr('name', `searchPaths.${index}.text`);
- });
-
- table
- .find('.path-cache')
- .find('input')
- .each(function (index) {
- $(this).attr('name', `searchPaths.${index}.cache`);
- });
- table
- .find('.path-category')
- .find('input')
- .each(function (index) {
- $(this).attr('name', `searchPaths.${index}.types`);
- });
- table
- .find('.path-config')
- .find('input')
- .each(function (index) {
- $(this).attr('name', `searchPaths.${index}.config`);
- });
- }
-
- async _onDeletePath(event) {
- event.preventDefault();
-
- const li = event.currentTarget.closest('.table-row');
- li.remove();
-
- const table = $(event.currentTarget).closest('.token-variant-table');
- this._reIndexPaths(table);
-
- this.setPosition(); // Auto-resize window
- }
-
- async _onSearchSourceTextChange(event) {
- const image = this._pathIcon(event.target.value);
- const imgur = image === 'fas fa-info';
- const json = image === 'fas fa-brackets-curly';
-
- const imgurControl = $(event.currentTarget).closest('.table-row').find('.imgur-control');
- if (imgur) imgurControl.addClass('active');
- else imgurControl.removeClass('active');
-
- const jsonControl = $(event.currentTarget).closest('.table-row').find('.json-control');
- if (json) jsonControl.addClass('active');
- else jsonControl.removeClass('active');
-
- $(event.currentTarget).closest('.table-row').find('.path-image i').attr('class', image);
- }
-
- // Return icon appropriate for the path provided
- _pathIcon(source) {
- if (source.startsWith('s3')) {
- return 'fas fa-database';
- } else if (source.startsWith('rolltable')) {
- return 'fas fa-dice';
- } else if (source.startsWith('forgevtt') || source.startsWith('forge-bazaar')) {
- return 'fas fa-hammer';
- } else if (source.startsWith('imgur')) {
- return 'fas fa-info';
- } else if (source.startsWith('json')) {
- return 'fas fa-brackets-curly';
- }
-
- return 'fas fa-folder';
- }
-
- /**
- * @param {Event} event
- * @param {Object} formData
- */
- async _updateObject(event, formData) {
- const settings = this.settings;
- formData = expandObject(formData);
-
- // Search Paths
- settings.searchPaths = formData.hasOwnProperty('searchPaths') ? Object.values(formData.searchPaths) : [];
- settings.searchPaths.forEach((path) => {
- if (!path.source) path.source = 'data';
- if (path.types) path.types = path.types.split(',');
- else path.types = [];
- if (path.config) {
- try {
- path.config = JSON.parse(path.config);
- } catch (e) {
- delete path.config;
- }
- } else delete path.config;
- });
-
- // Search Filters
- for (const filter in formData.searchFilters) {
- if (!this._validRegex(formData.searchFilters[filter].regex)) formData.searchFilters[filter].regex = '';
- }
- mergeObject(settings.searchFilters, formData.searchFilters);
-
- // Algorithm
- formData.algorithm.fuzzyLimit = parseInt(formData.algorithm.fuzzyLimit);
- if (isNaN(formData.algorithm.fuzzyLimit) || formData.algorithm.fuzzyLimit < 1) formData.algorithm.fuzzyLimit = 50;
- formData.algorithm.fuzzyThreshold = (100 - formData.algorithm.fuzzyThreshold) / 100;
- mergeObject(settings.algorithm, formData.algorithm);
-
- // Randomizer
- mergeObject(settings.randomizer, formData.randomizer);
-
- // Pop-up
- mergeObject(settings.popup, formData.popup);
-
- // Permissions
- mergeObject(settings.permissions, formData.permissions);
-
- // Token HUD
- mergeObject(settings.worldHud, formData.worldHud);
-
- // Internal Effects
- mergeObject(settings.internalEffects, formData.internalEffects);
-
- // Misc
- mergeObject(settings, {
- keywordSearch: formData.keywordSearch,
- excludedKeywords: formData.excludedKeywords,
- systemHpPath: formData.systemHpPath?.trim(),
- runSearchOnPath: formData.runSearchOnPath,
- imgurClientId: formData.imgurClientId,
- enableStatusConfig: formData.enableStatusConfig,
- disableNotifs: formData.disableNotifs,
- staticCache: formData.staticCache,
- staticCacheFile: formData.staticCacheFile,
- tilesEnabled: formData.tilesEnabled,
- stackStatusConfig: formData.stackStatusConfig,
- mergeGroup: formData.mergeGroup,
- customImageCategories: (formData.customImageCategories || '')
- .split(',')
- .map((t) => t.trim())
- .filter((t) => t),
- disableEffectIcons: formData.disableEffectIcons,
- displayEffectIconsOnHover: formData.displayEffectIconsOnHover,
- filterEffectIcons: formData.filterEffectIcons,
- hideElevationTooltip: formData.hideElevationTooltip,
- hideTokenBorder: formData.hideTokenBorder,
- filterCustomEffectIcons: formData.filterCustomEffectIcons,
- filterIconList: (formData.filterIconList || '')
- .split(',')
- .map((t) => t.trim())
- .filter((t) => t),
- updateTokenProto: formData.updateTokenProto,
- imgNameContainsDimensions: formData.imgNameContainsDimensions,
- imgNameContainsFADimensions: formData.imgNameContainsFADimensions,
- playVideoOnHover: formData.playVideoOnHover,
- pauseVideoOnHoverOut: formData.pauseVideoOnHoverOut,
- disableImageChangeOnPolymorphed: formData.disableImageChangeOnPolymorphed,
- disableImageUpdateOnNonPrototype: formData.disableImageUpdateOnNonPrototype,
- disableTokenUpdateAnimation: formData.disableTokenUpdateAnimation,
- mappingsCurrentSceneOnly: formData.mappingsCurrentSceneOnly,
- evaluateOverlayOnHover: formData.evaluateOverlayOnHover,
- });
-
- // Global Mappings
- settings.globalMappings = TVA_CONFIG.globalMappings;
-
- // Save Settings
- if (this.dummySettings) {
- mergeObjectFix(this.dummySettings, settings, { insertKeys: false });
- } else {
- updateSettings(settings);
- }
- }
- }
-
- // ========================
- // v8 support, broken merge
- // ========================
- export function mergeObjectFix(
- original,
- other = {},
- {
- insertKeys = true,
- insertValues = true,
- overwrite = true,
- recursive = true,
- inplace = true,
- enforceTypes = false,
- } = {},
- _d = 0
- ) {
- other = other || {};
- if (!(original instanceof Object) || !(other instanceof Object)) {
- throw new Error('One of original or other are not Objects!');
- }
- const options = { insertKeys, insertValues, overwrite, recursive, inplace, enforceTypes };
-
- // Special handling at depth 0
- if (_d === 0) {
- if (!inplace) original = deepClone(original);
- if (Object.keys(original).some((k) => /\./.test(k))) original = expandObject(original);
- if (Object.keys(other).some((k) => /\./.test(k))) other = expandObject(other);
- }
-
- // Iterate over the other object
- for (let k of Object.keys(other)) {
- const v = other[k];
- if (original.hasOwnProperty(k)) _mergeUpdate(original, k, v, options, _d + 1);
- else _mergeInsertFix(original, k, v, options, _d + 1);
- }
- return original;
- }
-
- function _mergeInsertFix(original, k, v, { insertKeys, insertValues } = {}, _d) {
- // Recursively create simple objects
- if (v?.constructor === Object && insertKeys) {
- original[k] = mergeObjectFix({}, v, { insertKeys: true, inplace: true });
- return;
- }
-
- // Delete a key
- if (k.startsWith('-=')) {
- delete original[k.slice(2)];
- return;
- }
-
- // Insert a key
- const canInsert = (_d <= 1 && insertKeys) || (_d > 1 && insertValues);
- if (canInsert) original[k] = v;
- }
-
- function _mergeUpdate(original, k, v, { insertKeys, insertValues, enforceTypes, overwrite, recursive } = {}, _d) {
- const x = original[k];
- const tv = getType(v);
- const tx = getType(x);
-
- // Recursively merge an inner object
- if (tv === 'Object' && tx === 'Object' && recursive) {
- return mergeObjectFix(
- x,
- v,
- {
- insertKeys: insertKeys,
- insertValues: insertValues,
- overwrite: overwrite,
- inplace: true,
- enforceTypes: enforceTypes,
- },
- _d
- );
- }
-
- // Overwrite an existing value
- if (overwrite) {
- if (tx !== 'undefined' && tv !== tx && enforceTypes) {
- throw new Error(`Mismatched data types encountered during object merge.`);
- }
- original[k] = v;
- }
- }
|