iteration on tabletop UI
This commit is contained in:
@@ -19,34 +19,18 @@
|
||||
<v-flex
|
||||
style="height: 24px; flex-basis: 300px; flex-grow: 100;"
|
||||
>
|
||||
<div
|
||||
column
|
||||
align-center
|
||||
<health-bar-progress
|
||||
:model="model"
|
||||
style="cursor: pointer;"
|
||||
class="bar"
|
||||
@click="edit"
|
||||
>
|
||||
<div
|
||||
style="height: 24px; width: 100%; position: relative; transition: background-color 0.5s ease;"
|
||||
:style="{
|
||||
backgroundColor: barBackgroundColor
|
||||
class="value"
|
||||
:class="{
|
||||
'white--text': isTextLight,
|
||||
'black--text': !isTextLight,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="filler"
|
||||
style="height: 100%; transform-origin: left; transition: all 0.5s ease;"
|
||||
:style="{
|
||||
backgroundColor: barColor,
|
||||
transform: `scaleX(${fillFraction})`,
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
class="value"
|
||||
:class="{
|
||||
'white--text': isTextLight,
|
||||
'black--text': !isTextLight,
|
||||
}"
|
||||
style="font-size: 15px;
|
||||
style="font-size: 15px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
@@ -55,11 +39,10 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
text-align: center;"
|
||||
>
|
||||
{{ model.value }} / {{ model.total }}
|
||||
</div>
|
||||
>
|
||||
{{ model.value }} / {{ model.total }}
|
||||
</div>
|
||||
</div>
|
||||
</health-bar-progress>
|
||||
<v-menu
|
||||
v-model="editing"
|
||||
absolute
|
||||
@@ -85,11 +68,13 @@
|
||||
<script lang="js">
|
||||
import IncrementMenu from '/imports/client/ui/components/IncrementMenu.vue';
|
||||
import isDarkColor from '/imports/client/ui/utility/isDarkColor.js';
|
||||
import HealthBarProgress from '/imports/client/ui/properties/components/attributes/HealthBarProgress.vue';
|
||||
import chroma from 'chroma-js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IncrementMenu
|
||||
IncrementMenu,
|
||||
HealthBarProgress,
|
||||
},
|
||||
inject: {
|
||||
theme: {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="bar"
|
||||
@click="e => $emit('click', e)"
|
||||
>
|
||||
<div
|
||||
style="width: 100%; position: relative; transition: background-color 0.5s ease;"
|
||||
:style="{
|
||||
backgroundColor: barBackgroundColor,
|
||||
height: `${height}px`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="filler"
|
||||
style="height: 100%; transform-origin: left; transition: all 0.5s ease;"
|
||||
:style="{
|
||||
backgroundColor: barColor,
|
||||
transform: `scaleX(${fillFraction})`,
|
||||
}"
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import chroma from 'chroma-js';
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 24,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fillFraction() {
|
||||
let fraction = this.model.value / this.model.total;
|
||||
if (fraction < 0) fraction = 0;
|
||||
if (fraction > 1) fraction = 1;
|
||||
return fraction;
|
||||
},
|
||||
color() {
|
||||
return this.model.color || this.$vuetify.theme.currentTheme.primary
|
||||
},
|
||||
barColor() {
|
||||
const fraction = this.model.value / this.model.total;
|
||||
if (!Number.isFinite(fraction)) return this.color;
|
||||
if (fraction > 0.5) {
|
||||
return this.color;
|
||||
} else if (this.model.healthBarColorMid && this.model.healthBarColorLow) {
|
||||
return chroma.mix(this.model.healthBarColorLow, this.model.healthBarColorMid, fraction * 2).hex();
|
||||
} else if (this.model.healthBarColorMid) {
|
||||
return this.model.healthBarColorMid;
|
||||
}
|
||||
return this.color;
|
||||
},
|
||||
barBackgroundColor() {
|
||||
return chroma(this.barColor)
|
||||
.darken(1.5)
|
||||
.desaturate(1.5)
|
||||
.hex();
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -80,7 +80,6 @@
|
||||
bottom:0;
|
||||
right: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
"
|
||||
>
|
||||
<v-slide-y-reverse-transition mode="out-in">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template lang="html">
|
||||
<v-card
|
||||
style="height: 100px; width: 70px;"
|
||||
:style="`height: ${height}px; width: ${width}px; overflow: hidden;`"
|
||||
class="tabletop-creature-card"
|
||||
:class="{ active }"
|
||||
:hover="hasClickListener"
|
||||
@@ -9,20 +9,28 @@
|
||||
@mouseleave="hover = false"
|
||||
v-on="hasClickListener ? {click: () => $emit('click')} : {}"
|
||||
>
|
||||
<v-progress-linear
|
||||
v-if="variables.hitPoints"
|
||||
:value="variables.hitPoints.value * 100 / variables.hitPoints.total"
|
||||
/>
|
||||
<v-img
|
||||
:src="model.picture"
|
||||
aspect-ratio="1"
|
||||
:src="model.picture || '/images/ui/missing-portrait.png'"
|
||||
:lazy-src="loadingImg"
|
||||
:height="height"
|
||||
:width="width"
|
||||
class="align-end"
|
||||
:class="{placeholder: !model.picture}"
|
||||
gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
|
||||
position="top center"
|
||||
/>
|
||||
<div
|
||||
class="small-title"
|
||||
>
|
||||
{{ model.name }}
|
||||
</div>
|
||||
<v-card-title
|
||||
class="small-title"
|
||||
v-text="model.name"
|
||||
/>
|
||||
<health-bar-progress
|
||||
v-for="bar in healthBars"
|
||||
:key="bar._id"
|
||||
:model="bar"
|
||||
:height="4"
|
||||
style="opacity: 0.7; margin-top: 2px"
|
||||
/>
|
||||
</v-img>
|
||||
<card-highlight :active="hover" />
|
||||
<div class="d-flex justify-center">
|
||||
<v-scale-transition>
|
||||
@@ -42,24 +50,35 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import CardHighlight from '/imports/client/ui/components/CardHighlight.vue';
|
||||
import HealthBarProgress from '/imports/client/ui/properties/components/attributes/HealthBarProgress.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardHighlight,
|
||||
HealthBarProgress,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 75,
|
||||
},
|
||||
active: Boolean,
|
||||
targeted: Boolean,
|
||||
showTargetBtn: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
hover: false,
|
||||
loadingImg: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAXdJREFUWEftlq1vAkEQxedSB7pYDklBNsgmyCa4Cv5GBI6k8tJKUglULrWcBlvySA4B+/HeAcV07O3t/ObtvNnNHsyc3TEyAGRm+T0Yfs3WFwE0mk3bbbe12WsDtDsda+e5PbZaR4C1c/a9XEowtQC6vZ499ftniaCECiEDoPLnwSBa5WdRWLnZUErIAK+jkeHcY4HkgGBCAmCqr5KyKkgAobP3VXoTAJw9VGBitVhQjpAUeBkOD7Zj4sc5+5rPk0slAOUIkBwQqZAAUD1UYOImALAfbMjE+2xGjWhJASRmGpFtQOwnAzAqTCcTRqTDGhkAP8UGklK9BIDKq9svZUVcShjHZVkmnUApoNjvVHvAfBRFsCGTAJckr2AAAVf4Igqg+D7VdaHJGAVQRm8KIKRCFOBtPE7tK3333ZBBAOXuZyl8Fv1TAF8fBAGYkctWXq3zPdWCANdswJgdgwDM41NVwOeEf4BoE6be/3WO4PSdeARQN7vm+j0BlQ9wWvLB6AAAAABJRU5ErkJggg==',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -67,10 +86,33 @@ export default {
|
||||
return this.$listeners && !!this.$listeners.click;
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
meteor: {
|
||||
variables() {
|
||||
return CreatureVariables.findOne({ _creatureId: this.model._id }) || {};
|
||||
healthBars() {
|
||||
const folderIds = CreatureProperties.find({
|
||||
'ancestors.id': this.model._id,
|
||||
type: 'folder',
|
||||
groupStats: true,
|
||||
hideStatsGroup: true,
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, { fields: { _id: 1 } }).map(folder => folder._id);
|
||||
|
||||
// Get the properties that need to be shown as a health bar
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.model._id,
|
||||
'parent.id': {
|
||||
$nin: folderIds,
|
||||
},
|
||||
type: 'attribute',
|
||||
attributeType: 'healthBar',
|
||||
healthBarNoDamage: { $ne: true },
|
||||
inactive: { $ne: true } ,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: {
|
||||
order: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,10 +121,21 @@ export default {
|
||||
<style lang="css" scoped>
|
||||
.small-title {
|
||||
font-size: 12px;
|
||||
padding: 4px;
|
||||
padding: 4px 4px 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
line-height: normal;
|
||||
}
|
||||
.active {
|
||||
transform: scale(1.2);
|
||||
margin-left: 12px !important;
|
||||
margin-right: 12px !important;
|
||||
transform-origin: top center;
|
||||
}
|
||||
.tabletop-creature-card {
|
||||
transition: all .15s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="creatureId"
|
||||
class="selected-creature-bar d-flex pa-3"
|
||||
class="selected-creature-bar d-flex pa-3 align-end"
|
||||
style="gap: 8px;"
|
||||
>
|
||||
<!--
|
||||
@@ -35,7 +35,7 @@
|
||||
style="width: 300px;"
|
||||
:style="{
|
||||
width: '300px',
|
||||
opacity: selectedIcon ? 1 : 0.5,
|
||||
opacity: selectedIcon ? 1 : 0.7,
|
||||
transition: 'opacity 0.2s ease',
|
||||
}"
|
||||
:model="selectedProp"
|
||||
@@ -52,6 +52,54 @@
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<v-card
|
||||
v-if="iconGroups.buffs"
|
||||
class="buffs-card"
|
||||
>
|
||||
<div
|
||||
v-for="(row, rowIndex) in iconGroups.buffs.rows"
|
||||
:key="rowIndex"
|
||||
class="d-flex"
|
||||
>
|
||||
<template
|
||||
v-for="(icon, iconIndex) in row"
|
||||
>
|
||||
<creature-bar-icon
|
||||
:key="icon.propId || iconIndex"
|
||||
:prop-id="icon.propId"
|
||||
:icon="icon.icon"
|
||||
:selected="selectedIcon === icon"
|
||||
:data-id="icon.propId || icon.standardId"
|
||||
@click="e => selectIcon(e, icon)"
|
||||
@mouseenter="e => hoverIcon(e, icon)"
|
||||
@mouseleave="unHoverIcon(icon)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</v-card>
|
||||
<v-card
|
||||
class="creature-portrait"
|
||||
:width="90"
|
||||
:height="120"
|
||||
>
|
||||
<v-img
|
||||
v-if="creature.picture"
|
||||
:height="120"
|
||||
:src="creature.picture"
|
||||
position="top center"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="fill-height d-flex align-center justify-center"
|
||||
style="opacity: 0.2;"
|
||||
>
|
||||
<v-icon
|
||||
size="90"
|
||||
>
|
||||
mdi-account
|
||||
</v-icon>
|
||||
</div>
|
||||
</v-card>
|
||||
<v-card
|
||||
v-for="group in iconGroups"
|
||||
:key="group.name"
|
||||
@@ -259,6 +307,7 @@ export default {
|
||||
{ type: 'folder', groupStats: true },
|
||||
{ type: 'attribute' },
|
||||
{ type: 'toggle' },
|
||||
{ type: 'buff' }
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -314,6 +363,7 @@ export default {
|
||||
if (prop._placedInGroup) return;
|
||||
let groupName;
|
||||
switch (prop.type) {
|
||||
case 'buff': groupName = 'Buffs'; break;
|
||||
case 'action': groupName = 'Actions'; break;
|
||||
case 'resource': groupName = 'Resources'; break;
|
||||
case 'folder': groupName = 'Folders'; break;
|
||||
@@ -321,7 +371,9 @@ export default {
|
||||
if (!groupName) return;
|
||||
if (!groupsByName[groupName]) {
|
||||
groupsByName[groupName] = { name: groupName, iconList: [] };
|
||||
defaultGroups.push(groupsByName[groupName]);
|
||||
if (groupName !== 'Buffs') { // don't add buffs to the default groups, it is handled differently
|
||||
defaultGroups.push(groupsByName[groupName]);
|
||||
}
|
||||
}
|
||||
groupsByName[groupName].iconList.push({ propId: prop._id });
|
||||
});
|
||||
@@ -343,10 +395,16 @@ export default {
|
||||
|
||||
iconGroups.push(...defaultGroups);
|
||||
|
||||
// Store a specific reference to buffs outside of the list order
|
||||
iconGroups.buffs = groupsByName['Buffs'];
|
||||
|
||||
// Divide the icons into rows
|
||||
iconGroups.forEach(group => {
|
||||
group.rows = splitToNChunks(group.iconList, this.rows);
|
||||
});
|
||||
if (iconGroups.buffs) {
|
||||
iconGroups.buffs.rows = splitToNChunks(iconGroups.buffs.iconList, this.rows);
|
||||
}
|
||||
|
||||
return iconGroups;
|
||||
}
|
||||
|
||||
BIN
app/public/images/ui/missing-portrait.png
Normal file
BIN
app/public/images/ui/missing-portrait.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Reference in New Issue
Block a user