Moved tabletop characters to left side of the screen
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -5,6 +5,7 @@
|
|||||||
"blackbox",
|
"blackbox",
|
||||||
"Crits",
|
"Crits",
|
||||||
"cyrb",
|
"cyrb",
|
||||||
|
"denormalized",
|
||||||
"EJSON",
|
"EJSON",
|
||||||
"healthbar",
|
"healthbar",
|
||||||
"healthbars",
|
"healthbars",
|
||||||
@@ -13,7 +14,8 @@
|
|||||||
"ngraph",
|
"ngraph",
|
||||||
"ostrio",
|
"ostrio",
|
||||||
"snackbars",
|
"snackbars",
|
||||||
|
"Spellcasting",
|
||||||
"uncomputed",
|
"uncomputed",
|
||||||
"walkdown"
|
"walkdown"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,65 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema'
|
import ColorSchema, { Colored } from '/imports/api/properties/subSchemas/ColorSchema';
|
||||||
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema';
|
import SharingSchema, { Shared } from '/imports/api/sharing/SharingSchema';
|
||||||
import SharingSchema from '/imports/api/sharing/SharingSchema';
|
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||||
|
|
||||||
//set up the collection for creatures
|
export type Creature = Colored & Shared & {
|
||||||
let Creatures = new Mongo.Collection('creatures');
|
// 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?
|
//slowed down by carrying too much?
|
||||||
useVariantEncumbrance: {
|
useVariantEncumbrance: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -62,7 +114,7 @@ let CreatureSettingsSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let IconGroupSchema = new SimpleSchema({
|
const IconGroupSchema = new SimpleSchema({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
@@ -79,7 +131,7 @@ let IconGroupSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let CreatureTabletopSettingsSchema = new SimpleSchema({
|
const CreatureTabletopSettingsSchema = new SimpleSchema({
|
||||||
iconGroups: {
|
iconGroups: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
@@ -90,7 +142,7 @@ let CreatureTabletopSettingsSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let CreatureSchema = new SimpleSchema({
|
const CreatureSchema = new SimpleSchema({
|
||||||
// Strings
|
// Strings
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -139,11 +191,6 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Mechanics
|
|
||||||
deathSave: {
|
|
||||||
type: deathSaveSchema,
|
|
||||||
defaultValue: {},
|
|
||||||
},
|
|
||||||
// Stats that are computed and denormalised outside of recomputation
|
// Stats that are computed and denormalised outside of recomputation
|
||||||
denormalizedStats: {
|
denormalizedStats: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -159,6 +206,10 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
propCount: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
// Does the character need a recompute?
|
// Does the character need a recompute?
|
||||||
dirty: {
|
dirty: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -174,11 +225,6 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
defaultValue: 'pc',
|
defaultValue: 'pc',
|
||||||
allowedValues: ['pc', 'npc', 'monster'],
|
allowedValues: ['pc', 'npc', 'monster'],
|
||||||
},
|
},
|
||||||
damageMultipliers: {
|
|
||||||
type: Object,
|
|
||||||
blackbox: true,
|
|
||||||
defaultValue: {}
|
|
||||||
},
|
|
||||||
computeErrors: {
|
computeErrors: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -196,9 +242,9 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Tabletop
|
// Tabletop
|
||||||
tabletop: {
|
tabletopId: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
initiativeRoll: {
|
initiativeRoll: {
|
||||||
@@ -220,6 +266,7 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
CreatureSchema.extend(ColorSchema);
|
CreatureSchema.extend(ColorSchema);
|
||||||
CreatureSchema.extend(SharingSchema);
|
CreatureSchema.extend(SharingSchema);
|
||||||
|
|
||||||
|
//@ts-expect-error attachSchema not defined
|
||||||
Creatures.attachSchema(CreatureSchema);
|
Creatures.attachSchema(CreatureSchema);
|
||||||
|
|
||||||
|
|
||||||
@@ -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 } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import buildCreatureComputation from './computation/buildCreatureComputation';
|
|||||||
import computeCreatureComputation from './computation/computeCreatureComputation';
|
import computeCreatureComputation from './computation/computeCreatureComputation';
|
||||||
import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties';
|
import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties';
|
||||||
import writeScope from './computation/writeComputation/writeScope';
|
import writeScope from './computation/writeComputation/writeScope';
|
||||||
import writeErrors from './computation/writeComputation/writeErrors';
|
import writeErrorsAndPropCount from './computation/writeComputation/writeErrorsAndPropCount';
|
||||||
|
|
||||||
export default async function computeCreature(creatureId) {
|
export default async function computeCreature(creatureId) {
|
||||||
if (Meteor.isClient) return;
|
if (Meteor.isClient) return;
|
||||||
@@ -32,7 +32,7 @@ async function computeComputation(computation, creatureId) {
|
|||||||
console.error(logError);
|
console.error(logError);
|
||||||
} finally {
|
} finally {
|
||||||
checkPropertyCount(computation)
|
checkPropertyCount(computation)
|
||||||
writeErrors(creatureId, computation.errors);
|
writeErrorsAndPropCount(creatureId, computation.errors, computation.props.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|||||||
import { RefSchema } from '/imports/api/parenting/ChildSchema';
|
import { RefSchema } from '/imports/api/parenting/ChildSchema';
|
||||||
import { assertDocEditPermission, assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertDocEditPermission, assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
import { compact } from 'lodash';
|
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';
|
import { fetchDocByRefAsync, getCollectionByName, moveDocBetweenRoots, moveDocWithinRoot } from '/imports/api/parenting/parentingFunctions';
|
||||||
|
|
||||||
const moveBetweenRoots = new ValidatedMethod({
|
const moveBetweenRoots = new ValidatedMethod({
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
|
export interface Colored {
|
||||||
|
color?: string,
|
||||||
|
}
|
||||||
|
|
||||||
const ColorSchema = new SimpleSchema({
|
const ColorSchema = new SimpleSchema({
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -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;
|
|
||||||
@@ -2,15 +2,25 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import '/imports/api/sharing/sharing';
|
import '/imports/api/sharing/sharing';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
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: {
|
owner: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
//@ts-expect-error index not defined
|
||||||
index: 1
|
index: 1
|
||||||
},
|
},
|
||||||
readers: {
|
readers: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
|
//@ts-expect-error index not defined
|
||||||
index: 1,
|
index: 1,
|
||||||
maxCount: STORAGE_LIMITS.readersCount,
|
maxCount: STORAGE_LIMITS.readersCount,
|
||||||
},
|
},
|
||||||
@@ -21,6 +31,7 @@ let SharingSchema = new SimpleSchema({
|
|||||||
writers: {
|
writers: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
|
//@ts-expect-error index not defined
|
||||||
index: 1,
|
index: 1,
|
||||||
maxCount: STORAGE_LIMITS.writersCount,
|
maxCount: STORAGE_LIMITS.writersCount,
|
||||||
},
|
},
|
||||||
@@ -31,6 +42,7 @@ let SharingSchema = new SimpleSchema({
|
|||||||
public: {
|
public: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
|
//@ts-expect-error index not defined
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
readersCanCopy: {
|
readersCanCopy: {
|
||||||
@@ -1,6 +1,23 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
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({
|
const InitiativeSchema = new SimpleSchema({
|
||||||
active: {
|
active: {
|
||||||
@@ -23,7 +40,7 @@ const InitiativeSchema = new SimpleSchema({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// All creatures in a tabletop have a shared time and space.
|
// All creatures in a tabletop have a shared time and space.
|
||||||
let TabletopSchema = new SimpleSchema({
|
const TabletopSchema = new SimpleSchema({
|
||||||
// Details
|
// Details
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -43,13 +60,38 @@ let TabletopSchema = new SimpleSchema({
|
|||||||
owner: String,
|
owner: String,
|
||||||
// The owner will need to included in one of these arrays for specific permissions
|
// 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
|
// A user should not appear in more than one of the following arrays
|
||||||
gameMasters: [String],
|
gameMasters: {
|
||||||
players: [String],
|
type: Array,
|
||||||
spectators: [String],
|
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?
|
// Does everyone else have the spectator permission?
|
||||||
public: {
|
public: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
defaultValue: false,
|
optional: true,
|
||||||
|
//@ts-expect-error Index not defined in simpl-schema package
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -61,10 +103,12 @@ let TabletopSchema = new SimpleSchema({
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//@ts-expect-error attachSchema not defined in simpl-schema package
|
||||||
Tabletops.attachSchema(TabletopSchema);
|
Tabletops.attachSchema(TabletopSchema);
|
||||||
|
|
||||||
import '/imports/api/tabletop/methods/removeTabletop';
|
import '/imports/api/tabletop/methods/removeTabletop';
|
||||||
import '/imports/api/tabletop/methods/insertTabletop';
|
import '/imports/api/tabletop/methods/insertTabletop';
|
||||||
|
import '/imports/api/tabletop/methods/updateTabletop';
|
||||||
import '/imports/api/tabletop/methods/addCreaturesToTabletop';
|
import '/imports/api/tabletop/methods/addCreaturesToTabletop';
|
||||||
|
|
||||||
export default Tabletops;
|
export default Tabletops;
|
||||||
@@ -2,7 +2,6 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import { assertUserInTabletop } from './shared/tabletopPermissions';
|
import { assertUserInTabletop } from './shared/tabletopPermissions';
|
||||||
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
|
|
||||||
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
|
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||||
|
|
||||||
@@ -16,15 +15,16 @@ const addCreaturesToTabletop = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
'creatureIds.$': {
|
'creatureIds.$': {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
tabletopId: {
|
tabletopId: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
}).validator(),
|
}).validator(),
|
||||||
|
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
|
// @ts-expect-error Rate limit not defined
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
@@ -37,16 +37,16 @@ const addCreaturesToTabletop = new ValidatedMethod({
|
|||||||
}
|
}
|
||||||
assertUserHasPaidBenefits(this.userId);
|
assertUserHasPaidBenefits(this.userId);
|
||||||
assertUserInTabletop(tabletopId, this.userId);
|
assertUserInTabletop(tabletopId, this.userId);
|
||||||
assertAdmin(this.userId);
|
|
||||||
|
|
||||||
Creatures.update({
|
Creatures.update({
|
||||||
_id: { $in: creatureIds },
|
_id: { $in: creatureIds },
|
||||||
|
// You must have write permission for the creatures you
|
||||||
$or: [
|
$or: [
|
||||||
{ writers: this.userId },
|
{ writers: this.userId },
|
||||||
{ owner: this.userId },
|
{ owner: this.userId },
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
$set: { tabletop: tabletopId },
|
$set: { tabletopId },
|
||||||
}, {
|
}, {
|
||||||
multi: true,
|
multi: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import Tabletops from '../Tabletops';
|
import Tabletops from '../Tabletops';
|
||||||
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
|
import { assertUserHasPaidBenefits, getUserTier } from '/imports/api/users/patreon/tiers';
|
||||||
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
|
|
||||||
|
|
||||||
const insertTabletop = new ValidatedMethod({
|
const insertTabletop = new ValidatedMethod({
|
||||||
|
|
||||||
@@ -11,8 +10,9 @@ const insertTabletop = new ValidatedMethod({
|
|||||||
validate: null,
|
validate: null,
|
||||||
|
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
|
// @ts-expect-error Rate limit not defined
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 2,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -22,13 +22,24 @@ const insertTabletop = new ValidatedMethod({
|
|||||||
'You need to be logged in to insert a tabletop');
|
'You need to be logged in to insert a tabletop');
|
||||||
}
|
}
|
||||||
assertUserHasPaidBenefits(this.userId);
|
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({
|
return Tabletops.insert({
|
||||||
gameMaster: this.userId,
|
owner: this.userId,
|
||||||
|
gameMasters: [this.userId],
|
||||||
|
players: [],
|
||||||
|
spectators: [],
|
||||||
|
initiative: {
|
||||||
|
active: false,
|
||||||
|
roundNumber: 0,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default insertTabletop;
|
export default insertTabletop;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -2,7 +2,6 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import Tabletops from '../Tabletops';
|
import Tabletops from '../Tabletops';
|
||||||
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
|
|
||||||
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
|
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers';
|
||||||
import { assertUserIsTabletopOwner } from './shared/tabletopPermissions';
|
import { assertUserIsTabletopOwner } from './shared/tabletopPermissions';
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures';
|
import Creatures from '/imports/api/creature/creatures/Creatures';
|
||||||
@@ -14,11 +13,12 @@ const removeTabletop = new ValidatedMethod({
|
|||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
tabletopId: {
|
tabletopId: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
}).validator(),
|
}).validator(),
|
||||||
|
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
|
// @ts-expect-error Rate limit not defined
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 5,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
@@ -31,7 +31,6 @@ const removeTabletop = new ValidatedMethod({
|
|||||||
}
|
}
|
||||||
assertUserHasPaidBenefits(this.userId);
|
assertUserHasPaidBenefits(this.userId);
|
||||||
assertUserIsTabletopOwner(tabletopId, this.userId);
|
assertUserIsTabletopOwner(tabletopId, this.userId);
|
||||||
assertAdmin(this.userId);
|
|
||||||
|
|
||||||
let removed = Tabletops.remove({
|
let removed = Tabletops.remove({
|
||||||
_id: tabletopId,
|
_id: tabletopId,
|
||||||
|
|||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
53
app/imports/api/tabletop/methods/updateTabletop.js
Normal file
53
app/imports/api/tabletop/methods/updateTabletop.js
Normal 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
@@ -9,6 +9,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 0,
|
minimumEntitledCents: 0,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 5,
|
characterSlots: 5,
|
||||||
|
tabletopSlots: 0,
|
||||||
fileStorage: 50,
|
fileStorage: 50,
|
||||||
paidBenefits: false,
|
paidBenefits: false,
|
||||||
}, {
|
}, {
|
||||||
@@ -16,6 +17,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 100,
|
minimumEntitledCents: 100,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 5,
|
characterSlots: 5,
|
||||||
|
tabletopSlots: 0,
|
||||||
fileStorage: 50,
|
fileStorage: 50,
|
||||||
paidBenefits: false,
|
paidBenefits: false,
|
||||||
}, {
|
}, {
|
||||||
@@ -23,6 +25,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 300,
|
minimumEntitledCents: 300,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 5,
|
characterSlots: 5,
|
||||||
|
tabletopSlots: 0,
|
||||||
fileStorage: 50,
|
fileStorage: 50,
|
||||||
paidBenefits: false,
|
paidBenefits: false,
|
||||||
}, {
|
}, {
|
||||||
@@ -31,6 +34,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 500,
|
minimumEntitledCents: 500,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 20,
|
characterSlots: 20,
|
||||||
|
tabletopSlots: 4,
|
||||||
fileStorage: 200,
|
fileStorage: 200,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
}, {
|
}, {
|
||||||
@@ -39,6 +43,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 1000,
|
minimumEntitledCents: 1000,
|
||||||
invites: 2,
|
invites: 2,
|
||||||
characterSlots: 50,
|
characterSlots: 50,
|
||||||
|
tabletopSlots: 10,
|
||||||
fileStorage: 500,
|
fileStorage: 500,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
}, {
|
}, {
|
||||||
@@ -47,6 +52,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 2000,
|
minimumEntitledCents: 2000,
|
||||||
invites: 5,
|
invites: 5,
|
||||||
characterSlots: 120,
|
characterSlots: 120,
|
||||||
|
tabletopSlots: 24,
|
||||||
fileStorage: 1000,
|
fileStorage: 1000,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
}, {
|
}, {
|
||||||
@@ -55,6 +61,7 @@ const TIERS = Object.freeze([
|
|||||||
minimumEntitledCents: 5000,
|
minimumEntitledCents: 5000,
|
||||||
invites: 15,
|
invites: 15,
|
||||||
characterSlots: -1, // Unlimited characters
|
characterSlots: -1, // Unlimited characters
|
||||||
|
tabletopSlots: -1, // Unlimited tabletops
|
||||||
fileStorage: 2000,
|
fileStorage: 2000,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
},
|
},
|
||||||
@@ -66,6 +73,7 @@ const GUEST_TIER = Object.freeze({
|
|||||||
guest: true,
|
guest: true,
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: 20,
|
characterSlots: 20,
|
||||||
|
tabletopSlots: 4,
|
||||||
fileStorage: 200,
|
fileStorage: 200,
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
});
|
});
|
||||||
@@ -76,6 +84,7 @@ const PATREON_DISABLED_TIER = Object.freeze({
|
|||||||
name: 'Outlander',
|
name: 'Outlander',
|
||||||
invites: 0,
|
invites: 0,
|
||||||
characterSlots: -1, // Can have infinitely many characters
|
characterSlots: -1, // Can have infinitely many characters
|
||||||
|
tabletopSlots: -1, // Infinite tabletops
|
||||||
fileStorage: 1000000, // 1TB file storage
|
fileStorage: 1000000, // 1TB file storage
|
||||||
paidBenefits: true,
|
paidBenefits: true,
|
||||||
});
|
});
|
||||||
|
|||||||
58
app/imports/client/ui/components/HexagonProgress.vue
Normal file
58
app/imports/client/ui/components/HexagonProgress.vue
Normal 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>
|
||||||
@@ -58,7 +58,10 @@ export default {
|
|||||||
addTabletop(){
|
addTabletop(){
|
||||||
this.addTabletopLoading = true;
|
this.addTabletopLoading = true;
|
||||||
insertTabletop.call(error => {
|
insertTabletop.call(error => {
|
||||||
if (error) snackbar(error.message);
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
|
}
|
||||||
this.addTabletopLoading = false;
|
this.addTabletopLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/client/ui/dialogStack/DialogBase.vue';
|
||||||
import CharacterSheet from '/imports/client/ui/creature/character/CharacterSheet.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 {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -68,6 +68,36 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-row>
|
</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-container>
|
||||||
<v-footer
|
<v-footer
|
||||||
inset
|
inset
|
||||||
@@ -96,13 +126,14 @@
|
|||||||
import addCreaturesToTabletop from '/imports/api/tabletop/methods/addCreaturesToTabletop';
|
import addCreaturesToTabletop from '/imports/api/tabletop/methods/addCreaturesToTabletop';
|
||||||
import TabletopCreatureCard from '/imports/client/ui/tabletop/TabletopCreatureCard.vue';
|
import TabletopCreatureCard from '/imports/client/ui/tabletop/TabletopCreatureCard.vue';
|
||||||
import TabletopMap from '/imports/client/ui/tabletop/TabletopMap.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 MiniCharacterSheet from '/imports/client/ui/creature/character/MiniCharacterSheet.vue';
|
||||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
import ActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
|
import ActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
|
||||||
import SelectedCreatureBar from '/imports/client/ui/tabletop/selectedCreatureBar/SelectedCreatureBar.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 = {}) {
|
const getProperties = function (creatureId, selector = {}) {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
@@ -129,6 +160,7 @@ export default {
|
|||||||
ActionCard,
|
ActionCard,
|
||||||
MiniCharacterSheet,
|
MiniCharacterSheet,
|
||||||
SelectedCreatureBar,
|
SelectedCreatureBar,
|
||||||
|
TabletopCreatureListItem,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
@@ -159,7 +191,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
creatures(){
|
creatures(){
|
||||||
return Creatures.find({ tabletop: this.model._id });
|
return Creatures.find({ tabletopId: this.model._id });
|
||||||
},
|
},
|
||||||
actions(){
|
actions(){
|
||||||
return getProperties(this.activeCreatureId, { type: 'action', actionType: { $ne: 'event'} });
|
return getProperties(this.activeCreatureId, { type: 'action', actionType: { $ne: 'event'} });
|
||||||
@@ -196,7 +228,10 @@ export default {
|
|||||||
tabletopId: this.model._id,
|
tabletopId: this.model._id,
|
||||||
creatureIds: charIds,
|
creatureIds: charIds,
|
||||||
}, error => {
|
}, error => {
|
||||||
if (error) snackbar(error.message);
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
snackbar({ text: error.message || error.toString() });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
48
app/imports/client/ui/tabletop/TabletopCreatureListItem.vue
Normal file
48
app/imports/client/ui/tabletop/TabletopCreatureListItem.vue
Normal 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>
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<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 CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||||
import TabletopActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
|
import TabletopActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
|
||||||
import CreatureBarIcon from '/imports/client/ui/tabletop/selectedCreatureBar/CreatureBarIcon.vue';
|
import CreatureBarIcon from '/imports/client/ui/tabletop/selectedCreatureBar/CreatureBarIcon.vue';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { serialMap } from '/imports/api/utility/asyncMap';
|
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 ParseNode from '/imports/parser/parseTree/ParseNode';
|
||||||
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
import ResolveFunction from '/imports/parser/types/ResolveFunction';
|
||||||
import MapFunction from '/imports/parser/types/MapFunction';
|
import MapFunction from '/imports/parser/types/MapFunction';
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ Meteor.publish('tabletops', function () {
|
|||||||
}
|
}
|
||||||
return Tabletops.find({
|
return Tabletops.find({
|
||||||
$or: [
|
$or: [
|
||||||
|
{ owner: userId },
|
||||||
{ players: userId },
|
{ players: userId },
|
||||||
{ gameMaster: userId },
|
{ gameMasters: userId },
|
||||||
|
{ spectators: userId },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -24,12 +26,15 @@ Meteor.publish('tabletop', function (tabletopId) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
this.autorun(function () {
|
this.autorun(function () {
|
||||||
|
if (!userId) return [];
|
||||||
const self = this;
|
const self = this;
|
||||||
let tabletopCursor = Tabletops.find({
|
let tabletopCursor = Tabletops.find({
|
||||||
_id: tabletopId,
|
_id: tabletopId,
|
||||||
$or: [
|
$or: [
|
||||||
|
{ owner: userId },
|
||||||
{ players: userId },
|
{ players: userId },
|
||||||
{ gameMaster: userId },
|
{ gameMasters: userId },
|
||||||
|
{ spectators: userId },
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
let tabletop = tabletopCursor.fetch()[0];
|
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
|
// read permission of this specific creature, so publish as few fields as
|
||||||
// possible
|
// possible
|
||||||
let creatureSummaries = Creatures.find({
|
let creatureSummaries = Creatures.find({
|
||||||
tabletop: tabletopId,
|
tabletopId,
|
||||||
}, {
|
}, {
|
||||||
fields: {
|
fields: {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
picture: 1,
|
picture: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
tabletop: 1,
|
tabletopId: 1,
|
||||||
initiativeRoll: 1,
|
initiativeRoll: 1,
|
||||||
settings: 1,
|
settings: 1,
|
||||||
|
propCount: 1,
|
||||||
},
|
},
|
||||||
|
limit: 110,
|
||||||
});
|
});
|
||||||
const creatureIds = creatureSummaries.map(c => c._id);
|
const creatureIds = creatureSummaries.map(c => c._id);
|
||||||
creatureIds.forEach(creatureId => {
|
creatureIds.forEach(creatureId => {
|
||||||
@@ -59,10 +66,14 @@ Meteor.publish('tabletop', function (tabletopId) {
|
|||||||
});
|
});
|
||||||
const variables = CreatureVariables.find({
|
const variables = CreatureVariables.find({
|
||||||
_creatureId: { $in: creatureIds }
|
_creatureId: { $in: creatureIds }
|
||||||
|
}, {
|
||||||
|
limit: 110,
|
||||||
});
|
});
|
||||||
let properties = CreatureProperties.find({
|
let properties = CreatureProperties.find({
|
||||||
'ancestors.id': { $in: creatureIds },
|
'ancestors.id': { $in: creatureIds },
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
|
}, {
|
||||||
|
limit: 10_000,
|
||||||
});
|
});
|
||||||
const logs = CreatureLogs.find({
|
const logs = CreatureLogs.find({
|
||||||
tabletopId,
|
tabletopId,
|
||||||
|
|||||||
Reference in New Issue
Block a user