From 35464128a01b2ca8686de0faed4a06f6bfa445e4 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 10 Jul 2017 16:26:38 +0200 Subject: [PATCH 01/17] Added basic export for improved initiative --- rpg-docs/Model/Character/Characters.js | 3 + .../views/character/characterSheet.html | 4 + .../client/views/character/characterSheet.js | 7 + .../export/exportDialog/exportDialog.html | 29 +++ .../export/exportDialog/exportDialog.js | 45 +++++ .../export/improvedInitiativeJson.js | 191 ++++++++++++++++++ 6 files changed, 279 insertions(+) create mode 100644 rpg-docs/client/views/character/export/exportDialog/exportDialog.html create mode 100644 rpg-docs/client/views/character/export/exportDialog/exportDialog.js create mode 100644 rpg-docs/client/views/character/export/improvedInitiativeJson.js diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js index 20e717e1..0b807d34 100644 --- a/rpg-docs/Model/Character/Characters.js +++ b/rpg-docs/Model/Character/Characters.js @@ -184,6 +184,9 @@ Schemas.Character = new SimpleSchema({ defaultValue: "whitelist", allowedValues: ["whitelist", "public"], }, + "settings.exportFeatures": {type: Boolean, defaultValue: true}, + "settings.exportAttacks": {type: Boolean, defaultValue: true}, + "settings.exportDescription": {type: Boolean, defaultValue: true}, }); Characters.attachSchema(Schemas.Character); diff --git a/rpg-docs/client/views/character/characterSheet.html b/rpg-docs/client/views/character/characterSheet.html index bb9ce825..cc6f11e3 100644 --- a/rpg-docs/client/views/character/characterSheet.html +++ b/rpg-docs/client/views/character/characterSheet.html @@ -24,6 +24,10 @@ Settings + + + Export to Improved Initiative + {{/if}} diff --git a/rpg-docs/client/views/character/characterSheet.js b/rpg-docs/client/views/character/characterSheet.js index 006c42f4..c7052c44 100644 --- a/rpg-docs/client/views/character/characterSheet.js +++ b/rpg-docs/client/views/character/characterSheet.js @@ -203,4 +203,11 @@ Template.characterSheet.events({ element: event.currentTarget.parentElement.parentElement, }); }, + "click #characterExport": function(event, instance){ + pushDialogStack({ + data: this, + template: "exportDialog", + element: event.currentTarget.parentElement.parentElement, + }); + }, }); diff --git a/rpg-docs/client/views/character/export/exportDialog/exportDialog.html b/rpg-docs/client/views/character/export/exportDialog/exportDialog.html new file mode 100644 index 00000000..4f575ed1 --- /dev/null +++ b/rpg-docs/client/views/character/export/exportDialog/exportDialog.html @@ -0,0 +1,29 @@ + diff --git a/rpg-docs/client/views/character/export/exportDialog/exportDialog.js b/rpg-docs/client/views/character/export/exportDialog/exportDialog.js new file mode 100644 index 00000000..e961c33d --- /dev/null +++ b/rpg-docs/client/views/character/export/exportDialog/exportDialog.js @@ -0,0 +1,45 @@ +Template.exportDialog.helpers({ + character: function(){ + return Characters.findOne(this._id); + }, + improvedInitiativeJson: function(){ + var options = { + features: this.settings.exportFeatures, + attacks: this.settings.exportAttacks, + description: this.settings.exportDescription, + } + return improvedInitiativeJson(this._id, options); + }, +}); + +Template.exportDialog.events({ + "change #exportFeatures": function(event, template){ + Characters.update(this._id, {$set: { + "settings.exportFeatures": event.target.checked, + }}); + }, + "change #exportAttacks": function(event, template){ + Characters.update(this._id, {$set: { + "settings.exportAttacks": event.target.checked, + }}); + }, + "change #exportDescription": function(event, template){ + Characters.update(this._id, {$set: { + "settings.exportDescription": event.target.checked, + }}); + }, + "click #copyExportButton": function(event, template){ + var copyTextarea = template.find(".iiexport"); + copyTextarea && copyTextarea.select(); + try { + var successful = document.execCommand("copy"); + var msg = successful ? "successful" : "unsuccessful"; + console.log("Copying text command was " + msg); + } catch (err) { + console.log("Oops, unable to copy"); + } + }, + "click .doneButton": function(event, instance){ + popDialogStack(); + }, +}) diff --git a/rpg-docs/client/views/character/export/improvedInitiativeJson.js b/rpg-docs/client/views/character/export/improvedInitiativeJson.js new file mode 100644 index 00000000..d5e3298e --- /dev/null +++ b/rpg-docs/client/views/character/export/improvedInitiativeJson.js @@ -0,0 +1,191 @@ +improvedInitiativeJson = function(charId, options){ + options = options || { + features: true, + attacks: true, + description: true, + }; + var char = Characters.findOne(charId); + if (!char) return; + var baseValue = function(attributeName){ + return Characters.calculate.attributeBase(charId, attributeName); + }; + var skillMod = function(skillName){ + return Characters.calculate.skillMod(charId, skillName); + }; + var damageMods = getDamageMods(charId); + return JSON.stringify({ + "Id": char._id, + "Name": char.name, + "Source": "DiceCloud", + "Type": char.race, + "HP": { + "Value": baseValue("hitPoints"), + "Notes": getHitDiceString(charId) || "", + }, + "AC": { + "Value": baseValue("armor"), + "Notes": getArmorString(charId) || "", + }, + "InitiativeModifier": skillMod("initiative"), + "Speed": ["" + baseValue("speed")], + "Abilities": { + "Str": baseValue("strength"), + "Dex": baseValue("dexterity"), + "Con": baseValue("constitution"), + "Cha": baseValue("charisma"), + "Int": baseValue("intelligence"), + "Wis": baseValue("wisdom"), + }, + "DamageVulnerabilities": damageMods.vulnerabilities, + "DamageResistances": damageMods.resistances, + "DamageImmunities": damageMods.immunities, + "ConditionImmunities": [], + "Saves": [ + {"Name": "Str", "Modifier": skillMod("strengthSave")}, + {"Name": "Dex", "Modifier": skillMod("dexteritySave")}, + {"Name": "Con", "Modifier": skillMod("constitutionSave")}, + {"Name": "Int", "Modifier": skillMod("intelligenceSave")}, + {"Name": "Wis", "Modifier": skillMod("wisdomSave")}, + {"Name": "Cha", "Modifier": skillMod("charismaSave")}, + ], + "Skills": getSkills(charId), + "Senses": [], + "Languages": getLanguages(charId), + "Challenge": "", + "Traits": options.features ? getTraits(charId) : [], + "Actions": options.attacks ? getActions(charId) : [], + "Reactions": [], + "LegendaryActions": [], + "Description": options.description ? char.description : "", + "Player": Meteor.user().username, + }, null, 2); +} + +var getHitDiceString = function(charId){ + var d6 = Characters.calculate.attributeBase(charId, "d6HitDice"); + var d8 = Characters.calculate.attributeBase(charId, "d8HitDice"); + var d10 = Characters.calculate.attributeBase(charId, "d10HitDice"); + var d12 = Characters.calculate.attributeBase(charId, "d12HitDice"); + var con = Characters.calculate.abilityMod(charId,"constitution"); + var string = "" + + (d6 ? `${d6}d6 + ` : "") + + (d8 ? `${d8}d8 + ` : "") + + (d10 ? `${d10}d10 + ` : "") + + (d12 ? `${d12}d12 + ` : "") + + con; +} + +var getArmorString = function(charId){ + var bases = Effects.find({ + charId: charId, + stat: "armor", + operation: "base", + enabled: true, + }).map(e => ({ + ame: e.name, + value: evaluateEffect(charId, e), + })); + var base = bases.length && _.max(bases, b => b.value).name || ""; + var effects = Effects.find({ + charId: charId, + stat: "armor", + operation: {$ne: "base"}, + enabled: true, + }).map(e => e.name); + var strings = base ? [base] : []; + strings = strings.concat(effects); + return strings.join(", "); +} + +var getDamageMods = function(charId){ + // jscs:disable maximumLineLength + var multipliers = [ + {name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")}, + {name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")}, + {name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")}, + {name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")}, + {name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")}, + {name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")}, + {name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")}, + {name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")}, + {name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")}, + {name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")}, + {name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")}, + {name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")}, + {name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")}, + ]; + // jscs:enable maximumLineLength + multipliers = _.groupBy(multipliers, "value"); + return { + "immunities": multipliers["0"] || [], + "resistances": multipliers["0.5"] || [], + "weaknesses": multipliers["2"] || [], + }; +} + +var getSkills = function(charId){ + var allSkills = [ + {name: "acrobatics", attribute: "dexterity"}, + {name: "animalHandling", attribute: "wisdom"}, + {name: "arcana", attribute: "intelligence"}, + {name: "athletics", attribute: "strength"}, + {name: "deception", attribute: "charisma"}, + {name: "history", attribute: "intelligence"}, + {name: "insight", attribute: "wisdom"}, + {name: "intimidation", attribute: "charisma"}, + {name: "investigation", attribute: "intelligence"}, + {name: "medicine", attribute: "wisdom"}, + {name: "nature", attribute: "intelligence"}, + {name: "perception", attribute: "wisdom"}, + {name: "performance", attribute: "charisma"}, + {name: "persuasion", attribute: "charisma"}, + {name: "religion", attribute: "intelligence"}, + {name: "sleightOfHand", attribute: "dexterity"}, + {name: "stealth", attribute: "dexterity"}, + {name: "survival", attribute: "wisdom"}, + ]; + var skills = []; + _.each(allSkills, skill => { + var value = Characters.calculate.skillMod(charId, skill.name); + var mod = Characters.calculate.abilityMod(charId, skill.attribute); + if (value !== mod){ + skills.push({Name: skill.name, Modifier: value}); + } + }); + return skills; +}; + +var getLanguages = function(charId){ + return Proficiencies.find({ + charId, + enabled: true, + type: "language", + }).map(l => l.name); +}; + +var getTraits = function(charId){ + return Features.find( + {charId: charId}, + {sort: {color: 1, name: 1}} + ).map(f => ({ + Name: f.name, + // evaluateShortString helper + Content: evaluateString( + charId, f.description && f.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0] + ) || "", + Usage: "", + })); +} + +var getActions = function(charId){ + return Attacks.find( + {charId, enabled: true}, + {sort: {color: 1, name: 1}} + ).map(a => ({ + Name: a.name, + Content: `+${evaluate(charId, a.attackBonus)} to hit, ` + + `${evaluateString(charId, a.damage)} ${a.damageType} damage, ` + + `${a.details}`, + Usage: "", + })); +} From 4a6fca98bc366d085b2d8d9598ee02c51e856b96 Mon Sep 17 00:00:00 2001 From: Thaum Rystra Date: Mon, 10 Jul 2017 20:59:00 +0200 Subject: [PATCH 02/17] Cleaned up export dialog, fixed copying --- .../export/exportDialog/exportDialog.css | 4 ++ .../export/exportDialog/exportDialog.html | 44 ++++++++++--------- .../export/exportDialog/exportDialog.js | 25 ++++++++--- 3 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 rpg-docs/client/views/character/export/exportDialog/exportDialog.css diff --git a/rpg-docs/client/views/character/export/exportDialog/exportDialog.css b/rpg-docs/client/views/character/export/exportDialog/exportDialog.css new file mode 100644 index 00000000..5a701686 --- /dev/null +++ b/rpg-docs/client/views/character/export/exportDialog/exportDialog.css @@ -0,0 +1,4 @@ +.exportDialog .iiexport { + overflow-y: auto; + width: 100% !important; +} diff --git a/rpg-docs/client/views/character/export/exportDialog/exportDialog.html b/rpg-docs/client/views/character/export/exportDialog/exportDialog.html index 4f575ed1..78ba88a9 100644 --- a/rpg-docs/client/views/character/export/exportDialog/exportDialog.html +++ b/rpg-docs/client/views/character/export/exportDialog/exportDialog.html @@ -1,26 +1,28 @@ diff --git a/rpg-docs/lib/functions/evaluate.js b/rpg-docs/lib/functions/evaluate.js index 9fce8c79..77cb34bb 100644 --- a/rpg-docs/lib/functions/evaluate.js +++ b/rpg-docs/lib/functions/evaluate.js @@ -8,7 +8,8 @@ })(); //evaluates a calculation string -evaluate = function(charId, string){ +evaluate = function(charId, string, opts){ + var spellListId = opts && opts.spellListId; if (!string) return string; string = string.replace(/\b[a-z]+\b/gi, function(sub){ //fields @@ -43,6 +44,12 @@ evaluate = function(charId, string){ if (sub.toUpperCase() === "LEVEL"){ return Characters.calculate.level(charId); } + if (spellListId && sub.toUpperCase() === "DC") { + var list = SpellLists.findOne(spellListId); + if (list && list.saveDC){ + return evaluate(charId, list.saveDC); + } + } return sub; }); try { @@ -66,6 +73,17 @@ evaluateString = function(charId, string){ return result; }; +evaluateSpellString = function (charId, spellListId, string) { + //define brackets as curly brackets around anything that isn't a curly bracket + if (!string) return string; + var brackets = /\{[^\{\}]*\}/g; + var result = string.replace(brackets, function(exp){ + exp = exp.replace(/(\{|\})/g, ""); //remove curly brackets + return evaluate(charId, exp, {spellListId}); + }); + return result; +} + //returns the value of the effect if it exists, //otherwise returns the result of the calculation if it exists, //otherwise returns 0 From 99c72d1e109e041c854473c9f95ca461d86aa6c2 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 10:39:43 +0200 Subject: [PATCH 08/17] Made names optional for all collections closes #92 --- rpg-docs/Model/Character/Actions.js | 2 ++ rpg-docs/Model/Character/Attacks.js | 1 + rpg-docs/Model/Character/Buffs.js | 1 + rpg-docs/Model/Character/Classes.js | 2 +- rpg-docs/Model/Character/Experience.js | 2 +- rpg-docs/Model/Character/Features.js | 2 +- rpg-docs/Model/Character/Notes.js | 2 +- rpg-docs/Model/Character/SpellLists.js | 2 +- rpg-docs/Model/Character/Spells.js | 1 + rpg-docs/Model/Inventory/Containers.js | 2 +- rpg-docs/Model/Inventory/Items.js | 2 +- 11 files changed, 12 insertions(+), 7 deletions(-) diff --git a/rpg-docs/Model/Character/Actions.js b/rpg-docs/Model/Character/Actions.js index a4e2e236..31caa805 100644 --- a/rpg-docs/Model/Character/Actions.js +++ b/rpg-docs/Model/Character/Actions.js @@ -11,10 +11,12 @@ Schemas.Action = new SimpleSchema({ }, name: { type: String, + optional: true, trim: false, }, description: { type: String, + optional: true, trim: false, }, type: { diff --git a/rpg-docs/Model/Character/Attacks.js b/rpg-docs/Model/Character/Attacks.js index 2450439a..4a2dcc72 100644 --- a/rpg-docs/Model/Character/Attacks.js +++ b/rpg-docs/Model/Character/Attacks.js @@ -12,6 +12,7 @@ Schemas.Attack = new SimpleSchema({ name: { type: String, defaultValue: "New Attack", + optional: true, trim: false, }, details: { diff --git a/rpg-docs/Model/Character/Buffs.js b/rpg-docs/Model/Character/Buffs.js index 3d8c41c5..b2a367c2 100644 --- a/rpg-docs/Model/Character/Buffs.js +++ b/rpg-docs/Model/Character/Buffs.js @@ -8,6 +8,7 @@ Schemas.Buff = new SimpleSchema({ }, name: { type: String, + optional: true, trim: false, }, description: { diff --git a/rpg-docs/Model/Character/Classes.js b/rpg-docs/Model/Character/Classes.js index ac036230..52e4df9e 100644 --- a/rpg-docs/Model/Character/Classes.js +++ b/rpg-docs/Model/Character/Classes.js @@ -2,7 +2,7 @@ Classes = new Mongo.Collection("classes"); Schemas.Class = new SimpleSchema({ charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, - name: {type: String, trim: false}, + name: {type: String, optional: true, trim: false}, level: {type: Number}, createdAt: { type: Date, diff --git a/rpg-docs/Model/Character/Experience.js b/rpg-docs/Model/Character/Experience.js index ff6d5932..4dc22b2a 100644 --- a/rpg-docs/Model/Character/Experience.js +++ b/rpg-docs/Model/Character/Experience.js @@ -2,7 +2,7 @@ Experiences = new Mongo.Collection("experience"); Schemas.Experience = new SimpleSchema({ charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, - name: {type: String, defaultValue: "New Experience", trim: false}, + name: {type: String, optional: true, trim: false, defaultValue: "New Experience"}, description: {type: String, optional: true, trim: false}, value: {type: Number, defaultValue: 0}, dateAdded: { diff --git a/rpg-docs/Model/Character/Features.js b/rpg-docs/Model/Character/Features.js index 29b8c7d5..468d6fe3 100644 --- a/rpg-docs/Model/Character/Features.js +++ b/rpg-docs/Model/Character/Features.js @@ -2,7 +2,7 @@ Features = new Mongo.Collection("features"); Schemas.Feature = new SimpleSchema({ charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, - name: {type: String, trim: false}, + name: {type: String, optional: true, trim: false}, description: {type: String, optional: true, trim: false}, uses: {type: String, optional: true, trim: false}, used: {type: Number, defaultValue: 0}, diff --git a/rpg-docs/Model/Character/Notes.js b/rpg-docs/Model/Character/Notes.js index 8d1afdf5..f1ce7aa4 100644 --- a/rpg-docs/Model/Character/Notes.js +++ b/rpg-docs/Model/Character/Notes.js @@ -2,7 +2,7 @@ Notes = new Mongo.Collection("notes"); Schemas.Note = new SimpleSchema({ charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, - name: {type: String, trim: false}, + name: {type: String, optional: true, trim: false}, description: {type: String, optional: true, trim: false}, color: { type: String, diff --git a/rpg-docs/Model/Character/SpellLists.js b/rpg-docs/Model/Character/SpellLists.js index 16aa2dab..982be871 100644 --- a/rpg-docs/Model/Character/SpellLists.js +++ b/rpg-docs/Model/Character/SpellLists.js @@ -2,7 +2,7 @@ SpellLists = new Mongo.Collection("spellLists"); Schemas.SpellLists = new SimpleSchema({ charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, - name: {type: String, trim: false}, + name: {type: String, optional: true, trim: false}, description: {type: String, optional: true, trim: false}, saveDC: {type: String, optional: true, trim: false}, attackBonus: {type: String, optional: true, trim: false}, diff --git a/rpg-docs/Model/Character/Spells.js b/rpg-docs/Model/Character/Spells.js index f04e2fa7..58f21cd2 100644 --- a/rpg-docs/Model/Character/Spells.js +++ b/rpg-docs/Model/Character/Spells.js @@ -9,6 +9,7 @@ Schemas.Spell = new SimpleSchema({ }, name: { type: String, + optional: true, trim: false, defaultValue: "New Spell", }, diff --git a/rpg-docs/Model/Inventory/Containers.js b/rpg-docs/Model/Inventory/Containers.js index 94761c58..2318c930 100644 --- a/rpg-docs/Model/Inventory/Containers.js +++ b/rpg-docs/Model/Inventory/Containers.js @@ -2,7 +2,7 @@ Containers = new Mongo.Collection("containers"); Schemas.Container = new SimpleSchema({ - name: {type: String, trim: false}, + name: {type: String, optional: true, trim: false}, charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, isCarried: {type: Boolean}, weight: {type: Number, min: 0, defaultValue: 0, decimal: true}, diff --git a/rpg-docs/Model/Inventory/Items.js b/rpg-docs/Model/Inventory/Items.js index c626c16f..6c5c00e1 100644 --- a/rpg-docs/Model/Inventory/Items.js +++ b/rpg-docs/Model/Inventory/Items.js @@ -1,7 +1,7 @@ Items = new Mongo.Collection("items"); Schemas.Item = new SimpleSchema({ - name: {type: String, defaultValue: "New Item", trim: false}, + name: {type: String, optional: true, trim: false, defaultValue: "New Item"}, plural: {type: String, optional: true, trim: false}, description:{type: String, optional: true, trim: false}, charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner From 089feae26f4a93fc5ad886423f909e0d00e1e902 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 10:56:42 +0200 Subject: [PATCH 09/17] Spell slots are now available in calculations closes #11 --- rpg-docs/lib/functions/evaluate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpg-docs/lib/functions/evaluate.js b/rpg-docs/lib/functions/evaluate.js index 77cb34bb..a66eed27 100644 --- a/rpg-docs/lib/functions/evaluate.js +++ b/rpg-docs/lib/functions/evaluate.js @@ -11,7 +11,7 @@ evaluate = function(charId, string, opts){ var spellListId = opts && opts.spellListId; if (!string) return string; - string = string.replace(/\b[a-z]+\b/gi, function(sub){ + string = string.replace(/\b[a-z,1-9]+\b/gi, function(sub){ //fields if (Schemas.Character.schema(sub)){ return Characters.calculate.fieldValue(charId, sub); From 3227cd093472cc33d23eb5e4cbf5c3f3b4878739 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 13:14:04 +0200 Subject: [PATCH 10/17] Add character names to the end of their URL's Closes #21, makes links to characters human readable --- rpg-docs/.meteor/packages | 1 + rpg-docs/.meteor/versions | 1 + rpg-docs/Model/Character/Characters.js | 7 ++++++ rpg-docs/Routes/Routes.js | 24 +++++++++++++++---- .../views/characterList/characterList.html | 2 +- .../views/characterList/characterList.js | 10 +++++++- .../views/characterList/characterSideList.js | 2 +- rpg-docs/server/migrations/migrations.js | 20 ++++++++++++++-- rpg-docs/server/publications/characterList.js | 1 + 9 files changed, 59 insertions(+), 9 deletions(-) diff --git a/rpg-docs/.meteor/packages b/rpg-docs/.meteor/packages index 0928a7e4..89495d83 100644 --- a/rpg-docs/.meteor/packages +++ b/rpg-docs/.meteor/packages @@ -48,3 +48,4 @@ es5-shim@4.6.15 differential:vulcanize reactive-dict percolate:synced-cron +ongoworks:speakingurl diff --git a/rpg-docs/.meteor/versions b/rpg-docs/.meteor/versions index 04f53178..c2254df9 100644 --- a/rpg-docs/.meteor/versions +++ b/rpg-docs/.meteor/versions @@ -85,6 +85,7 @@ npm-mongo@2.2.16_1 oauth@1.1.12 oauth2@1.1.11 observe-sequence@1.0.14 +ongoworks:speakingurl@9.0.0 ordered-dict@1.0.9 percolate:migrations@0.9.8 percolate:synced-cron@1.3.2 diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js index 88fbc911..b16e3324 100644 --- a/rpg-docs/Model/Character/Characters.js +++ b/rpg-docs/Model/Character/Characters.js @@ -4,6 +4,7 @@ Characters = new Mongo.Collection("characters"); Schemas.Character = new SimpleSchema({ //strings name: {type: String, defaultValue: "", trim: false, optional: true}, + urlName: {type: String, defaultValue: "", trim: false, optional: true}, alignment: {type: String, defaultValue: "", trim: false, optional: true}, gender: {type: String, defaultValue: "", trim: false, optional: true}, race: {type: String, defaultValue: "", trim: false, optional: true}, @@ -540,6 +541,12 @@ if (Meteor.isServer){ Items .remove({charId: character._id}); Containers .remove({charId: character._id}); }); + Characters.after.update(function(userId, doc, fieldNames, modifier, options) { + if (_.contains(fieldNames, "name")){ + var urlName = getSlug(doc.name, {maintainCase: true}); + Characters.update(doc._id, {$set: {urlName}}); + } + }); } Characters.allow({ diff --git a/rpg-docs/Routes/Routes.js b/rpg-docs/Routes/Routes.js index 5f2906c2..14ac42ff 100644 --- a/rpg-docs/Routes/Routes.js +++ b/rpg-docs/Routes/Routes.js @@ -24,7 +24,7 @@ Router.map(function() { this.route("characterList", { path: "/characterList", waitOn: function(){ - return subsManager.subscribe("characterList", Meteor.userId()); + return subsManager.subscribe("characterList"); }, onAfterAction: function() { document.title = appName + " - Characters"; @@ -32,11 +32,27 @@ Router.map(function() { fastRender: true, }); - this.route("characterSheet", { - path: "/character/:_id", + this.route("characterSheetNaked", { + path: "/character/:_id/", waitOn: function(){ return [ - subsManager.subscribe("singleCharacter", this.params._id, Meteor.userId()), + subsManager.subscribe("singleCharacter", this.params._id), + ]; + }, + action: function(){ + var _id = this.params._id + var character = Characters.findOne(_id); + var urlName = character && character.urlName; + var path = `\/character\/${_id}\/${urlName}`; + Router.go(path,{},{replaceState:true}); + }, + }); + + this.route("characterSheet", { + path: "/character/:_id/:urlName", + waitOn: function(){ + return [ + subsManager.subscribe("singleCharacter", this.params._id), ]; }, data: function() { diff --git a/rpg-docs/client/views/characterList/characterList.html b/rpg-docs/client/views/characterList/characterList.html index 1603eaa5..82e12b92 100644 --- a/rpg-docs/client/views/characterList/characterList.html +++ b/rpg-docs/client/views/characterList/characterList.html @@ -11,7 +11,7 @@ {{#if characters.count}}
{{# each characters}} - + diff --git a/rpg-docs/client/views/characterList/characterList.js b/rpg-docs/client/views/characterList/characterList.js index c5971a03..254b2854 100644 --- a/rpg-docs/client/views/characterList/characterList.js +++ b/rpg-docs/client/views/characterList/characterList.js @@ -10,7 +10,15 @@ Template.characterList.helpers({ ] }, { - fields: {name: 1, picture: 1, color: 1, race: 1, alignment: 1, gender: 1}, + fields: { + name: 1, + urlName: 1, + picture: 1, + color: 1, + race: 1, + alignment: 1, + gender: 1, + }, sort: {name: 1}, } ); diff --git a/rpg-docs/client/views/characterList/characterSideList.js b/rpg-docs/client/views/characterList/characterSideList.js index 915bf87a..010905c2 100644 --- a/rpg-docs/client/views/characterList/characterSideList.js +++ b/rpg-docs/client/views/characterList/characterSideList.js @@ -14,7 +14,7 @@ Template.characterSideList.helpers({ ] }, { - fields: {name: 1}, + fields: {name: 1, urlName: 1}, sort: {name: 1}, } ); diff --git a/rpg-docs/server/migrations/migrations.js b/rpg-docs/server/migrations/migrations.js index 9eee2347..17ca3cf3 100644 --- a/rpg-docs/server/migrations/migrations.js +++ b/rpg-docs/server/migrations/migrations.js @@ -125,7 +125,7 @@ Migrations.add({ } }); var effect = Effects.findOne({ - charId: char._id, name: "Natural Carrying Capacity" + charId: char._id, name: "Natural Carrying Capacity", }); if (effect) return; Effects.insert({ @@ -141,7 +141,7 @@ Migrations.add({ }, }); effect = Effects.findOne({ - charId: char._id, name: "Natural Carrying Capacity" + charId: char._id, name: "Natural Carrying Capacity", }); if (!effect) throw "Carry capacity effect should be set by now." }); @@ -150,3 +150,19 @@ Migrations.add({ return; }, }); + +Migrations.add({ + version: 5, + name: "Gives all characters a URL name", + up: function() { + //update characters + Characters.find({}).forEach(function(char){ + if (char.urlName) return; + var urlName = getSlug(char.name, {maintainCase: true}); + Characters.update(char._id, {$set: {urlName}}); + }); + }, + down: function(){ + return; + }, +}); diff --git a/rpg-docs/server/publications/characterList.js b/rpg-docs/server/publications/characterList.js index b8c24b83..91e8e019 100644 --- a/rpg-docs/server/publications/characterList.js +++ b/rpg-docs/server/publications/characterList.js @@ -15,6 +15,7 @@ Meteor.publish("characterList", function(){ { fields: { name: 1, + urlName: 1, race: 1, alignment: 1, gender: 1, From 6a84c836444b98837faf762d92f6348f52d1c6ae Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 13:42:04 +0200 Subject: [PATCH 11/17] Made passive scores show up for all skills with bonuses to passive score closes #56 --- .../stats/skillDialog/skillDialog.html | 12 +++++++ .../stats/skillDialog/skillDialog.js | 19 +++++++++- .../character/stats/skillRow/skillRow.html | 2 +- .../character/stats/skillRow/skillRow.js | 12 +++++++ .../client/views/character/stats/stats.html | 36 +++++++++---------- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/rpg-docs/client/views/character/stats/skillDialog/skillDialog.html b/rpg-docs/client/views/character/stats/skillDialog/skillDialog.html index 8edf4caa..a0918755 100644 --- a/rpg-docs/client/views/character/stats/skillDialog/skillDialog.html +++ b/rpg-docs/client/views/character/stats/skillDialog/skillDialog.html @@ -61,6 +61,18 @@ Total {{characterCalculate "skillMod" charId skillName}} + {{#each passiveEffects}} + + {{sourceName}} + Passive Bonus: {{statValue}} + + {{/each}} + {{#if showPassiveTotal}} + + Passive Score + {{characterCalculate "passiveSkill" charId skillName}} + + {{/if}}
diff --git a/rpg-docs/client/views/character/stats/skillDialog/skillDialog.js b/rpg-docs/client/views/character/stats/skillDialog/skillDialog.js index 759c1f11..d3b5f6cd 100644 --- a/rpg-docs/client/views/character/stats/skillDialog/skillDialog.js +++ b/rpg-docs/client/views/character/stats/skillDialog/skillDialog.js @@ -122,7 +122,7 @@ Template.skillDialogView.helpers({ var char = Characters.findOne(this.charId); if (!char) return; var prof = Characters.calculate.proficiency(this.charId, this.skillName); - var proficiencyBonus = + var proficiencyBonus = Characters.calculate.attributeValue(this.charId, "proficiencyBonus"); return prof * proficiencyBonus; }, @@ -189,6 +189,23 @@ Template.skillDialogView.helpers({ enabled: true, }); }, + passiveEffects: function(){ + return Effects.find({ + charId: this.charId, + stat: this.skillName, + operation: "passiveAdd", + enabled: true, + }); + }, + showPassiveTotal: function(){ + if (this.skillName === "perception") return true; + return Effects.find({ + charId: this.charId, + stat: this.skillName, + operation: "passiveAdd", + enabled: true, + }).count(); + }, ability: function(){ var opts = {fields: {}}; opts.fields[this.skillName] = 1; diff --git a/rpg-docs/client/views/character/stats/skillRow/skillRow.html b/rpg-docs/client/views/character/stats/skillRow/skillRow.html index 0b01f919..207dbac2 100644 --- a/rpg-docs/client/views/character/stats/skillRow/skillRow.html +++ b/rpg-docs/client/views/character/stats/skillRow/skillRow.html @@ -14,7 +14,7 @@ {{#if conditionalCount}} * {{/if}} - {{#if showPassive}} + {{#if isPassiveShown}} ({{characterCalculate "passiveSkill" ../_id skill}}) {{/if}} diff --git a/rpg-docs/client/views/character/stats/skillRow/skillRow.js b/rpg-docs/client/views/character/stats/skillRow/skillRow.js index 20915294..25a15d6c 100644 --- a/rpg-docs/client/views/character/stats/skillRow/skillRow.js +++ b/rpg-docs/client/views/character/stats/skillRow/skillRow.js @@ -38,4 +38,16 @@ Template.skillRow.helpers({ operation: "conditional", }).count(); }, + isPassiveShown: function(){ + if (this.showPassive === "forced") return true; + if (this.showPassive === "ifNeeded"){ + var charId = Template.parentData()._id; + return Effects.find({ + charId, + stat: this.skill, + operation: "passiveAdd", + enabled: true, + }).count(); + } + }, }); diff --git a/rpg-docs/client/views/character/stats/stats.html b/rpg-docs/client/views/character/stats/stats.html index 3a5ce782..3cde018b 100644 --- a/rpg-docs/client/views/character/stats/stats.html +++ b/rpg-docs/client/views/character/stats/stats.html @@ -49,24 +49,24 @@ Skills
- {{> skillRow name="Acrobatics" skill="acrobatics"}} - {{> skillRow name="Animal Handling" skill="animalHandling"}} - {{> skillRow name="Arcana" skill="arcana"}} - {{> skillRow name="Athletics" skill="athletics"}} - {{> skillRow name="Deception" skill="deception"}} - {{> skillRow name="History" skill="history"}} - {{> skillRow name="Insight" skill="insight"}} - {{> skillRow name="Intimidation" skill="intimidation"}} - {{> skillRow name="Investigation" skill="investigation"}} - {{> skillRow name="Medicine" skill="medicine"}} - {{> skillRow name="Nature" skill="nature"}} - {{> skillRow name="Perception" skill="perception" showPassive="true"}} - {{> skillRow name="Performance" skill="performance"}} - {{> skillRow name="Persuasion" skill="persuasion"}} - {{> skillRow name="Religion" skill="religion"}} - {{> skillRow name="Sleight of Hand" skill="sleightOfHand"}} - {{> skillRow name="Stealth" skill="stealth"}} - {{> skillRow name="Survival" skill="survival"}} + {{> skillRow name="Acrobatics" skill="acrobatics" showPassive="ifNeeded"}} + {{> skillRow name="Animal Handling" skill="animalHandling" showPassive="ifNeeded"}} + {{> skillRow name="Arcana" skill="arcana" showPassive="ifNeeded"}} + {{> skillRow name="Athletics" skill="athletics" showPassive="ifNeeded"}} + {{> skillRow name="Deception" skill="deception" showPassive="ifNeeded"}} + {{> skillRow name="History" skill="history" showPassive="ifNeeded"}} + {{> skillRow name="Insight" skill="insight" showPassive="ifNeeded"}} + {{> skillRow name="Intimidation" skill="intimidation" showPassive="ifNeeded"}} + {{> skillRow name="Investigation" skill="investigation" showPassive="ifNeeded"}} + {{> skillRow name="Medicine" skill="medicine" showPassive="ifNeeded"}} + {{> skillRow name="Nature" skill="nature" showPassive="ifNeeded"}} + {{> skillRow name="Perception" skill="perception" showPassive="forced"}} + {{> skillRow name="Performance" skill="performance" showPassive="ifNeeded"}} + {{> skillRow name="Persuasion" skill="persuasion" showPassive="ifNeeded"}} + {{> skillRow name="Religion" skill="religion" showPassive="ifNeeded"}} + {{> skillRow name="Sleight of Hand" skill="sleightOfHand" showPassive="ifNeeded"}} + {{> skillRow name="Stealth" skill="stealth" showPassive="ifNeeded"}} + {{> skillRow name="Survival" skill="survival" showPassive="ifNeeded"}}
From 8b061f7aa94e42778237ad3d5c4cedbab4ca8f84 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 15:53:03 +0200 Subject: [PATCH 12/17] Improved look and feel of effect editing --- .../lib/requestAnimationFramePolyfill.js | 28 +++++++ .../effects/effectEdit/effectEdit.html | 18 ++-- .../effects/effectEdit/effectEdit.js | 82 ++++++++++++------- .../dicecloud-selectable.html | 4 +- 4 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 rpg-docs/client/lib/requestAnimationFramePolyfill.js diff --git a/rpg-docs/client/lib/requestAnimationFramePolyfill.js b/rpg-docs/client/lib/requestAnimationFramePolyfill.js new file mode 100644 index 00000000..0fb801e6 --- /dev/null +++ b/rpg-docs/client/lib/requestAnimationFramePolyfill.js @@ -0,0 +1,28 @@ +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating + +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel + +// MIT license +var lastTime = 0; +var vendors = ["ms", "moz", "webkit", "o"]; +for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; +} + +if (!window.requestAnimationFrame) +window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; +}; + +if (!window.cancelAnimationFrame) +window.cancelAnimationFrame = function(id) { + clearTimeout(id); +}; diff --git a/rpg-docs/client/views/character/effects/effectEdit/effectEdit.html b/rpg-docs/client/views/character/effects/effectEdit/effectEdit.html index 3ae51b94..078cd946 100644 --- a/rpg-docs/client/views/character/effects/effectEdit/effectEdit.html +++ b/rpg-docs/client/views/character/effects/effectEdit/effectEdit.html @@ -20,26 +20,28 @@
{{#each statGroups}} -
+
{{this}}
- {{#each stats}} - {{name}} - {{/each}} + + {{#each stats}} + {{name}} + {{/each}} + {{/each}} {{#if operations}} {{#each operations}} - {{name}} + {{name}} {{/each}} {{else}} {{#if showMultiplierOperations}} - Resistance - Vulnerability - Immunity + Resistance + Vulnerability + Immunity {{else}}
diff --git a/rpg-docs/client/views/character/effects/effectEdit/effectEdit.js b/rpg-docs/client/views/character/effects/effectEdit/effectEdit.js index d18538fa..b47f7219 100644 --- a/rpg-docs/client/views/character/effects/effectEdit/effectEdit.js +++ b/rpg-docs/client/views/character/effects/effectEdit/effectEdit.js @@ -149,47 +149,71 @@ Template.effectEdit.helpers({ effectValue: function(){ return this.calculation || this.value; }, + isGroupSelected: function(groupName, statName){ + var stat = statsDict[statName] + return stat && (stat.group === groupName); + }, }); +var setStat = function(statName, effectId){ + var setter = {stat: statName}; + var effect = Effects.findOne(this._id); + var group = statsDict[statName].group; + if (group === "Saving Throws" || group === "Skills"){ + // Skills must have a valid skill operation + if (!_.contains( + _.map(skillOperations, ao => ao.operation), + effect.operation + )){ + setter.operation = "add"; + } + } else if (group !== "Weakness/Resistance"){ + // Attributes must have a valid attribute operation + if (!_.contains( + _.map(attributeOperations, ao => ao.operation), + effect.operation + )){ + setter.operation = "base"; + } + } else { + // Weakness/Resistance must have a mul operation and value + if (effect.operation !== "mul"){ + setter.operation = "mul"; + } + if (!_.contains([0, 0.5, 2], effect.value)){ + setter.value = 0.5; + } + } + Effects.update(effectId, {$set: setter}); +}; + +var scrollAnimationId; +var scrollElementToView = element => { + var scrollFunction = function(){ + element.scrollIntoView(); + scrollAnimationId = requestAnimationFrame(scrollFunction); + }; + return scrollFunction; +} + Template.effectEdit.events({ "click #deleteButton": function(event, instance){ Effects.softRemoveNode(instance.data.id); GlobalUI.deletedToast(instance.data.id, "Effects", "Effect"); popDialogStack(); }, + "click .statGroupTitle": function(event, instance){ + var groupName = this.toString(); + var firstStat = statGroups[groupName][0].stat; + setStat(firstStat, instance.data.id); + scrollAnimationId = requestAnimationFrame(scrollElementToView(event.target)); + _.delay(() => cancelAnimationFrame(scrollAnimationId), 300); + }, "iron-select .statMenu": function(event){ var detail = event.originalEvent.detail; var statName = detail.item.getAttribute("name"); if (statName == this.stat) return; - var setter = {stat: statName}; - var group = Blaze.getData(detail.item).group; - var effect = Effects.findOne(this._id); - if (group === "Saving Throws" || group === "Skills"){ - // Skills must have a valid skill operation - if (!_.contains( - _.map(skillOperations, ao => ao.operation), - effect.operation - )){ - setter.operation = "add"; - } - } else if (group !== "Weakness/Resistance"){ - // Attributes must have a valid attribute operation - if (!_.contains( - _.map(attributeOperations, ao => ao.operation), - effect.operation - )){ - setter.operation = "base"; - } - } else { - // Weakness/Resistance must have a mul operation and value - if (effect.operation !== "mul"){ - setter.operation = "mul"; - } - if (!_.contains([0, 0.5, 2], effect.value)){ - setter.value = 0.5; - } - } - Effects.update(this._id, {$set: setter}); + setStat(statName, this._id); }, "iron-select .operationMenu": function(event){ var detail = event.originalEvent.detail; diff --git a/rpg-docs/public/custom_components/dicecloud-selector/dicecloud-selectable.html b/rpg-docs/public/custom_components/dicecloud-selector/dicecloud-selectable.html index fceeba7e..c88c3a30 100644 --- a/rpg-docs/public/custom_components/dicecloud-selector/dicecloud-selectable.html +++ b/rpg-docs/public/custom_components/dicecloud-selector/dicecloud-selectable.html @@ -249,7 +249,9 @@ }, _updateItems: function() { - var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); + var nodes = this.selectable + ? Polymer.dom(this).querySelectorAll(this.selectable) + : Polymer.dom(this).queryDistributedElements('*'); nodes = Array.prototype.filter.call(nodes, this._bindFilterItem); this._setItems(nodes); }, From 2bacb296bae49dbb187d532ac6cdf2039d1ff30f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 16:01:43 +0200 Subject: [PATCH 13/17] Fixed new effects dialog animation makes some progress on #72 --- .../views/character/effects/effectsEditList/effectsEditList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpg-docs/client/views/character/effects/effectsEditList/effectsEditList.js b/rpg-docs/client/views/character/effects/effectsEditList/effectsEditList.js index 0732431b..0a8646d4 100644 --- a/rpg-docs/client/views/character/effects/effectsEditList/effectsEditList.js +++ b/rpg-docs/client/views/character/effects/effectsEditList/effectsEditList.js @@ -33,7 +33,7 @@ Template.effectsEditList.events({ template: "effectEdit", data: {id: effectId}, element: event.currentTarget, - returnElement: instance.find(`tr.effect[data-id='${effectId}']`), + returnElement: () => instance.find(`tr.effect[data-id='${effectId}']`), }); }, "tap .edit-effect": function(event, template){ From 35b6fe20ae705597099a1de7287b6aaf2a796434 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 16:16:00 +0200 Subject: [PATCH 14/17] Reset migrations to line up with current DiceCloud server --- rpg-docs/server/migrations/migrations.js | 130 ----------------------- 1 file changed, 130 deletions(-) diff --git a/rpg-docs/server/migrations/migrations.js b/rpg-docs/server/migrations/migrations.js index 17ca3cf3..641a98ae 100644 --- a/rpg-docs/server/migrations/migrations.js +++ b/rpg-docs/server/migrations/migrations.js @@ -23,136 +23,6 @@ Meteor.methods({ Migrations.add({ version: 1, - name: "converts effect proficiencies to proficiency objects, removes types from assets", - up: function() { - //convert proficiency effects to proficiency objects - Effects.find({operation: "proficiency"}).forEach(function(effect){ - var type = "skill"; - if (_.contains(SAVES, effect.stat)) type = "save"; - Proficiencies.insert({ - charId: effect.charId, - name: effect.stat, - value: effect.value, - parent: _.clone(effect.parent), - type: type, - enabled: effect.enabled, - }, function(err){ - if (!err) Effects.remove(effect._id); - }); - }); - //store type as a parent group if it's needed - Effects.find({"parent.collection": "Characters"}).forEach(function(e){ - Effects.update(e._id, {$set: {"parent.group": e.type}}); - }); - Attacks.find({"parent.collection": "Characters"}).forEach(function(a){ - Attacks.update(a._id, {$set: {"parent.group": a.type}}); - }); - //remove type - Effects.update({}, {$unset: {type: ""}}, {validate: false, multi: true}); - Attacks.update({}, {$unset: {type: ""}}, {validate: false, multi: true}); - //remove languages and proficiencies - Characters.update( - {}, - {$unset: {languages: "", proficiencies: ""}}, - {validate: false, multi: true} - ); - }, -}); - -Migrations.add({ - version: 2, - name: "Converts attacks from damage dice and damage bonus to a string with curly bracket calculations, adds settings.showIncrement to items", - up: function() { - //update attacks - Attacks.find({}).forEach(function(attack) { - if (!attack.damage && attack.damageDice && attack.damageBonus){ - var newDamage = attack.damageDice + - " + {" + attack.damageBonus + "}"; - Attacks.update( - attack._id, - { - $unset: { - damageBonus: "", - damageDice: "", - }, - $set: { - damage: newDamage - }, - }, - {validate: false}); - } - }); - //update Items - Items.update( - {settings: undefined}, - {$set: {"settings.showIncrement" : false}}, - {validate: false, multi: true} - ); - }, -}); - -Migrations.add({ - version: 3, - name: "Converts attacks from damage dice and damage bonus to a string with curly bracket calculations, adds settings.showIncrement to items", - up: function() { - //update characters - Characters.update( - {"settings.useVariantEncumbrance": undefined}, - {$set: {"settings.useVariantEncumbrance" : false}}, - {validate: false, multi: true} - ); - Characters.update( - {"settings.useStandardEncumbrance": undefined}, - {$set: {"settings.useStandardEncumbrance" : true}}, - {validate: false, multi: true} - ); - }, -}); - -Migrations.add({ - version: 4, - name: "Adds an effect to give characters a base carry capacity", - up: function() { - //update characters - - Characters.find({}).forEach(function(char){ - Characters.update(char._id, { - $set: { - carryMultiplier: { - adjustment: 0, - reset: "longRest", - } - } - }); - var effect = Effects.findOne({ - charId: char._id, name: "Natural Carrying Capacity", - }); - if (effect) return; - Effects.insert({ - charId: char._id, - name: "Natural Carrying Capacity", - stat: "carryMultiplier", - operation: "base", - value: "1", - parent: { - id: char._id, - collection: "Characters", - group: "Inate", - }, - }); - effect = Effects.findOne({ - charId: char._id, name: "Natural Carrying Capacity", - }); - if (!effect) throw "Carry capacity effect should be set by now." - }); - }, - down: function(){ - return; - }, -}); - -Migrations.add({ - version: 5, name: "Gives all characters a URL name", up: function() { //update characters From a411fb2b430b127c8696ccf4317fa8b29bfbfd35 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 16:44:23 +0200 Subject: [PATCH 15/17] Fixed migration for failed slug strings --- rpg-docs/Model/Character/Characters.js | 2 +- rpg-docs/server/migrations/migrations.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js index b16e3324..f8a2d33f 100644 --- a/rpg-docs/Model/Character/Characters.js +++ b/rpg-docs/Model/Character/Characters.js @@ -543,7 +543,7 @@ if (Meteor.isServer){ }); Characters.after.update(function(userId, doc, fieldNames, modifier, options) { if (_.contains(fieldNames, "name")){ - var urlName = getSlug(doc.name, {maintainCase: true}); + var urlName = getSlug(doc.name, {maintainCase: true}) || "-"; Characters.update(doc._id, {$set: {urlName}}); } }); diff --git a/rpg-docs/server/migrations/migrations.js b/rpg-docs/server/migrations/migrations.js index 641a98ae..34e73414 100644 --- a/rpg-docs/server/migrations/migrations.js +++ b/rpg-docs/server/migrations/migrations.js @@ -28,7 +28,7 @@ Migrations.add({ //update characters Characters.find({}).forEach(function(char){ if (char.urlName) return; - var urlName = getSlug(char.name, {maintainCase: true}); + var urlName = getSlug(char.name, {maintainCase: true}) || "-"; Characters.update(char._id, {$set: {urlName}}); }); }, From ac23afac5d4cd42dc457bdcf297678c3d96d7eb0 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 13 Jul 2017 17:16:23 +0200 Subject: [PATCH 16/17] Stopped characters with poor names from having failed URL's --- rpg-docs/Model/Character/Characters.js | 5 ++++- rpg-docs/Routes/Routes.js | 2 +- rpg-docs/client/globalHelpers/characterPath.js | 3 +++ rpg-docs/client/views/characterList/characterList.html | 2 +- rpg-docs/client/views/characterList/characterSideList.html | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 rpg-docs/client/globalHelpers/characterPath.js diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js index f8a2d33f..e75430b2 100644 --- a/rpg-docs/Model/Character/Characters.js +++ b/rpg-docs/Model/Character/Characters.js @@ -4,7 +4,7 @@ Characters = new Mongo.Collection("characters"); Schemas.Character = new SimpleSchema({ //strings name: {type: String, defaultValue: "", trim: false, optional: true}, - urlName: {type: String, defaultValue: "", trim: false, optional: true}, + urlName: {type: String, defaultValue: "-", trim: false, optional: true}, alignment: {type: String, defaultValue: "", trim: false, optional: true}, gender: {type: String, defaultValue: "", trim: false, optional: true}, race: {type: String, defaultValue: "", trim: false, optional: true}, @@ -547,6 +547,9 @@ if (Meteor.isServer){ Characters.update(doc._id, {$set: {urlName}}); } }); + Characters.before.insert(function(userId, doc) { + doc.urlName = getSlug(doc.name, {maintainCase: true}) || "-"; + }); } Characters.allow({ diff --git a/rpg-docs/Routes/Routes.js b/rpg-docs/Routes/Routes.js index 14ac42ff..90676c79 100644 --- a/rpg-docs/Routes/Routes.js +++ b/rpg-docs/Routes/Routes.js @@ -43,7 +43,7 @@ Router.map(function() { var _id = this.params._id var character = Characters.findOne(_id); var urlName = character && character.urlName; - var path = `\/character\/${_id}\/${urlName}`; + var path = `\/character\/${_id}\/${urlName || "-"}`; Router.go(path,{},{replaceState:true}); }, }); diff --git a/rpg-docs/client/globalHelpers/characterPath.js b/rpg-docs/client/globalHelpers/characterPath.js new file mode 100644 index 00000000..723ee07b --- /dev/null +++ b/rpg-docs/client/globalHelpers/characterPath.js @@ -0,0 +1,3 @@ +Template.registerHelper("characterPath", function(char) { + return `\/character\/${char._id}\/${char.urlName || "-"}`; +}); diff --git a/rpg-docs/client/views/characterList/characterList.html b/rpg-docs/client/views/characterList/characterList.html index 82e12b92..acde48cb 100644 --- a/rpg-docs/client/views/characterList/characterList.html +++ b/rpg-docs/client/views/characterList/characterList.html @@ -11,7 +11,7 @@ {{#if characters.count}}
{{# each characters}} - + diff --git a/rpg-docs/client/views/characterList/characterSideList.html b/rpg-docs/client/views/characterList/characterSideList.html index e65d7804..38a19859 100644 --- a/rpg-docs/client/views/characterList/characterSideList.html +++ b/rpg-docs/client/views/characterList/characterSideList.html @@ -2,7 +2,7 @@ {{#if characters.count}}
{{#each characters}} - +
{{name}} From 44a1daf6f8cc13a427bb496baf3bb1efe5a22265 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 14 Jul 2017 17:09:30 +0200 Subject: [PATCH 17/17] Grouping characters by party now works closes #75, finally --- rpg-docs/Model/Campaign/Party.js | 38 ++++++++- .../views/characterList/characterList.css | 10 ++- .../views/characterList/characterList.html | 83 +++++++++++++------ .../views/characterList/characterList.js | 83 ++++++++++++++----- .../views/characterList/characterSideList.css | 15 +++- .../characterList/characterSideList.html | 42 +++++++--- .../views/characterList/characterSideList.js | 51 ++++++++---- .../characterList/partyDialog/partyDialog.css | 3 + .../partyDialog/partyDialog.html | 32 +++++++ .../characterList/partyDialog/partyDialog.js | 62 ++++++++++++++ .../paperTemplates/baseDialog/baseDialog.js | 2 + rpg-docs/server/publications/characterList.js | 41 +++++---- 12 files changed, 358 insertions(+), 104 deletions(-) create mode 100644 rpg-docs/client/views/characterList/partyDialog/partyDialog.css create mode 100644 rpg-docs/client/views/characterList/partyDialog/partyDialog.html create mode 100644 rpg-docs/client/views/characterList/partyDialog/partyDialog.js diff --git a/rpg-docs/Model/Campaign/Party.js b/rpg-docs/Model/Campaign/Party.js index 990e8e11..d48dbb97 100644 --- a/rpg-docs/Model/Campaign/Party.js +++ b/rpg-docs/Model/Campaign/Party.js @@ -1,8 +1,42 @@ Parties = new Mongo.Collection("parties"); Schemas.Party = new SimpleSchema({ - //each character/monster can only be in one party at a time - //each party can only be in a single instance at a time + name: { + type: String, + defaultValue: "New Party", + trim: false, + optional: true, + }, + characters: { + type: [String], + regEx: SimpleSchema.RegEx.Id, + index: 1, + defaultValue: [], + }, + owner: { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, }); Parties.attachSchema(Schemas.Party); + +Parties.allow({ + insert: function(userId, doc) { + return userId && doc.owner === userId; + }, + update: function(userId, doc, fields, modifier) { + return userId && doc.owner === userId; + }, + remove: function(userId, doc) { + return userId && doc.owner === userId; + }, + fetch: ["owner"], +}); + +Parties.deny({ + update: function(userId, docs, fields, modifier) { + // can't change owners + return _.contains(fields, "owner"); + } +}); diff --git a/rpg-docs/client/views/characterList/characterList.css b/rpg-docs/client/views/characterList/characterList.css index f25313b8..f33acebb 100644 --- a/rpg-docs/client/views/characterList/characterList.css +++ b/rpg-docs/client/views/characterList/characterList.css @@ -8,8 +8,16 @@ position: relative; } -.character-card .image { +.partyHeader { + display: inline-block; +} +.partyHeader iron-icon { + visibility: hidden; +} + +.partyHeader:hover iron-icon{ + visibility: initial; } .character-card .initials { diff --git a/rpg-docs/client/views/characterList/characterList.html b/rpg-docs/client/views/characterList/characterList.html index acde48cb..a5b00dd2 100644 --- a/rpg-docs/client/views/characterList/characterList.html +++ b/rpg-docs/client/views/characterList/characterList.html @@ -10,31 +10,27 @@ {{#if currentUser}} {{#if characters.count}}
- {{# each characters}} - - - - {{#unless picture}} -
- {{initials name}} -
- {{/unless}} - - -
- {{name}} -
-
- {{alignment}} {{gender}} {{race}} -
-
-
- -
+ {{# each charactersWithNoParty}} + {{> characterCard}} {{/each}} {{> gridPadding class="character-card flex layout vertical" num=12}}
+ {{# each party in parties}} +
+ {{#with party}} +
+ {{name}} + +
+ {{/with}} +
+ {{# each charactersInParty party._id}} + {{> characterCard}} + {{/each}} + {{> gridPadding class="character-card flex layout vertical" num=12}} +
+
+ {{/each}} {{else}}
You don't seem to have any characters yet
@@ -47,9 +43,46 @@
{{/if}}
- + {{#fabMenu}} +
+ + + New Party +
+
+ + + New Character +
+ {{/fabMenu}}
+ + diff --git a/rpg-docs/client/views/characterList/characterList.js b/rpg-docs/client/views/characterList/characterList.js index 254b2854..e35a5571 100644 --- a/rpg-docs/client/views/characterList/characterList.js +++ b/rpg-docs/client/views/characterList/characterList.js @@ -1,35 +1,57 @@ Template.characterList.helpers({ - characters(){ + characters() { var userId = Meteor.userId(); return Characters.find( - { - $or: [ - {readers: userId}, - {writers: userId}, - {owner: userId}, - ] - }, - { - fields: { - name: 1, - urlName: 1, - picture: 1, - color: 1, - race: 1, - alignment: 1, - gender: 1, - }, - sort: {name: 1}, - } + {$or: [{readers: userId}, {writers: userId}, {owner: userId}]}, + {sort: {name: 1}} ); }, + parties() { + return Parties.find({owner: Meteor.userId()}); + }, + charactersInParty(partyId) { + var userId = Meteor.userId(); + var party = Parties.findOne(partyId); + return Characters.find( + { + _id: {$in: party.characters}, + $or: [{readers: userId}, {writers: userId}, {owner: userId}], + }, + {sort: {name: 1}} + ); + }, + charactersWithNoParty() { + var userId = Meteor.userId(); + var charArrays = Parties.find({owner: userId}).map(p => p.characters); + var partyChars = _.uniq(_.flatten(charArrays)); + return Characters.find( + { + _id: {$nin: partyChars}, + $or: [{readers: userId}, {writers: userId}, {owner: userId}], + }, + {sort: {name: 1}} + ); + }, +}); + +Template.characterCard.helpers({ initials(name){ return name.replace(/[^A-Z]/g, ""); }, -}) +}); Template.characterList.events({ - "tap .addCharacter": function(event, template) { + "click .partyHeader": function(event, instance){ + pushDialogStack({ + template: "partyDialog", + data: { + _id: this._id, + startEditing: true, + }, + element: event.currentTarget.parentElement, + }); + }, + "click .addCharacter": function(event, instance) { pushDialogStack({ template: "newCharacterDialog", element: event.currentTarget, @@ -37,8 +59,23 @@ Template.characterList.events({ if (!character) return; character.owner = Meteor.userId(); let _id = Characters.insert(character); - Router.go("characterSheet", {_id}); + let urlName = getSlug(character.name, {maintainCase: true}) || "-" + Router.go("characterSheet", {_id, urlName}); }, }) }, + "click .addParty": function(event, instance) { + var partyId = Parties.insert({ + owner: Meteor.userId(), + }); + pushDialogStack({ + template: "partyDialog", + data: { + _id: partyId, + startEditing: true, + }, + element: event.currentTarget, + returnElement: instance.find(`.party[data-id='${partyId}']`), + }); + }, }); diff --git a/rpg-docs/client/views/characterList/characterSideList.css b/rpg-docs/client/views/characterList/characterSideList.css index e48e0b94..1b08159b 100644 --- a/rpg-docs/client/views/characterList/characterSideList.css +++ b/rpg-docs/client/views/characterList/characterSideList.css @@ -2,8 +2,21 @@ prevent character names from wrapping */ -.character-name { +.side-list .character-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + +.side-list .partyHead { + font-weight: 500; + cursor: pointer; +} + +.side-list .partyHead iron-icon { + transition: transform 0.3s ease; +} + +.side-list .partyHead iron-icon.open { + transform: rotate(90deg); +} diff --git a/rpg-docs/client/views/characterList/characterSideList.html b/rpg-docs/client/views/characterList/characterSideList.html index 38a19859..8a3fe6fd 100644 --- a/rpg-docs/client/views/characterList/characterSideList.html +++ b/rpg-docs/client/views/characterList/characterSideList.html @@ -1,15 +1,31 @@ diff --git a/rpg-docs/client/views/characterList/characterSideList.js b/rpg-docs/client/views/characterList/characterSideList.js index 010905c2..18e4e800 100644 --- a/rpg-docs/client/views/characterList/characterSideList.js +++ b/rpg-docs/client/views/characterList/characterSideList.js @@ -1,33 +1,50 @@ Template.characterSideList.onCreated(function() { this.subscribe("characterList"); + this.openedParties = new ReactiveVar(new Set()); }); Template.characterSideList.helpers({ - characters: function() { + parties() { + var userId = Meteor.userId(); + return Parties.find({owner: userId}); + }, + charactersInParty() { var userId = Meteor.userId(); return Characters.find( { - $or: [ - {readers: userId}, - {writers: userId}, - {owner: userId}, - ] + _id: {$in: this.characters}, + $or: [{readers: userId}, {writers: userId}, {owner: userId}], }, - { - fields: {name: 1, urlName: 1}, - sort: {name: 1}, - } + {sort: {name: 1}} ); }, + charactersWithNoParty() { + var userId = Meteor.userId(); + var charArrays = Parties.find({owner: userId}).map(p => p.characters); + var partyChars = _.uniq(_.flatten(charArrays)); + return Characters.find( + { + _id: {$nin: partyChars}, + $or: [{readers: userId}, {writers: userId}, {owner: userId}], + }, + {sort: {name: 1}} + ); + }, + isOpen(id) { + var openedParties = Template.instance().openedParties.get(); + console.log(openedParties); + return openedParties.has(id); + }, }); Template.characterSideList.events({ - "tap .singleLineItem": function(event, instance) { - //Router.go("characterSheet", {_id: this._id}); - $("core-drawer-panel").get(0).closeDrawer(); - }, - "tap core-item": function() { - Router.go("characterList"); - $("core-drawer-panel").get(0).closeDrawer(); + "click .partyHead": function(event, instance){ + var openedParties = instance.openedParties.get(); + if (openedParties.has(this._id)){ + openedParties.delete(this._id); + } else { + openedParties.add(this._id); + } + instance.openedParties.set(openedParties); }, }); diff --git a/rpg-docs/client/views/characterList/partyDialog/partyDialog.css b/rpg-docs/client/views/characterList/partyDialog/partyDialog.css new file mode 100644 index 00000000..f4ddf6ed --- /dev/null +++ b/rpg-docs/client/views/characterList/partyDialog/partyDialog.css @@ -0,0 +1,3 @@ +.partyEdit .inPartyCheckbox { + margin-bottom: 8px; +} diff --git a/rpg-docs/client/views/characterList/partyDialog/partyDialog.html b/rpg-docs/client/views/characterList/partyDialog/partyDialog.html new file mode 100644 index 00000000..9c6d1b86 --- /dev/null +++ b/rpg-docs/client/views/characterList/partyDialog/partyDialog.html @@ -0,0 +1,32 @@ + + + + + diff --git a/rpg-docs/client/views/characterList/partyDialog/partyDialog.js b/rpg-docs/client/views/characterList/partyDialog/partyDialog.js new file mode 100644 index 00000000..cb1c8e97 --- /dev/null +++ b/rpg-docs/client/views/characterList/partyDialog/partyDialog.js @@ -0,0 +1,62 @@ +Template.partyDialog.helpers({ + party(){ + return Parties.findOne(this._id); + } +}); + +Template.partyDetails.helpers({ + getCharacters (){ + var userId = Meteor.userId(); + return Characters.find( + { + _id: {$in: this.characters}, + $or: [{readers: userId}, {writers: userId}, {owner: userId}], + }, + {sort: {name: 1}} + ); + } +}); + +Template.partyEdit.helpers({ + allCharacters() { + var userId = Meteor.userId(); + return Characters.find( + {$or: [{readers: userId}, {writers: userId}, {owner: userId}]}, + {sort: {name: 1}} + ); + }, + charInParty(charId) { + return _.contains(Template.parentData().characters, charId); + }, +}); + +Template.partyDialog.events({ + "click #deleteButton": function(event, instance){ + Parties.remove(instance.data._id); + popDialogStack(); + }, + "click #doneEditingButton": function(event, instance){ + popDialogStack(); + }, +}); + +Template.partyEdit.events({ + "change .inPartyCheckbox": function(event, instance){ + var currentCharacters = this.characters; + var checked = event.currentTarget.checked; + var charId = this._id; + var partyId = instance.data._id; + if (checked){ + Parties.update(partyId, {$addToSet: {characters: charId}}); + } else { + Parties.update(partyId, {$pull: {characters: charId}}); + } + }, + "input .partyNameInput": function(event, instance){ + var name = event.currentTarget.value; + Parties.update(this._id, {$set: {name}}, { + removeEmptyStrings: false, + trimStrings: false, + }); + }, +}); diff --git a/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js b/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js index 09526a48..2d51ead3 100644 --- a/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js +++ b/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js @@ -4,11 +4,13 @@ Template.baseDialog.onCreated(function(){ Template.baseDialog.helpers({ editing: function(){ + if (!Template.parentData() || !Template.parentData().charId) return true; return Template.instance().editing.get() && canEditCharacter(Template.parentData().charId); }, showEdit: function() { if (this.hideEdit) return false; + if (!Template.parentData() || !Template.parentData().charId) return true; return canEditCharacter(Template.parentData().charId); }, }); diff --git a/rpg-docs/server/publications/characterList.js b/rpg-docs/server/publications/characterList.js index 91e8e019..a33dd71d 100644 --- a/rpg-docs/server/publications/characterList.js +++ b/rpg-docs/server/publications/characterList.js @@ -4,27 +4,24 @@ Meteor.publish("characterList", function(){ this.ready(); return; } - return Characters.find( - { - $or: [ - {readers: userId}, - {writers: userId}, - {owner: userId}, - ] - }, - { - fields: { - name: 1, - urlName: 1, - race: 1, - alignment: 1, - gender: 1, - readers: 1, - writers:1, - owner: 1, - color: 1, - picture: 1, + return [ + Characters.find( + {$or: [{readers: userId}, {writers: userId}, {owner: userId}]}, + { + fields: { + name: 1, + urlName: 1, + race: 1, + alignment: 1, + gender: 1, + readers: 1, + writers:1, + owner: 1, + color: 1, + picture: 1, + } } - } - ); + ), + Parties.find({owner: userId}), + ]; });