Merge branch 'version-2-dev' into version-2
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { some, intersection, difference, remove, includes } from 'lodash';
|
import { some, intersection, difference, remove, includes } from 'lodash';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
||||||
import logErrors from './shared/logErrors.js';
|
import logErrors from './shared/logErrors.js';
|
||||||
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
|
||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
} from '/imports/api/engine/loadCreatures.js';
|
} from '/imports/api/engine/loadCreatures.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
export default function applyDamage(node, actionContext){
|
export default function applyDamage(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
const applyChildren = function(){
|
const applyChildren = function () {
|
||||||
applyNodeTriggers(node, 'after', actionContext);
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
node.children.forEach(child => applyProperty(child, actionContext));
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
};
|
};
|
||||||
@@ -28,10 +28,10 @@ export default function applyDamage(node, actionContext){
|
|||||||
// Determine if the hit is critical
|
// Determine if the hit is critical
|
||||||
let criticalHit = scope['$criticalHit']?.value &&
|
let criticalHit = scope['$criticalHit']?.value &&
|
||||||
prop.damageType !== 'healing' // Can't critically heal
|
prop.damageType !== 'healing' // Can't critically heal
|
||||||
;
|
;
|
||||||
// Double the damage rolls if the hit is critical
|
// Double the damage rolls if the hit is critical
|
||||||
let context = new Context({
|
let context = new Context({
|
||||||
options: {doubleRolls: criticalHit},
|
options: { doubleRolls: criticalHit },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gather all the lines we need to log into an array
|
// Gather all the lines we need to log into an array
|
||||||
@@ -40,8 +40,8 @@ export default function applyDamage(node, actionContext){
|
|||||||
|
|
||||||
// roll the dice only and store that string
|
// roll the dice only and store that string
|
||||||
applyEffectsToCalculationParseNode(prop.amount, actionContext.log);
|
applyEffectsToCalculationParseNode(prop.amount, actionContext.log);
|
||||||
const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context);
|
const { result: rolled } = resolve('roll', prop.amount.parseNode, scope, context);
|
||||||
if (rolled.parseType !== 'constant'){
|
if (rolled.parseType !== 'constant') {
|
||||||
logValue.push(toString(rolled));
|
logValue.push(toString(rolled));
|
||||||
}
|
}
|
||||||
logErrors(context.errors, actionContext);
|
logErrors(context.errors, actionContext);
|
||||||
@@ -50,13 +50,13 @@ export default function applyDamage(node, actionContext){
|
|||||||
context.errors = [];
|
context.errors = [];
|
||||||
|
|
||||||
// Resolve the roll to a final value
|
// Resolve the roll to a final value
|
||||||
const {result: reduced} = resolve('reduce', rolled, scope, context);
|
const { result: reduced } = resolve('reduce', rolled, scope, context);
|
||||||
logErrors(context.errors, actionContext);
|
logErrors(context.errors, actionContext);
|
||||||
|
|
||||||
// Store the result
|
// Store the result
|
||||||
if (reduced.parseType === 'constant'){
|
if (reduced.parseType === 'constant') {
|
||||||
prop.amount.value = reduced.value;
|
prop.amount.value = reduced.value;
|
||||||
} else if (reduced.parseType === 'error'){
|
} else if (reduced.parseType === 'error') {
|
||||||
prop.amount.value = null;
|
prop.amount.value = null;
|
||||||
} else {
|
} else {
|
||||||
prop.amount.value = toString(reduced);
|
prop.amount.value = toString(reduced);
|
||||||
@@ -64,7 +64,7 @@ export default function applyDamage(node, actionContext){
|
|||||||
let damage = +reduced.value;
|
let damage = +reduced.value;
|
||||||
|
|
||||||
// If we didn't end up with a constant of finite amount, give up
|
// If we didn't end up with a constant of finite amount, give up
|
||||||
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)){
|
if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)) {
|
||||||
return applyChildren();
|
return applyChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export default function applyDamage(node, actionContext){
|
|||||||
// Memoise the damage suffix for the log
|
// Memoise the damage suffix for the log
|
||||||
let suffix = (criticalHit ? ' critical ' : ' ') +
|
let suffix = (criticalHit ? ' critical ' : ' ') +
|
||||||
prop.damageType +
|
prop.damageType +
|
||||||
(prop.damageType !== 'healing' ? ' damage ': '');
|
(prop.damageType !== 'healing' ? ' damage ' : '');
|
||||||
|
|
||||||
if (damageTargets && damageTargets.length) {
|
if (damageTargets && damageTargets.length) {
|
||||||
// Iterate through all the targets
|
// Iterate through all the targets
|
||||||
@@ -107,7 +107,7 @@ export default function applyDamage(node, actionContext){
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Log the damage done
|
// Log the damage done
|
||||||
if (target._id === actionContext.creature._id){
|
if (target._id === actionContext.creature._id) {
|
||||||
// Target is same as self, log damage as such
|
// Target is same as self, log damage as such
|
||||||
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
||||||
} else {
|
} else {
|
||||||
@@ -136,33 +136,33 @@ export default function applyDamage(node, actionContext){
|
|||||||
return applyChildren();
|
return applyChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
function applyDamageMultipliers({ target, damage, damageProp, logValue }) {
|
||||||
const damageType = damageProp?.damageType;
|
const damageType = damageProp?.damageType;
|
||||||
if (!damageType) return damage;
|
if (!damageType) return damage;
|
||||||
|
|
||||||
const multiplier = target?.variables?.[damageType];
|
const multiplier = target?.variables?.[damageType];
|
||||||
if (!multiplier) return damage;
|
if (!multiplier) return damage;
|
||||||
|
|
||||||
const damageTypeText = damageType == 'healing' ? 'healing': `${damageType} damage`;
|
const damageTypeText = damageType == 'healing' ? 'healing' : `${damageType} damage`;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
multiplier.immunity &&
|
multiplier.immunity &&
|
||||||
some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity'))
|
some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity'))
|
||||||
){
|
) {
|
||||||
logValue.push(`Immune to ${damageTypeText}`);
|
logValue.push(`Immune to ${damageTypeText}`);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
multiplier.resistance &&
|
multiplier.resistance &&
|
||||||
some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance'))
|
some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance'))
|
||||||
){
|
) {
|
||||||
logValue.push(`Resistant to ${damageTypeText}`);
|
logValue.push(`Resistant to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage / 2);
|
damage = Math.floor(damage / 2);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
multiplier.vulnerability &&
|
multiplier.vulnerability &&
|
||||||
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability'))
|
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability'))
|
||||||
){
|
) {
|
||||||
logValue.push(`Vulnerable to ${damageTypeText}`);
|
logValue.push(`Vulnerable to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage * 2);
|
damage = Math.floor(damage * 2);
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
function multiplierAppliesTo(damageProp, multiplierType){
|
function multiplierAppliesTo(damageProp, multiplierType) {
|
||||||
return multiplier => {
|
return multiplier => {
|
||||||
// Apply the default 'ignore x' tags
|
// Apply the default 'ignore x' tags
|
||||||
if (includes(damageProp.tags, `ignore ${multiplierType}`)) return false;
|
if (includes(damageProp.tags, `ignore ${multiplierType}`)) return false;
|
||||||
@@ -187,7 +187,7 @@ function multiplierAppliesTo(damageProp, multiplierType){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function dealDamage({target, damageType, amount, actionContext}){
|
function dealDamage({ target, damageType, amount, actionContext }) {
|
||||||
// Get all the health bars and do damage to them
|
// Get all the health bars and do damage to them
|
||||||
let healthBars = getPropertiesOfType(target._id, 'attribute');
|
let healthBars = getPropertiesOfType(target._id, 'attribute');
|
||||||
|
|
||||||
@@ -239,6 +239,14 @@ function dealDamage({target, damageType, amount, actionContext}){
|
|||||||
actionContext
|
actionContext
|
||||||
});
|
});
|
||||||
damageLeft -= damageAdded;
|
damageLeft -= damageAdded;
|
||||||
|
// Prevent overflow
|
||||||
|
if (
|
||||||
|
damageType === 'healing' ?
|
||||||
|
healthBar.healthBarNoHealingOverflow :
|
||||||
|
healthBar.healthBarNoDamageOverflow
|
||||||
|
) {
|
||||||
|
damageLeft = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return totalDamage;
|
return totalDamage;
|
||||||
}
|
}
|
||||||
|
|||||||
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||||
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
|
import {
|
||||||
|
assertDocCopyPermission,
|
||||||
|
assertDocEditPermission
|
||||||
|
} from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import {
|
||||||
|
setLineageOfDocs,
|
||||||
|
renewDocIds
|
||||||
|
} from '/imports/api/parenting/parenting.js';
|
||||||
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
|
var snackbar;
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar = require(
|
||||||
|
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
||||||
|
).snackbar
|
||||||
|
}
|
||||||
|
|
||||||
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
|
const copyLibraryNodeTo = new ValidatedMethod({
|
||||||
|
name: 'libraryNodes.copyTo',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: RefSchema,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 1,
|
||||||
|
timeInterval: 10000,
|
||||||
|
},
|
||||||
|
run({ _id, parent }) {
|
||||||
|
if (parent.collection !== 'libraryNodes' && parent.collection !== 'libraries') {
|
||||||
|
throw new Meteor.Error('Invalid destination',
|
||||||
|
'Library documents can only be copied to destinations inside other libraries'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const libraryNode = LibraryNodes.findOne(_id);
|
||||||
|
const parentDoc = fetchDocByRef(parent);
|
||||||
|
assertDocCopyPermission(libraryNode, this.userId);
|
||||||
|
assertDocEditPermission(parentDoc, this.userId);
|
||||||
|
|
||||||
|
let decendants = LibraryNodes.find({
|
||||||
|
'ancestors.id': _id,
|
||||||
|
removed: { $ne: true },
|
||||||
|
}, {
|
||||||
|
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||||
|
sort: { order: 1 },
|
||||||
|
}).fetch();
|
||||||
|
|
||||||
|
if (decendants.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||||
|
decendants.pop();
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar({
|
||||||
|
text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
LibraryNodes.batchInsert(nodes);
|
||||||
|
|
||||||
|
// Tree structure changed by inserts, reorder the tree
|
||||||
|
reorderDocs({
|
||||||
|
collection: LibraryNodes,
|
||||||
|
ancestorId: parent.collection === 'libraries' ? parent.id : parentDoc.ancestors[0].id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default copyLibraryNodeTo;
|
||||||
@@ -16,7 +16,7 @@ if (Meteor.isClient) {
|
|||||||
).snackbar
|
).snackbar
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUPLICATE_CHILDREN_LIMIT = 50;
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
const duplicateLibraryNode = new ValidatedMethod({
|
const duplicateLibraryNode = new ValidatedMethod({
|
||||||
name: 'libraryNodes.duplicate',
|
name: 'libraryNodes.duplicate',
|
||||||
@@ -28,7 +28,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 1,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id }) {
|
run({ _id }) {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
import '/imports/api/library/methods/copyLibraryNodeTo.js';
|
||||||
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
||||||
import '/imports/api/library/methods/updateReferenceNode.js';
|
import '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Control how the health bar handles overflow
|
||||||
|
healthBarNoDamageOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarNoHealingOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Control when the health bar takes damage or healing
|
||||||
healthBarDamageOrder: {
|
healthBarDamageOrder: {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -107,6 +117,14 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
hideWhenTotalZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
hideWhenValueZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// Automatically zero the adjustment on these conditions
|
// Automatically zero the adjustment on these conditions
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ let SharingSchema = new SimpleSchema({
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
|
readersCanCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SharingSchema;
|
export default SharingSchema;
|
||||||
|
|||||||
@@ -27,6 +27,26 @@ const setPublic = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setReadersCanCopy = new ValidatedMethod({
|
||||||
|
name: 'sharing.setReadersCanCopy',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
docRef: RefSchema,
|
||||||
|
readersCanCopy: { type: Boolean },
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ docRef, readersCanCopy }) {
|
||||||
|
let doc = fetchDocByRef(docRef);
|
||||||
|
assertOwnership(doc, this.userId);
|
||||||
|
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||||
|
$set: { readersCanCopy },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const updateUserSharePermissions = new ValidatedMethod({
|
const updateUserSharePermissions = new ValidatedMethod({
|
||||||
name: 'sharing.updateUserSharePermissions',
|
name: 'sharing.updateUserSharePermissions',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
@@ -129,4 +149,4 @@ const transferOwnership = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
export { setPublic, setReadersCanCopy, updateUserSharePermissions, transferOwnership };
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import { _ } from 'meteor/underscore';
|
import { _ } from 'meteor/underscore';
|
||||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
function assertIdValid(userId){
|
function assertIdValid(userId) {
|
||||||
if (!userId || typeof userId !== 'string'){
|
if (!userId || typeof userId !== 'string') {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'No user ID. Are you logged in?');
|
'No user ID. Are you logged in?');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertdocExists(doc){
|
function assertdocExists(doc) {
|
||||||
if (!doc){
|
if (!doc) {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'Permission denied: No such document exists');
|
'Permission denied: No such document exists');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertOwnership(doc, userId){
|
export function assertOwnership(doc, userId) {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.owner === userId ){
|
|
||||||
|
if (doc.owner === userId) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
@@ -37,13 +38,12 @@ export function assertEditPermission(doc, userId) {
|
|||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'services.patreon': 1,
|
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Admin override
|
// Admin override
|
||||||
if (user.roles && user.roles.includes('admin')){
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export function assertEditPermission(doc, userId) {
|
|||||||
if (
|
if (
|
||||||
doc.owner === userId ||
|
doc.owner === userId ||
|
||||||
_.contains(doc.writers, userId)
|
_.contains(doc.writers, userId)
|
||||||
){
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Meteor.Error('Edit permission denied',
|
throw new Meteor.Error('Edit permission denied',
|
||||||
@@ -59,9 +59,46 @@ export function assertEditPermission(doc, userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoot(doc){
|
/**
|
||||||
|
* Assert that the user can edit the root document which manages its own sharing
|
||||||
|
* permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertCopyPermission(doc, userId) {
|
||||||
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]){
|
const user = Meteor.users.findOne(userId, {
|
||||||
|
fields: {
|
||||||
|
'roles': 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin override
|
||||||
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the user is authorized for this specific document
|
||||||
|
if (
|
||||||
|
doc.owner === userId ||
|
||||||
|
_.contains(doc.writers, userId)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
(_.contains(doc.readers, userId) || doc.public) &&
|
||||||
|
doc.readersCanCopy
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Meteor.Error('Copy permission denied',
|
||||||
|
'You do not have permission to copy this document');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoot(doc) {
|
||||||
|
assertdocExists(doc);
|
||||||
|
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
||||||
return fetchDocByRef(doc.ancestors[0]);
|
return fetchDocByRef(doc.ancestors[0]);
|
||||||
} else {
|
} else {
|
||||||
return doc;
|
return doc;
|
||||||
@@ -74,11 +111,22 @@ function getRoot(doc){
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertDocEditPermission(doc, userId){
|
export function assertDocEditPermission(doc, userId) {
|
||||||
let root = getRoot(doc);
|
let root = getRoot(doc);
|
||||||
assertEditPermission(root, userId);
|
assertEditPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can copy a descendant document whose root ancestor
|
||||||
|
* implements sharing permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertDocCopyPermission(doc, userId) {
|
||||||
|
let root = getRoot(doc);
|
||||||
|
assertCopyPermission(root, userId);
|
||||||
|
}
|
||||||
|
|
||||||
export function assertViewPermission(doc, userId) {
|
export function assertViewPermission(doc, userId) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.public) return true;
|
if (doc.public) return true;
|
||||||
@@ -88,17 +136,17 @@ export function assertViewPermission(doc, userId) {
|
|||||||
doc.owner === userId ||
|
doc.owner === userId ||
|
||||||
_.contains(doc.readers, userId) ||
|
_.contains(doc.readers, userId) ||
|
||||||
_.contains(doc.writers, userId)
|
_.contains(doc.writers, userId)
|
||||||
){
|
) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Admin override
|
// Admin override
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (user.roles && user.roles.includes('admin')){
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,20 +161,20 @@ export function assertViewPermission(doc, userId) {
|
|||||||
*
|
*
|
||||||
* Warning: the doc and userId must be set by a trusted source
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
*/
|
*/
|
||||||
export function assertDocViewPermission(doc, userId){
|
export function assertDocViewPermission(doc, userId) {
|
||||||
let root = getRoot(doc);
|
let root = getRoot(doc);
|
||||||
assertViewPermission(root, userId);
|
assertViewPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertAdmin(userId){
|
export function assertAdmin(userId) {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
let user = Meteor.users.findOne(userId, {fields: {roles: 1}});
|
let user = Meteor.users.findOne(userId, { fields: { roles: 1 } });
|
||||||
if (!user){
|
if (!user) {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'UserId does not match any existing user');
|
'UserId does not match any existing user');
|
||||||
}
|
}
|
||||||
let isAdmin = user.roles && user.roles.includes('admin')
|
let isAdmin = user.roles && user.roles.includes('admin')
|
||||||
if (!isAdmin){
|
if (!isAdmin) {
|
||||||
throw new Meteor.Error('Permission denied',
|
throw new Meteor.Error('Permission denied',
|
||||||
'User does not have the admin role');
|
'User does not have the admin role');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
:outlined="!!label"
|
:outlined="!!label"
|
||||||
:icon="!label"
|
:icon="!label"
|
||||||
:min-width="label && 108"
|
:min-width="label && 108"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -124,6 +125,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
//hex string
|
//hex string
|
||||||
value: {
|
value: {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.duplicate"
|
v-if="$listeners && $listeners.duplicate"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('duplicate')"
|
@click="$emit('duplicate')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -80,8 +81,23 @@
|
|||||||
<v-icon>mdi-content-copy</v-icon>
|
<v-icon>mdi-content-copy</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="$listeners && $listeners.copy"
|
||||||
|
:disabled="context.copyPermission === false"
|
||||||
|
@click="$emit('copy')"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
Copy To
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-icon>mdi-content-duplicate</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.move"
|
v-if="$listeners && $listeners.move"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('move')"
|
@click="$emit('move')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -95,6 +111,7 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.remove"
|
v-if="$listeners && $listeners.remove"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('remove')"
|
@click="$emit('remove')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -157,6 +174,9 @@ export default {
|
|||||||
PropertyIcon,
|
PropertyIcon,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
},
|
},
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -365,6 +365,10 @@ const getProperties = function (creature, filter, options = {
|
|||||||
filter.removed = { $ne: true };
|
filter.removed = { $ne: true };
|
||||||
filter.inactive = { $ne: true };
|
filter.inactive = { $ne: true };
|
||||||
filter.overridden = { $ne: true };
|
filter.overridden = { $ne: true };
|
||||||
|
filter.$nor = [
|
||||||
|
{ hideWhenTotalZero: true, total: 0 },
|
||||||
|
{ hideWhenValueZero: true, value: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
return CreatureProperties.find(filter, options);
|
return CreatureProperties.find(filter, options);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
:embedded="embedded"
|
:embedded="embedded"
|
||||||
@duplicate="duplicate"
|
@duplicate="duplicate"
|
||||||
@move="move"
|
@move="move"
|
||||||
|
@copy="copy"
|
||||||
@remove="remove"
|
@remove="remove"
|
||||||
@toggle-editing="editing = !editing"
|
@toggle-editing="editing = !editing"
|
||||||
@color-changed="value => change({path: ['color'], value})"
|
@color-changed="value => change({path: ['color'], value})"
|
||||||
@@ -95,10 +96,13 @@
|
|||||||
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
|
import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js';
|
||||||
import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js';
|
import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import {
|
||||||
|
assertDocEditPermission, assertDocCopyPermission
|
||||||
|
} from '/imports/api/sharing/sharingPermissions.js';
|
||||||
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
||||||
|
import copyLibraryNodeTo from '/imports/api/library/methods/copyLibraryNodeTo.js';
|
||||||
|
|
||||||
let formIndex = {};
|
let formIndex = {};
|
||||||
for (let key in propertyFormIndex){
|
for (let key in propertyFormIndex){
|
||||||
@@ -126,7 +130,7 @@
|
|||||||
},
|
},
|
||||||
reactiveProvide: {
|
reactiveProvide: {
|
||||||
name: 'context',
|
name: 'context',
|
||||||
include: ['editPermission', 'isLibraryForm'],
|
include: ['editPermission', 'copyPermission', 'isLibraryForm'],
|
||||||
},
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
editing: !!this.startInEditTab,
|
editing: !!this.startInEditTab,
|
||||||
@@ -162,6 +166,14 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
copyPermission(){
|
||||||
|
try {
|
||||||
|
assertDocCopyPermission(this.model, Meteor.userId());
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyName,
|
getPropertyName,
|
||||||
@@ -200,6 +212,37 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
copy(){
|
||||||
|
const thisId = this._id;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'move-library-node-dialog',
|
||||||
|
elementId: 'property-toolbar-menu-button',
|
||||||
|
data: {
|
||||||
|
action: 'Copy',
|
||||||
|
},
|
||||||
|
callback(parentId){
|
||||||
|
if (!parentId) return;
|
||||||
|
copyLibraryNodeTo.call({
|
||||||
|
_id: thisId,
|
||||||
|
parent: {
|
||||||
|
collection: 'libraryNodes',
|
||||||
|
id: parentId
|
||||||
|
},
|
||||||
|
}, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
snackbar({
|
||||||
|
text: error.reason || error.message || error.toString(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
snackbar({
|
||||||
|
text: 'Copied successfully',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
change({path, value, ack}){
|
change({path, value, ack}){
|
||||||
updateLibraryNode.call({_id: this.currentId, path, value}, (error) =>{
|
updateLibraryNode.call({_id: this.currentId, path, value}, (error) =>{
|
||||||
if (ack){
|
if (ack){
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
@click="$store.dispatch('popDialogStack', node._id)"
|
@click="$store.dispatch('popDialogStack', node._id)"
|
||||||
>
|
>
|
||||||
Move
|
{{ action || 'Move' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</dialog-base>
|
</dialog-base>
|
||||||
@@ -30,6 +30,12 @@ export default {
|
|||||||
DialogBase,
|
DialogBase,
|
||||||
LibraryAndNode,
|
LibraryAndNode,
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
node: undefined,
|
node: undefined,
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ export default {
|
|||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
inactive: { $ne: true },
|
inactive: { $ne: true },
|
||||||
overridden: { $ne: true },
|
overridden: { $ne: true },
|
||||||
|
$nor: [
|
||||||
|
{ hideWhenTotalZero: true, total: 0 },
|
||||||
|
{ hideWhenValueZero: true, value: 0 },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
if (creature.settings.hideUnusedStats) {
|
if (creature.settings.hideUnusedStats) {
|
||||||
filter.hide = { $ne: true };
|
filter.hide = { $ne: true };
|
||||||
|
|||||||
@@ -106,10 +106,17 @@
|
|||||||
/>
|
/>
|
||||||
<smart-switch
|
<smart-switch
|
||||||
label="Ignore damage"
|
label="Ignore damage"
|
||||||
|
class="mr-4"
|
||||||
:value="model.healthBarNoDamage"
|
:value="model.healthBarNoDamage"
|
||||||
:error-messages="errors.healthBarNoDamage"
|
:error-messages="errors.healthBarNoDamage"
|
||||||
@change="change('healthBarNoDamage', ...arguments)"
|
@change="change('healthBarNoDamage', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
<smart-switch
|
||||||
|
label="Prevent damage overflow"
|
||||||
|
:value="model.healthBarNoDamageOverflow"
|
||||||
|
:error-messages="errors.healthBarNoDamageOverflow"
|
||||||
|
@change="change('healthBarNoDamageOverflow', ...arguments)"
|
||||||
|
/>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
<v-layout wrap>
|
<v-layout wrap>
|
||||||
<text-field
|
<text-field
|
||||||
@@ -125,14 +132,20 @@
|
|||||||
/>
|
/>
|
||||||
<smart-switch
|
<smart-switch
|
||||||
label="Ignore healing"
|
label="Ignore healing"
|
||||||
|
class="mr-4"
|
||||||
:value="model.healthBarNoHealing"
|
:value="model.healthBarNoHealing"
|
||||||
:error-messages="errors.healthBarNoHealing"
|
:error-messages="errors.healthBarNoHealing"
|
||||||
@change="change('healthBarNoHealing', ...arguments)"
|
@change="change('healthBarNoHealing', ...arguments)"
|
||||||
/>
|
/>
|
||||||
|
<smart-switch
|
||||||
|
label="Prevent healing overflow"
|
||||||
|
:value="model.healthBarNoHealingOverflow"
|
||||||
|
:error-messages="errors.healthBarNoHealingOverflow"
|
||||||
|
@change="change('healthBarNoHealingOverflow', ...arguments)"
|
||||||
|
/>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</form-section>
|
</form-section>
|
||||||
</v-expand-transition>
|
</v-expand-transition>
|
||||||
|
|
||||||
<form-section
|
<form-section
|
||||||
v-if="$slots.children"
|
v-if="$slots.children"
|
||||||
name="Children"
|
name="Children"
|
||||||
@@ -151,26 +164,74 @@
|
|||||||
@change="change('tags', ...arguments)"
|
@change="change('tags', ...arguments)"
|
||||||
/>
|
/>
|
||||||
<div class="layout column align-center">
|
<div class="layout column align-center">
|
||||||
<smart-switch
|
<v-row dense>
|
||||||
v-if="model.attributeType !== 'hitDice'"
|
<v-col
|
||||||
label="Allow decimal values"
|
cols="12"
|
||||||
class="no-flex"
|
sm="6"
|
||||||
:value="model.decimal"
|
md="4"
|
||||||
:error-messages="errors.decimal"
|
>
|
||||||
@change="change('decimal', ...arguments)"
|
<smart-switch
|
||||||
/>
|
v-if="model.attributeType !== 'hitDice'"
|
||||||
<smart-switch
|
label="Allow decimal values"
|
||||||
label="Can be damaged into negative values"
|
class="mx-4"
|
||||||
:value="model.ignoreLowerLimit"
|
:value="model.decimal"
|
||||||
:error-messages="errors.ignoreLowerLimit"
|
:error-messages="errors.decimal"
|
||||||
@change="change('ignoreLowerLimit', ...arguments)"
|
@change="change('decimal', ...arguments)"
|
||||||
/>
|
/>
|
||||||
<smart-switch
|
</v-col>
|
||||||
label="Can be incremented above total"
|
<v-col
|
||||||
:value="model.ignoreUpperLimit"
|
cols="12"
|
||||||
:error-messages="errors.ignoreUpperLimit"
|
sm="6"
|
||||||
@change="change('ignoreUpperLimit', ...arguments)"
|
md="4"
|
||||||
/>
|
>
|
||||||
|
<smart-switch
|
||||||
|
label="Can be damaged into negative values"
|
||||||
|
class="mx-4"
|
||||||
|
:value="model.ignoreLowerLimit"
|
||||||
|
:error-messages="errors.ignoreLowerLimit"
|
||||||
|
@change="change('ignoreLowerLimit', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<smart-switch
|
||||||
|
label="Can be incremented above total"
|
||||||
|
class="mx-4"
|
||||||
|
:value="model.ignoreUpperLimit"
|
||||||
|
:error-messages="errors.ignoreUpperLimit"
|
||||||
|
@change="change('ignoreUpperLimit', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<smart-switch
|
||||||
|
label="Hide when total is zero"
|
||||||
|
class="mx-4"
|
||||||
|
:value="model.hideWhenTotalZero"
|
||||||
|
:error-messages="errors.hideWhenTotalZero"
|
||||||
|
@change="change('hideWhenTotalZero', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
|
<smart-switch
|
||||||
|
label="Hide when value is zero"
|
||||||
|
class="mx-4"
|
||||||
|
:value="model.hideWhenValueZero"
|
||||||
|
:error-messages="errors.hideWhenValueZero"
|
||||||
|
@change="change('hideWhenValueZero', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
<div
|
<div
|
||||||
class="layout justify-center"
|
class="layout justify-center"
|
||||||
style="align-self: stretch;"
|
style="align-self: stretch;"
|
||||||
|
|||||||
@@ -13,6 +13,16 @@
|
|||||||
:value="!!model.public + ''"
|
:value="!!model.public + ''"
|
||||||
@change="(value, ack) => setSheetPublic({value, ack})"
|
@change="(value, ack) => setSheetPublic({value, ack})"
|
||||||
/>
|
/>
|
||||||
|
<smart-select
|
||||||
|
v-if="docRef.collection === 'libraries'"
|
||||||
|
label="Who can copy from this library"
|
||||||
|
:items="[
|
||||||
|
{text: 'Only people with edit permission', value: 'false'},
|
||||||
|
{text: 'Anyone with read permission', value: 'true'}
|
||||||
|
]"
|
||||||
|
:value="!!model.readersCanCopy + ''"
|
||||||
|
@change="(value, ack) => setReadersCanCopy({value, ack})"
|
||||||
|
/>
|
||||||
<text-field
|
<text-field
|
||||||
v-if="model.public && docRef.collection === 'libraries'"
|
v-if="model.public && docRef.collection === 'libraries'"
|
||||||
readonly
|
readonly
|
||||||
@@ -30,6 +40,7 @@
|
|||||||
@change="(value, ack) => getUser({value, ack})"
|
@change="(value, ack) => getUser({value, ack})"
|
||||||
/>
|
/>
|
||||||
<v-btn
|
<v-btn
|
||||||
|
class="ml-2 mt-2"
|
||||||
:disabled="userFoundState !== 'found'"
|
:disabled="userFoundState !== 'found'"
|
||||||
@click="updateSharing(userId, 'reader')"
|
@click="updateSharing(userId, 'reader')"
|
||||||
>
|
>
|
||||||
@@ -126,6 +137,7 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import {
|
import {
|
||||||
setPublic,
|
setPublic,
|
||||||
|
setReadersCanCopy,
|
||||||
updateUserSharePermissions
|
updateUserSharePermissions
|
||||||
} from '/imports/api/sharing/sharing.js';
|
} from '/imports/api/sharing/sharing.js';
|
||||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
@@ -157,6 +169,14 @@ export default {
|
|||||||
ack(error && error.reason || error);
|
ack(error && error.reason || error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setReadersCanCopy({ value, ack }) {
|
||||||
|
setReadersCanCopy.call({
|
||||||
|
docRef: this.docRef,
|
||||||
|
readersCanCopy: value === 'true',
|
||||||
|
}, (error) => {
|
||||||
|
ack(error && error.reason || error);
|
||||||
|
});
|
||||||
|
},
|
||||||
getUser({ value, ack }) {
|
getUser({ value, ack }) {
|
||||||
this.userSearched = value;
|
this.userSearched = value;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dicecloud",
|
"name": "dicecloud",
|
||||||
"version": "2.0.42",
|
"version": "2.0.43",
|
||||||
"description": "Unofficial Online Realtime D&D 5e App",
|
"description": "Unofficial Online Realtime D&D 5e App",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -124,4 +124,4 @@
|
|||||||
"vuetify/no-deprecated-classes": "error"
|
"vuetify/no-deprecated-classes": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user