Iterated on Tabletop UX
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
import computeCreature from '/imports/api/engine/computeCreature';
|
||||
|
||||
/**
|
||||
* Recomputes all ancestor creatures of this property
|
||||
* @deprecated
|
||||
*/
|
||||
export default function recomputeCreaturesByProperty(property) {
|
||||
computeCreature.call(property.root.id);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import computeCreature from '/imports/api/engine/computeCreature';
|
||||
|
||||
export default function recomputeCreatureMixin(methodOptions) {
|
||||
let runFunc = methodOptions.run;
|
||||
methodOptions.run = function ({ charId }) {
|
||||
const result = runFunc.apply(this, arguments);
|
||||
if (
|
||||
methodOptions.skipRecompute &&
|
||||
methodOptions.skipRecompute.apply(this, arguments)
|
||||
) {
|
||||
return result;
|
||||
}
|
||||
computeCreature(charId);
|
||||
return result;
|
||||
};
|
||||
return methodOptions;
|
||||
}
|
||||
@@ -130,7 +130,6 @@ const ActionSchema = new SimpleSchema({
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-expect-error Collections2 lacks TypeScript support
|
||||
EngineActions.attachSchema(ActionSchema);
|
||||
|
||||
export default EngineActions;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import EngineActions, { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||
import mutationToPropUpdates from './mutationToPropUpdates';
|
||||
import mutationToLogUpdates from '/imports/api/engine/action/functions/mutationToLogUpdates';
|
||||
import { union } from 'lodash';
|
||||
import { union, uniq } from 'lodash';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
import bulkWrite from '/imports/api/engine/shared/bulkWrite';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
import computeCreature from '/imports/api/engine/computeCreature';
|
||||
|
||||
export default async function writeActionResults(action: EngineAction) {
|
||||
if (!action._id) throw new Meteor.Error('type-error', 'Action does not have an _id');
|
||||
@@ -32,14 +32,10 @@ export default async function writeActionResults(action: EngineAction) {
|
||||
// Write the bulk updates
|
||||
const bulkWritePromise = bulkWrite(creaturePropUpdates, CreatureProperties);
|
||||
|
||||
// Mark the creatures as dirty
|
||||
const creaturePromise = Creatures.updateAsync({
|
||||
_id: { $in: [action.creatureId, ...allTargetIds] },
|
||||
}, {
|
||||
$set: { dirty: true },
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
await Promise.all([engineActionPromise, logPromise, bulkWritePromise]);
|
||||
|
||||
return Promise.all([engineActionPromise, logPromise, bulkWritePromise, creaturePromise]);
|
||||
// Recompute the creatures involved
|
||||
const recomputePromises = uniq([action.creatureId, ...allTargetIds]).map(creatureId => computeCreature(creatureId));
|
||||
|
||||
return Promise.all(recomputePromises);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import EngineActions from '/imports/api/engine/action/EngineActions';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
||||
import { getCreature } from '/imports/api/engine/loadCreatures';
|
||||
@@ -10,7 +9,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
export const runAction = new ValidatedMethod({
|
||||
name: 'actions.runAction',
|
||||
validate: null,
|
||||
validate: null, //TODO
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 10,
|
||||
@@ -31,7 +30,6 @@ export const runAction = new ValidatedMethod({
|
||||
await applyAction(action, userInput);
|
||||
|
||||
// Persist changes
|
||||
const writePromise = writeActionResults(action);
|
||||
return writePromise;
|
||||
return writeActionResults(action);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,14 +4,15 @@ import {
|
||||
createTestCreature,
|
||||
getRandomIds,
|
||||
removeAllCreaturesAndProps,
|
||||
runActionById
|
||||
runActionById,
|
||||
TestCreature
|
||||
} from '/imports/api/engine/action/functions/actionEngineTest.testFn';
|
||||
|
||||
const [
|
||||
creatureId, silencedNoteId
|
||||
] = getRandomIds(2);
|
||||
|
||||
const actionTestCreature = {
|
||||
const actionTestCreature: TestCreature = {
|
||||
_id: creatureId,
|
||||
props: [
|
||||
{
|
||||
@@ -1,4 +1,5 @@
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
import VERSION from '/imports/constants/VERSION';
|
||||
|
||||
export default function writeErrorsAndPropCount(creatureId, errors = [], propCount) {
|
||||
if (errors.length) {
|
||||
@@ -7,6 +8,7 @@ export default function writeErrorsAndPropCount(creatureId, errors = [], propCou
|
||||
computeErrors: errors,
|
||||
propCount,
|
||||
lastComputedAt: new Date(),
|
||||
computeVersion: VERSION,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -14,6 +16,7 @@ export default function writeErrorsAndPropCount(creatureId, errors = [], propCou
|
||||
$set: {
|
||||
propCount,
|
||||
lastComputedAt: new Date(),
|
||||
computeVersion: VERSION,
|
||||
}, $unset: { computeErrors: 1 }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// in the UI because of incompatibility with latency compensation. If the
|
||||
// duplicate redraws can be fixed, this is a strictly better way of processing
|
||||
// writes
|
||||
export default function bulkWrite<T>(bulkWriteOps, collection: Mongo.Collection<T>): void | Promise<any> {
|
||||
export default async function bulkWrite<T>(bulkWriteOps, collection: Mongo.Collection<T>): Promise<any> {
|
||||
if (!bulkWriteOps.length) return;
|
||||
// bulkWrite is only available on the server
|
||||
if (!Meteor.isServer) {
|
||||
|
||||
@@ -17,10 +17,12 @@ type BaseDoActionParams = {
|
||||
type DoTaskParams = BaseDoActionParams & {
|
||||
task: Task;
|
||||
propId?: undefined;
|
||||
targetIds?: undefined;
|
||||
}
|
||||
|
||||
type DoActionParams = BaseDoActionParams & {
|
||||
propId: string;
|
||||
targetIds: string[];
|
||||
task?: undefined;
|
||||
}
|
||||
|
||||
@@ -30,12 +32,16 @@ type DoActionParams = BaseDoActionParams & {
|
||||
* the decisions the user makes, then applying the action as a method call to the server with the
|
||||
* saved decisions, which will persist the action results.
|
||||
*/
|
||||
export default async function doAction({ propId, creatureId, $store, elementId, task }: DoActionParams | DoTaskParams): Promise<any | void> {
|
||||
export default async function doAction({ propId, creatureId, $store, elementId, task, targetIds }: DoActionParams | DoTaskParams): Promise<any | void> {
|
||||
if (!task) {
|
||||
targetIds ??= [];
|
||||
if (!propId) throw new Meteor.Error('no-prop-id', 'Either propId or task must be provided');
|
||||
const prop = getSingleProperty(creatureId, propId);
|
||||
if (!prop) throw new Meteor.Error('not-found', 'Property not found');
|
||||
task = {
|
||||
prop: getSingleProperty(creatureId, propId),
|
||||
targetIds: [],
|
||||
prop,
|
||||
targetIds,
|
||||
subtaskFn: undefined,
|
||||
};
|
||||
}
|
||||
// Create the action
|
||||
@@ -52,12 +58,15 @@ export default async function doAction({ propId, creatureId, $store, elementId,
|
||||
// Get the inserted and cleaned action instance
|
||||
const action = EngineActions.findOne(actionId);
|
||||
|
||||
console.log(action);
|
||||
|
||||
if (!action) throw new Meteor.Error('not-found', 'The action could not be found');
|
||||
|
||||
// Applying the action is deterministic, so we apply it, if it asks for user input, we escape and
|
||||
// create a dialog that will re-apply the action, but with the ability to actually get input
|
||||
// Either way, call the action method afterwards
|
||||
try {
|
||||
if (!action._id) throw new Meteor.Error('no-action-id', 'Action ID is required');
|
||||
const finishedAction = await applyAction(
|
||||
action, getErrorOnInputRequestProvider(action._id), { simulate: true }
|
||||
);
|
||||
@@ -96,7 +105,7 @@ const throwInputRequestedError = () => {
|
||||
throw 'input-requested';
|
||||
}
|
||||
|
||||
function getErrorOnInputRequestProvider(actionId) {
|
||||
function getErrorOnInputRequestProvider(actionId: string) {
|
||||
const errorOnInputRequest: InputProvider = {
|
||||
targetIds: throwInputRequestedError,
|
||||
nextStep: throwInputRequestedError,
|
||||
|
||||
@@ -185,4 +185,3 @@ export default {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
resolveimport { toString } from '/imports/parser/toString';
|
||||
|
||||
@@ -3,48 +3,60 @@
|
||||
<div
|
||||
v-for="(contentGroup, index) in contentByTargetId"
|
||||
:key="index"
|
||||
class="d-flex justify-space-between"
|
||||
>
|
||||
<h3
|
||||
<div class="d-flex flex-wrap">
|
||||
<div
|
||||
v-for="(content, contentIndex) in contentGroup.content"
|
||||
:key="contentIndex"
|
||||
class="mx-2 my-1"
|
||||
:class="{'full-width': !content.inline}"
|
||||
>
|
||||
<div
|
||||
class="content-name text-body"
|
||||
>
|
||||
{{ content.name }}
|
||||
</div>
|
||||
<markdown-text
|
||||
v-if="content.value"
|
||||
class="content-value text-body-2"
|
||||
:markdown="content.value"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
style="min-height: 12px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="contentGroup.targetIds.length"
|
||||
class="content-target-ids"
|
||||
class="content-target-ids d-flex flex-column justify-center"
|
||||
>
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
<v-list-item-avatar
|
||||
<v-tooltip
|
||||
v-for="creature in contentGroup.targetCreatures"
|
||||
:key="creature._id"
|
||||
:color="model.color || 'grey'"
|
||||
size="32"
|
||||
left
|
||||
>
|
||||
<img
|
||||
v-if="creature.avatarPicture"
|
||||
:src="creature.avatarPicture"
|
||||
:alt="creature.name"
|
||||
>
|
||||
<span v-else>
|
||||
{{ creature.name && creature.name[0] || '?' }}
|
||||
</span>
|
||||
</v-list-item-avatar>
|
||||
</h3>
|
||||
<div
|
||||
v-for="(content, contentIndex) in contentGroup.content"
|
||||
:key="contentIndex"
|
||||
class="content-line"
|
||||
>
|
||||
<h4
|
||||
class="content-name"
|
||||
style="min-height: 12px;"
|
||||
>
|
||||
{{ content.name }}
|
||||
</h4>
|
||||
<markdown-text
|
||||
v-if="content.value"
|
||||
class="content-value"
|
||||
:markdown="content.value"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
style="min-height: 12px;"
|
||||
/>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-list-item-avatar
|
||||
:color="model.color || 'grey'"
|
||||
size="28"
|
||||
class="ma-2"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<img
|
||||
v-if="creature.avatarPicture"
|
||||
:src="creature.avatarPicture"
|
||||
:alt="creature.name"
|
||||
>
|
||||
<span v-else>
|
||||
{{ creature.name && creature.name[0] || '?' }}
|
||||
</span>
|
||||
</v-list-item-avatar>
|
||||
</template>
|
||||
<span>{{ creature.name }}</span>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,19 +116,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.content-line {
|
||||
min-height: 24px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
/** change the first content line to have no margin top*/
|
||||
.content-line:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.content-line .details {
|
||||
display: inline-block;
|
||||
}
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
.content-target-ids {
|
||||
border-left: solid 1px hsl(0deg 0% 50% / 20%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
@@ -34,7 +34,6 @@ const SingleLibraryToolbar = () => import('/imports/client/ui/library/SingleLibr
|
||||
const Tabletops = () => import('/imports/client/ui/pages/Tabletops.vue');
|
||||
const Tabletop = () => import('/imports/client/ui/pages/Tabletop.vue');
|
||||
const TabletopToolbar = () => import('/imports/client/ui/tabletop/TabletopToolbar.vue');
|
||||
const TabletopRightDrawer = () => import('/imports/client/ui/tabletop/TabletopRightDrawer.vue');
|
||||
const Admin = () => import('/imports/client/ui/pages/Admin.vue');
|
||||
const Maintenance = () => import('/imports/client/ui/pages/Maintenance.vue');
|
||||
const Files = () => import('/imports/client/ui/pages/Files.vue');
|
||||
@@ -216,7 +215,6 @@ RouterFactory.configure(router => {
|
||||
components: {
|
||||
default: Tabletop,
|
||||
toolbar: TabletopToolbar,
|
||||
rightDrawer: TabletopRightDrawer,
|
||||
},
|
||||
beforeEnter: ensureLoggedIn,
|
||||
}, {
|
||||
|
||||
@@ -135,7 +135,7 @@ export default {
|
||||
},
|
||||
targets: {
|
||||
type: Array,
|
||||
default: undefined,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -221,6 +221,7 @@ export default {
|
||||
doAction({
|
||||
propId: this.model._id,
|
||||
creatureId: this.model.root.id,
|
||||
targetIds: this.targets,
|
||||
$store: this.$store,
|
||||
elementId: 'do-action-button',
|
||||
}).catch((e) => {
|
||||
@@ -249,6 +250,7 @@ export default {
|
||||
height .3s ease;
|
||||
max-width: 100vw;
|
||||
position: relative;
|
||||
max-height: calc(100vh - 144px);
|
||||
}
|
||||
.action-card.tabletop-active {
|
||||
margin-top: -100px;
|
||||
|
||||
@@ -3,16 +3,6 @@
|
||||
class="tabletop layout column"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<tabletop-map
|
||||
class="play-area"
|
||||
style="
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
"
|
||||
/>
|
||||
<v-container
|
||||
fluid
|
||||
>
|
||||
@@ -22,6 +12,12 @@
|
||||
style="flex-wrap: nowrap; overflow-x: auto; padding-bottom: 50px; min-width: 200px;"
|
||||
@wheel="transformScroll($event)"
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
@click="toggleDrawer"
|
||||
>
|
||||
<v-icon> mdi-menu </v-icon>
|
||||
</v-btn>
|
||||
<tabletop-creature-card
|
||||
v-for="creature in creatures"
|
||||
:key="creature._id"
|
||||
@@ -71,9 +67,19 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-row>
|
||||
<div
|
||||
class="d-flex align-stretch"
|
||||
style="max-height: calc(100vh - 364px);"
|
||||
>
|
||||
<v-spacer />
|
||||
<tabletop-log-stream
|
||||
:tabletop-id="$route.params.id"
|
||||
class="pl-4"
|
||||
style="overflow: auto; max-width: 500px;"
|
||||
/>
|
||||
</div>
|
||||
</v-container>
|
||||
<v-footer
|
||||
inset
|
||||
class="pa-0"
|
||||
style="
|
||||
background: none;
|
||||
@@ -89,12 +95,25 @@
|
||||
<v-slide-y-reverse-transition mode="out-in">
|
||||
<selected-creature-bar
|
||||
:key="activeCreatureId"
|
||||
ref="selectedCreatureBar"
|
||||
:creature-id="activeCreatureId"
|
||||
:targets="targets"
|
||||
@active-action-change="activeActionId = $event"
|
||||
@remove="removeCreature(activeCreatureId)"
|
||||
/>
|
||||
</v-slide-y-reverse-transition>
|
||||
</v-footer>
|
||||
<tabletop-map
|
||||
class="play-area"
|
||||
style="
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: -50;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -102,6 +121,7 @@
|
||||
import addCreaturesToTabletop from '/imports/api/tabletop/methods/addCreaturesToTabletop';
|
||||
import TabletopCreatureCard from '/imports/client/ui/tabletop/TabletopCreatureCard.vue';
|
||||
import TabletopMap from '/imports/client/ui/tabletop/TabletopMap.vue';
|
||||
import TabletopLogStream from '/imports/client/ui/tabletop/TabletopLogStream.vue';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
@@ -110,6 +130,8 @@ import SelectedCreatureBar from '/imports/client/ui/tabletop/selectedCreatureBar
|
||||
import addCreaturesFromLibraryToTabletop from '/imports/api/tabletop/methods/addCreaturesFromLibraryToTabletop';
|
||||
import removeCreatureFromTabletop from '/imports/api/tabletop/methods/removeCreatureFromTabletop';
|
||||
import { getFilter } from '/imports/api/parenting/parentingFunctions';
|
||||
import { mapMutations } from 'vuex';
|
||||
import doAction from '/imports/client/ui/creature/actions/doAction';
|
||||
|
||||
const getProperties = function (creatureId, selector = {}) {
|
||||
return CreatureProperties.find({
|
||||
@@ -132,6 +154,7 @@ export default {
|
||||
TabletopCreatureCard,
|
||||
TabletopMap,
|
||||
SelectedCreatureBar,
|
||||
TabletopLogStream,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
@@ -154,9 +177,24 @@ export default {
|
||||
activeCreatureId(id) {
|
||||
this.$root.$emit('active-tabletop-character-change', id);
|
||||
},
|
||||
activeActionId(id) {
|
||||
activeActionId() {
|
||||
this.targets = [];
|
||||
},
|
||||
targets(val) {
|
||||
if (val.length === 1 && this.activeAction?.target === 'singleTarget') {
|
||||
doAction({
|
||||
propId: this.activeActionId,
|
||||
creatureId: this.activeCreatureId,
|
||||
targetIds: this.targets,
|
||||
$store: this.$store,
|
||||
elementId: 'tabletop-action-card',
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
snackbar({ text: e.message || e.reason || e.toString() });
|
||||
});
|
||||
this.$refs.selectedCreatureBar.selectedIcon = undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
creatures(){
|
||||
@@ -165,10 +203,11 @@ export default {
|
||||
actions(){
|
||||
return getProperties(this.activeCreatureId, { type: 'action', actionType: { $ne: 'event'} });
|
||||
},
|
||||
activeAction() {
|
||||
return CreatureProperties.findOne(this.activeActionId);
|
||||
},
|
||||
moreTargets() {
|
||||
// Disable portrait targeting for now, they aren't used by the action engine yet
|
||||
return false;
|
||||
const activeAction = CreatureProperties.findOne(this.activeActionId);
|
||||
const activeAction = this.activeAction;
|
||||
if (!activeAction) return;
|
||||
if (activeAction.target === 'singleTarget') {
|
||||
return this.targets.length === 0;
|
||||
@@ -186,6 +225,9 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'toggleDrawer',
|
||||
]),
|
||||
addCreature() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'select-creatures-dialog',
|
||||
|
||||
@@ -37,8 +37,9 @@
|
||||
v-if="showTargetBtn"
|
||||
:color="targeted ? 'accent' : ''"
|
||||
:elevation="targeted ? 8 : 2"
|
||||
fab
|
||||
style="margin-top: -16px;"
|
||||
small
|
||||
fab
|
||||
@click.stop.prevent="targeted ? $emit('untarget') : $emit('target')"
|
||||
>
|
||||
<v-icon>{{ targeted ? 'mdi-target' : 'mdi-target' }}</v-icon>
|
||||
@@ -50,12 +51,10 @@
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
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: {
|
||||
@@ -87,21 +86,9 @@ export default {
|
||||
},
|
||||
meteor: {
|
||||
healthBars() {
|
||||
const folderIds = CreatureProperties.find({
|
||||
'root.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({
|
||||
'root.id': this.model._id,
|
||||
'parentId': {
|
||||
$nin: folderIds,
|
||||
},
|
||||
type: 'attribute',
|
||||
attributeType: 'healthBar',
|
||||
healthBarNoDamage: { $ne: true },
|
||||
|
||||
43
app/imports/client/ui/tabletop/TabletopLogStream.vue
Normal file
43
app/imports/client/ui/tabletop/TabletopLogStream.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="d-flex flex-column-reverse"
|
||||
style="overflow: auto;"
|
||||
>
|
||||
<tabletop-log-stream-entry
|
||||
v-for="log in logs"
|
||||
:key="log._id"
|
||||
:model="log"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
import TabletopLogStreamEntry from '/imports/client/ui/tabletop/TabletopLogStreamEntry.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TabletopLogStreamEntry,
|
||||
},
|
||||
props: {
|
||||
tabletopId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
logs() {
|
||||
const filter = {};
|
||||
if (this.tabletopId) {
|
||||
filter.tabletopId = this.tabletopId;
|
||||
} else if (this.creatureId) {
|
||||
filter.creatureId = this.creatureId;
|
||||
}
|
||||
return CreatureLogs.find(filter, {
|
||||
sort: {date: -1},
|
||||
limit: 100
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
72
app/imports/client/ui/tabletop/TabletopLogStreamEntry.vue
Normal file
72
app/imports/client/ui/tabletop/TabletopLogStreamEntry.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template lang="html">
|
||||
<div class="pa-2 pt-0 my-2 stream-entry">
|
||||
<v-list-item
|
||||
v-if="model.creatureId"
|
||||
dense
|
||||
class="pl-0"
|
||||
>
|
||||
<v-list-item-avatar
|
||||
:color="model.color || 'grey'"
|
||||
size="32"
|
||||
>
|
||||
<img
|
||||
v-if="creature.avatarPicture"
|
||||
:src="creature.avatarPicture"
|
||||
:alt="creature.name"
|
||||
>
|
||||
<span v-else>
|
||||
{{ creature.name && creature.name[0] || '?' }}
|
||||
</span>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ creature.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<tabletop-log-content
|
||||
v-if="model.text || (model.content && model.content.length)"
|
||||
:model="model.content"
|
||||
:show-silenced="showSilenced"
|
||||
class="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import TabletopLogContent from '/imports/client/ui/log/TabletopLogContent.vue';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||
|
||||
// TODO move content filtering to this component so we can determine if any content was hidden
|
||||
// then show a button to reveal silenced content at a lower opacity
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TabletopLogContent,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showName: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSilenced: false,
|
||||
};
|
||||
},
|
||||
meteor: {
|
||||
creature() {
|
||||
return Creatures.findOne(this.model.creatureId);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.stream-entry {
|
||||
background-color: hsl(0deg 0% 50% / 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +1,9 @@
|
||||
<template lang="html">
|
||||
<v-app-bar
|
||||
app
|
||||
dark
|
||||
clipped-right
|
||||
dense
|
||||
color="secondary"
|
||||
>
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
<v-toolbar-title>
|
||||
Tabletop
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-app-bar-nav-icon @click="toggleRightDrawer" />
|
||||
</v-app-bar>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { mapMutations } from 'vuex';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'toggleDrawer',
|
||||
'toggleRightDrawer',
|
||||
]),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
transition: 'opacity 0.2s ease',
|
||||
}"
|
||||
:model="selectedProp"
|
||||
:targets="targets"
|
||||
data-id="tabletop-action-card"
|
||||
@close-menu="menuOpen = false"
|
||||
@dialog-opened="menuOpen = false"
|
||||
@@ -186,6 +187,10 @@ export default {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
targets: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user