Moved tabletop characters to left side of the screen

This commit is contained in:
Thaum Rystra
2024-04-12 17:05:20 +02:00
parent 4793b34a55
commit 08640f2bf2
27 changed files with 496 additions and 1370 deletions

View File

@@ -5,6 +5,7 @@
"blackbox",
"Crits",
"cyrb",
"denormalized",
"EJSON",
"healthbar",
"healthbars",
@@ -13,7 +14,8 @@
"ngraph",
"ostrio",
"snackbars",
"Spellcasting",
"uncomputed",
"walkdown"
]
}
}

View File

@@ -1,13 +1,65 @@
import SimpleSchema from 'simpl-schema';
import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema'
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema';
import SharingSchema from '/imports/api/sharing/SharingSchema';
import ColorSchema, { Colored } from '/imports/api/properties/subSchemas/ColorSchema';
import SharingSchema, { Shared } from '/imports/api/sharing/SharingSchema';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
//set up the collection for creatures
let Creatures = new Mongo.Collection('creatures');
export type Creature = Colored & Shared & {
// Strings
_id: string,
name?: string,
alignment?: string,
gender?: string,
picture?: string,
avatarPicture?: string,
let CreatureSettingsSchema = new SimpleSchema({
// Libraries
allowedLibraries: string[],
allowedLibraryCollections: string[],
// Stats that are computed and denormalized outside of recomputation
denormalizedStats: {
xp: number,
milestoneLevels: number,
},
propCount: number,
// Does the character need a recompute?
dirty?: boolean,
// Version of computation engine that was last used to compute this creature
computeVersion?: string,
type: 'pc' | 'npc' | 'monster',
computeErrors: {
type: string,
details?: any,
}[],
// Tabletop
tabletopId?: string,
initiativeRoll?: number,
tabletopSettings?: {
iconGroups: {
name?: string,
iconIds: string[],
}[],
},
settings: {
useVariantEncumbrance?: true,
hideSpellcasting?: true,
hideRestButtons?: true,
swapStatAndModifier?: true,
hideUnusedStats?: true,
showTreeTab?: true,
hideSpellsTab?: true,
hideCalculationErrors?: true,
hitDiceResetMultiplier?: number,
discordWebhook?: string,
},
};
//set up the collection for creatures
const Creatures = new Mongo.Collection<Creature>('creatures');
const CreatureSettingsSchema = new SimpleSchema({
//slowed down by carrying too much?
useVariantEncumbrance: {
type: Boolean,
@@ -62,7 +114,7 @@ let CreatureSettingsSchema = new SimpleSchema({
},
});
let IconGroupSchema = new SimpleSchema({
const IconGroupSchema = new SimpleSchema({
name: {
type: String,
max: STORAGE_LIMITS.name,
@@ -79,7 +131,7 @@ let IconGroupSchema = new SimpleSchema({
},
});
let CreatureTabletopSettingsSchema = new SimpleSchema({
const CreatureTabletopSettingsSchema = new SimpleSchema({
iconGroups: {
type: Array,
defaultValue: [],
@@ -90,7 +142,7 @@ let CreatureTabletopSettingsSchema = new SimpleSchema({
},
});
let CreatureSchema = new SimpleSchema({
const CreatureSchema = new SimpleSchema({
// Strings
name: {
type: String,
@@ -139,11 +191,6 @@ let CreatureSchema = new SimpleSchema({
regEx: SimpleSchema.RegEx.Id,
},
// Mechanics
deathSave: {
type: deathSaveSchema,
defaultValue: {},
},
// Stats that are computed and denormalised outside of recomputation
denormalizedStats: {
type: Object,
@@ -159,6 +206,10 @@ let CreatureSchema = new SimpleSchema({
type: SimpleSchema.Integer,
defaultValue: 0,
},
propCount: {
type: SimpleSchema.Integer,
defaultValue: 0,
},
// Does the character need a recompute?
dirty: {
type: Boolean,
@@ -174,11 +225,6 @@ let CreatureSchema = new SimpleSchema({
defaultValue: 'pc',
allowedValues: ['pc', 'npc', 'monster'],
},
damageMultipliers: {
type: Object,
blackbox: true,
defaultValue: {}
},
computeErrors: {
type: Array,
optional: true,
@@ -196,9 +242,9 @@ let CreatureSchema = new SimpleSchema({
},
// Tabletop
tabletop: {
tabletopId: {
type: String,
regEx: SimpleSchema.RegEx.id,
regEx: SimpleSchema.RegEx.Id,
optional: true,
},
initiativeRoll: {
@@ -220,6 +266,7 @@ let CreatureSchema = new SimpleSchema({
CreatureSchema.extend(ColorSchema);
CreatureSchema.extend(SharingSchema);
//@ts-expect-error attachSchema not defined
Creatures.attachSchema(CreatureSchema);

View File

@@ -1,9 +0,0 @@
import Creatures from '/imports/api/creature/creatures/Creatures';
export default function (creatureId, errors = []) {
if (errors.length) {
Creatures.update(creatureId, { $set: { computeErrors: errors } });
} else {
Creatures.update(creatureId, { $unset: { computeErrors: 1 } });
}
}

View File

@@ -0,0 +1,9 @@
import Creatures from '/imports/api/creature/creatures/Creatures';
export default function writeErrorsAndPropCount(creatureId, errors = [], propCount) {
if (errors.length) {
Creatures.update(creatureId, { $set: { computeErrors: errors, propCount } });
} else {
Creatures.update(creatureId, { $set: { propCount }, $unset: { computeErrors: 1 } });
}
}

View File

@@ -2,7 +2,7 @@ import buildCreatureComputation from './computation/buildCreatureComputation';
import computeCreatureComputation from './computation/computeCreatureComputation';
import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties';
import writeScope from './computation/writeComputation/writeScope';
import writeErrors from './computation/writeComputation/writeErrors';
import writeErrorsAndPropCount from './computation/writeComputation/writeErrorsAndPropCount';
export default async function computeCreature(creatureId) {
if (Meteor.isClient) return;
@@ -32,7 +32,7 @@ async function computeComputation(computation, creatureId) {
console.error(logError);
} finally {
checkPropertyCount(computation)
writeErrors(creatureId, computation.errors);
writeErrorsAndPropCount(creatureId, computation.errors, computation.props.length);
}
}

View File

@@ -4,7 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { RefSchema } from '/imports/api/parenting/ChildSchema';
import { assertDocEditPermission, assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { compact } from 'lodash';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import Creatures from '/imports/api/creature/creatures/Creatures';
import { fetchDocByRefAsync, getCollectionByName, moveDocBetweenRoots, moveDocWithinRoot } from '/imports/api/parenting/parentingFunctions';
const moveBetweenRoots = new ValidatedMethod({

View File

@@ -1,5 +1,9 @@
import SimpleSchema from 'simpl-schema';
export interface Colored {
color?: string,
}
const ColorSchema = new SimpleSchema({
color: {
type: String,

View File

@@ -1,26 +0,0 @@
import SimpleSchema from 'simpl-schema';
const DeathSavesSchema = new SimpleSchema({
pass: {
type: SimpleSchema.Integer,
min: 0,
max: 3,
defaultValue: 0,
},
fail: {
type: SimpleSchema.Integer,
min: 0,
max: 3,
defaultValue: 0,
},
canDeathSave: {
type: Boolean,
defaultValue: true,
},
stable: {
type: Boolean,
defaultValue: false,
},
});
export default DeathSavesSchema;

View File

@@ -2,15 +2,25 @@ import SimpleSchema from 'simpl-schema';
import '/imports/api/sharing/sharing';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
let SharingSchema = new SimpleSchema({
export interface Shared {
owner: string,
readers: string[],
writers: string[],
public: boolean,
readersCanCopy?: true,
}
const SharingSchema = new SimpleSchema({
owner: {
type: String,
regEx: SimpleSchema.RegEx.Id,
//@ts-expect-error index not defined
index: 1
},
readers: {
type: Array,
defaultValue: [],
//@ts-expect-error index not defined
index: 1,
maxCount: STORAGE_LIMITS.readersCount,
},
@@ -21,6 +31,7 @@ let SharingSchema = new SimpleSchema({
writers: {
type: Array,
defaultValue: [],
//@ts-expect-error index not defined
index: 1,
maxCount: STORAGE_LIMITS.writersCount,
},
@@ -31,6 +42,7 @@ let SharingSchema = new SimpleSchema({
public: {
type: Boolean,
defaultValue: false,
//@ts-expect-error index not defined
index: 1,
},
readersCanCopy: {

View File

@@ -1,6 +1,23 @@
import SimpleSchema from 'simpl-schema';
let Tabletops = new Mongo.Collection('tabletops');
export type Tabletop = {
name?: string,
description?: string,
imageUrl?: string,
owner: string,
gameMasters: string[],
players: string[],
spectators: string[],
public?: true,
initiative: {
active: boolean,
roundNumber: number,
initiativeNumber?: number,
activeCreature?: string,
},
}
const Tabletops = new Mongo.Collection<Tabletop>('tabletops');
const InitiativeSchema = new SimpleSchema({
active: {
@@ -23,7 +40,7 @@ const InitiativeSchema = new SimpleSchema({
});
// All creatures in a tabletop have a shared time and space.
let TabletopSchema = new SimpleSchema({
const TabletopSchema = new SimpleSchema({
// Details
name: {
type: String,
@@ -43,13 +60,38 @@ let TabletopSchema = new SimpleSchema({
owner: String,
// The owner will need to included in one of these arrays for specific permissions
// A user should not appear in more than one of the following arrays
gameMasters: [String],
players: [String],
spectators: [String],
gameMasters: {
type: Array,
defaultValue: [],
},
'gameMasters.$': {
type: String,
//@ts-expect-error Index not defined in simpl-schema package
index: 1,
},
players: {
type: Array,
defaultValue: [],
},
'players.$': {
type: String,
//@ts-expect-error Index not defined in simpl-schema package
index: 1,
},
spectators: {
type: Array,
defaultValue: [],
},
'spectators.$': {
type: String,
//@ts-expect-error Index not defined in simpl-schema package
index: 1,
},
// Does everyone else have the spectator permission?
public: {
type: Boolean,
defaultValue: false,
optional: true,
//@ts-expect-error Index not defined in simpl-schema package
index: 1,
},
@@ -61,10 +103,12 @@ let TabletopSchema = new SimpleSchema({
});
//@ts-expect-error attachSchema not defined in simpl-schema package
Tabletops.attachSchema(TabletopSchema);
import '/imports/api/tabletop/methods/removeTabletop';
import '/imports/api/tabletop/methods/insertTabletop';
import '/imports/api/tabletop/methods/updateTabletop';
import '/imports/api/tabletop/methods/addCreaturesToTabletop';
export default Tabletops;

View File

@@ -2,7 +2,6 @@ import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { assertUserInTabletop } from './shared/tabletopPermissions';
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
import Creatures from '/imports/api/creature/creatures/Creatures';
@@ -16,15 +15,16 @@ const addCreaturesToTabletop = new ValidatedMethod({
},
'creatureIds.$': {
type: String,
regEx: SimpleSchema.RegEx.id,
regEx: SimpleSchema.RegEx.Id,
},
tabletopId: {
type: String,
regEx: SimpleSchema.RegEx.id,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
// @ts-expect-error Rate limit not defined
rateLimit: {
numRequests: 10,
timeInterval: 5000,
@@ -37,16 +37,16 @@ const addCreaturesToTabletop = new ValidatedMethod({
}
assertUserHasPaidBenefits(this.userId);
assertUserInTabletop(tabletopId, this.userId);
assertAdmin(this.userId);
Creatures.update({
_id: { $in: creatureIds },
// You must have write permission for the creatures you
$or: [
{ writers: this.userId },
{ owner: this.userId },
],
}, {
$set: { tabletop: tabletopId },
$set: { tabletopId },
}, {
multi: true,
});

View File

@@ -1,8 +1,7 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Tabletops from '../Tabletops';
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
import { assertUserHasPaidBenefits, getUserTier } from '/imports/api/users/patreon/tiers';
const insertTabletop = new ValidatedMethod({
@@ -11,8 +10,9 @@ const insertTabletop = new ValidatedMethod({
validate: null,
mixins: [RateLimiterMixin],
// @ts-expect-error Rate limit not defined
rateLimit: {
numRequests: 5,
numRequests: 2,
timeInterval: 5000,
},
@@ -22,13 +22,24 @@ const insertTabletop = new ValidatedMethod({
'You need to be logged in to insert a tabletop');
}
assertUserHasPaidBenefits(this.userId);
assertAdmin(this.userId);
let tier = getUserTier(this.userId);
const currentTabletopCount = Tabletops.find({ owner: this.userId }).count();
if (tier.tabletopSlots !== -1 && tier.tabletopSlots <= currentTabletopCount) {
throw new Meteor.Error('limit-reached', 'You have reached your maximum number of tabletops');
}
return Tabletops.insert({
gameMaster: this.userId,
owner: this.userId,
gameMasters: [this.userId],
players: [],
spectators: [],
initiative: {
active: false,
roundNumber: 0,
},
});
},
});
export default insertTabletop;

View File

@@ -0,0 +1,53 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { assertUserInTabletop } from './shared/tabletopPermissions';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
import Creatures from '/imports/api/creature/creatures/Creatures';
const addCreaturesToTabletop = new ValidatedMethod({
name: 'tabletops.addCreatures',
validate: new SimpleSchema({
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
tabletopId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
// @ts-expect-error Rate limit not defined
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
run({ tabletopId, creatureIds }) {
if (!this.userId) {
throw new Meteor.Error('tabletops.addCreatures.denied',
'You need to be logged in to remove a tabletop');
}
assertUserHasPaidBenefits(this.userId);
assertUserInTabletop(tabletopId, this.userId);
Creatures.update({
_id: { $in: creatureIds },
$or: [
{ writers: this.userId },
{ owner: this.userId },
],
}, {
$set: { tabletop: tabletopId },
}, {
multi: true,
});
},
});
export default addCreaturesToTabletop;

View File

@@ -2,7 +2,6 @@ import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Tabletops from '../Tabletops';
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
import { assertUserIsTabletopOwner } from './shared/tabletopPermissions';
import Creatures from '/imports/api/creature/creatures/Creatures';
@@ -14,11 +13,12 @@ const removeTabletop = new ValidatedMethod({
validate: new SimpleSchema({
tabletopId: {
type: String,
regEx: SimpleSchema.RegEx.id,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
// @ts-expect-error Rate limit not defined
rateLimit: {
numRequests: 5,
timeInterval: 5000,
@@ -31,7 +31,6 @@ const removeTabletop = new ValidatedMethod({
}
assertUserHasPaidBenefits(this.userId);
assertUserIsTabletopOwner(tabletopId, this.userId);
assertAdmin(this.userId);
let removed = Tabletops.remove({
_id: tabletopId,

View File

@@ -1,25 +0,0 @@
import Tabletops from '../../Tabletops';
export function assertUserInTabletop(tabletopId, userId) {
let tabletop = Tabletops.findOne(tabletopId);
if (!tabletop) {
throw new Meteor.Error('Tabletop does not exist',
'No tabletop could be found for the given tabletop id');
}
if (tabletop.gameMaster !== userId && !tabletop.players.includes(userId)) {
throw new Meteor.Error('Not in tabletop',
'The user is not the gamemaster or a player in the given tabletop');
}
}
export function assertUserIsTabletopOwner(tabletopId, userId) {
let tabletop = Tabletops.findOne(tabletopId);
if (!tabletop) {
throw new Meteor.Error('Tabletop does not exist',
'No tabletop could be found for the given tabletop id');
}
if (tabletop.gameMaster !== userId) {
throw new Meteor.Error('Not the owner',
'The user is not the owner of the given tabletop');
}
}

View File

@@ -0,0 +1,41 @@
import Tabletops, { Tabletop } from '/imports/api/tabletop/Tabletops';
function assertTabletopExists(tabletop: Tabletop | undefined): asserts tabletop is Tabletop {
if (!tabletop) {
throw new Meteor.Error('Tabletop does not exist',
'No tabletop could be found for the given tabletop id');
}
}
export function assertUserInTabletop(tabletopId, userId) {
const tabletop = Tabletops.findOne(tabletopId, {
fields: { gameMasters: 1, players: 1 }
});
assertTabletopExists(tabletop);
if (tabletop.gameMasters.includes(userId) && !tabletop.players.includes(userId)) {
throw new Meteor.Error('Not in tabletop',
'The user is not the gamemaster or a player in the given tabletop');
}
}
export function assertUserGameMasterOfTabletop(tabletopId, userId) {
const tabletop = Tabletops.findOne(tabletopId, {
fields: { gameMasters: 1 },
});
assertTabletopExists(tabletop);
if (tabletop.gameMasters.includes(userId)) {
throw new Meteor.Error('not-game-master',
'The user is not a game master in the given tabletop');
}
}
export function assertUserIsTabletopOwner(tabletopId, userId) {
const tabletop = Tabletops.findOne(tabletopId, {
fields: { owner: 1 },
});
assertTabletopExists(tabletop);
if (tabletop.owner === userId) {
throw new Meteor.Error('not-owner',
'The user is not the owner of the given tabletop');
}
}

View File

@@ -0,0 +1,53 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Tabletops from '../Tabletops';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
import { assertUserIsTabletopOwner } from './shared/tabletopPermissions';
const removeTabletop = new ValidatedMethod({
name: 'tabletops.update',
validate({ _id, path }) {
if (!_id) return false;
// Allowed fields
let allowedFields = [
'name',
'description',
'imageUrl',
];
if (!allowedFields.includes(path[0])) {
throw new Meteor.Error('tabletops.update.denied',
'This field can\'t be updated using this method');
}
},
mixins: [RateLimiterMixin],
// @ts-expect-error Rate limit not defined
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({ _id, path, value }) {
if (!this.userId) {
throw new Meteor.Error('tabletops.remove.denied',
'You need to be logged in to remove a tabletop');
}
assertUserHasPaidBenefits(this.userId);
assertUserIsTabletopOwner(_id, this.userId);
if (value === undefined || value === null) {
Tabletops.update(_id, {
$unset: { [path.join('.')]: 1 },
});
} else {
Tabletops.update(_id, {
$set: { [path.join('.')]: value },
});
}
},
});
export default removeTabletop;

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 0,
invites: 0,
characterSlots: 5,
tabletopSlots: 0,
fileStorage: 50,
paidBenefits: false,
}, {
@@ -16,6 +17,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 100,
invites: 0,
characterSlots: 5,
tabletopSlots: 0,
fileStorage: 50,
paidBenefits: false,
}, {
@@ -23,6 +25,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 300,
invites: 0,
characterSlots: 5,
tabletopSlots: 0,
fileStorage: 50,
paidBenefits: false,
}, {
@@ -31,6 +34,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 500,
invites: 0,
characterSlots: 20,
tabletopSlots: 4,
fileStorage: 200,
paidBenefits: true,
}, {
@@ -39,6 +43,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 1000,
invites: 2,
characterSlots: 50,
tabletopSlots: 10,
fileStorage: 500,
paidBenefits: true,
}, {
@@ -47,6 +52,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 2000,
invites: 5,
characterSlots: 120,
tabletopSlots: 24,
fileStorage: 1000,
paidBenefits: true,
}, {
@@ -55,6 +61,7 @@ const TIERS = Object.freeze([
minimumEntitledCents: 5000,
invites: 15,
characterSlots: -1, // Unlimited characters
tabletopSlots: -1, // Unlimited tabletops
fileStorage: 2000,
paidBenefits: true,
},
@@ -66,6 +73,7 @@ const GUEST_TIER = Object.freeze({
guest: true,
invites: 0,
characterSlots: 20,
tabletopSlots: 4,
fileStorage: 200,
paidBenefits: true,
});
@@ -76,6 +84,7 @@ const PATREON_DISABLED_TIER = Object.freeze({
name: 'Outlander',
invites: 0,
characterSlots: -1, // Can have infinitely many characters
tabletopSlots: -1, // Infinite tabletops
fileStorage: 1000000, // 1TB file storage
paidBenefits: true,
});

View File

@@ -0,0 +1,58 @@
<template>
<div
class="hexagon-progress"
:style="fillStyle"
>
<div class="hexagon-content">
<slot />
</div>
</div>
</template>
<script>
export default {
props: {
percent: {
type: Number,
required: true
}
},
computed: {
fillStyle() {
return {
'--p': `${this.percent}%`
};
}
}
};
</script>
<style>
.hexagon-progress {
position: relative;
clip-path: polygon(
50% 0%,
100% 25%,
100% 75%,
50% 100%,
0% 75%,
0% 25%
);
background: conic-gradient(red var(--p),#0000 0);
background-color: #5e1010; /* adjust the color as needed */
}
.hexagon-content {
position: absolute;
inset: 4px;
background-color: #252525;
clip-path: polygon(
50% 0%,
100% 25%,
100% 75%,
50% 100%,
0% 75%,
0% 25%
);
}
</style>

View File

@@ -58,7 +58,10 @@ export default {
addTabletop(){
this.addTabletopLoading = true;
insertTabletop.call(error => {
if (error) snackbar(error.message);
if (error) {
console.error(error)
snackbar({ text: error.reason || error.message || error.toString() });
}
this.addTabletopLoading = false;
});
}

View File

@@ -59,7 +59,7 @@
<script lang="js">
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
import CharacterSheet from '/imports/client/ui/creature/character/CharacterSheet.vue';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import Creatures from '/imports/api/creature/creatures/Creatures';
export default {
components: {

View File

@@ -68,6 +68,36 @@
</v-btn>
</div>
</v-row>
<div class="d-flex flex-column align-start ml-2">
<div class="d-flex flex-column">
<tabletop-creature-list-item
v-for="creature in creatures"
:key="creature._id"
:model="creature"
:title="creature.name"
:active="activeCreatureId === creature._id"
:targeted="targets.includes(creature._id)"
:show-target-btn="targets.includes(creature._id) || moreTargets"
v-on="(!activeActionId || (targets.includes(creature._id) || moreTargets)) ? {
click: () => {
if (activeActionId) {
if (targets.includes(creature._id)) {
untarget(creature._id)
} else {
if (moreTargets) targets.push(creature._id);
}
} else {
activeCreatureId = creature._id;
targets = [];
activeActionId = undefined;
}
}
} : {}"
@target="targets.push(creature._id)"
@untarget="untarget(creature._id)"
/>
</div>
</div>
</v-container>
<v-footer
inset
@@ -96,13 +126,14 @@
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 Creatures from '/imports/api/creature/creatures/Creatures.js';
import Creatures from '/imports/api/creature/creatures/Creatures';
import MiniCharacterSheet from '/imports/client/ui/creature/character/MiniCharacterSheet.vue';
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
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';
import TabletopCreatureListItem from '/imports/client/ui/tabletop/TabletopCreatureListItem.vue';
const getProperties = function (creatureId, selector = {}) {
return CreatureProperties.find({
@@ -129,6 +160,7 @@ export default {
ActionCard,
MiniCharacterSheet,
SelectedCreatureBar,
TabletopCreatureListItem,
},
props: {
model: {
@@ -159,7 +191,7 @@ export default {
},
},
creatures(){
return Creatures.find({ tabletop: this.model._id });
return Creatures.find({ tabletopId: this.model._id });
},
actions(){
return getProperties(this.activeCreatureId, { type: 'action', actionType: { $ne: 'event'} });
@@ -196,7 +228,10 @@ export default {
tabletopId: this.model._id,
creatureIds: charIds,
}, error => {
if (error) snackbar(error.message);
if (error) {
console.error(error)
snackbar({ text: error.message || error.toString() });
}
});
},
});

View File

@@ -0,0 +1,48 @@
<template>
<div
class="d-flex align-center"
>
<hexagon-progress
:percent="66"
style="z-index: 1; width: 60px; height: 60px; margin-right: -16px"
>
<hexagon-progress
:percent="30"
style="width: 100%; height: 100%"
>
<v-img
contain
src="/images/paragons/kira.png"
/>
</hexagon-progress>
</hexagon-progress>
<v-card
class="flex-grow-1"
style="margin: 16px 16px 16px 0px;"
>
<div style="margin: 8px 8px 8px 24px;">
{{ title }}
</div>
</v-card>
</div>
</template>
<script lang="js">
import HexagonProgress from '/imports/client/ui/components/HexagonProgress.vue';
export default {
components: {
HexagonProgress,
},
props: {
title: {
type: String,
default: 'Title'
},
}
};
</script>
<style scoped>
</style>

View File

@@ -135,7 +135,7 @@
</template>
<script lang="js">
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import Creatures from '/imports/api/creature/creatures/Creatures';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
import TabletopActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
import CreatureBarIcon from '/imports/client/ui/tabletop/selectedCreatureBar/CreatureBarIcon.vue';

View File

@@ -1,5 +1,5 @@
import { serialMap } from '/imports/api/utility/asyncMap';
import constant, { ConstantValueType } from '/imports/parser/parseTree/constant';
import constant from '/imports/parser/parseTree/constant';
import ParseNode from '/imports/parser/parseTree/ParseNode';
import ResolveFunction from '/imports/parser/types/ResolveFunction';
import MapFunction from '/imports/parser/types/MapFunction';

View File

@@ -12,8 +12,10 @@ Meteor.publish('tabletops', function () {
}
return Tabletops.find({
$or: [
{ owner: userId },
{ players: userId },
{ gameMaster: userId },
{ gameMasters: userId },
{ spectators: userId },
],
});
});
@@ -24,12 +26,15 @@ Meteor.publish('tabletop', function (tabletopId) {
return [];
}
this.autorun(function () {
if (!userId) return [];
const self = this;
let tabletopCursor = Tabletops.find({
_id: tabletopId,
$or: [
{ owner: userId },
{ players: userId },
{ gameMaster: userId },
{ gameMasters: userId },
{ spectators: userId },
]
});
let tabletop = tabletopCursor.fetch()[0];
@@ -41,17 +46,19 @@ Meteor.publish('tabletop', function (tabletopId) {
// read permission of this specific creature, so publish as few fields as
// possible
let creatureSummaries = Creatures.find({
tabletop: tabletopId,
tabletopId,
}, {
fields: {
_id: 1,
name: 1,
picture: 1,
avatarPicture: 1,
tabletop: 1,
tabletopId: 1,
initiativeRoll: 1,
settings: 1,
propCount: 1,
},
limit: 110,
});
const creatureIds = creatureSummaries.map(c => c._id);
creatureIds.forEach(creatureId => {
@@ -59,10 +66,14 @@ Meteor.publish('tabletop', function (tabletopId) {
});
const variables = CreatureVariables.find({
_creatureId: { $in: creatureIds }
}, {
limit: 110,
});
let properties = CreatureProperties.find({
'ancestors.id': { $in: creatureIds },
removed: { $ne: true },
}, {
limit: 10_000,
});
const logs = CreatureLogs.find({
tabletopId,