Compare commits

...

27 Commits

Author SHA1 Message Date
Stefan Zermatten
93ac9215c2 Merge branch 'version-2-dev' into version-2 2022-10-10 16:53:10 +02:00
Stefan Zermatten
a6b501a62c Fixed error on missing group prop in tree node 2022-10-10 16:51:02 +02:00
Stefan Zermatten
e956bacf07 Added actionType to effective tags 2022-10-10 16:49:10 +02:00
Stefan Zermatten
60b6b283b1 Folders now get their children applied by actions 2022-10-10 16:45:53 +02:00
Stefan Zermatten
1c9b390551 Added ritual casting 2022-10-09 23:11:06 +02:00
Stefan Zermatten
21a487635d Removed unused code from action cards 2022-10-09 21:56:42 +02:00
Stefan Zermatten
c92a26d5e6 Action cards no longer display folder or the descendants of buffs 2022-10-09 21:56:01 +02:00
Stefan Zermatten
49b514b8f3 Load common dialogs more aggressively 2022-10-09 20:55:50 +02:00
Stefan Zermatten
5cb835c536 Got basic typescript tools working 2022-10-09 17:33:43 +02:00
Stefan Zermatten
aa8f2d230d Hunted the last of the \t's to extinction 2022-10-09 16:56:28 +02:00
Stefan Zermatten
2fa913b09a Applied style rules to genocide all \t characters 2022-10-09 16:01:36 +02:00
Stefan Zermatten
de598c70a7 Fixed rolled effects not applying to checks 2022-10-09 11:10:50 +02:00
Stefan Zermatten
baecdeff24 Fixed bug where items with zero quantity have active children 2022-10-09 10:10:21 +02:00
Stefan Zermatten
d4b7d22b5f Fixed toggled off spells showing in spell list 2022-09-26 09:43:00 +02:00
Stefan Zermatten
87f79737e8 Fixed empty calculated inline fields showing calc 2022-09-25 12:39:49 +02:00
Stefan Zermatten
9f0ffe13f8 Updated meteor to fix observer bugs 2022-09-13 17:34:46 +02:00
Stefan Zermatten
adaa31d76c damage tags to ignore multipliers 2022-09-13 17:34:30 +02:00
Stefan Zermatten
b051d764f8 Slot cards have slot color as outline 2022-09-13 15:47:31 +02:00
Stefan Zermatten
ffb5b4a4f3 Libraries show name in page title 2022-09-13 15:44:37 +02:00
Stefan Zermatten
fd87b7fb75 Added advantage popup to spell cast 2022-09-09 13:20:54 +02:00
Stefan Zermatten
f035902842 Removed unused file 2022-09-08 14:47:12 +02:00
Stefan Zermatten
dbc5f7253f Finished basic docs 2022-09-05 14:36:39 +02:00
Stefan Zermatten
f0e7253374 Updated docs 2022-09-01 13:33:28 +02:00
Stefan Zermatten
ffe37bf907 Added more property help docs 2022-09-01 12:18:29 +02:00
Stefan Zermatten
a63e2099d3 Added documentation UI and began documenting props 2022-08-31 14:43:38 +02:00
Stefan Zermatten
0308e4e7a7 Merge branch 'version-2' into version-2-dev 2022-08-29 11:30:55 +02:00
Stefan Zermatten
43f8df09f0 Fixed client crash when effects target calcs 2022-08-26 09:42:34 +02:00
280 changed files with 8796 additions and 6249 deletions

View File

@@ -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-beta280.7
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

View File

@@ -1 +1 @@
METEOR@2.7.3 METEOR@2.8-beta.7

View File

@@ -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
@@ -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-beta280.7
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-beta280.7
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-beta280.7
modules-runtime@0.13.0 modules-runtime@0.13.0
mongo@1.15.0 mongo@1.16.0-beta280.7
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-beta280.7
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

View File

@@ -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' },
}); });
}, },

View File

@@ -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,
}}); }
});
}, },
}); });

View File

@@ -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 = []

View 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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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);
} }

View File

@@ -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.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;

View File

@@ -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){
@@ -106,3 +111,21 @@ function rollCheck(prop, actionContext) {
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};
}

View File

@@ -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 = {};

View File

@@ -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;

View File

@@ -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');

View File

@@ -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';
/** /**

View File

@@ -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,11 +21,22 @@ 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,
// ancestors: linkedNode.data.ancestors, // ancestors: linkedNode.data.ancestors,
}); });
@@ -33,7 +46,7 @@ export default function aggregateEffect({node, linkedNode, link}){
// 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 // Skip aggregating if the result is not resolved completely
if (typeof result === 'string') return; if (typeof result === 'string' || result === undefined) 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':

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 };

View File

@@ -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){

View File

View File

@@ -0,0 +1,34 @@
import { propsByDocsPath } from '/imports/constants/PROPERTIES.js';
// Manual doc paths
const docPaths = [
'computed-fields',
'inline-calculations',
'dependency-loops',
'docs',
'tags',
];
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();
});

View File

@@ -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';

View File

@@ -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 },

View File

@@ -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;

View File

@@ -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;
} }

View File

@@ -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>

View File

@@ -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;
} }

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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],

View File

@@ -54,6 +54,19 @@
</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"
@click="$emit('duplicate')" @click="$emit('duplicate')"
@@ -137,6 +150,7 @@ 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: {
@@ -171,7 +185,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 +198,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>

View File

@@ -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>

View File

@@ -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;
} }

View File

@@ -149,7 +149,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 +267,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -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>

View File

@@ -28,8 +28,10 @@
default: undefined, default: undefined,
}, },
}, },
data(){ return { data() {
return {
expanded: false, expanded: false,
}}, }
},
}; };
</script> </script>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -56,4 +56,5 @@
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,7 +1,5 @@
<template lang="html"> <template lang="html">
<div <div class="stats-tab ma-2">
class="stats-tab ma-2"
>
<health-bar-card-container :creature-id="creatureId" /> <health-bar-card-container :creature-id="creatureId" />
<column-layout> <column-layout>
@@ -171,9 +169,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
@@ -253,18 +249,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"
@@ -420,9 +404,11 @@
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 } });
@@ -494,19 +480,6 @@
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;
}); });
}, },
}, },
@@ -542,9 +515,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 +537,5 @@
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -89,4 +89,5 @@
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -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>

View File

@@ -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>

View File

@@ -83,4 +83,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -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'],

View File

@@ -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;

View File

@@ -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'],

View File

@@ -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>

View File

@@ -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;
} }

View File

@@ -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,

View 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>

View File

@@ -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>

View File

@@ -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());

View File

@@ -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() {

View File

@@ -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>

View File

@@ -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'},
]; ];

View File

@@ -144,4 +144,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -164,4 +164,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -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>

View File

@@ -53,14 +53,21 @@ export default {
}, },
mixins: [schemaFormMixin], mixins: [schemaFormMixin],
props: { props: {
propertyName: String, propertyName: {
type: String, type: String,
default: undefined,
},
type: {
type: String,
default: undefined,
},
}, },
reactiveProvide: { reactiveProvide: {
name: 'context', name: 'context',
include: ['debounceTime', 'isLibraryForm'], include: ['debounceTime', 'isLibraryForm'],
}, },
data(){return { data() {
return {
model: { model: {
type: this.type, type: this.type,
}, },
@@ -68,7 +75,8 @@ export default {
validationContext: undefined, validationContext: undefined,
debounceTime: 0, debounceTime: 0,
isLibraryForm: true, isLibraryForm: true,
};}, };
},
watch: { watch: {
type(newType) { type(newType) {
if (!newType) return; if (!newType) return;
@@ -83,4 +91,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -30,8 +30,10 @@
DialogBase, DialogBase,
LibraryAndNode, LibraryAndNode,
}, },
data(){return { data() {
return {
node: undefined, node: undefined,
};}, };
},
}; };
</script> </script>

View File

@@ -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: {
library() { library() {
return Libraries.findOne(this.$route.params.id); return Libraries.findOne(this.$route.params.id);
@@ -106,4 +108,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -8,5 +8,5 @@ marked.setOptions({
silent: true, silent: true,
smartLists: true, smartLists: true,
smartypants: true, smartypants: true,
baseUrl: 'https://dicecloud.com', //baseUrl: 'https://dicecloud.com',
}); });

View File

@@ -0,0 +1,100 @@
<template>
<v-container class="documentation">
<v-row>
<v-col cols="12">
<v-fade-transition mode="out-in">
<v-card
v-if="doc"
:key="path"
>
<v-card-text>
<markdown-text
:markdown="doc"
@click="mdClick"
/>
</v-card-text>
</v-card>
<v-progress-circular
v-else-if="!$subReady.docs"
indeterminate
color="primary"
size="32"
/>
<v-card v-else-if="!doc">
<v-card-title>
Help document not found for {{ title }}
</v-card-title>
</v-card>
</v-fade-transition>
</v-col>
</v-row>
</v-container>
</template>
<script lang="js">
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
import Docs from '/imports/api/docs/Docs.js';
import { propsByDocsPath } from '/imports/constants/PROPERTIES.js';
export default {
components: {
MarkdownText,
},
data() {
return {
action: undefined,
};
},
computed: {
path() {
return this.$route.params.docPath || 'docs';
},
prop() {
return propsByDocsPath.get(this.path);
},
title() {
if (this.prop) {
return this.prop.name + ' Docs';
} else {
const titleCase = this.path.replace(
/(\w*)(\W+)/g,
function (txt, word) {
return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase() + ' ';
}
);
return titleCase || 'DiceCloud Docs';
}
}
},
meteor: {
$subscribe: {
'docs'() {
return [this.path];
},
},
doc() {
const doc = Docs.findOne(this.path);
return doc && doc.text;
},
},
watch: {
title: {
immediate: true,
handler(value) {
this.$store.commit('setPageTitle', value);
}
}
},
methods: {
mdClick(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();
this.$router.push('/docs/' + path);
}
}
}
</script>

View File

@@ -0,0 +1,56 @@
<template>
<v-container class="documentation">
<v-row>
<v-col cols="12">
<v-card>
<v-card-text class="markdown">
<h1>Functions</h1>
<div
v-for="fn in functions"
:key="fn.name"
class="mb-3"
>
<h3>{{ fn.name }}</h3>
<div class="my-2">
{{ fn.comment }}
</div>
<table style="min-width: initial;">
<tr
v-for="example in fn.examples"
:key="example.input"
>
<td>
<code>{{ example.input }}</code>
</td>
<td>
<v-icon>mdi-arrow-right</v-icon>
</td>
<td>
<code>{{ example.result }}</code>
</td>
</tr>
</table>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</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});
}
console.log(fns);
return fns;
}
}
}
</script>

View File

@@ -9,10 +9,27 @@
<script lang="js"> <script lang="js">
import SingleCardLayout from '/imports/ui/layouts/SingleCardLayout.vue'; import SingleCardLayout from '/imports/ui/layouts/SingleCardLayout.vue';
import LibraryAndNode from '/imports/ui/library/LibraryAndNode.vue'; import LibraryAndNode from '/imports/ui/library/LibraryAndNode.vue';
import Libraries from '/imports/api/library/Libraries.js';
export default { export default {
components: { components: {
SingleCardLayout, SingleCardLayout,
LibraryAndNode, LibraryAndNode,
}, },
watch: {
'library.name'(newName) {
this.$store.commit('setPageTitle', newName || 'Library');
},
},
mounted() {
this.$store.commit('setPageTitle', this.library && this.library.name || 'Library');
},
meteor: {
library(){
let libraryId = this.$route.params.id;
if (!libraryId) return;
return Libraries.findOne(libraryId, {fields: {name: 1}});
},
}
}; };
</script> </script>

View File

@@ -38,9 +38,7 @@
:disabled="model.insufficientResources || !context.editPermission" :disabled="model.insufficientResources || !context.editPermission"
@click.stop="doAction" @click.stop="doAction"
> >
<property-icon <property-icon :model="model" />
:model="model"
/>
</v-btn> </v-btn>
</div> </div>
<div <div
@@ -50,9 +48,7 @@
@mouseleave="hovering = false" @mouseleave="hovering = false"
@click="$emit('click')" @click="$emit('click')"
> >
<div <div class="action-title my-1">
class="action-title my-1"
>
{{ model.name || propertyName }} {{ model.name || propertyName }}
</div> </div>
<div class="action-sub-title layout align-center"> <div class="action-sub-title layout align-center">
@@ -89,16 +85,13 @@
/> />
</template> </template>
<template v-if="model.summary"> <template v-if="model.summary">
<markdown-text <markdown-text :markdown="model.summary.value || model.summary.text" />
:markdown="model.summary.value || model.summary.text"
/>
</template> </template>
<v-divder v-if="model.children && model.children.length" /> <v-divider v-if="children && children.length" />
<creature-properties-tree <tree-node-list
style="width: 100%;" v-if="children && children.length"
expanded start-expanded
:root="{collection: 'creatureProperties', id: model._id}" :children="children"
@length="childrenLength = $event"
@selected="e => $emit('sub-click', e)" @selected="e => $emit('sub-click', e)"
/> />
</div> </div>
@@ -117,7 +110,10 @@ import RollPopup from '/imports/ui/components/RollPopup.vue';
import MarkdownText from '/imports/ui/components/MarkdownText.vue'; import MarkdownText from '/imports/ui/components/MarkdownText.vue';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js'; import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
import CardHighlight from '/imports/ui/components/CardHighlight.vue'; import CardHighlight from '/imports/ui/components/CardHighlight.vue';
import CreaturePropertiesTree from '/imports/ui/creature/creatureProperties/CreaturePropertiesTree.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 { export default {
components: { components: {
@@ -127,7 +123,7 @@ export default {
PropertyIcon, PropertyIcon,
RollPopup, RollPopup,
CardHighlight, CardHighlight,
CreaturePropertiesTree, TreeNodeList,
}, },
inject: { inject: {
context: { context: {
@@ -145,11 +141,13 @@ export default {
required: true, required: true,
}, },
}, },
data(){return { data() {
return {
activated: undefined, activated: undefined,
doActionLoading: false, doActionLoading: false,
hovering: false, hovering: false,
}}, }
},
computed: { computed: {
rollBonus() { rollBonus() {
if (!this.model.attackRoll) return; if (!this.model.attackRoll) return;
@@ -174,6 +172,35 @@ export default {
return `$vuetify.icons.${this.model.actionType}`; return `$vuetify.icons.${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);
},
},
methods: { methods: {
click(e) { click(e) {
this.$emit('click', e); this.$emit('click', e);
@@ -209,9 +236,11 @@ export default {
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),
transform 0.075s ease; transform 0.075s ease;
} }
.action-card.active { .action-card.active {
transform: scale(0.92); transform: scale(0.92);
} }
.action-title { .action-title {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
@@ -225,6 +254,7 @@ export default {
transition: .3s cubic-bezier(.25, .8, .5, 1); transition: .3s cubic-bezier(.25, .8, .5, 1);
width: 100%; width: 100%;
} }
.action-sub-title { .action-sub-title {
color: #9e9e9e; color: #9e9e9e;
flex-grow: 0; flex-grow: 0;
@@ -236,15 +266,19 @@ export default {
text-overflow: ellipsis; text-overflow: ellipsis;
width: 100%; width: 100%;
} }
.action-child { .action-child {
height: 32px; height: 32px;
} }
.theme--light.muted-text { .theme--light.muted-text {
color: rgba(0, 0, 0, .3) !important; color: rgba(0, 0, 0, .3) !important;
} }
.theme--dark.muted-text { .theme--dark.muted-text {
color: hsla(0, 0%, 100%, .3) !important; color: hsla(0, 0%, 100%, .3) !important;
} }
.action-card { .action-card {
transition: transform 0.15s cubic; transition: transform 0.15s cubic;
} }
@@ -254,9 +288,11 @@ export default {
.action-card.theme--light.muted-text .v-icon { .action-card.theme--light.muted-text .v-icon {
color: rgba(0, 0, 0, .3) !important; color: rgba(0, 0, 0, .3) !important;
} }
.action-card.theme--dark.muted-text .v-icon { .action-card.theme--dark.muted-text .v-icon {
color: hsla(0, 0%, 100%, .3) !important; color: hsla(0, 0%, 100%, .3) !important;
} }
.action-card .property-description>p:last-of-type { .action-card .property-description>p:last-of-type {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@@ -69,9 +69,11 @@ export default {
props: { props: {
model: { type: Object, required: true }, model: { type: Object, required: true },
}, },
data(){return { data() {
return {
checkLoading: false, checkLoading: false,
}}, }
},
computed: { computed: {
hasClickListener() { hasClickListener() {
return this.$listeners && this.$listeners.click return this.$listeners && this.$listeners.click
@@ -113,21 +115,27 @@ export default {
.ability-list-tile { .ability-list-tile {
background: inherit; background: inherit;
} }
.ability-list-tile>>>.v-list__tile { .ability-list-tile>>>.v-list__tile {
height: 88px; height: 88px;
} }
.ability-list-tile>>>.v-list__tile__action--stack { .ability-list-tile>>>.v-list__tile__action--stack {
justify-content: center; justify-content: center;
} }
.value { .value {
font-weight: 600; font-weight: 600;
font-size: 24px !important; font-size: 24px !important;
color: rgba(0, 0, 0, 0.54); color: rgba(0, 0, 0, 0.54);
} }
.theme--dark .value { .theme--dark .value {
color: rgba(255, 255, 255, 0.54); color: rgba(255, 255, 255, 0.54);
} }
.mod, .value {
.mod,
.value {
text-align: center; text-align: center;
width: 100%; width: 100%;
min-width: 42px; min-width: 42px;

View File

@@ -104,9 +104,18 @@ export default {
}, },
}, },
props: { props: {
value: Number, value: {
maxValue: Number, type: Number,
name: String, default: undefined,
},
maxValue: {
type: Number,
default: undefined,
},
name: {
type: String,
default: undefined,
},
color: { color: {
type: String, type: String,
default() { default() {
@@ -196,6 +205,7 @@ export default {
.health-bar { .health-bar {
background: inherit; background: inherit;
} }
.name { .name {
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
@@ -204,52 +214,66 @@ export default {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
} }
.name:hover { .name:hover {
font-weight: 500; font-weight: 500;
} }
.bar { .bar {
transition: box-shadow 0.2s; transition: box-shadow 0.2s;
} }
.bar:hover { .bar:hover {
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12) !important; 0 1px 5px 0 rgba(0, 0, 0, 0.12) !important;
} }
.hover { .hover {
background: #f5f5f5 !important; background: #f5f5f5 !important;
} }
.theme--dark .hover { .theme--dark .hover {
background: #515151 !important; background: #515151 !important;
} }
.filled.theme--light { .filled.theme--light {
background: #fff !important; background: #fff !important;
} }
.filled.theme--dark { .filled.theme--dark {
background: #424242 !important; background: #424242 !important;
} }
.background-transition-enter-active, .background-transition-enter-active,
.background-transition-leave-active { .background-transition-leave-active {
transition: all 0.2s; transition: all 0.2s;
} }
.background-transition-enter, .background-transition-enter,
.background-transition-leave-to { .background-transition-leave-to {
opacity: 0; opacity: 0;
} }
.transition-enter-active { .transition-enter-active {
transition: all 0.2s; transition: all 0.2s;
} }
.transition-leave-active { .transition-leave-active {
transition: all 0.3s; transition: all 0.3s;
} }
.transition-enter-to, .transition-enter-to,
.transition-leave { .transition-leave {
opacity: 1; opacity: 1;
transform: scaleY(1) !important; transform: scaleY(1) !important;
} }
.transition-enter, .transition-enter,
.transition-leave-to { .transition-leave-to {
opacity: 0; opacity: 0;
transform: scaleY(0) !important; transform: scaleY(0) !important;
} }
.page-tint { .page-tint {
position: fixed; position: fixed;
top: 0; top: 0;

View File

@@ -31,9 +31,7 @@
</v-btn> </v-btn>
</v-layout> </v-layout>
<v-layout <v-layout align-end>
align-end
>
<div class="text-h4"> <div class="text-h4">
{{ model.value }} {{ model.value }}
</div> </div>
@@ -69,9 +67,11 @@ export default {
required: true, required: true,
} }
}, },
data(){ return{ data() {
return {
hover: false, hover: false,
}}, }
},
computed: { computed: {
signedConMod() { signedConMod() {
return numberToSignedString(this.model.constitutionMod); return numberToSignedString(this.model.constitutionMod);
@@ -92,30 +92,39 @@ export default {
.hit-dice-list-tile { .hit-dice-list-tile {
background: inherit; background: inherit;
} }
.hit-dice-list-tile>>>.v-list__tile { .hit-dice-list-tile>>>.v-list__tile {
height: 88px; height: 88px;
} }
.left { .left {
height: 100%; height: 100%;
} }
.buttons { .buttons {
height: 100%; height: 100%;
} }
.buttons>.v-btn { .buttons>.v-btn {
margin: 0; margin: 0;
} }
.hit-dice-list-tile.hover { .hit-dice-list-tile.hover {
background: #f5f5f5 !important; background: #f5f5f5 !important;
} }
.theme--dark .hit-dice-list-tile.hover { .theme--dark .hit-dice-list-tile.hover {
background: #515151 !important; background: #515151 !important;
} }
.content { .content {
cursor: pointer; cursor: pointer;
} }
.max-value { .max-value {
color: rgba(0, 0, 0, .54); color: rgba(0, 0, 0, .54);
} }
.theme--dark .max-value { .theme--dark .max-value {
color: rgba(255, 255, 255, 0.54); color: rgba(255, 255, 255, 0.54);
} }

View File

@@ -22,9 +22,7 @@
<v-icon>mdi-chevron-down</v-icon> <v-icon>mdi-chevron-down</v-icon>
</v-btn> </v-btn>
</div> </div>
<div <div class="layout align-center value pl-2 pr-3">
class="layout align-center value pl-2 pr-3"
>
<div class="text-h4"> <div class="text-h4">
{{ model.value }} {{ model.value }}
</div> </div>
@@ -66,9 +64,11 @@ import CardHighlight from '/imports/ui/components/CardHighlight.vue';
required: true, required: true,
} }
}, },
data(){ return{ data() {
return {
hover: false, hover: false,
}}, }
},
methods: { methods: {
click(e) { click(e) {
this.$emit('click', e); this.$emit('click', e);
@@ -84,23 +84,30 @@ import CardHighlight from '/imports/ui/components/CardHighlight.vue';
.resource-card { .resource-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);
} }
.resource-card>div { .resource-card>div {
padding-top: 16px; padding-top: 16px;
padding-bottom: 16px; padding-bottom: 16px;
} }
.buttons, .value {
.buttons,
.value {
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
} }
.buttons>.v-btn { .buttons>.v-btn {
margin: 0; margin: 0;
} }
.content { .content {
cursor: pointer; cursor: pointer;
} }
.max-value { .max-value {
color: rgba(0, 0, 0, .54); color: rgba(0, 0, 0, .54);
} }
.theme--dark .max-value { .theme--dark .max-value {
color: rgba(255, 255, 255, 0.54); color: rgba(255, 255, 255, 0.54);
} }

View File

@@ -7,9 +7,7 @@
v-on="hasClickListener ? {click} : {}" v-on="hasClickListener ? {click} : {}"
> >
<v-list-item-content> <v-list-item-content>
<v-list-item-title <v-list-item-title v-if="Number.isFinite(model.total)">
v-if="Number.isFinite(model.total)"
>
<div <div
v-if="model.total > 4" v-if="model.total > 4"
class="layout value" class="layout value"
@@ -84,22 +82,28 @@ export default {
.spell-slot-list-tile { .spell-slot-list-tile {
background: inherit; background: inherit;
} }
.v-list__tile__action { .v-list__tile__action {
width: 112px; width: 112px;
flex-shrink: 0; flex-shrink: 0;
} }
.spell-slot-list-tile.hover { .spell-slot-list-tile.hover {
background: #f5f5f5 !important; background: #f5f5f5 !important;
} }
.theme--dark .spell-slot-list-tile.hover { .theme--dark .spell-slot-list-tile.hover {
background: #515151 !important; background: #515151 !important;
} }
.content { .content {
cursor: pointer; cursor: pointer;
} }
.max-value { .max-value {
color: rgba(0, 0, 0, .54); color: rgba(0, 0, 0, .54);
} }
.theme--dark .max-value { .theme--dark .max-value {
color: rgba(255, 255, 255, 0.54); color: rgba(255, 255, 255, 0.54);
} }

View File

@@ -10,7 +10,7 @@
</v-toolbar-title> </v-toolbar-title>
<v-spacer /> <v-spacer />
</template> </template>
<v-card-text v-if="model.summary"> <v-card-text v-if="summaryText">
<property-description <property-description
text text
:model="model.summary" :model="model.summary"
@@ -34,6 +34,16 @@
required: true, required: true,
}, },
}, },
computed: {
summaryText() {
if (!this.model || !this.model.summary) return;
if (typeof this.model.summary.value === 'string') {
return this.model.summary.value;
} else {
return this.model.summary.text
}
},
}
}; };
</script> </script>

View File

@@ -30,9 +30,7 @@
> >
$vuetify.icons.two_coins $vuetify.icons.two_coins
</v-icon> </v-icon>
<coin-value <coin-value :value="value" />
:value="value"
/>
</v-toolbar-title> </v-toolbar-title>
</template> </template>
<v-card-text class="px-0"> <v-card-text class="px-0">
@@ -110,4 +108,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -52,9 +52,11 @@ export default {
preparingSpells: Boolean, preparingSpells: Boolean,
equipment: Boolean, equipment: Boolean,
}, },
data(){ return { data() {
return {
dataItems: [], dataItems: [],
}}, }
},
computed: { computed: {
levels() { levels() {
let levels = new Set(); let levels = new Set();

View File

@@ -62,9 +62,11 @@ export default {
props: { props: {
preparingSpells: Boolean, preparingSpells: Boolean,
}, },
data(){return { data() {
return {
incrementLoading: false, incrementLoading: false,
}}, }
},
computed: { computed: {
hasClickListener() { hasClickListener() {
return this.$listeners && !!this.$listeners.click; return this.$listeners && !!this.$listeners.click;
@@ -111,6 +113,7 @@ export default {
.item-avatar { .item-avatar {
min-width: 32px; min-width: 32px;
} }
.item { .item {
background-color: inherit; background-color: inherit;
} }

View File

@@ -48,9 +48,11 @@ export default {
required: true, required: true,
}, },
}, },
data(){ return{ data() {
return {
hover: false, hover: false,
}}, }
},
computed: { computed: {
isDark() { isDark() {
return isDarkColor(this.model.color); return isDarkColor(this.model.color);
@@ -69,4 +71,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -81,9 +81,11 @@ export default {
}, },
hideModifier: Boolean, hideModifier: Boolean,
}, },
data(){return { data() {
return {
checkLoading: false, checkLoading: false,
}}, }
},
computed: { computed: {
displayedModifier() { displayedModifier() {
let mod = this.model.value; let mod = this.model.value;
@@ -127,9 +129,11 @@ export default {
.prof-icon { .prof-icon {
min-width: 30px; min-width: 30px;
} }
.prof-mod { .prof-mod {
min-width: 32px; min-width: 32px;
} }
.v-icon.theme--light { .v-icon.theme--light {
color: rgba(0, 0, 0, 0.54) !important; color: rgba(0, 0, 0, 0.54) !important;
} }

View File

@@ -91,6 +91,20 @@
</v-list-item-title> </v-list-item-title>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
<v-list-item
key="ritual-dummy-slot"
class="spell-slot-list-tile"
:class="{ 'primary--text': selectedSlotId === 'ritual' }"
value="ritual"
:disabled="!canCastSpellWithSlot(selectedSpell, 'ritual')"
@click="selectedSlotId = 'ritual'"
>
<v-list-item-content>
<v-list-item-title>
Cast as ritual
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<spell-slot-list-tile <spell-slot-list-tile
v-for="spellSlot in spellSlots" v-for="spellSlot in spellSlots"
:key="spellSlot._id" :key="spellSlot._id"
@@ -105,13 +119,13 @@
</template> </template>
<template slot="right"> <template slot="right">
<div <div
key="spell-title" key="spell-title-right"
class="text-h6 my-3" class="text-h6 my-3"
> >
Spell Spell
</div> </div>
<v-list-item-group <v-list-item-group
key="slot-list" key="slot-list-right"
v-model="selectedSpellId" v-model="selectedSpellId"
> >
<template v-for="spell in computedSpells"> <template v-for="spell in computedSpells">
@@ -145,10 +159,24 @@
> >
Cancel Cancel
</v-btn> </v-btn>
<roll-popup
v-if="selectedSpell && selectedSpell.attackRoll"
text
color="primary"
class="mx-2"
:disabled="!canCast"
:name="selectedSpell.name"
:advantage="selectedSpell.attackRoll && selectedSpell.attackRoll.advantage"
@roll="cast"
>
Cast
</roll-popup>
<v-btn <v-btn
v-else
text text
:disabled="!canCast" :disabled="!canCast"
class="primary--text" class="mx-2 px-4"
color="primary"
@click="cast" @click="cast"
> >
Cast Cast
@@ -164,6 +192,7 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
import spellsWithSubheaders from '/imports/ui/properties/components/spells/spellsWithSubheaders.js'; import spellsWithSubheaders from '/imports/ui/properties/components/spells/spellsWithSubheaders.js';
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue'; import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue'; import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue';
import RollPopup from '/imports/ui/components/RollPopup.vue';
import { find } from 'lodash'; import { find } from 'lodash';
const slotFilter = { const slotFilter = {
@@ -178,6 +207,7 @@ const slotFilter = {
export default { export default {
components: { components: {
DialogBase, DialogBase,
RollPopup,
SplitListLayout, SplitListLayout,
SpellSlotListTile, SpellSlotListTile,
SpellListTile, SpellListTile,
@@ -196,7 +226,8 @@ export default {
default: undefined, default: undefined,
}, },
}, },
data(){ return { data() {
return {
searchString: undefined, searchString: undefined,
selectedSlotId: this.slotId, selectedSlotId: this.slotId,
selectedSpellId: this.spellId, selectedSpellId: this.spellId,
@@ -206,13 +237,14 @@ export default {
searchError: undefined, searchError: undefined,
filterMenuOpen: false, filterMenuOpen: false,
booleanFilters: { booleanFilters: {
verbal: {name: 'Verbal', enabled: false, value: false}, verbal: { name: 'Verbal', enabled: false, value: true },
somatic: {name: 'Somatic', enabled: false, value: false}, somatic: { name: 'Somatic', enabled: false, value: true },
material: {name: 'Material', enabled: false, value: false}, material: { name: 'Material', enabled: false, value: true },
concentration: {name: 'Concentration', enabled: false, value: false}, concentration: { name: 'Concentration', enabled: false, value: true },
ritual: {name: 'Ritual', enabled: false, value: false}, ritual: { name: 'Ritual', enabled: false, value: true },
},
}
}, },
}},
computed: { computed: {
computedSpells() { computedSpells() {
return spellsWithSubheaders(this.spells); return spellsWithSubheaders(this.spells);
@@ -242,13 +274,14 @@ export default {
selectedSpell: { selectedSpell: {
handler(spell) { handler(spell) {
if (!spell) return; if (!spell) return;
if(spell.level === 0 || spell.castWithoutSpellSlots){ if (this.selectedSlotId && this.canCastSpellWithSlot(
this.selectedSlotId = 'no-slot'; spell, this.selectedSlotId, this.selectedSlot
} else if ( )) return;
!this.selectedSlotId || if (
this.selectedSlotId == 'no-slot' || (spell.level === 0 || spell.castWithoutSpellSlots)
this.selectedSlot.spellSlotLevel.value < spell.level
) { ) {
this.selectedSlotId = 'no-slot';
} else {
const newSlot = find( const newSlot = find(
CreatureProperties.find({ CreatureProperties.find({
'ancestors.id': this.creatureId, 'ancestors.id': this.creatureId,
@@ -262,6 +295,8 @@ export default {
); );
if (newSlot) { if (newSlot) {
this.selectedSlotId = newSlot._id; this.selectedSlotId = newSlot._id;
} else if (spell.ritual) {
this.selectedSlotId = 'ritual';
} }
} }
}, },
@@ -314,24 +349,27 @@ export default {
spell.castWithoutSpellSlots && spell.castWithoutSpellSlots &&
spell.insufficientResources spell.insufficientResources
) return false; ) return false;
return (!spell.level || spell.castWithoutSpellSlots) ? ( if (spell.ritual && slotId === 'ritual') return true;
if (!spell.level || spell.castWithoutSpellSlots) {
// Cantrips and no-slot spells // Cantrips and no-slot spells
slotId && slotId === 'no-slot' return slotId && slotId === 'no-slot'
) : ( } else {
// Leveled spells // Leveled spells
slotId !== 'no-slot' && return slotId !== 'no-slot' && slot && spell && (
slot && spell && (
spell.level <= slot.spellSlotLevel.value spell.level <= slot.spellSlotLevel.value
) );
) }
}, },
cast(){ cast({ advantage }) {
let selectedSlotId = this.selectedSlotId; let selectedSlotId = this.selectedSlotId;
if (selectedSlotId === 'no-slot') selectedSlotId = undefined; const ritual = selectedSlotId === 'ritual';
if (selectedSlotId === 'no-slot' || selectedSlotId === 'ritual') selectedSlotId = undefined;
this.$store.dispatch('popDialogStack', { this.$store.dispatch('popDialogStack', {
spellId: this.selectedSpellId, spellId: this.selectedSpellId,
slotId: selectedSlotId, slotId: selectedSlotId,
}) advantage,
ritual,
});
} }
}, },
meteor: { meteor: {
@@ -384,6 +422,7 @@ export default {
.v-list { .v-list {
flex-basis: 200px; flex-basis: 200px;
} }
.v-list.spells { .v-list.spells {
flex-grow: 1; flex-grow: 1;
} }

View File

@@ -62,9 +62,11 @@ export default {
}, },
preparingSpells: Boolean, preparingSpells: Boolean,
}, },
data(){ return { data() {
return {
dataSpells: [], dataSpells: [],
}}, }
},
computed: { computed: {
levels() { levels() {
let levels = new Set(); let levels = new Set();
@@ -126,4 +128,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -74,9 +74,11 @@ export default {
}, },
organize: Boolean, organize: Boolean,
}, },
data(){ return { data() {
return {
preparingSpells: false, preparingSpells: false,
}}, }
},
meteor: { meteor: {
spells() { spells() {
let filter = { let filter = {
@@ -128,4 +130,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -99,12 +99,16 @@ export default {
.spell-avatar { .spell-avatar {
min-width: 32px; min-width: 32px;
} }
.spell { .spell {
background-color: inherit; background-color: inherit;
} }
.primary--text .v-icon, .primary--text .v-list__tile__sub-title {
.primary--text .v-icon,
.primary--text .v-list__tile__sub-title {
color: #b71c1c color: #b71c1c
} }
.theme--light.info-icon { .theme--light.info-icon {
color: rgba(0, 0, 0, .54) !important; color: rgba(0, 0, 0, .54) !important;
} }

View File

@@ -87,14 +87,16 @@ export default {
default: undefined, default: undefined,
}, },
}, },
data(){return { data() {
return {
adjustmentOps: [ adjustmentOps: [
{ text: 'Damage', value: 'increment' }, { text: 'Damage', value: 'increment' },
{ text: 'Set', value: 'set' }, { text: 'Set', value: 'set' },
], ],
damageHint: 'The amount of damage to apply to the selected stat, can be a calculation or roll. Negative values will restore the selected from previous damage. If the operation is set, this is the final value of the stat instead.', damageHint: 'The amount of damage to apply to the selected stat, can be a calculation or roll. Negative values will restore the selected from previous damage. If the operation is set, this is the final value of the stat instead.',
setHint: 'The value of the stat after applying this adjustment. The stat\'s value can\'t exceed its total', setHint: 'The value of the stat after applying this adjustment. The stat\'s value can\'t exceed its total',
}}, }
},
computed: { computed: {
targetOptions() { targetOptions() {
if (this.parentTarget === 'singleTarget') { if (this.parentTarget === 'singleTarget') {
@@ -137,4 +139,5 @@ export default {
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -140,9 +140,7 @@
<slot name="children" /> <slot name="children" />
</form-section> </form-section>
<form-section <form-section name="Advanced">
name="Advanced"
>
<smart-combobox <smart-combobox
label="Tags" label="Tags"
multiple multiple
@@ -291,9 +289,11 @@
.no-flex { .no-flex {
flex: initial; flex: initial;
} }
.layout.row.wrap { .layout.row.wrap {
margin-right: -8px; margin-right: -8px;
} }
.layout.row.wrap>* { .layout.row.wrap>* {
margin-right: 8px; margin-right: 8px;
} }

View File

@@ -64,7 +64,8 @@
default: undefined, default: undefined,
}, },
}, },
data(){return { data() {
return {
typeOptions: [ typeOptions: [
{ value: 'if', text: 'If condition is true' }, { value: 'if', text: 'If condition is true' },
{ value: 'hit', text: 'Attack hit' }, { value: 'hit', text: 'Attack hit' },
@@ -75,7 +76,8 @@
{ value: 'random', text: 'Random' }, { value: 'random', text: 'Random' },
{ value: 'index', text: 'Calculated index' }, { value: 'index', text: 'Calculated index' },
], ],
}}, }
},
computed: { computed: {
typeHint() { typeHint() {
switch (this.model.branchType) { switch (this.model.branchType) {
@@ -95,4 +97,5 @@
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -44,9 +44,7 @@
> >
<slot name="children" /> <slot name="children" />
</form-section> </form-section>
<form-section <form-section name="Advanced">
name="Advanced"
>
<v-row dense> <v-row dense>
<v-col <v-col
cols="12" cols="12"
@@ -104,7 +102,8 @@
export default { export default {
mixins: [propertyFormMixin], mixins: [propertyFormMixin],
data(){return { data() {
return {
targetOptions: [ targetOptions: [
{ {
text: 'Self', text: 'Self',
@@ -114,9 +113,11 @@
value: 'target', value: 'target',
}, },
], ],
}}, }
},
} }
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -1,8 +1,6 @@
<template lang="html"> <template lang="html">
<div class="class-form"> <div class="class-form">
<v-row <v-row dense>
dense
>
<v-col <v-col
cols="12" cols="12"
md="6" md="6"
@@ -45,9 +43,7 @@
<slot name="children" /> <slot name="children" />
</form-section> </form-section>
<form-section <form-section name="Advanced">
name="Advanced"
>
<smart-combobox <smart-combobox
label="Tags" label="Tags"
hint="This class's own tags" hint="This class's own tags"

View File

@@ -77,4 +77,5 @@
</script> </script>
<style lang="css" scoped> <style lang="css" scoped>
</style> </style>

View File

@@ -63,9 +63,7 @@
<slot name="children" /> <slot name="children" />
</form-section> </form-section>
<form-section <form-section name="Advanced">
name="Advanced"
>
<smart-combobox <smart-combobox
label="Tags" label="Tags"
multiple multiple

Some files were not shown because too many files have changed in this diff Show More