From 65d1bac0dc119bd30ca4636650292e8905985e45 Mon Sep 17 00:00:00 2001 From: Frogvall Date: Wed, 14 Nov 2018 06:40:22 +0100 Subject: [PATCH 01/10] Updated heat metal according to SRD/PHB --- dataSources/srd/spells.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dataSources/srd/spells.json b/dataSources/srd/spells.json index d4907c6a..6e51208c 100644 --- a/dataSources/srd/spells.json +++ b/dataSources/srd/spells.json @@ -2194,9 +2194,9 @@ } }, { - "castingTime": "bonus action", + "castingTime": "action", "description": "Choose a manufactured metal object, such as a metal weapon or a suit of heavy or medium metal armor, that you can see within range. You cause the object to glow red-hot. Any creature in physical contact with the object takes 2d8 fire damage when you cast the spell. Until the spell ends, you can use a bonus action on each of your subsequent turns to cause this damage again.\n\nIf a creature is holding or wearing the object and takes the damage from it, the creature must succeed on a DC {DC} Constitution saving throw or drop the object if it can. If it doesn’t drop the object, it has disadvantage on attack rolls and ability checks until the start of your next turn.\n\n***At Higher Levels.*** When you cast this spell using a spell slot of 3rd level or higher, the damage increases by 1d8 for each slot level above 2nd.", - "duration": "Instantaneous", + "duration": "Concentration, up to 1 minute", "level": 2, "range": "60 feet", "school": "Transmutation", @@ -2204,8 +2204,9 @@ "name": "Heat Metal", "components": { "verbal": true, - "somatic": false, - "concentration": false + "somatic": true, + "concentration": true, + "material": "a piece of iron and a flame" } }, { From e2822b9f22361303f4d619a4ca549ede23812515 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 28 Jan 2019 11:35:56 +0200 Subject: [PATCH 02/10] added naive backup restore --- app/.gitignore | 1 + app/lib/functions/backupRestoreCharacter.js | 33 +++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 app/lib/functions/backupRestoreCharacter.js diff --git a/app/.gitignore b/app/.gitignore index 086ca397..2df4ae3a 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -8,3 +8,4 @@ private/oldClient nohup.out node_modules dump +.cache diff --git a/app/lib/functions/backupRestoreCharacter.js b/app/lib/functions/backupRestoreCharacter.js new file mode 100644 index 00000000..ba95f4af --- /dev/null +++ b/app/lib/functions/backupRestoreCharacter.js @@ -0,0 +1,33 @@ +let characterCollections = [ + Actions, + Attacks, + Buffs, + Classes, + CustomBuffs, + Effects, + Experiences, + Features, + Notes, + Proficiencies, + SpellLists, + Items, + Containers, +]; + +function backupCharacter(charId){ + let characterDump = {}; + characterDump.characters = [Characters.findOne(charId)]; + characterCollections.map( + c => characterDump[c._name] = c.find({charId}).fetch() + ); + return characterDump; +}; + +function restoreCharacter(characterDump){ + for (collectionName in characterDump){ + let collection = Meteor.Collection.get(collectionName); + for (doc in characterDump[collectionName]){ + collection.insert(doc); + } + } +}; From b052e8dd196aac86968f2cf308d065fd168cc376 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 31 Jan 2019 10:09:19 +0200 Subject: [PATCH 03/10] Update README.md --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f70527ca..12240434 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,71 @@ -RPG Docs +DiceCloud ======== This is the repo for [DiceCloud](dicecloud.com). +DiceCloud is a free, auditable, real-time character sheet for D&D 5e. + +Philosophy +---------- + +Setting up your character on DiceCloud takes a little longer than +just filling it in on a paper character sheet would. The goal of using an +online sheet is to make actually playing the game more streamlined, and +ultimately more fun. So putting a little extra effort into setting up a +character now pays off over and over again once you're playing. + +The idea is to track where each number comes from, and allow you to easily make +changes on the fly. Let's look at a hypothetical example. + +> You need to swim through a sunken section of dungeon to fetch the quest's Thing. +> You'll need to take off your magical Plate Armor of +1 Constitution to swim +> without sinking, of course. +> +> Taking it off will take away that disadvantage on +> stealth checks, change your armor class, your speed and your constitution, and +> which in turn changes your hit points and your constitution saving throw. +> Working out all those changes in the middle of a game will drag the game to a +> halt. +> +> Fortunately you have DiceCloud, so it's a matter of dragging +> your Plate Armor +1 Con from your "equipment" box to your "backpack" box and +> you're done. Your hitpoints change correctly, your saving throws are up to date, +> your armor class goes back to reflecting the fact that you have natural armor +> from being a dragonborn. Your character sheet keeps up and you +> ultimately get more time to play the game. Huzzah! + Getting started --------------- +Running DiceCloud locally, either to host it yourself away from an internet +connection, or to contribute to developing it further, is fairly +straightforward and it should work on Linux, Windows, and Mac. + +You'll need to have installed: + +- [git](https://www.atlassian.com/git/tutorials/install-git) +- [Meteor](https://www.meteor.com/install) +- [Bower](https://bower.io/) + +Then, it's just a matter of cloning this repository into a folder, installing the bower dependencies and running +`meteor` in the app directory. + `git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud` `cd dicecloud` `cd app` `bower install` `meteor` + +You should see this: + +``` +=> Started proxy. +=> Started MongoDB. +=> Started your app. + +=> App running at: http://localhost:3000/ +``` + +Now, visiting [](http://localhost:3000/) should show you an empty instance of +DiceCloud running. + From 4ea02c4fbb6a8c39d952751a3af32a4c523874e3 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 31 Jan 2019 10:10:56 +0200 Subject: [PATCH 04/10] Fixed hidden link to localhost --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12240434..bed794c7 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,6 @@ You should see this: => App running at: http://localhost:3000/ ``` -Now, visiting [](http://localhost:3000/) should show you an empty instance of +Now, visiting http://localhost:3000/ should show you an empty instance of DiceCloud running. From 3f325356666c4c93838aa15af0938b724bf32b7c Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 31 Jan 2019 10:11:32 +0200 Subject: [PATCH 05/10] fixed link to dicecloud repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bed794c7..6c81e7ae 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ You'll need to have installed: Then, it's just a matter of cloning this repository into a folder, installing the bower dependencies and running `meteor` in the app directory. -`git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud` +`git clone https://github.com/ThaumRystra/DiceCloud dicecloud` `cd dicecloud` `cd app` `bower install` From 0260824c2f13cf8307a5edab282483ccf02ff6f0 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 11 Feb 2019 10:09:18 +0200 Subject: [PATCH 06/10] Made gave backup and restore the ability to change ids for all docs --- app/lib/functions/backupRestoreCharacter.js | 47 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/app/lib/functions/backupRestoreCharacter.js b/app/lib/functions/backupRestoreCharacter.js index ba95f4af..d08ef919 100644 --- a/app/lib/functions/backupRestoreCharacter.js +++ b/app/lib/functions/backupRestoreCharacter.js @@ -3,6 +3,7 @@ let characterCollections = [ Attacks, Buffs, Classes, + Conditions, CustomBuffs, Effects, Experiences, @@ -10,21 +11,55 @@ let characterCollections = [ Notes, Proficiencies, SpellLists, + Spells, + TemporaryHitPoints, Items, Containers, ]; -function backupCharacter(charId){ +function dumpCharacter(charId){ let characterDump = {}; - characterDump.characters = [Characters.findOne(charId)]; - characterCollections.map( - c => characterDump[c._name] = c.find({charId}).fetch() - ); + characterDump.character = Characters.findOne(charId); + characterCollections.forEach(c => { + characterDump.collections[c._name] = c.find({charId}).fetch(); + }); return characterDump; }; +function giveCharacterDumpNewIds(characterDump){ + // Give the character a new Id + const newCharId = Random.id(); + characterDump.character._id = newCharId; + + // Give all documents a new Id, and store the mapping from old to new + let idMap = {}; // {oldId: newId} + for (let colName in characterDump.collections){ + for (let doc of characterDump.collections[colName]){ + let oldId = doc._id; + let newId = Random.id(); + doc._id = newId; + idMap[oldId] = newId; + } + } + + // Replace all references to old Ids with new ones + for (let colName in characterDump.collections){ + for (let doc of characterDump.collections[colName]){ + // Replace the character Id with the new one + doc.charId = newCharId; + // Replace the parent reference id with a new id + if (doc.parent && doc.parent.id){ + let newParentId = idMap[doc.parent.id]; + if(!newParentId) throw `Can't find the mapping for id ${doc.parent.id}`; + doc.parent.id = newParentId; + } + } + } +} + function restoreCharacter(characterDump){ - for (collectionName in characterDump){ + Characters.insert(characterDump.character); + for (collectionName in characterDump.collections){ let collection = Meteor.Collection.get(collectionName); for (doc in characterDump[collectionName]){ collection.insert(doc); From 3343f8a8135506f6d1e099b44aa2e0443e73721b Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 11 Feb 2019 10:17:43 +0200 Subject: [PATCH 07/10] Allowed canViewCharacter to take in a character instead of a charId to save a database read --- app/lib/functions/permissions.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/lib/functions/permissions.js b/app/lib/functions/permissions.js index 8f160297..37b4ff73 100644 --- a/app/lib/functions/permissions.js +++ b/app/lib/functions/permissions.js @@ -5,12 +5,14 @@ canEditCharacter = function(charId, userId){ return (userId === char.owner || _.contains(char.writers, userId)); }; -canViewCharacter = function(charId, userId){ +canViewCharacter = function(char, userId){ userId = userId || Meteor.userId(); - var char = Characters.findOne( - charId, - {fields: {owner: 1, writers: 1, readers: 1, "settings.viewPermission": 1}} - ); + if (typeof char !== 'object'){ + char = Characters.findOne( + charId, + {fields: {owner: 1, writers: 1, readers: 1, "settings.viewPermission": 1}} + ); + } if (!char) return true; return userId === char.owner || char.settings.viewPermission === "public" || From 9d86cb8bee1a8590bc285fdab37569689940d384 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 11 Feb 2019 10:21:11 +0200 Subject: [PATCH 08/10] Added the copy character method --- app/lib/methods/characterCopyPaste.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/lib/methods/characterCopyPaste.js diff --git a/app/lib/methods/characterCopyPaste.js b/app/lib/methods/characterCopyPaste.js new file mode 100644 index 00000000..d078b45b --- /dev/null +++ b/app/lib/methods/characterCopyPaste.js @@ -0,0 +1,22 @@ +// Uses '/lib/functions/backupRestoreCharacter.js' to do most the work + +Meteor.methods({ + copyCharacter: function(charId) { + const userId = Meteor.userId(); + let character = Characters.findOne(charId); + + // Need at least view level permission to make a copy for yourself + if (!canViewCharacter(character, userId)) return; + + let characterDump = dumpCharacter(charId); + giveCharacterDumpNewIds(characterDump); + + // Remove all readers and writers, make this user the new owner + characterDump.character.readers = []; + characterDump.character.writers = []; + characterDump.character.owner = userId; + + // Write the character back to the database + restoreCharacter(characterDump); + }, +}); From 1ebb0d25275b8f37fc5648480cbf54b50d1410ff Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 11 Feb 2019 11:11:51 +0200 Subject: [PATCH 09/10] Got character copying working --- .../views/character/characterSheet.html | 10 +- app/client/views/character/characterSheet.js | 12 +++ app/lib/functions/backupRestoreCharacter.js | 61 +++++++----- app/lib/methods/characterCopyPaste.js | 4 + app/package-lock.json | 93 +++++++++++++++++-- app/package.json | 1 + 6 files changed, 148 insertions(+), 33 deletions(-) diff --git a/app/client/views/character/characterSheet.html b/app/client/views/character/characterSheet.html index 8785892b..efc54123 100644 --- a/app/client/views/character/characterSheet.html +++ b/app/client/views/character/characterSheet.html @@ -31,9 +31,17 @@ Settings - + Export to Improved Initiative + + + Make a copy + + + + Download a backup + {{else}} diff --git a/app/client/views/character/characterSheet.js b/app/client/views/character/characterSheet.js index a2a549f4..0c43d387 100644 --- a/app/client/views/character/characterSheet.js +++ b/app/client/views/character/characterSheet.js @@ -234,6 +234,18 @@ Template.characterSheet.events({ element: event.currentTarget.parentElement.parentElement, }); }, + "click #characterCopy": function(event, instance){ + Meteor.call("copyCharacter", this._id, (error, char) => { + if (error){ + console.error(error); + } else { + Router.go(`/character/${char._id}/${char.urlName || "-"}`); + } + }); + }, + "click #characterDump": function(event, instance){ + saveCharacterDump(this._id); + }, "click #unshareCharacter": function(event, instance){ pushDialogStack({ data: this, diff --git a/app/lib/functions/backupRestoreCharacter.js b/app/lib/functions/backupRestoreCharacter.js index d08ef919..9863d12f 100644 --- a/app/lib/functions/backupRestoreCharacter.js +++ b/app/lib/functions/backupRestoreCharacter.js @@ -1,24 +1,29 @@ -let characterCollections = [ - Actions, - Attacks, - Buffs, - Classes, - Conditions, - CustomBuffs, - Effects, - Experiences, - Features, - Notes, - Proficiencies, - SpellLists, - Spells, - TemporaryHitPoints, - Items, - Containers, -]; +import { saveAs } from 'file-saver'; -function dumpCharacter(charId){ - let characterDump = {}; +let characterCollections = []; +Meteor.startup(() => { + characterCollections = [ + Actions, + Attacks, + Buffs, + Classes, + Conditions, + CustomBuffs, + Effects, + Experiences, + Features, + Notes, + Proficiencies, + SpellLists, + Spells, + TemporaryHitPoints, + Items, + Containers, + ]; +}); + +dumpCharacter = function(charId){ + let characterDump = {collections: {}}; characterDump.character = Characters.findOne(charId); characterCollections.forEach(c => { characterDump.collections[c._name] = c.find({charId}).fetch(); @@ -26,13 +31,23 @@ function dumpCharacter(charId){ return characterDump; }; -function giveCharacterDumpNewIds(characterDump){ +saveCharacterDump = function(charId){ + let dump = dumpCharacter(charId); + let textDump = JSON.stringify(dump, null, 2); + let charName = dump.character.name; + let blob = new Blob([textDump], {type: "application/json;charset=utf-8"}); + saveAs(blob, `${charName}.JSON`); +}; + +giveCharacterDumpNewIds = function(characterDump){ // Give the character a new Id + const oldCharId = characterDump.character._id; const newCharId = Random.id(); characterDump.character._id = newCharId; + let idMap = {[oldCharId]: newCharId}; // {oldId: newId} + // Give all documents a new Id, and store the mapping from old to new - let idMap = {}; // {oldId: newId} for (let colName in characterDump.collections){ for (let doc of characterDump.collections[colName]){ let oldId = doc._id; @@ -57,7 +72,7 @@ function giveCharacterDumpNewIds(characterDump){ } } -function restoreCharacter(characterDump){ +restoreCharacter = function(characterDump){ Characters.insert(characterDump.character); for (collectionName in characterDump.collections){ let collection = Meteor.Collection.get(collectionName); diff --git a/app/lib/methods/characterCopyPaste.js b/app/lib/methods/characterCopyPaste.js index d078b45b..76173202 100644 --- a/app/lib/methods/characterCopyPaste.js +++ b/app/lib/methods/characterCopyPaste.js @@ -16,7 +16,11 @@ Meteor.methods({ characterDump.character.writers = []; characterDump.character.owner = userId; + // Rename the character so it's obviously a copy + characterDump.character.name += " - Copy"; + // Write the character back to the database restoreCharacter(characterDump); + return characterDump.character; }, }); diff --git a/app/package-lock.json b/app/package-lock.json index 12e5ed0b..3c606c15 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -45,7 +45,7 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { @@ -74,6 +74,7 @@ }, "block-stream": { "version": "0.0.9", + "resolved": false, "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "requires": { "inherits": "~2.0.0" @@ -131,7 +132,7 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "core-js": { @@ -139,6 +140,11 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -209,6 +215,11 @@ "resolved": "https://registry.npmjs.org/fibers/-/fibers-2.0.2.tgz", "integrity": "sha512-HfVRxhYG7C8Jl9FqtrlElMR2z/8YiLQVDKf67MLY25Ic+ILx3ecmklfT1v3u+7P5/4vEFjuxaAFXhr2/Afwk5g==" }, + "file-saver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.0.tgz", + "integrity": "sha512-cYM1ic5DAkg25pHKgi5f10ziAM7RJU37gaH1XQlyNDrtUnzhC/dfoV9zf2OmF0RMKi42jG5B0JWBnPQqyj/G6g==" + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -259,7 +270,7 @@ }, "graceful-fs": { "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "resolved": false, "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "har-schema": { @@ -361,6 +372,7 @@ }, "inherits": { "version": "2.0.3", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "invert-kv": { @@ -383,7 +395,7 @@ }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" @@ -1064,6 +1076,37 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "stream-http": { @@ -1076,6 +1119,37 @@ "readable-stream": "^2.3.3", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "string_decoder": { @@ -1434,7 +1508,7 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "os-homedir": { @@ -1578,6 +1652,7 @@ }, "minimist": { "version": "1.2.0", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "strip-json-comments": { @@ -1815,7 +1890,7 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "shebang-command": { @@ -1833,7 +1908,7 @@ }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "source-map": { @@ -1909,7 +1984,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -2159,7 +2234,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "requires": { "string-width": "^1.0.1", diff --git a/app/package.json b/app/package.json index 13ecdf36..fbbadeae 100644 --- a/app/package.json +++ b/app/package.json @@ -17,6 +17,7 @@ "bower": "^1.7.9", "core-js": "^2.5.7", "fibers": "^2.0.2", + "file-saver": "^2.0.0", "meteor-node-stubs": "^0.3.3", "qrcode": "^1.3.0", "source-map-support": "^0.5.9", From 23d43f7d4332420016f44fb29fb6a1efcf561af3 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 11 Feb 2019 11:58:45 +0200 Subject: [PATCH 10/10] Added character restore functionality --- .../views/characterList/characterList.html | 7 +++ .../views/characterList/characterList.js | 16 ++++++ .../characterRestoreDialog.html | 31 ++++++++++++ .../characterRestoreDialog.js | 49 +++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.html create mode 100644 app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.js diff --git a/app/client/views/characterList/characterList.html b/app/client/views/characterList/characterList.html index 94962a5b..c6bc6c97 100644 --- a/app/client/views/characterList/characterList.html +++ b/app/client/views/characterList/characterList.html @@ -58,6 +58,13 @@ {{#simpleTooltip class="always"}} New Character {{/simpleTooltip}} +
+ + + {{#simpleTooltip class="always"}} Restore from backup {{/simpleTooltip}} +
{{/fabMenu}} diff --git a/app/client/views/characterList/characterList.js b/app/client/views/characterList/characterList.js index 5bdee54b..47ba3abe 100644 --- a/app/client/views/characterList/characterList.js +++ b/app/client/views/characterList/characterList.js @@ -81,4 +81,20 @@ Template.characterList.events({ returnElement: instance.find(`.party[data-id='${partyId}']`), }); }, + "click .restoreCharacter": function(event, instance) { + pushDialogStack({ + template: "characterRestoreDialog", + element: event.currentTarget, + callback(dump){ + if (!dump) return; + dump.character.name += " - Restored" + giveCharacterDumpNewIds(dump); + restoreCharacter(dump); + Router.go("characterSheet", { + _id: dump.character._id, + urlName: dump.character.urlName || '-', + }); + }, + }) + }, }); diff --git a/app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.html b/app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.html new file mode 100644 index 00000000..d37292cb --- /dev/null +++ b/app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.html @@ -0,0 +1,31 @@ + diff --git a/app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.js b/app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.js new file mode 100644 index 00000000..560a2429 --- /dev/null +++ b/app/client/views/characterList/characterRestoreDialog/characterRestoreDialog.js @@ -0,0 +1,49 @@ +Template.characterRestoreDialog.onCreated(function(){ + this.dump = {}; + this.valid = new ReactiveVar(false); + this.error = new ReactiveVar(null); +}); + +Template.characterRestoreDialog.helpers({ + invalid(){ + return !Template.instance().valid.get(); + }, + error(){ + return Template.instance().error.get(); + }, +}); + +const fail = function(instance){ + instance.valid.set(false); + instance.error.set("Failed to convert file into a valid character"); + instance.dump = undefined; +}; + +Template.characterRestoreDialog.events({ + "input .fileInput": function(event, instance){ + let input = event.currentTarget.$.input; + let reader = new FileReader(); + reader.onload = function(){ + let dumpString = reader.result; + try { + let dump = JSON.parse(dumpString); + if (dump && dump.character && dump.collections){ + instance.valid.set(true); + instance.error.set(null); + instance.dump = dump; + } else { + fail(instance); + } + } catch (e) { + fail(instance); + } + }; + reader.readAsText(input.files[0]); + }, + "click .cancelButton": function(event, instance){ + popDialogStack(); + }, + "click .addButton": function(event, instance){ + popDialogStack(instance.dump); + }, +});