Merge branch 'version-2-dev' into version-2
This commit is contained in:
15
README.md
15
README.md
@@ -66,5 +66,20 @@ You should see this:
|
||||
=> App running at: http://localhost:3000/
|
||||
```
|
||||
|
||||
Environmental Variables
|
||||
-----------------------
|
||||
|
||||
```
|
||||
MAIL_URL=smtp://<your smtp mail url>
|
||||
METEOR_SETTINGS={ "public": { "environment": "production", "patreon": { "clientId": "<your patreon client ID>", "campaignId": "<your campaign id>" } }, "patreon": { "clientSecret": "<your client secret>", "creatorAccessToken": "<your creator access token>" } }
|
||||
MONGO_OPLOG_URL=mongodb+srv://<your url for the oplog account of your mongo database>
|
||||
MONGO_URL=mongodb+srv://<your url for the read/write account of your mongo database>
|
||||
NPM_CONFIG_PRODUCTION=true
|
||||
PROJECT_DIR=app
|
||||
ROOT_URL=https://<url of your DiceCloud instance>
|
||||
DEFAULT_LIBRARIES=<comma separated list of library ids that will be subscribed by default: "abc123,def456">
|
||||
DISABLE_PATREON=<"true" if you want to prevent features being locked behind Patreon tiers>
|
||||
```
|
||||
|
||||
Now, visiting [](http://localhost:3000/) should show you an empty instance of
|
||||
DiceCloud running.
|
||||
|
||||
@@ -3,42 +3,42 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
accounts-password@1.7.0
|
||||
random@1.2.0
|
||||
accounts-password
|
||||
random
|
||||
dburles:collection-helpers
|
||||
reactive-var@1.0.11
|
||||
underscore@1.0.10
|
||||
reactive-var
|
||||
underscore
|
||||
momentjs:moment
|
||||
dburles:mongo-collection-instances
|
||||
accounts-google@1.3.3
|
||||
email@2.0.0
|
||||
accounts-google
|
||||
email
|
||||
meteorhacks:subs-manager
|
||||
chuangbo:marked
|
||||
meteor-base@1.4.0
|
||||
mobile-experience@1.1.0
|
||||
mongo@1.11.0
|
||||
session@1.2.0
|
||||
tracker@1.2.0
|
||||
logging@1.2.0
|
||||
reload@1.3.1
|
||||
ejson@1.1.1
|
||||
check@1.3.1
|
||||
standard-minifier-js@2.6.0
|
||||
shell-server@0.5.0
|
||||
meteor-base
|
||||
mobile-experience
|
||||
mongo
|
||||
session
|
||||
tracker
|
||||
logging
|
||||
reload
|
||||
ejson
|
||||
check
|
||||
standard-minifier-js
|
||||
shell-server
|
||||
templates:array
|
||||
ecmascript@0.15.1
|
||||
es5-shim@4.8.0
|
||||
reactive-dict@1.3.0
|
||||
ecmascript
|
||||
es5-shim
|
||||
reactive-dict
|
||||
percolate:synced-cron
|
||||
ongoworks:speakingurl
|
||||
service-configuration@1.0.11
|
||||
dynamic-import@0.6.0
|
||||
ddp-rate-limiter@1.0.9
|
||||
rate-limit@1.0.9
|
||||
service-configuration
|
||||
dynamic-import
|
||||
ddp-rate-limiter
|
||||
rate-limit
|
||||
mdg:validated-method
|
||||
akryum:vue-router2
|
||||
static-html
|
||||
aldeed:collection2@3.0.0
|
||||
aldeed:collection2
|
||||
aldeed:schema-index
|
||||
zer0th:meteor-vuetify-loader
|
||||
accounts-patreon
|
||||
|
||||
@@ -1 +1 @@
|
||||
METEOR@2.2
|
||||
METEOR@2.2.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
accounts-base@1.9.0
|
||||
accounts-google@1.3.3
|
||||
accounts-oauth@1.2.0
|
||||
accounts-password@1.7.0
|
||||
accounts-password@1.7.1
|
||||
accounts-patreon@0.1.0
|
||||
akryum:npm-check@0.1.2
|
||||
akryum:vue-component@0.15.2
|
||||
@@ -9,19 +9,19 @@ akryum:vue-component-dev-client@0.4.7
|
||||
akryum:vue-component-dev-server@0.1.4
|
||||
akryum:vue-router2@0.2.3
|
||||
akryum:vue-sass@0.1.2
|
||||
aldeed:collection2@3.3.0
|
||||
aldeed:collection2@3.4.1
|
||||
aldeed:schema-index@3.0.0
|
||||
allow-deny@1.1.0
|
||||
autoupdate@1.7.0
|
||||
babel-compiler@7.6.1
|
||||
babel-compiler@7.6.2
|
||||
babel-runtime@1.5.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze-tools@1.1.1
|
||||
blaze-tools@1.1.2
|
||||
boilerplate-generator@1.7.1
|
||||
bozhao:link-accounts@2.3.2
|
||||
bozhao:link-accounts@2.4.0
|
||||
caching-compiler@1.2.2
|
||||
caching-html-compiler@1.2.0
|
||||
caching-html-compiler@1.2.1
|
||||
callback-hook@1.3.0
|
||||
check@1.3.1
|
||||
chuangbo:marked@0.3.5_1
|
||||
@@ -30,17 +30,17 @@ coffeescript-compiler@2.4.1
|
||||
dburles:collection-helpers@1.1.0
|
||||
dburles:mongo-collection-instances@0.3.5
|
||||
ddp@1.4.0
|
||||
ddp-client@2.4.0
|
||||
ddp-client@2.4.1
|
||||
ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.0.9
|
||||
ddp-server@2.3.2
|
||||
ddp-server@2.3.3
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.6.0
|
||||
ecmascript@0.15.1
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.11.0
|
||||
ecmascript-runtime-server@0.10.0
|
||||
ecmascript-runtime-client@0.11.1
|
||||
ecmascript-runtime-server@0.10.1
|
||||
ejson@1.1.1
|
||||
email@2.0.0
|
||||
es5-shim@4.8.0
|
||||
@@ -48,10 +48,10 @@ fetch@0.1.1
|
||||
geojson-utils@1.0.10
|
||||
google-oauth@1.3.0
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.1.1
|
||||
htmljs@1.1.0
|
||||
http@1.4.3
|
||||
id-map@1.1.0
|
||||
html-tools@1.1.2
|
||||
htmljs@1.1.1
|
||||
http@1.4.4
|
||||
id-map@1.1.1
|
||||
inter-process-messaging@0.1.1
|
||||
lai:collection-extensions@0.2.1_1
|
||||
launch-screen@1.2.1
|
||||
@@ -63,8 +63,8 @@ meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
mikowals:batch-insert@1.2.0
|
||||
minifier-css@1.5.3
|
||||
minifier-js@2.6.0
|
||||
minifier-css@1.5.4
|
||||
minifier-js@2.6.1
|
||||
minimongo@1.6.2
|
||||
mobile-experience@1.1.0
|
||||
mobile-status-bar@1.1.0
|
||||
@@ -72,11 +72,11 @@ modern-browsers@0.1.5
|
||||
modules@0.16.0
|
||||
modules-runtime@0.12.0
|
||||
momentjs:moment@2.29.1
|
||||
mongo@1.11.0
|
||||
mongo@1.11.1
|
||||
mongo-decimal@0.1.2
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
npm-bcrypt@0.9.3
|
||||
mongo-id@1.0.8
|
||||
npm-bcrypt@0.9.4
|
||||
npm-mongo@3.9.0
|
||||
oauth@1.3.2
|
||||
oauth2@1.3.0
|
||||
@@ -98,7 +98,7 @@ promise@0.11.2
|
||||
raix:eventemitter@1.0.0
|
||||
random@1.2.0
|
||||
rate-limit@1.0.9
|
||||
react-fast-refresh@0.1.0
|
||||
react-fast-refresh@0.1.1
|
||||
reactive-dict@1.3.0
|
||||
reactive-var@1.0.11
|
||||
reload@1.3.1
|
||||
@@ -112,18 +112,18 @@ shell-server@0.5.0
|
||||
simple:json-routes@2.1.0
|
||||
simple:rest@1.1.1
|
||||
simple:rest-method-mixin@1.0.1
|
||||
socket-stream-client@0.3.1
|
||||
spacebars-compiler@1.2.1
|
||||
socket-stream-client@0.3.3
|
||||
spacebars-compiler@1.3.0
|
||||
srp@1.1.0
|
||||
standard-minifier-js@2.6.0
|
||||
static-html@1.3.0
|
||||
standard-minifier-js@2.6.1
|
||||
static-html@1.3.2
|
||||
templates:array@1.0.3
|
||||
templating-tools@1.2.0
|
||||
tmeasday:check-npm-versions@1.0.1
|
||||
templating-tools@1.2.1
|
||||
tmeasday:check-npm-versions@1.0.2
|
||||
tracker@1.2.0
|
||||
typescript@4.2.2
|
||||
underscore@1.0.10
|
||||
url@1.3.1
|
||||
url@1.3.2
|
||||
webapp@1.10.1
|
||||
webapp-hashing@1.1.0
|
||||
zer0th:meteor-vuetify-loader@0.1.30
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width initial-scale=1.0, user-scalable=no">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
let Parties = new Mongo.Collection('parties');
|
||||
|
||||
let partySchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: 'New Party',
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
creatures: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
'creatures.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
});
|
||||
|
||||
Parties.attachSchema(partySchema);
|
||||
|
||||
export default Parties;
|
||||
@@ -62,6 +62,7 @@ function applyPropertyAndWalkChildren({prop, children, targets, ...options}){
|
||||
export default function applyProperties({ forest, targets, ...options}){
|
||||
forest.forEach(node => {
|
||||
let prop = node.node;
|
||||
options.actionContext[`#${prop.type}`] = prop;
|
||||
let children = node.children;
|
||||
if (shouldSplit(prop) && targets.length){
|
||||
targets.forEach(target => {
|
||||
|
||||
@@ -2,12 +2,13 @@ import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import { doActionWork } from '/imports/api/creature/actions/doAction.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js';
|
||||
|
||||
const castSpellWithSlot = new ValidatedMethod({
|
||||
name: 'creatureProperties.castSpellWithSlot',
|
||||
@@ -61,9 +62,11 @@ const castSpellWithSlot = new ValidatedMethod({
|
||||
value: 1,
|
||||
});
|
||||
}
|
||||
let actionContext = getAncestorContext(spell);
|
||||
|
||||
doActionWork({
|
||||
action: spell,
|
||||
context: {slotLevel},
|
||||
actionContext: {slotLevel, ...actionContext},
|
||||
creature,
|
||||
targets: target ? [target] : [],
|
||||
method: this,
|
||||
|
||||
@@ -2,15 +2,16 @@ import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
||||
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js';
|
||||
|
||||
const doAction = new ValidatedMethod({
|
||||
name: 'creatureProperties.doAction',
|
||||
@@ -36,6 +37,10 @@ const doAction = new ValidatedMethod({
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
// Check permissions
|
||||
let creature = getRootCreatureAncestor(action);
|
||||
|
||||
// Build ancestor context
|
||||
let actionContext = getAncestorContext(action);
|
||||
|
||||
assertEditPermission(creature, this.userId);
|
||||
let targets = [];
|
||||
targetIds.forEach(targetId => {
|
||||
@@ -43,7 +48,7 @@ const doAction = new ValidatedMethod({
|
||||
assertEditPermission(target, this.userId);
|
||||
targets.push(target);
|
||||
});
|
||||
doActionWork({action, creature, targets, method: this});
|
||||
doActionWork({action, creature, targets, actionContext, method: this});
|
||||
|
||||
// The acting creature might have used ammo
|
||||
recomputeInventory(creature._id);
|
||||
@@ -64,7 +69,7 @@ export function doActionWork({
|
||||
action,
|
||||
creature,
|
||||
targets,
|
||||
context = {},
|
||||
actionContext = {},
|
||||
method
|
||||
}){
|
||||
// Create the log
|
||||
@@ -83,7 +88,7 @@ export function doActionWork({
|
||||
}];
|
||||
applyProperties({
|
||||
forest: startingForest,
|
||||
actionContext: context,
|
||||
actionContext,
|
||||
creature,
|
||||
targets,
|
||||
log,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import roll from '/imports/parser/roll.js';
|
||||
|
||||
const doCheck = new ValidatedMethod({
|
||||
|
||||
15
app/imports/api/creature/actions/getAncestorContext.js
Normal file
15
app/imports/api/creature/actions/getAncestorContext.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
|
||||
export default function getAncestorContext(prop){
|
||||
// Build ancestor context
|
||||
const actionContext = {};
|
||||
let ancestorIds = prop.ancestors.map(ref => ref.id);
|
||||
CreatureProperties.find({
|
||||
_id: {$in: ancestorIds}
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
}).forEach(ancestor => {
|
||||
actionContext[`#${ancestor.type}`] = ancestor;
|
||||
});
|
||||
return actionContext;
|
||||
}
|
||||
57
app/imports/api/creature/archive/ArchivedCreatures.js
Normal file
57
app/imports/api/creature/archive/ArchivedCreatures.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
// Archived creatures is an immutable collection of creatures that are no longer
|
||||
// in use and can be safely archived by the mongoDB hosting service.
|
||||
// It keeps the working datasets like creatureProperties much smaller
|
||||
// than they would otherwise be.
|
||||
let ArchivedCreatures = new Mongo.Collection('archivedCreatures');
|
||||
|
||||
// We use blackbox objects for everything:
|
||||
// - saves time checking every object against a schema
|
||||
// - doesn't accidentaly create indices defined in subschemas
|
||||
// - The objects we are archiving have already been checked against their
|
||||
// own schemas
|
||||
let ArchivedCreatureSchema = new SimpleSchema({
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
// The primary index on this collection
|
||||
index: 1,
|
||||
},
|
||||
archiveDate: {
|
||||
type: Date,
|
||||
// Indexed so the archiving system can archive documents when they
|
||||
// get to a certain age
|
||||
index: 1,
|
||||
},
|
||||
creature: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
properties: {
|
||||
type: Array,
|
||||
},
|
||||
'properties.$': {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
experiences: {
|
||||
type: Array,
|
||||
},
|
||||
'experiences.$': {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
logs: {
|
||||
type: Array,
|
||||
},
|
||||
'logs.$': {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
});
|
||||
|
||||
ArchivedCreatures.attachSchema(ArchivedCreatureSchema);
|
||||
|
||||
import '/imports/api/creature/archive/methods/index.js';
|
||||
export default ArchivedCreatures;
|
||||
66
app/imports/api/creature/archive/methods/archiveCreatures.js
Normal file
66
app/imports/api/creature/archive/methods/archiveCreatures.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertOwnership } from '/imports/api/creature/creatures/creaturePermissions.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 Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
||||
|
||||
function archiveCreature(creatureId){
|
||||
// Build the archive document
|
||||
const creature = Creatures.findOne(creatureId);
|
||||
const properties = CreatureProperties.find({'ancestors.id': creatureId}).fetch();
|
||||
const experiences = Experiences.find({creatureId}).fetch();
|
||||
const logs = CreatureLogs.find({creatureId}).fetch();
|
||||
let archiveCreature = {
|
||||
owner: creature.owner,
|
||||
archiveDate: new Date(),
|
||||
creature,
|
||||
properties,
|
||||
experiences,
|
||||
logs,
|
||||
};
|
||||
|
||||
// Insert it
|
||||
let id = ArchivedCreatures.insert(archiveCreature);
|
||||
|
||||
// Remove the original creature
|
||||
removeCreatureWork(creatureId);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
const archiveCreatures = new ValidatedMethod({
|
||||
name: 'Creatures.methods.archiveCreatures',
|
||||
validate: new SimpleSchema({
|
||||
creatureIds: {
|
||||
type: Array,
|
||||
max: 10,
|
||||
},
|
||||
'creatureIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({creatureIds}) {
|
||||
for (let id of creatureIds){
|
||||
assertOwnership(id, this.userId)
|
||||
}
|
||||
let archivedIds = [];
|
||||
for (let id of creatureIds){
|
||||
let archivedId = archiveCreature(id);
|
||||
archivedIds.push(archivedId);
|
||||
}
|
||||
return archivedIds;
|
||||
},
|
||||
});
|
||||
|
||||
export default archiveCreatures;
|
||||
2
app/imports/api/creature/archive/methods/index.js
Normal file
2
app/imports/api/creature/archive/methods/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import '/imports/api/creature/archive/methods/archiveCreatures.js';
|
||||
import '/imports/api/creature/archive/methods/restoreCreatures.js';
|
||||
77
app/imports/api/creature/archive/methods/restoreCreatures.js
Normal file
77
app/imports/api/creature/archive/methods/restoreCreatures.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertOwnership } from '/imports/api/sharing/sharingPermissions.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 Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
||||
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
|
||||
function restoreCreature(archiveId){
|
||||
// Get the archive
|
||||
const archivedCreature = ArchivedCreatures.findOne(archiveId);
|
||||
|
||||
// Insert the creature sub documents
|
||||
// They still have their original _id's
|
||||
Creatures.insert(archivedCreature.creature);
|
||||
try {
|
||||
// Add all the properties
|
||||
if (archivedCreature.properties && archivedCreature.properties.length){
|
||||
CreatureProperties.batchInsert(archivedCreature.properties);
|
||||
}
|
||||
if (archivedCreature.experiences && archivedCreature.experiences.length){
|
||||
Experiences.batchInsert(archivedCreature.experiences);
|
||||
}
|
||||
if (archivedCreature.logs && archivedCreature.logs.length){
|
||||
CreatureLogs.batchInsert(archivedCreature.logs);
|
||||
}
|
||||
// Remove the archived creature
|
||||
ArchivedCreatures.remove(archiveId);
|
||||
} catch (e) {
|
||||
// If the above fails, delete the inserted creature
|
||||
removeCreatureWork(archivedCreature.creature._id);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Do not recompute. The creature was in a computed and ordered state when
|
||||
// we archived it, just restore everything as-is
|
||||
|
||||
return archivedCreature.creature._id;
|
||||
}
|
||||
|
||||
const restoreCreatures = new ValidatedMethod({
|
||||
name: 'Creatures.methods.restoreCreatures',
|
||||
validate: new SimpleSchema({
|
||||
archiveIds: {
|
||||
type: Array,
|
||||
max: 10,
|
||||
},
|
||||
'archiveIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({archiveIds}) {
|
||||
for (let id of archiveIds){
|
||||
let archivedCreature = ArchivedCreatures.findOne(id, {
|
||||
fields: {owner: 1}
|
||||
});
|
||||
assertOwnership(archivedCreature, this.userId)
|
||||
}
|
||||
let creatureIds = [];
|
||||
for (let id of archiveIds){
|
||||
let creatureId = restoreCreature(id);
|
||||
creatureIds.push(creatureId);
|
||||
}
|
||||
return creatureIds;
|
||||
},
|
||||
});
|
||||
|
||||
export default restoreCreatures;
|
||||
@@ -1,6 +1,6 @@
|
||||
export default class EffectAggregator{
|
||||
constructor(){
|
||||
this.base = 0;
|
||||
this.base = undefined;
|
||||
this.add = 0;
|
||||
this.mul = 1;
|
||||
this.min = Number.NEGATIVE_INFINITY;
|
||||
@@ -20,7 +20,13 @@ export default class EffectAggregator{
|
||||
switch(effect.operation){
|
||||
case 'base':
|
||||
// Take the largest base value
|
||||
this.base = result > this.base ? result : this.base;
|
||||
if (Number.isFinite(result)){
|
||||
if(Number.isFinite(this.base)){
|
||||
this.base = Math.max(this.base, result);
|
||||
} else {
|
||||
this.base = result;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'add':
|
||||
// Add all adds together
|
||||
|
||||
@@ -14,7 +14,14 @@ export default function combineStat(stat, aggregator, memo){
|
||||
}
|
||||
|
||||
function getAggregatorResult(stat, aggregator){
|
||||
let base = Math.max(aggregator.base, stat.baseValue || 0);
|
||||
let base;
|
||||
if (!Number.isFinite(aggregator.base)){
|
||||
base = stat.baseValue || 0;
|
||||
} else if (!Number.isFinite(stat.baseValue)){
|
||||
base = aggregator.base || 0;
|
||||
} else {
|
||||
base = Math.max(aggregator.base, stat.baseValue);
|
||||
}
|
||||
let result = (base + aggregator.add) * aggregator.mul;
|
||||
if (result < aggregator.min) {
|
||||
result = aggregator.min;
|
||||
@@ -137,7 +144,8 @@ function combineSkill(stat, aggregator, memo){
|
||||
}
|
||||
|
||||
// Combine everything to get the final result
|
||||
let result = (aggregator.base + stat.abilityMod + profBonus + aggregator.add) * aggregator.mul;
|
||||
let base = aggregator.base || 0;
|
||||
let result = (base + stat.abilityMod + profBonus + aggregator.add) * aggregator.mul;
|
||||
if (result < aggregator.min) result = aggregator.min;
|
||||
if (result > aggregator.max) result = aggregator.max;
|
||||
if (aggregator.set !== undefined) {
|
||||
|
||||
@@ -102,24 +102,31 @@ export default function computeStat(stat, memo){
|
||||
prop: statInstance,
|
||||
memo
|
||||
});
|
||||
statInstance.baseValue = +result.value;
|
||||
result.value = +result.value;
|
||||
if (!isNaN(result.value)){
|
||||
statInstance.baseValue = result.value;
|
||||
} else {
|
||||
statInstance.baseValue = undefined;
|
||||
}
|
||||
statInstance.dependencies = union(statInstance.dependencies, dependencies);
|
||||
if (context.errors.length){
|
||||
statInstance.baseValueErrors = context.errors;
|
||||
}
|
||||
// Apply all the base values
|
||||
effects.push({
|
||||
operation: 'base',
|
||||
calculation: statInstance.baseValueCalculation,
|
||||
result: statInstance.baseValue,
|
||||
stats: [statInstance.variableName],
|
||||
dependencies: statInstance.overridden ?
|
||||
if (Number.isFinite(statInstance.baseValue)){
|
||||
effects.push({
|
||||
operation: 'base',
|
||||
calculation: statInstance.baseValueCalculation,
|
||||
result: statInstance.baseValue,
|
||||
stats: [statInstance.variableName],
|
||||
dependencies: statInstance.overridden ?
|
||||
union(statInstance.dependencies, [statInstance._id]) :
|
||||
[],
|
||||
computationDetails: {
|
||||
computed: true,
|
||||
},
|
||||
});
|
||||
computationDetails: {
|
||||
computed: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pick, forOwn } from 'lodash';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import VERSION from '/imports/constants/VERSION.js';
|
||||
|
||||
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import ComputationMemo from '/imports/api/creature/computation/engine/ComputationMemo.js';
|
||||
import getComputationProperties from '/imports/api/creature/computation/engine/getComputationProperties.js';
|
||||
import computeMemo from '/imports/api/creature/computation/engine/computeMemo.js';
|
||||
@@ -11,7 +11,7 @@ import { recomputeDamageMultipliersById } from '/imports/api/creature/denormalis
|
||||
import recomputeSlotFullness from '/imports/api/creature/denormalise/recomputeSlotFullness.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import getDependentProperties from '/imports/api/creature/computation/engine/getDependentProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||
|
||||
export const recomputeCreature = new ValidatedMethod({
|
||||
|
||||
37
app/imports/api/creature/creatureFolders/CreatureFolders.js
Normal file
37
app/imports/api/creature/creatureFolders/CreatureFolders.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
let CreatureFolders = new Mongo.Collection('creatureFolders');
|
||||
|
||||
let creatureFolderSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
creatures: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
'creatures.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
},
|
||||
archived: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
});
|
||||
|
||||
CreatureFolders.attachSchema(creatureFolderSchema);
|
||||
|
||||
import '/imports/api/creature/creatureFolders/methods.js/index.js';
|
||||
export default CreatureFolders;
|
||||
@@ -0,0 +1,4 @@
|
||||
import '/imports/api/creature/creatureFolders/methods.js/insertCreatureFolder.js';
|
||||
import '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
||||
import '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
||||
import '/imports/api/creature/creatureFolders/methods.js/moveCreatureToFolder.js';
|
||||
@@ -0,0 +1,46 @@
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const insertCreatureFolder = new ValidatedMethod({
|
||||
name: 'creatureFolders.methods.insert',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run() {
|
||||
// Ensure logged in
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('creatureFolders.methods.insert.denied',
|
||||
'You need to be logged in to insert a folder');
|
||||
}
|
||||
// Limit folders to 50 per user
|
||||
let existingFolders = CreatureFolders.find({
|
||||
owner: userId
|
||||
}, {
|
||||
fields: {order: 1},
|
||||
sort: {order :-1}
|
||||
});
|
||||
if (existingFolders.count() >= 50){
|
||||
throw new Meteor.Error('creatureFolders.methods.insert.denied',
|
||||
'You can not have more than 50 folders');
|
||||
}
|
||||
// Make the new folder the last in the order
|
||||
let order = 0;
|
||||
let lastFolder = existingFolders.fetch()[0];
|
||||
if (lastFolder){
|
||||
order = (lastFolder.order || 0) + 1;
|
||||
}
|
||||
// Insert
|
||||
return CreatureFolders.insert({
|
||||
name: 'Folder',
|
||||
owner: userId,
|
||||
order,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default insertCreatureFolder;
|
||||
@@ -0,0 +1,45 @@
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const moveCreatureToFolder = new ValidatedMethod({
|
||||
name: 'creatureFolders.methods.moveCreatureToFolder',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({creatureId, folderId}) {
|
||||
// Ensure logged in
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||
'You need to be logged in to remove a folder');
|
||||
}
|
||||
// Check that this folder is owned by the user
|
||||
if (folderId){
|
||||
let existingFolder = CreatureFolders.findOne(folderId);
|
||||
if (existingFolder.owner !== userId){
|
||||
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||
'This folder does not belong to you');
|
||||
}
|
||||
}
|
||||
// Remove from other folders
|
||||
CreatureFolders.update({
|
||||
owner: userId
|
||||
}, {
|
||||
$pull: {creatures: creatureId},
|
||||
}, {
|
||||
multi: true,
|
||||
});
|
||||
if (folderId){
|
||||
// Add to this folder
|
||||
CreatureFolders.update(folderId, {
|
||||
$addToSet: {creatures: creatureId},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default moveCreatureToFolder;
|
||||
@@ -0,0 +1,31 @@
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const removeCreatureFolder = new ValidatedMethod({
|
||||
name: 'creatureFolders.methods.remove',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}) {
|
||||
// Ensure logged in
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||
'You need to be logged in to remove a folder');
|
||||
}
|
||||
// Check that this folder is owned by the user
|
||||
let existingFolder = CreatureFolders.findOne(_id);
|
||||
if (existingFolder.owner !== userId){
|
||||
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||
'This folder does not belong to you');
|
||||
}
|
||||
// Remove
|
||||
return CreatureFolders.remove(_id);
|
||||
},
|
||||
});
|
||||
|
||||
export default removeCreatureFolder;
|
||||
@@ -0,0 +1,43 @@
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const reorderCreatureFolder = new ValidatedMethod({
|
||||
name: 'creatureFolders.methods.reorder',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, order}) {
|
||||
// Ensure logged in
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('creatureFolders.methods.reorder.denied',
|
||||
'You need to be logged in to reorder a folder');
|
||||
}
|
||||
// Check that this folder is owned by the user
|
||||
let existingFolder = CreatureFolders.findOne(_id);
|
||||
if (existingFolder.owner !== userId){
|
||||
throw new Meteor.Error('creatureFolders.methods.reorder.denied',
|
||||
'This folder does not belong to you');
|
||||
}
|
||||
// First give it the new order, it should end in 0.5 putting it between two other docs
|
||||
CreatureFolders.update(_id, {$set: {order}});
|
||||
this.unblock();
|
||||
// Reorder all the folders with integer numbers in this new order
|
||||
CreatureFolders.find({
|
||||
owner: userId
|
||||
}, {
|
||||
fields: {order: 1,},
|
||||
sort: {order: -1}
|
||||
}).forEach((folder, index) => {
|
||||
if (folder.order !== index){
|
||||
CreatureFolders.update(_id, {$set: {order: index}})
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default reorderCreatureFolder;
|
||||
@@ -0,0 +1,31 @@
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const updateCreatureFolderName = new ValidatedMethod({
|
||||
name: 'creatureFolders.methods.updateName',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, name}) {
|
||||
// Ensure logged in
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||
'You need to be logged in to update a folder');
|
||||
}
|
||||
// Check that this folder is owned by the user
|
||||
let existingFolder = CreatureFolders.findOne(_id);
|
||||
if (existingFolder.owner !== userId){
|
||||
throw new Meteor.Error('creatureFolders.methods.updateName.denied',
|
||||
'This folder does not belong to you');
|
||||
}
|
||||
// Update
|
||||
return CreatureFolders.update(_id, {$set: {name}});
|
||||
},
|
||||
});
|
||||
|
||||
export default updateCreatureFolderName;
|
||||
@@ -1,4 +1,4 @@
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
|
||||
export default function getRootCreatureAncestor(property){
|
||||
return Creatures.findOne(property.ancestors[0].id);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import { recomputePropertyDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import { recomputeCreatureByDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
|
||||
@@ -192,6 +192,9 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
return true;
|
||||
});
|
||||
|
||||
// TODO: Force the referencedNode to take the old id of the reference
|
||||
// such that the reference's children can be kept
|
||||
|
||||
// Give the new referenced sub-tree new ids
|
||||
renewDocIds({
|
||||
docArray: addedNodes,
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema.js'
|
||||
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
|
||||
import SharingSchema from '/imports/api/sharing/SharingSchema.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers.js';
|
||||
import defaultCharacterProperties from '/imports/api/creature/defaultCharacterProperties.js';
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
import '/imports/api/creature/removeCreature.js';
|
||||
import '/imports/api/creature/restCreature.js';
|
||||
|
||||
//set up the collection for creatures
|
||||
let Creatures = new Mongo.Collection('creatures');
|
||||
@@ -176,92 +167,8 @@ CreatureSchema.extend(SharingSchema);
|
||||
|
||||
Creatures.attachSchema(CreatureSchema);
|
||||
|
||||
const insertCreature = new ValidatedMethod({
|
||||
|
||||
name: 'creatures.insertCreature',
|
||||
|
||||
validate: null,
|
||||
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run() {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Creatures.methods.insert.denied',
|
||||
'You need to be logged in to insert a creature');
|
||||
}
|
||||
assertUserHasPaidBenefits(this.userId);
|
||||
|
||||
// Create the creature document
|
||||
let creatureId = Creatures.insert({
|
||||
owner: this.userId,
|
||||
});
|
||||
|
||||
// Insert the default properties
|
||||
// Not batchInsert because we want the properties cleaned by the schema
|
||||
let baseId;
|
||||
defaultCharacterProperties(creatureId).forEach(prop => {
|
||||
let id = CreatureProperties.insert(prop);
|
||||
if (prop.name === 'Ruleset'){
|
||||
baseId = id;
|
||||
}
|
||||
});
|
||||
|
||||
if (Meteor.isServer){
|
||||
// Insert the 5e ruleset as the default base
|
||||
insertPropertyFromLibraryNode.call({
|
||||
nodeId: 'iHbhfcg3AL5isSWbw',
|
||||
parentRef: {id: baseId, collection: 'creatureProperties'},
|
||||
order: 0.5,
|
||||
});
|
||||
}
|
||||
|
||||
this.unblock();
|
||||
return creatureId;
|
||||
},
|
||||
});
|
||||
|
||||
const updateCreature = new ValidatedMethod({
|
||||
name: 'creatures.update',
|
||||
validate({_id, path}){
|
||||
if (!_id) return false;
|
||||
// Allowed fields
|
||||
let allowedFields = [
|
||||
'name',
|
||||
'alignment',
|
||||
'gender',
|
||||
'picture',
|
||||
'avatarPicture',
|
||||
'color',
|
||||
'settings',
|
||||
];
|
||||
if (!allowedFields.includes(path[0])){
|
||||
throw new Meteor.Error('Creatures.methods.update.denied',
|
||||
'This field can\'t be updated using this method');
|
||||
}
|
||||
},
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}) {
|
||||
let creature = Creatures.findOne(_id);
|
||||
assertEditPermission(creature, this.userId);
|
||||
if (value === undefined || value === null){
|
||||
Creatures.update(_id, {
|
||||
$unset: {[path.join('.')]: 1},
|
||||
});
|
||||
} else {
|
||||
Creatures.update(_id, {
|
||||
$set: {[path.join('.')]: value},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
import '/imports/api/creature/creatures/methods/index.js';
|
||||
|
||||
export default Creatures;
|
||||
export { CreatureSchema, insertCreature, updateCreature };
|
||||
export { CreatureSchema };
|
||||
@@ -1,4 +1,4 @@
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import {
|
||||
assertEditPermission as editPermission,
|
||||
assertViewPermission as viewPermission,
|
||||
5
app/imports/api/creature/creatures/methods/index.js
Normal file
5
app/imports/api/creature/creatures/methods/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import '/imports/api/creature/creatures/methods/insertCreature.js';
|
||||
import '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
import '/imports/api/creature/creatures/methods/restCreature.js';
|
||||
import '/imports/api/creature/creatures/methods/transferCreatureOwnership.js';
|
||||
import '/imports/api/creature/creatures/methods/updateCreature.js';
|
||||
70
app/imports/api/creature/creatures/methods/insertCreature.js
Normal file
70
app/imports/api/creature/creatures/methods/insertCreature.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
import defaultCharacterProperties from '/imports/api/creature/creatures/defaultCharacterProperties.js';
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
|
||||
const insertCreature = new ValidatedMethod({
|
||||
|
||||
name: 'creatures.insertCreature',
|
||||
|
||||
validate: null,
|
||||
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run() {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Creatures.methods.insert.denied',
|
||||
'You need to be logged in to insert a creature');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
|
||||
let currentCharacterCount = Creatures.find({
|
||||
owner: this.userId,
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
}).count();
|
||||
|
||||
if (
|
||||
tier.characterSlots !== -1 &&
|
||||
currentCharacterCount >= tier.characterSlots
|
||||
){
|
||||
throw new Meteor.Error('Creatures.methods.insert.denied',
|
||||
`You are already at your limit of ${tier.characterSlots} characters`)
|
||||
}
|
||||
|
||||
// Create the creature document
|
||||
let creatureId = Creatures.insert({
|
||||
owner: this.userId,
|
||||
});
|
||||
|
||||
// Insert the default properties
|
||||
// Not batchInsert because we want the properties cleaned by the schema
|
||||
let baseId;
|
||||
defaultCharacterProperties(creatureId).forEach(prop => {
|
||||
let id = CreatureProperties.insert(prop);
|
||||
if (prop.name === 'Ruleset'){
|
||||
baseId = id;
|
||||
}
|
||||
});
|
||||
|
||||
if (Meteor.isServer){
|
||||
// Insert the 5e ruleset as the default base
|
||||
insertPropertyFromLibraryNode.call({
|
||||
nodeId: 'iHbhfcg3AL5isSWbw',
|
||||
parentRef: {id: baseId, collection: 'creatureProperties'},
|
||||
order: 0.5,
|
||||
});
|
||||
}
|
||||
|
||||
return creatureId;
|
||||
},
|
||||
});
|
||||
|
||||
export default insertCreature;
|
||||
@@ -1,8 +1,8 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertOwnership } from '/imports/api/creature/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import { assertOwnership } from '/imports/api/creature/creatures/creaturePermissions.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 Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||
@@ -1,9 +1,9 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
|
||||
const restCreature = new ValidatedMethod({
|
||||
@@ -0,0 +1,55 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { assertOwnership } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
const transferCreatureOwnership = new ValidatedMethod({
|
||||
|
||||
name: 'creatures.methods.transferOwnership',
|
||||
|
||||
validate: new SimpleSchema({
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run({creatureId, userId}) {
|
||||
assertOwnership(creatureId, this.userId);
|
||||
|
||||
let tier = getUserTier(userId);
|
||||
let currentCharacterCount = Creatures.find({
|
||||
owner: userId,
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
}).count();
|
||||
|
||||
if (
|
||||
tier.characterSlots !== -1 &&
|
||||
currentCharacterCount >= tier.characterSlots
|
||||
){
|
||||
throw new Meteor.Error('Creatures.methods.transferOwnership.denied',
|
||||
'The new owner is already at their character limit')
|
||||
}
|
||||
|
||||
Creatures.update(creatureId, {
|
||||
$set: {owner: userId},
|
||||
});
|
||||
|
||||
return creatureId;
|
||||
},
|
||||
});
|
||||
|
||||
export default transferCreatureOwnership;
|
||||
45
app/imports/api/creature/creatures/methods/updateCreature.js
Normal file
45
app/imports/api/creature/creatures/methods/updateCreature.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
|
||||
|
||||
const updateCreature = new ValidatedMethod({
|
||||
name: 'creatures.update',
|
||||
validate({_id, path}){
|
||||
if (!_id) return false;
|
||||
// Allowed fields
|
||||
let allowedFields = [
|
||||
'name',
|
||||
'alignment',
|
||||
'gender',
|
||||
'picture',
|
||||
'avatarPicture',
|
||||
'color',
|
||||
'settings',
|
||||
];
|
||||
if (!allowedFields.includes(path[0])){
|
||||
throw new Meteor.Error('Creatures.methods.update.denied',
|
||||
'This field can\'t be updated using this method');
|
||||
}
|
||||
},
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}) {
|
||||
let creature = Creatures.findOne(_id);
|
||||
assertEditPermission(creature, this.userId);
|
||||
if (value === undefined || value === null){
|
||||
Creatures.update(_id, {
|
||||
$unset: {[path.join('.')]: 1},
|
||||
});
|
||||
} else {
|
||||
Creatures.update(_id, {
|
||||
$set: {[path.join('.')]: value},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default updateCreature;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
|
||||
export const recomputeDamageMultipliers = new ValidatedMethod({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
||||
|
||||
export default function recomputeInventory(creatureId){
|
||||
|
||||
@@ -2,8 +2,8 @@ import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
|
||||
let Experiences = new Mongo.Collection('experiences');
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import LogContentSchema from '/imports/api/creature/log/LogContentSchema.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import {assertEditPermission} from '/imports/api/creature/creaturePermissions.js';
|
||||
import {assertEditPermission} from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import {
|
||||
parse,
|
||||
CompilationContext,
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
assertEditPermission,
|
||||
assertViewPermission,
|
||||
assertOwnership,
|
||||
} from '/imports/api/creature/creaturePermissions.js';
|
||||
} from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
|
||||
// Checks if the method has permission to run on the document. If the document
|
||||
// has a charId, that creature is checked, otherwise if it has an _id and the
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { _ } from 'meteor/underscore';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
function assertIdValid(userId){
|
||||
if (!userId || typeof userId !== 'string'){
|
||||
@@ -48,13 +47,6 @@ export function assertEditPermission(doc, userId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure the user is of a tier with paid benefits
|
||||
let tier = getUserTier(user);
|
||||
if (!tier.paidBenefits){
|
||||
throw new Meteor.Error('Edit permission denied',
|
||||
`The ${tier.name} tier does not allow you to edit this document`);
|
||||
}
|
||||
|
||||
// Ensure the user is authorized for this specific document
|
||||
if (
|
||||
doc.owner === userId ||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import Tabletops, { assertUserInTabletop } from '/imports/api/tabletop/Tabletops.js';
|
||||
|
||||
let Messages = new Mongo.Collection('messages');
|
||||
|
||||
@@ -2,7 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
|
||||
let Tabletops = new Mongo.Collection('tabletops');
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import '/imports/api/users/deleteMyAccount.js';
|
||||
const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LIBRARIES.split(',') || [];
|
||||
|
||||
const userSchema = new SimpleSchema({
|
||||
username: {
|
||||
@@ -63,7 +64,7 @@ const userSchema = new SimpleSchema({
|
||||
},
|
||||
subscribedLibraries: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
defaultValue: defaultLibraries,
|
||||
max: 100,
|
||||
},
|
||||
'subscribedLibraries.$': {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Libraries, {removeLibaryWork} from '/imports/api/library/Libraries.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import {removeCreatureWork} from '/imports/api/creature/removeCreature.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import {removeCreatureWork} from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
|
||||
Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||
name: 'users.deleteMyAccount',
|
||||
|
||||
@@ -1,58 +1,78 @@
|
||||
import { findLast } from 'lodash';
|
||||
import getEntitledCents from '/imports/api/users/patreon/getEntitledCents.js';
|
||||
import Invites from '/imports/api/users/Invites.js';
|
||||
const patreonDisabled = !!process.env.DISABLE_PATREON;
|
||||
|
||||
const TIERS = [
|
||||
const TIERS = Object.freeze([
|
||||
{
|
||||
name: 'Commoner',
|
||||
minimumEntitledCents: 0,
|
||||
invites: 0,
|
||||
characterSlots: 0, //5,
|
||||
paidBenefits: false,
|
||||
}, {
|
||||
name: 'Dreamer',
|
||||
minimumEntitledCents: 100,
|
||||
invites: 0,
|
||||
characterSlots: 0, //5,
|
||||
paidBenefits: false,
|
||||
}, {
|
||||
name: 'Wanderer',
|
||||
minimumEntitledCents: 300,
|
||||
invites: 0,
|
||||
characterSlots: 0, //5,
|
||||
paidBenefits: false,
|
||||
}, {
|
||||
//cost per user $5
|
||||
name: 'Adventurer',
|
||||
minimumEntitledCents: 500,
|
||||
invites: 0,
|
||||
characterSlots: -1, //20,
|
||||
paidBenefits: true,
|
||||
}, {
|
||||
//cost per user $3.33
|
||||
name: 'Hero',
|
||||
minimumEntitledCents: 1000,
|
||||
invites: 2,
|
||||
characterSlots: -1, //50,
|
||||
paidBenefits: true,
|
||||
}, {
|
||||
//cost per user $3.333
|
||||
name: 'Legend',
|
||||
minimumEntitledCents: 2000,
|
||||
invites: 5,
|
||||
characterSlots: -1, //120,
|
||||
paidBenefits: true,
|
||||
}, {
|
||||
//cost per user $3.125
|
||||
name: 'Paragon',
|
||||
minimumEntitledCents: 5000,
|
||||
invites: 15,
|
||||
characterSlots: -1, // Unlimited characters
|
||||
paidBenefits: true,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const GUEST_TIER = {
|
||||
// Companion tier should be equivalent to the Adventurer tier
|
||||
const GUEST_TIER = Object.freeze({
|
||||
name: 'Companion',
|
||||
guest: true,
|
||||
invites: 0,
|
||||
characterSlots: 20,
|
||||
paidBenefits: true,
|
||||
}
|
||||
});
|
||||
|
||||
// When patreon features are disabled, give all the users the same tier
|
||||
// with no limitations
|
||||
const PATREON_DISABLED_TIER = Object.freeze({
|
||||
name: 'Outlander',
|
||||
invites: 0,
|
||||
characterSlots: -1, // Can have infinitely many characters
|
||||
paidBenefits: true,
|
||||
});
|
||||
|
||||
export function getTierByEntitledCents(entitledCents = 0){
|
||||
if (patreonDisabled) return PATREON_DISABLED_TIER;
|
||||
return findLast(TIERS, tier => entitledCents >= tier.minimumEntitledCents);
|
||||
}
|
||||
|
||||
@@ -66,6 +86,7 @@ export function getUserTier(user){
|
||||
});
|
||||
if (!user) throw 'User not found';
|
||||
}
|
||||
if (patreonDisabled) return PATREON_DISABLED_TIER;
|
||||
const entitledCents = getEntitledCents(user);
|
||||
const tier = getTierByEntitledCents(entitledCents);
|
||||
if (tier.paidBenefits) return tier;
|
||||
|
||||
@@ -24,11 +24,11 @@ const PROPERTIES = Object.freeze({
|
||||
name: 'Class level'
|
||||
},
|
||||
constant: {
|
||||
icon: 'anchor',
|
||||
icon: 'mdi-anchor',
|
||||
name: 'Constant'
|
||||
},
|
||||
container: {
|
||||
icon: 'work',
|
||||
icon: 'mdi-bag-personal-outline',
|
||||
name: 'Container'
|
||||
},
|
||||
damage: {
|
||||
@@ -44,23 +44,23 @@ const PROPERTIES = Object.freeze({
|
||||
name: 'Effect'
|
||||
},
|
||||
feature: {
|
||||
icon: 'subject',
|
||||
icon: 'mdi-text-subject',
|
||||
name: 'Feature'
|
||||
},
|
||||
folder: {
|
||||
icon: 'folder',
|
||||
icon: 'mdi-folder-outline',
|
||||
name: 'Folder'
|
||||
},
|
||||
item: {
|
||||
icon: '$vuetify.icons.item',
|
||||
icon: 'mdi-cube-outline',
|
||||
name: 'Item'
|
||||
},
|
||||
note: {
|
||||
icon: 'note',
|
||||
icon: 'mdi-note-outline',
|
||||
name: 'Note'
|
||||
},
|
||||
proficiency: {
|
||||
icon: 'radio_button_checked',
|
||||
icon: 'mdi-brightness-1',
|
||||
name: 'Proficiency'
|
||||
},
|
||||
roll: {
|
||||
@@ -68,7 +68,7 @@ const PROPERTIES = Object.freeze({
|
||||
name: 'Roll'
|
||||
},
|
||||
reference: {
|
||||
icon: 'link',
|
||||
icon: 'mdi-vector-link',
|
||||
name: 'Reference',
|
||||
libraryOnly: true,
|
||||
},
|
||||
@@ -81,11 +81,11 @@ const PROPERTIES = Object.freeze({
|
||||
name: 'Skill'
|
||||
},
|
||||
propertySlot: {
|
||||
icon: 'tab_unselected',
|
||||
icon: 'mdi-power-socket-eu',
|
||||
name: 'Slot'
|
||||
},
|
||||
slotFiller: {
|
||||
icon: 'picture_in_picture',
|
||||
icon: 'mdi-power-plug-outline',
|
||||
name: 'Slot filler'
|
||||
},
|
||||
spellList: {
|
||||
|
||||
File diff suppressed because one or more lines are too long
19
app/imports/server/publications/archivedCreatures.js
Normal file
19
app/imports/server/publications/archivedCreatures.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
||||
|
||||
Meteor.publish('archivedCreatures', function(){
|
||||
this.autorun(function (){
|
||||
var userId = this.userId;
|
||||
if (!userId) {
|
||||
return [];
|
||||
}
|
||||
return ArchivedCreatures.find({
|
||||
owner: userId,
|
||||
}, {
|
||||
fields: {
|
||||
creature: 1,
|
||||
owner: 1,
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Parties from '/imports/api/creature/Parties.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
|
||||
Meteor.publish('characterList', function(){
|
||||
this.autorun(function (){
|
||||
@@ -36,7 +36,7 @@ Meteor.publish('characterList', function(){
|
||||
}
|
||||
}
|
||||
),
|
||||
Parties.find({owner: userId}),
|
||||
CreatureFolders.find({owner: userId}),
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||
import { assertViewPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
|
||||
let schema = new SimpleSchema({
|
||||
creatureId: {
|
||||
|
||||
@@ -6,5 +6,6 @@ import '/imports/server/publications/experiences.js';
|
||||
import '/imports/server/publications/users.js';
|
||||
import '/imports/server/publications/icons.js';
|
||||
import '/imports/server/publications/tabletops.js';
|
||||
import '/imports/server/publications/slotFillers.js'
|
||||
import '/imports/server/publications/ownedDocuments.js'
|
||||
import '/imports/server/publications/slotFillers.js';
|
||||
import '/imports/server/publications/ownedDocuments.js';
|
||||
import '/imports/server/publications/archivedCreatures.js';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
|
||||
Meteor.publish('ownedDocuments', function(){
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import Creatures from '/imports/api/creature/Creatures.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 { assertViewPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import recomputeInvetory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import VERSION from '/imports/constants/VERSION.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Tabletops from '/imports/api/tabletop/Tabletops.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import Messages from '/imports/api/tabletop/Messages.js';
|
||||
|
||||
Meteor.publish('tabletops', function(){
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
icon
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>format_paint</v-icon>
|
||||
<v-icon>mdi-format-paint</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card class="overflow-hidden">
|
||||
@@ -30,7 +30,7 @@
|
||||
v-if="kebabColor === colorOption"
|
||||
:class="{dark: isDark(colorOption, shade)}"
|
||||
>
|
||||
check
|
||||
mdi-check
|
||||
</v-icon>
|
||||
</v-scroll-y-transition>
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@
|
||||
v-if="kebabShade === shadeOption"
|
||||
:class="isDark(color, shade) ? 'dark' : 'light'"
|
||||
>
|
||||
check
|
||||
mdi-check
|
||||
</v-icon>
|
||||
</v-scroll-y-transition>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@click.stop
|
||||
>
|
||||
<slot>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</slot>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
class="filled"
|
||||
@click="toggleAdd(); $nextTick(() => $refs.editInput.focus())"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="context.editPermission === false"
|
||||
class="filled"
|
||||
@click="toggleSubtract(); $nextTick(() => $refs.editInput.focus())"
|
||||
>
|
||||
<v-icon>remove</v-icon>
|
||||
<v-icon>mdi-minus</v-icon>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-text-field
|
||||
@@ -48,7 +48,7 @@
|
||||
class="mx-2 filled"
|
||||
@click="commitEdit"
|
||||
>
|
||||
<v-icon>done</v-icon>
|
||||
<v-icon>mdi-check</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:small="!flat"
|
||||
@@ -58,7 +58,7 @@
|
||||
class="filled"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
</v-layout>
|
||||
@@ -117,11 +117,11 @@
|
||||
operationIcon(operation) {
|
||||
switch (operation) {
|
||||
case 'set':
|
||||
return 'forward';
|
||||
return 'mdi-forward';
|
||||
case 'add':
|
||||
return 'add';
|
||||
return 'mdi-plus';
|
||||
case 'subtract':
|
||||
return 'remove';
|
||||
return 'mdi-minus';
|
||||
}
|
||||
},
|
||||
toggleAdd(){
|
||||
|
||||
47
app/imports/ui/components/SharedIcon.vue
Normal file
47
app/imports/ui/components/SharedIcon.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template lang="html">
|
||||
<v-tooltip
|
||||
v-if="accessRights === 'reader' || accessRights === 'writer'"
|
||||
bottom
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-icon
|
||||
style="opacity: 0.4"
|
||||
v-on="on"
|
||||
>
|
||||
{{ accessRights === 'reader' ? 'mdi-file-eye' : 'mdi-file-edit' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<span>{{ accessText }}</span>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
props:{
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
meteor:{
|
||||
accessRights(){
|
||||
let userId = Meteor.userId();
|
||||
if (this.model.owner === userId) return 'owner'
|
||||
else if (this.model.writers.includes(userId)) return 'writer';
|
||||
else if (this.model.readers.includes(userId)) return 'reader';
|
||||
else if (this.model.public) return 'public';
|
||||
else return 'denied'
|
||||
},
|
||||
accessText(){
|
||||
switch (this.accessRights){
|
||||
case 'writer': return 'Shared with edit permission';
|
||||
case 'reader': return 'Shared as view-only';
|
||||
case 'public': return 'Shared as publicly viewable';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@
|
||||
<v-text-field
|
||||
:value="formattedSafeValue"
|
||||
v-bind="$attrs"
|
||||
prepend-icon="event"
|
||||
prepend-icon="mdi-calendar"
|
||||
readonly
|
||||
:loading="loading"
|
||||
:error-messages="errors"
|
||||
|
||||
@@ -24,29 +24,29 @@
|
||||
v-else
|
||||
large
|
||||
>
|
||||
highlight_alt
|
||||
mdi-select-search
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<div class="layout">
|
||||
<div class="layout row align-center">
|
||||
<text-field
|
||||
ref="iconSearchField"
|
||||
label="Search icons"
|
||||
append-icon="search"
|
||||
append-icon="mdi-search"
|
||||
clearable
|
||||
hide-details
|
||||
class="ma-2"
|
||||
:value="searchString"
|
||||
@change="search"
|
||||
/>
|
||||
<v-btn
|
||||
icon
|
||||
text
|
||||
@click="select()"
|
||||
>
|
||||
<v-icon>
|
||||
cancel
|
||||
</v-icon>
|
||||
clear
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-layout
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
icon
|
||||
@click="back"
|
||||
>
|
||||
<v-icon>arrow_back</v-icon>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<property-icon
|
||||
:model="model"
|
||||
@@ -50,7 +50,7 @@
|
||||
data-id="property-toolbar-menu-button"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>more_vert</v-icon>
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
@@ -64,7 +64,7 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon>file_copy</v-icon>
|
||||
<v-icon>mdi-content-copy</v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
@@ -77,7 +77,7 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon>send</v-icon>
|
||||
<v-icon>mdi-send</v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
@@ -90,7 +90,7 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon>delete</v-icon>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -112,13 +112,13 @@
|
||||
v-if="editing"
|
||||
key="doneIcon"
|
||||
>
|
||||
done
|
||||
mdi-check
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-else
|
||||
key="createIcon"
|
||||
>
|
||||
create
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
</v-slide-y-transition>
|
||||
</v-btn>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
v-bind="attrs"
|
||||
@click="closeSnackbar"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
@click.stop="expanded = !expanded"
|
||||
>
|
||||
<v-icon v-if="canExpand && (hasChildren || organize)">
|
||||
chevron_right
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<div
|
||||
@@ -31,7 +31,7 @@
|
||||
:class="selected && 'primary--text'"
|
||||
:disabled="expanded"
|
||||
>
|
||||
drag_handle
|
||||
mdi-drag
|
||||
</v-icon>
|
||||
<!--{{node && node.order}}-->
|
||||
<tree-node-view
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import {updateCreature} from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import updateCreature from '/imports/api/creature/creatures/methods/updateCreature.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import CreatureForm from '/imports/ui/creature/CreatureForm.vue'
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<template
|
||||
lang="html"
|
||||
functional
|
||||
>
|
||||
<v-list-item v-bind="$attrs">
|
||||
<v-list-item-avatar :color="model.color || 'grey'">
|
||||
<img
|
||||
v-if="model.avatarPicture"
|
||||
:src="model.avatarPicture"
|
||||
:alt="model.name"
|
||||
>
|
||||
<template v-else>
|
||||
{{ model.initial }}
|
||||
</template>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ model.name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ model.alignment }} {{ model.gender }} {{ model.race }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action v-if="selection">
|
||||
<v-checkbox
|
||||
:input-value="selected && selected.has(model._id)"
|
||||
@change="$emit('select')"
|
||||
/>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
selection: Boolean,
|
||||
selected: {
|
||||
type: Set,
|
||||
default: () => new Set(),
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -7,14 +7,14 @@
|
||||
@click="rest"
|
||||
>
|
||||
<v-icon left>
|
||||
{{ type === 'shortRest' ? 'snooze' : 'bedtime' }}
|
||||
{{ type === 'shortRest' ? 'mdi-music-rest-quarter' : 'mdi-bed' }}
|
||||
</v-icon>
|
||||
{{ type === 'shortRest' ? 'Short Rest' : 'Long Rest' }}
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import restCreature from '/imports/api/creature/restCreature.js';
|
||||
import restCreature from '/imports/api/creature/creatures/methods/restCreature.js';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
|
||||
215
app/imports/ui/creature/archive/ArchiveDialog.vue
Normal file
215
app/imports/ui/creature/archive/ArchiveDialog.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template lang="html">
|
||||
<dialog-base>
|
||||
<template #toolbar>
|
||||
<v-toolbar-title>
|
||||
{{ mode === 'archive' ? 'Archive' : 'Restore' }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn-toggle
|
||||
v-model="mode"
|
||||
mandatory
|
||||
>
|
||||
<v-btn value="archive">
|
||||
<span>Archive</span>
|
||||
<v-icon right>
|
||||
mdi-archive-arrow-down
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn value="restore">
|
||||
<span>Restore</span>
|
||||
<v-icon right>
|
||||
mdi-archive-arrow-up-outline
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</template>
|
||||
<creature-folder-list
|
||||
selection
|
||||
:creatures="mode === 'archive' ? CreaturesWithNoParty : archiveCreaturesWithNoParty"
|
||||
:folders="mode === 'archive' ? folders : archivefolders"
|
||||
:selected-creature="selectedCreature"
|
||||
@creature-selected="id => selectedCreature = id"
|
||||
/>
|
||||
<v-spacer slot="actions" />
|
||||
<v-btn
|
||||
slot="actions"
|
||||
text
|
||||
:loading="archiveActionLoading"
|
||||
:disabled="!numSelected"
|
||||
color="primary"
|
||||
@click="archiveAction"
|
||||
>
|
||||
{{ mode === 'archive' ? 'Archive' : 'Restore' }}
|
||||
<template v-if="numSelected > 1">
|
||||
{{ numSelected }} characters
|
||||
</template>
|
||||
<template v-else-if="numSelected === 1">
|
||||
character
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
slot="actions"
|
||||
text
|
||||
@click="$store.dispatch('popDialogStack')"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolderList.vue';
|
||||
import archiveCreatures from '/imports/api/creature/archive/methods/archiveCreatures.js';
|
||||
import restoreCreatures from '/imports/api/creature/archive/methods/restoreCreatures.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
const characterTransform = function(char){
|
||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||
char.initial = char.name && char.name[0] || '?';
|
||||
return char;
|
||||
};
|
||||
|
||||
const creatureFields = {
|
||||
'color': 1,
|
||||
'avatarPicture': 1,
|
||||
'name': 1,
|
||||
'initial': 1,
|
||||
'alignment': 1,
|
||||
'gender': 1,
|
||||
'race': 1,
|
||||
'readers': 1,
|
||||
'writers': 1,
|
||||
'owner': 1,
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
CreatureFolderList,
|
||||
},
|
||||
data(){return {
|
||||
selectedCreature: null,
|
||||
mode: 'archive',
|
||||
archiveActionLoading: false,
|
||||
}},
|
||||
computed: {
|
||||
numSelected(){
|
||||
return this.selectedCreature ? 1 : 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
mode(){
|
||||
this.selectedCreature = null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
archiveAction(){
|
||||
if (!this.selectedCreature) return;
|
||||
this.archiveActionLoading = true;
|
||||
if (this.mode === 'archive'){
|
||||
archiveCreatures.call({
|
||||
creatureIds: [this.selectedCreature],
|
||||
}, error => {
|
||||
this.archiveActionLoading = false;
|
||||
if (!error) return;
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
});
|
||||
} else if (this.mode === 'restore'){
|
||||
let archiveId = ArchivedCreatures.findOne({
|
||||
'creature._id': this.selectedCreature
|
||||
})._id;
|
||||
restoreCreatures.call({
|
||||
archiveIds: [archiveId],
|
||||
}, error => {
|
||||
this.archiveActionLoading = false;
|
||||
if (!error) return;
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
});
|
||||
}
|
||||
this.selectedCreature = null;
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'archivedCreatures': [],
|
||||
},
|
||||
folders(){
|
||||
const userId = Meteor.userId();
|
||||
let folders = CreatureFolders.find(
|
||||
{owner: userId, archived: {$ne: true}},
|
||||
{sort: {order: 1}},
|
||||
).map(folder => {
|
||||
folder.creatures = Creatures.find(
|
||||
{
|
||||
_id: {$in: folder.creatures || []},
|
||||
owner: userId,
|
||||
}, {
|
||||
sort: {name: 1},
|
||||
fields: creatureFields,
|
||||
}
|
||||
).map(characterTransform);
|
||||
return folder;
|
||||
});
|
||||
folders = folders.filter(folder => !!folder.creatures.length);
|
||||
return folders;
|
||||
},
|
||||
CreaturesWithNoParty() {
|
||||
var userId = Meteor.userId();
|
||||
var charArrays = CreatureFolders.find({owner: userId}).map(p => p.creatures);
|
||||
var folderChars = _.uniq(_.flatten(charArrays));
|
||||
return Creatures.find(
|
||||
{
|
||||
_id: {$nin: folderChars},
|
||||
owner: userId,
|
||||
}, {
|
||||
sort: {name: 1},
|
||||
fields: creatureFields,
|
||||
}
|
||||
).map(characterTransform);
|
||||
},
|
||||
archivefolders(){
|
||||
const userId = Meteor.userId();
|
||||
let folders = CreatureFolders.find(
|
||||
{owner: userId},
|
||||
{sort: {order: 1}},
|
||||
).map(folder => {
|
||||
folder.creatures = ArchivedCreatures.find(
|
||||
{
|
||||
'creature._id': {$in: folder.creatures || []},
|
||||
owner: userId,
|
||||
}, {
|
||||
sort: {'creature.name': 1},
|
||||
fields: {creature: 1},
|
||||
}
|
||||
).map(arc => characterTransform(arc.creature));
|
||||
return folder;
|
||||
});
|
||||
folders = folders.filter(folder => !!folder.creatures.length);
|
||||
return folders;
|
||||
},
|
||||
archiveCreaturesWithNoParty() {
|
||||
var userId = Meteor.userId();
|
||||
var charArrays = CreatureFolders.find({owner: userId}).map(p => p.creatures);
|
||||
var folderChars = _.uniq(_.flatten(charArrays));
|
||||
return ArchivedCreatures.find(
|
||||
{
|
||||
'creature._id': {$nin: folderChars},
|
||||
owner: userId,
|
||||
}, {
|
||||
sort: {'creature.name': 1},
|
||||
fields: {creature: 1},
|
||||
}
|
||||
).map(arc => characterTransform(arc.creature));
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -31,9 +31,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import removeCreature from '/imports/api/creature/removeCreature.js';
|
||||
import removeCreature from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -69,14 +69,14 @@
|
||||
<script lang="js">
|
||||
//TODO add a "no character found" screen if shown on a false address
|
||||
// or on a character the user does not have permission to view
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import StatsTab from '/imports/ui/creature/character/characterSheetTabs/StatsTab.vue';
|
||||
import FeaturesTab from '/imports/ui/creature/character/characterSheetTabs/FeaturesTab.vue';
|
||||
import InventoryTab from '/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue';
|
||||
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
|
||||
import CharacterTab from '/imports/ui/creature/character/characterSheetTabs/CharacterTab.vue';
|
||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
style="transition: transform 0.2s ease-in-out"
|
||||
:style="fab && 'transform: rotate(45deg)'"
|
||||
>
|
||||
add
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
</transition>
|
||||
</v-btn>
|
||||
@@ -42,7 +42,7 @@
|
||||
color="primary"
|
||||
data-id="insert-creature-property-btn"
|
||||
label="New Property"
|
||||
icon="create"
|
||||
icon="mdi-pencil"
|
||||
:disabled="!editPermission"
|
||||
@click="insertTreeProperty"
|
||||
/>
|
||||
@@ -51,7 +51,7 @@
|
||||
color="primary"
|
||||
data-id="insert-creature-property-from-library-btn"
|
||||
label="Property From Library"
|
||||
icon="book"
|
||||
icon="mdi-library-shelves"
|
||||
:disabled="!editPermission"
|
||||
@click="propertyFromLibrary"
|
||||
/>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
>
|
||||
<div :key="$route.meta.title">
|
||||
<template v-if="creature">
|
||||
<shared-icon :model="creature" />
|
||||
<v-menu
|
||||
bottom
|
||||
left
|
||||
@@ -37,30 +38,30 @@
|
||||
icon
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>more_vert</v-icon>
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list v-if="editPermission">
|
||||
<v-list-item @click="deleteCharacter">
|
||||
<v-list-item-title>
|
||||
<v-icon>delete</v-icon> Delete
|
||||
<v-icon>mdi-delete</v-icon> Delete
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showCharacterForm">
|
||||
<v-list-item-title>
|
||||
<v-icon>create</v-icon> Edit details
|
||||
<v-icon>mdi-pencil</v-icon> Edit details
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showShareDialog">
|
||||
<v-list-item-title>
|
||||
<v-icon>share</v-icon> Sharing
|
||||
<v-icon>mdi-share-variant</v-icon> Sharing
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-list v-else>
|
||||
<v-list-item @click="unshareWithMe">
|
||||
<v-list-item-title>
|
||||
<v-icon>delete</v-icon> Unshare with me
|
||||
<v-icon>mdi-delete</v-icon> Unshare with me
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
@@ -122,14 +123,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import removeCreature from '/imports/api/creature/removeCreature.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import removeCreature from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
import { mapMutations } from 'vuex';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { updateUserSharePermissions } from '/imports/api/sharing/sharing.js';
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import CharacterSheetFab from '/imports/ui/creature/character/CharacterSheetFab.vue';
|
||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||
import SharedIcon from '/imports/ui/components/SharedIcon.vue';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
@@ -137,6 +139,7 @@ export default {
|
||||
},
|
||||
components: {
|
||||
CharacterSheetFab,
|
||||
SharedIcon,
|
||||
},
|
||||
computed: {
|
||||
creatureId(){
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
data-id="experience-info-button"
|
||||
@click="showExperienceList"
|
||||
>
|
||||
<v-icon>info</v-icon>
|
||||
<v-icon>mdi-information-outline</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
<v-list-item-action>
|
||||
@@ -82,7 +82,7 @@
|
||||
data-id="experience-add-button"
|
||||
@click="addExperience"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
@@ -115,7 +115,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import ContainerCard from '/imports/ui/properties/components/inventory/ContainerCard.vue';
|
||||
import ToolbarCard from '/imports/ui/components/ToolbarCard.vue';
|
||||
@@ -183,10 +183,24 @@ export default {
|
||||
});
|
||||
},
|
||||
equipmentParentRef(){
|
||||
return getParentRefByTag(this.creatureId, BUILT_IN_TAGS.equipment);
|
||||
return getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.equipment
|
||||
) || getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.inventory
|
||||
) || {
|
||||
id: this.creatureId,
|
||||
collection: 'creatures'
|
||||
};
|
||||
},
|
||||
carriedParentRef(){
|
||||
return getParentRefByTag(this.creatureId, BUILT_IN_TAGS.carried);
|
||||
return getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.carried
|
||||
) || getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.inventory
|
||||
) || {
|
||||
id: this.creatureId,
|
||||
collection: 'creatures'
|
||||
};
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
icon
|
||||
@click.stop="softRemove(buff._id)"
|
||||
>
|
||||
<v-icon>delete</v-icon>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
@@ -320,7 +320,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
slot="extension"
|
||||
v-model="filterString"
|
||||
:items="filterOptions"
|
||||
prepend-inner-icon="search"
|
||||
prepend-inner-icon="mdi-search"
|
||||
class="mx-4"
|
||||
hide-no-data
|
||||
hide-selected
|
||||
|
||||
38
app/imports/ui/creature/creatureList/ArchiveButton.vue
Normal file
38
app/imports/ui/creature/creatureList/ArchiveButton.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template lang="html">
|
||||
<v-btn
|
||||
:icon="!text"
|
||||
:text="text"
|
||||
:data-id="randomId"
|
||||
v-bind="$attrs"
|
||||
@click="openArchive"
|
||||
>
|
||||
<template v-if="text">
|
||||
Archive Characters
|
||||
</template>
|
||||
<v-icon :right="text">
|
||||
mdi-archive
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
props: {
|
||||
text: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
randomId: Random.id(),
|
||||
}},
|
||||
methods: {
|
||||
openArchive(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'archive-dialog',
|
||||
elementId: this.randomId,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
{{ creatureCount }} /
|
||||
<v-icon v-if="characterSlots === -1">
|
||||
mdi-infinity
|
||||
</v-icon>
|
||||
<template v-else>
|
||||
{{ characterSlots }}
|
||||
</template>
|
||||
<archive-button />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
import ArchiveButton from '/imports/ui/creature/creatureList/ArchiveButton.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ArchiveButton,
|
||||
},
|
||||
meteor: {
|
||||
creatureCount(){
|
||||
return Creatures.find({owner: Meteor.userId()}).count();
|
||||
},
|
||||
characterSlots(){
|
||||
return getUserTier(Meteor.userId()).characterSlots;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
</style>
|
||||
108
app/imports/ui/creature/creatureList/CreatureFolderHeader.vue
Normal file
108
app/imports/ui/creature/creatureList/CreatureFolderHeader.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template lang="html">
|
||||
<v-list-item style="min-height: 60px;">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
<template v-if="!renaming">
|
||||
{{ model.name }}
|
||||
</template>
|
||||
<text-field
|
||||
v-if="renaming"
|
||||
ref="name-input"
|
||||
regular
|
||||
hide-details
|
||||
dense
|
||||
:value="model.name"
|
||||
@change="renameFolder"
|
||||
@click.native.stop=""
|
||||
@input.native.stop=""
|
||||
@keydown.native.stop=""
|
||||
@keyup.native.stop=""
|
||||
/>
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<template v-if="!selection && !dense">
|
||||
<v-list-item-action v-if="renaming || open">
|
||||
<v-btn
|
||||
icon
|
||||
style="flex-grow: 0"
|
||||
@click.stop="renaming = !renaming"
|
||||
>
|
||||
<v-icon v-if="renaming">
|
||||
mdi-check
|
||||
</v-icon>
|
||||
<v-icon v-else>
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
<v-list-item-action v-if="open">
|
||||
<v-btn
|
||||
icon
|
||||
style="flex-grow: 0"
|
||||
@click.stop="removeFolder"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Vue from 'vue';
|
||||
import updateCreatureFolderName from '/imports/api/creature/creatureFolders/methods.js/updateCreatureFolderName.js';
|
||||
import removeCreatureFolder from '/imports/api/creature/creatureFolders/methods.js/removeCreatureFolder.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
open: Boolean,
|
||||
selection: Boolean,
|
||||
dense: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
renaming: false,
|
||||
}},
|
||||
watch: {
|
||||
renaming(value){
|
||||
if (!value) return;
|
||||
Vue.nextTick(() => {
|
||||
this.$refs['name-input'].focus();
|
||||
});
|
||||
},
|
||||
},
|
||||
methods:{
|
||||
renameFolder(name, ack){
|
||||
updateCreatureFolderName.call({
|
||||
_id: this.model._id,
|
||||
name
|
||||
}, error => {
|
||||
ack(error);
|
||||
if (!error) return;
|
||||
console.error(error);
|
||||
snackbar({
|
||||
text: error.reason,
|
||||
});
|
||||
});
|
||||
},
|
||||
removeFolder(){
|
||||
removeCreatureFolder.call({
|
||||
_id: this.model._id
|
||||
}, error => {
|
||||
if (!error) return;
|
||||
console.error(error);
|
||||
snackbar({
|
||||
text: error.reason,
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
70
app/imports/ui/creature/creatureList/CreatureFolderList.vue
Normal file
70
app/imports/ui/creature/creatureList/CreatureFolderList.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template lang="html">
|
||||
<v-list
|
||||
expand
|
||||
>
|
||||
<creature-list
|
||||
:creatures="creatures"
|
||||
:selection="selection"
|
||||
:selected-creature="selectedCreature"
|
||||
:dense="dense"
|
||||
@creature-selected="id => $emit('creature-selected', id)"
|
||||
/>
|
||||
<v-list-group
|
||||
v-for="folder in folders"
|
||||
:key="folder._id"
|
||||
v-model="openFolders[folder._id]"
|
||||
group="folder"
|
||||
>
|
||||
<template #activator>
|
||||
<creature-folder-header
|
||||
:open="openFolders[folder._id]"
|
||||
:model="folder"
|
||||
:selection="selection"
|
||||
:dense="dense"
|
||||
/>
|
||||
</template>
|
||||
<creature-list
|
||||
:creatures="folder.creatures"
|
||||
:folder-id="folder._id"
|
||||
:selection="selection"
|
||||
:selected-creature="selectedCreature"
|
||||
:dense="dense"
|
||||
@creature-selected="id => $emit('creature-selected', id)"
|
||||
/>
|
||||
</v-list-group>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureFolderHeader from '/imports/ui/creature/creatureList/CreatureFolderHeader.vue';
|
||||
import CreatureList from '/imports/ui/creature/creatureList/CreatureList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CreatureFolderHeader,
|
||||
CreatureList,
|
||||
},
|
||||
props:{
|
||||
creatures: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
folders: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selection: Boolean,
|
||||
selectedCreature: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
dense: Boolean,
|
||||
},
|
||||
data(){return{
|
||||
openFolders: {},
|
||||
}},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
103
app/imports/ui/creature/creatureList/CreatureList.vue
Normal file
103
app/imports/ui/creature/creatureList/CreatureList.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template lang="html">
|
||||
<draggable
|
||||
v-model="dataCreatures"
|
||||
style="min-height: 24px;"
|
||||
:sort="false"
|
||||
:group="`creature-list`"
|
||||
ghost-class="ghost"
|
||||
draggable=".creature"
|
||||
handle=".handle"
|
||||
:animation="200"
|
||||
@change="draggableChange"
|
||||
>
|
||||
<creature-list-tile
|
||||
v-for="creature in dataCreatures"
|
||||
:key="creature._id"
|
||||
class="creature"
|
||||
:model="creature"
|
||||
:selection="selection"
|
||||
:is-selected="selectedCreature === creature._id"
|
||||
v-bind="selection ? {} : {to: creature.url}"
|
||||
:dense="dense"
|
||||
@click="$emit('creature-selected', creature._id)"
|
||||
/>
|
||||
</draggable>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureListTile from '/imports/ui/creature/creatureList/CreatureListTile.vue';
|
||||
import draggable from 'vuedraggable';
|
||||
import moveCreatureToFolder from '/imports/api/creature/creatureFolders/methods.js/moveCreatureToFolder.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CreatureListTile,
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
creatures: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
folderId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
selection: Boolean,
|
||||
selectedCreature: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
dense: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
dataCreatures: [],
|
||||
}},
|
||||
watch:{
|
||||
creatures(newValue){
|
||||
this.dataCreatures = newValue;
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
this.dataCreatures = this.creatures;
|
||||
},
|
||||
methods: {
|
||||
draggableChange({added, moved}){
|
||||
let event = added || moved;
|
||||
if (event){
|
||||
/*
|
||||
// If this item is now adjacent to another, set the order accordingly
|
||||
let order;
|
||||
let before = this.dataCreatures[event.newIndex - 1];
|
||||
let after = this.dataCreatures[event.newIndex + 1];
|
||||
if (before && before._id){
|
||||
order = before.order + 0.5;
|
||||
} else if (after && after._id) {
|
||||
order = after.order - 0.5;
|
||||
} else {
|
||||
order = -0.5;
|
||||
}
|
||||
*/
|
||||
let doc = event.element;
|
||||
moveCreatureToFolder.call({
|
||||
creatureId: doc._id,
|
||||
folderId: this.folderId
|
||||
}, error => {
|
||||
if (!error) return;
|
||||
console.error(error);
|
||||
snackbar({
|
||||
text: error.reason,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
selectionChange(index){
|
||||
this.$emit('creatureSelected', this.dataCreatures[index]._id)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
70
app/imports/ui/creature/creatureList/CreatureListTile.vue
Normal file
70
app/imports/ui/creature/creatureList/CreatureListTile.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template
|
||||
lang="html"
|
||||
functional
|
||||
>
|
||||
<v-list-item
|
||||
v-bind="$attrs"
|
||||
:class="isSelected && 'primary--text v-list-item--active'"
|
||||
v-on="selection ? { click() {$emit('click')} } : {}"
|
||||
>
|
||||
<v-list-item-avatar
|
||||
:color="isSelected ? 'red darken-1' : model.color || 'grey'"
|
||||
class="white--text"
|
||||
style="transition: background 0.3s;"
|
||||
>
|
||||
<v-fade-transition leave-absolute>
|
||||
<v-icon v-if="isSelected">
|
||||
mdi-check
|
||||
</v-icon>
|
||||
<img
|
||||
v-else-if="model.avatarPicture"
|
||||
:src="model.avatarPicture"
|
||||
:alt="model.name"
|
||||
>
|
||||
<template v-else>
|
||||
<span>
|
||||
{{ model.initial }}
|
||||
</span>
|
||||
</template>
|
||||
</v-fade-transition>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ model.name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="!dense">
|
||||
{{ model.alignment }} {{ model.gender }} {{ model.race }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action v-if="!dense">
|
||||
<shared-icon :model="model" />
|
||||
</v-list-item-action>
|
||||
<v-list-item-action v-if="!selection && !dense">
|
||||
<v-icon
|
||||
style="height: 100%; width: 40px; cursor: move;"
|
||||
class="handle"
|
||||
>
|
||||
mdi-drag
|
||||
</v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SharedIcon from '/imports/ui/components/SharedIcon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SharedIcon,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
selection: Boolean,
|
||||
isSelected: Boolean,
|
||||
dense: Boolean,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@
|
||||
v-if="index !== 0"
|
||||
:key="index"
|
||||
>
|
||||
chevron_right
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
<span
|
||||
v-if="noLinks"
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
data-id="insert-creature-property-btn"
|
||||
@click="addProperty"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
Property
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -82,7 +82,7 @@ import softRemoveProperty from '/imports/api/creature/creatureProperties/methods
|
||||
import restoreProperty from '/imports/api/creature/creatureProperties/methods/restoreProperty.js';
|
||||
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
|
||||
import duplicateProperty from '/imports/api/creature/creatureProperties/methods/duplicateProperty.js';
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import PropertyToolbar from '/imports/ui/components/propertyToolbar.vue';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
@@ -91,7 +91,7 @@ import propertyFormIndex from '/imports/ui/properties/forms/shared/propertyFormI
|
||||
import propertyViewerIndex from '/imports/ui/properties/viewers/shared/propertyViewerIndex.js';
|
||||
import CreaturePropertiesTree from '/imports/ui/creature/creatureProperties/CreaturePropertiesTree.vue';
|
||||
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { get, findLast } from 'lodash';
|
||||
import equipItem from '/imports/api/creature/creatureProperties/methods/equipItem.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
data-id="experience-add-button"
|
||||
@click="addExperience"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
@click="recompute"
|
||||
>
|
||||
<v-icon>refresh</v-icon>
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<div
|
||||
@@ -72,11 +72,10 @@
|
||||
<v-list-item-action>
|
||||
<v-btn
|
||||
icon
|
||||
flat
|
||||
:loading="experiencesRemovalLoading.has(experience._id)"
|
||||
@click="removeExperience(experience._id)"
|
||||
>
|
||||
<v-icon>delete</v-icon>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<text-field
|
||||
prepend-inner-icon="search"
|
||||
prepend-inner-icon="mdi-search"
|
||||
regular
|
||||
hide-details
|
||||
:value="searchValue"
|
||||
@@ -153,7 +153,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
small
|
||||
@click.stop="remove(child)"
|
||||
>
|
||||
<v-icon>delete</v-icon>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
@@ -44,7 +44,7 @@
|
||||
style="background-color: inherit;"
|
||||
@click="fillSlot(slot)"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
icon
|
||||
@click="back"
|
||||
>
|
||||
<v-icon>arrow_back</v-icon>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<slot name="toolbar" />
|
||||
</v-toolbar>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ArchiveDialog from '/imports/ui/creature/archive/ArchiveDialog.vue';
|
||||
import CastSpellWithSlotDialog from '/imports/ui/properties/components/spells/CastSpellWithSlotDialog.vue';
|
||||
import CreatureFormDialog from '/imports/ui/creature/CreatureFormDialog.vue';
|
||||
import CreaturePropertyCreationDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue';
|
||||
@@ -22,6 +23,7 @@ import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||
|
||||
export default {
|
||||
ArchiveDialog,
|
||||
CastSpellWithSlotDialog,
|
||||
CreatureFormDialog,
|
||||
CreaturePropertyCreationDialog,
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
VLayout,
|
||||
...DialogComponentIndex,
|
||||
},
|
||||
data(){return {
|
||||
hiddenElements: [],
|
||||
}},
|
||||
computed: {
|
||||
dialogs(){
|
||||
return this.$store.state.dialogStack.dialogs;
|
||||
@@ -130,7 +133,7 @@
|
||||
// hide the source
|
||||
source.style.transition = 'none';
|
||||
source.style.opacity = '0';
|
||||
this.hiddenElement = source;
|
||||
this.hiddenElements.push(source);
|
||||
|
||||
// Instantly mock the source
|
||||
target.style.transition = 'none';
|
||||
@@ -153,6 +156,7 @@
|
||||
},
|
||||
doLeave(target, done){
|
||||
let elementId;
|
||||
let hiddenElement = this.hiddenElements.pop();
|
||||
let returnElementId = this.$store.state.dialogStack.currentReturnElement;
|
||||
if (returnElementId) {
|
||||
elementId = returnElementId;
|
||||
@@ -166,7 +170,7 @@
|
||||
let source = this.getTopElementByDataId(elementId);
|
||||
if (!source){
|
||||
console.warn(`Can't find source for ${elementId}`);
|
||||
if (this.hiddenElement) this.hiddenElement.style.opacity = null;
|
||||
if (hiddenElement) hiddenElement.style.opacity = null;
|
||||
done();
|
||||
return;
|
||||
}
|
||||
@@ -180,10 +184,10 @@
|
||||
// If the source and the hidden Element are different
|
||||
// hide the source and reveal the hidden element
|
||||
let originalSourceTransition = source.style.transition;
|
||||
if (this.hiddenElement !== source){
|
||||
if (hiddenElement !== source){
|
||||
source.style.transition = 'none';
|
||||
source.style.opacity = '0';
|
||||
this.hiddenElement.style.opacity = null;
|
||||
hiddenElement.style.opacity = null;
|
||||
}
|
||||
setTimeout(() => {
|
||||
source.style.opacity = null;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<code>{{ example.input }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<v-icon>arrow_right_alt</v-icon>
|
||||
<v-icon>mdi-arrow-right-thick</v-icon>
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ example.result }}</code>
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
import DialogStack from '/imports/ui/dialogStack/DialogStack.vue';
|
||||
import { mapMutations } from 'vuex';
|
||||
import SnackbarQueue from '/imports/ui/components/snackbars/SnackbarQueue.vue';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -96,7 +97,9 @@
|
||||
meteor: {
|
||||
darkMode(){
|
||||
let user = Meteor.user();
|
||||
return user && user.darkMode;
|
||||
if (!user) return;
|
||||
let tier = getUserTier(user);
|
||||
return tier.paidBenefits && user.darkMode;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
to="/account"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>settings</v-icon>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Account Settings</span>
|
||||
@@ -48,69 +48,34 @@
|
||||
<v-list-item-title>
|
||||
{{ link.title }}
|
||||
</v-list-item-title>
|
||||
<v-icon v-if="link.href">
|
||||
mdi-open-in-new
|
||||
</v-icon>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
</v-list>
|
||||
<v-list
|
||||
avatar
|
||||
>
|
||||
<v-list-item
|
||||
v-for="character in CreaturesWithNoParty"
|
||||
:key="character._id"
|
||||
:to="character.url"
|
||||
>
|
||||
<v-list-item-avatar :color="character.color || 'grey'">
|
||||
<img
|
||||
v-if="character.avatarPicture"
|
||||
:src="character.avatarPicture"
|
||||
:alt="character.name"
|
||||
>
|
||||
<template v-else>
|
||||
{{ character.initial }}
|
||||
</template>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-title>
|
||||
{{ character.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-group
|
||||
v-for="party in parties"
|
||||
:key="party._id"
|
||||
>
|
||||
<v-list-item slot="activator">
|
||||
<v-list-item-title>
|
||||
{{ party.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-for="character in characterDocs"
|
||||
:key="character._id"
|
||||
:to="character.url"
|
||||
>
|
||||
<v-list-item-avatar :color="character.color || 'grey'">
|
||||
<img
|
||||
v-if="character.avatarPicture"
|
||||
:src="character.avatarPicture"
|
||||
:alt="character.name"
|
||||
>
|
||||
<template v-else>
|
||||
{{ character.initial }}
|
||||
</template>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-title>
|
||||
{{ character.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
</v-list>
|
||||
<creature-folder-list
|
||||
dense
|
||||
:creatures="CreaturesWithNoParty"
|
||||
:folders="folders"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import Parties from '/imports/api/creature/Parties.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolderList.vue';
|
||||
|
||||
const characterTransform = function(char){
|
||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||
char.initial = char.name && char.name[0] || '?';
|
||||
return char;
|
||||
};
|
||||
export default {
|
||||
components: {
|
||||
CreatureFolderList
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'characterList': [],
|
||||
@@ -125,55 +90,48 @@
|
||||
links(){
|
||||
let isLoggedIn = !!Meteor.userId();
|
||||
let links = [
|
||||
{title: 'Home', icon: 'home', to: '/'},
|
||||
{title: 'Characters', icon: 'portrait', to: '/characterList', requireLogin: true},
|
||||
{title: 'Library', icon: 'book', to: '/library', requireLogin: true},
|
||||
{title: 'Home', icon: 'mdi-home', to: '/'},
|
||||
{title: 'Characters', icon: 'mdi-account-group', to: '/characterList', requireLogin: true},
|
||||
{title: 'Library', icon: 'mdi-library-shelves', to: '/library', requireLogin: true},
|
||||
//{title: 'Tabletops', icon: 'api', to: '/tabletops', requireLogin: true},
|
||||
//{title: 'Friends', icon: 'people', to: '/friends', requireLogin: true},
|
||||
{title: 'Feedback', icon: 'bug_report', to: '/feedback'},
|
||||
{title: 'About', icon: 'subject', to: '/about'},
|
||||
{title: 'Patreon', icon: '', href: 'https://www.patreon.com/dicecloud'},
|
||||
{title: 'Github', icon: '', href: 'https://github.com/ThaumRystra/DiceCloud/tree/version-2'},
|
||||
{title: 'Feedback', icon: 'mdi-bug', to: '/feedback'},
|
||||
{title: 'About', icon: 'mdi-sign-text', to: '/about'},
|
||||
{title: 'Patreon', icon: 'mdi-patreon', href: 'https://www.patreon.com/dicecloud'},
|
||||
{title: 'Github', icon: 'mdi-github', href: 'https://github.com/ThaumRystra/DiceCloud/tree/version-2'},
|
||||
];
|
||||
return links.filter(link => !link.requireLogin || isLoggedIn);
|
||||
},
|
||||
parties(){
|
||||
folders(){
|
||||
const userId = Meteor.userId();
|
||||
return Parties.find(
|
||||
{owner: userId},
|
||||
{sort: {name: 1}},
|
||||
).map(party => {
|
||||
party.characterDocs = Creatures.find(
|
||||
let folders = CreatureFolders.find(
|
||||
{owner: userId, archived: {$ne: true}},
|
||||
{sort: {order: 1}},
|
||||
).map(folder => {
|
||||
folder.creatures = Creatures.find(
|
||||
{
|
||||
_id: {$in: party.Creatures},
|
||||
_id: {$in: folder.creatures || []},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
}, {
|
||||
sort: {name: 1},
|
||||
fields: {name: 1, urlName: 1},
|
||||
}
|
||||
).map(char => {
|
||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||
char.initial = char.name && char.name[0] || '?';
|
||||
return char;
|
||||
});
|
||||
return party;
|
||||
).map(characterTransform);
|
||||
return folder;
|
||||
});
|
||||
folders = folders.filter(folder => !!folder.creatures.length);
|
||||
return folders;
|
||||
},
|
||||
CreaturesWithNoParty() {
|
||||
var userId = Meteor.userId();
|
||||
var charArrays = Parties.find({owner: userId}).map(p => p.Creatures);
|
||||
var partyChars = _.uniq(_.flatten(charArrays));
|
||||
var charArrays = CreatureFolders.find({owner: userId}).map(p => p.creatures);
|
||||
var folderChars = _.uniq(_.flatten(charArrays));
|
||||
return Creatures.find(
|
||||
{
|
||||
_id: {$nin: partyChars},
|
||||
_id: {$nin: folderChars},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
},
|
||||
{sort: {name: 1}}
|
||||
).map(char => {
|
||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||
char.initial = char.name && char.name[0] || '?';
|
||||
return char;
|
||||
});
|
||||
).map(characterTransform);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
data-id="insert-library-node-button"
|
||||
@click="insertLibraryNode"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
<slot />
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user