iteration on tabletop UI
This commit is contained in:
@@ -19,34 +19,18 @@
|
|||||||
<v-flex
|
<v-flex
|
||||||
style="height: 24px; flex-basis: 300px; flex-grow: 100;"
|
style="height: 24px; flex-basis: 300px; flex-grow: 100;"
|
||||||
>
|
>
|
||||||
<div
|
<health-bar-progress
|
||||||
column
|
:model="model"
|
||||||
align-center
|
|
||||||
style="cursor: pointer;"
|
style="cursor: pointer;"
|
||||||
class="bar"
|
|
||||||
@click="edit"
|
@click="edit"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 24px; width: 100%; position: relative; transition: background-color 0.5s ease;"
|
class="value"
|
||||||
:style="{
|
:class="{
|
||||||
backgroundColor: barBackgroundColor
|
'white--text': isTextLight,
|
||||||
|
'black--text': !isTextLight,
|
||||||
}"
|
}"
|
||||||
>
|
style="font-size: 15px;
|
||||||
<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;
|
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -55,11 +39,10 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
text-align: center;"
|
text-align: center;"
|
||||||
>
|
>
|
||||||
{{ model.value }} / {{ model.total }}
|
{{ model.value }} / {{ model.total }}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</health-bar-progress>
|
||||||
<v-menu
|
<v-menu
|
||||||
v-model="editing"
|
v-model="editing"
|
||||||
absolute
|
absolute
|
||||||
@@ -85,11 +68,13 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import IncrementMenu from '/imports/client/ui/components/IncrementMenu.vue';
|
import IncrementMenu from '/imports/client/ui/components/IncrementMenu.vue';
|
||||||
import isDarkColor from '/imports/client/ui/utility/isDarkColor.js';
|
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';
|
import chroma from 'chroma-js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
IncrementMenu
|
IncrementMenu,
|
||||||
|
HealthBarProgress,
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
theme: {
|
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;
|
bottom:0;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<v-slide-y-reverse-transition mode="out-in">
|
<v-slide-y-reverse-transition mode="out-in">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-card
|
<v-card
|
||||||
style="height: 100px; width: 70px;"
|
:style="`height: ${height}px; width: ${width}px; overflow: hidden;`"
|
||||||
class="tabletop-creature-card"
|
class="tabletop-creature-card"
|
||||||
:class="{ active }"
|
:class="{ active }"
|
||||||
:hover="hasClickListener"
|
:hover="hasClickListener"
|
||||||
@@ -9,20 +9,28 @@
|
|||||||
@mouseleave="hover = false"
|
@mouseleave="hover = false"
|
||||||
v-on="hasClickListener ? {click: () => $emit('click')} : {}"
|
v-on="hasClickListener ? {click: () => $emit('click')} : {}"
|
||||||
>
|
>
|
||||||
<v-progress-linear
|
|
||||||
v-if="variables.hitPoints"
|
|
||||||
:value="variables.hitPoints.value * 100 / variables.hitPoints.total"
|
|
||||||
/>
|
|
||||||
<v-img
|
<v-img
|
||||||
:src="model.picture"
|
:src="model.picture || '/images/ui/missing-portrait.png'"
|
||||||
aspect-ratio="1"
|
: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"
|
position="top center"
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="small-title"
|
|
||||||
>
|
>
|
||||||
{{ model.name }}
|
<v-card-title
|
||||||
</div>
|
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" />
|
<card-highlight :active="hover" />
|
||||||
<div class="d-flex justify-center">
|
<div class="d-flex justify-center">
|
||||||
<v-scale-transition>
|
<v-scale-transition>
|
||||||
@@ -42,24 +50,35 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<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 CardHighlight from '/imports/client/ui/components/CardHighlight.vue';
|
||||||
|
import HealthBarProgress from '/imports/client/ui/properties/components/attributes/HealthBarProgress.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardHighlight,
|
CardHighlight,
|
||||||
|
HealthBarProgress,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 75,
|
||||||
|
},
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
targeted: Boolean,
|
targeted: Boolean,
|
||||||
showTargetBtn: Boolean,
|
showTargetBtn: Boolean,
|
||||||
},
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
hover: false,
|
hover: false,
|
||||||
|
loadingImg: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAXdJREFUWEftlq1vAkEQxedSB7pYDklBNsgmyCa4Cv5GBI6k8tJKUglULrWcBlvySA4B+/HeAcV07O3t/ObtvNnNHsyc3TEyAGRm+T0Yfs3WFwE0mk3bbbe12WsDtDsda+e5PbZaR4C1c/a9XEowtQC6vZ499ftniaCECiEDoPLnwSBa5WdRWLnZUErIAK+jkeHcY4HkgGBCAmCqr5KyKkgAobP3VXoTAJw9VGBitVhQjpAUeBkOD7Zj4sc5+5rPk0slAOUIkBwQqZAAUD1UYOImALAfbMjE+2xGjWhJASRmGpFtQOwnAzAqTCcTRqTDGhkAP8UGklK9BIDKq9svZUVcShjHZVkmnUApoNjvVHvAfBRFsCGTAJckr2AAAVf4Igqg+D7VdaHJGAVQRm8KIKRCFOBtPE7tK3333ZBBAOXuZyl8Fv1TAF8fBAGYkctWXq3zPdWCANdswJgdgwDM41NVwOeEf4BoE6be/3WO4PSdeARQN7vm+j0BlQ9wWvLB6AAAAABJRU5ErkJggg==',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -67,10 +86,33 @@ export default {
|
|||||||
return this.$listeners && !!this.$listeners.click;
|
return this.$listeners && !!this.$listeners.click;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
|
||||||
meteor: {
|
meteor: {
|
||||||
variables() {
|
healthBars() {
|
||||||
return CreatureVariables.findOne({ _creatureId: this.model._id }) || {};
|
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>
|
<style lang="css" scoped>
|
||||||
.small-title {
|
.small-title {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 4px;
|
padding: 4px 4px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
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>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div
|
<div
|
||||||
v-if="creatureId"
|
v-if="creatureId"
|
||||||
class="selected-creature-bar d-flex pa-3"
|
class="selected-creature-bar d-flex pa-3 align-end"
|
||||||
style="gap: 8px;"
|
style="gap: 8px;"
|
||||||
>
|
>
|
||||||
<!--
|
<!--
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
style="width: 300px;"
|
style="width: 300px;"
|
||||||
:style="{
|
:style="{
|
||||||
width: '300px',
|
width: '300px',
|
||||||
opacity: selectedIcon ? 1 : 0.5,
|
opacity: selectedIcon ? 1 : 0.7,
|
||||||
transition: 'opacity 0.2s ease',
|
transition: 'opacity 0.2s ease',
|
||||||
}"
|
}"
|
||||||
:model="selectedProp"
|
:model="selectedProp"
|
||||||
@@ -52,6 +52,54 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</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-card
|
||||||
v-for="group in iconGroups"
|
v-for="group in iconGroups"
|
||||||
:key="group.name"
|
:key="group.name"
|
||||||
@@ -259,6 +307,7 @@ export default {
|
|||||||
{ type: 'folder', groupStats: true },
|
{ type: 'folder', groupStats: true },
|
||||||
{ type: 'attribute' },
|
{ type: 'attribute' },
|
||||||
{ type: 'toggle' },
|
{ type: 'toggle' },
|
||||||
|
{ type: 'buff' }
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -314,6 +363,7 @@ export default {
|
|||||||
if (prop._placedInGroup) return;
|
if (prop._placedInGroup) return;
|
||||||
let groupName;
|
let groupName;
|
||||||
switch (prop.type) {
|
switch (prop.type) {
|
||||||
|
case 'buff': groupName = 'Buffs'; break;
|
||||||
case 'action': groupName = 'Actions'; break;
|
case 'action': groupName = 'Actions'; break;
|
||||||
case 'resource': groupName = 'Resources'; break;
|
case 'resource': groupName = 'Resources'; break;
|
||||||
case 'folder': groupName = 'Folders'; break;
|
case 'folder': groupName = 'Folders'; break;
|
||||||
@@ -321,7 +371,9 @@ export default {
|
|||||||
if (!groupName) return;
|
if (!groupName) return;
|
||||||
if (!groupsByName[groupName]) {
|
if (!groupsByName[groupName]) {
|
||||||
groupsByName[groupName] = { name: groupName, iconList: [] };
|
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 });
|
groupsByName[groupName].iconList.push({ propId: prop._id });
|
||||||
});
|
});
|
||||||
@@ -343,10 +395,16 @@ export default {
|
|||||||
|
|
||||||
iconGroups.push(...defaultGroups);
|
iconGroups.push(...defaultGroups);
|
||||||
|
|
||||||
|
// Store a specific reference to buffs outside of the list order
|
||||||
|
iconGroups.buffs = groupsByName['Buffs'];
|
||||||
|
|
||||||
// Divide the icons into rows
|
// Divide the icons into rows
|
||||||
iconGroups.forEach(group => {
|
iconGroups.forEach(group => {
|
||||||
group.rows = splitToNChunks(group.iconList, this.rows);
|
group.rows = splitToNChunks(group.iconList, this.rows);
|
||||||
});
|
});
|
||||||
|
if (iconGroups.buffs) {
|
||||||
|
iconGroups.buffs.rows = splitToNChunks(iconGroups.buffs.iconList, this.rows);
|
||||||
|
}
|
||||||
|
|
||||||
return iconGroups;
|
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