Merge branch 'version-2' into version-2-tabletop

This commit is contained in:
Stefan Zermatten
2022-10-22 19:29:31 +02:00
437 changed files with 18762 additions and 8849 deletions

View File

@@ -38,9 +38,7 @@
:disabled="model.insufficientResources || !context.editPermission || !!targetingError"
@click.stop="doAction"
>
<property-icon
:model="model"
/>
<property-icon :model="model" />
</v-btn>
</div>
<div
@@ -50,9 +48,7 @@
@mouseleave="hovering = false"
@click="$emit('click')"
>
<div
class="action-title my-1"
>
<div class="action-title my-1">
{{ model.name || propertyName }}
</div>
<div class="action-sub-title layout align-center">
@@ -97,10 +93,15 @@
/>
</template>
<template v-if="model.summary">
<markdown-text
:markdown="model.summary.value || model.summary.text"
/>
<markdown-text :markdown="model.summary.value || model.summary.text" />
</template>
<v-divider v-if="children && children.length" />
<tree-node-list
v-if="children && children.length"
start-expanded
:children="children"
@selected="e => $emit('sub-click', e)"
/>
</div>
<card-highlight :active="hovering" />
</v-card>
@@ -115,8 +116,12 @@ import ItemConsumedView from '/imports/ui/properties/components/actions/ItemCons
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { some } from 'lodash';
export default {
components: {
@@ -125,7 +130,8 @@ export default {
MarkdownText,
PropertyIcon,
RollPopup,
CardHighlight
CardHighlight,
TreeNodeList,
},
inject: {
context: {
@@ -147,20 +153,22 @@ export default {
default: undefined,
},
},
data(){return {
activated: undefined,
doActionLoading: false,
hovering: false,
}},
data() {
return {
activated: undefined,
doActionLoading: false,
hovering: false,
}
},
computed: {
rollBonus(){
rollBonus() {
if (!this.model.attackRoll) return;
return numberToSignedString(this.model.attackRoll.value);
},
rollBonusTooLong(){
rollBonusTooLong() {
return this.rollBonus && this.rollBonus.length > 3;
},
propertyName(){
propertyName() {
return getPropertyName(this.model.type);
},
cardClasses() {
@@ -185,12 +193,41 @@ export default {
}
return undefined;
}
},
},
meteor: {
children() {
const indicesOfTerminatingProps = [];
const decendants = CreatureProperties.find({
'ancestors.id': this.model._id,
'removed': { $ne: true },
}, {
sort: {order: 1}
}).map(prop => {
// Get all the props we don't want to show the decendants of and
// where they might appear in the ancestor list
if (prop.type === 'buff' || prop.type === 'folder') {
indicesOfTerminatingProps.push({
id: prop._id,
ancestorIndex: prop.ancestors.length,
});
}
return prop;
}).filter(prop => {
// Filter out folders entirely
if (prop.type === 'folder') return false;
// Filter out decendants of terminating props
return !some(indicesOfTerminatingProps, buffIndex => {
return prop.ancestors[buffIndex.ancestorIndex]?.id === buffIndex.id;
});
});
return nodeArrayToTree(decendants);
},
},
methods: {
click(e){
this.$emit('click', e);
},
doAction({advantage}){
click(e) {
this.$emit('click', e);
},
doAction({ advantage }) {
this.doActionLoading = true;
this.shwing();
doAction.call({
@@ -201,13 +238,13 @@ export default {
}
}, error => {
this.doActionLoading = false;
if (error){
if (error) {
console.error(error);
snackbar({text: error.reason});
snackbar({ text: error.reason });
}
});
},
shwing(){
shwing() {
this.activated = true;
setTimeout(() => {
this.activated = undefined;
@@ -222,9 +259,11 @@ export default {
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
transform 0.075s ease;
}
.action-card.active {
transform: scale(0.92);
}
.action-title {
font-size: 16px;
font-weight: 400;
@@ -235,9 +274,10 @@ export default {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: .3s cubic-bezier(.25,.8,.5,1);
transition: .3s cubic-bezier(.25, .8, .5, 1);
width: 100%;
}
.action-sub-title {
color: #9e9e9e;
flex-grow: 0;
@@ -249,15 +289,19 @@ export default {
text-overflow: ellipsis;
width: 100%;
}
.action-child {
height: 32px;
}
.theme--light.muted-text {
color: rgba(0,0,0,.3) !important;
color: rgba(0, 0, 0, .3) !important;
}
.theme--dark.muted-text {
color: hsla(0,0%,100%,.3) !important;
color: hsla(0, 0%, 100%, .3) !important;
}
.action-card {
transition: transform 0.15s cubic;
}
@@ -265,12 +309,14 @@ export default {
<style lang="css">
.action-card.theme--light.muted-text .v-icon {
color: rgba(0,0,0,.3) !important;
color: rgba(0, 0, 0, .3) !important;
}
.action-card.theme--dark.muted-text .v-icon {
color: hsla(0,0%,100%,.3) !important;
color: hsla(0, 0%, 100%, .3) !important;
}
.action-card .property-description > p:last-of-type {
.action-card .property-description>p:last-of-type {
margin-bottom: 0;
}
</style>

View File

@@ -55,7 +55,7 @@
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
@@ -66,23 +66,25 @@ export default {
default: {},
},
},
props: {
model: {type: Object, required: true},
},
data(){return {
checkLoading: false,
}},
computed: {
hasClickListener(){
props: {
model: { type: Object, required: true },
},
data() {
return {
checkLoading: false,
}
},
computed: {
hasClickListener() {
return this.$listeners && this.$listeners.click
},
},
methods: {
numberToSignedString,
click(e){
this.$emit('click', e);
},
check({advantage}){
},
},
methods: {
numberToSignedString,
click(e) {
this.$emit('click', e);
},
check({ advantage }) {
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
@@ -91,15 +93,15 @@ export default {
},
}, error => {
this.checkLoading = false;
if (error){
if (error) {
console.error(error);
snackbar({text: error.reason});
snackbar({ text: error.reason });
}
});
},
},
},
meteor: {
swapScoresAndMods(){
swapScoresAndMods() {
let user = Meteor.user();
return user &&
user.preferences &&
@@ -110,26 +112,32 @@ export default {
</script>
<style lang="css" scoped>
.ability-list-tile {
background: inherit;
}
.ability-list-tile >>> .v-list__tile {
height: 88px;
}
.ability-list-tile >>> .v-list__tile__action--stack {
justify-content: center;
}
.value {
font-weight: 600;
font-size: 24px !important;
color: rgba(0, 0, 0, 0.54);
}
.theme--dark .value {
color: rgba(255, 255, 255, 0.54);
}
.mod, .value {
text-align: center;
width: 100%;
min-width: 42px;
}
.ability-list-tile {
background: inherit;
}
.ability-list-tile>>>.v-list__tile {
height: 88px;
}
.ability-list-tile>>>.v-list__tile__action--stack {
justify-content: center;
}
.value {
font-weight: 600;
font-size: 24px !important;
color: rgba(0, 0, 0, 0.54);
}
.theme--dark .value {
color: rgba(255, 255, 255, 0.54);
}
.mod,
.value {
text-align: center;
width: 100%;
min-width: 42px;
}
</style>

View File

@@ -27,9 +27,9 @@
<div class="text-body-1 mb-1">
{{ displayedText }}
</div>
<div v-if="!hideBreadcrumbs && model.ancestors">
<div v-if="!hideBreadcrumbs && ancestors">
<breadcrumbs
:model="model"
:model="{...model, ancestors}"
class="text-caption"
no-links
no-icons
@@ -41,94 +41,101 @@
</template>
<script lang="js">
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
import Breadcrumbs from '/imports/ui/creature/creatureProperties/Breadcrumbs.vue';
import { isFinite } from 'lodash';
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
import Breadcrumbs from '/imports/ui/creature/creatureProperties/Breadcrumbs.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { isFinite } from 'lodash';
export default {
components: {
Breadcrumbs,
export default {
components: {
Breadcrumbs,
},
props: {
hideBreadcrumbs: Boolean,
model: {
type: Object,
required: true,
},
props: {
hideBreadcrumbs: Boolean,
model: {
type: Object,
required: true,
},
},
computed: {
hasClickListener(){
return this.$listeners && this.$listeners.click
},
computed: {
hasClickListener(){
return this.$listeners && this.$listeners.click
},
displayedText(){
if (this.model.operation === 'conditional'){
return this.model.text || this.model.name || this.operation
} else {
return this.model.name || this.operation
}
},
resolvedValue(){
let amount = this.model.amount;
if (!amount) return;
return amount.value !== undefined ? amount.value : amount.calculation;
},
effectIcon(){
let value = this.resolvedValue;
return getEffectIcon(this.model.operation, value);
},
operation(){
switch(this.model.operation) {
case 'base': return 'Base value';
case 'add': return 'Add';
case 'mul': return 'Multiply';
case 'min': return 'Minimum';
case 'max': return 'Maximum';
case 'advantage': return 'Advantage';
case 'disadvantage': return 'Disadvantage';
case 'passiveAdd': return 'Passive bonus';
case 'fail': return 'Always fail';
case 'conditional': return 'Conditional benefit' ;
default: return '';
}
},
showValue(){
switch(this.model.operation) {
case 'base': return true;
case 'add': return true;
case 'mul': return true;
case 'min': return true;
case 'max': return true;
case 'advantage': return false;
case 'disadvantage': return false;
case 'passiveAdd': return true;
case 'fail': return false;
case 'conditional': return false;
default: return false;
}
},
displayedValue(){
let value = this.resolvedValue;
switch(this.model.operation) {
case 'base': return value;
case 'add': return isFinite(value) ? Math.abs(value) : value;
case 'mul': return value;
case 'min': return value;
case 'max': return value;
case 'advantage': return;
case 'disadvantage': return;
case 'passiveAdd': return isFinite(value) ? Math.abs(value) : value;
case 'fail': return;
case 'conditional': return undefined;
default: return undefined;
}
displayedText(){
if (this.model.operation === 'conditional'){
return this.model.text || this.model.name || this.operation
} else {
return this.model.name || this.operation
}
},
methods: {
click(e){
this.$emit('click', e);
},
resolvedValue() {
let amount = this.model.amount;
if (!amount) return;
return amount.value !== undefined ? amount.value : amount.calculation;
},
};
effectIcon(){
let value = this.resolvedValue;
return getEffectIcon(this.model.operation, value);
},
operation(){
switch(this.model.operation) {
case 'base': return 'Base value';
case 'add': return 'Add';
case 'mul': return 'Multiply';
case 'min': return 'Minimum';
case 'max': return 'Maximum';
case 'advantage': return 'Advantage';
case 'disadvantage': return 'Disadvantage';
case 'passiveAdd': return 'Passive bonus';
case 'fail': return 'Always fail';
case 'conditional': return 'Conditional benefit' ;
default: return '';
}
},
showValue(){
switch(this.model.operation) {
case 'base': return true;
case 'add': return true;
case 'mul': return true;
case 'min': return true;
case 'max': return true;
case 'advantage': return false;
case 'disadvantage': return false;
case 'passiveAdd': return true;
case 'fail': return false;
case 'conditional': return false;
default: return false;
}
},
displayedValue(){
let value = this.resolvedValue;
switch(this.model.operation) {
case 'base': return value;
case 'add': return isFinite(value) ? Math.abs(value) : value;
case 'mul': return value;
case 'min': return value;
case 'max': return value;
case 'advantage': return;
case 'disadvantage': return;
case 'passiveAdd': return isFinite(value) ? Math.abs(value) : value;
case 'fail': return;
case 'conditional': return undefined;
default: return undefined;
}
}
},
meteor: {
ancestors() {
const prop = CreatureProperties.findOne(this.model._id);
return prop && prop.ancestors || [];
}
},
methods: {
click(e){
this.$emit('click', e);
},
},
};
</script>
<style lang="css" scoped>

View File

@@ -41,7 +41,7 @@
style="height: 100%; transform-origin: left; transition: all 0.5s ease;"
:style="{
backgroundColor: barColor,
transform: `scaleX(${value / maxValue})`,
transform: `scaleX(${fillFraction})`,
}"
/>
<div
@@ -92,87 +92,102 @@ import IncrementMenu from '/imports/ui/components/IncrementMenu.vue';
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
import chroma from 'chroma-js';
export default {
components: {
IncrementMenu
},
inject: {
theme: {
default: {
isDark: false,
},
export default {
components: {
IncrementMenu
},
inject: {
theme: {
default: {
isDark: false,
},
},
props: {
value: Number,
maxValue: Number,
name: String,
color: {
type: String,
default() {
return this.$vuetify.theme.currentTheme.primary
},
},
props: {
value: {
type: Number,
default: undefined,
},
maxValue: {
type: Number,
default: undefined,
},
name: {
type: String,
default: undefined,
},
color: {
type: String,
default() {
return this.$vuetify.theme.currentTheme.primary
},
midColor: {
type: String,
default: undefined,
},
lowColor: {
type: String,
default: undefined,
},
_id: String,
},
data() {
return {
editing: false,
hover: false,
};
},
computed: {
barColor() {
const fraction = this.value / this.maxValue;
if (!Number.isFinite(fraction)) return this.color;
if (fraction > 0.5){
return this.color;
} else if (this.midColor && this.lowColor) {
return chroma.mix(this.lowColor, this.midColor, fraction * 2).hex();
} else if (this.midColor){
return this.midColor;
}
},
midColor: {
type: String,
default: undefined,
},
lowColor: {
type: String,
default: undefined,
},
_id: String,
},
data() {
return {
editing: false,
hover: false,
};
},
computed: {
fillFraction() {
let fraction = this.value / this.maxValue;
if (fraction < 0) fraction = 0;
if (fraction > 1) fraction = 1;
return fraction;
},
barColor() {
const fraction = this.value / this.maxValue;
if (!Number.isFinite(fraction)) return this.color;
if (fraction > 0.5) {
return this.color;
},
barBackgroundColor(){
return chroma(this.barColor)
} else if (this.midColor && this.lowColor) {
return chroma.mix(this.lowColor, this.midColor, fraction * 2).hex();
} else if (this.midColor) {
return this.midColor;
}
return this.color;
},
barBackgroundColor() {
return chroma(this.barColor)
.darken(1.5)
.desaturate(1.5)
.hex();
},
isTextLight(){
return isDarkColor(this.barBackgroundColor);
/* Change color at the halfway mark
const fraction = this.value / this.maxValue;
if (fraction >= 0.5){
return isDarkColor(this.barColor);
} else {
return isDarkColor(this.barBackgroundColor);
}
*/
}
},
methods: {
edit() {
this.editing = true;
},
cancelEdit() {
this.editing = false;
},
changeIncrementMenu(e){
this.$emit('change', e);
this.editing = false;
isTextLight() {
return isDarkColor(this.barBackgroundColor);
/* Change color at the halfway mark
const fraction = this.value / this.maxValue;
if (fraction >= 0.5){
return isDarkColor(this.barColor);
} else {
return isDarkColor(this.barBackgroundColor);
}
},
};
*/
}
},
methods: {
edit() {
this.editing = true;
},
cancelEdit() {
this.editing = false;
},
changeIncrementMenu(e) {
this.$emit('change', e);
this.editing = false;
}
},
};
</script>
<style>
@@ -187,70 +202,85 @@ import chroma from 'chroma-js';
</style>
<style scoped>
.health-bar {
background: inherit;
}
.name {
text-align: center;
cursor: pointer;
min-width: 150px;
flex-basis: 150px;
flex-grow: 1;
flex-shrink: 1;
}
.name:hover {
font-weight: 500;
}
.bar {
transition: box-shadow 0.2s;
}
.bar:hover {
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12) !important;
}
.hover {
background: #f5f5f5 !important;
}
.theme--dark .hover {
background: #515151 !important;
}
.filled.theme--light {
background: #fff !important;
}
.filled.theme--dark {
background: #424242 !important;
}
.background-transition-enter-active,
.background-transition-leave-active {
transition: all 0.2s;
}
.background-transition-enter,
.background-transition-leave-to {
opacity: 0;
}
.transition-enter-active {
transition: all 0.2s;
}
.transition-leave-active {
transition: all 0.3s;
}
.transition-enter-to,
.transition-leave {
opacity: 1;
transform: scaleY(1) !important;
}
.transition-enter,
.transition-leave-to {
opacity: 0;
transform: scaleY(0) !important;
}
.page-tint {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.15);
z-index: 6;
}
.health-bar {
background: inherit;
}
.name {
text-align: center;
cursor: pointer;
min-width: 150px;
flex-basis: 150px;
flex-grow: 1;
flex-shrink: 1;
}
.name:hover {
font-weight: 500;
}
.bar {
transition: box-shadow 0.2s;
}
.bar:hover {
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12) !important;
}
.hover {
background: #f5f5f5 !important;
}
.theme--dark .hover {
background: #515151 !important;
}
.filled.theme--light {
background: #fff !important;
}
.filled.theme--dark {
background: #424242 !important;
}
.background-transition-enter-active,
.background-transition-leave-active {
transition: all 0.2s;
}
.background-transition-enter,
.background-transition-leave-to {
opacity: 0;
}
.transition-enter-active {
transition: all 0.2s;
}
.transition-leave-active {
transition: all 0.3s;
}
.transition-enter-to,
.transition-leave {
opacity: 1;
transform: scaleY(1) !important;
}
.transition-enter,
.transition-leave-to {
opacity: 0;
transform: scaleY(0) !important;
}
.page-tint {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.15);
z-index: 6;
}
</style>

View File

@@ -17,17 +17,17 @@
</template>
<script lang="js">
import HealthBar from '/imports/ui/properties/components/attributes/HealthBar.vue';
import HealthBar from '/imports/ui/properties/components/attributes/HealthBar.vue';
export default {
components: {
HealthBar,
},
props: {
attributes: {
type: Array,
required: true
},
},
}
export default {
components: {
HealthBar,
},
props: {
attributes: {
type: Array,
required: true
},
},
}
</script>

View File

@@ -12,60 +12,60 @@
</template>
<script lang="js">
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import HealthBarCard from '/imports/ui/properties/components/attributes/HealthBarCard.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import HealthBarCard from '/imports/ui/properties/components/attributes/HealthBarCard.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
export default {
components: {
HealthBarCard,
},
props: {
creatureId: {
type: String,
required: true
},
},
meteor: {
creature(){
return Creatures.findOne(this.creatureId, {fields: {settings: 1}});
},
attributes(){
let creature = this.creature;
if (!creature) return;
let filter = {
'ancestors.id': creature._id,
type: 'attribute',
attributeType: 'healthBar',
removed: {$ne: true},
inactive: {$ne: true},
overridden: {$ne: true},
};
if (creature.settings.hideUnusedStats){
filter.hide = {$ne: true};
}
return CreatureProperties.find(filter, {
sort: {order: 1}
});
},
},
methods: {
healthBarClicked({_id}){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: {_id},
});
},
healthBarChanged({_id, change}){
damageProperty.call({
_id,
operation: change.type,
value: change.value
});
},
},
};
export default {
components: {
HealthBarCard,
},
props: {
creatureId: {
type: String,
required: true
},
},
meteor: {
creature() {
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
},
attributes() {
let creature = this.creature;
if (!creature) return;
let filter = {
'ancestors.id': creature._id,
type: 'attribute',
attributeType: 'healthBar',
removed: { $ne: true },
inactive: { $ne: true },
overridden: { $ne: true },
};
if (creature.settings.hideUnusedStats) {
filter.hide = { $ne: true };
}
return CreatureProperties.find(filter, {
sort: { order: 1 }
});
},
},
methods: {
healthBarClicked({ _id }) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: { _id },
});
},
healthBarChanged({ _id, change }) {
damageProperty.call({
_id,
operation: change.type,
value: change.value
});
},
},
};
</script>

View File

@@ -31,9 +31,7 @@
</v-btn>
</v-layout>
<v-layout
align-end
>
<v-layout align-end>
<div class="text-h4">
{{ model.value }}
</div>
@@ -63,60 +61,71 @@ export default {
inject: {
context: { default: {} }
},
props: {
props: {
model: {
type: Object,
required: true,
}
},
data(){ return{
hover: false,
}},
},
data() {
return {
hover: false,
}
},
computed: {
signedConMod(){
signedConMod() {
return numberToSignedString(this.model.constitutionMod);
},
},
methods: {
click(e){
this.$emit('click', e);
},
increment(value){
this.$emit('change', {type: 'increment', value})
},
},
methods: {
click(e) {
this.$emit('click', e);
},
increment(value) {
this.$emit('change', { type: 'increment', value })
},
},
};
</script>
<style lang="css" scoped>
.hit-dice-list-tile {
background: inherit;
}
.hit-dice-list-tile >>> .v-list__tile {
height: 88px;
}
.left {
height: 100%;
}
.buttons {
height: 100%;
}
.buttons > .v-btn {
margin: 0;
}
.hit-dice-list-tile.hover {
background: #f5f5f5 !important;
}
.theme--dark .hit-dice-list-tile.hover {
background: #515151 !important;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0,0,0,.54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
.hit-dice-list-tile {
background: inherit;
}
.hit-dice-list-tile>>>.v-list__tile {
height: 88px;
}
.left {
height: 100%;
}
.buttons {
height: 100%;
}
.buttons>.v-btn {
margin: 0;
}
.hit-dice-list-tile.hover {
background: #f5f5f5 !important;
}
.theme--dark .hit-dice-list-tile.hover {
background: #515151 !important;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0, 0, 0, .54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
</style>

View File

@@ -8,7 +8,7 @@
<v-btn
icon
small
:disabled="model.value >= model.total || context.editPermission === false"
:disabled="(model.value >= model.total && !model.ignoreUpperLimit) || context.editPermission === false"
@click="increment(1)"
>
<v-icon>mdi-chevron-up</v-icon>
@@ -16,19 +16,20 @@
<v-btn
icon
small
:disabled="model.value <= 0 || context.editPermission === false"
:disabled="(model.value <= 0 && !model.ignoreLowerLimit) || context.editPermission === false"
@click="increment(-1)"
>
<v-icon>mdi-chevron-down</v-icon>
</v-btn>
</div>
<div
class="layout align-center value pl-2 pr-3"
>
<div class="layout align-center value pl-2 pr-3">
<div class="text-h4">
{{ model.value }}
</div>
<div class="text-h6 ml-2 max-value">
<div
v-if="model.total !== 0"
class="text-h6 ml-2 max-value"
>
/{{ model.total }}
</div>
</div>
@@ -50,55 +51,64 @@
<script lang="js">
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
export default {
components: {
CardHighlight,
export default {
components: {
CardHighlight,
},
inject: {
context: { default: {} }
},
props: {
model: {
type: Object,
required: true,
}
},
data() {
return {
hover: false,
}
},
methods: {
click(e) {
this.$emit('click', e);
},
inject: {
context: { default: {} }
increment(value) {
this.$emit('change', { type: 'increment', value })
},
props: {
model: {
type: Object,
required: true,
}
},
data(){ return{
hover: false,
}},
methods: {
click(e){
this.$emit('click', e);
},
increment(value){
this.$emit('change', {type: 'increment', value})
},
},
};
},
};
</script>
<style lang="css" scoped>
.resource-card {
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.resource-card > div {
padding-top: 16px;
padding-bottom: 16px;
}
.buttons, .value {
flex-shrink: 0;
flex-grow: 0;
}
.buttons > .v-btn {
margin: 0;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0,0,0,.54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
.resource-card {
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.resource-card>div {
padding-top: 16px;
padding-bottom: 16px;
}
.buttons,
.value {
flex-shrink: 0;
flex-grow: 0;
}
.buttons>.v-btn {
margin: 0;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0, 0, 0, .54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
</style>

View File

@@ -7,9 +7,7 @@
v-on="hasClickListener ? {click} : {}"
>
<v-list-item-content>
<v-list-item-title
v-if="Number.isFinite(model.total)"
>
<v-list-item-title v-if="Number.isFinite(model.total)">
<div
v-if="model.total > 4"
class="layout value"
@@ -57,50 +55,56 @@
<script lang="js">
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
export default {
props: {
model: {
props: {
model: {
type: Object,
required: true,
},
dark: Boolean,
hideCastButton: Boolean,
disabled: Boolean,
},
computed: {
hasClickListener(){
},
computed: {
hasClickListener() {
return this.$listeners && !!this.$listeners.click;
},
},
methods: {
signed: numberToSignedString,
click(e){
this.$emit('click', e);
},
},
},
methods: {
signed: numberToSignedString,
click(e) {
this.$emit('click', e);
},
},
};
</script>
<style lang="css" scoped>
.spell-slot-list-tile {
background: inherit;
}
.v-list__tile__action {
width: 112px;
flex-shrink: 0;
}
.spell-slot-list-tile.hover {
background: #f5f5f5 !important;
}
.theme--dark .spell-slot-list-tile.hover {
background: #515151 !important;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0,0,0,.54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
.spell-slot-list-tile {
background: inherit;
}
.v-list__tile__action {
width: 112px;
flex-shrink: 0;
}
.spell-slot-list-tile.hover {
background: #f5f5f5 !important;
}
.theme--dark .spell-slot-list-tile.hover {
background: #515151 !important;
}
.content {
cursor: pointer;
}
.max-value {
color: rgba(0, 0, 0, .54);
}
.theme--dark .max-value {
color: rgba(255, 255, 255, 0.54);
}
</style>

View File

@@ -10,7 +10,7 @@
</v-toolbar-title>
<v-spacer />
</template>
<v-card-text v-if="model.summary">
<v-card-text v-if="summaryText">
<property-description
text
:model="model.summary"
@@ -20,21 +20,31 @@
</template>
<script lang="js">
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
export default {
components: {
ToolbarCard,
PropertyDescription,
},
props: {
model: {
type: Object,
required: true,
},
},
};
export default {
components: {
ToolbarCard,
PropertyDescription,
},
props: {
model: {
type: Object,
required: true,
},
},
computed: {
summaryText() {
if (!this.model || !this.model.summary) return;
if (typeof this.model.summary.value === 'string') {
return this.model.summary.value;
} else {
return this.model.summary.text
}
},
}
};
</script>
<style lang="css" scoped>

View File

@@ -30,9 +30,7 @@
>
$vuetify.icons.two_coins
</v-icon>
<coin-value
:value="value"
/>
<coin-value :value="value" />
</v-toolbar-title>
</template>
<v-card-text class="px-0">
@@ -52,57 +50,57 @@ import CoinValue from '/imports/ui/components/CoinValue.vue';
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
export default {
components: {
ToolbarCard,
components: {
ToolbarCard,
ItemList,
CoinValue,
},
props: {
model: {
},
props: {
model: {
type: Object,
required: true,
},
},
},
computed: {
weight(){
weight() {
const contentWeight = this.model.contentsWeightless ?
0 :
this.model.contentsWeight || 0;
const ownWeight = this.model.weight || 0;
return stripFloatingPointOddities(contentWeight + ownWeight);
},
value(){
value() {
const contentValue = this.model.contentsValue || 0;
const ownValue = this.model.value || 0;
return contentValue + ownValue;
}
},
methods: {
clickContainer(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: {_id},
});
},
clickProperty(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `tree-node-${_id}`,
data: {_id},
});
},
},
methods: {
clickContainer(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: { _id },
});
},
clickProperty(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `tree-node-${_id}`,
data: { _id },
});
},
},
meteor: {
items(){
items() {
return CreatureProperties.find({
'parent.id': this.model._id,
type: {$in: ['item', 'container']},
removed: {$ne: true},
equipped: {$ne: true},
deactivatedByAncestor: {$ne: true},
type: { $in: ['item', 'container'] },
removed: { $ne: true },
equipped: { $ne: true },
deactivatedByAncestor: { $ne: true },
}, {
sort: {order: 1},
sort: { order: 1 },
});
},
}
@@ -110,4 +108,5 @@ export default {
</script>
<style lang="css" scoped>
</style>

View File

@@ -52,40 +52,42 @@ export default {
preparingSpells: Boolean,
equipment: Boolean,
},
data(){ return {
dataItems: [],
}},
data() {
return {
dataItems: [],
}
},
computed: {
levels(){
levels() {
let levels = new Set();
this.items.forEach(item => levels.add(item.level));
return levels;
},
},
watch: {
items(value){
items(value) {
this.dataItems = value;
}
},
mounted(){
mounted() {
this.dataItems = this.items;
},
methods: {
clickProperty(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: _id,
data: {_id},
});
},
change({added, moved}){
clickProperty(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: _id,
data: { _id },
});
},
change({ added, moved }) {
let event = added || moved;
if (event){
if (event) {
// If this item is now adjacent to another, set the order accordingly
let order;
let before = this.dataItems[event.newIndex - 1];
let after = this.dataItems[event.newIndex + 1];
if (before && before._id){
if (before && before._id) {
order = before.order + 0.5;
} else if (after && after._id) {
order = after.order - 0.5;
@@ -101,7 +103,7 @@ export default {
parentRef: this.parentRef,
order,
});
if (doc.type === 'item' && doc.equipped != this.equipment){
if (doc.type === 'item' && doc.equipped != this.equipment) {
updateCreatureProperty.call({
_id: doc._id,
path: ['equipped'],
@@ -111,6 +113,6 @@ export default {
}
setTimeout(() => this.dataItems = this.items, 0);
},
}
}
}
</script>

View File

@@ -49,10 +49,10 @@ import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeView
import PROPERTIES from '/imports/constants/PROPERTIES.js';
import adjustQuantity from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components:{
components: {
IncrementButton,
},
mixins: [treeNodeViewMixin],
@@ -62,20 +62,22 @@ export default {
props: {
preparingSpells: Boolean,
},
data(){return {
incrementLoading: false,
}},
data() {
return {
incrementLoading: false,
}
},
computed: {
hasClickListener(){
hasClickListener() {
return this.$listeners && !!this.$listeners.click;
},
title(){
title() {
let model = this.model;
if (!model) return;
if (model.quantity !== 1){
if (model.plural){
if (model.quantity !== 1) {
if (model.plural) {
return `${model.quantity} ${model.plural}`;
} else if (model.name){
} else if (model.name) {
return `${model.quantity} ${model.name}`;
}
} else if (model.name) {
@@ -86,10 +88,10 @@ export default {
}
},
methods: {
click(e){
this.$emit('click', e);
},
changeQuantity({type, value}) {
click(e) {
this.$emit('click', e);
},
changeQuantity({ type, value }) {
this.incrementLoading = true;
adjustQuantity.call({
_id: this.model._id,
@@ -97,8 +99,8 @@ export default {
value: value
}, error => {
this.incrementLoading = false;
if (error){
snackbar({text: error.reason});
if (error) {
snackbar({ text: error.reason });
console.error(error);
}
});
@@ -111,6 +113,7 @@ export default {
.item-avatar {
min-width: 32px;
}
.item {
background-color: inherit;
}

View File

@@ -31,10 +31,10 @@ import isDarkColor from '/imports/ui/utility/isDarkColor.js';
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
export default {
components: {
PropertyDescription,
components: {
PropertyDescription,
CardHighlight,
},
},
inject: {
theme: {
default: {
@@ -42,31 +42,34 @@ export default {
},
},
},
props: {
model: {
props: {
model: {
type: Object,
required: true,
},
},
data(){ return{
hover: false,
}},
},
data() {
return {
hover: false,
}
},
computed: {
isDark(){
isDark() {
return isDarkColor(this.model.color);
},
},
methods: {
clickProperty(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: {_id},
});
},
},
methods: {
clickProperty(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: { _id },
});
},
},
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,72 @@
<template>
<v-card
v-if="model"
v-bind="$attrs"
:data-id="`point-buy-card-${model._id}`"
:style="`border: solid 1px ${accentColor};`"
hover
class="slot-card d-flex flex-column"
@mouseover="hover = true"
@mouseleave="hover = false"
@click="$emit('click')"
>
<card-highlight
:active="hover"
/>
<v-card-title>
{{ model.name || 'Point Buy' }}
</v-card-title>
<v-card-text>
{{ model.spent }}
<template v-if="model.total && (typeof model.total.value === 'number')">
/ {{ model.total && model.total.value }}
</template>
</v-card-text>
<v-spacer />
<v-card-actions>
<v-spacer />
<v-btn
icon
color="accent"
@click.stop="$emit('ignore')"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</template>
<script lang="js">
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
export default {
components: {
CardHighlight,
},
inject: {
theme: {
default: {
isDark: false,
},
},
},
props: {
model: {
type: Object,
default: undefined,
},
},
data(){ return {
hover: false,
}},
computed: {
accentColor(){
if (this.theme.isDark){
return this.$vuetify.theme.themes.dark.primary;
} else {
return this.$vuetify.theme.themes.light.primary;
}
}
},
}
</script>

View File

@@ -8,7 +8,7 @@
<v-list-item-title class="d-flex align-center">
<roll-popup
v-if="!hideModifier"
class="prof-mod mr-1"
class="prof-mod mr-1 flex-shrink-0"
button-class="pl-3 pr-2"
text
:roll-text="displayedModifier"
@@ -43,7 +43,7 @@
:value="model.proficiency"
class="prof-icon ml-3 mr-2"
/>
<div>
<div class="text-truncate">
{{ model.name }}
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
*
@@ -62,7 +62,7 @@ import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import ProficiencyIcon from '/imports/ui/properties/shared/ProficiencyIcon.vue';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import doCheck from '/imports/api/engine/actions/doCheck.js';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
@@ -74,37 +74,39 @@ export default {
default: {},
},
},
props: {
props: {
model: {
type: Object,
required: true,
},
hideModifier: Boolean,
},
data(){return {
checkLoading: false,
}},
computed: {
displayedModifier(){
let mod = this.model.value;
if (this.model.fail){
return 'fail';
} else {
return numberToSignedString(mod);
}
},
hasClickListener(){
},
data() {
return {
checkLoading: false,
}
},
computed: {
displayedModifier() {
let mod = this.model.value;
if (this.model.fail) {
return 'fail';
} else {
return numberToSignedString(mod);
}
},
hasClickListener() {
return this.$listeners && this.$listeners.click
},
passiveScore(){
},
passiveScore() {
return 10 + this.model.value + this.model.passiveBonus;
}
},
methods: {
click(e){
this.$emit('click', e);
},
check({advantage}){
},
methods: {
click(e) {
this.$emit('click', e);
},
check({ advantage }) {
this.checkLoading = true;
doCheck.call({
propId: this.model._id,
@@ -113,24 +115,26 @@ export default {
},
}, error => {
this.checkLoading = false;
if (error){
if (error) {
console.error(error);
snackbar({text: error.reason});
snackbar({ text: error.reason });
}
});
},
}
}
}
</script>
<style lang="css" scoped>
.prof-icon {
min-width: 30px;
}
.prof-mod {
min-width: 32px;
}
.v-icon.theme--light {
color: rgba(0, 0, 0, 0.54) !important;
}
.prof-icon {
min-width: 30px;
}
.prof-mod {
min-width: 32px;
}
.v-icon.theme--light {
color: rgba(0, 0, 0, 0.54) !important;
}
</style>

View File

@@ -91,6 +91,20 @@
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
key="ritual-dummy-slot"
class="spell-slot-list-tile"
:class="{ 'primary--text': selectedSlotId === 'ritual' }"
value="ritual"
:disabled="!canCastSpellWithSlot(selectedSpell, 'ritual')"
@click="selectedSlotId = 'ritual'"
>
<v-list-item-content>
<v-list-item-title>
Cast as ritual
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<spell-slot-list-tile
v-for="spellSlot in spellSlots"
:key="spellSlot._id"
@@ -105,13 +119,13 @@
</template>
<template slot="right">
<div
key="spell-title"
key="spell-title-right"
class="text-h6 my-3"
>
Spell
</div>
<v-list-item-group
key="slot-list"
key="slot-list-right"
v-model="selectedSpellId"
>
<template v-for="spell in computedSpells">
@@ -145,10 +159,24 @@
>
Cancel
</v-btn>
<roll-popup
v-if="selectedSpell && selectedSpell.attackRoll"
text
color="primary"
class="mx-2"
:disabled="!canCast"
:name="selectedSpell.name"
:advantage="selectedSpell.attackRoll && selectedSpell.attackRoll.advantage"
@roll="cast"
>
Cast
</roll-popup>
<v-btn
v-else
text
:disabled="!canCast"
class="primary--text"
class="mx-2 px-4"
color="primary"
@click="cast"
>
Cast
@@ -164,20 +192,22 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
import spellsWithSubheaders from '/imports/ui/properties/components/spells/spellsWithSubheaders.js';
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import { find } from 'lodash';
const slotFilter = {
type: 'attribute',
attributeType: 'spellSlot',
removed: {$ne: true},
inactive: {$ne: true},
overridden: {$ne: true},
'spellSlotLevel.value': {$gte: 1},
removed: { $ne: true },
inactive: { $ne: true },
overridden: { $ne: true },
'spellSlotLevel.value': { $gte: 1 },
};
export default {
components: {
DialogBase,
RollPopup,
SplitListLayout,
SpellSlotListTile,
SpellListTile,
@@ -196,36 +226,38 @@ export default {
default: undefined,
},
},
data(){ return {
searchString: undefined,
selectedSlotId: this.slotId,
selectedSpellId: this.spellId,
selectedSlot: undefined,
selectedSpell: undefined,
searchValue: undefined,
searchError: undefined,
filterMenuOpen: false,
booleanFilters: {
verbal: {name: 'Verbal', enabled: false, value: false},
somatic: {name: 'Somatic', enabled: false, value: false},
material: {name: 'Material', enabled: false, value: false},
concentration: {name: 'Concentration', enabled: false, value: false},
ritual: {name: 'Ritual', enabled: false, value: false},
},
}},
data() {
return {
searchString: undefined,
selectedSlotId: this.slotId,
selectedSpellId: this.spellId,
selectedSlot: undefined,
selectedSpell: undefined,
searchValue: undefined,
searchError: undefined,
filterMenuOpen: false,
booleanFilters: {
verbal: { name: 'Verbal', enabled: false, value: true },
somatic: { name: 'Somatic', enabled: false, value: true },
material: { name: 'Material', enabled: false, value: true },
concentration: { name: 'Concentration', enabled: false, value: true },
ritual: { name: 'Ritual', enabled: false, value: true },
},
}
},
computed: {
computedSpells(){
computedSpells() {
return spellsWithSubheaders(this.spells);
},
canCast(){
canCast() {
if (!this.selectedSpell || !this.selectedSlotId) return false;
return this.canCastSpellWithSlot(
this.selectedSpell, this.selectedSlotId, this.selectedSlot
);
},
filtersApplied(){
for (let key in this.booleanFilters){
if (this.booleanFilters[key].enabled){
filtersApplied() {
for (let key in this.booleanFilters) {
if (this.booleanFilters[key].enabled) {
return true;
}
}
@@ -234,79 +266,82 @@ export default {
},
watch: {
selectedSpellId: {
handler(spellId){
handler(spellId) {
this.selectedSpell = CreatureProperties.findOne(spellId)
},
immediate: true
},
selectedSpell: {
handler(spell){
handler(spell) {
if (!spell) return;
if(spell.level === 0 || spell.castWithoutSpellSlots){
this.selectedSlotId = 'no-slot';
} else if (
!this.selectedSlotId ||
this.selectedSlotId == 'no-slot' ||
this.selectedSlot.spellSlotLevel.value < spell.level
if (this.selectedSlotId && this.canCastSpellWithSlot(
spell, this.selectedSlotId, this.selectedSlot
)) return;
if (
(spell.level === 0 || spell.castWithoutSpellSlots)
) {
this.selectedSlotId = 'no-slot';
} else {
const newSlot = find(
CreatureProperties.find({
'ancestors.id': this.creatureId,
...slotFilter
}, {
sort: {'spellSlotLevel.value': 1, order: 1},
sort: { 'spellSlotLevel.value': 1, order: 1 },
}).fetch(),
slot => {
return this.canCastSpellWithSlot(spell, slot._id, slot)
}
);
if (newSlot){
if (newSlot) {
this.selectedSlotId = newSlot._id;
} else if (spell.ritual) {
this.selectedSlotId = 'ritual';
}
}
},
immediate: true,
},
selectedSlotId: {
handler(slotId){
handler(slotId) {
this.selectedSlot = CreatureProperties.findOne(slotId);
},
immediate: true
},
selectedSlot:{
handler(slot){
selectedSlot: {
handler(slot) {
if (!slot) return;
if (!this.selectedSpell) return;
if(this.selectedSpell.level > slot.spellSlotLevel.value){
if (this.selectedSpell.level > slot.spellSlotLevel.value) {
this.selectedSpellId = undefined;
}
},
immediate: true,
},
},
mounted(){
if (this.selectedSpellId){
this.$vuetify.goTo('.spell.v-list-item--active', {container: '.right'});
mounted() {
if (this.selectedSpellId) {
this.$vuetify.goTo('.spell.v-list-item--active', { container: '.right' });
}
},
methods: {
clearBooleanFilters(){
for (let key in this.booleanFilters){
clearBooleanFilters() {
for (let key in this.booleanFilters) {
this.booleanFilters[key].enabled = false;
}
},
spellDialog(_id){
spellDialog(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `spell-info-btn-${_id}`,
data: {_id},
});
component: 'creature-property-dialog',
elementId: `spell-info-btn-${_id}`,
data: { _id },
});
},
searchChanged(val, ack){
searchChanged(val, ack) {
this.searchValue = val;
setTimeout(ack, 200);
},
canCastSpellWithSlot(spell, slotId, slot){
canCastSpellWithSlot(spell, slotId, slot) {
if (slot && !slot.value) return false;
if (!spell) return true;
if (!slotId) return true;
@@ -314,66 +349,69 @@ export default {
spell.castWithoutSpellSlots &&
spell.insufficientResources
) return false;
return (!spell.level || spell.castWithoutSpellSlots) ? (
if (spell.ritual && slotId === 'ritual') return true;
if (!spell.level || spell.castWithoutSpellSlots) {
// Cantrips and no-slot spells
slotId && slotId === 'no-slot'
) : (
return slotId && slotId === 'no-slot'
} else {
// Leveled spells
slotId !== 'no-slot' &&
slot && spell && (
spell.level <= slot.spellSlotLevel.value
)
)
return slotId !== 'no-slot' && slot && spell && (
spell.level <= slot.spellSlotLevel.value
);
}
},
cast(){
cast({ advantage }) {
let selectedSlotId = this.selectedSlotId;
if (selectedSlotId === 'no-slot') selectedSlotId = undefined;
const ritual = selectedSlotId === 'ritual';
if (selectedSlotId === 'no-slot' || selectedSlotId === 'ritual') selectedSlotId = undefined;
this.$store.dispatch('popDialogStack', {
spellId: this.selectedSpellId,
slotId: selectedSlotId,
})
advantage,
ritual,
});
}
},
meteor: {
spells(){
spells() {
let filter = {
'ancestors.id': this.creatureId,
removed: {$ne: true},
inactive: {$ne: true},
removed: { $ne: true },
inactive: { $ne: true },
$or: [
{prepared: true},
{alwaysPrepared: true},
{ prepared: true },
{ alwaysPrepared: true },
],
};
// Apply the filters from the filter menu
for (let key in this.booleanFilters){
if (this.booleanFilters[key].enabled){
for (let key in this.booleanFilters) {
if (this.booleanFilters[key].enabled) {
let value = this.booleanFilters[key].value;
if (key === 'material'){
filter[key] = {$exists: this.booleanFilters[key].value};
if (key === 'material') {
filter[key] = { $exists: this.booleanFilters[key].value };
} else {
filter[key] = value ? true: {$ne: true};
filter[key] = value ? true : { $ne: true };
}
}
}
// Apply the search string to the name field
if (this.searchValue){
if (this.searchValue) {
filter.name = {
$regex: this.searchValue.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'),
$options: 'i'
};
}
return CreatureProperties.find(filter, {
sort: {order: 1}
sort: { order: 1 }
});
},
spellSlots(){
spellSlots() {
return CreatureProperties.find({
'ancestors.id': this.creatureId,
...slotFilter
}, {
sort: {'spellSlotLevel.value': 1, order: 1},
sort: { 'spellSlotLevel.value': 1, order: 1 },
});
},
},
@@ -381,10 +419,11 @@ export default {
</script>
<style lang="css" scoped>
.v-list {
flex-basis: 200px;
}
.v-list.spells {
flex-grow: 1;
}
.v-list {
flex-basis: 200px;
}
.v-list.spells {
flex-grow: 1;
}
</style>

View File

@@ -62,48 +62,50 @@ export default {
},
preparingSpells: Boolean,
},
data(){ return {
dataSpells: [],
}},
data() {
return {
dataSpells: [],
}
},
computed: {
levels(){
levels() {
let levels = new Set();
this.spells.forEach(spell => levels.add(spell.level));
return levels;
},
computedSpells: {
get(){
get() {
return spellsWithSubheaders(this.dataSpells);
},
set(value){
set(value) {
this.dataSpells = value;
},
}
},
watch: {
spells(value){
spells(value) {
this.dataSpells = spellsWithSubheaders(value);
}
},
mounted(){
mounted() {
this.dataSpells = spellsWithSubheaders(this.spells);
},
methods: {
clickProperty(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `spell-list-tile-${_id}`,
data: {_id},
});
},
change({added, moved}){
clickProperty(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `spell-list-tile-${_id}`,
data: { _id },
});
},
change({ added, moved }) {
let event = added || moved;
if (event){
if (event) {
// If this spell is now adjacent to another, set the order accordingly
let order;
let before = this.dataSpells[event.newIndex - 1];
let after = this.dataSpells[event.newIndex + 1];
if (before && before._id){
if (before && before._id) {
order = before.order + 0.5;
} else if (after && after._id) {
order = after.order - 0.5;
@@ -121,9 +123,10 @@ export default {
});
}
},
}
}
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -63,32 +63,34 @@ import SpellList from '/imports/ui/properties/components/spells/SpellList.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
export default {
components: {
ToolbarCard,
components: {
ToolbarCard,
SpellList,
},
props: {
model: {
},
props: {
model: {
type: Object,
required: true,
},
organize: Boolean,
},
data(){ return {
preparingSpells: false,
}},
organize: Boolean,
},
data() {
return {
preparingSpells: false,
}
},
meteor: {
spells(){
spells() {
let filter = {
'ancestors.id': this.model._id,
type: 'spell',
removed: {$ne: true},
removed: { $ne: true },
};
if (this.preparingSpells){
filter.deactivatedByAncestor = {$ne: true};
filter.deactivatedByToggle = {$ne: true};
if (this.preparingSpells) {
filter.deactivatedByAncestor = { $ne: true };
filter.deactivatedByToggle = { $ne: true };
} else {
filter.inactive = {$ne: true};
filter.inactive = { $ne: true };
}
return CreatureProperties.find(filter, {
sort: {
@@ -97,35 +99,36 @@ export default {
}
});
},
numPrepared(){
numPrepared() {
return CreatureProperties.find({
'ancestors.id': this.model._id,
type: 'spell',
removed: {$ne: true},
removed: { $ne: true },
prepared: true,
alwaysPrepared: {$ne: true},
deactivatedByAncestor: {$ne: true},
deactivatedByToggle: {$ne: true},
alwaysPrepared: { $ne: true },
deactivatedByAncestor: { $ne: true },
deactivatedByToggle: { $ne: true },
}).count();
},
preparedError(){
preparedError() {
if (!this.model.maxPrepared) return;
let numPrepared = this.numPrepared;
let maxPrepared = this.model.maxPrepared.value || 0;
return numPrepared !== maxPrepared
},
},
methods: {
clickSpellList(_id){
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: {_id},
});
},
}
methods: {
clickSpellList(_id) {
this.$store.commit('pushDialogStack', {
component: 'creature-property-dialog',
elementId: `${_id}`,
data: { _id },
});
},
}
};
</script>
<style lang="css" scoped>
</style>

View File

@@ -67,10 +67,10 @@ export default {
disabled: Boolean,
},
computed: {
hasClickListener(){
hasClickListener() {
return this.$listeners && !!this.$listeners.click;
},
spellComponents(){
spellComponents() {
let components = [];
if (this.model.ritual) components.push('R');
if (this.model.concentration) components.push('C');
@@ -81,10 +81,10 @@ export default {
},
},
methods: {
click(e){
this.$emit('click', e);
},
setPrepared(val, ack){
click(e) {
this.$emit('click', e);
},
setPrepared(val, ack) {
updateCreatureProperty.call({
_id: this.model._id,
path: ['prepared'],
@@ -99,13 +99,17 @@ export default {
.spell-avatar {
min-width: 32px;
}
.spell {
background-color: inherit;
}
.primary--text .v-icon, .primary--text .v-list__tile__sub-title {
.primary--text .v-icon,
.primary--text .v-list__tile__sub-title {
color: #b71c1c
}
.theme--light.info-icon{
color: rgba(0,0,0,.54) !important;
.theme--light.info-icon {
color: rgba(0, 0, 0, .54) !important;
}
</style>