-
-
-
-
-
-
$emit('change', {path: ['description', ...path], value, ack})"
/>
-
+
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
diff --git a/app/imports/client/ui/properties/forms/NoteForm.vue b/app/imports/client/ui/properties/forms/NoteForm.vue
index 36d693bb..43397b4f 100644
--- a/app/imports/client/ui/properties/forms/NoteForm.vue
+++ b/app/imports/client/ui/properties/forms/NoteForm.vue
@@ -1,18 +1,10 @@
-
-
$emit('change', {path: ['summary', ...path], value, ack})"
/>
@@ -21,27 +13,17 @@
label="Description"
hint="Text that does not fit in the summary"
:model="model.description"
- :error-messages="errors.description"
+ :error-messages="errors['description.text']"
@change="({path, value, ack}) =>
$emit('change', {path: ['description', ...path], value, ack})"
/>
-
-
-
-
+
+
diff --git a/app/imports/client/ui/properties/forms/PointBuyForm.vue b/app/imports/client/ui/properties/forms/PointBuyForm.vue
index 4698559d..e366abf9 100644
--- a/app/imports/client/ui/properties/forms/PointBuyForm.vue
+++ b/app/imports/client/ui/properties/forms/PointBuyForm.vue
@@ -1,26 +1,13 @@
@@ -188,6 +185,9 @@ export default {
PointBuySpendForm,
},
mixins: [propertyFormMixin, attributeListMixin],
+ inject: {
+ context: { default: {} }
+ },
data() {
return {
addRowLoading: false,
diff --git a/app/imports/client/ui/properties/forms/PointBuySpendForm.vue b/app/imports/client/ui/properties/forms/PointBuySpendForm.vue
index 1eeb8ed8..5fbbaf28 100644
--- a/app/imports/client/ui/properties/forms/PointBuySpendForm.vue
+++ b/app/imports/client/ui/properties/forms/PointBuySpendForm.vue
@@ -118,6 +118,7 @@ export default {
const currentSpent = this.model.spent;
let newSpent = currentSpent - row.spent;
const costFunction = EJSON.clone(row.cost || this.model.cost);
+ if (!costFunction?.parseNode) return;
if (costFunction) costFunction.parseLevel = 'reduce';
evaluateCalculation(costFunction, { value });
if (Number.isFinite(costFunction.value)) {
diff --git a/app/imports/client/ui/properties/forms/ProficiencyForm.vue b/app/imports/client/ui/properties/forms/ProficiencyForm.vue
index a8a18d09..f66c7055 100644
--- a/app/imports/client/ui/properties/forms/ProficiencyForm.vue
+++ b/app/imports/client/ui/properties/forms/ProficiencyForm.vue
@@ -1,49 +1,86 @@
-
-
-
-
-
+
+
+
+
+
+ {
+ if (val === 'skills') val = undefined;
+ if (val === 'tags') val = true;
+ change('targetByTags', val, ack);
+ }"
+ />
+
+
+
+ $emit('change', e)"
+ @push="e => $emit('push', e)"
+ @pull="e => $emit('pull', e)"
+ />
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -51,10 +88,12 @@
import ProficiencySelect from '/imports/client/ui/properties/forms/shared/ProficiencySelect.vue';
import skillListMixin from '/imports/client/ui/properties/forms/shared/lists/skillListMixin.js';
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
+import TagTargeting from '/imports/client/ui/properties/forms/shared/TagTargeting.vue';
export default {
components: {
ProficiencySelect,
+ TagTargeting,
},
mixins: [propertyFormMixin, skillListMixin],
};
diff --git a/app/imports/client/ui/properties/forms/ReferenceForm.vue b/app/imports/client/ui/properties/forms/ReferenceForm.vue
index 88032c03..1248f89a 100644
--- a/app/imports/client/ui/properties/forms/ReferenceForm.vue
+++ b/app/imports/client/ui/properties/forms/ReferenceForm.vue
@@ -1,34 +1,66 @@
-
@@ -36,12 +68,12 @@
import TreeNodeView from '/imports/client/ui/properties/treeNodeViews/TreeNodeView.vue';
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
import updateReferenceNode from '/imports/api/library/methods/updateReferenceNode.js';
- import PropertyField from '/imports/client/ui/properties/viewers/shared/PropertyField.vue';
+ import OutlinedInput from '/imports/client/ui/properties/viewers/shared/OutlinedInput.vue';
export default {
components: {
TreeNodeView,
- PropertyField,
+ OutlinedInput,
},
mixins: [propertyFormMixin],
data(){return {
diff --git a/app/imports/client/ui/properties/forms/RollForm.vue b/app/imports/client/ui/properties/forms/RollForm.vue
index 87b4664a..27b326c4 100644
--- a/app/imports/client/ui/properties/forms/RollForm.vue
+++ b/app/imports/client/ui/properties/forms/RollForm.vue
@@ -1,17 +1,6 @@
-
-
-
-
-
- $emit('change', {path: ['roll', ...path], value, ack})"
- />
-
-
-
-
-
-
-
-
-
-
-
-
+ $emit('change', {path: ['roll', ...path], value, ack})"
+ />
+
+
+
+
+
+
diff --git a/app/imports/client/ui/properties/forms/SavingThrowForm.vue b/app/imports/client/ui/properties/forms/SavingThrowForm.vue
index b2bd4cf5..4b5aaf25 100644
--- a/app/imports/client/ui/properties/forms/SavingThrowForm.vue
+++ b/app/imports/client/ui/properties/forms/SavingThrowForm.vue
@@ -1,25 +1,13 @@
-
-
-
@@ -43,41 +31,29 @@
cols="12"
md="6"
>
-
-
-
-
-
-
+
+
+
+
+
+
@@ -87,25 +63,5 @@ import propertyFormMixin from '/imports/client/ui/properties/forms/shared/proper
export default {
mixins: [saveListMixin, propertyFormMixin],
- computed: {
- targetOptions() {
- return [
- {
- text: 'Self',
- value: 'self',
- }, {
- text: 'Target',
- value: 'target',
- },
- ];
- },
- targetOptionHint() {
- let hints = {
- self: 'The save will be applied to the character taking the action',
- target: 'The save will be applied to the targets of the action',
- };
- return hints[this.model.target];
- }
- },
};
diff --git a/app/imports/client/ui/properties/forms/SkillForm.vue b/app/imports/client/ui/properties/forms/SkillForm.vue
index e7c2ab54..7078ea59 100644
--- a/app/imports/client/ui/properties/forms/SkillForm.vue
+++ b/app/imports/client/ui/properties/forms/SkillForm.vue
@@ -1,85 +1,105 @@
@@ -89,11 +109,13 @@ import ProficiencySelect from '/imports/client/ui/properties/forms/shared/Profic
import FormSection from '/imports/client/ui/properties/forms/shared/FormSection.vue';
import createListOfProperties from '/imports/client/ui/properties/forms/shared/lists/createListOfProperties.js';
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
+import TagTargeting from '/imports/client/ui/properties/forms/shared/TagTargeting.vue';
export default {
components: {
ProficiencySelect,
FormSection,
+ TagTargeting,
},
mixins: [propertyFormMixin],
data() {
diff --git a/app/imports/client/ui/properties/forms/SlotFillerForm.vue b/app/imports/client/ui/properties/forms/SlotFillerForm.vue
deleted file mode 100644
index 88e0f350..00000000
--- a/app/imports/client/ui/properties/forms/SlotFillerForm.vue
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/imports/client/ui/properties/forms/SlotForm.vue b/app/imports/client/ui/properties/forms/SlotForm.vue
index e973932b..14fab234 100644
--- a/app/imports/client/ui/properties/forms/SlotForm.vue
+++ b/app/imports/client/ui/properties/forms/SlotForm.vue
@@ -1,200 +1,144 @@
@@ -203,19 +147,19 @@
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
import FormSection from '/imports/client/ui/properties/forms/shared/FormSection.vue';
import PROPERTIES from '/imports/constants/PROPERTIES.js';
-import { SlotSchema } from '/imports/api/properties/Slots.js';
+import TagTargeting from '/imports/client/ui/properties/forms/shared/TagTargeting.vue';
+import OutlinedInput from '/imports/client/ui/properties/viewers/shared/OutlinedInput.vue';
export default {
components: {
FormSection,
+ TagTargeting,
+ OutlinedInput,
},
mixins: [propertyFormMixin],
inject: {
context: { default: {} }
},
- props: {
- classForm: Boolean,
- },
data() {
let slotTypes = [];
for (let key in PROPERTIES) {
@@ -223,8 +167,6 @@ export default {
}
return {
slotTypes,
- addExtraTagsLoading: false,
- extraTagOperations: ['OR', 'NOT'],
uniqueOptions: [{
text: 'Each property inside this slot should be unique',
value: 'uniqueInSlot',
@@ -234,29 +176,7 @@ export default {
}],
};
},
- computed: {
- extraTagsFull() {
- if (!this.model.extraTags) return false;
- let maxCount = SlotSchema.get('extraTags', 'maxCount');
- return this.model.extraTags.length >= maxCount;
- }
- },
methods: {
- acknowledgeAddResult() {
- this.addExtraTagsLoading = false;
- },
- addExtraTags() {
- this.addExtraTagsLoading = true;
- this.$emit('push', {
- path: ['extraTags'],
- value: {
- _id: Random.id(),
- operation: 'OR',
- tags: [],
- },
- ack: this.acknowledgeAddResult,
- });
- },
testSlot() {
if (!this.context.isLibraryForm) return;
this.$store.commit('pushDialogStack', {
diff --git a/app/imports/client/ui/properties/forms/SpellForm.vue b/app/imports/client/ui/properties/forms/SpellForm.vue
index 14c06817..34a6cce1 100644
--- a/app/imports/client/ui/properties/forms/SpellForm.vue
+++ b/app/imports/client/ui/properties/forms/SpellForm.vue
@@ -1,17 +1,5 @@
-
-
- $emit('change', e)"
- />
-
-
-
-
-
-
@@ -205,6 +187,7 @@
attackSwitch = e"
@@ -218,7 +201,18 @@
:error-messages="errors.attackRoll"
@change="({path, value, ack}) =>
$emit('change', {path: ['attackRoll', ...path], value, ack})"
- />
+ >
+
+
+ mdi-close
+
+
+
@@ -226,18 +220,18 @@
label="Summary"
hint="This will appear in the action card in the character sheet, summarise what the action does"
:model="model.summary"
- :error-messages="errors.summary"
+ :error-messages="errors['summary.text']"
@change="({path, value, ack}) =>
$emit('change', {path: ['summary', ...path], value, ack})"
/>
$emit('change', {path: ['description', ...path], value, ack})"
/>
-
+
-
-
-
-
-
-
-
-
+
@@ -311,7 +287,6 @@
-
-
diff --git a/app/imports/client/ui/properties/forms/ToggleForm.vue b/app/imports/client/ui/properties/forms/ToggleForm.vue
index 3a9f80fe..24cbd6d5 100644
--- a/app/imports/client/ui/properties/forms/ToggleForm.vue
+++ b/app/imports/client/ui/properties/forms/ToggleForm.vue
@@ -1,18 +1,6 @@
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+ $emit('change', {path: ['condition', ...path], value, ack})"
+ />
+
+
+
+
+
+
+
+ $emit('change', e)"
+ @push="e => $emit('push', e)"
+ @pull="e => $emit('pull', e)"
+ />
+
-
-
- $emit('change', {path: ['condition', ...path], value, ack})"
- />
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/imports/client/ui/properties/forms/shared/IconColorMenu.vue b/app/imports/client/ui/properties/forms/shared/IconColorMenu.vue
index aa58ed16..824f645c 100644
--- a/app/imports/client/ui/properties/forms/shared/IconColorMenu.vue
+++ b/app/imports/client/ui/properties/forms/shared/IconColorMenu.vue
@@ -1,32 +1,39 @@
-
- $emit('change', {path: ['color'], value})"
- />
-
-
+
$emit('change', {path: ['icon'], value, ack})"
/>
-
+
+
+ $emit('change', {path: ['color'], value})"
+ />
+
diff --git a/app/imports/client/ui/properties/forms/shared/TagTargeting.vue b/app/imports/client/ui/properties/forms/shared/TagTargeting.vue
new file mode 100644
index 00000000..fb9663a0
--- /dev/null
+++ b/app/imports/client/ui/properties/forms/shared/TagTargeting.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+ mdi-plus
+
+
+
+
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/imports/client/ui/properties/forms/shared/propertyFormIndex.js b/app/imports/client/ui/properties/forms/shared/propertyFormIndex.js
index 28cf4172..def2dba2 100644
--- a/app/imports/client/ui/properties/forms/shared/propertyFormIndex.js
+++ b/app/imports/client/ui/properties/forms/shared/propertyFormIndex.js
@@ -22,7 +22,6 @@ import RollForm from '/imports/client/ui/properties/forms/RollForm.vue';
import SavingThrowForm from '/imports/client/ui/properties/forms/SavingThrowForm.vue';
import SkillForm from '/imports/client/ui/properties/forms/SkillForm.vue';
import SlotForm from '/imports/client/ui/properties/forms/SlotForm.vue';
-import SlotFillerForm from '/imports/client/ui/properties/forms/SlotFillerForm.vue';
import SpellListForm from '/imports/client/ui/properties/forms/SpellListForm.vue';
import SpellForm from '/imports/client/ui/properties/forms/SpellForm.vue';
import ToggleForm from '/imports/client/ui/properties/forms/ToggleForm.vue';
@@ -53,7 +52,6 @@ export default {
roll: RollForm,
savingThrow: SavingThrowForm,
skill: SkillForm,
- slotFiller: SlotFillerForm,
spellList: SpellListForm,
spell: SpellForm,
toggle: ToggleForm,
diff --git a/app/imports/client/ui/properties/forms/shared/propertyFormMixin.js b/app/imports/client/ui/properties/forms/shared/propertyFormMixin.js
index 58c3ec28..84f6760e 100644
--- a/app/imports/client/ui/properties/forms/shared/propertyFormMixin.js
+++ b/app/imports/client/ui/properties/forms/shared/propertyFormMixin.js
@@ -19,22 +19,12 @@ export default {
default: () => ({}),
},
},
- mounted(){
- // Don't autofocus on mobile, it brings up the on-screen keyboard
- if (this.$vuetify.breakpoint.smAndDown) return;
-
- setTimeout(() => {
- if (this.$refs.focusFirst && this.$refs.focusFirst.focus){
- this.$refs.focusFirst.focus()
- }
- }, 300);
- },
methods: {
- change(path, value, ack){
- if (!Array.isArray(path)){
+ change(path, value, ack) {
+ if (!Array.isArray(path)) {
path = [path];
}
- this.$emit('change', {path, value, ack});
+ this.$emit('change', { path, value, ack });
}
},
}
diff --git a/app/imports/client/ui/properties/shared/PropertySelectCard.vue b/app/imports/client/ui/properties/shared/PropertySelectCard.vue
index 2e7f08f8..e8fab6fe 100644
--- a/app/imports/client/ui/properties/shared/PropertySelectCard.vue
+++ b/app/imports/client/ui/properties/shared/PropertySelectCard.vue
@@ -2,6 +2,8 @@
$emit('click', e)"
>
@@ -46,6 +47,7 @@
>
@@ -72,6 +74,10 @@ export default {
type: Array,
default: undefined,
},
+ currentType: {
+ type: String,
+ default: undefined,
+ }
},
data() {
return {
diff --git a/app/imports/client/ui/properties/shared/PropertyViewer.vue b/app/imports/client/ui/properties/shared/PropertyViewer.vue
index 20a3aeed..de7777b0 100644
--- a/app/imports/client/ui/properties/shared/PropertyViewer.vue
+++ b/app/imports/client/ui/properties/shared/PropertyViewer.vue
@@ -1,11 +1,149 @@
-
+ >
+
+
+
+
+ Inactive
+
+
+
Deactivated by:
+
+
+
+
+
+ Deactivated by ancestor
+
+
+ Deactivated by own settings
+
+
+
+
+ selectSubProperty(id)"
+ />
+
+
+
+
+
+ Can fill slots
+
+
+ Searchable from character sheet
+
+
+
+
+
+
+
+
+
+
+ {{ tag }}
+
+
+
+
+
+
+
+ {{ tag }}
+
+
+
+
+
+
+
+
This property can't be viewed yet.
@@ -13,15 +151,49 @@
diff --git a/app/imports/client/ui/properties/viewers/ActionViewer.vue b/app/imports/client/ui/properties/viewers/ActionViewer.vue
index b76c1b4c..fb989d21 100644
--- a/app/imports/client/ui/properties/viewers/ActionViewer.vue
+++ b/app/imports/client/ui/properties/viewers/ActionViewer.vue
@@ -197,7 +197,7 @@ export default {
slotId,
ritual,
scope: {
- $attackAdvantage: advantage,
+ '~attackAdvantage': { value: advantage },
},
}, error => {
if (!error) return;
diff --git a/app/imports/client/ui/properties/viewers/EffectViewer.vue b/app/imports/client/ui/properties/viewers/EffectViewer.vue
index db389971..37914808 100644
--- a/app/imports/client/ui/properties/viewers/EffectViewer.vue
+++ b/app/imports/client/ui/properties/viewers/EffectViewer.vue
@@ -17,39 +17,10 @@
name="Amount"
:value="displayedValue || ' '"
/>
-
-
-
-
- {{ tag }}
-
-
-
-
- {{ ex.operation }}
-
-
-
- {{ extraTag }}
-
-
-
-
-
+ :model="model"
+ />
import propertyViewerMixin from '/imports/client/ui/properties/viewers/shared/propertyViewerMixin.js';
+import PropertyTargetTags from '/imports/client/ui/properties/viewers/shared/PropertyTargetTags.vue';
import getEffectIcon from '/imports/client/ui/utility/getEffectIcon.js';
import { isFinite } from 'lodash';
export default {
+ components: {
+ PropertyTargetTags,
+ },
mixins: [propertyViewerMixin],
computed: {
resolvedValue() {
diff --git a/app/imports/client/ui/properties/viewers/FolderViewer.vue b/app/imports/client/ui/properties/viewers/FolderViewer.vue
index 91e24a6d..dc0c979d 100644
--- a/app/imports/client/ui/properties/viewers/FolderViewer.vue
+++ b/app/imports/client/ui/properties/viewers/FolderViewer.vue
@@ -1,5 +1,18 @@
-
+
-
diff --git a/app/imports/client/ui/properties/viewers/SkillViewer.vue b/app/imports/client/ui/properties/viewers/SkillViewer.vue
index fdbf34cb..d790436a 100644
--- a/app/imports/client/ui/properties/viewers/SkillViewer.vue
+++ b/app/imports/client/ui/properties/viewers/SkillViewer.vue
@@ -53,6 +53,9 @@
name="Overridden"
value="Overriden by another property with the same variable name"
/>
+
-
-
-
-
-
-
@@ -16,6 +16,9 @@
name="Condition"
:calculation="model.condition"
/>
+
@@ -23,8 +26,12 @@
diff --git a/app/imports/client/ui/properties/viewers/shared/OutlinedInput.vue b/app/imports/client/ui/properties/viewers/shared/OutlinedInput.vue
new file mode 100644
index 00000000..1b82bf6b
--- /dev/null
+++ b/app/imports/client/ui/properties/viewers/shared/OutlinedInput.vue
@@ -0,0 +1,58 @@
+
+
+
+ {{ name }}
+
+
+
+
+
+
+
+
diff --git a/app/imports/client/ui/properties/viewers/shared/PropertyField.vue b/app/imports/client/ui/properties/viewers/shared/PropertyField.vue
index f72a91a5..fe1f0d51 100644
--- a/app/imports/client/ui/properties/viewers/shared/PropertyField.vue
+++ b/app/imports/client/ui/properties/viewers/shared/PropertyField.vue
@@ -6,18 +6,18 @@
v-bind="cols"
class="mb-3"
>
-
-
{{ name }}
-
+
+
-
+
\ No newline at end of file
diff --git a/app/imports/client/ui/properties/viewers/shared/propertyViewerIndex.js b/app/imports/client/ui/properties/viewers/shared/propertyViewerIndex.js
index 456bdbf5..29753094 100644
--- a/app/imports/client/ui/properties/viewers/shared/propertyViewerIndex.js
+++ b/app/imports/client/ui/properties/viewers/shared/propertyViewerIndex.js
@@ -22,7 +22,6 @@ import RollViewer from '/imports/client/ui/properties/viewers/RollViewer.vue';
import SkillViewer from '/imports/client/ui/properties/viewers/SkillViewer.vue';
import SavingThrowViewer from '/imports/client/ui/properties/viewers/SavingThrowViewer.vue';
import SlotViewer from '/imports/client/ui/properties/viewers/SlotViewer.vue';
-import SlotFillerViewer from '/imports/client/ui/properties/viewers/SlotFillerViewer.vue';
import SpellListViewer from '/imports/client/ui/properties/viewers/SpellListViewer.vue';
import SpellViewer from '/imports/client/ui/properties/viewers/SpellViewer.vue';
import ToggleViewer from '/imports/client/ui/properties/viewers/ToggleViewer.vue';
@@ -52,7 +51,6 @@ export default {
roll: RollViewer,
reference: ReferenceViewer,
savingThrow: SavingThrowViewer,
- slotFiller: SlotFillerViewer,
skill: SkillViewer,
spellList: SpellListViewer,
spell: SpellViewer,
diff --git a/app/imports/client/ui/router.js b/app/imports/client/ui/router.js
index b7e473be..4f3fe2cc 100644
--- a/app/imports/client/ui/router.js
+++ b/app/imports/client/ui/router.js
@@ -9,6 +9,7 @@ const CharacterListToolbarItems = () => import('/imports/client/ui/creature/crea
const Library = () => import('/imports/client/ui/pages/Library.vue');
const LibraryCollection = () => import('/imports/client/ui/pages/LibraryCollection.vue');
const LibraryCollectionToolbar = () => import('/imports/client/ui/library/LibraryCollectionToolbar.vue');
+const LibraryBrowser = () => import('/imports/client/ui/pages/LibraryBrowser.vue');
const CharacterSheetPage = () => import('/imports/client/ui/pages/CharacterSheetPage.vue');
const CharacterSheetToolbar = () => import('/imports/client/ui/creature/character/CharacterSheetToolbar.vue');
const CharacterSheetRightDrawer = () => import('/imports/client/ui/creature/character/CharacterSheetRightDrawer.vue');
@@ -168,6 +169,15 @@ RouterFactory.configure(router => {
meta: {
title: 'Library Collection',
},
+ }, {
+ name: 'libraryBrowser',
+ path: '/community-libraries',
+ components: {
+ default: LibraryBrowser,
+ },
+ meta: {
+ title: 'Community Libraries',
+ },
}, {
name: 'characterSheet',
path: '/character/:id',
@@ -377,7 +387,11 @@ RouterFactory.configure(router => {
function redirectIfMaintenance(to, from, next) {
if (!MAINTENANCE_MODE) return next();
- if (to?.path === '/admin' || to?.path === '/maintenance' || to?.path === '/sign-in') return next();
+ if (
+ to?.path === '/admin' ||
+ to?.path === '/maintenance' ||
+ to?.path === '/sign-in'
+ ) return next();
Tracker.autorun((computation) => {
if (userSubscription.ready()) {
computation.stop();
diff --git a/app/imports/client/ui/sharing/ShareDialog.vue b/app/imports/client/ui/sharing/ShareDialog.vue
index 7db4aa24..5b81c469 100644
--- a/app/imports/client/ui/sharing/ShareDialog.vue
+++ b/app/imports/client/ui/sharing/ShareDialog.vue
@@ -27,7 +27,7 @@
v-if="model.public && docRef.collection === 'libraries'"
readonly
label="Link"
- :value="'https://beta.dicecloud.com' + $router.resolve({
+ :value="'https://dicecloud.com' + $router.resolve({
name: 'singleLibrary',
params: { id: model._id },
}).href"
diff --git a/app/imports/client/ui/utility/numberFormatter.js b/app/imports/client/ui/utility/numberFormatter.js
new file mode 100644
index 00000000..794498f3
--- /dev/null
+++ b/app/imports/client/ui/utility/numberFormatter.js
@@ -0,0 +1,3 @@
+const formatter = Intl.NumberFormat('en', { notation: 'compact' });
+
+export default formatter;
\ No newline at end of file
diff --git a/app/imports/client/ui/vuexStore.js b/app/imports/client/ui/vuexStore.js
index d4578aa0..fec340cd 100644
--- a/app/imports/client/ui/vuexStore.js
+++ b/app/imports/client/ui/vuexStore.js
@@ -17,6 +17,7 @@ const store = new Vuex.Store({
pageTitle: undefined,
characterSheetTabs: {},
showDetailsDialog: false,
+ formExpansions: {},
},
getters: {
tabById: (state) => (id) => {
@@ -30,7 +31,10 @@ const store = new Vuex.Store({
} else {
return tabs[tabNumber]
}
- }
+ },
+ formExpansionByType: (state) => (type) => {
+ return state.formExpansions[type] || [];
+ },
},
mutations: {
toggleDrawer(state) {
@@ -68,6 +72,9 @@ const store = new Vuex.Store({
setShowDetailsDialog(state, value) {
state.showDetailsDialog = value;
},
+ setFormExpansion(state, { type, value }) {
+ state.formExpansions[type] = value;
+ },
},
});
diff --git a/app/imports/constants/PROPERTIES.js b/app/imports/constants/PROPERTIES.js
index f3db0cbe..4686082c 100644
--- a/app/imports/constants/PROPERTIES.js
+++ b/app/imports/constants/PROPERTIES.js
@@ -170,13 +170,6 @@ const PROPERTIES = Object.freeze({
helpText: 'A slot in the character sheet is used to specify that a property needs to be selected from a library to fill the slot. The slot can determine what tags it is looking for, and any subscribed library property with matching tags can fill the slot',
suggestedParents: [],
},
- slotFiller: {
- icon: 'mdi-power-plug-outline',
- name: 'Slot filler',
- docsPath: 'property/slot-filler',
- helpText: 'A slot filler allows for more advanced logic when it attempts to fill a slot. It can masquarade as any property type, and calculate whether it should fill a slot or not.',
- suggestedParents: ['propertySlot'],
- },
spellList: {
icon: '$vuetify.icons.spell_list',
name: 'Spell list',
@@ -209,14 +202,25 @@ const PROPERTIES = Object.freeze({
export default PROPERTIES;
-export function getPropertyName(type){
- return type && PROPERTIES[type] && PROPERTIES[type].name;
+export function getPropertyName(type) {
+ return (type && PROPERTIES[type] && PROPERTIES[type].name) || type;
}
-export function getPropertyIcon(type){
+export function getPropertyIcon(type) {
return type && PROPERTIES[type] && PROPERTIES[type].icon;
}
+export function getSuggestedChildren(type) {
+ const suggestions = [];
+ for (const key in PROPERTIES) {
+ const prop = PROPERTIES[key];
+ if (prop.suggestedParents.includes(type)) {
+ suggestions.push({ type: key, details: prop });
+ }
+ }
+ return suggestions;
+}
+
const propsByDocsPath = new Map();
for (const key in PROPERTIES) {
diff --git a/app/imports/constants/SCHEMA_VERSION.js b/app/imports/constants/SCHEMA_VERSION.js
index 4ac57384..829e4147 100644
--- a/app/imports/constants/SCHEMA_VERSION.js
+++ b/app/imports/constants/SCHEMA_VERSION.js
@@ -1,3 +1,3 @@
-const SCHEMA_VERSION = 1;
+const SCHEMA_VERSION = 2;
export default SCHEMA_VERSION;
diff --git a/app/imports/constants/VARIABLE_NAME_REGEX.js b/app/imports/constants/VARIABLE_NAME_REGEX.js
index ac7c1804..8ea249ca 100644
--- a/app/imports/constants/VARIABLE_NAME_REGEX.js
+++ b/app/imports/constants/VARIABLE_NAME_REGEX.js
@@ -1,4 +1,4 @@
// Must contain a letter, and be made of word characters only
-const VARIABLE_NAME_REGEX = /^[a-z][\w-]*$/i;
+const VARIABLE_NAME_REGEX = /^[~#]?[a-zA-Z]*[a-ce-zA-Z][a-zA-Z0-9_]*$/i;
export default VARIABLE_NAME_REGEX;
diff --git a/app/imports/migrations/archive/cleanArchiveAt2.js b/app/imports/migrations/archive/cleanArchiveAt2.js
new file mode 100644
index 00000000..224734ba
--- /dev/null
+++ b/app/imports/migrations/archive/cleanArchiveAt2.js
@@ -0,0 +1,16 @@
+import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
+
+export default function cleanAt2(archive) {
+ archive.properties = archive.properties.map(prop => {
+ let cleanProp = prop;
+ try {
+ const schema = CreatureProperties.simpleSchema(prop);
+ // Clean according to schema
+ cleanProp = schema.clean(prop);
+ schema.validate(cleanProp);
+ } catch (e) {
+ console.warn('Failed to clean archive prop', { propId: prop._id, error: e.message || e.reason || e.toString() });
+ }
+ return cleanProp;
+ });
+}
diff --git a/app/imports/migrations/archive/migrateArchive.js b/app/imports/migrations/archive/migrateArchive.js
new file mode 100644
index 00000000..029b146e
--- /dev/null
+++ b/app/imports/migrations/archive/migrateArchive.js
@@ -0,0 +1,28 @@
+import migrateTo1 from './migrateArchiveTo1.js';
+import migrate1To2 from './migrateArchive1To2.js';
+import cleanAt2 from './cleanArchiveAt2.js';
+
+/* eslint no-fallthrough: "off" -- Using switch fallthrough to run all
+migration steps after the current version of the file. */
+export default function migrateArchive(archive) {
+ switch (archive.meta.schemaVersion) {
+ // V1 of DiceCloud
+ case 'version1':
+ migrateLegacyArchive(archive);
+ // V2 of DiceCloud, Schema version 1
+ case 1:
+ migrateTo1(archive);
+ migrate1To2(archive);
+ // V2 of DiceCloud, Schema version 2
+ case 2:
+ cleanAt2(archive);
+ break;
+ default:
+ throw 'Archive version not supported';
+ }
+}
+
+function migrateLegacyArchive() {
+ // TODO:
+ throw 'Not implemented';
+}
diff --git a/app/imports/migrations/archive/migrateArchive1To2.js b/app/imports/migrations/archive/migrateArchive1To2.js
new file mode 100644
index 00000000..6038c2a9
--- /dev/null
+++ b/app/imports/migrations/archive/migrateArchive1To2.js
@@ -0,0 +1,50 @@
+import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
+import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
+import { get } from 'lodash';
+
+const dollarSignRegex = /(\W)\$(\w+)/gi;
+
+export default function migrate1To2(archive) {
+ archive.properties = archive.properties.map(prop => {
+ try {
+ // Migrate slot fillers to folders
+ if (prop.type === 'slotFiller') {
+ prop.type = 'folder';
+ }
+ // Migrate slot filler slot type to folders
+ if (prop.slotType === 'slotFiller') {
+ prop.slotType = 'folder';
+ }
+ // Get the schema
+ const schema = CreatureProperties.simpleSchema(prop);
+ // Replace dollar signs in calculations with tildes
+ schema.inlineCalculationFields().forEach(key => {
+ applyFnToKey(prop, key, (prop, key) => {
+ const inlineCalcObj = get(prop, key);
+ const string = inlineCalcObj?.text;
+ if (!string) return;
+ const newString = string.replace(dollarSignRegex, '$1~$2');
+ if (string !== newString) {
+ inlineCalcObj.text = newString;
+ inlineCalcObj.hash = null;
+ }
+ });
+ });
+ schema.computedFields().forEach(key => {
+ applyFnToKey(prop, key, (prop, key) => {
+ const inlineCalcObj = get(prop, key);
+ const string = inlineCalcObj?.calculation;
+ if (!string) return;
+ const newString = string.replace(dollarSignRegex, '$1~$2');
+ if (string !== newString) {
+ inlineCalcObj.calculation = newString;
+ inlineCalcObj.hash = null;
+ }
+ });
+ });
+ } catch (e) {
+ console.warn('Property migration 1 -> 2 failed: ', { propId: prop._id, error: e.message || e.reason || e.toString() });
+ }
+ return prop;
+ });
+}
diff --git a/app/imports/migrations/server/dbv1/cleanAt1.js b/app/imports/migrations/archive/migrateArchiveTo1.js
similarity index 62%
rename from app/imports/migrations/server/dbv1/cleanAt1.js
rename to app/imports/migrations/archive/migrateArchiveTo1.js
index d58e3c74..6e8fd558 100644
--- a/app/imports/migrations/server/dbv1/cleanAt1.js
+++ b/app/imports/migrations/archive/migrateArchiveTo1.js
@@ -1,39 +1,45 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { get, set } from 'lodash';
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
-import { calculationUp } from '/imports/migrations/server/dbv1/dbv1.js';
-export default function cleanAt1(archive){
+
+function calculationUp(val) {
+ if (typeof val !== 'string') return val;
+ if (!val.replace) console.log({ val, replace: val.replace });
+ return val.replace(/#(\w+).(\w+)Result/g, '#$1.$2')
+ .replace(/\.value/g, '.total')
+ .replace(/\.currentValue/g, '.value');
+}
+
+export default function migrateTo1(archive) {
archive.properties = archive.properties.map(prop => {
- let cleanProp = prop;
try {
if (prop.type === 'attack') prop.type = 'action';
+ if (prop.type === 'slotFiller') prop.type = 'folder';
// Get the schema
const schema = CreatureProperties.simpleSchema(prop);
// Clean all the text fields with inline calcs
schema.inlineCalculationFields().forEach(key => {
applyFnToKey(prop, key, (prop, key) => {
let field = get(prop, key);
- if (typeof field === 'string' || typeof field === 'number'){
+ if (typeof field === 'string' || typeof field === 'number') {
field = calculationUp(field);
- set(prop, key, {text: `${field}`});
+ set(prop, key, { text: `${field}` });
}
});
});
schema.computedFields().forEach(key => {
applyFnToKey(prop, key, (prop, key) => {
let field = get(prop, key) || get(prop, key + 'Calculation');
- if (typeof field === 'string' || typeof field === 'number'){
+ if (typeof field === 'string' || typeof field === 'number') {
field = calculationUp(field);
- set(prop, key, {calculation: `${field}`});
+ set(prop, key, { calculation: `${field}` });
}
});
});
- cleanProp = schema.clean(prop);
- schema.validate(cleanProp);
- } catch (e){
- console.warn({propId: prop._id, error: e.message || e.reason || e.toString()});
+ } catch (e) {
+ console.warn('Property migration -> 1 failed: ', { propId: prop._id, error: e.message || e.reason || e.toString() });
}
- return cleanProp;
+ return prop;
});
}
diff --git a/app/imports/migrations/server/dbv1/dbv1.js b/app/imports/migrations/server/dbv1/dbv1.js
index 8ecfef81..afcce135 100644
--- a/app/imports/migrations/server/dbv1/dbv1.js
+++ b/app/imports/migrations/server/dbv1/dbv1.js
@@ -10,52 +10,52 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
Migrations.add({
version: 1,
name: 'Unifies calculated field schema',
- up(){
+ up() {
migrate();
},
- down(){
- migrate({reversed: true});
+ down() {
+ migrate({ reversed: true });
},
});
-function migrate({reversed} = {}){
+function migrate({ reversed } = {}) {
console.log('migrating creature properties');
- migrateCollection({collection: CreatureProperties, reversed});
+ migrateCollection({ collection: CreatureProperties, reversed });
console.log('migrating library nodes')
- migrateCollection({collection: LibraryNodes, reversed});
+ migrateCollection({ collection: LibraryNodes, reversed });
}
-function migrateCollection({collection, reversed}){
+function migrateCollection({ collection, reversed }) {
const bulk = collection.rawCollection().initializeUnorderedBulkOp();
collection.find({}).forEach(prop => {
- const newProp = migrateProperty({collection, reversed, prop});
+ const newProp = migrateProperty({ collection, reversed, prop });
bulk.find({ _id: prop._id }).replaceOne(newProp);
});
bulk.execute();
}
-export function migrateProperty({collection, reversed, prop}){
+export function migrateProperty({ collection, reversed, prop }) {
const transforms = [
...(transformsByPropType[prop.type] || []),
- {from: 'dependencies'}
+ { from: 'dependencies' }
];
let migratedProp = transformFields(prop, transforms, reversed);
- const schema = collection.simpleSchema({type: migratedProp.type});
+ const schema = collection.simpleSchema({ type: migratedProp.type });
// Only clean if the schema version matches our destination version
- if(!reversed && SCHEMA_VERSION === 1){
+ if (!reversed && SCHEMA_VERSION >= 1) {
try {
migratedProp = schema.clean(migratedProp);
schema.validate(migratedProp);
- } catch(e){
- if (e.details[0]?.type === 'maxString'){
+ } catch (e) {
+ if (e.details[0]?.type === 'maxString') {
console.log({
prop: prop,
details: e.details,
});
} else {
- console.warn({prop, error: e});
+ console.warn({ prop, error: e });
}
}
}
@@ -74,31 +74,31 @@ const transformsByPropType = {
'action': actionTransforms,
'adjustment': [
...getComputedPropertyTransforms('amount'),
- {from: 'target', to: 'target', up: simplifyTarget},
+ { from: 'target', to: 'target', up: simplifyTarget },
],
'attack': [
...actionTransforms,
...getComputedPropertyTransforms('rollBonus', 'attackRoll'),
//change type to action
- {from: 'type', to: 'type', up: () => 'action'},
+ { from: 'type', to: 'type', up: () => 'action' },
],
'attribute': [
// from: baseValue must be first or else it will delete the field we need
- {from: 'baseValue', to: 'baseValue.value', up: nanToNull},
- {from: 'baseValueCalculation', to: 'baseValue.calculation', up: calculationUp, down: calculationDown},
- {from: 'baseValueErrors', to: 'baseValue.errors', up: trimErrors},
+ { from: 'baseValue', to: 'baseValue.value', up: nanToNull },
+ { from: 'baseValueCalculation', to: 'baseValue.calculation', up: calculationUp, down: calculationDown },
+ { from: 'baseValueErrors', to: 'baseValue.errors', up: trimErrors },
...getComputedPropertyTransforms('spellSlotLevel'),
...getInlineComputationTransforms('description'),
- {from: 'value', to: 'total', up: nanToNull},
- {from: 'currentValue', to: 'value', up: nanToNull},
- {from: 'proficiency', to: 'proficiency', up: stripZero},
+ { from: 'value', to: 'total', up: nanToNull },
+ { from: 'currentValue', to: 'value', up: nanToNull },
+ { from: 'proficiency', to: 'proficiency', up: stripZero },
],
'buff': [
...getComputedPropertyTransforms('duration'),
...getInlineComputationTransforms('description'),
- {from: 'value', to: 'total', up: nanToNull},
- {from: 'target', to: 'target', up: simplifyTarget},
- {from: 'applied'},
+ { from: 'value', to: 'total', up: nanToNull },
+ { from: 'target', to: 'target', up: simplifyTarget },
+ { from: 'applied' },
],
'classLevel': [
...getInlineComputationTransforms('description'),
@@ -108,20 +108,22 @@ const transformsByPropType = {
],
'damage': [
...getComputedPropertyTransforms('amount'),
- {from: 'target', to: 'target', up: simplifyTarget},
+ { from: 'target', to: 'target', up: simplifyTarget },
],
'effect': [
- {from: 'calculation', to: 'amount.calculation'},
- {from: 'result', to: 'amount.value', up: nanToNull},
- {from: 'errors', to: 'amount.errors', up: trimErrors},
- {from: 'name', to: 'name', up(val, src, doc){
- if (src.operation === 'conditional'){
- doc.text = val;
- return;
- } else {
- return val;
+ { from: 'calculation', to: 'amount.calculation' },
+ { from: 'result', to: 'amount.value', up: nanToNull },
+ { from: 'errors', to: 'amount.errors', up: trimErrors },
+ {
+ from: 'name', to: 'name', up(val, src, doc) {
+ if (src.operation === 'conditional') {
+ doc.text = val;
+ return;
+ } else {
+ return val;
+ }
}
- }},
+ },
],
'feature': [
...getInlineComputationTransforms('summary'),
@@ -139,20 +141,20 @@ const transformsByPropType = {
],
'savingThrow': [
...getComputedPropertyTransforms('dc'),
- {from: 'target', to: 'target', up: simplifyTarget},
+ { from: 'target', to: 'target', up: simplifyTarget },
],
'skill': [
...getComputedPropertyTransforms('baseValue'),
...getInlineComputationTransforms('description'),
- {from: 'value', to: 'value', up: nanToNull},
- {from: 'passiveBonus', to: 'passiveBonus', up: nanToNull},
- {from: 'proficiency', to: 'proficiency', up: stripZero},
+ { from: 'value', to: 'value', up: nanToNull },
+ { from: 'passiveBonus', to: 'passiveBonus', up: nanToNull },
+ { from: 'proficiency', to: 'proficiency', up: stripZero },
],
'spell': [
...actionTransforms,
],
'proficiency': [
- {from: 'value', to: 'value', up: stripZero},
+ { from: 'value', to: 'value', up: stripZero },
],
'propertySlot': [
...getComputedPropertyTransforms('quantityExpected'),
@@ -166,70 +168,70 @@ const transformsByPropType = {
...getInlineComputationTransforms('description'),
],
'toggle': [
- {from: 'condition', to: 'condition.calculation'},
- {from: 'toggleResult', to: 'condition.value', up: nanToNull},
- {from: 'errors', to: 'condition.errors', up: trimErrors},
+ { from: 'condition', to: 'condition.calculation' },
+ { from: 'toggleResult', to: 'condition.value', up: nanToNull },
+ { from: 'errors', to: 'condition.errors', up: trimErrors },
],
};
-function getComputedPropertyTransforms(key, toKey){
+function getComputedPropertyTransforms(key, toKey) {
if (!toKey) toKey = key;
return [
- {from: key, to: `${toKey}.calculation`, up: calculationUp, down: calculationDown},
- {from: `${key}Result`, to: `${toKey}.value`, up: nanToNull},
- {from: `${key}Errors`, to: `${toKey}.errors`, up: trimErrors},
+ { from: key, to: `${toKey}.calculation`, up: calculationUp, down: calculationDown },
+ { from: `${key}Result`, to: `${toKey}.value`, up: nanToNull },
+ { from: `${key}Errors`, to: `${toKey}.errors`, up: trimErrors },
];
}
-function getInlineComputationTransforms(key){
+function getInlineComputationTransforms(key) {
return [
- {from: key, to: `${key}.text`, up: calculationUp, down: calculationDown},
- {from: `${key}Calculations`, to: `${key}.inlineCalculations`, up: calculationUp, down: calculationDown},
- {from: `${key}Calculations.$.result`, to: `${key}.inlineCalculations.$.value`},
+ { from: key, to: `${key}.text`, up: calculationUp, down: calculationDown },
+ { from: `${key}Calculations`, to: `${key}.inlineCalculations`, up: calculationUp, down: calculationDown },
+ { from: `${key}Calculations.$.result`, to: `${key}.inlineCalculations.$.value` },
];
}
-export function calculationUp(val){
+export function calculationUp(val) {
if (typeof val !== 'string') return val;
- if (!val.replace) console.log({val, replace: val.replace});
+ if (!val.replace) console.log({ val, replace: val.replace });
return val.replace(/#(\w+).(\w+)Result/g, '#$1.$2')
.replace(/\.value/g, '.total')
.replace(/\.currentValue/g, '.value');
}
-function calculationDown(val){
+function calculationDown(val) {
if (typeof val !== 'string') return val;
return val.replace(/\.value/g, '.currentValue').replace(/\.total/g, '.value');
}
-function nanToNull(val){
- if (Number.isNaN(val)){
+function nanToNull(val) {
+ if (Number.isNaN(val)) {
return null;
} else {
return val;
}
}
-function stripZero(val){
- if (val === 0){
+function stripZero(val) {
+ if (val === 0) {
return undefined;
} else {
return val;
}
}
-function simplifyTarget(val){
- if (val === 'self'){
+function simplifyTarget(val) {
+ if (val === 'self') {
return val;
} else {
return 'target';
}
}
-function trimErrors(arr){
- if(!arr) return arr;
+function trimErrors(arr) {
+ if (!arr) return arr;
arr.forEach(e => {
- if (e.message.length > STORAGE_LIMITS.errorMessage){
+ if (e.message.length > STORAGE_LIMITS.errorMessage) {
e.message = e.message.slice(0, STORAGE_LIMITS.errorMessage);
}
});
diff --git a/app/imports/migrations/server/dbv1/dbv1.test.js b/app/imports/migrations/server/dbv1/dbv1.test.js
index efbd4fdd..edc81d81 100644
--- a/app/imports/migrations/server/dbv1/dbv1.test.js
+++ b/app/imports/migrations/server/dbv1/dbv1.test.js
@@ -31,7 +31,7 @@ const exampleAction = {
'ancestors': [{
'collection': 'creatures',
'id': 'X9rzFhsgFhodYfHmG'
- }, ],
+ },],
'order': 315,
'summary': 'Curse a creature for 1 minute. The curse ends early if {warlock.level >14 ? "" : "the target dies, or"} you are incapacitated. \nGain the following benefits: \n- *Bonus to damage rolls against the cursed target of* **+{proficiencyBonus}**. \n- Any attack roll you make against the cursed target is a **critical hit on a roll of 19 or 20**. \n- If the cursed target dies, you **regain {warlock.level+charisma.modifier} hit points**. \n{warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."}',
'uses': '1',
@@ -45,21 +45,21 @@ const exampleAction = {
'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
}],
'summaryCalculations': [{
- 'calculation': 'warlock.level >14 ? "" : "the target dies, or"',
- 'result': 'the target dies, or'
- },
- {
- 'calculation': 'proficiencyBonus',
- 'result': '4'
- },
- {
- 'calculation': 'warlock.level+charisma.modifier',
- 'result': '15'
- },
- {
- 'calculation': 'warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
- 'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
- }
+ 'calculation': 'warlock.level >14 ? "" : "the target dies, or"',
+ 'result': 'the target dies, or'
+ },
+ {
+ 'calculation': 'proficiencyBonus',
+ 'result': '4'
+ },
+ {
+ 'calculation': 'warlock.level+charisma.modifier',
+ 'result': '15'
+ },
+ {
+ 'calculation': 'warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
+ 'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
+ }
]
};
@@ -76,13 +76,13 @@ const exampleAttribute = {
'collection': 'creatureProperties'
},
ancestors: [{
- 'collection': 'creatures',
- 'id': 'm9sdCvs6iDf7qRaGv'
- },
- {
- 'id': '8jSWKxvgQyKbunFtD',
- 'collection': 'creatureProperties'
- }
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv'
+ },
+ {
+ 'id': '8jSWKxvgQyKbunFtD',
+ 'collection': 'creatureProperties'
+ }
],
order: 84,
value: 20,
@@ -110,13 +110,13 @@ const expectedMigratedAttribute = {
'collection': 'creatureProperties'
},
ancestors: [{
- 'collection': 'creatures',
- 'id': 'm9sdCvs6iDf7qRaGv'
- },
- {
- 'id': '8jSWKxvgQyKbunFtD',
- 'collection': 'creatureProperties'
- }
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv'
+ },
+ {
+ 'id': '8jSWKxvgQyKbunFtD',
+ 'collection': 'creatureProperties'
+ }
],
order: 84,
total: 20,
@@ -205,11 +205,10 @@ const expectedMigratedAttack = {
}],
'order': 56,
'usesUsed': 2,
- libraryTags: [],
}
-describe('migrateProperty', function() {
- it('Migrates actions reversibly', function() {
+describe('migrateProperty', function () {
+ it('Migrates actions reversibly', function () {
const action = {
...exampleAction
};
@@ -226,7 +225,7 @@ describe('migrateProperty', function() {
assert.deepEqual(action, exampleAction, 'action should not be bashed');
assert.deepEqual(exampleAction, reversedAction, 'operation should be reversible');
});
- it('Migrates attributes as expected', function() {
+ it('Migrates attributes as expected', function () {
const attribute = {
...exampleAttribute
};
@@ -237,7 +236,7 @@ describe('migrateProperty', function() {
assert.deepEqual(newAttribute, expectedMigratedAttribute,
'Attribute should match the expected result');
});
- it('Migrates attacks as expected', function() {
+ it('Migrates attacks as expected', function () {
const attribute = {
...exampleAttack
};
diff --git a/app/imports/migrations/server/dbv2/dbv2.js b/app/imports/migrations/server/dbv2/dbv2.js
new file mode 100644
index 00000000..9df7b2ce
--- /dev/null
+++ b/app/imports/migrations/server/dbv2/dbv2.js
@@ -0,0 +1,153 @@
+import { Migrations } from 'meteor/percolate:migrations';
+import LibraryNodes from '/imports/api/library/LibraryNodes.js';
+import { union, get } from 'lodash';
+import Libraries from '/imports/api/library/Libraries.js';
+import LibraryCollections from '/imports/api/library/LibraryCollections.js';
+import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
+import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
+import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
+
+// Git version 2.0.52
+// Database version 2
+Migrations.add({
+ version: 2,
+ name: 'Separates creature property tags from library tags',
+
+ up() {
+ console.log('migrating up library nodes 1 -> 2');
+ migrateCollection(LibraryNodes, migratePropUp);
+ console.log('migrating up creature props 1 -> 2');
+ migrateCollection(CreatureProperties, migratePropUp);
+ console.log('Migrating up libraries and collections to count subscribers');
+ countSubscribers();
+ },
+
+ down() {
+ console.log('Migrating down library nodes 2 -> 1');
+ migrateCollection(LibraryNodes, migratePropDown);
+ console.log('Migrating down creature props 2 -> 1');
+ migrateCollection(CreatureProperties, migratePropDown);
+ },
+
+});
+
+function migrateCollection(collection, migrateDoc) {
+ const bulk = collection.rawCollection().initializeUnorderedBulkOp();
+ collection.find({}).forEach(doc => migrateDoc(bulk, doc, collection));
+ bulk.execute();
+}
+
+export function migratePropUp(bulk, prop, collection) {
+ let update;
+ if (prop.type === 'slotFiller') {
+ update = update || { $set: {} };
+ // Change the type to folder, slotFiller is deprecated
+ update.$set.type = 'folder'
+ // If the slot filler has an image set, move it
+ if (typeof prop.picture === 'string') {
+ update.$set.slotFillImage = prop.picture;
+ update.$unset = { picture: 1 };
+ }
+ }
+
+ // Don't look for slot fillers
+ if (prop.slotType === 'slotFiller') {
+ update = update || { $set: {} };
+ update.$set.slotType = 'folder'
+ }
+
+ // If there are tags, copy them to libraryTags and set findable flags
+ if (Array.isArray(prop.tags) && prop.tags.length && collection === LibraryNodes) {
+ update = update || { $set: {} };
+ update.$set.libraryTags = prop.tags;
+ update.$set.fillSlots = true;
+ update.$set.searchable = true;
+ }
+
+ // Replace dollar sign with tilde in calculated fields
+ update = dollarSignToTilde(prop, update);
+
+ // Add the update to the bulk op
+ if (update) {
+ bulk.find({ _id: prop._id }).updateOne(update);
+ }
+}
+
+export function migratePropDown(bulk, prop) {
+ const update = {
+ $unset: {
+ slotFillImage: 1,
+ slotFillerCondition: 1,
+ libraryTags: 1,
+ fillSlots: 1,
+ searchable: 1,
+ }
+ };
+ if (prop.libraryTags?.length) {
+ update.$set = {
+ tags: union(prop.libraryTags, prop.tags)
+ }
+ }
+ bulk.find({ _id: prop._id }).updateOne(update);
+}
+
+function countSubscribers() {
+ const bulkLib = Libraries.rawCollection().initializeUnorderedBulkOp();
+ Libraries.find({}, {
+ fields: { _id: 1 }
+ }).forEach(lib => {
+ bulkLib.find({ _id: lib._id }).updateOne({
+ $set: {
+ subscriberCount: Meteor.users.find({ subscribedLibraries: lib._id }).count(),
+ }
+ });
+ });
+ bulkLib.execute();
+
+ const bulkLibCols = Libraries.rawCollection().initializeUnorderedBulkOp();
+ LibraryCollections.find({}, {
+ fields: { _id: 1 }
+ }).forEach(col => {
+ bulkLibCols.find({ _id: col._id }).updateOne({
+ $set: {
+ subscriberCount: Meteor.users.find({ subscribedLibraryCollections: col._id }).count(),
+ }
+ });
+ });
+ bulkLibCols.execute();
+}
+
+const dollarSignRegex = /(\W)\$(\w+)/gi;
+function dollarSignToTilde(prop, update) {
+ computedSchemas[prop.type]?.inlineCalculationFields()?.forEach(calcKey => {
+ applyFnToKey(prop, calcKey, (prop, key) => {
+ const inlineCalcObj = get(prop, key);
+ const string = inlineCalcObj?.text;
+ if (!string) return;
+ const newString = string.replace(dollarSignRegex, '$1~$2');
+ if (string !== newString) {
+ // If changed
+ update = update || { $set: {} };
+ if (!update.$unset) update.$unset = {};
+ update.$unset[key + '.hash'] = 1; // zero the hash so it re-parses the calculation
+ update.$set[key + '.text'] = newString
+ }
+ });
+ });
+ computedSchemas[prop.type]?.computedFields()?.forEach(calcKey => {
+ applyFnToKey(prop, calcKey, (prop, key) => {
+ const inlineCalcObj = get(prop, key);
+ const string = inlineCalcObj?.calculation;
+ if (!string) return;
+ const newString = string.replace(dollarSignRegex, '$1~$2');
+ if (string !== newString) {
+ // If changed
+ update = update || { $set: {} };
+ if (!update.$unset) update.$unset = {};
+ update.$unset[key + '.hash'] = 1; // remove the hash so it re-parses the calculation
+ update.$set[key + '.calculation'] = newString
+ }
+ });
+ });
+ return update;
+}
diff --git a/app/imports/migrations/server/dbv2/dbv2.test.js b/app/imports/migrations/server/dbv2/dbv2.test.js
new file mode 100644
index 00000000..642f4c2f
--- /dev/null
+++ b/app/imports/migrations/server/dbv2/dbv2.test.js
@@ -0,0 +1,179 @@
+import { migratePropUp, migratePropDown } from './dbv2.js';
+import { assert } from 'chai';
+
+const exampleAttack = {
+ '_id': 'vw23EnJwBRcXEJg7i',
+ 'actionType': 'attack',
+ 'target': 'singleTarget',
+ 'tags': ['attack', 'magical', 'very cool'],
+ 'resources': {
+ 'itemsConsumed': [],
+ 'attributesConsumed': []
+ },
+ 'attackRoll': {
+ calculation: 'dexterity.modifier + proficiency$Bonus + 2 - hp.total + hp.value + $dollarSign',
+ hash: 1234567,
+ },
+ 'summary': {
+ text: 'What if we {$had} two {$dollarSigns?} ',
+ hash: 123456,
+ },
+ 'type': 'action',
+ 'name': 'Claws',
+ 'parent': {
+ 'id': 'Jpx8q3WjM5SCoGBm8',
+ 'collection': 'creatureProperties'
+ },
+ 'ancestors': [{
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv'
+ }, {
+ 'id': '3WS2xsSPAqB4eF9YH',
+ 'collection': 'creatureProperties'
+ }, {
+ 'id': 'rhYLEycvtHjcioaQL',
+ 'collection': 'creatureProperties'
+ }, {
+ 'id': 'Jpx8q3WjM5SCoGBm8',
+ 'collection': 'creatureProperties'
+ }],
+ 'order': 56,
+ 'usesUsed': 2,
+};
+
+const expectedAttackUpdate = {
+ $set: {
+ 'attackRoll.calculation': 'dexterity.modifier + proficiency$Bonus + 2 - hp.total + hp.value + ~dollarSign',
+ 'libraryTags': ['attack', 'magical', 'very cool'],
+ 'fillSlots': true,
+ 'searchable': true,
+ 'summary.text': 'What if we {~had} two {~dollarSigns?} ',
+ },
+ $unset: {
+ 'attackRoll.hash': 1,
+ 'summary.hash': 1,
+ },
+};
+
+const emptyFolderExample = {
+ _id: 'DXPYsHKF6W8Hh3hZs',
+ type: 'folder',
+ name: 'Empty Folder',
+ 'parent': {
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv',
+ },
+ 'ancestors': [{
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv',
+ }],
+};
+
+const exampleSlotFiller = {
+ _id: 'DXPYsHKF6888h3hZs',
+ type: 'slotFiller',
+ name: 'Slot Filler Example',
+ 'picture': 'https://url.to.pic',
+ 'tags': ['slot', 'tags'],
+ 'parent': {
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv',
+ },
+ 'ancestors': [{
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv',
+ }],
+};
+const expectedSlotFillerUpdate = {
+ $set: {
+ 'libraryTags': ['slot', 'tags'],
+ 'fillSlots': true,
+ 'searchable': true,
+ 'slotFillImage': 'https://url.to.pic',
+ },
+ $unset: {
+ picture: 1,
+ },
+};
+
+const DownMergeExample = {
+ _id: 'DXPYsHKF6W8Hh3hZs',
+ type: 'feature',
+ name: 'Feature With Tags and library Tags',
+ 'parent': {
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv',
+ },
+ 'ancestors': [{
+ 'collection': 'creatures',
+ 'id': 'm9sdCvs6iDf7qRaGv',
+ }],
+ 'libraryTags': ['tags', 'from', 'library'],
+ 'tags': ['attack', 'magical', 'very cool'],
+};
+
+const expectedDownMergeUpdate = {
+ $unset: {
+ slotFillImage: 1,
+ slotFillerCondition: 1,
+ libraryTags: 1,
+ fillSlots: 1,
+ searchable: 1,
+ },
+ $set: {
+ tags: ['tags', 'from', 'library', 'attack', 'magical', 'very cool'],
+ }
+};
+
+describe('dbv2 Migrate library nodes', function () {
+ it('Migrates attacks up', function () {
+ const bulk = stubBulk();
+ migratePropUp(bulk, exampleAttack);
+ const { query, update } = bulk.result();
+ assert.deepEqual(query, { _id: 'vw23EnJwBRcXEJg7i' }, 'The query should match the id of the given prop');
+ assert.deepEqual(update, expectedAttackUpdate, 'The update should match the expected update');
+ });
+ it('Migrates props without tags up', function () {
+ const bulk = stubBulk();
+ migratePropUp(bulk, emptyFolderExample);
+ const { query, update, timesFind, timesUpdate } = bulk.result();
+ assert.isUndefined(query, 'There should be no query on a prop with no tags');
+ assert.equal(timesFind, 0, 'Find should be called zero times on a prop with no tags');
+ assert.isUndefined(update, 'There should be no update on a prop with no tags');
+ assert.equal(timesUpdate, 0, 'Update should be called zero times on a prop with no tags');
+ });
+ it('Migrates slot fillers up', function () {
+ const bulk = stubBulk();
+ migratePropUp(bulk, exampleSlotFiller);
+ const { query, update } = bulk.result();
+ assert.deepEqual(query, { _id: 'DXPYsHKF6888h3hZs' }, 'The query should match the id of the given prop');
+ assert.deepEqual(update, expectedSlotFillerUpdate, 'The update should match the expected update');
+ });
+ it('Merges tags when down migrating', function () {
+ const bulk = stubBulk();
+ migratePropDown(bulk, DownMergeExample);
+ const { query, update } = bulk.result();
+ assert.deepEqual(query, { _id: 'DXPYsHKF6W8Hh3hZs' }, 'The query should match the id of the given prop');
+ assert.deepEqual(update, expectedDownMergeUpdate, 'The update should match the expected update');
+ });
+});
+
+// Create a stub for bulk udateOne operations that accepts a single op
+function stubBulk() {
+ let query, update, timesFind = 0, timesUpdate = 0;
+ return {
+ find(inputQuery) {
+ query = inputQuery;
+ timesFind += 1;
+ return {
+ updateOne(inputUpdate) {
+ update = inputUpdate;
+ timesUpdate += 1;
+ }
+ }
+ },
+ result() {
+ return { query, update, timesFind, timesUpdate }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/imports/migrations/server/index.js b/app/imports/migrations/server/index.js
index 54ca97d0..07b974f8 100644
--- a/app/imports/migrations/server/index.js
+++ b/app/imports/migrations/server/index.js
@@ -1 +1,2 @@
import './dbv1/dbv1.js';
+import './dbv2/dbv2.js';
diff --git a/app/imports/migrations/server/migrateArchive.js b/app/imports/migrations/server/migrateArchive.js
deleted file mode 100644
index 0fb557ed..00000000
--- a/app/imports/migrations/server/migrateArchive.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import cleanAt1 from '/imports/migrations/server/dbv1/cleanAt1.js';
-
-/* eslint no-fallthrough: "off" -- Using switch fallthrough to run all
-migration steps after the current version of the file. */
-export default function migrateArchive(archive){
- switch (archive.meta.schemaVersion){
- // V1 of DiceCloud
- case 'version1':
- migrateLegacyArchive(archive);
- // V2 of DiceCloud, Schema version 1
- case 1:
- cleanAt1(archive);
- }
-}
-
-function migrateLegacyArchive(archive){
- // TODO:
- throw 'Not implemented';
-}
diff --git a/app/imports/parser/functions.js b/app/imports/parser/functions.js
index 90a7e8df..9482fd9b 100644
--- a/app/imports/parser/functions.js
+++ b/app/imports/parser/functions.js
@@ -1,11 +1,12 @@
import resolve from '/imports/parser/resolve.js'
+import rollDice from '/imports/parser/rollDice.js';
export default {
'abs': {
comment: 'Returns the absolute value of a number',
examples: [
- {input: 'abs(9)', result: '9'},
- {input: 'abs(-3)', result: '3'},
+ { input: 'abs(9)', result: '9' },
+ { input: 'abs(-3)', result: '3' },
],
arguments: ['number'],
resultType: 'number',
@@ -14,8 +15,8 @@ export default {
'sqrt': {
comment: 'Returns the square root of a number',
examples: [
- {input: 'sqrt(16)', result: '4'},
- {input: 'sqrt(10)', result: '3.1622776601683795'},
+ { input: 'sqrt(16)', result: '4' },
+ { input: 'sqrt(10)', result: '3.1622776601683795' },
],
arguments: ['number'],
resultType: 'number',
@@ -23,14 +24,14 @@ export default {
},
'max': {
comment: 'Returns the largest of the given numbers',
- examples: [{input: 'max(12, 6, 3, 168)', result: '168'}],
+ examples: [{ input: 'max(12, 6, 3, 168)', result: '168' }],
arguments: anyNumberOf('number'),
resultType: 'number',
fn: Math.max,
},
'min': {
comment: 'Returns the smallest of the given numbers',
- examples: [{input: 'min(12, 6, 3, 168)', result: '3'}],
+ examples: [{ input: 'min(12, 6, 3, 168)', result: '3' }],
arguments: anyNumberOf('number'),
resultType: 'number',
fn: Math.min,
@@ -38,9 +39,9 @@ export default {
'round': {
comment: 'Returns the value of a number rounded to the nearest integer',
examples: [
- {input: 'round(5.95)', result: '6'},
- {input: 'round(5.5)', result: '6'},
- {input: 'round(5.05)', result: '5'},
+ { input: 'round(5.95)', result: '6' },
+ { input: 'round(5.5)', result: '6' },
+ { input: 'round(5.05)', result: '5' },
],
arguments: ['number'],
resultType: 'number',
@@ -49,10 +50,10 @@ export default {
'floor': {
comment: 'Rounds a number down to the next smallest integer',
examples: [
- {input: 'floor(5.95)', result: '5'},
- {input: 'floor(5.05)', result: '5'},
- {input: 'floor(5)', result: '5'},
- {input: 'floor(-5.5)', result: '-6'},
+ { input: 'floor(5.95)', result: '5' },
+ { input: 'floor(5.05)', result: '5' },
+ { input: 'floor(5)', result: '5' },
+ { input: 'floor(-5.5)', result: '-6' },
],
arguments: ['number'],
resultType: 'number',
@@ -61,10 +62,10 @@ export default {
'ceil': {
comment: 'Rounds a number up to the next largest integer',
examples: [
- {input: 'ceil(5.95)', result: '6'},
- {input: 'ceil(5.05)', result: '6'},
- {input: 'ceil(5)', result: '5'},
- {input: 'ceil(-5.5)', result: '-5'},
+ { input: 'ceil(5.95)', result: '6' },
+ { input: 'ceil(5.05)', result: '6' },
+ { input: 'ceil(5)', result: '5' },
+ { input: 'ceil(-5.5)', result: '-5' },
],
arguments: ['number'],
resultType: 'number',
@@ -73,21 +74,21 @@ export default {
'trunc': {
comment: 'Returns the integer part of a number by removing any fractional digits',
examples: [
- {input: 'trunc(5.95)', result: '5'},
- {input: 'trunc(5.05)', result: '5'},
- {input: 'trunc(5)', result: '5'},
- {input: 'trunc(-5.5)', result: '-5'},
+ { input: 'trunc(5.95)', result: '5' },
+ { input: 'trunc(5.05)', result: '5' },
+ { input: 'trunc(5)', result: '5' },
+ { input: 'trunc(-5.5)', result: '-5' },
],
- arguments:[ 'number'],
+ arguments: ['number'],
resultType: 'number',
fn: Math.trunc,
},
'sign': {
comment: 'Returns either a positive or negative 1, indicating the sign of a number, or zero',
examples: [
- {input: 'sign(-3)', result: '-1'},
- {input: 'sign(3)', result: '1'},
- {input: 'sign(0)', result: '0'},
+ { input: 'sign(-3)', result: '-1' },
+ { input: 'sign(3)', result: '1' },
+ { input: 'sign(0)', result: '0' },
],
arguments: ['number'],
resultType: 'number',
@@ -96,15 +97,15 @@ export default {
'tableLookup': {
comment: 'Returns the index of the last value in the array that is less than the specified amount',
examples: [
- {input: 'tableLookup([100, 300, 900], 457)', result: '2'},
- {input: 'tableLookup([100, 300, 900], 23)', result: '0'},
- {input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3'},
- {input: 'tableLookup([100, 300], 594)', result: '2'},
+ { input: 'tableLookup([100, 300, 900], 457)', result: '2' },
+ { input: 'tableLookup([100, 300, 900], 23)', result: '0' },
+ { input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3' },
+ { input: 'tableLookup([100, 300], 594)', result: '2' },
],
arguments: ['array', 'number'],
resultType: 'number',
- fn: function tableLookup(arrayNode, number){
- for(let i in arrayNode.values){
+ fn: function tableLookup(arrayNode, number) {
+ for (let i in arrayNode.values) {
let node = arrayNode.values[i];
if (node.value > number) return +i;
}
@@ -114,18 +115,146 @@ export default {
'resolve': {
comment: 'Forces the given calcultion to resolve into a number, even in calculations where it would usually keep the unknown values as is',
examples: [
- {input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'},
- {input: 'resolve(1d6)', result: '4'},
+ { input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7' },
+ { input: 'resolve(1d6)', result: '4' },
],
arguments: ['parseNode'],
- fn: function resolveFn(node){
- let {result} = resolve('reduce', node, this.scope, this.context);
+ fn: function resolveFn(node) {
+ let { result } = resolve('reduce', node, this.scope, this.context);
return result;
}
- }
+ },
+ 'dropLowest': {
+ comment: 'Removes one or more of the lowest values in a roll',
+ examples: [
+ ],
+ arguments: ['rollArray', 'number'],
+ maxResolveLevels: ['roll', 'reduce'],
+ minArguments: 1,
+ maxArguments: 2,
+ resultType: 'rollArray',
+ fn: function dropLowestFn(rollArray, numberToDrop = 1) {
+ // Create a new array where the values are sorted in ascending order
+ const sortedArray = [...rollArray.values].sort(function (a, b) {
+ return a.value - b.value;
+ });
+
+ // mark the N smallest elements as dropped
+ for (let i = 0; i < numberToDrop; i += 1) {
+ console.log('dropped ' + sortedArray[i].value);
+ sortedArray[i].disabled = true;
+ sortedArray[i].disabledBy = 'dropLowest';
+ }
+ return rollArray;
+ },
+ },
+ 'dropHighest': {
+ comment: 'Removes one or more of the highest values in a roll',
+ examples: [
+ ],
+ arguments: ['rollArray', 'number'],
+ maxResolveLevels: ['roll', 'reduce'],
+ minArguments: 1,
+ maxArguments: 2,
+ resultType: 'rollArray',
+ fn: function dropHighestFn(rollArray, numberToDrop = 1) {
+ // Create a new array where the values are sorted in ascending order
+ const sortedArray = [...rollArray.values].sort(function (a, b) {
+ return b.value - a.value;
+ });
+
+ // mark the N smallest elements as dropped
+ for (let i = 0; i < numberToDrop; i += 1) {
+ sortedArray[i].disabled = true;
+ sortedArray[i].disabledBy = 'dropHighest';
+ }
+ return rollArray;
+ },
+ },
+ 'reroll': {
+ comment: 'Rerolls if a number is less than or equal to the given value',
+ examples: [
+ ],
+ arguments: ['rollArray', 'number', 'boolean'],
+ maxResolveLevels: ['roll', 'reduce'],
+ minArguments: 1,
+ maxArguments: 3,
+ resultType: 'rollArray',
+ fn: function rerollFn(rollArray, numberToReroll = 1, keepNewRoll = false) {
+ let rollValues = rollArray.values
+ // Iterate through the roll values
+ for (let i = 0; i < rollValues.length; i += 1) {
+ // If the number is less than the reroll limit
+ if (rollValues[i].value <= numberToReroll) {
+ // Disable it
+ rollValues[i].disabled = true;
+ rollValues[i].disabledBy = 'reroll';
+ // Roll it again, insert the new roll into the list at the next index
+ rollValues.splice(i + 1, 0, {
+ value: rollDice(1, rollArray.diceSize)[0],
+ });
+ // Skip iterating the inserted roll if we are forced to keep it
+ if (keepNewRoll) {
+ i += 1;
+ }
+ }
+ if (i >= 100) {
+ this.context.error('Can\'t roll more than 100 dice at once');
+ return rollArray;
+ }
+ }
+ return rollArray;
+ },
+ },
+ 'explode': {
+ comment: 'Rerolls if a number is greater than or equal to the given value',
+ examples: [
+ ],
+ arguments: ['rollArray', 'number', 'number'],
+ maxResolveLevels: ['roll', 'reduce', 'reduce'],
+ minArguments: 1,
+ maxArguments: 3,
+ resultType: 'rollArray',
+ fn: function explodeFn(rollArray, depth = 1, numberToReroll = rollArray.diceSize) {
+ let overflowErrored = false;
+ if (depth > 99) depth = 99;
+ let rollValues = rollArray.values
+ // Iterate through the roll values
+ for (let i = 0; i < rollValues.length; i += 1) {
+ // If the number is greater than or equal to the reroll limit
+ // And there is space to reroll it
+ if (rollValues[i].value >= numberToReroll) {
+ rollValues[i].bold = true;
+ let explodeDepth = 1;
+ let explodeRoll;
+ do {
+ // Before inserting this roll, make sure the total dice in the roll
+ // Doesn't exceed 100
+ if (rollValues.length >= 100) {
+ if (!overflowErrored) {
+ this.context.error('Can\'t roll more than 100 dice at once');
+ overflowErrored = true;
+ }
+ break;
+ }
+ explodeDepth += 1;
+ explodeRoll = rollDice(1, rollArray.diceSize)[0];
+ const rollObj = {
+ value: explodeRoll,
+ italics: true,
+ };
+ // Insert the roll
+ rollValues.splice(i + 1, 0, rollObj);
+ i += 1;
+ } while (explodeDepth <= depth && explodeRoll >= numberToReroll)
+ }
+ }
+ return rollArray;
+ },
+ },
}
-function anyNumberOf(type){
+function anyNumberOf(type) {
let argumentArray = [type];
argumentArray.anyLength = true;
return argumentArray;
diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js
index 43f5b70f..8f513674 100644
--- a/app/imports/parser/grammar.js
+++ b/app/imports/parser/grammar.js
@@ -13,7 +13,7 @@ function id(x) { return x[0]; }
value: s => s.slice(1, -1).replace('\\n', '\n'),
},
name: {
- match: /[a-zA-Z_#$]*[a-ce-zA-Z_#$][a-zA-Z0-9_#$]*/,
+ match: /[~#]?[a-zA-Z]*[a-ce-zA-Z][a-zA-Z0-9_]*/,
type: moo.keywords({
'keywords': ['true', 'false'],
}),
diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne
index 3ebdba75..e008ac54 100644
--- a/app/imports/parser/grammar.ne
+++ b/app/imports/parser/grammar.ne
@@ -11,7 +11,7 @@
value: s => s.slice(1, -1).replace('\\n', '\n'),
},
name: {
- match: /[a-zA-Z_#$]*[a-ce-zA-Z_#$][a-zA-Z0-9_#$]*/,
+ match: /[~#]?[a-zA-Z]*[a-ce-zA-Z][a-zA-Z0-9_]*/,
type: moo.keywords({
'keywords': ['true', 'false'],
}),
diff --git a/app/imports/parser/parseTree/call.js b/app/imports/parser/parseTree/call.js
index 658c5f49..24b7d3c9 100644
--- a/app/imports/parser/parseTree/call.js
+++ b/app/imports/parser/parseTree/call.js
@@ -4,14 +4,14 @@ import functions from '/imports/parser/functions.js';
import resolve, { toString, traverse, map } from '../resolve.js';
const call = {
- create({functionName, args}) {
+ create({ functionName, args }) {
return {
parseType: 'call',
functionName,
args,
}
},
- resolve(fn, node, scope, context){
+ resolve(fn, node, scope, context) {
let func = functions[node.functionName];
// Check that the function exists
if (!func) {
@@ -25,9 +25,22 @@ const call = {
};
}
+ // Resolve a given node to a maximum depth of resolution
+ const resolveToLevel = (node, maxResolveFn = 'reduce') => {
+ // Determine the actual depth to resolve to
+ let resolveFn = 'reduce';
+ if (fn === 'compile' || maxResolveFn === 'compile') {
+ resolveFn = 'compile';
+ } else if (fn === 'roll' || maxResolveFn === 'roll') {
+ resolveFn = 'roll';
+ }
+ // Resolve
+ return resolve(resolveFn, node, scope, context);
+ }
+
// Resolve the arguments
- let resolvedArgs = node.args.map(arg => {
- let { result } = resolve(fn, arg, scope, context);
+ let resolvedArgs = node.args.map((arg, i) => {
+ let { result } = resolveToLevel(arg, func.maxResolveLevels?.[i]);
return result;
});
@@ -36,12 +49,12 @@ const call = {
node,
fn,
resolvedArgs,
- argumentsExpected: func.arguments,
+ func,
context,
});
- if (checkFailed){
- if (fn === 'reduce'){
+ if (checkFailed) {
+ if (fn === 'reduce') {
context.error(`Invalid arguments to ${node.functionName} function`);
return {
result: error.create({
@@ -66,7 +79,7 @@ const call = {
if (
arg.parseType === 'constant' &&
func.arguments[index] !== 'parseNode'
- ){
+ ) {
return arg.value;
} else {
return arg;
@@ -75,20 +88,21 @@ const call = {
try {
// Run the function
- let value = func.fn.apply({scope, context}, mappedArgs);
+ let value = func.fn.apply({
+ scope,
+ context,
+ }, mappedArgs);
let valueType = typeof value;
- if (valueType === 'number' || valueType === 'string' || valueType === 'boolean'){
+ if (valueType === 'number' || valueType === 'string' || valueType === 'boolean') {
// Convert constant results into constant nodes
return {
- result: constant.create({ value, valueType }),
+ result: constant.create({ value }),
context,
};
} else {
- return {
- result: value,
- context,
- };
+ // Resolve the return value
+ return resolve(fn, value, scope, context);
}
} catch (error) {
context.error(error.message || error);
@@ -101,26 +115,28 @@ const call = {
}
}
},
- toString(node){
+ toString(node) {
return `${node.functionName}(${node.args.map(arg => toString(arg)).join(', ')})`;
},
- traverse(node, fn){
+ traverse(node, fn) {
fn(node);
node.args.forEach(arg => traverse(arg, fn));
},
- map(node, fn){
+ map(node, fn) {
const resultingNode = fn(node);
- if (resultingNode === node){
+ if (resultingNode === node) {
node.args = node.args.map(arg => map(arg, fn));
}
return resultingNode;
},
- checkArugments({node, fn, argumentsExpected, resolvedArgs, context}){
+ checkArugments({ node, fn, func, resolvedArgs, context }) {
+ const argumentsExpected = func.arguments;
// Check that the number of arguments matches the number expected
if (
!argumentsExpected.anyLength &&
- argumentsExpected.length !== resolvedArgs.length
- ){
+ resolvedArgs.length > (func.maxArguments ?? argumentsExpected.length) ||
+ resolvedArgs.length < (func.minArguments ?? argumentsExpected.length)
+ ) {
context.error('Incorrect number of arguments ' +
`to ${node.functionName} function, ` +
`expected ${argumentsExpected.length} got ${resolvedArgs.length}`);
@@ -131,14 +147,14 @@ const call = {
// Check that each argument is of the correct type
resolvedArgs.forEach((node, index) => {
let type;
- if (argumentsExpected.anyLength){
+ if (argumentsExpected.anyLength) {
type = argumentsExpected[0];
} else {
type = argumentsExpected[index];
}
if (type === 'parseNode') return;
if (node.parseType !== type && node.valueType !== type) failed = true;
- if (failed && fn === 'reduce'){
+ if (failed && fn === 'reduce') {
let typeName = typeof type === 'string' ? type : type.constructor.name;
let nodeName = node.parseType;
context.error(`Incorrect arguments to ${node.functionName} function` +
diff --git a/app/imports/parser/parseTree/rollArray.js b/app/imports/parser/parseTree/rollArray.js
index 969b948d..30f37240 100644
--- a/app/imports/parser/parseTree/rollArray.js
+++ b/app/imports/parser/parseTree/rollArray.js
@@ -4,7 +4,7 @@ const rollArray = {
create({ values, diceSize, diceNum }) {
return {
parseType: 'rollArray',
- values,
+ values: values.map(v => ({ value: v })),
diceSize,
diceNum,
};
@@ -16,10 +16,13 @@ const rollArray = {
};
},
toString(node) {
- return `${node.diceNum || ''}d${node.diceSize} [ ${node.values.join(', ')} ]`;
+ return `${node.diceNum || ''}d${node.diceSize} [${valuesToString(node.values)}]`;
},
reduce(node, scope, context) {
- const total = node.values.reduce((a, b) => a + b, 0);
+ const total = node.values.reduce((a, b) => {
+ if (b.disabled) return a;
+ return a + b.value;
+ }, 0);
return {
result: constant.create({
value: total,
@@ -29,4 +32,15 @@ const rollArray = {
},
}
+function valuesToString(values) {
+ return values.map(v => {
+ let text = `${v.value}`;
+ if (v.disabled) text = `~~${text}~~`;
+ if (v.italics) text = `*${text}*`;
+ if (v.bold) text = `**${text}**`;
+ if (v.underline) text = `__${text}__`;
+ return text;
+ }).join(', ');
+}
+
export default rollArray;
diff --git a/app/imports/server/config/redisCaching.js b/app/imports/server/config/redisCaching.js
new file mode 100644
index 00000000..7ed21c26
--- /dev/null
+++ b/app/imports/server/config/redisCaching.js
@@ -0,0 +1,3 @@
+import LibraryNodes from '/imports/api/library/LibraryNodes.js';
+
+LibraryNodes.startCaching?.();
diff --git a/app/imports/server/discord/sendWebhook.js b/app/imports/server/discord/sendWebhook.js
index 044de8c3..a8a473df 100644
--- a/app/imports/server/discord/sendWebhook.js
+++ b/app/imports/server/discord/sendWebhook.js
@@ -1,5 +1,5 @@
import Discord from 'discord.js'
-export default function sendWebhook({webhookURL, data = {}}){
+export default function sendWebhook({ webhookURL, data = {} }) {
//webhookURL = https://discordapp.com/api/webhooks/
/
let urlArray = webhookURL.split('/');
let token = urlArray.pop();
@@ -9,11 +9,16 @@ export default function sendWebhook({webhookURL, data = {}}){
data.disableMentions = 'all';
const hook = new Discord.WebhookClient(id, token);
- // Send a message using the webhook
- hook.send(data);
+ try {
+ // Send a message using the webhook
+ hook.send(data);
+ } catch (e) {
+ // Swallow the error, we don't really care
+ console.error(e);
+ }
}
-export function sendWebhookAsCreature({creature, data = {}}){
+export function sendWebhookAsCreature({ creature, data = {} }) {
if (!creature || !creature.settings || !creature.settings.discordWebhook) return;
data.username = creature.name;
data.avatarURL = creature.avatarPicture;
diff --git a/app/imports/server/publications/library.js b/app/imports/server/publications/library.js
index 68c378b5..5499f246 100644
--- a/app/imports/server/publications/library.js
+++ b/app/imports/server/publications/library.js
@@ -14,15 +14,19 @@ const LIBRARY_NODE_TREE_FIELDS = {
order: 1,
parent: 1,
ancestors: 1,
- tags: 1,
- slotFillerCondition: 1,
removed: 1,
removedAt: 1,
// Actions
actionType: 1,
// SlotFillers
+ libraryTags: 1,
slotQuantityFilled: 1,
slotFillerType: 1,
+ slotFillerConditionNote: 1,
+ slotFillerCondition: 1,
+ fillSlots: 1,
+ searchable: 1,
+ slotFillImage: 1,
// Effect
operation: 1,
targetTags: 1,
@@ -78,7 +82,14 @@ Meteor.publish('libraryCollection', function (libraryCollectionId) {
}, {
sort: { name: 1 }
});
- return [libraryCollectionCursor, libraryCursor];
+ return [
+ libraryCollectionCursor,
+ libraryCursor,
+ Meteor.users.find(
+ libraryCollection.owner,
+ { fields: { username: 1 } }
+ ),
+ ];
});
});
})
@@ -137,6 +148,32 @@ Meteor.publish('libraries', function () {
});
});
+Meteor.publish('browseLibraries', function () {
+ if (!this.userId) return [];
+ return [
+ Libraries.find({
+ showInMarket: true,
+ public: true,
+ }, {
+ sort: {
+ subscriberCount: 1,
+ name: 1,
+ },
+ limit: 500,
+ }),
+ LibraryCollections.find({
+ showInMarket: true,
+ public: true,
+ }, {
+ sort: {
+ subscriberCount: 1,
+ name: 1
+ },
+ limit: 500,
+ }),
+ ];
+});
+
Meteor.publish('library', function (libraryId) {
if (!libraryId) return [];
libraryIdSchema.validate({ libraryId });
@@ -147,9 +184,15 @@ Meteor.publish('library', function (libraryId) {
catch (e) {
return this.error(e);
}
- return Libraries.find({
- _id: libraryId,
- });
+ return [
+ Libraries.find({
+ _id: libraryId,
+ }),
+ Meteor.users.find(
+ library.owner,
+ { fields: { username: 1 } }
+ ),
+ ];
});
});
diff --git a/app/imports/server/publications/searchLibraryNodes.js b/app/imports/server/publications/searchLibraryNodes.js
index cedbc7a8..062cfbbf 100644
--- a/app/imports/server/publications/searchLibraryNodes.js
+++ b/app/imports/server/publications/searchLibraryNodes.js
@@ -4,28 +4,31 @@ import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import getCreatureLibraryIds from '/imports/api/library/getCreatureLibraryIds.js';
import getUserLibraryIds from '/imports/api/library/getUserLibraryIds.js';
import { assertViewPermission } from '/imports/api/sharing/sharingPermissions.js';
+import escapeRegex from '/imports/api/utility/escapeRegex.js';
-Meteor.publish('selectedLibraryNodes', function(selectedNodeIds){
+Meteor.publish('selectedLibraryNodes', function (selectedNodeIds) {
check(selectedNodeIds, Array);
// Limit to 20 selected nodes
- if (selectedNodeIds.length > 20){
+ if (selectedNodeIds.length > 20) {
selectedNodeIds = selectedNodeIds.slice(0, 20);
}
let libraryViewPermissions = {};
// Check view permissions of all libraries
- for (let id of selectedNodeIds){
+ for (let id of selectedNodeIds) {
let node = LibraryNodes.findOne(id);
if (!node) continue;
let libraryId = node.ancestors[0].id;
- if (libraryViewPermissions[id]){
+ if (libraryViewPermissions[id]) {
continue;
} else {
- let library = Libraries.findOne(libraryId, {fields: {
- owner: 1,
- readers: 1,
- writers: 1,
- public: 1,
- }});
+ let library = Libraries.findOne(libraryId, {
+ fields: {
+ owner: 1,
+ readers: 1,
+ writers: 1,
+ public: 1,
+ }
+ });
assertViewPermission(library, this.userId);
libraryViewPermissions[id] = true;
}
@@ -33,15 +36,15 @@ Meteor.publish('selectedLibraryNodes', function(selectedNodeIds){
// Return all nodes and their children
return [LibraryNodes.find({
$or: [
- {_id: {$in: selectedNodeIds}},
- {'ancestors.id': {$in: selectedNodeIds}},
+ { _id: { $in: selectedNodeIds } },
+ { 'ancestors.id': { $in: selectedNodeIds } },
],
})];
});
-Meteor.publish('searchLibraryNodes', function(creatureId){
+Meteor.publish('searchLibraryNodes', function (creatureId) {
let self = this;
- this.autorun(function (){
+ this.autorun(function () {
let type = self.data('type');
if (!type) return [];
@@ -60,20 +63,19 @@ Meteor.publish('searchLibraryNodes', function(creatureId){
// Build a filter for nodes in those libraries that match the type
let filter = {
- 'ancestors.id': {$in: libraryIds},
- removed: {$ne: true},
- tags: {$ne: []}, // Only tagged library nodes are considered
+ 'ancestors.id': { $in: libraryIds },
+ removed: { $ne: true },
+ searchable: true //library nodes must opt-in
};
- if (type){
+ if (type) {
filter.$or = [{
- type,
- },{
- type: 'slotFiller',
- slotFillerType: type,
+ type,
+ }, {
+ slotFillerType: type,
}];
}
- this.autorun(function(){
+ this.autorun(function () {
// Get the limit of the documents the user can fetch
var limit = self.data('limit') || 32;
check(limit, Number);
@@ -83,28 +85,34 @@ Meteor.publish('searchLibraryNodes', function(creatureId){
check(searchTerm, String);
let options = undefined;
- if (searchTerm){
- filter.$text = {$search: searchTerm};
+ if (searchTerm) {
+ filter.name = { $regex: escapeRegex(searchTerm), '$options': 'i' };
+ // filter.$text = {$search: searchTerm};
options = {
+ /*
// relevant documents have a higher score.
fields: {
score: { $meta: 'textScore' }
},
+ */
sort: {
// `score` property specified in the projection fields above.
- score: { $meta: 'textScore' },
+ // score: { $meta: 'textScore' },
'ancestors.0.id': 1,
name: 1,
order: 1,
}
}
} else {
- delete filter.$text
- options = {sort: {
- 'ancestors.0.id': 1,
- name: 1,
- order: 1,
- }};
+ //delete filter.$text
+ delete filter.name;
+ options = {
+ sort: {
+ 'ancestors.0.id': 1,
+ name: 1,
+ order: 1,
+ }
+ };
}
options.limit = limit;
@@ -118,17 +126,17 @@ Meteor.publish('searchLibraryNodes', function(creatureId){
Mongo.Collection._publishCursor(libraries, self, 'libraries');
let observeHandle = cursor.observeChanges({
- added: function (id, fields) {
- fields._searchResult = true;
- self.added('libraryNodes', id, fields);
- },
- changed: function (id, fields) {
- self.changed('libraryNodes', id, fields);
- },
- removed: function (id) {
- self.removed('libraryNodes', id);
- }
+ added: function (id, fields) {
+ fields._searchResult = true;
+ self.added('libraryNodes', id, fields);
},
+ changed: function (id, fields) {
+ self.changed('libraryNodes', id, fields);
+ },
+ removed: function (id) {
+ self.removed('libraryNodes', id);
+ }
+ },
// Publications don't mutate the documents
{ nonMutatingCallbacks: true }
);
diff --git a/app/imports/server/publications/singleCharacter.js b/app/imports/server/publications/singleCharacter.js
index 28d2da6f..b41c9dcd 100644
--- a/app/imports/server/publications/singleCharacter.js
+++ b/app/imports/server/publications/singleCharacter.js
@@ -19,10 +19,10 @@ Meteor.publish('singleCharacter', function (creatureId) {
const self = this;
try {
schema.validate({ creatureId });
- } catch (e){
+ } catch (e) {
this.error(e);
}
- this.autorun(function (computation){
+ this.autorun(function (computation) {
let userId = this.userId;
let permissionCreature = Creatures.findOne({
_id: creatureId,
@@ -32,11 +32,11 @@ Meteor.publish('singleCharacter', function (creatureId) {
try { assertViewPermission(permissionCreature, userId) }
catch (e) { return [] }
loadCreature(creatureId, self);
- if (permissionCreature.computeVersion !== VERSION && computation.firstRun){
+ if (permissionCreature.computeVersion !== VERSION && computation.firstRun) {
try {
computeCreature(creatureId)
}
- catch(e){ console.error(e) }
+ catch (e) { console.error(e) }
}
return [
Creatures.find({
@@ -52,7 +52,13 @@ Meteor.publish('singleCharacter', function (creatureId) {
creatureId,
}, {
limit: 20,
- sort: {date: -1},
+ sort: { date: -1 },
+ }),
+ // Also publish the owner's username
+ Meteor.users.find(permissionCreature.owner, {
+ fields: {
+ username: 1,
+ },
}),
];
});
diff --git a/app/imports/server/publications/slotFillers.js b/app/imports/server/publications/slotFillers.js
index 6427af79..fba54ca6 100644
--- a/app/imports/server/publications/slotFillers.js
+++ b/app/imports/server/publications/slotFillers.js
@@ -5,22 +5,28 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js'
import getCreatureLibraryIds from '/imports/api/library/getCreatureLibraryIds.js';
import { LIBRARY_NODE_TREE_FIELDS } from '/imports/server/publications/library.js';
+import escapeRegex from '/imports/api/utility/escapeRegex.js';
-Meteor.publish('slotFillers', function(slotId, searchTerm){
+Meteor.publish('slotFillers', function (slotId, searchTerm, isDummySlot) {
if (searchTerm) check(searchTerm, String);
let self = this;
- this.autorun(function (){
+ this.autorun(function () {
let userId = this.userId;
if (!userId) {
return [];
}
- // Get the slot
- let slot = CreatureProperties.findOne(slotId);
- if (!slot){
- return [];
+
+ // Get the slot from the right collection
+ let slot;
+ if (isDummySlot) {
+ slot = LibraryNodes.findOne(slotId);
+ } else {
+ slot = CreatureProperties.findOne(slotId);
}
+ if (!slot) return [];
+
// Get all the ids of libraries the user can access
const creatureId = slot.ancestors[0].id;
const libraryIds = getCreatureLibraryIds(creatureId, userId);
@@ -36,31 +42,32 @@ Meteor.publish('slotFillers', function(slotId, searchTerm){
});
// Build a filter for nodes in those libraries that match the slot
- let filter = getSlotFillFilter({slot, libraryIds});
-
- this.autorun(function(){
+ let filter = getSlotFillFilter({ slot, libraryIds });
+ this.autorun(function () {
// Get the limit of the documents the user can fetch
var limit = self.data('limit') || 50;
check(limit, Number);
let options = undefined;
- if (searchTerm){
- filter.$text = {$search: searchTerm};
+ if (searchTerm) {
+ filter.name = { $regex: escapeRegex(searchTerm), '$options': 'i' };
+ //filter.$text = { $search: searchTerm };
options = {
// relevant documents have a higher score.
fields: {
- _score: { $meta: 'textScore' },
+ //_score: { $meta: 'textScore' },
...LIBRARY_NODE_TREE_FIELDS,
},
sort: {
// `score` property specified in the projection fields above.
- _score: { $meta: 'textScore' },
+ //_score: { $meta: 'textScore' },
name: 1,
order: 1,
}
}
} else {
- delete filter.$text
+ //delete filter.$text
+ delete filter.name
options = {
sort: {
name: 1,
@@ -73,6 +80,7 @@ Meteor.publish('slotFillers', function(slotId, searchTerm){
self.autorun(function () {
self.setData('countAll', LibraryNodes.find(filter).count());
+ self.setData('libraryNodeFilter', EJSON.stringify(filter));
});
self.autorun(function () {
return [
@@ -84,18 +92,18 @@ Meteor.publish('slotFillers', function(slotId, searchTerm){
});
});
-Meteor.publish('classFillers', function(classId){
+Meteor.publish('classFillers', function (classId) {
let self = this;
if (!classId) return [];
- this.autorun(function (){
+ this.autorun(function () {
let userId = this.userId;
if (!userId) {
return [];
}
// Get the class
let classProp = CreatureProperties.findOne(classId);
- if (!classProp){
+ if (!classProp) {
return [];
}
@@ -114,15 +122,16 @@ Meteor.publish('classFillers', function(classId){
});
// Build a filter for nodes in those libraries that match the slot
- let filter = getSlotFillFilter({slot: classProp, libraryIds});
+ let filter = getSlotFillFilter({ slot: classProp, libraryIds });
- this.autorun(function(){
+ this.autorun(function () {
// Get the limit of the documents the user can fetch
var limit = self.data('limit') || 50;
check(limit, Number);
let options = {
sort: {
+ level: 1,
name: 1,
order: 1,
},
@@ -132,6 +141,7 @@ Meteor.publish('classFillers', function(classId){
self.autorun(function () {
self.setData('countAll', LibraryNodes.find(filter).count());
+ self.setData('libraryNodeFilter', EJSON.stringify(filter));
});
self.autorun(function () {
return [LibraryNodes.find(filter, options), libraries];
diff --git a/app/imports/server/rest/apiPublications/healthCheck.js b/app/imports/server/rest/apiPublications/healthCheck.js
new file mode 100644
index 00000000..c6aa00a8
--- /dev/null
+++ b/app/imports/server/rest/apiPublications/healthCheck.js
@@ -0,0 +1,37 @@
+// A simple endpoint that does a single round trip to the database to check everything is working
+
+const HealthCheckCollection = new Mongo.Collection('healthCheck');
+
+// Don't use redis oplog optimization on this collection, we want to hit the database every time
+HealthCheckCollection.disableRedis?.();
+
+const healthCheckDoc = {
+ status: 'ok',
+};
+
+// Add the health check doc on startup if it's missing
+// There should only be this single doc in the collection
+// A capped collection would be marginally faster, but it's a pain to make one in Meteor
+Meteor.startup(function () {
+ if (!HealthCheckCollection.findOne()) {
+ HealthCheckCollection.insert(healthCheckDoc);
+ }
+});
+
+Meteor.method('api-status', function () {
+ let dbHealthDoc;
+ try {
+ dbHealthDoc = HealthCheckCollection.findOne();
+ } catch (e) {
+ this.setHttpStatusCode(503);
+ }
+ if (dbHealthDoc?.status === 'ok') {
+ this.setHttpStatusCode(200);
+ } else {
+ this.setHttpStatusCode(500);
+ }
+ return dbHealthDoc || {};
+}, {
+ httpMethod: 'GET',
+ url: 'api/status'
+});
diff --git a/app/imports/server/rest/apiPublications/index.js b/app/imports/server/rest/apiPublications/index.js
index 765d7988..e95d23db 100644
--- a/app/imports/server/rest/apiPublications/index.js
+++ b/app/imports/server/rest/apiPublications/index.js
@@ -1 +1,2 @@
import './creature.js';
+import './healthCheck.js';
diff --git a/app/jsconfig.json b/app/jsconfig.json
index b97e629d..82b81ff2 100644
--- a/app/jsconfig.json
+++ b/app/jsconfig.json
@@ -10,14 +10,25 @@
"paths": {
"/*": [
"./*"
+ ],
+ "meteor/aldeed:collection2": [
+ "packages\\collection2\\collection2.js"
]
- }
+ },
+ "checkJs": false,
+ "allowJs": true
},
"vueCompilerOptions": {
- "target": 2,
+ "target": 2 // For Vue version <= 2.6.14
},
"exclude": [
"node_modules",
- "**/node_modules/*"
- ]
+ "**/node_modules/*",
+ ".meteor"
+ ],
+ "typeAcquisition": {
+ "include": [
+ "meteor"
+ ]
+ }
}
\ No newline at end of file
diff --git a/app/package-lock.json b/app/package-lock.json
index cae51f46..9fe1b74e 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -50,15 +50,15 @@
}
},
"@babel/parser": {
- "version": "7.20.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
- "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
+ "version": "7.22.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.3.tgz",
+ "integrity": "sha512-vrukxyW/ep8UD1UDzOYpTKQ6abgjFoeG6L+4ar9+c5TN9QnlqiOi6QK7LSR5ewm/ERyGkT/Ai6VboNrxhbr9Uw==",
"dev": true
},
"@babel/runtime": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
- "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
+ "version": "7.21.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
+ "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
@@ -83,6 +83,29 @@
"mime-types": "^2.1.12"
}
},
+ "@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
+ "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
+ "dev": true
+ }
+ }
+ },
+ "@eslint-community/regexpp": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz",
+ "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==",
+ "dev": true
+ },
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@@ -178,6 +201,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "@types/mocha": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz",
+ "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==",
+ "dev": true
+ },
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -185,18 +214,19 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz",
- "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz",
+ "integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "5.44.0",
- "@typescript-eslint/type-utils": "5.44.0",
- "@typescript-eslint/utils": "5.44.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.59.2",
+ "@typescript-eslint/type-utils": "5.59.2",
+ "@typescript-eslint/utils": "5.59.2",
"debug": "^4.3.4",
+ "grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
- "regexpp": "^3.2.0",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
},
@@ -211,9 +241,9 @@
}
},
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -222,14 +252,14 @@
}
},
"@typescript-eslint/parser": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz",
- "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz",
+ "integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "5.44.0",
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/typescript-estree": "5.44.0",
+ "@typescript-eslint/scope-manager": "5.59.2",
+ "@typescript-eslint/types": "5.59.2",
+ "@typescript-eslint/typescript-estree": "5.59.2",
"debug": "^4.3.4"
},
"dependencies": {
@@ -245,23 +275,23 @@
}
},
"@typescript-eslint/scope-manager": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz",
- "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz",
+ "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/visitor-keys": "5.44.0"
+ "@typescript-eslint/types": "5.59.2",
+ "@typescript-eslint/visitor-keys": "5.59.2"
}
},
"@typescript-eslint/type-utils": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz",
- "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz",
+ "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==",
"dev": true,
"requires": {
- "@typescript-eslint/typescript-estree": "5.44.0",
- "@typescript-eslint/utils": "5.44.0",
+ "@typescript-eslint/typescript-estree": "5.59.2",
+ "@typescript-eslint/utils": "5.59.2",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@@ -278,19 +308,19 @@
}
},
"@typescript-eslint/types": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz",
- "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz",
+ "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz",
- "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz",
+ "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/visitor-keys": "5.44.0",
+ "@typescript-eslint/types": "5.59.2",
+ "@typescript-eslint/visitor-keys": "5.59.2",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -308,9 +338,9 @@
}
},
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -319,34 +349,25 @@
}
},
"@typescript-eslint/utils": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz",
- "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz",
+ "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==",
"dev": true,
"requires": {
+ "@eslint-community/eslint-utils": "^4.2.0",
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
- "@typescript-eslint/scope-manager": "5.44.0",
- "@typescript-eslint/types": "5.44.0",
- "@typescript-eslint/typescript-estree": "5.44.0",
+ "@typescript-eslint/scope-manager": "5.59.2",
+ "@typescript-eslint/types": "5.59.2",
+ "@typescript-eslint/typescript-estree": "5.59.2",
"eslint-scope": "^5.1.1",
- "eslint-utils": "^3.0.0",
"semver": "^7.3.7"
},
"dependencies": {
- "eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
- "dev": true,
- "requires": {
- "eslint-visitor-keys": "^2.0.0"
- }
- },
"semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
+ "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -355,49 +376,79 @@
}
},
"@typescript-eslint/visitor-keys": {
- "version": "5.44.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz",
- "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==",
+ "version": "5.59.2",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz",
+ "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "5.44.0",
+ "@typescript-eslint/types": "5.59.2",
"eslint-visitor-keys": "^3.3.0"
},
"dependencies": {
"eslint-visitor-keys": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
- "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
+ "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
"dev": true
}
}
},
"@vue/compiler-core": {
- "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==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
+ "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
"dev": true,
"requires": {
- "@babel/parser": "^7.16.4",
- "@vue/shared": "3.2.45",
+ "@babel/parser": "^7.21.3",
+ "@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
- "source-map": "^0.6.1"
+ "source-map-js": "^1.0.2"
}
},
"@vue/compiler-dom": {
- "version": "3.2.45",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz",
- "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
+ "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
"dev": true,
"requires": {
- "@vue/compiler-core": "3.2.45",
- "@vue/shared": "3.2.45"
+ "@vue/compiler-core": "3.3.4",
+ "@vue/shared": "3.3.4"
+ }
+ },
+ "@vue/reactivity": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
+ "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
+ "dev": true,
+ "requires": {
+ "@vue/shared": "3.3.4"
+ }
+ },
+ "@vue/runtime-core": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
+ "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
+ "dev": true,
+ "requires": {
+ "@vue/reactivity": "3.3.4",
+ "@vue/shared": "3.3.4"
+ }
+ },
+ "@vue/runtime-dom": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
+ "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
+ "dev": true,
+ "requires": {
+ "@vue/runtime-core": "3.3.4",
+ "@vue/shared": "3.3.4",
+ "csstype": "^3.1.1"
}
},
"@vue/shared": {
- "version": "3.2.45",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz",
- "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
+ "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==",
"dev": true
},
"abbrev": {
@@ -425,6 +476,11 @@
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true
},
+ "add-event-listener": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/add-event-listener/-/add-event-listener-0.0.1.tgz",
+ "integrity": "sha512-hjRmkeDqFUWEFcDHP/Lp0Pa4MhIJk/oQX8B7lFiNrjBKHjf0q+ivCJrucY8d8UI5d0QkZgV2jGdAGXxEZcm3nA=="
+ },
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -530,9 +586,9 @@
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
},
"aws-sdk": {
- "version": "2.1262.0",
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1262.0.tgz",
- "integrity": "sha512-XbaK/XUIxwLEBnHANhJ0RTZtiU288lFRj5FllSihQ5Kb0fibKyW8kJFPsY+NzzDezLH5D3WdGbTKb9fycn5TbA==",
+ "version": "2.1373.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1373.0.tgz",
+ "integrity": "sha512-3/P79VU2VVeiof25bn8TSepEhWCRhwuQGYoEWX/2pApQRJAY+w/3JFVKHjbAe3NYEEBNeiAE6PQ9DqWz5Pp+Lw==",
"requires": {
"buffer": "4.9.2",
"events": "1.1.1",
@@ -543,7 +599,7 @@
"url": "0.10.3",
"util": "^0.12.4",
"uuid": "8.0.0",
- "xml2js": "0.4.19"
+ "xml2js": "0.5.0"
},
"dependencies": {
"uuid": {
@@ -875,6 +931,46 @@
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
},
+ "csstype": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+ "dev": true
+ },
+ "cytoscape": {
+ "version": "3.25.0",
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.25.0.tgz",
+ "integrity": "sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==",
+ "requires": {
+ "heap": "^0.2.6",
+ "lodash": "^4.17.21"
+ }
+ },
+ "cytoscape-dagre": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-2.5.0.tgz",
+ "integrity": "sha512-VG2Knemmshop4kh5fpLO27rYcyUaaDkRw+6PiX4bstpB+QFt0p2oauMrsjVbUamGWQ6YNavh7x2em2uZlzV44g==",
+ "requires": {
+ "dagre": "^0.8.5"
+ }
+ },
+ "cytoscape-klay": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/cytoscape-klay/-/cytoscape-klay-3.1.4.tgz",
+ "integrity": "sha512-VwPj0VR25GPfy6qXVQRi/MYlZM/zkdvRhHlgqbM//lSvstgM6fhp3ik/uM8Wr8nlhskfqz/M1fIDmR6UckbS2A==",
+ "requires": {
+ "klayjs": "^0.4.1"
+ }
+ },
+ "dagre": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
+ "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
+ "requires": {
+ "graphlib": "^2.1.8",
+ "lodash": "^4.17.15"
+ }
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -986,9 +1082,9 @@
}
},
"dompurify": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
- "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.5.tgz",
+ "integrity": "sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA=="
},
"ecc-jsbn": {
"version": "0.1.2",
@@ -1299,9 +1395,9 @@
"dev": true
},
"fastq": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
- "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
@@ -1416,9 +1512,9 @@
"dev": true
},
"get-intrinsic": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
- "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -1433,6 +1529,11 @@
"assert-plus": "^1.0.0"
}
},
+ "gintersect": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/gintersect/-/gintersect-0.1.0.tgz",
+ "integrity": "sha512-jps8Ckj6u8yLxOYzBVJbPqvRdeHOINQgRtufaLHkunwNQcSEdZU0ejPBapSimXJEQ9mdQW4hsEUN7DfJEcTvQQ=="
+ },
"github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@@ -1491,6 +1592,20 @@
"get-intrinsic": "^1.1.3"
}
},
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "graphlib": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
+ "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
+ "requires": {
+ "lodash": "^4.17.15"
+ }
+ },
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@@ -1550,6 +1665,11 @@
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
+ "heap": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
+ "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
+ },
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -1575,9 +1695,9 @@
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"ignore": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
- "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA=="
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
},
"ignore-styles": {
"version": "5.0.1",
@@ -1763,6 +1883,11 @@
"verror": "1.10.0"
}
},
+ "klayjs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/klayjs/-/klayjs-0.4.1.tgz",
+ "integrity": "sha512-WUNxuO7O79TEkxCj6OIaK5TJBkaWaR/IKNTakgV9PwDn+mrr63MLHed34AcE2yTaDntgO6l0zGFIzhcoTeroTA=="
+ },
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -1792,7 +1917,7 @@
"lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
- "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA="
+ "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
},
"lodash.template": {
"version": "4.5.0",
@@ -1850,9 +1975,9 @@
}
},
"marked": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz",
- "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw=="
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A=="
},
"merge2": {
"version": "1.4.1",
@@ -2761,11 +2886,89 @@
"randexp": "0.4.6"
}
},
+ "ngraph.centrality": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/ngraph.centrality/-/ngraph.centrality-0.3.0.tgz",
+ "integrity": "sha512-Qmu9dDHJAx+GAW2AMqmhaub1rINS+fHZGZJ3zPI36ENAXmVNQ/Jkq79br1sg6NUHz/pRBT9MXMuwDyYKmMt8Mw=="
+ },
"ngraph.events": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.1.tgz",
"integrity": "sha512-D4C+nXH/RFxioGXQdHu8ELDtC6EaCiNsZtih0IvyGN81OZSUby4jXoJ5+RNWasfsd0FnKxxpAROyUMzw64QNsw=="
},
+ "ngraph.expose": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/ngraph.expose/-/ngraph.expose-0.0.0.tgz",
+ "integrity": "sha512-Hr88MuhgoSLVGf2aaaXcKl22Rn95duWsjRcoeJMP9PtFmYHGFw/3ctDqBf5phnIyktm0P/Quxs5EGg6xgJcZAQ=="
+ },
+ "ngraph.forcelayout": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-0.5.0.tgz",
+ "integrity": "sha512-qOd1S9unFLw313+l0M/Dk1MePLDUSl4h9RyOtAbo0CyeefnN4PICiRz0LOewR5WuFmQD0/RmZLpjTKu0H7LTKQ==",
+ "requires": {
+ "ngraph.events": "0.0.4",
+ "ngraph.physics.simulator": "^0.3.0"
+ },
+ "dependencies": {
+ "ngraph.events": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.4.tgz",
+ "integrity": "sha512-SY7MdNQoy5KyaVxg03PYCnGF6J7l4p8lEdmYm/5oIqFAmLhg0BmzZzlRqobJ0nEPT6xZlonUQbvCcXtarPZNrg=="
+ }
+ }
+ },
+ "ngraph.fromjson": {
+ "version": "0.1.9",
+ "resolved": "https://registry.npmjs.org/ngraph.fromjson/-/ngraph.fromjson-0.1.9.tgz",
+ "integrity": "sha512-f3GLjbUq239wx4s5A0fDptj9dcNeaEIJU3gm74hWvYK7onD7sFtedP7jVHZA7UJ2FwkKgEhzbPeltv92ycuKZQ==",
+ "requires": {
+ "ngraph.graph": "0.0.14"
+ },
+ "dependencies": {
+ "ngraph.events": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.3.tgz",
+ "integrity": "sha512-UrewofHOFk/05otBm9GD4DA3PTEY/yaElhCclmGC4IcmAYaSDRrC3lENQxJ00AzeBnz1GY2xH7Ct7AfIdhsdWA=="
+ },
+ "ngraph.graph": {
+ "version": "0.0.14",
+ "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-0.0.14.tgz",
+ "integrity": "sha512-ERTLng4KrsGbR7iLZFvg5H+zJ7V+SY8RDqZKYCnOZib5W8M5LCvcil9/8eiJcTRUIPPXW3j8hqPCdLnBvgsn/A==",
+ "requires": {
+ "ngraph.events": "0.0.3"
+ }
+ }
+ }
+ },
+ "ngraph.generators": {
+ "version": "0.0.19",
+ "resolved": "https://registry.npmjs.org/ngraph.generators/-/ngraph.generators-0.0.19.tgz",
+ "integrity": "sha512-P3XqB1sH4zrzM6bMGTtuT/6K76Rnhf1qE8Zu7PkAvhQVCQzdLYiL2/8DwhcPLsetRJHNJv0uwpW9TpntBAqKrw==",
+ "requires": {
+ "ngraph.graph": "0.0.14",
+ "ngraph.random": "0.1.0"
+ },
+ "dependencies": {
+ "ngraph.events": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.3.tgz",
+ "integrity": "sha512-UrewofHOFk/05otBm9GD4DA3PTEY/yaElhCclmGC4IcmAYaSDRrC3lENQxJ00AzeBnz1GY2xH7Ct7AfIdhsdWA=="
+ },
+ "ngraph.graph": {
+ "version": "0.0.14",
+ "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-0.0.14.tgz",
+ "integrity": "sha512-ERTLng4KrsGbR7iLZFvg5H+zJ7V+SY8RDqZKYCnOZib5W8M5LCvcil9/8eiJcTRUIPPXW3j8hqPCdLnBvgsn/A==",
+ "requires": {
+ "ngraph.events": "0.0.3"
+ }
+ },
+ "ngraph.random": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-0.1.0.tgz",
+ "integrity": "sha512-KXCfzk/ZB79BxQSWMvYPGayx3Mb+7n5GPnc8SW0rwysqRV/3QxEKrLU/UVC8eGjc2SYGofqX+uhUE6IXfqR5VA=="
+ }
+ }
+ },
"ngraph.graph": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-19.1.0.tgz",
@@ -2774,11 +2977,59 @@
"ngraph.events": "^1.2.1"
}
},
+ "ngraph.merge": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-0.0.1.tgz",
+ "integrity": "sha512-iXchI5xMjYzA96mee//O7I7gtd4cCakWaSTu11aMTxRDbvBK2qpDDytYg58jO3usAUkjFxBdy1gxYppKmBDuRQ=="
+ },
"ngraph.path": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ngraph.path/-/ngraph.path-1.4.0.tgz",
"integrity": "sha512-yJZay4tP0wcjqkkf8zlMQ/T+JOgU+EWfdE4w4TG8OS94B12J/+Z44UOYxVJErE8E6/wFunX1hMZEB1/GHsBYHg=="
},
+ "ngraph.physics.primitives": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ngraph.physics.primitives/-/ngraph.physics.primitives-0.0.7.tgz",
+ "integrity": "sha512-7jPm14fYcuJ9kytOVNOKxFy6r/Uu9Dnj++uT3iR9XkBcsBahn2xcYJkV6vF1bIb1fQ5XrDCRjRIOcMwEum6jwQ=="
+ },
+ "ngraph.physics.simulator": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/ngraph.physics.simulator/-/ngraph.physics.simulator-0.3.0.tgz",
+ "integrity": "sha512-ObW+HL+hQBIIdc6xG/+qrLe8qv+Sf0X3lq/l2hsjFrIwWtpRKLrSvUUoXiNIeFqRmY/C+PkGo3U+XY523lJ+Fw==",
+ "requires": {
+ "ngraph.events": "0.0.3",
+ "ngraph.expose": "0.0.0",
+ "ngraph.merge": "0.0.1",
+ "ngraph.physics.primitives": "0.0.7",
+ "ngraph.quadtreebh": "0.0.4",
+ "ngraph.random": "0.0.1"
+ },
+ "dependencies": {
+ "ngraph.events": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.3.tgz",
+ "integrity": "sha512-UrewofHOFk/05otBm9GD4DA3PTEY/yaElhCclmGC4IcmAYaSDRrC3lENQxJ00AzeBnz1GY2xH7Ct7AfIdhsdWA=="
+ }
+ }
+ },
+ "ngraph.quadtreebh": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/ngraph.quadtreebh/-/ngraph.quadtreebh-0.0.4.tgz",
+ "integrity": "sha512-xTIkWGXt5Ajnoq9VOr0xDOI9ZL+q4sPhD0Z7vxvn4MCa+l0wf43rg0C7qv0t+RIOgbQBAp0xDpn568hpXAckJA==",
+ "requires": {
+ "ngraph.random": "0.0.1"
+ }
+ },
+ "ngraph.random": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-0.0.1.tgz",
+ "integrity": "sha512-QPKU7ChXF/VrvMQxVo9aWcvXCXp98VfL4nKUteTW/olDqeUqQ61t7m+jvFb8Dj7kKvlKlnsbDA1aWLJGmm17XA=="
+ },
+ "ngraph.tojson": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/ngraph.tojson/-/ngraph.tojson-0.1.4.tgz",
+ "integrity": "sha512-Ii2BTqi8zBRMLH8vTc8pMUKQFJaqbgttG9DKUaazoPVpwC/ww4jyTOHe2ZKaGGZRepnGLqSZ27wZUm7n8MjIgA=="
+ },
"node-abi": {
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.26.0.tgz",
@@ -2920,9 +3171,9 @@
"dev": true
},
"pretty-bytes": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz",
- "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg=="
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.0.tgz",
+ "integrity": "sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ=="
},
"prism-media": {
"version": "1.3.1",
@@ -3212,6 +3463,14 @@
"is-arrayish": "^0.3.1"
}
},
+ "simplesvg": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/simplesvg/-/simplesvg-0.0.10.tgz",
+ "integrity": "sha512-iCVx1A/kI4U3cGPRMRQaGLbIFNDXuB8rsaAsO2mM5wYFDs/MrfmHhrSCqNbOylgt9MhhZU3uMsSQnZM853kwXQ==",
+ "requires": {
+ "add-event-listener": "0.0.1"
+ }
+ },
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -3271,6 +3530,12 @@
"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",
@@ -3549,9 +3814,9 @@
"dev": true
},
"typescript": {
- "version": "4.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
- "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"dev": true
},
"uri-js": {
@@ -3616,10 +3881,43 @@
"extsprintf": "^1.2.0"
}
},
+ "vivagraphjs": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/vivagraphjs/-/vivagraphjs-0.12.0.tgz",
+ "integrity": "sha512-Air+vUHXAWj8NTWUnbU800yKC7SiHpCVwpKIPfDtr5436YoMd7cpg8blt6Fn9xarx+sz1osRxGHBHTaHvcsR6Q==",
+ "requires": {
+ "gintersect": "0.1.0",
+ "ngraph.centrality": "0.3.0",
+ "ngraph.events": "0.0.3",
+ "ngraph.forcelayout": "0.5.0",
+ "ngraph.fromjson": "0.1.9",
+ "ngraph.generators": "0.0.19",
+ "ngraph.graph": "0.0.14",
+ "ngraph.merge": "0.0.1",
+ "ngraph.random": "0.0.1",
+ "ngraph.tojson": "0.1.4",
+ "simplesvg": "0.0.10"
+ },
+ "dependencies": {
+ "ngraph.events": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-0.0.3.tgz",
+ "integrity": "sha512-UrewofHOFk/05otBm9GD4DA3PTEY/yaElhCclmGC4IcmAYaSDRrC3lENQxJ00AzeBnz1GY2xH7Ct7AfIdhsdWA=="
+ },
+ "ngraph.graph": {
+ "version": "0.0.14",
+ "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-0.0.14.tgz",
+ "integrity": "sha512-ERTLng4KrsGbR7iLZFvg5H+zJ7V+SY8RDqZKYCnOZib5W8M5LCvcil9/8eiJcTRUIPPXW3j8hqPCdLnBvgsn/A==",
+ "requires": {
+ "ngraph.events": "0.0.3"
+ }
+ }
+ }
+ },
"vue": {
- "version": "2.6.10",
- "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
- "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
+ "version": "2.6.14",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
+ "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
"vue-eslint-parser": {
"version": "7.11.0",
@@ -3688,9 +3986,9 @@
}
},
"vuetify": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.12.tgz",
- "integrity": "sha512-qe3hcMpWmT1O15tp+p65lOS7UKZ/hQYQktCsw9iXx2u3RwVbX6GR82gY2iROrKsiAzYDvMgrYxWQwY/pUfkekw=="
+ "version": "2.6.15",
+ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.15.tgz",
+ "integrity": "sha512-2a6sBSHzivXgi9pZMyHuzTgMyInCkj/BrVwTnoCa1Y/Dnfwj7lkWzgKQDScbGVK0q4vJ+YHoBBrLOmnhz1R0YA=="
},
"vuetify-upload-button": {
"version": "2.0.2",
@@ -3763,18 +4061,18 @@
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg=="
},
"xml2js": {
- "version": "0.4.19",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
- "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"requires": {
"sax": ">=0.6.0",
- "xmlbuilder": "~9.0.1"
+ "xmlbuilder": "~11.0.0"
}
},
"xmlbuilder": {
- "version": "9.0.7",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
- "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ=="
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"yallist": {
"version": "4.0.0",
diff --git a/app/package.json b/app/package.json
index 7cee6eb3..e92fb1b9 100644
--- a/app/package.json
+++ b/app/package.json
@@ -20,35 +20,40 @@
"npm": "6.13.x"
},
"dependencies": {
- "@babel/runtime": "^7.20.6",
+ "@babel/runtime": "^7.21.5",
"@chenfengyuan/vue-countdown": "^1.1.5",
"@tozd/vue-observer-utils": "^0.5.0",
- "aws-sdk": "^2.1262.0",
+ "aws-sdk": "^2.1373.0",
"bcrypt": "^5.1.0",
"chroma-js": "^2.4.2",
"css-box-shadow": "^1.0.0-3",
+ "cytoscape": "^3.25.0",
+ "cytoscape-dagre": "^2.5.0",
+ "cytoscape-klay": "^3.1.4",
+ "dagre": "^0.8.5",
"date-fns": "^1.30.1",
"ddp-rate-limiter-mixin": "^1.1.10",
"discord.js": "^12.5.3",
- "dompurify": "^2.4.1",
- "ignore": "^5.2.1",
+ "dompurify": "^2.4.5",
+ "ignore": "^5.2.4",
"ignore-styles": "^5.0.1",
"lodash": "^4.17.20",
- "marked": "^4.2.3",
+ "marked": "^4.3.0",
"meteor-node-stubs": "^1.2.5",
"minify-css-string": "^1.0.0",
"moo": "^0.5.2",
"nearley": "^2.19.1",
"ngraph.graph": "^19.1.0",
"ngraph.path": "^1.4.0",
- "pretty-bytes": "^6.0.0",
+ "pretty-bytes": "^6.1.0",
"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",
- "vue": "2.6.10",
+ "vivagraphjs": "^0.12.0",
+ "vue": "2.6.14",
"vue-meteor-tracker": "^2.0.0",
"vue-reactive-provide": "^0.3.0",
"vue-router": "^3.6.5",
@@ -58,14 +63,16 @@
"vuex": "^3.1.3"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^5.44.0",
- "@typescript-eslint/parser": "^5.44.0",
- "@vue/compiler-dom": "^3.2.45",
+ "@types/mocha": "^10.0.1",
+ "@typescript-eslint/eslint-plugin": "^5.59.2",
+ "@typescript-eslint/parser": "^5.59.2",
+ "@vue/compiler-dom": "^3.3.4",
+ "@vue/runtime-dom": "^3.3.4",
"chai": "^4.3.7",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.20.0",
"eslint-plugin-vuetify": "^1.1.0",
- "typescript": "^4.9.3"
+ "typescript": "^5.0"
},
"eslintConfig": {
"extends": [
@@ -118,4 +125,4 @@
]
}
}
-}
\ No newline at end of file
+}
diff --git a/app/packages/redis-oplog b/app/packages/redis-oplog
new file mode 160000
index 00000000..83e302c1
--- /dev/null
+++ b/app/packages/redis-oplog
@@ -0,0 +1 @@
+Subproject commit 83e302c15456d6744047c50fc8d1add9e739b001
diff --git a/app/public/images/paragons/vibes.png b/app/public/images/paragons/vibes.png
new file mode 100644
index 00000000..026cacac
Binary files /dev/null and b/app/public/images/paragons/vibes.png differ
diff --git a/app/redis-settings.json b/app/redis-settings.json
new file mode 100644
index 00000000..45fc18d7
--- /dev/null
+++ b/app/redis-settings.json
@@ -0,0 +1,19 @@
+{
+ "redisOplog": {
+ "redis": {
+ "port": 6379,
+ "host": "127.0.0.1"
+ },
+ "retryIntervalMs": 1000,
+ "mutationDefaults": {
+ "optimistic": true,
+ "pushToRedis": true
+ },
+ "cacheTimeout": 1800000,
+ "cacheTimer": 300000,
+ "secondaryReads": null,
+ "raceDetectionDelay": 1000,
+ "raceDetection": true,
+ "debug": false
+ }
+}
\ No newline at end of file
diff --git a/app/server/main.js b/app/server/main.js
index 642113e0..eddb6d67 100644
--- a/app/server/main.js
+++ b/app/server/main.js
@@ -5,6 +5,7 @@ import '/imports/server/rest/index.js';
import '/imports/server/config/accountsEmailConfig.js';
import '/imports/server/config/simpleSchemaDebug.js';
import '/imports/server/config/SyncedCronConfig.js';
+import '/imports/server/config/redisCaching.js';
import '/imports/server/publications/index.js';
import '/imports/server/cron/deleteSoftRemovedDocuments.js';
import '/imports/api/parenting/organizeMethods.js';