Files
DiceCloud/app/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.vue
2023-07-17 15:56:30 +02:00

374 lines
11 KiB
Vue

<template lang="html">
<div
v-if="creatureId"
class="selected-creature-bar d-flex pa-3 justify-center"
style="gap: 8px; min-width: 100%;"
>
<!--
<tabletop-buff-icons
creature-id="creatureId"
@select-icon="selectIcon"
/>
<tabletop-portrait
creature-id="creatureId"
@select-icon="selectIcon"
/>
-->
<v-menu
v-model="menuOpen"
v-click-outside="{
handler: clickOutsideMenu,
include: menuClickOutsideInclude,
}"
:position-x="menuX"
:position-y="menuY"
absolute
top
:nudge-left="150"
origin="center bottom"
:close-on-click="false"
:content-class="`tabletop-prop-menu rows-${rows}`"
:close-on-content-click="false"
>
<tabletop-action-card
v-if="selectedProp && selectedProp.type === 'action'"
style="width: 300px;"
:style="{
width: '300px',
opacity: selectedIcon ? 1 : 0.5,
transition: 'opacity 0.2s ease',
}"
:model="selectedProp"
/>
<v-card
v-else-if="activeIcon && activeIcon.tab"
style="width: 300px"
>
<v-card-title>
<v-icon left>
{{ activeIcon.icon }}
</v-icon>
{{ activeIcon.tabName }}
</v-card-title>
</v-card>
</v-menu>
<v-card
v-for="group in iconGroups"
:key="group.name"
>
<div
v-for="(row, rowIndex) in group.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>
<!--<tabletop-actions
creature-id="creatureId"
@select-icon="selectIcon"
/>
<tabletop-detail-popover />
-->
</div>
</template>
<script lang="js">
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import TabletopActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
import CreatureBarIcon from '/imports/client/ui/tabletop/selectedCreatureBar/CreatureBarIcon.vue';
//import TabletopPortrait from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopPortrait.vue';
//import TabletopBuffIcons from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopBuffIcons.vue';
//import TabletopActions from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopActions.vue';
//import TabletopGroupedFolders from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopGroupedFolders.vue';
//import TabletopResources from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopResources.vue';
//import TabletopCreatureSheetTabs from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopCreatureSheetTabs.vue';
//import TabletopDetailPopover from '/imports/client/ui/tabletop/selectedCreatureBar/TabletopDetailPopover.vue';
function splitToNChunks(inputArray, n) {
let result = [];
const array = [...inputArray] // Create shallow copy, because splice mutates array
for (let i = n; i > 0; i--) {
result.push(array.splice(0, Math.ceil(array.length / i)));
}
return result;
}
export default {
components: {
//TabletopPortrait,
//TabletopBuffIcons,
//TabletopActions,
//TabletopGroupedFolders,
//TabletopResources,
//TabletopCreatureSheetTabs,
CreatureBarIcon,
TabletopActionCard,
},
props: {
creatureId: {
type: String,
default: undefined,
},
},
data() {
return {
rows: 2,
hoveredIcon: undefined,
selectedIcon: undefined,
menuOpen: false,
menuX: 200,
menuY: window.innerHeight - 200,
};
},
computed: {
activeIcon() {
return this.selectedIcon || this.hoveredIcon;
}
},
watch: {
menuOpen(val) {
if (!val && this.selectIcon) {
this.selectedIcon = undefined;
}
},
},
methods: {
log(e) {
console.log(e);
},
hoverIcon(e, icon) {
if (this.selectedIcon) return;
// this.menuX = e.clientX - (e.clientX % 44);
const { left, right } = e.target.getBoundingClientRect();
const x = ( left + right ) / 2
this.menuX = x;
this.hoveredIcon = icon;
this.menuOpen = true;
},
unHoverIcon(icon) {
if (this.hoveredIcon === icon) {
this.hoveredIcon = undefined;
if (!this.selectedIcon) {
this.menuOpen = false;
}
}
},
selectIcon(e, icon) {
if (icon.tab) {
this.openCharacterSheet(icon.tab, icon.standardId);
return;
}
if (this.selectedIcon === icon) {
this.selectedIcon = undefined;
this.menuOpen = false;
return;
}
const { left, right } = e.target.getBoundingClientRect();
const x = ( left + right ) / 2
this.menuX = x;
this.selectedIcon = icon;
this.menuOpen = true;
},
clickOutsideMenu () {
this.menuOpen = false;
},
menuClickOutsideInclude() {
return [
document.querySelector('.selected-creature-bar'),
document.querySelector('.tabletop-prop-menu')
];
},
openCharacterSheet(tab, elementId) {
this.$store.commit(
'setTabForCharacterSheet',
{ id: this.creatureId, tab }
);
this.$store.commit('pushDialogStack', {
component: 'character-sheet-dialog',
elementId,
data: {
creatureId: this.creatureId,
},
});
},
},
meteor: {
creature() {
if (!this.creatureId) return;
return Creatures.findOne(this.creatureId)
},
selectedProp() {
const propId = this.activeIcon?.propId;
if (!propId) return;
return CreatureProperties.findOne(propId);
},
iconGroups() {
if (!this.creature) return;
const iconGroups = [];
// Get the standard icons
const standardIconsById = {
'cast-spell': {standardId: 'cast-spell', groupName: 'Standard Actions', icon: 'mdi-fire' },
'make-check': {standardId: 'make-check', groupName: 'Standard Actions', icon: 'mdi-radiobox-marked' },
'roll-dice': {standardId: 'roll-dice', groupName: 'Standard Actions', icon: 'mdi-dice-d20' },
'tab-stats': {standardId: 'tab-stats', groupName: 'Tabs', icon: 'mdi-chart-box', tab: 'stats', tabName: 'Stats' },
'tab-actions': {standardId: 'tab-actions', groupName: 'Tabs', icon: 'mdi-lightning-bolt', tab: 'actions', tabName: 'Actions' },
'tab-spells': this.creature?.settings?.hideSpellsTab ? undefined : {standardId: 'tab-spells', groupName: 'Tabs', icon: 'mdi-fire', tab: 'spells', tabName: 'Spells' },
'tab-inventory': {standardId: 'tab-inventory', groupName: 'Tabs', icon: 'mdi-cube', tab: 'inventory', tabName: 'Inventory' },
'tab-features': {standardId: 'tab-features', groupName: 'Tabs', icon: 'mdi-text', tab: 'features', tabName: 'Features' },
'tab-journal': {standardId: 'tab-journal', groupName: 'Tabs', icon: 'mdi-book-open-variant', tab: 'journal', tabName: 'Journal' },
'tab-build': {standardId: 'tab-build', groupName: 'Tabs', icon: 'mdi-wrench', tab: 'build', tabName: 'Build' },
};
// Get the folders that could hide a property
const folderIds = CreatureProperties.find({
'ancestors.id': this.creatureId,
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 an icon
const filter = {
'ancestors.id': this.creatureId,
'parent.id': {
$nin: folderIds,
},
$and: [
{
$or: [
{ type: 'action' },
{ type: 'folder', groupStats: true },
{ type: 'attribute' },
{ type: 'toggle' },
],
},
{
$or: [
{ inactive: { $ne: true } },
{ type: 'toggle' },
]
}
],
removed: { $ne: true },
};
if (this.creature.settings?.hideUnusedStats) {
filter.hide = { $ne: true };
}
// Get all the properties we wish to display, with just their IDs, and store them
const propsById = {};
const props = [];
CreatureProperties.find(filter, {
sort: { order: -1 },
fields: { _id: 1, type: 1 },
}).forEach(prop => {
props.push(prop);
propsById[prop._id] = prop;
});
// Using the creature's custom icon groups, collect the props into groups
this.creature.tabletopSettings?.iconGroups.forEach(group => {
const iconList = [];
group.iconIds?.forEach(id => {
if (propsById[id]) {
const prop = propsById[id];
prop._placedInGroup = true;
iconList.push({ propId: prop._id });
} else if (standardIconsById[id]) {
const standardIcon = standardIconsById[id];
standardIcon._placedInGroup = true;
iconList.push(standardIcon);
}
});
iconGroups.push({
name: group.name,
iconList,
});
});
// Default groups
let groupsByName = {};
let defaultGroups = [];
// Add default groups for props that have not yet been collected into custom groups
props.forEach(prop => {
if (prop._placedInGroup) return;
let groupName;
switch (prop.type) {
case 'action': groupName = 'Actions'; break;
case 'resource': groupName = 'Resources'; break;
case 'folder': groupName = 'Folders'; break;
}
if (!groupName) return;
if (!groupsByName[groupName]) {
groupsByName[groupName] = { name: groupName, iconList: [] };
defaultGroups.push(groupsByName[groupName]);
}
groupsByName[groupName].iconList.push({ propId: prop._id });
});
// Add default groups for standard icons
for (let key in standardIconsById) {
const standardIcon = standardIconsById[key];
if (!standardIcon) continue;
if (standardIcon._placedInGroup) continue;
const groupName = standardIcon.groupName || 'no';
if (!groupsByName[groupName]) {
groupsByName[groupName] = { name: groupName, iconList: [] };
defaultGroups.push(groupsByName[groupName]);
}
groupsByName[groupName].iconList.push(standardIcon);
}
iconGroups.push(...defaultGroups);
// Divide the icons into rows
iconGroups.forEach(group => {
group.rows = splitToNChunks(group.iconList, this.rows);
});
return iconGroups;
}
},
}
</script>
<style lang="css">
.tabletop-prop-menu {
top: unset !important;
transition: all 0.2s ease;
}
.tabletop-prop-menu.rows-1 {
bottom: 68px;
}
.tabletop-prop-menu.rows-2 {
bottom: 112px;
}
.tabletop-prop-menu.rows-3 {
bottom: 156px;
}
.tabletop-prop-menu.rows-4 {
bottom: 200px;
}
</style>