Progress on Tabletop CRPG-style design

This commit is contained in:
Stefan Zermatten
2023-07-07 13:49:31 +02:00
parent eef4adfab7
commit a568510f43
5 changed files with 172 additions and 82 deletions

View File

@@ -241,7 +241,6 @@ export default {
return undefined;
}
},
// @ts-ignore
meteor: {
children() {
const indicesOfTerminatingProps = [];

View File

@@ -72,37 +72,23 @@
<v-footer
inset
class="pa-0"
style="background: none;
box-shadow: none;
position: absolute;
left: 0;
bottom:0;
right: 0;"
style="
background: none;
box-shadow: none;
position: absolute;
left: 0;
bottom:0;
right: 0;
overflow-x: auto;
overflow-y: hidden;
"
>
<v-container fluid>
<v-row
dense
class="action-row overflow-x-auto align-end"
style="flex-wrap: nowrap; padding-top: 100px;"
@wheel="transformScroll($event)"
>
<mini-character-sheet
v-if="activeCreatureId"
data-id="mini-character-sheet"
:creature-id="activeCreatureId"
@click="openCharacterSheetDialog"
/>
<action-card
v-for="action in actions"
:key="action._id"
:model="action"
:active="activeActionId === action._id"
:targets="targets"
@activate="activeActionId = action._id"
@deactivate="activeActionId = undefined; targets = [];"
/>
</v-row>
</v-container>
<v-slide-y-reverse-transition mode="out-in">
<selected-creature-bar
:key="activeCreatureId"
:creature-id="activeCreatureId"
/>
</v-slide-y-reverse-transition>
</v-footer>
</div>
</template>
@@ -117,6 +103,7 @@ import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
import ActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
import SelectedCreatureBar from '/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.vue';
const getProperties = function (creatureId, selector = {}) {
return CreatureProperties.find({
@@ -142,6 +129,7 @@ export default {
TabletopMap,
ActionCard,
MiniCharacterSheet,
SelectedCreatureBar,
},
props: {
model: {

View File

@@ -0,0 +1,47 @@
<template lang="html">
<v-btn
icon
plain
large
@click="$emit('click')"
@mouseover="$emit('hover', $event)"
>
<property-icon
v-if="prop"
:model="prop"
/>
<v-icon v-else-if="icon">
{{ icon }}
</v-icon>
<v-icon v-else>
mdi-help
</v-icon>
</v-btn>
</template>
<script lang="js">
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue';
export default {
components: {
PropertyIcon,
},
props: {
propId: {
type: String,
default: undefined,
},
icon: {
type: String,
default: undefined,
}
},
meteor: {
prop() {
if (!this.propId) return;
return CreatureProperties.findOne(this.propId);
},
},
}
</script>

View File

@@ -1,8 +1,10 @@
<template lang="html">
<div
v-if="creatureId"
class="selected-creature-bar d-flex"
class="selected-creature-bar d-flex pa-4"
style="gap: 8px;"
>
<!--
<tabletop-buff-icons
creature-id="creatureId"
@select-icon="selectIcon"
@@ -11,75 +13,124 @@
creature-id="creatureId"
@select-icon="selectIcon"
/>
<tabletop-actions
creature-id="creatureId"
@select-icon="selectIcon"
/>
<tabletop-grouped-folders
creature-id="creatureId"
@select-icon="selectIcon"
/>
<tabletop-resources
creature-id="creatureId"
@select-icon="selectIcon"
/>
<tabletop-creature-sheet-tabs
-->
<v-menu
v-model="selectedProp"
:position-x="menuX"
:position-y="menuY"
absolute
offset-y
:close-on-content-click="false"
>
<tabletop-action-card
v-if="selectedProp && selectedProp.type === 'action'"
:model="selectedProp"
/>
</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"
@click="selectedIcon = icon"
@hover="e => {selectedIcon = icon; menuX = e.clientX; menuY = e.clientY; log(e)}"
/>
</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';
//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,
},
//TabletopPortrait,
//TabletopBuffIcons,
//TabletopActions,
//TabletopGroupedFolders,
//TabletopResources,
//TabletopCreatureSheetTabs,
CreatureBarIcon,
TabletopActionCard,
},
props: {
creatureId: {
type: String,
default: undefined,
},
},.
},
data() {
return {
rows: 2,
selectedIcon: undefined,
menuX: 200,
menuY: 200,
};
},
meteor: {
creature() {
if (!this.creatureId) return;
return Creatures.findOne(this.creatureId)
},
selectedProp() {
if (!this.selectedIcon?.propId) return;
return CreatureProperties.findOne(this.selectedIcon.propId);
},
iconGroups() {
if (!this.creature) return;
const iconGroups = [];
// Get the standard icons
const standardIconsById = {
'cast-spell': { groupName: 'standardActions' },
'make-check': { groupName: 'standardActions' },
'roll-dice': { groupName: 'standardActions' },
'tab-stats': { groupName: 'tabs' },
'tab-actions': { groupName: 'tabs' },
'tab-spells': { groupName: 'tabs' },
'tab-inventory': { groupName: 'tabs' },
'tab-features': { groupName: 'tabs' },
'tab-journal': { groupName: 'tabs' },
'tab-build': { groupName: 'tabs' },
'cast-spell': { groupName: 'Standard Actions', icon: 'mdi-fire' },
'make-check': { groupName: 'Standard Actions', icon: 'mdi-radiobox-marked' },
'roll-dice': { groupName: 'Standard Actions', icon: 'mdi-dice-d20' },
'tab-stats': { groupName: 'Tabs', icon: 'mdi-chart-box' },
'tab-actions': { groupName: 'Tabs', icon: 'mdi-lightning-bolt' },
'tab-spells': { groupName: 'Tabs', icon: 'mdi-fire' },
'tab-inventory': { groupName: 'Tabs', icon: 'mdi-cube' },
'tab-features': { groupName: 'Tabs', icon: 'mdi-text' },
'tab-journal': { groupName: 'Tabs', icon: 'mdi-book-open-variant' },
'tab-build': { groupName: 'Tabs', icon: 'mdi-wrench' },
};
// Get the folders that could hide a property
@@ -125,7 +176,7 @@ export default {
const props = [];
CreatureProperties.find(filter, {
sort: { order: -1 },
fields: { _id: 1 },
fields: { _id: 1, type: 1 },
}).forEach(prop => {
props.push(prop);
propsById[prop._id] = prop;
@@ -164,6 +215,7 @@ export default {
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]);
@@ -176,14 +228,22 @@ export default {
const standardIcon = standardIconsById[key];
if (standardIcon._placedInGroup) return;
const groupName = standardIcon.groupName;
const groupName = standardIcon.groupName || 'no';
if (!groupsByName[groupName]) {
groupsByName[groupName] = { name: groupName, iconList: [] };
defaultGroups.push(groupsByName[groupName]);
}
groupsByName[groupName].iconList.push({ standardId: key });
groupsByName[groupName].iconList.push({ standardId: key, icon: standardIcon.icon });
}
iconGroups.push(...defaultGroups);
// Divide the icons into rows
iconGroups.forEach(group => {
group.rows = splitToNChunks(group.iconList, this.rows);
});
return iconGroups;
}
},
@@ -191,16 +251,12 @@ export default {
selectIcon(e) {
this.$emit('select-icon', e);
},
log(e) {
console.log(e);
},
}
}
</script>
<style lang="css" scoped>
.selected-creature-bar {
height: 140px;
max-height: 30%;
width: 100%;
overflow-x: auto;
overflow-y: visible;
}
</style>

2
app/package-lock.json generated
View File

@@ -1917,7 +1917,7 @@
"lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
"integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA="
"integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg=="
},
"lodash.template": {
"version": "4.5.0",