Began migration of queries to nested sets
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [{
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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, '']}},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -180,10 +180,7 @@ export default {
|
||||
doc: {
|
||||
name: 'New Doc',
|
||||
},
|
||||
parentRef: this.doc && {
|
||||
id: this.docId,
|
||||
collection: 'docs',
|
||||
},
|
||||
parentId: this.docId,
|
||||
}, ack);
|
||||
},
|
||||
remove({ ack } = {}) {
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user