Prototyped the barcode idea; Added a basic production system.

This commit is contained in:
Wynne Crisman
2019-10-07 15:51:50 -07:00
parent 8211da6b39
commit 2e57558ef4
50 changed files with 8949 additions and 782 deletions

View File

@@ -1,31 +1,46 @@
<template name="LabelMaker">
<div id="labelMaker">
<div class="labelOptions"></div>
<div class="labelContents">
<div class="labelOptions">
<div>Label Size</div>
<div><label for="labelWidth">Width (mm):</label> <input type="number" name="labelWidth" class="labelWidth input" step="0.25" value="{{labelWidth}}"/></div>
<div><label for="labelHeight">Height (mm):</label> <input type="number" name="labelHeight" class="labelHeight input" step="0.25" value="{{labelHeight}}"/></div>
<div><label for="labelSpacing">Spacing (in):</label> <input type="number" name="labelSpacing" class="labelSpacing input" step="0.05" value="{{labelSpacing}}"/></div>
<div>Label Contents</div>
<!-- <label for="title1YOffset">Vertical Offset:</label> <input type="number" name="title1YOffset" class="title1YOffset input" value="{{title1YOffset}}"/>-->
<div><label for="title1">Title:</label> <input type="text" name="title1" class="title1 input" value="{{title1}}"/></div>
<div><label for="title2">Title:</label> <input type="text" name="title2" class="title2 input" value="{{title2}}"/></div>
<div><label for="ingredients" style="vertical-align: top">Ingredients:</label> <textarea name="ingredients" class="ingredients">{{ingredients}}</textarea></div>
<div><label for="date">Date:</label> <input type="number" name="date" class="date input" value="{{date}}"/></div>
<div><label for="date">Starting Number:</label> <input type="number" name="startNumber" class="startNumber input" value="{{startNumber}}"/></div>
<div><label for="date">Count:</label> <input type="number" name="count" class="count input" value="{{count}}"/></div>
<div><button name="generate" class="generate">Generate</button></div>
<div><button name="print" class="print">Print</button></div>
<div><button name="preview" class="preview">Preview</button></div>
</div>
<div class="labelContainer">
<div class="labelSample label">
<img class="labelLogo" alt="logo" src="/images/PetitTetonLabelLogo_2in Width_v1.png"/>
<div class="labelTagline">We grow it. We can it.</div>
{{{labelText}}}
<div class='label'>
<!-- /images/Logo_0.8x0.73_300ppi.png-->
<!-- <div class='barcodeContainer'><svg class='barcode' jsbarcode-format='upc' jsbarcode-value='123456789012' jsbarcode-textmargin='0' jsbarcode-fontoptions='bold' jsbarcode-margin='0' jsbarcode-width='1.5em' jsbarcode-height='10em'></svg></div>-->
<!-- <img class="qrcode" src="">-->
<div id="qrcode" class="qrcode"></div>
<img class='labelLogo' alt='logo' src='/images/3x2 Label Logo BW.svg'/>
<div class='title1'>{{title1}}</div>
<div class='title2'>{{title2}}</div>
<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>: {{ingredients}}</div>
<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'></span> FD1951 (<span class="date">{{date}}</span>)</div>
<div class='instructions'>Refrigerate after opening; return jar when done</div>
<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>
<div class='website'>www.PetitTeton.com</div>
</div>
</div>
</div>
</template>
<!-- <div class="canvasContainer">-->
<!-- &lt;!&ndash; 3x2" == 76x50mm; 300ppi == 11.81ppmm; So 3x2" == 897 x 590x. &ndash;&gt;-->
<!--&lt;!&ndash; <canvas class="labelCanvas" width="{{labelPxWidthActual}}" height="{{labelPxHeightActual}}">&ndash;&gt;-->
<!--&lt;!&ndash; </canvas>&ndash;&gt;-->
<!-- <canvas class="labelCanvas" width="{{labelPxWidth}}" height="{{labelPxHeight}}">-->
<!-- </canvas>-->
<!-- </div>-->
<div class="testImage">
<template name="Labels">
{{each label}}
<div class="label">
<img class="labelLogo" alt="logo" src="/images/PetitTetonLabelLogo_2in Width_v1.png"/>
<div class="labelTagline">We grow it. We can it.</div>
{{{labelText}}}
</div>
<div class="printableLabel"></div>
</div>
{{/each}}
</template>

View File

@@ -1,93 +1,163 @@
#labelMaker
margin: 10px 20px
height: 100%
width: 100%
margin 10px 20px
height 100%
width 100%
overflow-y auto
.labelContents
label
font-family: TimesNewRoman, Times New Roman, Times
font-weight: 200
font-size: 14px
.title1
width: 500px
font-family: TimesNewRoman, Times New Roman, Times
font-size: .142in
font-weight: 800
line-height: .142in
.title2
width: 500px
font-family: TimesNewRoman, Times New Roman, Times
font-size: 14px
font-weight: 800
line-height: 16px
.ingredients
width: 500px
height: 100px
font-family: TimesNewRoman, Times New Roman, Times
font-size: 12px
font-weight: 100
line-height: 14px
.date
width: 500px
font-family: TimesNewRoman, Times New Roman, Times
font-size: 12px
font-weight: 100
line-height: 14px
.labelContainer
text-align: center
width: 100%
width-min: 3in
height-min: 2in
background-color: grey
padding: 20px
.labelSample
display: inline-block
width: 3in
height: 2in
background-color: white
color: black
text-align: center
.labelLogo
width: .8in
.labelTagline
font-family: TimesNewRoman, Times New Roman, Times
font-size: .1in
font-weight: 100
line-height: .2in
@media not print
.labelOptions
label
font-family TimesNewRoman, Times New Roman, Times
font-weight 200
font-size 14px
.title1
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .2in
font-weight: 800
text-transform: uppercase
width 500px
font-family TimesNewRoman, Times New Roman, Times
font-size .142in
font-weight 800
line-height .142in
.title2
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .15in
font-weight: 800
width 500px
font-family TimesNewRoman, Times New Roman, Times
font-size 14px
font-weight 800
line-height 16px
.ingredients
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .1in
font-weight: 100
width 500px
height 100px
font-family TimesNewRoman, Times New Roman, Times
font-size 12px
font-weight 100
line-height 14px
.date
width 500px
font-family TimesNewRoman, Times New Roman, Times
font-size 12px
font-weight 100
line-height 14px
.labelContainer
text-align center
width 100%
width-min 3in
height-min 2in
background-color grey
padding 20px
.labels
display none
.printableLabel
display none
.label
display inline-block
width 3in
height 2in
.canvasContainer
padding 10px
background-color gray
@media all
.label
position relative
background-color white
color black
text-align center
font-family TimesNewRoman, Times New Roman, Times
//font-family Arial, Helvetica, sans-serif
font-size .1in
width 3in
height 2in
.barcodeContainer
position absolute
transform rotate(270deg) scale(0.7)
right -10em
top 7em
.qrcode
position absolute
left 3px
top 3px
//padding: 2px
//border: 2px solid black
.labelLogo
width 8em
padding 0
margin 0
padding-top .15em
margin-bottom .2em
.labelLogo3
width 14em
padding 0
margin 0
padding-top .15em
margin-bottom .8em
.labelTagline
font-size 1em
font-weight 100
line-height 1em
.title1
width 100%
font-size 2.5em
line-height .9em
font-weight 800
text-transform uppercase
.title2
width 100%
font-size 1.5em
line-height .9em
font-weight 800
padding-bottom .2em
.ingredients
width 100%
font-size 1.2em
font-weight 100
line-height 1em
min-height 2em
.ingredientsEnding
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .1in
font-weight: 100
width 100%
font-size 1.2em
font-weight 100
.instructions
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .1in
font-weight: 800
width 100%
font-size 1.2em
font-weight 800
.address
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .1in
font-weight: 100
width 100%
font-size 1.2em
font-weight 100
.website
width: 100%
font-family: TimesNewRoman, Times New Roman, Times
font-size: .1in
font-weight: 100
width 100%
font-size 1.2em
font-weight 100
@media print
@page
size 3in 2in
margin 0
padding 0
.labelOptions, .labelContainer, .canvasContainer, .labelCanvas
display none
.printableLabel
display block
margin 0
padding 0
//.printableLabels
// display inline-block
// margin 0
// padding 0
//#labelMaker
// display none
//.labels
// display block
//.label
// display block
// width 3in
// height 2in
// //width 100%
// //height 100%
// page-break-after always
// page-break-inside avoid
.canvasContainer
display none
@media print
.labelMaker
margin 0
padding 0
overflow visible

View File

@@ -1,75 +1,416 @@
import dti from 'dom-to-image';
import './Label.html';
import PDF from 'jspdf';
import { saveAs } from 'file-saver';
import swal from 'sweetalert2';
import dragula from 'dragula';
//import JsBarcode from 'JsBarcode';
import QRCode from '/imports/util/qrcode/qrcode';
//let QRCode = require('/imports/util/qrcode/qrcode.js');
console.log(QRCode);
//let {qrcode, svg2url} = require('pure-svg-code');
//let QRCode = require('qrcode-svg');
//******************************************************************
//** Creates printable labels for a roll style printer.
//******************************************************************
let PREFIX = "LabelMaker_";
let PX_PER_MM = 300 / 25.4;
let SCREEN_PX_PER_MM = 96 / 25.4;
let imagePath = "/images/3x2 Label Logo BW.svg";//"/images/Logo_0.8x0.73_300ppi.png";
function generateLabels(title1, title2, ingredients, date, count, topSpacing, bottomSpacing) {
//TODO: Allow logo to be removed or altered with an alternative logo for sizing fixes
let label = "<div class='label'>" +
"<div class='barcodeContainer'><svg class='barcode' jsbarcode-format='UPC' jsbarcode-value='123456789012' jsbarcode-textmargin='0' jsbarcode-fontoptions='bold' jsbarcode-margin='0' jsbarcode-width='2in' jsbarcode-height='10em'></svg></div>" +
//"<div style='height: " + (topSpacing) + "in'></div>" +
//"<img class='labelLogo' alt='logo' src='/images/PetitTetonLabelLogo_2in Width_v1.png'/>" +
"<img class='labelLogo' alt='logo' src='" + imagePath + "'/>" +
//"<div class='labelTagline'>We grow it. We can it.</div>" +
"<div class='title1'>" + title1 + "</div>" +
"<div class='title2'>" + (title2 === undefined ? "" : title2) + "</div>" +
"<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>:" + ingredients + "</div>" +
"<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'>8oz</span> FD1951 (" + date + ")</div>" +
"<div class='instructions'>Refrigerate after opening; return jar when done</div>" +
"<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>" +
"<div class='website'>www.PetitTeton.com</div>" +
//"<div style='height: " + bottomSpacing + "in'></div>" +
"</div>";
let labels = "";
//TODO: Include bar code and identifying numbers.
for(let i = 0; i < count; i++) {
labels += label;
}
return labels;
}
function printImageOld(template, dataUrl) {
let img = new Image();
img.onload = function() {
let imageCanvas = $('.labelCanvas')[0];
let ctx = imageCanvas.getContext('2d');
//let threshold = 255 + 200 + 0;
let desiredContrast = 100;
let contrastCorrectionFactor = (259 * (desiredContrast + 255)) / (255 * (259 - desiredContrast));
imageCanvas.width = img.width;
imageCanvas.height = img.height;
//console.log("Generated image: " + img.width + ", " + img.height);
//ctx.scale(0.25, 0.25);
ctx.drawImage(img, 0, 0);
let imageData = ctx.getImageData(0, 0, imageCanvas.width, imageCanvas.height);
let data = imageData.data;
//Run three times
for(let c = 0; c < 3; c++) {
//Set each pixel to either black or white with the given threshold.
for(let i = 0; i < data.length; i += 4) {
data[i] = contrastCorrectionFactor * (data[i] - 128) + 128;
data[i + 1] = contrastCorrectionFactor * (data[i + 1] - 128) + 128;
data[i + 2] = contrastCorrectionFactor * (data[i + 2] - 128) + 128;
//if(data[i] + data[i + 1] + data[i + 2] < threshold) {
// data[i] = 0;
// data[i + 1] = 0;
// data[i + 2] = 0;
//}
//else {
// data[i] = 255;
// data[i + 1] = 255;
// data[i + 2] = 255;
//}
data[i + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
//Convert the canvas content to an image for printing. (cannot print a canvas?)
//sendToPrinter(template, imageCanvas.toDataURL("image/png"));
//Save to disk.
//imageCanvas.toBlob(function(blob) {
// saveAs(blob, "label.png");
//}, "image/png", 1);
//imageCanvas.toBlob(function(blob) {
// let url = URL.createObjectURL(blob);
// window.open(url, "_blank");
//}, "image/png", 1);
//let imgData = imageCanvas.toDataURL('image/jpeg', 1.0);
imageCanvas.toBlob(function(blob) {
let pdf = new PDF();
let url = URL.createObjectURL(blob);
pdf.addImage(url, "image/png", 0, 0);
pdf.save("label.pdf");
}, "image/png", 1);
};
img.src = dataUrl;
}
function printImage(template, raw, width, height) {
let data = raw;
//Run three times
for(let c = 0; c < 3; c++) {
//Set each pixel to either black or white with the given threshold.
for(let i = 0; i < data.length; i += 4) {
data[i] = contrastCorrectionFactor * (data[i] - 128) + 128;
data[i + 1] = contrastCorrectionFactor * (data[i + 1] - 128) + 128;
data[i + 2] = contrastCorrectionFactor * (data[i + 2] - 128) + 128;
//if(data[i] + data[i + 1] + data[i + 2] < threshold) {
// data[i] = 0;
// data[i + 1] = 0;
// data[i + 2] = 0;
//}
//else {
// data[i] = 255;
// data[i + 1] = 255;
// data[i + 2] = 255;
//}
data[i + 3] = 255;
}
}
let url = URL.createObjectURL(new Blob(raw, {type: "image/png"}));
let test = new Image();
test.src = url;
$('.testImage').html(test);
}
// Note: Not working correctly. Output seems to be at 96dpi instead of 300+dpi. Using a large image scaled down in in an Image tag seems to not print well.
function sendToPrinter(template, dataUrl) {
let canvasImg = new Image();
canvasImg.onload = function() {
//window.print();
};
canvasImg.src = dataUrl;
canvasImg.style.height = template.labelHeight.get() + "mm";
canvasImg.style.width = template.labelWidth.get() + "mm";
$(".printableLabel").html(canvasImg);
//$('.printableLabel').html(img);
window.print();
}
function loadImage(url) {
return new Promise(r => { let i = new Image(); i.onload = (() => r(i)); i.src = url; });
}
async function refreshLabelCanvas(template) {
let mmWidth = template.labelWidth.get();
let mmHeight = template.labelHeight.get();
let title1 = template.title1.get();
let title1Font = template.title1Font.get();
let title1YOffset = template.title1YOffset.get();
let title2 = template.title2.get();
let title2Font = template.title2Font.get();
let title2YOffset = template.title2YOffset.get();
let ingredients = template.ingredients.get();
let ingredientsFont = template.ingredientsFont.get();
let ingredientsYOffset = template.ingredientsYOffset.get();
let date = template.date.get();
let $labelCanvas = $('.labelCanvas');
let ctx = $labelCanvas[0].getContext('2d');
let width = Math.floor(mmWidth * PX_PER_MM);
let height = Math.floor(mmHeight * PX_PER_MM);
let center = width / 2;
let img = await loadImage(imagePath);
let imageWidth = 241;
let imageHeight = 219;
let ingredientsY = 180;
let ingredientsEndingY = 240;
let instructionsY = 280;
let addressY = 320;
let websiteY = 380;
ctx.fillStyle = 'white';
ctx.fillRect(0,0, width, height);
ctx.fillStyle = 'black';
ctx.drawImage(img, center - (imageWidth / 2), 0);
ctx.font = title1Font;
ctx.textAlign = 'center';
ctx.fillText(title1, center, title1YOffset);
////TODO: Allow logo to be removed or altered with an alternative logo for sizing fixes
//let label = "<div class='label'>" +
// "<div style='height: " + topSpacing + "in'></div>" +
// //"<img class='labelLogo' alt='logo' src='/images/PetitTetonLabelLogo_2in Width_v1.png'/>" +
// "<img class='labelLogo' alt='logo' src='/images/Logo_0.8x0.73_300ppi.png'/>" +
// "<div class='labelTagline'>We grow it. We can it.</div>" +
// "<div class='title1'>" + title1 + "</div>" +
// "<div class='title2'>" + (title2 === undefined ? "" : title2) + "</div>" +
// "<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>:" + ingredients + "</div>" +
// "<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'>8oz</span> FD1951 (" + date + ")</div>" +
// "<div class='instructions'>Refrigerate after opening; return jar when done</div>" +
// "<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>" +
// "<div class='website'>www.PetitTeton.com</div>" +
// "<div style='height: " + bottomSpacing + "in'></div>" +
// "</div>";
//let labels = "";
//
////TODO: Include bar code and identifying numbers.
//for(let i = 0; i < count; i++) {
// labels += label;
//}
//
//return labels;
}
function bufferToBase64(buf) {
var binstr = Array.prototype.map.call(buf, function (ch) {
return String.fromCharCode(ch);
}).join('');
return btoa(binstr);
}
Template.LabelMaker.onCreated(function() {
this.labelWidth = new ReactiveVar(76);
this.labelHeight = new ReactiveVar(50);
this.title1 = new ReactiveVar("Strawberry");
this.title2 = new ReactiveVar("w/ Espelette");
this.ingredients = new ReactiveVar("*strawberry, sugar, *espelette");
this.date = new ReactiveVar(19001);
//Session.set(PREFIX + "title1", "Strawberry");
//Session.set(PREFIX + "title1Font", "50px Times New Roman");
//Session.set(PREFIX + "title1YOffset", 260);
//Session.set(PREFIX + "title2", "w/ Espelette");
//Session.set(PREFIX + "title2Font", "40px Times New Roman");
//Session.set(PREFIX + "title2YOffset", 340);
//Session.set(PREFIX + "ingredients", "*strawberry, sugar, *espelette");
//Session.set(PREFIX + "date", 19001);
//Session.set(PREFIX + "count", 3);
});
Template.LabelMaker.onRendered(function() {
let template = this;
//Re-run this routine when ever the session variables change.
//Tracker.autorun(function() {
// //refreshLabelCanvas(Session.get(PREFIX + "title1"), Session.get(PREFIX + "title2"), Session.get(PREFIX + "ingredients"), Session.get(PREFIX + "date"), 1, Session.get(PREFIX + "labelSpacing"), 0);
// refreshLabelCanvas(template);
//});
//
//template.$('.labelContainer').on('DOMSubtreeModified', function() {
// console.log("Initialized barcode");
// //
//});
//JsBarcode(".barcode").init();
//const svgString = qrcode({content: "1234567890", padding: 0, width: 60, height: 60, color: "#000000", background: "#FFFFFF", ecl: "L"});
//this.$('.qrcode').attr('src', svg2url(svgString));
//this.$('.qrcode').attr('src', svg2url(new QRCode({content: '1234567890', width: 80, height: 80, color: "#000000", background: "#FFFFFF"}).svg()));
new QRCode(document.getElementById("qrcode"), {text: "1234567890", width: 60, height: 60});
});
Template.LabelMaker.onDestroyed(function() {
});
Template.LabelMaker.events({
'change .title1': function(event, template) {
Session.set(PREFIX + "title1", $(event.target).val());
'change .labelWidth': function(e, t) {
let x = $(e.target).val();
t.labelWidth.set(!Number.isNaN(x) && x > 0 ? parseFloat(x) : 76);
},
'change .title2': function(event, template) {
Session.set(PREFIX + "title2", $(event.target).val());
'change .labelHeight': function(event, template) {
let x = $(event.target).val();
t.labelHeight.set(!Number.isNaN(x) && x > 0 ? parseFloat(x) : 50);
},
'change .ingredients': function(event, template) {
Session.set(PREFIX + "ingredients", $(event.target).val());
},
'change .date': function(event, template) {
Session.set(PREFIX + "date", parseInt($(event.target).val()));
},
'click .generate': function(event, template) {
'change .title1': function(e, t) {t.title1.set($(e.target).val());},
'change .title1Font': function(e, t) {t.title1Font.set($(e.target).val());},
'change .title1YOffset': function(e, t) {t.title1YOffset.set(parseInt($(e.target).val()));},
'change .title2': function(e, t) {t.title2.set($(e.target).val());},
'change .title2Font': function(e, t) {t.title2Font.set($(e.target).val());},
'change .title2YOffset': function(e, t) {t.title2YOffset.set(parseInt($(e.target).val()));},
'change .ingredients': function(e, t) {t.ingredients.set($(e.target).val());},
'change .ingredientsFont': function(e, t) {t.ingredientsFont.set($(e.target).val());},
'change .ingredientsYOffset': function(e, t) {t.ingredientsYOffset.set(parseInt($(e.target).val()));},
'change .date': function(e, t) {t.date.set(parseInt($(e.target).val()));},
'click .preview': function(event, template) {
let params = {};
params['title1'] = "Strawberry";
params['title2'] = "";
params['ingredients'] = "Fairies";
params['date'] = "1234";
window.open('/PrintLabel?' + $.param(params));
},
'click .print': function(event, template) {
let _this = template;
let node = $('.label')[0];
let width = _this.labelWidth.get();
let height =_this.labelHeight.get();
let title1 =_this.title1.get();
let title2 =_this.title2.get();
let ingredients =_this.ingredients.get();
let date =_this.date.get();
Meteor.call('printLabels', width, height, "3x2Standard", title1, title2, ingredients, date, (error, result) => {
if(error) {
console.log(error);
}
else {
const blob = new Blob([result], {type: 'application/pdf'});
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "labels.pdf";
link.click();
}
});
//dti.toBlob(node, {bgcolor: 'white'}).then(function(blob) {
// printImage(template, blob);
//}).catch(function(err) {
// console.log(err);
//});
//dti.toPng(node, {bgcolor: 'white'}).then(function(dataUrl) {
// printImageOld(template, dataUrl);
//}).catch(function(err) {
// console.log(err);
//});
//let count = template.$('input[name="count"]').val();
//let spacing = Session.get(PREFIX + "labelSpacing");
//
////count = (count === undefined || Number.isNaN(count)) ? 1 : parseInt(count);
////
////if(count < 1) {
//// count = 1;
////}
////
////Session.set(PREFIX + "generatedLabels", generateLabels(Session.get(PREFIX + "title1"), Session.get(PREFIX + "title2"), Session.get(PREFIX + "ingredients"), Session.get(PREFIX + "date"), count, 0, spacing));
//
//let $label = $('.printableLabel');
//
//console.log($label);
//console.log($label.length);
//domtoimage.toPng($label).then(function(dataUrl) {
// console.log("A");
// let img = new Image();
// img.src = dataUrl;
// $('.printableLabel').html(img);
// //caman('.printableLabel img', function() {
// // this.contrast(100);
// // this.render();
// // window.print();
// //});
//}).catch(function(error) {
// console.error("failed to convert dom to image");
// console.error(error);
//});
}
});
Template.LabelMaker.helpers({
title1: function() {return Session.get(PREFIX + "title1")},
title2: function() {return Session.get(PREFIX + "title2")},
ingredients: function() {return Session.get(PREFIX + "ingredients")},
date: function() {return Session.get(PREFIX + "date")},
labelText: function() {
return "<div class='title1'>" + Session.get(PREFIX + "title1") + "</div>" +
"<div class='title2'>" + Session.get(PREFIX + "title2") + "</div>" +
"<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>:" + Session.get(PREFIX + "ingredients") + "</div>" +
"<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'>8oz</span> FD1951 (" + Session.get(PREFIX + "date") + ")</div>" +
"<div class='instructions'>Refrigerate after opening; return jar when done</div>" +
"<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>" +
"<div class='website'>www.PetitTeton.com</div>";
}
});
Template.Labels.onCreated(function() {
});
Template.Labels.onRendered(function() {
let template = this;
});
Template.Labels.onDestroyed(function() {
});
Template.Labels.events({
});
Template.Labels.helpers({
labels: function() {return Session.get(PREFIX + "labels")},
title2: function() {return Session.get(PREFIX + "title2")},
ingredients: function() {return Session.get(PREFIX + "ingredients")},
date: function() {return Session.get(PREFIX + "date")},
labelText: function() {
return "<div class='title1'>" + Session.get(PREFIX + "title1") + "</div>" +
"<div class='title2'>" + Session.get(PREFIX + "title2") + "</div>" +
"<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>:" + Session.get(PREFIX + "ingredients") + "</div>" +
"<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'>8oz</span> FD1951 (" + Session.get(PREFIX + "date") + ")</div>" +
"<div class='instructions'>Refrigerate after opening; return jar when done</div>" +
"<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>" +
"<div class='website'>www.PetitTeton.com</div>";
title1: function() {return Template.instance().title1.get();},
title2: function() {return Template.instance().title2.get();},
ingredients: function() {return Template.instance().ingredients.get();},
date: function() {return Template.instance().date.get();},
labelWidth: function() {return Template.instance().labelWidth.get();},
labelHeight: function() {return Template.instance().labelHeight.get();},
sampleLabel: function() {
let t = Template.instance();
setTimeout(function() {JsBarcode(".barcode").init();}, 500);
return generateLabels(t.title1.get(), t.title2.get(), t.ingredients.get(), t.date.get(), 1, 0);
},
//canvasLabel: function() {
// return generateLabelCanvas(Session.get(PREFIX + "title1"), Session.get(PREFIX + "title2"), Session.get(PREFIX + "ingredients"), Session.get(PREFIX + "date"), 1, Session.get(PREFIX + "labelSpacing"), 0);
//},
labels: function() {
return Session.get(PREFIX + "generatedLabels");
},
labelPxWidth: function() {
//console.log("label width: " + Template.instance().labelWidth.get());
//console.log("px_per_mm: " + PX_PER_MM);
//console.log("labelPxWidth: " + (Template.instance().labelWidth.get() * PX_PER_MM));
return Math.floor(Template.instance().labelWidth.get() * PX_PER_MM);
},
labelPxHeight: function() {
return Math.floor(Template.instance().labelHeight.get() * PX_PER_MM);
},
labelPxWidthActual: function() {
//console.log("label width: " + Template.instance().labelWidth.get());
//console.log("px_per_mm: " + PX_PER_MM);
//console.log("labelPxWidth: " + (Template.instance().labelWidth.get() * PX_PER_MM));
return Math.floor(Template.instance().labelWidth.get() * SCREEN_PX_PER_MM);
},
labelPxHeightActual: function() {
return Math.floor(Template.instance().labelHeight.get() * SCREEN_PX_PER_MM);
}
});

View File

@@ -0,0 +1,19 @@
<template name="PrintLabel">
<div id="PrintLabel">
<div class="labelContainer">
<div class='label'>
<!-- /images/Logo_0.8x0.73_300ppi.png-->
<!-- <div class='barcodeContainer'><svg class='barcode' jsbarcode-format='upc' jsbarcode-value='123456789012' jsbarcode-textmargin='0' jsbarcode-fontoptions='bold' jsbarcode-margin='0' jsbarcode-width='1.5em' jsbarcode-height='10em'></svg></div>-->
<div id="qrcode" class="qrcode" src=""></div>
<img class='labelLogo' alt='logo' src='/images/3x2 Label Logo BW.svg'/>
<div class='title1'></div>
<div class='title2'></div>
<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>: </div>
<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'></span> FD1951 (<span class="date"></span>)</div>
<div class='instructions'>Refrigerate after opening; return jar when done</div>
<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>
<div class='website'>www.PetitTeton.com</div>
</div>
</div>
</div>
</template>

85
imports/ui/PrintLabel.import.css vendored Normal file
View File

@@ -0,0 +1,85 @@
#PrintLabel .labelContainer {
text-align: center;
width: 100%;
width-min: 3in;
height-min: 2in;
background-color: #808080;
padding: 20px;
}
#PrintLabel .labels {
display: none;
}
#PrintLabel .printableLabel {
display: none;
}
#PrintLabel .label {
display: inline-block;
width: 3in;
height: 2in;
}
#PrintLabel .canvasContainer {
padding: 10px;
background-color: #808080;
}
#PrintLabel .label {
background-color: #fff;
color: #000;
text-align: center;
font-family: TimesNewRoman, Times New Roman, Times;
font-size: 0.2in;
width: 6in;
height: 4in;
}
#PrintLabel .label .labelLogo {
width: 8em;
padding: 0;
margin: 0;
padding-top: 0.15em;
margin-bottom: -0.25em;
}
#PrintLabel .label .labelTagline {
font-size: 1em;
font-weight: 100;
line-height: 1em;
}
#PrintLabel .label .title1 {
width: 100%;
font-size: 2.5em;
line-height: 0.9em;
font-weight: 800;
text-transform: uppercase;
}
#PrintLabel .label .title2 {
width: 100%;
font-size: 1.5em;
line-height: 0.9em;
font-weight: 800;
padding-bottom: 0.2em;
}
#PrintLabel .label .ingredients {
width: 100%;
font-size: 1.2em;
font-weight: 100;
line-height: 1em;
min-height: 2em;
}
#PrintLabel .label .ingredientsEnding {
width: 100%;
font-size: 1.2em;
font-weight: 100;
}
#PrintLabel .label .instructions {
width: 100%;
font-size: 1.2em;
font-weight: 800;
}
#PrintLabel .label .address {
width: 100%;
font-size: 1.2em;
font-weight: 100;
}
#PrintLabel .label .website {
width: 100%;
font-size: 1.2em;
font-weight: 100;
}

92
imports/ui/PrintLabel.import.styl vendored Normal file
View File

@@ -0,0 +1,92 @@
#PrintLabel
.labelContainer
text-align center
//width 100%
width-min 3in
width 3in
height-min 2in
height 2in
//background-color grey
//padding 20px
.labels
display none
.printableLabel
display none
.label
display inline-block
width 3in
height 2in
.canvasContainer
padding 10px
background-color gray
.label
position relative
background-color white
color black
text-align center
font-family TimesNewRoman, Times New Roman, Times
//font-family Arial, Helvetica, sans-serif
font-size .1in
width 3in
height 2in
.barcodeContainer
position absolute
transform rotate(270deg)
right -4.5em
top 5em
.qrcode
position absolute
left 10px
top 10px
.labelLogo
width 8em
padding 0
margin 0
padding-top .15em
margin-bottom .2em
.labelLogo3
width 14em
padding 0
margin 0
padding-top .15em
margin-bottom .8em
.labelTagline
font-size 1em
font-weight 100
line-height 1em
.title1
width 100%
font-size 2.5em
line-height .9em
font-weight 800
text-transform uppercase
.title2
width 100%
font-size 1.5em
line-height .9em
font-weight 800
padding-bottom .2em
.ingredients
width 100%
font-size 1.2em
font-weight 100
line-height 1em
min-height 2em
.ingredientsEnding
width 100%
font-size 1.2em
font-weight 100
.instructions
width 100%
font-size 1.2em
font-weight 800
.address
width 100%
font-size 1.2em
font-weight 100
.website
width 100%
font-size 1.2em
font-weight 100

32
imports/ui/PrintLabel.js Normal file
View File

@@ -0,0 +1,32 @@
import './PrintLabel.html';
import JsBarcode from 'JsBarcode';
//import QRCode from "../util/qrcode/qrcode";
import QRCode from '/imports/util/qrcode/qrcode';
//let {qrcode, svg2url} = require('pure-svg-code');
Template.PrintLabel.onCreated(function() {
function getUrlVars() {
let vars = {};
let parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = decodeURIComponent(value);
});
return vars;
}
this.vars = getUrlVars();
});
Template.PrintLabel.onRendered(function() {
let vars = this.vars;
$('.title1').html(vars['title1']);
$('.title2').html(vars['title2'] === undefined ? "" : vars['title2']);
$('.ingredients').append(vars['ingredients']);
$('.date').html(vars['date']);
$('.size').html(vars['size']);
//JsBarcode(".barcode").init();
//const svgString = qrcode({content: "1234567890", padding: 0, width: 50, height: 50, color: "#000000", background: "#FFFFFF", ecl: "L"});
//this.$('.qrcode').attr('src', svg2url(svgString));
new QRCode(document.getElementById("qrcode"), {text: "1234567890", width: 60, height: 60});
});

View File

@@ -1,5 +1,136 @@
<template name="Production">
<div id="production">
todo
{{#if Template.subscriptionsReady}}
<div class="tableControls">
<div class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr>
<th class="hasLabels"></th>
<th class="name">Name {{>BatchSearch columnName='name'}}</th>
<th class="date">Date {{>BatchDateRangeSearch columnName='date' width='90%'}}</th>
<th class="amount">Amount</th>
<th class="cook">Cook {{>BatchSearch columnName='cook' collectionQueryColumnName='name' collection='Workers' collectionResultColumnName='_id'}}</th>
<th class="canner">Canner {{>BatchSearch columnName='canner' collectionQueryColumnName='name' collection='Workers' collectionResultColumnName='_id'}}</th>
<th class="comment">Comment</th>
<th class="actions">Actions <span class="newButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span> <span class="showDeletedButton btn btn-success {{showDeletedSelected}}"><i class="fa fa-trash" aria-hidden="true"></i></span></th>
</tr>
</thead>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#if displayNew}}
{{> BatchNew}}
{{/if}}
{{#each batches}}
{{> Batch}}
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
{{else}}
{{/if}}
</div>
</template>
<template name="Batch">
<tr class="{{getRowClass}}">
{{#if editing}}
{{> BatchEditor}}
{{else}}
<td class="hasLabels noselect left"><i class="fa fa-print clickable {{hasLabelsClass}}" aria-hidden="true"></i></td>
<td class="name noselect nonclickable left">{{name}}</td>
<td class="date noselect nonclickable left">{{date}}</td>
<td class="amount noselect nonclickable left">{{amount}}</td>
<td class="cook noselect nonclickable left">{{cook}}</td>
<td class="canner noselect nonclickable left">{{canner}}</td>
<td class="comment noselect nonclickable left">{{comment}}</td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;{{#if isDeleted}}<i class="actionUndelete fa fa-check-circle fa-lg noselect clickable" title="Delete" aria-hidden="true"></i>{{else}}<i class="actionDelete fa fa-times-circle fa-lg noselect clickable" title="Delete" aria-hidden="true"></i>{{/if}}</td>
{{/if}}
</tr>
</template>
<template name="BatchEditor">
<td colspan="7" class="editorTd">
<form name="editorForm" class="insertForm" autocomplete="off">
<div class="grid">
<div class="col-4-12">
<div class="editorDiv heading">{{name}}</div>
<div class="editorDiv heading">{{date}}</div>
<div class="editorDiv heading">Cook: {{cook}} / Canner: {{canner}}</div>
</div>
<div class="col-2-12">
<div class="editorDiv"><label>Amount:</label><input type="number" class="form-control amount" name="amount" min="0" step="1" data-schema-key='amount' value="{{amount}}" required></div>
</div>
<div class="col-6-12">
<div class="editorDiv"><label for="batchEditorComment">Comment:</label><textarea id="batchEditorComment" class="comment" rows="4" cols="50" style="width: 100%; height: 80px">{{comment}}</textarea></div>
</div>
</div>
</form>
</td>
<td class="center editorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
</template>
<template name="BatchNew">
<td colspan="7" class="editorTd">
<form name="insertForm" class="insertForm" autocomplete="off">
<div class="grid">
<div class="col-4-12">
<div class="form-group">
<label class='control-label'>Product</label>
<input name="product" class="form-control" type="text" required/>
</div>
<div class="form-group">
<label class='control-label'>Date</label>
<input name="date" class="form-control" type="date" data-schema-key='date' value="{{today}}" required>
</div>
</div>
<div class="col-4-12">
<div class="form-group">
<label class='control-label'>Cook</label>
<input name="cook" class="form-control" type="text" required/>
</div>
<div class="form-group">
<label class='control-label'>Canner</label>
<input name="canner" class="form-control" type="text" required/>
</div>
</div>
{{#each productMeasures}}
{{>InsertBatchMeasure this}}
{{/each}}
</div>
<div class="editorDiv" style="width: 100%; height: 150px"><label for="batchNewComment">Comment:</label><textarea id="batchNewComment" class="comment" rows="4" cols="50" style="width: 100%; height: 120px">{{comment}}</textarea></div>
</form>
</td>
<td class="center editorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
</template>
<template name="InsertBatchMeasure">
<div class="col-2-12 insertMeasure">
<input type="hidden" class="measureId" value="{{this._id}}">
<div class="form-group">
<label class='control-label'>{{name}} Count</label>
<input type="number" class="form-control amount" name="amount" min="0" data-schema-key='amount' value="{{amount}}" required>
</div>
</div>
</template>
<template name="BatchSearch">
<div class="search">
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}"/>
</div>
</template>
<template name="BatchDateRangeSearch">
<div style="padding-right: 10px; width: {{width}};"><input type="date" class="searchDateStartInput" value="{{startDate}}" data-schema-key='date' required> - <input type="date" class="searchDateEndInput" value="{{endDate}}" data-schema-key='date' required></div>
</template>

View File

@@ -1,60 +1,142 @@
#production
margin: 10px 20px
display: table
content-box: border-box
padding: 10px 20px
height: 100%
//Flex container options.
flex-flow: column nowrap
justify-content: space-around //Spacing between sales along the primary axis. (vertical spacing for a column layout)
align-items: flex-start //Align the sales within a line along the primary axis. (horizontal alignment for a column layout)
align-content: center //Spacing between lines along the secondary axis. (spacing between columns for a column layout)
display: -webkit-box
display: -moz-box
display: -ms-flexbox
display: -moz-flex
display: -webkit-flex
display: flex
width: 100%
text-align: left
.editor
height: 100%
overflow-y: auto
.insertSale
flex: none
.tableControls
text-align: right
margin-right: 20px
margin-bottom: 4px
display: table
width: 100%
.contentControls
vertical-align: bottom
display: table-cell
text-align: right
min-width: 100px
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.formGroupHeading
font-size: 1.6em
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
font-style: normal
font-variant: normal
font-weight: 500
.grid
flex: auto
align-self: stretch
overflow-y: auto
overflow-x: auto
margin-bottom: 20px
border: 0
padding-top: 20px
.table > thead > tr > th
border: 0
padding-top: 0
padding-bottom: 6px
.left
text-align: left
.center
text-align: center
.dataTable
table-layout: fixed
.tdLarge
font-size: 1.3em
.saleRemove
color: red
margin-left: 8px
.saleEdit
color: darkblue
margin-right: 8px
.table
table-layout: fixed
min-width: 100%
thead, tbody
> tr
> .hasLabels
width: 30px
.hasLabels
color green
.noLabels
color red
> .name
//width: auto
width: 100%
> .date
//width: auto
min-width: 150px
max-width: 180px
> .amount
//width: auto
min-width: 100px
max-width: 100px
> .cook
//width: auto
min-width: 150px
max-width: 180px
> .canner
//width: auto
min-width: 150px
max-width: 180px
> .comment
width: 220px
min-width: 220px
max-width: 220px
> .actions
width: 90px
min-width: 90px
max-width: 90px
> tr.deleted
background-color: gray
.separatedTableHeader
table
thead
> tr
> th.actions
text-align: center
.newButton
margin-top: 4px
padding: 0 12px
.fa-plus-circle
display: inline-block
.fa-times-circle
display: none
.newButton:active
background-color: #fb557b
color: black
.fa-times-circle
display: inline-block
.fa-plus-circle
display: none
.showDeletedButton
margin-top: 4px
padding: 0 12px
color gray
.showDeletedButton.selected
color black
.listRow
display: table-row
.listCell
display: table-cell
position: relative
height: 100%
width: 100%
.tableContainer
position: absolute
top: 0
bottom: 0
left: 0
right: 0
width: auto
height: auto
border: 0
font-size: 12.5px
overflow-y: auto
table
thead
visibility: hidden
display: none
.search
margin: 3px 0 2px 1px
.editorTd
background: #deeac0
input, select
width: 100%
.editorDiv
margin: 4px 0
label
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
font-size: .9em
padding-bottom: 4px
select2
font-size: .4em
> tbody
> tr
.actionRemove
color: #F77
.actionEdit
color: #44F
.editorApply
color: green
.editorCancel
color: red

View File

@@ -1,2 +1,446 @@
import './Production.html';
let QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "Production.";
Tracker.autorun(function() {
//Meteor.subscribe("batches");
Meteor.subscribe("workers");
Meteor.subscribe("products");
Meteor.subscribe("measures");
});
Template.Production.onCreated(function() {
Session.set(PREFIX + "displayNew", false); //Whether the new dialog is inlined in the table and visible.
Session.set(PREFIX + "sortOption", 'date'); //Allows us to sort the results of the batch query by batch attribute.
Session.set(PREFIX + 'skipCount', 0); //Allows us to page through the results of the batch query. Currently not used.
Session.set(PREFIX + 'batchCount', 0); //A count of all batches in the system (that fit our current query). Useful for paging and dynamic loading.
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
Session.set(PREFIX + "showDeleted", false);
Session.set(PREFIX + "editedId", undefined);
Tracker.autorun(function() {
let sortOption = Session.get(PREFIX + "sortOption");
let sort = sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1};
//let showOnlyComments = Session.get(PREFIX + "showOnlyComments"); //Not needed here. Shows how to limit the query to only records with certain features.
let query = _.clone(Session.get(PREFIX + 'searchQuery'));
//if(showOnlyComments) {
// if(!query) query = {};
// query.comment = {$exists: true};
//}
if(!Session.get(PREFIX + "showDeleted")) {
if(!query) query = {};
query.deletedAt = {$exists: false};
}
Template.Production.batchesSubscription = Meteor.subscribe("batches", query, sort, QUERY_LIMIT, Session.get(PREFIX + 'skipCount'));
Session.set(PREFIX + 'batchCount', Meteor.call('getBatchCount', Session.get(PREFIX + 'searchQuery')));
});
});
Template.Production.onDestroyed(function() {
if(Template.Production.batchSubscription) {
Template.Production.batchSubscription.stop();
}
});
Template.Production.onRendered(function() {
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.Production.helpers({
displayNew: function() {
return Session.get(PREFIX + "displayNew");
},
batches: function() {
let sortOption = Session.get(PREFIX + "sortOption");
return Meteor.collections.Batches.find({}, {sort: (sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1})});
},
disableLoadMore: function() {
return Session.get(PREFIX + 'batchCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
},
showDeletedSelected: function() {
return (Session.get(PREFIX + "showDeleted")) ? "selected" : "";
}
});
Template.Production.events({
'click .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .newButton': function(event, template) {
if(template.$('.newButton').hasClass('active')) {
Session.set(PREFIX + 'displayNew', false);
}
else {
Session.set(PREFIX + 'displayNew', true);
Session.set(PREFIX + "editedId", undefined); //Clear the edited product so that only one editor is open at a time.
}
template.$('.newButton').toggleClass('active');
},
'click .showDeletedButton': function(event, template) {
Session.set(PREFIX + "showDeleted", !Session.get(PREFIX + "showDeleted")); //Toggle the display of deleted production.
}
});
Template.Batch.onCreated(function() {
});
Template.Batch.onRendered(function() {
});
Template.Batch.helpers({
hasLabelsClass: function() {
return this.hasLabels === true ? "hasLabels" : "noLabels";
},
name: function() {
let product = Meteor.collections.Products.findOne({_id: this.productId});
let measure = Meteor.collections.Measures.findOne({_id: this.measureId});
return product.name + " (" + measure.name + ")";
},
date: function() {
return moment(this.date, "YYYYMMDD").format("MM/DD/YYYY");
},
cook: function() {
let worker = Meteor.collections.Workers.findOne({_id: this.cookId});
return worker.name;
},
canner: function() {
let worker = Meteor.collections.Workers.findOne({_id: this.cannerId});
return worker.name;
},
editing: function() {
let editedId = Session.get(PREFIX + "editedId");
return editedId && editedId.toString() === this._id.toString();
},
getRowClass: function() {
return this.deletedAt ? "deleted" : "";
},
isDeleted: function() {
return this.deletedAt;
}
});
Template.Batch.events({
"click .hasLabels": function(event, template) {
Meteor.call('setBatchHasLabels', this._id, !(this.hasLabels === true), function(error, result) {
if(error) sAlert.error(error);
});
},
"click .actionEdit": function(event, template) {
Session.set(PREFIX + "editedId", this._id);
Session.set(PREFIX + 'displayNew', false); //Ensure the new editor is closed.
template.parentTemplate().$('.newButton').removeClass('active');
},
"click .actionDelete": function(event, template) {
Meteor.call('deleteBatch', this._id, function(error, result) {
if(error) sAlert.error(error);
else sAlert.success("Production Batch Deleted");
});
},
'click .actionUndelete': function(event, template) {
Meteor.call('undeleteBatch', this._id, function(error, result) {
if(error) sAlert.error(error);
else sAlert.success("Production Batch No Longer Deleted");
});
}
});
Template.BatchEditor.onCreated(function() {
});
Template.BatchEditor.onRendered(function() {
});
Template.BatchEditor.helpers({
name: function() {
let product = Meteor.collections.Products.findOne({_id: this.productId});
let measure = Meteor.collections.Measures.findOne({_id: this.measureId});
return (product ? product.name : "") + " (" + (measure ? measure.name : "") + ")";
},
date: function() {
return moment(this.date, "YYYYMMDD").format("MM/DD/YYYY");
},
cook: function() {
let worker = Meteor.collections.Workers.findOne({_id: this.cookId});
return worker.name;
},
canner: function() {
let worker = Meteor.collections.Workers.findOne({_id: this.cannerId});
return worker.name;
}
});
Template.BatchEditor.events({
'click .editorCancel': function(event, template) {
Session.set(PREFIX + "editedId", undefined);
},
'click input[type="submit"]': function(event, template) {
event.preventDefault();
template.$('.insertForm').data('bs.validator').validate(function(isValid) {
if(isValid) {
//Allow the user to edit the comment and the amount produced. Sometimes jars pop after the product cools and these things need to be recorded.
//TODO
}
});
}
});
Template.BatchNew.onCreated(function() {
this.selectedProduct = new ReactiveVar();
this.selectedCook = new ReactiveVar();
this.selectedCanner = new ReactiveVar();
});
Template.BatchNew.onRendered(function() {
this.$('.insertForm').validator();
this.$('[name="product"]').buildCombo({cursor: Meteor.collections.Products.find({$or: [{hidden: false}, {hidden: {$exists:false}}]}), selection: this.selectedProduct, textAttr: 'name', listClass: 'comboList', getClasses: function(data) {
return (data && data.deactivated) ? "deactivated" : "";
}});
this.$('[name="cook"]').buildCombo({cursor: Meteor.collections.Workers.find({}), selection: this.selectedCook, textAttr: 'name', listClass: 'comboList'});
this.$('[name="canner"]').buildCombo({cursor: Meteor.collections.Workers.find({}), selection: this.selectedCanner, textAttr: 'name', listClass: 'comboList'});
this.$('input[name="product"]').focus();
});
Template.BatchNew.helpers({
//products: function() {
// return Meteor.collections.Products.find({});
//},
//productSelected: function() {
// let product = this;
// let batch = Template.parentData();
//
// return batch.productId === product._id ? "selected" : "";
//},
productMeasures: function() {
//Show only the list allowed by the product
let product = Template.instance().selectedProduct.get();
if(product) {
let measures = Meteor.collections.Measures.find({}).fetch();
let measuresById = {};
let allowedMeasureIds = product.measures;
let allowedMeasures = [];
//Create a hashmap of measures by their id.
for(let measure of measures) measuresById[measure._id.toString()] = measure;
//Create a list of measures allowed for the product.
for(let measureId of allowedMeasureIds) {
allowedMeasures.push(measuresById[measureId.toString()]);
}
return allowedMeasures;
}
else return [];
},
today: () => {
return moment().format("YYYY-MM-DD");
}
});
Template.BatchNew.events({
'change input[name="date"]': function(event, template) {
template.selectedDate.set(moment(event.target.value, "YYYY-MM-DD").toDate());
},
'click .editorCancel': function(event, template) {
Session.set(PREFIX + "editedId", undefined);
},
'click .editorApply': function(event, template) {
event.preventDefault();
template.$('.insertForm').data('bs.validator').validate(function(isValid) {
if(isValid) {
let batches = [];
let insertMeasure = template.$(".insertMeasure");
let batch = {
date: ~~(moment(template.find("[name='date']").value, "YYYY-MM-DD").format("YYYYMMDD")), // Note: ~~ performs a bitwise not which is a fast method of converting a string to a number.
productId: template.selectedProduct.get()._id,
cookId: template.selectedCook.get()._id,
cannerId: template.selectedCanner.get()._id,
comment: template.$('.comment').val()
};
//Iterate over the measures for the batch (based on the product chosen) and amounts.
for(let next = 0; next < insertMeasure.length; next++) {
let nextMeasure = $(insertMeasure[next]);
let measureId = nextMeasure.find(".measureId").val();
let amount = parseFloat(nextMeasure.find(".amount").val());
if(amount > 0) {
let next = _.clone(batch);
next.measureId = measureId;
next.amount = amount;
batches.push(next);
}
}
Meteor.call('insertBatches', batches, function(error) {
if(error) sAlert.error("Failed to insert the batch!\n" + error);
else {
let productCombo = template.$("input[name='product']");
let measureFields = template.$(".amount");
sAlert.success("Production batches created.");
//Clear the measure quantity fields so the user can enter another sale without the quantities already set.
measureFields.val(0);
//Set the focus to the product field of the form.
//productCombo.focus();
//Clear the product since it is highly unlikely the same product will be added twice for the same date.
productCombo.val("");
Session.set(PREFIX + "displayNew", false);
}
});
}
});
}
});
Template.InsertBatchMeasure.onCreated(function() {
let _this = this;
this.amount = new ReactiveVar(0);
});
Template.InsertBatchMeasure.onRendered(function() {
});
Template.InsertBatchMeasure.helpers({
amount: function() {
return Template.instance().amount.get();
}
});
Template.InsertBatchMeasure.events({
'change .amount': function(event, template) {
template.amount.set(parseFloat($(event.target).val()));
},
'focus input[name="amount"]': function(event, template) {
//See http://stackoverflow.com/questions/3150275/jquery-input-select-all-on-focus
//Handle selecting the text in the field on receipt of focus.
let $this = $(this)
.one('mouseup.mouseupSelect', function() {
$this.select();
return false;
})
.one('mousedown', function() {
// compensate for untriggered 'mouseup' caused by focus via tab
$this.off('mouseup.mouseupSelect');
})
.select();
}
});
Template.BatchSearch.onCreated(function() {
});
Template.BatchSearch.onRendered(function() {
});
Template.BatchSearch.helpers({
searchValue: function() {
let searchFields = Session.get(PREFIX + 'searchFields');
return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : '';
}
});
Template.BatchSearch.events({
"keyup .searchInput": _.throttle(function(event, template) {
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
let searchFields = Session.get(PREFIX + 'searchFields') || {};
let searchValue = template.$(event.target).val();
if(searchValue) {
if(this.number) searchValue = parseFloat(searchValue);
// A collection name will be provided if there is a related table of data that will contain the text provided and will map to an ID that is then searched for in the current table of data.
// For example we are displaying a table of Sales which has the ID of a Product. The Product table has a Name field and the search box searches for Product Names. The ID's of the Products found should be used to filter the Sales by Product ID.
if(this.collection) {
let ids = Meteor.collections[this.collection].find({[this.collectionQueryColumnName]: {$regex: searchValue, $options: 'i'}}, {fields: {[this.collectionResultColumnName]: 1}}).fetch();
//Convert the ids to an array of ids instead of an array of objects containing an id.
for(let i = 0; i < ids.length; i++) {ids[i] = ids[i]._id;}
searchQuery[this.columnName] = {$in: ids};
searchFields[this.columnName] = searchValue;
}
else {
searchFields[this.columnName] = searchQuery[this.columnName] = searchValue;
}
}
else {
//Remove columns from the search query whose values are empty so we don't bother the database with them.
delete searchQuery[this.columnName];
delete searchFields[this.columnName];
}
Session.set(PREFIX + 'searchQuery', searchQuery);
Session.set(PREFIX + 'searchFields', searchFields);
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
Session.set(PREFIX + "queryLimit", QUERY_LIMIT); //Reset the query limit in case we loaded more
}, 500)
});
Template.BatchDateRangeSearch.helpers({
startDate: function() {
let searchFields = Session.get(PREFIX + 'searchFields');
let searchValue = (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : {};
return searchValue.start ? moment(searchValue.start.toString(), "YYYYMMDD").format("MM/DD/YYYY") : "";
},
endDate: function() {
let searchFields = Session.get(PREFIX + 'searchFields');
let searchValue = (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : {};
return searchValue.end ? moment(searchValue.end.toString(), "YYYYMMDD").format("MM/DD/YYYY") : "";
}
});
Template.BatchDateRangeSearch.events({
"change .searchDateStartInput": function(event, template) {Template.DateRangeSearch.dateChanged(true, event, template)},
"keyup .searchDateStartInput": _.throttle(function(event, template) {Template.DateRangeSearch.dateChanged(true, event, template)}, 500),
"change .searchDateEndInput": function(event, template) {Template.DateRangeSearch.dateChanged(false, event, template)},
"keyup .searchDateEndInput": _.throttle(function(event, template) {Template.DateRangeSearch.dateChanged(false, event, template)}, 500)
});
Template.BatchDateRangeSearch.dateChanged = function(isStart, event, template) {
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
let searchFields = Session.get(PREFIX + 'searchFields') || {};
let searchValue = template.$(event.target).val();
let columnName = template.data.columnName;
if(searchValue) {
let search = searchQuery[columnName];
// Create a search object and attach it to the searchFields and searchQuery objects if needed.
if(!search) {
search = {type: 'dateRange'};
searchFields[columnName] = searchQuery[columnName] = search;
}
// Use moment to parse date and convert it to YYYYMMDD for searching the database.
searchValue = ~~(moment(searchValue, searchValue.includes("-") ? "YYYY-MM-DD" : "MM/DD/YYYY").format("YYYYMMDD")); // Note: ~~ performs a bitwise not which is a fast method of converting a string to a number.
// Save the search ending date.
isStart ? search.start = searchValue : search.end = searchValue;
}
else {
if(searchQuery[columnName]) {
// Remove columns from the search query whose values are empty so we don't bother the database with them.
if(isStart) {
delete searchQuery[columnName].start;
delete searchFields[columnName].start;
}
else {
delete searchQuery[columnName].end;
delete searchFields[columnName].end;
}
}
}
Session.set(PREFIX + 'searchQuery', searchQuery);
Session.set(PREFIX + 'searchFields', searchFields);
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
Session.set(PREFIX + "queryLimit", QUERY_LIMIT); //Reset the query limit in case we loaded more
};

View File

@@ -22,7 +22,7 @@
<th class="tags">Tags {{>ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
<th class="aliases">Aliases {{>ProductSearch columnName='aliases'}}</th>
<th class="measures">Measures {{>ProductSearch columnName='measures' collectionQueryColumnName='name' collection='Measures' collectionResultColumnName='_id'}}</th>
<th class="actions">Actions <span class="newProductButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
<th class="actions">Actions <span class="newButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
</tr>
</thead>
</table>
@@ -32,7 +32,7 @@
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#if displayNewProduct}}
{{#if displayNew}}
{{> ProductEditor isNew=true}}
{{/if}}
{{#each products}}
@@ -75,7 +75,7 @@
</template>
<template name="ProductEditor">
<td colspan="4" class="productEditorTd">
<td colspan="4" class="editorTd">
<div class="editorDiv"><label>Name:</label><input name="name" class="form-control" type="text" value="{{name}}" autocomplete="off" required></div>
<div class="editorDiv"><label>Tags:</label>
<select class="productTagsEditor" multiple="multiple">
@@ -99,7 +99,7 @@
</select>
</div>
</td>
<td class="center productEditorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
<td class="center editorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
</template>
<template name="ConvertProduct">
@@ -109,11 +109,11 @@
<input name="product" class="form-control" type="text" required/>
<label><em>Convert sales from this product to an alternate.</em></label>
</td>
<td class="center productEditorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
<td class="center editorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
</template>
<template name="ProductSearch">
<div class="productSearch">
<div class="search">
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}"/>
</div>
</template>

View File

@@ -69,14 +69,14 @@
> tr
> th.actions
text-align: center
.newProductButton
.newButton
margin-top: 4px
padding: 0 12px
.fa-plus-circle
display: inline-block
.fa-times-circle
display: none
.newProductButton:active
.newButton:active
background-color: #fb557b
color: black
.fa-times-circle
@@ -105,9 +105,9 @@
thead
visibility: hidden
display: none
.productSearch
.search
margin: 3px 0 2px 1px
.productEditorTd
.editorTd
background: #deeac0
input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor
width: 100%

View File

@@ -12,7 +12,7 @@ Tracker.autorun(function() {
});
Template.Products.onCreated(function() {
Session.set(PREFIX + "displayNewProduct", false);
Session.set(PREFIX + "displayNew", false);
Session.set(PREFIX + "showHidden", false);
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
@@ -25,8 +25,8 @@ Template.Products.onRendered(function() {
});
});
Template.Products.helpers({
displayNewProduct: function() {
return Session.get(PREFIX + "displayNewProduct");
displayNew: function() {
return Session.get(PREFIX + "displayNew");
},
products: function() {
let skipCount = Session.get(PREFIX + 'skipCount') || 0;
@@ -68,16 +68,16 @@ Template.Products.events({
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .newProductButton': function(event, template) {
if(template.$('.newProductButton').hasClass('active')) {
Session.set(PREFIX + 'displayNewProduct', false);
'click .newButton': function(event, template) {
if(template.$('.newButton').hasClass('active')) {
Session.set(PREFIX + 'displayNew', false);
}
else {
Session.set(PREFIX + 'displayNewProduct', true);
Session.set(PREFIX + "editedProduct", undefined); //Clear the edited product so that only one editor is open at a time.
Session.set(PREFIX + 'displayNew', true);
Session.set(PREFIX + "editedId", undefined); //Clear the edited product so that only one editor is open at a time.
Session.set(PREFIX + "convertedProduct", undefined); //Clear the converted product so that only one editor is open at a time.
}
template.$('.newProductButton').toggleClass('active');
template.$('.newButton').toggleClass('active');
},
'change input[name="showHidden"]': function(event, template) {
//console.log("changed " + $(event.target).prop('checked'));
@@ -166,9 +166,9 @@ Template.Product.helpers({
return result;
},
editing: function() {
let editedProduct = Session.get(PREFIX + "editedProduct");
let editedId = Session.get(PREFIX + "editedId");
return editedProduct == this._id;
return editedId == this._id;
},
converting: function() {
let convertedProduct = Session.get(PREFIX + "convertedProduct");
@@ -181,10 +181,10 @@ Template.Product.helpers({
});
Template.Product.events({
"click .actionEdit": function(event, template) {
Session.set(PREFIX + "editedProduct", this._id);
Session.set(PREFIX + 'displayNewProduct', false); //Ensure the new product editor is closed.
Session.set(PREFIX + "editedId", this._id);
Session.set(PREFIX + 'displayNew', false); //Ensure the new editor is closed.
Session.set(PREFIX + "convertedProduct", undefined); //Clear the converted product so that only one editor is open at a time.
template.parentTemplate().$('.newProductButton').removeClass('active');
template.parentTemplate().$('.newButton').removeClass('active');
},
"click .actionDeactivate": function(event, template) {
Meteor.call('deactivateProduct', this._id, function(error, result) {
@@ -206,9 +206,9 @@ Template.Product.events({
},
"click .actionConvert": function(event, template) {
Session.set(PREFIX + "convertedProduct", this._id);
Session.set(PREFIX + 'displayNewProduct', false); //Ensure the new product editor is closed.
Session.set(PREFIX + "editedProduct", undefined); //Clear the edited product so that only one editor is open at a time.
template.$('.newProductButton').removeClass('active');
Session.set(PREFIX + 'displayNew', false); //Ensure the new editor is closed.
Session.set(PREFIX + "editedId", undefined); //Clear the edited product so that only one editor is open at a time.
template.$('.newButton').removeClass('active');
},
'click .actionHide': function(event, template) {
Meteor.call('hideProduct', this._id, function(error, result) {
@@ -249,9 +249,9 @@ Template.ProductEditor.helpers({
});
Template.ProductEditor.events({
"click .editorCancel": function(event, template) {
Session.set(PREFIX + "editedProduct", undefined);
Session.set(PREFIX + 'displayNewProduct', false);
template.parentTemplate().$('.newProductButton').removeClass('active');
Session.set(PREFIX + "editedId", undefined);
Session.set(PREFIX + 'displayNew', false);
template.parentTemplate().$('.newButton').removeClass('active');
},
"click .editorApply": function(event, template) {
let name = template.$("input[name='name']").val().trim();
@@ -263,13 +263,13 @@ Template.ProductEditor.events({
aliases = aliases.map((n)=>n.id);
measures = measures.map((n)=>n.id);
if(Session.get(PREFIX + 'displayNewProduct')) {
if(Session.get(PREFIX + 'displayNew')) {
Meteor.call("createProduct", name, tags, aliases, measures, function(error, result) {
if(error) sAlert.error(error);
else {
sAlert.success("Product created.");
Session.set(PREFIX + 'displayNewProduct', false);
template.parentTemplate().$('.newProductButton').removeClass('active');
Session.set(PREFIX + 'displayNew', false);
template.parentTemplate().$('.newButton').removeClass('active');
}
});
}
@@ -278,8 +278,8 @@ Template.ProductEditor.events({
if(error) sAlert.error(error);
else {
sAlert.success("Product updated.");
Session.set(PREFIX + "editedProduct", undefined);
template.parentTemplate().$('.newProductButton').removeClass('active');
Session.set(PREFIX + "editedId", undefined);
template.parentTemplate().$('.newButton').removeClass('active');
}
});
}
@@ -296,10 +296,10 @@ Template.ConvertProduct.onRendered(function() {
});
Template.ConvertProduct.events({
"click .editorCancel": function(event, template) {
Session.set(PREFIX + "editedProduct", undefined);
Session.set(PREFIX + "editedId", undefined);
Session.set(PREFIX + "convertedProduct", undefined);
Session.set(PREFIX + 'displayNewProduct', false);
template.parentTemplate().$('.newProductButton').removeClass('active');
Session.set(PREFIX + 'displayNew', false);
template.parentTemplate().$('.newButton').removeClass('active');
},
"click .editorApply": function(event, template) {
let productId = template.selectedProduct.get()._id;
@@ -308,10 +308,10 @@ Template.ConvertProduct.events({
if(error) sAlert.error(error);
else {
sAlert.success("Sales of this product were converted.");
Session.set(PREFIX + "editedProduct", undefined);
Session.set(PREFIX + "editedId", undefined);
Session.set(PREFIX + "convertedProduct", undefined);
Session.set(PREFIX + 'displayNewProduct', false);
template.parentTemplate().$('.newProductButton').removeClass('active');
Session.set(PREFIX + 'displayNew', false);
template.parentTemplate().$('.newButton').removeClass('active');
}
});
}

View File

@@ -128,45 +128,4 @@
{{> Template.dynamic template=content}}
</div>
</div>
</template>
<!--<template name="Body">-->
<!--<div id="layoutBody">-->
<!--<div class="bodyTableRow">-->
<!--<div class="left bodyTableCell">-->
<!--<ul>-->
<!--{{#if isInRole 'manage'}}-->
<!--<li class="{{isActiveRoute 'UserManagement'}}">-->
<!--<a href="{{pathFor 'UserManagement'}}">-->
<!--User Management-->
<!--</a>-->
<!--</li>-->
<!--{{/if}}-->
<!--<li class="{{isActiveRoute 'Sales'}}">-->
<!--<a href="{{pathFor 'Sales'}}">-->
<!--Sales <span class="tag">Test Tag</span>-->
<!--</a>-->
<!--</li>-->
<!--<li class="{{isActiveRoute 'Production'}}">-->
<!--<a href="{{pathFor 'Production'}}">-->
<!--Production <span class="tag">sample</span>-->
<!--</a>-->
<!--</li>-->
<!--</ul>-->
<!--</div>-->
<!--<div class="bodyTableCell">-->
<!--<div class="bodyTable">-->
<!--<div class="header bodyTableRow">-->
<!--&nbsp;-->
<!--</div>-->
<!--<div class="content bodyTableRow">-->
<!--{{> Template.dynamic template=content}}-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
<!--<div class="footer bodyTableRow">-->
<!--&copy; Petit Teton LLC 2017-->
<!--</div>-->
<!--</div>-->
<!--</template>-->
</template>

View File

@@ -18,202 +18,211 @@
height: 100%
width: 100%
nav.leftSidebarContainer
z-index:999
position: fixed
top: 0
width: 220px
padding: 0
height: 100%
border: 0
vertical-align: top
text-align: left
background-color: #90b272 //Old browsers
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
font-size: 14px
font-weight: 700
overflow: visible
margin: 0 0 0 -220px
-webkit-transition: .5s ease-in
-moz-transition: .5s ease-in
-o-transition: .5s ease-in
-ms-transition: .5s ease-in
transition: .5s ease-in
.leftSidebarMenuButton
position: absolute
right: -30px
@media not print
nav.leftSidebarContainer
z-index:999
position: fixed
top: 0
width: 220px
padding: 0
height: 100%
border: 0
vertical-align: top
text-align: left
background-color: #90b272 //Old browsers
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
font-size: 14px
font-weight: 700
overflow: visible
margin: 0 0 0 -220px
-webkit-transition: .5s ease-in
-moz-transition: .5s ease-in
-o-transition: .5s ease-in
-ms-transition: .5s ease-in
transition: .5s ease-in
-webkit-border-top-right-radius: 5px
-webkit-border-bottom-right-radius: 5px
-moz-border-radius-topright: 5px
-moz-border-radius-bottomright: 5px
border-top-right-radius: 5px
border-bottom-right-radius: 5px
color: black
font-size: 20px
line-height: 20px
font-weight: 900
text-align: center
text-decoration: none
width: 30px
height: 30px
padding: 5px 0
background-color: #90b272
display: block
border-top: 1px solid #494
border-right: 1px solid #494
border-bottom: 1px solid #494
.leftSidebarMenuButton:hover
color: rgba(150,0,0,.5)
nav.generalSidebar
.leftSidebarMenuButton
top: 10px
nav.graphsSidebar
.leftSidebarMenuButton
top: 50px
nav.settingsSidebar
.leftSidebarMenuButton
top: 90px
nav.menuHide .leftSidebarMenuButton
right: 60px
nav.menuShow
margin: 0
nav.menuShow .leftSidebarMenuButton
right: -15px
-webkit-transform: rotate(45deg) !important
-moz-transform: rotate(45deg) !important
-o-transform: rotate(45deg) !important
-ms-transform: rotate(45deg) !important
transform: rotate(45deg) !important
-moz-border-radius-bottomright: 0
//border-top-right-radius: 0
border-bottom-right-radius: 0
border-bottom: 0
.leftSidebar
flex: 0 0 auto
display: flex
flex-flow: column
justify-content: flex-start
align-items: flex-start
align-content: stretch
height: 100%
//position: absolute
border: 0
vertical-align: top
padding: 0
text-align: left
//top: 0px
//left: 0px
//bottom: 0px
width: 220px
//Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D
background-color: #90b272 //Old browsers
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
font-size: 14px
font-weight: 700
overflow: hidden
.logoArea
flex: 0 0 auto
width: 100%
.signOut
.leftSidebarMenuButton
position: absolute
left: 10px
top: 10px
color: white
cursor: pointer
.signOut:hover
color: #BBB
.signOut:active
right: -30px
-webkit-transition: .5s ease-in
-moz-transition: .5s ease-in
-o-transition: .5s ease-in
-ms-transition: .5s ease-in
transition: .5s ease-in
-webkit-border-top-right-radius: 5px
-webkit-border-bottom-right-radius: 5px
-moz-border-radius-topright: 5px
-moz-border-radius-bottomright: 5px
border-top-right-radius: 5px
border-bottom-right-radius: 5px
color: black
.logo
font-size: 20px
line-height: 20px
font-weight: 900
text-align: center
margin-top: 20px
img:hover
//-webkit-animation: neon6_drop 1.5s ease-in-out infinite alternate;
//-moz-animation: neon6_drop 1.5s ease-in-out infinite alternate;
animation: neon6_drop 1.5s ease-in-out infinite alternate;
.menuArea
flex: 1 1 auto
width: 100%
ul
padding: 20px 0 0 0
margin: 0
list-style: none
li:first-child
border-top: 1px solid #e4e5e7
li
border-bottom: 1px solid #e4e5e7
color: #96a2ae
text-transform: uppercase
display: block
a
color: black
padding: 10px 20px
cursor: pointer
text-decoration: none
display: block
.tag
padding: .3em .6em
margin-top: -.2em
font-size: .8em
color: #ddd
white-space: nowrap
vertical-align: baseline
border-radius: .5em
border: 1px solid #000000
float: right
.subMenu
background-color: #999
padding: .3em .6em
margin-top: -.2em
font-size: .8em
color: #fff
white-space: nowrap
vertical-align: baseline
border-radius: .5em
border: 1px solid #000000
float: right
.subMenu.active
background-color: #333
li:hover
// Note: neon6 is defined in effects.import.styl
background-color: #666
-webkit-animation: neon6 1.5s ease-in-out infinite alternate
-moz-animation: neon6 1.5s ease-in-out infinite alternate
animation: neon6 1.5s ease-in-out infinite alternate
.subMenu
// Note: neon6 is defined in effects.import.styl
background-color: #999
-webkit-animation: neon7 1.5s ease-in-out infinite alternate
-moz-animation: neon7 1.5s ease-in-out infinite alternate
animation: neon7 1.5s ease-in-out infinite alternate
li.active
background-color: #333
> a
color: #96a2ae
li.active:hover
background-color: #333
> a
color: white
.footer
text-decoration: none
width: 30px
height: 30px
padding: 5px 0
background-color: #90b272
display: block
border-top: 1px solid #494
border-right: 1px solid #494
border-bottom: 1px solid #494
.leftSidebarMenuButton:hover
color: rgba(150,0,0,.5)
nav.generalSidebar
.leftSidebarMenuButton
top: 10px
nav.graphsSidebar
.leftSidebarMenuButton
top: 50px
nav.settingsSidebar
.leftSidebarMenuButton
top: 90px
nav.menuHide .leftSidebarMenuButton
right: 60px
nav.menuShow
margin: 0
nav.menuShow .leftSidebarMenuButton
right: -15px
-webkit-transform: rotate(45deg) !important
-moz-transform: rotate(45deg) !important
-o-transform: rotate(45deg) !important
-ms-transform: rotate(45deg) !important
transform: rotate(45deg) !important
-moz-border-radius-bottomright: 0
//border-top-right-radius: 0
border-bottom-right-radius: 0
border-bottom: 0
.leftSidebar
flex: 0 0 auto
width: 100%
font-size: 9px
text-align: center
display: flex
flex-flow: column
justify-content: flex-start
align-items: flex-start
align-content: stretch
height: 100%
//position: absolute
border: 0
vertical-align: top
padding: 0
text-align: left
//top: 0px
//left: 0px
//bottom: 0px
width: 220px
//Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D
background-color: #90b272 //Old browsers
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
font-size: 14px
font-weight: 700
overflow: hidden
.contentBody
flex: 1 1 1px
padding: 10px 20px
-webkit-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
-moz-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
box-shadow: inset 8px 0px 10px -3px rgba(168,165,168,1)
overflow: hidden
.logoArea
flex: 0 0 auto
width: 100%
.signOut
position: absolute
left: 10px
top: 10px
color: white
cursor: pointer
.signOut:hover
color: #BBB
.signOut:active
color: black
.logo
text-align: center
margin-top: 20px
img:hover
//-webkit-animation: neon6_drop 1.5s ease-in-out infinite alternate;
//-moz-animation: neon6_drop 1.5s ease-in-out infinite alternate;
animation: neon6_drop 1.5s ease-in-out infinite alternate;
.menuArea
flex: 1 1 auto
width: 100%
ul
padding: 20px 0 0 0
margin: 0
list-style: none
li:first-child
border-top: 1px solid #e4e5e7
li
border-bottom: 1px solid #e4e5e7
color: #96a2ae
text-transform: uppercase
display: block
a
color: black
padding: 10px 20px
cursor: pointer
text-decoration: none
display: block
.tag
padding: .3em .6em
margin-top: -.2em
font-size: .8em
color: #ddd
white-space: nowrap
vertical-align: baseline
border-radius: .5em
border: 1px solid #000000
float: right
.subMenu
background-color: #999
padding: .3em .6em
margin-top: -.2em
font-size: .8em
color: #fff
white-space: nowrap
vertical-align: baseline
border-radius: .5em
border: 1px solid #000000
float: right
.subMenu.active
background-color: #333
li:hover
// Note: neon6 is defined in effects.import.styl
background-color: #666
-webkit-animation: neon6 1.5s ease-in-out infinite alternate
-moz-animation: neon6 1.5s ease-in-out infinite alternate
animation: neon6 1.5s ease-in-out infinite alternate
.subMenu
// Note: neon6 is defined in effects.import.styl
background-color: #999
-webkit-animation: neon7 1.5s ease-in-out infinite alternate
-moz-animation: neon7 1.5s ease-in-out infinite alternate
animation: neon7 1.5s ease-in-out infinite alternate
li.active
background-color: #333
> a
color: #96a2ae
li.active:hover
background-color: #333
> a
color: white
.footer
flex: 0 0 auto
width: 100%
font-size: 9px
text-align: center
.contentBody
flex: 1 1 1px
padding: 10px 20px
-webkit-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
-moz-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
box-shadow: inset 8px 0 10px -3px rgba(168,165,168,1)
overflow: hidden
@media print
nav.leftSidebarContainer
display: none
.contentBody
flex: 1 1 1px
padding: 0
margin: 0
overflow: hidden

View File

@@ -0,0 +1,3 @@
<template name="Empty">
{{> Template.dynamic template=content}}
</template>

View File

@@ -0,0 +1,2 @@
import { Template } from 'meteor/templating';
import './Empty.html';