diff --git a/app/.gitignore b/app/.gitignore index da83c4f3..e4ba5ce6 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -3,6 +3,7 @@ .demeteorized .cache .vscode +fileStorage settings.json public/components public/_imports.html diff --git a/app/.meteor/packages b/app/.meteor/packages index dc62173e..ff36c66b 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -4,27 +4,27 @@ # but you can also edit it by hand. accounts-password@2.3.1 -random@1.2.0 -underscore@1.0.10 +random@1.2.1 +underscore@1.0.11 dburles:mongo-collection-instances accounts-google@1.4.0 -email@2.2.1 +email@2.2.2 meteor-base@1.5.1 mobile-experience@1.1.0 -mongo@1.16.0 -session@1.2.0 -tracker@1.2.0 +mongo@1.16.1 +session@1.2.1 +tracker@1.2.1 logging@1.3.1 reload@1.3.1 -ejson@1.1.2 -check@1.3.1 +ejson@1.1.3 +check@1.3.2 standard-minifier-js@2.8.1 shell-server@0.5.0 -ecmascript@0.16.2 +ecmascript@0.16.3 es5-shim@4.8.0 -service-configuration@1.3.0 +service-configuration@1.3.1 dynamic-import@0.7.2 -ddp-rate-limiter@1.1.0 +ddp-rate-limiter@1.1.1 rate-limit@1.0.9 mdg:validated-method static-html@1.3.2 @@ -37,7 +37,6 @@ simple:rest simple:rest-method-mixin mikowals:batch-insert peerlibrary:subscription-data -seba:minifiers-autoprefixer zer0th:meteor-vuetify-loader akryum:vue-component akryum:vue-router2 @@ -49,3 +48,4 @@ simple:rest-json-error-handler littledata:synced-cron mdg:meteor-apm-agent typescript@4.5.4 +seba:minifiers-autoprefixer diff --git a/app/.meteor/release b/app/.meteor/release index 1d2a6d0f..42890181 100644 --- a/app/.meteor/release +++ b/app/.meteor/release @@ -1 +1 @@ -METEOR@2.8.0 +METEOR@2.8.1 diff --git a/app/.meteor/versions b/app/.meteor/versions index b9f87f06..3f632f0b 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -1,4 +1,4 @@ -accounts-base@2.2.4 +accounts-base@2.2.5 accounts-google@1.4.0 accounts-oauth@1.4.1 accounts-password@2.3.1 @@ -22,26 +22,26 @@ bozhao:link-accounts@2.6.1 caching-compiler@1.2.2 caching-html-compiler@1.2.1 callback-hook@1.4.0 -check@1.3.1 +check@1.3.2 coffeescript@2.4.1 coffeescript-compiler@2.4.1 dburles:mongo-collection-instances@0.3.6 -ddp@1.4.0 -ddp-client@2.6.0 +ddp@1.4.1 +ddp-client@2.6.1 ddp-common@1.4.0 -ddp-rate-limiter@1.1.0 +ddp-rate-limiter@1.1.1 ddp-server@2.6.0 -diff-sequence@1.1.1 +diff-sequence@1.1.2 dynamic-import@0.7.2 -ecmascript@0.16.2 +ecmascript@0.16.3 ecmascript-runtime@0.8.0 ecmascript-runtime-client@0.12.1 ecmascript-runtime-server@0.11.0 -ejson@1.1.2 -email@2.2.1 +ejson@1.1.3 +email@2.2.2 es5-shim@4.8.0 -fetch@0.1.1 -geojson-utils@1.0.10 +fetch@0.1.2 +geojson-utils@1.0.11 google-oauth@1.4.2 hot-code-push@1.0.4 html-tools@1.1.3 @@ -57,7 +57,7 @@ localstorage@1.2.0 logging@1.3.1 mdg:meteor-apm-agent@3.5.1 mdg:validated-method@1.2.0 -meteor@1.10.1 +meteor@1.10.2 meteor-base@1.5.1 meteortesting:browser-tests@1.3.5 meteortesting:mocha@2.0.3 @@ -68,20 +68,20 @@ minifier-js@2.7.5 minimongo@1.9.0 mobile-experience@1.1.0 mobile-status-bar@1.1.0 -modern-browsers@0.1.8 +modern-browsers@0.1.9 modules@0.19.0 -modules-runtime@0.13.0 -mongo@1.16.0 +modules-runtime@0.13.1 +mongo@1.16.1 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 mongo-livedata@1.0.12 -npm-mongo@4.9.0 +npm-mongo@4.11.0 oauth@2.1.2 oauth2@1.3.1 ordered-dict@1.1.0 ostrio:cookies@2.7.2 -ostrio:files@2.3.0 +ostrio:files@2.3.2 patreon-oauth@0.1.0 peerlibrary:assert@0.3.0 peerlibrary:check-extension@0.7.0 @@ -93,20 +93,20 @@ peerlibrary:reactive-mongo@0.4.1 peerlibrary:reactive-publish@0.10.0 peerlibrary:server-autorun@0.8.0 peerlibrary:subscription-data@0.8.0 -percolate:migrations@1.0.3 -promise@0.12.0 +percolate:migrations@1.1.0 +promise@0.12.1 raix:eventemitter@1.0.0 -random@1.2.0 +random@1.2.1 rate-limit@1.0.9 react-fast-refresh@0.2.3 -reactive-dict@1.3.0 -reactive-var@1.0.11 +reactive-dict@1.3.1 +reactive-var@1.0.12 reload@1.3.1 retry@1.1.0 routepolicy@1.1.1 seba:minifiers-autoprefixer@2.0.1 -service-configuration@1.3.0 -session@1.2.0 +service-configuration@1.3.1 +session@1.2.1 sha@1.0.9 shell-server@0.5.0 simple:json-routes@2.3.1 @@ -120,10 +120,10 @@ standard-minifier-js@2.8.1 static-html@1.3.2 templating-tools@1.2.2 tmeasday:check-npm-versions@1.0.2 -tracker@1.2.0 +tracker@1.2.1 typescript@4.5.4 -underscore@1.0.10 +underscore@1.0.11 url@1.3.2 -webapp@1.13.1 -webapp-hashing@1.1.0 +webapp@1.13.2 +webapp-hashing@1.1.1 zer0th:meteor-vuetify-loader@0.1.41 diff --git a/app/client/main.js b/app/client/main.js index d9d770b7..6305c8ae 100644 --- a/app/client/main.js +++ b/app/client/main.js @@ -1,6 +1,6 @@ import '/imports/api/simpleSchemaConfig.js'; -import '/imports/ui/vueSetup.js'; -import '/imports/ui/styles/stylesIndex.js'; +import '/imports/client/ui/vueSetup.js'; +import '/imports/client/ui/styles/stylesIndex.js'; import '/imports/client/config.js'; import '/imports/client/serviceWorker.js'; diff --git a/app/imports/api/creature/archive/ArchiveCreatureFiles.js b/app/imports/api/creature/archive/ArchiveCreatureFiles.js index 1b699c0b..c0d1094e 100644 --- a/app/imports/api/creature/archive/ArchiveCreatureFiles.js +++ b/app/imports/api/creature/archive/ArchiveCreatureFiles.js @@ -6,13 +6,13 @@ import { CreatureSchema } from '/imports/api/creature/creatures/Creatures.js'; const ArchiveCreatureFiles = createS3FilesCollection({ collectionName: 'archiveCreatureFiles', - storagePath: Meteor.isDevelopment ? '/DiceCloud/archiveCreatures/' : 'assets/app/archiveCreatures', + storagePath: Meteor.isDevelopment ? '../../../../../fileStorage/archiveCreatures' : 'assets/app/archiveCreatures', onBeforeUpload(file) { // Allow upload files under 10MB, and only in json format if (file.size > 10485760) { return 'Please upload with size equal or less than 10MB'; } - if (!/json/i.test(file.extension)){ + if (!/json/i.test(file.extension)) { return 'Please upload only a JSON file'; } return true; diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index 507d82bd..f129a562 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -59,7 +59,7 @@ const damageProperty = new ValidatedMethod({ }, }); -export function damagePropertyWork({ prop, operation, value, actionContext }) { +export function damagePropertyWork({ prop, operation, value, actionContext, logFunction }) { // Save the value to the scope before applying the before triggers if (operation === 'increment') { @@ -105,6 +105,7 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) { // Also write it straight to the prop so that it is updated in the actionContext prop.damage = damage; prop.value = newValue; + logFunction?.(newValue); } else if (operation === 'increment') { let currentValue = prop.value || 0; let currentDamage = prop.damage || 0; @@ -125,6 +126,7 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) { // Also write it straight to the prop so that it is updated in the actionContext prop.damage += increment; prop.value -= increment; + logFunction?.(increment); } applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext); diff --git a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js index 5a8c84fe..9bb40898 100644 --- a/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/duplicateProperty.js @@ -12,7 +12,7 @@ import { reorderDocs } from '/imports/api/parenting/order.js'; var snackbar; if (Meteor.isClient) { snackbar = require( - '/imports/ui/components/snackbars/SnackbarQueue.js' + '/imports/client/ui/components/snackbars/SnackbarQueue.js' ).snackbar } diff --git a/app/imports/api/creature/creatures/Creatures.js b/app/imports/api/creature/creatures/Creatures.js index f13932ee..b94fe7e4 100644 --- a/app/imports/api/creature/creatures/Creatures.js +++ b/app/imports/api/creature/creatures/Creatures.js @@ -18,6 +18,11 @@ let CreatureSettingsSchema = new SimpleSchema({ type: Boolean, optional: true, }, + //hide rest buttons + hideRestButtons: { + type: Boolean, + optional: true, + }, // Swap around the modifier and stat swapStatAndModifier: { type: Boolean, diff --git a/app/imports/api/creature/creatures/methods/restCreature.js b/app/imports/api/creature/creatures/methods/restCreature.js index 49d228aa..7e7edb1e 100644 --- a/app/imports/api/creature/creatures/methods/restCreature.js +++ b/app/imports/api/creature/creatures/methods/restCreature.js @@ -6,6 +6,7 @@ import { assertEditPermission } from '/imports/api/creature/creatures/creaturePe import { union } from 'lodash'; import ActionContext from '/imports/api/engine/actions/ActionContext.js'; import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; const restCreature = new ValidatedMethod({ name: 'creature.methods.rest', @@ -49,7 +50,7 @@ const restCreature = new ValidatedMethod({ applyTriggers(afterTriggers, null, actionContext); // Insert log - actionContext.writeLog(); + actionContext.writeLog(); }, }); @@ -57,88 +58,113 @@ function doRestWork(restType, actionContext) { const creatureId = actionContext.creature._id; // Long rests reset short rest properties as well let resetFilter; - if (restType === 'shortRest'){ + if (restType === 'shortRest') { resetFilter = 'shortRest' } else { - resetFilter = {$in: ['shortRest', 'longRest']} + resetFilter = { $in: ['shortRest', 'longRest'] } } + resetProperties(creatureId, resetFilter, actionContext); + + // Reset half hit dice on a long rest, starting with the highest dice + if (restType === 'longRest') { + resetHitDice(creatureId, actionContext); + } +} + +export function resetProperties(creatureId, resetFilter, actionContext) { // Only apply to active properties - let filter = { + const filter = { 'ancestors.id': creatureId, reset: resetFilter, removed: { $ne: true }, inactive: { $ne: true }, }; // update all attribute's damage - filter.type = 'attribute'; - CreatureProperties.update(filter, { - $set: { - damage: 0, - dirty: true, - } - }, { - selector: {type: 'attribute'}, - multi: true, + const attributeFilter = { + ...filter, + type: 'attribute', + damage: { $ne: 0 }, + } + CreatureProperties.find(attributeFilter).forEach(prop => { + damagePropertyWork({ + prop, + operation: 'increment', + value: -prop.damage, + actionContext, + logFunction(increment) { + actionContext.addLog({ + name: prop.name, + value: increment < 0 ? `Restored ${-increment}` : `Removed ${-increment}` + }); + } + }); }); // Update all action-like properties' usesUsed - filter.type = {$in: [ - 'action', - 'attack', - 'spell' - ]}; - CreatureProperties.update(filter, { + const actionFilter = { + ...filter, + type: { + $in: ['action', 'spell'] + }, + usesUsed: { $ne: 0 }, + }; + CreatureProperties.find(actionFilter, { + fields: { name: 1, usesUsed: 1 } + }).forEach(prop => { + actionContext.addLog({ + name: prop.name, + value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses` + }); + }); + CreatureProperties.update(actionFilter, { $set: { usesUsed: 0, dirty: true, } }, { - selector: {type: 'action'}, + selector: { type: 'action' }, multi: true, }); - // Reset half hit dice on a long rest, starting with the highest dice - if (restType === 'longRest'){ - let hitDice = CreatureProperties.find({ - 'ancestors.id': creatureId, - type: 'attribute', - attributeType: 'hitDice', - removed: {$ne: true}, - inactive: {$ne: true}, - }, { - fields: { - hitDiceSize: 1, - damage: 1, - total: 1, +} + +function resetHitDice(creatureId, actionContext) { + let hitDice = CreatureProperties.find({ + 'ancestors.id': creatureId, + type: 'attribute', + attributeType: 'hitDice', + removed: { $ne: true }, + inactive: { $ne: true }, + }).fetch(); + // Use a collator to do sorting in natural order + let collator = new Intl.Collator('en', { + numeric: true, sensitivity: 'base' + }); + // Get the hit dice in decending order of hitDiceSize + let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize) + hitDice.sort(compare); + // Get the total number of hit dice that can be recovered this rest + let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0); + let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5; + let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1); + // recover each hit dice in turn until the recoverable amount is used up + let amountToRecover; + hitDice.forEach(hd => { + if (!recoverableHd) return; + amountToRecover = Math.min(recoverableHd, hd.damage ?? 0); + if (!amountToRecover) return; + recoverableHd -= amountToRecover; + damagePropertyWork({ + prop: hd, + operation: 'increment', + value: -amountToRecover, + actionContext, + logFunction(increment) { + actionContext.addLog({ + name: hd.name, + value: increment < 0 ? `Restored ${-increment} hit dice` : `Removed ${increment} hit dice` + }); } - }).fetch(); - // Use a collator to do sorting in natural order - let collator = new Intl.Collator('en', { - numeric: true, sensitivity: 'base' }); - // Get the hit dice in decending order of hitDiceSize - let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize) - hitDice.sort(compare); - // Get the total number of hit dice that can be recovered this rest - let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0); - let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5; - let recoverableHd = Math.max(Math.floor(totalHd*resetMultiplier), 1); - // recover each hit dice in turn until the recoverable amount is used up - let amountToRecover, resultingDamage; - hitDice.forEach(hd => { - if (!recoverableHd) return; - amountToRecover = Math.min(recoverableHd, hd.damage || 0); - if (!amountToRecover) return; - recoverableHd -= amountToRecover; - resultingDamage = hd.damage - amountToRecover; - CreatureProperties.update(hd._id, { - $set: { - damage: resultingDamage, - dirty: true, - } - }, { - selector: {type: 'attribute'}, - }); - }); - } + }); } export default restCreature; diff --git a/app/imports/api/docs/Docs.js b/app/imports/api/docs/Docs.js index 91961b16..cb8aeb4e 100644 --- a/app/imports/api/docs/Docs.js +++ b/app/imports/api/docs/Docs.js @@ -1,3 +1,324 @@ -if (Meteor.isServer) throw 'Client side only collection, don\'t import on server'; +import { Meteor } from 'meteor/meteor'; +import { Mongo } from 'meteor/mongo'; +import { ValidatedMethod } from 'meteor/mdg:validated-method'; +import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; +import SimpleSchema from 'simpl-schema'; +import { softRemove } from '/imports/api/parenting/softRemove.js'; +import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js'; +import { storedIconsSchema } from '/imports/api/icons/Icons.js'; +import '/imports/api/library/methods/index.js'; +import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +import { restore } from '/imports/api/parenting/softRemove.js'; +import { reorderDocs } from '/imports/api/parenting/order.js'; +import { getAncestry } from '/imports/api/parenting/parenting.js'; + 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, + regEx: SimpleSchema.RegEx.Id, + }, + name: { + type: String, + max: STORAGE_LIMITS.description, + }, + urlName: { + type: String, + regEx: /[a-z]+(?:[a-z]|-)+/, + min: 2, + max: STORAGE_LIMITS.variableName, + }, + href: { + type: String, + }, + description: { + type: String, + optional: true, + }, + published: { + type: Boolean, + optional: true, + }, + icon: { + type: storedIconsSchema, + optional: true, + max: STORAGE_LIMITS.icon, + }, +}); + +let schema = new SimpleSchema({}); +schema.extend(DocSchema); +schema.extend(ChildSchema); +schema.extend(SoftRemovableSchema); +Docs.attachSchema(schema); + +function assertDocsEditPermission(userId) { + if (!userId || typeof userId !== 'string') throw new Meteor.Error('No user id provided'); + const user = Meteor.users.findOne(userId); + if (!user) throw new Meteor.Error('User does not exist'); + if (!user?.roles?.includes?.('docsWriter')) throw ('Permission denied') +} + +function getDocLink(doc, urlName) { + if (!urlName) urlName = doc.urlName; + const address = ['/docs']; + doc.ancestors?.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 () { + return JSON.stringify(Docs.find({}).fetch(), null, 2); + } +} else if (Meteor.isServer) { + Meteor.startup(() => { + if (!Docs.findOne()) { + Assets.getText('docs/defaultDocs.json', (error, string) => { + const docs = JSON.parse(string) + docs.forEach(doc => Docs.insert(doc)); + }); + } + }); +} + +const insertDoc = new ValidatedMethod({ + name: 'docs.insert', + validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ doc, parentRef }) { + 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; + + const lastOrder = Docs.find({}, { sort: { order: -1 } }).fetch()[0]?.order || 0; + doc.order = lastOrder + 1; + doc.urlName = 'new-doc-' + (lastOrder + 1); + + doc.href = getDocLink(doc); + if (Docs.findOne({ href: doc.href })) { + throw new Meteor.Error('Link collision', 'A document with the same URL already exists'); + } + + const docId = Docs.insert(doc); + reorderDocs({ + collection: Docs, + ancestorId: 'root', + }); + return docId; + }, +}); + +const updateDoc = new ValidatedMethod({ + name: 'docs.update', + validate({ _id, path }) { + if (!_id) return false; + // We cannot change these fields with a simple update + switch (path[0]) { + case '_is': + return false; + } + }, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id, path, value }) { + assertDocsEditPermission(this.userId); + let pathString = path.join('.'); + let modifier; + // unset empty values + if (value === null || value === undefined) { + modifier = { $unset: { [pathString]: 1 } }; + } else { + modifier = { $set: { [pathString]: value } }; + } + if (pathString === 'urlName') { + const doc = Docs.findOne(_id); + const newLink = getDocLink(doc, value); + if (Docs.findOne({ href: newLink })) { + throw new Meteor.Error('Link collision', 'A document with the same URL already exists'); + } + modifier.$set = modifier.$set || {}; + modifier.$set.href = newLink; + rebuildDocAncestors(_id); + } + const updates = Docs.update(_id, modifier); + if (pathString === 'name' || pathString === 'urlName') { + rebuildDocAncestors(_id); + } + reorderDocs({ + collection: Docs, + ancestorId: 'root', + }); + return updates; + }, +}); + +const pushToDoc = new ValidatedMethod({ + name: 'docs.push', + validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id, path, value }) { + assertDocsEditPermission(this.userId); + return Docs.update(_id, { + $push: { [path.join('.')]: value }, + }); + } +}); + +const pullFromDoc = new ValidatedMethod({ + name: 'docs.pull', + validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id, path, itemId }) { + assertDocsEditPermission(this.userId); + return Docs.update(_id, { + $pull: { [path.join('.')]: { _id: itemId } }, + }); + } +}); + +const softRemoveDoc = new ValidatedMethod({ + name: 'docs.softRemove', + validate: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id }) { + assertDocsEditPermission(this.userId); + softRemove({ _id, collection: Docs }); + reorderDocs({ + collection: Docs, + ancestorId: 'root', + }); + } +}); + +const restoreDoc = new ValidatedMethod({ + name: 'docs.restore', + validate: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id }) { + assertDocsEditPermission(this.userId); + restore({ _id, collection: Docs }); + reorderDocs({ + collection: Docs, + ancestorId: 'root', + }); + } +}); + +export { + DocSchema, + insertDoc, + updateDoc, + pushToDoc, + pullFromDoc, + softRemoveDoc, + restoreDoc, +}; + export default Docs; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 8d6bd185..d749990d 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -5,8 +5,9 @@ import applyProperty from '../applyProperty.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js'; import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js'; -import numberToSignedString from '/imports/ui/utility/numberToSignedString.js'; +import numberToSignedString from '/imports/api/utility/numberToSignedString.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js'; export default function applyAction(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); @@ -16,7 +17,7 @@ export default function applyAction(node, actionContext) { // Log the name and summary let content = { name: prop.name }; - if (prop.summary?.text){ + if (prop.summary?.text) { recalculateInlineCalculations(prop.summary, actionContext); content.value = prop.summary.value; } @@ -29,24 +30,27 @@ export default function applyAction(node, actionContext) { const attack = prop.attackRoll || prop.attackRollBonus; // Attack if there is an attack roll - if (attack && attack.calculation){ - if (targets.length){ + if (attack && attack.calculation) { + if (targets.length) { targets.forEach(target => { - applyAttackToTarget({attack, target, actionContext}); + applyAttackToTarget({ attack, target, actionContext }); // Apply the children, but only to the current target actionContext.targets = [target]; applyChildren(node, actionContext); }); } else { - applyAttackWithoutTarget({attack, actionContext}); + applyAttackWithoutTarget({ attack, actionContext }); applyChildren(node, actionContext); } } else { applyChildren(node, actionContext); } + if (prop.actionType === 'event' && prop.variableName) { + resetProperties(actionContext.creature._id, prop.variableName, actionContext); + } } -function applyAttackWithoutTarget({attack, actionContext}){ +function applyAttackWithoutTarget({ attack, actionContext }) { delete actionContext.scope['$attackHit']; delete actionContext.scope['$attackMiss']; delete actionContext.scope['$criticalHit']; @@ -62,16 +66,16 @@ function applyAttackWithoutTarget({attack, actionContext}){ criticalMiss, } = rollAttack(attack, scope); let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit'; - if (scope['$attackAdvantage'] === 1){ + if (scope['$attackAdvantage'] === 1) { name += ' (Advantage)'; - } else if(scope['$attackAdvantage'] === -1){ + } else if (scope['$attackAdvantage'] === -1) { name += ' (Disadvantage)'; } - if (!criticalMiss){ - scope['$attackHit'] = {value: true} + if (!criticalMiss) { + scope['$attackHit'] = { value: true } } - if (!criticalHit){ - scope['$attackMiss'] = {value: true}; + if (!criticalHit) { + scope['$attackMiss'] = { value: true }; } actionContext.addLog({ @@ -81,7 +85,7 @@ function applyAttackWithoutTarget({attack, actionContext}){ }); } -function applyAttackToTarget({attack, target, actionContext}){ +function applyAttackToTarget({ attack, target, actionContext }) { const scope = actionContext.scope; delete scope['$attackHit']; delete scope['$attackMiss']; @@ -99,15 +103,15 @@ function applyAttackToTarget({attack, target, actionContext}){ criticalMiss, } = rollAttack(attack, scope); - if (target.variables.armor){ + if (target.variables.armor) { const armor = target.variables.armor.value; let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : - result > armor ? 'Hit!' : 'Miss!'; - if (scope['$attackAdvantage'] === 1){ + result > armor ? 'Hit!' : 'Miss!'; + if (scope['$attackAdvantage'] === 1) { name += ' (Advantage)'; - } else if(scope['$attackAdvantage'] === -1){ + } else if (scope['$attackAdvantage'] === -1) { name += ' (Disadvantage)'; } @@ -116,15 +120,15 @@ function applyAttackToTarget({attack, target, actionContext}){ value: `${resultPrefix}\n**${result}**`, inline: true, }); - if (criticalMiss || result < armor){ - scope['$attackMiss'] = {value: true}; + if (criticalMiss || result < armor) { + scope['$attackMiss'] = { value: true }; } else { - scope['$attackHit'] = {value: true}; + scope['$attackHit'] = { value: true }; } } else { actionContext.addLog({ name: 'Error', - value:'Target has no `armor`', + value: 'Target has no `armor`', }); actionContext.addLog({ name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit', @@ -134,10 +138,10 @@ function applyAttackToTarget({attack, target, actionContext}){ } } -function rollAttack(attack, scope){ +function rollAttack(attack, scope) { const rollModifierText = numberToSignedString(attack.value, true); let value, resultPrefix; - if (scope['$attackAdvantage'] === 1){ + if (scope['$attackAdvantage'] === 1) { const [a, b] = rollDice(2, 20); if (a >= b) { value = a; @@ -146,7 +150,7 @@ function rollAttack(attack, scope){ value = b; resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`; } - } else if (scope['$attackAdvantage'] === -1){ + } else if (scope['$attackAdvantage'] === -1) { const [a, b] = rollDice(2, 20); if (a <= b) { value = a; @@ -159,25 +163,26 @@ function rollAttack(attack, scope){ value = rollDice(1, 20)[0]; resultPrefix = `1d20 [${value}] ${rollModifierText}` } - scope['$attackRoll'] = {value}; + scope['$attackDiceRoll'] = { value }; const result = value + attack.value; - const {criticalHit, criticalMiss} = applyCrits(value, scope); - return {resultPrefix, result, value, criticalHit, criticalMiss}; + scope['$attackRoll'] = { result }; + const { criticalHit, criticalMiss } = applyCrits(value, scope); + return { resultPrefix, result, value, criticalHit, criticalMiss }; } -function applyCrits(value, scope){ +function applyCrits(value, scope) { let criticalHitTarget = scope.criticalHitTarget?.value || 20; let criticalHit = value >= criticalHitTarget; let criticalMiss; - if (criticalHit){ - scope['$criticalHit'] = {value: true}; + if (criticalHit) { + scope['$criticalHit'] = { value: true }; } else { criticalMiss = value === 1; - if (criticalMiss){ - scope['$criticalMiss'] = {value: true}; + if (criticalMiss) { + scope['$criticalMiss'] = { value: true }; } } - return {criticalHit, criticalMiss}; + return { criticalHit, criticalMiss }; } function applyChildren(node, actionContext) { @@ -185,9 +190,9 @@ function applyChildren(node, actionContext) { node.children.forEach(child => applyProperty(child, actionContext)); } -function spendResources(prop, actionContext){ +function spendResources(prop, actionContext) { // Check Uses - if (prop.usesLeft <= 0){ + if (prop.usesLeft <= 0) { if (!prop.silent) actionContext.addLog({ name: 'Error', value: `${prop.name || 'action'} does not have enough uses left`, @@ -195,7 +200,7 @@ function spendResources(prop, actionContext){ return true; } // Resources - if (prop.insufficientResources){ + if (prop.insufficientResources) { if (!prop.silent) actionContext.addLog({ name: 'Error', value: 'This creature doesn\'t have sufficient resources to perform this action', @@ -209,14 +214,14 @@ function spendResources(prop, actionContext){ try { prop.resources.itemsConsumed.forEach(itemConsumed => { recalculateCalculation(itemConsumed.quantity, actionContext); - if (!itemConsumed.itemId){ + if (!itemConsumed.itemId) { throw 'No ammo was selected for this prop'; } let item = CreatureProperties.findOne(itemConsumed.itemId); - if (!item || item.ancestors[0].id !== prop.ancestors[0].id){ + if (!item || item.ancestors[0].id !== prop.ancestors[0].id) { throw 'The prop\'s ammo was not found on the creature'; } - if (!item.equipped){ + if (!item.equipped) { throw 'The selected ammo is not equipped'; } if ( @@ -229,16 +234,16 @@ function spendResources(prop, actionContext){ value: itemConsumed.quantity.value, }); let logName = item.name; - if (itemConsumed.quantity.value > 1 || itemConsumed.quantity.value < -1){ + if (itemConsumed.quantity.value > 1 || itemConsumed.quantity.value < -1) { logName = item.plural || logName; } - if (itemConsumed.quantity.value > 0){ + if (itemConsumed.quantity.value > 0) { spendLog.push(logName + ': ' + itemConsumed.quantity.value); - } else if (itemConsumed.quantity.value < 0){ + } else if (itemConsumed.quantity.value < 0) { gainLog.push(logName + ': ' + -itemConsumed.quantity.value); } }); - } catch (e){ + } catch (e) { actionContext.addLog({ name: 'Error', value: e, @@ -251,9 +256,9 @@ function spendResources(prop, actionContext){ itemQuantityAdjustments.forEach(adjustQuantityWork); // Use uses - if (prop.usesLeft){ + if (prop.usesLeft) { CreatureProperties.update(prop._id, { - $inc: {usesUsed: 1} + $inc: { usesUsed: 1 } }, { selector: prop }); @@ -270,7 +275,7 @@ function spendResources(prop, actionContext){ if (!attConsumed.quantity?.value) return; let stat = actionContext.scope[attConsumed.variableName]; - if (!stat){ + if (!stat) { spendLog.push(stat.name + ': ' + ' not found'); return; } @@ -280,9 +285,9 @@ function spendResources(prop, actionContext){ value: attConsumed.quantity.value, actionContext, }); - if (attConsumed.quantity.value > 0){ + if (attConsumed.quantity.value > 0) { spendLog.push(stat.name + ': ' + attConsumed.quantity.value); - } else if (attConsumed.quantity.value < 0){ + } else if (attConsumed.quantity.value < 0) { gainLog.push(stat.name + ': ' + -attConsumed.quantity.value); } }); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js index 4e68eabe..4ee2cd92 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js @@ -14,6 +14,7 @@ import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js'; import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; +import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js'; export default function applyBuff(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); @@ -46,12 +47,17 @@ export default function applyBuff(node, actionContext) { copyNodeListToTarget(propList, target, oldParent); //Log the buff + let logValue = prop.description?.value + if (prop.description?.text) { + recalculateInlineCalculations(prop.description, actionContext); + logValue = prop.description?.value; + } if ((prop.name || prop.description?.value) && !prop.silent) { if (target._id === actionContext.creature._id) { // Targeting self actionContext.addLog({ name: prop.name, - value: prop.description?.value, + value: logValue, }); } else { // Targeting other @@ -60,7 +66,7 @@ export default function applyBuff(node, actionContext) { creatureId: target._id, content: [{ name: prop.name, - value: prop.description?.value, + value: logValue, }], } }); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index b1f15793..f05b7351 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -1,6 +1,6 @@ import { some, intersection, difference, remove, includes } from 'lodash'; 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 logErrors from './shared/logErrors.js'; import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js'; @@ -9,10 +9,11 @@ import { getPropertiesOfType } from '/imports/api/engine/loadCreatures.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js'; -export default function applyDamage(node, actionContext){ +export default function applyDamage(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); - const applyChildren = function(){ + const applyChildren = function () { applyNodeTriggers(node, 'after', actionContext); node.children.forEach(child => applyProperty(child, actionContext)); }; @@ -28,10 +29,10 @@ export default function applyDamage(node, actionContext){ // Determine if the hit is critical let criticalHit = scope['$criticalHit']?.value && prop.damageType !== 'healing' // Can't critically heal - ; + ; // Double the damage rolls if the hit is critical let context = new Context({ - options: {doubleRolls: criticalHit}, + options: { doubleRolls: criticalHit }, }); // Gather all the lines we need to log into an array @@ -40,8 +41,8 @@ export default function applyDamage(node, actionContext){ // roll the dice only and store that string applyEffectsToCalculationParseNode(prop.amount, actionContext.log); - const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context); - if (rolled.parseType !== 'constant'){ + const { result: rolled } = resolve('roll', prop.amount.parseNode, scope, context); + if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); } logErrors(context.errors, actionContext); @@ -50,13 +51,13 @@ export default function applyDamage(node, actionContext){ context.errors = []; // 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); // Store the result - if (reduced.parseType === 'constant'){ + if (reduced.parseType === 'constant') { prop.amount.value = reduced.value; - } else if (reduced.parseType === 'error'){ + } else if (reduced.parseType === 'error') { prop.amount.value = null; } else { prop.amount.value = toString(reduced); @@ -64,7 +65,7 @@ export default function applyDamage(node, actionContext){ let damage = +reduced.value; // 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(); } @@ -83,7 +84,7 @@ export default function applyDamage(node, actionContext){ // Memoise the damage suffix for the log let suffix = (criticalHit ? ' critical ' : ' ') + prop.damageType + - (prop.damageType !== 'healing' ? ' damage ': ''); + (prop.damageType !== 'healing' ? ' damage ' : ''); if (damageTargets && damageTargets.length) { // Iterate through all the targets @@ -107,7 +108,7 @@ export default function applyDamage(node, actionContext){ }); // 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 logValue.push(`**${damageDealt}** ${suffix} to self`); } else { @@ -135,33 +136,33 @@ export default function applyDamage(node, actionContext){ return applyChildren(); } -function applyDamageMultipliers({target, damage, damageProp, logValue}){ +function applyDamageMultipliers({ target, damage, damageProp, logValue }) { const damageType = damageProp?.damageType; if (!damageType) return damage; const multiplier = target?.variables?.[damageType]; if (!multiplier) return damage; - const damageTypeText = damageType == 'healing' ? 'healing': `${damageType} damage`; + const damageTypeText = damageType == 'healing' ? 'healing' : `${damageType} damage`; if ( multiplier.immunity && some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity')) - ){ + ) { logValue.push(`Immune to ${damageTypeText}`); return 0; } else { if ( multiplier.resistance && some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance')) - ){ + ) { logValue.push(`Resistant to ${damageTypeText}`); damage = Math.floor(damage / 2); } if ( multiplier.vulnerability && some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability')) - ){ + ) { logValue.push(`Vulnerable to ${damageTypeText}`); damage = Math.floor(damage * 2); } @@ -169,24 +170,25 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){ return damage; } -function multiplierAppliesTo(damageProp, multiplierType){ +function multiplierAppliesTo(damageProp, multiplierType) { return multiplier => { // Apply the default 'ignore x' tags - if (includes(damageProp.tags, `ignore ${multiplierType}`)) return false; + const effectiveTags = getEffectivePropTags(damageProp); + if (includes(effectiveTags, `ignore ${multiplierType}`)) return false; const hasRequiredTags = difference( - multiplier.includeTags, damageProp.tags + multiplier.includeTags, effectiveTags ).length === 0; const hasNoExcludedTags = intersection( - multiplier.excludeTags, damageProp.tags + multiplier.excludeTags, effectiveTags ).length === 0; return hasRequiredTags && hasNoExcludedTags; } } -function dealDamage({target, damageType, amount, actionContext}){ +function dealDamage({ target, damageType, amount, actionContext }) { // Get all the health bars and do damage to them let healthBars = getPropertiesOfType(target._id, 'attribute'); @@ -238,6 +240,14 @@ function dealDamage({target, damageType, amount, actionContext}){ actionContext }); damageLeft -= damageAdded; + // Prevent overflow + if ( + damageType === 'healing' ? + healthBar.healthBarNoHealingOverflow : + healthBar.healthBarNoDamageOverflow + ) { + damageLeft = 0; + } }); return totalDamage; } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index a5aa4bae..ca760d7a 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -1,10 +1,11 @@ import rollDice from '/imports/parser/rollDice.js'; import recalculateCalculation from './shared/recalculateCalculation.js'; import applyProperty from '../applyProperty.js'; -import numberToSignedString from '/imports/ui/utility/numberToSignedString.js'; +import numberToSignedString from '/imports/api/utility/numberToSignedString.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; +import { applyUnresolvedEffects } from '/imports/api/engine/actions/doCheck.js'; -export default function applySavingThrow(node, actionContext){ +export default function applySavingThrow(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; @@ -13,7 +14,7 @@ export default function applySavingThrow(node, actionContext){ recalculateCalculation(prop.dc, actionContext); const dc = (prop.dc?.value); - if (!isFinite(dc)){ + if (!isFinite(dc)) { actionContext.addLog({ name: 'Error', value: 'Saving throw requires a DC', @@ -29,8 +30,8 @@ export default function applySavingThrow(node, actionContext){ // If there are no save targets, apply all children as if the save both // succeeeded and failed - if (!saveTargets?.length){ - scope['$saveFailed'] = {value: true}; + if (!saveTargets?.length) { + scope['$saveFailed'] = { value: true }; scope['$saveSucceeded'] = { value: true }; applyNodeTriggers(node, 'after', actionContext); return node.children.forEach(child => applyProperty(child, actionContext)); @@ -51,7 +52,7 @@ export default function applySavingThrow(node, actionContext){ const save = target.variables[prop.stat]; - if (!save){ + if (!save) { actionContext.addLog({ name: 'Saving throw error', value: 'No saving throw found: ' + prop.stat, @@ -59,10 +60,14 @@ export default function applySavingThrow(node, actionContext){ return applyChildren(); } - const rollModifierText = numberToSignedString(save.value, true); + let rollModifierText = numberToSignedString(save.value, true); + let rollModifier = save.value + const { effectBonus, effectString } = applyUnresolvedEffects(save, scope) + rollModifierText += effectString; + rollModifier += effectBonus; let value, values, resultPrefix; - if (save.advantage === 1){ + if (save.advantage === 1) { const [a, b] = rollDice(2, 20); if (a >= b) { value = a; @@ -71,7 +76,7 @@ export default function applySavingThrow(node, actionContext){ value = b; resultPrefix = `Advantage\n1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`; } - } else if (save.advantage === -1){ + } else if (save.advantage === -1) { const [a, b] = rollDice(2, 20); if (a <= b) { value = a; @@ -85,14 +90,14 @@ export default function applySavingThrow(node, actionContext){ value = values[0]; resultPrefix = `1d20 [ ${value} ] ${rollModifierText}` } - scope['$saveDiceRoll'] = {value}; - const result = value + save.value || 0; - scope['$saveRoll'] = {value: result}; + scope['$saveDiceRoll'] = { value }; + const result = value + rollModifier || 0; + scope['$saveRoll'] = { value: result }; const saveSuccess = result >= dc; - if (saveSuccess){ - scope['$saveSucceeded'] = {value: true}; + if (saveSuccess) { + scope['$saveSucceeded'] = { value: true }; } else { - scope['$saveFailed'] = {value: true}; + scope['$saveFailed'] = { value: true }; } if (!prop.silent) actionContext.addLog({ name: saveSuccess ? 'Successful save' : 'Failed save', diff --git a/app/imports/api/engine/actions/doCheck.js b/app/imports/api/engine/actions/doCheck.js index a5bc653b..5d9b4164 100644 --- a/app/imports/api/engine/actions/doCheck.js +++ b/app/imports/api/engine/actions/doCheck.js @@ -4,7 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; import rollDice from '/imports/parser/rollDice.js'; -import numberToSignedString from '/imports/ui/utility/numberToSignedString.js'; +import numberToSignedString from '/imports/api/utility/numberToSignedString.js'; import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js'; import ActionContext from '/imports/api/engine/actions/ActionContext.js'; import evaluateCalculation from '/imports/api/engine/computation/utility/evaluateCalculation.js'; @@ -28,6 +28,7 @@ const doCheck = new ValidatedMethod({ const creatureId = prop.ancestors[0].id; const actionContext = new ActionContext(creatureId, [creatureId], this); Object.assign(actionContext.scope, scope); + actionContext.scope[`#${prop.type}`] = prop; // Check permissions assertEditPermission(actionContext.creature, this.userId); @@ -115,7 +116,7 @@ function rollCheck(prop, actionContext) { }); } -function applyUnresolvedEffects(prop, scope) { +export function applyUnresolvedEffects(prop, scope) { let effectBonus = 0; let effectString = ''; if (!prop.effects) { diff --git a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js index 67e4452e..68a19951 100644 --- a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js @@ -1,7 +1,7 @@ import findAncestorByType from '/imports/api/engine/computation/utility/findAncestorByType.js'; import { traverse } from '/imports/parser/resolve.js'; -export default function linkCalculationDependencies(dependencyGraph, prop, {propsById}){ +export default function linkCalculationDependencies(dependencyGraph, prop, { propsById }) { prop._computationDetails.calculations.forEach(calcObj => { // Store resolved ancestors const memo = { @@ -16,12 +16,13 @@ export default function linkCalculationDependencies(dependencyGraph, prop, {prop // Skip nodes that aren't symbols or accessors if (node.parseType !== 'symbol' && node.parseType !== 'accessor') return; // Link ancestor references as direct property dependencies - if (node.name[0] === '#'){ + if (node.name[0] === '#') { let ancestorProp = getAncestorProp( node.name.slice(1), memo, prop, propsById ); if (!ancestorProp) return; // Link the ancestor prop as a direct dependency + // TODO: we might be referencing a calculation sub-field, depend on that instead dependencyGraph.addLink( calcNodeId, ancestorProp._id, 'ancestorReference' ); @@ -34,16 +35,16 @@ export default function linkCalculationDependencies(dependencyGraph, prop, {prop }); // Store the resolved ancestors in this calculation's local scope if (memo.ancestors) { - calcObj._localScope = { ...calcObj._localScope, ...memo.ancestors}; + calcObj._localScope = { ...calcObj._localScope, ...memo.ancestors }; } }); } -function getAncestorProp(type, memo, prop, propsById){ - if (memo.ancestors && memo.ancestors['#' + type]){ +function getAncestorProp(type, memo, prop, propsById) { + if (memo.ancestors && memo.ancestors['#' + type]) { return memo.ancestors['#' + type]; } else { - var ancestorProp = findAncestorByType( prop, type, propsById ); + var ancestorProp = findAncestorByType(prop, type, propsById); if (!memo.ancestors) memo.ancestors = {}; memo.ancestors['#' + type] = ancestorProp; return ancestorProp; diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 44fc93e9..7ed69d19 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -23,23 +23,26 @@ const linkDependenciesByType = { toggle: linkToggle, } -export default function linkTypeDependencies(dependencyGraph, prop, computation){ +export default function linkTypeDependencies(dependencyGraph, prop, computation) { linkDependenciesByType[prop.type]?.(dependencyGraph, prop, computation); } -function dependOnCalc({dependencyGraph, prop, key}){ +function dependOnCalc({ dependencyGraph, prop, key }) { let calc = get(prop, key); if (!calc) return; - if (calc.type !== '_calculation'){ + if (calc.type !== '_calculation') { throw `Expected calculation got ${calc.type}` } dependencyGraph.addLink(prop._id, `${prop._id}.${key}`, 'calculation'); } -function linkAction(dependencyGraph, prop, {propsById}){ +function linkAction(dependencyGraph, prop, { propsById }) { + if (prop.variableName) { + dependencyGraph.addLink(prop.variableName, prop._id, 'eventDefinition'); + } // The action depends on its attack roll and uses calculations - dependOnCalc({dependencyGraph, prop, key: 'attackRoll'}); - dependOnCalc({dependencyGraph, prop, key: 'uses'}); + dependOnCalc({ dependencyGraph, prop, key: 'attackRoll' }); + dependOnCalc({ dependencyGraph, prop, key: 'uses' }); // Link the resources the action uses if (!prop.resources) return; @@ -47,7 +50,7 @@ function linkAction(dependencyGraph, prop, {propsById}){ prop.resources.itemsConsumed.forEach((itemConsumed, index) => { if (!itemConsumed.itemId) return; const item = propsById[itemConsumed.itemId]; - if (!item || item.inactive){ + if (!item || item.inactive) { // Unlink if the item doesn't exist or is inactive itemConsumed.itemId = undefined; return; @@ -79,48 +82,48 @@ function linkAction(dependencyGraph, prop, {propsById}){ }); } -function linkAdjustment(dependencyGraph, prop){ +function linkAdjustment(dependencyGraph, prop) { // Adjustment depends on its amount - dependOnCalc({dependencyGraph, prop, key: 'amount'}); + dependOnCalc({ dependencyGraph, prop, key: 'amount' }); } -function linkAttribute(dependencyGraph, prop){ +function linkAttribute(dependencyGraph, prop) { linkVariableName(dependencyGraph, prop); // Depends on spellSlotLevel - dependOnCalc({dependencyGraph, prop, key: 'spellSlotLevel'}); + dependOnCalc({ dependencyGraph, prop, key: 'spellSlotLevel' }); // Depends on base value - dependOnCalc({dependencyGraph, prop, key: 'baseValue'}); + dependOnCalc({ dependencyGraph, prop, key: 'baseValue' }); // hit dice depend on constitution - if (prop.attributeType === 'hitDice'){ + if (prop.attributeType === 'hitDice') { dependencyGraph.addLink(prop._id, 'constitution', 'hitDiceConMod'); } } -function linkBranch(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'condition'}); +function linkBranch(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'condition' }); } -function linkBuff(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'duration'}); +function linkBuff(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'duration' }); } function linkClassLevel(dependencyGraph, prop) { if (prop.inactive) return; // The variableName of the prop depends on the prop - if (prop.variableName && prop.level){ + if (prop.variableName && prop.level) { dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel'); // The level variable depends on the class variableName variable let existingLevelLink = dependencyGraph.getLink('level', prop.variableName); - if (!existingLevelLink){ + if (!existingLevelLink) { dependencyGraph.addLink('level', prop.variableName, 'level'); } } } -function linkDamage(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'amount'}); +function linkDamage(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'amount' }); } function linkEffects(dependencyGraph, prop, computation) { @@ -132,7 +135,7 @@ function linkEffects(dependencyGraph, prop, computation) { if (prop.inactive) { // Inactive effects apply to no stats return; - } else if (prop.targetByTags){ + } else if (prop.targetByTags) { getEffectTagTargets(prop, computation).forEach(targetId => { const targetProp = computation.propsById[targetId]; if ( @@ -147,8 +150,8 @@ function linkEffects(dependencyGraph, prop, computation) { // Otherwise target a field on that property const key = prop.targetField || getDefaultCalculationField(targetProp); const calcObj = get(targetProp, key); - if (calcObj && calcObj.calculation){ - dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id , 'effect'); + if (calcObj && calcObj.calculation) { + dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'effect'); } } }); @@ -161,14 +164,14 @@ function linkEffects(dependencyGraph, prop, computation) { } // Returns an array of IDs of the properties the effect targets -function getEffectTagTargets(effect, computation){ +function getEffectTagTargets(effect, computation) { let targets = getTargetListFromTags(effect.targetTags, computation); let notIds = []; - if (effect.extraTags){ + if (effect.extraTags) { effect.extraTags.forEach(ex => { if (ex.operation === 'OR') { targets = union(targets, getTargetListFromTags(ex.tags, computation)); - } else if (ex.operation === 'NOT'){ + } else if (ex.operation === 'NOT') { ex.tags.forEach(tag => { const idList = computation.propsWithTag[tag]; if (idList) { @@ -181,7 +184,7 @@ function getEffectTagTargets(effect, computation){ return difference(targets, notIds); } -function getTargetListFromTags(tags, computation){ +function getTargetListFromTags(tags, computation) { const targetTagIdLists = []; if (!tags) return []; tags.forEach(tag => { @@ -192,8 +195,8 @@ function getTargetListFromTags(tags, computation){ return targets; } -function getDefaultCalculationField(prop){ - switch (prop.type){ +function getDefaultCalculationField(prop) { + switch (prop.type) { case 'action': return 'attackRoll'; case 'adjustment': return 'amount'; case 'attribute': return 'baseValue'; @@ -223,13 +226,13 @@ function getDefaultCalculationField(prop){ } } -function linkRoll(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'roll'}); +function linkRoll(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'roll' }); } -function linkVariableName(dependencyGraph, prop){ +function linkVariableName(dependencyGraph, prop) { // The variableName of the prop depends on the prop if the prop is active - if (prop.variableName && !prop.inactive){ + if (prop.variableName && !prop.inactive) { dependencyGraph.addLink(prop.variableName, prop._id, 'definition'); } } @@ -243,7 +246,7 @@ function linkDamageMultiplier(dependencyGraph, prop) { }); } -function linkPointBuy(dependencyGraph, prop){ +function linkPointBuy(dependencyGraph, prop) { dependOnCalc({ dependencyGraph, prop, key: 'min' }); dependOnCalc({ dependencyGraph, prop, key: 'max' }); dependOnCalc({ dependencyGraph, prop, key: 'cost' }); @@ -265,7 +268,7 @@ function linkPointBuy(dependencyGraph, prop){ if (prop.inactive) return; } -function linkProficiencies(dependencyGraph, prop){ +function linkProficiencies(dependencyGraph, prop) { // The stats depend on the proficiency if (prop.inactive) return; prop.stats.forEach(statName => { @@ -274,36 +277,36 @@ function linkProficiencies(dependencyGraph, prop){ }); } -function linkSavingThrow(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'dc'}); +function linkSavingThrow(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'dc' }); } -function linkSkill(dependencyGraph, prop){ +function linkSkill(dependencyGraph, prop) { // Depends on base value dependOnCalc({ dependencyGraph, prop, key: 'baseValue' }); // Link dependents if (prop.inactive) return; linkVariableName(dependencyGraph, prop); // The prop depends on the variable references as the ability - if (prop.ability){ + if (prop.ability) { dependencyGraph.addLink(prop._id, prop.ability, 'skillAbilityScore'); } // Skills depend on the creature's proficiencyBonus dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus'); } -function linkSlot(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'quantityExpected'}); - dependOnCalc({dependencyGraph, prop, key: 'slotCondition'}); +function linkSlot(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'quantityExpected' }); + dependOnCalc({ dependencyGraph, prop, key: 'slotCondition' }); } -function linkSpellList(dependencyGraph, prop){ - dependOnCalc({dependencyGraph, prop, key: 'maxPrepared'}); - dependOnCalc({dependencyGraph, prop, key: 'attackRollBonus'}); - dependOnCalc({dependencyGraph, prop, key: 'dc'}); +function linkSpellList(dependencyGraph, prop) { + dependOnCalc({ dependencyGraph, prop, key: 'maxPrepared' }); + dependOnCalc({ dependencyGraph, prop, key: 'attackRollBonus' }); + dependOnCalc({ dependencyGraph, prop, key: 'dc' }); } -function linkToggle(dependencyGraph, prop){ +function linkToggle(dependencyGraph, prop) { linkVariableName(dependencyGraph, prop); - dependOnCalc({dependencyGraph, prop, key: 'condition'}); + dependOnCalc({ dependencyGraph, prop, key: 'condition' }); } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType.js b/app/imports/api/engine/computation/computeComputation/computeByType.js index 7597b976..0b5633e3 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType.js @@ -5,6 +5,7 @@ import skill from './computeByType/computeSkill.js'; import pointBuy from './computeByType/computePointBuy.js'; import propertySlot from './computeByType/computeSlot.js'; import container from './computeByType/computeContainer.js'; +import spellList from './computeByType/computeSpellList.js'; import _calculation from './computeByType/computeCalculation.js'; export default Object.freeze({ @@ -17,4 +18,5 @@ export default Object.freeze({ pointBuy, propertySlot, spell: action, + spellList, }); diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js index c52dec00..663922a5 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeAction.js @@ -1,8 +1,8 @@ -export default function computeAction(computation, node){ +export default function computeAction(computation, node) { const prop = node.data; - if (prop.uses){ + if (prop.uses) { prop.usesLeft = prop.uses.value - (prop.usesUsed || 0); - if (!prop.usesLeft){ + if (!prop.usesLeft) { prop.insufficientResources = true; } } @@ -10,19 +10,19 @@ export default function computeAction(computation, node){ if (!prop.resources) return; prop.resources.itemsConsumed.forEach(itemConsumed => { if (!itemConsumed.itemId) return; - if (itemConsumed.available < itemConsumed.quantity?.value){ + if (itemConsumed.available < itemConsumed.quantity?.value) { prop.insufficientResources = true; } }); prop.resources.attributesConsumed.forEach(attConsumed => { if (!attConsumed.variableName) return; - if (attConsumed.available < attConsumed.quantity?.value){ + if (attConsumed.available < attConsumed.quantity?.value) { prop.insufficientResources = true; } }); } -function computeResources(computation, node){ +function computeResources(computation, node) { const resources = node.data?.resources; if (!resources) return; resources.attributesConsumed.forEach(attConsumed => { diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeSpellList.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeSpellList.js new file mode 100644 index 00000000..499ddb97 --- /dev/null +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeSpellList.js @@ -0,0 +1,10 @@ +export default function computeSpelllist(computation, node) { + const prop = node.data; + + const ability = computation.scope[prop.ability]; + if (Number.isFinite(ability?.modifier)) { + prop.abilityMod = ability.modifier; + } else if (Number.isFinite(ability?.value)) { + prop.abilityMod = ability.value; + } +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js index 279ad3d6..5ef7d0be 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable.js @@ -7,7 +7,7 @@ import computeVariableAsToggle from './computeVariable/computeVariableAsToggle.j import computeImplicitVariable from './computeVariable/computeImplicitVariable.js'; import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; -export default function computeVariable(computation, node){ +export default function computeVariable(computation, node) { const scope = computation.scope; if (!node.data) node.data = {}; aggregateLinks(computation, node); @@ -15,7 +15,7 @@ export default function computeVariable(computation, node){ // Don't add to the scope if the node id is not a legitimate variable name // Without this `some.thing` could break the entire sheet as a database key if (!VARIABLE_NAME_REGEX.test(node.id)) return; - if (node.data.definingProp){ + if (node.data.definingProp) { // Add the defining variable to the scope scope[node.id] = node.data.definingProp } else { @@ -24,7 +24,7 @@ export default function computeVariable(computation, node){ } } -function aggregateLinks(computation, node){ +function aggregateLinks(computation, node) { computation.dependencyGraph.forEachLinkedNode( node.id, (linkedNode, link) => { @@ -32,11 +32,12 @@ function aggregateLinks(computation, node){ // Ignore inactive props if (linkedNode.data.inactive) return; // Apply all the aggregations - let arg = {node, linkedNode, link, computation}; + let arg = { node, linkedNode, link, computation }; aggregate.classLevel(arg); aggregate.damageMultiplier(arg); aggregate.definition(arg); aggregate.effect(arg); + aggregate.eventDefinition(arg); aggregate.inventory(arg); aggregate.proficiency(arg); }, @@ -44,7 +45,7 @@ function aggregateLinks(computation, node){ ); } -function combineAggregations(computation, node){ +function combineAggregations(computation, node) { combineMultiplierAggregator(node); node.data.overridenProps?.forEach(prop => { computeVariableProp(computation, node, prop); @@ -52,51 +53,51 @@ function combineAggregations(computation, node){ computeVariableProp(computation, node, node.data.definingProp); } -function computeVariableProp(computation, node, prop){ +function computeVariableProp(computation, node, prop) { if (!prop) return; // Combine damage multipliers in all props so that they can't be overridden - if (node.data.immunity){ + if (node.data.immunity) { prop.immunity = node.data.immunity; prop.immunities = node.data.immunities; } - if (node.data.resistance){ + if (node.data.resistance) { prop.resistance = node.data.resistance; prop.resistances = node.data.resistances; } - if (node.data.vulnerability){ + if (node.data.vulnerability) { prop.vulnerability = node.data.vulnerability; prop.vulnerabilities = node.data.vulnerabilities; } - if (prop.type === 'attribute'){ + if (prop.type === 'attribute') { computeVariableAsAttribute(computation, node, prop); - } else if (prop.type === 'skill'){ + } else if (prop.type === 'skill') { computeVariableAsSkill(computation, node, prop); - } else if (prop.type === 'constant'){ + } else if (prop.type === 'constant') { computeVariableAsConstant(computation, node, prop); - } else if (prop.type === 'class'){ + } else if (prop.type === 'class') { computeVariableAsClass(computation, node, prop); - } else if (prop.type === 'toggle'){ + } else if (prop.type === 'toggle') { computeVariableAsToggle(computation, node, prop); } } -function combineMultiplierAggregator(node){ +function combineMultiplierAggregator(node) { // get a reference to the aggregator const aggregator = node.data.multiplierAggregator; if (!aggregator) return; // Combine - if (aggregator.immunities?.length){ + if (aggregator.immunities?.length) { node.data.immunity = true; node.data.immunities = aggregator.immunities; } - if (aggregator.resistances?.length){ + if (aggregator.resistances?.length) { node.data.resistance = true; node.data.resistances = aggregator.resistances; } - if (aggregator.vulnerabilities?.length){ + if (aggregator.vulnerabilities?.length) { node.data.vulnerability = true; node.data.vulnerabilities = aggregator.vulnerabilities; } diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js index 3b1b987e..4bbf2e3e 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEffect.js @@ -1,6 +1,6 @@ import { pick } from 'lodash'; -export default function aggregateEffect({node, linkedNode, link}){ +export default function aggregateEffect({ node, linkedNode, link }) { if (link.data !== 'effect') return; // store the effect aggregator, its presence indicates that the variable is // targeted by effects @@ -38,21 +38,22 @@ export default function aggregateEffect({node, linkedNode, link}){ operation: linkedNode.data.operation, amount: effectAmount, type: linkedNode.data.type, + text: linkedNode.data.text, // ancestors: linkedNode.data.ancestors, }); // get a shorter reference to the aggregator document const aggregator = node.data.effectAggregator; // Get the result of the effect - const result = linkedNode.data.amount?.value; - // Skip aggregating if the result is not resolved completely - if (typeof result === 'string' || result === undefined) return; + let result = linkedNode.data.amount?.value; + if (typeof result !== 'number') result = undefined; + // Aggregate the effect based on its operation - switch(linkedNode.data.operation){ + switch (linkedNode.data.operation) { case 'base': // Take the largest base value - if (Number.isFinite(result)){ - if(Number.isFinite(aggregator.base)){ + if (Number.isFinite(result)) { + if (Number.isFinite(aggregator.base)) { aggregator.base = Math.max(aggregator.base, result); } else { aggregator.base = result; diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEventDefinition.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEventDefinition.js new file mode 100644 index 00000000..f36cf5b9 --- /dev/null +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/aggregateEventDefinition.js @@ -0,0 +1,22 @@ + +export default function aggregateEventDefinition({ node, linkedNode, link }) { + // Look at all event definition links + if (link.data !== 'eventDefinition') return; + + // Store which property is THE defining event and which are overridden + const prop = linkedNode.data; + // get current defining event + const definingEvent = node.data.definingEvent; + // Find the last defining event + if ( + !definingEvent || + prop.order > definingEvent.order + ) { + // override the current defining prop + if (definingEvent) definingEvent.overridden = true; + // set this prop as the new defining prop + node.data.definingEvent = prop; + } else { + prop.overridden = true; + } +} diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js index 4b455afa..3a617494 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/aggregate/index.js @@ -1,6 +1,7 @@ import definition from './aggregateDefinition.js'; import damageMultiplier from './aggregateDamageMultiplier.js'; import effect from './aggregateEffect.js'; +import eventDefinition from './aggregateEventDefinition.js'; import proficiency from './aggregateProficiency.js'; import classLevel from './aggregateClassLevel.js'; import inventory from './aggregateInventory.js'; @@ -10,6 +11,7 @@ export default Object.freeze({ damageMultiplier, definition, effect, + eventDefinition, inventory, proficiency, }); diff --git a/app/imports/api/files/UserImages.js b/app/imports/api/files/UserImages.js index fad4576b..9638d2f0 100644 --- a/app/imports/api/files/UserImages.js +++ b/app/imports/api/files/UserImages.js @@ -2,7 +2,7 @@ import { createS3FilesCollection } from '/imports/api/files/s3FileStorage.js'; const UserImages = createS3FilesCollection({ collectionName: 'userImages', - storagePath: Meteor.isDevelopment ? '/DiceCloud/userImages/' : 'assets/app/userImages', + storagePath: Meteor.isDevelopment ? '../../../../../fileStorage/userImages' : 'assets/app/userImages', onBeforeUpload(file) { // Allow upload files under 10MB if (file.size > 10485760) { diff --git a/app/imports/api/files/s3FileStorage.js b/app/imports/api/files/s3FileStorage.js index 3c3c540e..1ef22730 100644 --- a/app/imports/api/files/s3FileStorage.js +++ b/app/imports/api/files/s3FileStorage.js @@ -4,7 +4,9 @@ import { each, clone } from 'lodash'; import { Random } from 'meteor/random'; import { FilesCollection } from 'meteor/ostrio:files'; import stream from 'stream'; -import S3 from 'aws-sdk/clients/s3'; +if (Meteor.isServer) { + import S3 from '/imports/api/files/server/s3.js'; +} /* See fs-extra and graceful-fs NPM packages */ /* For better i/o performance */ @@ -21,7 +23,7 @@ Meteor.settings.useS3 = !!( s3Conf && s3Conf.key && s3Conf.secret && s3Conf.bucket && s3Conf.endpoint ); -const bound = Meteor.bindEnvironment((callback) => { +const bound = Meteor.bindEnvironment((callback) => { return callback(); }); @@ -43,14 +45,14 @@ if (Meteor.isServer && Meteor.settings.useS3) { } }); - createS3FilesCollection = function({ + createS3FilesCollection = function ({ collectionName, storagePath, onBeforeUpload, onAfterUpload, - debug = !Meteor.isProduction, + debug,// = !Meteor.isProduction, allowClientCode = false, - }){ + }) { const collection = new FilesCollection({ collectionName, storagePath, @@ -58,7 +60,7 @@ if (Meteor.isServer && Meteor.settings.useS3) { onAfterUpload(fileRef) { // Call the provided afterUpload hook first onAfterUpload?.(fileRef); - + // Start moving files to AWS:S3 // after fully received by the Meteor server @@ -128,19 +130,19 @@ if (Meteor.isServer && Meteor.settings.useS3) { }; if (http.request.headers.range) { - const vRef = fileRef.versions[version]; - let range = clone(http.request.headers.range); + const vRef = fileRef.versions[version]; + let range = clone(http.request.headers.range); const array = range.split(/bytes=([0-9]*)-([0-9]*)/); const start = parseInt(array[1]); - let end = parseInt(array[2]); + let end = parseInt(array[2]); if (isNaN(end)) { // Request data from AWS:S3 by small chunks - end = (start + this.chunkSize) - 1; + end = (start + this.chunkSize) - 1; if (end >= vRef.size) { - end = vRef.size - 1; + end = vRef.size - 1; } } - opts.Range = `bytes=${start}-${end}`; + opts.Range = `bytes=${start}-${end}`; http.request.headers.range = `bytes=${start}-${end}`; } @@ -198,9 +200,9 @@ if (Meteor.isServer && Meteor.settings.useS3) { _origRemove.call(this, search); }; - collection.readJSONFile = async function(file){ + collection.readJSONFile = async function (file) { // If there is the pipepath, use s3 to get the file - if (file?.versions?.original?.meta?.pipePath){ + if (file?.versions?.original?.meta?.pipePath) { const path = file.versions.original.meta.pipePath; const data = await s3.getObject({ Bucket: s3Conf.bucket, @@ -217,14 +219,14 @@ if (Meteor.isServer && Meteor.settings.useS3) { return collection; } } else { - createS3FilesCollection = function({ + createS3FilesCollection = function ({ collectionName, storagePath, onBeforeUpload, onAfterUpload, - debug = !Meteor.isProduction, + debug,// = !Meteor.isProduction, allowClientCode = false, - }){ + }) { const collection = new FilesCollection({ collectionName, storagePath, @@ -236,7 +238,7 @@ if (Meteor.isServer && Meteor.settings.useS3) { if (Meteor.isServer) { // Use the normal file system to read files - collection.readJSONFile = async function(file){ + collection.readJSONFile = async function (file) { const fileString = await fsp.readFile(file.path, 'utf8'); return JSON.parse(fileString); }; diff --git a/app/imports/api/files/server/s3.js b/app/imports/api/files/server/s3.js new file mode 100644 index 00000000..83719fa6 --- /dev/null +++ b/app/imports/api/files/server/s3.js @@ -0,0 +1,2 @@ +import S3 from 'aws-sdk/clients/s3'; +export default S3; diff --git a/app/imports/api/library/methods/copyLibraryNodeTo.js b/app/imports/api/library/methods/copyLibraryNodeTo.js new file mode 100644 index 00000000..ea9ce06f --- /dev/null +++ b/app/imports/api/library/methods/copyLibraryNodeTo.js @@ -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/client/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; diff --git a/app/imports/api/library/methods/duplicateLibraryNode.js b/app/imports/api/library/methods/duplicateLibraryNode.js index 107f3c6b..83f20106 100644 --- a/app/imports/api/library/methods/duplicateLibraryNode.js +++ b/app/imports/api/library/methods/duplicateLibraryNode.js @@ -12,11 +12,11 @@ import { reorderDocs } from '/imports/api/parenting/order.js'; var snackbar; if (Meteor.isClient) { snackbar = require( - '/imports/ui/components/snackbars/SnackbarQueue.js' + '/imports/client/ui/components/snackbars/SnackbarQueue.js' ).snackbar } -const DUPLICATE_CHILDREN_LIMIT = 50; +const DUPLICATE_CHILDREN_LIMIT = 500; const duplicateLibraryNode = new ValidatedMethod({ name: 'libraryNodes.duplicate', @@ -28,7 +28,7 @@ const duplicateLibraryNode = new ValidatedMethod({ }).validator(), mixins: [RateLimiterMixin], rateLimit: { - numRequests: 5, + numRequests: 1, timeInterval: 5000, }, run({ _id }) { diff --git a/app/imports/api/library/methods/index.js b/app/imports/api/library/methods/index.js index 1b566bc5..e67eec8a 100644 --- a/app/imports/api/library/methods/index.js +++ b/app/imports/api/library/methods/index.js @@ -1,2 +1,3 @@ +import '/imports/api/library/methods/copyLibraryNodeTo.js'; import '/imports/api/library/methods/duplicateLibraryNode.js'; import '/imports/api/library/methods/updateReferenceNode.js'; diff --git a/app/imports/ui/log/LogComponent.vue b/app/imports/api/log/LogComponent.vue similarity index 100% rename from app/imports/ui/log/LogComponent.vue rename to app/imports/api/log/LogComponent.vue diff --git a/app/imports/api/parenting/nodesToTree.js b/app/imports/api/parenting/nodesToTree.js index b8589967..3e4b5733 100644 --- a/app/imports/api/parenting/nodesToTree.js +++ b/app/imports/api/parenting/nodesToTree.js @@ -1,10 +1,10 @@ import { union, difference, sortBy, findLast } from 'lodash'; -export function nodeArrayToTree(nodes){ +export function nodeArrayToTree(nodes) { // Store a dict and list of all the nodes let nodeIndex = {}; let nodeList = []; - nodes.forEach( node => { + nodes.forEach(node => { let treeNode = { node: node, children: [], @@ -20,7 +20,7 @@ export function nodeArrayToTree(nodes){ treeNode.node.ancestors, ancestor => !!nodeIndex[ancestor.id] ); - if (ancestorInForest){ + if (ancestorInForest) { nodeIndex[ancestorInForest.id].children.push(treeNode); } else { forest.push(treeNode); @@ -33,13 +33,13 @@ export function nodeArrayToTree(nodes){ export default function nodesToTree({ collection, ancestorId, filter, options = {}, includeFilteredDocAncestors = false, includeFilteredDocDescendants = false -}){ +}) { // Setup the filter let collectionFilter = { 'ancestors.id': ancestorId, - 'removed': {$ne: true}, + 'removed': { $ne: true }, }; - if (filter){ + if (filter) { collectionFilter = { ...collectionFilter, ...filter, @@ -49,7 +49,7 @@ export default function nodesToTree({ let collectionSort = { order: 1 }; - if (options && options.sort){ + if (options && options.sort) { collectionSort = { ...collectionSort, ...options.sort, @@ -58,7 +58,7 @@ export default function nodesToTree({ let collectionOptions = { sort: collectionSort, } - if (options){ + if (options) { collectionOptions = { ...collectionOptions, ...options, @@ -74,10 +74,10 @@ export default function nodesToTree({ let ancestors = []; let ancestorIds = []; let docIds = []; - if (filter && (includeFilteredDocAncestors || includeFilteredDocDescendants)){ + if (filter && (includeFilteredDocAncestors || includeFilteredDocDescendants)) { docIds = docs.map(doc => doc._id) } - if (filter && includeFilteredDocAncestors){ + if (filter && includeFilteredDocAncestors) { // Add all ancestor ids to an array docs.forEach(doc => { ancestorIds = union(ancestorIds, doc.ancestors.map(ref => ref.id)); @@ -86,19 +86,19 @@ export default function nodesToTree({ ancestorIds = difference(ancestorIds, docIds); // Get the docs from the collection, don't worry about `removed` docs, // if their descendant was not removed, neither are they - ancestors = collection.find({_id: {$in: ancestorIds}}).map(doc => { + ancestors = collection.find({ _id: { $in: ancestorIds } }).map(doc => { // Mark that the nodes are ancestors of the found nodes doc._ancestorOfMatchedDocument = true; return doc; }); } let descendants = []; - if (filter && includeFilteredDocDescendants){ + if (filter && includeFilteredDocDescendants) { let exludeIds = union(ancestorIds, docIds); descendants = collection.find({ - '_id': {$nin: exludeIds}, - 'ancestors.id': {$in: docIds}, - 'removed': {$ne: true}, + '_id': { $nin: exludeIds }, + 'ancestors.id': { $in: docIds }, + 'removed': { $ne: true }, }).map(doc => { // Mark that the nodes are descendants of the found nodes doc._descendantOfMatchedDocument = true; diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index 1ff4d6fd..d4a5c4b4 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; import { storedIconsSchema } from '/imports/api/icons/Icons.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js'; /* * Actions are things a character can do @@ -24,9 +25,17 @@ let ActionSchema = createPropertySchema({ // long actions take longer than 1 round to cast actionType: { type: String, - allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long'], + allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long', 'event'], defaultValue: 'action', }, + // If the action type is an event, what is the variable name of that event? + variableName: { + type: String, + optional: true, + regEx: VARIABLE_NAME_REGEX, + min: 2, + max: STORAGE_LIMITS.variableName, + }, // Who is the action directed at target: { type: String, @@ -56,8 +65,10 @@ let ActionSchema = createPropertySchema({ // How this action's uses are reset automatically reset: { type: String, - allowedValues: ['longRest', 'shortRest'], optional: true, + regEx: VARIABLE_NAME_REGEX, + min: 2, + max: STORAGE_LIMITS.variableName, }, // Resources resources: { @@ -74,7 +85,7 @@ let ActionSchema = createPropertySchema({ 'resources.itemsConsumed.$._id': { type: String, regEx: SimpleSchema.RegEx.Id, - autoValue(){ + autoValue() { if (!this.isSet) return Random.id(); } }, @@ -101,7 +112,7 @@ let ActionSchema = createPropertySchema({ 'resources.attributesConsumed.$._id': { type: String, regEx: SimpleSchema.RegEx.Id, - autoValue(){ + autoValue() { if (!this.isSet) return Random.id(); } }, @@ -151,6 +162,12 @@ const ComputedOnlyActionSchema = createPropertySchema({ optional: true, removeBeforeCompute: true, }, + // Denormalised tag if event is overridden by one with the same variable name + overridden: { + type: Boolean, + optional: true, + removeBeforeCompute: true, + }, // Resources resources: { type: Object, @@ -218,4 +235,4 @@ const ComputedActionSchema = new SimpleSchema() .extend(ActionSchema) .extend(ComputedOnlyActionSchema); -export { ActionSchema, ComputedOnlyActionSchema, ComputedActionSchema}; +export { ActionSchema, ComputedOnlyActionSchema, ComputedActionSchema }; diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index d486d5f9..94e5d657 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -28,8 +28,7 @@ let AttributeSchema = createPropertySchema({ 'stat', // Speed, Armor Class 'modifier', // Proficiency Bonus, displayed as +x 'hitDice', // d12 hit dice - 'healthBar', // Hitpoints, Temporary Hitpoints, can take damage - 'bar', // Displayed as a health bar, can't take damage + 'healthBar', // Hitpoints, Temporary Hitpoints 'resource', // Rages, sorcery points 'spellSlot', // Level 1, 2, 3... spell slots 'utility', // Aren't displayed, Jump height, Carry capacity @@ -69,6 +68,16 @@ let AttributeSchema = createPropertySchema({ type: Boolean, 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: { type: SimpleSchema.Integer, optional: true, @@ -107,11 +116,21 @@ let AttributeSchema = createPropertySchema({ type: Boolean, optional: true, }, + hideWhenTotalZero: { + type: Boolean, + optional: true, + }, + hideWhenValueZero: { + type: Boolean, + optional: true, + }, // Automatically zero the adjustment on these conditions reset: { type: String, optional: true, - allowedValues: ['shortRest', 'longRest'], + regEx: VARIABLE_NAME_REGEX, + min: 2, + max: STORAGE_LIMITS.variableName, }, }); diff --git a/app/imports/api/properties/Folders.js b/app/imports/api/properties/Folders.js index 45055ce1..66a10355 100644 --- a/app/imports/api/properties/Folders.js +++ b/app/imports/api/properties/Folders.js @@ -7,6 +7,29 @@ let FolderSchema = new createPropertySchema({ name: { type: String, max: STORAGE_LIMITS.name, + optional: true, + }, + groupStats: { + type: Boolean, + optional: true, + }, + hideStatsGroup: { + type: Boolean, + optional: true, + }, + tab: { + type: String, + optional: true, + allowedValues: [ + 'stats', 'features', 'actions', 'spells', 'inventory', 'journal', 'build' + ], + }, + location: { + type: String, + optional: true, + allowedValues: [ + 'start', 'events', 'stats', 'skills', 'proficiencies', 'end' + ], }, }); diff --git a/app/imports/api/properties/SpellLists.js b/app/imports/api/properties/SpellLists.js index 01434ea2..285832eb 100644 --- a/app/imports/api/properties/SpellLists.js +++ b/app/imports/api/properties/SpellLists.js @@ -17,6 +17,12 @@ let SpellListSchema = createPropertySchema({ type: 'fieldToCompute', optional: true, }, + // The variable name of the ability this spell relies on + ability: { + type: String, + optional: true, + max: STORAGE_LIMITS.variableName, + }, // Calculation of The attack roll bonus used by spell attacks in this list attackRollBonus: { type: 'fieldToCompute', @@ -38,6 +44,12 @@ const ComputedOnlySpellListSchema = createPropertySchema({ type: 'computedOnlyField', optional: true, }, + // Computed value determined by the ability + abilityMod: { + type: SimpleSchema.Integer, + optional: true, + removeBeforeCompute: true, + }, attackRollBonus: { type: 'computedOnlyField', optional: true, diff --git a/app/imports/api/sharing/SharingSchema.js b/app/imports/api/sharing/SharingSchema.js index 2a4f87c3..629f90bb 100644 --- a/app/imports/api/sharing/SharingSchema.js +++ b/app/imports/api/sharing/SharingSchema.js @@ -33,6 +33,10 @@ let SharingSchema = new SimpleSchema({ defaultValue: false, index: 1, }, + readersCanCopy: { + type: Boolean, + optional: true, + }, }); export default SharingSchema; diff --git a/app/imports/api/sharing/sharing.js b/app/imports/api/sharing/sharing.js index f6c8d8b7..7062ff40 100644 --- a/app/imports/api/sharing/sharing.js +++ b/app/imports/api/sharing/sharing.js @@ -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({ name: 'sharing.updateUserSharePermissions', validate: new SimpleSchema({ @@ -129,4 +149,4 @@ const transferOwnership = new ValidatedMethod({ }, }); -export { setPublic, updateUserSharePermissions, transferOwnership }; +export { setPublic, setReadersCanCopy, updateUserSharePermissions, transferOwnership }; diff --git a/app/imports/api/sharing/sharingPermissions.js b/app/imports/api/sharing/sharingPermissions.js index 58629c62..c01675f4 100644 --- a/app/imports/api/sharing/sharingPermissions.js +++ b/app/imports/api/sharing/sharingPermissions.js @@ -1,24 +1,25 @@ -import { _ } from 'meteor/underscore'; +import { includes } from 'lodash'; import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; -function assertIdValid(userId){ - if (!userId || typeof userId !== 'string'){ +function assertIdValid(userId) { + if (!userId || typeof userId !== 'string') { throw new Meteor.Error('Permission denied', 'No user ID. Are you logged in?'); } } -function assertdocExists(doc){ - if (!doc){ +function assertdocExists(doc) { + if (!doc) { throw new Meteor.Error('Permission denied', 'Permission denied: No such document exists'); } } -export function assertOwnership(doc, userId){ +export function assertOwnership(doc, userId) { assertIdValid(userId); assertdocExists(doc); - if (doc.owner === userId ){ + + if (doc.owner === userId) { return true; } else { throw new Meteor.Error('Permission denied', @@ -37,21 +38,20 @@ export function assertEditPermission(doc, userId) { assertdocExists(doc); const user = Meteor.users.findOne(userId, { fields: { - 'services.patreon': 1, 'roles': 1, } }); // Admin override - if (user.roles && user.roles.includes('admin')){ + 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) - ){ + includes(doc.writers, userId) + ) { return true; } else { 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); - 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 || + includes(doc.writers, userId) + ) { + return true; + } else if ( + (includes(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]); } else { return doc; @@ -74,11 +111,22 @@ function getRoot(doc){ * * 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); 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) { assertdocExists(doc); if (doc.public) return true; @@ -86,19 +134,19 @@ export function assertViewPermission(doc, userId) { if ( doc.owner === userId || - _.contains(doc.readers, userId) || - _.contains(doc.writers, userId) - ){ + includes(doc.readers, userId) || + includes(doc.writers, userId) + ) { return true; } else { - + // Admin override const user = Meteor.users.findOne(userId, { fields: { 'roles': 1, } }); - if (user.roles && user.roles.includes('admin')){ + if (user.roles && user.roles.includes('admin')) { return true; } @@ -113,20 +161,20 @@ export function assertViewPermission(doc, userId) { * * 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); assertViewPermission(root, userId); } -export function assertAdmin(userId){ +export function assertAdmin(userId) { assertIdValid(userId); - let user = Meteor.users.findOne(userId, {fields: {roles: 1}}); - if (!user){ + let user = Meteor.users.findOne(userId, { fields: { roles: 1 } }); + if (!user) { throw new Meteor.Error('Permission denied', 'UserId does not match any existing user'); } let isAdmin = user.roles && user.roles.includes('admin') - if (!isAdmin){ + if (!isAdmin) { throw new Meteor.Error('Permission denied', 'User does not have the admin role'); } diff --git a/app/imports/ui/tabletop/CharacterSheetDialog.vue b/app/imports/api/tabletop/CharacterSheetDialog.vue similarity index 100% rename from app/imports/ui/tabletop/CharacterSheetDialog.vue rename to app/imports/api/tabletop/CharacterSheetDialog.vue diff --git a/app/imports/ui/tabletop/map/TabletopMap.vue b/app/imports/api/tabletop/map/TabletopMap.vue similarity index 100% rename from app/imports/ui/tabletop/map/TabletopMap.vue rename to app/imports/api/tabletop/map/TabletopMap.vue diff --git a/app/imports/ui/tabletop/three/OrbitControls.js b/app/imports/api/tabletop/three/OrbitControls.js similarity index 100% rename from app/imports/ui/tabletop/three/OrbitControls.js rename to app/imports/api/tabletop/three/OrbitControls.js diff --git a/app/imports/ui/utility/numberToSignedString.js b/app/imports/api/utility/numberToSignedString.js similarity index 100% rename from app/imports/ui/utility/numberToSignedString.js rename to app/imports/api/utility/numberToSignedString.js diff --git a/app/imports/ui/components/CardHighlight.vue b/app/imports/client/ui/components/CardHighlight.vue similarity index 100% rename from app/imports/ui/components/CardHighlight.vue rename to app/imports/client/ui/components/CardHighlight.vue diff --git a/app/imports/ui/components/CoinValue.vue b/app/imports/client/ui/components/CoinValue.vue similarity index 89% rename from app/imports/ui/components/CoinValue.vue rename to app/imports/client/ui/components/CoinValue.vue index b13aa9ef..d5ccc0f8 100644 --- a/app/imports/ui/components/CoinValue.vue +++ b/app/imports/client/ui/components/CoinValue.vue @@ -19,7 +19,7 @@ diff --git a/app/imports/ui/components/RollPopup.vue b/app/imports/client/ui/components/RollPopup.vue similarity index 97% rename from app/imports/ui/components/RollPopup.vue rename to app/imports/client/ui/components/RollPopup.vue index 0167bc1d..f9f20f66 100644 --- a/app/imports/ui/components/RollPopup.vue +++ b/app/imports/client/ui/components/RollPopup.vue @@ -64,7 +64,7 @@ + + + + diff --git a/app/imports/ui/components/global/IconPicker.vue b/app/imports/client/ui/components/global/IconPicker.vue similarity index 94% rename from app/imports/ui/components/global/IconPicker.vue rename to app/imports/client/ui/components/global/IconPicker.vue index 13ef7954..02a4f878 100644 --- a/app/imports/ui/components/global/IconPicker.vue +++ b/app/imports/client/ui/components/global/IconPicker.vue @@ -78,8 +78,8 @@ diff --git a/app/imports/ui/components/global/SmartSwitch.vue b/app/imports/client/ui/components/global/SmartSwitch.vue similarity index 77% rename from app/imports/ui/components/global/SmartSwitch.vue rename to app/imports/client/ui/components/global/SmartSwitch.vue index 716e0998..a0ce5668 100644 --- a/app/imports/ui/components/global/SmartSwitch.vue +++ b/app/imports/client/ui/components/global/SmartSwitch.vue @@ -10,7 +10,7 @@ + + diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue new file mode 100644 index 00000000..b5254836 --- /dev/null +++ b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -0,0 +1,629 @@ + + + + + diff --git a/app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/TreeTab.vue similarity index 89% rename from app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue rename to app/imports/client/ui/creature/character/characterSheetTabs/TreeTab.vue index ae2988fc..4d92e0b1 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/TreeTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/TreeTab.vue @@ -54,10 +54,10 @@ + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue new file mode 100644 index 00000000..1e87f3f4 --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/app/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue similarity index 54% rename from app/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue rename to app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue index a5e307cc..a8094314 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedSpells.vue @@ -1,38 +1,53 @@ + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue new file mode 100644 index 00000000..abfbfeef --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue @@ -0,0 +1,247 @@ + + + + + + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue new file mode 100644 index 00000000..2bb4adb9 --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue new file mode 100644 index 00000000..386aabcd --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue new file mode 100644 index 00000000..4be30e77 --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue new file mode 100644 index 00000000..404da2aa --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpell.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpell.vue new file mode 100644 index 00000000..bc1fbc75 --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpell.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue new file mode 100644 index 00000000..4dbf8d14 --- /dev/null +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue @@ -0,0 +1,45 @@ + + + \ No newline at end of file diff --git a/app/imports/ui/creature/creatureList/ArchiveButton.vue b/app/imports/client/ui/creature/creatureList/ArchiveButton.vue similarity index 100% rename from app/imports/ui/creature/creatureList/ArchiveButton.vue rename to app/imports/client/ui/creature/creatureList/ArchiveButton.vue diff --git a/app/imports/ui/creature/creatureList/CharacterListToolbarItems.vue b/app/imports/client/ui/creature/creatureList/CharacterListToolbarItems.vue similarity index 53% rename from app/imports/ui/creature/creatureList/CharacterListToolbarItems.vue rename to app/imports/client/ui/creature/creatureList/CharacterListToolbarItems.vue index df427b85..6886b492 100644 --- a/app/imports/ui/creature/creatureList/CharacterListToolbarItems.vue +++ b/app/imports/client/ui/creature/creatureList/CharacterListToolbarItems.vue @@ -1,13 +1,13 @@ \ No newline at end of file diff --git a/app/imports/client/ui/docs/DocCard.vue b/app/imports/client/ui/docs/DocCard.vue new file mode 100644 index 00000000..7ad6c8bd --- /dev/null +++ b/app/imports/client/ui/docs/DocCard.vue @@ -0,0 +1,57 @@ + + + + + \ No newline at end of file diff --git a/app/imports/client/ui/docs/DocEditForm.vue b/app/imports/client/ui/docs/DocEditForm.vue new file mode 100644 index 00000000..42d1ae54 --- /dev/null +++ b/app/imports/client/ui/docs/DocEditForm.vue @@ -0,0 +1,213 @@ + + + \ No newline at end of file diff --git a/app/imports/client/ui/docs/DocListItem.vue b/app/imports/client/ui/docs/DocListItem.vue new file mode 100644 index 00000000..3b7f1675 --- /dev/null +++ b/app/imports/client/ui/docs/DocListItem.vue @@ -0,0 +1,30 @@ + + + diff --git a/app/imports/client/ui/docs/DocToolbar.vue b/app/imports/client/ui/docs/DocToolbar.vue new file mode 100644 index 00000000..56c77859 --- /dev/null +++ b/app/imports/client/ui/docs/DocToolbar.vue @@ -0,0 +1,55 @@ + + + diff --git a/app/imports/client/ui/docs/DocViewer.vue b/app/imports/client/ui/docs/DocViewer.vue new file mode 100644 index 00000000..661cf158 --- /dev/null +++ b/app/imports/client/ui/docs/DocViewer.vue @@ -0,0 +1,136 @@ + + + \ No newline at end of file diff --git a/app/imports/client/ui/docs/getDocLink.js b/app/imports/client/ui/docs/getDocLink.js new file mode 100644 index 00000000..0d3be2cb --- /dev/null +++ b/app/imports/client/ui/docs/getDocLink.js @@ -0,0 +1,9 @@ +export default function getDocLink(doc, urlName) { + if (!urlName) urlName = doc.urlName; + const address = ['/docs']; + doc.ancestors?.forEach(a => { + address.push(a.urlName); + }); + address.push(urlName); + return address.join('/'); +} \ No newline at end of file diff --git a/app/imports/ui/files/ArchiveFileCard.vue b/app/imports/client/ui/files/ArchiveFileCard.vue similarity index 96% rename from app/imports/ui/files/ArchiveFileCard.vue rename to app/imports/client/ui/files/ArchiveFileCard.vue index d8ec1f25..f1de6d3b 100644 --- a/app/imports/ui/files/ArchiveFileCard.vue +++ b/app/imports/client/ui/files/ArchiveFileCard.vue @@ -33,7 +33,7 @@ diff --git a/app/imports/ui/layouts/SingleCardLayout.vue b/app/imports/client/ui/layouts/SingleCardLayout.vue similarity index 100% rename from app/imports/ui/layouts/SingleCardLayout.vue rename to app/imports/client/ui/layouts/SingleCardLayout.vue diff --git a/app/imports/ui/library/InsertLibraryNodeButton.vue b/app/imports/client/ui/library/InsertLibraryNodeButton.vue similarity index 100% rename from app/imports/ui/library/InsertLibraryNodeButton.vue rename to app/imports/client/ui/library/InsertLibraryNodeButton.vue diff --git a/app/imports/ui/library/LibraryAndNode.vue b/app/imports/client/ui/library/LibraryAndNode.vue similarity index 87% rename from app/imports/ui/library/LibraryAndNode.vue rename to app/imports/client/ui/library/LibraryAndNode.vue index aa6601db..781fcac8 100644 --- a/app/imports/ui/library/LibraryAndNode.vue +++ b/app/imports/client/ui/library/LibraryAndNode.vue @@ -80,18 +80,18 @@ diff --git a/app/imports/ui/library/MoveLibraryNodeDialog.vue b/app/imports/client/ui/library/MoveLibraryNodeDialog.vue similarity index 71% rename from app/imports/ui/library/MoveLibraryNodeDialog.vue rename to app/imports/client/ui/library/MoveLibraryNodeDialog.vue index ce412813..7151391d 100644 --- a/app/imports/ui/library/MoveLibraryNodeDialog.vue +++ b/app/imports/client/ui/library/MoveLibraryNodeDialog.vue @@ -16,20 +16,26 @@ color="primary" @click="$store.dispatch('popDialogStack', node._id)" > - Move + {{ action || 'Move' }} + + \ No newline at end of file diff --git a/app/imports/ui/pages/Documentation.vue b/app/imports/client/ui/pages/Documentation.vue similarity index 96% rename from app/imports/ui/pages/Documentation.vue rename to app/imports/client/ui/pages/Documentation.vue index d8c83c53..0147de1a 100644 --- a/app/imports/ui/pages/Documentation.vue +++ b/app/imports/client/ui/pages/Documentation.vue @@ -35,7 +35,7 @@ + + diff --git a/app/imports/ui/properties/components/actions/ItemConsumedView.vue b/app/imports/client/ui/properties/components/actions/ItemConsumedView.vue similarity index 96% rename from app/imports/ui/properties/components/actions/ItemConsumedView.vue rename to app/imports/client/ui/properties/components/actions/ItemConsumedView.vue index 3363c5a4..f0eae75a 100644 --- a/app/imports/ui/properties/components/actions/ItemConsumedView.vue +++ b/app/imports/client/ui/properties/components/actions/ItemConsumedView.vue @@ -83,7 +83,7 @@ diff --git a/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue b/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue new file mode 100644 index 00000000..c1976688 --- /dev/null +++ b/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue @@ -0,0 +1,97 @@ + + + + + \ No newline at end of file diff --git a/app/imports/ui/properties/components/attributes/AttributeEffect.vue b/app/imports/client/ui/properties/components/attributes/AttributeEffect.vue similarity index 96% rename from app/imports/ui/properties/components/attributes/AttributeEffect.vue rename to app/imports/client/ui/properties/components/attributes/AttributeEffect.vue index 5725a3ef..43f4e212 100644 --- a/app/imports/ui/properties/components/attributes/AttributeEffect.vue +++ b/app/imports/client/ui/properties/components/attributes/AttributeEffect.vue @@ -41,8 +41,8 @@ + + diff --git a/app/imports/client/ui/properties/components/attributes/ResourceCardContent.vue b/app/imports/client/ui/properties/components/attributes/ResourceCardContent.vue new file mode 100644 index 00000000..1f9de91f --- /dev/null +++ b/app/imports/client/ui/properties/components/attributes/ResourceCardContent.vue @@ -0,0 +1,89 @@ + + + + + \ No newline at end of file diff --git a/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue b/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue new file mode 100644 index 00000000..3abae994 --- /dev/null +++ b/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/app/imports/ui/properties/components/attributes/SpellSlotListTile.vue b/app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue similarity index 95% rename from app/imports/ui/properties/components/attributes/SpellSlotListTile.vue rename to app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue index 584fc765..26b5b4da 100644 --- a/app/imports/ui/properties/components/attributes/SpellSlotListTile.vue +++ b/app/imports/client/ui/properties/components/attributes/SpellSlotListTile.vue @@ -53,7 +53,7 @@ \ No newline at end of file diff --git a/app/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue b/app/imports/client/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue similarity index 100% rename from app/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue rename to app/imports/client/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue diff --git a/app/imports/ui/properties/components/effects/InlineEffect.vue b/app/imports/client/ui/properties/components/effects/InlineEffect.vue similarity index 97% rename from app/imports/ui/properties/components/effects/InlineEffect.vue rename to app/imports/client/ui/properties/components/effects/InlineEffect.vue index 9ac97e38..611ada43 100644 --- a/app/imports/ui/properties/components/effects/InlineEffect.vue +++ b/app/imports/client/ui/properties/components/effects/InlineEffect.vue @@ -32,7 +32,7 @@ + + \ No newline at end of file diff --git a/app/imports/client/ui/properties/components/folders/folderGroupComponents/ActionGroupComponent.vue b/app/imports/client/ui/properties/components/folders/folderGroupComponents/ActionGroupComponent.vue new file mode 100644 index 00000000..acf0d8d5 --- /dev/null +++ b/app/imports/client/ui/properties/components/folders/folderGroupComponents/ActionGroupComponent.vue @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/app/imports/client/ui/properties/components/folders/folderGroupComponents/AttributeGroupComponent.vue b/app/imports/client/ui/properties/components/folders/folderGroupComponents/AttributeGroupComponent.vue new file mode 100644 index 00000000..a2ef661d --- /dev/null +++ b/app/imports/client/ui/properties/components/folders/folderGroupComponents/AttributeGroupComponent.vue @@ -0,0 +1,95 @@ + + + + + \ No newline at end of file diff --git a/app/imports/client/ui/properties/components/folders/propertyComponentIndex.js b/app/imports/client/ui/properties/components/folders/propertyComponentIndex.js new file mode 100644 index 00000000..de9ce052 --- /dev/null +++ b/app/imports/client/ui/properties/components/folders/propertyComponentIndex.js @@ -0,0 +1,61 @@ +import action from '/imports/client/ui/properties/components/folders/folderGroupComponents/ActionGroupComponent.vue'; +//import adjustment from ''; +import attribute from './folderGroupComponents/AttributeGroupComponent.vue'; +import buff from '/imports/client/ui/properties/components/buffs/BuffListItem.vue'; +//import buffRemover from ''; +//import branch from ''; +//import constant from ''; +import container from '/imports/client/ui/properties/components/inventory/ContainerCard.vue'; +//import classComponent from ''; +//import classLevel from ''; +//import damage from ''; +//import damageMultiplier from ''; +//import effect from ''; +import feature from '/imports/client/ui/properties/components/features/FeatureCard.vue'; +// import folder from ''; +import item from '/imports/client/ui/properties/components/inventory/ItemListTile.vue'; +import note from '/imports/client/ui/properties/components/persona/NoteCard.vue'; +//import pointBuy from ''; +//import proficiency from ''; +//import propertySlot from ''; +//import reference from ''; +//import roll from ''; +//import savingThrow from ''; +import skill from '/imports/client/ui/properties/components/skills/SkillListTile.vue'; +//import slotFiller from ''; +//import spellList from ''; +//import spell from ''; +import toggle from '/imports/client/ui/properties/components/toggles/ToggleCard.vue'; +//import trigger from ''; + +export default { + action, + //adjustment, + attribute, + buff, + //buffRemover, + //branch, + //constant, + container, + //class: classComponent, + //classLevel, + //damage, + //damageMultiplier, + //effect, + feature, + //folder, + item, + note, + //pointBuy, + //proficiency, + //propertySlot, + //reference, + //roll, + //savingThrow, + skill, + //slotFiller, + //spellList, + //spell, + toggle, + //trigger, +}; diff --git a/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js b/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js new file mode 100644 index 00000000..5097143e --- /dev/null +++ b/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js @@ -0,0 +1,53 @@ +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js' +import FolderGroupCard from '/imports/client/ui/properties/components/folders/FolderGroupCard.vue'; +import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js'; +import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js'; + +function getFolders(creatureId, tab, location) { + return CreatureProperties.find({ + 'ancestors.id': creatureId, + groupStats: true, + inactive: { $ne: true }, + removed: { $ne: true }, + tab, + location, + }); +} + +export default { + components: { + FolderGroupCard, + }, + meteor: { + startFolders() { + return getFolders(this.creatureId, this.tabName, 'start'); + }, + endFolders() { + return getFolders(this.creatureId, this.tabName, 'end'); + }, + }, + methods: { + clickProperty({ _id }) { + this.$store.commit('pushDialogStack', { + component: 'creature-property-dialog', + elementId: `${_id}`, + data: { _id }, + }); + }, + clickTreeProperty({ _id }) { + this.$store.commit('pushDialogStack', { + component: 'creature-property-dialog', + elementId: `tree-node-${_id}`, + data: { _id }, + }); + }, + softRemove(_id) { + softRemoveProperty.call({ _id }, error => { + if (error) { + snackbar({ text: error.reason || error.message || error.toString() }); + console.error(error); + } + }); + }, + } +}; \ No newline at end of file diff --git a/app/imports/ui/properties/components/inventory/ContainerCard.vue b/app/imports/client/ui/properties/components/inventory/ContainerCard.vue similarity index 91% rename from app/imports/ui/properties/components/inventory/ContainerCard.vue rename to app/imports/client/ui/properties/components/inventory/ContainerCard.vue index 0843dcfd..b2d6de9f 100644 --- a/app/imports/ui/properties/components/inventory/ContainerCard.vue +++ b/app/imports/client/ui/properties/components/inventory/ContainerCard.vue @@ -43,10 +43,10 @@ diff --git a/app/imports/ui/properties/forms/ItemConsumedForm.vue b/app/imports/client/ui/properties/forms/ItemConsumedForm.vue similarity index 89% rename from app/imports/ui/properties/forms/ItemConsumedForm.vue rename to app/imports/client/ui/properties/forms/ItemConsumedForm.vue index d65fd6bc..dad6139b 100644 --- a/app/imports/ui/properties/forms/ItemConsumedForm.vue +++ b/app/imports/client/ui/properties/forms/ItemConsumedForm.vue @@ -31,7 +31,7 @@ diff --git a/app/imports/ui/properties/forms/ProficiencyForm.vue b/app/imports/client/ui/properties/forms/ProficiencyForm.vue similarity index 82% rename from app/imports/ui/properties/forms/ProficiencyForm.vue rename to app/imports/client/ui/properties/forms/ProficiencyForm.vue index 4fcf2825..a8a18d09 100644 --- a/app/imports/ui/properties/forms/ProficiencyForm.vue +++ b/app/imports/client/ui/properties/forms/ProficiencyForm.vue @@ -48,9 +48,9 @@ diff --git a/app/imports/ui/properties/forms/ToggleForm.vue b/app/imports/client/ui/properties/forms/ToggleForm.vue similarity index 97% rename from app/imports/ui/properties/forms/ToggleForm.vue rename to app/imports/client/ui/properties/forms/ToggleForm.vue index 5f98729c..3a9f80fe 100644 --- a/app/imports/ui/properties/forms/ToggleForm.vue +++ b/app/imports/client/ui/properties/forms/ToggleForm.vue @@ -94,7 +94,7 @@ diff --git a/app/imports/ui/properties/treeNodeViews/SavingThrowTreeNode.vue b/app/imports/client/ui/properties/treeNodeViews/SavingThrowTreeNode.vue similarity index 89% rename from app/imports/ui/properties/treeNodeViews/SavingThrowTreeNode.vue rename to app/imports/client/ui/properties/treeNodeViews/SavingThrowTreeNode.vue index 10767f99..63bbdbe5 100644 --- a/app/imports/ui/properties/treeNodeViews/SavingThrowTreeNode.vue +++ b/app/imports/client/ui/properties/treeNodeViews/SavingThrowTreeNode.vue @@ -17,7 +17,7 @@ + + diff --git a/app/imports/ui/tabletop/TabletopRightDrawer.vue b/app/imports/client/ui/tabletop/TabletopRightDrawer.vue similarity index 87% rename from app/imports/ui/tabletop/TabletopRightDrawer.vue rename to app/imports/client/ui/tabletop/TabletopRightDrawer.vue index 9d5fc55f..4a82217d 100644 --- a/app/imports/ui/tabletop/TabletopRightDrawer.vue +++ b/app/imports/client/ui/tabletop/TabletopRightDrawer.vue @@ -10,7 +10,7 @@ - - diff --git a/app/imports/ui/dialogStack/DialogComponentIndex.js b/app/imports/ui/dialogStack/DialogComponentIndex.js deleted file mode 100644 index 58724cb3..00000000 --- a/app/imports/ui/dialogStack/DialogComponentIndex.js +++ /dev/null @@ -1,74 +0,0 @@ -// Load commonly used dialogs immediately -import AddCreaturePropertyDialog from '/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue'; -import CharacterCreationDialog from '/imports/ui/creature/character/CharacterCreationDialog.vue'; -import CastSpellWithSlotDialog from '/imports/ui/properties/components/spells/CastSpellWithSlotDialog.vue'; -import CreatureFormDialog from '/imports/ui/creature/CreatureFormDialog.vue'; -import CreaturePropertyCreationDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue'; -import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'; -import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'; -import CreatureRootDialog from '/imports/ui/creature/character/CreatureRootDialog.vue'; -import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue'; -import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue'; -import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue'; -import HelpDialog from '/imports/ui/dialogStack/HelpDialog.vue'; -import LevelUpDialog from '/imports/ui/creature/slots/LevelUpDialog.vue'; -import SelectLibraryNodeDialog from '/imports/ui/library/SelectLibraryNodeDialog.vue'; -import SlotFillDialog from '/imports/ui/creature/slots/SlotFillDialog.vue'; -import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue'; -import TransferOwnershipDialog from '/imports/ui/sharing/TransferOwnershipDialog.vue'; - -// Lazily load less common dialogs -const ArchiveDialog = () => import('/imports/ui/creature/archive/ArchiveDialog.vue'); -const CastSpellWithSlotDialog = () => import('/imports/ui/properties/components/spells/CastSpellWithSlotDialog.vue'); -const CharacterSheetDialog = () => import('/imports/ui/tabletop/CharacterSheetDialog.vue'); -const CreatureFormDialog = () => import('/imports/ui/creature/CreatureFormDialog.vue'); -const CreaturePropertyCreationDialog = () => import('/imports/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue'); -const CreaturePropertyDialog = () => import('/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'); -const CreaturePropertyFromLibraryDialog = () => import('/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'); -const DeleteConfirmationDialog = () => import('/imports/ui/dialogStack/DeleteConfirmationDialog.vue'); -const DeleteUserAccountDialog = () => import('/imports/ui/user/DeleteUserAccountDialog.vue'); -const InviteDialog = () => import('/imports/ui/user/InviteDialog.vue'); -const LibraryCollectionCreationDialog = () => import('/imports/ui/library/LibraryCollectionCreationDialog.vue'); -const LibraryCollectionEditDialog = () => import('/imports/ui/library/LibraryCollectionEditDialog.vue'); -const LibraryCreationDialog = () => import('/imports/ui/library/LibraryCreationDialog.vue'); -const LibraryEditDialog = () => import('/imports/ui/library/LibraryEditDialog.vue'); -const LibraryNodeCreationDialog = () => import('/imports/ui/library/LibraryNodeCreationDialog.vue'); -const LibraryNodeDialog = () => import('/imports/ui/library/LibraryNodeDialog.vue'); -const MoveLibraryNodeDialog = () => import('/imports/ui/library/MoveLibraryNodeDialog.vue'); -const SelectCreaturesDialog = () => import('/imports/ui/tabletop/SelectCreaturesDialog.vue'); -const ShareDialog = () => import('/imports/ui/sharing/ShareDialog.vue'); -const UsernameDialog = () => import('/imports/ui/user/UsernameDialog.vue'); - -export default { - AddCreaturePropertyDialog, - ArchiveDialog, - CastSpellWithSlotDialog, - CharacterSheetDialog, - CharacterCreationDialog, - CreatureFormDialog, - CreaturePropertyCreationDialog, - CreaturePropertyDialog, - CreaturePropertyFromLibraryDialog, - CreatureRootDialog, - DeleteConfirmationDialog, - DeleteUserAccountDialog, - ExperienceInsertDialog, - ExperienceListDialog, - HelpDialog, - InviteDialog, - LevelUpDialog, - LibraryCollectionCreationDialog, - LibraryCollectionEditDialog, - LibraryCreationDialog, - LibraryEditDialog, - LibraryNodeCreationDialog, - LibraryNodeDialog, - MoveLibraryNodeDialog, - SelectCreaturesDialog, - SelectLibraryNodeDialog, - ShareDialog, - SlotFillDialog, - TierTooLowDialog, - TransferOwnershipDialog, - UsernameDialog, -}; diff --git a/app/imports/ui/layouts/Sidebar.vue b/app/imports/ui/layouts/Sidebar.vue deleted file mode 100644 index b5c87d51..00000000 --- a/app/imports/ui/layouts/Sidebar.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - diff --git a/app/imports/ui/properties/components/attributes/AttributeCard.vue b/app/imports/ui/properties/components/attributes/AttributeCard.vue deleted file mode 100644 index 7b3e1492..00000000 --- a/app/imports/ui/properties/components/attributes/AttributeCard.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - - - diff --git a/app/imports/ui/properties/components/attributes/ResourceCard.vue b/app/imports/ui/properties/components/attributes/ResourceCard.vue deleted file mode 100644 index c5759892..00000000 --- a/app/imports/ui/properties/components/attributes/ResourceCard.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - - - diff --git a/app/imports/ui/properties/forms/FolderForm.vue b/app/imports/ui/properties/forms/FolderForm.vue deleted file mode 100644 index 77c96aec..00000000 --- a/app/imports/ui/properties/forms/FolderForm.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - - - diff --git a/app/imports/ui/properties/forms/shared/propertyFormIndex.js b/app/imports/ui/properties/forms/shared/propertyFormIndex.js deleted file mode 100644 index bd6c6ba9..00000000 --- a/app/imports/ui/properties/forms/shared/propertyFormIndex.js +++ /dev/null @@ -1,61 +0,0 @@ -const ActionForm = () => import('/imports/ui/properties/forms/ActionForm.vue'); -const AdjustmentForm = () => import('/imports/ui/properties/forms/AdjustmentForm.vue'); -const AttributeForm = () => import('/imports/ui/properties/forms/AttributeForm.vue'); -const BuffForm = () => import('/imports/ui/properties/forms/BuffForm.vue'); -const BuffRemoverForm = () => import('/imports/ui/properties/forms/BuffRemoverForm.vue'); -const BranchForm = () => import('/imports/ui/properties/forms/BranchForm.vue'); -const ClassForm = () => import('/imports/ui/properties/forms/ClassForm.vue'); -const ClassLevelForm = () => import('/imports/ui/properties/forms/ClassLevelForm.vue'); -const ConstantForm = () => import('/imports/ui/properties/forms/ConstantForm.vue'); -const ContainerForm = () => import('/imports/ui/properties/forms/ContainerForm.vue'); -const DamageForm = () => import('/imports/ui/properties/forms/DamageForm.vue'); -const DamageMultiplierForm = () => import('/imports/ui/properties/forms/DamageMultiplierForm.vue'); -const EffectForm = () => import('/imports/ui/properties/forms/EffectForm.vue'); -const FeatureForm = () => import('/imports/ui/properties/forms/FeatureForm.vue'); -const FolderForm = () => import('/imports/ui/properties/forms/FolderForm.vue'); -const ItemForm = () => import('/imports/ui/properties/forms/ItemForm.vue'); -const NoteForm = () => import('/imports/ui/properties/forms/NoteForm.vue'); -const PointBuyForm = () => import('/imports/ui/properties/forms/PointBuyForm.vue'); -const ProficiencyForm = () => import('/imports/ui/properties/forms/ProficiencyForm.vue'); -const ReferenceForm = () => import('/imports/ui/properties/forms/ReferenceForm.vue'); -const RollForm = () => import('/imports/ui/properties/forms/RollForm.vue'); -const SavingThrowForm = () => import('/imports/ui/properties/forms/SavingThrowForm.vue'); -const SkillForm = () => import('/imports/ui/properties/forms/SkillForm.vue'); -const SlotForm = () => import('/imports/ui/properties/forms/SlotForm.vue'); -const SlotFillerForm = () => import('/imports/ui/properties/forms/SlotFillerForm.vue'); -const SpellListForm = () => import('/imports/ui/properties/forms/SpellListForm.vue'); -const SpellForm = () => import('/imports/ui/properties/forms/SpellForm.vue'); -const ToggleForm = () => import('/imports/ui/properties/forms/ToggleForm.vue'); -const TriggerForm = () => import('/imports/ui/properties/forms/TriggerForm.vue'); - -export default { - action: ActionForm, - adjustment: AdjustmentForm, - attribute: AttributeForm, - buff: BuffForm, - buffRemover: BuffRemoverForm, - branch: BranchForm, - constant: ConstantForm, - container: ContainerForm, - class: ClassForm, - classLevel: ClassLevelForm, - damage: DamageForm, - damageMultiplier: DamageMultiplierForm, - effect: EffectForm, - feature: FeatureForm, - folder: FolderForm, - item: ItemForm, - note: NoteForm, - pointBuy: PointBuyForm, - proficiency: ProficiencyForm, - propertySlot: SlotForm, - reference: ReferenceForm, - roll: RollForm, - savingThrow: SavingThrowForm, - skill: SkillForm, - slotFiller: SlotFillerForm, - spellList: SpellListForm, - spell: SpellForm, - toggle: ToggleForm, - trigger: TriggerForm, -}; diff --git a/app/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js b/app/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js deleted file mode 100644 index 1f9772e8..00000000 --- a/app/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js +++ /dev/null @@ -1,23 +0,0 @@ -import DefaultTreeNode from '/imports/ui/properties/treeNodeViews/DefaultTreeNode.vue'; -import AdjustmentTreeNode from '/imports/ui/properties/treeNodeViews/AdjustmentTreeNode.vue'; -import BranchTreeNode from '/imports/ui/properties/treeNodeViews/BranchTreeNode.vue'; -import ItemTreeNode from '/imports/ui/properties/treeNodeViews/ItemTreeNode.vue'; -import DamageTreeNode from '/imports/ui/properties/treeNodeViews/DamageTreeNode.vue'; -import EffectTreeNode from '/imports/ui/properties/treeNodeViews/EffectTreeNode.vue'; -import ClassLevelTreeNode from '/imports/ui/properties/treeNodeViews/ClassLevelTreeNode.vue'; -import ProficiencyTreeNode from '/imports/ui/properties/treeNodeViews/ProficiencyTreeNode.vue'; -import ReferenceTreeNode from '/imports/ui/properties/treeNodeViews/ReferenceTreeNode.vue'; -import SavingThrowTreeNode from '/imports/ui/properties/treeNodeViews/SavingThrowTreeNode.vue'; - -export default { - default: DefaultTreeNode, - adjustment: AdjustmentTreeNode, - branch: BranchTreeNode, - classLevel: ClassLevelTreeNode, - damage: DamageTreeNode, - effect: EffectTreeNode, - item: ItemTreeNode, - proficiency: ProficiencyTreeNode, - reference: ReferenceTreeNode, - savingThrow: SavingThrowTreeNode, -} diff --git a/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js b/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js deleted file mode 100644 index 15d148e7..00000000 --- a/app/imports/ui/properties/viewers/shared/propertyViewerIndex.js +++ /dev/null @@ -1,61 +0,0 @@ -const ActionViewer = () => import ('/imports/ui/properties/viewers/ActionViewer.vue'); -const AdjustmentViewer = () => import ('/imports/ui/properties/viewers/AdjustmentViewer.vue'); -const AttributeViewer = () => import ('/imports/ui/properties/viewers/AttributeViewer.vue'); -const BuffViewer = () => import ('/imports/ui/properties/viewers/BuffViewer.vue'); -const BuffRemoverViewer = () => import ('/imports/ui/properties/viewers/BuffRemoverViewer.vue'); -const BranchViewer = () => import ('/imports/ui/properties/viewers/BranchViewer.vue'); -const ContainerViewer = () => import ('/imports/ui/properties/viewers/ContainerViewer.vue'); -const ClassViewer = () => import ('/imports/ui/properties/viewers/ClassViewer.vue'); -const ClassLevelViewer = () => import ('/imports/ui/properties/viewers/ClassLevelViewer.vue'); -const ConstantViewer = () => import ('/imports/ui/properties/viewers/ConstantViewer.vue'); -const DamageViewer = () => import ('/imports/ui/properties/viewers/DamageViewer.vue'); -const DamageMultiplierViewer = () => import ('/imports/ui/properties/viewers/DamageMultiplierViewer.vue'); -const EffectViewer = () => import ('/imports/ui/properties/viewers/EffectViewer.vue'); -const FeatureViewer = () => import ('/imports/ui/properties/viewers/FeatureViewer.vue'); -const FolderViewer = () => import ('/imports/ui/properties/viewers/FolderViewer.vue'); -const ItemViewer = () => import ('/imports/ui/properties/viewers/ItemViewer.vue'); -const NoteViewer = () => import ('/imports/ui/properties/viewers/NoteViewer.vue'); -const PointBuyViewer = () => import ('/imports/ui/properties/viewers/PointBuyViewer.vue'); -const ProficiencyViewer = () => import ('/imports/ui/properties/viewers/ProficiencyViewer.vue'); -const ReferenceViewer = () => import ('/imports/ui/properties/viewers/ReferenceViewer.vue'); -const RollViewer = () => import ('/imports/ui/properties/viewers/RollViewer.vue'); -const SkillViewer = () => import ('/imports/ui/properties/viewers/SkillViewer.vue'); -const SavingThrowViewer = () => import ('/imports/ui/properties/viewers/SavingThrowViewer.vue'); -const SlotViewer = () => import ('/imports/ui/properties/viewers/SlotViewer.vue'); -const SlotFillerViewer = () => import ('/imports/ui/properties/viewers/SlotFillerViewer.vue'); -const SpellListViewer = () => import ('/imports/ui/properties/viewers/SpellListViewer.vue'); -const SpellViewer = () => import ('/imports/ui/properties/viewers/SpellViewer.vue'); -const ToggleViewer = () => import ('/imports/ui/properties/viewers/ToggleViewer.vue'); -const TriggerViewer = () => import ('/imports/ui/properties/viewers/TriggerViewer.vue'); - -export default { - action: ActionViewer, - adjustment: AdjustmentViewer, - attribute: AttributeViewer, - buff: BuffViewer, - buffRemover: BuffRemoverViewer, - branch: BranchViewer, - container: ContainerViewer, - class: ClassViewer, - classLevel: ClassLevelViewer, - constant: ConstantViewer, - damage: DamageViewer, - damageMultiplier: DamageMultiplierViewer, - effect: EffectViewer, - feature: FeatureViewer, - folder: FolderViewer, - item: ItemViewer, - note: NoteViewer, - pointBuy: PointBuyViewer, - proficiency: ProficiencyViewer, - propertySlot: SlotViewer, - roll: RollViewer, - reference: ReferenceViewer, - savingThrow: SavingThrowViewer, - slotFiller: SlotFillerViewer, - skill: SkillViewer, - spellList: SpellListViewer, - spell: SpellViewer, - toggle: ToggleViewer, - trigger: TriggerViewer, -}; diff --git a/app/imports/ui/properties/viewers/shared/propertyViewerMixin.js b/app/imports/ui/properties/viewers/shared/propertyViewerMixin.js deleted file mode 100644 index de4278f2..00000000 --- a/app/imports/ui/properties/viewers/shared/propertyViewerMixin.js +++ /dev/null @@ -1,23 +0,0 @@ -import PropertyName from '/imports/ui/properties/viewers/shared/PropertyName.vue'; -import PropertyVariableName from '/imports/ui/properties/viewers/shared/PropertyVariableName.vue'; -import PropertyField from '/imports/ui/properties/viewers/shared/PropertyField.vue'; -import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'; -import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue'; - -const propertyViewerMixin = { - components: { - PropertyName, - PropertyVariableName, - PropertyField, - PropertyDescription, - PropertyTags, - }, - props: { - model: { - type: Object, - required: true, - }, - }, -}; - -export default propertyViewerMixin; diff --git a/app/package-lock.json b/app/package-lock.json index f028cd05..f3020a8e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "dicecloud", - "version": "2.0.42", + "version": "2.0.43", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -50,17 +50,17 @@ } }, "@babel/parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.4.tgz", - "integrity": "sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", + "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", "dev": true }, "@babel/runtime": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz", - "integrity": "sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==", + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", + "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.10" } }, "@chenfengyuan/vue-countdown": { @@ -179,22 +179,23 @@ "dev": true }, "@types/semver": { - "version": "7.3.12", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", - "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.1.tgz", - "integrity": "sha512-FsWboKkWdytGiXT5O1/R9j37YgcjO8MKHSUmWnIEjVaz0krHkplPnYi7mwdb+5+cs0toFNQb0HIrN7zONdIEWg==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz", + "integrity": "sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.40.1", - "@typescript-eslint/type-utils": "5.40.1", - "@typescript-eslint/utils": "5.40.1", + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/type-utils": "5.43.0", + "@typescript-eslint/utils": "5.43.0", "debug": "^4.3.4", "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" @@ -221,14 +222,14 @@ } }, "@typescript-eslint/parser": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.40.1.tgz", - "integrity": "sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.43.0.tgz", + "integrity": "sha512-2iHUK2Lh7PwNUlhFxxLI2haSDNyXvebBO9izhjhMoDC+S3XI9qt2DGFUsiJ89m2k7gGYch2aEpYqV5F/+nwZug==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.40.1", - "@typescript-eslint/types": "5.40.1", - "@typescript-eslint/typescript-estree": "5.40.1", + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/typescript-estree": "5.43.0", "debug": "^4.3.4" }, "dependencies": { @@ -244,23 +245,23 @@ } }, "@typescript-eslint/scope-manager": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.40.1.tgz", - "integrity": "sha512-jkn4xsJiUQucI16OLCXrLRXDZ3afKhOIqXs4R3O+M00hdQLKR58WuyXPZZjhKLFCEP2g+TXdBRtLQ33UfAdRUg==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz", + "integrity": "sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.40.1", - "@typescript-eslint/visitor-keys": "5.40.1" + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/visitor-keys": "5.43.0" } }, "@typescript-eslint/type-utils": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.40.1.tgz", - "integrity": "sha512-DLAs+AHQOe6n5LRraXiv27IYPhleF0ldEmx6yBqBgBLaNRKTkffhV1RPsjoJBhVup2zHxfaRtan8/YRBgYhU9Q==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz", + "integrity": "sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.40.1", - "@typescript-eslint/utils": "5.40.1", + "@typescript-eslint/typescript-estree": "5.43.0", + "@typescript-eslint/utils": "5.43.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -277,19 +278,19 @@ } }, "@typescript-eslint/types": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.40.1.tgz", - "integrity": "sha512-Icg9kiuVJSwdzSQvtdGspOlWNjVDnF3qVIKXdJ103o36yRprdl3Ge5cABQx+csx960nuMF21v8qvO31v9t3OHw==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.43.0.tgz", + "integrity": "sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.1.tgz", - "integrity": "sha512-5QTP/nW5+60jBcEPfXy/EZL01qrl9GZtbgDZtDPlfW5zj/zjNrdI2B5zMUHmOsfvOr2cWqwVdWjobCiHcedmQA==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz", + "integrity": "sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.40.1", - "@typescript-eslint/visitor-keys": "5.40.1", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/visitor-keys": "5.43.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -318,16 +319,16 @@ } }, "@typescript-eslint/utils": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.40.1.tgz", - "integrity": "sha512-a2TAVScoX9fjryNrW6BZRnreDUszxqm9eQ9Esv8n5nXApMW0zeANUYlwh/DED04SC/ifuBvXgZpIK5xeJHQ3aw==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.43.0.tgz", + "integrity": "sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.40.1", - "@typescript-eslint/types": "5.40.1", - "@typescript-eslint/typescript-estree": "5.40.1", + "@typescript-eslint/scope-manager": "5.43.0", + "@typescript-eslint/types": "5.43.0", + "@typescript-eslint/typescript-estree": "5.43.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -354,12 +355,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.40.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.1.tgz", - "integrity": "sha512-A2DGmeZ+FMja0geX5rww+DpvILpwo1OsiQs0M+joPWJYsiEFBLsH0y1oFymPNul6Z5okSmHpP4ivkc2N0Cgfkw==", + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz", + "integrity": "sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.40.1", + "@typescript-eslint/types": "5.43.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { @@ -372,31 +373,31 @@ } }, "@vue/compiler-core": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", - "integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", + "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", "dev": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.41", + "@vue/shared": "3.2.45", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } }, "@vue/compiler-dom": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz", - "integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", + "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", "dev": true, "requires": { - "@vue/compiler-core": "3.2.41", - "@vue/shared": "3.2.41" + "@vue/compiler-core": "3.2.45", + "@vue/shared": "3.2.45" } }, "@vue/shared": { - "version": "3.2.41", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz", - "integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==", + "version": "3.2.45", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", + "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==", "dev": true }, "abbrev": { @@ -444,11 +445,6 @@ "uri-js": "^4.2.2" } }, - "animejs": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz", - "integrity": "sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA=" - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -469,16 +465,6 @@ "color-convert": "^1.9.0" } }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, "aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -544,9 +530,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.1234.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1234.0.tgz", - "integrity": "sha512-QFuSeYM8DPiXytspLnWToy4MWJPGYFCgAy4hi+lRd6ueJtCey7MBIgFNHW814uAJzKUDEdJUJZPvDZvtLM1d/Q==", + "version": "2.1258.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1258.0.tgz", + "integrity": "sha512-siqNFXlhJZVN1BizPZebJViFXtTUPgcA+yLfHKl2eC4Ied7kE7spOjZmAzpuiGUTzFagk1oWCaJ1Hit4llIoGg==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -594,9 +580,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -627,12 +613,6 @@ } } }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, "bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -702,25 +682,20 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", - "deep-eql": "^3.0.1", + "deep-eql": "^4.1.2", "get-func-name": "^2.0.0", "loupe": "^2.3.1", "pathval": "^1.1.1", @@ -784,22 +759,6 @@ "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", "dev": true }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -810,16 +769,6 @@ "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -955,11 +904,6 @@ "ms": "2.1.2" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" - }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -969,9 +913,9 @@ } }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.2.tgz", + "integrity": "sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -988,15 +932,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1012,11 +947,6 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" }, - "dijkstrajs": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz", - "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==" - }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1056,9 +986,9 @@ } }, "dompurify": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", - "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", + "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==" }, "ecc-jsbn": { "version": "0.1.2", @@ -1074,11 +1004,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "encode-utf8": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", - "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1096,47 +1021,6 @@ "ansi-colors": "^4.1.1" } }, - "es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1441,15 +1325,6 @@ "to-regex-range": "^5.0.1" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -1507,40 +1382,17 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" - }, "gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -1557,11 +1409,6 @@ "wide-align": "^1.1.2" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -1578,15 +1425,6 @@ "has-symbols": "^1.0.3" } }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1645,6 +1483,14 @@ "slash": "^3.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1680,25 +1526,12 @@ "function-bind": "^1.1.1" } }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "requires": { - "get-intrinsic": "^1.1.1" - } - }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -1751,12 +1584,6 @@ "resolved": "https://registry.npmjs.org/ignore-styles/-/ignore-styles-5.0.1.tgz", "integrity": "sha1-tJ7yJ0va/NikiAqWa/440aC/RnE=" }, - "immutable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", - "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", - "dev": true - }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -1792,16 +1619,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -1816,45 +1633,11 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1883,67 +1666,21 @@ "is-extglob": "^2.1.1" } }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "requires": { - "has-symbols": "^1.0.2" - } - }, "is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" } }, @@ -1952,14 +1689,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "requires": { - "call-bind": "^1.0.2" - } - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2034,25 +1763,6 @@ "verror": "1.10.0" } }, - "less": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/less/-/less-1.4.2.tgz", - "integrity": "sha1-t97v6Yo6h77jZEEbPfLR7+WkEtA=", - "requires": { - "mime": "1.2.x", - "mkdirp": "~0.3.4", - "request": ">=2.12.0", - "ycssmin": ">=1.0.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", - "optional": true - } - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2063,14 +1773,6 @@ "type-check": "~0.4.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2090,7 +1792,7 @@ "lodash.omit": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=" }, "lodash.template": { "version": "4.5.0", @@ -2116,9 +1818,9 @@ "dev": true }, "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", "dev": true, "requires": { "get-func-name": "^2.0.0" @@ -2147,29 +1849,10 @@ } } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "marked": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", - "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==" - }, - "mem": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/mem/-/mem-6.1.1.tgz", - "integrity": "sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.3", - "mimic-fn": "^3.0.0" - } + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.2.tgz", + "integrity": "sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ==" }, "merge2": { "version": "1.4.1", @@ -2972,12 +2655,6 @@ "picomatch": "^2.3.1" } }, - "mime": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", - "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", - "optional": true - }, "mime-db": { "version": "1.46.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", @@ -2991,12 +2668,6 @@ "mime-db": "1.46.0" } }, - "mimic-fn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", - "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", - "dev": true - }, "mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -3008,9 +2679,9 @@ "integrity": "sha1-IBvZSSceGfbgrwodwMzFg95HxjA=" }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -3073,6 +2744,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -3131,12 +2808,6 @@ "abbrev": "1" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, "npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -3158,27 +2829,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3201,33 +2851,6 @@ "word-wrap": "^1.2.3" } }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3237,11 +2860,6 @@ "callsites": "^3.0.0" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3276,11 +2894,6 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" - }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -3341,16 +2954,10 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "qrcode": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz", - "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==", - "requires": { - "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - } + "qrcode.vue": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-1.7.0.tgz", + "integrity": "sha512-R7t6Y3fDDtcU7L4rtqwGUDP9xD64gJhIwpfjhRCTKmBoYF6SS49PIJHRJ048cse6OI7iwTwgyy2C46N9Ygoc6g==" }, "qs": { "version": "6.5.2", @@ -3410,30 +3017,11 @@ "util-deprecate": "^1.0.1" } }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, "regenerator-runtime": { "version": "0.13.10", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -3467,22 +3055,12 @@ "uuid": "^3.3.2" } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, "requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -3528,32 +3106,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sass": { - "version": "1.55.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.55.0.tgz", - "integrity": "sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A==", - "dev": true, - "requires": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - } - }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -3617,16 +3174,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, "signal-exit": { <<<<<<< HEAD "version": "3.0.2", @@ -3730,12 +3277,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -3789,26 +3330,6 @@ "strip-ansi": "^6.0.1" } }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3838,14 +3359,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "styles": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/styles/-/styles-0.2.1.tgz", - "integrity": "sha1-hJJ7pEf6pvJJ7NIK3wu4X606UUE=", - "requires": { - "less": "~1.4.0" - } - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4047,27 +3560,11 @@ "dev": true }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", "dev": true }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -4202,9 +3699,9 @@ } }, "vuetify": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.11.tgz", - "integrity": "sha512-SX6BzS068t/RMj0d/sxxS/LsKdk9FpkL7CWShLyOMmJa/hiG63L0bvcopEaWYdRzzuQt7Nx174sk+KB6SvmYBw==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.12.tgz", + "integrity": "sha512-qe3hcMpWmT1O15tp+p65lOS7UKZ/hQYQktCsw9iXx2u3RwVbX6GR82gY2iROrKsiAzYDvMgrYxWQwY/pUfkekw==" }, "vuetify-upload-button": { "version": "2.0.2", @@ -4239,34 +3736,17 @@ "isexe": "^2.0.0" } }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" - }, "which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" + "is-typed-array": "^1.1.10" } }, "wide-align": { @@ -4283,39 +3763,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4340,48 +3787,10 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==" }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "ycssmin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ycssmin/-/ycssmin-1.0.1.tgz", - "integrity": "sha1-fN3o23jPqwDSkBw7IwHjBPr03xY=", - "optional": true } } } diff --git a/app/package.json b/app/package.json index 976d4a56..6036d934 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dicecloud", - "version": "2.0.42", + "version": "2.0.44", "description": "Unofficial Online Realtime D&D 5e App", "license": "GPL-3.0", "repository": { @@ -11,6 +11,7 @@ "scripts": { "run": "meteor", "debug": "meteor --inspect", + "bundle-viz": "meteor --extra-packages bundle-visualizer --production", "test": "meteor test --driver-package meteortesting:mocha --port 3001", "build": "meteor build ../build --architecture os.linux.x86_64" }, @@ -19,23 +20,21 @@ "npm": "6.13.x" }, "dependencies": { - "@babel/runtime": "^7.19.4", + "@babel/runtime": "^7.20.1", "@chenfengyuan/vue-countdown": "^1.1.5", "@tozd/vue-observer-utils": "^0.5.0", - "animejs": "^2.2.0", - "aws-sdk": "^2.1234.0", + "aws-sdk": "^2.1258.0", "bcrypt": "^5.1.0", "chroma-js": "^2.4.2", - "core-js": "^2.6.11", "css-box-shadow": "^1.0.0-3", "date-fns": "^1.30.1", "ddp-rate-limiter-mixin": "^1.1.10", "discord.js": "^12.5.3", - "dompurify": "^2.4.0", + "dompurify": "^2.4.1", "ignore": "^5.2.0", "ignore-styles": "^5.0.1", "lodash": "^4.17.20", - "marked": "^4.1.1", + "marked": "^4.2.2", "meteor-node-stubs": "^1.2.5", "minify-css-string": "^1.0.0", "moo": "^0.5.2", @@ -43,35 +42,30 @@ "ngraph.graph": "^19.1.0", "ngraph.path": "^1.4.0", "pretty-bytes": "^6.0.0", - "qrcode": "^1.5.1", + "qrcode.vue": "^1.7.0", "request": "^2.88.2", "sharp": "^0.30.7", "simpl-schema": "^1.13.1", "source-map-support": "^0.5.21", "speakingurl": "^14.0.1", - "styles": "^0.2.1", - "three": "^0.139.2", - "underscore": "^1.13.6", "vue": "2.6.10", "vue-meteor-tracker": "^2.0.0", "vue-reactive-provide": "^0.3.0", "vue-router": "^3.6.5", "vuedraggable": "^2.23.2", - "vuetify": "^2.6.11", + "vuetify": "^2.6.12", "vuetify-upload-button": "^2.0.2", "vuex": "^3.1.3" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.40.1", - "@typescript-eslint/parser": "^5.40.1", - "@vue/compiler-dom": "^3.2.41", - "chai": "^4.3.6", + "@typescript-eslint/eslint-plugin": "^5.43.0", + "@typescript-eslint/parser": "^5.43.0", + "@vue/compiler-dom": "^3.2.45", + "chai": "^4.3.7", "eslint": "^7.32.0", "eslint-plugin-vue": "^7.20.0", "eslint-plugin-vuetify": "^1.1.0", - "mem": "^6.1.1", - "sass": "^1.55.0", - "typescript": "^4.8.4" + "typescript": "^4.9.3" }, "eslintConfig": { "extends": [ @@ -121,8 +115,7 @@ "quotes": [ "error", "single" - ], - "vuetify/no-deprecated-classes": "error" + ] } } } \ No newline at end of file diff --git a/app/private/docs/defaultDocs.json b/app/private/docs/defaultDocs.json new file mode 100644 index 00000000..7eaad790 --- /dev/null +++ b/app/private/docs/defaultDocs.json @@ -0,0 +1,775 @@ +[ + { + "_id": "ioei4uvDdGTAFqZrB", + "name": "Properties", + "order": 1, + "urlName": "properties", + "tags": [], + "ancestors": [], + "href": "/docs/properties", + "published": true, + "description": "Properties are all the things you can add to a character, like ability scores, actions, spells, and items." + }, + { + "_id": "Lt5ccP99yjDsh4oMJ", + "name": "Actions", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 2, + "urlName": "actions", + "tags": [], + "description": "Actions are things your character can do. When an action is taken, all the properties under it are applied.\n\nAdd actions to your character sheet, then add children under the action to determine what happenes when the action is applied.\n\nWhen an action is applied it will create an entry in the character's log detailing all the properties that were applied and what their results were.\n\nThe following properties can all be applied by an action: \n\n - [Attribute Damage](/docs/property/attribute-damage)\n - [Branches](/docs/property/branch)\n - [Buffs](/docs/property/buff)\n - [Buff Removers](/docs/property/remove-buff)\n - [Damage](/docs/property/damage)\n - [Notes](/docs/property/note)\n - [Rolls](/docs/property/roll)\n - [Saving Throws](/docs/property/saving-throw)\n - Other actions\n\n---\n\n### Name\n\nThe name of the action.\n\n### Action type\n\nHow long the action takes to perform.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Attack roll\n\nA [computed field](/docs/computed-fields) which calculates the attack roll modifier. If this field is empty, no attack roll will be made. Use 0 to make an attack roll without a modifier.\n\nThe following variables may be added to the action scope when attack rolls are made:\n\n - `$attackDiceRoll` The value of the d20 roll before any modifiers were applied.\n - `$attackRoll` The total attack roll after modifiers.\n - `$criticalHit` Set to `true` if the attack roll's d20 is a natural 20. If `criticalHitTarget` is set, the attack roll's d20 must instead be equal to or greater than `criticalHitTarget` for this to be set to `true`.\n - `$criticalMiss` Set to `true` if the attack roll was not a critical hit and rolled a natural 1 on the d20 roll.\n - `$attackHit` If the attack roll is higher than or equal to the target's AC or a critical hit this is set to `true`. Remains unset if there is no target for the attack unless the attack is a critical hit.\n - `$attackMiss` If the attack roll is lower than the target's AC or a critical miss, this is set to `true`. Remains unset if there is no target for the attack unless the attack is a critical miss.\n\n### Summary\n\nA brief overview of what the action does. This will appear in the action card, and shows in the log when the action is applied.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Description\n\nA more detailed description of the action. The description does not show in the action card or the log when the action is applied.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Resource\n\nA resource can be any attribute that has a variable name. If the resource attribute is less than the amount required, the action can't be applied.\n\nIf you want to reduce an attribute when taking the action, but want the action to be applied regardless of the value of that attribute, consider using an [Attribute Damage](/docs/property/attribute-damage) property as a child of the action instead. Also use Attribute Damage when the amount to reduce the attribute is determined by a dice roll rather than a stable computed number.\n\n#### Resource attribute\n\nThe variable name of the attribute that will be consumed when taking this action.\n\n#### Resource quantity\n\nA [computed field](/docs/computed-fields) which determines how much of the attribute is required to apply this action. This amount will be deducted from the attribute every time the action is taken.\n\n### Ammo\n\nAmmo represents items that are requied to take the action. If an item is not selected, or there is insufficient quantity of the selected item, the action can't be appled.\n\n#### Ammo item\n\nSpecify what tag an item must have to be considered valid ammo for this action. Any item with this tag can be selected as ammo for this action.\n\n#### Ammo quantity\n\nA [computed field](/docs/computed-fields) which determines how many of the selected items are required to take this action. The quantity is deducted from the total quantity of the item when this action is applied.\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Target\n\nWho this action should apply to. The properties under the action will be applied to the Targets.\n\n- **Self** The action will apply its properties to the creature taking the action\n- **Single Target** The action will apply its properties without a target (for now)\n- **Multiple Targets** The action will apply its properties without a target (for now)\n\n### Uses\n\nA [computed field](/docs/computed-fields) which determines how many times this action can be used before it needs to be reset.\n\n### Uses used\n\nHow many of this action's uses have already been used. Should ideally be between 0 and the total uses available. This number is set to 0 when the action has uses and its uses are reset.\n\n### Don't show in log\n\nWhen this is true, the action does not show up in the log. This does not stop the action's children from appearing in the log when they are applied.\n\n### Reset\n\nIf set, the uses used field is set to 0 at the appropriate time.\n\n- **Long rest** Reset when the long rest button is pushed\n- **Short rest** Reset when either the long or short rest button is pushed", + "published": true, + "href": "/docs/properties/actions" + }, + { + "_id": "FwpkjToybWQKCDhSr", + "name": "Attribute Damage", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 3, + "urlName": "attribute-damage", + "tags": [], + "description": "When applied, attribute damage reduces the value of the [Attribute](/docs/properties/attributes) by some amount or set the value of an attribute to some amount. Attribute damage can by applied by actions or triggers.\n\nUsing a negative value to damage an attribute will heal the attribute instead.\n\n---\n\n### Attribute\n\nThe variable name of the attribute to target.\n\n### Amount\n\nA [computed field](/docs/computed-fields) which determined the amount to damage the attribute or set the attribute's value to.\n\n### Operation\n\n- **Damage** Reduce the value of the attribute by the amount, negative values heal the attribute instead\n- **Set** Set the value of the attribute to the amount\n\n### Target\n\n- **Target** Apply the attribute damage to the same target as the action applying this property\n- **Self** Apply the attribute damage to the creature taking the action\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Don't show in log\n\nWhen this is set, the attribute damage is applied, but does not show in the log.", + "href": "/docs/properties/attribute-damage", + "published": true + }, + { + "_id": "qdqo83AqkzQhtC2Em", + "name": "Attributes", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 4, + "urlName": "attributes", + "tags": [], + "description": "Attributes represent the numerical values of the creature.\n\nAttributes can be targeted by [effects](/docs/property/effect) which can change their total value in a non-destructive way. For example, if a class level gives you an ability score increase of +2 strength when it is taken, instead of directly editing the strength attribute, you add an effect to the class level that adds 2 to strength. The total value of strength will increase by 2 and it will show a record of that ability score increase and where it came from.\n\nAttributes, [skills](/docs/properties/skill), and [effects](/docs/property/effect) are the core properties of DiceCloud's creature engine.\n\nAttributes have the following fields that can be accessed in calculations with `variableName.field`:\n\n- `.total` The total of the attribute before being damaged\n- `.damage` the amount of damage the attribute has taken\n- `.value` The current value of the attribute including damage. `variableName` and `variableName.value` are equivalent.\n- `.modifier` If the attribute is an ability, this is its roll modifier, eg. `strength.modifier` is +2 when `strength.value` is 14\n\n---\n\n### Base value\n\nA [computed field](/docs/computed-fields) that determines the starting value of the attribute before it is modified by effects and other properties. Multiple properties can set the base value for a given variable name, when this happens the highest base value is chosen, and then all other effects are applied.\n\n### Name\n\nThe name of the attribute\n\n### Variable name\n\nThe name used to refer to the attribute in calculations and by effects. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\nIf multiple attributes share a variable name, only the last attribute on the [character tree](/docs/tree) will count as the defining attribute and appear on the sheet, while other attributes with that variable name will be used as base value [effects](/docs/property/effect).\n\n### Attribute type\n\n- **Ability** Ablity scores like Strength, Dexterity, etc. Ability scores get a modifier which can be accessed in calculations as `variableName.modifier`,\n- **Stat** Any numerical value that appears on the sheet. Speed, armor class.\n- **Modifier** Any numical value that appears on the sheet with a `+` or `-` sign, eg. Proficiency bonus.\n- **Hit Dice** Hit dice let you select the appropriate hit dice size. Creatures regain half their total hit dice on long rest.\n- **Health Bar** Health bars can by made to take or ignore damage in a specified order\n- **Resource** Rages, sourcery points, things that are spent to use actions.\n- **Spell Slot** Spell slots have a specific level and are used to cast spells.\n- **Utility** Utility attributes don't show up anywhere on the sheet, but can still be used for calculations\n\n### Description\n\nA detailed description of the attribute.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Health bar settings\n\nHealth bars can take or ignore damage and healing from applied damage properties targeting a creature. A lower ordered health bar will take damage before a higher ordered one.\n\nHealth bars can also change color depending on their value. At 50%+ full they are their property color, between 50% and 0% they fade from their half-full color to their empty color.\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Allow decimal values\n\nIf this is set, the attribute will not round-down when its value has a decimal.\n\n### Can be damaged into negative values\n\nIf this is set the attribute can be damaged past zero into negative values.\n\n### Can be incremented above total\n\nIf this is set the attribute can have negative damage such that the value exceeds the total. This can be useful if you are using the attribute to count, it can start at zero and be healed upwards to keep count.\n\n### Reset\n\nIf set, the damage on this attribute is reset to 0 at the appropriate time.\n\n- **Long rest** Reset when the long rest button is pushed\n- **Short rest** Reset when either the long or short rest button is pushed", + "published": true, + "href": "/docs/properties/attributes" + }, + { + "_id": "X5NKw8m6ruy9Srynd", + "name": "Branches", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 5, + "urlName": "branches", + "tags": [], + "published": true, + "description": "Branches are applied by actions, when they are applied they can control which of their immediate children are applied.\n\n---\n\n### Branch type\n\n- **If condition is true** Apply children if the condition (a [computed field](/docs/computed-fields)) resolves to `true` or a non-zero number\n- **Attack hit** Apply children if the attack roll hit the target\n- **Attack hit** Apply children if the attack roll missed the target\n- **Save failed** Apply children if target failed its saving throw\n- **Save suceeded** Apply children if target made its saving throw\n- **Apply to each target** Apply children separately to each target\n- **Random** Apply one of the immediate children at random\n- **Calculated Index** Use the index (a [computed field](/docs/computed-fields)) to choose which child to apply, starting at 1 for the first child.\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Don't show in log\n\nWhen this is set, the branch is applied, but does not show in the log. This does not prevent its children from appearing in the log.", + "href": "/docs/properties/branches" + }, + { + "_id": "7KzMFHqo8DJtFtj7Q", + "name": "New Doc", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 6, + "urlName": "new-doc-6", + "href": "/docs/properties/new-doc-6", + "tags": [], + "removed": true, + "removedAt": "2022-11-21T15:45:45.975Z" + }, + { + "_id": "aEmrQdfWwxCAWB5Jq", + "name": "New Doc", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 7, + "urlName": "new-doc-7", + "href": "/docs/properties/new-doc-7", + "tags": [], + "removed": true, + "removedAt": "2022-11-21T15:49:16.862Z" + }, + { + "_id": "hraKfb96M3LKEfYdp", + "name": "Buffs", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 8, + "urlName": "buffs", + "href": "/docs/properties/buffs", + "tags": [], + "description": "Buffs are temporary changes to a character sheet that can be applied by actions. When a buff is applied, it is copied to the target character along with all of its children properties. \n\nBuffs can either be manually removed from the stats page, or be removed by an action applying a [buff remover](/docs/property/remove-buff/) property.\n\n### Variable freezing\n\nWhen a buff is applied, all the calculations in the child properties have their variables frozen to their values at the time the buff is applied. You can prevent this behavior for the whole buff by using the `don't freeze variables` option, or on an individual variable reference by prefixing the variable with the keyword `$target.`.\n\nFor example, if a character has 10 strength and 16 dexterity, and applies a buff with some child property containing the calculation `$target.strength + dexterity` the property's calculation will become `strength + 16` when it is copied to the target character.\n\n---\n\n### Name\n\nThe name of the buff.\n\n### Description\n\nDescription of the applied buff.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Target\n\n- **Target** Apply the buff to the target of the action\n- **Self** Apply the buff to the creature taking the action\n\n### Hide remove button\n\nIf this is set, the remove button next to the buff on the stats page will be hidden. Use this when you expect the buff to be removed automatically by another action.\n\n### Don't show in log\n\nIf set, the buff will not show its name and description in the log when applied.\n\n### Don't freeze variables\n\nPrevent the buff from freezing variables in child property calculations to their value at the time the buff was applied.\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "ipi8yXDwR3bpH4bbP", + "name": "New Doc", + "order": 9, + "urlName": "new-doc-9", + "href": "/docs/new-doc-9", + "tags": [], + "ancestors": [], + "removed": true, + "removedAt": "2022-11-21T15:49:40.176Z" + }, + { + "_id": "E2DFwsCoiKy2Rc9Mz", + "name": "Concepts", + "order": 10, + "urlName": "concepts", + "href": "/docs/concepts", + "ancestors": [], + "published": true, + "description": "The inner workings of DiceCloud's character engine" + }, + { + "_id": "5Fwk543djf4hfqxCc", + "name": "Class levels", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 11, + "urlName": "class-levels", + "href": "/docs/properties/class-levels", + "description": "A class level is a property that represents a single level in a class. It is generally used as a child of a [Class property](/docs/property/class).\n\nFeatures and bonuses that are given by a class level get added as children of the class level.\n\n---\n\n### Level\n\nWhich level this property represents.\n\n### Name\n\nThe name of the class or subclass this level is part of\n\n### Variable name\n\nThe same variable name of the class this level belongs to.\n\n### Description\n\nA description of the benefits gained with this level.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "yX4ZuoHQwtGv3QJXX", + "name": "Classes", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 12, + "urlName": "classes", + "href": "/docs/properties/classes", + "published": true, + "description": "A class is a property that expects [class levels](/docs/property/class-level) as its immediate children.\n\nLeveling up a class means choosing, or manually adding, class level properties to it. Class levels with the same variable name as the class, and that match all the required tags are found in libraries and added to the class.\n\nThe total level of the class can be accessed in calculations using `classVariableName.level`.\n\n## Making your own class\n\nSee [Create a Class](/docs/walkthroughs/create-a-class)\n\n---\n\n### Name\n\nThe name of the class\n\n### Variable name\n\nThe name used to refer to the class in calculations. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\n### Description\n\nA description of the class.\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Tags required\n\nOnly class levels with the same variable name as the class, and with tags that match the tags required will be returned from libraries when leveling up this class.\n\n### Condition\n\nA [computed field](/docs/computed-fields) to determine if the class is allowed to level up. If this field results in `true` or a number that is not 0, the class can be levelled, otherwise leveling is disabled." + }, + { + "_id": "giHu6Ej7qvsZr4zrJ", + "name": "Constants", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 13, + "urlName": "constants", + "href": "/docs/properties/constants", + "description": "Constants are properties that store some primitive value in a variable name for use in other calculations.\n\nUnlike attributes, constants can store more than just numbers:\n\n- Arrays: `[1,2,3,4]`\n- Text strings: `'I am a cat'`\n- Numbers: `3.14`\n- Boolean values: `true`, `false`\n- Dice rolls: `1d20 + 2`\n\nConstants just can't use other variables in their calculations.\n\n### Overriding constants\n\nIf multiple constants have the same variable name, only the last active constant in the [character tree](/docs/tree) will be used as the definition for that variable name.\n\nThis can be used to re-write the value of some constant by ensuring there is a new active constant later in the sheet.\n\n---\n\n### Name\n\nThe name of the constants\n\n### Variable Name\n\nThe name used to refer to the constant in calculations. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\n### Value\n\nA [calculation](/docs/computed-fields) of the final value of the constant.", + "published": true + }, + { + "_id": "ghEJ3rh6YHCfuBirJ", + "name": "Containers", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 14, + "urlName": "containers", + "href": "/docs/properties/containers", + "description": "Containers are things that [items](/docs/property/item) can be put inside of.\n\n---\n\n### Name\n\nThe name of the container\n\n### Carried\n\nIf this is set the weight of the container and its contents will be added to the character's weight carried.\n\n### Value\n\nThe value of the container in gold pieces. Silver pieces are worth 0.1 gp and copper pieces are worth 0.01 gp. So a container that is worth 2 gp 4 sp 7 cp will have a value of 2.47 gp.\n\n### Weight\n\nThe weight of the container in lb.\n\n### Description\n\nA description of the container. \n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Contents are weightless\n\nIf this is set and the container is carried, only the container's own weight will be added to the weight carried by the creature.", + "published": true + }, + { + "_id": "B7xb4iC33np9JurzR", + "name": "Damage multipliers", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 15, + "urlName": "damage-multipliers", + "href": "/docs/properties/damage-multipliers", + "description": "Damage multipliers are used to define vulnerability, resistance, and immunity to damage types.\n\nA single multiplier can apply to multiple damage types, and choose whether or not to apply to an incoming source of damage based on the tags present on that damage.\n\n---\n\n### Name\n\nThe name of the feature that gives this damage multiplier\n\n### Value\n\n- **Immunity** The creature takes no damage from matching damage sources\n- **Resistance** Damage from matching sources is halved.\n- **Vulnerability** Damage from matching sources is doubled.\n\n### Damage types\n\nA list of damage types that this property applies to. Custom types can be used.\n\n### Damage tags required\n\nThis damage multiplier will only be applied if the incoming damage has all of these tags present.\n\n### Damage tags excluded\n\nThis damage multiplier will only apply if the incoming damage has none of these tags present.\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "oiQLbNiip7JHyGBG4", + "name": "Damage", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 16, + "urlName": "damage", + "href": "/docs/properties/damage", + "published": true, + "description": "Damage can be applied by an action to damage a target creature's [health bars](/docs/property/attribute). The damage will be modified by [damage multipliers](/docs/property/damage-multiplier), which apply vulnerability, resistance, and immunity before the damage is applied.\n\n---\n\n### Damage\n\nA [computed field](/docs/computed-fields) that determines how much damage to do to the target creature.\n\n### Damage type\n\nDamage type determines how the damage is treated by [damage multipliers](/docs/property/damage-multiplier). A custom type can be used, or one of the existing types can be selected.\n\nThere are two special damage types:\n\n**Extra damage** Damage with the `extra` type will take on the damage type of whatever damage was applied before it by an action. So if an action deals 12 `piercing` damage and `3` extra damage, it will instead deal 15 `piercing` damage.\n\n**Healing** Damage with the `healing` type will heal a creature instead of damaging them.\n\n### Target\n\n- **Target** Apply the damage to the target of the action\n- **Self** Apply the damage to the creature taking the action\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Don't show in log\n\nIf set, the damage will be applied but not show in the log." + }, + { + "_id": "FHdAjYY2er9xfYsJs", + "name": "Effects", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 17, + "urlName": "effects", + "href": "/docs/properties/effects", + "description": "Effects are the core of the DiceCloud engine. Effect change the values of attributes, skills, and calculations in a way that is transparent and auditable, keeping character sheets organized and understandable, even when using intricate homebrew rules on high level characters.\n\n---\n\n### Name\n\nThe name of the feature that causes this effect.\n\n### Operation\n\nThe operation determines what the effect will do to the affected property or calcualtion.\n\n- **Base Value** Set the base value of the affected property. If a property has multiple base values, the highest is used\n- **Add** Add the value to the affected property or calculation\n- **Muliply** Multiply the affected property by the value\n- **Minimum** Prevent the affected property from having a value less than the effect value\n- **Maximum** Prevent the affected property from having a value greater than the effect value\n- **Maximum** Prevent the affected property from having a value greater than the effect value\n- **Set** Set the value affected property to the effect value\n- **Advantage** Give advantage to checks made using the affected property\n- **Disadvantage** Give disadvantage to checks made using the affected property\n- **Passive bonus** Add the effect value to the passive scores based on the affected property\n- **Fail** Checks made using the affected property automatically fail\n- **Conditional benefit** Add some text to the affected property describing the benefit recieved\n\n### Value\n\nA [computed field](/docs/computed-fields) that determines the value of the effect.\n\n### Text\n\nIf the operation is a conditional benefit, the note text that will show on affected properties.\n\n### Target stats by variable name\n\nIf selected the effect will apply to all properties that have the given variable names.\n\n### Variable names\n\nA list of variable names of properties to target with this effect.\n\n### Target properties by tags\n\nWhen targeting properties by tag, any property can be targeted with an effect. If the property is one that can usually be targeted by variable name, the effect will apply as ususal, however if the effect targets another property, it will apply to a [computed field](/docs/computed-fields) on the property instead.\n\nThese effects can be used for adding a bonus to a specific attack or damage roll, or manipulating any computed field on the creature.\n\n### Tags required\n\nOnly properties that match the required tags will be targeted by the effect.\n\n### Target field\n\nIf a property has multiple computed fields, which field should be targeted by this effect.\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "GAEPgagGYv3a2QWnE", + "name": "Features", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 18, + "urlName": "features", + "href": "/docs/properties/features", + "description": "Features appear on the features tab. Classes, backgrounds, and race can all give a creature features.\n\n---\n\n### Name\n\nThe name of the feature.\n\n### Summary\n\nA summary of the feature. This will appear on the feature card.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Description\n\nA detailed description of the feature.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "7o6sWuQeoBtiQRLdL", + "name": "Folders", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 19, + "urlName": "folders", + "href": "/docs/properties/folders", + "published": true, + "description": "Folders allow the [character tree](/docs/tree) to be organized.\n\n### Folders in actions\n\nWhen a folder is the child of an action, it and its children will not show on the action card, but will still appear in the detail view of the action and be applied when the action is taken.\n\n---\n\n### Name\n\nThe name of the folder.\n\n### Tags\n\nSee [Tags](/docs/tags)" + }, + { + "_id": "5MsdJBbpALgMnYBwk", + "name": "Items", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 20, + "urlName": "items", + "href": "/docs/properties/items", + "published": true, + "description": "Items are shown on the Inventory tab. Items can be carried, put in containers, or equipped on a creature. The children of an item are not active unless the item is equipped.\n\n---\n\n### Icon\n\nAn icon representing the item.\n\n### Equipped\n\nIf set, the item appears in the equipment list on the inventory tab and its children become active on the creature.\n\n### Name\n\nThe name of the item.\n\n### Plural name\n\nThe name to use if the quantity of the item is higher than 1.\n\n### Value\n\nThe value of a single item in gold pieces. Silver pieces are worth 0.1 gp and copper pieces are worth 0.01 gp. So an item that is worth 2 gp 4 sp 7 cp will have a value of 2.47 gp.\n\n### Weight\n\nThe weight of a single item in lb.\n\n### Quantity\n\nNumber of items. The value and quantity will be multiplied by the quantity to get the total value and weight of this stack of items.\n\n### Description\n\nA description of the item. \n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Show increment button\n\nIf this is set, the item will show an increment button in the detail view and on the inventory tab. This button can be used to quickly adjust the quantity of the item.\n\n### Requires attunemnt\n\nIf set, the item requires attunemnt to use.\n\n### Attuned\n\nIf set, the item is attuned and counts towards the total number of attuned items for the creature.\n\nIf a child property needs to determine if its parent item is attuned it can use `#item.attuned` in calculations, see *Ancestor references* in [computed fields](/docs/computed-fields)." + }, + { + "_id": "74utQna6D4ayYyrLp", + "name": "Notes", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 21, + "urlName": "notes", + "href": "/docs/properties/notes", + "published": true, + "description": "Notes are used to store text on the creature that does not have a direct mechanical impact. Notes appear on the journal tab when active on the character, or are shown in the log when applied by an [action](/docs/property/action).\n\n---\n\n### Name\n\nName of the note.\n\n### Summary\n\nA summary of the note. This will appear on the note card and in the log when applied by an [action](/docs/property/action).\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Description\n\nA detailed description of the feature.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)" + }, + { + "_id": "cuhusZb8xYW8dj743", + "name": "Point buy", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 22, + "urlName": "point-buy", + "href": "/docs/properties/point-buy", + "published": true, + "description": "A point buy is a set of rows that lets the user choose a set of stats based on a cost per stat.\n\n---\n\n### Table name\n\nThe name of the point buy table.\n\n### Min\n\nThe lowest value available for each row\n\n### Max\n\nThe highest value available for each row\n\n### Cost\n\nA function that uses `value` as the value of a row and determines the cost of that value. For standard D&D 5e 27 point buy, this function is `[0, 1, 2, 3, 4, 5, 7, 9][value - 7]`\n\n### Total available points\n\nA [computed field](/docs/computed-fields) that determines how many points are available to spend in total\n\n## Rows\n\nUp to 32 rows can be added to a point buy table\n\n### Row name\n\nThe name of the row that will appear in the table\n\n### Row variable name\n\nThe variable name of the row that can be used in calculations. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\nIf the variable name matches an attribute with the same variable name, the row's value will be used as a base value for that attribute." + }, + { + "_id": "Jh92aYezHsEbSkriy", + "name": "Proficiencies", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 23, + "urlName": "proficiencies", + "href": "/docs/properties/proficiencies", + "description": "Proficiencies add proficiency to an existing skill on the creature. If you need to add a tool or language proficiency to a creature, use a [Skill](/docs/property/skill) instead.\n\n---\n\n### Name\n\nName of the feature that is adding this proficiency\n\n### Skills\n\nA list of variable names of the skills to add proficiency to.\n\n### Proficiency\n\nHow much proficiency to add to the skill. If a skill has multiple proficiencies added to it, the highest one will be used.\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "Nik9WERFxgjnp4cpE", + "name": "Remove Buff", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 24, + "urlName": "remove-buff", + "href": "/docs/properties/remove-buff", + "published": true, + "description": "This property can remove a specific buff from a targeted creature.\n\n### Name\n\nThe name of the property. This shows in the log when the property is applied.\n\n### Remove parent buff\n\nWhen this is set and the property is applied, the property will remove the nearest parent buff. If this property is not the child of any buffs, it will log an error.\n\n### Remove all\n\nWhen this is set, all buffs that match the target tags will be removed from the targeted creature. If not set, only the first buff found with the matching tags will be removed.\n\n### Target\n\n- **Target** Matching buffs will be removed from the targeted creature\n- **Self** Matching buffs will be removed from the creature that applied the action\n\n### Tags required\n\nAny buff that has all of the required tags will be removed when the property is applied.\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Don't show in log\n\nWhen this is set, the property is applied, but does not show in the log." + }, + { + "_id": "8e67Pmq7RvggHp4pX", + "name": "Rolls", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 25, + "urlName": "rolls", + "href": "/docs/properties/rolls", + "published": true, + "description": "Rolls are properties that store the result of a calculation to a variable name when applied by an [action](/docs/property/action). The variable name only exists for the duration of that particalar action.\n\nRolls can be useful if you need to deal the same damage to multiple targets, or if damage needs to be rolled then halved by succeeding on a saving throw.\n\n---\n\n### Name\n\nName of the roll. This will be shown in the log when the roll is applied.\n\n### Variable name\n\nThe variable name to store the result of the roll for the duration of the action. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\n### Roll\n\nA [computed field](/docs/computed-fields) that is computed when the roll is applied by an action.\n\n### Don't show in log\n\nIf set, the roll will be applied and store its result in the variable name, but not be shown in the log.\n\n### Tags\n\nSee [Tags](/docs/tags)" + }, + { + "_id": "Ecc7oWEtoJgXaYLtS", + "name": "Saving throws", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 26, + "urlName": "saving-throws", + "href": "/docs/properties/saving-throws", + "description": "Saving throws are properties that cause the target to make a saving throw when applied. If you want to add a type of saving throw like Strength Save to a creature, use a [skill](/docs/property/skill) instead.\n\nWhen a saving throw is applied, the following variables are added to the scope of that action:\n\n- `$saveFailed` Set to `true` if the target failed its saving throw or there are no targets for the saving throw\n- `$saveSucceeded` Set to `true` if the target made its saving throw or there are no targets for the saving throw\n- `$saveDiceRoll` The unmodified d20 roll the target made to save\n- `$saveRoll` The final value of the saving throw roll after modifiers\n\n### Name\n\nThe name of the saving throw. Usually the ability saving throw targeted: \"Strength Save\".\n\n### DC\n\nThe DC of the saving throw that the target needs to meet\n\n### Save\n\nThe variable name of the skill that will be used to make the saving throw.\n\n### Target\n\n- **Target** Apply the saving throw to the targets of the action. Each target will make the saving throw in turn. Child properties will be applied to each target separately with the results of their individual saving throw. If a value like damage needs to be shared between targets, it should be calculated in a [Roll](/docs/property/roll) before the saving throw.\n- **Self** Apply the saving throw to the creature taking the action. The creature taking the action will become the target for all child properties.\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Don't show in log\n\nIf set, the saving throw will not show in the log when applied, but will still be rolled and apply its children.", + "published": true + }, + { + "_id": "BKW9roawHgYcP2act", + "name": "Skills", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 27, + "urlName": "skills", + "href": "/docs/properties/skills", + "description": "Skills represent things the creature can be proficient in. Skills can have their values or behavior modified by [effects](/docs/property/effect), and their proficiencies modified by [proficiencies](/docs/property/proficiency).\n\n---\n\n### Name\n\nThe name of the skill.\n\n### Variable name\n\nThe name used to refer to the skill in calculations and by effects. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\n### Ability\n\nThe ability score that is the basis for checks made with this skill\n\n### Type\n\n- **Skill** Regular skills like *Athletics*, *Sleight* of Hand\n- **Save** Saving throws like *Strength*, *Charisma*\n- **Check** Checks that aren't skill like *Initiative*\n- **Tool** Tool proficiencies\n- **Weapon** Weapon proficiencies\n- **Armor** Armor proficiencies\n- **Language** Language proficiencies\n- **Utility** Skills that don't show on the charcater sheet but can be used in calculations\n\n### Description\n\nA detailed description of the skill.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Base value\n\nA [computed field](/docs/computed-fields) that determines the starting value of the skill before it is modified by effects and other properties. Multiple properties can set the base value for a given variable name, when this happens the highest base value is chosen, and then all other effects are applied.\n\n### Base proficiency\n\nThe starting proficiency of the skill.", + "published": true + }, + { + "_id": "X2uArSapfWvQZ6meS", + "name": "Slot filler", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 28, + "urlName": "slot-filler", + "href": "/docs/properties/slot-filler", + "published": true, + "description": "A slot filler is a property that can be used to add more complex behavior to filling a [slot](/docs/property/slot) from a library.\n\n---\n\n### Name\n\nThe name of the slot filler that will show when choosing the filler from the library.\n\n### Icon\n\nIcon of the slot filler\n\n### Description\n\nA detailed description of the slot filler.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Picture URL\n\nA link to an image to use for this slot filler when being chosen from a library.\n\n### Type\n\nSlot fillers can pretend to be any type of property when a slot is being filled.\n\n### Quantity\n\nHow many spaces the slot filler will take up in a slot.\n\n### Condition\n\nA [computed field](/docs/computed-fields) that determines whether this slot filler can be added to a character.\n\n### Tags\n\nSee [Tags](/docs/tags)" + }, + { + "_id": "bj5Bh5gsmjkLpYqA4", + "name": "Slots", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 29, + "urlName": "slots", + "href": "/docs/properties/slots", + "description": "Slots are the main way creatures interact with libraries. A slot can be filled by choosing a property from a library that fits that particular slot.\n\nIn a complete library, a creature can be built entirely by choosing which properties to fill each slot with.\n\nSlots show up on the build tab, and are highlighted when they have space that can be filled.\n\nIf you are building a creature without a library, you should either ignore slots entirely, or fill them with your own custom properties.\n\n---\n\n### Name\n\nThe name of the slot.\n\n### Type \n\nWhat kind of property this slot expects to fill it.\n\n### Tags required\n\nProperties in a library must have the required tags to fill the slot.\n\n### Quantity\n\nHow many properties are expected to fill this slot. Use 0 for allowing an unlimited number of properties.\n\n### Condition\n\nA [computed field](/docs/computed-fields) that determines whether this slot can accept new properties.\n\n### Unique\n\nThe slot can control how it deals with the uniqueness of properties that fill it.\n\n### Description\n\nA detailed description of the attribute.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Hide when full\n\nWhen set the slot will hide itself when it is filled.\n\n### Ignored\n\nWhen set the slot will not show a prompt card on the build tab.\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "h9Jw5bfSLq3D2jmeD", + "name": "Spell lists", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 30, + "urlName": "spell-lists", + "href": "/docs/properties/spell-lists", + "published": true, + "description": "Spell lists are collections of [spells](/docs/property/spell).\n\n---\n\n### Name\n\nThe name of the spell list.\n\n### Description\n\nA detailed description of the spell list.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Maximum prepared spells\n\nA [computed field](/docs/computed-fields) that determines how many spells can be considered ready to cast in this spell list.\n\n### Spell casting ability\n\nThe spellcasting ablity for this spell list. The variable name of the ability can be accessed using `#spellList.ability` and the ability modifier with `#spellList.abilityMod`. Setting this field will automatically update Spell save DC and Attack roll bonus if they aren't set manually.\n\n### Spell save DC\n\nA [computed field](/docs/computed-fields) that determines the DC of saving throws in this spell list. Spells can access the DC of their spell list using `#spellList.dc`\n\n### Attack roll bonus\n\nA [computed field](/docs/computed-fields) that determines the bonus to add to a d20 when making a spell attack with a spell in this spell list. Spells can access the attack roll bonus of their spell list using `#spellList.attackRollBonus`\n\n### Tags\n\nSee [Tags](/docs/tags)" + }, + { + "_id": "Mji9Cnp2TcFHmQebt", + "name": "Spells", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 31, + "urlName": "spells", + "href": "/docs/properties/spells", + "description": "Spells work similarly to [actions](/docs/property/action). They appear on the spells tab and can be cast with or without using up spell slots.\n\n---\n\n### Always prepared\n\nA spell that is always prepared does not count towards the spell list's maximum prepared spells and is always active and ready to cast.\n\n### Prepared\n\nA prepared spell is ready to cast and counts against a spell list's maximum prepared spells.\n\n### Cast without spell slots\n\nWhen set, this spell can be cast without consuming spell slots. It will however consume its own uses and resources.\n\n### School\n\nWhat school the spell belongs to.\n\n### Casting time\n\nHow long the spell takes to Cast\n\n### Range\n\nThe range of the spell\n\n### Duration\n\nHow long the spell lasts\n\n### Components\n\nWhether the spell requires verbal, somatic, or material components and whether the spell is a ritual or requires concentration.\n\n### Target\n\nWho this spell should apply to. The properties under the spell will be applied to the targets.\n\n- **Self** The spell will apply its properties to the creature casting the spell\n- **Single Target** The spell will apply its properties without a target (for now)\n- **Multiple Targets** The spell will apply its properties without a target (for now)\n\n### Attack roll\n\nA [computed field](/docs/computed-fields) which calculates the spell attack roll modifier. If this field is empty, no attack roll will be made. Use 0 to make an attack roll without a modifier. To use the spell list's attack roll bonus use `#spellList.attackRollBonus`.\n\nThe following variables may be added to the action scope when attack rolls are made:\n\n - `$attackDiceRoll` The value of the d20 roll before any modifiers were applied.\n - `$attackRoll` The total attack roll after modifiers.\n - `$criticalHit` Set to `true` if the attack roll's d20 is a natural 20. If `criticalHitTarget` is set, the attack roll's d20 must instead be equal to or greater than `criticalHitTarget` for this to be set to `true`.\n - `$criticalMiss` Set to `true` if the attack roll was not a critical hit and rolled a natural 1 on the d20 roll.\n - `$attackHit` If the attack roll is higher than or equal to the target's AC or a critical hit this is set to `true`. Remains unset if there is no target for the attack unless the attack is a critical hit.\n - `$attackMiss` If the attack roll is lower than the target's AC or a critical miss, this is set to `true`. Remains unset if there is no target for the attack unless the attack is a critical miss.\n\n### Summary\n\nA brief overview of what the spell does. This will show in the log when the spell is cast.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Description\n\nA more detailed description of the spell. The description does not show in the log when the spell is cast.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Resource\n\nA resource can be any attribute that has a variable name. If the resource attribute is less than the amount required, the spell can't be cast.\n\nIf you want to reduce an attribute when casting the spell, but want the spell to be applied regardless of the value of that attribute, consider using an [Attribute Damage](/docs/property/attribute-damage) property as a child of the spell instead. Also use Attribute Damage when the amount to reduce the attribute is determined by a dice roll rather than a stable computed number.\n\n#### Resource attribute\n\nThe variable name of the attribute that will be consumed when casting this spell.\n\n#### Resource quantity\n\nA [computed field](/docs/computed-fields) which determines how much of the attribute is required to apply this spell. This amount will be deducted from the attribute every time the spell is cast\n### Ammo\n\nAmmo represents items that are requied to cast the spell. If an item is not selected, or there is insufficient quantity of the selected item, the spell can't be appled.\n\n#### Ammo item\n\nSpecify what tag an item must have to be considered valid ammo for this spell. Any item with this tag can be selected as ammo for this spell.\n\n#### Ammo quantity\n\nA [computed field](/docs/computed-fields) which determines how many of the selected items are required to cast this spell. The quantity is deducted from the total quantity of the item when this spell is applied.\n\n### Uses\n\nA [computed field](/docs/computed-fields) which determines how many times this spell can be used before it needs to be reset.\n\n### Uses used\n\nHow many of this spell's uses have already been used. Should ideally be between 0 and the total uses available. This number is set to 0 when the spell has uses and its uses are reset.\n\n### Reset\n\nIf set, the uses used field is set to 0 at the appropriate time.\n\n- **Long rest** Reset when the long rest button is pushed\n- **Short rest** Reset when either the long or short rest button is pushed\n\n\n### Tags\n\nSee [Tags](/docs/tags)", + "published": true + }, + { + "_id": "AXBHkYpg8ABbyk6qz", + "name": "Toggles", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 32, + "urlName": "toggles", + "href": "/docs/properties/toggles", + "published": true, + "description": "Toggles are a way to turn on and off parts of a creature. When a toggle is off, none of its children will be active.\n\nCalculated toggles should be avoided if possible, because while they offer a lot of power and flexibility to the creature engine, they often create [dependency loops](/docs/dependency-loops) that can be difficult to troubleshoot, causing parts of a creature to calculate incorrectly.\n\nCalculated toggles can be applied by [actions](/docs/property/action) and will apply their children if the condition is true, but they should be avoided in favor of [conditional branches](/docs/property/branch) which can do the same, but are more efficient.\n\n---\n\n### Name\n\nThe name of the toggle.\n\n### Variable name\n\nThe name used to refer to the value of the toggle in calculations. Must start with a letter and be made up of only letters and numbers without spaces, symbols, or punctiation.\n\n### Show on character sheet\n\nIf set, the toggle with show a checkbox on the character sheet. A calculated toggle will show a disabled checkbox, filled in if the toggle's calculation returned `true` or a value that isn't 0.\n\n### State\n\n- **Enabled** The toggle and its children are active\n- **Disabled** The toggle and its children are inactive\n- **Calculated** The active status of the toggle depends on the result of the condition. Use with caution.\n\n### Condition\n\nA [computed field](/docs/computed-fields) that determines if the toggle is active. Use with caution.\n\n### Tags\n\nSee [Tags](/docs/tags)" + }, + { + "_id": "v7eRZRdMoDPah7ZtE", + "name": "Triggers", + "parent": { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs" + }, + "ancestors": [ + { + "id": "ioei4uvDdGTAFqZrB", + "collection": "docs", + "name": "Properties", + "urlName": "properties" + } + ], + "order": 33, + "urlName": "triggers", + "href": "/docs/properties/triggers", + "description": "Triggers apply their children whenever their condition is met. They work like [actions](/docs/property/action) that are taken automatically.\n\n---\n\n### Name\n\nThe name of the trigger.\n\n### Timing\n\n- **Before** The trigger is applied before the triggering event takes place\n- **After** The trigger is fired after the triggering event\n\n### Event\n\n- **Do action** While the creature is doing an action, the action property specified in *Event type* is applied\n- **Roll check** The creature makes a check\n- **Attribute damaged or healed** One of the creature's attributes changed value through attribute damage or manual adjustment\n- **Short or long rest**\n- **Short rest**\n- **Long Rest**\n\n### Event type\n\nThe trigger will apply when this property type is applied by the action\n\n### Tags required\n\nIf this trigger is fired by a property, the property must match these tags for the trigger to fire.\n\n### Condition\n\nA [computed field](/docs/computed-fields) to determine if the trigger should fire. The trigger will fire if the condition field is empty or if it returns `true` or a value that isn't 0.\n\n### Description\n\nA detailed description of the trigger.\n\nAllows [inline calculations](/docs/inline-calculations).\n\n### Tags\n\nSee [Tags](/docs/tags)\n\n### Don't show in log\n\nWhen this is true, the trigger does not show up in the log. This does not stop the trigger's children from appearing in the log when they are applied.", + "published": true + }, + { + "_id": "bTLn3sMpzxr7SAfD7", + "name": "Computed fields", + "parent": { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs" + }, + "ancestors": [ + { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs", + "name": "Concepts", + "urlName": "concepts" + } + ], + "order": 34, + "urlName": "computed-fields", + "href": "/docs/concepts/computed-fields", + "published": true, + "description": "Some fields in DiceCloud creature properties expect calculations. These fields are then computed by the DiceCloud engine.\n\nSome fields, like the value of an attirbute, resolve down to a single number, while others, like the damage to deal in an attack, only simplify their calculation as far as they can, and then resolve down to a number when applied. Avoid adding dice rolls to calculations that expect to resolve down to a number, because they will re-roll every time the creature is recalculated, causing instability in the creature's stats.\n\n## Parser\n\nThe DiceCloud parser can understand the following syntax:\n\n| | |\n| :- | :- |\n| **Numbers** | `13`, `3.14` |\n| **Dice rolls** | `3d6`, `(1 + 2)d4`|\n| **Strings of text** | `'Some text'`, `\"some other text\"` |\n| **Boolean values** | `true` or `false`. When DiceCloud expects a boolean, `0`, an empty string `''` and `false` are all considered false by DiceCloud's engine, every other value is considered true. |\n| **Variable names** | `variableName` |\n| **Addition and subtraction** | `1 + 2 + 3`, `12 - 6` |\n| **Multiplication** | `6 * 4`, `12 * 2` = `24` |\n| **Exponents** | `3 ^ 2` Raise 3 to the power of 2 |\n| **Modulo** | Returns the remainder of a division operation `15 % 6` = `3` |\n| **AND** | `&` or `&&`: Returns the value of the right hand side if the left side is true `true & 'cat'` = `'cat'` |\n| **OR** | `|` or `||`: Returns the left hand side if it is true, otherwise returns the right hand side `'dog' || 'cat'` = `'dog'` |\n| **NOT** | `!` returns false if the value after it is true, otherwise returns false |\n| **Comparisons** | greater than: `>`, less than: `<`, greater than or equal to: `>=`, less than or equal to: `<=`, equal: `=` or `==` or `===`, not equal: `!=` or `!==` |\n| **If-else** | `condition ? resultIfTrue : resultIfFalse`, `level > 10 ? 'high tier' : 'low tier'` |\n| **Arrays** | lists of values `[3, 6, 9, 12]`. |\n| **Array Indexes** | A value can be chosen from an array using another set of square brackets: `[3, 6, 9, 12][2]` = `[6]` because `[2]` fetches the 2nd value in the array. Arrays start at 1 in DiceCloud so that level tables can have 20 entries and be accessed by `array[level]`. |\n| **Function calls** | `functionName(argument1, argument1)` See [Functions](/docs/functions) for a full list of available functions. |\n\n## Special variables\n\n### Built-in variables\n\nThese variables are added to the creature automatically when relevant. They can be overriden if needed by creating a property with the same variable name. They can also be targetted by effects.\n\n- `xp` A total of all the experiences with xp added to the character sheet\n- `milestoneLevels` A total of all the experiences with milestone levels added to the character sheet\n- `itemsAttuned` Number of items the creature is attuned to\n- `weightEquipment` Total weight of all equipment on the creature\n- `valueEquipment` Total value of all equipment on the creature\n- `weightTotal` Total weight of the creature's entire inventory\n- `valueTotal` Total value of the creature's entire inventory\n- `weightCarried` Total weight of all carried items and containers\n- `valueCarried` Total value of all carried items and containers\n- `level` The current level of the creature, including all class levels\n- `criticalHitTarget` Defaults to 20, the natural roll needed to consider an attack roll as a critical hit\n\n### Action variables\n\nThese variables are available during an action after the relevant property has been applied.\n\nFor Advanced users, a [Roll](/docs/property/roll) can set these variables, overriding the default behavior.\n\n#### [Actions](/docs/property/action)\n\n- `$attackDiceRoll` The value of the d20 roll before any modifiers were applied.\n- `$attackRoll` The total attack roll after modifiers.\n- `$criticalHit` Set to `true` if the attack roll's d20 is a natural 20. If `criticalHitTarget` is set, the attack roll's d20 must instead be equal to or greater than `criticalHitTarget` for this to be set to `true`.\n- `$criticalMiss` Set to `true` if the attack roll was not a critical hit and rolled a natural 1 on the d20 roll.\n- `$attackHit` If the attack roll is higher than or equal to the target's AC or a critical hit this is set to `true`. Remains unset if there is no target for the attack unless the attack is a critical hit.\n- `$attackMiss` If the attack roll is lower than the target's AC or a critical miss, this is set to `true`. Remains unset if there is no target for the attack unless the attack is a critical miss.\n\n#### [Damage](/docs/property/damage)\n\n- `$lastDamageType` The type of damage dealt last, any damage that has the `extra` type will use this damage type instead\n\n#### [Saving throws](/docs/property/saving-throw)\n\n- `$saveFailed` Set to `true` if the target failed its saving throw or there are no targets for the saving throw\n- `$saveSucceeded` Set to `true` if the target made its saving throw or there are no targets for the saving throw\n- `$saveDiceRoll` The unmodified d20 roll the target made to save\n- `$saveRoll` The final value of the saving throw roll after modifiers\n\n## Ancestor references\n\nThe ancestors of a property can be accessed directly using the `#ancestorType` syntax.\n\nFor example, a spell might need to know the save DC of the spell list that it is inside of, it can use `#spellList.dc`.\n\nTriggers and their children work differently: They don't have access to their own ancestors, but rather inherit the ancestors of the property that caused them to fire. For example, a trigger at the root of the creature's tree might be fired by a spell being cast, you can still use references to ancestors like `#spellList.attackRollBonus` inside that trigger as if it were under the spell itself." + }, + { + "_id": "o8u2Z5gZW54ZXNeZB", + "name": "Dependency loops", + "parent": { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs" + }, + "ancestors": [ + { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs", + "name": "Concepts", + "urlName": "concepts" + } + ], + "order": 35, + "urlName": "dependency-loops", + "href": "/docs/concepts/dependency-loops", + "published": true, + "description": "When a variable is referenced in a calculation, that calculation can be said to depend on that variable. In order for the calculation to compute, the value of the variable needs to be known.\n\nBut consider the following property values that could be added to a creature\n\n- The creature's Strength base value is set to `dexterity + 1` so that it will always have 1 more strength than dexterity\n- The creature's Dexterity base value is set to `constitution + 1` so that it will always have 1 more dexterity than constitution\n- The creature's Constitution base value is set to `strength` so that its constitution is always equal to its strength\n\nIt is not possible to resolve these calculations, not just because no values exist which satisfy the constraints, but because strength depends on dexterity which depends on constitution which depends on strength. None can be computed before the others have finalized their values. This is a dependency loop.\n\nMost dependency loops that appear in actual DiceCloud creatures are less trivial than this example, but they cause the same result: a sheet that can't be accurately computed. In these cases, DiceCloud does its best, chooses an order to resolve the calculations arbitrarily, and continues calculating. An error will show on the Build tab to let you know that something went wrong.\n\n![dependency loop example](/images/docs/dependency-loop.png)\n\n## Toggles\n\nCalculated [Toggles](/docs/property/toggle) are the main source of dependency loops on creatures, because they create a dependency that isn't as obvious as a calculation might be. When a toggle is in calculated mode, its children do not know whether they are active or not until the calulation is resolved. Because of this, every calculation under the toggle depends on the toggles calaculation, making the chance for a loop to be formed more likely the more children are under a toggle.\n\nConsider this example\n\n- A calculated toggle that is active if `strength < 10`\n- An effect under that toggle that adds 2 to `strength`\n\nThe effect can't compute, because it does not know if it is active yet, so the toggle must compute its calculation first. The toggle needs to know if `strength` is greater than 10. Strength depends on all of the effects targeting it, it must know if the +2 effect is active or not. This creates a dependency loop, because there is no valid order in which everything can be calculated.\n\n## Troubleshooting a dependency loop\n\n- First, identify all the properties that make up the dependency loop. These are linked in the depdency loop error message. The field names in square brackets after the property name indicates which calculations on the property are directly involved.\n- Move any properties in the loop out from being children of calculated Toggles\n- Use static values in place of variables where they are not stricly needed\n- Ask for [help](/feedback)" + }, + { + "_id": "KFkmXFLQrdPQNpJ7X", + "name": "Inline Calculations", + "parent": { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs" + }, + "ancestors": [ + { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs", + "name": "Concepts", + "urlName": "concepts" + } + ], + "order": 36, + "urlName": "inline-calculations", + "href": "/docs/concepts/inline-calculations", + "description": "Most long-format fields allow inline [calculations](/docs/computed-fields) to be included. Calculations inside of curly bracers will be computed down to numbers using the characters stats.\n\nFor example a creature's strength attribute may have the following in its description: `Your carrying capacity is {strength * 15} lbs.`\n\nWhen the creature is calculated, if it has 8 strength, the action description will become: \"Your carrying capacity is 120 lbs.\"\n\nIf a description includes a dice roll, only the part that can be calculated to a single number should be included in the calulation bracers: `The attack does an extra {paladin.level}d8 damage`, which becomes `The attack does an extra 4d8 damage`.\n\nDo not inlclude the dice roll in the calaculation: `The attack does an extra {(paladin.level)d8} damage`, because it will become `The attack does an extra 16 damage` but the number 16 will change every time the creature recalculates.", + "published": true + }, + { + "_id": "QFtqb7y5kLPDJoWXG", + "name": "Tags", + "parent": { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs" + }, + "ancestors": [ + { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs", + "name": "Concepts", + "urlName": "concepts" + } + ], + "order": 37, + "urlName": "tags", + "href": "/docs/concepts/tags", + "description": "Tags are used to reference multiple similar properties at once. A slot can require only properties from your library that has matching tags, effects and some other properties can also target properties to apply to by tags.\n\n## Built in tags\n\nProperties have specific tags automatically for use with tag-targeting. These aren't relevant for slots and finding properties in a library with specific tags.\n\n- `#type` Actions will have the `#action` tag, etc. Damage will either have the tag `#damage` or the tag `#healing` if the damage type is healing\n- `variableName` if a property has a variable name it will be included as a tag\n- The type of damage done by a [damage](/docs/property/damage) property: `bludgeoning`, `slashing`, `...` \n- The skill type of a [skill](/docs/property/skill) property: `skill`, `save`, `check`, `tool`, `weapon`, `armor`, `language`, `utility`\n- The attribute type of an [attribute](/docs/property/attribute) property: `ability`, `stat`, `modifier`, `hitDice`, `healthBar`, `resource`, `spellSlot`, `utility`\n- When the property resets: `longRest`, `shortRest`" + }, + { + "_id": "3dkEFEnwH4ShSY2BS", + "name": "Functions", + "parent": { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs" + }, + "ancestors": [ + { + "id": "E2DFwsCoiKy2Rc9Mz", + "collection": "docs", + "name": "Concepts", + "urlName": "concepts" + } + ], + "order": 38, + "urlName": "functions", + "href": "/docs/concepts/functions", + "description": "## min\nReturns the smallest of the given numbers\n`min(12, 6, 3, 168)` = `3`\n\n## round\nReturns the value of a number rounded to the nearest integer\n`round(5.95)` = `6`\n`round(5.5)` = `6`\n`round(5.05)` = `5`\n\n## floor\nRounds a number down to the next smallest integer\n`floor(5.95)` = `5`\n`floor(5.05)` = `5`\n`floor(5)` = `5`\n`floor(-5.5)` = `-6`\n\n## ceil\nRounds a number up to the next largest integer\n`ceil(5.95)` = `6`\n`ceil(5.05)` = `6`\n`ceil(5)` = `5`\n`ceil(-5.5)` = `-5`\n\n## trunc\nReturns the integer part of a number by removing any fractional digits\n`trunc(5.95)` = `5`\n`trunc(5.05)` = `5`\n`trunc(5)` = `5`\n`trunc(-5.5)` = `-5`\n\n## sign\nReturns either a positive or negative 1, indicating the sign of a number, or zero\n`sign(-3)` = `-1`\n`sign(3)` = `1`\n`sign(0)` = `0`\n\n## tableLookup\nReturns the index of the last value in the array that is less than the specified amount\n`tableLookup([100, 300, 900], 457)` = `2`\n`tableLookup([100, 300, 900], 23)` = `0`\n`tableLookup([100, 300, 900, 1200], 900)` = `3`\n`tableLookup([100, 300], 594)` = `2`\n\n## resolve\nForces the given calcultion to resolve into a number, even in calculations where it would usually keep the unknown values as is\n`resolve(someUndefinedVariable + 3 + 4)` = `7`\n`resolve(1d6)` = `4`", + "published": true + } +] \ No newline at end of file diff --git a/app/private/docs/property/skill.md b/app/private/docs/property/skill.md index 51185673..5b25b9ad 100644 --- a/app/private/docs/property/skill.md +++ b/app/private/docs/property/skill.md @@ -1,6 +1,6 @@ # Skills -Skills represent things the creature can be proficient in. Skills can have their values or behavior modifier by [effects](/docs/property/effect), and their proficiencies modified by [proficiencies](/docs/property/proficiency). +Skills represent things the creature can be proficient in. Skills can have their values or behavior modified by [effects](/docs/property/effect), and their proficiencies modified by [proficiencies](/docs/property/proficiency). --- diff --git a/app/private/docs/property/spell-list.md b/app/private/docs/property/spell-list.md index b36885c8..6216b3c0 100644 --- a/app/private/docs/property/spell-list.md +++ b/app/private/docs/property/spell-list.md @@ -18,6 +18,10 @@ Allows [inline calculations](/docs/inline-calculations). A [computed field](/docs/computed-fields) that determines how many spells can be considered ready to cast in this spell list. +### Spell casting ability + +The spellcasting ablity for this spell list. The variable name of the ability can be accessed using `#spellList.ability` and the ability modifier with `#spellList.abilityMod`. Setting this field will automatically update Spell save DC and Attack roll bonus if they aren't set manually. + ### Spell save DC A [computed field](/docs/computed-fields) that determines the DC of saving throws in this spell list. Spells can access the DC of their spell list using `#spellList.dc` diff --git a/app/public/images/print/octogonBorder.png b/app/public/images/print/octagonBorder.png similarity index 100% rename from app/public/images/print/octogonBorder.png rename to app/public/images/print/octagonBorder.png