Prototyped the barcode idea; Added a basic production system.
This commit is contained in:
@@ -33,8 +33,8 @@ arillo:flow-router-helpers # Provides various template helpers such as {{pathFo
|
|||||||
#tomwasd:flow-router-seo
|
#tomwasd:flow-router-seo
|
||||||
kadira:blaze-layout
|
kadira:blaze-layout
|
||||||
|
|
||||||
shell-server@0.4.0 # ???
|
#shell-server@0.4.0 # ???
|
||||||
meteortoys:allthings
|
#meteortoys:allthings
|
||||||
stylus@2.513.13
|
stylus@2.513.13
|
||||||
session@1.2.0
|
session@1.2.0
|
||||||
##browser-policy # Adds support for specifying browser level security rules related to content and what's allowed to laod on the page.
|
##browser-policy # Adds support for specifying browser level security rules related to content and what's allowed to laod on the page.
|
||||||
@@ -55,7 +55,7 @@ aldeed:collection2
|
|||||||
#twbs:bootstrap # Requires jquery 1.9-2.x, not 3+
|
#twbs:bootstrap # Requires jquery 1.9-2.x, not 3+
|
||||||
fortawesome:fontawesome
|
fortawesome:fontawesome
|
||||||
momentjs:moment
|
momentjs:moment
|
||||||
mizzao:bootboxjs # ???
|
#mizzao:bootboxjs # ???
|
||||||
aldeed:template-extension
|
aldeed:template-extension
|
||||||
juliancwirko:s-alert # Client error/alert handling
|
juliancwirko:s-alert # Client error/alert handling
|
||||||
jcbernack:reactive-aggregate # Allows us to create a new client collection (from the server) with the contents being an aggregate of server data. Note that aggregation can only be done on the server currently as mini-mongo does not support it.
|
jcbernack:reactive-aggregate # Allows us to create a new client collection (from the server) with the contents being an aggregate of server data. Note that aggregation can only be done on the server currently as mini-mongo does not support it.
|
||||||
@@ -66,3 +66,4 @@ markdown@1.0.12
|
|||||||
wcrisman:jquery-custom-scrollbar
|
wcrisman:jquery-custom-scrollbar
|
||||||
underscore@1.0.10
|
underscore@1.0.10
|
||||||
meteorhacks:aggregate # Allows databaseName.aggragate(pipeline) calls the exact same way you would on the command line in the mongo tool.
|
meteorhacks:aggregate # Allows databaseName.aggragate(pipeline) calls the exact same way you would on the command line in the mongo tool.
|
||||||
|
babrahams:constellation
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ arillo:flow-router-helpers@0.5.2
|
|||||||
autoupdate@1.6.0
|
autoupdate@1.6.0
|
||||||
babel-compiler@7.3.4
|
babel-compiler@7.3.4
|
||||||
babel-runtime@1.3.0
|
babel-runtime@1.3.0
|
||||||
|
babrahams:constellation@0.4.10
|
||||||
|
babrahams:editable-json@0.6.5
|
||||||
|
babrahams:temple@0.4.7
|
||||||
base64@1.0.12
|
base64@1.0.12
|
||||||
binary-heap@1.0.11
|
binary-heap@1.0.11
|
||||||
blaze@2.3.3
|
blaze@2.3.3
|
||||||
@@ -23,6 +26,14 @@ caching-html-compiler@1.1.3
|
|||||||
callback-hook@1.1.0
|
callback-hook@1.1.0
|
||||||
check@1.3.1
|
check@1.3.1
|
||||||
coffeescript@1.0.17
|
coffeescript@1.0.17
|
||||||
|
constellation:autopublish@0.4.7
|
||||||
|
constellation:console@1.4.7
|
||||||
|
constellation:plugins@0.4.9
|
||||||
|
constellation:position@0.4.7
|
||||||
|
constellation:session@0.4.7
|
||||||
|
constellation:subscriptions@0.4.7
|
||||||
|
constellation:tiny@0.4.7
|
||||||
|
dburles:mongo-collection-instances@0.3.5
|
||||||
ddp@1.4.0
|
ddp@1.4.0
|
||||||
ddp-client@2.3.3
|
ddp-client@2.3.3
|
||||||
ddp-common@1.4.0
|
ddp-common@1.4.0
|
||||||
@@ -41,6 +52,8 @@ es5-shim@4.8.0
|
|||||||
fetch@0.1.1
|
fetch@0.1.1
|
||||||
fortawesome:fontawesome@4.7.0
|
fortawesome:fontawesome@4.7.0
|
||||||
geojson-utils@1.0.10
|
geojson-utils@1.0.10
|
||||||
|
gwendall:body-events@0.1.6
|
||||||
|
gwendall:session-json@0.1.7
|
||||||
hot-code-push@1.0.4
|
hot-code-push@1.0.4
|
||||||
html-tools@1.0.11
|
html-tools@1.0.11
|
||||||
htmljs@1.0.11
|
htmljs@1.0.11
|
||||||
@@ -52,6 +65,7 @@ jquery@1.11.11
|
|||||||
juliancwirko:s-alert@3.2.0
|
juliancwirko:s-alert@3.2.0
|
||||||
kadira:blaze-layout@2.3.0
|
kadira:blaze-layout@2.3.0
|
||||||
kadira:flow-router@2.12.1
|
kadira:flow-router@2.12.1
|
||||||
|
lai:collection-extensions@0.2.1_1
|
||||||
launch-screen@1.1.1
|
launch-screen@1.1.1
|
||||||
livedata@1.0.18
|
livedata@1.0.18
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
@@ -62,27 +76,9 @@ meteor@1.9.3
|
|||||||
meteor-base@1.4.0
|
meteor-base@1.4.0
|
||||||
meteorhacks:aggregate@1.3.0
|
meteorhacks:aggregate@1.3.0
|
||||||
meteorhacks:collection-utils@1.2.0
|
meteorhacks:collection-utils@1.2.0
|
||||||
meteortoys:allthings@4.0.0
|
|
||||||
meteortoys:authenticate@4.0.0
|
|
||||||
meteortoys:autopub@4.0.0
|
|
||||||
meteortoys:blueprint@4.0.0
|
|
||||||
meteortoys:email@4.0.0
|
|
||||||
meteortoys:hotreload@4.0.0
|
|
||||||
meteortoys:listen@4.0.0
|
|
||||||
meteortoys:method@4.0.0
|
|
||||||
meteortoys:mobile@4.0.0
|
|
||||||
meteortoys:pub@4.0.0
|
|
||||||
meteortoys:result@4.0.0
|
|
||||||
meteortoys:shell@4.0.0
|
|
||||||
meteortoys:status@4.0.0
|
|
||||||
meteortoys:sub@4.0.0
|
|
||||||
meteortoys:throttle@4.0.0
|
|
||||||
meteortoys:toggle@4.0.0
|
|
||||||
meteortoys:toykit@4.0.2
|
|
||||||
minifier-css@1.4.2
|
minifier-css@1.4.2
|
||||||
minifier-js@2.4.1
|
minifier-js@2.4.1
|
||||||
minimongo@1.4.5
|
minimongo@1.4.5
|
||||||
mizzao:bootboxjs@4.4.0
|
|
||||||
mobile-experience@1.0.5
|
mobile-experience@1.0.5
|
||||||
mobile-status-bar@1.0.14
|
mobile-status-bar@1.0.14
|
||||||
modern-browsers@0.1.4
|
modern-browsers@0.1.4
|
||||||
@@ -94,8 +90,6 @@ mongo-decimal@0.1.1
|
|||||||
mongo-dev-server@1.1.0
|
mongo-dev-server@1.1.0
|
||||||
mongo-id@1.0.7
|
mongo-id@1.0.7
|
||||||
mongo-livedata@1.0.12
|
mongo-livedata@1.0.12
|
||||||
msavin:jetsetter@4.0.0
|
|
||||||
msavin:mongol@4.0.1
|
|
||||||
npm-bcrypt@0.9.3
|
npm-bcrypt@0.9.3
|
||||||
npm-mongo@3.1.2
|
npm-mongo@3.1.2
|
||||||
observe-sequence@1.0.16
|
observe-sequence@1.0.16
|
||||||
@@ -115,7 +109,6 @@ routepolicy@1.1.0
|
|||||||
service-configuration@1.0.11
|
service-configuration@1.0.11
|
||||||
session@1.2.0
|
session@1.2.0
|
||||||
sha@1.0.9
|
sha@1.0.9
|
||||||
shell-server@0.4.0
|
|
||||||
socket-stream-client@0.2.2
|
socket-stream-client@0.2.2
|
||||||
softwarerero:accounts-t9n@1.3.11
|
softwarerero:accounts-t9n@1.3.11
|
||||||
spacebars@1.0.15
|
spacebars@1.0.15
|
||||||
|
|||||||
62
README.md
62
README.md
@@ -1,4 +1,22 @@
|
|||||||
# Petit Teton Data Management Application (PTApp)
|
# Current Configuration
|
||||||
|
|
||||||
|
See below for initial setup and updating instructions.
|
||||||
|
|
||||||
|
Currently we have three servers in two locations. Media and FS2 are computers residing on the 18501 property (big barn back top room, and Wynne/Sarah house), and FS1 is a computer residing in SF. All three are accessible via Putty (a windows SSH client - any SSH client will work) via the IP's 192.168.3.101 (Media), 192.168.2.239 (FS1), and 192.168.3.164 (FS2). Note that FS1 is in SF, but is accessible via the LAN because we have a VPN setup between our routers (192.168.3.1, and 192.168.2.1). The VPN makes them look like they are on the same network.
|
||||||
|
|
||||||
|
If you SSH (Putty) into each server you can update the server using APT (`sudo apt update`, followed by `update apt upgrade`), reboot `sudo shutdown -r now`, and perform other maintenance routines.
|
||||||
|
|
||||||
|
**Currently** Media is the primary server for the PTApp (the Meteor Petit Teton Webapp - versus the Petit Teton web site which is a customer focused informational web site). FS2 is the primary server for all other web sites and web apps and it clones its SSL certifications and www folders to the other two machines. Ultimately the PTApp will need to also be on FS2 primarily along with SSL certificates and an Nginx configuration that only allows certain access outside our LAN (for sales interaction at markets and Cam's home access). The database (MongoDB) is installed on all three machines, forming a cluster such that data written to any of the three is propagated to the other two automatically.
|
||||||
|
|
||||||
|
Database backups are not trivial due to the cluster system. Essentially we are doing a simple backup/restore and ignoring the possibility that the database we take from might not have all the latest updates. Doing this on our primary server (FS2) helps ensure that we don't have any problems. MongoDB being a very flexible database and our app not caring too much about data integrity helps as well.
|
||||||
|
|
||||||
|
TODO: Linux update procedure.
|
||||||
|
|
||||||
|
TODO: Backup procedure.
|
||||||
|
|
||||||
|
TODO: Mongo update procedure.
|
||||||
|
|
||||||
|
# Setup Petit Teton Data Management Application (PTApp)
|
||||||
|
|
||||||
This application is designed to track sales and production data.
|
This application is designed to track sales and production data.
|
||||||
|
|
||||||
@@ -27,21 +45,22 @@ Package this application by running 'npm run build' from the command line in the
|
|||||||
4. Modify the system to always start Mongod: `sudo systemctl enable mongod`
|
4. Modify the system to always start Mongod: `sudo systemctl enable mongod`
|
||||||
10. Run tools: (see [descriptions](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/#packages))
|
10. Run tools: (see [descriptions](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/#packages))
|
||||||
- mongod (the mongo database server)
|
- mongod (the mongo database server)
|
||||||
- mongo (command line shell for interacting with a running db)
|
- mongo (command line shell for interacting with a running db)
|
||||||
- mongoimport (imports data from: Extended JSON, CSV, or TSV formats created by mongoexport or 3rd party tools)
|
- mongoimport (imports data from: Extended JSON, CSV, or TSV formats created by mongoexport or 3rd party tools)
|
||||||
- bsondump (converts bson file formats [binary] into human readable formats [JSON] - use with mongodump files)
|
- bsondump (converts bson file formats [binary] into human readable formats [JSON] - use with mongodump files)
|
||||||
- mongodump (produces binary output for backups - not for use with shared clusters and replica sets)
|
- mongodump (produces binary output for backups - not for use with shared clusters and replica sets)
|
||||||
- mongoexport (produces JSON or CSV data)
|
- mongoexport (produces JSON or CSV data)
|
||||||
- mongofiles (command line gridfs integration - for manipulating files)
|
- mongofiles (command line gridfs integration - for manipulating files)
|
||||||
- mongooplog (replication & polling - ?)
|
- mongooplog (replication & polling - ?)
|
||||||
- mongoperf
|
- mongoperf
|
||||||
- mongorestore
|
- mongorestore
|
||||||
- mongostat
|
- mongostat
|
||||||
- mongotop (stats)
|
- mongotop (stats)
|
||||||
11. Install nodejs & npm
|
11. Install nodejs & npm
|
||||||
1. `sudo apt-get install nodejs`
|
1. `sudo apt-get install nodejs`
|
||||||
2. `sudo apt-get install npm`
|
2. `sudo apt-get install npm`
|
||||||
12. Install n
|
12. Install n
|
||||||
|
|
||||||
1. `sudo npm install -g n`
|
1. `sudo npm install -g n`
|
||||||
13. Update nodejs to 4.7.0 (for use with meteor 1.4 - may be another nodejs version for newer meteor versions): `n bin 4.7.0` (may need to first download 4.7.0 - see other docs on linux & nodejs)
|
13. Update nodejs to 4.7.0 (for use with meteor 1.4 - may be another nodejs version for newer meteor versions): `n bin 4.7.0` (may need to first download 4.7.0 - see other docs on linux & nodejs)
|
||||||
1. `sudo n 4.7.0`
|
1. `sudo n 4.7.0`
|
||||||
@@ -93,11 +112,12 @@ Package this application by running 'npm run build' from the command line in the
|
|||||||
|
|
||||||
NOTE: Use MongoBooster on a windows development machine to connect to the dev database (localhost:3001) and to export.
|
NOTE: Use MongoBooster on a windows development machine to connect to the dev database (localhost:3001) and to export.
|
||||||
|
|
||||||
#Updating a Meteor Deployment
|
# Updating a Meteor Deployment (OLD)
|
||||||
|
|
||||||
1. Run the NPM script for building the app. This can be done either from Webstorm by viewing the NPM display (shows a list of scripts in the package.json file), or typing `npm run build` from the command line. Alternatively you can simply type the build command in the command line: `meteor build --server-only ../` to build it. The command should exit with code zero for success.
|
1. Run the NPM script for building the app. This can be done either from Webstorm by viewing the NPM display (shows a list of scripts in the package.json file), or typing `npm run build` from the command line. Alternatively you can simply type the build command in the command line: `meteor build --server-only ../` to build it. The command should exit with code zero for success.
|
||||||
2. Find the archive file: it should be in the parent directory if you ran the above script exactly, otherwise it is where ever you specified (path at the end of the command). It should be called "PetitTetonMeteor.tar.gz" as of the writing of this documentation.
|
2. Find the archive file: it should be in the parent directory if you ran the above script exactly, otherwise it is where ever you specified (path at the end of the command). It should be called "PetitTetonMeteor.tar.gz" as of the writing of this documentation.
|
||||||
3. Copy the archive to the server. Use what ever tools you want for this, samba and drag and drop works great. Otherwise sftp, or other method also works.
|
3. Copy the archive to the server (**currently** Media/www/PTApp). Use what ever tools you want for this, samba and drag and drop works great. Otherwise sftp, or other method also works.
|
||||||
4. Navigate to /var/www/PTApp (or what ever the folder is).
|
4. Navigate to /var/www/PTApp (or what ever the folder is). `cd /var/www/PTApp`
|
||||||
5. Use `sudo tar -xvzf PetitTetonMeteor.tar.gz` to unpack it.
|
5. Use `sudo tar -xvzf PetitTetonMeteor.tar.gz` to unpack it.
|
||||||
6. Delete the archive (optional): `sudo rm PetitTetonMeteor.tar.gz`
|
6. Delete the archive (optional): `sudo rm PetitTetonMeteor.tar.gz`
|
||||||
7. Modify the owner of the app: `sudo chown -R www-data bundle` Run this from inside the project directory /var/www/PTApp.
|
7. Modify the owner of the app: `sudo chown -R www-data bundle` Run this from inside the project directory /var/www/PTApp.
|
||||||
@@ -105,23 +125,27 @@ NOTE: Use MongoBooster on a windows development machine to connect to the dev da
|
|||||||
9. Optional: Run NPM's install to update the dependancies (if they changed): `cd /var/www/PTApp/bundle/programs/server && npm install && cd /var/www/PTApp`
|
9. Optional: Run NPM's install to update the dependancies (if they changed): `cd /var/www/PTApp/bundle/programs/server && npm install && cd /var/www/PTApp`
|
||||||
10. Restart the meteor app: `sudo passenger-config restart-app /var/www/PTApp`
|
10. Restart the meteor app: `sudo passenger-config restart-app /var/www/PTApp`
|
||||||
|
|
||||||
# Updating a Meteor Deployment #2
|
# Updating a Meteor Deployment (NEW)
|
||||||
|
|
||||||
1. Run the NPM script for building the app `npm run build` which will package the app for the selected platform (in the package.json definition for the build script). Can double click the build script in WebStorm's UI for NPM alternatively. Can also manually run the script: `npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../`. This will generate an archive file for the project that is production ready.
|
1. Run the NPM script for building the app `npm run build` which will package the app for the selected platform (in the package.json definition for the build script). Can double click the build script in WebStorm's UI for NPM alternatively. Can also manually run the script: `npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../`. This will generate an archive file for the project that is production ready.
|
||||||
2. Copy the archive to the deployment server. Can use Samba for this. I place it in the web folder for the app.
|
2. Copy the archive to the deployment server (**currently** Media/www/PTApp). Can use Samba for this. I place it in the web folder for the app.
|
||||||
3. Run the deploy.sh script `sudo ./deploy.sh` which exists in the web directory for the app: `/var/www/PTApp`. This will unpack the archive, remove the archive, change file permissions, run NPM's install on the app, and restart the app in Phusion Passenger.
|
3. Run the deploy.sh script `sudo ./deploy.sh` which exists in the web directory for the app: `/var/www/PTApp`. This will unpack the archive, remove the archive, change file permissions, run NPM's install on the app, and restart the app in Phusion Passenger.
|
||||||
4. Look at the debug output by viewing the html files stored in `/tmp`. Use Samba to view them remotely.
|
4. Look at the debug output by viewing the html files stored in `/tmp`. Use Samba to view them remotely.
|
||||||
5. Look at the Nginx logs (should be the same as the stuff in /tmp).
|
5. Look at the Nginx logs (should be the same as the stuff in /tmp).
|
||||||
|
|
||||||
# Updating a Meteor Deployment *with* a NodeJS and Meteor version change.
|
# Updating a Meteor Deployment *with* a NodeJS and Meteor version change.
|
||||||
|
Check which version of meteor you have on the development machine (meteor is not installed ever on the production machine). If the version has changed, it may require a newer version of NodeJS. You have to read the Meteor notes for your version (or older versions) to figure out which NodeJS is required.
|
||||||
|
|
||||||
1. Update the meteor app as normal (copy it to the /var/www/xxx directory as a build bundle, then run the script to unpack it).
|
1. Update the meteor app as normal (copy it to the /var/www/xxx directory as a build bundle, then run the script to unpack it).
|
||||||
2. Run `sudo n` to get the current version of NodeJS being used. Use `sudo n x.x.x` to download and change to the new version of NodeJS.
|
2. Run `sudo n` to get the current version of NodeJS being used. Use `sudo n x.x.x` to download and change to the new version of NodeJS.
|
||||||
3. Edit the app's nginx file in /etc/nginx/sites-available/ to reference the new NodeJS install location. For example my current install location is specified as `server {... passenger_nodejs /usr/local/n/versions/node/8.9.3/bin/node ...}`}
|
3. Edit the app's nginx file in /etc/nginx/sites-available/ to reference the new NodeJS install location. For example my current install location is specified as `server {... passenger_nodejs /usr/local/n/versions/node/8.9.3/bin/node ...}`}
|
||||||
|
|
||||||
|
## Running Server Side Code
|
||||||
|
|
||||||
#Running Server Side Code
|
|
||||||
This is useful for importing data or running scripts that might perform some one time task.
|
This is useful for importing data or running scripts that might perform some one time task.
|
||||||
|
|
||||||
1. Open a console and enter the server shell for meteor.
|
1. Open a console and enter the server shell for meteor.
|
||||||
|
|
||||||
#Server Error Handling
|
## Server Error Handling
|
||||||
|
|
||||||
Errors are generated in meteor, but handled by passenger. Passenger will log the error on the client screen with a code, and will log the same message in the nginx error logs. The error will be findable in the tmp folder as an html file starting with passenger and ending with the code. This is the place to look for the real error output.
|
Errors are generated in meteor, but handled by passenger. Passenger will log the error on the client screen with a code, and will log the same message in the nginx error logs. The error will be findable in the tmp folder as an html file starting with passenger and ending with the code. This is the place to look for the real error output.
|
||||||
@@ -14,6 +14,7 @@ import '/imports/util/resize/ResizeSensor.js';
|
|||||||
import '/imports/util/resize/ElementQueries.js';
|
import '/imports/util/resize/ElementQueries.js';
|
||||||
import '/imports/ui/layouts/Body.js';
|
import '/imports/ui/layouts/Body.js';
|
||||||
import '/imports/ui/layouts/Login.js';
|
import '/imports/ui/layouts/Login.js';
|
||||||
|
import '/imports/ui/layouts/Empty.js';
|
||||||
import '/imports/ui/accounts/accounts.js';
|
import '/imports/ui/accounts/accounts.js';
|
||||||
import '/imports/util/select2/select2.css';
|
import '/imports/util/select2/select2.css';
|
||||||
import '/imports/util/select2/select2.full.js';
|
import '/imports/util/select2/select2.full.js';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>PT App</title>
|
<title>PT App</title>
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/camanjs/4.0.0/caman.full.min.js"></script>
|
||||||
<!--<meta http-equiv="content-type" content="text/html; charset=UTF8">-->
|
<!--<meta http-equiv="content-type" content="text/html; charset=UTF8">-->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="UTF8">
|
<meta charset="UTF8">
|
||||||
|
|||||||
4195
client/main.css
Normal file
4195
client/main.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -189,3 +189,5 @@ body
|
|||||||
@import "../imports/ui/Reports.import.styl"
|
@import "../imports/ui/Reports.import.styl"
|
||||||
@import "../imports/ui/Label.import.styl"
|
@import "../imports/ui/Label.import.styl"
|
||||||
@import "../imports/ui/TestList.import.styl"
|
@import "../imports/ui/TestList.import.styl"
|
||||||
|
|
||||||
|
@import "../imports/ui/PrintLabel.import.styl"
|
||||||
81
imports/api/Barcode.js
Normal file
81
imports/api/Barcode.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import {Mongo} from "meteor/mongo";
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||||
|
|
||||||
|
Barcodes = new Mongo.Collection('Barcodes');
|
||||||
|
|
||||||
|
// A simple mapping between a concatenation of the product & measure ID and a unique sequential number for the barcode. This allows us to have a small number to keep our barcodes simple, while maintaining the more traditional MongoDB ID's for the Product and Measure.
|
||||||
|
const BarcodesSchema = new SimpleSchema({
|
||||||
|
barcodeId: {
|
||||||
|
type: Number,
|
||||||
|
label: "Barcode ID",
|
||||||
|
optional: false,
|
||||||
|
index: 1,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
productAndMeasureId: { //Just the two ids jammed together with a single space between them.
|
||||||
|
type: String,
|
||||||
|
label: "Product And Measure ID",
|
||||||
|
optional: false,
|
||||||
|
index: 1,
|
||||||
|
unique: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(Meteor.isServer) {
|
||||||
|
//Meteor.publish('barcodes', function() {
|
||||||
|
// return Barcodes.find({});
|
||||||
|
//});
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
getBarcodeId: function(productId, measureId) {
|
||||||
|
check(productId, String);
|
||||||
|
check(measureId, String);
|
||||||
|
|
||||||
|
let hasProduct = Meteor.collections.Products.findOne({_id: productId}, {fields: {}});
|
||||||
|
let hasMeasure = Meteor.collections.Measures.findOne({_id: measureId}, {fields: {}});
|
||||||
|
|
||||||
|
if(hasProduct && hasMeasure) {
|
||||||
|
let existing = Barcodes.findOne({productAndMeasureId: productId + ' ' + measureId});
|
||||||
|
|
||||||
|
if(existing) {
|
||||||
|
return existing.barcodeId;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let c = 0;
|
||||||
|
|
||||||
|
//Try a thousand times before failing. Should never fail, should also not ever need to try a thousand times (unless we somehow automate label generation to the point where a 1000 processes at once are requesting labels that have never been generated before - highly unlikely).
|
||||||
|
while(c++ < 1000) {
|
||||||
|
//Lookup the most likely next barcode id from the db, then attempt to insert with it. If it fails due to duplication, then increment and repeat.
|
||||||
|
let cursor = Products.find({}, {barcodeId: 1}).sort({barcodeId: -1}).limit(1); //Since currently products are never removed, we shouldn't need to detect sequence gaps and fill them in (odds are we will never use more than 10k numbers anyway).
|
||||||
|
let barcodeId = cursor.hasNext() ? cursor.next().barcodeId + 1 : 1;
|
||||||
|
|
||||||
|
Barcodes.insert({productAndMeasureId: productId + ' ' + measureId, barcodeId}, function(err, id) {
|
||||||
|
if(err) console.log(err);
|
||||||
|
else return barcodeId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we are still here, then there was a massive failure (c exceeded 1000).
|
||||||
|
console.log("We failed to generate a new barcode ID 1000 times, so we are giving up. This should never happen.");
|
||||||
|
throw new Meteor.Error(403, "Unable to generate a barcode ID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Cannot find either the product or the measure in the db. Cannot give an id.
|
||||||
|
console.log("Unable to generate a barcode ID because we could not find the product " + productId + " OR we could not find the measure " + measureId);
|
||||||
|
throw new Meteor.Error(403, "Unable to find the product or the measure. Both must exist in order to generate a barcode ID.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
Barcodes.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Barcodes;
|
||||||
265
imports/api/Batch.js
Normal file
265
imports/api/Batch.js
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Mongo } from 'meteor/mongo';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notes:
|
||||||
|
* The Batch object has a date field which stores the date as a number in the format YYYYMMDD. Converting this number into a local date is done with moment(batch.date.toString(), "YYYYMMDD").toDate(), and converting it to a number from a date can be accomplished with ~~(moment(date).format("YYYYMMDD")), where the ~~ is a bitwise not and converts a string to a number quickly and reliably.
|
||||||
|
* A Batch in this system refers to one or more instances of cooking or preparing a product on a given date. It does NOT refer to each instance of cooking the product on that date (what might be called a batch in a kitchen). This might be more effectively called a Run, but that is a confusing word to use in a software system, so I chose to reuse the word Batch since we will not be tracking kitchen batches, just kitchen runs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let Batches = new Mongo.Collection('Batches');
|
||||||
|
let BatchesSchema = new SimpleSchema({
|
||||||
|
date: {
|
||||||
|
type: Number, // A number in the format of YYYYMMDD to allow for searching using greater and less than, and to prevent timezones from messing everything up.
|
||||||
|
label: "Date",
|
||||||
|
optional: false,
|
||||||
|
index: 1
|
||||||
|
},
|
||||||
|
timestamp: { //This is based off the date with zero for the time and set to GMT (Zulu time).
|
||||||
|
type: Date,
|
||||||
|
label: "Timestamp",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
weekOfYear: {
|
||||||
|
type: Number,
|
||||||
|
label: "Week Of Year",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
type: Number,
|
||||||
|
label: "Amount",
|
||||||
|
optional: false,
|
||||||
|
decimal: true
|
||||||
|
},
|
||||||
|
measureId: {
|
||||||
|
type: String,
|
||||||
|
label: "Measure Id",
|
||||||
|
trim: false,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1
|
||||||
|
},
|
||||||
|
productId: {
|
||||||
|
type: String,
|
||||||
|
label: "Product Id",
|
||||||
|
trim: false,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1,
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
cookId: {
|
||||||
|
type: String,
|
||||||
|
label: "Cook Worker Id",
|
||||||
|
trim: false,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1
|
||||||
|
},
|
||||||
|
cannerId: {
|
||||||
|
type: String,
|
||||||
|
label: "Canner Worker Id",
|
||||||
|
trim: false,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1,
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
hasLabels: {
|
||||||
|
type: Boolean,
|
||||||
|
label: "Has Labels",
|
||||||
|
optional: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
comment: {
|
||||||
|
type: String,
|
||||||
|
trim: false,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
label: "Created On",
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
deletedAt: {
|
||||||
|
type: Date,
|
||||||
|
label: "Deleted On",
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Batches.attachSchema(BatchesSchema);
|
||||||
|
//Ensure that the product ID, measure ID, and date combination are unique.
|
||||||
|
// Note: I took this out because while it provides for cleaner views, it is overly complicated and could be easily done with a cleanup routine after the fact, or by aggregating the data in the queries.
|
||||||
|
// What makes this complicated is the notes, cook, and canner references which may not be the same.
|
||||||
|
//Batches.createIndex({productId: 1, measureId: 1, date: 1}, {unique: true, name: "ProductMeasureDateIndex"});
|
||||||
|
|
||||||
|
if(Meteor.isServer) {
|
||||||
|
Meteor.publish('batches', function(query, sort, limit = 100, skipCount) {
|
||||||
|
let dbQuery = [];
|
||||||
|
|
||||||
|
if(query) {
|
||||||
|
_.each(_.keys(query), function(key) {
|
||||||
|
//if(_.isObject(query[key])) dbQuery.push({[key]: query[key]});
|
||||||
|
if(_.isObject(query[key])) {
|
||||||
|
if(query[key].type === 'dateRange') {
|
||||||
|
if(query[key].start && query[key].end)
|
||||||
|
dbQuery.push({[key]: {$gte: query[key].start, $lte: query[key].end}});
|
||||||
|
else if(query[key].start)
|
||||||
|
dbQuery.push({[key]: {$gte: query[key].start}});
|
||||||
|
else if(query[key].end)
|
||||||
|
dbQuery.push({[key]: {$lte: query[key].end}});
|
||||||
|
// Do nothing if a start and/or end are not provided.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dbQuery.push({[key]: query[key]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]});
|
||||||
|
else {
|
||||||
|
let searchValue = query[key];
|
||||||
|
let searches = searchValue && searchValue.length > 0 ? searchValue.split(/\s+/) : undefined;
|
||||||
|
|
||||||
|
for(let search of searches) {
|
||||||
|
dbQuery.push({[key]: {$regex: '\\b' + search, $options: 'i'}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_.isNumber(limit)) limit = 100;
|
||||||
|
if(!_.isNumber(skipCount) || skipCount < 0) skipCount = 0;
|
||||||
|
|
||||||
|
dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {};
|
||||||
|
return Meteor.collections.Batches.find(dbQuery, {limit: limit, sort, skip: skipCount});
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
getBatchCount: function(query) {
|
||||||
|
//TODO: Validate the query?
|
||||||
|
return Sales.find(query).count();
|
||||||
|
},
|
||||||
|
insertBatches: function(batches) { //Insert one or more batches (if one, you can pass just the batch).
|
||||||
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
//Force it to be an array if it isn't.
|
||||||
|
if(!Array.isArray(batches)) batches = [batches];
|
||||||
|
|
||||||
|
//Validate them all.
|
||||||
|
for(let batch of batches) {
|
||||||
|
check(batch, {
|
||||||
|
date: Number, // TODO: Check that the format is YYYYMMDD
|
||||||
|
amount: Match.Where(function(x) {
|
||||||
|
check(x, Number);
|
||||||
|
return x > 0;
|
||||||
|
}),
|
||||||
|
measureId: String,
|
||||||
|
productId: String,
|
||||||
|
cookId: String,
|
||||||
|
cannerId: String,
|
||||||
|
comment: Match.Optional(String)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let batch of batches) {
|
||||||
|
let dateString = batch.date.toString();
|
||||||
|
|
||||||
|
batch.createdAt = new Date();
|
||||||
|
batch.timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");
|
||||||
|
batch.weekOfYear = batch.timestamp.getWeek().toString();
|
||||||
|
|
||||||
|
if(batch.hasLabels === undefined) batch.hasLabels = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let batch of batches) {
|
||||||
|
Batches.insert(batch, function(err, id) {
|
||||||
|
if(err) console.log(err);
|
||||||
|
}, {bypassCollection2: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw new Meteor.Error(403, "Not authorized.");
|
||||||
|
},
|
||||||
|
deleteBatch: function(id) { //Does not actually delete the batch, but rather just marks it for deleting by applying a deletion date.
|
||||||
|
check(id, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
let deletedAt = new Date();
|
||||||
|
|
||||||
|
//Batches.remove(id);
|
||||||
|
Batches.update(id, {$set: {deletedAt}}, function(err, id) {
|
||||||
|
if(err) console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else throw new Meteor.Error(403, "Not authorized.");
|
||||||
|
},
|
||||||
|
undeleteBatch: function(id) { //Revokes the previous deletion.
|
||||||
|
check(id, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
Batches.update(id, {$unset: {deletedAt:""}}, function(err, id) {
|
||||||
|
if(err) console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else throw new Meteor.Error(403, "Not authorized.");
|
||||||
|
},
|
||||||
|
editBatchComment: function(id, comment) {
|
||||||
|
check(id, String);
|
||||||
|
check(comment, String);
|
||||||
|
//Trim and convert empty comment to undefined.
|
||||||
|
comment = comment ? comment.trim() : undefined;
|
||||||
|
comment = comment && comment.length > 0 ? comment : undefined;
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
console.log("Changed comment of " + id + " to: " + comment);
|
||||||
|
|
||||||
|
if(comment) {
|
||||||
|
Batches.update(id, {$set: {comment}}, function(error, count) {
|
||||||
|
if(error) throw new Meteor.Error(400, "Unexpected database error: " + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Batches.update(id, {$unset: {comment: ""}}, function(error, count) {
|
||||||
|
if(error) throw new Meteor.Error(400, "Unexpected database error: " + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw new Meteor.Error(403, "Not authorized.");
|
||||||
|
},
|
||||||
|
updateBatch: function(id, date, amount) {
|
||||||
|
check(id, String);
|
||||||
|
check(date, Number); // TODO: Check that the format is YYYYMMDD
|
||||||
|
check(amount, Number);
|
||||||
|
|
||||||
|
let dateString = date.toString();
|
||||||
|
let timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");
|
||||||
|
let weekOfYear = timestamp.getWeek().toString();
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
Batches.update(id, {$set: {date, amount, timestamp, weekOfYear}}, function(err, id) {
|
||||||
|
if(err) console.log(err);
|
||||||
|
}, {bypassCollection2: true});
|
||||||
|
}
|
||||||
|
else throw new Meteor.Error(403, "Not authorized.");
|
||||||
|
},
|
||||||
|
setBatchHasLabels: function(id, hasLabels) {
|
||||||
|
//console.log(id);
|
||||||
|
//console.log(hasLabels);
|
||||||
|
//check(id, Meteor.validators.ObjectID);
|
||||||
|
check(id, String);
|
||||||
|
check(hasLabels, Boolean);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
Batches.update(id, {$set: {hasLabels}}, function(err, id) {
|
||||||
|
if(err) console.log(err);
|
||||||
|
}, {bypassCollection2: true});
|
||||||
|
}
|
||||||
|
else throw new Meteor.Error(403, "Not authorized.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
Batches.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Batches;
|
||||||
130
imports/api/Label.js
Normal file
130
imports/api/Label.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Mongo } from 'meteor/mongo';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||||
|
|
||||||
|
|
||||||
|
if(Meteor.isServer) {
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
//let Future = Npm.require('fibers/future');
|
||||||
|
//
|
||||||
|
//async function printLabels(data, callback) {
|
||||||
|
// let params = "";
|
||||||
|
// let url = Meteor.absoluteUrl("/LabelPrint");
|
||||||
|
//
|
||||||
|
// params = Object.keys(data).map(function(k) {
|
||||||
|
// return encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
|
||||||
|
// }).join('&');
|
||||||
|
//
|
||||||
|
// url += "?" + params;
|
||||||
|
//
|
||||||
|
// const browser = await puppeteer.launch();
|
||||||
|
// const page = await browser.newPage();
|
||||||
|
// console.log("Going to: " + url);
|
||||||
|
// await page.goto(url, {waitUntil: 'networkidle0'});
|
||||||
|
// // By removing the `path` option, we will receive a `Buffer` from `page.pdf`.
|
||||||
|
// const pdf = await page.pdf({ width: "6in", height: "4in"}); // format: "A4" //path: 'C:\\Users\\Grumpy\\label.pdf'
|
||||||
|
//
|
||||||
|
// await browser.close();
|
||||||
|
// callback(null, pdf);
|
||||||
|
//}
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
//printLabels: function(width, height, layout, title1, title2, ingredients, date) {
|
||||||
|
// console.log("Loaded Label");
|
||||||
|
// let future = new Future();
|
||||||
|
//
|
||||||
|
// let boundCallback = Meteor.bindEnvironment(function(err, res) {
|
||||||
|
// if(err) {
|
||||||
|
// future.throw(err);
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// future.return(res);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// printLabels({width, height, layout, title1, title2, ingredients, date}, boundCallback);
|
||||||
|
//
|
||||||
|
// return future.wait();
|
||||||
|
//}
|
||||||
|
|
||||||
|
async printLabels(width, height, layout, title1, title2, ingredients, date) {
|
||||||
|
let data = {width, height, layout, title1, title2, ingredients, date};
|
||||||
|
let params = "";
|
||||||
|
let url = Meteor.absoluteUrl("/PrintLabel");
|
||||||
|
|
||||||
|
//Switch to the static page - for some reason the Meteor page is not loading correctly (it just appears blank).
|
||||||
|
//url = Meteor.absoluteUrl("/LabelPrint.html");
|
||||||
|
|
||||||
|
params = Object.keys(data).map(function(k) {
|
||||||
|
return encodeURIComponent(k) + "=" + encodeURIComponent(data[k]);
|
||||||
|
}).join('&');
|
||||||
|
|
||||||
|
url += "?" + params;
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch();
|
||||||
|
const page = await browser.newPage();
|
||||||
|
//url = Meteor.absoluteUrl("/StaticTest.html");
|
||||||
|
//url = Meteor.absoluteUrl("/StaticTest.html");
|
||||||
|
console.log("Going to: " + url);
|
||||||
|
await page.goto(url, {waitUntil: 'networkidle0'}); //, {waitUntil: 'networkidle0'}
|
||||||
|
const pdf = await page.pdf({width: '3in', height: '2in'}); //path: 'C:\\Users\\Grumpy\\label.pdf',
|
||||||
|
//const pdf = await page.pdf({format: 'A4'});
|
||||||
|
//await page.pdf({path: 'C:\\Users\\Grumpy\\label.pdf', width: '6in', height: '4in'});
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
return new Uint8Array(pdf, 0, pdf.length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Returns a JSON containing a denormalized list of products {product_id, measure_id, product_name, measure_name, price, }
|
||||||
|
WebApp.connectHandlers.use("/labels/GetBarCodeData", (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let barcodes = Meteor.collections.Barcodes.find({}, {fields: {_id: 0, barcodeId: 1, productAndMeasureId: 1}});
|
||||||
|
let measures = Meteor.collections.Measures.find({}, {fields: {_id: 1, name: 1}, sort: {order: 1}}).fetch();
|
||||||
|
//Note: Price data looks like this: {XZ5Z3CM49NDrJNADA /* MeasureID */: {price: 10.5, effectiveDate: ISODate("2017-01-12T13:14:18.876-08:00"), previousPrice: 9}, ...}
|
||||||
|
//Measures is an array of MeasureIDs valid for this product.
|
||||||
|
let products = Meteor.collections.Products.find({}, {fields: {_id: 1, name: 1, measures: 1, prices: 1}, sort: {order: 1}}).fetch();
|
||||||
|
//let measuresById = measures.reduce((map, measure) => (map[measure._id] = measure), {});
|
||||||
|
let measuresById = {};
|
||||||
|
let barcodesByProductAndMeasureIds = {};
|
||||||
|
let result = {};
|
||||||
|
let today = new Date();
|
||||||
|
|
||||||
|
for(measure of measures) measuresById[measure._id] = measure;
|
||||||
|
for(barcode of barcodes) barcodesByProductAndMeasureIds[barcode.productAndMeasureId] = barcode.barcodeId;
|
||||||
|
//console.log(measuresById);
|
||||||
|
|
||||||
|
//for(let measureId of Object.keys(measuresById)) {
|
||||||
|
// console.log(measureId + ":" + measuresById[measureId].name);
|
||||||
|
//}
|
||||||
|
|
||||||
|
for(let product of products) {
|
||||||
|
for(let measureId of product.measures) {
|
||||||
|
let measureName = measuresById[measureId] ? measuresById[measureId].name : undefined;
|
||||||
|
let priceData = product.prices ? product.prices[measureId] : undefined;
|
||||||
|
let price = (priceData ? (priceData.effectiveDate && moment(priceData.effectiveDate).isAfter(today) ? priceData.previousPrice : priceData.price) : 0); //Get the price based on the effective date - whether we should use the new price or the old.
|
||||||
|
let barcodeId = barcodesByProductAndMeasureIds[productId + " " + measureId];
|
||||||
|
|
||||||
|
//Ignore any product/measure combinations that don't have barcodes.
|
||||||
|
if(barcodeId) {
|
||||||
|
result[barcodeId] = {productId: product._id, measureId: measureId, productName: product.name, measureName, price};
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Pass the product & measure data separately from the barcodes also to handle the missing barcode scenario. When a user types in a product name and picks a measure, we can record in the sale the product ID and measure ID, and we can lookup any pricing data.
|
||||||
|
|
||||||
|
//Log any errors so we can figure out what is going on.
|
||||||
|
if(measureName === undefined) {
|
||||||
|
//Note: We will pass a price of zero if the price is unknown. This should be fine for now.
|
||||||
|
console.log(product._id + " " + product.name + " references a measure (" + measureId + ") which is not in the measures array.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.end(JSON.stringify(result), "JSON");
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err);
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -120,4 +120,11 @@ if(Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
Measures.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
export default Measures;
|
export default Measures;
|
||||||
@@ -132,12 +132,12 @@ const ProductsSchema = new SimpleSchema({
|
|||||||
label: "Updated On",
|
label: "Updated On",
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
deactivated: {
|
deactivated: { //This is turned on first, if true it will hide the product in production views, but keep the product available in the sale views. It is intended to be turned on for products that are no longer produced, but for which we have remaining inventory.
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: "Deactivated",
|
label: "Deactivated",
|
||||||
optional: true
|
optional: true
|
||||||
},
|
},
|
||||||
hidden: {
|
hidden: { //Deactivated must first be true. Hides the product everywhere in the system except in historical pages. The inventory should be all sold prior to hiding a product.
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
label: "Hidden",
|
label: "Hidden",
|
||||||
optional: true
|
optional: true
|
||||||
@@ -188,6 +188,10 @@ if(Meteor.isServer) {
|
|||||||
if(measures) check(measures, [String]);
|
if(measures) check(measures, [String]);
|
||||||
|
|
||||||
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_UPDATE])) {
|
||||||
|
//Lookup the most likely next barcode id from the db, then attempt to insert with it. If it fails due to duplication, then increment and repeat.
|
||||||
|
//let cursor = Products.find({}, {barCodeId: 1}).sort({barCodeId: -1}).limit(1); //Since currently products are never removed, we shouldn't need to detect sequence gaps and fill them in (odds are we will never use more than 10k numbers anyway).
|
||||||
|
//let barCodeId = cursor.hasNext() ? cursor.next().barCodeId : 1;
|
||||||
|
//
|
||||||
Products.insert({name, tags, aliases, measures, createdAt: new Date()}, {bypassCollection2: true}, function(err, id) {
|
Products.insert({name, tags, aliases, measures, createdAt: new Date()}, {bypassCollection2: true}, function(err, id) {
|
||||||
if(err) console.log(err);
|
if(err) console.log(err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -90,4 +90,11 @@ if(Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
ProductTags.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
export default ProductTags;
|
export default ProductTags;
|
||||||
@@ -157,4 +157,11 @@ if(Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
SalesSheets.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
export default SalesSheets;
|
export default SalesSheets;
|
||||||
@@ -126,4 +126,11 @@ if(Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
Venues.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
export default Venues;
|
export default Venues;
|
||||||
@@ -3,7 +3,7 @@ import { Mongo } from 'meteor/mongo';
|
|||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
import {SimpleSchema} from 'meteor/aldeed:simple-schema';
|
||||||
|
|
||||||
Workers = new Mongo.Collection('Workers');
|
let Workers = new Mongo.Collection('Workers');
|
||||||
let WORKER_ACTIVITIES = ['sales', 'prep', 'canning', 'farming'];
|
let WORKER_ACTIVITIES = ['sales', 'prep', 'canning', 'farming'];
|
||||||
let workersSchema = new SimpleSchema({
|
let workersSchema = new SimpleSchema({
|
||||||
name: {
|
name: {
|
||||||
@@ -60,7 +60,7 @@ let workersSchema = new SimpleSchema({
|
|||||||
workersSchema.constants = {activities: WORKER_ACTIVITIES};
|
workersSchema.constants = {activities: WORKER_ACTIVITIES};
|
||||||
Workers.attachSchema(workersSchema);
|
Workers.attachSchema(workersSchema);
|
||||||
|
|
||||||
if(Meteor.isServer) Meteor.publish('Workers', function() {
|
if(Meteor.isServer) Meteor.publish('workers', function() {
|
||||||
return Workers.find({});
|
return Workers.find({});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,4 +122,11 @@ if(Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Allows the client to do DB interaction without calling server side methods, while still retaining control over whether the user can make changes.
|
||||||
|
Workers.allow({
|
||||||
|
insert: function() {return false;},
|
||||||
|
update: function() {return false;},
|
||||||
|
remove: function() {return false;}
|
||||||
|
});
|
||||||
|
|
||||||
export default Workers;
|
export default Workers;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {Mongo} from 'meteor/mongo';
|
||||||
import Measures from "./Measure.js";
|
import Measures from "./Measure.js";
|
||||||
import Venues from "./Venue.js";
|
import Venues from "./Venue.js";
|
||||||
import Products from "./Product.js";
|
import Products from "./Product.js";
|
||||||
@@ -8,15 +9,18 @@ import Logs from "./Logs.js";
|
|||||||
import Users from "./User.js";
|
import Users from "./User.js";
|
||||||
import UserRoles from "./Roles.js";
|
import UserRoles from "./Roles.js";
|
||||||
import Workers from "./Worker.js";
|
import Workers from "./Worker.js";
|
||||||
|
import Barcodes from "./Barcode.js";
|
||||||
|
import Batches from "./Batch.js";
|
||||||
import './Reports.js';
|
import './Reports.js';
|
||||||
|
import './Label.js';
|
||||||
|
|
||||||
//Save the collections in the Meteor.collections property for easy access without name conflicts.
|
//Save the collections in the Meteor.collections property for easy access without name conflicts.
|
||||||
Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles, Workers};
|
Meteor.collections = {Measures, Venues, Products, ProductTags, Sales, SalesSheets, Logs, Users, UserRoles, Workers, Barcodes, Batches};
|
||||||
|
|
||||||
//If this is the server then setup the default admin user if none exist.
|
//If this is the server then setup the default admin user if none exist.
|
||||||
if(Meteor.isServer) {
|
if(Meteor.isServer) {
|
||||||
//Change this to find admin users, create a default admin user if none exists.
|
//Change this to find admin users, create a default admin user if none exists.
|
||||||
if(Users.find({}).count() == 0) {
|
if(Users.find({}).count() === 0) {
|
||||||
try {
|
try {
|
||||||
console.log("Creating a default admin user: admin/admin");
|
console.log("Creating a default admin user: admin/admin");
|
||||||
|
|
||||||
@@ -28,4 +32,14 @@ if(Meteor.isServer) {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Meteor.validators = {};
|
||||||
|
Meteor.validators.ObjectID = Match.Where(function(id) {
|
||||||
|
if(id instanceof Mongo.ObjectID) {
|
||||||
|
id = id._str;
|
||||||
|
}
|
||||||
|
|
||||||
|
check(id, String);
|
||||||
|
return /[0-9a-fA-F]{24}/.test(id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -123,6 +123,13 @@ pri.route('/labels', {
|
|||||||
BlazeLayout.render('Body', {content: 'LabelMaker'});
|
BlazeLayout.render('Body', {content: 'LabelMaker'});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
FlowRouter.route('/PrintLabel', {
|
||||||
|
name: 'PrintLabel',
|
||||||
|
action: function(params, queryParams) {
|
||||||
|
require("/imports/ui/PrintLabel.js");
|
||||||
|
BlazeLayout.render('Empty', {content: 'PrintLabel'});
|
||||||
|
}
|
||||||
|
});
|
||||||
pri.route('/testList', {
|
pri.route('/testList', {
|
||||||
name: 'TestList',
|
name: 'TestList',
|
||||||
action: function(params, queryParams) {
|
action: function(params, queryParams) {
|
||||||
|
|||||||
@@ -1,31 +1,46 @@
|
|||||||
<template name="LabelMaker">
|
<template name="LabelMaker">
|
||||||
<div id="labelMaker">
|
<div id="labelMaker">
|
||||||
<div class="labelOptions"></div>
|
<div class="labelOptions">
|
||||||
<div class="labelContents">
|
<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="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="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="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">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><button name="print" class="print">Print</button></div>
|
||||||
<div><label for="date">Count:</label> <input type="number" name="count" class="count input" value="{{count}}"/></div>
|
<div><button name="preview" class="preview">Preview</button></div>
|
||||||
<div><button name="generate" class="generate">Generate</button></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="labelContainer">
|
<div class="labelContainer">
|
||||||
<div class="labelSample label">
|
<div class='label'>
|
||||||
<img class="labelLogo" alt="logo" src="/images/PetitTetonLabelLogo_2in Width_v1.png"/>
|
<!-- /images/Logo_0.8x0.73_300ppi.png-->
|
||||||
<div class="labelTagline">We grow it. We can it.</div>
|
<!-- <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>-->
|
||||||
{{{labelText}}}
|
<!-- <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>
|
</div>
|
||||||
</div>
|
<!-- <div class="canvasContainer">-->
|
||||||
</template>
|
<!-- <!– 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">
|
</div>
|
||||||
{{each label}}
|
<div class="printableLabel"></div>
|
||||||
<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>
|
||||||
{{/each}}
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
236
imports/ui/Label.import.styl
vendored
236
imports/ui/Label.import.styl
vendored
@@ -1,93 +1,163 @@
|
|||||||
|
|
||||||
#labelMaker
|
#labelMaker
|
||||||
margin: 10px 20px
|
margin 10px 20px
|
||||||
height: 100%
|
height 100%
|
||||||
width: 100%
|
width 100%
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
.labelContents
|
@media not print
|
||||||
label
|
.labelOptions
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
label
|
||||||
font-weight: 200
|
font-family TimesNewRoman, Times New Roman, Times
|
||||||
font-size: 14px
|
font-weight 200
|
||||||
.title1
|
font-size 14px
|
||||||
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
|
|
||||||
.title1
|
.title1
|
||||||
width: 100%
|
width 500px
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
font-family TimesNewRoman, Times New Roman, Times
|
||||||
font-size: .2in
|
font-size .142in
|
||||||
font-weight: 800
|
font-weight 800
|
||||||
text-transform: uppercase
|
line-height .142in
|
||||||
.title2
|
.title2
|
||||||
width: 100%
|
width 500px
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
font-family TimesNewRoman, Times New Roman, Times
|
||||||
font-size: .15in
|
font-size 14px
|
||||||
font-weight: 800
|
font-weight 800
|
||||||
|
line-height 16px
|
||||||
.ingredients
|
.ingredients
|
||||||
width: 100%
|
width 500px
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
height 100px
|
||||||
font-size: .1in
|
font-family TimesNewRoman, Times New Roman, Times
|
||||||
font-weight: 100
|
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
|
.ingredientsEnding
|
||||||
width: 100%
|
width 100%
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
font-size 1.2em
|
||||||
font-size: .1in
|
font-weight 100
|
||||||
font-weight: 100
|
|
||||||
.instructions
|
.instructions
|
||||||
width: 100%
|
width 100%
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
font-size 1.2em
|
||||||
font-size: .1in
|
font-weight 800
|
||||||
font-weight: 800
|
|
||||||
.address
|
.address
|
||||||
width: 100%
|
width 100%
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
font-size 1.2em
|
||||||
font-size: .1in
|
font-weight 100
|
||||||
font-weight: 100
|
|
||||||
.website
|
.website
|
||||||
width: 100%
|
width 100%
|
||||||
font-family: TimesNewRoman, Times New Roman, Times
|
font-size 1.2em
|
||||||
font-size: .1in
|
font-weight 100
|
||||||
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 './Label.html';
|
||||||
|
import PDF from 'jspdf';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
import swal from 'sweetalert2';
|
import swal from 'sweetalert2';
|
||||||
import dragula from 'dragula';
|
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.
|
//** Creates printable labels for a roll style printer.
|
||||||
//******************************************************************
|
//******************************************************************
|
||||||
|
|
||||||
let PREFIX = "LabelMaker_";
|
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() {
|
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() {
|
Template.LabelMaker.onRendered(function() {
|
||||||
let template = this;
|
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.onDestroyed(function() {
|
||||||
});
|
});
|
||||||
Template.LabelMaker.events({
|
Template.LabelMaker.events({
|
||||||
'change .title1': function(event, template) {
|
'change .labelWidth': function(e, t) {
|
||||||
Session.set(PREFIX + "title1", $(event.target).val());
|
let x = $(e.target).val();
|
||||||
},
|
|
||||||
'change .title2': function(event, template) {
|
|
||||||
Session.set(PREFIX + "title2", $(event.target).val());
|
|
||||||
},
|
|
||||||
'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) {
|
|
||||||
|
|
||||||
|
t.labelWidth.set(!Number.isNaN(x) && x > 0 ? parseFloat(x) : 76);
|
||||||
|
},
|
||||||
|
'change .labelHeight': function(event, template) {
|
||||||
|
let x = $(event.target).val();
|
||||||
|
|
||||||
|
t.labelHeight.set(!Number.isNaN(x) && x > 0 ? parseFloat(x) : 50);
|
||||||
|
},
|
||||||
|
|
||||||
|
'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({
|
Template.LabelMaker.helpers({
|
||||||
title1: function() {return Session.get(PREFIX + "title1")},
|
title1: function() {return Template.instance().title1.get();},
|
||||||
title2: function() {return Session.get(PREFIX + "title2")},
|
title2: function() {return Template.instance().title2.get();},
|
||||||
ingredients: function() {return Session.get(PREFIX + "ingredients")},
|
ingredients: function() {return Template.instance().ingredients.get();},
|
||||||
date: function() {return Session.get(PREFIX + "date")},
|
date: function() {return Template.instance().date.get();},
|
||||||
labelText: function() {
|
labelWidth: function() {return Template.instance().labelWidth.get();},
|
||||||
return "<div class='title1'>" + Session.get(PREFIX + "title1") + "</div>" +
|
labelHeight: function() {return Template.instance().labelHeight.get();},
|
||||||
"<div class='title2'>" + Session.get(PREFIX + "title2") + "</div>" +
|
sampleLabel: function() {
|
||||||
"<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>:" + Session.get(PREFIX + "ingredients") + "</div>" +
|
let t = Template.instance();
|
||||||
"<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'>8oz</span> FD1951 (" + Session.get(PREFIX + "date") + ")</div>" +
|
setTimeout(function() {JsBarcode(".barcode").init();}, 500);
|
||||||
"<div class='instructions'>Refrigerate after opening; return jar when done</div>" +
|
return generateLabels(t.title1.get(), t.title2.get(), t.ingredients.get(), t.date.get(), 1, 0);
|
||||||
"<div class='address'>18601 Hwy 128, Yorkville, CA 95494</div>" +
|
},
|
||||||
"<div class='website'>www.PetitTeton.com</div>";
|
//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() {
|
||||||
Template.Labels.onCreated(function() {
|
return Session.get(PREFIX + "generatedLabels");
|
||||||
});
|
},
|
||||||
Template.Labels.onRendered(function() {
|
labelPxWidth: function() {
|
||||||
let template = this;
|
//console.log("label width: " + Template.instance().labelWidth.get());
|
||||||
});
|
//console.log("px_per_mm: " + PX_PER_MM);
|
||||||
Template.Labels.onDestroyed(function() {
|
//console.log("labelPxWidth: " + (Template.instance().labelWidth.get() * PX_PER_MM));
|
||||||
});
|
return Math.floor(Template.instance().labelWidth.get() * PX_PER_MM);
|
||||||
Template.Labels.events({
|
},
|
||||||
});
|
labelPxHeight: function() {
|
||||||
Template.Labels.helpers({
|
return Math.floor(Template.instance().labelHeight.get() * PX_PER_MM);
|
||||||
labels: function() {return Session.get(PREFIX + "labels")},
|
},
|
||||||
title2: function() {return Session.get(PREFIX + "title2")},
|
labelPxWidthActual: function() {
|
||||||
ingredients: function() {return Session.get(PREFIX + "ingredients")},
|
//console.log("label width: " + Template.instance().labelWidth.get());
|
||||||
date: function() {return Session.get(PREFIX + "date")},
|
//console.log("px_per_mm: " + PX_PER_MM);
|
||||||
labelText: function() {
|
//console.log("labelPxWidth: " + (Template.instance().labelWidth.get() * PX_PER_MM));
|
||||||
return "<div class='title1'>" + Session.get(PREFIX + "title1") + "</div>" +
|
return Math.floor(Template.instance().labelWidth.get() * SCREEN_PX_PER_MM);
|
||||||
"<div class='title2'>" + Session.get(PREFIX + "title2") + "</div>" +
|
},
|
||||||
"<div class='ingredients'><span class='ingredientsPrefix'>Ingredients</span>:" + Session.get(PREFIX + "ingredients") + "</div>" +
|
labelPxHeightActual: function() {
|
||||||
"<div class='ingredientsEnding'>*<span style='font-style: oblique'>grown by us</span> <span class='size'>8oz</span> FD1951 (" + Session.get(PREFIX + "date") + ")</div>" +
|
return Math.floor(Template.instance().labelHeight.get() * SCREEN_PX_PER_MM);
|
||||||
"<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>";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
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">
|
<template name="Production">
|
||||||
<div id="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>
|
</div>
|
||||||
</template>
|
</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
|
#production
|
||||||
margin: 10px 20px
|
display: table
|
||||||
|
content-box: border-box
|
||||||
|
padding: 10px 20px
|
||||||
height: 100%
|
height: 100%
|
||||||
//Flex container options.
|
width: 100%
|
||||||
flex-flow: column nowrap
|
text-align: left
|
||||||
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
|
|
||||||
|
|
||||||
.editor
|
.tableControls
|
||||||
height: 100%
|
text-align: right
|
||||||
overflow-y: auto
|
margin-right: 20px
|
||||||
|
margin-bottom: 4px
|
||||||
.insertSale
|
display: table
|
||||||
flex: none
|
|
||||||
width: 100%
|
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
|
.table
|
||||||
font-size: 1.6em
|
table-layout: fixed
|
||||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
min-width: 100%
|
||||||
font-style: normal
|
thead, tbody
|
||||||
font-variant: normal
|
> tr
|
||||||
font-weight: 500
|
> .hasLabels
|
||||||
|
width: 30px
|
||||||
.grid
|
.hasLabels
|
||||||
flex: auto
|
color green
|
||||||
align-self: stretch
|
.noLabels
|
||||||
overflow-y: auto
|
color red
|
||||||
overflow-x: auto
|
> .name
|
||||||
margin-bottom: 20px
|
//width: auto
|
||||||
border: 0
|
width: 100%
|
||||||
padding-top: 20px
|
> .date
|
||||||
|
//width: auto
|
||||||
.table > thead > tr > th
|
min-width: 150px
|
||||||
border: 0
|
max-width: 180px
|
||||||
padding-top: 0
|
> .amount
|
||||||
padding-bottom: 6px
|
//width: auto
|
||||||
|
min-width: 100px
|
||||||
.left
|
max-width: 100px
|
||||||
text-align: left
|
> .cook
|
||||||
.center
|
//width: auto
|
||||||
text-align: center
|
min-width: 150px
|
||||||
|
max-width: 180px
|
||||||
.dataTable
|
> .canner
|
||||||
table-layout: fixed
|
//width: auto
|
||||||
|
min-width: 150px
|
||||||
.tdLarge
|
max-width: 180px
|
||||||
font-size: 1.3em
|
> .comment
|
||||||
.saleRemove
|
width: 220px
|
||||||
color: red
|
min-width: 220px
|
||||||
margin-left: 8px
|
max-width: 220px
|
||||||
.saleEdit
|
> .actions
|
||||||
color: darkblue
|
width: 90px
|
||||||
margin-right: 8px
|
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';
|
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="tags">Tags {{>ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
|
||||||
<th class="aliases">Aliases {{>ProductSearch columnName='aliases'}}</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="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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
|
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#if displayNewProduct}}
|
{{#if displayNew}}
|
||||||
{{> ProductEditor isNew=true}}
|
{{> ProductEditor isNew=true}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each products}}
|
{{#each products}}
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="ProductEditor">
|
<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>Name:</label><input name="name" class="form-control" type="text" value="{{name}}" autocomplete="off" required></div>
|
||||||
<div class="editorDiv"><label>Tags:</label>
|
<div class="editorDiv"><label>Tags:</label>
|
||||||
<select class="productTagsEditor" multiple="multiple">
|
<select class="productTagsEditor" multiple="multiple">
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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>
|
||||||
|
|
||||||
<template name="ConvertProduct">
|
<template name="ConvertProduct">
|
||||||
@@ -109,11 +109,11 @@
|
|||||||
<input name="product" class="form-control" type="text" required/>
|
<input name="product" class="form-control" type="text" required/>
|
||||||
<label><em>Convert sales from this product to an alternate.</em></label>
|
<label><em>Convert sales from this product to an alternate.</em></label>
|
||||||
</td>
|
</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>
|
||||||
|
|
||||||
<template name="ProductSearch">
|
<template name="ProductSearch">
|
||||||
<div class="productSearch">
|
<div class="search">
|
||||||
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}"/>
|
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
8
imports/ui/Products.import.styl
vendored
8
imports/ui/Products.import.styl
vendored
@@ -69,14 +69,14 @@
|
|||||||
> tr
|
> tr
|
||||||
> th.actions
|
> th.actions
|
||||||
text-align: center
|
text-align: center
|
||||||
.newProductButton
|
.newButton
|
||||||
margin-top: 4px
|
margin-top: 4px
|
||||||
padding: 0 12px
|
padding: 0 12px
|
||||||
.fa-plus-circle
|
.fa-plus-circle
|
||||||
display: inline-block
|
display: inline-block
|
||||||
.fa-times-circle
|
.fa-times-circle
|
||||||
display: none
|
display: none
|
||||||
.newProductButton:active
|
.newButton:active
|
||||||
background-color: #fb557b
|
background-color: #fb557b
|
||||||
color: black
|
color: black
|
||||||
.fa-times-circle
|
.fa-times-circle
|
||||||
@@ -105,9 +105,9 @@
|
|||||||
thead
|
thead
|
||||||
visibility: hidden
|
visibility: hidden
|
||||||
display: none
|
display: none
|
||||||
.productSearch
|
.search
|
||||||
margin: 3px 0 2px 1px
|
margin: 3px 0 2px 1px
|
||||||
.productEditorTd
|
.editorTd
|
||||||
background: #deeac0
|
background: #deeac0
|
||||||
input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor
|
input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Tracker.autorun(function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Template.Products.onCreated(function() {
|
Template.Products.onCreated(function() {
|
||||||
Session.set(PREFIX + "displayNewProduct", false);
|
Session.set(PREFIX + "displayNew", false);
|
||||||
Session.set(PREFIX + "showHidden", false);
|
Session.set(PREFIX + "showHidden", false);
|
||||||
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
|
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
|
||||||
});
|
});
|
||||||
@@ -25,8 +25,8 @@ Template.Products.onRendered(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
Template.Products.helpers({
|
Template.Products.helpers({
|
||||||
displayNewProduct: function() {
|
displayNew: function() {
|
||||||
return Session.get(PREFIX + "displayNewProduct");
|
return Session.get(PREFIX + "displayNew");
|
||||||
},
|
},
|
||||||
products: function() {
|
products: function() {
|
||||||
let skipCount = Session.get(PREFIX + 'skipCount') || 0;
|
let skipCount = Session.get(PREFIX + 'skipCount') || 0;
|
||||||
@@ -68,16 +68,16 @@ Template.Products.events({
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
|
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
|
||||||
},
|
},
|
||||||
'click .newProductButton': function(event, template) {
|
'click .newButton': function(event, template) {
|
||||||
if(template.$('.newProductButton').hasClass('active')) {
|
if(template.$('.newButton').hasClass('active')) {
|
||||||
Session.set(PREFIX + 'displayNewProduct', false);
|
Session.set(PREFIX + 'displayNew', false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Session.set(PREFIX + 'displayNewProduct', true);
|
Session.set(PREFIX + 'displayNew', true);
|
||||||
Session.set(PREFIX + "editedProduct", undefined); //Clear the edited product so that only one editor is open at a time.
|
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.
|
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) {
|
'change input[name="showHidden"]': function(event, template) {
|
||||||
//console.log("changed " + $(event.target).prop('checked'));
|
//console.log("changed " + $(event.target).prop('checked'));
|
||||||
@@ -166,9 +166,9 @@ Template.Product.helpers({
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
editing: function() {
|
editing: function() {
|
||||||
let editedProduct = Session.get(PREFIX + "editedProduct");
|
let editedId = Session.get(PREFIX + "editedId");
|
||||||
|
|
||||||
return editedProduct == this._id;
|
return editedId == this._id;
|
||||||
},
|
},
|
||||||
converting: function() {
|
converting: function() {
|
||||||
let convertedProduct = Session.get(PREFIX + "convertedProduct");
|
let convertedProduct = Session.get(PREFIX + "convertedProduct");
|
||||||
@@ -181,10 +181,10 @@ Template.Product.helpers({
|
|||||||
});
|
});
|
||||||
Template.Product.events({
|
Template.Product.events({
|
||||||
"click .actionEdit": function(event, template) {
|
"click .actionEdit": function(event, template) {
|
||||||
Session.set(PREFIX + "editedProduct", this._id);
|
Session.set(PREFIX + "editedId", this._id);
|
||||||
Session.set(PREFIX + 'displayNewProduct', false); //Ensure the new product editor is closed.
|
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.
|
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) {
|
"click .actionDeactivate": function(event, template) {
|
||||||
Meteor.call('deactivateProduct', this._id, function(error, result) {
|
Meteor.call('deactivateProduct', this._id, function(error, result) {
|
||||||
@@ -206,9 +206,9 @@ Template.Product.events({
|
|||||||
},
|
},
|
||||||
"click .actionConvert": function(event, template) {
|
"click .actionConvert": function(event, template) {
|
||||||
Session.set(PREFIX + "convertedProduct", this._id);
|
Session.set(PREFIX + "convertedProduct", this._id);
|
||||||
Session.set(PREFIX + 'displayNewProduct', false); //Ensure the new product editor is closed.
|
Session.set(PREFIX + 'displayNew', false); //Ensure the new editor is closed.
|
||||||
Session.set(PREFIX + "editedProduct", undefined); //Clear the edited product so that only one editor is open at a time.
|
Session.set(PREFIX + "editedId", undefined); //Clear the edited product so that only one editor is open at a time.
|
||||||
template.$('.newProductButton').removeClass('active');
|
template.$('.newButton').removeClass('active');
|
||||||
},
|
},
|
||||||
'click .actionHide': function(event, template) {
|
'click .actionHide': function(event, template) {
|
||||||
Meteor.call('hideProduct', this._id, function(error, result) {
|
Meteor.call('hideProduct', this._id, function(error, result) {
|
||||||
@@ -249,9 +249,9 @@ Template.ProductEditor.helpers({
|
|||||||
});
|
});
|
||||||
Template.ProductEditor.events({
|
Template.ProductEditor.events({
|
||||||
"click .editorCancel": function(event, template) {
|
"click .editorCancel": function(event, template) {
|
||||||
Session.set(PREFIX + "editedProduct", undefined);
|
Session.set(PREFIX + "editedId", undefined);
|
||||||
Session.set(PREFIX + 'displayNewProduct', false);
|
Session.set(PREFIX + 'displayNew', false);
|
||||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
template.parentTemplate().$('.newButton').removeClass('active');
|
||||||
},
|
},
|
||||||
"click .editorApply": function(event, template) {
|
"click .editorApply": function(event, template) {
|
||||||
let name = template.$("input[name='name']").val().trim();
|
let name = template.$("input[name='name']").val().trim();
|
||||||
@@ -263,13 +263,13 @@ Template.ProductEditor.events({
|
|||||||
aliases = aliases.map((n)=>n.id);
|
aliases = aliases.map((n)=>n.id);
|
||||||
measures = measures.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) {
|
Meteor.call("createProduct", name, tags, aliases, measures, function(error, result) {
|
||||||
if(error) sAlert.error(error);
|
if(error) sAlert.error(error);
|
||||||
else {
|
else {
|
||||||
sAlert.success("Product created.");
|
sAlert.success("Product created.");
|
||||||
Session.set(PREFIX + 'displayNewProduct', false);
|
Session.set(PREFIX + 'displayNew', false);
|
||||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
template.parentTemplate().$('.newButton').removeClass('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -278,8 +278,8 @@ Template.ProductEditor.events({
|
|||||||
if(error) sAlert.error(error);
|
if(error) sAlert.error(error);
|
||||||
else {
|
else {
|
||||||
sAlert.success("Product updated.");
|
sAlert.success("Product updated.");
|
||||||
Session.set(PREFIX + "editedProduct", undefined);
|
Session.set(PREFIX + "editedId", undefined);
|
||||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
template.parentTemplate().$('.newButton').removeClass('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -296,10 +296,10 @@ Template.ConvertProduct.onRendered(function() {
|
|||||||
});
|
});
|
||||||
Template.ConvertProduct.events({
|
Template.ConvertProduct.events({
|
||||||
"click .editorCancel": function(event, template) {
|
"click .editorCancel": function(event, template) {
|
||||||
Session.set(PREFIX + "editedProduct", undefined);
|
Session.set(PREFIX + "editedId", undefined);
|
||||||
Session.set(PREFIX + "convertedProduct", undefined);
|
Session.set(PREFIX + "convertedProduct", undefined);
|
||||||
Session.set(PREFIX + 'displayNewProduct', false);
|
Session.set(PREFIX + 'displayNew', false);
|
||||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
template.parentTemplate().$('.newButton').removeClass('active');
|
||||||
},
|
},
|
||||||
"click .editorApply": function(event, template) {
|
"click .editorApply": function(event, template) {
|
||||||
let productId = template.selectedProduct.get()._id;
|
let productId = template.selectedProduct.get()._id;
|
||||||
@@ -308,10 +308,10 @@ Template.ConvertProduct.events({
|
|||||||
if(error) sAlert.error(error);
|
if(error) sAlert.error(error);
|
||||||
else {
|
else {
|
||||||
sAlert.success("Sales of this product were converted.");
|
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 + "convertedProduct", undefined);
|
||||||
Session.set(PREFIX + 'displayNewProduct', false);
|
Session.set(PREFIX + 'displayNew', false);
|
||||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
template.parentTemplate().$('.newButton').removeClass('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,44 +129,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>-->
|
|
||||||
387
imports/ui/layouts/Body.import.styl
vendored
387
imports/ui/layouts/Body.import.styl
vendored
@@ -18,202 +18,211 @@
|
|||||||
height: 100%
|
height: 100%
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
nav.leftSidebarContainer
|
@media not print
|
||||||
z-index:999
|
nav.leftSidebarContainer
|
||||||
position: fixed
|
z-index:999
|
||||||
top: 0
|
position: fixed
|
||||||
width: 220px
|
top: 0
|
||||||
padding: 0
|
width: 220px
|
||||||
height: 100%
|
padding: 0
|
||||||
border: 0
|
height: 100%
|
||||||
vertical-align: top
|
border: 0
|
||||||
text-align: left
|
vertical-align: top
|
||||||
background-color: #90b272 //Old browsers
|
text-align: left
|
||||||
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
|
background-color: #90b272 //Old browsers
|
||||||
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
|
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
|
||||||
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
|
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
|
||||||
font-size: 14px
|
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
|
||||||
font-weight: 700
|
font-size: 14px
|
||||||
overflow: visible
|
font-weight: 700
|
||||||
margin: 0 0 0 -220px
|
overflow: visible
|
||||||
-webkit-transition: .5s ease-in
|
margin: 0 0 0 -220px
|
||||||
-moz-transition: .5s ease-in
|
|
||||||
-o-transition: .5s ease-in
|
|
||||||
-ms-transition: .5s ease-in
|
|
||||||
transition: .5s ease-in
|
|
||||||
.leftSidebarMenuButton
|
|
||||||
position: absolute
|
|
||||||
right: -30px
|
|
||||||
-webkit-transition: .5s ease-in
|
-webkit-transition: .5s ease-in
|
||||||
-moz-transition: .5s ease-in
|
-moz-transition: .5s ease-in
|
||||||
-o-transition: .5s ease-in
|
-o-transition: .5s ease-in
|
||||||
-ms-transition: .5s ease-in
|
-ms-transition: .5s ease-in
|
||||||
transition: .5s ease-in
|
transition: .5s ease-in
|
||||||
-webkit-border-top-right-radius: 5px
|
.leftSidebarMenuButton
|
||||||
-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
|
|
||||||
position: absolute
|
position: absolute
|
||||||
left: 10px
|
right: -30px
|
||||||
top: 10px
|
-webkit-transition: .5s ease-in
|
||||||
color: white
|
-moz-transition: .5s ease-in
|
||||||
cursor: pointer
|
-o-transition: .5s ease-in
|
||||||
.signOut:hover
|
-ms-transition: .5s ease-in
|
||||||
color: #BBB
|
transition: .5s ease-in
|
||||||
.signOut:active
|
-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
|
color: black
|
||||||
.logo
|
font-size: 20px
|
||||||
|
line-height: 20px
|
||||||
|
font-weight: 900
|
||||||
text-align: center
|
text-align: center
|
||||||
margin-top: 20px
|
text-decoration: none
|
||||||
img:hover
|
width: 30px
|
||||||
//-webkit-animation: neon6_drop 1.5s ease-in-out infinite alternate;
|
height: 30px
|
||||||
//-moz-animation: neon6_drop 1.5s ease-in-out infinite alternate;
|
padding: 5px 0
|
||||||
animation: neon6_drop 1.5s ease-in-out infinite alternate;
|
background-color: #90b272
|
||||||
.menuArea
|
display: block
|
||||||
flex: 1 1 auto
|
border-top: 1px solid #494
|
||||||
width: 100%
|
border-right: 1px solid #494
|
||||||
ul
|
border-bottom: 1px solid #494
|
||||||
padding: 20px 0 0 0
|
.leftSidebarMenuButton:hover
|
||||||
margin: 0
|
color: rgba(150,0,0,.5)
|
||||||
list-style: none
|
nav.generalSidebar
|
||||||
li:first-child
|
.leftSidebarMenuButton
|
||||||
border-top: 1px solid #e4e5e7
|
top: 10px
|
||||||
li
|
nav.graphsSidebar
|
||||||
border-bottom: 1px solid #e4e5e7
|
.leftSidebarMenuButton
|
||||||
color: #96a2ae
|
top: 50px
|
||||||
text-transform: uppercase
|
nav.settingsSidebar
|
||||||
display: block
|
.leftSidebarMenuButton
|
||||||
a
|
top: 90px
|
||||||
color: black
|
nav.menuHide .leftSidebarMenuButton
|
||||||
padding: 10px 20px
|
right: 60px
|
||||||
cursor: pointer
|
nav.menuShow
|
||||||
text-decoration: none
|
margin: 0
|
||||||
display: block
|
nav.menuShow .leftSidebarMenuButton
|
||||||
.tag
|
right: -15px
|
||||||
padding: .3em .6em
|
-webkit-transform: rotate(45deg) !important
|
||||||
margin-top: -.2em
|
-moz-transform: rotate(45deg) !important
|
||||||
font-size: .8em
|
-o-transform: rotate(45deg) !important
|
||||||
color: #ddd
|
-ms-transform: rotate(45deg) !important
|
||||||
white-space: nowrap
|
transform: rotate(45deg) !important
|
||||||
vertical-align: baseline
|
-moz-border-radius-bottomright: 0
|
||||||
border-radius: .5em
|
//border-top-right-radius: 0
|
||||||
border: 1px solid #000000
|
border-bottom-right-radius: 0
|
||||||
float: right
|
border-bottom: 0
|
||||||
.subMenu
|
.leftSidebar
|
||||||
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
|
flex: 0 0 auto
|
||||||
width: 100%
|
display: flex
|
||||||
font-size: 9px
|
flex-flow: column
|
||||||
text-align: center
|
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
|
.logoArea
|
||||||
flex: 1 1 1px
|
flex: 0 0 auto
|
||||||
padding: 10px 20px
|
width: 100%
|
||||||
-webkit-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
|
.signOut
|
||||||
-moz-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
|
position: absolute
|
||||||
box-shadow: inset 8px 0px 10px -3px rgba(168,165,168,1)
|
left: 10px
|
||||||
overflow: hidden
|
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';
|
||||||
46
imports/util/qrcode/README.md
Normal file
46
imports/util/qrcode/README.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# QRCode.js
|
||||||
|
QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM.
|
||||||
|
QRCode.js has no dependencies.
|
||||||
|
|
||||||
|
## Basic Usages
|
||||||
|
```
|
||||||
|
<div id="qrcode"></div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
new QRCode(document.getElementById("qrcode"), "http://jindo.dev.naver.com/collie");
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
or with some options
|
||||||
|
|
||||||
|
```
|
||||||
|
<div id="qrcode"></div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||||
|
text: "http://jindo.dev.naver.com/collie",
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
colorDark : "#000000",
|
||||||
|
colorLight : "#ffffff",
|
||||||
|
correctLevel : QRCode.CorrectLevel.H
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
and you can use some methods
|
||||||
|
|
||||||
|
```
|
||||||
|
qrcode.clear(); // clear the code.
|
||||||
|
qrcode.makeCode("http://naver.com"); // make another code.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Compatibility
|
||||||
|
IE6~10, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC.
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
twitter @davidshimjs
|
||||||
|
|
||||||
|
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||||
|
|
||||||
616
imports/util/qrcode/qrcode.js
Normal file
616
imports/util/qrcode/qrcode.js
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview
|
||||||
|
* - Using the 'QRCode for Javascript library'
|
||||||
|
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
|
||||||
|
* - this library has no dependencies.
|
||||||
|
*
|
||||||
|
* @author davidshimjs
|
||||||
|
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
|
||||||
|
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
|
||||||
|
*/
|
||||||
|
var QRCode;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// QRCode for JavaScript
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009 Kazuhiko Arase
|
||||||
|
//
|
||||||
|
// URL: http://www.d-project.com/
|
||||||
|
//
|
||||||
|
// Licensed under the MIT license:
|
||||||
|
// http://www.opensource.org/licenses/mit-license.php
|
||||||
|
//
|
||||||
|
// The word "QR Code" is registered trademark of
|
||||||
|
// DENSO WAVE INCORPORATED
|
||||||
|
// http://www.denso-wave.com/qrcode/faqpatent-e.html
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
function QR8bitByte(data) {
|
||||||
|
this.mode = QRMode.MODE_8BIT_BYTE;
|
||||||
|
this.data = data;
|
||||||
|
this.parsedData = [];
|
||||||
|
|
||||||
|
// Added to support UTF-8 Characters
|
||||||
|
for (var i = 0, l = this.data.length; i < l; i++) {
|
||||||
|
var byteArray = [];
|
||||||
|
var code = this.data.charCodeAt(i);
|
||||||
|
|
||||||
|
if (code > 0x10000) {
|
||||||
|
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
|
||||||
|
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
|
||||||
|
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
|
||||||
|
byteArray[3] = 0x80 | (code & 0x3F);
|
||||||
|
} else if (code > 0x800) {
|
||||||
|
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
|
||||||
|
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
|
||||||
|
byteArray[2] = 0x80 | (code & 0x3F);
|
||||||
|
} else if (code > 0x80) {
|
||||||
|
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
|
||||||
|
byteArray[1] = 0x80 | (code & 0x3F);
|
||||||
|
} else {
|
||||||
|
byteArray[0] = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parsedData.push(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
|
||||||
|
|
||||||
|
if (this.parsedData.length != this.data.length) {
|
||||||
|
this.parsedData.unshift(191);
|
||||||
|
this.parsedData.unshift(187);
|
||||||
|
this.parsedData.unshift(239);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QR8bitByte.prototype = {
|
||||||
|
getLength: function (buffer) {
|
||||||
|
return this.parsedData.length;
|
||||||
|
},
|
||||||
|
write: function (buffer) {
|
||||||
|
for (var i = 0, l = this.parsedData.length; i < l; i++) {
|
||||||
|
buffer.put(this.parsedData[i], 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function QRCodeModel(typeNumber, errorCorrectLevel) {
|
||||||
|
this.typeNumber = typeNumber;
|
||||||
|
this.errorCorrectLevel = errorCorrectLevel;
|
||||||
|
this.modules = null;
|
||||||
|
this.moduleCount = 0;
|
||||||
|
this.dataCache = null;
|
||||||
|
this.dataList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
|
||||||
|
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
|
||||||
|
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
|
||||||
|
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
|
||||||
|
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
|
||||||
|
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
|
||||||
|
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
|
||||||
|
this.modules[r][6]=(r%2==0);}
|
||||||
|
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
|
||||||
|
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
|
||||||
|
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
|
||||||
|
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
|
||||||
|
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
|
||||||
|
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
|
||||||
|
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
|
||||||
|
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
|
||||||
|
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
|
||||||
|
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
|
||||||
|
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
|
||||||
|
+buffer.getLengthInBits()
|
||||||
|
+">"
|
||||||
|
+totalDataCount*8
|
||||||
|
+")");}
|
||||||
|
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
|
||||||
|
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
|
||||||
|
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
||||||
|
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
||||||
|
buffer.put(QRCodeModel.PAD1,8);}
|
||||||
|
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
|
||||||
|
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
|
||||||
|
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
|
||||||
|
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
|
||||||
|
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
|
||||||
|
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
|
||||||
|
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
|
||||||
|
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
|
||||||
|
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
|
||||||
|
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
|
||||||
|
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
|
||||||
|
if(r==0&&c==0){continue;}
|
||||||
|
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
|
||||||
|
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
|
||||||
|
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
|
||||||
|
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
|
||||||
|
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
|
||||||
|
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
|
||||||
|
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
|
||||||
|
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
|
||||||
|
while(n>=256){n-=255;}
|
||||||
|
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
|
||||||
|
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
|
||||||
|
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
|
||||||
|
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
|
||||||
|
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
|
||||||
|
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
|
||||||
|
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
|
||||||
|
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
|
||||||
|
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
|
||||||
|
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
|
||||||
|
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
|
||||||
|
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
|
||||||
|
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
|
||||||
|
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
|
||||||
|
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
|
||||||
|
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
|
||||||
|
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
|
||||||
|
|
||||||
|
function _isSupportCanvas() {
|
||||||
|
return typeof CanvasRenderingContext2D != "undefined";
|
||||||
|
}
|
||||||
|
|
||||||
|
// android 2.x doesn't support Data-URI spec
|
||||||
|
function _getAndroid() {
|
||||||
|
var android = false;
|
||||||
|
var sAgent = navigator.userAgent;
|
||||||
|
|
||||||
|
if (/android/i.test(sAgent)) { // android
|
||||||
|
android = true;
|
||||||
|
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
|
||||||
|
|
||||||
|
if (aMat && aMat[1]) {
|
||||||
|
android = parseFloat(aMat[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return android;
|
||||||
|
}
|
||||||
|
|
||||||
|
var svgDrawer = (function() {
|
||||||
|
|
||||||
|
var Drawing = function (el, htOption) {
|
||||||
|
this._el = el;
|
||||||
|
this._htOption = htOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
Drawing.prototype.draw = function (oQRCode) {
|
||||||
|
var _htOption = this._htOption;
|
||||||
|
var _el = this._el;
|
||||||
|
var nCount = oQRCode.getModuleCount();
|
||||||
|
var nWidth = Math.floor(_htOption.width / nCount);
|
||||||
|
var nHeight = Math.floor(_htOption.height / nCount);
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
function makeSVG(tag, attrs) {
|
||||||
|
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
||||||
|
for (var k in attrs)
|
||||||
|
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
|
||||||
|
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
|
||||||
|
_el.appendChild(svg);
|
||||||
|
|
||||||
|
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
|
||||||
|
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
|
||||||
|
|
||||||
|
for (var row = 0; row < nCount; row++) {
|
||||||
|
for (var col = 0; col < nCount; col++) {
|
||||||
|
if (oQRCode.isDark(row, col)) {
|
||||||
|
var child = makeSVG("use", {"x": String(col), "y": String(row)});
|
||||||
|
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
|
||||||
|
svg.appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Drawing.prototype.clear = function () {
|
||||||
|
while (this._el.hasChildNodes())
|
||||||
|
this._el.removeChild(this._el.lastChild);
|
||||||
|
};
|
||||||
|
return Drawing;
|
||||||
|
})();
|
||||||
|
|
||||||
|
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
|
||||||
|
|
||||||
|
// Drawing in DOM by using Table tag
|
||||||
|
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
|
||||||
|
var Drawing = function (el, htOption) {
|
||||||
|
this._el = el;
|
||||||
|
this._htOption = htOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the QRCode
|
||||||
|
*
|
||||||
|
* @param {QRCode} oQRCode
|
||||||
|
*/
|
||||||
|
Drawing.prototype.draw = function (oQRCode) {
|
||||||
|
var _htOption = this._htOption;
|
||||||
|
var _el = this._el;
|
||||||
|
var nCount = oQRCode.getModuleCount();
|
||||||
|
var nWidth = Math.floor(_htOption.width / nCount);
|
||||||
|
var nHeight = Math.floor(_htOption.height / nCount);
|
||||||
|
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
|
||||||
|
|
||||||
|
for (var row = 0; row < nCount; row++) {
|
||||||
|
aHTML.push('<tr>');
|
||||||
|
|
||||||
|
for (var col = 0; col < nCount; col++) {
|
||||||
|
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
|
||||||
|
}
|
||||||
|
|
||||||
|
aHTML.push('</tr>');
|
||||||
|
}
|
||||||
|
|
||||||
|
aHTML.push('</table>');
|
||||||
|
_el.innerHTML = aHTML.join('');
|
||||||
|
|
||||||
|
// Fix the margin values as real size.
|
||||||
|
var elTable = _el.childNodes[0];
|
||||||
|
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
|
||||||
|
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
|
||||||
|
|
||||||
|
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
|
||||||
|
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the QRCode
|
||||||
|
*/
|
||||||
|
Drawing.prototype.clear = function () {
|
||||||
|
this._el.innerHTML = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
return Drawing;
|
||||||
|
})() : (function () { // Drawing in Canvas
|
||||||
|
function _onMakeImage() {
|
||||||
|
this._elImage.src = this._elCanvas.toDataURL("image/png");
|
||||||
|
this._elImage.style.display = "block";
|
||||||
|
this._elCanvas.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android 2.1 bug workaround
|
||||||
|
// http://code.google.com/p/android/issues/detail?id=5141
|
||||||
|
if (this._android && this._android <= 2.1) {
|
||||||
|
var factor = 1 / window.devicePixelRatio;
|
||||||
|
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
|
||||||
|
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
|
||||||
|
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
|
||||||
|
for (var i = arguments.length - 1; i >= 1; i--) {
|
||||||
|
arguments[i] = arguments[i] * factor;
|
||||||
|
}
|
||||||
|
} else if (typeof dw == "undefined") {
|
||||||
|
arguments[1] *= factor;
|
||||||
|
arguments[2] *= factor;
|
||||||
|
arguments[3] *= factor;
|
||||||
|
arguments[4] *= factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawImage.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the user's browser supports Data URI or not
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Function} fSuccess Occurs if it supports Data URI
|
||||||
|
* @param {Function} fFail Occurs if it doesn't support Data URI
|
||||||
|
*/
|
||||||
|
function _safeSetDataURI(fSuccess, fFail) {
|
||||||
|
var self = this;
|
||||||
|
self._fFail = fFail;
|
||||||
|
self._fSuccess = fSuccess;
|
||||||
|
|
||||||
|
// Check it just once
|
||||||
|
if (self._bSupportDataURI === null) {
|
||||||
|
var el = document.createElement("img");
|
||||||
|
var fOnError = function() {
|
||||||
|
self._bSupportDataURI = false;
|
||||||
|
|
||||||
|
if (self._fFail) {
|
||||||
|
self._fFail.call(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var fOnSuccess = function() {
|
||||||
|
self._bSupportDataURI = true;
|
||||||
|
|
||||||
|
if (self._fSuccess) {
|
||||||
|
self._fSuccess.call(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
el.onabort = fOnError;
|
||||||
|
el.onerror = fOnError;
|
||||||
|
el.onload = fOnSuccess;
|
||||||
|
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
|
||||||
|
return;
|
||||||
|
} else if (self._bSupportDataURI === true && self._fSuccess) {
|
||||||
|
self._fSuccess.call(self);
|
||||||
|
} else if (self._bSupportDataURI === false && self._fFail) {
|
||||||
|
self._fFail.call(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawing QRCode by using canvas
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {HTMLElement} el
|
||||||
|
* @param {Object} htOption QRCode Options
|
||||||
|
*/
|
||||||
|
var Drawing = function (el, htOption) {
|
||||||
|
this._bIsPainted = false;
|
||||||
|
this._android = _getAndroid();
|
||||||
|
|
||||||
|
this._htOption = htOption;
|
||||||
|
this._elCanvas = document.createElement("canvas");
|
||||||
|
this._elCanvas.width = htOption.width;
|
||||||
|
this._elCanvas.height = htOption.height;
|
||||||
|
el.appendChild(this._elCanvas);
|
||||||
|
this._el = el;
|
||||||
|
this._oContext = this._elCanvas.getContext("2d");
|
||||||
|
this._bIsPainted = false;
|
||||||
|
this._elImage = document.createElement("img");
|
||||||
|
this._elImage.alt = "Scan me!";
|
||||||
|
this._elImage.style.display = "none";
|
||||||
|
this._el.appendChild(this._elImage);
|
||||||
|
this._bSupportDataURI = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the QRCode
|
||||||
|
*
|
||||||
|
* @param {QRCode} oQRCode
|
||||||
|
*/
|
||||||
|
Drawing.prototype.draw = function (oQRCode) {
|
||||||
|
var _elImage = this._elImage;
|
||||||
|
var _oContext = this._oContext;
|
||||||
|
var _htOption = this._htOption;
|
||||||
|
|
||||||
|
var nCount = oQRCode.getModuleCount();
|
||||||
|
var nWidth = _htOption.width / nCount;
|
||||||
|
var nHeight = _htOption.height / nCount;
|
||||||
|
var nRoundedWidth = Math.round(nWidth);
|
||||||
|
var nRoundedHeight = Math.round(nHeight);
|
||||||
|
|
||||||
|
_elImage.style.display = "none";
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
for (var row = 0; row < nCount; row++) {
|
||||||
|
for (var col = 0; col < nCount; col++) {
|
||||||
|
var bIsDark = oQRCode.isDark(row, col);
|
||||||
|
var nLeft = col * nWidth;
|
||||||
|
var nTop = row * nHeight;
|
||||||
|
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
||||||
|
_oContext.lineWidth = 1;
|
||||||
|
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
||||||
|
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
|
||||||
|
|
||||||
|
// 안티 앨리어싱 방지 처리
|
||||||
|
_oContext.strokeRect(
|
||||||
|
Math.floor(nLeft) + 0.5,
|
||||||
|
Math.floor(nTop) + 0.5,
|
||||||
|
nRoundedWidth,
|
||||||
|
nRoundedHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
_oContext.strokeRect(
|
||||||
|
Math.ceil(nLeft) - 0.5,
|
||||||
|
Math.ceil(nTop) - 0.5,
|
||||||
|
nRoundedWidth,
|
||||||
|
nRoundedHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._bIsPainted = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the image from Canvas if the browser supports Data URI.
|
||||||
|
*/
|
||||||
|
Drawing.prototype.makeImage = function () {
|
||||||
|
if (this._bIsPainted) {
|
||||||
|
_safeSetDataURI.call(this, _onMakeImage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the QRCode is painted or not
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Drawing.prototype.isPainted = function () {
|
||||||
|
return this._bIsPainted;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the QRCode
|
||||||
|
*/
|
||||||
|
Drawing.prototype.clear = function () {
|
||||||
|
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
|
||||||
|
this._bIsPainted = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Number} nNumber
|
||||||
|
*/
|
||||||
|
Drawing.prototype.round = function (nNumber) {
|
||||||
|
if (!nNumber) {
|
||||||
|
return nNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(nNumber * 1000) / 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Drawing;
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type by string length
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {String} sText
|
||||||
|
* @param {Number} nCorrectLevel
|
||||||
|
* @return {Number} type
|
||||||
|
*/
|
||||||
|
function _getTypeNumber(sText, nCorrectLevel) {
|
||||||
|
var nType = 1;
|
||||||
|
var length = _getUTF8Length(sText);
|
||||||
|
|
||||||
|
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
|
||||||
|
var nLimit = 0;
|
||||||
|
|
||||||
|
switch (nCorrectLevel) {
|
||||||
|
case QRErrorCorrectLevel.L :
|
||||||
|
nLimit = QRCodeLimitLength[i][0];
|
||||||
|
break;
|
||||||
|
case QRErrorCorrectLevel.M :
|
||||||
|
nLimit = QRCodeLimitLength[i][1];
|
||||||
|
break;
|
||||||
|
case QRErrorCorrectLevel.Q :
|
||||||
|
nLimit = QRCodeLimitLength[i][2];
|
||||||
|
break;
|
||||||
|
case QRErrorCorrectLevel.H :
|
||||||
|
nLimit = QRCodeLimitLength[i][3];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length <= nLimit) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nType++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nType > QRCodeLimitLength.length) {
|
||||||
|
throw new Error("Too long data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return nType;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getUTF8Length(sText) {
|
||||||
|
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
|
||||||
|
return replacedText.length + (replacedText.length != sText ? 3 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class QRCode
|
||||||
|
* @constructor
|
||||||
|
* @example
|
||||||
|
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var oQRCode = new QRCode("test", {
|
||||||
|
* text : "http://naver.com",
|
||||||
|
* width : 128,
|
||||||
|
* height : 128
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* oQRCode.clear(); // Clear the QRCode.
|
||||||
|
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement|String} el target element or 'id' attribute of element.
|
||||||
|
* @param {Object|String} vOption
|
||||||
|
* @param {String} vOption.text QRCode link data
|
||||||
|
* @param {Number} [vOption.width=256]
|
||||||
|
* @param {Number} [vOption.height=256]
|
||||||
|
* @param {String} [vOption.colorDark="#000000"]
|
||||||
|
* @param {String} [vOption.colorLight="#ffffff"]
|
||||||
|
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
|
||||||
|
*/
|
||||||
|
QRCode = function (el, vOption) {
|
||||||
|
this._htOption = {
|
||||||
|
width : 256,
|
||||||
|
height : 256,
|
||||||
|
typeNumber : 4,
|
||||||
|
colorDark : "#000000",
|
||||||
|
colorLight : "#ffffff",
|
||||||
|
correctLevel : QRErrorCorrectLevel.H
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof vOption === 'string') {
|
||||||
|
vOption = {
|
||||||
|
text : vOption
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrites options
|
||||||
|
if (vOption) {
|
||||||
|
for (var i in vOption) {
|
||||||
|
this._htOption[i] = vOption[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof el == "string") {
|
||||||
|
el = document.getElementById(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._htOption.useSVG) {
|
||||||
|
Drawing = svgDrawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._android = _getAndroid();
|
||||||
|
this._el = el;
|
||||||
|
this._oQRCode = null;
|
||||||
|
this._oDrawing = new Drawing(this._el, this._htOption);
|
||||||
|
|
||||||
|
if (this._htOption.text) {
|
||||||
|
this.makeCode(this._htOption.text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the QRCode
|
||||||
|
*
|
||||||
|
* @param {String} sText link data
|
||||||
|
*/
|
||||||
|
QRCode.prototype.makeCode = function (sText) {
|
||||||
|
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
|
||||||
|
this._oQRCode.addData(sText);
|
||||||
|
this._oQRCode.make();
|
||||||
|
this._el.title = sText;
|
||||||
|
this._oDrawing.draw(this._oQRCode);
|
||||||
|
this.makeImage();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the Image from Canvas element
|
||||||
|
* - It occurs automatically
|
||||||
|
* - Android below 3 doesn't support Data-URI spec.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
QRCode.prototype.makeImage = function () {
|
||||||
|
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
|
||||||
|
this._oDrawing.makeImage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the QRCode
|
||||||
|
*/
|
||||||
|
QRCode.prototype.clear = function () {
|
||||||
|
this._oDrawing.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name QRCode.CorrectLevel
|
||||||
|
*/
|
||||||
|
QRCode.CorrectLevel = QRErrorCorrectLevel;
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default QRCode;
|
||||||
1
imports/util/qrcode/qrcode.min.js
vendored
Normal file
1
imports/util/qrcode/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1619
package-lock.json
generated
1619
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -6,16 +6,24 @@
|
|||||||
"build": "npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../"
|
"build": "npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.2.0",
|
"@babel/runtime": "^7.5.5",
|
||||||
"babel-runtime": "^6.18.0",
|
"babel-runtime": "^6.18.0",
|
||||||
"csv-parse": "latest",
|
"csv-parse": "^4.4.6",
|
||||||
"d3": "^4.4.2",
|
"d3": "^4.4.2",
|
||||||
|
"dom-to-image": "latest",
|
||||||
"dragula": "^3.7.2",
|
"dragula": "^3.7.2",
|
||||||
"jquery": "^3.1.1",
|
"file-saver": "^2.0.2",
|
||||||
|
"jquery": "^3.4.1",
|
||||||
"jquery-mousewheel": "^3.1.13",
|
"jquery-mousewheel": "^3.1.13",
|
||||||
|
"jsbarcode": "^3.11.0",
|
||||||
|
"jspdf": "^1.5.3",
|
||||||
"malihu-custom-scrollbar-plugin": "latest",
|
"malihu-custom-scrollbar-plugin": "latest",
|
||||||
"meteor-node-stubs": "^0.2.4",
|
"meteor-node-stubs": "^0.4.1",
|
||||||
|
"nocache": "^2.1.0",
|
||||||
"properties-reader": "0.0.15",
|
"properties-reader": "0.0.15",
|
||||||
|
"puppeteer": "^1.20.0",
|
||||||
|
"pure-svg-code": "^1.0.6",
|
||||||
|
"qrcode-svg": "^1.0.0",
|
||||||
"simpl-schema": "latest",
|
"simpl-schema": "latest",
|
||||||
"sweetalert2": "^6.3.8"
|
"sweetalert2": "^6.3.8"
|
||||||
}
|
}
|
||||||
|
|||||||
3
public/JsBarcode.all.min.js
vendored
Normal file
3
public/JsBarcode.all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
106
public/LabelPrint.css
Normal file
106
public/LabelPrint.css
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#PrintLabel {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.labelContainer {
|
||||||
|
text-align: center;
|
||||||
|
width-min: 3in;
|
||||||
|
width: 3in;
|
||||||
|
height-min: 2in;
|
||||||
|
height: 2in;
|
||||||
|
}
|
||||||
|
.labels {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.printableLabel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 3in;
|
||||||
|
height: 2in;
|
||||||
|
}
|
||||||
|
.canvasContainer {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #808080;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
text-align: center;
|
||||||
|
font-family: TimesNewRoman, Times New Roman, Times;
|
||||||
|
font-size: 0.1in;
|
||||||
|
width: 3in;
|
||||||
|
height: 2in;
|
||||||
|
}
|
||||||
|
.label .barcodeContainer {
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(270deg) scale(0.7);
|
||||||
|
right: -10em;
|
||||||
|
top: 7em;
|
||||||
|
}
|
||||||
|
.label .labelLogo {
|
||||||
|
width: 8em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0.15em;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
.label .labelLogo3 {
|
||||||
|
width: 14em;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 0.15em;
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
}
|
||||||
|
.label .labelTagline {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 100;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
.label .title1 {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 2.5em;
|
||||||
|
line-height: 0.9em;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.label .title2 {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 0.9em;
|
||||||
|
font-weight: 800;
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
.label .ingredients {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 100;
|
||||||
|
line-height: 1em;
|
||||||
|
min-height: 2em;
|
||||||
|
}
|
||||||
|
.label .ingredientsEnding {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
.label .instructions {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.label .address {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
.label .website {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
1
public/StaticTest.html
Normal file
1
public/StaticTest.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello World!
|
||||||
64
public/images/3x2 Label Logo BW.svg
Normal file
64
public/images/3x2 Label Logo BW.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 31 KiB |
90
public/images/3x2 Label Logo2 BW.svg
Normal file
90
public/images/3x2 Label Logo2 BW.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 27 KiB |
66
public/images/3x2 Label Logo3 BW.svg
Normal file
66
public/images/3x2 Label Logo3 BW.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/Logo_0.8x0.73_300ppi.png
Normal file
BIN
public/images/Logo_0.8x0.73_300ppi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
public/images/Petit Teton Logo Only.png
Normal file
BIN
public/images/Petit Teton Logo Only.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
2
public/jquery-3.4.1.min.js
vendored
Normal file
2
public/jquery-3.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,6 +4,9 @@ import '/imports/startup/both';
|
|||||||
import '/imports/api';
|
import '/imports/api';
|
||||||
import '/imports/startup/server/postStartup/version.js'; //Run this right after the api - relies on the API to upgrade the app database & data to the current version.
|
import '/imports/startup/server/postStartup/version.js'; //Run this right after the api - relies on the API to upgrade the app database & data to the current version.
|
||||||
|
|
||||||
|
//const nocache = require('nocache');
|
||||||
|
//app.use(nocache());
|
||||||
|
|
||||||
let PropertiesReader = require('properties-reader');
|
let PropertiesReader = require('properties-reader');
|
||||||
let props = PropertiesReader('./assets/app/release.properties');
|
let props = PropertiesReader('./assets/app/release.properties');
|
||||||
|
|
||||||
@@ -19,3 +22,8 @@ if (!process.env.MAIL_URL) {
|
|||||||
// console.log("Mail settings: " + process.env.MAIL_URL);
|
// console.log("Mail settings: " + process.env.MAIL_URL);
|
||||||
|
|
||||||
if(Meteor.log) Meteor.log.info("Server Started");
|
if(Meteor.log) Meteor.log.info("Server Started");
|
||||||
|
|
||||||
|
WebApp.rawConnectHandlers.use('/', function(req, res, next) {
|
||||||
|
res.setHeader('cache-control', 'no-cache');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user