Merge branch 'version-2-print' into version-2-dev
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<div class="character-sheet-printed fill-height">
|
||||
<v-fade-transition mode="out-in">
|
||||
<div
|
||||
v-if="!$subReady.singleCharacter"
|
||||
key="character-loading"
|
||||
class="fill-height layout justify-center align-center"
|
||||
>
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="primary"
|
||||
size="64"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="!creature">
|
||||
<v-layout
|
||||
column
|
||||
align-center
|
||||
justify-center
|
||||
>
|
||||
<h2 style="margin: 48px 28px 16px">
|
||||
Character not found
|
||||
</h2>
|
||||
<h3>
|
||||
Either this character does not exist, or you don't have permission
|
||||
to view it.
|
||||
</h3>
|
||||
</v-layout>
|
||||
</div>
|
||||
<v-theme-provider
|
||||
v-else
|
||||
light
|
||||
>
|
||||
<div class="page pa-3">
|
||||
<div class="px-3 d-flex align-center">
|
||||
<div class="logo-background" />
|
||||
<div class="creature-name mr-3">
|
||||
{{ creature.name }}
|
||||
</div>
|
||||
<div class="text-right flex mr-4">
|
||||
<div v-if="creature.alignment || background">
|
||||
{{ creature.alignment }} {{ background }}
|
||||
</div>
|
||||
<dir v-if="race || creature.gender">
|
||||
{{ race }} {{ creature.gender }}
|
||||
</dir>
|
||||
<div v-if="level && classes && classes.length === 1">
|
||||
Level {{ level }} {{ classes[0].name }}
|
||||
</div>
|
||||
<div v-else-if="level">
|
||||
Level {{ level }} ({{ classes.map(c => `${c.name} ${c.level}`).join(', ') }})
|
||||
</div>
|
||||
</div>
|
||||
<qrcode-vue
|
||||
style="height: 100px"
|
||||
render-as="svg"
|
||||
:value="creatureUrl"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="text-right mt-3 mr-4"
|
||||
style="font-size: 8pt; margin-bottom: -4px;"
|
||||
>
|
||||
{{ creatureUrl }}
|
||||
</div>
|
||||
<printed-stats :creature-id="creatureId" />
|
||||
<printed-inventory :creature-id="creatureId" />
|
||||
<printed-spells
|
||||
v-if="!creature.settings.hideSpellsTab"
|
||||
:creature-id="creatureId"
|
||||
/>
|
||||
</div>
|
||||
</v-theme-provider>
|
||||
</v-fade-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import PrintedStats from '/imports/ui/creature/character/printedCharacterSheet/PrintedStats.vue';
|
||||
import PrintedInventory from '/imports/ui/creature/character/printedCharacterSheet/PrintedInventory.vue';
|
||||
import PrintedSpells from '/imports/ui/creature/character/printedCharacterSheet/PrintedSpells.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PrintedStats,
|
||||
PrintedInventory,
|
||||
PrintedSpells,
|
||||
QrcodeVue,
|
||||
},
|
||||
computed: {
|
||||
creatureId() {
|
||||
return this.$route.params.id
|
||||
},
|
||||
creatureUrl() {
|
||||
let props = this.$router.resolve({
|
||||
name: 'characterSheet',
|
||||
params: { id: this.creatureId},
|
||||
});
|
||||
return new URL(props?.href, document.baseURI).href
|
||||
},
|
||||
level() {
|
||||
return this.variables?.level?.value;
|
||||
},
|
||||
highestLevels(){
|
||||
let highestLevels = {};
|
||||
let highestLevelsList = [];
|
||||
this.classLevels.forEach(classLevel => {
|
||||
let name = classLevel.variableName;
|
||||
if (
|
||||
!highestLevels[name] ||
|
||||
highestLevels[name].level < classLevel.level
|
||||
){
|
||||
highestLevels[name] = classLevel;
|
||||
}
|
||||
});
|
||||
for (let name in highestLevels){
|
||||
highestLevelsList.push(highestLevels[name]);
|
||||
}
|
||||
highestLevelsList.sort((a, b) => a.level - b.level);
|
||||
return highestLevelsList;
|
||||
},
|
||||
classes() {
|
||||
return [
|
||||
...this.highestLevels,
|
||||
...this.classProperties
|
||||
].sort((a, b) => a.order - b.order);
|
||||
},
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['creatureId', 'editPermission'],
|
||||
},
|
||||
watch: {
|
||||
'creature.name'(value) {
|
||||
this.$store.commit('setPageTitle', value ? ('Print ' + value) : 'Print Character Sheet');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.commit('setPageTitle',
|
||||
(this.creature && this.creature.name) ?
|
||||
('Print ' + this.creature.name) :
|
||||
'Print Character Sheet'
|
||||
);
|
||||
this.nameObserver = Creatures.find({
|
||||
creatureId: this.creatureId,
|
||||
}, {
|
||||
fields: { name: 1 },
|
||||
}).observe({
|
||||
added: ({ name }) =>
|
||||
this.$store.commit('setPageTitle', name ? ('Print ' + name) : 'Print Character Sheet'),
|
||||
changed: ({ name }) =>
|
||||
this.$store.commit('setPageTitle', name ? ('Print ' + name) : 'Print Character Sheet'),
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.nameObserver.stop();
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'singleCharacter'() {
|
||||
return [this.creatureId];
|
||||
},
|
||||
},
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId);
|
||||
},
|
||||
variables() {
|
||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||
},
|
||||
race() {
|
||||
if (this.variables?.race?.value?.valueType === 'string') return this.variables.race.value.value;
|
||||
const prop = CreatureProperties.findOne({
|
||||
'ancestors.id': this.creatureId,
|
||||
tags: 'race',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
overridden: { $ne: true },
|
||||
});
|
||||
if (prop?.name) return prop.name;
|
||||
return '';
|
||||
},
|
||||
background() {
|
||||
if (this.variables?.background?.value?.valueType === 'string') return this.variables.background.value.value;
|
||||
const prop = CreatureProperties.findOne({
|
||||
'ancestors.id': this.creatureId,
|
||||
tags: 'background',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
overridden: { $ne: true },
|
||||
});
|
||||
if (prop?.name) return prop.name;
|
||||
return '';
|
||||
},
|
||||
classProperties(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'class',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
}).fetch();
|
||||
},
|
||||
classLevels() {
|
||||
const classVariableNames = this.classProperties.map(c => c.variableName)
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'classLevel',
|
||||
variableName: {$nin: classVariableNames},
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
},
|
||||
editPermission() {
|
||||
try {
|
||||
assertEditPermission(this.creature, Meteor.userId());
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.character-sheet-printed {
|
||||
background: white;
|
||||
color: black;
|
||||
font-size: 11pt;
|
||||
}
|
||||
.page {
|
||||
padding: 4px;
|
||||
}
|
||||
.character-sheet-printed .inactive {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.character-sheet-printed .creature-name {
|
||||
font-size: 24pt;
|
||||
background-color: white;
|
||||
}
|
||||
.character-sheet-printed .logo-background {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 8px;
|
||||
background-image: url(/crown-dice-logo-cropped-transparent.png);
|
||||
background-size: contain;
|
||||
background-position: 0 center;
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.character-sheet-printed .v-divider {
|
||||
border-color: rgba(0,0,0,0.3);
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.character-sheet-printed .double-border {
|
||||
position: relative;
|
||||
padding: 11px 10px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.character-sheet-printed .double-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-image-source: url(/images/print/doubleLineImageBorder.png);
|
||||
border-image-slice: 110 126 fill;
|
||||
border-image-width: 16px;
|
||||
border-image-repeat: stretch;
|
||||
box-sizing: content-box;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.character-sheet-printed .octagon-border {
|
||||
position: relative;
|
||||
padding: 4px 20px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.character-sheet-printed .octagon-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-image: url(/images/print/octagonBorder.png) 124 118 fill;
|
||||
border-image-width: 22px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.character-sheet-printed .stats .label {
|
||||
font-size: 10pt;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.character-sheet-printed .label {
|
||||
font-size: 14pt;
|
||||
font-variant: all-small-caps;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.character-sheet-printed .span-all {
|
||||
column-span: all;
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.character-sheet-printed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.character-sheet-printed .page {
|
||||
width: 210mm;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
header {
|
||||
display: none !important;
|
||||
}
|
||||
nav {
|
||||
display: none !important;
|
||||
}
|
||||
.v-main {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,270 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="inventory"
|
||||
style="page-break-before: always;"
|
||||
>
|
||||
<column-layout wide-columns>
|
||||
<div class="span-all">
|
||||
<div class="double-border">
|
||||
<div class="label text-center">
|
||||
Inventory
|
||||
</div>
|
||||
<div class="d-flex inventory-stat">
|
||||
<v-icon>$vuetify.icons.injustice</v-icon>
|
||||
Weight Carried:
|
||||
{{ weightCarried }} lb
|
||||
</div>
|
||||
<div class="d-flex inventory-stat">
|
||||
<v-icon>$vuetify.icons.cash</v-icon>
|
||||
Net worth:
|
||||
<coin-value
|
||||
class="ml-2"
|
||||
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex inventory-stat">
|
||||
<v-icon>$vuetify.icons.spell</v-icon>
|
||||
Items attuned:
|
||||
{{ variables.itemsAttuned && variables.itemsAttuned.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span-all">
|
||||
<div class="octagon-border label text-center">
|
||||
Equipped
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in equippedItems"
|
||||
:key="item._id"
|
||||
>
|
||||
<printed-item
|
||||
class="double-border"
|
||||
:model="item"
|
||||
/>
|
||||
</div>
|
||||
<div class="span-all">
|
||||
<div class="octagon-border label text-center">
|
||||
Carried
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in carriedItems"
|
||||
:key="item._id"
|
||||
>
|
||||
<printed-item
|
||||
class="double-border"
|
||||
:model="item"
|
||||
/>
|
||||
</div>
|
||||
<template
|
||||
v-for="container in containersWithoutAncestorContainers"
|
||||
>
|
||||
<div
|
||||
:key="container._id"
|
||||
class="span-all container-header"
|
||||
>
|
||||
<printed-container
|
||||
class="octagon-border"
|
||||
:model="container"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in container.items"
|
||||
:key="item._id"
|
||||
>
|
||||
<printed-item
|
||||
class="double-border"
|
||||
:model="item"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</column-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/getParentRefByTag.js';
|
||||
import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js';
|
||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||
import PrintedItem from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue';
|
||||
import PrintedContainer from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue';
|
||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
CoinValue,
|
||||
PrintedItem,
|
||||
PrintedContainer,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
organize: false,
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
containers() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'container',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, {
|
||||
fields: {
|
||||
color: 1,
|
||||
variables: 1,
|
||||
}
|
||||
});
|
||||
},
|
||||
variables() {
|
||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||
},
|
||||
containersWithoutAncestorContainers() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.containerIds
|
||||
},
|
||||
type: 'container',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
}).map(c => {
|
||||
c.items = CreatureProperties.find({
|
||||
'parent.id': c._id,
|
||||
type: { $in: ['item', 'container'] },
|
||||
removed: { $ne: true },
|
||||
equipped: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
return c;
|
||||
});
|
||||
},
|
||||
carriedItems() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.containerIds
|
||||
},
|
||||
type: 'item',
|
||||
equipped: { $ne: true },
|
||||
removed: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
equippedItems() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
},
|
||||
type: 'item',
|
||||
equipped: true,
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
equipmentParentRef() {
|
||||
return getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.equipment
|
||||
) || getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.inventory
|
||||
) || {
|
||||
id: this.creatureId,
|
||||
collection: 'creatures'
|
||||
};
|
||||
},
|
||||
carriedParentRef() {
|
||||
return getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.carried
|
||||
) || getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.inventory
|
||||
) || {
|
||||
id: this.creatureId,
|
||||
collection: 'creatures'
|
||||
};
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
containerIds() {
|
||||
return this.containers.map(container => container._id);
|
||||
},
|
||||
weightCarried() {
|
||||
return stripFloatingPointOddities(
|
||||
this.variables &&
|
||||
this.variables.weightCarried &&
|
||||
this.variables.weightCarried.value || 0
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty(_id) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `tree-node-${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.octagon-border {
|
||||
position: relative;
|
||||
padding: 4px 20px;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.octagon-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-image: url(/images/print/octagonBorder.png) 124 118 fill;
|
||||
border-image-width: 22px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14pt;
|
||||
font-variant: small-caps;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.inventory-stat {
|
||||
font-size: 12pt;
|
||||
line-height: 32px;
|
||||
}
|
||||
.inventory-stat > .v-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.container-header {
|
||||
page-break-after: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="spells"
|
||||
style="page-break-before: always;"
|
||||
>
|
||||
<column-layout wide-columns>
|
||||
<div class="span-all">
|
||||
<div class="label text-center octagon-border">
|
||||
Spells
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="spell in spellsWithoutList"
|
||||
:key="spell._id"
|
||||
>
|
||||
<printed-spell :model="spell" />
|
||||
</div>
|
||||
<template
|
||||
v-for="spellList in spellListsWithoutAncestorSpellLists"
|
||||
>
|
||||
<div
|
||||
:key="spellList._id"
|
||||
class="span-all"
|
||||
>
|
||||
<printed-spell-list
|
||||
:model="spellList"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="spell in spellList.spells"
|
||||
:key="spell._id"
|
||||
>
|
||||
<printed-spell :model="spell" />
|
||||
</div>
|
||||
</template>
|
||||
</column-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import PrintedSpell from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedSpell.vue';
|
||||
import PrintedSpellList from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
PrintedSpell,
|
||||
PrintedSpellList,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
organize: false,
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
spellLists() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'spellList',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
spellsWithoutList() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.spellListIds,
|
||||
},
|
||||
type: 'spell',
|
||||
removed: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
deactivatedByToggle: { $ne: true },
|
||||
}, {
|
||||
sort: {
|
||||
level: 1,
|
||||
order: 1,
|
||||
}
|
||||
});
|
||||
},
|
||||
spellListsWithoutAncestorSpellLists() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.spellListIds,
|
||||
},
|
||||
type: 'spellList',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
}).map(sl => {
|
||||
sl.spells = CreatureProperties.find({
|
||||
'ancestors.id': sl._id,
|
||||
type: 'spell',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: {
|
||||
level: 1,
|
||||
order: 1,
|
||||
}
|
||||
}).fetch();
|
||||
return sl;
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
spellListIds() {
|
||||
return this.spellLists?.map(spellList => spellList._id);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,639 @@
|
||||
<template lang="html">
|
||||
<div class="stats">
|
||||
<column-layout>
|
||||
<div
|
||||
v-if="abilities.length"
|
||||
class="ability-scores"
|
||||
>
|
||||
<div class="layout flex column">
|
||||
<div
|
||||
v-for="ability in abilities"
|
||||
:key="ability._id"
|
||||
class="ability"
|
||||
>
|
||||
<div class="score">
|
||||
<div class="double-border top big-number">
|
||||
<template v-if="creature.settings.swapScoresAndMods">
|
||||
{{ ability.total }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ numberToSignedString(ability.modifier) }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<template v-if="creature.settings.swapScoresAndMods">
|
||||
{{ numberToSignedString(ability.modifier) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ ability.total }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="double-border name label">
|
||||
{{ ability.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="toggle in toggles"
|
||||
:key="toggle._id"
|
||||
class="number-label"
|
||||
>
|
||||
<div class="box double-border" />
|
||||
<div class="label double-border">
|
||||
{{ toggle.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="stat in stats"
|
||||
:key="stat._id"
|
||||
class="number-label"
|
||||
:class="stat.variableName == 'armor' && 'shield-number-label'"
|
||||
>
|
||||
<div
|
||||
:class="stat.variableName == 'armor' ? 'shield-border' : 'octagon-border'"
|
||||
class="number big-number"
|
||||
>
|
||||
{{ stat.value }}
|
||||
</div>
|
||||
<div class="label double-border">
|
||||
{{ stat.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="modifier in modifiers"
|
||||
:key="modifier._id"
|
||||
class="number-label"
|
||||
>
|
||||
<div class="number octagon-border big-number">
|
||||
{{ numberToSignedString(modifier.value) }}
|
||||
</div>
|
||||
<div class="label double-border">
|
||||
{{ modifier.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="check in checks"
|
||||
:key="check._id"
|
||||
class="number-label"
|
||||
>
|
||||
<div class="number octagon-border big-number">
|
||||
{{ numberToSignedString(check.value) }}
|
||||
</div>
|
||||
<div class="label double-border">
|
||||
{{ check.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="healthBar in healthBars"
|
||||
:key="healthBar._id"
|
||||
class="m-2"
|
||||
>
|
||||
<div class="double-border">
|
||||
<div class="label">
|
||||
Total: {{ healthBar.total }}
|
||||
</div>
|
||||
<div style="height: 60px;" />
|
||||
<div
|
||||
style="text-align: center;"
|
||||
class="label"
|
||||
>
|
||||
{{ healthBar.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="multipliers && multipliers.length">
|
||||
<printed-damage-multipliers
|
||||
class="double-border"
|
||||
:multipliers="multipliers"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="hitDice.length"
|
||||
class="hit-dice m-2"
|
||||
>
|
||||
<div class="double-border">
|
||||
<div>
|
||||
<span class="label">
|
||||
Total:
|
||||
</span>
|
||||
<span
|
||||
v-for="hitDie in hitDice"
|
||||
:key="hitDie._id"
|
||||
style="margin-right: 4px;"
|
||||
>
|
||||
{{ hitDie.total }}{{ hitDie.hitDiceSize }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="height: 60px;" />
|
||||
<div
|
||||
style="text-align: center;"
|
||||
class="label"
|
||||
>
|
||||
Hit Dice
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="resource in resources"
|
||||
:key="resource._id"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
:class="resource.total <= 8 && 'mb-2'"
|
||||
>
|
||||
<div
|
||||
v-if="resource.total <= 8"
|
||||
class="label"
|
||||
>
|
||||
{{ resource.name }}
|
||||
</div>
|
||||
<div
|
||||
v-if="resource.total > 8"
|
||||
>
|
||||
total: {{ resource.total }}
|
||||
<div style="height: 60px;" />
|
||||
</div>
|
||||
<div
|
||||
v-if="resource.total <= 8"
|
||||
class="d-flex justify-end"
|
||||
>
|
||||
<div
|
||||
v-for="i in resource.total"
|
||||
:key="i"
|
||||
class="resource-bubble"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="resource.total > 8"
|
||||
class="label text-center"
|
||||
>
|
||||
{{ resource.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="spellSlots && spellSlots.length"
|
||||
>
|
||||
<div class="double-border">
|
||||
<div class="label text-center">
|
||||
Spell Slots
|
||||
</div>
|
||||
<div
|
||||
v-for="spellSlot in spellSlots"
|
||||
:key="spellSlot._id"
|
||||
class="mb-7"
|
||||
:class="spellSlot.total <= 8 && 'mb-7'"
|
||||
>
|
||||
<div class="label">
|
||||
{{ spellSlot.name }}
|
||||
</div>
|
||||
<div
|
||||
v-if="spellSlot.total > 8"
|
||||
>
|
||||
Total: {{ spellSlot.total }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="d-flex"
|
||||
>
|
||||
<div
|
||||
v-for="i in spellSlot.total"
|
||||
:key="i"
|
||||
class="resource-bubble"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="savingThrows.length"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<printed-skill
|
||||
v-for="save in savingThrows"
|
||||
:key="save._id"
|
||||
:model="save"
|
||||
:data-id="save._id"
|
||||
/>
|
||||
<div class="label text-center">
|
||||
Saving Throws
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="skills.length"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<printed-skill
|
||||
v-for="skill in skills"
|
||||
:key="skill._id"
|
||||
:model="skill"
|
||||
:data-id="skill._id"
|
||||
/>
|
||||
<div class="label text-center">
|
||||
Skills
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="weapons && weapons.length"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<printed-skill
|
||||
v-for="weapon in weapons"
|
||||
:key="weapon._id"
|
||||
hide-modifier
|
||||
:model="weapon"
|
||||
:data-id="weapon._id"
|
||||
/>
|
||||
<div class="label text-center">
|
||||
Weapons
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="armors && armors.length"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<printed-skill
|
||||
v-for="armor in armors"
|
||||
:key="armor._id"
|
||||
hide-modifier
|
||||
:model="armor"
|
||||
:data-id="armor._id"
|
||||
/>
|
||||
<div class="label text-center">
|
||||
Armor
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="tools && tools.length"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<printed-skill
|
||||
v-for="tool in tools"
|
||||
:key="tool._id"
|
||||
hide-modifier
|
||||
:model="tool"
|
||||
:data-id="tool._id"
|
||||
/>
|
||||
<div class="label text-center">
|
||||
Tools
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="languages && languages.length"
|
||||
>
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<printed-skill
|
||||
v-for="language in languages"
|
||||
:key="language._id"
|
||||
hide-modifier
|
||||
:model="language"
|
||||
:data-id="language._id"
|
||||
/>
|
||||
<div class="label text-center">
|
||||
Languages
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="note in notes"
|
||||
:key="note._id"
|
||||
>
|
||||
<div class="double-border">
|
||||
<div class="label text-center">
|
||||
{{ note.name }}
|
||||
</div>
|
||||
<property-description
|
||||
text
|
||||
:model="note.summary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="action in actions"
|
||||
:key="action._id"
|
||||
>
|
||||
<div class="double-border">
|
||||
<printed-action
|
||||
:model="action"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="feature in features"
|
||||
:key="feature._id"
|
||||
>
|
||||
<div class="double-border">
|
||||
<div class="label text-center">
|
||||
{{ feature.name }}
|
||||
</div>
|
||||
<property-description
|
||||
text
|
||||
:model="feature.summary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</column-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import PrintedAction from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import PrintedSkill from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue';
|
||||
import PrintedDamageMultipliers from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue';
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
|
||||
const getProperties = function (creature, filter, options = {
|
||||
sort: { order: 1 }
|
||||
}) {
|
||||
if (!creature) return;
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
filter.hide = { $ne: true };
|
||||
}
|
||||
filter['ancestors.id'] = creature._id;
|
||||
filter.removed = { $ne: true };
|
||||
filter.inactive = { $ne: true };
|
||||
filter.overridden = { $ne: true };
|
||||
filter.$nor = [
|
||||
{ hideWhenTotalZero: true, total: 0 },
|
||||
{ hideWhenValueZero: true, value: 0 },
|
||||
];
|
||||
|
||||
return CreatureProperties.find(filter, options);
|
||||
};
|
||||
|
||||
const getAttributeOfType = function (creature, type) {
|
||||
return getProperties(creature, {
|
||||
type: 'attribute',
|
||||
attributeType: type,
|
||||
});
|
||||
};
|
||||
|
||||
const getSkillOfType = function (creature, type) {
|
||||
return getProperties(creature, {
|
||||
type: 'skill',
|
||||
skillType: type,
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
PrintedDamageMultipliers,
|
||||
PrintedAction,
|
||||
PrintedSkill,
|
||||
PropertyDescription,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
doCheckLoading: false,
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
||||
},
|
||||
abilities() {
|
||||
return getAttributeOfType(this.creature, 'ability');
|
||||
},
|
||||
stats() {
|
||||
return getAttributeOfType(this.creature, 'stat');
|
||||
},
|
||||
toggles() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'toggle',
|
||||
removed: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
showUI: true,
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
healthBars() {
|
||||
return getAttributeOfType(this.creature, 'healthBar');
|
||||
},
|
||||
modifiers() {
|
||||
return getAttributeOfType(this.creature, 'modifier');
|
||||
},
|
||||
resources() {
|
||||
return getAttributeOfType(this.creature, 'resource');
|
||||
},
|
||||
spellSlots() {
|
||||
return getAttributeOfType(this.creature, 'spellSlot');
|
||||
},
|
||||
hasSpells() {
|
||||
const cursor = getProperties(this.creature, {
|
||||
type: 'spell',
|
||||
})
|
||||
return cursor && cursor.count();
|
||||
},
|
||||
hitDice() {
|
||||
return getAttributeOfType(this.creature, 'hitDice');
|
||||
},
|
||||
checks() {
|
||||
return getSkillOfType(this.creature, 'check');
|
||||
},
|
||||
savingThrows() {
|
||||
return getSkillOfType(this.creature, 'save');
|
||||
},
|
||||
skills() {
|
||||
return getSkillOfType(this.creature, 'skill');
|
||||
},
|
||||
tools() {
|
||||
return getSkillOfType(this.creature, 'tool');
|
||||
},
|
||||
weapons() {
|
||||
return getSkillOfType(this.creature, 'weapon');
|
||||
},
|
||||
armors() {
|
||||
return getSkillOfType(this.creature, 'armor');
|
||||
},
|
||||
languages() {
|
||||
return getSkillOfType(this.creature, 'language');
|
||||
},
|
||||
actions() {
|
||||
return getProperties(this.creature, { type: 'action' }, {
|
||||
sort: { actionType: 1, order: 1 }
|
||||
});
|
||||
},
|
||||
appliedBuffs() {
|
||||
return getProperties(this.creature, { type: 'buff' });
|
||||
},
|
||||
multipliers() {
|
||||
return getProperties(this.creature, {
|
||||
type: 'damageMultiplier'
|
||||
}, {
|
||||
sort: { value: 1, order: 1 }
|
||||
});
|
||||
},
|
||||
features() {
|
||||
return getProperties(this.creature, { type: 'feature' });
|
||||
},
|
||||
notes(){
|
||||
return getProperties(this.creature, { type: 'note', summary: {$exists: true} });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
numberToSignedString,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.shield-border {
|
||||
min-width: 64px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
aspect-ratio: 0.87;
|
||||
padding: 12px;
|
||||
}
|
||||
.shield-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: url(/images/print/shieldBorder.png);
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -1;
|
||||
}
|
||||
.shield-number-label {
|
||||
align-items: center !important;
|
||||
}
|
||||
.big-number {
|
||||
font-size: 20pt;
|
||||
}
|
||||
.ability {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.ability .score {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.ability .top {
|
||||
min-width: 64px;
|
||||
text-align: center;
|
||||
margin-bottom: -10px;
|
||||
padding: 14px;
|
||||
z-index: 1;
|
||||
}
|
||||
.ability .bottom {
|
||||
font-size: 10pt;
|
||||
position: relative;
|
||||
padding: 0 16px;
|
||||
z-index: 2;
|
||||
}
|
||||
.ability .bottom::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: solid white;
|
||||
border-image-source: url(/images/print/upwardPointingBorder.png);
|
||||
border-image-slice: 0 85 fill;
|
||||
border-image-width: 0 16px;
|
||||
border-image-outset: 0px 0px;
|
||||
border-image-repeat: stretch;
|
||||
box-sizing: content-box;
|
||||
z-index: -1;
|
||||
}
|
||||
.ability .name {
|
||||
margin-top: 10px;
|
||||
margin-left: -16px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.number-label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 10pt;
|
||||
font-variant: small-caps;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.number-label .label {
|
||||
margin-top: 4px;
|
||||
margin-left: -30px;
|
||||
padding-left: 34px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.number-label .number {
|
||||
min-width: 72px;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.number-label .box {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-left: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.resource-bubble {
|
||||
margin-bottom: -20px;
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
background-color: white;
|
||||
border: solid black 2px;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,247 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="action-card"
|
||||
:class="cardClasses"
|
||||
>
|
||||
<div class="label text-center">
|
||||
{{ actionTypeName }}
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<div class="avatar">
|
||||
<div
|
||||
v-if="rollBonus"
|
||||
>
|
||||
<template v-if="rollBonus && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
color="rgba(0,0,0,0.7)"
|
||||
/>
|
||||
</div>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
color="rgba(0,0,0,0.7)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="action-header flex d-flex column justify-center pl-1"
|
||||
>
|
||||
<div class="action-title my-1">
|
||||
{{ model.name || propertyName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="Number.isFinite(model.uses)"
|
||||
class="action-sub-title d-flex align-center"
|
||||
>
|
||||
{{ model.uses }} uses
|
||||
</div>
|
||||
<div class="pb-3">
|
||||
<div
|
||||
v-if="model.resources && model.resources.attributesConsumed.length ||
|
||||
model.resources.itemsConsumed.length"
|
||||
class="resources my-2"
|
||||
>
|
||||
<div
|
||||
v-for="attributeConsumed in model.resources.attributesConsumed"
|
||||
:key="attributeConsumed._id"
|
||||
class="layout align-center justify-start"
|
||||
>
|
||||
Cost: {{ attributeConsumed.quantity && attributeConsumed.quantity.value }} {{ attributeConsumed.statName || attributeConsumed.variableName }}
|
||||
</div>
|
||||
<div
|
||||
v-for="itemConsumed in model.resources.itemsConsumed"
|
||||
:key="itemConsumed._id"
|
||||
>
|
||||
<template v-if="itemConsumed.itemName">
|
||||
Uses: {{ itemConsumed.quantity && itemConsumed.quantity.value || 0 }} {{ itemConsumed.itemName || itemConsumed.tag }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="model.summary">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import MarkdownText from '/imports/ui/components/MarkdownText.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: {
|
||||
AttributeConsumedView,
|
||||
ItemConsumedView,
|
||||
MarkdownText,
|
||||
PropertyIcon,
|
||||
TreeNodeList,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activated: undefined,
|
||||
doActionLoading: false,
|
||||
hovering: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rollBonus() {
|
||||
if (!this.model.attackRoll) return;
|
||||
return numberToSignedString(this.model.attackRoll.value);
|
||||
},
|
||||
rollBonusTooLong() {
|
||||
return this.rollBonus && this.rollBonus.length > 3;
|
||||
},
|
||||
propertyName() {
|
||||
return getPropertyName(this.model.type);
|
||||
},
|
||||
cardClasses() {
|
||||
return {
|
||||
'theme--dark': this.theme.isDark,
|
||||
'theme--light': !this.theme.isDark,
|
||||
'muted-text': this.model.insufficientResources,
|
||||
'active': this.activated,
|
||||
'elevation-8': this.hovering,
|
||||
}
|
||||
},
|
||||
actionTypeName() {
|
||||
return {
|
||||
'action': 'Action',
|
||||
'bonus': 'Bonus Action',
|
||||
'attack': 'Attack',
|
||||
'reaction': 'Reaction',
|
||||
'free': 'Free Action',
|
||||
'long': 'Long Action'
|
||||
}[this.model.actionType] || this.model.actionType
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.action-card {
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
|
||||
transform 0.075s ease;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
font-size: 18pt;
|
||||
text-align: center;
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 10pt;
|
||||
font-variant: small-caps;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
transition: .3s cubic-bezier(.25, .8, .5, 1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resources {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.action-child {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.theme--light.muted-text {
|
||||
color: rgba(0, 0, 0, .3) !important;
|
||||
}
|
||||
|
||||
.theme--dark.muted-text {
|
||||
color: hsla(0, 0%, 100%, .3) !important;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
transition: transform 0.15s cubic;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="css">
|
||||
.action-card.theme--light.muted-text .v-icon {
|
||||
color: rgba(0, 0, 0, .3) !important;
|
||||
}
|
||||
|
||||
.action-card.theme--dark.muted-text .v-icon {
|
||||
color: hsla(0, 0%, 100%, .3) !important;
|
||||
}
|
||||
|
||||
.action-card .property-description>p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="d-flex justify-center">
|
||||
<property-icon
|
||||
class="ml-2"
|
||||
color="rgba(0,0,0,0.7)"
|
||||
:model="model"
|
||||
/>
|
||||
<div class="label">
|
||||
{{ model.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="model.value !== undefined || model.weight !== undefined"
|
||||
class="weight-value my-2 d-flex justify-space-between"
|
||||
>
|
||||
<div class="value ml-4">
|
||||
<div
|
||||
v-if="model.value !== undefined"
|
||||
>
|
||||
<v-layout align-center>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.two_coins
|
||||
</v-icon>
|
||||
<coin-value
|
||||
class="mr-2"
|
||||
:value="model.value"
|
||||
/>
|
||||
</v-layout>
|
||||
|
||||
<v-layout
|
||||
align-center
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.cash
|
||||
</v-icon>
|
||||
<coin-value
|
||||
:value="model.contentsValue"
|
||||
/>
|
||||
<span
|
||||
class="ml-1"
|
||||
>
|
||||
contents
|
||||
</span>
|
||||
</v-layout>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="weight ml-4">
|
||||
<div
|
||||
v-if="model.weight !== undefined"
|
||||
>
|
||||
<v-layout align-center>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.weight
|
||||
</v-icon>
|
||||
{{ model.weight }} lb
|
||||
</v-layout>
|
||||
|
||||
<v-layout
|
||||
align-center
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.injustice
|
||||
</v-icon>
|
||||
{{ model.contentsWeight }} lb
|
||||
<span
|
||||
class="ml-1"
|
||||
>
|
||||
contents
|
||||
</span>
|
||||
</v-layout>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<property-description
|
||||
text
|
||||
:model="model.description"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeViewMixin.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoinValue,
|
||||
PropertyDescription,
|
||||
},
|
||||
mixins: [treeNodeViewMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
preparingSpells: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
incrementLoading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasClickListener() {
|
||||
return this.$listeners && !!this.$listeners.click;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.item-avatar {
|
||||
min-width: 32px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<div
|
||||
v-for="(multiplier, multiplierIndex) in multipliers"
|
||||
:key="multiplier._id"
|
||||
:data-id="multiplier._id"
|
||||
@click="$emit('click-multiplier', {_id: multiplier._id})"
|
||||
>
|
||||
<v-divider v-if="multiplierIndex" />
|
||||
<div>
|
||||
<div
|
||||
v-if="multiplier.name"
|
||||
class="label text-center"
|
||||
>
|
||||
{{ multiplier.name }}
|
||||
</div>
|
||||
<div class="font-weight-medium">
|
||||
{{ title(multiplier) }}
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-center">
|
||||
{{ multiplier.damageTypes.join(', ') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="multiplier.includeTags && multiplier.includeTags.length"
|
||||
class="d-flex flex-wrap align-center"
|
||||
>
|
||||
<div>
|
||||
For:
|
||||
</div>
|
||||
{{ multiplier.includeTags.join(', ') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="multiplier.excludeTags && multiplier.excludeTags.length"
|
||||
class="d-flex flex-wrap align-center"
|
||||
>
|
||||
<div>
|
||||
Except:
|
||||
</div>
|
||||
{{ multiplier.excludeTags.join(', ') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
props: {
|
||||
multipliers:{
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
title(prop){
|
||||
switch (prop.value){
|
||||
case 0: return 'Immunity';
|
||||
case 0.5: return 'Resistance';
|
||||
case 2: return 'Vulnerability';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.label {
|
||||
font-size: 10pt;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<div class="d-flex justify-space-between">
|
||||
<div class="label">
|
||||
{{ title }}
|
||||
<template v-if="attunementText">
|
||||
({{ attunementText }})
|
||||
</template>
|
||||
</div>
|
||||
<property-icon
|
||||
class="ml-2"
|
||||
color="rgba(0,0,0,0.7)"
|
||||
:model="model"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="model.value !== undefined || model.weight !== undefined"
|
||||
class="weight-value my-2 d-flex justify-space-between"
|
||||
>
|
||||
<div class="value ml-4">
|
||||
<div
|
||||
v-if="model.value !== undefined"
|
||||
>
|
||||
<v-layout
|
||||
v-if="model.quantity > 1"
|
||||
align-center
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.cash
|
||||
</v-icon>
|
||||
<coin-value
|
||||
:value="model.value * model.quantity"
|
||||
/>
|
||||
</v-layout>
|
||||
<v-layout align-center>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.two_coins
|
||||
</v-icon>
|
||||
<coin-value
|
||||
class="mr-2"
|
||||
:value="model.value"
|
||||
/>
|
||||
<span
|
||||
v-if="model.quantity > 1"
|
||||
class="ml-1"
|
||||
>
|
||||
each
|
||||
</span>
|
||||
</v-layout>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="weight ml-4">
|
||||
<div
|
||||
v-if="model.weight !== undefined"
|
||||
>
|
||||
<v-layout
|
||||
v-if="model.quantity > 1"
|
||||
align-center
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.injustice
|
||||
</v-icon>
|
||||
{{ totalWeight }} lb
|
||||
</v-layout>
|
||||
<v-layout align-center>
|
||||
<v-icon
|
||||
class="mr-2"
|
||||
small
|
||||
>
|
||||
$vuetify.icons.weight
|
||||
</v-icon>
|
||||
{{ model.weight }} lb
|
||||
<span
|
||||
v-if="model.quantity > 1"
|
||||
class="ml-1"
|
||||
>
|
||||
each
|
||||
</span>
|
||||
</v-layout>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<property-description
|
||||
text
|
||||
:model="model.description"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeViewMixin.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CoinValue,
|
||||
PropertyDescription,
|
||||
},
|
||||
mixins: [treeNodeViewMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
preparingSpells: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
incrementLoading: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasClickListener() {
|
||||
return this.$listeners && !!this.$listeners.click;
|
||||
},
|
||||
title() {
|
||||
let model = this.model;
|
||||
if (!model) return;
|
||||
if (model.quantity !== 1) {
|
||||
if (model.plural) {
|
||||
return `${model.quantity} ${model.plural}`;
|
||||
} else if (model.name) {
|
||||
return `${model.quantity} ${model.name}`;
|
||||
}
|
||||
} else if (model.name) {
|
||||
return model.name;
|
||||
}
|
||||
let prop = PROPERTIES[model.type]
|
||||
return prop && prop.name;
|
||||
},
|
||||
totalValue() {
|
||||
return stripFloatingPointOddities(this.model.value * this.model.quantity);
|
||||
},
|
||||
totalWeight() {
|
||||
return stripFloatingPointOddities(this.model.weight * this.model.quantity);
|
||||
},
|
||||
attunementText() {
|
||||
if (this.model.requiresAttunement) {
|
||||
if (this.model.attuned) return 'Attuned';
|
||||
return 'Requires attunement';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.item-avatar {
|
||||
min-width: 32px;
|
||||
}
|
||||
.item .label {
|
||||
font-size: 14pt;
|
||||
font-variant: all-small-caps;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="printed-skill pl-0 d-flex align-center"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<div
|
||||
v-if="!hideModifier"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<proficiency-icon
|
||||
:value="model.proficiency"
|
||||
class="prof-icon"
|
||||
/>
|
||||
<div class="prof-mod ml-2 mr-4 text-right">
|
||||
{{ displayedModifier }}
|
||||
</div>
|
||||
<v-icon
|
||||
v-if="model.advantage > 0"
|
||||
size="20px"
|
||||
>
|
||||
mdi-chevron-double-up
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-if="model.advantage < 0"
|
||||
size="20px"
|
||||
>
|
||||
mdi-chevron-double-down
|
||||
</v-icon>
|
||||
</div>
|
||||
<proficiency-icon
|
||||
v-else
|
||||
:value="model.proficiency"
|
||||
class="prof-icon mr-2"
|
||||
/>
|
||||
<div class="text-truncate">
|
||||
{{ model.name }}
|
||||
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
|
||||
*
|
||||
</template>
|
||||
<template v-if="'passiveBonus' in model">
|
||||
({{ passiveScore }})
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import ProficiencyIcon from '/imports/ui/properties/shared/ProficiencyIcon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProficiencyIcon,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
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);
|
||||
}
|
||||
},
|
||||
passiveScore() {
|
||||
return 10 + this.model.value + this.model.passiveBonus;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.printed-skill{
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.prof-icon {
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.prof-mod {
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.v-icon.theme--light {
|
||||
color: rgba(0, 0, 0, 0.7) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,82 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="double-border"
|
||||
>
|
||||
<div
|
||||
v-if="model.name"
|
||||
class="label"
|
||||
>
|
||||
{{ model.name }}
|
||||
</div>
|
||||
<div v-if="model.level">
|
||||
{{ levelText }} {{ model.school }} {{ model.ritual ? '(ritual)' : '' }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ model.school }} cantrip
|
||||
</div>
|
||||
<div>
|
||||
Casting Time: {{ model.castingTime }}
|
||||
</div>
|
||||
<div>
|
||||
Range: {{ model.range }}
|
||||
</div>
|
||||
<div>
|
||||
Components: {{ spellComponents }}
|
||||
</div>
|
||||
<div>
|
||||
Duration: {{ model.duration }}
|
||||
</div>
|
||||
<property-description
|
||||
text
|
||||
:model="model.summary"
|
||||
/>
|
||||
<v-divider class="my-2" />
|
||||
<property-description
|
||||
text
|
||||
:model="model.description"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
|
||||
const levelText = [
|
||||
'cantrip', '1st-level', '2nd-level', '3rd-level', '4th-level', '5th-level',
|
||||
'6th-level', '7th-level', '8th-level', '9th-level'
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PropertyDescription,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
levelText() {
|
||||
return levelText[this.model.level]
|
||||
},
|
||||
spellComponents() {
|
||||
let components = [];
|
||||
if (this.model.ritual) components.push('Ritual');
|
||||
if (this.model.concentration) components.push('Concentration');
|
||||
if (this.model.verbal) components.push('Verbal');
|
||||
if (this.model.somatic) components.push('Somatic');
|
||||
if (this.model.material) components.push(`Material (${this.model.material})`);
|
||||
return components.join(', ');
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.label {
|
||||
font-size: 14pt;
|
||||
font-variant: all-small-caps;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="octagon-border">
|
||||
<div class="label text-center">
|
||||
{{ model.name }}
|
||||
</div>
|
||||
<div>
|
||||
Spell Save DC: {{ model.dc && model.dc.value }}
|
||||
</div>
|
||||
<div>
|
||||
Spell Attack Bonus: {{ model.attackRollBonus && model.attackRollBonus.value }}
|
||||
</div>
|
||||
<div>
|
||||
Maximum prepared spells: {{ model.maxPrepared && model.maxPrepared.value }}
|
||||
</div>
|
||||
<property-description
|
||||
text
|
||||
:model="model.description"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PropertyDescription,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -12,6 +12,7 @@ const LibraryCollectionToolbar = () => import('/imports/ui/library/LibraryCollec
|
||||
const CharacterSheetPage = () => import('/imports/ui/pages/CharacterSheetPage.vue');
|
||||
const CharacterSheetToolbar = () => import('/imports/ui/creature/character/CharacterSheetToolbar.vue');
|
||||
const CharacterSheetRightDrawer = () => import('/imports/ui/creature/character/CharacterSheetRightDrawer.vue');
|
||||
const CharacterSheetPrinted = () => import('/imports/ui/creature/character/printedCharacterSheet/CharacterSheetPrinted.vue');
|
||||
const SignIn = () => import('/imports/ui/pages/SignIn.vue');
|
||||
const Register = () => import('/imports/ui/pages/Register.vue');
|
||||
const IconAdmin = () => import('/imports/ui/icons/IconAdmin.vue');
|
||||
@@ -177,6 +178,16 @@ RouterFactory.configure(router => {
|
||||
meta: {
|
||||
title: 'Character Sheet',
|
||||
},
|
||||
}, {
|
||||
name: 'printCharacterSheet',
|
||||
path: '/print-character/:id',
|
||||
alias: '/print-character/:id/:urlName',
|
||||
components: {
|
||||
default: CharacterSheetPrinted,
|
||||
},
|
||||
meta: {
|
||||
title: 'Print Character Sheet',
|
||||
},
|
||||
}, {
|
||||
path: '/tabletops',
|
||||
name: 'tabletops',
|
||||
|
||||
5
app/package-lock.json
generated
5
app/package-lock.json
generated
@@ -3169,6 +3169,11 @@
|
||||
"yargs": "^15.3.1"
|
||||
}
|
||||
},
|
||||
"qrcode.vue": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-1.7.0.tgz",
|
||||
"integrity": "sha512-R7t6Y3fDDtcU7L4rtqwGUDP9xD64gJhIwpfjhRCTKmBoYF6SS49PIJHRJ048cse6OI7iwTwgyy2C46N9Ygoc6g=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"ngraph.path": "^1.4.0",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
"request": "^2.88.2",
|
||||
"sharp": "^0.30.7",
|
||||
"simpl-schema": "^1.13.1",
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user