From aa8f2d230dd05733bf598397fab285547239bf4f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sun, 9 Oct 2022 16:56:28 +0200 Subject: [PATCH] Hunted the last of the \t's to extinction --- .../creatureFolders/CreatureFolders.js | 54 +- .../api/creature/journal/JournalEntry.js | 86 +-- app/imports/api/library/Libraries.js | 174 +++--- app/imports/api/properties/Buffs.js | 136 ++--- app/imports/api/properties/Containers.js | 116 ++-- app/imports/api/properties/Items.js | 116 ++-- app/imports/api/properties/Spells.js | 174 +++--- .../api/properties/subSchemas/ColorSchema.js | 12 +- .../properties/subSchemas/DeathSavesSchema.js | 40 +- app/imports/api/sharing/SharingSchema.js | 60 +- app/imports/api/users/Users.js | 576 +++++++++--------- app/imports/parser/grammar.js | 2 +- .../server/cron/deleteSoftRemovedDocuments.js | 78 +-- app/imports/ui/components/ColumnLayout.vue | 54 +- .../CreaturePropertiesTree.vue | 132 ++-- app/imports/ui/dialogStack/mockElement.js | 108 ++-- .../ui/properties/viewers/BuffViewer.vue | 48 +- app/jsconfig.json | 20 + app/public/manifest.json | 82 +-- 19 files changed, 1044 insertions(+), 1024 deletions(-) create mode 100644 app/jsconfig.json diff --git a/app/imports/api/creature/creatureFolders/CreatureFolders.js b/app/imports/api/creature/creatureFolders/CreatureFolders.js index dc411ec6..e0e784cc 100644 --- a/app/imports/api/creature/creatureFolders/CreatureFolders.js +++ b/app/imports/api/creature/creatureFolders/CreatureFolders.js @@ -4,33 +4,33 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; let CreatureFolders = new Mongo.Collection('creatureFolders'); let creatureFolderSchema = new SimpleSchema({ - name: { - type: String, - trim: false, - optional: true, - max: STORAGE_LIMITS.name, - }, - creatures: { - type: Array, - defaultValue: [], - }, - 'creatures.$': { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - owner: { - type: String, - regEx: SimpleSchema.RegEx.Id, - index: 1, - }, - archived: { - type: Boolean, - optional: true, - }, - order: { - type: Number, - defaultValue: 0, - }, + name: { + type: String, + trim: false, + optional: true, + max: STORAGE_LIMITS.name, + }, + creatures: { + type: Array, + defaultValue: [], + }, + 'creatures.$': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + owner: { + type: String, + regEx: SimpleSchema.RegEx.Id, + index: 1, + }, + archived: { + type: Boolean, + optional: true, + }, + order: { + type: Number, + defaultValue: 0, + }, }); CreatureFolders.attachSchema(creatureFolderSchema); diff --git a/app/imports/api/creature/journal/JournalEntry.js b/app/imports/api/creature/journal/JournalEntry.js index 5e65cb2b..74b885ea 100644 --- a/app/imports/api/creature/journal/JournalEntry.js +++ b/app/imports/api/creature/journal/JournalEntry.js @@ -2,49 +2,49 @@ import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; let ExperienceSchema = new SimpleSchema({ - title: { - type: String, - optional: true, - max: STORAGE_LIMITS.name, - }, - // Potentially long description of the event - description: { - type: String, - optional: true, - max: STORAGE_LIMITS.description, - }, - // The real-world date that it occured - date: { - type: Date, - autoValue: function () { - // If the date isn't set, set it to now - if (!this.isSet) { - return new Date(); - } - }, - }, - // The date in-world of this event - worldDate: { - type: String, - optional: true, - max: STORAGE_LIMITS.name, - }, - // Tags to better find this entry later - tags: { - type: Array, - defaultValue: [], - maxCount: STORAGE_LIMITS.tagCount, - }, - 'tags.$': { - type: String, - max: STORAGE_LIMITS.tagLength, - }, - // ID of the journal this entry belongs to - journalId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - index: 1, - } + title: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + // Potentially long description of the event + description: { + type: String, + optional: true, + max: STORAGE_LIMITS.description, + }, + // The real-world date that it occured + date: { + type: Date, + autoValue: function () { + // If the date isn't set, set it to now + if (!this.isSet) { + return new Date(); + } + }, + }, + // The date in-world of this event + worldDate: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + // Tags to better find this entry later + tags: { + type: Array, + defaultValue: [], + maxCount: STORAGE_LIMITS.tagCount, + }, + 'tags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, + // ID of the journal this entry belongs to + journalId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + index: 1, + } }); export { ExperienceSchema }; diff --git a/app/imports/api/library/Libraries.js b/app/imports/api/library/Libraries.js index 1e46c672..696f0001 100644 --- a/app/imports/api/library/Libraries.js +++ b/app/imports/api/library/Libraries.js @@ -20,15 +20,15 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; let Libraries = new Mongo.Collection('libraries'); let LibrarySchema = new SimpleSchema({ - name: { - type: String, - max: STORAGE_LIMITS.name, - }, - description: { - type: String, - optional: true, - max: STORAGE_LIMITS.summary, - }, + name: { + type: String, + max: STORAGE_LIMITS.name, + }, + description: { + type: String, + optional: true, + max: STORAGE_LIMITS.summary, + }, }); LibrarySchema.extend(SharingSchema); @@ -38,96 +38,96 @@ Libraries.attachSchema(LibrarySchema); export default Libraries; const insertLibrary = new ValidatedMethod({ - name: 'libraries.insert', - mixins: [ - simpleSchemaMixin, - ], - schema: LibrarySchema.omit('owner'), - run(library) { - if (!this.userId) { - throw new Meteor.Error('Libraries.methods.insert.denied', - 'You need to be logged in to insert a library'); - } - let tier = getUserTier(this.userId); - if (!tier.paidBenefits) { - throw new Meteor.Error('Libraries.methods.insert.denied', - `The ${tier.name} tier does not allow you to insert a library`); - } - library.owner = this.userId; - return Libraries.insert(library); - }, + name: 'libraries.insert', + mixins: [ + simpleSchemaMixin, + ], + schema: LibrarySchema.omit('owner'), + run(library) { + if (!this.userId) { + throw new Meteor.Error('Libraries.methods.insert.denied', + 'You need to be logged in to insert a library'); + } + let tier = getUserTier(this.userId); + if (!tier.paidBenefits) { + throw new Meteor.Error('Libraries.methods.insert.denied', + `The ${tier.name} tier does not allow you to insert a library`); + } + library.owner = this.userId; + return Libraries.insert(library); + }, }); const updateLibraryName = new ValidatedMethod({ - name: 'libraries.updateName', - validate: new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.id - }, - name: { - type: String, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ _id, name }) { - let library = Libraries.findOne(_id); - assertEditPermission(library, this.userId); - Libraries.update(_id, { $set: { name } }); - }, + name: 'libraries.updateName', + validate: new SimpleSchema({ + _id: { + type: String, + regEx: SimpleSchema.RegEx.id + }, + name: { + type: String, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id, name }) { + let library = Libraries.findOne(_id); + assertEditPermission(library, this.userId); + Libraries.update(_id, { $set: { name } }); + }, }); const updateLibraryDescription = new ValidatedMethod({ - name: 'libraries.updateDescription', - validate: new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.id - }, - description: { - type: String, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ _id, description }) { - let library = Libraries.findOne(_id); - assertEditPermission(library, this.userId); - Libraries.update(_id, { $set: { description } }); - }, + name: 'libraries.updateDescription', + validate: new SimpleSchema({ + _id: { + type: String, + regEx: SimpleSchema.RegEx.id + }, + description: { + type: String, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id, description }) { + let library = Libraries.findOne(_id); + assertEditPermission(library, this.userId); + Libraries.update(_id, { $set: { description } }); + }, }); const removeLibrary = new ValidatedMethod({ - name: 'libraries.remove', - validate: new SimpleSchema({ - _id: { - type: String, - regEx: SimpleSchema.RegEx.id - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ _id }) { - let library = Libraries.findOne(_id); - assertOwnership(library, this.userId); - this.unblock(); - removeLibaryWork(_id) - } + name: 'libraries.remove', + validate: new SimpleSchema({ + _id: { + type: String, + regEx: SimpleSchema.RegEx.id + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id }) { + let library = Libraries.findOne(_id); + assertOwnership(library, this.userId); + this.unblock(); + removeLibaryWork(_id) + } }); export function removeLibaryWork(libraryId) { - Libraries.remove(libraryId); - LibraryNodes.remove({ 'ancestors.id': libraryId }); + Libraries.remove(libraryId); + LibraryNodes.remove({ 'ancestors.id': libraryId }); } export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, removeLibrary }; diff --git a/app/imports/api/properties/Buffs.js b/app/imports/api/properties/Buffs.js index 86584694..386c402b 100644 --- a/app/imports/api/properties/Buffs.js +++ b/app/imports/api/properties/Buffs.js @@ -3,79 +3,79 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; let BuffSchema = createPropertySchema({ - name: { - type: String, - optional: true, - max: STORAGE_LIMITS.name, - }, - description: { - type: 'inlineCalculationFieldToCompute', - optional: true, - }, - hideRemoveButton: { - type: Boolean, - optional: true, - }, - // How many rounds this buff lasts - duration: { - type: 'fieldToCompute', - optional: true, - }, - target: { - type: String, - allowedValues: [ - 'self', - 'target', - ], - defaultValue: 'target', - }, - // Prevent the property from showing up in the log - silent: { - type: Boolean, - optional: true, - }, - // Prevent the children from being crystalized - skipCrystalization: { - type: Boolean, - optional: true, - }, + name: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + description: { + type: 'inlineCalculationFieldToCompute', + optional: true, + }, + hideRemoveButton: { + type: Boolean, + optional: true, + }, + // How many rounds this buff lasts + duration: { + type: 'fieldToCompute', + optional: true, + }, + target: { + type: String, + allowedValues: [ + 'self', + 'target', + ], + defaultValue: 'target', + }, + // Prevent the property from showing up in the log + silent: { + type: Boolean, + optional: true, + }, + // Prevent the children from being crystalized + skipCrystalization: { + type: Boolean, + optional: true, + }, }); let ComputedOnlyBuffSchema = createPropertySchema({ - description: { - type: 'computedOnlyInlineCalculationField', - optional: true, - max: STORAGE_LIMITS.description, - }, - duration: { - type: 'computedOnlyField', - optional: true, - }, - durationSpent: { - type: Number, - optional: true, - min: 0, - }, - appliedBy: { - type: Object, - optional: true, - }, - 'appliedBy.name': { - type: String, - max: STORAGE_LIMITS.name, - }, - 'appliedBy.id': { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - 'appliedBy.collection': { - type: String, - max: STORAGE_LIMITS.collectionName, - }, + description: { + type: 'computedOnlyInlineCalculationField', + optional: true, + max: STORAGE_LIMITS.description, + }, + duration: { + type: 'computedOnlyField', + optional: true, + }, + durationSpent: { + type: Number, + optional: true, + min: 0, + }, + appliedBy: { + type: Object, + optional: true, + }, + 'appliedBy.name': { + type: String, + max: STORAGE_LIMITS.name, + }, + 'appliedBy.id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + 'appliedBy.collection': { + type: String, + max: STORAGE_LIMITS.collectionName, + }, }); const ComputedBuffSchema = new SimpleSchema() - .extend(BuffSchema) - .extend(ComputedOnlyBuffSchema); + .extend(BuffSchema) + .extend(ComputedOnlyBuffSchema); export { BuffSchema, ComputedOnlyBuffSchema, ComputedBuffSchema }; diff --git a/app/imports/api/properties/Containers.js b/app/imports/api/properties/Containers.js index 565e0be6..6936bd15 100644 --- a/app/imports/api/properties/Containers.js +++ b/app/imports/api/properties/Containers.js @@ -3,69 +3,69 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; let ContainerSchema = createPropertySchema({ - name: { - type: String, - optional: true, - trim: false, - max: STORAGE_LIMITS.name, - }, - carried: { - type: Boolean, - defaultValue: true, - optional: true, - }, - contentsWeightless: { - type: Boolean, - optional: true, - }, - weight: { - type: Number, - min: 0, - optional: true, - }, - value: { - type: Number, - min: 0, - optional: true, - }, - description: { - type: 'inlineCalculationFieldToCompute', - optional: true, - }, + name: { + type: String, + optional: true, + trim: false, + max: STORAGE_LIMITS.name, + }, + carried: { + type: Boolean, + defaultValue: true, + optional: true, + }, + contentsWeightless: { + type: Boolean, + optional: true, + }, + weight: { + type: Number, + min: 0, + optional: true, + }, + value: { + type: Number, + min: 0, + optional: true, + }, + description: { + type: 'inlineCalculationFieldToCompute', + optional: true, + }, }); const ComputedOnlyContainerSchema = createPropertySchema({ - description: { - type: 'computedOnlyInlineCalculationField', - optional: true, - }, - // Weight of all the contents, zero if `contentsWeightless` is true - contentsWeight: { - type: Number, - optional: true, - removeBeforeCompute: true, - }, - // Weight of all the carried contents (some sub-containers might not be carried) - // zero if `contentsWeightless` is true - carriedWeight: { - type: Number, - optional: true, - removeBeforeCompute: true, - }, - contentsValue: { - type: Number, - optional: true, - removeBeforeCompute: true, - }, - carriedValue: { - type: Number, - optional: true, - removeBeforeCompute: true, - }, + description: { + type: 'computedOnlyInlineCalculationField', + optional: true, + }, + // Weight of all the contents, zero if `contentsWeightless` is true + contentsWeight: { + type: Number, + optional: true, + removeBeforeCompute: true, + }, + // Weight of all the carried contents (some sub-containers might not be carried) + // zero if `contentsWeightless` is true + carriedWeight: { + type: Number, + optional: true, + removeBeforeCompute: true, + }, + contentsValue: { + type: Number, + optional: true, + removeBeforeCompute: true, + }, + carriedValue: { + type: Number, + optional: true, + removeBeforeCompute: true, + }, }); const ComputedContainerSchema = new SimpleSchema() - .extend(ComputedOnlyContainerSchema) - .extend(ContainerSchema); + .extend(ComputedOnlyContainerSchema) + .extend(ContainerSchema); export { ContainerSchema, ComputedOnlyContainerSchema, ComputedContainerSchema }; diff --git a/app/imports/api/properties/Items.js b/app/imports/api/properties/Items.js index 2183d567..a328cfcf 100644 --- a/app/imports/api/properties/Items.js +++ b/app/imports/api/properties/Items.js @@ -3,69 +3,69 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; const ItemSchema = createPropertySchema({ - name: { - type: String, - optional: true, - max: STORAGE_LIMITS.name, - }, - // Plural name of the item, if there is more than one - plural: { - type: String, - optional: true, - max: STORAGE_LIMITS.name, - }, - description: { - type: 'inlineCalculationFieldToCompute', - optional: true, - }, - // Number currently held - quantity: { - type: SimpleSchema.Integer, - min: 0, - defaultValue: 1 - }, - // Weight per item in the stack - weight: { - type: Number, - min: 0, - optional: true, - }, - // Value per item in the stack, in gold pieces - value: { - type: Number, - min: 0, - optional: true, - }, - // If this item is equipped, it requires attunement - requiresAttunement: { - type: Boolean, - optional: true, - }, - attuned: { - type: Boolean, - optional: true, - }, - // Show increment/decrement buttons in item lists - showIncrement: { - type: Boolean, - optional: true, - }, - // Unequipped items shouldn't affect creature stats - equipped: { - type: Boolean, - defaultValue: false, - }, + name: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + // Plural name of the item, if there is more than one + plural: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + description: { + type: 'inlineCalculationFieldToCompute', + optional: true, + }, + // Number currently held + quantity: { + type: SimpleSchema.Integer, + min: 0, + defaultValue: 1 + }, + // Weight per item in the stack + weight: { + type: Number, + min: 0, + optional: true, + }, + // Value per item in the stack, in gold pieces + value: { + type: Number, + min: 0, + optional: true, + }, + // If this item is equipped, it requires attunement + requiresAttunement: { + type: Boolean, + optional: true, + }, + attuned: { + type: Boolean, + optional: true, + }, + // Show increment/decrement buttons in item lists + showIncrement: { + type: Boolean, + optional: true, + }, + // Unequipped items shouldn't affect creature stats + equipped: { + type: Boolean, + defaultValue: false, + }, }); let ComputedOnlyItemSchema = createPropertySchema({ - description: { - type: 'computedOnlyInlineCalculationField', - optional: true, - }, + description: { + type: 'computedOnlyInlineCalculationField', + optional: true, + }, }); const ComputedItemSchema = new SimpleSchema() - .extend(ItemSchema) - .extend(ComputedOnlyItemSchema); + .extend(ItemSchema) + .extend(ComputedOnlyItemSchema); export { ItemSchema, ComputedItemSchema, ComputedOnlyItemSchema }; diff --git a/app/imports/api/properties/Spells.js b/app/imports/api/properties/Spells.js index bcb4ee3b..575c9b5e 100644 --- a/app/imports/api/properties/Spells.js +++ b/app/imports/api/properties/Spells.js @@ -3,99 +3,99 @@ import SimpleSchema from 'simpl-schema'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; const magicSchools = [ - 'abjuration', - 'conjuration', - 'divination', - 'enchantment', - 'evocation', - 'illusion', - 'necromancy', - 'transmutation', + 'abjuration', + 'conjuration', + 'divination', + 'enchantment', + 'evocation', + 'illusion', + 'necromancy', + 'transmutation', ]; let SpellSchema = new SimpleSchema({}) - .extend(ActionSchema) - .extend({ - name: { - type: String, - optional: true, - max: STORAGE_LIMITS.name, - }, - // If it's always prepared, it doesn't count against the number of spells - // prepared in a spell list, and enabled should be true - alwaysPrepared: { - type: Boolean, - optional: true, - }, - prepared: { - type: Boolean, - optional: true, - }, - // This spell ignores spell slot rules - castWithoutSpellSlots: { - type: Boolean, - optional: true, - }, - hasAttackRoll: { - type: Boolean, - optional: true, - }, - castingTime: { - type: String, - optional: true, - defaultValue: 'action', - max: STORAGE_LIMITS.spellDetail, - }, - range: { - type: String, - optional: true, - max: STORAGE_LIMITS.spellDetail, - }, - duration: { - type: String, - optional: true, - defaultValue: 'Instantaneous', - max: STORAGE_LIMITS.spellDetail, - }, - verbal: { - type: Boolean, - optional: true, - }, - somatic: { - type: Boolean, - optional: true, - }, - concentration: { - type: Boolean, - optional: true, - }, - material: { - type: String, - optional: true, - max: STORAGE_LIMITS.spellDetail, - }, - ritual: { - type: Boolean, - optional: true, - }, - level: { - type: SimpleSchema.Integer, - defaultValue: 1, - max: 9, - min: 0, - }, - school: { - type: String, - defaultValue: 'abjuration', - allowedValues: magicSchools, - }, - }); + .extend(ActionSchema) + .extend({ + name: { + type: String, + optional: true, + max: STORAGE_LIMITS.name, + }, + // If it's always prepared, it doesn't count against the number of spells + // prepared in a spell list, and enabled should be true + alwaysPrepared: { + type: Boolean, + optional: true, + }, + prepared: { + type: Boolean, + optional: true, + }, + // This spell ignores spell slot rules + castWithoutSpellSlots: { + type: Boolean, + optional: true, + }, + hasAttackRoll: { + type: Boolean, + optional: true, + }, + castingTime: { + type: String, + optional: true, + defaultValue: 'action', + max: STORAGE_LIMITS.spellDetail, + }, + range: { + type: String, + optional: true, + max: STORAGE_LIMITS.spellDetail, + }, + duration: { + type: String, + optional: true, + defaultValue: 'Instantaneous', + max: STORAGE_LIMITS.spellDetail, + }, + verbal: { + type: Boolean, + optional: true, + }, + somatic: { + type: Boolean, + optional: true, + }, + concentration: { + type: Boolean, + optional: true, + }, + material: { + type: String, + optional: true, + max: STORAGE_LIMITS.spellDetail, + }, + ritual: { + type: Boolean, + optional: true, + }, + level: { + type: SimpleSchema.Integer, + defaultValue: 1, + max: 9, + min: 0, + }, + school: { + type: String, + defaultValue: 'abjuration', + allowedValues: magicSchools, + }, + }); const ComputedOnlySpellSchema = new SimpleSchema() - .extend(ComputedOnlyActionSchema); + .extend(ComputedOnlyActionSchema); const ComputedSpellSchema = new SimpleSchema() - .extend(SpellSchema) - .extend(ComputedOnlySpellSchema); + .extend(SpellSchema) + .extend(ComputedOnlySpellSchema); export { SpellSchema, ComputedOnlySpellSchema, ComputedSpellSchema }; diff --git a/app/imports/api/properties/subSchemas/ColorSchema.js b/app/imports/api/properties/subSchemas/ColorSchema.js index f258c269..0597b336 100644 --- a/app/imports/api/properties/subSchemas/ColorSchema.js +++ b/app/imports/api/properties/subSchemas/ColorSchema.js @@ -1,12 +1,12 @@ import SimpleSchema from 'simpl-schema'; const ColorSchema = new SimpleSchema({ - color: { - type: String, - // match hex colors of the form #A23 or #A23f56 - regEx: /^#([a-f0-9]{3}){1,2}\b$/i, - optional: true, - }, + color: { + type: String, + // match hex colors of the form #A23 or #A23f56 + regEx: /^#([a-f0-9]{3}){1,2}\b$/i, + optional: true, + }, }); export default ColorSchema; diff --git a/app/imports/api/properties/subSchemas/DeathSavesSchema.js b/app/imports/api/properties/subSchemas/DeathSavesSchema.js index ea7f2933..b6238544 100644 --- a/app/imports/api/properties/subSchemas/DeathSavesSchema.js +++ b/app/imports/api/properties/subSchemas/DeathSavesSchema.js @@ -1,26 +1,26 @@ import SimpleSchema from 'simpl-schema'; const DeathSavesSchema = new SimpleSchema({ - pass: { - type: SimpleSchema.Integer, - min: 0, - max: 3, - defaultValue: 0, - }, - fail: { - type: SimpleSchema.Integer, - min: 0, - max: 3, - defaultValue: 0, - }, - canDeathSave: { - type: Boolean, - defaultValue: true, - }, - stable: { - type: Boolean, - defaultValue: false, - }, + pass: { + type: SimpleSchema.Integer, + min: 0, + max: 3, + defaultValue: 0, + }, + fail: { + type: SimpleSchema.Integer, + min: 0, + max: 3, + defaultValue: 0, + }, + canDeathSave: { + type: Boolean, + defaultValue: true, + }, + stable: { + type: Boolean, + defaultValue: false, + }, }); export default DeathSavesSchema; diff --git a/app/imports/api/sharing/SharingSchema.js b/app/imports/api/sharing/SharingSchema.js index a22da7cd..2a4f87c3 100644 --- a/app/imports/api/sharing/SharingSchema.js +++ b/app/imports/api/sharing/SharingSchema.js @@ -3,36 +3,36 @@ import '/imports/api/sharing/sharing.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; let SharingSchema = new SimpleSchema({ - owner: { - type: String, - regEx: SimpleSchema.RegEx.Id, - index: 1 - }, - readers: { - type: Array, - defaultValue: [], - index: 1, - maxCount: STORAGE_LIMITS.readersCount, - }, - 'readers.$': { - type: String, - regEx: SimpleSchema.RegEx.Id - }, - writers: { - type: Array, - defaultValue: [], - index: 1, - maxCount: STORAGE_LIMITS.writersCount, - }, - 'writers.$': { - type: String, - regEx: SimpleSchema.RegEx.Id - }, - public: { - type: Boolean, - defaultValue: false, - index: 1, - }, + owner: { + type: String, + regEx: SimpleSchema.RegEx.Id, + index: 1 + }, + readers: { + type: Array, + defaultValue: [], + index: 1, + maxCount: STORAGE_LIMITS.readersCount, + }, + 'readers.$': { + type: String, + regEx: SimpleSchema.RegEx.Id + }, + writers: { + type: Array, + defaultValue: [], + index: 1, + maxCount: STORAGE_LIMITS.writersCount, + }, + 'writers.$': { + type: String, + regEx: SimpleSchema.RegEx.Id + }, + public: { + type: Boolean, + defaultValue: false, + index: 1, + }, }); export default SharingSchema; diff --git a/app/imports/api/users/Users.js b/app/imports/api/users/Users.js index 40e50311..54328914 100644 --- a/app/imports/api/users/Users.js +++ b/app/imports/api/users/Users.js @@ -11,321 +11,321 @@ const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LI const defaultLibraryCollections = process.env.DEFAULT_LIBRARY_COLLECTIONS && process.env.DEFAULT_LIBRARY_COLLECTIONS.split(',') || []; const userSchema = new SimpleSchema({ - username: { - type: String, - optional: true, - max: 30, - min: 4, - }, - emails: { - type: Array, - optional: true, - }, - 'emails.$': { - type: Object, - }, - 'emails.$.address': { - type: String, - regEx: SimpleSchema.RegEx.Email, - }, - 'emails.$.verified': { - type: Boolean, - }, - registered_emails: { - type: Array, - optional: true, - }, - 'registered_emails.$': { - type: Object, - blackbox: true, - }, - createdAt: { - type: Date - }, - services: { - type: Object, - optional: true, - blackbox: true, - }, - roles: { - type: Array, - optional: true, - }, - 'roles.$': { - type: String - }, - // In order to avoid an 'Exception in setInterval callback' from Meteor - heartbeat: { - type: Date, - optional: true, - }, - apiKey: { - type: String, - index: 1, - optional: true, - }, - darkMode: { - type: Boolean, - optional: true, - }, - subscribedLibraries: { - type: Array, - defaultValue: defaultLibraries, - maxCount: 100, - }, - 'subscribedLibraries.$': { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - subscribedLibraryCollections: { - type: Array, - defaultValue: defaultLibraryCollections, - maxCount: 100, - }, - 'subscribedLibraryCollections.$': { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - subscribedCharacters: { - type: Array, - defaultValue: [], - max: 100, - }, - 'subscribedCharacters.$': { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - fileStorageUsed: { - type: Number, - optional: true, - }, - profile: { - type: Object, - blackbox: true, - optional: true, - }, - preferences: { - type: Object, - optional: true, - defaultValue: {}, - }, - 'preferences.swapAbilityScoresAndModifiers': { - type: Boolean, - optional: true, - }, - 'preferences.hidePropertySelectDialogHelp': { - type: Boolean, - optional: true, - }, + username: { + type: String, + optional: true, + max: 30, + min: 4, + }, + emails: { + type: Array, + optional: true, + }, + 'emails.$': { + type: Object, + }, + 'emails.$.address': { + type: String, + regEx: SimpleSchema.RegEx.Email, + }, + 'emails.$.verified': { + type: Boolean, + }, + registered_emails: { + type: Array, + optional: true, + }, + 'registered_emails.$': { + type: Object, + blackbox: true, + }, + createdAt: { + type: Date + }, + services: { + type: Object, + optional: true, + blackbox: true, + }, + roles: { + type: Array, + optional: true, + }, + 'roles.$': { + type: String + }, + // In order to avoid an 'Exception in setInterval callback' from Meteor + heartbeat: { + type: Date, + optional: true, + }, + apiKey: { + type: String, + index: 1, + optional: true, + }, + darkMode: { + type: Boolean, + optional: true, + }, + subscribedLibraries: { + type: Array, + defaultValue: defaultLibraries, + maxCount: 100, + }, + 'subscribedLibraries.$': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + subscribedLibraryCollections: { + type: Array, + defaultValue: defaultLibraryCollections, + maxCount: 100, + }, + 'subscribedLibraryCollections.$': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + subscribedCharacters: { + type: Array, + defaultValue: [], + max: 100, + }, + 'subscribedCharacters.$': { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + fileStorageUsed: { + type: Number, + optional: true, + }, + profile: { + type: Object, + blackbox: true, + optional: true, + }, + preferences: { + type: Object, + optional: true, + defaultValue: {}, + }, + 'preferences.swapAbilityScoresAndModifiers': { + type: Boolean, + optional: true, + }, + 'preferences.hidePropertySelectDialogHelp': { + type: Boolean, + optional: true, + }, }); Meteor.users.attachSchema(userSchema); Meteor.users.generateApiKey = new ValidatedMethod({ - name: 'users.generateApiKey', - validate: null, - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run() { - if (Meteor.isClient) return; - var user = Meteor.users.findOne(this.userId); - if (!user) return; - if (user && user.apiKey) return; - var apiKey = Random.id(30); - Meteor.users.update(this.userId, { $set: { apiKey } }); - }, + name: 'users.generateApiKey', + validate: null, + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run() { + if (Meteor.isClient) return; + var user = Meteor.users.findOne(this.userId); + if (!user) return; + if (user && user.apiKey) return; + var apiKey = Random.id(30); + Meteor.users.update(this.userId, { $set: { apiKey } }); + }, }); Meteor.users.setDarkMode = new ValidatedMethod({ - name: 'users.setDarkMode', - validate: new SimpleSchema({ - darkMode: { type: Boolean }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ darkMode }) { - if (!this.userId) return; - Meteor.users.update(this.userId, { $set: { darkMode } }); - }, + name: 'users.setDarkMode', + validate: new SimpleSchema({ + darkMode: { type: Boolean }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ darkMode }) { + if (!this.userId) return; + Meteor.users.update(this.userId, { $set: { darkMode } }); + }, }); Meteor.users.sendVerificationEmail = new ValidatedMethod({ - name: 'users.sendVerificationEmail', - validate: new SimpleSchema({ - userId: { - type: String, - optional: true, - }, - address: { - type: String, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ userId, address }) { - userId = this.userId || userId; - let user = Meteor.users.findOne(userId); - if (!user) { - throw new Meteor.Error('User not found', - 'Can\'t send a validation email to a user that does not exist'); - } - if (!some(user.emails, email => email.address === address)) { - throw new Meteor.Error('Email address not found', - 'The specified email address wasn\'t found on this user account'); - } - Accounts.sendVerificationEmail(userId, address); - } + name: 'users.sendVerificationEmail', + validate: new SimpleSchema({ + userId: { + type: String, + optional: true, + }, + address: { + type: String, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ userId, address }) { + userId = this.userId || userId; + let user = Meteor.users.findOne(userId); + if (!user) { + throw new Meteor.Error('User not found', + 'Can\'t send a validation email to a user that does not exist'); + } + if (!some(user.emails, email => email.address === address)) { + throw new Meteor.Error('Email address not found', + 'The specified email address wasn\'t found on this user account'); + } + Accounts.sendVerificationEmail(userId, address); + } }); Meteor.users.canPickUsername = new ValidatedMethod({ - name: 'users.canPickUsername', - validate: userSchema.pick('username').validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ username }) { - if (Meteor.isClient) return; - let user = Accounts.findUserByUsername(username); - // You can pick your own username - if (user && user._id === this.userId) { - return false; - } - return !!user; - } + name: 'users.canPickUsername', + validate: userSchema.pick('username').validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ username }) { + if (Meteor.isClient) return; + let user = Accounts.findUserByUsername(username); + // You can pick your own username + if (user && user._id === this.userId) { + return false; + } + return !!user; + } }); Meteor.users.setUsername = new ValidatedMethod({ - name: 'users.setUsername', - validate: userSchema.pick('username').validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ username }) { - if (!this.userId) throw 'Can only set your username if logged in'; - if (Meteor.isClient) return; - return Accounts.setUsername(this.userId, username) - } + name: 'users.setUsername', + validate: userSchema.pick('username').validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ username }) { + if (!this.userId) throw 'Can only set your username if logged in'; + if (Meteor.isClient) return; + return Accounts.setUsername(this.userId, username) + } }); Meteor.users.setPreference = new ValidatedMethod({ - name: 'users.setPreference', - validate: new SimpleSchema({ - preference: { - type: String, - }, - value: { - type: SimpleSchema.oneOf(Boolean), - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ preference, value }) { - if (!this.userId) throw 'You can only set preferences once logged in'; - let prefPath = `preferences.${preference}` - if (value == true) { - return Meteor.users.update(this.userId, { - $set: { [prefPath]: true }, - }); - } else { - return Meteor.users.update(this.userId, { - $unset: { [prefPath]: 1 }, - }); - } - }, + name: 'users.setPreference', + validate: new SimpleSchema({ + preference: { + type: String, + }, + value: { + type: SimpleSchema.oneOf(Boolean), + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ preference, value }) { + if (!this.userId) throw 'You can only set preferences once logged in'; + let prefPath = `preferences.${preference}` + if (value == true) { + return Meteor.users.update(this.userId, { + $set: { [prefPath]: true }, + }); + } else { + return Meteor.users.update(this.userId, { + $unset: { [prefPath]: 1 }, + }); + } + }, }); Meteor.users.subscribeToLibrary = new ValidatedMethod({ - name: 'users.subscribeToLibrary', - validate: new SimpleSchema({ - libraryId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - subscribe: { - type: Boolean, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ libraryId, subscribe }) { - if (!this.userId) throw 'Can only subscribe if logged in'; - if (subscribe) { - return Meteor.users.update(this.userId, { - $addToSet: { subscribedLibraries: libraryId }, - }); - } else { - return Meteor.users.update(this.userId, { - $pullAll: { subscribedLibraries: libraryId }, - }); - } - } + name: 'users.subscribeToLibrary', + validate: new SimpleSchema({ + libraryId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + subscribe: { + type: Boolean, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ libraryId, subscribe }) { + if (!this.userId) throw 'Can only subscribe if logged in'; + if (subscribe) { + return Meteor.users.update(this.userId, { + $addToSet: { subscribedLibraries: libraryId }, + }); + } else { + return Meteor.users.update(this.userId, { + $pullAll: { subscribedLibraries: libraryId }, + }); + } + } }); Meteor.users.subscribeToLibraryCollection = new ValidatedMethod({ - name: 'users.subscribeToLibraryCollection', - validate: new SimpleSchema({ - libraryCollectionId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - }, - subscribe: { - type: Boolean, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ libraryCollectionId, subscribe }) { - if (!this.userId) throw 'Can only subscribe if logged in'; - if (subscribe) { - return Meteor.users.update(this.userId, { - $addToSet: { subscribedLibraryCollections: libraryCollectionId }, - }); - } else { - return Meteor.users.update(this.userId, { - $pullAll: { subscribedLibraryCollections: libraryCollectionId }, - }); - } - } + name: 'users.subscribeToLibraryCollection', + validate: new SimpleSchema({ + libraryCollectionId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + subscribe: { + type: Boolean, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ libraryCollectionId, subscribe }) { + if (!this.userId) throw 'Can only subscribe if logged in'; + if (subscribe) { + return Meteor.users.update(this.userId, { + $addToSet: { subscribedLibraryCollections: libraryCollectionId }, + }); + } else { + return Meteor.users.update(this.userId, { + $pullAll: { subscribedLibraryCollections: libraryCollectionId }, + }); + } + } }); Meteor.users.findUserByUsernameOrEmail = new ValidatedMethod({ - name: 'users.findUserByUsernameOrEmail', - validate: new SimpleSchema({ - usernameOrEmail: { - type: String, - }, - }).validator(), - mixins: [RateLimiterMixin], - rateLimit: { - numRequests: 5, - timeInterval: 5000, - }, - run({ usernameOrEmail }) { - if (Meteor.isClient) return; - let user = Accounts.findUserByUsername(usernameOrEmail) || - Accounts.findUserByEmail(usernameOrEmail); - return user && user._id; - } + name: 'users.findUserByUsernameOrEmail', + validate: new SimpleSchema({ + usernameOrEmail: { + type: String, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ usernameOrEmail }) { + if (Meteor.isClient) return; + let user = Accounts.findUserByUsername(usernameOrEmail) || + Accounts.findUserByEmail(usernameOrEmail); + return user && user._id; + } }); diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js index d8a449f8..ec217527 100644 --- a/app/imports/parser/grammar.js +++ b/app/imports/parser/grammar.js @@ -4,7 +4,7 @@ function id(x) { return x[0]; } import node from './parseTree/_index.js'; - import moo from 'moo'; + import moo from 'moo'; const lexer = moo.compile({ number: /[0-9]+(?:\.[0-9]+)?/, diff --git a/app/imports/server/cron/deleteSoftRemovedDocuments.js b/app/imports/server/cron/deleteSoftRemovedDocuments.js index 22a1d5ad..b22d0293 100644 --- a/app/imports/server/cron/deleteSoftRemovedDocuments.js +++ b/app/imports/server/cron/deleteSoftRemovedDocuments.js @@ -4,47 +4,47 @@ import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js'; import { SyncedCron } from 'meteor/littledata:synced-cron'; Meteor.startup(() => { - const collections = [ - CreatureProperties, - LibraryNodes, - ]; + const collections = [ + CreatureProperties, + LibraryNodes, + ]; - /** - * Deletes all soft removed documents that were removed more than 1 day ago - * and were not restored - * @return {Number} Number of documents removed - */ - const deleteOldSoftRemovedDocs = function () { - const now = new Date(); - const yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000)); - collections.forEach(collection => { - collection.remove({ - removed: true, - removedAt: { $lt: yesterday } // dates *before* yesterday - }, function (error) { - if (error) { - console.error(JSON.stringify(error, null, 2)); - } - }); - }); - }; + /** + * Deletes all soft removed documents that were removed more than 1 day ago + * and were not restored + * @return {Number} Number of documents removed + */ + const deleteOldSoftRemovedDocs = function () { + const now = new Date(); + const yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000)); + collections.forEach(collection => { + collection.remove({ + removed: true, + removedAt: { $lt: yesterday } // dates *before* yesterday + }, function (error) { + if (error) { + console.error(JSON.stringify(error, null, 2)); + } + }); + }); + }; - SyncedCron.add({ - name: 'deleteSoftRemovedDocs', - schedule: function (parser) { - return parser.text('every 10 minutes'); - }, - job: deleteOldSoftRemovedDocs, - }); + SyncedCron.add({ + name: 'deleteSoftRemovedDocs', + schedule: function (parser) { + return parser.text('every 10 minutes'); + }, + job: deleteOldSoftRemovedDocs, + }); - SyncedCron.start(); + SyncedCron.start(); - // Add a method to manually trigger removal - Meteor.methods({ - deleteOldSoftRemovedDocs() { - assertAdmin(this.userId); - this.unblock(); - deleteOldSoftRemovedDocs(); - }, - }); + // Add a method to manually trigger removal + Meteor.methods({ + deleteOldSoftRemovedDocs() { + assertAdmin(this.userId); + this.unblock(); + deleteOldSoftRemovedDocs(); + }, + }); }); diff --git a/app/imports/ui/components/ColumnLayout.vue b/app/imports/ui/components/ColumnLayout.vue index 3b53c998..8d3fc918 100644 --- a/app/imports/ui/components/ColumnLayout.vue +++ b/app/imports/ui/components/ColumnLayout.vue @@ -12,46 +12,46 @@ diff --git a/app/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue b/app/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue index 6acf3c2e..8cc185f2 100644 --- a/app/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue +++ b/app/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue @@ -19,72 +19,72 @@ import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue'; import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js'; export default { - components: { - TreeNodeList, - }, - props: { - root: { - type: Object, - default: undefined, - }, - organize: Boolean, - selectedNode: { - type: Object, - default: undefined, - }, - filter: { - type: Object, - default: undefined, - }, - group: { - type: String, - default: 'creatureProperties' - }, - expanded: Boolean, - }, - meteor: { - children() { - const children = nodesToTree({ - collection: CreatureProperties, - ancestorId: this.root.id, - filter: this.filter, - includeFilteredDocAncestors: true, - includeFilteredDocDescendants: true, - }); - this.$emit('length', children.length); - return children; - }, - }, - methods: { - reordered({ doc, newIndex }) { - reorderDoc.call({ - docRef: { - id: doc._id, - collection: 'creatureProperties', - }, - order: newIndex, - }); - }, - reorganized({ doc, parent, newIndex }) { - let parentRef; - if (parent) { - parentRef = { - id: parent._id, - collection: 'creatureProperties', - }; - } else { - parentRef = this.root; - } - organizeDoc.call({ - docRef: { - id: doc._id, - collection: 'creatureProperties', - }, - parentRef, - order: newIndex, - }); - }, - }, + components: { + TreeNodeList, + }, + props: { + root: { + type: Object, + default: undefined, + }, + organize: Boolean, + selectedNode: { + type: Object, + default: undefined, + }, + filter: { + type: Object, + default: undefined, + }, + group: { + type: String, + default: 'creatureProperties' + }, + expanded: Boolean, + }, + meteor: { + children() { + const children = nodesToTree({ + collection: CreatureProperties, + ancestorId: this.root.id, + filter: this.filter, + includeFilteredDocAncestors: true, + includeFilteredDocDescendants: true, + }); + this.$emit('length', children.length); + return children; + }, + }, + methods: { + reordered({ doc, newIndex }) { + reorderDoc.call({ + docRef: { + id: doc._id, + collection: 'creatureProperties', + }, + order: newIndex, + }); + }, + reorganized({ doc, parent, newIndex }) { + let parentRef; + if (parent) { + parentRef = { + id: parent._id, + collection: 'creatureProperties', + }; + } else { + parentRef = this.root; + } + organizeDoc.call({ + docRef: { + id: doc._id, + collection: 'creatureProperties', + }, + parentRef, + order: newIndex, + }); + }, + }, }; diff --git a/app/imports/ui/dialogStack/mockElement.js b/app/imports/ui/dialogStack/mockElement.js index e3f19654..6f179af5 100644 --- a/app/imports/ui/dialogStack/mockElement.js +++ b/app/imports/ui/dialogStack/mockElement.js @@ -2,65 +2,65 @@ import { parse, stringify } from 'css-box-shadow'; // Only supports border radius defined like "20px" or "100%" const transformedRadius = (radiusString, deltaWidth, deltaHeight) => { - if (/^\d+\.?\d*px$/.test(radiusString)) { - //The radius is defined in pixel units, so get the radius as a number - const rad = +radiusString.match(/\d+\.?\d*/)[0]; - // Set the x and y radius of the "to" element, compensating for scale - return `${rad / deltaWidth}px / ${rad / deltaHeight}px`; - } else if (/^\d+\.?\d*%$/.test(radiusString)) { - //The radius is defined as a percentage, so just use it as is - return radiusString; - } + if (/^\d+\.?\d*px$/.test(radiusString)) { + //The radius is defined in pixel units, so get the radius as a number + const rad = +radiusString.match(/\d+\.?\d*/)[0]; + // Set the x and y radius of the "to" element, compensating for scale + return `${rad / deltaWidth}px / ${rad / deltaHeight}px`; + } else if (/^\d+\.?\d*%$/.test(radiusString)) { + //The radius is defined as a percentage, so just use it as is + return radiusString; + } }; const transformedBoxShadow = (shadowString, deltaWidth, deltaHeight) => { - if (shadowString === 'none') return shadowString; - if (shadowString[0] === 'r') { - let strings = shadowString.match(/rgba\([^)]+\)[^,]+/g); - strings = strings.map(string => { - // Move color to end - let m = string.match(/(rgba\([^)]+\))([^,]+)/); - return `${m[2].trim()} ${m[1]}`; - }); - shadowString = strings.join(', '); - } - let scaleAverage = (deltaWidth + deltaHeight) / 2; - let shadows = parse(shadowString); - shadows.forEach(shadow => { - shadow.offsetX /= deltaWidth; - shadow.offsetY /= deltaHeight; - shadow.blurRadius /= scaleAverage; - shadow.spreadRadius /= scaleAverage; - }) - return stringify(shadows); + if (shadowString === 'none') return shadowString; + if (shadowString[0] === 'r') { + let strings = shadowString.match(/rgba\([^)]+\)[^,]+/g); + strings = strings.map(string => { + // Move color to end + let m = string.match(/(rgba\([^)]+\))([^,]+)/); + return `${m[2].trim()} ${m[1]}`; + }); + shadowString = strings.join(', '); + } + let scaleAverage = (deltaWidth + deltaHeight) / 2; + let shadows = parse(shadowString); + shadows.forEach(shadow => { + shadow.offsetX /= deltaWidth; + shadow.offsetY /= deltaHeight; + shadow.blurRadius /= scaleAverage; + shadow.spreadRadius /= scaleAverage; + }) + return stringify(shadows); } export default function mockElement({ source, target, offset = { x: 0, y: 0 } }) { - if (!source || !target) throw `Can't mock without ${source ? 'target' : 'source'}`; - let sourceRect = source.getBoundingClientRect(); - let targetRect = target.getBoundingClientRect(); + if (!source || !target) throw `Can't mock without ${source ? 'target' : 'source'}`; + let sourceRect = source.getBoundingClientRect(); + let targetRect = target.getBoundingClientRect(); - // Get how must the target change to become the source - const deltaWidth = sourceRect.width / targetRect.width; - const deltaHeight = sourceRect.height / targetRect.height; - const deltaLeft = sourceRect.left - targetRect.left + offset.x; - const deltaTop = sourceRect.top - targetRect.top + offset.y; - // Mock the source - target.style.transform = `translate(${deltaLeft}px, ${deltaTop}px) ` + - `scale(${deltaWidth}, ${deltaHeight})`; - // Mock the background color unless it's completely transparent - let backgroundColor = getComputedStyle(source).backgroundColor - if (backgroundColor !== 'rgba(0, 0, 0, 0)') { - target.style.backgroundColor = backgroundColor; - } - // Edge might not combine all border radii into a single value, - // So we just sample the top left one if we need to - let oldRadius = getComputedStyle(source).borderRadius || - getComputedStyle(source).borderTopLeftRadius; - let borderRadius = transformedRadius(oldRadius, deltaWidth, deltaHeight); - target.style.borderRadius = borderRadius; - let boxShadow = transformedBoxShadow( - getComputedStyle(source).boxShadow, deltaWidth, deltaHeight - ); - target.style.setProperty('box-shadow', boxShadow, 'important'); + // Get how must the target change to become the source + const deltaWidth = sourceRect.width / targetRect.width; + const deltaHeight = sourceRect.height / targetRect.height; + const deltaLeft = sourceRect.left - targetRect.left + offset.x; + const deltaTop = sourceRect.top - targetRect.top + offset.y; + // Mock the source + target.style.transform = `translate(${deltaLeft}px, ${deltaTop}px) ` + + `scale(${deltaWidth}, ${deltaHeight})`; + // Mock the background color unless it's completely transparent + let backgroundColor = getComputedStyle(source).backgroundColor + if (backgroundColor !== 'rgba(0, 0, 0, 0)') { + target.style.backgroundColor = backgroundColor; + } + // Edge might not combine all border radii into a single value, + // So we just sample the top left one if we need to + let oldRadius = getComputedStyle(source).borderRadius || + getComputedStyle(source).borderTopLeftRadius; + let borderRadius = transformedRadius(oldRadius, deltaWidth, deltaHeight); + target.style.borderRadius = borderRadius; + let boxShadow = transformedBoxShadow( + getComputedStyle(source).boxShadow, deltaWidth, deltaHeight + ); + target.style.setProperty('box-shadow', boxShadow, 'important'); } diff --git a/app/imports/ui/properties/viewers/BuffViewer.vue b/app/imports/ui/properties/viewers/BuffViewer.vue index 231b60ae..214f33f2 100644 --- a/app/imports/ui/properties/viewers/BuffViewer.vue +++ b/app/imports/ui/properties/viewers/BuffViewer.vue @@ -25,41 +25,41 @@ import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyV import numberToSignedString from '/imports/ui/utility/numberToSignedString.js'; export default { - mixins: [propertyViewerMixin], - computed: { - reset() { - let reset = this.model.reset - if (reset === 'shortRest') { - return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier - } on a short rest`; - } else if (reset === 'longRest') { - return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier - } on a long rest`; - } else { - return undefined; - } - } - }, - methods: { - numberToSignedString, - } + mixins: [propertyViewerMixin], + computed: { + reset() { + let reset = this.model.reset + if (reset === 'shortRest') { + return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier + } on a short rest`; + } else if (reset === 'longRest') { + return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier + } on a long rest`; + } else { + return undefined; + } + } + }, + methods: { + numberToSignedString, + } } diff --git a/app/jsconfig.json b/app/jsconfig.json new file mode 100644 index 00000000..d113b9c5 --- /dev/null +++ b/app/jsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node", + "target": "ES2020", + "jsx": "react", + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": ".", + "paths": { + "/*": [ + "./*" + ] + } + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] +} \ No newline at end of file diff --git a/app/public/manifest.json b/app/public/manifest.json index 70392a3f..4d831406 100644 --- a/app/public/manifest.json +++ b/app/public/manifest.json @@ -1,43 +1,43 @@ { - "name": "DiceCloud", - "icons": [ - { - "src": "\/android-chrome-36x36.png?v=lk6WXp6Pmj", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "\/android-chrome-48x48.png?v=lk6WXp6Pmj", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "\/android-chrome-72x72.png?v=lk6WXp6Pmj", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "\/android-chrome-96x96.png?v=lk6WXp6Pmj", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "\/android-chrome-144x144.png?v=lk6WXp6Pmj", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "\/android-chrome-192x192.png?v=lk6WXp6Pmj", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - } - ], - "start_url": "\/", - "display": "standalone" + "name": "DiceCloud", + "icons": [ + { + "src": "\/android-chrome-36x36.png?v=lk6WXp6Pmj", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-chrome-48x48.png?v=lk6WXp6Pmj", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-chrome-72x72.png?v=lk6WXp6Pmj", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-chrome-96x96.png?v=lk6WXp6Pmj", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-chrome-144x144.png?v=lk6WXp6Pmj", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-chrome-192x192.png?v=lk6WXp6Pmj", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ], + "start_url": "\/", + "display": "standalone" } \ No newline at end of file