From b7a4a3d3fa30bd5639e282dca51b8d976c6d4b48 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 25 Feb 2019 15:38:57 +0200 Subject: [PATCH] Added simple feature UI components and insertion dialog --- app/imports/api/creature/Creatures.js | 3 + .../api/creature/creatureComputation.js | 41 ++++++- .../api/creature/properties/Attributes.js | 18 +-- .../api/creature/properties/Features.js | 51 +++++++- .../api/creature/subSchemas/ColorSchema.js | 3 - .../api/creature/subSchemas/OrderSchema.js | 6 + app/imports/ui/StoryBook.vue | 2 + app/imports/ui/character/CharacterSheet.vue | 8 ++ app/imports/ui/character/FeaturesTab.vue | 73 +++++++++++ .../ui/components/AttributeCreationDialog.vue | 20 ++-- app/imports/ui/components/AttributeEdit.vue | 1 - .../ui/components/FeatureCard.Story.vue | 51 ++++++++ app/imports/ui/components/FeatureCard.vue | 60 ++++++++++ .../ui/components/FeatureCreationDialog.vue | 78 ++++++++++++ app/imports/ui/components/FeatureEdit.vue | 113 ++++++++++++++++++ app/imports/ui/components/ToolbarCard.vue | 38 ++++++ app/imports/ui/components/global/TextArea.vue | 7 ++ app/imports/ui/dialogStack/DialogBase.vue | 2 +- .../ui/dialogStack/DialogComponentIndex.js | 2 + app/imports/ui/dialogStack/DialogStack.vue | 2 +- app/imports/ui/utility/evaluate.js | 29 +++++ app/package-lock.json | 62 ---------- app/sharedMain.js | 2 +- 23 files changed, 579 insertions(+), 93 deletions(-) create mode 100644 app/imports/api/creature/subSchemas/OrderSchema.js create mode 100644 app/imports/ui/character/FeaturesTab.vue create mode 100644 app/imports/ui/components/FeatureCard.Story.vue create mode 100644 app/imports/ui/components/FeatureCard.vue create mode 100644 app/imports/ui/components/FeatureCreationDialog.vue create mode 100644 app/imports/ui/components/FeatureEdit.vue create mode 100644 app/imports/ui/components/ToolbarCard.vue create mode 100644 app/imports/ui/utility/evaluate.js diff --git a/app/imports/api/creature/Creatures.js b/app/imports/api/creature/Creatures.js index 2feea3ce..79d2f1ba 100644 --- a/app/imports/api/creature/Creatures.js +++ b/app/imports/api/creature/Creatures.js @@ -36,6 +36,9 @@ let creatureSchema = schema({ level: {type: SimpleSchema.Integer, defaultValue: 0}, type: {type: String, defaultValue: "pc", allowedValues: ["pc", "npc", "monster"]}, + //computed + variables: {type: Object, blackbox: true}, + //permissions owner: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, readers: {type: Array, defaultValue: [], index: 1}, diff --git a/app/imports/api/creature/creatureComputation.js b/app/imports/api/creature/creatureComputation.js index 29b24f77..eb352a06 100644 --- a/app/imports/api/creature/creatureComputation.js +++ b/app/imports/api/creature/creatureComputation.js @@ -94,9 +94,48 @@ function writeCreature(char) { writeSkills(char); writeDamageMultipliers(char); writeEffects(char); - Creatures.update(char.id, {$set: {level: char.level}}); + writeCreatureDoc(char); }; +function writeCreatureDoc(char) { + // Store all the variables, using the same priority as computation evaluation + // Attributes + let variables = {}; + for (let key in char.atts){ + variables[key] = char.atts[key].result; + if ( + char.atts[key].attributeType === 'ability' && + !variables.hasOwnProperty(key + 'Mod') + ){ + variables[key + 'Mod'] = char.atts[key].mod; + } + } + for (let key in char.skills){ + if (!variables.hasOwnProperty(key)){ + variables[key] = char.skills[key].result; + } + } + // Damage Multipliers + for (let key in char.dms){ + if (!variables.hasOwnProperty(key)){ + variables[key] = char.dms[key].result; + } + } + // Class levels + for (let key in char.classes){ + if (!variables.hasOwnProperty(key + 'Level')){ + variables[key + 'Level'] = char.classes[key].level; + } + } + // Creature level + if (!variables.hasOwnProperty('level')){ + variables['level'] = char.level; + } + + // Write the creature + Creatures.update(char.id, {$set: {level: char.level, variables}}); +} + /* * Write all the attributes from the in-memory char object to the Attirbute docs */ diff --git a/app/imports/api/creature/properties/Attributes.js b/app/imports/api/creature/properties/Attributes.js index 6daa70c1..efc3806a 100644 --- a/app/imports/api/creature/properties/Attributes.js +++ b/app/imports/api/creature/properties/Attributes.js @@ -2,6 +2,7 @@ import {makeChild} from "/imports/api/parenting.js"; import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js"; +import OrderSchema from "/imports/api/creature/subSchemas/OrderSchema.js"; import { canEditCreature } from '/imports/api/creature/creaturePermission.js'; import { recomputeCreatureById } from '/imports/api/creature/creatureComputation.js' import { getHighestOrder } from '/imports/api/order.js'; @@ -29,12 +30,6 @@ let attributeSchema = schema({ regEx: /^\w*[a-z]\w*$/i, index: 1, }, - // Attributes need to store their order to keep the sheet consistent - order: { - type: SimpleSchema.Integer, - // Indexed because we update order in bulk using the current order as a query - index: 1, - }, type: { type: String, allowedValues: [ @@ -58,6 +53,10 @@ let attributeSchema = schema({ type: Number, defaultValue: 0, }, + enabled: { + type: Boolean, + defaultValue: true, + }, // The computed modifier, provided the attribute is an ability mod: { type: SimpleSchema.Integer, @@ -82,6 +81,8 @@ let attributeSchema = schema({ type: Number, optional: true, }, + // Attributes need to store their order to keep the sheet consistent + order: OrderSchema(), color: ColorSchema(), }); @@ -108,10 +109,9 @@ const insertAttribute = new ValidatedMethod({ validate: schema({ attribute: { - type: Object, - blackbox: true, + type: attributeSchema.omit('order', 'parent'), }, - }).validator(), + }).validator({ clean: true }), run({attribute}) { const charId = attribute.charId; diff --git a/app/imports/api/creature/properties/Features.js b/app/imports/api/creature/properties/Features.js index f1982998..de79f0f9 100644 --- a/app/imports/api/creature/properties/Features.js +++ b/app/imports/api/creature/properties/Features.js @@ -1,6 +1,10 @@ import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.js"; +import OrderSchema from "/imports/api/creature/subSchemas/OrderSchema.js"; +import { canEditCreature } from '/imports/api/creature/creaturePermission.js'; +import { recomputeCreatureById } from '/imports/api/creature/creatureComputation.js' +import { getHighestOrder } from '/imports/api/order.js'; import {makeParent} from "/imports/api/parenting.js"; let Features = new Mongo.Collection("features"); @@ -13,17 +17,58 @@ let featureSchema = schema({ used: {type: SimpleSchema.Integer, defaultValue: 0}, reset: { type: String, - allowedValues: ["manual", "longRest", "shortRest"], - defaultValue: "manual", + allowedValues: ["longRest", "shortRest"], + optional: true, }, enabled: {type: Boolean, defaultValue: true}, alwaysEnabled:{type: Boolean, defaultValue: true}, + order: { + type: SimpleSchema.Integer, + // Indexed because we update order in bulk using the current order as a query + index: 1, + defaultValue: 0, + }, + order: OrderSchema(), + color: ColorSchema(), }); Features.attachSchema(featureSchema); -Features.attachSchema(ColorSchema); //Features.attachBehaviour("softRemovable"); makeParent(Features, ["name", "enabled"]); //parents of effects and attacks +const insertFeature = new ValidatedMethod({ + + name: "Features.methods.insert", + + validate: schema({ + feature: { + type: featureSchema.omit('order', 'parent'), + }, + }).validator({clean: true}), + + run({feature}) { + const charId = feature.charId; + if (canEditCreature(charId, this.userId)){ + // Set order + feature.order = getHighestOrder({ + collection: Features, + charId, + }) + 1; + + // Set parent + feature.parent = { + id: charId, + collection: 'Creatures', + }; + + // Insert + let featureId = Features.insert(feature); + recomputeCreatureById(charId); + return featureId; + } + }, +}); + export default Features; +export { insertFeature } diff --git a/app/imports/api/creature/subSchemas/ColorSchema.js b/app/imports/api/creature/subSchemas/ColorSchema.js index a66af43e..4c768017 100644 --- a/app/imports/api/creature/subSchemas/ColorSchema.js +++ b/app/imports/api/creature/subSchemas/ColorSchema.js @@ -1,6 +1,3 @@ -import SimpleSchema from 'simpl-schema'; -import schema from '/imports/api/schema.js'; - const ColorSchema = ({optional = false} = {}) => ({ type: String, defaultValue: "#9E9E9E", diff --git a/app/imports/api/creature/subSchemas/OrderSchema.js b/app/imports/api/creature/subSchemas/OrderSchema.js new file mode 100644 index 00000000..3b0ae145 --- /dev/null +++ b/app/imports/api/creature/subSchemas/OrderSchema.js @@ -0,0 +1,6 @@ +const OrderSchema = () => ({ + type: Number, + index: true, +}); + +export default OrderSchema; diff --git a/app/imports/ui/StoryBook.vue b/app/imports/ui/StoryBook.vue index 90c30651..fcc7ff1e 100644 --- a/app/imports/ui/StoryBook.vue +++ b/app/imports/ui/StoryBook.vue @@ -47,6 +47,7 @@ import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue'; import EffectEdit from '/imports/ui/components/EffectEdit.Story.vue'; import EffectEditExpansionList from '/imports/ui/components/EffectEditExpansionList.Story.vue'; + import FeatureCard from '/imports/ui/components/FeatureCard.Story.vue'; import HealthBar from '/imports/ui/components/HealthBar.Story.vue'; import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue'; import IconSearch from '/imports/ui/components/IconSearch.Story.vue'; @@ -64,6 +65,7 @@ DialogStack, EffectEdit, EffectEditExpansionList, + FeatureCard, HealthBar, HitDiceListTile, IconSearch, diff --git a/app/imports/ui/character/CharacterSheet.vue b/app/imports/ui/character/CharacterSheet.vue index 5b20c029..20428a75 100644 --- a/app/imports/ui/character/CharacterSheet.vue +++ b/app/imports/ui/character/CharacterSheet.vue @@ -15,6 +15,9 @@ > Stats + + + Features Tree @@ -25,6 +28,9 @@ + + + @@ -43,6 +49,7 @@ import { mapMutations } from "vuex"; import { theme } from '/imports/ui/theme.js'; import StatsTab from '/imports/ui/character/StatsTab.vue'; + import FeaturesTab from '/imports/ui/character/FeaturesTab.vue'; import CharacterTreeView from '/imports/ui/character/CharacterTreeView.vue'; import { recomputeCreature } from '/imports/api/creature/creatureComputation.js' @@ -53,6 +60,7 @@ }, components: { StatsTab, + FeaturesTab, CharacterTreeView, }, data(){return { diff --git a/app/imports/ui/character/FeaturesTab.vue b/app/imports/ui/character/FeaturesTab.vue new file mode 100644 index 00000000..e4be1597 --- /dev/null +++ b/app/imports/ui/character/FeaturesTab.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/app/imports/ui/components/AttributeCreationDialog.vue b/app/imports/ui/components/AttributeCreationDialog.vue index 26d5a360..e031b9c9 100644 --- a/app/imports/ui/components/AttributeCreationDialog.vue +++ b/app/imports/ui/components/AttributeCreationDialog.vue @@ -9,16 +9,15 @@ @change="change" :debounce-time="0" /> -
- - - Insert Attribute - -
+ + + Insert Attribute + @@ -26,7 +25,6 @@ import AttributeEdit from '/imports/ui/components/AttributeEdit.vue'; import Attributes from '/imports/api/creature/properties/Attributes.js'; import DialogBase from '/imports/ui/dialogStack/DialogBase.vue'; - import { Tracker } from 'meteor/tracker'; export default { components: { diff --git a/app/imports/ui/components/AttributeEdit.vue b/app/imports/ui/components/AttributeEdit.vue index 09276ff0..b93b78e2 100644 --- a/app/imports/ui/components/AttributeEdit.vue +++ b/app/imports/ui/components/AttributeEdit.vue @@ -49,7 +49,6 @@ /> + +
+ +
+
+ + + + + diff --git a/app/imports/ui/components/FeatureCreationDialog.vue b/app/imports/ui/components/FeatureCreationDialog.vue new file mode 100644 index 00000000..1220a9e2 --- /dev/null +++ b/app/imports/ui/components/FeatureCreationDialog.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/app/imports/ui/components/FeatureEdit.vue b/app/imports/ui/components/FeatureEdit.vue new file mode 100644 index 00000000..a41d3677 --- /dev/null +++ b/app/imports/ui/components/FeatureEdit.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/app/imports/ui/components/ToolbarCard.vue b/app/imports/ui/components/ToolbarCard.vue new file mode 100644 index 00000000..a26a0501 --- /dev/null +++ b/app/imports/ui/components/ToolbarCard.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/app/imports/ui/components/global/TextArea.vue b/app/imports/ui/components/global/TextArea.vue index 7a609f1c..9eb2f07e 100644 --- a/app/imports/ui/components/global/TextArea.vue +++ b/app/imports/ui/components/global/TextArea.vue @@ -4,6 +4,7 @@ :loading="loading" :error-messages="errors" :value="safeValue" + :auto-grow="autoGrow" @input="input" @focus="focused = true" @blur="focused = false" @@ -15,5 +16,11 @@ export default { mixins: [SmartInput], + props: { + autoGrow: { + type: Boolean, + default: true, + }, + }, }; diff --git a/app/imports/ui/dialogStack/DialogBase.vue b/app/imports/ui/dialogStack/DialogBase.vue index c0ec5b57..b8dc2586 100644 --- a/app/imports/ui/dialogStack/DialogBase.vue +++ b/app/imports/ui/dialogStack/DialogBase.vue @@ -16,7 +16,7 @@ - + diff --git a/app/imports/ui/dialogStack/DialogComponentIndex.js b/app/imports/ui/dialogStack/DialogComponentIndex.js index 38f825fc..ee1d1411 100644 --- a/app/imports/ui/dialogStack/DialogComponentIndex.js +++ b/app/imports/ui/dialogStack/DialogComponentIndex.js @@ -1,11 +1,13 @@ import AttributeDialog from '/imports/ui/components/AttributeDialog.vue'; import AttributeDialogContainer from '/imports/ui/components/AttributeDialogContainer.vue'; import AttributeCreationDialog from '/imports/ui/components/AttributeCreationDialog.vue'; +import FeatureCreationDialog from '/imports/ui/components/FeatureCreationDialog.vue'; import SkillDialogContainer from '/imports/ui/components/SkillDialogContainer.vue'; export default { AttributeDialog, AttributeDialogContainer, AttributeCreationDialog, + FeatureCreationDialog, SkillDialogContainer, }; diff --git a/app/imports/ui/dialogStack/DialogStack.vue b/app/imports/ui/dialogStack/DialogStack.vue index 9f0132bf..538e1817 100644 --- a/app/imports/ui/dialogStack/DialogStack.vue +++ b/app/imports/ui/dialogStack/DialogStack.vue @@ -217,7 +217,7 @@ right: 0; bottom: 0; pointer-events: none; - z-index: 3; + z-index: 4; } .dialog-sizer { position: relative; diff --git a/app/imports/ui/utility/evaluate.js b/app/imports/ui/utility/evaluate.js new file mode 100644 index 00000000..05d66b29 --- /dev/null +++ b/app/imports/ui/utility/evaluate.js @@ -0,0 +1,29 @@ +// Computations resolve to numbers +// vars is a dict of variables to substitute +function evaluateComputation(string, vars){ + if (!string) return string; + // Replace all the string variables with numbers if possible + let substitutedString = string.replace( + /\w*[a-z]\w*/gi, + sub => vars.hasOwnProperty(sub) ? vars[sub] : sub + ); + + // Evaluate the expression to a number or return it as is. + try { + return math.eval(substitutedString); + } catch (e){ + return substitutedString; + } +}; + +// Strings can have computations in bracers like so: {computation} +// vars is a dict of variables to substitute +function evaluateString(string, vars){ + if (!string) return string; + // Compute everything inside bracers + return string.replace(/\{([^\{\}]*)\}/g, function(match, p1){ + return evaluateComputation(p1, vars); + }); +} + +export { evaluateComputation, evaluateString }; diff --git a/app/package-lock.json b/app/package-lock.json index 803fb77c..b765c90a 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1406,37 +1406,6 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-http": { @@ -1449,37 +1418,6 @@ "readable-stream": "^2.3.3", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "string_decoder": { diff --git a/app/sharedMain.js b/app/sharedMain.js index 08f51eb8..d8101acb 100644 --- a/app/sharedMain.js +++ b/app/sharedMain.js @@ -2,5 +2,5 @@ import SimpleSchema from 'simpl-schema'; import schema from '/imports/api/schema.js'; if (Meteor.isDevelopment){ - SimpleSchema.debug = true + //SimpleSchema.debug = true }