Compare commits

..

9 Commits

Author SHA1 Message Date
Thaum Rystra
058ee2691f Merge branch 'feature-print'
# Conflicts:
#	rpg-docs/package-lock.json
2018-03-03 17:18:16 +02:00
Thaum Rystra
f0cf7f4956 Added QR code and finished page 1 2018-03-03 17:13:36 +02:00
Thaum Rystra
75c8720b04 Moved printed character sheets to their own page
This makes sure the entire printed sheet is rendered before the browser  attempts to print it, solving all manner of errors
2018-03-03 11:13:16 +02:00
Thaum Rystra
f73f2f670f Formatted all existing printed character sheet fields nicely 2018-03-02 21:40:21 +02:00
Thaum Rystra
c6e62e1cfa Added borders to ability scores and AC 2018-03-02 07:25:37 +02:00
Thaum Rystra
67956d9a42 Fixed dropdown boxes being clipped in dialogs, updated Meteor 2018-01-26 18:18:49 +02:00
Stefan Zermatten
64b3ca6066 Added proficiencies to printed sheet; some fixes 2017-12-12 15:54:21 +02:00
Stefan Zermatten
8349f7da9b Merge branch 'master' into feature-print 2017-12-06 09:22:57 +02:00
Stefan Zermatten
00a050d337 Added basic printing functionality 2017-09-11 09:27:01 +02:00
39 changed files with 2423 additions and 306 deletions

View File

@@ -3,12 +3,12 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
accounts-password@1.4.0
accounts-ui@1.1.9
accounts-password@1.5.0
accounts-ui@1.2.0
random@1.0.10
dburles:collection-helpers
reactive-var@1.0.11
underscore
underscore@1.0.10
aldeed:collection2
matb33:collection-hooks
zimme:collection-softremovable
@@ -17,40 +17,40 @@ dburles:mongo-collection-instances
percolate:migrations
ecwyne:mathjs
useraccounts:polymer
accounts-google@1.2.0
accounts-google@1.3.0
splendido:accounts-meld
email@1.2.3
meteorhacks:subs-manager
chuangbo:marked
reywood:iron-router-ga
meteor-base@1.1.0
mobile-experience@1.0.4
mongo@1.2.0
meteor-base@1.2.0
mobile-experience@1.0.5
mongo@1.3.1
blaze-html-templates
session@1.1.7
jquery@1.11.10
tracker@1.1.3
logging@1.1.17
logging@1.1.19
reload@1.1.11
ejson@1.0.14
ejson@1.1.0
spacebars
check@1.2.5
useraccounts:iron-routing
wizonesolutions:canonical
standard-minifier-js@2.1.1
shell-server@0.2.4
standard-minifier-js@2.2.0
shell-server@0.3.0
seba:minifiers-autoprefixer
nikogosovd:multiple-uihooks
templates:array
ecmascript@0.8.2
ecmascript@0.9.0
es5-shim@4.6.15
differential:vulcanize
reactive-dict@1.1.9
reactive-dict@1.2.0
percolate:synced-cron
ongoworks:speakingurl
service-configuration@1.0.11
google-config-ui
dynamic-import
ddp-rate-limiter
rate-limit
google-config-ui@1.0.0
dynamic-import@0.2.0
ddp-rate-limiter@1.0.7
rate-limit@1.0.8
iron:router

View File

@@ -1 +1 @@
METEOR@1.6
METEOR@1.6.0.1

View File

@@ -1,5 +1,5 @@
accounts-base@1.4.0
accounts-google@1.3.0
accounts-base@1.4.2
accounts-google@1.3.1
accounts-oauth@1.1.15
accounts-password@1.5.0
accounts-ui@1.2.0
@@ -19,7 +19,7 @@ blaze@2.3.2
blaze-html-templates@1.1.2
blaze-tools@1.0.10
boilerplate-generator@1.3.1
caching-compiler@1.1.9
caching-compiler@1.1.11
caching-html-compiler@1.1.2
callback-hook@1.0.10
check@1.2.5
@@ -46,7 +46,7 @@ email@1.2.3
es5-shim@4.6.15
geojson-utils@1.0.10
google-config-ui@1.0.0
google-oauth@1.2.4
google-oauth@1.2.5
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
@@ -63,7 +63,7 @@ iron:url@1.1.0
jquery@1.11.10
lai:collection-extensions@0.2.1_1
launch-screen@1.1.1
less@2.7.11
less@2.7.12
livedata@1.0.18
localstorage@1.2.0
logging@1.1.19
@@ -74,26 +74,26 @@ meteor-base@1.2.0
meteorhacks:subs-manager@1.6.4
minifier-css@1.2.16
minifier-js@2.2.2
minimongo@1.4.2
minimongo@1.4.3
mobile-experience@1.0.5
mobile-status-bar@1.0.14
modules@0.11.0
modules-runtime@0.9.1
momentjs:moment@2.19.2
mongo@1.3.0
modules@0.11.3
modules-runtime@0.9.2
momentjs:moment@2.20.1
mongo@1.3.1
mongo-dev-server@1.1.0
mongo-id@1.0.6
nikogosovd:multiple-uihooks@0.1.8
npm-bcrypt@0.9.3
npm-mongo@2.2.33
oauth@1.2.0
npm-mongo@2.2.34
oauth@1.2.1
oauth2@1.2.0
observe-sequence@1.0.16
ongoworks:speakingurl@9.0.0
ordered-dict@1.0.9
percolate:migrations@0.9.8
percolate:synced-cron@1.3.2
promise@0.10.0
promise@0.10.1
raix:eventemitter@0.1.3
random@1.0.10
rate-limit@1.0.8
@@ -107,14 +107,14 @@ seba:minifiers-autoprefixer@1.0.1
service-configuration@1.0.11
session@1.1.7
sha@1.0.9
shell-server@0.3.0
shell-server@0.3.1
softwarerero:accounts-t9n@1.3.11
spacebars@1.0.15
spacebars-compiler@1.1.3
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.1
srp@1.0.10
standard-minifier-js@2.2.2
standard-minifier-js@2.2.3
templates:array@1.0.3
templating@1.3.2
templating-compiler@1.3.3

View File

@@ -78,6 +78,35 @@ Router.map(function() {
fastRender: true,
});
this.route("printedCharacterSheet", {
path: "/character/:_id/:urlName/print",
waitOn: function(){
return [
subsManager.subscribe("singleCharacter", this.params._id),
];
},
data: function() {
var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data;
},
onAfterAction: function() {
var char = Characters.findOne({_id: this.params._id}, {fields: {name: 1}});
var name = char && char.name;
if (name){
document.title = name + " - Printing";
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/print-character");
this.next();
},
});
this.route("library", {
path: "/library",
waitOn: function(){

View File

@@ -0,0 +1,174 @@
// jscs:disable
// https://github.com/chunksnbits/jquery-quickfit
(function ($) {
var Quickfit, QuickfitHelper, defaults, pluginName;
pluginName = 'quickfit';
defaults = {
min: 8,
max: 12,
tolerance: 0.02,
truncate: false,
width: null,
sampleNumberOfLetters: 10,
sampleFontSize: 12
};
QuickfitHelper = (function () {
var sharedInstance = null;
QuickfitHelper.instance = function (options) {
if (!sharedInstance) {
sharedInstance = new QuickfitHelper(options);
}
return sharedInstance;
};
function QuickfitHelper(options) {
this.options = options;
this.item = $('<span id="meassure"></span>');
this.item.css({
position: 'absolute',
left: '-1000px',
top: '-1000px',
'font-size': "" + this.options.sampleFontSize + "px"
});
$('body').append(this.item);
this.meassures = {};
}
QuickfitHelper.prototype.getMeassure = function (letter) {
var currentMeassure;
currentMeassure = this.meassures[letter];
if (!currentMeassure) {
currentMeassure = this.setMeassure(letter);
}
return currentMeassure;
};
QuickfitHelper.prototype.setMeassure = function (letter) {
var currentMeassure, index, sampleLetter, text, _ref;
text = '';
sampleLetter = letter === ' ' ? '&nbsp;' : letter;
for (index = 0, _ref = this.options.sampleNumberOfLetters - 1; 0 <= _ref ? index <= _ref : index >= _ref; 0 <= _ref ? index++ : index--) {
text += sampleLetter;
}
this.item.html(text);
currentMeassure = this.item.width() / this.options.sampleNumberOfLetters / this.options.sampleFontSize;
this.meassures[letter] = currentMeassure;
return currentMeassure;
};
return QuickfitHelper;
})();
Quickfit = (function () {
function Quickfit(element, options) {
this.$element = element;
this.options = $.extend({}, defaults, options);
this.$element = $(this.$element);
this._defaults = defaults;
this._name = pluginName;
this.quickfitHelper = QuickfitHelper.instance(this.options);
}
Quickfit.prototype.fit = function () {
var elementWidth;
if (!this.options.width) {
elementWidth = this.$element.width();
this.options.width = elementWidth - this.options.tolerance * elementWidth;
}
if (this.text = this.$element.attr('data-quickfit')) {
this.previouslyTruncated = true;
} else {
this.text = this.$element.text();
}
this.calculateFontSize();
if (this.options.truncate) this.truncate();
return {
$element: this.$element,
size: this.fontSize
};
};
Quickfit.prototype.calculateFontSize = function () {
var letter, textWidth, i;
textWidth = 0;
for (i = 0; i < this.text.length; ++i) {
letter = this.text.charAt(i);
textWidth += this.quickfitHelper.getMeassure(letter);
}
this.targetFontSize = parseInt(this.options.width / textWidth);
return this.fontSize = Math.max(this.options.min, Math.min(this.options.max, this.targetFontSize));
};
Quickfit.prototype.truncate = function () {
var index, lastLetter, letter, textToAdd, textWidth;
if (this.fontSize > this.targetFontSize) {
textToAdd = '';
textWidth = 3 * this.quickfitHelper.getMeassure('.') * this.fontSize;
index = 0;
while (textWidth < this.options.width && index < this.text.length) {
letter = this.text[index++];
if (lastLetter) textToAdd += lastLetter;
textWidth += this.fontSize * this.quickfitHelper.getMeassure(letter);
lastLetter = letter;
}
if (textToAdd.length + 1 === this.text.length) {
textToAdd = this.text;
} else {
textToAdd += '...';
}
this.textWasTruncated = true;
return this.$element.attr('data-quickfit', this.text).html(textToAdd);
} else {
if (this.previouslyTruncated) {
return this.$element.html(this.text);
}
}
};
return Quickfit;
})();
return $.fn.quickfit = function (options) {
var measurements = [];
// Separate measurements from repaints
// First calculate all measurements...
var $elements = this.each(function () {
var measurement = new Quickfit(this, options).fit();
measurements.push(measurement);
return measurement.$element;
});
// ... then apply the measurements.
for (var i = 0; i < measurements.length; i++) {
var measurement = measurements[i];
measurement.$element.css({ fontSize: measurement.size + 'px' });
}
return $elements;
};
})(jQuery, window);

View File

@@ -0,0 +1,12 @@
Session.setDefault("isPrinting", false);
if (window.matchMedia) {
var mediaQueryList = window.matchMedia("print");
mediaQueryList.addListener(function(mql) {
if (mql.matches) {
Session.set("isPrinting", true);
Tracker.flush();
} else {
Session.set("isPrinting", false);
}
});
}

View File

@@ -0,0 +1,17 @@
removeDuplicateProficiencies = function(proficiencies) {
dict = {};
proficiencies.forEach(function(prof) {
if (prof.name in dict) { //if we have already gone over another proficiency for the same thing
if (dict[prof.name].value < prof.value) {
dict[prof.name] = prof; //then take the new one if it's higher, otherwise leave it
}
} else {
dict[prof.name] = prof; //if it wasn't already there, store it
}
});
profs = []
_.forEach(dict, function(prof) {
profs.push(prof);
})
return profs;
};

View File

@@ -20,6 +20,12 @@
<iron-icon icon="social:share" item-icon></iron-icon>
Share
</paper-icon-item>
<a href={{printUrl}}>
<paper-icon-item id="printButton">
<iron-icon icon="print" item-icon></iron-icon>
Print
</paper-icon-item>
</a>
<paper-icon-item id="characterSettings">
<iron-icon icon="settings" item-icon></iron-icon>
Settings

View File

@@ -165,6 +165,12 @@ var getTab = function(charId){
};
Template.characterSheet.helpers({
printing: function(){
return Session.get("isPrinting");
},
printUrl: function(){
return `/character/${this._id}/${this.urlName || "-"}/print`
},
selectedTab: function(){
return getTab(this._id);
},
@@ -181,8 +187,8 @@ Template.characterSheet.helpers({
const step = Session.get("newUserExperienceStep");
if (selected == tab) return false;
return (tab === 1 && step === 0) ||
(tab === 5 && step === 1) ||
(tab === 0 && step === 2);
(tab === 5 && step === 1) ||
(tab === 0 && step === 2);
},
});

View File

@@ -1,21 +1,3 @@
var removeDuplicateProficiencies = function(proficiencies) {
dict = {};
proficiencies.forEach(function(prof) {
if (prof.name in dict) { //if we have already gone over another proficiency for the same thing
if (dict[prof.name].value < prof.value) {
dict[prof.name] = prof; //then take the new one if it's higher, otherwise leave it
}
} else {
dict[prof.name] = prof; //if it wasn't already there, store it
}
});
profs = []
_.forEach(dict, function(prof) {
profs.push(prof);
})
return profs;
};
Template.features.helpers({
features: function(){
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});

View File

@@ -0,0 +1,3 @@
.printedAbility .title.paper-font-subhead {
font-size: 2.5mm !important;
}

View File

@@ -0,0 +1,13 @@
<template name="printedAbility">
<div class="printedAbility layout vertical center double-border">
<div class="paper-font-subhead title flex layout horizontal center">
{{title}}
</div>
<div class="paper-font-display1 stat">
{{characterCalculate "attributeValue" ../_id ability}}
</div>
<div class="paper-font-subhead modifier">
{{abilityMod}}
</div>
</div>
</template>

View File

@@ -0,0 +1,9 @@
Template.printedAbility.helpers({
abilityMod: function() {
return signedString(
Characters.calculate.abilityMod(
Template.parentData()._id, this.ability
)
);
}
});

View File

@@ -0,0 +1,23 @@
<template name="printedAttack">
<div class="printedAttack" style="margin-bottom: 2mm">
<div class="layout horizontal">
<div class="paper-font-headline layout horizontal center"
style="margin-right: 1mm; min-width: 32px; text-align: right;">
{{evaluateAttackBonus charId attack}}
</div>
<div class="flex layout vertical">
<div class="paper-font-body2">
{{attack.name}}
</div>
<div>
{{evaluateDamage charId attack}}&nbsp;{{attack.damageType}}
</div>
{{#if attack.details}}
<div>
{{attack.details}}
</div>
{{/if}}
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,30 @@
Template.printedAttack.helpers({
evaluateAttackBonus: function(charId, attack) {
if (attack.parent.collection == "Spells") {
var spell = Spells.findOne(attack.parent.id);
if (spell) {
bonus = evaluate(charId, attack.attackBonus, {
"spellListId": spell.parent.id
});
}
} else {
var bonus = evaluate(charId, attack.attackBonus);
}
if (_.isFinite(bonus)) {
return bonus > 0 ? "+" + bonus : "" + bonus;
} else {
return bonus;
}
},
evaluateDamage: function(charId, attack) {
if (attack.parent.collection == "Spells") {
var spell = Spells.findOne(attack.parent.id);
if (spell) {
return evaluateSpellString(charId, spell.parent.id, attack.damage);
}
} else {
return evaluateString(charId, attack.damage);
}
},
});

View File

@@ -0,0 +1,190 @@
.printed .page {
width: 100%;
padding: 6mm;
page-break-inside: avoid;
page-break-after: always;
}
.printed .shrink-to-fit {
white-space: nowrap;
overflow: hidden;
}
.printed p {
margin-bottom: 1mm;
}
.printed .double-border {
position: relative;
padding: 11px 10px;
}
.printed .double-border > * {
position: relative;
}
.printed .double-border:before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 16px solid transparent;
border-image-source: url(/png/doubleLineImageBorder.png);
border-image-slice: 110 126 fill;
border-image-repeat: stretch;
box-sizing: content-box;
}
.printed .double-border.printedAbility {
padding: 11px 6px 0;
margin-bottom: 3mm;
}
.printed .double-border.printedAbility:last-of-type {
margin-bottom: 0;
}
.printed .printedAbility .modifier {
position: relative;
top: 4px;
z-index: 1;
padding: 2px 18px;
background-image: url(/png/upwardPointingBorder.png);
background-position: center;
background-size: contain;
background-repeat: no-repeat;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
.printed .octogon-border {
position: relative;
padding: 0 20px;
}
.printed .octogon-border:before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 22px solid transparent;
border-image: url(/png/octogonBorder.png) 124 118 fill;
z-index: -1;
}
.printed iron-icon {
width: 16px;
min-width: 16px;
height: 16px;
min-height: 16px;
}
.printed .proficiencies, .printed .attacks, .printed .background {
overflow: hidden;
}
.printed .shield-background {
background: url(/png/shieldBorder.png);
background-size: cover;
background-repeat: no-repeat;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
padding: 4px 8px 8px;
width: 80px;
height: 91px;
position: relative;
}
.printed .shield-background .paper-font-subhead {
width: 64px;
text-align: center;
line-height: 1.1;
}
.printed {
font-size: 3mm;
}
.printed .paper-font-body2 {
font-size: 3mm;
line-height: 4mm;
}
.printed .paper-font-subhead {
font-size: 3mm !important;
line-height: 3.5mm !important;
font-weight: bold !important;
text-transform: uppercase !important;
}
.printed .paper-font-subhead.modifier {
font-size: 4mm !important;
line-height: 6mm !important;
}
.printed .paper-font-display1 {
font-size: 7mm !important;
line-height: 12mm !important;
}
.printed .paper-font-headline {
font-size: 5mm !important;
line-height: 6mm !important;
}
.printed .lined-background {
background-image: url(/png/horizontalLine.png);
background-size: 100% 4mm;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
@media screen {
.printed .page {
width: 210mm;
height: 297mm;
background: white;
margin: 8px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2);
}
.printed .page-holder {
width: calc(210mm + 16px);
}
.printed {
overflow: auto;
padding-left:
}
}
@media print {
app-drawer {
display: none;
}
app-header {
display: none;
}
.printed {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
background: #fff;
}
.printed .page-holder {
height: 100%
}
.printed .page {
height: 100%;
}
}

View File

@@ -0,0 +1,276 @@
<template name="printedCharacterSheet">
<div class="fit printed-character-sheet layout vertical">
<app-header fixed effects="waterfall">
<app-toolbar class="medium {{colorClass}} layout horizontal center" style="z-index: 2;">
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<paper-icon-button icon="arrow-back" class="backButton"></paper-icon-button>
<div class="flex character-name">
{{name}}
</div>
<div style="position: relative;">
<paper-icon-button icon="print" class="printButton"></paper-icon-button>
{{#simpleTooltip}} Print {{/simpleTooltip}}
</div>
</app-toolbar>
</app-header>
<div class="printed flex">
<div class="page-holder">
<div class="page">
<div class="layout vertical" style="height: 100%;">
<div class="layout horizontal center" style="margin-bottom: 4mm">
<img src="http://localhost:3000/crown-dice-logo-cropped-transparent.png" style="width: 60px; margin-right: 2mm">
<div class="characterName paper-font-title" style="margin-right: 4mm">
{{name}}
</div>
<div class="paper-font-body2">
<div>
{{#each classes}}
<span style="margin-right: 2mm;">
{{name}}&nbsp;{{level}}
</span>
{{/each}}
</div>
<div>
{{character.alignment}} {{character.gender}} {{character.race}}
</div>
</div>
<div class="flex layout vertical end" style="margin-right: 2mm;">
<div class="paper-font-body2 " style="font-size: 5mm !important;">
dicecloud.com
</div>
<div>
{{characterUrl}}
</div>
</div>
<canvas id="qrCode"></canvas>
</div>
<div class="columns layout horizontal flex">
<div class="col1 flex layout vertical">
<div class="layout vertical center-justified" style="min-height: 100px; margin-bottom: 4mm;">
<div class="initiative" style="margin-bottom: 2mm;">
{{> printedLongStat stat="" name="Inspiration" prefix=""}}
</div>
<div class="proficiencyBonus">
{{> printedLongStat stat="proficiencyBonus" name="Proficiency Bonus" prefix="+"}}
</div>
</div>
<div class="layout horizontal">
<div class="abilities layout vertical justified" style="margin-right: 4mm;">
{{> printedAbility ability="strength" title="Strength"}}
{{> printedAbility ability="dexterity" title="Dexterity"}}
{{> printedAbility ability="constitution" title="Constitution"}}
{{> printedAbility ability="intelligence" title="Intelligence"}}
{{> printedAbility ability="wisdom" title="Wisdom"}}
{{> printedAbility ability="charisma" title="Charisma"}}
</div>
<div class="flex layout vertical">
<div class="saves double-border" style="margin-bottom: 2mm">
<div>
{{> printedSkillRow name="Strength" skill="strengthSave"}}
{{> printedSkillRow name="Dexterity" skill="dexteritySave"}}
{{> printedSkillRow name="Constitution" skill="constitutionSave"}}
{{> printedSkillRow name="Intelligence" skill="intelligenceSave"}}
{{> printedSkillRow name="Wisdom" skill="wisdomSave"}}
{{> printedSkillRow name="Charisma" skill="charismaSave"}}
</div>
<div class="paper-font-subhead layout vertical center">
Saving Throws
</div>
</div>
<div class="skills double-border">
<div>
{{> printedSkillRow name="Acrobatics" skill="acrobatics"}}
{{> printedSkillRow name="Animal Handling" skill="animalHandling"}}
{{> printedSkillRow name="Arcana" skill="arcana"}}
{{> printedSkillRow name="Athletics" skill="athletics"}}
{{> printedSkillRow name="Deception" skill="deception"}}
{{> printedSkillRow name="History" skill="history"}}
{{> printedSkillRow name="Insight" skill="insight"}}
{{> printedSkillRow name="Intimidation" skill="intimidation"}}
{{> printedSkillRow name="Investigation" skill="investigation"}}
{{> printedSkillRow name="Medicine" skill="medicine"}}
{{> printedSkillRow name="Nature" skill="nature"}}
{{> printedSkillRow name="Perception" skill="perception" showPassive="true"}}
{{> printedSkillRow name="Performance" skill="performance"}}
{{> printedSkillRow name="Persuasion" skill="persuasion"}}
{{> printedSkillRow name="Religion" skill="religion"}}
{{> printedSkillRow name="Sleight of Hand" skill="sleightOfHand"}}
{{> printedSkillRow name="Stealth" skill="stealth"}}
{{> printedSkillRow name="Survival" skill="survival"}}
</div>
<div class="paper-font-subhead layout vertical center">
Skills
</div>
</div>
</div>
</div>
<div class="proficiencies flex double-border" style="margin-top: 4mm">
<div class="paper-font-subhead layout vertical center" style="margin-bottom: 2mm;">
Proficiencies
</div>
<div class="layout horizontal">
<div class="flex" style="margin-right: 2mm">
{{#if armorProfs.length}}
<div class="paper-font-subhead" style="margin-bottom: 1mm;">Armor</div>
{{/if}}
{{#each armorProfs}}
{{> printedProficiency}}
{{/each}}
{{#if weaponProfs.length}}
<div class="paper-font-subhead" style="margin: 2mm 0 1mm;">Weapons</div>
{{/if}}
{{#each weaponProfs}}
{{> printedProficiency}}
{{/each}}
</div>
{{#if toolProfs.length}}
<div class="flex">
<div class="paper-font-subhead" style="margin-bottom: 1mm;">Tools</div>
{{#each toolProfs}}
{{> printedProficiency}}
{{/each}}
</div>
{{/if}}
</div>
</div>
</div>
<div class="col2 flex layout vertical" style="margin-left: 4mm; margin-right: 4mm;">
<div class="layout horizontal center justified" style="min-height: 100px; margin-bottom: 4mm;">
<div class="armor">
{{> printedSquareStat stat="armor" name="Armor Class" class="shield-background"}}
</div>
<div class="inititive">
{{> printedSquareStat stat="initiative" name="Initiative" isSkill="true" class="double-border"}}
</div>
<div class="speed">
{{> printedSquareStat stat="speed" name="Speed" class="double-border"}}
</div>
</div>
<div class="hitpoints layout vertical double-border" style="margin-bottom: 2mm;">
<div>
Hit point maximum:
<span class="paper-font-subhead">
{{characterCalculate "attributeBase" _id "hitPoints"}}
</span>
</div>
<div class="flex" style="width: 3cm; height: 2cm;">
<!-- Space for writing -->
</div>
<div class="layout vertical center paper-font-subhead">
Hit Points
</div>
</div>
<div class="tempHitpoints layout vertical double-border" style="margin-bottom: 2mm;">
<div style="width: 3cm; height: 1.5cm;">
<!-- Space for writing -->
</div>
<div class="layout vertical center paper-font-subhead">
Temporary Hit Points
</div>
</div>
<div class="layout horizontal" style="margin-bottom: 4mm;">
<div class="hitDice double-border flex layout vertical" style="margin-right: 2mm;">
<div>
Total:
<span class="paper-font-subhead" style="text-transform: none !important;">
{{hitDiceTotal}}
</span>
</div>
<div class="flex" style="min-height: 1cm;">
<!-- Space for writing -->
</div>
<div class="paper-font-subhead layout vertical center">
Hit Dice
</div>
</div>
<div class="deathSaves layout vertical center double-border">
<div class="" style="margin-bottom: 1mm;">
Successes
</div>
<div class="layout horizontal center">
<iron-icon icon="radio-button-unchecked"></iron-icon>
<iron-icon icon="radio-button-unchecked"></iron-icon>
<iron-icon icon="radio-button-unchecked"></iron-icon>
</div>
<div class="" style="margin: 1mm 0;">
Failures
</div>
<div class="layout horizontal center">
<iron-icon icon="radio-button-unchecked"></iron-icon>
<iron-icon icon="radio-button-unchecked"></iron-icon>
<iron-icon icon="radio-button-unchecked"></iron-icon>
</div>
<div class="paper-font-subhead layout vertical center" style="margin-top: 2mm;">
Death Saves
</div>
</div>
</div>
<div class="attacks double-border flex">
<div class="paper-font-subhead layout vertical center" style="margin-bottom: 2mm;">
Attacks
</div>
{{#each attack in attacks}}
{{> printedAttack attack=attack charId=_id}}
{{/each}}
</div>
</div>
<div class="col3 flex layout vertical">
<div class="Languages double-border" style="min-height: 100px; margin-bottom: 4mm;">
<div class="paper-font-subhead layout vertical center" style="margin-bottom: 2mm;">
Languages
</div>
<div class="layout horizontal">
<div class="flex" style="margin-right: 2mm;">
{{#each languageProfs.left}}
{{> printedProficiency}}
{{/each}}
</div>
{{#if languageProfs.right.length}}
<div class="flex">
{{#each languageProfs.right}}
{{> printedProficiency}}
{{/each}}
</div>
{{/if}}
</div>
</div>
<div class="traits double-border">
{{#markdown}}{{evaluateShortString character._id character.personality}}{{/markdown}}
<div class="paper-font-subhead layout vertical center">
Personality traits
</div>
</div>
<div class="ideals double-border" style="margin-top: 2mm">
{{#markdown}}{{evaluateShortString character._id character.ideals}}{{/markdown}}
<div class="paper-font-subhead layout vertical center">
Ideals
</div>
</div>
<div class="bonds double-border" style="margin-top: 2mm">
{{#markdown}}{{evaluateShortString character._id character.bonds}}{{/markdown}}
<div class="paper-font-subhead layout vertical center">
Bonds
</div>
</div>
<div class="flaws double-border" style="margin-top: 2mm">
{{#markdown}}{{evaluateShortString character._id character.flaws}}{{/markdown}}
<div class="paper-font-subhead layout vertical center">
Flaws
</div>
</div>
<div class="background double-border flex layout vertical" style="margin-top: 2mm">
<div class="paper-font-subhead layout vertical center" style="margin-bottom: 4mm">
Notes
</div>
<div class="flex lined-background">
<!-- lined space for writing -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,80 @@
import QRCode from "qrcode"
Template.printedCharacterSheet.onRendered(function(){
// Quickfit is only called once on rendering, text will not resize reactively
this.$(".shrink-to-fit").quickfit({
min: 7,
max: 36,
truncate: true,
});
let url = `https://dicecloud.com/character/${this.data._id}`;
let canvas = this.find("#qrCode");
QRCode.toCanvas(canvas, url, {
margin: 0,
width: 200,
}, function(error){
$(canvas).css("width", "60px").css("height", "60px");
if (error) console.error(error)
});
});
Template.printedCharacterSheet.helpers({
character(){
return Characters.findOne(this._id);
},
classes: function(){
return Classes.find({charId: this._id}, {sort: {createdAt: 1}});
},
weaponProfs: function(){
var profs = Proficiencies.find({charId: this._id, type: "weapon"});
return removeDuplicateProficiencies(profs);
},
armorProfs: function(){
var profs = Proficiencies.find({charId: this._id, type: "armor"});
return removeDuplicateProficiencies(profs);
},
toolProfs: function(){
var profs = Proficiencies.find({charId: this._id, type: "tool"});
return removeDuplicateProficiencies(profs);
},
languageProfs: function(){
var profs = Proficiencies.find({charId: this._id, type: "language"});
profs = removeDuplicateProficiencies(profs);
if (profs.length > 3){
var halfway = Math.floor(profs.length / 2);
var left = profs.slice(0, halfway);
var right = profs.slice(halfway);
return {left, right};
} else {
return {left: profs, right: []};
}
},
attacks: function(){
return Attacks.find(
{charId: this._id, enabled: true},
{sort: {color: 1, name: 1}});
},
hitDiceTotal: function(){
let d6 = Characters.calculate.attributeValue(this._id, "d6HitDice");
let d8 = Characters.calculate.attributeValue(this._id, "d8HitDice");
let d10 = Characters.calculate.attributeValue(this._id, "d10HitDice");
let d12 = Characters.calculate.attributeValue(this._id, "d12HitDice");
d6 = d6 ? d6 + "d6" : "";
d8 = d8 ? d8 + "d8" : "";
d10 = d10 ? d10 + "d10" : "";
d12 = d12 ? d12 + "d12" : "";
return [d6, d8, d10, d12].filter(Boolean).join(" ");
},
characterUrl: function(){
return `/character/${this._id}`
},
});
Template.printedCharacterSheet.events({
"click .printButton": function(event, instance){
print();
},
"click .backButton": function(event, instance){
history && history.back();
},
});

View File

@@ -0,0 +1,20 @@
.printedLongStat .title {
white-space: nowrap;
margin-left: 2mm;
}
.printedLongStat .numbers {
z-index: 1;
min-width: 74px;
min-height: 45px;
}
.printed .printedLongStat.double-border{
padding: 0;
}
.printed .printedLongStat.double-border:before {
top: 4px;
bottom: 4px;
left: 33px;
}

View File

@@ -0,0 +1,16 @@
<template name="printedLongStat">
<div class="printedLongStat layout horizontal double-border">
<div class="numbers paper-font-display1 octogon-border">
{{#if stat}}
{{#if isSkill}}
{{prefix}}{{skillMod}}
{{else}}
{{prefix}}{{characterCalculate "attributeValue" ../_id stat}}
{{/if}}
{{/if}}
</div>
<div class="paper-font-subhead title flex layout horizontal center">
{{name}}
</div>
</div>
</template>

View File

@@ -0,0 +1,9 @@
Template.printedLongStat.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.stat
)
);
},
});

View File

@@ -0,0 +1,3 @@
.printedProficiency iron-icon {
margin-right: 2mm;
}

View File

@@ -0,0 +1,6 @@
<template name="printedProficiency">
<div class="printedProficiency layout horizontal center">
<iron-icon icon="{{profIcon}}"></iron-icon>
<div>{{getName}}</div>
</div>
</template>

View File

@@ -0,0 +1,40 @@
Template.printedProficiency.helpers({
profIcon: function(){
var prof = this.value;
if (prof > 0 && prof < 1) return "image:brightness-2";
if (prof === 1) return "image:brightness-1";
if (prof > 1) return "av:album";
return "radio-button-off";
},
getName: function(){
if (this.type === "skill") return skills[this.name];
if (this.type === "save") return saves[this.name];
return this.name;
},
});
Template.printedProficiency.events({
"click .proficiency": function(event, instance){
if (this.parent.collection == "Characters") {
if (this.parent.group == "background") {
pushDialogStack({
template: "backgroundDialog",
data: {
"charId": this.charId,
"field":"background",
"title":"Background",
"color":"j",
},
element: event.currentTarget,
})
return;
}
}
openParentDialog({
parent: this.parent,
charId: this.charId,
element: event.currentTarget,
});
}
});

View File

@@ -0,0 +1,10 @@
.printedSkillRow {
height: 24px;
min-width: 140px;
}
.printedSkillRow .skill-mod {
width: 36px;
text-align: center;
font-size: 3.5mm;
}

View File

@@ -0,0 +1,21 @@
<template name="printedSkillRow">
<div class="printedSkillRow layout horizontal center">
<iron-icon icon="{{profIcon}}"></iron-icon>
{{#if failSkill}}
<div class="fail skill-mod">fail</div>
{{else}}
<div class="{{advantage}} skill-mod">
{{skillMod}}
</div>
{{/if}}
<div flex>
{{name}}
{{#if conditionalCount}}
*
{{/if}}
{{#if showPassive}}
({{characterCalculate "passiveSkill" ../_id skill}})
{{/if}}
</div>
</div>
</template>

View File

@@ -0,0 +1,41 @@
Template.printedSkillRow.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.skill
)
);
},
profIcon: function(){
var charId = Template.parentData()._id;
var prof = Characters.calculate.proficiency(charId, this.skill);
if (prof > 0 && prof < 1) return "image:brightness-2";
if (prof === 1) return "image:brightness-1";
if (prof > 1) return "av:album";
return "radio-button-unchecked";
},
failSkill: function(){
var charId = Template.parentData()._id;
return Effects.find({
charId: charId,
stat: this.skill,
enabled: true,
operation: "fail",
}).count();
},
advantage: function(){
var charId = Template.parentData()._id;
var advantage = Characters.calculate.advantage(charId, this.skill);
if (advantage > 0) return "advantage";
if (advantage < 0) return "disadvantage";
},
conditionalCount: function(){
var charId = Template.parentData()._id;
return Effects.find({
charId: charId,
stat: this.skill,
enabled: true,
operation: "conditional",
}).count();
},
});

View File

@@ -0,0 +1,7 @@
.printedSquareStat {
min-width: 67px;
}
.printedSquareStat .title.paper-font-subhead {
font-size: 2.5mm !important;
}

View File

@@ -0,0 +1,14 @@
<template name="printedSquareStat">
<div class="printedSquareStat layout vertical center {{class}}">
<div class="numbers paper-font-display1">
{{#if isSkill}}
{{prefix}}{{skillMod}}
{{else}}
{{prefix}}{{characterCalculate "attributeValue" ../_id stat}}
{{/if}}
</div>
<div class="paper-font-subhead title">
{{name}}
</div>
</div>
</template>

View File

@@ -0,0 +1,9 @@
Template.printedSquareStat.helpers({
skillMod: function() {
return signedString(
Characters.calculate.skillMod(
Template.parentData()._id, this.stat
)
);
},
});

View File

@@ -23,4 +23,17 @@
<meta name="msapplication-TileColor" content="#b91d1d">
<meta name="msapplication-TileImage" content="/mstile-144x144.png?v=lk6WXp6Pmj">
<meta name="theme-color" content="#d12929">
<style type="text/css" media="print">
@page {
margin: 0mm;
}
html {
margin: 0px;
}
* {
-webkit-transition: none !important;
transition: none !important;
}
</style>
</head>

View File

@@ -68,7 +68,7 @@
transform-origin: top left;
transition: top 400ms ease, left 400ms ease;
z-index: 3;
overflow: hidden;
overflow: visible;
}
.dialog-stack .dialog .testButton {

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,8 @@
"babel-runtime": "^6.23.0",
"bcrypt": "^1.0.3",
"bower": "^1.7.9",
"core-js": "^2.5.1"
"core-js": "^2.5.1",
"meteor-node-stubs": "^0.3.2",
"qrcode": "^1.2.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB