diff --git a/app/imports/api/creature/creatureProperties/CreatureProperties.js b/app/imports/api/creature/creatureProperties/CreatureProperties.js index 102c8e8c..87b5f072 100644 --- a/app/imports/api/creature/creatureProperties/CreatureProperties.js +++ b/app/imports/api/creature/creatureProperties/CreatureProperties.js @@ -65,7 +65,6 @@ let CreaturePropertySchema = new SimpleSchema({ }, 'dependencies.$': { type: String, - regEx: SimpleSchema.RegEx.Id, }, }); diff --git a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js index 650b9681..60761aa8 100644 --- a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js @@ -2,9 +2,24 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; -import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; -import { insertPropertyWork } from '/imports/api/creature/creatureProperties/methods/insertProperty.js'; +import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; +import { + setLineageOfDocs, + renewDocIds +} from '/imports/api/parenting/parenting.js'; +import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js'; +import { reorderDocs } from '/imports/api/parenting/order.js'; +import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js'; +import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js'; +var snackbar; +if (Meteor.isClient){ + snackbar = require( + '/imports/ui/components/snackbars/SnackbarQueue.js' + ).snackbar +} + +const DUPLICATE_CHILDREN_LIMIT = 50; const duplicateProperty = new ValidatedMethod({ name: 'creatureProperties.duplicate', @@ -20,13 +35,70 @@ const duplicateProperty = new ValidatedMethod({ timeInterval: 5000, }, run({_id}) { - let creatureProperty = CreatureProperties.findOne(_id); - let rootCreature = getRootCreatureAncestor(creatureProperty); - assertEditPermission(rootCreature, this.userId); - insertPropertyWork({ - property: creatureProperty, - creature: rootCreature, + let property = CreatureProperties.findOne(_id); + let creature = getRootCreatureAncestor(property); + + assertEditPermission(creature, this.userId); + + // Renew the doc ID + let randomSrc = DDP.randomStream('duplicateProperty'); + let propertyId = randomSrc.id(); + property._id = propertyId; + + // Get all the descendants + let nodes = CreatureProperties.find({ + 'ancestors.id': _id, + removed: {$ne: true}, + }, { + limit: DUPLICATE_CHILDREN_LIMIT + 1, + sort: {order: 1}, + }).fetch(); + + // Alert the user if the limit was hit + if (nodes.length > DUPLICATE_CHILDREN_LIMIT){ + nodes.pop(); + if (Meteor.isClient){ + snackbar({ + text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`, + }); + } + } + + // re-map all the ancestors + setLineageOfDocs({ + docArray: nodes, + newAncestry : [ + ...property.ancestors, + {id: propertyId, collection: 'creatureProperties'} + ], + oldParent : {id: _id, collection: 'creatureProperties'}, + }); + + // Give the docs new IDs without breaking internal references + renewDocIds({docArray: nodes}); + + // Order the root node + property.order += 0.5; + + // Insert the properties + CreatureProperties.batchInsert([property, ...nodes]); + + // Tree structure changed by inserts, reorder the tree + reorderDocs({ + collection: CreatureProperties, + ancestorId: property.ancestors[0].id, }); + + // Inserting the active status of the property needs to be denormalised + recomputeInactiveProperties(creature._id); + + // Recompute the inventory + recomputeInventory(creature._id); + + // Inserting a creature property invalidates dependencies: full recompute + recomputeCreatureByDoc(creature); + + return propertyId; }, }); diff --git a/app/imports/api/library/methods/duplicateLibraryNode.js b/app/imports/api/library/methods/duplicateLibraryNode.js index 2fa6782e..4da84997 100644 --- a/app/imports/api/library/methods/duplicateLibraryNode.js +++ b/app/imports/api/library/methods/duplicateLibraryNode.js @@ -34,7 +34,9 @@ const duplicateLibraryNode = new ValidatedMethod({ run({_id}) { let libraryNode = LibraryNodes.findOne(_id); assertDocEditPermission(libraryNode, this.userId); - let libraryNodeId = Random.id(); + + let randomSrc = DDP.randomStream('duplicateLibraryNode'); + let libraryNodeId = randomSrc.id(); libraryNode._id = libraryNodeId; let nodes = LibraryNodes.find({ diff --git a/app/imports/api/properties/Adjustments.js b/app/imports/api/properties/Adjustments.js index 035595ac..8830f437 100644 --- a/app/imports/api/properties/Adjustments.js +++ b/app/imports/api/properties/Adjustments.js @@ -33,7 +33,7 @@ const AdjustmentSchema = new SimpleSchema({ const ComputedOnlyAdjustmentSchema = new SimpleSchema({ amountResult: { - type: SimpleSchema.Integer, + type: SimpleSchema.oneOf(String, Number), optional: true, }, amountErrors: { diff --git a/app/imports/api/properties/Damages.js b/app/imports/api/properties/Damages.js index 06b81fca..15884872 100644 --- a/app/imports/api/properties/Damages.js +++ b/app/imports/api/properties/Damages.js @@ -29,7 +29,7 @@ const DamageSchema = new SimpleSchema({ const ComputedOnlyDamageSchema = new SimpleSchema({ amountResult: { - type: SimpleSchema.oneOf(String, SimpleSchema.Integer), + type: SimpleSchema.oneOf(String, Number), optional: true, }, amountErrors: { diff --git a/app/imports/api/properties/subSchemas/InlineComputationSchema.js b/app/imports/api/properties/subSchemas/InlineComputationSchema.js index 8f39692b..cddb8645 100644 --- a/app/imports/api/properties/subSchemas/InlineComputationSchema.js +++ b/app/imports/api/properties/subSchemas/InlineComputationSchema.js @@ -10,7 +10,11 @@ const InlineComputationSchema = new SimpleSchema({ type: String, optional: true, }, - errors: ErrorSchema, + errors: { + type: Array, + optional: true, + }, + 'errors.$': ErrorSchema, }); export default InlineComputationSchema; diff --git a/app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue b/app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue index cea74c52..3c35ae41 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue +++ b/app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue @@ -53,6 +53,7 @@ embedded :_id="selected" @removed="selected = undefined" + @duplicated="id => selected = id" /> diff --git a/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue b/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue index 3eb954cc..28d94e65 100644 --- a/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue +++ b/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue @@ -158,12 +158,12 @@ export default { methods: { getPropertyName, duplicate(){ - duplicateProperty.call({_id: this.currentId}, (error) => { + duplicateProperty.call({_id: this.currentId}, (error, id) => { if (error) { console.error(error); } if (this.embedded){ - this.$emit('duplicated'); + this.$emit('duplicated', id); } else { this.$store.dispatch('popDialogStack'); }