Big improvements in UX for tabletop actions
This commit is contained in:
@@ -43,12 +43,17 @@ let CreatureLogSchema = new SimpleSchema({
|
||||
type: String,
|
||||
index: 1,
|
||||
},
|
||||
// The tabletops this log is associated with
|
||||
// The tabletop this log is associated with
|
||||
tabletopId: {
|
||||
type: String,
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
// The action that caused this log entry
|
||||
actionId: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
creatureName: {
|
||||
type: String,
|
||||
optional: true,
|
||||
|
||||
@@ -27,10 +27,12 @@ export default async function writeActionResults(action: EngineAction) {
|
||||
content: logContents,
|
||||
creatureId: action.creatureId,
|
||||
tabletopId: action.tabletopId,
|
||||
actionId: action._id,
|
||||
});
|
||||
|
||||
// Write the bulk updates
|
||||
const bulkWritePromise = bulkWrite(creaturePropUpdates, CreatureProperties);
|
||||
// Write the bulk updates, force them to sequential mode means we immediately get the results
|
||||
// in the subscription, rather than waiting for oplog tailing to catch up
|
||||
const bulkWritePromise = bulkWrite(creaturePropUpdates, CreatureProperties, true);
|
||||
|
||||
await Promise.all([engineActionPromise, logPromise, bulkWritePromise]);
|
||||
|
||||
|
||||
@@ -116,24 +116,27 @@ export default async function applyDamagePropTask(
|
||||
if (increment > currentValue && !targetProp.ignoreLowerLimit) increment = currentValue;
|
||||
// Can't decrease damage below zero
|
||||
if (-increment > currentDamage && !targetProp.ignoreUpperLimit) increment = -currentDamage;
|
||||
damage = currentDamage + increment;
|
||||
newValue = targetProp.total - damage;
|
||||
// Write the results
|
||||
result.mutations.push({
|
||||
targetIds: [targetId],
|
||||
updates: [{
|
||||
propId: targetProp._id,
|
||||
inc: { damage: increment, value: -increment },
|
||||
type: targetProp.type,
|
||||
}],
|
||||
contents: [{
|
||||
name: increment >= 0 ? 'Attribute damaged' : 'Attribute restored',
|
||||
value: `${numberToSignedString(-increment)} ${getPropertyTitle(targetProp)}`,
|
||||
inline: true,
|
||||
...task.silent && { silenced: true },
|
||||
}]
|
||||
});
|
||||
if (targetId === action.creatureId) setScope(result, targetProp, newValue, damage);
|
||||
// Only increment if the increment is non-zero
|
||||
if (increment !== 0) {
|
||||
damage = currentDamage + increment;
|
||||
newValue = targetProp.total - damage;
|
||||
// Write the results
|
||||
result.mutations.push({
|
||||
targetIds: [targetId],
|
||||
updates: [{
|
||||
propId: targetProp._id,
|
||||
inc: { damage: increment, value: -increment },
|
||||
type: targetProp.type,
|
||||
}],
|
||||
contents: [{
|
||||
name: increment >= 0 ? 'Attribute damaged' : 'Attribute restored',
|
||||
value: `${numberToSignedString(-increment)} ${getPropertyTitle(targetProp)}`,
|
||||
inline: true,
|
||||
...task.silent && { silenced: true },
|
||||
}]
|
||||
});
|
||||
if (targetId === action.creatureId) setScope(result, targetProp, newValue, damage);
|
||||
}
|
||||
}
|
||||
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.after', userInput);
|
||||
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.afterChildren', userInput);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// 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 async function bulkWrite<T>(bulkWriteOps, collection: Mongo.Collection<T>): Promise<any> {
|
||||
export default async function bulkWrite<T>(bulkWriteOps, collection: Mongo.Collection<T>, forceSequential?: true): Promise<any> {
|
||||
if (!bulkWriteOps.length) return;
|
||||
// bulkWrite is only available on the server
|
||||
if (!Meteor.isServer) {
|
||||
if (!Meteor.isServer || forceSequential) {
|
||||
return writePropertiesSequentially(bulkWriteOps, collection);
|
||||
}
|
||||
return collection.rawCollection().bulkWrite(
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
<template lang="html">
|
||||
<div class="d-flex flex-column">
|
||||
<v-toolbar
|
||||
class="base-dialog-toolbar"
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
@click="cancel"
|
||||
<div class="overflow-visible">
|
||||
<v-slide-x-reverse-transition hide-on-leave>
|
||||
<v-card
|
||||
:key="`${activeInput}`"
|
||||
elevation="6"
|
||||
class="action-dialog"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>
|
||||
Action
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<div class="action-dialog-content">
|
||||
<div class="action-dialog-layout d-flex">
|
||||
<component
|
||||
:is="activeInput"
|
||||
v-if="activeInput"
|
||||
@@ -26,43 +17,23 @@
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="action-input"
|
||||
/>
|
||||
<div
|
||||
class="log-preview card-raised-background d-flex flex-column align-end justify-end"
|
||||
style="flex-basis: 256px;"
|
||||
class="log-preview card-raised-background"
|
||||
>
|
||||
<v-card
|
||||
v-if="allLogContent && allLogContent.length"
|
||||
class="ma-2 log-entry"
|
||||
>
|
||||
<v-card-text
|
||||
class="pa-2"
|
||||
>
|
||||
<log-content :model="allLogContent" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<tabletop-log-stream-entry :model="simulatedLog" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-dialog-actions pa-2 d-flex justify-end">
|
||||
<v-btn
|
||||
v-if="actionDone"
|
||||
text
|
||||
color="accent"
|
||||
@click="finishAction"
|
||||
>
|
||||
Done
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
text
|
||||
color="accent"
|
||||
@click="continueAction"
|
||||
>
|
||||
Next
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
v-if="!activeInput"
|
||||
large
|
||||
text
|
||||
color="accent"
|
||||
style="width: 100%"
|
||||
class="done-button"
|
||||
@click="finishAction"
|
||||
>
|
||||
Done
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-slide-x-reverse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -79,6 +50,9 @@ import LogContent from '/imports/client/ui/log/LogContent.vue';
|
||||
//import RollInput from '/imports/client/ui/creature/actions/input/RollInput.vue';
|
||||
import TargetsInput from '/imports/client/ui/creature/actions/input/TargetsInput.vue';
|
||||
import CastSpellInput from '/imports/client/ui/creature/actions/input/CastSpellInput.vue';
|
||||
import { runAction } from '/imports/api/engine/action/methods/runAction';
|
||||
import TabletopLogStreamEntry from '/imports/client/ui/tabletop/TabletopLogStreamEntry.vue';
|
||||
import mutationToLogUpdates from '/imports/api/engine/action/functions/mutationToLogUpdates';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -90,6 +64,7 @@ export default {
|
||||
//RollInput,
|
||||
TargetsInput,
|
||||
CastSpellInput,
|
||||
TabletopLogStreamEntry,
|
||||
},
|
||||
props: {
|
||||
actionId: {
|
||||
@@ -100,6 +75,10 @@ export default {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
actionFinishedCallback: {
|
||||
type: Function,
|
||||
default: undefined,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -112,6 +91,7 @@ export default {
|
||||
activeInputParams: {},
|
||||
userInput: undefined,
|
||||
userInputReady: true,
|
||||
actionPromise: undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -121,17 +101,19 @@ export default {
|
||||
resultJson() {
|
||||
return JSON.stringify(this.actionResult, null, 2);
|
||||
},
|
||||
allLogContent() {
|
||||
simulatedLog() {
|
||||
const action = this.actionResult;
|
||||
const contents = [];
|
||||
action?.results?.forEach(result => {
|
||||
result.mutations?.forEach(mutation => {
|
||||
mutation.contents?.forEach(logContent => {
|
||||
contents.push(logContent);
|
||||
});
|
||||
const content = [];
|
||||
action?.results.forEach(result => {
|
||||
result.mutations.forEach(mutation => {
|
||||
content.push(...mutationToLogUpdates(mutation));
|
||||
});
|
||||
});
|
||||
return contents;
|
||||
return {
|
||||
content,
|
||||
creatureId: action?.creatureId,
|
||||
tabletopId: action?.tabletopId,
|
||||
}
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
@@ -144,7 +126,7 @@ export default {
|
||||
this.startAction({ stepThrough: false });
|
||||
},
|
||||
methods: {
|
||||
startAction({ stepThrough }) {
|
||||
async startAction({ stepThrough }) {
|
||||
this.actionBusy = true;
|
||||
this.actionResult = {
|
||||
...this.action,
|
||||
@@ -152,13 +134,15 @@ export default {
|
||||
_isSimulation: undefined,
|
||||
taskCount: undefined,
|
||||
};
|
||||
applyAction(
|
||||
this.actionResult, this, { simulate: true, stepThrough}
|
||||
).then(() => {
|
||||
this.actionDone = true;
|
||||
this.actionBusy = false;
|
||||
this.activeInput = undefined;
|
||||
await applyAction(this.actionResult, this, { simulate: true, stepThrough });
|
||||
const actionResult = await runAction.callAsync({
|
||||
actionId: this.actionResult._id,
|
||||
decisions: this.actionResult._decisions
|
||||
});
|
||||
this.actionDone = true;
|
||||
this.actionBusy = false;
|
||||
this.activeInput = undefined;
|
||||
if (this.actionFinishedCallback) this.actionFinishedCallback(actionResult);
|
||||
},
|
||||
stepAction() {
|
||||
if (this.actionResult) {
|
||||
@@ -172,7 +156,7 @@ export default {
|
||||
}
|
||||
this.resumeActionFn?.();
|
||||
},
|
||||
finishAction() {
|
||||
async finishAction() {
|
||||
this.$store.dispatch('popDialogStack', this.actionResult);
|
||||
},
|
||||
promiseInput() {
|
||||
@@ -196,6 +180,8 @@ export default {
|
||||
},
|
||||
// inputProvider methods
|
||||
async targetIds(target) {
|
||||
// Only get targets if we are on a tabletop
|
||||
if (this.$router.currentRoute.name !== 'tabletop') return [];
|
||||
this.userInput = [];
|
||||
this.activeInputParams = {
|
||||
target,
|
||||
@@ -251,43 +237,19 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.base-dialog-toolbar {
|
||||
z-index: 2;
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.action-dialog-content {
|
||||
container-type: size;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.action-dialog-content, .action-dialog-layout {
|
||||
height: 100%;
|
||||
.action-dialog {
|
||||
max-height: min(100%, 800px);
|
||||
max-width: min(100%, 1000px);
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.action-input {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-preview {
|
||||
flex-basis: 256px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex-basis: 300px;
|
||||
}
|
||||
|
||||
@container (max-width: 600px) {
|
||||
.action-dialog-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
.action-input {
|
||||
height: unset;
|
||||
}
|
||||
.log-preview {
|
||||
flex-basis: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,7 @@ type BaseDoActionParams = {
|
||||
creatureId: string;
|
||||
$store: Store<any>;
|
||||
elementId: string;
|
||||
callback?: (action: EngineAction) => void;
|
||||
}
|
||||
|
||||
type DoTaskParams = BaseDoActionParams & {
|
||||
@@ -32,7 +33,7 @@ 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, targetIds }: DoActionParams | DoTaskParams): Promise<any | void> {
|
||||
export default async function doAction({ propId, creatureId, $store, elementId, task, targetIds, callback }: DoActionParams | DoTaskParams): Promise<any | void> {
|
||||
if (!task) {
|
||||
targetIds ??= [];
|
||||
if (!propId) throw new Meteor.Error('no-prop-id', 'Either propId or task must be provided');
|
||||
@@ -58,8 +59,6 @@ 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
|
||||
@@ -73,26 +72,21 @@ export default async function doAction({ propId, creatureId, $store, elementId,
|
||||
return callActionMethod(finishedAction);
|
||||
} catch (e) {
|
||||
if (e !== 'input-requested') throw e;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
$store.commit('pushDialogStack', {
|
||||
component: 'action-dialog',
|
||||
elementId,
|
||||
data: {
|
||||
actionId,
|
||||
task,
|
||||
actionFinishedCallback: resolve,
|
||||
},
|
||||
async callback(action: EngineAction) {
|
||||
try {
|
||||
if (action) await callActionMethod(action);
|
||||
resolve();
|
||||
}
|
||||
catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
return elementId;
|
||||
callback(action) {
|
||||
resolve();
|
||||
return callback?.(action);
|
||||
},
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="choice-input">
|
||||
<v-card-title>
|
||||
{{ target === 'singleTarget' ? 'Target' : 'Targets' }}
|
||||
</v-card-title>
|
||||
<v-list-item
|
||||
v-for="creature in creatures"
|
||||
:key="creature._id"
|
||||
@@ -37,9 +40,14 @@
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-btn
|
||||
large
|
||||
text
|
||||
color="accent"
|
||||
class="mt-4"
|
||||
style="width: 100%"
|
||||
@click="$emit('continue');"
|
||||
>
|
||||
Done
|
||||
{{ !value.length ? 'No target' : 'Continue' }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
@@ -73,6 +81,10 @@ export default {
|
||||
newValue = [...this.value, id];
|
||||
}
|
||||
this.$emit('input', newValue);
|
||||
// If we are selecting a single creature, we are done
|
||||
if (this.target === 'singleTarget') {
|
||||
this.$emit('continue');
|
||||
}
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
@@ -87,4 +99,4 @@ export default {
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -139,11 +139,10 @@ import CharacterTab from '/imports/client/ui/creature/character/characterSheetTa
|
||||
import BuildTab from '/imports/client/ui/creature/character/characterSheetTabs/BuildTab.vue';
|
||||
import TreeTab from '/imports/client/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||
import CharacterSheetFab from '/imports/client/ui/creature/character/CharacterSheetFab.vue';
|
||||
import ActionsTab from '/imports/client/ui/creature/character/characterSheetTabs/ActionsTab.vue';
|
||||
import CharacterSheetInitiative from '/imports/client/ui/creature/character/CharacterSheetInitiative.vue';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -156,7 +155,6 @@ export default {
|
||||
BuildTab,
|
||||
TreeTab,
|
||||
CharacterSheetFab,
|
||||
CharacterSheetInitiative,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
@@ -165,7 +163,7 @@ export default {
|
||||
},
|
||||
embedded: Boolean,
|
||||
},
|
||||
// @ts-ignore
|
||||
// @ts-expect-error reactive provide not typed
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['creatureId', 'editPermission'],
|
||||
@@ -197,29 +195,29 @@ export default {
|
||||
changed: ({ name }) =>
|
||||
this.$store.commit('setPageTitle', name || 'Character Sheet'),
|
||||
});
|
||||
let that = this;
|
||||
this.logObserver = CreatureLogs.find({
|
||||
creatureId: this.creatureId,
|
||||
}).observe({
|
||||
added({ content }) {
|
||||
if (!that.$subReady.singleCharacter) return;
|
||||
if (that.$store.state.rightDrawer) return;
|
||||
snackbar({ content });
|
||||
},
|
||||
});
|
||||
if (this.$route.name === 'characterSheet') {
|
||||
let that = this;
|
||||
this.logObserver = CreatureLogs.find({
|
||||
creatureId: this.creatureId,
|
||||
}).observe({
|
||||
added({ content }) {
|
||||
if (!that.$subReady.singleCharacter) return;
|
||||
if (that.$store.state.rightDrawer) return;
|
||||
if (that.$store.state.dialogStack.dialogs.length) return;
|
||||
snackbar({ content });
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.nameObserver.stop();
|
||||
this.logObserver.stop();
|
||||
this.nameObserver?.stop();
|
||||
this.logObserver?.stop();
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'singleCharacter'() {
|
||||
return [this.creatureId];
|
||||
},
|
||||
'otherTabletopCreatures'() {
|
||||
return [this.creatureId];
|
||||
},
|
||||
},
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, {
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
// Use in combination with browser's animation speed override to do slow-mod debugging
|
||||
const animationSpeed = 1;
|
||||
|
||||
const unsizedDialogs = new Set(['image-preview-dialog']);
|
||||
const unsizedDialogs = new Set(['image-preview-dialog', 'action-dialog']);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -129,7 +129,7 @@
|
||||
const num = length - 1;
|
||||
const left = (num - index) * -OFFSET;
|
||||
const top = (num - index) * -OFFSET;
|
||||
return `left: calc(${left}px + 50%); top: calc(${top}px + 50%)`;
|
||||
return `left: calc(${left}px + 50%); top: calc(${top}px + 50%);${index < num ? ' filter: brightness(0.7);': ''}`;
|
||||
},
|
||||
getTopElementByDataId(elementId, offset = 0){
|
||||
let stackLength = this.$store.state.dialogStack.dialogs.length - offset;
|
||||
@@ -166,7 +166,16 @@
|
||||
|
||||
// Instantly mock the source
|
||||
target.style.transition = 'none';
|
||||
mockElement({ source, target });
|
||||
// If we are using unsized dialogs, first let it layout with no opacity, then mock and
|
||||
// carry on, otherwise it has no size
|
||||
if (target.classList.contains('unsized-dialog')) {
|
||||
target.style.opacity = '0';
|
||||
await new Promise(requestAnimationFrame);
|
||||
mockElement({ source, target });
|
||||
target.style.opacity = '1';
|
||||
} else {
|
||||
mockElement({ source, target });
|
||||
}
|
||||
|
||||
// Wait one frame before hiding the source so we know our mock is in place
|
||||
await new Promise(requestAnimationFrame);
|
||||
|
||||
@@ -1,41 +1,51 @@
|
||||
<template lang="html">
|
||||
<v-container
|
||||
v-if="!$subReady.tabletop"
|
||||
key="Tabletop"
|
||||
fluid
|
||||
class="fill-height"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<v-row>
|
||||
<v-col cols="1">
|
||||
<v-progress-circular indeterminate />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<tabletop-component
|
||||
v-else-if="tabletop"
|
||||
:model="tabletop"
|
||||
/>
|
||||
<v-container
|
||||
v-else
|
||||
fluid
|
||||
class="fill-height"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<v-row
|
||||
class="pa-4"
|
||||
<v-fade-transition mode="out-in">
|
||||
<v-container
|
||||
v-if="!$subReady.tabletop"
|
||||
key="Loading"
|
||||
fluid
|
||||
class="fill-height"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="8"
|
||||
<v-row justify="center">
|
||||
<v-col cols="1">
|
||||
<v-progress-circular
|
||||
:size="100"
|
||||
:width="10"
|
||||
color="primary"
|
||||
style="opacity: 0.5"
|
||||
indeterminate
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<tabletop-component
|
||||
v-else-if="tabletop"
|
||||
key="Tabletop"
|
||||
:model="tabletop"
|
||||
/>
|
||||
<v-container
|
||||
v-else
|
||||
key="Not Found"
|
||||
fluid
|
||||
class="fill-height"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<v-row
|
||||
class="pa-4"
|
||||
>
|
||||
<p>This tabletop was not found</p>
|
||||
<p>Either it does not exist, or you do not have permission to view it</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<p>This tabletop was not found</p>
|
||||
<p>Either it does not exist, or you do not have permission to view it</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-fade-transition>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
|
||||
@@ -224,6 +224,7 @@ export default {
|
||||
targetIds: this.targets,
|
||||
$store: this.$store,
|
||||
elementId: 'do-action-button',
|
||||
callback: action => action?._id || this.model._id,
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
snackbar({ text: e.message || e.reason || e.toString() });
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
>
|
||||
<v-row
|
||||
dense
|
||||
class="initiative-row flex-grow-0"
|
||||
style="flex-wrap: nowrap; overflow-x: auto; padding-bottom: 50px; min-width: 200px;"
|
||||
class="initiative-row flex-grow-0 overflow-x-auto"
|
||||
style="flex-wrap: nowrap; padding-bottom: 64px; min-width: 200px;"
|
||||
@wheel="transformScroll($event)"
|
||||
>
|
||||
<v-btn
|
||||
@@ -69,7 +69,7 @@
|
||||
</v-row>
|
||||
<div
|
||||
class="d-flex align-stretch"
|
||||
style="max-height: calc(100vh - 364px);"
|
||||
style="max-height: calc(100vh - 364px); margin-top: -24px;"
|
||||
>
|
||||
<v-spacer />
|
||||
<tabletop-log-stream
|
||||
@@ -188,9 +188,12 @@ export default {
|
||||
targetIds: this.targets,
|
||||
$store: this.$store,
|
||||
elementId: 'tabletop-action-card',
|
||||
callback: action => action?._id || this.activeActionId,
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
snackbar({ text: e.message || e.reason || e.toString() });
|
||||
}).finally(() => {
|
||||
this.doActionLoading = false;
|
||||
});
|
||||
this.$refs.selectedCreatureBar.selectedIcon = undefined;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
:key="bar._id"
|
||||
:model="bar"
|
||||
:height="4"
|
||||
style="opacity: 0.7; margin-top: 2px"
|
||||
style="opacity: 0.8; margin-top: 2px"
|
||||
/>
|
||||
</v-img>
|
||||
<div class="d-flex justify-center">
|
||||
@@ -37,7 +37,6 @@
|
||||
v-if="showTargetBtn"
|
||||
:color="targeted ? 'accent' : ''"
|
||||
:elevation="targeted ? 8 : 2"
|
||||
style="margin-top: -16px;"
|
||||
small
|
||||
fab
|
||||
@click.stop.prevent="targeted ? $emit('untarget') : $emit('target')"
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<tabletop-log-stream-entry
|
||||
v-for="log in logs"
|
||||
:key="log._id"
|
||||
class="stream-entry"
|
||||
:class="{'hidden': hideAction(log.actionId)}"
|
||||
:model="log"
|
||||
/>
|
||||
</div>
|
||||
@@ -14,6 +16,7 @@
|
||||
<script lang="js">
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs';
|
||||
import TabletopLogStreamEntry from '/imports/client/ui/tabletop/TabletopLogStreamEntry.vue';
|
||||
import dialogStackStore from '/imports/client/ui/dialogStack/dialogStackStore.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -25,6 +28,17 @@ export default {
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
openActionDialogs(){
|
||||
const dialogs = this.$store.state.dialogStack.dialogs;
|
||||
return new Set(dialogs.map(dialog => dialog.data?.actionId).filter(actionId => !!actionId));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hideAction(actionId){
|
||||
return this.openActionDialogs.has(actionId);
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
logs() {
|
||||
const filter = {};
|
||||
@@ -41,3 +55,14 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.stream-entry {
|
||||
background-color: hsl(0deg 0% 50% / 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template lang="html">
|
||||
<div class="pa-2 pt-0 my-2 stream-entry">
|
||||
<div
|
||||
class="pa-2 pt-0 my-1 rounded-sm"
|
||||
:data-id="model.actionId"
|
||||
>
|
||||
<v-list-item
|
||||
v-if="model.creatureId"
|
||||
v-if="model.creatureId && creature"
|
||||
dense
|
||||
class="pl-0"
|
||||
>
|
||||
@@ -64,9 +67,3 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.stream-entry {
|
||||
background-color: hsl(0deg 0% 50% / 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user