Prototyped the barcode idea; Added a basic production system.
This commit is contained in:
@@ -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">-->
|
||||
<!-- <!– 3x2" == 76x50mm; 300ppi == 11.81ppmm; So 3x2" == 897 x 590x. –>-->
|
||||
<!--<!– <canvas class="labelCanvas" width="{{labelPxWidthActual}}" height="{{labelPxHeightActual}}">–>-->
|
||||
<!--<!– </canvas>–>-->
|
||||
<!-- <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>
|
||||
|
||||
236
imports/ui/Label.import.styl
vendored
236
imports/ui/Label.import.styl
vendored
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
19
imports/ui/PrintLabel.html
Normal file
19
imports/ui/PrintLabel.html
Normal 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
85
imports/ui/PrintLabel.import.css
vendored
Normal 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
92
imports/ui/PrintLabel.import.styl
vendored
Normal 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
32
imports/ui/PrintLabel.js
Normal 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});
|
||||
});
|
||||
@@ -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> / {{#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> / <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> / <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>
|
||||
192
imports/ui/Production.import.styl
vendored
192
imports/ui/Production.import.styl
vendored
@@ -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
|
||||
@@ -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
|
||||
};
|
||||
@@ -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> / <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> / <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> / <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> / <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>
|
||||
8
imports/ui/Products.import.styl
vendored
8
imports/ui/Products.import.styl
vendored
@@ -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%
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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">-->
|
||||
<!-- -->
|
||||
<!--</div>-->
|
||||
<!--<div class="content bodyTableRow">-->
|
||||
<!--{{> Template.dynamic template=content}}-->
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<!--<div class="footer bodyTableRow">-->
|
||||
<!--© Petit Teton LLC 2017-->
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<!--</template>-->
|
||||
</template>
|
||||
387
imports/ui/layouts/Body.import.styl
vendored
387
imports/ui/layouts/Body.import.styl
vendored
@@ -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
|
||||
3
imports/ui/layouts/Empty.html
Normal file
3
imports/ui/layouts/Empty.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<template name="Empty">
|
||||
{{> Template.dynamic template=content}}
|
||||
</template>
|
||||
2
imports/ui/layouts/Empty.js
Normal file
2
imports/ui/layouts/Empty.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import './Empty.html';
|
||||
Reference in New Issue
Block a user