Began migration of queries to nested sets

This commit is contained in:
Thaum Rystra
2023-10-03 16:28:20 +02:00
parent 28a19f2037
commit f63d2ad254
41 changed files with 242 additions and 392 deletions

View File

@@ -9,11 +9,12 @@ import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
import Experiences from '/imports/api/creature/experience/Experiences';
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature';
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export function getArchiveObj(creatureId) {
// Build the archive document
const creature = Creatures.findOne(creatureId);
const properties = CreatureProperties.find({ 'ancestors.id': creatureId }).fetch();
const properties = CreatureProperties.find({ ...getFilter.descendantsOfRoot(creatureId) }).fetch();
const experiences = Experiences.find({ creatureId }).fetch();
const logs = CreatureLogs.find({ creatureId }).fetch();
let archiveCreature = {
@@ -68,7 +69,7 @@ const archiveCreatureToFile = new ValidatedMethod({
async run({ creatureId }) {
assertOwnership(creatureId, this.userId);
if (Meteor.isServer) {
archiveCreature(creatureId, this.userId);
archiveCreature(creatureId);
} else {
removeCreatureWork(creatureId);
}

View File

@@ -21,8 +21,14 @@ export default function verifyArchiveSafety({ meta, creature, properties, experi
}
});
properties.forEach(prop => {
if (prop.ancestors[0].id !== creatureId) {
throw new Meteor.Error('Malicious prop', 'Properties contains an entry for the wrong creature');
if (meta.schemaVersion.schemaVersion >= 3) {
if (prop.root?.id !== creatureId) {
throw new Meteor.Error('Malicious prop', 'Properties contains an entry for the wrong creature');
}
} else {
if (prop.ancestors?.[0]?.id !== creatureId) {
throw new Meteor.Error('Malicious prop', 'Properties contains an entry for the wrong creature');
}
}
});
}

View File

@@ -10,8 +10,8 @@ import {
assertCopyPermission
} from '/imports/api/sharing/sharingPermissions';
import {
setLineageOfDocs,
getAncestry,
fetchDocByRef,
getFilter,
renewDocIds
} from '/imports/api/parenting/parentingFunctions';
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
@@ -40,20 +40,20 @@ const copyPropertyToLibrary = new ValidatedMethod({
},
run({ propId, parentRef, order }) {
// get the new ancestry for the properties
let { parentDoc, ancestors } = getAncestry({ parentRef });
const parentDoc = fetchDocByRef(parentRef);
// Check permission to edit the destination
let rootLibrary;
if (parentRef.collection === 'libraries') {
rootLibrary = parentDoc;
} else if (parentRef.collection === 'libraryNodes') {
rootLibrary = Libraries.findOne(parentDoc.ancestors[0].id)
rootLibrary = Libraries.findOne(parentDoc.root.id)
} else {
throw `${parentRef.collection} is not a valid parent collection`
}
assertEditPermission(rootLibrary, this.userId);
const insertedRootNode = insertNodeFromProperty(propId, ancestors, order, this);
const insertedRootNode = insertNodeFromProperty(propId, order, this);
// Tree structure changed by inserts, reorder the tree
rebuildNestedSets(LibraryNodes, rootLibrary._id);
@@ -63,7 +63,7 @@ const copyPropertyToLibrary = new ValidatedMethod({
},
});
function insertNodeFromProperty(propId, ancestors, order, method) {
function insertNodeFromProperty(propId, order, method) {
// Fetch the property and its descendants, provided they have not been
// removed
let prop = CreatureProperties.findOne({
@@ -83,9 +83,9 @@ function insertNodeFromProperty(propId, ancestors, order, method) {
// Make sure we can edit this property
assertDocEditPermission(prop, method.userId);
let oldParent = prop.parent;
let oldParentId = prop.parentId;
const propCursor = CreatureProperties.find({
'ancestors.id': propId,
...getFilter.descendants(prop),
removed: { $ne: true },
});
@@ -105,13 +105,6 @@ function insertNodeFromProperty(propId, ancestors, order, method) {
// properties
assertSourceLibraryCopyPermission(props, method);
// re-map all the ancestors
setLineageOfDocs({
docArray: props,
newAncestry: ancestors,
oldParent,
});
// Give the docs new IDs without breaking internal references
renewDocIds({
docArray: props,
@@ -119,11 +112,8 @@ function insertNodeFromProperty(propId, ancestors, order, method) {
});
// Order the root node
if (order === undefined) {
rebuildNestedSets(LibraryNodes, prop.root.id);
} else {
prop.order = order;
}
prop.left = Number.MAX_SAFE_INTEGER - 1;
prop.right = Number.MAX_SAFE_INTEGER;
// Clean the props
props = cleanProps(props);
@@ -135,8 +125,8 @@ function insertNodeFromProperty(propId, ancestors, order, method) {
/**
*
* @param {[Property]} props The properties to check
* @param {String} userId The userId trying to copy these properties to a library
* @param props The properties to check
* @param userId The userId trying to copy these properties to a library
* Checks that every property can be copied out of the library that originated it by this user
*/
function assertSourceLibraryCopyPermission(props, method) {
@@ -155,9 +145,9 @@ function assertSourceLibraryCopyPermission(props, method) {
LibraryNodes.find({
_id: { $in: libraryNodeIds }
}, {
fields: { ancestors: 1 }
fields: { root: 1 }
}).forEach(node => {
sourceLibIds.add(node.ancestors?.[0]?.id);
sourceLibIds.add(node.root.id);
});
// Assert copy permission on each of those libraries

View File

@@ -28,7 +28,7 @@ const damageProperty = new ValidatedMethod({
if (!prop) throw new Meteor.Error(
'Damage property failed', 'Property doesn\'t exist'
);
const creatureId = prop.ancestors[0].id;
const creatureId = prop.root.id;
const actionContext = new ActionContext(creatureId, [creatureId], this);
// Check permissions

View File

@@ -5,7 +5,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor';
import {
setLineageOfDocs,
getFilter,
renewDocIds
} from '/imports/api/parenting/parentingFunctions';
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
@@ -33,6 +33,8 @@ const duplicateProperty = new ValidatedMethod({
},
run({ _id }) {
let property = CreatureProperties.findOne(_id);
if (!property) throw new Meteor.Error('not-found', 'The source property was not found');
let creature = getRootCreatureAncestor(property);
assertEditPermission(creature, this.userId);
@@ -49,7 +51,7 @@ const duplicateProperty = new ValidatedMethod({
// Get all the descendants
let nodes = CreatureProperties.find({
'ancestors.id': _id,
...getFilter.descendants(property),
removed: { $ne: true },
}, {
limit: DUPLICATE_CHILDREN_LIMIT + 1,
@@ -66,22 +68,13 @@ const duplicateProperty = new ValidatedMethod({
}
}
// 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
const allNodes = [property, ...nodes];
renewDocIds({ docArray: allNodes });
// Order the root node
property.order += 0.5;
property.left = Number.MAX_SAFE_INTEGER - 1;
property.right = Number.MAX_SAFE_INTEGER;
// Mark the sheet as needing recompute
property.dirty = true;

View File

@@ -17,7 +17,7 @@ const flipToggle = new ValidatedMethod({
run({ _id }) {
// Permission
let property = CreatureProperties.findOne(_id, {
fields: { type: 1, ancestors: 1, enabled: 1, disabled: 1 }
fields: { type: 1, root: 1, enabled: 1, disabled: 1 }
});
if (property.type !== 'toggle') {
throw new Meteor.Error('wrong property',

View File

@@ -1,8 +1,9 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default function getParentRefByTag(creatureId, tag) {
let prop = CreatureProperties.findOne({
'ancestors.id': creatureId,
...getFilter.descendantsOfRoot(creatureId),
removed: { $ne: true },
inactive: { $ne: true },
tags: tag,

View File

@@ -1,3 +1,5 @@
import { getFilter } from "/imports/api/parenting/parentingFunctions";
export default function getSlotFillFilter({ slot, libraryIds }) {
if (!slot) throw 'Slot is required for getSlotFillFilter';
@@ -6,9 +8,14 @@ export default function getSlotFillFilter({ slot, libraryIds }) {
let filter = {
fillSlots: true,
removed: { $ne: true },
$and: []
$and: [],
};
filter['ancestors.id'] = { $in: libraryIds };
if (libraryIds.length) {
Object.assign(
filter,
getFilter.descendantsOfAllRoots(libraryIds)
);
}
if (slot.slotType) {
filter.$and.push({
$or: [{

View File

@@ -33,7 +33,7 @@ describe('Slot fill filter', function () {
$or: [{
libraryTags: { $all: ['tag1', 'tag2'] }
}],
'ancestors.id': { $in: ['libraryId1', 'libraryId2'] },
'root.id': { $in: ['libraryId1', 'libraryId2'] },
removed: { $ne: true },
fillSlots: true,
});
@@ -76,7 +76,7 @@ describe('Slot fill filter', function () {
$and: [
{ libraryTags: { $nin: ['tag5', 'tag6', 'tag7', 'tag8'] } },
],
'ancestors.id': { $in: ['libraryId1', 'libraryId2'] },
'root.id': { $in: ['libraryId1', 'libraryId2'] },
removed: { $ne: true },
fillSlots: true,
});

View File

@@ -14,6 +14,8 @@ const updateCreatureProperty = new ValidatedMethod({
case 'order':
case 'parent':
case 'ancestors':
case 'root':
case 'parentId':
case 'damage':
throw new Meteor.Error('Permission denied',
'This property can\'t be updated directly');
@@ -27,7 +29,7 @@ const updateCreatureProperty = new ValidatedMethod({
run({ _id, path, value }) {
// Permission
let property = CreatureProperties.findOne(_id, {
fields: { type: 1, ancestors: 1 }
fields: { type: 1, root: 1 }
});
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);

View File

@@ -2,11 +2,8 @@ import computeCreature from '/imports/api/engine/computeCreature';
/**
* Recomputes all ancestor creatures of this property
* @deprecated
*/
export default function recomputeCreaturesByProperty(property) {
for (let ref of property.ancestors) {
if (ref.collection === 'creatures') {
computeCreature.call(ref.id);
}
}
computeCreature.call(property.root.id);
}

View File

@@ -5,7 +5,6 @@ export default function defaultCharacterProperties(creatureId) {
const creatureRef = { collection: 'creatures', id: creatureId };
let randomSrc = DDP.randomStream('defaultProperties');
const inventoryId = randomSrc.id();
const inventoryRef = { collection: 'creatureProperties', id: inventoryId };
return [
{
type: 'propertySlot',
@@ -17,31 +16,35 @@ export default function defaultCharacterProperties(creatureId) {
hideWhenFull: true,
spaceLeft: 1,
totalFilled: 0,
order: 0,
parent: creatureRef,
ancestors: [creatureRef],
left: 1,
right: 2,
parentId: creatureId,
root: creatureRef,
}, {
_id: inventoryId,
type: 'folder',
name: 'Inventory',
tags: [BUILT_IN_TAGS.inventory],
order: 1,
parent: creatureRef,
ancestors: [creatureRef],
left: 3,
right: 8,
parentId: creatureId,
root: creatureRef,
}, {
type: 'folder',
name: 'Equipment',
tags: [BUILT_IN_TAGS.equipment],
order: 2,
parent: inventoryRef,
ancestors: [creatureRef, inventoryRef],
left: 4,
right: 5,
parentId: inventoryId,
root: creatureRef,
}, {
type: 'folder',
name: 'Carried',
tags: [BUILT_IN_TAGS.carried],
order: 3,
parent: inventoryRef,
ancestors: [creatureRef, inventoryRef],
left: 6,
right: 7,
parent: inventoryId,
root: creatureRef,
},
];
}

View File

@@ -7,10 +7,11 @@ import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
import Experiences from '/imports/api/creature/experience/Experiences';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
function removeRelatedDocuments(creatureId) {
CreatureVariables.remove({ _creatureId: creatureId });
CreatureProperties.remove({ 'ancestors.id': creatureId });
CreatureProperties.remove(getFilter.descendantsOfRoot(creatureId));
CreatureLogs.remove({ creatureId });
Experiences.remove({ creatureId });
}

View File

@@ -7,6 +7,7 @@ import { union } from 'lodash';
import ActionContext from '/imports/api/engine/actions/ActionContext';
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers';
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
const restCreature = new ValidatedMethod({
name: 'creature.methods.rest',
@@ -74,7 +75,7 @@ function doRestWork(restType, actionContext) {
export function resetProperties(creatureId, resetFilter, actionContext) {
// Only apply to active properties
const filter = {
'ancestors.id': creatureId,
...getFilter.descendantsOfRoot(creatureId),
reset: resetFilter,
removed: { $ne: true },
inactive: { $ne: true },
@@ -128,7 +129,7 @@ export function resetProperties(creatureId, resetFilter, actionContext) {
function resetHitDice(creatureId, actionContext) {
let hitDice = CreatureProperties.find({
'ancestors.id': creatureId,
...getFilter.descendantsOfRoot(creatureId),
type: 'attribute',
attributeType: 'hitDice',
removed: { $ne: true },

View File

@@ -1,59 +0,0 @@
import {
updateChildren,
updateDescendants,
} from '/imports/api/parenting/parentingFunctions';
import { inheritedFields } from '/imports/api/parenting/ChildSchema';
import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS';
// This mixin can be safely applied to all update methods which are validated
// with the updateSchemaMixin. It will propagate updates to fields which
// are inherited and normalised on the parent or ancestor docs
// It should have neglible performance impact for updates that aren't inherited
function propagateInheritanceUpdate({ _id, update }) {
let childModifier = {};
let descendantModifier = {};
// For each operator
for (let operator of MONGO_OPERATORS) {
// If the operator is in the update, for each field
if (update[operator]) for (let field in update[operator]) {
// Get the top level field that was actually modified
const indexOfDot = field.indexOf('.');
let modifiedField;
if (indexOfDot !== -1) {
modifiedField = field.substring(0, indexOfDot);
} else {
modifiedField = field;
}
// If that field is updated and inherited
if (inheritedFields.has(modifiedField)) {
// Perform the same update on the descendants
if (!childModifier[operator]) childModifier[operator] = {};
if (!descendantModifier[operator]) descendantModifier[operator] = {};
childModifier[operator][`parent.${field}`] = update[operator][field];
descendantModifier[operator][`ancestors.$.${field}`] = update[operator][field];
}
}
}
// Update the parent object of its children
updateChildren({
parentId: _id,
modifier: childModifier,
});
// Update the ancestors object of its descendants
updateDescendants({
ancestorId: _id,
modifier: descendantModifier,
});
}
export default function propagateInheritanceUpdateMixin(methodOptions) {
let runFunc = methodOptions.run;
methodOptions.run = function ({ _id, update }) {
const result = runFunc.apply(this, arguments);
propagateInheritanceUpdate({ _id, update });
return result;
};
return methodOptions;
}

View File

@@ -9,53 +9,11 @@ import { storedIconsSchema } from '/imports/api/icons/Icons';
import '/imports/api/library/methods/index';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import { restore } from '/imports/api/parenting/softRemove';
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
import { getAncestry } from '/imports/api/parenting/parentingFunctions';
import { getFilter, rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
import ChildSchema from '/imports/api/parenting/ChildSchema';
const Docs = new Mongo.Collection('docs');
const RefSchema = new SimpleSchema({
id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1
},
collection: {
type: String,
max: STORAGE_LIMITS.collectionName,
},
urlName: {
type: String,
regEx: /[a-z]+(?:[a-z]|-)+/,
min: 2,
max: STORAGE_LIMITS.variableName,
optional: true,
},
name: {
type: String,
max: STORAGE_LIMITS.description,
optional: true,
},
});
let ChildSchema = new SimpleSchema({
order: {
type: Number,
},
parent: {
type: RefSchema,
optional: true,
},
ancestors: {
type: Array,
defaultValue: [],
maxCount: STORAGE_LIMITS.ancestorCount,
},
'ancestors.$': {
type: RefSchema,
},
});
let DocSchema = new SimpleSchema({
_id: {
type: String,
@@ -105,38 +63,14 @@ function assertDocsEditPermission(userId) {
function getDocLink(doc, urlName) {
if (!urlName) urlName = doc.urlName;
const address = ['/docs'];
doc.ancestors?.forEach(a => {
const ancestorDocs = Docs.find(getFilter.ancestors(doc));
ancestorDocs?.forEach(a => {
address.push(a.urlName);
});
address.push(urlName);
return address.join('/');
}
function rebuildDocAncestors(docId) {
const newDoc = Docs.findOne(docId);
Docs.find({ 'ancestors.id': docId }).forEach(doc => {
doc.ancestors.forEach((a, i) => {
if (a.id === docId) {
Docs.update(doc._id, {
$set: {
[`ancestors.${i}`]: {
id: newDoc._id,
collection: 'docs',
urlName: newDoc.urlName,
name: newDoc.name,
}
}
});
}
});
doc = Docs.findOne(doc._id);
const newLink = getDocLink(doc);
if (doc.href !== newLink) {
Docs.update(doc._id, { $set: { href: newLink } })
}
});
}
// Add a means of seeding new servers with documentation
if (Meteor.isClient) {
Docs.getJsonDocs = function () {
@@ -161,18 +95,11 @@ const insertDoc = new ValidatedMethod({
numRequests: 5,
timeInterval: 5000,
},
run({ doc, parentRef }) {
run({ doc, parentId }) {
delete doc._id;
assertDocsEditPermission(this.userId);
// get the new ancestry for the properties
if (parentRef) {
var { ancestors } = getAncestry({
parentRef,
inheritedFields: { name: 1, urlName: 1 },
});
}
doc.parent = parentRef;
doc.ancestors = ancestors;
doc.parentId = parentId;
const lastOrder = Docs.find({}, { sort: { order: -1 } }).fetch()[0]?.order || 0;
doc.order = lastOrder + 1;
@@ -184,7 +111,7 @@ const insertDoc = new ValidatedMethod({
}
const docId = Docs.insert(doc);
rebuildNestedSets(Docs, 'root');
rebuildNestedSets(Docs);
return docId;
},
});
@@ -222,13 +149,9 @@ const updateDoc = new ValidatedMethod({
}
modifier.$set = modifier.$set || {};
modifier.$set.href = newLink;
rebuildDocAncestors(_id);
}
const updates = Docs.update(_id, modifier);
if (pathString === 'name' || pathString === 'urlName') {
rebuildDocAncestors(_id);
}
rebuildNestedSets(Docs, 'root');
rebuildNestedSets(Docs);
return updates;
},
});
@@ -278,7 +201,7 @@ const softRemoveDoc = new ValidatedMethod({
run({ _id }) {
assertDocsEditPermission(this.userId);
softRemove({ _id, collection: Docs });
rebuildNestedSets(Docs, 'root');
rebuildNestedSets(Docs);
}
});
@@ -294,8 +217,8 @@ const restoreDoc = new ValidatedMethod({
},
run({ _id }) {
assertDocsEditPermission(this.userId);
restore({ _id, collection: Docs });
rebuildNestedSets(Docs, 'root');
restore('docs', _id);
rebuildNestedSets(Docs);
}
});

View File

@@ -8,7 +8,7 @@ import { damagePropertyWork } from '/imports/api/creature/creatureProperties/met
import numberToSignedString from '/imports/api/utility/numberToSignedString';
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers';
import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature';
import { TreeNode } from '/imports/api/parenting/parentingFunctions';
import { TreeNode, hasAncestorRelationship } from '/imports/api/parenting/parentingFunctions';
import { Action } from '/imports/api/properties/Actions';
import { LogContent } from '/imports/api/creature/log/LogContentSchema';
import { Item } from '/imports/api/properties/Items';
@@ -327,20 +327,3 @@ function spendResources(prop: Action, actionContext) {
inline: true,
});
}
function hasAncestorRelationship(a, b) {
let top, bottom;
if (a.ancestors.length === b.ancestors.length) {
// Can't be ancestors of one another if they have the same number of ancestors
return false;
} else if (a.ancestors.length > b.ancestors.length) {
// longer ancestor list goes on the bottom
top = b;
bottom = a;
} else {
top = a;
bottom = b;
}
const expectedAncestorPosition = top.ancestors.length;
return bottom.ancestors[expectedAncestorPosition]?.id === top._id;
}

View File

@@ -48,7 +48,7 @@ const doAction = new ValidatedMethod({
run({ spellId, slotId, ritual, targetIds = [], scope = {} }) {
// Get action context
let spell = CreatureProperties.findOne(spellId);
const creatureId = spell.ancestors[0].id;
const creatureId = spell.root.id;
const actionContext = new ActionContext(creatureId, targetIds, this);
// Check permissions

View File

@@ -25,7 +25,8 @@ const doCheck = new ValidatedMethod({
},
run({ propId, scope }) {
const prop = CreatureProperties.findOne(propId);
const creatureId = prop.ancestors[0].id;
if (!prop) throw new Meteor.Error('not-found', 'The property was not found');
const creatureId = prop.root.id;
const actionContext = new ActionContext(creatureId, [creatureId], this);
Object.assign(actionContext.scope, scope);
actionContext.scope[`#${prop.type}`] = prop;

View File

@@ -7,6 +7,7 @@ import { assertEditPermission, assertOwnership } from '/imports/api/sharing/shar
import LibraryNodes from '/imports/api/library/LibraryNodes';
import { getUserTier } from '/imports/api/users/patreon/tiers'
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
/**
* Libraries are trees of library nodes where each node represents a character
@@ -160,7 +161,7 @@ const removeLibrary = new ValidatedMethod({
export function removeLibaryWork(libraryId) {
Libraries.remove(libraryId);
LibraryNodes.remove({ 'ancestors.id': libraryId });
LibraryNodes.remove(getFilter.descendantsOfRoot(libraryId));
}
export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, updateLibraryShowInMarket, removeLibrary };

View File

@@ -15,7 +15,7 @@ import '/imports/api/library/methods/index';
import { updateReferenceNodeWork } from '/imports/api/library/methods/updateReferenceNode';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
import { restore } from '/imports/api/parenting/softRemove';
import { getAncestry } from '/imports/api/parenting/parentingFunctions';
import { fetchDocByRef, getAncestry } from '/imports/api/parenting/parentingFunctions';
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
let LibraryNodes = new Mongo.Collection('libraryNodes');
@@ -122,7 +122,7 @@ for (let key in propertySchemasIndex) {
function getLibrary(node) {
if (!node) throw new Meteor.Error('No node provided');
let library = Libraries.findOne(node.ancestors[0].id);
let library = Libraries.findOne(node.root.id);
if (!library) throw new Meteor.Error('Library does not exist');
return library;
}
@@ -148,22 +148,20 @@ const insertNode = new ValidatedMethod({
},
run({ libraryNode, parentRef }) {
// get the new ancestry
let { parentDoc, ancestors } = getAncestry({ parentRef });
const parentDoc = fetchDocByRef(parentRef);
// Check permission to edit
let root;
if (parentRef.collection === 'libraries') {
root = parentDoc;
} else if (parentRef.collection === 'libraryNodes') {
root = Libraries.findOne(parentDoc.ancestors[0].id);
root = Libraries.findOne(parentDoc.root.id);
libraryNode.parentId = parentRef.id;
} else {
throw `${parentRef.collection} is not a valid parent collection`
}
assertEditPermission(root, this.userId);
// Set the ancestry of the library node
libraryNode.parent = parentRef;
libraryNode.ancestors = ancestors;
// Remove its ID if it came with one to force a random one to be generated
// server-side
delete libraryNode._id;
@@ -195,6 +193,8 @@ const updateLibraryNode = new ValidatedMethod({
case 'order':
case 'parent':
case 'ancestors':
case 'parentId':
case 'root':
return false;
}
},
@@ -296,7 +296,7 @@ const restoreLibraryNode = new ValidatedMethod({
let node = LibraryNodes.findOne(_id);
assertNodeEditPermission(node, this.userId);
// Do work
restore({ _id, collection: LibraryNodes });
restore(LibraryNodes, _id);
}
});

View File

@@ -9,7 +9,8 @@ import {
} from '/imports/api/sharing/sharingPermissions';
import {
setLineageOfDocs,
renewDocIds
renewDocIds,
getFilter
} from '/imports/api/parenting/parentingFunctions';
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
import { fetchDocByRef } from '/imports/api/parenting/parentingFunctions';
@@ -46,12 +47,14 @@ const copyLibraryNodeTo = new ValidatedMethod({
);
}
const libraryNode = LibraryNodes.findOne(_id);
if (!libraryNode) throw new Meteor.Error('not-found', 'Library node was not found');
const parentDoc = fetchDocByRef(parent);
assertDocCopyPermission(libraryNode, this.userId);
assertDocEditPermission(parentDoc, this.userId);
let decendants = LibraryNodes.find({
'ancestors.id': _id,
...getFilter.descendants(libraryNode),
removed: { $ne: true },
}, {
limit: DUPLICATE_CHILDREN_LIMIT + 1,
@@ -69,26 +72,16 @@ const copyLibraryNodeTo = new ValidatedMethod({
const nodes = [libraryNode, ...decendants];
const newAncestry = parentDoc.ancestors || [];
newAncestry.push(parent);
// re-map all the ancestors
setLineageOfDocs({
docArray: nodes,
newAncestry,
oldParent: libraryNode.parent,
});
// Give the docs new IDs without breaking internal references
renewDocIds({ docArray: nodes });
// Order the root node
libraryNode.order = (parentDoc.order || 0) + 0.5;
libraryNode.left = Number.MAX_SAFE_INTEGER - 1;
libraryNode.right = Number.MAX_SAFE_INTEGER;
LibraryNodes.batchInsert(nodes);
// Tree structure changed by inserts, reorder the tree
// TODO: rebuild tree nested sets
rebuildNestedSets(LibraryNodes, parentDoc.root.id);
},
});

View File

@@ -5,7 +5,8 @@ import LibraryNodes from '/imports/api/library/LibraryNodes';
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions';
import {
setLineageOfDocs,
renewDocIds
renewDocIds,
getFilter
} from '/imports/api/parenting/parentingFunctions';
import { rebuildNestedSets } from '/imports/api/parenting/parentingFunctions';
@@ -33,6 +34,8 @@ const duplicateLibraryNode = new ValidatedMethod({
},
run({ _id }) {
let libraryNode = LibraryNodes.findOne(_id);
if (!libraryNode) throw new Meteor.Error('not-found', 'Library node was not found');
assertDocEditPermission(libraryNode, this.userId);
let randomSrc = DDP.randomStream('duplicateLibraryNode');
@@ -40,7 +43,7 @@ const duplicateLibraryNode = new ValidatedMethod({
libraryNode._id = libraryNodeId;
let nodes = LibraryNodes.find({
'ancestors.id': _id,
...getFilter.descendants(libraryNode),
removed: { $ne: true },
}, {
limit: DUPLICATE_CHILDREN_LIMIT + 1,
@@ -56,16 +59,6 @@ const duplicateLibraryNode = new ValidatedMethod({
}
}
// re-map all the ancestors
setLineageOfDocs({
docArray: nodes,
newAncestry: [
...libraryNode.ancestors,
{ id: libraryNodeId, collection: 'libraryNodes' }
],
oldParent: { id: _id, collection: 'libraryNodes' },
});
// Give the docs new IDs without breaking internal references
const allNodes = [libraryNode, ...nodes];
renewDocIds({ docArray: allNodes });
@@ -76,10 +69,7 @@ const duplicateLibraryNode = new ValidatedMethod({
LibraryNodes.batchInsert(allNodes);
// Tree structure changed by inserts, reorder the tree
reorderDocs({
collection: LibraryNodes,
ancestorId: libraryNode.ancestors[0].id,
});
rebuildNestedSets(LibraryNodes, libraryNode.root.id);
return libraryNodeId;
},

View File

@@ -11,8 +11,8 @@ export default function getDefaultSlotFiller(slot) {
type: slotType,
libraryTags: slot.slotTags || [],
name: 'Custom ' + slot.name || 'slot filler',
parent: { collection: 'creatureProperties', id: slot._id },
ancestors: [...slot.ancestors, { collection: 'creatureProperties', id: slot._id }],
parentId: slot._id,
root: { ...slot.root },
};
return filler;
}

View File

@@ -45,8 +45,8 @@ function updateReferenceNodeWork(node, userId) {
try {
doc = fetchDocByRef(node.ref);
if (doc.removed) throw 'Property has been deleted';
if (doc.ancestors[0].id !== node.ancestors[0].id) {
library = fetchDocByRef(doc.ancestors[0]);
if (doc.root.id !== node.root.id) {
library = fetchDocByRef(doc.root);
assertViewPermission(library, userId)
}
} catch (e) {

View File

@@ -241,6 +241,20 @@ export const getFilter = {
});
return filter;
},
descendantsOfRoot(rootId: string) {
return {
'root.id': rootId,
}
},
/**
* @param rootIds a non-empty array of ids
*/
descendantsOfAllRoots(rootIds: string[]) {
if (!rootIds.length) throw 'rootIds can\'t be empty';
return {
'root.id': { $in: rootIds },
};
},
descendants(doc: TreeDoc) {
return {
'root.id': doc.root.id,
@@ -373,6 +387,26 @@ export function compareOrder(docA, docB) {
}
}
/**
* Determine if two properties have an ancestor relationship, returns true if A is an ancestor of B
* or B is an ancestor of A
*/
export function hasAncestorRelationship(propA: TreeDoc, propB: TreeDoc): boolean {
// If they don't share a root, they can't share an ancestor relationship
if (propA.root.id !== propB.root.id) {
return false;
}
// Return if there is an ancestor relationship in either direction
return isAncestor(propA, propB) || isAncestor(propB, propA);
}
/**
* Returns true if A is a direct ancestor of B, assuming their roots are equal
*/
export function isAncestor(propA: TreeDoc, propB: TreeDoc): boolean {
return propA.left < propB.left && propA.right > propB.right;
}
/**
* @deprecated Just set left to Number.MAX_SAFE_INTEGER instead
*/

View File

@@ -51,7 +51,7 @@ const restoreError = function () {
);
};
export async function restore(collection: Mongo.Collection<TreeDoc> | string, doc: TreeDoc | string, extraUpdates) {
export async function restore(collection: Mongo.Collection<TreeDoc> | string, doc: TreeDoc | string, extraUpdates?) {
if (typeof collection === 'string') {
collection = getCollectionByName(collection);
}

View File

@@ -98,8 +98,8 @@ export function assertCopyPermission(doc, userId) {
function getRoot(doc) {
assertdocExists(doc);
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
return fetchDocByRef(doc.ancestors[0]);
if (doc.root) {
return fetchDocByRef(doc.root);
} else {
return doc;
}

View File

@@ -86,6 +86,7 @@
import { canBeParent } from '/imports/api/parenting/parentingFunctions';
import { getPropertyIcon } from '/imports/constants/PROPERTIES';
import TreeNodeView from '/imports/client/ui/properties/treeNodeViews/TreeNodeView.vue';
import { isAncestor } from '/imports/api/parenting/parentingFunctions';
import { some } from 'lodash';
export default {
@@ -121,9 +122,9 @@ export default {
},
data() {
return {
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id) ||
false,
expanded: this.startExpanded ||
this.node._ancestorOfMatchedDocument ||
isAncestor(this.node, this.selectedNode),
}
},
computed: {
@@ -152,8 +153,8 @@ export default {
this.expanded = !!value ||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
},
'selectedNode.ancestors'(value) {
this.expanded = !!some(value, ref => ref.id === this.node._id) || this.expanded;
'selectedNode.parentId'() {
this.expanded = isAncestor(this.node, this.selectedNode) || this.expanded;
},
},
beforeCreate() {

View File

@@ -131,6 +131,7 @@ import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue'
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty';
import restoreProperty from '/imports/api/creature/creatureProperties/methods/restoreProperty';
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle';
import { isAncestor } from '/imports/api/parenting/parentingFunctions';
export default {
name: 'BuildTreeNode',
@@ -233,11 +234,10 @@ export default {
},
watch: {
'node._ancestorOfMatchedDocument'(value){
this.expanded = !!value ||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
this.expanded = !!value || isAncestor(this.node, this.selectedNode);
},
'selectedNode.ancestors'(value){
this.expanded = !!some(value, ref => ref.id === this.node._id) || this.expanded;
'selectedNode.parentId'(){
this.expanded = isAncestor(this.node, this.selectedNode) || this.expanded;
},
},
beforeCreate() {

View File

@@ -40,6 +40,7 @@ import ColumnLayout from '/imports/client/ui/components/ColumnLayout.vue';
import ActionCard from '/imports/client/ui/properties/components/actions/ActionCard.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -56,10 +57,11 @@ export default {
data() { return {
tabName: 'actions',
}},
// @ts-ignore Meteor isn't defined on vue
meteor: {
actions() {
const folderIds = CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'folder',
groupStats: true,
hideStatsGroup: true,
@@ -68,9 +70,7 @@ export default {
}, { fields: { _id: 1 } }).map(folder => folder._id);
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
...getFilter.descendantsOfRoot(this.creatureId),
'parent.id': {
$nin: folderIds,
},

View File

@@ -211,6 +211,7 @@ import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue'
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty';
import getPropertyTitle from '/imports/client/ui/properties/shared/getPropertyTitle';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
function traverse(tree, callback, parents = []){
tree.forEach(node => {
@@ -271,6 +272,7 @@ export default {
return this.hiddenSlots.length + this.hiddenPointBuys.length;
},
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
creature(){
return Creatures.findOne(this.creatureId);
@@ -281,7 +283,7 @@ export default {
hiddenPointBuys() {
return CreatureProperties.find({
type: 'pointBuy',
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
ignored: true,
pointsLeft: {$ne: 0},
removed: {$ne: true},
@@ -291,7 +293,7 @@ export default {
hiddenSlots(){
return CreatureProperties.find({
type: 'propertySlot',
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
ignored: true,
$and: [
{
@@ -313,7 +315,7 @@ export default {
},
classProperties(){
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'class',
removed: {$ne: true},
inactive: {$ne: true},
@@ -324,7 +326,7 @@ export default {
classLevels() {
const classVariableNames = this.classProperties.map(c => c.variableName)
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'classLevel',
variableName: {$nin: classVariableNames},
removed: {$ne: true},
@@ -335,7 +337,7 @@ export default {
},
slotBuildTree(){
const slots = CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: {$in: ['propertySlot', 'pointBuy']},
$or: [
{'slotCondition.value': {$nin: [false, 0, '']}},

View File

@@ -36,6 +36,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
import ColumnLayout from '/imports/client/ui/components/ColumnLayout.vue';
import FeatureCard from '/imports/client/ui/properties/components/features/FeatureCard.vue';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -54,10 +55,11 @@ export default {
tabName: 'features',
};
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
features() {
const folderIds = CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'folder',
groupStats: true,
hideStatsGroup: true,
@@ -66,9 +68,7 @@ export default {
}, { fields: { _id: 1 } }).map(folder => folder._id);
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
...getFilter.descendantsOfRoot(this.creatureId),
'parent.id': {
$nin: folderIds,
},

View File

@@ -118,6 +118,7 @@ import CoinValue from '/imports/client/ui/components/CoinValue.vue';
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities';
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -140,10 +141,11 @@ export default {
tabName: 'inventory',
};
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
folderIds() {
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'folder',
groupStats: true,
hideStatsGroup: true,
@@ -153,9 +155,7 @@ export default {
},
containers() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
...getFilter.descendantsOfRoot(this.creatureId),
'parent.id': {
$nin: this.folderIds,
},
@@ -164,7 +164,7 @@ export default {
inactive: { $ne: true },
}, {
sort: { order: 1 },
});
}).fetch();
},
creature() {
return Creatures.findOne(this.creatureId, {
@@ -179,11 +179,9 @@ export default {
},
containersWithoutAncestorContainers() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
$nin: this.containerIds,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(this.containers),
parentId: {
$nin: this.folderIds,
},
type: 'container',
@@ -195,11 +193,9 @@ export default {
},
carriedItems() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
$nin: this.containerIds,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(this.containers),
parentId: {
$nin: this.folderIds,
},
type: 'item',
@@ -213,9 +209,7 @@ export default {
},
equippedItems() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
...getFilter.descendantsOfRoot(this.creatureId),
type: 'item',
equipped: true,
removed: { $ne: true },
@@ -246,9 +240,6 @@ export default {
},
},
computed: {
containerIds() {
return this.containers.map(container => container._id);
},
weightCarried() {
return stripFloatingPointOddities(
this.variables &&

View File

@@ -39,6 +39,7 @@ import NoteCard from '/imports/client/ui/properties/components/persona/NoteCard.
import CreatureSummary from '/imports/client/ui/creature/character/CreatureSummary.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -58,11 +59,12 @@ export default {
tabName: 'journal',
};
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
notes() {
// Get all the notes that aren't children of group folders or of other displayed notes
const folderIds = CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'folder',
groupStats: true,
hideStatsGroup: true,
@@ -71,27 +73,23 @@ export default {
}, { fields: { _id: 1 } }).map(folder => folder._id);
const noteFilter = {
'ancestors.id': {
$eq: this.creatureId,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
'parentId': {
$nin: folderIds,
},
type: 'note',
removed: { $ne: true },
inactive: { $ne: true },
};
const noteIds = CreatureProperties.find(noteFilter, {
sort: { order: 1 },
fields: {_id: 1},
}).map(note => note._id);
noteFilter['ancestors.id'] = {
$eq: this.creatureId,
$nin: noteIds,
}
const allNotes = CreatureProperties.find(noteFilter, {
sort: { left: 1 },
});
return CreatureProperties.find(noteFilter, {
return CreatureProperties.find({
...noteFilter,
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(allNotes),
}, {
sort: {order: 1},
});
},

View File

@@ -55,6 +55,7 @@ import SpellListCard from '/imports/client/ui/properties/components/spells/Spell
import SpellList from '/imports/client/ui/properties/components/spells/SpellList.vue';
import tabFoldersMixin from '/imports/client/ui/properties/components/folders/tabFoldersMixin';
import SpellSlotCard from '/imports/client/ui/properties/components/attributes/SpellSlotCard.vue';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -76,10 +77,11 @@ export default {
tabName: 'spells',
}
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
folderIds() {
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'folder',
groupStats: true,
hideStatsGroup: true,
@@ -89,7 +91,7 @@ export default {
},
hasSpellSlots() {
return !!CreatureProperties.findOne({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
inactive: { $ne: true },
removed: { $ne: true },
overridden: { $ne: true },
@@ -100,10 +102,8 @@ export default {
},
spellSlots() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
'parentId': {
$nin: this.folderIds,
},
inactive: { $ne: true },
@@ -121,22 +121,20 @@ export default {
},
spellLists() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
'parentId': {
$nin: this.folderIds,
},
type: 'spellList',
removed: { $ne: true },
inactive: { $ne: true },
}, {
sort: { order: 1 }
});
sort: { left: 1 }
}).fetch();
},
hasSpells() {
return !!CreatureProperties.findOne({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'spell',
removed: { $ne: true },
inactive: { $ne: true },
@@ -144,11 +142,9 @@ export default {
},
spellsWithoutList() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
$nin: this.spellListIds,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(this.spellLists),
parentId: {
$nin: this.folderIds,
},
type: 'spell',
@@ -164,11 +160,9 @@ export default {
},
spellListsWithoutAncestorSpellLists() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
$nin: this.spellListIds,
},
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(this.spellLists),
parentId: {
$nin: this.folderIds,
},
type: 'spellList',

View File

@@ -411,6 +411,7 @@ import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue'
import FolderGroupCard from '/imports/client/ui/properties/components/folders/FolderGroupCard.vue';
import { get, set, uniqBy } from 'lodash';
import { docsToForest } from '/imports/api/parenting/parentingFunctions';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
function walkDown(forest, callback){
let stack = [...forest];
@@ -516,12 +517,13 @@ export default {
return uniqBy(conditionals, '_id');
},
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
properties() {
const creature = this.creature;
if (!creature) return;
const folderIds = CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'folder',
groupStats: true,
hideStatsGroup: true,
@@ -530,8 +532,8 @@ export default {
}, { fields: { _id: 1 } }).map(folder => folder._id);
const filter = {
'ancestors.id': this.creatureId,
'parent.id': {
...getFilter.descendantsOfRoot(this.creatureId),
parentId: {
$nin: folderIds,
},
$or: [
@@ -585,7 +587,7 @@ export default {
toggles() {
return CreatureProperties.find({
type: 'toggle',
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
removed: { $ne: true },
deactivatedByAncestor: { $ne: true },
deactivatedByToggle: { $ne: true },

View File

@@ -91,6 +91,7 @@ import PrintedSpells from '/imports/client/ui/creature/character/printedCharacte
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
import QrcodeVue from 'qrcode.vue'
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -138,6 +139,7 @@ export default {
].sort((a, b) => a.order - b.order);
},
},
// @ts-ignore reactiveProvide isn't defined on vue
reactiveProvide: {
name: 'context',
include: ['creatureId', 'editPermission'],
@@ -182,7 +184,7 @@ export default {
race() {
if (this.variables?.race?.value?.valueType === 'string') return this.variables.race.value.value;
const prop = CreatureProperties.findOne({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
tags: 'race',
removed: { $ne: true },
inactive: { $ne: true },
@@ -194,7 +196,7 @@ export default {
background() {
if (this.variables?.background?.value?.valueType === 'string') return this.variables.background.value.value;
const prop = CreatureProperties.findOne({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
tags: 'background',
removed: { $ne: true },
inactive: { $ne: true },
@@ -205,7 +207,7 @@ export default {
},
classProperties(){
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'class',
removed: {$ne: true},
inactive: {$ne: true},
@@ -216,7 +218,7 @@ export default {
classLevels() {
const classVariableNames = this.classProperties.map(c => c.variableName)
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'classLevel',
variableName: {$nin: classVariableNames},
removed: {$ne: true},

View File

@@ -82,6 +82,7 @@ import stripFloatingPointOddities from '/imports/api/engine/computation/utility/
import PrintedLineItem from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedLineItem.vue';
import PrintedContainer from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue';
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
import { getFilter } from '/imports/api/parenting/parentingFunctions';
export default {
components: {
@@ -101,10 +102,11 @@ export default {
organize: false,
}
},
// @ts-ignore Meteor isn't defined on vue
meteor: {
containers() {
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...getFilter.descendantsOfRoot(this.creatureId),
type: 'container',
removed: { $ne: true },
inactive: { $ne: true },
@@ -125,10 +127,8 @@ export default {
},
containersWithoutAncestorContainers() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
$nin: this.containerIds
},
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(this.containers),
type: 'container',
removed: { $ne: true },
inactive: { $ne: true },
@@ -150,10 +150,8 @@ export default {
},
carriedItems() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
$nin: this.containerIds
},
...getFilter.descendantsOfRoot(this.creatureId),
$not: getFilter.descendantsOfAll(this.containers),
type: 'item',
equipped: { $ne: true },
removed: { $ne: true },
@@ -165,9 +163,7 @@ export default {
},
equippedItems() {
return CreatureProperties.find({
'ancestors.id': {
$eq: this.creatureId,
},
...getFilter.descendantsOfRoot(this.creatureId),
type: 'item',
equipped: true,
removed: { $ne: true },
@@ -198,9 +194,6 @@ export default {
},
},
computed: {
containerIds() {
return this.containers.map(container => container._id);
},
weightCarried() {
return stripFloatingPointOddities(
this.variables &&

View File

@@ -180,10 +180,7 @@ export default {
doc: {
name: 'New Doc',
},
parentRef: this.doc && {
id: this.docId,
collection: 'docs',
},
parentId: this.docId,
}, ack);
},
remove({ ack } = {}) {

View File

@@ -11,6 +11,8 @@ Migrations.add({
migrateCollection('libraryNodes');
console.log('migrating up creature props 2 -> 3');
migrateCollection('creatureProperties');
console.log('migrating up docs 2 -> 3');
migrateCollection('docs');
console.log('New schema fields added, if it was done correctly remove the old fields manually');
},