Compare commits
54 Commits
2.0-beta.4
...
2.0-beta.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c55d572134 | ||
|
|
0a2b60990e | ||
|
|
a437ff5aef | ||
|
|
3d31d62860 | ||
|
|
8377231254 | ||
|
|
1ec29365cb | ||
|
|
60b21c1901 | ||
|
|
03f87b0afa | ||
|
|
48291d2c8f | ||
|
|
1cedf55fbf | ||
|
|
bed4d4b162 | ||
|
|
a1d992ec8d | ||
|
|
008ef62517 | ||
|
|
c436309ba8 | ||
|
|
0bfdb73b47 | ||
|
|
a462cc5ca2 | ||
|
|
5d57a74667 | ||
|
|
21b0029df7 | ||
|
|
c0ccafa787 | ||
|
|
d63ad9ea8f | ||
|
|
8f56a60fb1 | ||
|
|
358ae46627 | ||
|
|
0b1db3c40c | ||
|
|
0ad7e659d2 | ||
|
|
58c3875dc7 | ||
|
|
84f506f1fe | ||
|
|
d0a3ccc76a | ||
|
|
93ac9215c2 | ||
|
|
a6b501a62c | ||
|
|
e956bacf07 | ||
|
|
60b6b283b1 | ||
|
|
1c9b390551 | ||
|
|
21a487635d | ||
|
|
c92a26d5e6 | ||
|
|
49b514b8f3 | ||
|
|
5cb835c536 | ||
|
|
aa8f2d230d | ||
|
|
2fa913b09a | ||
|
|
de598c70a7 | ||
|
|
baecdeff24 | ||
|
|
d4b7d22b5f | ||
|
|
87f79737e8 | ||
|
|
9f0ffe13f8 | ||
|
|
adaa31d76c | ||
|
|
b051d764f8 | ||
|
|
ffb5b4a4f3 | ||
|
|
fd87b7fb75 | ||
|
|
f035902842 | ||
|
|
dbc5f7253f | ||
|
|
f0e7253374 | ||
|
|
ffe37bf907 | ||
|
|
a63e2099d3 | ||
|
|
0308e4e7a7 | ||
|
|
43f8df09f0 |
@@ -11,14 +11,14 @@ accounts-google@1.4.0
|
|||||||
email@2.2.1
|
email@2.2.1
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mongo@1.15.0
|
mongo@1.16.0
|
||||||
session@1.2.0
|
session@1.2.0
|
||||||
tracker@1.2.0
|
tracker@1.2.0
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
reload@1.3.1
|
reload@1.3.1
|
||||||
ejson@1.1.2
|
ejson@1.1.2
|
||||||
check@1.3.1
|
check@1.3.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.1
|
||||||
shell-server@0.5.0
|
shell-server@0.5.0
|
||||||
ecmascript@0.16.2
|
ecmascript@0.16.2
|
||||||
es5-shim@4.8.0
|
es5-shim@4.8.0
|
||||||
@@ -48,3 +48,4 @@ simple:rest-bearer-token-parser
|
|||||||
simple:rest-json-error-handler
|
simple:rest-json-error-handler
|
||||||
littledata:synced-cron
|
littledata:synced-cron
|
||||||
mdg:meteor-apm-agent
|
mdg:meteor-apm-agent
|
||||||
|
typescript@4.5.4
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
METEOR@2.7.3
|
METEOR@2.8.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
accounts-base@2.2.3
|
accounts-base@2.2.4
|
||||||
accounts-google@1.4.0
|
accounts-google@1.4.0
|
||||||
accounts-oauth@1.4.1
|
accounts-oauth@1.4.1
|
||||||
accounts-password@2.3.1
|
accounts-password@2.3.1
|
||||||
@@ -12,7 +12,7 @@ aldeed:collection2@3.5.0
|
|||||||
aldeed:schema-index@3.0.0
|
aldeed:schema-index@3.0.0
|
||||||
allow-deny@1.1.1
|
allow-deny@1.1.1
|
||||||
autoupdate@1.8.0
|
autoupdate@1.8.0
|
||||||
babel-compiler@7.9.0
|
babel-compiler@7.9.2
|
||||||
babel-runtime@1.5.1
|
babel-runtime@1.5.1
|
||||||
base64@1.0.12
|
base64@1.0.12
|
||||||
binary-heap@1.0.11
|
binary-heap@1.0.11
|
||||||
@@ -27,10 +27,10 @@ coffeescript@2.4.1
|
|||||||
coffeescript-compiler@2.4.1
|
coffeescript-compiler@2.4.1
|
||||||
dburles:mongo-collection-instances@0.3.6
|
dburles:mongo-collection-instances@0.3.6
|
||||||
ddp@1.4.0
|
ddp@1.4.0
|
||||||
ddp-client@2.5.0
|
ddp-client@2.6.0
|
||||||
ddp-common@1.4.0
|
ddp-common@1.4.0
|
||||||
ddp-rate-limiter@1.1.0
|
ddp-rate-limiter@1.1.0
|
||||||
ddp-server@2.5.0
|
ddp-server@2.6.0
|
||||||
diff-sequence@1.1.1
|
diff-sequence@1.1.1
|
||||||
dynamic-import@0.7.2
|
dynamic-import@0.7.2
|
||||||
ecmascript@0.16.2
|
ecmascript@0.16.2
|
||||||
@@ -55,33 +55,33 @@ littledata:synced-cron@1.5.1
|
|||||||
livedata@1.0.18
|
livedata@1.0.18
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
mdg:meteor-apm-agent@3.5.0
|
mdg:meteor-apm-agent@3.5.1
|
||||||
mdg:validated-method@1.2.0
|
mdg:validated-method@1.2.0
|
||||||
meteor@1.10.0
|
meteor@1.10.1
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
meteortesting:browser-tests@1.3.5
|
meteortesting:browser-tests@1.3.5
|
||||||
meteortesting:mocha@2.0.3
|
meteortesting:mocha@2.0.3
|
||||||
meteortesting:mocha-core@8.1.2
|
meteortesting:mocha-core@8.1.2
|
||||||
mikowals:batch-insert@1.3.0
|
mikowals:batch-insert@1.3.0
|
||||||
minifier-css@1.6.0
|
minifier-css@1.6.1
|
||||||
minifier-js@2.7.4
|
minifier-js@2.7.5
|
||||||
minimongo@1.8.0
|
minimongo@1.9.0
|
||||||
mobile-experience@1.1.0
|
mobile-experience@1.1.0
|
||||||
mobile-status-bar@1.1.0
|
mobile-status-bar@1.1.0
|
||||||
modern-browsers@0.1.8
|
modern-browsers@0.1.8
|
||||||
modules@0.18.0
|
modules@0.19.0
|
||||||
modules-runtime@0.13.0
|
modules-runtime@0.13.0
|
||||||
mongo@1.15.0
|
mongo@1.16.0
|
||||||
mongo-decimal@0.1.3
|
mongo-decimal@0.1.3
|
||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
mongo-id@1.0.8
|
mongo-id@1.0.8
|
||||||
mongo-livedata@1.0.12
|
mongo-livedata@1.0.12
|
||||||
npm-mongo@4.3.1
|
npm-mongo@4.9.0
|
||||||
oauth@2.1.2
|
oauth@2.1.2
|
||||||
oauth2@1.3.1
|
oauth2@1.3.1
|
||||||
ordered-dict@1.1.0
|
ordered-dict@1.1.0
|
||||||
ostrio:cookies@2.7.2
|
ostrio:cookies@2.7.2
|
||||||
ostrio:files@2.0.1
|
ostrio:files@2.3.0
|
||||||
patreon-oauth@0.1.0
|
patreon-oauth@0.1.0
|
||||||
peerlibrary:assert@0.3.0
|
peerlibrary:assert@0.3.0
|
||||||
peerlibrary:check-extension@0.7.0
|
peerlibrary:check-extension@0.7.0
|
||||||
@@ -116,7 +116,7 @@ simple:rest-json-error-handler@1.1.1
|
|||||||
simple:rest-method-mixin@1.1.0
|
simple:rest-method-mixin@1.1.0
|
||||||
socket-stream-client@0.5.0
|
socket-stream-client@0.5.0
|
||||||
spacebars-compiler@1.3.1
|
spacebars-compiler@1.3.1
|
||||||
standard-minifier-js@2.8.0
|
standard-minifier-js@2.8.1
|
||||||
static-html@1.3.2
|
static-html@1.3.2
|
||||||
templating-tools@1.2.2
|
templating-tools@1.2.2
|
||||||
tmeasday:check-npm-versions@1.0.2
|
tmeasday:check-npm-versions@1.0.2
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ const flipToggle = new ValidatedMethod({
|
|||||||
|
|
||||||
// Invert the current value, disabled is the canonical store of value
|
// Invert the current value, disabled is the canonical store of value
|
||||||
const currentValue = !property.disabled;
|
const currentValue = !property.disabled;
|
||||||
CreatureProperties.update(_id, {$set: {
|
CreatureProperties.update(_id, {
|
||||||
|
$set: {
|
||||||
enabled: !currentValue,
|
enabled: !currentValue,
|
||||||
disabled: currentValue,
|
disabled: currentValue,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}}, {
|
}
|
||||||
|
}, {
|
||||||
selector: { type: 'toggle' },
|
selector: { type: 'toggle' },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ let CreatureSettingsSchema = new SimpleSchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
//hide rest buttons
|
||||||
|
hideRestButtons: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// Swap around the modifier and stat
|
// Swap around the modifier and stat
|
||||||
swapStatAndModifier: {
|
swapStatAndModifier: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@@ -62,16 +62,37 @@ function doRestWork(restType, actionContext) {
|
|||||||
} else {
|
} else {
|
||||||
resetFilter = { $in: ['shortRest', 'longRest'] }
|
resetFilter = { $in: ['shortRest', 'longRest'] }
|
||||||
}
|
}
|
||||||
|
resetProperties(creatureId, resetFilter, actionContext);
|
||||||
|
|
||||||
|
// Reset half hit dice on a long rest, starting with the highest dice
|
||||||
|
if (restType === 'longRest') {
|
||||||
|
resetHitDice(creatureId, actionContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetProperties(creatureId, resetFilter, actionContext) {
|
||||||
// Only apply to active properties
|
// Only apply to active properties
|
||||||
let filter = {
|
const filter = {
|
||||||
'ancestors.id': creatureId,
|
'ancestors.id': creatureId,
|
||||||
reset: resetFilter,
|
reset: resetFilter,
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
inactive: { $ne: true },
|
inactive: { $ne: true },
|
||||||
};
|
};
|
||||||
// update all attribute's damage
|
// update all attribute's damage
|
||||||
filter.type = 'attribute';
|
const attributeFilter = {
|
||||||
CreatureProperties.update(filter, {
|
...filter,
|
||||||
|
type: 'attribute',
|
||||||
|
damage: { $ne: 0 },
|
||||||
|
}
|
||||||
|
CreatureProperties.find(attributeFilter, {
|
||||||
|
fields: { name: 1, damage: 1 }
|
||||||
|
}).forEach(prop => {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: prop.name,
|
||||||
|
value: prop.damage >= 0 ? `Restored ${prop.damage}` : `Removed ${-prop.damage}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
CreatureProperties.update(attributeFilter, {
|
||||||
$set: {
|
$set: {
|
||||||
damage: 0,
|
damage: 0,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
@@ -81,12 +102,22 @@ function doRestWork(restType, actionContext) {
|
|||||||
multi: true,
|
multi: true,
|
||||||
});
|
});
|
||||||
// Update all action-like properties' usesUsed
|
// Update all action-like properties' usesUsed
|
||||||
filter.type = {$in: [
|
const actionFilter = {
|
||||||
'action',
|
...filter,
|
||||||
'attack',
|
type: {
|
||||||
'spell'
|
$in: ['action', 'spell']
|
||||||
]};
|
},
|
||||||
CreatureProperties.update(filter, {
|
usesUsed: { $ne: 0 },
|
||||||
|
};
|
||||||
|
CreatureProperties.find(actionFilter, {
|
||||||
|
fields: { name: 1, usesUsed: 1 }
|
||||||
|
}).forEach(prop => {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: prop.name,
|
||||||
|
value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
CreatureProperties.update(actionFilter, {
|
||||||
$set: {
|
$set: {
|
||||||
usesUsed: 0,
|
usesUsed: 0,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
@@ -95,8 +126,9 @@ function doRestWork(restType, actionContext) {
|
|||||||
selector: { type: 'action' },
|
selector: { type: 'action' },
|
||||||
multi: true,
|
multi: true,
|
||||||
});
|
});
|
||||||
// Reset half hit dice on a long rest, starting with the highest dice
|
}
|
||||||
if (restType === 'longRest'){
|
|
||||||
|
function resetHitDice(creatureId, actionContext) {
|
||||||
let hitDice = CreatureProperties.find({
|
let hitDice = CreatureProperties.find({
|
||||||
'ancestors.id': creatureId,
|
'ancestors.id': creatureId,
|
||||||
type: 'attribute',
|
type: 'attribute',
|
||||||
@@ -105,6 +137,7 @@ function doRestWork(restType, actionContext) {
|
|||||||
inactive: { $ne: true },
|
inactive: { $ne: true },
|
||||||
}, {
|
}, {
|
||||||
fields: {
|
fields: {
|
||||||
|
name: 1,
|
||||||
hitDiceSize: 1,
|
hitDiceSize: 1,
|
||||||
damage: 1,
|
damage: 1,
|
||||||
total: 1,
|
total: 1,
|
||||||
@@ -129,6 +162,10 @@ function doRestWork(restType, actionContext) {
|
|||||||
if (!amountToRecover) return;
|
if (!amountToRecover) return;
|
||||||
recoverableHd -= amountToRecover;
|
recoverableHd -= amountToRecover;
|
||||||
resultingDamage = hd.damage - amountToRecover;
|
resultingDamage = hd.damage - amountToRecover;
|
||||||
|
actionContext.addLog({
|
||||||
|
name: hd.name,
|
||||||
|
value: amountToRecover >= 0 ? `Restored ${amountToRecover} hit dice` : `Removed ${-amountToRecover} hit dice`
|
||||||
|
});
|
||||||
CreatureProperties.update(hd._id, {
|
CreatureProperties.update(hd._id, {
|
||||||
$set: {
|
$set: {
|
||||||
damage: resultingDamage,
|
damage: resultingDamage,
|
||||||
@@ -139,6 +176,5 @@ function doRestWork(restType, actionContext) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default restCreature;
|
export default restCreature;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ let ExperienceSchema = new SimpleSchema({
|
|||||||
|
|
||||||
Experiences.attachSchema(ExperienceSchema);
|
Experiences.attachSchema(ExperienceSchema);
|
||||||
|
|
||||||
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
const insertExperienceForCreature = function ({ experience, creatureId }) {
|
||||||
if (experience.xp) {
|
if (experience.xp) {
|
||||||
Creatures.update(creatureId, {
|
Creatures.update(creatureId, {
|
||||||
$inc: { 'denormalizedStats.xp': experience.xp },
|
$inc: { 'denormalizedStats.xp': experience.xp },
|
||||||
@@ -172,11 +172,13 @@ const recomputeExperiences = new ValidatedMethod({
|
|||||||
xp += experience.xp || 0;
|
xp += experience.xp || 0;
|
||||||
milestoneLevels += experience.levels || 0;
|
milestoneLevels += experience.levels || 0;
|
||||||
});
|
});
|
||||||
Creatures.update(creatureId, {$set: {
|
Creatures.update(creatureId, {
|
||||||
|
$set: {
|
||||||
'denormalizedStats.xp': xp,
|
'denormalizedStats.xp': xp,
|
||||||
'denormalizedStats.milestoneLevels': milestoneLevels,
|
'denormalizedStats.milestoneLevels': milestoneLevels,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -99,14 +99,16 @@ const insertCreatureLog = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
run({ log }) {
|
run({ log }) {
|
||||||
const creatureId = log.creatureId;
|
const creatureId = log.creatureId;
|
||||||
const creature = Creatures.findOne(creatureId, {fields: {
|
const creature = Creatures.findOne(creatureId, {
|
||||||
|
fields: {
|
||||||
readers: 1,
|
readers: 1,
|
||||||
writers: 1,
|
writers: 1,
|
||||||
owner: 1,
|
owner: 1,
|
||||||
'settings.discordWebhook': 1,
|
'settings.discordWebhook': 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
// Build the new log
|
// Build the new log
|
||||||
let id = insertCreatureLogWork({ log, creature, method: this })
|
let id = insertCreatureLogWork({ log, creature, method: this })
|
||||||
@@ -154,14 +156,16 @@ const logRoll = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
}).validator(),
|
}).validator(),
|
||||||
run({ roll, creatureId }) {
|
run({ roll, creatureId }) {
|
||||||
const creature = Creatures.findOne(creatureId, {fields: {
|
const creature = Creatures.findOne(creatureId, {
|
||||||
|
fields: {
|
||||||
readers: 1,
|
readers: 1,
|
||||||
writers: 1,
|
writers: 1,
|
||||||
owner: 1,
|
owner: 1,
|
||||||
'settings.discordWebhook': 1,
|
'settings.discordWebhook': 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatarPicture: 1,
|
avatarPicture: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
assertEditPermission(creature, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
||||||
let logContent = []
|
let logContent = []
|
||||||
|
|||||||
3
app/imports/api/docs/Docs.js
Normal file
3
app/imports/api/docs/Docs.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
if (Meteor.isServer) throw 'Client side only collection, don\'t import on server';
|
||||||
|
const Docs = new Mongo.Collection('docs');
|
||||||
|
export default Docs;
|
||||||
@@ -4,6 +4,7 @@ import branch from './applyPropertyByType/applyBranch.js';
|
|||||||
import buff from './applyPropertyByType/applyBuff.js';
|
import buff from './applyPropertyByType/applyBuff.js';
|
||||||
import buffRemover from './applyPropertyByType/applyBuffRemover.js';
|
import buffRemover from './applyPropertyByType/applyBuffRemover.js';
|
||||||
import damage from './applyPropertyByType/applyDamage.js';
|
import damage from './applyPropertyByType/applyDamage.js';
|
||||||
|
import folder from './applyPropertyByType/applyFolder.js';
|
||||||
import note from './applyPropertyByType/applyNote.js';
|
import note from './applyPropertyByType/applyNote.js';
|
||||||
import roll from './applyPropertyByType/applyRoll.js';
|
import roll from './applyPropertyByType/applyRoll.js';
|
||||||
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
||||||
@@ -16,6 +17,7 @@ const applyPropertyByType = {
|
|||||||
buff,
|
buff,
|
||||||
buffRemover,
|
buffRemover,
|
||||||
damage,
|
damage,
|
||||||
|
folder,
|
||||||
note,
|
note,
|
||||||
roll,
|
roll,
|
||||||
savingThrow,
|
savingThrow,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/met
|
|||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js';
|
||||||
|
|
||||||
export default function applyAction(node, actionContext) {
|
export default function applyAction(node, actionContext) {
|
||||||
applyNodeTriggers(node, 'before', actionContext);
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
@@ -44,6 +45,9 @@ export default function applyAction(node, actionContext) {
|
|||||||
} else {
|
} else {
|
||||||
applyChildren(node, actionContext);
|
applyChildren(node, actionContext);
|
||||||
}
|
}
|
||||||
|
if (prop.actionType === 'event' && prop.variableName) {
|
||||||
|
resetProperties(actionContext.creature._id, prop.variableName, actionContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyAttackWithoutTarget({ attack, actionContext }) {
|
function applyAttackWithoutTarget({ attack, actionContext }) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { some, intersection, difference, remove } from 'lodash';
|
import { some, intersection, difference, remove, includes } from 'lodash';
|
||||||
import applyProperty from '../applyProperty.js';
|
import applyProperty from '../applyProperty.js';
|
||||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||||
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
||||||
@@ -147,21 +147,21 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
multiplier.immunity &&
|
multiplier.immunity &&
|
||||||
some(multiplier.immunities, multiplierAppliesTo(damageProp))
|
some(multiplier.immunities, multiplierAppliesTo(damageProp, 'immunity'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Immune to ${damageTypeText}`);
|
logValue.push(`Immune to ${damageTypeText}`);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
multiplier.resistance &&
|
multiplier.resistance &&
|
||||||
some(multiplier.resistances, multiplierAppliesTo(damageProp))
|
some(multiplier.resistances, multiplierAppliesTo(damageProp, 'resistance'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Resistant to ${damageTypeText}`);
|
logValue.push(`Resistant to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage / 2);
|
damage = Math.floor(damage / 2);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
multiplier.vulnerability &&
|
multiplier.vulnerability &&
|
||||||
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp))
|
some(multiplier.vulnerabilities, multiplierAppliesTo(damageProp, 'vulnerability'))
|
||||||
) {
|
) {
|
||||||
logValue.push(`Vulnerable to ${damageTypeText}`);
|
logValue.push(`Vulnerable to ${damageTypeText}`);
|
||||||
damage = Math.floor(damage * 2);
|
damage = Math.floor(damage * 2);
|
||||||
@@ -170,8 +170,11 @@ function applyDamageMultipliers({target, damage, damageProp, logValue}){
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
function multiplierAppliesTo(damageProp){
|
function multiplierAppliesTo(damageProp, multiplierType) {
|
||||||
return multiplier => {
|
return multiplier => {
|
||||||
|
// Apply the default 'ignore x' tags
|
||||||
|
if (includes(damageProp.tags, `ignore ${multiplierType}`)) return false;
|
||||||
|
|
||||||
const hasRequiredTags = difference(
|
const hasRequiredTags = difference(
|
||||||
multiplier.includeTags, damageProp.tags
|
multiplier.includeTags, damageProp.tags
|
||||||
).length === 0;
|
).length === 0;
|
||||||
@@ -236,6 +239,14 @@ function dealDamage({target, damageType, amount, actionContext}){
|
|||||||
actionContext
|
actionContext
|
||||||
});
|
});
|
||||||
damageLeft -= damageAdded;
|
damageLeft -= damageAdded;
|
||||||
|
// Prevent overflow
|
||||||
|
if (
|
||||||
|
damageType === 'healing' ?
|
||||||
|
healthBar.healthBarNoHealingOverflow :
|
||||||
|
healthBar.healthBarNoDamageOverflow
|
||||||
|
) {
|
||||||
|
damageLeft = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return totalDamage;
|
return totalDamage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||||
|
import applyProperty from '../applyProperty.js';
|
||||||
|
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
|
|
||||||
|
export default function applyFolder(node, actionContext) {
|
||||||
|
// Apply triggers
|
||||||
|
applyNodeTriggers(node, 'before', actionContext);
|
||||||
|
applyNodeTriggers(node, 'after', actionContext);
|
||||||
|
// Apply children
|
||||||
|
node.children.forEach(child => applyProperty(child, actionContext));
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import logErrors from './logErrors.js';
|
|||||||
export default function recalculateCalculation(calc, actionContext, context){
|
export default function recalculateCalculation(calc, actionContext, context){
|
||||||
if (!calc?.parseNode) return;
|
if (!calc?.parseNode) return;
|
||||||
calc._parseLevel = 'reduce';
|
calc._parseLevel = 'reduce';
|
||||||
applyEffectsToCalculationParseNode(calc, actionContext.log);
|
applyEffectsToCalculationParseNode(calc, actionContext);
|
||||||
evaluateCalculation(calc, actionContext.scope, context);
|
evaluateCalculation(calc, actionContext.scope, context);
|
||||||
logErrors(calc.errors, actionContext.log);
|
logErrors(calc.errors, actionContext);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ const doAction = new ValidatedMethod({
|
|||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
ritual: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
targetIds: {
|
targetIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
@@ -41,7 +45,7 @@ const doAction = new ValidatedMethod({
|
|||||||
numRequests: 10,
|
numRequests: 10,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ spellId, slotId, targetIds = [], scope = {} }) {
|
run({ spellId, slotId, ritual, targetIds = [], scope = {} }) {
|
||||||
// Get action context
|
// Get action context
|
||||||
let spell = CreatureProperties.findOne(spellId);
|
let spell = CreatureProperties.findOne(spellId);
|
||||||
const creatureId = spell.ancestors[0].id;
|
const creatureId = spell.ancestors[0].id;
|
||||||
@@ -64,7 +68,8 @@ const doAction = new ValidatedMethod({
|
|||||||
let slotLevel = spell.level || 0;
|
let slotLevel = spell.level || 0;
|
||||||
let slot;
|
let slot;
|
||||||
|
|
||||||
if (slotId && !spell.castWithoutSpellSlots){
|
// If a spell requires a slot, make sure a slot is spent
|
||||||
|
if (spell.level && !spell.castWithoutSpellSlots && !(ritual && spell.ritual)) {
|
||||||
slot = CreatureProperties.findOne(slotId);
|
slot = CreatureProperties.findOne(slotId);
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
throw new Meteor.Error('No slot',
|
throw new Meteor.Error('No slot',
|
||||||
@@ -101,10 +106,16 @@ const doAction = new ValidatedMethod({
|
|||||||
name: `Casting using a level ${slotLevel} spell slot`
|
name: `Casting using a level ${slotLevel} spell slot`
|
||||||
});
|
});
|
||||||
} else if (slotLevel) {
|
} else if (slotLevel) {
|
||||||
|
if (ritual) {
|
||||||
|
actionContext.addLog({
|
||||||
|
name: `Ritual casting at level ${slotLevel}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: `Casting at level ${slotLevel}`
|
name: `Casting at level ${slotLevel}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actionContext.scope['slotLevel'] = slotLevel;
|
actionContext.scope['slotLevel'] = slotLevel;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import rollDice from '/imports/parser/rollDice.js';
|
|||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
import { applyTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||||
|
import evaluateCalculation from '/imports/api/engine/computation/utility/evaluateCalculation.js';
|
||||||
|
|
||||||
const doCheck = new ValidatedMethod({
|
const doCheck = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doCheck',
|
name: 'creatureProperties.doCheck',
|
||||||
@@ -72,7 +73,11 @@ function rollCheck(prop, actionContext) {
|
|||||||
throw (`${prop.type} not supported for checks`);
|
throw (`${prop.type} not supported for checks`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rollModifierText = numberToSignedString(rollModifier, true);
|
let rollModifierText = numberToSignedString(rollModifier, true);
|
||||||
|
|
||||||
|
const { effectBonus, effectString } = applyUnresolvedEffects(prop, scope)
|
||||||
|
rollModifierText += effectString;
|
||||||
|
rollModifier += effectBonus;
|
||||||
|
|
||||||
let value, values, resultPrefix;
|
let value, values, resultPrefix;
|
||||||
if (scope['$checkAdvantage'] === 1) {
|
if (scope['$checkAdvantage'] === 1) {
|
||||||
@@ -101,8 +106,29 @@ function rollCheck(prop, actionContext) {
|
|||||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||||
}
|
}
|
||||||
const result = (value + rollModifier) || 0;
|
const result = (value + rollModifier) || 0;
|
||||||
|
scope['$checkDiceRoll'] = value;
|
||||||
|
scope['$checkRoll'] = result;
|
||||||
|
scope['$checkModifier'] = rollModifier;
|
||||||
actionContext.addLog({
|
actionContext.addLog({
|
||||||
name: logName,
|
name: logName,
|
||||||
value: `${resultPrefix} **${result}**`,
|
value: `${resultPrefix} **${result}**`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyUnresolvedEffects(prop, scope) {
|
||||||
|
let effectBonus = 0;
|
||||||
|
let effectString = '';
|
||||||
|
if (!prop.effects) {
|
||||||
|
return { effectBonus, effectString };
|
||||||
|
}
|
||||||
|
prop.effects.forEach(effect => {
|
||||||
|
if (!effect.amount?.parseNode) return;
|
||||||
|
if (effect.operation !== 'add') return;
|
||||||
|
effect.amount._parseLevel = 'reduce';
|
||||||
|
evaluateCalculation(effect.amount, scope);
|
||||||
|
if (typeof effect.amount?.value !== 'number') return;
|
||||||
|
effectBonus += effect.amount.value;
|
||||||
|
effectString += ` ${effect.amount.value < 0 ? '-' : '+'} [${effect.amount.calculation}] ${Math.abs(effect.amount.value)}`
|
||||||
|
});
|
||||||
|
return { effectBonus, effectString };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { EJSON } from 'meteor/ejson';
|
import { EJSON } from 'meteor/ejson';
|
||||||
import createGraph from 'ngraph.graph';
|
import createGraph, { Graph } from 'ngraph.graph';
|
||||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||||
|
|
||||||
|
interface CreatureProperty {
|
||||||
|
_id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class CreatureComputation {
|
export default class CreatureComputation {
|
||||||
constructor(properties, creature, variables){
|
originalPropsById: object;
|
||||||
|
propsById: object;
|
||||||
|
propsWithTag: object;
|
||||||
|
scope: object;
|
||||||
|
props: Array<CreatureProperty>;
|
||||||
|
dependencyGraph: Graph;
|
||||||
|
errors: Array<object>;
|
||||||
|
creature: object;
|
||||||
|
variables: object;
|
||||||
|
|
||||||
|
constructor(properties: Array<CreatureProperty>, creature: object, variables: object) {
|
||||||
// Set up fields
|
// Set up fields
|
||||||
this.originalPropsById = {};
|
this.originalPropsById = {};
|
||||||
this.propsById = {};
|
this.propsById = {};
|
||||||
@@ -29,8 +29,8 @@ function childrenActive(prop){
|
|||||||
// Children of disabled properties are always inactive
|
// Children of disabled properties are always inactive
|
||||||
if (prop.disabled) return false;
|
if (prop.disabled) return false;
|
||||||
switch (prop.type){
|
switch (prop.type){
|
||||||
// Only equipped items have active children
|
// Only equipped items with non-zero quantity have active children
|
||||||
case 'item': return !!prop.equipped;
|
case 'item': return !!prop.equipped && prop.quantity !== 0;
|
||||||
// The children of actions, spells, and triggers are always inactive
|
// The children of actions, spells, and triggers are always inactive
|
||||||
case 'action': return false;
|
case 'action': return false;
|
||||||
case 'spell': return false;
|
case 'spell': return false;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ export default function computeToggleDependencies(node, dependencyGraph){
|
|||||||
prop.enabled
|
prop.enabled
|
||||||
) return;
|
) return;
|
||||||
walkDown(node.children, child => {
|
walkDown(node.children, child => {
|
||||||
// Only for children that aren't inactive
|
|
||||||
if (child.node.inactive) return;
|
|
||||||
// The child nodes depend on the toggle condition compuation
|
// The child nodes depend on the toggle condition compuation
|
||||||
child.node._computationDetails.toggleAncestors.push(prop);
|
child.node._computationDetails.toggleAncestors.push(prop);
|
||||||
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import computeToggleDependencies from './buildComputation/computeToggleDependenc
|
|||||||
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js';
|
||||||
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
import linkTypeDependencies from './buildComputation/linkTypeDependencies.js';
|
||||||
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js';
|
||||||
import CreatureComputation from './CreatureComputation.js';
|
import CreatureComputation from './CreatureComputation.ts';
|
||||||
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
import removeSchemaFields from './buildComputation/removeSchemaFields.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import skill from './computeByType/computeSkill.js';
|
|||||||
import pointBuy from './computeByType/computePointBuy.js';
|
import pointBuy from './computeByType/computePointBuy.js';
|
||||||
import propertySlot from './computeByType/computeSlot.js';
|
import propertySlot from './computeByType/computeSlot.js';
|
||||||
import container from './computeByType/computeContainer.js';
|
import container from './computeByType/computeContainer.js';
|
||||||
|
import spellList from './computeByType/computeSpellList.js';
|
||||||
import _calculation from './computeByType/computeCalculation.js';
|
import _calculation from './computeByType/computeCalculation.js';
|
||||||
|
|
||||||
export default Object.freeze({
|
export default Object.freeze({
|
||||||
@@ -17,4 +18,5 @@ export default Object.freeze({
|
|||||||
pointBuy,
|
pointBuy,
|
||||||
propertySlot,
|
propertySlot,
|
||||||
spell: action,
|
spell: action,
|
||||||
|
spellList,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default function computeSpelllist(computation, node) {
|
||||||
|
const prop = node.data;
|
||||||
|
|
||||||
|
const ability = computation.scope[prop.ability];
|
||||||
|
prop.abilityMod = ability?.modifier || 0;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
export default function aggregateEffect({ node, linkedNode, link }) {
|
export default function aggregateEffect({ node, linkedNode, link }) {
|
||||||
if (link.data !== 'effect') return;
|
if (link.data !== 'effect') return;
|
||||||
// store the effect aggregator, its presence indicates that the variable is
|
// store the effect aggregator, its presence indicates that the variable is
|
||||||
@@ -19,12 +21,24 @@ export default function aggregateEffect({node, linkedNode, link}){
|
|||||||
|
|
||||||
// Store a summary of the effect itself
|
// Store a summary of the effect itself
|
||||||
node.data.effects = node.data.effects || [];
|
node.data.effects = node.data.effects || [];
|
||||||
|
// Store either just
|
||||||
|
let effectAmount;
|
||||||
|
if (!linkedNode.data.amount) {
|
||||||
|
effectAmount = undefined;
|
||||||
|
} else if (typeof linkedNode.data.amount.value === 'string') {
|
||||||
|
effectAmount = pick(linkedNode.data.amount, [
|
||||||
|
'calculation', 'parseNode', 'parseError', 'value'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
effectAmount = pick(linkedNode.data.amount, ['value']);
|
||||||
|
}
|
||||||
node.data.effects.push({
|
node.data.effects.push({
|
||||||
_id: linkedNode.data._id,
|
_id: linkedNode.data._id,
|
||||||
name: linkedNode.data.name,
|
name: linkedNode.data.name,
|
||||||
operation: linkedNode.data.operation,
|
operation: linkedNode.data.operation,
|
||||||
amount: linkedNode.data.amount && {value: linkedNode.data.amount.value},
|
amount: effectAmount,
|
||||||
type: linkedNode.data.type,
|
type: linkedNode.data.type,
|
||||||
|
text: linkedNode.data.text,
|
||||||
// ancestors: linkedNode.data.ancestors,
|
// ancestors: linkedNode.data.ancestors,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,8 +46,7 @@ export default function aggregateEffect({node, linkedNode, link}){
|
|||||||
const aggregator = node.data.effectAggregator;
|
const aggregator = node.data.effectAggregator;
|
||||||
// Get the result of the effect
|
// Get the result of the effect
|
||||||
const result = linkedNode.data.amount?.value;
|
const result = linkedNode.data.amount?.value;
|
||||||
// Skip aggregating if the result is not resolved completely
|
|
||||||
if (typeof result === 'string') return;
|
|
||||||
// Aggregate the effect based on its operation
|
// Aggregate the effect based on its operation
|
||||||
switch (linkedNode.data.operation) {
|
switch (linkedNode.data.operation) {
|
||||||
case 'base':
|
case 'base':
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default function evaluateToggles(computation, node){
|
|||||||
let toggles = prop._computationDetails?.toggleAncestors;
|
let toggles = prop._computationDetails?.toggleAncestors;
|
||||||
if (!toggles) return;
|
if (!toggles) return;
|
||||||
toggles.forEach(toggle => {
|
toggles.forEach(toggle => {
|
||||||
if (prop.inactive || !toggle.condition) return;
|
if (!toggle.condition) return;
|
||||||
if (!toggle.condition.value){
|
if (!toggle.condition.value){
|
||||||
prop.inactive = true;
|
prop.inactive = true;
|
||||||
prop.deactivatedByToggle = true;
|
prop.deactivatedByToggle = true;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export default function getEffectivePropTags(prop) {
|
|||||||
if (prop.variableName) tags.push(prop.variableName);
|
if (prop.variableName) tags.push(prop.variableName);
|
||||||
if (prop.damageType) tags.push(prop.damageType);
|
if (prop.damageType) tags.push(prop.damageType);
|
||||||
if (prop.skillType) tags.push(prop.skillType);
|
if (prop.skillType) tags.push(prop.skillType);
|
||||||
|
if (prop.actionType) tags.push(prop.actionType);
|
||||||
if (prop.attributeType) tags.push(prop.attributeType);
|
if (prop.attributeType) tags.push(prop.attributeType);
|
||||||
if (prop.reset) tags.push(prop.reset);
|
if (prop.reset) tags.push(prop.reset);
|
||||||
return tags;
|
return tags;
|
||||||
|
|||||||
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
97
app/imports/api/library/methods/copyLibraryNodeTo.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||||
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
|
import {
|
||||||
|
assertDocCopyPermission,
|
||||||
|
assertDocEditPermission
|
||||||
|
} from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import {
|
||||||
|
setLineageOfDocs,
|
||||||
|
renewDocIds
|
||||||
|
} from '/imports/api/parenting/parenting.js';
|
||||||
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
|
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||||
|
|
||||||
|
var snackbar;
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar = require(
|
||||||
|
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
||||||
|
).snackbar
|
||||||
|
}
|
||||||
|
|
||||||
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
|
const copyLibraryNodeTo = new ValidatedMethod({
|
||||||
|
name: 'libraryNodes.copyTo',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
_id: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: RefSchema,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 1,
|
||||||
|
timeInterval: 10000,
|
||||||
|
},
|
||||||
|
run({ _id, parent }) {
|
||||||
|
if (parent.collection !== 'libraryNodes' && parent.collection !== 'libraries') {
|
||||||
|
throw new Meteor.Error('Invalid destination',
|
||||||
|
'Library documents can only be copied to destinations inside other libraries'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const libraryNode = LibraryNodes.findOne(_id);
|
||||||
|
const parentDoc = fetchDocByRef(parent);
|
||||||
|
assertDocCopyPermission(libraryNode, this.userId);
|
||||||
|
assertDocEditPermission(parentDoc, this.userId);
|
||||||
|
|
||||||
|
let decendants = LibraryNodes.find({
|
||||||
|
'ancestors.id': _id,
|
||||||
|
removed: { $ne: true },
|
||||||
|
}, {
|
||||||
|
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||||
|
sort: { order: 1 },
|
||||||
|
}).fetch();
|
||||||
|
|
||||||
|
if (decendants.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||||
|
decendants.pop();
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
snackbar({
|
||||||
|
text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = [libraryNode, ...decendants];
|
||||||
|
|
||||||
|
const newAncestry = parentDoc.ancestors || [];
|
||||||
|
newAncestry.push(parent);
|
||||||
|
// re-map all the ancestors
|
||||||
|
setLineageOfDocs({
|
||||||
|
docArray: nodes,
|
||||||
|
newAncestry,
|
||||||
|
oldParent: libraryNode.parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give the docs new IDs without breaking internal references
|
||||||
|
renewDocIds({ docArray: nodes });
|
||||||
|
|
||||||
|
// Order the root node
|
||||||
|
libraryNode.order = (parentDoc.order || 0) + 0.5;
|
||||||
|
|
||||||
|
LibraryNodes.batchInsert(nodes);
|
||||||
|
|
||||||
|
// Tree structure changed by inserts, reorder the tree
|
||||||
|
reorderDocs({
|
||||||
|
collection: LibraryNodes,
|
||||||
|
ancestorId: parent.collection === 'libraries' ? parent.id : parentDoc.ancestors[0].id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default copyLibraryNodeTo;
|
||||||
@@ -16,7 +16,7 @@ if (Meteor.isClient){
|
|||||||
).snackbar
|
).snackbar
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUPLICATE_CHILDREN_LIMIT = 50;
|
const DUPLICATE_CHILDREN_LIMIT = 500;
|
||||||
|
|
||||||
const duplicateLibraryNode = new ValidatedMethod({
|
const duplicateLibraryNode = new ValidatedMethod({
|
||||||
name: 'libraryNodes.duplicate',
|
name: 'libraryNodes.duplicate',
|
||||||
@@ -28,7 +28,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
mixins: [RateLimiterMixin],
|
mixins: [RateLimiterMixin],
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
numRequests: 5,
|
numRequests: 1,
|
||||||
timeInterval: 5000,
|
timeInterval: 5000,
|
||||||
},
|
},
|
||||||
run({ _id }) {
|
run({ _id }) {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
import '/imports/api/library/methods/copyLibraryNodeTo.js';
|
||||||
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
import '/imports/api/library/methods/duplicateLibraryNode.js';
|
||||||
import '/imports/api/library/methods/updateReferenceNode.js';
|
import '/imports/api/library/methods/updateReferenceNode.js';
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
let SoftRemovableSchema = new SimpleSchema({
|
let SoftRemovableSchema = new SimpleSchema({
|
||||||
"removed": {
|
'removed': {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
"removedAt": {
|
'removedAt': {
|
||||||
type: Date,
|
type: Date,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
"removedWith": {
|
'removedWith': {
|
||||||
optional: true,
|
optional: true,
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
|||||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||||
|
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Actions are things a character can do
|
* Actions are things a character can do
|
||||||
@@ -24,9 +25,17 @@ let ActionSchema = createPropertySchema({
|
|||||||
// long actions take longer than 1 round to cast
|
// long actions take longer than 1 round to cast
|
||||||
actionType: {
|
actionType: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long'],
|
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long', 'event'],
|
||||||
defaultValue: 'action',
|
defaultValue: 'action',
|
||||||
},
|
},
|
||||||
|
// If the action type is an event, what is the variable name of that event?
|
||||||
|
variableName: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
|
},
|
||||||
// Who is the action directed at
|
// Who is the action directed at
|
||||||
target: {
|
target: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -56,8 +65,10 @@ let ActionSchema = createPropertySchema({
|
|||||||
// How this action's uses are reset automatically
|
// How this action's uses are reset automatically
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: ['longRest', 'shortRest'],
|
|
||||||
optional: true,
|
optional: true,
|
||||||
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
},
|
},
|
||||||
// Resources
|
// Resources
|
||||||
resources: {
|
resources: {
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ let AttributeSchema = createPropertySchema({
|
|||||||
'stat', // Speed, Armor Class
|
'stat', // Speed, Armor Class
|
||||||
'modifier', // Proficiency Bonus, displayed as +x
|
'modifier', // Proficiency Bonus, displayed as +x
|
||||||
'hitDice', // d12 hit dice
|
'hitDice', // d12 hit dice
|
||||||
'healthBar', // Hitpoints, Temporary Hitpoints, can take damage
|
'healthBar', // Hitpoints, Temporary Hitpoints
|
||||||
'bar', // Displayed as a health bar, can't take damage
|
|
||||||
'resource', // Rages, sorcery points
|
'resource', // Rages, sorcery points
|
||||||
'spellSlot', // Level 1, 2, 3... spell slots
|
'spellSlot', // Level 1, 2, 3... spell slots
|
||||||
'utility', // Aren't displayed, Jump height, Carry capacity
|
'utility', // Aren't displayed, Jump height, Carry capacity
|
||||||
@@ -69,6 +68,16 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Control how the health bar handles overflow
|
||||||
|
healthBarNoDamageOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
healthBarNoHealingOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Control when the health bar takes damage or healing
|
||||||
healthBarDamageOrder: {
|
healthBarDamageOrder: {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -107,11 +116,21 @@ let AttributeSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
hideWhenTotalZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
hideWhenValueZero: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
// Automatically zero the adjustment on these conditions
|
// Automatically zero the adjustment on these conditions
|
||||||
reset: {
|
reset: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
allowedValues: ['shortRest', 'longRest'],
|
regEx: VARIABLE_NAME_REGEX,
|
||||||
|
min: 2,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ let FolderSchema = new createPropertySchema({
|
|||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
groupStats: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ let SpellListSchema = createPropertySchema({
|
|||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// The variable name of the ability this spell relies on
|
||||||
|
ability: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
max: STORAGE_LIMITS.variableName,
|
||||||
|
},
|
||||||
// Calculation of The attack roll bonus used by spell attacks in this list
|
// Calculation of The attack roll bonus used by spell attacks in this list
|
||||||
attackRollBonus: {
|
attackRollBonus: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
@@ -38,6 +44,12 @@ const ComputedOnlySpellListSchema = createPropertySchema({
|
|||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Computed value determined by the ability
|
||||||
|
abilityMod: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
attackRollBonus: {
|
attackRollBonus: {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ let SharingSchema = new SimpleSchema({
|
|||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
|
readersCanCopy: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SharingSchema;
|
export default SharingSchema;
|
||||||
|
|||||||
@@ -27,6 +27,26 @@ const setPublic = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setReadersCanCopy = new ValidatedMethod({
|
||||||
|
name: 'sharing.setReadersCanCopy',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
docRef: RefSchema,
|
||||||
|
readersCanCopy: { type: Boolean },
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({ docRef, readersCanCopy }) {
|
||||||
|
let doc = fetchDocByRef(docRef);
|
||||||
|
assertOwnership(doc, this.userId);
|
||||||
|
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||||
|
$set: { readersCanCopy },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const updateUserSharePermissions = new ValidatedMethod({
|
const updateUserSharePermissions = new ValidatedMethod({
|
||||||
name: 'sharing.updateUserSharePermissions',
|
name: 'sharing.updateUserSharePermissions',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
@@ -129,4 +149,4 @@ const transferOwnership = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
export { setPublic, setReadersCanCopy, updateUserSharePermissions, transferOwnership };
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ function assertdocExists(doc){
|
|||||||
export function assertOwnership(doc, userId) {
|
export function assertOwnership(doc, userId) {
|
||||||
assertIdValid(userId);
|
assertIdValid(userId);
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
|
|
||||||
if (doc.owner === userId) {
|
if (doc.owner === userId) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -37,7 +38,6 @@ export function assertEditPermission(doc, userId) {
|
|||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
const user = Meteor.users.findOne(userId, {
|
const user = Meteor.users.findOne(userId, {
|
||||||
fields: {
|
fields: {
|
||||||
'services.patreon': 1,
|
|
||||||
'roles': 1,
|
'roles': 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -59,6 +59,43 @@ export function assertEditPermission(doc, userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can edit the root document which manages its own sharing
|
||||||
|
* permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertCopyPermission(doc, userId) {
|
||||||
|
assertIdValid(userId);
|
||||||
|
assertdocExists(doc);
|
||||||
|
const user = Meteor.users.findOne(userId, {
|
||||||
|
fields: {
|
||||||
|
'roles': 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin override
|
||||||
|
if (user.roles && user.roles.includes('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the user is authorized for this specific document
|
||||||
|
if (
|
||||||
|
doc.owner === userId ||
|
||||||
|
_.contains(doc.writers, userId)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else if (
|
||||||
|
(_.contains(doc.readers, userId) || doc.public) &&
|
||||||
|
doc.readersCanCopy
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Meteor.Error('Copy permission denied',
|
||||||
|
'You do not have permission to copy this document');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getRoot(doc) {
|
function getRoot(doc) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
if (doc.ancestors && doc.ancestors.length && doc.ancestors[0]) {
|
||||||
@@ -79,6 +116,17 @@ export function assertDocEditPermission(doc, userId){
|
|||||||
assertEditPermission(root, userId);
|
assertEditPermission(root, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the user can copy a descendant document whose root ancestor
|
||||||
|
* implements sharing permissions.
|
||||||
|
*
|
||||||
|
* Warning: the doc and userId must be set by a trusted source
|
||||||
|
*/
|
||||||
|
export function assertDocCopyPermission(doc, userId) {
|
||||||
|
let root = getRoot(doc);
|
||||||
|
assertCopyPermission(root, userId);
|
||||||
|
}
|
||||||
|
|
||||||
export function assertViewPermission(doc, userId) {
|
export function assertViewPermission(doc, userId) {
|
||||||
assertdocExists(doc);
|
assertdocExists(doc);
|
||||||
if (doc.public) return true;
|
if (doc.public) return true;
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
|
|||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
const dbVersion = Migrations.getVersion();
|
const dbVersion = Migrations.getVersion();
|
||||||
|
// If there are no users, this is a new DB, set the version to latest
|
||||||
|
const aUser = Meteor.users.findOne({});
|
||||||
|
const latestVersion = Migrations._list[Migrations._list.length - 1].version
|
||||||
|
if (!aUser && dbVersion !== latestVersion) {
|
||||||
|
Migrations._collection.update({ _id: 'control' }, { version: latestVersion });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise put the app in maintenance mode if it's not the right version
|
||||||
if (
|
if (
|
||||||
!Meteor.settings.public.maintenanceMode &&
|
!Meteor.settings.public.maintenanceMode &&
|
||||||
dbVersion !== undefined &&
|
dbVersion !== undefined &&
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ const PROPERTIES = Object.freeze({
|
|||||||
action: {
|
action: {
|
||||||
icon: '$vuetify.icons.action',
|
icon: '$vuetify.icons.action',
|
||||||
name: 'Action',
|
name: 'Action',
|
||||||
|
docsPath: 'property/action',
|
||||||
helpText: 'Actions are things your character can do. When an action is taken, all the properties under it are activated.',
|
helpText: 'Actions are things your character can do. When an action is taken, all the properties under it are activated.',
|
||||||
suggestedParents: ['classLevel', 'feature', 'item'],
|
suggestedParents: ['classLevel', 'feature', 'item'],
|
||||||
},
|
},
|
||||||
attribute: {
|
attribute: {
|
||||||
icon: '$vuetify.icons.attribute',
|
icon: '$vuetify.icons.attribute',
|
||||||
name: 'Attribute',
|
name: 'Attribute',
|
||||||
|
docsPath: 'property/attribute',
|
||||||
helpText: 'Attributes are the numbered statistics of your character, excluding rolls you might add proficiency bonus to, those are skills.',
|
helpText: 'Attributes are the numbered statistics of your character, excluding rolls you might add proficiency bonus to, those are skills.',
|
||||||
examples: 'Ability scores, speed, hit points, ki',
|
examples: 'Ability scores, speed, hit points, ki',
|
||||||
suggestedParents: ['classLevel', 'buff'],
|
suggestedParents: ['classLevel', 'buff'],
|
||||||
@@ -15,48 +17,56 @@ const PROPERTIES = Object.freeze({
|
|||||||
adjustment: {
|
adjustment: {
|
||||||
icon: '$vuetify.icons.attribute_damage',
|
icon: '$vuetify.icons.attribute_damage',
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
|
docsPath: 'property/attribute-damage',
|
||||||
helpText: 'Attribute damage reduces the current value of an attribute when it is applied by an action. A negative value causes the attribute to increase instead, up to its normal maximum.',
|
helpText: 'Attribute damage reduces the current value of an attribute when it is applied by an action. A negative value causes the attribute to increase instead, up to its normal maximum.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
buff: {
|
buff: {
|
||||||
icon: '$vuetify.icons.buff',
|
icon: '$vuetify.icons.buff',
|
||||||
name: 'Buff',
|
name: 'Buff',
|
||||||
|
docsPath: 'property/buff',
|
||||||
helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.',
|
helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
buffRemover: {
|
buffRemover: {
|
||||||
icon: '$vuetify.icons.buffRemover',
|
icon: '$vuetify.icons.buffRemover',
|
||||||
name: 'Remove Buff',
|
name: 'Remove Buff',
|
||||||
|
docsPath: 'property/remove-buff',
|
||||||
helpText: 'Removes a buff from the target character',
|
helpText: 'Removes a buff from the target character',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
branch: {
|
branch: {
|
||||||
icon: 'mdi-file-tree',
|
icon: 'mdi-file-tree',
|
||||||
name: 'Branch',
|
name: 'Branch',
|
||||||
|
docsPath: 'property/branch',
|
||||||
helpText: 'When a branch is activated as a child of an action, it can control which of its children get activated.',
|
helpText: 'When a branch is activated as a child of an action, it can control which of its children get activated.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell'],
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
icon: 'mdi-card-account-details',
|
icon: 'mdi-card-account-details',
|
||||||
name: 'Class',
|
name: 'Class',
|
||||||
|
docsPath: 'property/class',
|
||||||
helpText: 'Your character should ideally have one starting class. Classes hold class levels',
|
helpText: 'Your character should ideally have one starting class. Classes hold class levels',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
classLevel: {
|
classLevel: {
|
||||||
icon: '$vuetify.icons.class_level',
|
icon: '$vuetify.icons.class_level',
|
||||||
name: 'Class level',
|
name: 'Class level',
|
||||||
|
docsPath: 'property/class-level',
|
||||||
helpText: 'Class levels represent a single level gained in a class',
|
helpText: 'Class levels represent a single level gained in a class',
|
||||||
suggestedParents: ['class'],
|
suggestedParents: ['class'],
|
||||||
},
|
},
|
||||||
constant: {
|
constant: {
|
||||||
icon: 'mdi-anchor',
|
icon: 'mdi-anchor',
|
||||||
name: 'Constant',
|
name: 'Constant',
|
||||||
|
docsPath: 'property/constant',
|
||||||
helpText: 'A constant can define a static value that can be used in calculations elsewhere in the sheet',
|
helpText: 'A constant can define a static value that can be used in calculations elsewhere in the sheet',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
icon: 'mdi-bag-personal-outline',
|
icon: 'mdi-bag-personal-outline',
|
||||||
name: 'Container',
|
name: 'Container',
|
||||||
|
docsPath: 'property/container',
|
||||||
helpText: 'A container holds items in the inventory',
|
helpText: 'A container holds items in the inventory',
|
||||||
examples: 'Coin pouch, backpack',
|
examples: 'Coin pouch, backpack',
|
||||||
suggestedParents: ['folder'],
|
suggestedParents: ['folder'],
|
||||||
@@ -64,18 +74,21 @@ const PROPERTIES = Object.freeze({
|
|||||||
damage: {
|
damage: {
|
||||||
icon: '$vuetify.icons.damage',
|
icon: '$vuetify.icons.damage',
|
||||||
name: 'Damage',
|
name: 'Damage',
|
||||||
|
docsPath: 'property/damage',
|
||||||
helpText: 'When damage is activated by an action it reduces the hit points of the target creature by the calculated amount.',
|
helpText: 'When damage is activated by an action it reduces the hit points of the target creature by the calculated amount.',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
damageMultiplier: {
|
damageMultiplier: {
|
||||||
icon: '$vuetify.icons.damage_multiplier',
|
icon: '$vuetify.icons.damage_multiplier',
|
||||||
name: 'Damage multiplier',
|
name: 'Damage multiplier',
|
||||||
|
docsPath: 'property/damage-multiplier',
|
||||||
helpText: 'Resistance, vulnerability, and immunity.',
|
helpText: 'Resistance, vulnerability, and immunity.',
|
||||||
suggestedParents: ['classLevel', 'feature', 'item'],
|
suggestedParents: ['classLevel', 'feature', 'item'],
|
||||||
},
|
},
|
||||||
effect: {
|
effect: {
|
||||||
icon: '$vuetify.icons.effect',
|
icon: '$vuetify.icons.effect',
|
||||||
name: 'Effect',
|
name: 'Effect',
|
||||||
|
docsPath: 'property/effect',
|
||||||
helpText: 'Effects change the value or state of attributes and skills.',
|
helpText: 'Effects change the value or state of attributes and skills.',
|
||||||
examples: '+2 Strength, Advantage on dexterity saving throws',
|
examples: '+2 Strength, Advantage on dexterity saving throws',
|
||||||
suggestedParents: ['buff', 'classLevel', 'feature', 'folder', 'item'],
|
suggestedParents: ['buff', 'classLevel', 'feature', 'folder', 'item'],
|
||||||
@@ -83,42 +96,49 @@ const PROPERTIES = Object.freeze({
|
|||||||
feature: {
|
feature: {
|
||||||
icon: 'mdi-text-subject',
|
icon: 'mdi-text-subject',
|
||||||
name: 'Feature',
|
name: 'Feature',
|
||||||
|
docsPath: 'property/feature',
|
||||||
helpText: 'Descriptive or narrative features your character has access to',
|
helpText: 'Descriptive or narrative features your character has access to',
|
||||||
suggestedParents: ['classLevel', 'folder'],
|
suggestedParents: ['classLevel', 'folder'],
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
icon: 'mdi-folder-outline',
|
icon: 'mdi-folder-outline',
|
||||||
name: 'Folder',
|
name: 'Folder',
|
||||||
|
docsPath: 'property/feature',
|
||||||
helpText: 'A way to organise other properties on the character',
|
helpText: 'A way to organise other properties on the character',
|
||||||
suggestedParents: ['folder'],
|
suggestedParents: ['action', 'folder'],
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
icon: 'mdi-cube-outline',
|
icon: 'mdi-cube-outline',
|
||||||
name: 'Item',
|
name: 'Item',
|
||||||
|
docsPath: 'property/item',
|
||||||
helpText: 'Objects and equipment your charcter finds on their adventures',
|
helpText: 'Objects and equipment your charcter finds on their adventures',
|
||||||
suggestedParents: ['container'],
|
suggestedParents: ['container'],
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
icon: 'mdi-note-outline',
|
icon: 'mdi-note-outline',
|
||||||
name: 'Note',
|
name: 'Note',
|
||||||
|
docsPath: 'property/note',
|
||||||
helpText: 'Notes about your character and their adventures',
|
helpText: 'Notes about your character and their adventures',
|
||||||
suggestedParents: ['note', 'folder'],
|
suggestedParents: ['note', 'folder'],
|
||||||
},
|
},
|
||||||
pointBuy: {
|
pointBuy: {
|
||||||
icon: 'mdi-table',
|
icon: 'mdi-table',
|
||||||
name: 'Point Buy',
|
name: 'Point Buy',
|
||||||
|
docsPath: 'property/point-buy',
|
||||||
helpText: 'A point buy table that allows the user to select an array of values that match a given cost',
|
helpText: 'A point buy table that allows the user to select an array of values that match a given cost',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
proficiency: {
|
proficiency: {
|
||||||
icon: 'mdi-brightness-1',
|
icon: 'mdi-brightness-1',
|
||||||
name: 'Proficiency',
|
name: 'Proficiency',
|
||||||
|
docsPath: 'property/proficiency',
|
||||||
helpText: 'Proficiencies apply your proficiency bonus to skills already on your character sheet.',
|
helpText: 'Proficiencies apply your proficiency bonus to skills already on your character sheet.',
|
||||||
suggestedParents: ['buff', 'classLevel', 'feature', 'folder'],
|
suggestedParents: ['buff', 'classLevel', 'feature', 'folder'],
|
||||||
},
|
},
|
||||||
roll: {
|
roll: {
|
||||||
icon: '$vuetify.icons.roll',
|
icon: '$vuetify.icons.roll',
|
||||||
name: 'Roll',
|
name: 'Roll',
|
||||||
|
docsPath: 'property/roll',
|
||||||
helpText: 'When activated by an action, rolls perform a calculation and temporarily store the result for other properties under the same action to use',
|
helpText: 'When activated by an action, rolls perform a calculation and temporarily store the result for other properties under the same action to use',
|
||||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||||
},
|
},
|
||||||
@@ -132,48 +152,56 @@ const PROPERTIES = Object.freeze({
|
|||||||
savingThrow: {
|
savingThrow: {
|
||||||
icon: '$vuetify.icons.saving_throw',
|
icon: '$vuetify.icons.saving_throw',
|
||||||
name: 'Saving throw',
|
name: 'Saving throw',
|
||||||
|
docsPath: 'property/saving-throw',
|
||||||
helpText: 'When a saving throw is activated by an action, it causes the target to make a saving throw, if the saving throw fails, the children properties of the saving throw are activated.',
|
helpText: 'When a saving throw is activated by an action, it causes the target to make a saving throw, if the saving throw fails, the children properties of the saving throw are activated.',
|
||||||
suggestedParents: ['action', 'attack', 'spell'],
|
suggestedParents: ['action', 'attack', 'spell'],
|
||||||
},
|
},
|
||||||
skill: {
|
skill: {
|
||||||
icon: '$vuetify.icons.skill',
|
icon: '$vuetify.icons.skill',
|
||||||
name: 'Skill',
|
name: 'Skill',
|
||||||
|
docsPath: 'property/skill',
|
||||||
helpText: 'Skills, saves, languages, and weapon and tool proficiencies are all skills. Skills can have a default proficiency set. Proficiencies and effects can change the value and state of skills.',
|
helpText: 'Skills, saves, languages, and weapon and tool proficiencies are all skills. Skills can have a default proficiency set. Proficiencies and effects can change the value and state of skills.',
|
||||||
suggestedParents: ['classLevel', 'folder'],
|
suggestedParents: ['classLevel', 'folder'],
|
||||||
},
|
},
|
||||||
propertySlot: {
|
propertySlot: {
|
||||||
icon: 'mdi-power-socket-eu',
|
icon: 'mdi-power-socket-eu',
|
||||||
name: 'Slot',
|
name: 'Slot',
|
||||||
|
docsPath: 'property/slot',
|
||||||
helpText: 'A slot in the character sheet is used to specify that a property needs to be selected from a library to fill the slot. The slot can determine what tags it is looking for, and any subscribed library property with matching tags can fill the slot',
|
helpText: 'A slot in the character sheet is used to specify that a property needs to be selected from a library to fill the slot. The slot can determine what tags it is looking for, and any subscribed library property with matching tags can fill the slot',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
slotFiller: {
|
slotFiller: {
|
||||||
icon: 'mdi-power-plug-outline',
|
icon: 'mdi-power-plug-outline',
|
||||||
name: 'Slot filler',
|
name: 'Slot filler',
|
||||||
|
docsPath: 'property/slot-filler',
|
||||||
helpText: 'A slot filler allows for more advanced logic when it attempts to fill a slot. It can masquarade as any property type, and calculate whether it should fill a slot or not.',
|
helpText: 'A slot filler allows for more advanced logic when it attempts to fill a slot. It can masquarade as any property type, and calculate whether it should fill a slot or not.',
|
||||||
suggestedParents: ['propertySlot'],
|
suggestedParents: ['propertySlot'],
|
||||||
},
|
},
|
||||||
spellList: {
|
spellList: {
|
||||||
icon: '$vuetify.icons.spell_list',
|
icon: '$vuetify.icons.spell_list',
|
||||||
name: 'Spell list',
|
name: 'Spell list',
|
||||||
|
docsPath: 'property/spell-list',
|
||||||
helpText: 'A list of spells on your character sheet. It can provide a DC and spell attack bonus to the spells within',
|
helpText: 'A list of spells on your character sheet. It can provide a DC and spell attack bonus to the spells within',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
spell: {
|
spell: {
|
||||||
icon: '$vuetify.icons.spell',
|
icon: '$vuetify.icons.spell',
|
||||||
name: 'Spell',
|
name: 'Spell',
|
||||||
|
docsPath: 'property/spell',
|
||||||
helpText: 'A spell your character can potentially cast',
|
helpText: 'A spell your character can potentially cast',
|
||||||
suggestedParents: ['spellList'],
|
suggestedParents: ['spellList'],
|
||||||
},
|
},
|
||||||
toggle: {
|
toggle: {
|
||||||
icon: '$vuetify.icons.toggle',
|
icon: '$vuetify.icons.toggle',
|
||||||
name: 'Toggle',
|
name: 'Toggle',
|
||||||
|
docsPath: 'property/toggle',
|
||||||
helpText: 'Togggles allow parts of the character sheet to be turned on and off, either manually or as the result of a calculation.',
|
helpText: 'Togggles allow parts of the character sheet to be turned on and off, either manually or as the result of a calculation.',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
trigger: {
|
trigger: {
|
||||||
icon: 'mdi-electric-switch',
|
icon: 'mdi-electric-switch',
|
||||||
name: 'Trigger',
|
name: 'Trigger',
|
||||||
|
docsPath: 'property/trigger',
|
||||||
helpText: 'Triggers apply their children in response to events on the character sheet, such as taking an action or receiving damage',
|
helpText: 'Triggers apply their children in response to events on the character sheet, such as taking an action or receiving damage',
|
||||||
suggestedParents: [],
|
suggestedParents: [],
|
||||||
},
|
},
|
||||||
@@ -188,3 +216,17 @@ export function getPropertyName(type){
|
|||||||
export function getPropertyIcon(type){
|
export function getPropertyIcon(type){
|
||||||
return type && PROPERTIES[type] && PROPERTIES[type].icon;
|
return type && PROPERTIES[type] && PROPERTIES[type].icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const propsByDocsPath = new Map();
|
||||||
|
|
||||||
|
for (const key in PROPERTIES) {
|
||||||
|
const prop = PROPERTIES[key];
|
||||||
|
if (prop.docsPath) {
|
||||||
|
propsByDocsPath.set(prop.docsPath, {
|
||||||
|
...prop,
|
||||||
|
type: key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { propsByDocsPath };
|
||||||
|
|||||||
@@ -112,10 +112,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'resolve': {
|
'resolve': {
|
||||||
comment: 'Forces the given calcultion to resolve into a number',
|
comment: 'Forces the given calcultion to resolve into a number, even in calculations where it would usually keep the unknown values as is',
|
||||||
examples: [
|
examples: [
|
||||||
{input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'},
|
{input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'},
|
||||||
{input: 'resolve(3d6)', result: '2'},
|
{input: 'resolve(1d6)', result: '4'},
|
||||||
],
|
],
|
||||||
arguments: ['parseNode'],
|
arguments: ['parseNode'],
|
||||||
fn: function resolveFn(node){
|
fn: function resolveFn(node){
|
||||||
|
|||||||
0
app/imports/server/action.js
Normal file
0
app/imports/server/action.js
Normal file
35
app/imports/server/publications/docs.js
Normal file
35
app/imports/server/publications/docs.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { propsByDocsPath } from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
|
|
||||||
|
// Manual doc paths
|
||||||
|
const docPaths = [
|
||||||
|
'computed-fields',
|
||||||
|
'inline-calculations',
|
||||||
|
'dependency-loops',
|
||||||
|
'docs',
|
||||||
|
'tags',
|
||||||
|
'walkthroughs/create-a-class',
|
||||||
|
];
|
||||||
|
const docs = new Map();
|
||||||
|
docPaths.forEach(path => {
|
||||||
|
docs.set(path, Assets.getText(`docs/${path}.md`))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Doc paths for properties
|
||||||
|
propsByDocsPath.forEach(prop => {
|
||||||
|
docs.set(prop.docsPath, Assets.getText(`docs/${prop.docsPath}.md`));
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish('docs', function (path) {
|
||||||
|
if (!path) {
|
||||||
|
docs.forEach((text, path) => {
|
||||||
|
this.added('docs', path, { text });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const text = docs.get(path);
|
||||||
|
if (text) {
|
||||||
|
this.added('docs', path, { text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ready();
|
||||||
|
});
|
||||||
@@ -11,3 +11,4 @@ import '/imports/server/publications/ownedDocuments.js';
|
|||||||
import '/imports/server/publications/searchLibraryNodes.js';
|
import '/imports/server/publications/searchLibraryNodes.js';
|
||||||
import '/imports/server/publications/archiveFiles.js';
|
import '/imports/server/publications/archiveFiles.js';
|
||||||
import '/imports/server/publications/userImages.js';
|
import '/imports/server/publications/userImages.js';
|
||||||
|
import '/imports/server/publications/docs.js';
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import Invites from '/imports/api/users/Invites.js';
|
|||||||
|
|
||||||
Meteor.publish('user', function () {
|
Meteor.publish('user', function () {
|
||||||
return [
|
return [
|
||||||
Meteor.users.find(this.userId, {fields: {
|
Meteor.users.find(this.userId, {
|
||||||
|
fields: {
|
||||||
roles: 1,
|
roles: 1,
|
||||||
username: 1,
|
username: 1,
|
||||||
apiKey: 1,
|
apiKey: 1,
|
||||||
@@ -22,7 +23,8 @@ Meteor.publish('user', function(){
|
|||||||
'services.google.name': 1,
|
'services.google.name': 1,
|
||||||
'services.google.email': 1,
|
'services.google.email': 1,
|
||||||
'services.google.locale': 1,
|
'services.google.locale': 1,
|
||||||
}}),
|
}
|
||||||
|
}),
|
||||||
Invites.find({
|
Invites.find({
|
||||||
$or: [
|
$or: [
|
||||||
{ inviter: this.userId },
|
{ inviter: this.userId },
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
:outlined="!!label"
|
:outlined="!!label"
|
||||||
:icon="!label"
|
:icon="!label"
|
||||||
:min-width="label && 108"
|
:min-width="label && 108"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
v-on="on"
|
v-on="on"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -124,6 +125,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
//hex string
|
//hex string
|
||||||
value: {
|
value: {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default {
|
|||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-layout.wide-columns {
|
.column-layout.wide-columns {
|
||||||
column-count: 12;
|
column-count: 12;
|
||||||
column-fill: balance;
|
column-fill: balance;
|
||||||
@@ -35,14 +36,18 @@ export default {
|
|||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
.column-layout > div, .column-layout > span > div {
|
|
||||||
|
.column-layout>div,
|
||||||
|
.column-layout>span>div {
|
||||||
/*
|
/*
|
||||||
Table and width set because firefox does not support break-inside: avoid
|
Table and width set because firefox does not support break-inside: avoid
|
||||||
*/
|
*/
|
||||||
display: table;
|
display: table;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
backface-visibility: hidden;
|
||||||
-webkit-backface-visibility: hidden;
|
-webkit-backface-visibility: hidden;
|
||||||
|
transform: translateX(0);
|
||||||
-webkit-transform: translateX(0);
|
-webkit-transform: translateX(0);
|
||||||
-webkit-column-break-inside: avoid;
|
-webkit-column-break-inside: avoid;
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
.filled.theme--light {
|
.filled.theme--light {
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filled.theme--dark {
|
.filled.theme--dark {
|
||||||
background: #424242 !important;
|
background: #424242 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<div
|
<div
|
||||||
class="markdown"
|
class="markdown"
|
||||||
|
@click="e => $emit('click', e)"
|
||||||
v-html="compiledMarkdown"
|
v-html="compiledMarkdown"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
46
app/imports/ui/components/ResetSelector.vue
Normal file
46
app/imports/ui/components/ResetSelector.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<smart-select
|
||||||
|
label="Reset"
|
||||||
|
clearable
|
||||||
|
style="flex-basis: 300px;"
|
||||||
|
:hint="hint"
|
||||||
|
:items="resetOptions"
|
||||||
|
:value="value"
|
||||||
|
:error-messages="errorMessages"
|
||||||
|
:menu-props="{auto: true, lazy: true}"
|
||||||
|
@change="(value, ack) => $emit('change', value, ack)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import createListOfProperties from '/imports/ui/properties/forms/shared/lists/createListOfProperties.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: [String, Number, Date, Array, Object, Boolean],
|
||||||
|
errorMessages: [String, Array],
|
||||||
|
hint: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
resetOptions() {
|
||||||
|
const eventActions = createListOfProperties({
|
||||||
|
type: 'action',
|
||||||
|
actionType: 'event',
|
||||||
|
}, true);
|
||||||
|
const defaultEvents = [
|
||||||
|
{
|
||||||
|
text: 'Short rest',
|
||||||
|
value: 'shortRest',
|
||||||
|
}, {
|
||||||
|
text: 'Long rest',
|
||||||
|
value: 'longRest',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return [...defaultEvents, ...eventActions];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -44,9 +44,11 @@
|
|||||||
},
|
},
|
||||||
transparentToolbar: Boolean,
|
transparentToolbar: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
hovering: false,
|
hovering: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isDark() {
|
isDark() {
|
||||||
return isDarkColor(this.color);
|
return isDarkColor(this.color);
|
||||||
@@ -72,9 +74,11 @@
|
|||||||
.toolbar-card .v-toolbar__title {
|
.toolbar-card .v-toolbar__title {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-card {
|
.toolbar-card {
|
||||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import { format } from 'date-fns';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [SmartInput],
|
mixins: [SmartInput],
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedSafeValue() {
|
formattedSafeValue() {
|
||||||
return format(this.safeValue, 'YYYY-MM-DD')
|
return format(this.safeValue, 'YYYY-MM-DD')
|
||||||
@@ -53,4 +55,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -97,11 +97,13 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
searchString: '',
|
searchString: '',
|
||||||
icons: [],
|
icons: [],
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
menu(value) {
|
menu(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -131,4 +133,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,9 +28,11 @@
|
|||||||
props: {
|
props: {
|
||||||
multiple: Boolean,
|
multiple: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
searchInput: '',
|
searchInput: '',
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// Multiple combobox gets a long default debounce time while single
|
// Multiple combobox gets a long default debounce time while single
|
||||||
// value gets a shorter one
|
// value gets a shorter one
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export default {
|
|||||||
context: { default: {} }
|
context: { default: {} }
|
||||||
},
|
},
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
ackErrors: null,
|
ackErrors: null,
|
||||||
rulesErrors: null,
|
rulesErrors: null,
|
||||||
@@ -22,7 +23,8 @@ export default {
|
|||||||
dirty: false,
|
dirty: false,
|
||||||
safeValue: this.value,
|
safeValue: this.value,
|
||||||
inputValue: this.value,
|
inputValue: this.value,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
value: [String, Number, Date, Array, Object, Boolean],
|
value: [String, Number, Date, Array, Object, Boolean],
|
||||||
errorMessages: [String, Array],
|
errorMessages: [String, Array],
|
||||||
|
|||||||
@@ -54,8 +54,22 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
v-if="docsPath"
|
||||||
|
@click="helpDialog"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
Help
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-icon>mdi-help</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.duplicate"
|
v-if="$listeners && $listeners.duplicate"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('duplicate')"
|
@click="$emit('duplicate')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -67,8 +81,23 @@
|
|||||||
<v-icon>mdi-content-copy</v-icon>
|
<v-icon>mdi-content-copy</v-icon>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="$listeners && $listeners.copy"
|
||||||
|
:disabled="context.copyPermission === false"
|
||||||
|
@click="$emit('copy')"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
Copy To
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-icon>mdi-content-duplicate</v-icon>
|
||||||
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.move"
|
v-if="$listeners && $listeners.move"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('move')"
|
@click="$emit('move')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -82,6 +111,7 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="$listeners && $listeners.remove"
|
v-if="$listeners && $listeners.remove"
|
||||||
|
:disabled="context.editPermission === false"
|
||||||
@click="$emit('remove')"
|
@click="$emit('remove')"
|
||||||
>
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
@@ -137,12 +167,16 @@ import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
|||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
||||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||||
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PropertyIcon,
|
PropertyIcon,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
},
|
},
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -171,7 +205,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return model.name || getPropertyName(model.type);
|
return model.name || getPropertyName(model.type);
|
||||||
}
|
},
|
||||||
|
docsPath() {
|
||||||
|
const propDef = PROPERTIES[this.model.type];
|
||||||
|
return propDef && propDef.docsPath;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
colorChanged(value){
|
colorChanged(value){
|
||||||
@@ -180,6 +218,15 @@ export default {
|
|||||||
back(){
|
back(){
|
||||||
this.$store.dispatch('popDialogStack');
|
this.$store.dispatch('popDialogStack');
|
||||||
},
|
},
|
||||||
|
helpDialog() {
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'help-dialog',
|
||||||
|
elementId: 'property-toolbar-menu-button',
|
||||||
|
data: {
|
||||||
|
path: this.docsPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
default: undefined,
|
||||||
},
|
},
|
||||||
organize: Boolean,
|
organize: Boolean,
|
||||||
children: {
|
children: {
|
||||||
@@ -118,11 +118,13 @@
|
|||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
startExpanded: Boolean,
|
startExpanded: Boolean,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
|
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
|
||||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id) ||
|
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id) ||
|
||||||
false,
|
false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasChildren() {
|
hasChildren() {
|
||||||
return this.children && !!this.children.length || this.lazy && !this.expanded;
|
return this.children && !!this.children.length || this.lazy && !this.expanded;
|
||||||
@@ -168,43 +170,56 @@
|
|||||||
.rotate-90 {
|
.rotate-90 {
|
||||||
transform: rotate(90deg) translateZ(0);
|
transform: rotate(90deg) translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-area {
|
.drag-area {
|
||||||
box-shadow: -2px 0px 0px 0px #808080;
|
box-shadow: -2px 0px 0px 0px #808080;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty .drag-area {
|
.empty .drag-area {
|
||||||
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty .v-btn {
|
.empty .v-btn {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.found {
|
.found {
|
||||||
background: rgba(200, 0, 0, 0.1) !important;
|
background: rgba(200, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost {
|
.ghost {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background: rgba(251, 0, 0, 0.3);
|
background: rgba(251, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-icon.v-icon--disabled {
|
.v-icon.v-icon--disabled {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-icon {
|
.v-icon {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--light .tree-node-title:hover {
|
.theme--light .tree-node-title:hover {
|
||||||
background-color: rgba(0, 0, 0, .04);
|
background-color: rgba(0, 0, 0, .04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark .tree-node-title:hover {
|
.theme--dark .tree-node-title:hover {
|
||||||
background-color: rgba(255, 255, 255, .04);
|
background-color: rgba(255, 255, 255, .04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-node-title {
|
.tree-node-title {
|
||||||
transition: background ease 0.3s, color ease 0.15s;
|
transition: background ease 0.3s, color ease 0.15s;
|
||||||
}
|
}
|
||||||
.tree-node-title, .dummy-node {
|
|
||||||
|
.tree-node-title,
|
||||||
|
.dummy-node {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,8 +43,14 @@
|
|||||||
TreeNode,
|
TreeNode,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
node: Object,
|
node: {
|
||||||
group: String,
|
type: Object,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
organize: Boolean,
|
organize: Boolean,
|
||||||
lazy: Boolean,
|
lazy: Boolean,
|
||||||
children: {
|
children: {
|
||||||
@@ -61,10 +67,12 @@
|
|||||||
},
|
},
|
||||||
startExpanded: Boolean,
|
startExpanded: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
expanded: this.startExpanded || false,
|
expanded: this.startExpanded || false,
|
||||||
displayedChildren: [],
|
displayedChildren: [],
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasChildren() {
|
hasChildren() {
|
||||||
return this.children && this.children.length;
|
return this.children && this.children.length;
|
||||||
@@ -125,9 +133,11 @@
|
|||||||
.flip-list-leave-active {
|
.flip-list-leave-active {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flip-list-move {
|
.flip-list-move {
|
||||||
transition: transform 0.5s;
|
transition: transform 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-move {
|
.no-move {
|
||||||
transition: transform 0s;
|
transition: transform 0s;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,11 @@
|
|||||||
:input-value="model.settings.hideUnusedStats"
|
:input-value="model.settings.hideUnusedStats"
|
||||||
@change="value => $emit('change', {path: ['settings','hideUnusedStats'], value: !!value})"
|
@change="value => $emit('change', {path: ['settings','hideUnusedStats'], value: !!value})"
|
||||||
/>
|
/>
|
||||||
|
<v-switch
|
||||||
|
label="Hide rest buttons"
|
||||||
|
:input-value="model.settings.hideRestButtons"
|
||||||
|
@change="value => $emit('change', {path: ['settings','hideRestButtons'], value: !!value})"
|
||||||
|
/>
|
||||||
<v-switch
|
<v-switch
|
||||||
label="Show spells tab"
|
label="Show spells tab"
|
||||||
:input-value="!model.settings.hideSpellsTab"
|
:input-value="!model.settings.hideSpellsTab"
|
||||||
@@ -149,7 +154,8 @@ export default {
|
|||||||
},
|
},
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
},
|
},
|
||||||
data() { return {
|
data() {
|
||||||
|
return {
|
||||||
libraryCollections: this.model.allowedLibraryCollections,
|
libraryCollections: this.model.allowedLibraryCollections,
|
||||||
libraries: this.model.allowedLibraries,
|
libraries: this.model.allowedLibraries,
|
||||||
libraryWriteLoading: false,
|
libraryWriteLoading: false,
|
||||||
@@ -266,4 +272,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<dialog-base v-if="model" :color="model.color">
|
<dialog-base
|
||||||
|
v-if="model"
|
||||||
|
:color="model.color"
|
||||||
|
>
|
||||||
<template slot="toolbar">
|
<template slot="toolbar">
|
||||||
<v-toolbar-title>
|
<v-toolbar-title>
|
||||||
Character Details
|
Character Details
|
||||||
@@ -81,4 +84,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,8 +28,10 @@
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
inputName: undefined,
|
inputName: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
nameMatch() {
|
nameMatch() {
|
||||||
if (!this.name) return true;
|
if (!this.name) return true;
|
||||||
@@ -76,4 +78,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -12,9 +12,7 @@
|
|||||||
size="64"
|
size="64"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else-if="!creature">
|
||||||
v-else-if="!creature"
|
|
||||||
>
|
|
||||||
<v-layout
|
<v-layout
|
||||||
column
|
column
|
||||||
align-center
|
align-center
|
||||||
@@ -55,9 +53,7 @@
|
|||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<inventory-tab :creature-id="creatureId" />
|
<inventory-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item
|
<v-tab-item v-if="!creature.settings.hideSpellsTab">
|
||||||
v-if="!creature.settings.hideSpellsTab"
|
|
||||||
>
|
|
||||||
<spells-tab :creature-id="creatureId" />
|
<spells-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
@@ -66,9 +62,7 @@
|
|||||||
<v-tab-item>
|
<v-tab-item>
|
||||||
<build-tab :creature-id="creatureId" />
|
<build-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
<v-tab-item
|
<v-tab-item v-if="creature.settings.showTreeTab">
|
||||||
v-if="creature.settings.showTreeTab"
|
|
||||||
>
|
|
||||||
<tree-tab :creature-id="creatureId" />
|
<tree-tab :creature-id="creatureId" />
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
</v-tabs-items>
|
</v-tabs-items>
|
||||||
|
|||||||
@@ -11,17 +11,13 @@
|
|||||||
dense
|
dense
|
||||||
>
|
>
|
||||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<v-toolbar-title :key="$store.state.pageTitle">
|
<v-toolbar-title :key="$store.state.pageTitle">
|
||||||
{{ $store.state.pageTitle }}
|
{{ $store.state.pageTitle }}
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<v-layout
|
<v-layout
|
||||||
:key="$route.meta.title"
|
:key="$route.meta.title"
|
||||||
class="flex-shrink-0 flex-grow-0"
|
class="flex-shrink-0 flex-grow-0"
|
||||||
@@ -249,9 +245,11 @@ export default {
|
|||||||
.character-sheet-toolbar .v-tabs__container--grow .v-tabs__div {
|
.character-sheet-toolbar .v-tabs__container--grow .v-tabs__div {
|
||||||
max-width: 120px !important;
|
max-width: 120px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sheet-toolbar .v-tabs__bar {
|
.character-sheet-toolbar .v-tabs__bar {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-sheet-fab {
|
.character-sheet-fab {
|
||||||
bottom: -24px;
|
bottom: -24px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
|||||||
@@ -56,4 +56,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,9 +30,7 @@
|
|||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
<v-list-item-action>
|
<v-list-item-action>
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
<coin-value
|
<coin-value :value="variables && variables.valueTotal && variables.valueTotal.value|| 0" />
|
||||||
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
|
|
||||||
/>
|
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
@@ -85,9 +83,7 @@
|
|||||||
v-for="container in containersWithoutAncestorContainers"
|
v-for="container in containersWithoutAncestorContainers"
|
||||||
:key="container._id"
|
:key="container._id"
|
||||||
>
|
>
|
||||||
<container-card
|
<container-card :model="container" />
|
||||||
:model="container"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</column-layout>
|
</column-layout>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,9 +116,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
organize: false,
|
organize: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
containers() {
|
containers() {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
@@ -135,10 +133,12 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
creature() {
|
creature() {
|
||||||
return Creatures.findOne(this.creatureId, {fields: {
|
return Creatures.findOne(this.creatureId, {
|
||||||
|
fields: {
|
||||||
color: 1,
|
color: 1,
|
||||||
variables: 1,
|
variables: 1,
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
variables() {
|
variables() {
|
||||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||||
@@ -229,4 +229,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
organize: false,
|
organize: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
spellLists() {
|
spellLists() {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
@@ -103,4 +105,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,23 +1,53 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
|
<div class="stats-tab ma-2">
|
||||||
<div
|
<div
|
||||||
class="stats-tab ma-2"
|
v-if="healthBars.length"
|
||||||
|
class="px-2 pt-2"
|
||||||
>
|
>
|
||||||
<health-bar-card-container :creature-id="creatureId" />
|
<v-card class="pa-2">
|
||||||
|
<health-bar
|
||||||
|
v-for="healthBar in healthBars"
|
||||||
|
:key="healthBar._id"
|
||||||
|
:model="healthBar"
|
||||||
|
@change="({ type, value }) => incrementChange(healthBar._id, { type, value: -value })"
|
||||||
|
@click="clickProperty({_id: healthBar._id})"
|
||||||
|
/>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<column-layout>
|
<column-layout>
|
||||||
<div class="character-buttons">
|
<folder-group-card
|
||||||
|
v-for="folder in folders"
|
||||||
|
:key="folder._id"
|
||||||
|
:model="folder"
|
||||||
|
@click-property="clickProperty"
|
||||||
|
@sub-click="_id => clickTreeProperty({_id})"
|
||||||
|
@remove="softRemove"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="!creature.settings.hideRestButtons || (events && events.length)"
|
||||||
|
class="character-buttons"
|
||||||
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-text class="layout column align-center">
|
<v-card-text class="layout column align-center">
|
||||||
<rest-button
|
<rest-button
|
||||||
|
v-if="!creature.settings.hideRestButtons"
|
||||||
:creature-id="creatureId"
|
:creature-id="creatureId"
|
||||||
type="shortRest"
|
type="shortRest"
|
||||||
class="ma-1"
|
class="ma-1"
|
||||||
/>
|
/>
|
||||||
<rest-button
|
<rest-button
|
||||||
|
v-if="!creature.settings.hideRestButtons"
|
||||||
:creature-id="creatureId"
|
:creature-id="creatureId"
|
||||||
type="longRest"
|
type="longRest"
|
||||||
class="ma-1"
|
class="ma-1"
|
||||||
/>
|
/>
|
||||||
|
<event-button
|
||||||
|
v-for="event in events"
|
||||||
|
:key="event._id"
|
||||||
|
:model="event"
|
||||||
|
class="ma-1"
|
||||||
|
/>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,26 +65,14 @@
|
|||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-subheader>Buffs and conditions</v-subheader>
|
<v-subheader>Buffs and conditions</v-subheader>
|
||||||
<v-list-item
|
<buff-list-item
|
||||||
v-for="buff in appliedBuffs"
|
v-for="buff in appliedBuffs"
|
||||||
:key="buff._id"
|
:key="buff._id"
|
||||||
:data-id="buff._id"
|
:data-id="buff._id"
|
||||||
|
:model="buff"
|
||||||
@click="clickProperty({_id: buff._id})"
|
@click="clickProperty({_id: buff._id})"
|
||||||
>
|
@remove="softRemove(buff._id)"
|
||||||
<v-list-item-content>
|
/>
|
||||||
<v-list-item-title>
|
|
||||||
{{ buff.name }}
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item-content>
|
|
||||||
<v-list-item-action v-if="!buff.hideRemoveButton">
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
@click.stop="softRemove(buff._id)"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-list-item-action>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -171,9 +189,7 @@
|
|||||||
v-if="spellSlots && spellSlots.length || hasSpells"
|
v-if="spellSlots && spellSlots.length || hasSpells"
|
||||||
class="spell-slots"
|
class="spell-slots"
|
||||||
>
|
>
|
||||||
<v-card
|
<v-card data-id="spell-slot-card">
|
||||||
data-id="spell-slot-card"
|
|
||||||
>
|
|
||||||
<v-list
|
<v-list
|
||||||
v-if="spellSlots && spellSlots.length"
|
v-if="spellSlots && spellSlots.length"
|
||||||
two-line
|
two-line
|
||||||
@@ -186,7 +202,6 @@
|
|||||||
:model="spellSlot"
|
:model="spellSlot"
|
||||||
:data-id="spellSlot._id"
|
:data-id="spellSlot._id"
|
||||||
@click="clickProperty({_id: spellSlot._id})"
|
@click="clickProperty({_id: spellSlot._id})"
|
||||||
@cast="castSpellWithSlot(spellSlot._id)"
|
|
||||||
/>
|
/>
|
||||||
</v-list>
|
</v-list>
|
||||||
<div
|
<div
|
||||||
@@ -253,18 +268,6 @@
|
|||||||
@sub-click="_id => clickTreeProperty({_id})"
|
@sub-click="_id => clickTreeProperty({_id})"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-for="attack in attacks"
|
|
||||||
:key="attack._id"
|
|
||||||
class="attack"
|
|
||||||
>
|
|
||||||
<action-card
|
|
||||||
attack
|
|
||||||
:model="attack"
|
|
||||||
:data-id="attack._id"
|
|
||||||
@click="clickProperty({_id: attack._id})"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="weapons && weapons.length"
|
v-if="weapons && weapons.length"
|
||||||
@@ -354,11 +357,11 @@
|
|||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
|
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
|
||||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
|
import HealthBar from '/imports/ui/properties/components/attributes/HealthBar.vue';
|
||||||
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
||||||
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
|
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
|
||||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
import DamageMultiplierCard from '/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue';
|
import DamageMultiplierCard from '/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue';
|
||||||
import HealthBarCardContainer from '/imports/ui/properties/components/attributes/HealthBarCardContainer.vue';
|
|
||||||
import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue';
|
import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue';
|
||||||
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
||||||
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
||||||
@@ -367,10 +370,14 @@
|
|||||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
|
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
|
||||||
|
import BuffListItem from '/imports/ui/properties/components/buffs/BuffListItem.vue';
|
||||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||||
|
import EventButton from '/imports/ui/properties/components/actions/EventButton.vue';
|
||||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||||
|
import FolderGroupCard from '/imports/ui/properties/components/folders/FolderGroupCard.vue';
|
||||||
|
import { uniqBy } from 'lodash';
|
||||||
|
|
||||||
const getProperties = function(creature, filter, options = {
|
const getProperties = function (creature, folderIds, filter, options = {
|
||||||
sort: { order: 1 }
|
sort: { order: 1 }
|
||||||
}) {
|
}) {
|
||||||
if (!creature) return;
|
if (!creature) return;
|
||||||
@@ -378,22 +385,27 @@
|
|||||||
filter.hide = { $ne: true };
|
filter.hide = { $ne: true };
|
||||||
}
|
}
|
||||||
filter['ancestors.id'] = creature._id;
|
filter['ancestors.id'] = creature._id;
|
||||||
|
filter['parent.id'] = {$nin: folderIds},
|
||||||
filter.removed = { $ne: true };
|
filter.removed = { $ne: true };
|
||||||
filter.inactive = { $ne: true };
|
filter.inactive = { $ne: true };
|
||||||
filter.overridden = { $ne: true };
|
filter.overridden = { $ne: true };
|
||||||
|
filter.$nor = [
|
||||||
|
{ hideWhenTotalZero: true, total: 0 },
|
||||||
|
{ hideWhenValueZero: true, value: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
return CreatureProperties.find(filter, options);
|
return CreatureProperties.find(filter, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAttributeOfType = function(creature, type){
|
const getAttributeOfType = function (creature, folderIds, type) {
|
||||||
return getProperties(creature, {
|
return getProperties(creature, folderIds, {
|
||||||
type: 'attribute',
|
type: 'attribute',
|
||||||
attributeType: type,
|
attributeType: type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSkillOfType = function(creature, type){
|
const getSkillOfType = function (creature, folderIds, type) {
|
||||||
return getProperties(creature, {
|
return getProperties(creature, folderIds, {
|
||||||
type: 'skill',
|
type: 'skill',
|
||||||
skillType: type,
|
skillType: type,
|
||||||
});
|
});
|
||||||
@@ -401,18 +413,21 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
HealthBar,
|
||||||
RestButton,
|
RestButton,
|
||||||
|
BuffListItem,
|
||||||
AbilityListTile,
|
AbilityListTile,
|
||||||
AttributeCard,
|
AttributeCard,
|
||||||
ColumnLayout,
|
ColumnLayout,
|
||||||
DamageMultiplierCard,
|
DamageMultiplierCard,
|
||||||
HealthBarCardContainer,
|
|
||||||
HitDiceListTile,
|
HitDiceListTile,
|
||||||
SkillListTile,
|
SkillListTile,
|
||||||
ResourceCard,
|
ResourceCard,
|
||||||
SpellSlotListTile,
|
SpellSlotListTile,
|
||||||
ActionCard,
|
ActionCard,
|
||||||
ToggleCard,
|
ToggleCard,
|
||||||
|
EventButton,
|
||||||
|
FolderGroupCard,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
creatureId: {
|
creatureId: {
|
||||||
@@ -420,23 +435,36 @@
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
doCheckLoading: false,
|
doCheckLoading: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
creature() {
|
creature() {
|
||||||
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
folders() {
|
||||||
|
return getProperties(this.creature, [], { type: 'folder', groupStats: true });
|
||||||
|
},
|
||||||
|
folderIds() {
|
||||||
|
return this.folders.map(f => f._id);
|
||||||
|
},
|
||||||
|
healthBars() {
|
||||||
|
return getAttributeOfType(this.creature, this.folderIds, 'healthBar');
|
||||||
|
},
|
||||||
abilities() {
|
abilities() {
|
||||||
return getAttributeOfType(this.creature, 'ability');
|
return getAttributeOfType(this.creature, this.folderIds, 'ability');
|
||||||
},
|
},
|
||||||
stats() {
|
stats() {
|
||||||
return getAttributeOfType(this.creature, 'stat');
|
return getAttributeOfType(this.creature, this.folderIds, 'stat');
|
||||||
},
|
},
|
||||||
toggles() {
|
toggles() {
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
'ancestors.id': this.creatureId,
|
|
||||||
type: 'toggle',
|
type: 'toggle',
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
'parent.id': { $nin: this.folderIds },
|
||||||
removed: { $ne: true },
|
removed: { $ne: true },
|
||||||
deactivatedByAncestor: { $ne: true },
|
deactivatedByAncestor: { $ne: true },
|
||||||
showUI: true,
|
showUI: true,
|
||||||
@@ -445,68 +473,59 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
modifiers() {
|
modifiers() {
|
||||||
return getAttributeOfType(this.creature, 'modifier');
|
return getAttributeOfType(this.creature, this.folderIds, 'modifier');
|
||||||
},
|
},
|
||||||
resources() {
|
resources() {
|
||||||
return getAttributeOfType(this.creature, 'resource');
|
return getAttributeOfType(this.creature, this.folderIds, 'resource');
|
||||||
},
|
},
|
||||||
spellSlots() {
|
spellSlots() {
|
||||||
return getAttributeOfType(this.creature, 'spellSlot');
|
return getAttributeOfType(this.creature, this.folderIds, 'spellSlot');
|
||||||
},
|
},
|
||||||
hasSpells() {
|
hasSpells() {
|
||||||
const cursor = getProperties(this.creature, {
|
const cursor = getProperties(this.creature, this.folderIds, {
|
||||||
type: 'spell',
|
type: 'spell',
|
||||||
})
|
})
|
||||||
return cursor && cursor.count();
|
return cursor && cursor.count();
|
||||||
},
|
},
|
||||||
hitDice() {
|
hitDice() {
|
||||||
return getAttributeOfType(this.creature, 'hitDice');
|
return getAttributeOfType(this.creature, this.folderIds, 'hitDice');
|
||||||
},
|
},
|
||||||
checks() {
|
checks() {
|
||||||
return getSkillOfType(this.creature, 'check');
|
return getSkillOfType(this.creature, this.folderIds, 'check');
|
||||||
},
|
},
|
||||||
savingThrows() {
|
savingThrows() {
|
||||||
return getSkillOfType(this.creature, 'save');
|
return getSkillOfType(this.creature, this.folderIds, 'save');
|
||||||
},
|
},
|
||||||
skills() {
|
skills() {
|
||||||
return getSkillOfType(this.creature, 'skill');
|
return getSkillOfType(this.creature, this.folderIds, 'skill');
|
||||||
},
|
},
|
||||||
tools() {
|
tools() {
|
||||||
return getSkillOfType(this.creature, 'tool');
|
return getSkillOfType(this.creature, this.folderIds, 'tool');
|
||||||
},
|
},
|
||||||
weapons() {
|
weapons() {
|
||||||
return getSkillOfType(this.creature, 'weapon');
|
return getSkillOfType(this.creature, this.folderIds, 'weapon');
|
||||||
},
|
},
|
||||||
armors() {
|
armors() {
|
||||||
return getSkillOfType(this.creature, 'armor');
|
return getSkillOfType(this.creature, this.folderIds, 'armor');
|
||||||
},
|
},
|
||||||
languages() {
|
languages() {
|
||||||
return getSkillOfType(this.creature, 'language');
|
return getSkillOfType(this.creature, this.folderIds, 'language');
|
||||||
|
},
|
||||||
|
events() {
|
||||||
|
const events = getProperties(this.creature, this.folderIds, { type: 'action', actionType: 'event' });
|
||||||
|
return uniqBy(events.fetch(), e => e.variableName);
|
||||||
},
|
},
|
||||||
actions() {
|
actions() {
|
||||||
return getProperties(this.creature, {type: 'action'});
|
return getProperties(this.creature, this.folderIds, { type: 'action', actionType: { $ne: 'event' } });
|
||||||
},
|
},
|
||||||
appliedBuffs() {
|
appliedBuffs() {
|
||||||
return getProperties(this.creature, {type: 'buff'});
|
return getProperties(this.creature, this.folderIds, { type: 'buff' });
|
||||||
},
|
},
|
||||||
multipliers() {
|
multipliers() {
|
||||||
return getProperties(this.creature, {
|
return getProperties(this.creature, this.folderIds, {
|
||||||
type: 'damageMultiplier'
|
type: 'damageMultiplier'
|
||||||
}, {
|
}, {
|
||||||
sort: { value: 1, order: 1 }
|
sort: { value: 1, order: 1 }
|
||||||
});
|
|
||||||
},
|
|
||||||
attacks(){
|
|
||||||
let props = getProperties(this.creature, {type: 'attack'})
|
|
||||||
return props && props.map(attack => {
|
|
||||||
attack.children = CreatureProperties.find({
|
|
||||||
'ancestors.id': attack._id,
|
|
||||||
removed: {$ne: true},
|
|
||||||
inactive: {$ne: true},
|
|
||||||
}, {
|
|
||||||
sort: {order: 1}
|
|
||||||
});
|
|
||||||
return attack;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -526,13 +545,23 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
incrementChange(_id, { type, value }) {
|
incrementChange(_id, { type, value }) {
|
||||||
if (type === 'increment'){
|
damageProperty.call({
|
||||||
damageProperty.call({_id, operation: 'increment' ,value: -value});
|
_id,
|
||||||
|
operation: type,
|
||||||
|
value: -value
|
||||||
|
}, error => {
|
||||||
|
if (error) {
|
||||||
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
softRemove(_id) {
|
softRemove(_id) {
|
||||||
softRemoveProperty.call({ _id }, error => {
|
softRemoveProperty.call({ _id }, error => {
|
||||||
if (error) console.error(error);
|
if (error) {
|
||||||
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
castSpell() {
|
castSpell() {
|
||||||
@@ -542,9 +571,16 @@
|
|||||||
data: {
|
data: {
|
||||||
creatureId: this.creatureId,
|
creatureId: this.creatureId,
|
||||||
},
|
},
|
||||||
callback({spellId, slotId} = {}){
|
callback({ spellId, slotId, advantage, ritual } = {}) {
|
||||||
if (!spellId) return;
|
if (!spellId) return;
|
||||||
doCastSpell.call({spellId, slotId}, error => {
|
doCastSpell.call({
|
||||||
|
spellId,
|
||||||
|
slotId,
|
||||||
|
ritual,
|
||||||
|
scope: {
|
||||||
|
$attackAdvantage: advantage,
|
||||||
|
},
|
||||||
|
}, error => {
|
||||||
if (!error) return;
|
if (!error) return;
|
||||||
snackbar({ text: error.reason || error.message || error.toString() });
|
snackbar({ text: error.reason || error.message || error.toString() });
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -557,4 +593,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,339 @@
|
|||||||
|
<template>
|
||||||
|
<div class="character-sheet-printed fill-height">
|
||||||
|
<v-fade-transition mode="out-in">
|
||||||
|
<div
|
||||||
|
v-if="!$subReady.singleCharacter"
|
||||||
|
key="character-loading"
|
||||||
|
class="fill-height layout justify-center align-center"
|
||||||
|
>
|
||||||
|
<v-progress-circular
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
size="64"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!creature">
|
||||||
|
<v-layout
|
||||||
|
column
|
||||||
|
align-center
|
||||||
|
justify-center
|
||||||
|
>
|
||||||
|
<h2 style="margin: 48px 28px 16px">
|
||||||
|
Character not found
|
||||||
|
</h2>
|
||||||
|
<h3>
|
||||||
|
Either this character does not exist, or you don't have permission
|
||||||
|
to view it.
|
||||||
|
</h3>
|
||||||
|
</v-layout>
|
||||||
|
</div>
|
||||||
|
<v-theme-provider
|
||||||
|
v-else
|
||||||
|
light
|
||||||
|
>
|
||||||
|
<div class="page pa-3">
|
||||||
|
<div class="px-3 d-flex align-center">
|
||||||
|
<div class="logo-background" />
|
||||||
|
<div class="creature-name mr-3">
|
||||||
|
{{ creature.name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-right flex mr-4">
|
||||||
|
<div v-if="creature.alignment || background">
|
||||||
|
{{ creature.alignment }} {{ background }}
|
||||||
|
</div>
|
||||||
|
<dir v-if="race || creature.gender">
|
||||||
|
{{ race }} {{ creature.gender }}
|
||||||
|
</dir>
|
||||||
|
<div v-if="level && classes && classes.length === 1">
|
||||||
|
Level {{ level }} {{ classes[0].name }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="level">
|
||||||
|
Level {{ level }} ({{ classes.map(c => `${c.name} ${c.level}`).join(', ') }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<qrcode-vue
|
||||||
|
style="height: 100px"
|
||||||
|
render-as="svg"
|
||||||
|
:value="creatureUrl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-right mt-3 mr-4"
|
||||||
|
style="font-size: 8pt; margin-bottom: -4px;"
|
||||||
|
>
|
||||||
|
{{ creatureUrl }}
|
||||||
|
</div>
|
||||||
|
<printed-stats :creature-id="creatureId" />
|
||||||
|
<printed-inventory :creature-id="creatureId" />
|
||||||
|
<printed-spells
|
||||||
|
v-if="!creature.settings.hideSpellsTab"
|
||||||
|
:creature-id="creatureId"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-theme-provider>
|
||||||
|
</v-fade-transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import PrintedStats from '/imports/ui/creature/character/printedCharacterSheet/PrintedStats.vue';
|
||||||
|
import PrintedInventory from '/imports/ui/creature/character/printedCharacterSheet/PrintedInventory.vue';
|
||||||
|
import PrintedSpells from '/imports/ui/creature/character/printedCharacterSheet/PrintedSpells.vue';
|
||||||
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||||
|
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
|
||||||
|
import QrcodeVue from 'qrcode.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PrintedStats,
|
||||||
|
PrintedInventory,
|
||||||
|
PrintedSpells,
|
||||||
|
QrcodeVue,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
creatureId() {
|
||||||
|
return this.$route.params.id
|
||||||
|
},
|
||||||
|
creatureUrl() {
|
||||||
|
let props = this.$router.resolve({
|
||||||
|
name: 'characterSheet',
|
||||||
|
params: { id: this.creatureId},
|
||||||
|
});
|
||||||
|
return new URL(props?.href, document.baseURI).href
|
||||||
|
},
|
||||||
|
level() {
|
||||||
|
return this.variables?.level?.value;
|
||||||
|
},
|
||||||
|
highestLevels(){
|
||||||
|
let highestLevels = {};
|
||||||
|
let highestLevelsList = [];
|
||||||
|
this.classLevels.forEach(classLevel => {
|
||||||
|
let name = classLevel.variableName;
|
||||||
|
if (
|
||||||
|
!highestLevels[name] ||
|
||||||
|
highestLevels[name].level < classLevel.level
|
||||||
|
){
|
||||||
|
highestLevels[name] = classLevel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (let name in highestLevels){
|
||||||
|
highestLevelsList.push(highestLevels[name]);
|
||||||
|
}
|
||||||
|
highestLevelsList.sort((a, b) => a.level - b.level);
|
||||||
|
return highestLevelsList;
|
||||||
|
},
|
||||||
|
classes() {
|
||||||
|
return [
|
||||||
|
...this.highestLevels,
|
||||||
|
...this.classProperties
|
||||||
|
].sort((a, b) => a.order - b.order);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reactiveProvide: {
|
||||||
|
name: 'context',
|
||||||
|
include: ['creatureId', 'editPermission'],
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'creature.name'(value) {
|
||||||
|
this.$store.commit('setPageTitle', value ? ('Print ' + value) : 'Print Character Sheet');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.commit('setPageTitle',
|
||||||
|
(this.creature && this.creature.name) ?
|
||||||
|
('Print ' + this.creature.name) :
|
||||||
|
'Print Character Sheet'
|
||||||
|
);
|
||||||
|
this.nameObserver = Creatures.find({
|
||||||
|
creatureId: this.creatureId,
|
||||||
|
}, {
|
||||||
|
fields: { name: 1 },
|
||||||
|
}).observe({
|
||||||
|
added: ({ name }) =>
|
||||||
|
this.$store.commit('setPageTitle', name ? ('Print ' + name) : 'Print Character Sheet'),
|
||||||
|
changed: ({ name }) =>
|
||||||
|
this.$store.commit('setPageTitle', name ? ('Print ' + name) : 'Print Character Sheet'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.nameObserver.stop();
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'singleCharacter'() {
|
||||||
|
return [this.creatureId];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
creature() {
|
||||||
|
return Creatures.findOne(this.creatureId);
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||||
|
},
|
||||||
|
race() {
|
||||||
|
if (this.variables?.race?.value?.valueType === 'string') return this.variables.race.value.value;
|
||||||
|
const prop = CreatureProperties.findOne({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
tags: 'race',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
overridden: { $ne: true },
|
||||||
|
});
|
||||||
|
if (prop?.name) return prop.name;
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
background() {
|
||||||
|
if (this.variables?.background?.value?.valueType === 'string') return this.variables.background.value.value;
|
||||||
|
const prop = CreatureProperties.findOne({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
tags: 'background',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
overridden: { $ne: true },
|
||||||
|
});
|
||||||
|
if (prop?.name) return prop.name;
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
classProperties(){
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'class',
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
}, {
|
||||||
|
sort: {order: 1}
|
||||||
|
}).fetch();
|
||||||
|
},
|
||||||
|
classLevels() {
|
||||||
|
const classVariableNames = this.classProperties.map(c => c.variableName)
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'classLevel',
|
||||||
|
variableName: {$nin: classVariableNames},
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
}, {
|
||||||
|
sort: {order: 1}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editPermission() {
|
||||||
|
try {
|
||||||
|
assertEditPermission(this.creature, Meteor.userId());
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.character-sheet-printed {
|
||||||
|
background: white;
|
||||||
|
color: black;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
.page {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.character-sheet-printed .inactive {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
.character-sheet-printed .creature-name {
|
||||||
|
font-size: 24pt;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.character-sheet-printed .logo-background {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-image: url(/crown-dice-logo-cropped-transparent.png);
|
||||||
|
background-size: contain;
|
||||||
|
background-position: 0 center;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .v-divider {
|
||||||
|
border-color: rgba(0,0,0,0.3);
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .double-border {
|
||||||
|
position: relative;
|
||||||
|
padding: 11px 10px;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .double-border::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border-image-source: url(/images/print/doubleLineImageBorder.png);
|
||||||
|
border-image-slice: 110 126 fill;
|
||||||
|
border-image-width: 16px;
|
||||||
|
border-image-repeat: stretch;
|
||||||
|
box-sizing: content-box;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .octagon-border {
|
||||||
|
position: relative;
|
||||||
|
padding: 4px 20px;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
.character-sheet-printed .octagon-border::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border-image: url(/images/print/octagonBorder.png) 124 118 fill;
|
||||||
|
border-image-width: 22px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .stats .label {
|
||||||
|
font-size: 10pt;
|
||||||
|
font-variant: small-caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .label {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-variant: all-small-caps;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-sheet-printed .span-all {
|
||||||
|
column-span: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen {
|
||||||
|
.character-sheet-printed {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.character-sheet-printed .page {
|
||||||
|
width: 210mm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
header {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.v-main {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
class="inventory"
|
||||||
|
style="page-break-before: always;"
|
||||||
|
>
|
||||||
|
<column-layout wide-columns>
|
||||||
|
<div class="span-all">
|
||||||
|
<div class="double-border">
|
||||||
|
<div class="label text-center">
|
||||||
|
Inventory
|
||||||
|
</div>
|
||||||
|
<div class="d-flex inventory-stat">
|
||||||
|
<v-icon>$vuetify.icons.injustice</v-icon>
|
||||||
|
Weight Carried:
|
||||||
|
{{ weightCarried }} lb
|
||||||
|
</div>
|
||||||
|
<div class="d-flex inventory-stat">
|
||||||
|
<v-icon>$vuetify.icons.cash</v-icon>
|
||||||
|
Net worth:
|
||||||
|
<coin-value
|
||||||
|
class="ml-2"
|
||||||
|
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex inventory-stat">
|
||||||
|
<v-icon>$vuetify.icons.spell</v-icon>
|
||||||
|
Items attuned:
|
||||||
|
{{ variables.itemsAttuned && variables.itemsAttuned.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="span-all">
|
||||||
|
<div class="octagon-border label text-center">
|
||||||
|
Equipped
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="item in equippedItems"
|
||||||
|
:key="item._id"
|
||||||
|
>
|
||||||
|
<printed-item
|
||||||
|
class="double-border"
|
||||||
|
:model="item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="span-all">
|
||||||
|
<div class="octagon-border label text-center">
|
||||||
|
Carried
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="item in carriedItems"
|
||||||
|
:key="item._id"
|
||||||
|
>
|
||||||
|
<printed-item
|
||||||
|
class="double-border"
|
||||||
|
:model="item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template
|
||||||
|
v-for="container in containersWithoutAncestorContainers"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:key="container._id"
|
||||||
|
class="span-all container-header"
|
||||||
|
>
|
||||||
|
<printed-container
|
||||||
|
class="octagon-border"
|
||||||
|
:model="container"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="item in container.items"
|
||||||
|
:key="item._id"
|
||||||
|
>
|
||||||
|
<printed-item
|
||||||
|
class="double-border"
|
||||||
|
:model="item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</column-layout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
|
import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/getParentRefByTag.js';
|
||||||
|
import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js';
|
||||||
|
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||||
|
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||||
|
import PrintedItem from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue';
|
||||||
|
import PrintedContainer from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue';
|
||||||
|
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ColumnLayout,
|
||||||
|
CoinValue,
|
||||||
|
PrintedItem,
|
||||||
|
PrintedContainer,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
organize: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
containers() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'container',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
creature() {
|
||||||
|
return Creatures.findOne(this.creatureId, {
|
||||||
|
fields: {
|
||||||
|
color: 1,
|
||||||
|
variables: 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
variables() {
|
||||||
|
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||||
|
},
|
||||||
|
containersWithoutAncestorContainers() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': {
|
||||||
|
$eq: this.creatureId,
|
||||||
|
$nin: this.containerIds
|
||||||
|
},
|
||||||
|
type: 'container',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 },
|
||||||
|
}).map(c => {
|
||||||
|
c.items = CreatureProperties.find({
|
||||||
|
'parent.id': c._id,
|
||||||
|
type: { $in: ['item', 'container'] },
|
||||||
|
removed: { $ne: true },
|
||||||
|
equipped: { $ne: true },
|
||||||
|
deactivatedByAncestor: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 },
|
||||||
|
}).fetch();
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
carriedItems() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': {
|
||||||
|
$eq: this.creatureId,
|
||||||
|
$nin: this.containerIds
|
||||||
|
},
|
||||||
|
type: 'item',
|
||||||
|
equipped: { $ne: true },
|
||||||
|
removed: { $ne: true },
|
||||||
|
deactivatedByAncestor: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
equippedItems() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': {
|
||||||
|
$eq: this.creatureId,
|
||||||
|
},
|
||||||
|
type: 'item',
|
||||||
|
equipped: true,
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
equipmentParentRef() {
|
||||||
|
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
|
||||||
|
) || getParentRefByTag(
|
||||||
|
this.creatureId, BUILT_IN_TAGS.inventory
|
||||||
|
) || {
|
||||||
|
id: this.creatureId,
|
||||||
|
collection: 'creatures'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
containerIds() {
|
||||||
|
return this.containers.map(container => container._id);
|
||||||
|
},
|
||||||
|
weightCarried() {
|
||||||
|
return stripFloatingPointOddities(
|
||||||
|
this.variables &&
|
||||||
|
this.variables.weightCarried &&
|
||||||
|
this.variables.weightCarried.value || 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickProperty(_id) {
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'creature-property-dialog',
|
||||||
|
elementId: `tree-node-${_id}`,
|
||||||
|
data: { _id },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.octagon-border {
|
||||||
|
position: relative;
|
||||||
|
padding: 4px 20px;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
.octagon-border::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border-image: url(/images/print/octagonBorder.png) 124 118 fill;
|
||||||
|
border-image-width: 22px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-variant: small-caps;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-stat {
|
||||||
|
font-size: 12pt;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
.inventory-stat > .v-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-header {
|
||||||
|
page-break-after: avoid;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
class="spells"
|
||||||
|
style="page-break-before: always;"
|
||||||
|
>
|
||||||
|
<column-layout wide-columns>
|
||||||
|
<div class="span-all">
|
||||||
|
<div class="label text-center octagon-border">
|
||||||
|
Spells
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="spell in spellsWithoutList"
|
||||||
|
:key="spell._id"
|
||||||
|
>
|
||||||
|
<printed-spell :model="spell" />
|
||||||
|
</div>
|
||||||
|
<template
|
||||||
|
v-for="spellList in spellListsWithoutAncestorSpellLists"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:key="spellList._id"
|
||||||
|
class="span-all"
|
||||||
|
>
|
||||||
|
<printed-spell-list
|
||||||
|
:model="spellList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="spell in spellList.spells"
|
||||||
|
:key="spell._id"
|
||||||
|
>
|
||||||
|
<printed-spell :model="spell" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</column-layout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import PrintedSpell from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedSpell.vue';
|
||||||
|
import PrintedSpellList from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedSpellList.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ColumnLayout,
|
||||||
|
PrintedSpell,
|
||||||
|
PrintedSpellList,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
organize: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
spellLists() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'spellList',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
spellsWithoutList() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': {
|
||||||
|
$eq: this.creatureId,
|
||||||
|
$nin: this.spellListIds,
|
||||||
|
},
|
||||||
|
type: 'spell',
|
||||||
|
removed: { $ne: true },
|
||||||
|
deactivatedByAncestor: { $ne: true },
|
||||||
|
deactivatedByToggle: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: {
|
||||||
|
level: 1,
|
||||||
|
order: 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
spellListsWithoutAncestorSpellLists() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': {
|
||||||
|
$eq: this.creatureId,
|
||||||
|
$nin: this.spellListIds,
|
||||||
|
},
|
||||||
|
type: 'spellList',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 }
|
||||||
|
}).map(sl => {
|
||||||
|
sl.spells = CreatureProperties.find({
|
||||||
|
'ancestors.id': sl._id,
|
||||||
|
type: 'spell',
|
||||||
|
removed: { $ne: true },
|
||||||
|
inactive: { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: {
|
||||||
|
level: 1,
|
||||||
|
order: 1,
|
||||||
|
}
|
||||||
|
}).fetch();
|
||||||
|
return sl;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
spellListIds() {
|
||||||
|
return this.spellLists?.map(spellList => spellList._id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,639 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="stats">
|
||||||
|
<column-layout>
|
||||||
|
<div
|
||||||
|
v-if="abilities.length"
|
||||||
|
class="ability-scores"
|
||||||
|
>
|
||||||
|
<div class="layout flex column">
|
||||||
|
<div
|
||||||
|
v-for="ability in abilities"
|
||||||
|
:key="ability._id"
|
||||||
|
class="ability"
|
||||||
|
>
|
||||||
|
<div class="score">
|
||||||
|
<div class="double-border top big-number">
|
||||||
|
<template v-if="creature.settings.swapScoresAndMods">
|
||||||
|
{{ ability.total }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ numberToSignedString(ability.modifier) }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<template v-if="creature.settings.swapScoresAndMods">
|
||||||
|
{{ numberToSignedString(ability.modifier) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ ability.total }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="double-border name label">
|
||||||
|
{{ ability.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="toggle in toggles"
|
||||||
|
:key="toggle._id"
|
||||||
|
class="number-label"
|
||||||
|
>
|
||||||
|
<div class="box double-border" />
|
||||||
|
<div class="label double-border">
|
||||||
|
{{ toggle.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="stat in stats"
|
||||||
|
:key="stat._id"
|
||||||
|
class="number-label"
|
||||||
|
:class="stat.variableName == 'armor' && 'shield-number-label'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="stat.variableName == 'armor' ? 'shield-border' : 'octagon-border'"
|
||||||
|
class="number big-number"
|
||||||
|
>
|
||||||
|
{{ stat.value }}
|
||||||
|
</div>
|
||||||
|
<div class="label double-border">
|
||||||
|
{{ stat.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="modifier in modifiers"
|
||||||
|
:key="modifier._id"
|
||||||
|
class="number-label"
|
||||||
|
>
|
||||||
|
<div class="number octagon-border big-number">
|
||||||
|
{{ numberToSignedString(modifier.value) }}
|
||||||
|
</div>
|
||||||
|
<div class="label double-border">
|
||||||
|
{{ modifier.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="check in checks"
|
||||||
|
:key="check._id"
|
||||||
|
class="number-label"
|
||||||
|
>
|
||||||
|
<div class="number octagon-border big-number">
|
||||||
|
{{ numberToSignedString(check.value) }}
|
||||||
|
</div>
|
||||||
|
<div class="label double-border">
|
||||||
|
{{ check.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="healthBar in healthBars"
|
||||||
|
:key="healthBar._id"
|
||||||
|
class="m-2"
|
||||||
|
>
|
||||||
|
<div class="double-border">
|
||||||
|
<div class="label">
|
||||||
|
Total: {{ healthBar.total }}
|
||||||
|
</div>
|
||||||
|
<div style="height: 60px;" />
|
||||||
|
<div
|
||||||
|
style="text-align: center;"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
{{ healthBar.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="multipliers && multipliers.length">
|
||||||
|
<printed-damage-multipliers
|
||||||
|
class="double-border"
|
||||||
|
:multipliers="multipliers"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="hitDice.length"
|
||||||
|
class="hit-dice m-2"
|
||||||
|
>
|
||||||
|
<div class="double-border">
|
||||||
|
<div>
|
||||||
|
<span class="label">
|
||||||
|
Total:
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-for="hitDie in hitDice"
|
||||||
|
:key="hitDie._id"
|
||||||
|
style="margin-right: 4px;"
|
||||||
|
>
|
||||||
|
{{ hitDie.total }}{{ hitDie.hitDiceSize }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style="height: 60px;" />
|
||||||
|
<div
|
||||||
|
style="text-align: center;"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
Hit Dice
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="resource in resources"
|
||||||
|
:key="resource._id"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
:class="resource.total <= 8 && 'mb-2'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="resource.total <= 8"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
{{ resource.name }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="resource.total > 8"
|
||||||
|
>
|
||||||
|
total: {{ resource.total }}
|
||||||
|
<div style="height: 60px;" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="resource.total <= 8"
|
||||||
|
class="d-flex justify-end"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="i in resource.total"
|
||||||
|
:key="i"
|
||||||
|
class="resource-bubble"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="resource.total > 8"
|
||||||
|
class="label text-center"
|
||||||
|
>
|
||||||
|
{{ resource.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="spellSlots && spellSlots.length"
|
||||||
|
>
|
||||||
|
<div class="double-border">
|
||||||
|
<div class="label text-center">
|
||||||
|
Spell Slots
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="spellSlot in spellSlots"
|
||||||
|
:key="spellSlot._id"
|
||||||
|
class="mb-7"
|
||||||
|
:class="spellSlot.total <= 8 && 'mb-7'"
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
{{ spellSlot.name }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="spellSlot.total > 8"
|
||||||
|
>
|
||||||
|
Total: {{ spellSlot.total }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="i in spellSlot.total"
|
||||||
|
:key="i"
|
||||||
|
class="resource-bubble"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="savingThrows.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<printed-skill
|
||||||
|
v-for="save in savingThrows"
|
||||||
|
:key="save._id"
|
||||||
|
:model="save"
|
||||||
|
:data-id="save._id"
|
||||||
|
/>
|
||||||
|
<div class="label text-center">
|
||||||
|
Saving Throws
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="skills.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<printed-skill
|
||||||
|
v-for="skill in skills"
|
||||||
|
:key="skill._id"
|
||||||
|
:model="skill"
|
||||||
|
:data-id="skill._id"
|
||||||
|
/>
|
||||||
|
<div class="label text-center">
|
||||||
|
Skills
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="weapons && weapons.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<printed-skill
|
||||||
|
v-for="weapon in weapons"
|
||||||
|
:key="weapon._id"
|
||||||
|
hide-modifier
|
||||||
|
:model="weapon"
|
||||||
|
:data-id="weapon._id"
|
||||||
|
/>
|
||||||
|
<div class="label text-center">
|
||||||
|
Weapons
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="armors && armors.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<printed-skill
|
||||||
|
v-for="armor in armors"
|
||||||
|
:key="armor._id"
|
||||||
|
hide-modifier
|
||||||
|
:model="armor"
|
||||||
|
:data-id="armor._id"
|
||||||
|
/>
|
||||||
|
<div class="label text-center">
|
||||||
|
Armor
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="tools && tools.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<printed-skill
|
||||||
|
v-for="tool in tools"
|
||||||
|
:key="tool._id"
|
||||||
|
hide-modifier
|
||||||
|
:model="tool"
|
||||||
|
:data-id="tool._id"
|
||||||
|
/>
|
||||||
|
<div class="label text-center">
|
||||||
|
Tools
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="languages && languages.length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<printed-skill
|
||||||
|
v-for="language in languages"
|
||||||
|
:key="language._id"
|
||||||
|
hide-modifier
|
||||||
|
:model="language"
|
||||||
|
:data-id="language._id"
|
||||||
|
/>
|
||||||
|
<div class="label text-center">
|
||||||
|
Languages
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="note in notes"
|
||||||
|
:key="note._id"
|
||||||
|
>
|
||||||
|
<div class="double-border">
|
||||||
|
<div class="label text-center">
|
||||||
|
{{ note.name }}
|
||||||
|
</div>
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="note.summary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="action in actions"
|
||||||
|
:key="action._id"
|
||||||
|
>
|
||||||
|
<div class="double-border">
|
||||||
|
<printed-action
|
||||||
|
:model="action"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="feature in features"
|
||||||
|
:key="feature._id"
|
||||||
|
>
|
||||||
|
<div class="double-border">
|
||||||
|
<div class="label text-center">
|
||||||
|
{{ feature.name }}
|
||||||
|
</div>
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="feature.summary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</column-layout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||||
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
|
import PrintedAction from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedAction.vue';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
|
import PrintedSkill from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue';
|
||||||
|
import PrintedDamageMultipliers from '/imports/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue';
|
||||||
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||||
|
|
||||||
|
const getProperties = function (creature, filter, options = {
|
||||||
|
sort: { order: 1 }
|
||||||
|
}) {
|
||||||
|
if (!creature) return;
|
||||||
|
if (creature.settings.hideUnusedStats) {
|
||||||
|
filter.hide = { $ne: true };
|
||||||
|
}
|
||||||
|
filter['ancestors.id'] = creature._id;
|
||||||
|
filter.removed = { $ne: true };
|
||||||
|
filter.inactive = { $ne: true };
|
||||||
|
filter.overridden = { $ne: true };
|
||||||
|
filter.$nor = [
|
||||||
|
{ hideWhenTotalZero: true, total: 0 },
|
||||||
|
{ hideWhenValueZero: true, value: 0 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return CreatureProperties.find(filter, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAttributeOfType = function (creature, type) {
|
||||||
|
return getProperties(creature, {
|
||||||
|
type: 'attribute',
|
||||||
|
attributeType: type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSkillOfType = function (creature, type) {
|
||||||
|
return getProperties(creature, {
|
||||||
|
type: 'skill',
|
||||||
|
skillType: type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ColumnLayout,
|
||||||
|
PrintedDamageMultipliers,
|
||||||
|
PrintedAction,
|
||||||
|
PrintedSkill,
|
||||||
|
PropertyDescription,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
doCheckLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
creature() {
|
||||||
|
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
||||||
|
},
|
||||||
|
abilities() {
|
||||||
|
return getAttributeOfType(this.creature, 'ability');
|
||||||
|
},
|
||||||
|
stats() {
|
||||||
|
return getAttributeOfType(this.creature, 'stat');
|
||||||
|
},
|
||||||
|
toggles() {
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'toggle',
|
||||||
|
removed: { $ne: true },
|
||||||
|
deactivatedByAncestor: { $ne: true },
|
||||||
|
showUI: true,
|
||||||
|
}, {
|
||||||
|
sort: { order: 1 }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
healthBars() {
|
||||||
|
return getAttributeOfType(this.creature, 'healthBar');
|
||||||
|
},
|
||||||
|
modifiers() {
|
||||||
|
return getAttributeOfType(this.creature, 'modifier');
|
||||||
|
},
|
||||||
|
resources() {
|
||||||
|
return getAttributeOfType(this.creature, 'resource');
|
||||||
|
},
|
||||||
|
spellSlots() {
|
||||||
|
return getAttributeOfType(this.creature, 'spellSlot');
|
||||||
|
},
|
||||||
|
hasSpells() {
|
||||||
|
const cursor = getProperties(this.creature, {
|
||||||
|
type: 'spell',
|
||||||
|
})
|
||||||
|
return cursor && cursor.count();
|
||||||
|
},
|
||||||
|
hitDice() {
|
||||||
|
return getAttributeOfType(this.creature, 'hitDice');
|
||||||
|
},
|
||||||
|
checks() {
|
||||||
|
return getSkillOfType(this.creature, 'check');
|
||||||
|
},
|
||||||
|
savingThrows() {
|
||||||
|
return getSkillOfType(this.creature, 'save');
|
||||||
|
},
|
||||||
|
skills() {
|
||||||
|
return getSkillOfType(this.creature, 'skill');
|
||||||
|
},
|
||||||
|
tools() {
|
||||||
|
return getSkillOfType(this.creature, 'tool');
|
||||||
|
},
|
||||||
|
weapons() {
|
||||||
|
return getSkillOfType(this.creature, 'weapon');
|
||||||
|
},
|
||||||
|
armors() {
|
||||||
|
return getSkillOfType(this.creature, 'armor');
|
||||||
|
},
|
||||||
|
languages() {
|
||||||
|
return getSkillOfType(this.creature, 'language');
|
||||||
|
},
|
||||||
|
actions() {
|
||||||
|
return getProperties(this.creature, { type: 'action' }, {
|
||||||
|
sort: { actionType: 1, order: 1 }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
appliedBuffs() {
|
||||||
|
return getProperties(this.creature, { type: 'buff' });
|
||||||
|
},
|
||||||
|
multipliers() {
|
||||||
|
return getProperties(this.creature, {
|
||||||
|
type: 'damageMultiplier'
|
||||||
|
}, {
|
||||||
|
sort: { value: 1, order: 1 }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
features() {
|
||||||
|
return getProperties(this.creature, { type: 'feature' });
|
||||||
|
},
|
||||||
|
notes(){
|
||||||
|
return getProperties(this.creature, { type: 'note', summary: {$exists: true} });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
numberToSignedString,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.shield-border {
|
||||||
|
min-width: 64px !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 0.87;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.shield-border::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: url(/images/print/shieldBorder.png);
|
||||||
|
print-color-adjust: exact;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.shield-number-label {
|
||||||
|
align-items: center !important;
|
||||||
|
}
|
||||||
|
.big-number {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
.ability {
|
||||||
|
display: flex;
|
||||||
|
align-items: start;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
.ability .score {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.ability .top {
|
||||||
|
min-width: 64px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: -10px;
|
||||||
|
padding: 14px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.ability .bottom {
|
||||||
|
font-size: 10pt;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 16px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.ability .bottom::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
border: solid white;
|
||||||
|
border-image-source: url(/images/print/upwardPointingBorder.png);
|
||||||
|
border-image-slice: 0 85 fill;
|
||||||
|
border-image-width: 0 16px;
|
||||||
|
border-image-outset: 0px 0px;
|
||||||
|
border-image-repeat: stretch;
|
||||||
|
box-sizing: content-box;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.ability .name {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: -16px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 10pt;
|
||||||
|
font-variant: small-caps;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-label .label {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-left: -30px;
|
||||||
|
padding-left: 34px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-label .number {
|
||||||
|
min-width: 72px;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-label .box {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-left: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-bubble {
|
||||||
|
margin-bottom: -20px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
background-color: white;
|
||||||
|
border: solid black 2px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
class="action-card"
|
||||||
|
:class="cardClasses"
|
||||||
|
>
|
||||||
|
<div class="label text-center">
|
||||||
|
{{ actionTypeName }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<div class="avatar">
|
||||||
|
<div
|
||||||
|
v-if="rollBonus"
|
||||||
|
>
|
||||||
|
<template v-if="rollBonus && !rollBonusTooLong">
|
||||||
|
{{ rollBonus }}
|
||||||
|
</template>
|
||||||
|
<property-icon
|
||||||
|
v-else
|
||||||
|
:model="model"
|
||||||
|
color="rgba(0,0,0,0.7)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<property-icon
|
||||||
|
v-else
|
||||||
|
:model="model"
|
||||||
|
color="rgba(0,0,0,0.7)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="action-header flex d-flex column justify-center pl-1"
|
||||||
|
>
|
||||||
|
<div class="action-title my-1">
|
||||||
|
{{ model.name || propertyName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="Number.isFinite(model.uses)"
|
||||||
|
class="action-sub-title d-flex align-center"
|
||||||
|
>
|
||||||
|
{{ model.uses }} uses
|
||||||
|
</div>
|
||||||
|
<div class="pb-3">
|
||||||
|
<div
|
||||||
|
v-if="model.resources && model.resources.attributesConsumed.length ||
|
||||||
|
model.resources.itemsConsumed.length"
|
||||||
|
class="resources my-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="attributeConsumed in model.resources.attributesConsumed"
|
||||||
|
:key="attributeConsumed._id"
|
||||||
|
class="layout align-center justify-start"
|
||||||
|
>
|
||||||
|
Cost: {{ attributeConsumed.quantity && attributeConsumed.quantity.value }} {{ attributeConsumed.statName || attributeConsumed.variableName }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="itemConsumed in model.resources.itemsConsumed"
|
||||||
|
:key="itemConsumed._id"
|
||||||
|
>
|
||||||
|
<template v-if="itemConsumed.itemName">
|
||||||
|
Uses: {{ itemConsumed.quantity && itemConsumed.quantity.value || 0 }} {{ itemConsumed.itemName || itemConsumed.tag }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="model.summary">
|
||||||
|
<markdown-text :markdown="model.summary.value || model.summary.text" />
|
||||||
|
</template>
|
||||||
|
<v-divider v-if="children && children.length" />
|
||||||
|
<tree-node-list
|
||||||
|
v-if="children && children.length"
|
||||||
|
start-expanded
|
||||||
|
:children="children"
|
||||||
|
@selected="e => $emit('sub-click', e)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
|
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||||
|
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||||
|
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||||
|
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
|
||||||
|
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
|
||||||
|
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import { some } from 'lodash';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
AttributeConsumedView,
|
||||||
|
ItemConsumedView,
|
||||||
|
MarkdownText,
|
||||||
|
PropertyIcon,
|
||||||
|
TreeNodeList,
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
context: {
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
default: {
|
||||||
|
isDark: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activated: undefined,
|
||||||
|
doActionLoading: false,
|
||||||
|
hovering: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rollBonus() {
|
||||||
|
if (!this.model.attackRoll) return;
|
||||||
|
return numberToSignedString(this.model.attackRoll.value);
|
||||||
|
},
|
||||||
|
rollBonusTooLong() {
|
||||||
|
return this.rollBonus && this.rollBonus.length > 3;
|
||||||
|
},
|
||||||
|
propertyName() {
|
||||||
|
return getPropertyName(this.model.type);
|
||||||
|
},
|
||||||
|
cardClasses() {
|
||||||
|
return {
|
||||||
|
'theme--dark': this.theme.isDark,
|
||||||
|
'theme--light': !this.theme.isDark,
|
||||||
|
'muted-text': this.model.insufficientResources,
|
||||||
|
'active': this.activated,
|
||||||
|
'elevation-8': this.hovering,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actionTypeName() {
|
||||||
|
return {
|
||||||
|
'action': 'Action',
|
||||||
|
'bonus': 'Bonus Action',
|
||||||
|
'attack': 'Attack',
|
||||||
|
'reaction': 'Reaction',
|
||||||
|
'free': 'Free Action',
|
||||||
|
'long': 'Long Action'
|
||||||
|
}[this.model.actionType] || this.model.actionType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
children() {
|
||||||
|
const indicesOfTerminatingProps = [];
|
||||||
|
const decendants = CreatureProperties.find({
|
||||||
|
'ancestors.id': this.model._id,
|
||||||
|
'removed': { $ne: true },
|
||||||
|
}, {
|
||||||
|
sort: {order: 1}
|
||||||
|
}).map(prop => {
|
||||||
|
// Get all the props we don't want to show the decendants of and
|
||||||
|
// where they might appear in the ancestor list
|
||||||
|
if (prop.type === 'buff' || prop.type === 'folder') {
|
||||||
|
indicesOfTerminatingProps.push({
|
||||||
|
id: prop._id,
|
||||||
|
ancestorIndex: prop.ancestors.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prop;
|
||||||
|
}).filter(prop => {
|
||||||
|
// Filter out folders entirely
|
||||||
|
if (prop.type === 'folder') return false;
|
||||||
|
// Filter out decendants of terminating props
|
||||||
|
return !some(indicesOfTerminatingProps, buffIndex => {
|
||||||
|
return prop.ancestors[buffIndex.ancestorIndex]?.id === buffIndex.id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return nodeArrayToTree(decendants);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.action-card {
|
||||||
|
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
|
||||||
|
transform 0.075s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
font-size: 18pt;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 10pt;
|
||||||
|
font-variant: small-caps;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
transition: .3s cubic-bezier(.25, .8, .5, 1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources {
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-child {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--light.muted-text {
|
||||||
|
color: rgba(0, 0, 0, .3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--dark.muted-text {
|
||||||
|
color: hsla(0, 0%, 100%, .3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-card {
|
||||||
|
transition: transform 0.15s cubic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.action-card.theme--light.muted-text .v-icon {
|
||||||
|
color: rgba(0, 0, 0, .3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-card.theme--dark.muted-text .v-icon {
|
||||||
|
color: hsla(0, 0%, 100%, .3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-card .property-description>p:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex justify-center">
|
||||||
|
<property-icon
|
||||||
|
class="ml-2"
|
||||||
|
color="rgba(0,0,0,0.7)"
|
||||||
|
:model="model"
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
{{ model.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="model.value !== undefined || model.weight !== undefined"
|
||||||
|
class="weight-value my-2 d-flex justify-space-between"
|
||||||
|
>
|
||||||
|
<div class="value ml-4">
|
||||||
|
<div
|
||||||
|
v-if="model.value !== undefined"
|
||||||
|
>
|
||||||
|
<v-layout align-center>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.two_coins
|
||||||
|
</v-icon>
|
||||||
|
<coin-value
|
||||||
|
class="mr-2"
|
||||||
|
:value="model.value"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
|
|
||||||
|
<v-layout
|
||||||
|
align-center
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.cash
|
||||||
|
</v-icon>
|
||||||
|
<coin-value
|
||||||
|
:value="model.contentsValue"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
contents
|
||||||
|
</span>
|
||||||
|
</v-layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="weight ml-4">
|
||||||
|
<div
|
||||||
|
v-if="model.weight !== undefined"
|
||||||
|
>
|
||||||
|
<v-layout align-center>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.weight
|
||||||
|
</v-icon>
|
||||||
|
{{ model.weight }} lb
|
||||||
|
</v-layout>
|
||||||
|
|
||||||
|
<v-layout
|
||||||
|
align-center
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.injustice
|
||||||
|
</v-icon>
|
||||||
|
{{ model.contentsWeight }} lb
|
||||||
|
<span
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
contents
|
||||||
|
</span>
|
||||||
|
</v-layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="model.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeViewMixin.js';
|
||||||
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||||
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||||
|
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CoinValue,
|
||||||
|
PropertyDescription,
|
||||||
|
},
|
||||||
|
mixins: [treeNodeViewMixin],
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
preparingSpells: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
incrementLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasClickListener() {
|
||||||
|
return this.$listeners && !!this.$listeners.click;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.item-avatar {
|
||||||
|
min-width: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(multiplier, multiplierIndex) in multipliers"
|
||||||
|
:key="multiplier._id"
|
||||||
|
:data-id="multiplier._id"
|
||||||
|
@click="$emit('click-multiplier', {_id: multiplier._id})"
|
||||||
|
>
|
||||||
|
<v-divider v-if="multiplierIndex" />
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="multiplier.name"
|
||||||
|
class="label text-center"
|
||||||
|
>
|
||||||
|
{{ multiplier.name }}
|
||||||
|
</div>
|
||||||
|
<div class="font-weight-medium">
|
||||||
|
{{ title(multiplier) }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap align-center">
|
||||||
|
{{ multiplier.damageTypes.join(', ') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="multiplier.includeTags && multiplier.includeTags.length"
|
||||||
|
class="d-flex flex-wrap align-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
For:
|
||||||
|
</div>
|
||||||
|
{{ multiplier.includeTags.join(', ') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="multiplier.excludeTags && multiplier.excludeTags.length"
|
||||||
|
class="d-flex flex-wrap align-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
Except:
|
||||||
|
</div>
|
||||||
|
{{ multiplier.excludeTags.join(', ') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
multipliers:{
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
title(prop){
|
||||||
|
switch (prop.value){
|
||||||
|
case 0: return 'Immunity';
|
||||||
|
case 0.5: return 'Resistance';
|
||||||
|
case 2: return 'Vulnerability';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.label {
|
||||||
|
font-size: 10pt;
|
||||||
|
font-variant: small-caps;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<div class="d-flex justify-space-between">
|
||||||
|
<div class="label">
|
||||||
|
{{ title }}
|
||||||
|
<template v-if="attunementText">
|
||||||
|
({{ attunementText }})
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<property-icon
|
||||||
|
class="ml-2"
|
||||||
|
color="rgba(0,0,0,0.7)"
|
||||||
|
:model="model"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="model.value !== undefined || model.weight !== undefined"
|
||||||
|
class="weight-value my-2 d-flex justify-space-between"
|
||||||
|
>
|
||||||
|
<div class="value ml-4">
|
||||||
|
<div
|
||||||
|
v-if="model.value !== undefined"
|
||||||
|
>
|
||||||
|
<v-layout
|
||||||
|
v-if="model.quantity > 1"
|
||||||
|
align-center
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.cash
|
||||||
|
</v-icon>
|
||||||
|
<coin-value
|
||||||
|
:value="model.value * model.quantity"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
|
<v-layout align-center>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.two_coins
|
||||||
|
</v-icon>
|
||||||
|
<coin-value
|
||||||
|
class="mr-2"
|
||||||
|
:value="model.value"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="model.quantity > 1"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
each
|
||||||
|
</span>
|
||||||
|
</v-layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="weight ml-4">
|
||||||
|
<div
|
||||||
|
v-if="model.weight !== undefined"
|
||||||
|
>
|
||||||
|
<v-layout
|
||||||
|
v-if="model.quantity > 1"
|
||||||
|
align-center
|
||||||
|
class="mb-2"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.injustice
|
||||||
|
</v-icon>
|
||||||
|
{{ totalWeight }} lb
|
||||||
|
</v-layout>
|
||||||
|
<v-layout align-center>
|
||||||
|
<v-icon
|
||||||
|
class="mr-2"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
$vuetify.icons.weight
|
||||||
|
</v-icon>
|
||||||
|
{{ model.weight }} lb
|
||||||
|
<span
|
||||||
|
v-if="model.quantity > 1"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
each
|
||||||
|
</span>
|
||||||
|
</v-layout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="model.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeViewMixin.js';
|
||||||
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
import CoinValue from '/imports/ui/components/CoinValue.vue';
|
||||||
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||||
|
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CoinValue,
|
||||||
|
PropertyDescription,
|
||||||
|
},
|
||||||
|
mixins: [treeNodeViewMixin],
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
preparingSpells: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
incrementLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasClickListener() {
|
||||||
|
return this.$listeners && !!this.$listeners.click;
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
let model = this.model;
|
||||||
|
if (!model) return;
|
||||||
|
if (model.quantity !== 1) {
|
||||||
|
if (model.plural) {
|
||||||
|
return `${model.quantity} ${model.plural}`;
|
||||||
|
} else if (model.name) {
|
||||||
|
return `${model.quantity} ${model.name}`;
|
||||||
|
}
|
||||||
|
} else if (model.name) {
|
||||||
|
return model.name;
|
||||||
|
}
|
||||||
|
let prop = PROPERTIES[model.type]
|
||||||
|
return prop && prop.name;
|
||||||
|
},
|
||||||
|
totalValue() {
|
||||||
|
return stripFloatingPointOddities(this.model.value * this.model.quantity);
|
||||||
|
},
|
||||||
|
totalWeight() {
|
||||||
|
return stripFloatingPointOddities(this.model.weight * this.model.quantity);
|
||||||
|
},
|
||||||
|
attunementText() {
|
||||||
|
if (this.model.requiresAttunement) {
|
||||||
|
if (this.model.attuned) return 'Attuned';
|
||||||
|
return 'Requires attunement';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.item-avatar {
|
||||||
|
min-width: 32px;
|
||||||
|
}
|
||||||
|
.item .label {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-variant: all-small-caps;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
class="printed-skill pl-0 d-flex align-center"
|
||||||
|
>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<div
|
||||||
|
v-if="!hideModifier"
|
||||||
|
class="d-flex align-center"
|
||||||
|
>
|
||||||
|
<proficiency-icon
|
||||||
|
:value="model.proficiency"
|
||||||
|
class="prof-icon"
|
||||||
|
/>
|
||||||
|
<div class="prof-mod ml-2 mr-4 text-right">
|
||||||
|
{{ displayedModifier }}
|
||||||
|
</div>
|
||||||
|
<v-icon
|
||||||
|
v-if="model.advantage > 0"
|
||||||
|
size="20px"
|
||||||
|
>
|
||||||
|
mdi-chevron-double-up
|
||||||
|
</v-icon>
|
||||||
|
<v-icon
|
||||||
|
v-if="model.advantage < 0"
|
||||||
|
size="20px"
|
||||||
|
>
|
||||||
|
mdi-chevron-double-down
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
<proficiency-icon
|
||||||
|
v-else
|
||||||
|
:value="model.proficiency"
|
||||||
|
class="prof-icon mr-2"
|
||||||
|
/>
|
||||||
|
<div class="text-truncate">
|
||||||
|
{{ model.name }}
|
||||||
|
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
|
||||||
|
*
|
||||||
|
</template>
|
||||||
|
<template v-if="'passiveBonus' in model">
|
||||||
|
({{ passiveScore }})
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
|
import ProficiencyIcon from '/imports/ui/properties/shared/ProficiencyIcon.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ProficiencyIcon,
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
context: {
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hideModifier: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
checkLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayedModifier() {
|
||||||
|
let mod = this.model.value;
|
||||||
|
if (this.model.fail) {
|
||||||
|
return 'fail';
|
||||||
|
} else {
|
||||||
|
return numberToSignedString(mod);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
passiveScore() {
|
||||||
|
return 10 + this.model.value + this.model.passiveBonus;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.printed-skill{
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prof-icon {
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prof-mod {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-icon.theme--light {
|
||||||
|
color: rgba(0, 0, 0, 0.7) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
class="double-border"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="model.name"
|
||||||
|
class="label"
|
||||||
|
>
|
||||||
|
{{ model.name }}
|
||||||
|
</div>
|
||||||
|
<div v-if="model.level">
|
||||||
|
{{ levelText }} {{ model.school }} {{ model.ritual ? '(ritual)' : '' }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{{ model.school }} cantrip
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Casting Time: {{ model.castingTime }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Range: {{ model.range }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Components: {{ spellComponents }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Duration: {{ model.duration }}
|
||||||
|
</div>
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="model.summary"
|
||||||
|
/>
|
||||||
|
<v-divider class="my-2" />
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="model.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||||
|
|
||||||
|
const levelText = [
|
||||||
|
'cantrip', '1st-level', '2nd-level', '3rd-level', '4th-level', '5th-level',
|
||||||
|
'6th-level', '7th-level', '8th-level', '9th-level'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PropertyDescription,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
levelText() {
|
||||||
|
return levelText[this.model.level]
|
||||||
|
},
|
||||||
|
spellComponents() {
|
||||||
|
let components = [];
|
||||||
|
if (this.model.ritual) components.push('Ritual');
|
||||||
|
if (this.model.concentration) components.push('Concentration');
|
||||||
|
if (this.model.verbal) components.push('Verbal');
|
||||||
|
if (this.model.somatic) components.push('Somatic');
|
||||||
|
if (this.model.material) components.push(`Material (${this.model.material})`);
|
||||||
|
return components.join(', ');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.label {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-variant: all-small-caps;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="octagon-border">
|
||||||
|
<div class="label text-center">
|
||||||
|
{{ model.name }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Spell Save DC: {{ model.dc && model.dc.value }}
|
||||||
|
</div>
|
||||||
|
<div v-if="model.ability">
|
||||||
|
Spell casting ability: {{ model.ability }}
|
||||||
|
</div>
|
||||||
|
<div v-if="model.ability">
|
||||||
|
Spell casting ability modifier: {{ model.abilityMod }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Spell Attack Bonus: {{ model.attackRollBonus && model.attackRollBonus.value }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Maximum prepared spells: {{ model.maxPrepared && model.maxPrepared.value }}
|
||||||
|
</div>
|
||||||
|
<property-description
|
||||||
|
text
|
||||||
|
:model="model.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PropertyDescription,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -16,6 +16,14 @@
|
|||||||
flat
|
flat
|
||||||
@change="propertyHelpChanged"
|
@change="propertyHelpChanged"
|
||||||
/>
|
/>
|
||||||
|
<v-btn
|
||||||
|
v-if="tab === 1"
|
||||||
|
icon
|
||||||
|
data-id="help-button"
|
||||||
|
@click="helpDialog"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-help</v-icon>
|
||||||
|
</v-btn>
|
||||||
<text-field
|
<text-field
|
||||||
v-if="tab === 2"
|
v-if="tab === 2"
|
||||||
prepend-inner-icon="mdi-magnify"
|
prepend-inner-icon="mdi-magnify"
|
||||||
@@ -173,7 +181,7 @@
|
|||||||
<script lang="js">
|
<script lang="js">
|
||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import PROPERTIES, { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||||
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
||||||
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
|
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
|
||||||
@@ -235,7 +243,11 @@ export default {
|
|||||||
},
|
},
|
||||||
toolbarColor(){
|
toolbarColor(){
|
||||||
return getThemeColor('secondary');
|
return getThemeColor('secondary');
|
||||||
}
|
},
|
||||||
|
docsPath() {
|
||||||
|
const propDef = PROPERTIES[this.type];
|
||||||
|
return propDef && propDef.docsPath;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
type(newType){
|
type(newType){
|
||||||
@@ -259,6 +271,15 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
helpDialog() {
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'help-dialog',
|
||||||
|
elementId: 'help-button',
|
||||||
|
data: {
|
||||||
|
path: this.docsPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
searchChanged(val, ack){
|
searchChanged(val, ack){
|
||||||
this._subs.searchLibraryNodes.setData('searchTerm', val);
|
this._subs.searchLibraryNodes.setData('searchTerm', val);
|
||||||
this._subs.searchLibraryNodes.setData('limit', undefined);
|
this._subs.searchLibraryNodes.setData('limit', undefined);
|
||||||
|
|||||||
@@ -89,4 +89,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() { return {
|
data() {
|
||||||
|
return {
|
||||||
type: undefined,
|
type: undefined,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyName,
|
getPropertyName,
|
||||||
back() {
|
back() {
|
||||||
@@ -45,4 +47,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,11 +30,14 @@
|
|||||||
DialogBase,
|
DialogBase,
|
||||||
LibraryAndNode,
|
LibraryAndNode,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
node: undefined,
|
node: undefined,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -83,4 +83,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/
|
|||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
||||||
import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue';
|
import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
|
||||||
import { clone } from 'lodash';
|
import { clone } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -217,13 +216,15 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
selectedNodeIds: [],
|
selectedNodeIds: [],
|
||||||
searchInput: undefined,
|
searchInput: undefined,
|
||||||
searchValue: undefined,
|
searchValue: undefined,
|
||||||
showDisabled: false,
|
showDisabled: false,
|
||||||
disabledNodeCount: undefined,
|
disabledNodeCount: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
reactiveProvide: {
|
reactiveProvide: {
|
||||||
name: 'context',
|
name: 'context',
|
||||||
include: ['creatureId'],
|
include: ['creatureId'],
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ export default {
|
|||||||
}},
|
}},
|
||||||
computed: {
|
computed: {
|
||||||
accentColor() {
|
accentColor() {
|
||||||
if (this.theme.isDark){
|
if (this.model.color) {
|
||||||
|
return this.model.color
|
||||||
|
} else if (this.theme.isDark){
|
||||||
return this.$vuetify.theme.themes.dark.primary;
|
return this.$vuetify.theme.themes.dark.primary;
|
||||||
} else {
|
} else {
|
||||||
return this.$vuetify.theme.themes.light.primary;
|
return this.$vuetify.theme.themes.light.primary;
|
||||||
|
|||||||
@@ -218,13 +218,15 @@ export default {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
selectedNodeIds: [],
|
selectedNodeIds: [],
|
||||||
searchInput: undefined,
|
searchInput: undefined,
|
||||||
searchValue: undefined,
|
searchValue: undefined,
|
||||||
showDisabled: false,
|
showDisabled: false,
|
||||||
disabledNodeCount: undefined,
|
disabledNodeCount: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
reactiveProvide: {
|
reactiveProvide: {
|
||||||
name: 'context',
|
name: 'context',
|
||||||
include: ['creatureId'],
|
include: ['creatureId'],
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
Delete {{ typeName }}
|
Delete {{ typeName }}
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<div>
|
<div>
|
||||||
<v-alert type="warning" outlined>
|
<v-alert
|
||||||
|
type="warning"
|
||||||
|
outlined
|
||||||
|
>
|
||||||
This can't be undone
|
This can't be undone
|
||||||
</v-alert>
|
</v-alert>
|
||||||
<p v-if="name">
|
<p v-if="name">
|
||||||
@@ -12,9 +15,9 @@
|
|||||||
</p>
|
</p>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-if="name"
|
v-if="name"
|
||||||
|
v-model="inputName"
|
||||||
label="Confirmation"
|
label="Confirmation"
|
||||||
outlined
|
outlined
|
||||||
v-model="inputName"
|
|
||||||
/>
|
/>
|
||||||
<div class="layout justify-center">
|
<div class="layout justify-center">
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -45,12 +48,20 @@ export default {
|
|||||||
DialogBase,
|
DialogBase,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
typeName: String,
|
typeName: {
|
||||||
name: String,
|
type: String,
|
||||||
|
default: undefined,
|
||||||
},
|
},
|
||||||
data(){return {
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
inputName: undefined,
|
inputName: undefined,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
nameMatch() {
|
nameMatch() {
|
||||||
if (!this.name) return true;
|
if (!this.name) return true;
|
||||||
@@ -63,4 +74,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -65,9 +65,11 @@
|
|||||||
},
|
},
|
||||||
darkBody: Boolean,
|
darkBody: Boolean,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
offsetTop: 0,
|
offsetTop: 0,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isDark() {
|
isDark() {
|
||||||
return isDarkColor(this.computedColor);
|
return isDarkColor(this.computedColor);
|
||||||
@@ -99,13 +101,17 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-radius: 2px 2px 0 0;
|
border-radius: 2px 2px 0 0;
|
||||||
}
|
}
|
||||||
#base-dialog-body, .unwrapped-content {
|
|
||||||
|
#base-dialog-body,
|
||||||
|
.unwrapped-content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#base-dialog-body.dark-body {
|
#base-dialog-body.dark-body {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark #base-dialog-body.dark-body {
|
.theme--dark #base-dialog-body.dark-body {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
const AddCreaturePropertyDialog = () => import('/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue');
|
// Load commonly used dialogs immediately
|
||||||
|
import AddCreaturePropertyDialog from '/imports/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue';
|
||||||
|
import CharacterCreationDialog from '/imports/ui/creature/character/CharacterCreationDialog.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';
|
||||||
|
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue';
|
||||||
|
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue';
|
||||||
|
import CreatureRootDialog from '/imports/ui/creature/character/CreatureRootDialog.vue';
|
||||||
|
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
||||||
|
import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue';
|
||||||
|
import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue';
|
||||||
|
import HelpDialog from '/imports/ui/dialogStack/HelpDialog.vue';
|
||||||
|
import LevelUpDialog from '/imports/ui/creature/slots/LevelUpDialog.vue';
|
||||||
|
import SelectLibraryNodeDialog from '/imports/ui/library/SelectLibraryNodeDialog.vue';
|
||||||
|
import SlotFillDialog from '/imports/ui/creature/slots/SlotFillDialog.vue';
|
||||||
|
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||||
|
import TransferOwnershipDialog from '/imports/ui/sharing/TransferOwnershipDialog.vue';
|
||||||
|
|
||||||
|
// Lazily load less common dialogs
|
||||||
const ArchiveDialog = () => import('/imports/ui/creature/archive/ArchiveDialog.vue');
|
const ArchiveDialog = () => import('/imports/ui/creature/archive/ArchiveDialog.vue');
|
||||||
const CharacterCreationDialog = () => import('/imports/ui/creature/character/CharacterCreationDialog.vue');
|
|
||||||
const CastSpellWithSlotDialog = () => import('/imports/ui/properties/components/spells/CastSpellWithSlotDialog.vue');
|
|
||||||
const CreatureFormDialog = () => import('/imports/ui/creature/CreatureFormDialog.vue');
|
|
||||||
const CreaturePropertyCreationDialog = () => import('/imports/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue');
|
|
||||||
const CreaturePropertyDialog = () => import('/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue');
|
|
||||||
const CreaturePropertyFromLibraryDialog = () => import('/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue');
|
|
||||||
const CreatureRootDialog = () => import('/imports/ui/creature/character/CreatureRootDialog.vue');
|
|
||||||
const DeleteConfirmationDialog = () => import('/imports/ui/dialogStack/DeleteConfirmationDialog.vue');
|
|
||||||
const DeleteUserAccountDialog = () => import('/imports/ui/user/DeleteUserAccountDialog.vue');
|
const DeleteUserAccountDialog = () => import('/imports/ui/user/DeleteUserAccountDialog.vue');
|
||||||
const ExperienceInsertDialog = () => import( '/imports/ui/creature/experiences/ExperienceInsertDialog.vue');
|
|
||||||
const ExperienceListDialog = () => import( '/imports/ui/creature/experiences/ExperienceListDialog.vue');
|
|
||||||
const InviteDialog = () => import('/imports/ui/user/InviteDialog.vue');
|
const InviteDialog = () => import('/imports/ui/user/InviteDialog.vue');
|
||||||
const LevelUpDialog = () => import('/imports/ui/creature/slots/LevelUpDialog.vue');
|
|
||||||
const LibraryCollectionCreationDialog = () => import('/imports/ui/library/LibraryCollectionCreationDialog.vue');
|
const LibraryCollectionCreationDialog = () => import('/imports/ui/library/LibraryCollectionCreationDialog.vue');
|
||||||
const LibraryCollectionEditDialog = () => import('/imports/ui/library/LibraryCollectionEditDialog.vue');
|
const LibraryCollectionEditDialog = () => import('/imports/ui/library/LibraryCollectionEditDialog.vue');
|
||||||
const LibraryCreationDialog = () => import('/imports/ui/library/LibraryCreationDialog.vue');
|
const LibraryCreationDialog = () => import('/imports/ui/library/LibraryCreationDialog.vue');
|
||||||
@@ -21,11 +29,7 @@ const LibraryNodeCreationDialog = () => import('/imports/ui/library/LibraryNodeC
|
|||||||
const LibraryNodeDialog = () => import('/imports/ui/library/LibraryNodeDialog.vue');
|
const LibraryNodeDialog = () => import('/imports/ui/library/LibraryNodeDialog.vue');
|
||||||
const MoveLibraryNodeDialog = () => import('/imports/ui/library/MoveLibraryNodeDialog.vue');
|
const MoveLibraryNodeDialog = () => import('/imports/ui/library/MoveLibraryNodeDialog.vue');
|
||||||
const SelectCreaturesDialog = () => import('/imports/ui/tabletop/SelectCreaturesDialog.vue');
|
const SelectCreaturesDialog = () => import('/imports/ui/tabletop/SelectCreaturesDialog.vue');
|
||||||
const SelectLibraryNodeDialog = () => import('/imports/ui/library/SelectLibraryNodeDialog.vue');
|
|
||||||
const ShareDialog = () => import('/imports/ui/sharing/ShareDialog.vue');
|
const ShareDialog = () => import('/imports/ui/sharing/ShareDialog.vue');
|
||||||
const SlotFillDialog = () => import('/imports/ui/creature/slots/SlotFillDialog.vue');
|
|
||||||
const TierTooLowDialog = () => import('/imports/ui/user/TierTooLowDialog.vue');
|
|
||||||
const TransferOwnershipDialog = () => import('/imports/ui/sharing/TransferOwnershipDialog.vue');
|
|
||||||
const UsernameDialog = () => import('/imports/ui/user/UsernameDialog.vue');
|
const UsernameDialog = () => import('/imports/ui/user/UsernameDialog.vue');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -42,6 +46,7 @@ export default {
|
|||||||
DeleteUserAccountDialog,
|
DeleteUserAccountDialog,
|
||||||
ExperienceInsertDialog,
|
ExperienceInsertDialog,
|
||||||
ExperienceListDialog,
|
ExperienceListDialog,
|
||||||
|
HelpDialog,
|
||||||
InviteDialog,
|
InviteDialog,
|
||||||
LevelUpDialog,
|
LevelUpDialog,
|
||||||
LibraryCollectionCreationDialog,
|
LibraryCollectionCreationDialog,
|
||||||
|
|||||||
108
app/imports/ui/dialogStack/HelpDialog.vue
Normal file
108
app/imports/ui/dialogStack/HelpDialog.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<v-icon
|
||||||
|
slot="toolbar"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
|
mdi-help
|
||||||
|
</v-icon>
|
||||||
|
<v-toolbar-title slot="toolbar">
|
||||||
|
Help: {{ title }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div>
|
||||||
|
<v-progress-circular
|
||||||
|
v-if="!doc && !$subReady.docs"
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
size="32"
|
||||||
|
/>
|
||||||
|
<div v-else-if="!doc">
|
||||||
|
Help document not found for {{ title }}
|
||||||
|
</div>
|
||||||
|
<markdown-text
|
||||||
|
v-else
|
||||||
|
:markdown="doc"
|
||||||
|
@click="linkClick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<v-spacer slot="actions" />
|
||||||
|
<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 { propsByDocsPath } from '/imports/constants/PROPERTIES.js';
|
||||||
|
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
|
||||||
|
import Docs from '/imports/api/docs/Docs.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
MarkdownText,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
prop() {
|
||||||
|
return propsByDocsPath.get(this.path);
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
if (this.prop) {
|
||||||
|
return this.prop.name;
|
||||||
|
} else {
|
||||||
|
const titleCase = this.path.replace(
|
||||||
|
/(\w*)(\W+)/g,
|
||||||
|
function (txt, word) {
|
||||||
|
return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase() + ' ';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return titleCase || 'Character Sheet';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'docs'() {
|
||||||
|
return [this.path];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
doc() {
|
||||||
|
const doc = Docs.findOne(this.path);
|
||||||
|
return doc && doc.text;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
linkClick(e) {
|
||||||
|
const target = e.target || e.srcElement;
|
||||||
|
const href = target && target.href;
|
||||||
|
if (!href) return;
|
||||||
|
const path = href.split('/docs/')[1];
|
||||||
|
if (!path) return;
|
||||||
|
e.preventDefault();
|
||||||
|
target.dataset.id = path;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'help-dialog',
|
||||||
|
elementId: path,
|
||||||
|
data: {
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<template lang="html">
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
v-for="fn in functions"
|
|
||||||
:key="fn.name"
|
|
||||||
class="mb-3"
|
|
||||||
>
|
|
||||||
<h3>{{ fn.name }}</h3>
|
|
||||||
<div class="my-2">
|
|
||||||
{{ fn.comment }}
|
|
||||||
</div>
|
|
||||||
<table>
|
|
||||||
<tr
|
|
||||||
v-for="example in fn.examples"
|
|
||||||
:key="example.input"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
<code>{{ example.input }}</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<v-icon>mdi-arrow-right-thick</v-icon>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<code>{{ example.result }}</code>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="js">
|
|
||||||
import functions from '/imports/parser/functions.js';
|
|
||||||
export default {
|
|
||||||
computed:{
|
|
||||||
functions(){
|
|
||||||
let fns = [];
|
|
||||||
for (let name in functions){
|
|
||||||
let f = functions[name];
|
|
||||||
fns.push({name, ...f});
|
|
||||||
}
|
|
||||||
return fns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
</style>
|
|
||||||
@@ -44,10 +44,12 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
restoreLoading: false,
|
restoreLoading: false,
|
||||||
removeLoading: false,
|
removeLoading: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
characterSlots() {
|
characterSlots() {
|
||||||
return characterSlotsRemaining(Meteor.userId());
|
return characterSlotsRemaining(Meteor.userId());
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@@ -43,10 +42,12 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
restoreLoading: false,
|
restoreLoading: false,
|
||||||
removeLoading: false,
|
removeLoading: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
remove() {
|
remove() {
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
<router-view
|
<router-view name="toolbar" />
|
||||||
name="toolbar"
|
|
||||||
/>
|
|
||||||
<v-app-bar
|
<v-app-bar
|
||||||
v-if="!$route.matched[0] || !$route.matched[0].components.toolbar"
|
v-if="!$route.matched[0] || !$route.matched[0].components.toolbar"
|
||||||
app
|
app
|
||||||
@@ -19,22 +17,16 @@
|
|||||||
>
|
>
|
||||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||||
<v-toolbar-title>
|
<v-toolbar-title>
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<div :key="$store.state.pageTitle">
|
<div :key="$store.state.pageTitle">
|
||||||
{{ $store.state.pageTitle }}
|
{{ $store.state.pageTitle }}
|
||||||
</div>
|
</div>
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<div :key="$route.meta.title">
|
<div :key="$route.meta.title">
|
||||||
<router-view
|
<router-view name="toolbarItems" />
|
||||||
name="toolbarItems"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
<v-fade-transition
|
<v-fade-transition
|
||||||
@@ -45,22 +37,16 @@
|
|||||||
:key="$route.meta.title"
|
:key="$route.meta.title"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<router-view
|
<router-view name="toolbarExtension" />
|
||||||
name="toolbarExtension"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-main>
|
<v-main>
|
||||||
<v-fade-transition
|
<v-fade-transition mode="out-in">
|
||||||
mode="out-in"
|
|
||||||
>
|
|
||||||
<router-view />
|
<router-view />
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
</v-main>
|
</v-main>
|
||||||
<router-view
|
<router-view name="rightDrawer" />
|
||||||
name="rightDrawer"
|
|
||||||
/>
|
|
||||||
<dialog-stack />
|
<dialog-stack />
|
||||||
<snackbar-queue />
|
<snackbar-queue />
|
||||||
</v-app>
|
</v-app>
|
||||||
@@ -80,10 +66,12 @@
|
|||||||
DialogStack,
|
DialogStack,
|
||||||
SnackbarQueue,
|
SnackbarQueue,
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
tabs: 0,
|
tabs: 0,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
drawer: {
|
drawer: {
|
||||||
get() {
|
get() {
|
||||||
@@ -124,4 +112,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -99,6 +99,7 @@
|
|||||||
{title: 'Files', icon: 'mdi-file-multiple', to: '/my-files'},
|
{title: 'Files', icon: 'mdi-file-multiple', to: '/my-files'},
|
||||||
{title: 'Feedback', icon: 'mdi-bug', to: '/feedback'},
|
{title: 'Feedback', icon: 'mdi-bug', to: '/feedback'},
|
||||||
{title: 'About', icon: 'mdi-sign-text', to: '/about'},
|
{title: 'About', icon: 'mdi-sign-text', to: '/about'},
|
||||||
|
{title: 'Documentation', icon: 'mdi-book-open-variant', to: '/docs'},
|
||||||
{title: 'Patreon', icon: 'mdi-patreon', href: 'https://www.patreon.com/dicecloud'},
|
{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'},
|
{title: 'Github', icon: 'mdi-github', href: 'https://github.com/ThaumRystra/DiceCloud/tree/version-2'},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -144,4 +144,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions
|
|||||||
import { mapMutations } from 'vuex';
|
import { mapMutations } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
libraryCollection() {
|
libraryCollection() {
|
||||||
return LibraryCollections.findOne(this.$route.params.id);
|
return LibraryCollections.findOne(this.$route.params.id);
|
||||||
@@ -106,4 +108,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-fade-transition
|
<v-fade-transition hide-on-leave>
|
||||||
hide-on-leave
|
|
||||||
>
|
|
||||||
<tree-node-list
|
<tree-node-list
|
||||||
v-if="slowShouldSubscribe && $subReady.libraryNodes"
|
v-if="slowShouldSubscribe && $subReady.libraryNodes"
|
||||||
group="library"
|
group="library"
|
||||||
@@ -53,9 +51,11 @@
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data(){return {
|
data() {
|
||||||
|
return {
|
||||||
slowShouldSubscribe: this.shouldSubscribe,
|
slowShouldSubscribe: this.shouldSubscribe,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
shouldSubscribe(newValue) {
|
shouldSubscribe(newValue) {
|
||||||
if (this.timeoutId) {
|
if (this.timeoutId) {
|
||||||
@@ -132,4 +132,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
New Library
|
New Library
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
</template>
|
</template>
|
||||||
<template>
|
|
||||||
<text-field
|
<text-field
|
||||||
label="Name"
|
label="Name"
|
||||||
:value="library.name"
|
:value="library.name"
|
||||||
@@ -18,7 +17,6 @@
|
|||||||
:debounce-time="0"
|
:debounce-time="0"
|
||||||
@change="descriptionChanged"
|
@change="descriptionChanged"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
<template slot="actions">
|
<template slot="actions">
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -39,13 +37,15 @@
|
|||||||
components: {
|
components: {
|
||||||
DialogBase,
|
DialogBase,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data() {
|
||||||
|
return {
|
||||||
library: {
|
library: {
|
||||||
name: 'New Library',
|
name: 'New Library',
|
||||||
description: undefined,
|
description: undefined,
|
||||||
},
|
},
|
||||||
valid: true,
|
valid: true,
|
||||||
}},
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
nameChanged(val, ack) {
|
nameChanged(val, ack) {
|
||||||
if (val) {
|
if (val) {
|
||||||
@@ -66,4 +66,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -164,4 +164,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ export default {
|
|||||||
SelectablePropertyDialog,
|
SelectablePropertyDialog,
|
||||||
LibraryNodeInsertForm,
|
LibraryNodeInsertForm,
|
||||||
},
|
},
|
||||||
data() { return {
|
data() {
|
||||||
|
return {
|
||||||
type: undefined,
|
type: undefined,
|
||||||
};},
|
};
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyName,
|
getPropertyName,
|
||||||
},
|
},
|
||||||
@@ -28,4 +30,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user