diff --git a/app/imports/api/creature/CreatureProperties.js b/app/imports/api/creature/CreatureProperties.js index 2e7be60d..6eb1211a 100644 --- a/app/imports/api/creature/CreatureProperties.js +++ b/app/imports/api/creature/CreatureProperties.js @@ -9,7 +9,7 @@ import { recomputeCreature } from '/imports/api/creature/computation/recomputeCr import LibraryNodes from '/imports/api/library/LibraryNodes.js'; import Creatures from '/imports/api/creature/Creatures.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; -import { softRemove } from '/imports/api/parenting/softRemove.js'; +import { softRemove, restore } from '/imports/api/parenting/softRemove.js'; import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js'; import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex.js'; import { @@ -445,6 +445,23 @@ const softRemoveProperty = new ValidatedMethod({ } }); +const restoreProperty = new ValidatedMethod({ + name: 'creatureProperties.restore', + validate: new SimpleSchema({ + _id: SimpleSchema.RegEx.Id + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({_id}){ + let property = CreatureProperties.findOne(_id); + assertPropertyEditPermission(property, this.userId); + restore({_id, collection: CreatureProperties}); + recomputeCreatures(property); + } +}); export default CreatureProperties; export { @@ -458,5 +475,6 @@ export { selectAmmoItem, pushToProperty, pullFromProperty, - softRemoveProperty, + softRemoveProperty, + restoreProperty, }; diff --git a/app/imports/api/parenting/softRemove.js b/app/imports/api/parenting/softRemove.js index 1b53d348..9ef16f8c 100644 --- a/app/imports/api/parenting/softRemove.js +++ b/app/imports/api/parenting/softRemove.js @@ -41,16 +41,21 @@ const restoreError = function(){ }; export function restore({_id, collection}){ - collection = getCollectionByName(collection); + if (typeof collection === 'string') { + collection = getCollectionByName(collection); + } let numUpdated = collection.update({ _id, removedWith: {$exists: false} }, { $unset: { removed: 1, removedAt: 1, - }}); + }}, { + selector: {type: 'any'}, + },); if (numUpdated === 0) restoreError(); updateDescendants({ + collection, ancestorId: _id, filter: { removedWith: _id, diff --git a/app/imports/api/properties/ClassLevels.js b/app/imports/api/properties/ClassLevels.js index ed1c41e7..b49b4b4c 100644 --- a/app/imports/api/properties/ClassLevels.js +++ b/app/imports/api/properties/ClassLevels.js @@ -6,6 +6,10 @@ let ClassLevelSchema = new SimpleSchema({ type: String, optional: true, }, + description: { + type: String, + optional: true, + }, // The name of this class level's variable variableName: { type: String, diff --git a/app/imports/ui/components/snackbars/Snackbars.vue b/app/imports/ui/components/snackbars/Snackbars.vue new file mode 100644 index 00000000..b6c57603 --- /dev/null +++ b/app/imports/ui/components/snackbars/Snackbars.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/app/imports/ui/components/snackbars/snackboxStore.js b/app/imports/ui/components/snackbars/snackboxStore.js new file mode 100644 index 00000000..9ea306ef --- /dev/null +++ b/app/imports/ui/components/snackbars/snackboxStore.js @@ -0,0 +1,47 @@ +const snackbarStore = { + state: { + snackbars: [], + snackbarTimout: undefined, + }, + mutations: { + addSnackbar(state, value){ + state.snackbars.push(value) + }, + closeCurrentSnackbar (state){ + state.snackbars.shift(); + }, + cancelSnackbarTimeout (state){ + if(state.snackbarTimout){ + clearTimeout(state.snackbarTimout); + } + }, + setSnackbarTimout(state, value){ + state.snackbarTimout = value; + }, + }, + actions: { + snackbar({dispatch, commit}, value){ + // value = { + // text, + // showCloseButton, + // callback, + // callbackName + // } + commit('addSnackbar', value); + commit('setSnackbarTimout', setTimeout(() => { + dispatch('closeSnackbar'); + }, 5000)); + }, + closeSnackbar({dispatch, commit, state}){ + commit('closeCurrentSnackbar'); + commit('cancelSnackbarTimeout'); + if (state.snackbars.length){ + commit('setSnackbarTimout', setTimeout(() => { + dispatch('closeSnackbar'); + }, 5000)); + } + }, + } +}; + +export default snackbarStore; diff --git a/app/imports/ui/creature/character/CharacterSheet.vue b/app/imports/ui/creature/character/CharacterSheet.vue index 070f5919..b73f5e5e 100644 --- a/app/imports/ui/creature/character/CharacterSheet.vue +++ b/app/imports/ui/creature/character/CharacterSheet.vue @@ -93,9 +93,6 @@ required: true, }, }, - data(){return { - snackbars: [], - }}, reactiveProvide: { name: 'context', include: ['creature', 'editPermission'], @@ -124,10 +121,9 @@ added(doc){ if (!that.$subReady.singleCharacter) return; if (that.$store.state.rightDrawer) return; - if (that.snackbars.some(o => o._id === doc._id)) return; - doc.open = true; - that.$store.commit('snackbar', { - doc + that.$store.dispatch('snackbar', { + text: doc.text, + showCloseButton: true, }); }, }); diff --git a/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue b/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue index 905c13f4..df8f118b 100644 --- a/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue +++ b/app/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue @@ -66,6 +66,7 @@ import CreatureProperties, { pushToProperty, pullFromProperty, softRemoveProperty, + restoreProperty, } from '/imports/api/creature/CreatureProperties.js'; import Creatures from '/imports/api/creature/Creatures.js'; import PropertyToolbar from '/imports/ui/components/propertyToolbar.vue'; @@ -75,6 +76,7 @@ import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue'; import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormIndex.js'; import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js'; import CreaturePropertiesTree from '/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue'; +import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js'; import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js'; import { get, findLast } from 'lodash'; @@ -174,12 +176,20 @@ export default { }); }, remove(){ - softRemoveProperty.call({_id: this._id}); + const _id = this._id; + softRemoveProperty.call({_id}); if (this.embedded){ this.$emit('removed'); } else { this.$store.dispatch('popDialogStack'); } + this.$store.dispatch('snackbar', { + text: `Deleted ${getPropertyTitle(this.model)}`, + callbackName: 'undo', + callback(){ + restoreProperty.call({_id}); + }, + }); }, selectSubProperty(_id){ this.$store.commit('pushDialogStack', { diff --git a/app/imports/ui/creature/slots/SlotFillDialog.vue b/app/imports/ui/creature/slots/SlotFillDialog.vue index a2ddb9b5..7e758b27 100644 --- a/app/imports/ui/creature/slots/SlotFillDialog.vue +++ b/app/imports/ui/creature/slots/SlotFillDialog.vue @@ -19,7 +19,8 @@ style="max-width: 500px;" hover ripple - :class="{'primary theme--dark': node._id === (selectedNode && selectedNode._id)}" + :class="{'primary': node._id === (selectedNode && selectedNode._id)}" + :dark="node._id === (selectedNode && selectedNode._id)" @click="selectedNode = node" >

- - {{ getTitle(node) }}

{{ node.description }} @@ -108,14 +108,14 @@ import DialogBase from '/imports/ui/dialogStack/DialogBase.vue'; import { getPropertyName } from '/imports/constants/PROPERTIES.js'; import { parse, CompilationContext } from '/imports/parser/parser.js'; import PROPERTIES from '/imports/constants/PROPERTIES.js'; -import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue'; import ColumnLayout from '/imports/ui/components/ColumnLayout.vue'; +import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue'; export default { components: { DialogBase, ColumnLayout, - PropertyIcon, + TreeNodeView, }, props:{ slotId: { diff --git a/app/imports/ui/creature/slots/Slots.vue b/app/imports/ui/creature/slots/Slots.vue index c4719f0e..06995ff8 100644 --- a/app/imports/ui/creature/slots/Slots.vue +++ b/app/imports/ui/creature/slots/Slots.vue @@ -12,26 +12,31 @@ {{ slot.totalFilled }} / {{ slot.quantityExpected }} -
- - - + - delete - -
+ + + + + + delete + + + + import CreatureProperties from '/imports/api/creature/CreatureProperties.js'; import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue'; -import { softRemoveProperty, insertPropertyFromLibraryNode } from '/imports/api/creature/CreatureProperties.js'; +import { + insertPropertyFromLibraryNode, + softRemoveProperty, + restoreProperty +} from '/imports/api/creature/CreatureProperties.js'; import getActiveProperties from '/imports/api/creature/getActiveProperties.js'; +import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js'; + export default { components: { TreeNodeView, @@ -64,6 +75,13 @@ export default { }, }, methods: { + clickSlotChild({_id}){ + this.$store.commit('pushDialogStack', { + component: 'creature-property-dialog', + elementId: `slot-child-${_id}`, + data: {_id}, + }); + }, fillSlot(slot){ let slotId = slot._id; let creatureId = this.creatureId; @@ -90,8 +108,15 @@ export default { } }); }, - remove(_id){ - softRemoveProperty.call({_id}); + remove(model){ + softRemoveProperty.call({_id: model._id}); + this.$store.dispatch('snackbar', { + text: `Deleted ${getPropertyTitle(model)}`, + callbackName: 'undo', + callback(){ + restoreProperty.call({_id: model._id}); + }, + }); } }, meteor: { @@ -126,7 +151,4 @@ export default { diff --git a/app/imports/ui/layouts/AppLayout.vue b/app/imports/ui/layouts/AppLayout.vue index fae76472..3bcf8676 100644 --- a/app/imports/ui/layouts/AppLayout.vue +++ b/app/imports/ui/layouts/AppLayout.vue @@ -67,31 +67,7 @@ name="rightDrawer" /> - - {{ snackbar.text.split(/\n+/).pop() }} - - {{ snackbar.callbackName }} - - - close - - + @@ -101,11 +77,13 @@ import DialogStack from '/imports/ui/dialogStack/DialogStack.vue'; import { theme, darkTheme } from '/imports/ui/theme.js'; import { mapMutations } from 'vuex'; + import Snackbars from '/imports/ui/components/snackbars/Snackbars.vue'; export default { components: { Sidebar, DialogStack, + Snackbars, }, data(){return { name: 'Home', diff --git a/app/imports/ui/properties/forms/AdjustmentForm.vue b/app/imports/ui/properties/forms/AdjustmentForm.vue index 23272baa..183a72c3 100644 --- a/app/imports/ui/properties/forms/AdjustmentForm.vue +++ b/app/imports/ui/properties/forms/AdjustmentForm.vue @@ -29,6 +29,14 @@ :menu-props="{auto: true, lazy: true}" @change="change('target', ...arguments)" /> +
diff --git a/app/imports/ui/properties/forms/AttributeForm.vue b/app/imports/ui/properties/forms/AttributeForm.vue index c238df43..eab23559 100644 --- a/app/imports/ui/properties/forms/AttributeForm.vue +++ b/app/imports/ui/properties/forms/AttributeForm.vue @@ -65,6 +65,14 @@ name="Advanced" standalone > +
+
diff --git a/app/imports/ui/properties/forms/ClassLevelForm.vue b/app/imports/ui/properties/forms/ClassLevelForm.vue index 5164281d..afc91a99 100644 --- a/app/imports/ui/properties/forms/ClassLevelForm.vue +++ b/app/imports/ui/properties/forms/ClassLevelForm.vue @@ -27,6 +27,13 @@ @change="change('variableName', ...arguments)" />
+ +
+
diff --git a/app/imports/ui/properties/forms/DamageMultiplierForm.vue b/app/imports/ui/properties/forms/DamageMultiplierForm.vue index b0baf772..feb29f0c 100644 --- a/app/imports/ui/properties/forms/DamageMultiplierForm.vue +++ b/app/imports/ui/properties/forms/DamageMultiplierForm.vue @@ -28,6 +28,14 @@ @change="change('value', ...arguments)" />
+ diff --git a/app/imports/ui/properties/forms/ItemForm.vue b/app/imports/ui/properties/forms/ItemForm.vue index 79ff3630..04250b6c 100644 --- a/app/imports/ui/properties/forms/ItemForm.vue +++ b/app/imports/ui/properties/forms/ItemForm.vue @@ -79,12 +79,6 @@ name="Advanced" standalone > - + + diff --git a/app/imports/ui/properties/forms/ProficiencyForm.vue b/app/imports/ui/properties/forms/ProficiencyForm.vue index e4bd20e4..769ffd68 100644 --- a/app/imports/ui/properties/forms/ProficiencyForm.vue +++ b/app/imports/ui/properties/forms/ProficiencyForm.vue @@ -27,6 +27,14 @@ @change="change('value', ...arguments)" /> + diff --git a/app/imports/ui/properties/forms/SavingThrowForm.vue b/app/imports/ui/properties/forms/SavingThrowForm.vue index c4ed3410..1f471a7f 100644 --- a/app/imports/ui/properties/forms/SavingThrowForm.vue +++ b/app/imports/ui/properties/forms/SavingThrowForm.vue @@ -21,6 +21,16 @@ :error-messages="errors.stat" @change="change('stat', ...arguments)" /> + diff --git a/app/imports/ui/properties/forms/SkillForm.vue b/app/imports/ui/properties/forms/SkillForm.vue index 57381ce6..27f22104 100644 --- a/app/imports/ui/properties/forms/SkillForm.vue +++ b/app/imports/ui/properties/forms/SkillForm.vue @@ -44,6 +44,14 @@ name="Advanced" standalone > +
+ - - -