Compare commits
7 Commits
2.0-beta.2
...
2.0-beta.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4faea42371 | ||
|
|
9825872576 | ||
|
|
85b536bc46 | ||
|
|
9aa8203dcc | ||
|
|
217133137b | ||
|
|
aef7dbcbb3 | ||
|
|
6ff750417f |
@@ -21,13 +21,19 @@ export default function computeStat(stat, memo){
|
|||||||
// Before doing any work, mark this stat as busy
|
// Before doing any work, mark this stat as busy
|
||||||
stat.computationDetails.busyComputing = true;
|
stat.computationDetails.busyComputing = true;
|
||||||
|
|
||||||
let effects = stat.computationDetails.effects;
|
let effects = stat.computationDetails.effects || [];
|
||||||
let proficiencies = stat.computationDetails.proficiencies;
|
let proficiencies = stat.computationDetails.proficiencies || [];
|
||||||
|
|
||||||
// Get references to all the stats that share the variable name
|
// Get references to all the stats that share the variable name
|
||||||
let sameNameStats = stat.computationDetails.idsOfSameName.map(
|
let sameNameStats
|
||||||
id => memo.propsById[id]
|
|
||||||
);
|
if (stat.computationDetails.idsOfSameName){
|
||||||
|
sameNameStats = stat.computationDetails.idsOfSameName.map(
|
||||||
|
id => memo.propsById[id]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sameNameStats = [];
|
||||||
|
}
|
||||||
|
|
||||||
let allStats = [stat, ...sameNameStats];
|
let allStats = [stat, ...sameNameStats];
|
||||||
|
|
||||||
@@ -76,7 +82,9 @@ export default function computeStat(stat, memo){
|
|||||||
value: statInstance.baseProficiency,
|
value: statInstance.baseProficiency,
|
||||||
stats: [statInstance.variableName],
|
stats: [statInstance.variableName],
|
||||||
type: 'proficiency',
|
type: 'proficiency',
|
||||||
dependencies: [],
|
dependencies: statInstance.overridden ?
|
||||||
|
union(statInstance.dependencies, [statInstance._id]) :
|
||||||
|
[],
|
||||||
computationDetails: {
|
computationDetails: {
|
||||||
computed: true,
|
computed: true,
|
||||||
}
|
}
|
||||||
@@ -84,7 +92,7 @@ export default function computeStat(stat, memo){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute each active stat's baseValue calculation and apply it
|
// Compute each active stat's baseValue calculation and apply it
|
||||||
if (statInstance.baseValueCalculation) {
|
if (!statInstance.inactive) {
|
||||||
let {
|
let {
|
||||||
result,
|
result,
|
||||||
context,
|
context,
|
||||||
@@ -94,35 +102,27 @@ export default function computeStat(stat, memo){
|
|||||||
prop: statInstance,
|
prop: statInstance,
|
||||||
memo
|
memo
|
||||||
});
|
});
|
||||||
baseDependencies = union(baseDependencies, dependencies);
|
|
||||||
statInstance.baseValue = +result.value;
|
statInstance.baseValue = +result.value;
|
||||||
|
statInstance.dependencies = union(statInstance.dependencies, dependencies);
|
||||||
if (context.errors.length){
|
if (context.errors.length){
|
||||||
statInstance.baseValueErrors = context.errors;
|
statInstance.baseValueErrors = context.errors;
|
||||||
}
|
}
|
||||||
// Apply all the base values
|
// Apply all the base values
|
||||||
if (!statInstance.inactive){
|
effects.push({
|
||||||
effects.push({
|
operation: 'base',
|
||||||
operation: 'base',
|
calculation: statInstance.baseValueCalculation,
|
||||||
calculation: statInstance.baseValueCalculation,
|
result: statInstance.baseValue,
|
||||||
result: statInstance.baseValue,
|
stats: [statInstance.variableName],
|
||||||
stats: [statInstance.variableName],
|
dependencies: statInstance.overridden ?
|
||||||
dependencies: [],
|
union(statInstance.dependencies, [statInstance._id]) :
|
||||||
computationDetails: {
|
[],
|
||||||
computed: true,
|
computationDetails: {
|
||||||
},
|
computed: true,
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply all the base baseDependencies
|
|
||||||
allStats.forEach(statInstance => {
|
|
||||||
statInstance.dependencies = union(
|
|
||||||
statInstance.dependencies,
|
|
||||||
without(baseDependencies, statInstance._id)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compute and aggregate all the effects
|
// Compute and aggregate all the effects
|
||||||
let aggregator = new EffectAggregator();
|
let aggregator = new EffectAggregator();
|
||||||
let effectDeps = [];
|
let effectDeps = [];
|
||||||
@@ -132,7 +132,7 @@ export default function computeStat(stat, memo){
|
|||||||
if (effect.deactivatedByToggle) return;
|
if (effect.deactivatedByToggle) return;
|
||||||
|
|
||||||
// dependencies
|
// dependencies
|
||||||
if (effect._id) effectDeps = [effect._id];
|
if (effect._id) effectDeps = union(effectDeps, [effect._id]);
|
||||||
effectDeps = union(effectDeps, effect.dependencies);
|
effectDeps = union(effectDeps, effect.dependencies);
|
||||||
|
|
||||||
// Add computed effect to aggregator
|
// Add computed effect to aggregator
|
||||||
@@ -146,9 +146,9 @@ export default function computeStat(stat, memo){
|
|||||||
// Mark the stats as computed
|
// Mark the stats as computed
|
||||||
statInstance.computationDetails.computed = true;
|
statInstance.computationDetails.computed = true;
|
||||||
statInstance.computationDetails.busyComputing = false;
|
statInstance.computationDetails.busyComputing = false;
|
||||||
statInstance.dependencies = union(
|
// Only the active stat instance depeneds on the effects
|
||||||
statInstance.dependencies,
|
if (!statInstance.overridden){
|
||||||
effectDeps
|
statInstance.dependencies = union(statInstance.dependencies, effectDeps);
|
||||||
);
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
||||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||||
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||||
name: 'creatureProperties.insertPropertyFromLibraryNode',
|
name: 'creatureProperties.insertPropertyFromLibraryNode',
|
||||||
@@ -54,6 +55,7 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
|||||||
|
|
||||||
// Fetch the library node and its decendents, provided they have not been
|
// Fetch the library node and its decendents, provided they have not been
|
||||||
// removed
|
// removed
|
||||||
|
// TODO: Check permission to read the library this node is in
|
||||||
let node = LibraryNodes.findOne({
|
let node = LibraryNodes.findOne({
|
||||||
_id: nodeId,
|
_id: nodeId,
|
||||||
removed: {$ne: true},
|
removed: {$ne: true},
|
||||||
@@ -65,6 +67,9 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
|||||||
removed: {$ne: true},
|
removed: {$ne: true},
|
||||||
}).fetch();
|
}).fetch();
|
||||||
|
|
||||||
|
// Convert all references into actual nodes
|
||||||
|
nodes = reifyNodeReferences(nodes);
|
||||||
|
|
||||||
// The root node is first in the array of nodes
|
// The root node is first in the array of nodes
|
||||||
// It must get the first generated ID to prevent flickering
|
// It must get the first generated ID to prevent flickering
|
||||||
nodes = [node, ...nodes];
|
nodes = [node, ...nodes];
|
||||||
@@ -115,4 +120,95 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Covert node references into actual nodes
|
||||||
|
// TODO: check permissions for each library a reference node references
|
||||||
|
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||||
|
depth += 1;
|
||||||
|
// New nodes added this function
|
||||||
|
let newNodes = [];
|
||||||
|
|
||||||
|
// Filter out the reference nodes we replace
|
||||||
|
let resultingNodes = nodes.filter(node => {
|
||||||
|
|
||||||
|
// We have already visited this ref and replaced it
|
||||||
|
if (visitedRefs.has(node._id)) return false;
|
||||||
|
|
||||||
|
// Already replaced an ancestor node
|
||||||
|
for (let i; i < node.ancestors.length; i++){
|
||||||
|
if (visitedRefs.has(node.ancestors[i].id)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This isn't a reference node, continue as normal
|
||||||
|
if (node.type !== 'reference') return true;
|
||||||
|
|
||||||
|
// We have gone too deep, keep the reference node as an error
|
||||||
|
if (depth > 10){
|
||||||
|
if (Meteor.isClient) console.warn('Reference depth limit exceeded');
|
||||||
|
node.cache = {error: 'Reference depth limit exceeded'};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let referencedNode
|
||||||
|
try {
|
||||||
|
referencedNode = fetchDocByRef(node.ref);
|
||||||
|
referencedNode.order = node.order;
|
||||||
|
// We are definitely replacing this node, so add it to the list
|
||||||
|
visitedRefs.add(node._id);
|
||||||
|
} catch (e){
|
||||||
|
node.cache = {error: e.reason || e.message || e.toString()};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the descendants of the referenced node
|
||||||
|
let descendents = LibraryNodes.find({
|
||||||
|
'ancestors.id': referencedNode._id,
|
||||||
|
removed: {$ne: true},
|
||||||
|
}, {
|
||||||
|
sort: {order: 1},
|
||||||
|
}).fetch();
|
||||||
|
|
||||||
|
// We are adding the referenced node and its descendants
|
||||||
|
let addedNodes = [referencedNode, ...descendents];
|
||||||
|
|
||||||
|
// re-map all the ancestors to parent the new sub-tree into our existing
|
||||||
|
// node tree
|
||||||
|
setLineageOfDocs({
|
||||||
|
docArray: addedNodes,
|
||||||
|
newAncestry: node.ancestors,
|
||||||
|
oldParent: referencedNode.parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove all the looped references and descendents from the new nodes
|
||||||
|
// We can't rely on the reify recursion to do this, since the IDs are
|
||||||
|
// getting renewed before it is called
|
||||||
|
addedNodes = addedNodes.filter(node => {
|
||||||
|
// Exclude removed referenced
|
||||||
|
if (visitedRefs.has(node._id)) return false;
|
||||||
|
|
||||||
|
// Exclude descendants of removed references
|
||||||
|
for (let i; i < node.ancestors.length; i++){
|
||||||
|
if (visitedRefs.has(node.ancestors[i].id)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give the new referenced sub-tree new ids
|
||||||
|
renewDocIds({
|
||||||
|
docArray: addedNodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reify the subtree as well with recursion
|
||||||
|
addedNodes = reifyNodeReferences(addedNodes, visitedRefs, depth);
|
||||||
|
|
||||||
|
// Store the new nodes from this inner loop without altering the array
|
||||||
|
// we are looping over
|
||||||
|
newNodes.push(...addedNodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// We are done filtering the array, we can add the new nodes to it
|
||||||
|
resultingNodes.push(...newNodes);
|
||||||
|
|
||||||
|
return resultingNodes;
|
||||||
|
}
|
||||||
|
|
||||||
export default insertPropertyFromLibraryNode;
|
export default insertPropertyFromLibraryNode;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { softRemove } from '/imports/api/parenting/softRemove.js';
|
|||||||
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
|
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
|
||||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||||
import '/imports/api/library/methods/index.js';
|
import '/imports/api/library/methods/index.js';
|
||||||
|
import { updateReferenceNodeWork } from '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|
||||||
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
||||||
|
|
||||||
@@ -76,7 +77,12 @@ const insertNode = new ValidatedMethod({
|
|||||||
run(libraryNode) {
|
run(libraryNode) {
|
||||||
delete libraryNode._id;
|
delete libraryNode._id;
|
||||||
assertNodeEditPermission(libraryNode, this.userId);
|
assertNodeEditPermission(libraryNode, this.userId);
|
||||||
return LibraryNodes.insert(libraryNode);
|
let nodeId = LibraryNodes.insert(libraryNode);
|
||||||
|
if (libraryNode.type == 'reference'){
|
||||||
|
libraryNode._id = nodeId;
|
||||||
|
updateReferenceNodeWork(libraryNode, this.userId);
|
||||||
|
}
|
||||||
|
return nodeId;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,9 +115,14 @@ const updateLibraryNode = new ValidatedMethod({
|
|||||||
} else {
|
} else {
|
||||||
modifier = {$set: {[pathString]: value}};
|
modifier = {$set: {[pathString]: value}};
|
||||||
}
|
}
|
||||||
return LibraryNodes.update(_id, modifier, {
|
let numUpdated = LibraryNodes.update(_id, modifier, {
|
||||||
selector: {type: node.type},
|
selector: {type: node.type},
|
||||||
});
|
});
|
||||||
|
if (node.type == 'reference'){
|
||||||
|
node = LibraryNodes.findOne(_id);
|
||||||
|
updateReferenceNodeWork(node, this.userId);
|
||||||
|
}
|
||||||
|
return numUpdated;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
||||||
|
import '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|||||||
67
app/imports/api/library/methods/updateReferenceNode.js
Normal file
67
app/imports/api/library/methods/updateReferenceNode.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
|
import {
|
||||||
|
assertDocEditPermission,
|
||||||
|
assertViewPermission,
|
||||||
|
} from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
|
const updateReferenceNode = new ValidatedMethod({
|
||||||
|
name: 'libraryNodes.updateReferenceNode',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
}
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({_id}) {
|
||||||
|
let userId = this.userId;
|
||||||
|
let node = LibraryNodes.findOne(_id);
|
||||||
|
assertDocEditPermission(node, userId);
|
||||||
|
updateReferenceNodeWork(node, userId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function writeCache(_id, cache){
|
||||||
|
LibraryNodes.update(_id, {$set: {cache}}, {
|
||||||
|
selector: {type: 'reference'},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateReferenceNodeWork(node, userId){
|
||||||
|
let cache = {}
|
||||||
|
if (!node.ref){
|
||||||
|
writeCache(node._id, cache);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let doc, library;
|
||||||
|
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]);
|
||||||
|
assertViewPermission(library, userId)
|
||||||
|
}
|
||||||
|
} catch(e){
|
||||||
|
cache = {error: e.reason || e.message || e.toString()}
|
||||||
|
writeCache(node._id, cache);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cache = {
|
||||||
|
node: {name: doc.name, type: doc.type},
|
||||||
|
};
|
||||||
|
if (library){
|
||||||
|
cache.library = {name: library.name};
|
||||||
|
}
|
||||||
|
writeCache(node._id, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateReferenceNode;
|
||||||
|
export { updateReferenceNodeWork }
|
||||||
@@ -4,6 +4,11 @@ const RefSchema = new SimpleSchema({
|
|||||||
id: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
// TODO: Rather than indexing this field, index `ancestors.0.id` to only
|
||||||
|
// index the root of the ancestor heirarchy to significantly reduce
|
||||||
|
// index size and improve performance
|
||||||
|
// All queries on an ancestor document need to target `ancestors.0.id` first
|
||||||
|
// before targeting a younger ancestor
|
||||||
index: 1
|
index: 1
|
||||||
},
|
},
|
||||||
collection: {
|
collection: {
|
||||||
|
|||||||
47
app/imports/api/properties/References.js
Normal file
47
app/imports/api/properties/References.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
|
let ReferenceSchema = new SimpleSchema({
|
||||||
|
ref: {
|
||||||
|
type: Object,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
'ref.id': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'ref.collection': {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Denormalised store of referenced property's details
|
||||||
|
cache: {
|
||||||
|
type: Object,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
'cache.error': {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'cache.node': {
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'cache.node.name': {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'cache.node.type': {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
'cache.library': {
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'cache.library.name': {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export { ReferenceSchema };
|
||||||
@@ -15,6 +15,7 @@ import { FolderSchema } from '/imports/api/properties/Folders.js';
|
|||||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||||
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
||||||
@@ -42,6 +43,7 @@ const propertySchemasIndex = {
|
|||||||
note: ComputedOnlyNoteSchema,
|
note: ComputedOnlyNoteSchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: ComputedOnlySlotSchema,
|
propertySlot: ComputedOnlySlotSchema,
|
||||||
|
reference: ReferenceSchema,
|
||||||
roll: ComputedOnlyRollSchema,
|
roll: ComputedOnlyRollSchema,
|
||||||
savingThrow: ComputedOnlySavingThrowSchema,
|
savingThrow: ComputedOnlySavingThrowSchema,
|
||||||
skill: ComputedOnlySkillSchema,
|
skill: ComputedOnlySkillSchema,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { FolderSchema } from '/imports/api/properties/Folders.js';
|
|||||||
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
import { ComputedSavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
import { ComputedSavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||||
import { ComputedSkillSchema } from '/imports/api/properties/Skills.js';
|
import { ComputedSkillSchema } from '/imports/api/properties/Skills.js';
|
||||||
@@ -40,6 +41,7 @@ const propertySchemasIndex = {
|
|||||||
note: ComputedNoteSchema,
|
note: ComputedNoteSchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: ComputedSlotSchema,
|
propertySlot: ComputedSlotSchema,
|
||||||
|
reference: ReferenceSchema,
|
||||||
roll: ComputedRollSchema,
|
roll: ComputedRollSchema,
|
||||||
savingThrow: ComputedSavingThrowSchema,
|
savingThrow: ComputedSavingThrowSchema,
|
||||||
skill: ComputedSkillSchema,
|
skill: ComputedSkillSchema,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FeatureSchema } from '/imports/api/properties/Features.js';
|
|||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
|
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||||
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||||
import { SkillSchema } from '/imports/api/properties/Skills.js';
|
import { SkillSchema } from '/imports/api/properties/Skills.js';
|
||||||
@@ -40,6 +41,7 @@ const propertySchemasIndex = {
|
|||||||
note: NoteSchema,
|
note: NoteSchema,
|
||||||
proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: SlotSchema,
|
propertySlot: SlotSchema,
|
||||||
|
reference: ReferenceSchema,
|
||||||
roll: RollSchema,
|
roll: RollSchema,
|
||||||
savingThrow: SavingThrowSchema,
|
savingThrow: SavingThrowSchema,
|
||||||
skill: SkillSchema,
|
skill: SkillSchema,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function assertIdValid(userId){
|
|||||||
function assertdocExists(doc){
|
function assertdocExists(doc){
|
||||||
if (!doc){
|
if (!doc){
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'No such document exists');
|
'Permission denied: No such document exists');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ const PROPERTIES = Object.freeze({
|
|||||||
icon: '$vuetify.icons.roll',
|
icon: '$vuetify.icons.roll',
|
||||||
name: 'Roll'
|
name: 'Roll'
|
||||||
},
|
},
|
||||||
|
reference: {
|
||||||
|
icon: 'link',
|
||||||
|
name: 'Reference',
|
||||||
|
libraryOnly: true,
|
||||||
|
},
|
||||||
savingThrow: {
|
savingThrow: {
|
||||||
icon: '$vuetify.icons.saving_throw',
|
icon: '$vuetify.icons.saving_throw',
|
||||||
name: 'Saving throw'
|
name: 'Saving throw'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<selectable-property-dialog
|
<selectable-property-dialog
|
||||||
:value="forcedType || type"
|
:value="forcedType || type"
|
||||||
|
no-library-only-props
|
||||||
@input="e => type = e"
|
@input="e => type = e"
|
||||||
>
|
>
|
||||||
<creature-property-insert-form
|
<creature-property-insert-form
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import LibraryNodeCreationDialog from '/imports/ui/library/LibraryNodeCreationDi
|
|||||||
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
||||||
import MoveLibraryNodeDialog from '/imports/ui/library/MoveLibraryNodeDialog.vue'
|
import MoveLibraryNodeDialog from '/imports/ui/library/MoveLibraryNodeDialog.vue'
|
||||||
import SelectCreaturesDialog from '/imports/ui/tabletop/SelectCreaturesDialog.vue';
|
import SelectCreaturesDialog from '/imports/ui/tabletop/SelectCreaturesDialog.vue';
|
||||||
|
import SelectLibraryNodeDialog from '/imports/ui/library/SelectLibraryNodeDialog.vue';
|
||||||
import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
||||||
import SlotDetailsDialog from '/imports/ui/creature/slots/SlotDetailsDialog.vue';
|
import SlotDetailsDialog from '/imports/ui/creature/slots/SlotDetailsDialog.vue';
|
||||||
import SlotFillDialog from '/imports/ui/creature/slots/SlotFillDialog.vue';
|
import SlotFillDialog from '/imports/ui/creature/slots/SlotFillDialog.vue';
|
||||||
@@ -37,7 +38,8 @@ export default {
|
|||||||
LibraryNodeDialog,
|
LibraryNodeDialog,
|
||||||
MoveLibraryNodeDialog,
|
MoveLibraryNodeDialog,
|
||||||
SelectCreaturesDialog,
|
SelectCreaturesDialog,
|
||||||
ShareDialog,
|
SelectLibraryNodeDialog,
|
||||||
|
ShareDialog,
|
||||||
SlotDetailsDialog,
|
SlotDetailsDialog,
|
||||||
SlotFillDialog,
|
SlotFillDialog,
|
||||||
TierTooLowDialog,
|
TierTooLowDialog,
|
||||||
|
|||||||
47
app/imports/ui/library/SelectLibraryNodeDialog.vue
Normal file
47
app/imports/ui/library/SelectLibraryNodeDialog.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<v-toolbar-title slot="toolbar">
|
||||||
|
Select Library Property
|
||||||
|
</v-toolbar-title>
|
||||||
|
<library-and-node
|
||||||
|
slot="unwrapped-content"
|
||||||
|
style="height: 100%;"
|
||||||
|
selection
|
||||||
|
@selected="val => node = val"
|
||||||
|
/>
|
||||||
|
<template slot="actions">
|
||||||
|
<v-btn
|
||||||
|
text
|
||||||
|
color="primary"
|
||||||
|
@click="$store.dispatch('popDialogStack')"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</v-btn>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
text
|
||||||
|
color="primary"
|
||||||
|
@click="$store.dispatch('popDialogStack', node)"
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
import LibraryAndNode from '/imports/ui/library/LibraryAndNode.vue';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
LibraryAndNode,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
node: undefined,
|
||||||
|
};},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
69
app/imports/ui/properties/forms/ReferenceForm.vue
Normal file
69
app/imports/ui/properties/forms/ReferenceForm.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="folder-form layout justify-start wrap">
|
||||||
|
<v-text-field
|
||||||
|
label="Linked Property"
|
||||||
|
style="flex-basis: 300px;"
|
||||||
|
readonly
|
||||||
|
outlined
|
||||||
|
persistent-hint
|
||||||
|
:loading="linkLoading"
|
||||||
|
:value="
|
||||||
|
model.cache.node && model.cache.node.name ||
|
||||||
|
model.ref && model.ref.id
|
||||||
|
"
|
||||||
|
:hint="model.cache.library && model.cache.library.name"
|
||||||
|
:error-messages="model.cache.error || errors.ref"
|
||||||
|
prepend-inner-icon="link"
|
||||||
|
append-icon="refresh"
|
||||||
|
data-id="change-ref"
|
||||||
|
@click="changeReference"
|
||||||
|
@click:prepend-inner="changeReference"
|
||||||
|
@click:append="updateReferenceNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||||
|
import updateReferenceNode from '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [propertyFormMixin],
|
||||||
|
data(){return {
|
||||||
|
linkLoading: false,
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
changeReference(){
|
||||||
|
let that = this;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'select-library-node-dialog',
|
||||||
|
elementId: 'change-ref',
|
||||||
|
callback(node){
|
||||||
|
if (!node) return;
|
||||||
|
that.linkLoading = true;
|
||||||
|
that.$emit('change', {
|
||||||
|
path: ['ref'],
|
||||||
|
value: {
|
||||||
|
id: node._id,
|
||||||
|
collection: 'libraryNodes',
|
||||||
|
},
|
||||||
|
ack(){
|
||||||
|
that.linkLoading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateReferenceNode(){
|
||||||
|
if (!this.model._id) return;
|
||||||
|
this.linkLoading = true;
|
||||||
|
updateReferenceNode.call({_id: this.model._id}, () => {
|
||||||
|
this.linkLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,7 @@ import FolderForm from '/imports/ui/properties/forms/FolderForm.vue';
|
|||||||
import ItemForm from '/imports/ui/properties/forms/ItemForm.vue';
|
import ItemForm from '/imports/ui/properties/forms/ItemForm.vue';
|
||||||
import NoteForm from '/imports/ui/properties/forms/NoteForm.vue';
|
import NoteForm from '/imports/ui/properties/forms/NoteForm.vue';
|
||||||
import ProficiencyForm from '/imports/ui/properties/forms/ProficiencyForm.vue';
|
import ProficiencyForm from '/imports/ui/properties/forms/ProficiencyForm.vue';
|
||||||
|
import ReferenceForm from '/imports/ui/properties/forms/ReferenceForm.vue';
|
||||||
import RollForm from '/imports/ui/properties/forms/RollForm.vue';
|
import RollForm from '/imports/ui/properties/forms/RollForm.vue';
|
||||||
import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue';
|
import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue';
|
||||||
import SkillForm from '/imports/ui/properties/forms/SkillForm.vue';
|
import SkillForm from '/imports/ui/properties/forms/SkillForm.vue';
|
||||||
@@ -41,6 +42,7 @@ export default {
|
|||||||
note: NoteForm,
|
note: NoteForm,
|
||||||
proficiency: ProficiencyForm,
|
proficiency: ProficiencyForm,
|
||||||
propertySlot: SlotForm,
|
propertySlot: SlotForm,
|
||||||
|
reference: ReferenceForm,
|
||||||
roll: RollForm,
|
roll: RollForm,
|
||||||
savingThrow: SavingThrowForm,
|
savingThrow: SavingThrowForm,
|
||||||
skill: SkillForm,
|
skill: SkillForm,
|
||||||
|
|||||||
@@ -9,33 +9,35 @@
|
|||||||
wrap
|
wrap
|
||||||
fill-height
|
fill-height
|
||||||
>
|
>
|
||||||
<v-flex
|
<template v-for="(property, type) in PROPERTIES">
|
||||||
v-for="(property, type) in PROPERTIES"
|
<v-flex
|
||||||
:key="type"
|
v-if="!noLibraryOnlyProps || !property.libraryOnly"
|
||||||
sm4
|
:key="type"
|
||||||
xs6
|
sm4
|
||||||
>
|
xs6
|
||||||
<v-card
|
|
||||||
hover
|
|
||||||
style="height: 100%; overflow: hidden;"
|
|
||||||
@click="$emit('select', type)"
|
|
||||||
>
|
>
|
||||||
<div
|
<v-card
|
||||||
class="layout align-center justify-center"
|
hover
|
||||||
style="min-height: 70px;"
|
style="height: 100%; overflow: hidden;"
|
||||||
|
@click="$emit('select', type)"
|
||||||
>
|
>
|
||||||
<v-icon x-large>
|
<div
|
||||||
{{ property.icon }}
|
class="layout align-center justify-center"
|
||||||
</v-icon>
|
style="min-height: 70px;"
|
||||||
</div>
|
>
|
||||||
<h3
|
<v-icon x-large>
|
||||||
class="subtitle pb-3"
|
{{ property.icon }}
|
||||||
style="text-align: center;"
|
</v-icon>
|
||||||
>
|
</div>
|
||||||
{{ property.name }}
|
<h3
|
||||||
</h3>
|
class="subtitle pb-3"
|
||||||
</v-card>
|
style="text-align: center;"
|
||||||
</v-flex>
|
>
|
||||||
|
{{ property.name }}
|
||||||
|
</h3>
|
||||||
|
</v-card>
|
||||||
|
</v-flex>
|
||||||
|
</template>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-container>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,8 +45,12 @@
|
|||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data(){return {
|
props: {
|
||||||
|
noLibraryOnlyProps: Boolean,
|
||||||
|
},
|
||||||
|
data(){ return {
|
||||||
PROPERTIES,
|
PROPERTIES,
|
||||||
};},
|
};},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<property-selector
|
<property-selector
|
||||||
slot="unwrapped-content"
|
slot="unwrapped-content"
|
||||||
|
:no-library-only-props="noLibraryOnlyProps"
|
||||||
@select="type => $emit('input', type)"
|
@select="type => $emit('input', type)"
|
||||||
/>
|
/>
|
||||||
</dialog-base>
|
</dialog-base>
|
||||||
@@ -34,6 +35,7 @@ export default {
|
|||||||
PropertySelector,
|
PropertySelector,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
noLibraryOnlyProps: Boolean,
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="layout align-center justify-start">
|
||||||
|
<property-icon
|
||||||
|
v-if="!hideIcon"
|
||||||
|
class="mr-2"
|
||||||
|
:model="model"
|
||||||
|
:color="model.color"
|
||||||
|
:class="selected && 'primary--text'"
|
||||||
|
/>
|
||||||
|
<div class="text-no-wrap text-truncate">
|
||||||
|
{{ model.cache.node && model.cache.node.name || title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeViewMixin.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [treeNodeViewMixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -4,6 +4,7 @@ import ItemTreeNode from '/imports/ui/properties/treeNodeViews/ItemTreeNode.vue'
|
|||||||
import DamageTreeNode from '/imports/ui/properties/treeNodeViews/DamageTreeNode.vue';
|
import DamageTreeNode from '/imports/ui/properties/treeNodeViews/DamageTreeNode.vue';
|
||||||
import EffectTreeNode from '/imports/ui/properties/treeNodeViews/EffectTreeNode.vue';
|
import EffectTreeNode from '/imports/ui/properties/treeNodeViews/EffectTreeNode.vue';
|
||||||
import ClassLevelTreeNode from '/imports/ui/properties/treeNodeViews/ClassLevelTreeNode.vue';
|
import ClassLevelTreeNode from '/imports/ui/properties/treeNodeViews/ClassLevelTreeNode.vue';
|
||||||
|
import ReferenceTreeNode from '/imports/ui/properties/treeNodeViews/ReferenceTreeNode.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
default: DefaultTreeNode,
|
default: DefaultTreeNode,
|
||||||
@@ -12,4 +13,5 @@ export default {
|
|||||||
damage: DamageTreeNode,
|
damage: DamageTreeNode,
|
||||||
effect: EffectTreeNode,
|
effect: EffectTreeNode,
|
||||||
item: ItemTreeNode,
|
item: ItemTreeNode,
|
||||||
|
reference: ReferenceTreeNode,
|
||||||
}
|
}
|
||||||
|
|||||||
30
app/imports/ui/properties/viewers/ReferenceViewer.vue
Normal file
30
app/imports/ui/properties/viewers/ReferenceViewer.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="reference-viewer">
|
||||||
|
<property-field
|
||||||
|
v-if="model.cache.error"
|
||||||
|
name="Error"
|
||||||
|
:value="model.cache.error"
|
||||||
|
/>
|
||||||
|
<property-field
|
||||||
|
v-else-if="model.ref && model.ref.id"
|
||||||
|
name="Linked Property"
|
||||||
|
:value="model.cache.node && model.cache.node.name || model.ref.id"
|
||||||
|
/>
|
||||||
|
<property-field
|
||||||
|
v-if="model.cache.library && model.cache.library.name"
|
||||||
|
name="Library"
|
||||||
|
:value="model.cache.library.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [propertyViewerMixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,7 @@ import FolderViewer from '/imports/ui/properties/viewers/FolderViewer.vue';
|
|||||||
import ItemViewer from '/imports/ui/properties/viewers/ItemViewer.vue';
|
import ItemViewer from '/imports/ui/properties/viewers/ItemViewer.vue';
|
||||||
import NoteViewer from '/imports/ui/properties/viewers/NoteViewer.vue';
|
import NoteViewer from '/imports/ui/properties/viewers/NoteViewer.vue';
|
||||||
import ProficiencyViewer from '/imports/ui/properties/viewers/ProficiencyViewer.vue';
|
import ProficiencyViewer from '/imports/ui/properties/viewers/ProficiencyViewer.vue';
|
||||||
|
import ReferenceViewer from '/imports/ui/properties/viewers/ReferenceViewer.vue';
|
||||||
import RollViewer from '/imports/ui/properties/viewers/RollViewer.vue';
|
import RollViewer from '/imports/ui/properties/viewers/RollViewer.vue';
|
||||||
import SkillViewer from '/imports/ui/properties/viewers/SkillViewer.vue';
|
import SkillViewer from '/imports/ui/properties/viewers/SkillViewer.vue';
|
||||||
import SavingThrowViewer from '/imports/ui/properties/viewers/SavingThrowViewer.vue';
|
import SavingThrowViewer from '/imports/ui/properties/viewers/SavingThrowViewer.vue';
|
||||||
@@ -42,6 +43,7 @@ export default {
|
|||||||
proficiency: ProficiencyViewer,
|
proficiency: ProficiencyViewer,
|
||||||
propertySlot: SlotViewer,
|
propertySlot: SlotViewer,
|
||||||
roll: RollViewer,
|
roll: RollViewer,
|
||||||
|
reference: ReferenceViewer,
|
||||||
savingThrow: SavingThrowViewer,
|
savingThrow: SavingThrowViewer,
|
||||||
slotFiller: SlotFillerViewer,
|
slotFiller: SlotFillerViewer,
|
||||||
skill: SkillViewer,
|
skill: SkillViewer,
|
||||||
|
|||||||
Reference in New Issue
Block a user