Added simple feature UI components and insertion dialog
This commit is contained in:
@@ -36,6 +36,9 @@ let creatureSchema = schema({
|
|||||||
level: {type: SimpleSchema.Integer, defaultValue: 0},
|
level: {type: SimpleSchema.Integer, defaultValue: 0},
|
||||||
type: {type: String, defaultValue: "pc", allowedValues: ["pc", "npc", "monster"]},
|
type: {type: String, defaultValue: "pc", allowedValues: ["pc", "npc", "monster"]},
|
||||||
|
|
||||||
|
//computed
|
||||||
|
variables: {type: Object, blackbox: true},
|
||||||
|
|
||||||
//permissions
|
//permissions
|
||||||
owner: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
owner: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
readers: {type: Array, defaultValue: [], index: 1},
|
readers: {type: Array, defaultValue: [], index: 1},
|
||||||
|
|||||||
@@ -94,9 +94,48 @@ function writeCreature(char) {
|
|||||||
writeSkills(char);
|
writeSkills(char);
|
||||||
writeDamageMultipliers(char);
|
writeDamageMultipliers(char);
|
||||||
writeEffects(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
|
* Write all the attributes from the in-memory char object to the Attirbute docs
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {makeChild} from "/imports/api/parenting.js";
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import schema from '/imports/api/schema.js';
|
import schema from '/imports/api/schema.js';
|
||||||
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.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 { canEditCreature } from '/imports/api/creature/creaturePermission.js';
|
||||||
import { recomputeCreatureById } from '/imports/api/creature/creatureComputation.js'
|
import { recomputeCreatureById } from '/imports/api/creature/creatureComputation.js'
|
||||||
import { getHighestOrder } from '/imports/api/order.js';
|
import { getHighestOrder } from '/imports/api/order.js';
|
||||||
@@ -29,12 +30,6 @@ let attributeSchema = schema({
|
|||||||
regEx: /^\w*[a-z]\w*$/i,
|
regEx: /^\w*[a-z]\w*$/i,
|
||||||
index: 1,
|
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: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: [
|
allowedValues: [
|
||||||
@@ -58,6 +53,10 @@ let attributeSchema = schema({
|
|||||||
type: Number,
|
type: Number,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
enabled: {
|
||||||
|
type: Boolean,
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
// The computed modifier, provided the attribute is an ability
|
// The computed modifier, provided the attribute is an ability
|
||||||
mod: {
|
mod: {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
@@ -82,6 +81,8 @@ let attributeSchema = schema({
|
|||||||
type: Number,
|
type: Number,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Attributes need to store their order to keep the sheet consistent
|
||||||
|
order: OrderSchema(),
|
||||||
color: ColorSchema(),
|
color: ColorSchema(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,10 +109,9 @@ const insertAttribute = new ValidatedMethod({
|
|||||||
|
|
||||||
validate: schema({
|
validate: schema({
|
||||||
attribute: {
|
attribute: {
|
||||||
type: Object,
|
type: attributeSchema.omit('order', 'parent'),
|
||||||
blackbox: true,
|
|
||||||
},
|
},
|
||||||
}).validator(),
|
}).validator({ clean: true }),
|
||||||
|
|
||||||
run({attribute}) {
|
run({attribute}) {
|
||||||
const charId = attribute.charId;
|
const charId = attribute.charId;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import schema from '/imports/api/schema.js';
|
import schema from '/imports/api/schema.js';
|
||||||
import ColorSchema from "/imports/api/creature/subSchemas/ColorSchema.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";
|
import {makeParent} from "/imports/api/parenting.js";
|
||||||
|
|
||||||
let Features = new Mongo.Collection("features");
|
let Features = new Mongo.Collection("features");
|
||||||
@@ -13,17 +17,58 @@ let featureSchema = schema({
|
|||||||
used: {type: SimpleSchema.Integer, defaultValue: 0},
|
used: {type: SimpleSchema.Integer, defaultValue: 0},
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: ["manual", "longRest", "shortRest"],
|
allowedValues: ["longRest", "shortRest"],
|
||||||
defaultValue: "manual",
|
optional: true,
|
||||||
},
|
},
|
||||||
enabled: {type: Boolean, defaultValue: true},
|
enabled: {type: Boolean, defaultValue: true},
|
||||||
alwaysEnabled:{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(featureSchema);
|
||||||
Features.attachSchema(ColorSchema);
|
|
||||||
|
|
||||||
//Features.attachBehaviour("softRemovable");
|
//Features.attachBehaviour("softRemovable");
|
||||||
makeParent(Features, ["name", "enabled"]); //parents of effects and attacks
|
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 default Features;
|
||||||
|
export { insertFeature }
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import schema from '/imports/api/schema.js';
|
|
||||||
|
|
||||||
const ColorSchema = ({optional = false} = {}) => ({
|
const ColorSchema = ({optional = false} = {}) => ({
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: "#9E9E9E",
|
defaultValue: "#9E9E9E",
|
||||||
|
|||||||
6
app/imports/api/creature/subSchemas/OrderSchema.js
Normal file
6
app/imports/api/creature/subSchemas/OrderSchema.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const OrderSchema = () => ({
|
||||||
|
type: Number,
|
||||||
|
index: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default OrderSchema;
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue';
|
import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue';
|
||||||
import EffectEdit from '/imports/ui/components/EffectEdit.Story.vue';
|
import EffectEdit from '/imports/ui/components/EffectEdit.Story.vue';
|
||||||
import EffectEditExpansionList from '/imports/ui/components/EffectEditExpansionList.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 HealthBar from '/imports/ui/components/HealthBar.Story.vue';
|
||||||
import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue';
|
import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue';
|
||||||
import IconSearch from '/imports/ui/components/IconSearch.Story.vue';
|
import IconSearch from '/imports/ui/components/IconSearch.Story.vue';
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
DialogStack,
|
DialogStack,
|
||||||
EffectEdit,
|
EffectEdit,
|
||||||
EffectEditExpansionList,
|
EffectEditExpansionList,
|
||||||
|
FeatureCard,
|
||||||
HealthBar,
|
HealthBar,
|
||||||
HitDiceListTile,
|
HitDiceListTile,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
>
|
>
|
||||||
<v-tab>
|
<v-tab>
|
||||||
Stats
|
Stats
|
||||||
|
</v-tab>
|
||||||
|
<v-tab>
|
||||||
|
Features
|
||||||
</v-tab>
|
</v-tab>
|
||||||
<v-tab>
|
<v-tab>
|
||||||
Tree
|
Tree
|
||||||
@@ -25,6 +28,9 @@
|
|||||||
<v-tabs-items v-model="tab">
|
<v-tabs-items v-model="tab">
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<stats-tab :char-id="character._id"/>
|
<stats-tab :char-id="character._id"/>
|
||||||
|
</v-tab-item>
|
||||||
|
<v-tab-item>
|
||||||
|
<features-tab :char-id="character._id"/>
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<character-tree-view :char-id="character._id"/>
|
<character-tree-view :char-id="character._id"/>
|
||||||
@@ -43,6 +49,7 @@
|
|||||||
import { mapMutations } from "vuex";
|
import { mapMutations } from "vuex";
|
||||||
import { theme } from '/imports/ui/theme.js';
|
import { theme } from '/imports/ui/theme.js';
|
||||||
import StatsTab from '/imports/ui/character/StatsTab.vue';
|
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 CharacterTreeView from '/imports/ui/character/CharacterTreeView.vue';
|
||||||
import { recomputeCreature } from '/imports/api/creature/creatureComputation.js'
|
import { recomputeCreature } from '/imports/api/creature/creatureComputation.js'
|
||||||
|
|
||||||
@@ -53,6 +60,7 @@
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
StatsTab,
|
StatsTab,
|
||||||
|
FeaturesTab,
|
||||||
CharacterTreeView,
|
CharacterTreeView,
|
||||||
},
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
|
|||||||
73
app/imports/ui/character/FeaturesTab.vue
Normal file
73
app/imports/ui/character/FeaturesTab.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="features">
|
||||||
|
<column-layout>
|
||||||
|
<div v-for="feature in features" :key="feature._id">
|
||||||
|
<feature-card
|
||||||
|
v-bind="feature"
|
||||||
|
:data-id="feature._id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</column-layout>
|
||||||
|
|
||||||
|
<v-btn fixed fab bottom right
|
||||||
|
color="primary"
|
||||||
|
@click="insertFeature"
|
||||||
|
data-id="insert-feature-fab"
|
||||||
|
>
|
||||||
|
<v-icon>add</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import Features from '/imports/api/creature/properties/Features.js';
|
||||||
|
import { insertFeature } from '/imports/api/creature/properties/Features.js';
|
||||||
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
|
import FeatureCard from '/imports/ui/components/FeatureCard.vue';
|
||||||
|
import { evaluateComputation, evaluateString } from '/imports/ui/utility/evaluate.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
charId: String,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ColumnLayout,
|
||||||
|
FeatureCard,
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
features(){
|
||||||
|
let char = Creatures.findOne(this.charId, {fields: {variables: 1}});
|
||||||
|
if (!char) return [];
|
||||||
|
let vars = char.variables;
|
||||||
|
return Features.find({
|
||||||
|
charId: this.charId,
|
||||||
|
}, {
|
||||||
|
sort: {order: 1},
|
||||||
|
}).map(f => {
|
||||||
|
f.uses = evaluateComputation(f.uses, vars);
|
||||||
|
f.description = evaluateString(f.description, vars);
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
insertFeature(){
|
||||||
|
const charId = this.charId;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'feature-creation-dialog',
|
||||||
|
elementId: 'insert-feature-fab',
|
||||||
|
callback(feature){
|
||||||
|
if (!feature) return;
|
||||||
|
feature.charId = charId;
|
||||||
|
let featureId = insertFeature.call({feature});
|
||||||
|
return featureId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -9,16 +9,15 @@
|
|||||||
@change="change"
|
@change="change"
|
||||||
:debounce-time="0"
|
:debounce-time="0"
|
||||||
/>
|
/>
|
||||||
<div slot="actions">
|
<v-spacer slot="actions"/>
|
||||||
<v-spacer/>
|
<v-btn
|
||||||
<v-btn
|
flat
|
||||||
flat
|
slot="actions"
|
||||||
:disabled="!valid"
|
:disabled="!valid"
|
||||||
@click="$store.dispatch('popDialogStack', attribute)"
|
@click="$store.dispatch('popDialogStack', attribute)"
|
||||||
>
|
>
|
||||||
Insert Attribute
|
Insert Attribute
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
|
||||||
</dialog-base>
|
</dialog-base>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -26,7 +25,6 @@
|
|||||||
import AttributeEdit from '/imports/ui/components/AttributeEdit.vue';
|
import AttributeEdit from '/imports/ui/components/AttributeEdit.vue';
|
||||||
import Attributes from '/imports/api/creature/properties/Attributes.js';
|
import Attributes from '/imports/api/creature/properties/Attributes.js';
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import { Tracker } from 'meteor/tracker';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -49,7 +49,6 @@
|
|||||||
/>
|
/>
|
||||||
<smart-select
|
<smart-select
|
||||||
label="Reset"
|
label="Reset"
|
||||||
append-icon="arrow_drop_down"
|
|
||||||
clearable
|
clearable
|
||||||
:items="resetOptions"
|
:items="resetOptions"
|
||||||
:value="attribute.reset"
|
:value="attribute.reset"
|
||||||
|
|||||||
51
app/imports/ui/components/FeatureCard.Story.vue
Normal file
51
app/imports/ui/components/FeatureCard.Story.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<column-layout>
|
||||||
|
<div
|
||||||
|
v-for="(feature, index) in features"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<feature-card
|
||||||
|
v-bind="feature"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</column-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
|
import FeatureCard from '/imports/ui/components/FeatureCard.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
dontWrap: true,
|
||||||
|
components: {
|
||||||
|
ColumnLayout,
|
||||||
|
FeatureCard,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
name: 'Feature 1',
|
||||||
|
enabled: true,
|
||||||
|
alwaysEnabled: true,
|
||||||
|
description: `
|
||||||
|
|
||||||
|
blah blah, with
|
||||||
|
|
||||||
|
spacing
|
||||||
|
`,
|
||||||
|
color: '#f44336',
|
||||||
|
}, {
|
||||||
|
name: 'Feature 2',
|
||||||
|
enabled: false,
|
||||||
|
alwaysEnabled: false,
|
||||||
|
description: `Short Description`,
|
||||||
|
uses: 5,
|
||||||
|
used: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css' scoped>
|
||||||
|
</style>
|
||||||
60
app/imports/ui/components/FeatureCard.vue
Normal file
60
app/imports/ui/components/FeatureCard.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<toolbar-card :color="color" @click="$emit('click')">
|
||||||
|
<span slot="toolbar">
|
||||||
|
{{name}}
|
||||||
|
</span>
|
||||||
|
<v-spacer slot="toolbar"/>
|
||||||
|
<v-checkbox
|
||||||
|
hide-details
|
||||||
|
class="shrink"
|
||||||
|
v-if="!alwaysEnabled"
|
||||||
|
:value="enabled"
|
||||||
|
@change="enabled => $emit('change', {enabled})"
|
||||||
|
slot="toolbar"
|
||||||
|
/>
|
||||||
|
<v-card-text>
|
||||||
|
{{description}}
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions v-if="uses">
|
||||||
|
<v-spacer/>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
:disabled="uses - used <= 0"
|
||||||
|
@click="$emit('used')"
|
||||||
|
>
|
||||||
|
Use
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
:disabled="!used"
|
||||||
|
@click="$emit('reset')"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</toolbar-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
charId: String,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
uses: Number,
|
||||||
|
used: Number,
|
||||||
|
reset: String,
|
||||||
|
color: String,
|
||||||
|
enabled: Boolean,
|
||||||
|
alwaysEnabled: Boolean,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ToolbarCard,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
78
app/imports/ui/components/FeatureCreationDialog.vue
Normal file
78
app/imports/ui/components/FeatureCreationDialog.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<div slot="toolbar">
|
||||||
|
New Feature
|
||||||
|
</div>
|
||||||
|
<feature-edit
|
||||||
|
:feature="feature"
|
||||||
|
:errors="errors"
|
||||||
|
@change="change"
|
||||||
|
:debounce-time="0"
|
||||||
|
/>
|
||||||
|
<v-spacer slot="actions"/>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
slot="actions"
|
||||||
|
:disabled="!valid"
|
||||||
|
@click="$store.dispatch('popDialogStack', feature)"
|
||||||
|
>
|
||||||
|
Insert Feature
|
||||||
|
</v-btn>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FeatureEdit from '/imports/ui/components/FeatureEdit.vue';
|
||||||
|
import Features from '/imports/api/creature/properties/Features.js';
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FeatureEdit,
|
||||||
|
DialogBase,
|
||||||
|
},
|
||||||
|
data(){ return {
|
||||||
|
feature: {
|
||||||
|
name: 'New Feature',
|
||||||
|
description: null,
|
||||||
|
uses: null,
|
||||||
|
used: 0,
|
||||||
|
reset: null,
|
||||||
|
enabled: true,
|
||||||
|
alwaysEnabled: true,
|
||||||
|
color: '#9E9E9E',
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
change(update, ack){
|
||||||
|
for (key in update){
|
||||||
|
this.feature[key] = update[key];
|
||||||
|
}
|
||||||
|
if (ack) ack();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
this.validationContext = Features.simpleSchema().newContext();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
errors(){
|
||||||
|
this.valid = true;
|
||||||
|
let cleanAtt = this.validationContext.clean(this.feature)
|
||||||
|
this.validationContext.validate(cleanAtt, {keys: [
|
||||||
|
'name', 'description', 'uses', 'used', 'reset', 'enabled',
|
||||||
|
'alwaysEnabled', 'color',
|
||||||
|
]});
|
||||||
|
let errors = {};
|
||||||
|
this.validationContext.validationErrors().forEach(error => {
|
||||||
|
if (this.valid) this.valid = false;
|
||||||
|
errors[error.name] = Features.simpleSchema().messageForError(error);
|
||||||
|
});
|
||||||
|
return errors;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
113
app/imports/ui/components/FeatureEdit.vue
Normal file
113
app/imports/ui/components/FeatureEdit.vue
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div>
|
||||||
|
<text-field
|
||||||
|
label="Name"
|
||||||
|
:value="feature.name"
|
||||||
|
@change="(name, ack) => $emit('change', {name}, ack)"
|
||||||
|
:error-messages="errors.name"
|
||||||
|
:debounce-time="debounceTime"
|
||||||
|
/>
|
||||||
|
<text-field
|
||||||
|
label="Used"
|
||||||
|
type="number"
|
||||||
|
:value="feature.used"
|
||||||
|
@change="(used, ack) => $emit('change', {used}, ack)"
|
||||||
|
:error-messages="errors.used"
|
||||||
|
:debounce-time="debounceTime"
|
||||||
|
/>
|
||||||
|
<text-field
|
||||||
|
label="Uses"
|
||||||
|
:value="feature.uses"
|
||||||
|
@change="(uses, ack) => $emit('change', {uses}, ack)"
|
||||||
|
:error-messages="errors.uses"
|
||||||
|
:debounce-time="debounceTime"
|
||||||
|
/>
|
||||||
|
<smart-select
|
||||||
|
label="Reset"
|
||||||
|
clearable
|
||||||
|
:items="resetOptions"
|
||||||
|
:value="feature.reset"
|
||||||
|
:error-messages="errors.reset"
|
||||||
|
:menu-props="{auto: true, lazy: true}"
|
||||||
|
@change="(reset, ack) => $emit('change', {reset}, ack)"
|
||||||
|
:debounce-time="debounceTime"
|
||||||
|
/>
|
||||||
|
<smart-select
|
||||||
|
label="Enabled"
|
||||||
|
:items="enabledOptions"
|
||||||
|
:value="enabledStatus"
|
||||||
|
:error-messages="errors.enabled || errors.alwaysEnabled"
|
||||||
|
:menu-props="{auto: true, lazy: true}"
|
||||||
|
@change="changeEnabled"
|
||||||
|
:debounce-time="debounceTime"
|
||||||
|
/>
|
||||||
|
<text-area
|
||||||
|
label="Description"
|
||||||
|
:value="feature.description"
|
||||||
|
:error-messages="errors.description"
|
||||||
|
@change="(description, ack) => $emit('change', {description}, ack)"
|
||||||
|
:debounce-time="debounceTime"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
feature: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
debounceTime: Number,
|
||||||
|
},
|
||||||
|
data(){ return{
|
||||||
|
resetOptions: [
|
||||||
|
{
|
||||||
|
text: 'Short rest',
|
||||||
|
value: 'shortRest',
|
||||||
|
}, {
|
||||||
|
text: 'Long rest',
|
||||||
|
value: 'longRest',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
enabledOptions: [
|
||||||
|
{
|
||||||
|
text: 'Always enabled',
|
||||||
|
value: 'always',
|
||||||
|
}, {
|
||||||
|
text: 'Enabled',
|
||||||
|
value: 'enabled',
|
||||||
|
}, {
|
||||||
|
text: 'Disabled',
|
||||||
|
value: 'disabled',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}},
|
||||||
|
computed: {
|
||||||
|
enabledStatus(){
|
||||||
|
if (!this.feature) return;
|
||||||
|
if (this.feature.alwaysEnabled) return 'always';
|
||||||
|
if (this.feature.enabled) return 'enabled';
|
||||||
|
return 'disabled';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeEnabled(value, ack){
|
||||||
|
if (value === 'always'){
|
||||||
|
this.$emit('change', {enabled: true, alwaysEnabled: true}, ack);
|
||||||
|
} else if (value === 'enabled'){
|
||||||
|
this.$emit('change', {enabled: true, alwaysEnabled: false}, ack);
|
||||||
|
} else if (value === 'disabled'){
|
||||||
|
this.$emit('change', {enabled: false, alwaysEnabled: false}, ack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
38
app/imports/ui/components/ToolbarCard.vue
Normal file
38
app/imports/ui/components/ToolbarCard.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-card>
|
||||||
|
<v-toolbar
|
||||||
|
flat
|
||||||
|
style="transform: none;"
|
||||||
|
@click="$emit('click')"
|
||||||
|
:color="color"
|
||||||
|
:dark="isDark"
|
||||||
|
>
|
||||||
|
<slot name="toolbar"/>
|
||||||
|
</v-toolbar>
|
||||||
|
<div>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default(){
|
||||||
|
return this.$vuetify.theme.secondary;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isDark(){
|
||||||
|
return isDarkColor(this.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:error-messages="errors"
|
:error-messages="errors"
|
||||||
:value="safeValue"
|
:value="safeValue"
|
||||||
|
:auto-grow="autoGrow"
|
||||||
@input="input"
|
@input="input"
|
||||||
@focus="focused = true"
|
@focus="focused = true"
|
||||||
@blur="focused = false"
|
@blur="focused = false"
|
||||||
@@ -15,5 +16,11 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [SmartInput],
|
mixins: [SmartInput],
|
||||||
|
props: {
|
||||||
|
autoGrow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-card-text id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
|
<v-card-text id="base-dialog-body" v-scroll:#base-dialog-body="onScroll">
|
||||||
<v-tabs-items :value="isEditing ? 1 : 0">
|
<v-tabs-items :value="isEditing ? 1 : 0" touchless>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<slot/>
|
<slot/>
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import AttributeDialog from '/imports/ui/components/AttributeDialog.vue';
|
import AttributeDialog from '/imports/ui/components/AttributeDialog.vue';
|
||||||
import AttributeDialogContainer from '/imports/ui/components/AttributeDialogContainer.vue';
|
import AttributeDialogContainer from '/imports/ui/components/AttributeDialogContainer.vue';
|
||||||
import AttributeCreationDialog from '/imports/ui/components/AttributeCreationDialog.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';
|
import SkillDialogContainer from '/imports/ui/components/SkillDialogContainer.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
AttributeDialog,
|
AttributeDialog,
|
||||||
AttributeDialogContainer,
|
AttributeDialogContainer,
|
||||||
AttributeCreationDialog,
|
AttributeCreationDialog,
|
||||||
|
FeatureCreationDialog,
|
||||||
SkillDialogContainer,
|
SkillDialogContainer,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -217,7 +217,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 3;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
.dialog-sizer {
|
.dialog-sizer {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
29
app/imports/ui/utility/evaluate.js
Normal file
29
app/imports/ui/utility/evaluate.js
Normal file
@@ -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 };
|
||||||
62
app/package-lock.json
generated
62
app/package-lock.json
generated
@@ -1406,37 +1406,6 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "~2.0.1",
|
"inherits": "~2.0.1",
|
||||||
"readable-stream": "^2.0.2"
|
"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": {
|
"stream-http": {
|
||||||
@@ -1449,37 +1418,6 @@
|
|||||||
"readable-stream": "^2.3.3",
|
"readable-stream": "^2.3.3",
|
||||||
"to-arraybuffer": "^1.0.0",
|
"to-arraybuffer": "^1.0.0",
|
||||||
"xtend": "^4.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": {
|
"string_decoder": {
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import schema from '/imports/api/schema.js';
|
import schema from '/imports/api/schema.js';
|
||||||
|
|
||||||
if (Meteor.isDevelopment){
|
if (Meteor.isDevelopment){
|
||||||
SimpleSchema.debug = true
|
//SimpleSchema.debug = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user