diff --git a/app/imports/api/engine/loadCreatures.js b/app/imports/api/engine/loadCreatures.js
index aa036a37..829033cb 100644
--- a/app/imports/api/engine/loadCreatures.js
+++ b/app/imports/api/engine/loadCreatures.js
@@ -10,15 +10,17 @@ export const loadedCreatures = new Map(); // creatureId => {creature, properties
export function loadCreature(creatureId, subscription) {
if (!creatureId) throw 'creatureId is required';
let creature = loadedCreatures.get(creatureId);
+ if (!creature || !creature.subs.has(subscription)) {
+ subscription.onStop(() => {
+ unloadCreature(creatureId, subscription);
+ });
+ }
if (loadedCreatures.has(creatureId)) {
creature.subs.add(subscription);
} else {
creature = new LoadedCreature(subscription, creatureId);
loadedCreatures.set(creatureId, creature);
}
- subscription.onStop(() => {
- unloadCreature(creatureId, subscription);
- });
}
function unloadCreature(creatureId, subscription) {
@@ -43,7 +45,7 @@ export function getSingleProperty(creatureId, propertyId) {
const prop = CreatureProperties.findOne({
_id: propertyId,
'ancestors.id': creatureId,
- 'removed': {$ne: true},
+ 'removed': { $ne: true },
}, {
sort: { order: 1 },
});
@@ -61,7 +63,7 @@ export function getProperties(creatureId) {
// console.time(`Cache miss on creature properties: ${creatureId}`)
const props = CreatureProperties.find({
'ancestors.id': creatureId,
- 'removed': {$ne: true},
+ 'removed': { $ne: true },
}, {
sort: { order: 1 },
}).fetch();
@@ -73,7 +75,7 @@ export function getPropertiesOfType(creatureId, propType) {
if (loadedCreatures.has(creatureId)) {
const creature = loadedCreatures.get(creatureId);
const props = []
- for (const prop of creature.properties.values()){
+ for (const prop of creature.properties.values()) {
if (prop.type === propType) {
props.push(prop);
}
@@ -97,7 +99,7 @@ export function getCreature(creatureId) {
if (loadedCreatures.has(creatureId)) {
const loadedCreature = loadedCreatures.get(creatureId);
const creature = loadedCreature.creature;
- if (creature) {
+ if (creature) {
const cloneCreature = EJSON.clone(creature);
return cloneCreature;
}
@@ -118,7 +120,7 @@ export function getVariables(creatureId) {
}
}
// console.time(`Cache miss on variables: ${creatureId}`);
- const variables = CreatureVariables.findOne({_creatureId: creatureId});
+ const variables = CreatureVariables.findOne({ _creatureId: creatureId });
// console.timeEnd(`Cache miss on variables: ${creatureId}`);
return variables;
}
@@ -148,7 +150,7 @@ export function getProperyAncestors(creatureId, propertyId) {
// Fetch from database
return CreatureProperties.find({
_id: { $in: ancestorIds },
- removed: {$ne: true},
+ removed: { $ne: true },
}, {
sort: { order: 1 },
}).fetch();
@@ -164,7 +166,7 @@ export function getPropertyDecendants(creatureId, propertyId) {
if (loadedCreatures.has(creatureId)) {
const creature = loadedCreatures.get(creatureId);
const props = [];
- for(const prop of creature.properties.values()){
+ for (const prop of creature.properties.values()) {
if (prop.ancestors[expectedAncestorPostition]?.id === propertyId) {
props.push(prop);
}
@@ -216,7 +218,7 @@ class LoadedCreature {
compute();
},
});
-
+
// Observe the creature itself
self.creatureObserver = Creatures.find({
_id: creatureId,
@@ -239,7 +241,7 @@ class LoadedCreature {
self.variablesObserver = CreatureVariables.find({
_creatureId: creatureId,
}, {
- fields: { _creatureId: 0},
+ fields: { _creatureId: 0 },
}).observeChanges({
added(id, fields) {
fields._id = id;
diff --git a/app/imports/api/tabletop/map/TabletopMap.vue b/app/imports/api/tabletop/map/TabletopMap.vue
deleted file mode 100644
index 0c87c5ae..00000000
--- a/app/imports/api/tabletop/map/TabletopMap.vue
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/imports/client/ui/components/RollPopup.vue b/app/imports/client/ui/components/RollPopup.vue
index f9f20f66..a4521339 100644
--- a/app/imports/client/ui/components/RollPopup.vue
+++ b/app/imports/client/ui/components/RollPopup.vue
@@ -12,8 +12,8 @@
{ if (!noClick) e.stopPropagation(); }"
>
@@ -87,6 +87,7 @@ export default {
type: Number,
default: undefined,
},
+ noClick: Boolean,
},
data(){return {
open: false,
diff --git a/app/imports/client/ui/creature/character/CharacterSheet.vue b/app/imports/client/ui/creature/character/CharacterSheet.vue
index 75f4cbd8..568ebe79 100644
--- a/app/imports/client/ui/creature/character/CharacterSheet.vue
+++ b/app/imports/client/ui/creature/character/CharacterSheet.vue
@@ -30,17 +30,17 @@
$store.commit(
'setTabForCharacterSheet',
- {id: $route.params.id, tab: e}
+ {id: creatureId, tab: e}
)"
>
@@ -68,11 +68,10 @@
-
$store.commit(
'setTabForCharacterSheet',
- {id: $route.params.id, tab: e}
+ {id: creatureId, tab: e}
)"
>
@@ -164,7 +163,9 @@ export default {
type: String,
required: true,
},
+ embedded: Boolean,
},
+ // @ts-ignore
reactiveProvide: {
name: 'context',
include: ['creatureId', 'editPermission'],
@@ -250,7 +251,11 @@ export default {
diff --git a/app/imports/client/ui/dialogStack/DialogComponentIndex.js b/app/imports/client/ui/dialogStack/DialogComponentIndex.js
index 87530412..bb491fec 100644
--- a/app/imports/client/ui/dialogStack/DialogComponentIndex.js
+++ b/app/imports/client/ui/dialogStack/DialogComponentIndex.js
@@ -1,6 +1,7 @@
// Load commonly used dialogs immediately
import AddCreaturePropertyDialog from '/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue';
import CharacterCreationDialog from '/imports/client/ui/creature/character/CharacterCreationDialog.vue';
+import CharacterSheetDialog from '/imports/client/ui/tabletop/CharacterSheetDialog.vue';
import CastSpellWithSlotDialog from '/imports/client/ui/properties/components/spells/CastSpellWithSlotDialog.vue';
import CreatureFormDialog from '/imports/client/ui/creature/CreatureFormDialog.vue';
import CreaturePropertyCreationDialog from '/imports/client/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue';
@@ -8,9 +9,9 @@ import CreaturePropertyDialog from '/imports/client/ui/creature/creatureProperti
import CreaturePropertyFromLibraryDialog from '/imports/client/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue';
import CreatureRootDialog from '/imports/client/ui/creature/character/CreatureRootDialog.vue';
import DeleteConfirmationDialog from '/imports/client/ui/dialogStack/DeleteConfirmationDialog.vue';
-import ExperienceInsertDialog from '/imports/client/ui/creature/experiences/ExperienceInsertDialog.vue';
-import ExperienceListDialog from '/imports/client/ui/creature/experiences/ExperienceListDialog.vue';
-import HelpDialog from '/imports/client/ui/dialogStack/HelpDialog.vue';
+import ExperienceInsertDialog from '/imports/client/ui/creature/experiences/ExperienceInsertDialog.vue';
+import ExperienceListDialog from '/imports/client/ui/creature/experiences/ExperienceListDialog.vue';
+import HelpDialog from '/imports/client/ui/dialogStack/HelpDialog.vue';
import LevelUpDialog from '/imports/client/ui/creature/slots/LevelUpDialog.vue';
import SelectLibraryNodeDialog from '/imports/client/ui/library/SelectLibraryNodeDialog.vue';
import SlotFillDialog from '/imports/client/ui/creature/slots/SlotFillDialog.vue';
@@ -37,6 +38,7 @@ export default {
ArchiveDialog,
CastSpellWithSlotDialog,
CharacterCreationDialog,
+ CharacterSheetDialog,
CreatureFormDialog,
CreaturePropertyCreationDialog,
CreaturePropertyDialog,
diff --git a/app/imports/client/ui/layouts/Sidebar.vue b/app/imports/client/ui/layouts/Sidebar.vue
index 1b893f37..bf9d0562 100644
--- a/app/imports/client/ui/layouts/Sidebar.vue
+++ b/app/imports/client/ui/layouts/Sidebar.vue
@@ -80,6 +80,7 @@ export default {
components: {
CreatureFolderList
},
+ // @ts-ignore
meteor: {
$subscribe: {
'characterList': [],
@@ -97,7 +98,7 @@ export default {
{ title: 'Home', icon: 'mdi-home', to: '/' },
{ title: 'Characters', icon: 'mdi-account-group', to: '/character-list', requireLogin: true },
{ title: 'Library', icon: 'mdi-library-shelves', to: '/library', requireLogin: true },
- //{title: 'Tabletops', icon: 'api', to: '/tabletops', requireLogin: true},
+ {title: 'Tabletops', icon: 'mdi-table-furniture', to: '/tabletops', requireLogin: true},
//{title: 'Friends', icon: 'people', to: '/friends', requireLogin: true},
{ title: 'Files', icon: 'mdi-file-multiple', to: '/my-files' },
{ title: 'Feedback', icon: 'mdi-bug', to: '/feedback' },
diff --git a/app/imports/client/ui/properties/components/actions/AttributeConsumedView.vue b/app/imports/client/ui/properties/components/actions/AttributeConsumedView.vue
index 26571332..9ae84918 100644
--- a/app/imports/client/ui/properties/components/actions/AttributeConsumedView.vue
+++ b/app/imports/client/ui/properties/components/actions/AttributeConsumedView.vue
@@ -4,16 +4,23 @@
:class="insufficient && 'error--text'"
>
- {{ model.quantity && model.quantity.value }}
+ {{ model.quantity.value }}
{{ model.statName || model.variableName }}
+
+ ({{ model.available }})
+
diff --git a/app/imports/client/ui/properties/components/actions/ItemConsumedView.vue b/app/imports/client/ui/properties/components/actions/ItemConsumedView.vue
index f0eae75a..11be31fb 100644
--- a/app/imports/client/ui/properties/components/actions/ItemConsumedView.vue
+++ b/app/imports/client/ui/properties/components/actions/ItemConsumedView.vue
@@ -27,28 +27,30 @@
:color="model.itemColor"
/>
-
- {{ model.available }} / {{ quantity }}
-
-
- {{ model.available }}
-
+ {{ quantity }}
-
-
- {{ model.itemName }}
-
-
+
- Select item
-
+ {{ model.itemName }}
+
+
+ ({{ model.available }})
+
+
+
+ Select item
{
title: 'Print Character Sheet',
},
},
- /* Not ready for prime time <3
{
path: '/tabletops',
name: 'tabletops',
@@ -208,7 +207,6 @@ RouterFactory.configure(router => {
},
beforeEnter: ensureLoggedIn,
},
- */
{
path: '/friends',
components: {
diff --git a/app/imports/client/ui/styles/cardColors.css b/app/imports/client/ui/styles/cardColors.css
index 04f0f52e..2873e9a4 100644
--- a/app/imports/client/ui/styles/cardColors.css
+++ b/app/imports/client/ui/styles/cardColors.css
@@ -7,14 +7,22 @@
background: #151515;
}
+.card-background .v-tabs-items.theme--dark {
+ background: #151515;
+}
+
.theme--light .card-background {
background: #f6f6f6;
}
+.card-background .v-tabs-items.theme--light {
+ background: #f6f6f6;
+}
+
.theme--dark .card-raised-background {
background: #1d1d1d;
}
.theme--light .card-raised-background {
background: #fafafa;
-}
+}
\ No newline at end of file
diff --git a/app/imports/api/tabletop/CharacterSheetDialog.vue b/app/imports/client/ui/tabletop/CharacterSheetDialog.vue
similarity index 51%
rename from app/imports/api/tabletop/CharacterSheetDialog.vue
rename to app/imports/client/ui/tabletop/CharacterSheetDialog.vue
index 93367fe8..f3b010b0 100644
--- a/app/imports/api/tabletop/CharacterSheetDialog.vue
+++ b/app/imports/client/ui/tabletop/CharacterSheetDialog.vue
@@ -1,41 +1,60 @@
-
+
$store.commit(
+ 'setTabForCharacterSheet',
+ {id: creatureId, tab: e}
+ )"
>
Stats
- Features
-
-
- Inventory
+ Actions
Spells
- Character
+ Inventory
+
+
+ Features
+
+
+ Journal
+
+
+ Build
Tree
-
+
+
+
+
+
+
+
diff --git a/app/imports/client/ui/tabletop/TabletopActionCards.vue b/app/imports/client/ui/tabletop/TabletopActionCards.vue
deleted file mode 100644
index 5fe2efd8..00000000
--- a/app/imports/client/ui/tabletop/TabletopActionCards.vue
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/imports/client/ui/tabletop/TabletopComponent.vue b/app/imports/client/ui/tabletop/TabletopComponent.vue
index 603c8fbd..0b5fe967 100644
--- a/app/imports/client/ui/tabletop/TabletopComponent.vue
+++ b/app/imports/client/ui/tabletop/TabletopComponent.vue
@@ -19,16 +19,29 @@
{
+ if (activeActionId) {
+ if (targets.includes(creature._id)) {
+ untarget(creature._id)
+ } else {
+ targets.push(creature._id);
+ }
+ } else {
+ activeCreatureId = creature._id;
+ targets = [];
+ activeActionId = undefined;
+ }
+ }"
@target="targets.push(creature._id)"
@untarget="untarget(creature._id)"
/>
@@ -67,22 +80,23 @@
@@ -95,9 +109,29 @@ import addCreaturesToTabletop from '/imports/api/tabletop/methods/addCreaturesTo
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 TabletopActionCards from '/imports/client/ui/tabletop/TabletopActionCards.vue';
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.js';
+import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
+import ActionCard from '/imports/client/ui/tabletop/TabletopActionCard.vue';
+
+const getProperties = function (creatureId, selector = {}) {
+ return CreatureProperties.find({
+ 'ancestors.id': {
+ $eq: creatureId,
+ },
+ inactive: { $ne: true },
+ removed: { $ne: true },
+ overridden: { $ne: true },
+ $nor: [
+ { hideWhenTotalZero: true, total: 0 },
+ { hideWhenValueZero: true, value: 0 },
+ ],
+ ...selector,
+ }, {
+ sort: { order: 1 }
+ });
+}
export default {
components: {
@@ -118,8 +152,9 @@ export default {
},
data() {
return {
- activeCreature: undefined,
- targets: [],
+ activeCreatureId: undefined,
+ activeActionId: undefined,
+ targets: [],
}
},
meteor: {
@@ -132,28 +167,11 @@ export default {
return Creatures.find({ tabletop: this.model._id });
},
actions(){
- return getProperties(this.activeCreature, 'action').map(a => {
- delete a.summary;
- return a;
- });
+ return getProperties(this.activeCreatureId, { type: 'action', actionType: { $ne: 'event'} });
},
editPermission(){
try {
- assertEditPermission(this.activeCreature, Meteor.userId());
- return true;
- } catch (e) {
- return false;
- }
- },
- actions(){
- return getProperties(this.activeCreature, 'action').map(a => {
- delete a.summary;
- return a;
- });
- },
- editPermission(){
- try {
- assertEditPermission(this.activeCreature, Meteor.userId());
+ assertEditPermission(this.activeCreatureId, Meteor.userId());
return true;
} catch (e) {
return false;
@@ -184,7 +202,7 @@ export default {
component: 'character-sheet-dialog',
elementId: 'mini-character-sheet',
data: {
- creatureId: this.activeCreature,
+ creatureId: this.activeCreatureId,
},
});
},
@@ -199,7 +217,7 @@ export default {
if (!event.deltaY) {
return;
}
- event.currentTarget.scrollLeft += event.deltaY + event.deltaX;
+ event.currentTarget.scrollLeft += event.deltaY;
event.preventDefault();
},
untarget(id){
@@ -208,7 +226,7 @@ export default {
this.targets.splice(index, 1);
}
}
- }
+ },
}
@@ -220,12 +238,11 @@ export default {
width: 100px;
margin: 4px;
}
-.action-row > .v-card {
+.action-row > div {
flex-grow: 0;
flex-shrink: 0;
- max-height: 320px;
+ height: 120px;
width: 200px;
margin: 4px;
- overflow-y: hidden;
}
diff --git a/app/imports/client/ui/tabletop/TabletopCreatureCard.vue b/app/imports/client/ui/tabletop/TabletopCreatureCard.vue
index 656249e6..51677e49 100644
--- a/app/imports/client/ui/tabletop/TabletopCreatureCard.vue
+++ b/app/imports/client/ui/tabletop/TabletopCreatureCard.vue
@@ -1,15 +1,17 @@
-
- {{ targeted ? 'mdi-target' : 'mdi-target' }}
-
+
+
+ {{ targeted ? 'mdi-target' : 'mdi-target' }}
+
+
@@ -63,3 +78,9 @@ export default {
text-overflow: ellipsis;
}
+
+
diff --git a/app/imports/client/ui/tabletop/TabletopLog.vue b/app/imports/client/ui/tabletop/TabletopLog.vue
index 2dcd16ea..bdb671e6 100644
--- a/app/imports/client/ui/tabletop/TabletopLog.vue
+++ b/app/imports/client/ui/tabletop/TabletopLog.vue
@@ -1,16 +1,13 @@
-
+
\ No newline at end of file
diff --git a/app/imports/server/publications/tabletops.js b/app/imports/server/publications/tabletops.js
index 83bc84c1..88e8bd2e 100644
--- a/app/imports/server/publications/tabletops.js
+++ b/app/imports/server/publications/tabletops.js
@@ -2,37 +2,41 @@ import Tabletops from '/imports/api/tabletop/Tabletops.js';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
+import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
+import { loadCreature } from '/imports/api/engine/loadCreatures.js';
-Meteor.publish('tabletops', function(){
+Meteor.publish('tabletops', function () {
var userId = this.userId;
if (!userId) {
return [];
}
return Tabletops.find({
$or: [
- {players: userId},
- {gameMaster: userId},
+ { players: userId },
+ { gameMaster: userId },
],
});
});
-Meteor.publish('tabletop', function(tabletopId){
+Meteor.publish('tabletop', function (tabletopId) {
var userId = this.userId;
if (!userId) {
return [];
}
- this.autorun(function (){
+ this.autorun(function () {
+ const self = this;
let tabletopCursor = Tabletops.find({
_id: tabletopId,
$or: [
- {players: userId},
- {gameMaster: userId},
+ { players: userId },
+ { gameMaster: userId },
]
});
let tabletop = tabletopCursor.fetch()[0];
- if (!tabletop){
+ if (!tabletop) {
return [];
}
+
// Warning, this leaks data to users of the same tabletop who may not have
// read permission of this specific creature, so publish as few fields as
// possible
@@ -40,25 +44,31 @@ Meteor.publish('tabletop', function(tabletopId){
tabletop: tabletopId,
}, {
fields: {
+ _id: 1,
name: 1,
picture: 1,
avatarPicture: 1,
- variables: 1,
tabletop: 1,
initiativeRoll: 1,
},
});
const creatureIds = creatureSummaries.map(c => c._id);
+ creatureIds.forEach(creatureId => {
+ loadCreature(creatureId, self);
+ });
+ const variables = CreatureVariables.find({
+ _creatureId: { $in: creatureIds }
+ });
let properties = CreatureProperties.find({
- 'ancestors.0.id': {$in: creatureIds},
- removed: {$ne: true},
+ 'ancestors.id': { $in: creatureIds },
+ removed: { $ne: true },
});
const logs = CreatureLogs.find({
tabletopId,
}, {
limit: 50,
- sort: {date: -1},
+ sort: { date: -1 },
});
- return [ tabletopCursor, creatureSummaries, properties, logs]
+ return [tabletopCursor, creatureSummaries, properties, logs, variables]
})
});
diff --git a/app/package-lock.json b/app/package-lock.json
index ef158fca..7b048605 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "dicecloud",
- "version": "2.0.45",
+ "version": "2.0.51",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3175,15 +3175,9 @@
"dev": true
},
"signal-exit": {
-<<<<<<< HEAD
- "version": "3.0.2",
- "resolved": "",
- "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
-=======
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
->>>>>>> version-2
},
"simpl-schema": {
"version": "1.13.1",
@@ -3483,9 +3477,9 @@
"dev": true
},
"three": {
- "version": "0.139.2",
- "resolved": "https://registry.npmjs.org/three/-/three-0.139.2.tgz",
- "integrity": "sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg=="
+ "version": "0.148.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.148.0.tgz",
+ "integrity": "sha512-8uzVV+qhTPi0bOFs/3te3RW6hb3urL8jYEl6irjCWo/l6sr8MPNMcClFev/MMYeIxr0gmDcoXTy/8LXh/LXkfw=="
},
"to-regex-range": {
"version": "5.0.1",
diff --git a/app/package.json b/app/package.json
index 7cee6eb3..d5ca8927 100644
--- a/app/package.json
+++ b/app/package.json
@@ -48,6 +48,7 @@
"simpl-schema": "^1.13.1",
"source-map-support": "^0.5.21",
"speakingurl": "^14.0.1",
+ "three": "^0.148.0",
"vue": "2.6.10",
"vue-meteor-tracker": "^2.0.0",
"vue-reactive-provide": "^0.3.0",
@@ -118,4 +119,4 @@
]
}
}
-}
\ No newline at end of file
+}