Small progress on tabletop

This commit is contained in:
Stefan Zermatten
2021-10-16 19:05:35 +02:00
parent ea68cdf86f
commit 247353f0ed
22 changed files with 202 additions and 224 deletions

View File

@@ -10,13 +10,13 @@ export default function computeAction(computation, node){
if (!prop.resources) return;
prop.resources.itemsConsumed.forEach(itemConsumed => {
if (!itemConsumed.itemId) return;
if (itemConsumed.available < itemConsumed.quantity.value){
if (itemConsumed.available < itemConsumed.quantity?.value){
prop.insufficientResources = true;
}
});
prop.resources.attributesConsumed.forEach(attConsumed => {
if (!attConsumed.variableName) return;
if (attConsumed.available < attConsumed.quantity.value){
if (attConsumed.available < attConsumed.quantity?.value){
prop.insufficientResources = true;
}
});

View File

@@ -28,7 +28,7 @@ let BuffSchema = createPropertySchema({
'self',
'target',
],
defaultValue: 'every',
defaultValue: 'target',
},
});

View File

@@ -1,54 +0,0 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
const AttributeConsumedSchema = createPropertySchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
variableName: {
type: String,
optional: true,
max: STORAGE_LIMITS.variableName,
},
quantity: {
type: 'fieldToCompute',
optional: true,
},
});
const ComputedOnlyAttributeConsumedSchema = createPropertySchema({
quantity: {
type: 'computedOnlyField',
optional: true,
},
available: {
type: Number,
optional: true,
},
statId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
optional: true,
},
statName: {
type: String,
optional: true,
max: STORAGE_LIMITS.name,
},
});
const ComputedAttributeConsumedSchema = new SimpleSchema()
.extend(AttributeConsumedSchema)
.extend(ComputedOnlyAttributeConsumedSchema);
export {
AttributeConsumedSchema,
ComputedOnlyAttributeConsumedSchema,
ComputedAttributeConsumedSchema
};

View File

@@ -1,64 +0,0 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
const ItemConsumedSchema = createPropertySchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
tag: {
type: String,
optional: true,
},
quantity: {
type: 'fieldToCompute',
optional: true,
},
itemId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
optional: true,
},
});
const ComputedOnlyItemConsumedSchema = new SimpleSchema({
available: {
type: Number,
optional: true,
},
quantity: {
type: 'computedOnlyField',
optional: true,
},
itemName: {
type: String,
max: STORAGE_LIMITS.name,
optional: true,
},
itemIcon: {
type: storedIconsSchema,
optional: true,
max: STORAGE_LIMITS.icon,
},
itemColor: {
type: String,
optional: true,
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
},
});
const ComputedItemConsumedSchema = new SimpleSchema()
.extend(ItemConsumedSchema)
.extend(ComputedOnlyItemConsumedSchema);
export {
ItemConsumedSchema,
ComputedOnlyItemConsumedSchema,
ComputedItemConsumedSchema
};

View File

@@ -48,4 +48,8 @@ let TabletopSchema = new SimpleSchema({
Tabletops.attachSchema(TabletopSchema);
import '/imports/api/tabletop/methods/removeTabletop.js';
import '/imports/api/tabletop/methods/insertTabletop.js';
import '/imports/api/tabletop/methods/addCreaturesToTabletop.js';
export default Tabletops;

View File

@@ -4,7 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Tabletops from '../Tabletops.js';
import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers.js';
import { assertUserIsTabletopOwner } from './shared/tabletopPermissions/js';
import { assertUserIsTabletopOwner } from './shared/tabletopPermissions.js';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
const removeTabletop = new ValidatedMethod({

View File

@@ -130,7 +130,7 @@
return this.speedDialsByTab[tabs[this.tabNumber]];
},
speedDialsByTab() { return {
'stats': ['attribute', 'skill', 'action', 'attack', 'buff'],
'stats': ['attribute', 'skill', 'action', 'buff'],
'features': ['feature'],
'inventory': ['item', 'container'],
'spells': ['spellList', 'spell'],

View File

@@ -17,7 +17,7 @@
@change="propertyHelpChanged"
/>
<text-field
v-if="tab === 1"
v-if="tab === 2"
prepend-inner-icon="mdi-magnify"
regular
hide-details
@@ -183,8 +183,6 @@
import PropertySelector from '/imports/ui/properties/shared/PropertySelector.vue';
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
const SKIP_LIBRARY_PROP_TYPES = ['note', 'damage', 'adjustment']
export default {
components: {
PropertySelector,
@@ -274,11 +272,7 @@
changeType(type){
this._subs.searchLibraryNodes.setData('type', type);
if (!type) return;
if (SKIP_LIBRARY_PROP_TYPES.includes(type)){
this.tab = 1;
} else {
this.tab = 2;
}
this.tab = 1;
this.schema = propertySchemasIndex[type];
this.validationContext = this.schema.newContext();
let model = this.schema.clean({});

View File

@@ -1,27 +1,40 @@
<template lang="html">
<div
class="tabletop-page"
style="height: 100%;"
<v-container
v-if="!$subReady.tabletop"
fluid
class="fill-height"
align="center"
justify="center"
>
<div
v-if="!this.$subReady.tabletop"
class="layout column align-center justify-center"
style="height: 100%;"
>
<v-progress-circular indeterminate />
</div>
<tabletop-component
v-else-if="tabletop"
:model="tabletop"
/>
<div
v-else
<v-row>
<v-col cols="1">
<v-progress-circular indeterminate />
</v-col>
</v-row>
</v-container>
<tabletop-component
v-else-if="tabletop"
:model="tabletop"
/>
<v-container
v-else
fluid
class="fill-height"
align="center"
justify="center"
>
<v-row
class="pa-4"
>
<p>This tabletop was not found</p>
<p>Either it does not exist, or you do not have permission to view it</p>
</div>
</div>
<v-col
cols="12"
md="8"
>
<p>This tabletop was not found</p>
<p>Either it does not exist, or you do not have permission to view it</p>
</v-col>
</v-row>
</v-container>
</template>
<script lang="js">

View File

@@ -35,7 +35,9 @@
<script lang="js">
import SingleCardLayout from '/imports/ui/layouts/SingleCardLayout.vue'
import Tabletops, { insertTabletop } from '/imports/api/tabletop/Tabletops.js';
import Tabletops from '/imports/api/tabletop/Tabletops.js';
import insertTabletop from '/imports/api/tabletop/methods/insertTabletop.js';
import snackbar from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
@@ -55,7 +57,8 @@ export default {
methods: {
addTabletop(){
this.addTabletopLoading = true;
insertTabletop.call(() => {
insertTabletop.call(error => {
if (error) snackbar(error.message);
this.addTabletopLoading = false;
});
}

View File

@@ -97,7 +97,6 @@
:value="model.tags"
@change="change('tags', ...arguments)"
/>
<!--
<smart-select
label="Target"
style="flex-basis: 300px;"
@@ -107,7 +106,6 @@
:menu-props="{auto: true, lazy: true}"
@change="change('target', ...arguments)"
/>
-->
<v-row dense>
<v-col
cols="12"

View File

@@ -1,23 +1,33 @@
<template lang="html">
<div class="layout">
<smart-combobox
label="Attribute"
hint="The attribute variable name that will be consumed"
style="flex-basis: 300px;"
:items="attributeList"
:value="model.variableName"
:error-messages="errors.variableName"
@change="change('variableName', ...arguments)"
/>
<computed-field
label="Quantity"
hint="How much of the attribute will be consumed. If this amount is not available in the attribute, the action can't be taken"
:model="model.quantity"
:error-messages="errors.quantity"
@change="({path, value, ack}) =>
$emit('change', {path: ['quantity', ...path], value, ack})"
/>
</div>
<v-row dense>
<v-col
cols="12"
md="6"
>
<smart-combobox
label="Attribute"
hint="The attribute variable name that will be consumed"
style="flex-basis: 300px;"
:items="attributeList"
:value="model.variableName"
:error-messages="errors.variableName"
@change="change('variableName', ...arguments)"
/>
</v-col>
<v-col
cols="12"
md="6"
>
<computed-field
label="Quantity"
hint="How much of the attribute will be consumed. If this amount is not available in the attribute, the action can't be taken"
:model="model.quantity"
:error-messages="errors.quantity"
@change="({path, value, ack}) =>
$emit('change', {path: ['quantity', ...path], value, ack})"
/>
</v-col>
</v-row>
</template>
<script lang="js">

View File

@@ -17,6 +17,7 @@
icon
large
class="ma-3"
style="margin-bottom: 30px !important;"
@click="$emit('pull', {path: [i]})"
>
<v-icon>mdi-delete</v-icon>

View File

@@ -1,23 +1,33 @@
<template lang="html">
<div class="layout">
<text-field
label="Item"
hint="The item tag that will be consumed"
style="flex-basis: 300px;"
:value="model.tag"
:error-messages="errors.tag"
@change="change('tag', ...arguments)"
/>
<computed-field
label="Quantity"
hint="How many will be consumed"
style="flex-basis: 300px;"
:model="model.quantity"
:error-messages="errors.quantity"
@change="({path, value, ack}) =>
$emit('change', {path: ['quantity', ...path], value, ack})"
/>
</div>
<v-row dense>
<v-col
cols="12"
md="6"
>
<text-field
label="Item"
hint="The item tag that will be consumed"
style="flex-basis: 300px;"
:value="model.tag"
:error-messages="errors.tag"
@change="change('tag', ...arguments)"
/>
</v-col>
<v-col
cols="12"
md="6"
>
<computed-field
label="Quantity"
hint="How many will be consumed"
style="flex-basis: 300px;"
:model="model.quantity"
:error-messages="errors.quantity"
@change="({path, value, ack}) =>
$emit('change', {path: ['quantity', ...path], value, ack})"
/>
</v-col>
</v-row>
</template>
<script lang="js">

View File

@@ -17,6 +17,7 @@
icon
large
class="ma-3"
style="margin-bottom: 30px !important;"
@click="$emit('pull', {path: [i]})"
>
<v-icon>mdi-delete</v-icon>

View File

@@ -12,6 +12,12 @@
@push="({path, value, ack}) => $emit('push', {path: ['attributesConsumed', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['attributesConsumed', ...path], ack})"
/>
<div
v-if="model.itemsConsumed && model.itemsConsumed.length"
class="subheading"
>
Ammo
</div>
<items-consumed-list-form
:model="model.itemsConsumed"
@change="({path, value, ack}) => $emit('change', {path: ['itemsConsumed', ...path], value, ack})"
@@ -53,8 +59,6 @@
<script lang="js">
import AttributesConsumedListForm from '/imports/ui/properties/forms/AttributesConsumedListForm.vue';
import ItemsConsumedListForm from '/imports/ui/properties/forms/ItemsConsumedListForm.vue';
import ItemConsumedSchema from '/imports/api/properties/subSchemas/ItemConsumedSchema.js';
import AttributeConsumedSchema from '/imports/api/properties/subSchemas/AttributeConsumedSchema.js';
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
export default {
@@ -86,7 +90,7 @@
this.addResourceLoading = true;
this.$emit('push', {
path: ['attributesConsumed'],
value: AttributeConsumedSchema.clean({}),
value: {_id: Random.id()},
ack: this.acknowledgeAddResult,
});
},
@@ -94,7 +98,7 @@
this.addResourceLoading = true;
this.$emit('push', {
path: ['itemsConsumed'],
value: ItemConsumedSchema.clean({}),
value: {_id: Random.id()},
ack: this.acknowledgeAddResult,
});
},

View File

@@ -24,6 +24,7 @@ import PatreonLevelTooLow from '/imports/ui/pages/PatreonLevelTooLow.vue';
import Tabletops from '/imports/ui/pages/Tabletops.vue';
import Tabletop from '/imports/ui/pages/Tabletop.vue';
import TabletopToolbar from '/imports/ui/tabletop/TabletopToolbar.vue';
import TabletopRightDrawer from '/imports/ui/tabletop/TabletopRightDrawer.vue';
import Admin from '/imports/ui/pages/Admin.vue';
let userSubscription = Meteor.subscribe('user');
@@ -153,6 +154,7 @@ RouterFactory.configure(factory => {
components: {
default: Tabletop,
toolbar: TabletopToolbar,
rightDrawer: TabletopRightDrawer,
},
beforeEnter: ensureLoggedIn,
},{

View File

@@ -11,9 +11,9 @@
v-for="creature in creatures"
:key="creature._id"
:model="creature"
:selected="selected"
:is-selected="selected.includes(creature._id)"
selection
@select="toggleSelect(creature._id)"
@click="toggleSelect(creature._id)"
/>
</v-list>
<template slot="actions">
@@ -46,7 +46,7 @@ export default {
},
},
data(){return {
selected: new Set(),
selected: [],
}},
meteor: {
creatures(){
@@ -55,8 +55,12 @@ export default {
},
methods: {
toggleSelect(id){
let hadId = this.selected.delete(id);
if (!hadId) this.selected.add(id);
const index = this.selected.indexOf(id);
if (index === -1){
this.selected.push(id);
} else {
this.selected.splice(index, 1);
}
},
}
}

View File

@@ -1,6 +1,13 @@
<template lang="html">
<div class="tabletop">
<section class="initiative-row layout center">
<v-container
class="tabletop"
fluid
>
<v-row
dense
class="initiative-row"
style="flex-wrap: nowrap; overflow-x: auto;"
>
<tabletop-creature-card
v-for="creature in creatures"
:key="creature._id"
@@ -8,7 +15,7 @@
/>
<v-card
class="layout column justify-center align-center"
style="height: 162px; width: 100px;"
style="height: 150px; min-width: 120px;"
data-id="select-creatures"
hover
@click="addCreature"
@@ -17,36 +24,31 @@
<v-icon>mdi-plus</v-icon>
</div>
<v-card-title>
Add creature
Add<br>creature
</v-card-title>
</v-card>
</section>
<section class="play-area">
<tabletop-map />
<tabletop-log :tabletop-id="model._id" />
</section>
</v-row>
<tabletop-map class="play-area" />
<section class="action-row">
<mini-character-sheet />
<tabletop-action-cards />
</section>
</div>
</v-container>
</template>
<script lang="js">
import { addCreaturesToTabletop } from '/imports/api/tabletop/Tabletops.js';
import addCreaturesToTabletop from '/imports/api/tabletop/methods/addCreaturesToTabletop.js';
import TabletopCreatureCard from '/imports/ui/tabletop/TabletopCreatureCard.vue';
import TabletopMap from '/imports/ui/tabletop/TabletopMap.vue';
import TabletopLog from '/imports/ui/tabletop/TabletopLog.vue';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import TabletopActionCards from '/imports/ui/tabletop/TabletopActionCards.vue';
import MiniCharacterSheet from '/imports/ui/creature/character/MiniCharacterSheet.vue';
import snackbar from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
TabletopCreatureCard,
TabletopMap,
TabletopLog,
TabletopActionCards,
MiniCharacterSheet,
},
@@ -77,11 +79,13 @@ export default {
data: {
startingSelection: this.creatures.map(c => c._id),
},
callback: (characterSet) => {
if (!characterSet) return;
callback: (charIds) => {
if (!charIds) return;
addCreaturesToTabletop.call({
tabletopId: this.model._id,
creatureIds: Array.from(characterSet),
creatureIds: charIds,
}, error => {
if (error) snackbar(error.message);
});
},
});

View File

@@ -1,11 +1,17 @@
<template lang="html">
<v-card>
<v-card
style="height: 150px; min-width: 120px;"
>
<v-img
:src="model.picture"
aspect-ratio="1"
position="top center"
/>
<v-card-title>{{ model.name }}</v-card-title>
<div
class="small-title"
>
{{ model.name }}
</div>
</v-card>
</template>
@@ -21,4 +27,11 @@ export default {
</script>
<style lang="css" scoped>
.small-title {
font-size: 14px;
padding: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>

View File

@@ -0,0 +1,32 @@
<template lang="html">
<v-navigation-drawer
v-model="drawer"
app
right
clipped
>
<tabletop-log :tabletop-id="$route.params.id" />
</v-navigation-drawer>
</template>
<script lang="js">
import TabletopLog from '/imports/ui/tabletop/TabletopLog.vue';
export default {
components: {
TabletopLog,
},
computed: {
drawer: {
get () {
return this.$store.state.rightDrawer;
},
set (value) {
this.$store.commit('setRightDrawer', value);
},
},
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -1,16 +1,18 @@
<template lang="html">
<v-toolbar
<v-app-bar
app
color="secondary"
dark
clipped-right
dense
color="secondary"
>
<v-app-bar-nav-icon @click="toggleDrawer" />
<v-toolbar-title>
Tabletop
</v-toolbar-title>
<v-spacer />
</v-toolbar>
<v-app-bar-nav-icon @click="toggleRightDrawer" />
</v-app-bar>
</template>
<script lang="js">
@@ -20,6 +22,7 @@ export default {
methods: {
...mapMutations([
'toggleDrawer',
'toggleRightDrawer',
]),
}
}